1/*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <perf.h>
17#include <gtest/gtest-message.h>
18#include <gtest/gtest-test-part.h>
19#include <gtest/gtest.h>
20#include <istream>
21#include <libxml/globals.h>
22#include <libxml/xmlstring.h>
23#include <list>
24#include <map>
25#include <securec.h>
26#include <type_traits>
27#include <string>
28
29#include <sstream>
30#include <libxml/parser.h>
31using namespace std;
32
33namespace OHOS {
34namespace TestAW {
35
36#define ERR_MSG(...)    fprintf(stderr, __VA_ARGS__)
37#define INF_MSG(...)    fprintf(stdout, __VA_ARGS__)
38#define DBG_MSG(...)    fprintf(stdout, __VA_ARGS__)
39
40#define ID_LARGER_IS_BETTER    true
41#define ID_SMALLER_IS_BETTER   false
42
43namespace {
44    const auto XML_TAG_ROOT       = "configuration";
45    const auto XML_TAG_DATETIME   = "datetime";
46    const auto XML_TAG_URL        = "url";
47    const auto XML_TAG_CASENAME   = "name";
48    const int ID_PROPERTY_LENGTH = 64;
49}
50
51// BaseLineManager
52BaseLineManager::BaseLineManager()
53    : m_bNoBaseline(false)
54{
55}
56
57BaseLineManager::BaseLineManager(const string path)
58    : m_bNoBaseline(false)
59{
60    LoadConfig(path);
61}
62
63BaseLineManager::~BaseLineManager()
64{
65    if (m_bNoBaseline) {
66        ERR_MSG("[ WARNING  ] no baseline loaded, please manual check output value is OK.\n");
67    }
68}
69
70// parser configuration file in json format.
71bool BaseLineManager::LoadConfig(const string path)
72{
73    m_bNoBaseline = false;
74    if (!ReadXmlFile(path)) {
75        ERR_MSG("[ WARNING  ] failed to load config from %s\n", path.c_str());
76        m_bNoBaseline = true;
77        return false;
78    }
79
80    INF_MSG("[ PERF     ] Load BaseLines from: %s\n", path.c_str());
81    return true;
82}
83
84void ParseProperties(const xmlNode currNode, map<string, string>& properties)
85{
86    for (auto attrs = currNode.properties; attrs != nullptr; attrs = attrs->next) {
87        auto name = attrs->name;
88        if (name == nullptr) {
89            continue;
90        }
91        auto value = xmlGetProp(&currNode, name);
92        if (value == nullptr) {
93            continue;
94        }
95        string propName(reinterpret_cast<const char*>(name));
96        string propValue(reinterpret_cast<char*>(value));
97        properties[propName] = std::move(propValue);
98        xmlFree(value);
99    }
100}
101
102bool BaseLineManager::ReadXmlFile(string baselinePath)
103{
104    xmlDocPtr ptrXmlDoc = xmlReadFile(baselinePath.c_str(), nullptr, XML_PARSE_NOBLANKS);
105    if (ptrXmlDoc == nullptr) {
106        return false;
107    }
108
109    xmlNodePtr ptrRootNode = xmlDocGetRootElement(ptrXmlDoc);
110    if (ptrRootNode == nullptr || ptrRootNode->name == nullptr ||
111        xmlStrcmp(ptrRootNode->name, reinterpret_cast<const xmlChar*>(XML_TAG_ROOT))) {
112        xmlFreeDoc(ptrXmlDoc);
113        return false;
114    }
115
116    map<string, string> properties;
117    ParseProperties(*ptrRootNode, properties);
118    if (properties.count(XML_TAG_DATETIME) == 1) {
119        m_bastCfg.date = properties[XML_TAG_DATETIME];
120    }
121    if (properties.count(XML_TAG_URL) == 1) {
122        m_bastCfg.date = properties[XML_TAG_URL];
123    }
124
125    xmlNodePtr currNodePtr = ptrRootNode->xmlChildrenNode;
126    for (; currNodePtr != nullptr; currNodePtr = currNodePtr->next) {
127        if (currNodePtr->name == nullptr || currNodePtr->type == XML_COMMENT_NODE) {
128            xmlFreeDoc(ptrXmlDoc);
129            return false;
130        }
131
132        map<string, string> properties_temp;
133        ParseProperties(*currNodePtr, properties_temp);
134        m_bastCfg.items.push_back(properties_temp);
135    }
136
137    xmlFreeDoc(ptrXmlDoc);
138    return true;
139}
140
141bool BaseLineManager::IsNoBaseline()
142{
143    return m_bNoBaseline;
144}
145
146double BaseLineManager::StrtoDouble(const string& str)
147{
148    istringstream iss(str);
149    double num;
150    iss >> num;
151    return num;
152}
153
154bool BaseLineManager::GetExtraValueDouble(const string testcaseName, const string extra, double &value)
155{
156    if (testcaseName == "" || extra == "") {
157        DBG_MSG("[ ERROR    ] invalid arguments: testcaseName=%s, extra=%s\n", testcaseName.c_str(), extra.c_str());
158        return false;
159    }
160
161    for (auto iter = m_bastCfg.items.begin(); iter != m_bastCfg.items.end(); iter++) {
162        map<string, string> properties = *iter;
163        if (properties.count(XML_TAG_CASENAME) == 1 && properties[XML_TAG_CASENAME] == testcaseName) {
164            if (properties.count(extra) == 1) {
165                value = StrtoDouble(properties[extra]);
166            }
167            break;
168        }
169    }
170
171    return true;
172}
173
174// GtestPerfTestCase
175GtestPerfTestCase::GtestPerfTestCase(BaseLineManager* pManager,
176    testing::Test *tester,
177    int caseVersion,
178    std::string testClassName,
179    std::string testInterfaceName)
180{
181    m_pManager = pManager;
182    m_pTester = tester;
183    m_dCaseVersion = caseVersion;
184    m_strCaseName = "";
185    m_strTestClassName = testClassName;
186    m_strTestInterfaceName = testInterfaceName;
187
188    // get test case name from GTEST API.
189    // should be use tester->XXX() instead of this.
190    if (tester != nullptr && ::testing::UnitTest::GetInstance() != nullptr) {
191        m_strCaseName = string(::testing::UnitTest::GetInstance()->current_test_info()->name());
192    }
193
194    // start initialize.
195    Initialize();
196}
197
198bool GtestPerfTestCase::SetBaseLine(string testcaseName)
199{
200    if (testcaseName == "") {
201        return false;
202    }
203
204    m_strCaseName = testcaseName;
205
206    return Initialize();
207}
208
209void GtestPerfTestCase::ResetValues()
210{
211    m_bHasBaseLine = false;
212    m_dbBaseLine = -1.0;
213    m_bHasLastValue = false;
214    m_dbLastValue = -1.0;
215    m_bHasFloatRange = false;
216    m_dbFloatRange = -1.0;
217
218    m_bTestResult = false;
219    m_dbTestResult = -1.0;
220}
221
222bool GtestPerfTestCase::Initialize()
223{
224    // clear all values.
225    ResetValues();
226    if (m_strCaseName == "" || m_pManager == nullptr) {
227        return false;
228    }
229
230    // get baseline value
231    m_bHasBaseLine = m_pManager->GetExtraValueDouble(m_strCaseName, "baseline", m_dbBaseLine);
232    if (!m_bHasBaseLine) {
233        return false;
234    }
235
236    // get last test value from config.
237    m_bHasLastValue = m_pManager->GetExtraValueDouble(m_strCaseName, "lastvalue", m_dbLastValue);
238
239    // get float range value from config.
240    m_bHasFloatRange = m_pManager->GetExtraValueDouble(m_strCaseName, "floatrange", m_dbFloatRange);
241    // check values is valid, and update them.
242    if (m_bHasFloatRange && (m_dbFloatRange < 0 || m_dbFloatRange >= 1)) {
243        DBG_MSG("[ ERROR    ] %s has invalid float range: %f.\n", m_strCaseName.c_str(), m_dbFloatRange);
244        m_bHasFloatRange = false;
245    }
246
247    if (!m_bHasFloatRange) {
248        m_dbFloatRange = 0.0;
249    }
250
251    if (!m_bHasLastValue) {
252        m_dbLastValue = m_dbBaseLine;
253    }
254
255    return true;
256}
257
258// return true if testValue >= baseline value
259bool GtestPerfTestCase::ExpectLarger(double testValue)
260{
261    return ExpectValue(testValue, ID_LARGER_IS_BETTER);
262}
263
264// return true if testValue <= baseline value
265bool GtestPerfTestCase::ExpectSmaller(double testValue)
266{
267    return ExpectValue(testValue, ID_SMALLER_IS_BETTER);
268}
269
270bool GtestPerfTestCase::ExpectValue(double testValue, bool isLargerBetter)
271{
272    if (m_strCaseName == "") {
273        ERR_MSG("[ ERROR    ] failed to get testcase name.\n");
274        return false;
275    }
276
277    m_bTestResult = false;
278    m_dbTestResult = testValue;
279
280    // check pass or failed.
281    if (m_pManager != nullptr && m_pManager->IsNoBaseline()) {
282        // no baseline.json is loaded at startup.
283        // set result to TRUE, please check testValue manually.
284        m_bTestResult = true;
285        EXPECT_TRUE(m_bTestResult);
286    } else if (!m_bHasBaseLine) {
287        ERR_MSG("[ ERROR    ] %s has NO baseline.\n", m_strCaseName.c_str());
288        EXPECT_TRUE(m_bHasBaseLine);
289    } else {
290        double baseValue = -1;
291        if (isLargerBetter) {
292            baseValue = (m_dbLastValue >= m_dbBaseLine) ? m_dbLastValue : m_dbBaseLine;
293            EXPECT_GE(testValue, (baseValue * (1.0 - m_dbFloatRange)));
294            m_bTestResult = (testValue >= (baseValue * (1.0 - m_dbFloatRange))) ? true : false;
295        } else {
296            baseValue = (m_dbLastValue <= m_dbBaseLine) ? m_dbLastValue : m_dbBaseLine;
297            EXPECT_LE(testValue, (baseValue * (1.0 + m_dbFloatRange)));
298            m_bTestResult = (testValue <= (baseValue * (1.0 + m_dbFloatRange))) ? true : false;
299        }
300    }
301
302    // save result.
303    SaveResult(testValue);
304
305    return m_bTestResult;
306}
307
308bool GtestPerfTestCase::SaveResult(double testValue)
309{
310    char buffer[ID_PROPERTY_LENGTH] = {0};
311
312    if (m_pTester == nullptr) {
313        ERR_MSG("[ ERROR    ] m_pTester is nullptr.\n");
314        return false;
315    }
316
317    INF_MSG("[ PERF     ] %s: baseline:%f, test_result: %f\n", m_strCaseName.c_str(), m_dbBaseLine, testValue);
318
319    (void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
320    if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%g", m_dbBaseLine) > 0) {
321        m_pTester->RecordProperty("baseline", buffer);
322    }
323
324    (void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
325    if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%d", m_dCaseVersion) > 0) {
326        m_pTester->RecordProperty("tc_version", buffer);
327    }
328
329    (void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
330    if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%g", m_dbLastValue) > 0) {
331        m_pTester->RecordProperty("lastvalue", m_bHasLastValue ? buffer : "");
332    }
333
334    (void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
335    if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%g", testValue) > 0) {
336        m_pTester->RecordProperty("value", buffer);
337    }
338
339    m_pTester->RecordProperty("category", "performance");
340    m_pTester->RecordProperty("test_class", m_strTestClassName.c_str());
341    m_pTester->RecordProperty("test_interface", m_strTestInterfaceName.c_str());
342
343    return true;
344}
345} // namespace TestAW
346} // namespace OHOS
347