* Copyright (c) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <perf.h>
#include <gtest/gtest-message.h>
#include <gtest/gtest-test-part.h>
#include <gtest/gtest.h>
#include <istream>
#include <libxml/globals.h>
#include <libxml/xmlstring.h>
#include <list>
#include <map>
#include <securec.h>
#include <type_traits>
#include <string>
#include <sstream>
#include <libxml/parser.h>
using namespace std;
namespace OHOS {
namespace TestAW {
#define ERR_MSG(...) fprintf(stderr, __VA_ARGS__)
#define INF_MSG(...) fprintf(stdout, __VA_ARGS__)
#define DBG_MSG(...) fprintf(stdout, __VA_ARGS__)
#define ID_LARGER_IS_BETTER true
#define ID_SMALLER_IS_BETTER false
namespace {
const auto XML_TAG_ROOT = "configuration";
const auto XML_TAG_DATETIME = "datetime";
const auto XML_TAG_URL = "url";
const auto XML_TAG_CASENAME = "name";
const int ID_PROPERTY_LENGTH = 64;
}
BaseLineManager::BaseLineManager()
: m_bNoBaseline(false)
{
}
BaseLineManager::BaseLineManager(const string path)
: m_bNoBaseline(false)
{
LoadConfig(path);
}
BaseLineManager::~BaseLineManager()
{
if (m_bNoBaseline) {
ERR_MSG("[ WARNING ] no baseline loaded, please manual check output value is OK.\n");
}
}
bool BaseLineManager::LoadConfig(const string path)
{
m_bNoBaseline = false;
if (!ReadXmlFile(path)) {
ERR_MSG("[ WARNING ] failed to load config from %s\n", path.c_str());
m_bNoBaseline = true;
return false;
}
INF_MSG("[ PERF ] Load BaseLines from: %s\n", path.c_str());
return true;
}
void ParseProperties(const xmlNode currNode, map<string, string>& properties)
{
for (auto attrs = currNode.properties; attrs != nullptr; attrs = attrs->next) {
auto name = attrs->name;
if (name == nullptr) {
continue;
}
auto value = xmlGetProp(&currNode, name);
if (value == nullptr) {
continue;
}
string propName(reinterpret_cast<const char*>(name));
string propValue(reinterpret_cast<char*>(value));
properties[propName] = std::move(propValue);
xmlFree(value);
}
}
bool BaseLineManager::ReadXmlFile(string baselinePath)
{
xmlDocPtr ptrXmlDoc = xmlReadFile(baselinePath.c_str(), nullptr, XML_PARSE_NOBLANKS);
if (ptrXmlDoc == nullptr) {
return false;
}
xmlNodePtr ptrRootNode = xmlDocGetRootElement(ptrXmlDoc);
if (ptrRootNode == nullptr || ptrRootNode->name == nullptr ||
xmlStrcmp(ptrRootNode->name, reinterpret_cast<const xmlChar*>(XML_TAG_ROOT))) {
xmlFreeDoc(ptrXmlDoc);
return false;
}
map<string, string> properties;
ParseProperties(*ptrRootNode, properties);
if (properties.count(XML_TAG_DATETIME) == 1) {
m_bastCfg.date = properties[XML_TAG_DATETIME];
}
if (properties.count(XML_TAG_URL) == 1) {
m_bastCfg.date = properties[XML_TAG_URL];
}
xmlNodePtr currNodePtr = ptrRootNode->xmlChildrenNode;
for (; currNodePtr != nullptr; currNodePtr = currNodePtr->next) {
if (currNodePtr->name == nullptr || currNodePtr->type == XML_COMMENT_NODE) {
xmlFreeDoc(ptrXmlDoc);
return false;
}
map<string, string> properties_temp;
ParseProperties(*currNodePtr, properties_temp);
m_bastCfg.items.push_back(properties_temp);
}
xmlFreeDoc(ptrXmlDoc);
return true;
}
bool BaseLineManager::IsNoBaseline()
{
return m_bNoBaseline;
}
double BaseLineManager::StrtoDouble(const string& str)
{
istringstream iss(str);
double num;
iss >> num;
return num;
}
bool BaseLineManager::GetExtraValueDouble(const string testcaseName, const string extra, double &value)
{
if (testcaseName == "" || extra == "") {
DBG_MSG("[ ERROR ] invalid arguments: testcaseName=%s, extra=%s\n", testcaseName.c_str(), extra.c_str());
return false;
}
for (auto iter = m_bastCfg.items.begin(); iter != m_bastCfg.items.end(); iter++) {
map<string, string> properties = *iter;
if (properties.count(XML_TAG_CASENAME) == 1 && properties[XML_TAG_CASENAME] == testcaseName) {
if (properties.count(extra) == 1) {
value = StrtoDouble(properties[extra]);
}
break;
}
}
return true;
}
GtestPerfTestCase::GtestPerfTestCase(BaseLineManager* pManager,
testing::Test *tester,
int caseVersion,
std::string testClassName,
std::string testInterfaceName)
{
m_pManager = pManager;
m_pTester = tester;
m_dCaseVersion = caseVersion;
m_strCaseName = "";
m_strTestClassName = testClassName;
m_strTestInterfaceName = testInterfaceName;
if (tester != nullptr && ::testing::UnitTest::GetInstance() != nullptr) {
m_strCaseName = string(::testing::UnitTest::GetInstance()->current_test_info()->name());
}
Initialize();
}
bool GtestPerfTestCase::SetBaseLine(string testcaseName)
{
if (testcaseName == "") {
return false;
}
m_strCaseName = testcaseName;
return Initialize();
}
void GtestPerfTestCase::ResetValues()
{
m_bHasBaseLine = false;
m_dbBaseLine = -1.0;
m_bHasLastValue = false;
m_dbLastValue = -1.0;
m_bHasFloatRange = false;
m_dbFloatRange = -1.0;
m_bTestResult = false;
m_dbTestResult = -1.0;
}
bool GtestPerfTestCase::Initialize()
{
ResetValues();
if (m_strCaseName == "" || m_pManager == nullptr) {
return false;
}
m_bHasBaseLine = m_pManager->GetExtraValueDouble(m_strCaseName, "baseline", m_dbBaseLine);
if (!m_bHasBaseLine) {
return false;
}
m_bHasLastValue = m_pManager->GetExtraValueDouble(m_strCaseName, "lastvalue", m_dbLastValue);
m_bHasFloatRange = m_pManager->GetExtraValueDouble(m_strCaseName, "floatrange", m_dbFloatRange);
if (m_bHasFloatRange && (m_dbFloatRange < 0 || m_dbFloatRange >= 1)) {
DBG_MSG("[ ERROR ] %s has invalid float range: %f.\n", m_strCaseName.c_str(), m_dbFloatRange);
m_bHasFloatRange = false;
}
if (!m_bHasFloatRange) {
m_dbFloatRange = 0.0;
}
if (!m_bHasLastValue) {
m_dbLastValue = m_dbBaseLine;
}
return true;
}
bool GtestPerfTestCase::ExpectLarger(double testValue)
{
return ExpectValue(testValue, ID_LARGER_IS_BETTER);
}
bool GtestPerfTestCase::ExpectSmaller(double testValue)
{
return ExpectValue(testValue, ID_SMALLER_IS_BETTER);
}
bool GtestPerfTestCase::ExpectValue(double testValue, bool isLargerBetter)
{
if (m_strCaseName == "") {
ERR_MSG("[ ERROR ] failed to get testcase name.\n");
return false;
}
m_bTestResult = false;
m_dbTestResult = testValue;
if (m_pManager != nullptr && m_pManager->IsNoBaseline()) {
m_bTestResult = true;
EXPECT_TRUE(m_bTestResult);
} else if (!m_bHasBaseLine) {
ERR_MSG("[ ERROR ] %s has NO baseline.\n", m_strCaseName.c_str());
EXPECT_TRUE(m_bHasBaseLine);
} else {
double baseValue = -1;
if (isLargerBetter) {
baseValue = (m_dbLastValue >= m_dbBaseLine) ? m_dbLastValue : m_dbBaseLine;
EXPECT_GE(testValue, (baseValue * (1.0 - m_dbFloatRange)));
m_bTestResult = (testValue >= (baseValue * (1.0 - m_dbFloatRange))) ? true : false;
} else {
baseValue = (m_dbLastValue <= m_dbBaseLine) ? m_dbLastValue : m_dbBaseLine;
EXPECT_LE(testValue, (baseValue * (1.0 + m_dbFloatRange)));
m_bTestResult = (testValue <= (baseValue * (1.0 + m_dbFloatRange))) ? true : false;
}
}
SaveResult(testValue);
return m_bTestResult;
}
bool GtestPerfTestCase::SaveResult(double testValue)
{
char buffer[ID_PROPERTY_LENGTH] = {0};
if (m_pTester == nullptr) {
ERR_MSG("[ ERROR ] m_pTester is nullptr.\n");
return false;
}
INF_MSG("[ PERF ] %s: baseline:%f, test_result: %f\n", m_strCaseName.c_str(), m_dbBaseLine, testValue);
(void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%g", m_dbBaseLine) > 0) {
m_pTester->RecordProperty("baseline", buffer);
}
(void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%d", m_dCaseVersion) > 0) {
m_pTester->RecordProperty("tc_version", buffer);
}
(void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%g", m_dbLastValue) > 0) {
m_pTester->RecordProperty("lastvalue", m_bHasLastValue ? buffer : "");
}
(void)memset_s(buffer, sizeof(buffer), 0, sizeof(buffer));
if (snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%g", testValue) > 0) {
m_pTester->RecordProperty("value", buffer);
}
m_pTester->RecordProperty("category", "performance");
m_pTester->RecordProperty("test_class", m_strTestClassName.c_str());
m_pTester->RecordProperty("test_interface", m_strTestInterfaceName.c_str());
return true;
}
}
}