diff --git a/README_zh.md b/README_zh.md index b524f622..27d7da11 100644 --- a/README_zh.md +++ b/README_zh.md @@ -87,6 +87,8 @@ OpenHarmony LiteOS-A内核支持[Hi3516DV300](https://gitee.com/openharmony/docs 编译可以参考:[编译指导](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/quick-start/quickstart-pkg-3516-build.md) +测试参考:[单元测试](testsuites/unittest/tools/README.md) + ## 贡献 [如何贡献](https://gitee.com/openharmony/docs/blob/HEAD/zh-cn/contribute/%E5%8F%82%E4%B8%8E%E8%B4%A1%E7%8C%AE.md) diff --git a/testsuites/unittest/BUILD.gn b/testsuites/unittest/BUILD.gn index 0bfbaf78..b1ebb922 100644 --- a/testsuites/unittest/BUILD.gn +++ b/testsuites/unittest/BUILD.gn @@ -67,6 +67,8 @@ config("public_config_for_pressure") { group("unittest") { deps = [] if (ohos_build_type == "debug") { + deps += [ "tools:liteos_unittest_run" ] + # basic test if (LOSCFG_USER_TEST_BASIC == true) { if (LOSCFG_USER_TEST_LEVEL >= TEST_LEVEL_LOW) { diff --git a/testsuites/unittest/tools/BUILD.gn b/testsuites/unittest/tools/BUILD.gn new file mode 100644 index 00000000..842065d4 --- /dev/null +++ b/testsuites/unittest/tools/BUILD.gn @@ -0,0 +1,38 @@ +# Copyright (c) 2023-2023 Huawei Device Co., Ltd. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other materials +# provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used +# to endorse or promote products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import("//build/lite/config/test.gni") +import("//kernel/liteos_a/liteos.gni") +import("//kernel/liteos_a/testsuites/unittest/config.gni") + +unittest("liteos_unittest_run") { + output_extension = "bin" + output_dir = "$root_out_dir/test/unittest/kernel" + sources = [ "unittest_tools.cpp" ] + deps = [ "$THIRDPARTY_BOUNDS_CHECKING_FUNCTION_DIR:libsec_shared" ] +} diff --git a/testsuites/unittest/tools/README.md b/testsuites/unittest/tools/README.md new file mode 100644 index 00000000..a3cd21ae --- /dev/null +++ b/testsuites/unittest/tools/README.md @@ -0,0 +1,63 @@ +# LiteOS Unittest tools + +## 介绍 + +可执行程序 ***liteos_unittest_run.bin*** 是为了提升liteos unittest 测试效率的工具。 + +## 使用介绍 + +### 1.使用帮助 + +``` +OHOS # ./liteos_unittest_run.bin --help +Usage: +liteos_unittest_run.bin [testsuites_dir] [options] +options: + -r [1-1000] --- The number of repeated runs of the test program. + -m [smoke/full] --- Run the smoke or full test case in this directory. + -t [case] [args] -t ... --- Runs the specified executable program name. +``` +- testsuites_dir: unittest 用例所在的绝对路径 + +### 2.常见测试场景举例 + +假设单板上单元测试用例位于路径 ***/usr/bin/unittest*** 下。 + +#### 2.1 运行全量用例或smoke用例 + +- smoke命令 +``` +./liteos_unittest_run.bin /usr/bin/unittest -r 10 -m smoke +``` +注: -r 10 表示: smoke用例运行10次, 一般用于压测 + +- 全量命令 +``` +./liteos_unittest_run.bin /usr/bin/unittest -r 10 -m full +``` + +注: -r 10 表示: 全量用例运行10次, 一般用于压测 + +#### 2.2 单或多用例组合运行 + +- 单用例执行命令 +``` +./liteos_unittest_run.bin /usr/bin/unittest -r 10 -t liteos_a_basic_unittest.bin +``` +注: 只运行liteos_a_basic_unittest.bin 用例, 重复10次 + +``` +./liteos_unittest_run.bin /usr/bin/unittest -r 10 -t liteos_a_basic_unittest.bin --gtest_filter=ExcTest.ItTestExc002 +``` +注: 只运行liteos_a_basic_unittest.bin 用例中的ItTestExc002,重复10次 + +- 多用例组合命令 +``` +./liteos_unittest_run.bin /usr/bin/unittest -r 10 -t liteos_a_basic_unittest.bin -t liteos_a_process_basic_pthread_unittest_door.bin +``` +注: 先运行liteos_a_basic_unittest.bin再运行liteos_a_process_basic_pthread_unittest_door.bin, 重复10次。-t 最多5个。 + +``` +./liteos_unittest_run.bin /usr/bin/unittest -r 10 -t liteos_a_basic_unittest.bin --gtest_filter=ExcTest.ItTestExc002 -t liteos_a_process_basic_pthread_unittest_door.bin --gtest_filter=ProcessPthreadTest.ItTestPthread003 +``` +注:先运行liteos_a_basic_unittest.bin的ItTestExc002,再运行liteos_a_process_basic_pthread_unittest_door.bin的ItTestPthread003,重复10次。-t最多5个。 diff --git a/testsuites/unittest/tools/unittest_tools.cpp b/testsuites/unittest/tools/unittest_tools.cpp new file mode 100644 index 00000000..79ba1fe0 --- /dev/null +++ b/testsuites/unittest/tools/unittest_tools.cpp @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +const static int TEST_PATH_MAX = 255; +const static int TEST_PARAM_NUM = 5; +const static int TEST_CASE_MODE_FULL = 0; +const static int TEST_CASE_MODE_SMOKE = 1; +const static int TEST_DEFAULT_CASE_MAX = 5; +const static int TEST_T_PARAM_LEN = 2; +const static unsigned int TEST_CASE_FLAGS_ALL = 1; +const static unsigned int TEST_CASE_FLAGS_SPECIFY = 2; +const static unsigned int TEST_CASE_FAILED_COUNT_MAX = 500; +static unsigned int g_testsuitesCount = 0; +static unsigned int g_testsuitesFailedCount = 0; + +struct TestCase { + char bin[TEST_PATH_MAX]; + char *param[TEST_PARAM_NUM]; + int caseLen; +}; + +struct TestsuitesParam { + char testsuitesBin[TEST_PATH_MAX]; + char testsuitesDir[TEST_PATH_MAX]; + int testsuitesDirLen; + struct TestCase *testCase[TEST_DEFAULT_CASE_MAX]; + int testCaseNum; + int testMode; + int repeat; +}; + +static struct TestsuitesParam g_param; +static char *g_testsuitesFailedCase[TEST_CASE_FAILED_COUNT_MAX]; + +static vector GetAllTestsuites(string testsuitesDir) +{ + vector vFiles; + int suffixLen = strlen(".bin"); + DIR *dir = opendir(testsuitesDir.c_str()); + if (dir == nullptr) { + cout << "opendir " << testsuitesDir << " failed" << endl; + return vFiles; + } + + do { + struct dirent *pDir = readdir(dir); + if (pDir == nullptr) { + break; + } + + if ((strcmp(pDir->d_name, ".") == 0) || (strcmp(pDir->d_name, "..") == 0)) { + continue; + } + + if (pDir->d_type == DT_REG) { + int pathLen = strlen(pDir->d_name); + if (pathLen <= suffixLen) { + continue; + } + if (strcmp(".bin", (char *)(pDir->d_name + (pathLen - suffixLen))) != 0) { + continue; + } + + vFiles.push_back(testsuitesDir + "/" + pDir->d_name); + } + } while (1); + closedir(dir); + return vFiles; +} + +static bool TestsuitesIsSmoke(string testsuite, int fileLen, string smokeTest, int smokeTestLen) +{ + if (fileLen <= smokeTestLen) { + return false; + } + + if (strcmp(smokeTest.c_str(), (char *)(testsuite.c_str() + (fileLen - smokeTestLen))) == 0) { + return true; + } + return false; +} + +static void RunCase(const char *testCase, char *param[]) +{ + int ret; + int size; + int status = 0; + pid_t pid = fork(); + if (pid < 0) { + cout << "fork failed, " << strerror(errno) << endl; + return; + } + + g_testsuitesCount++; + if (pid == 0) { + cout << testCase << ":" << endl; + ret = execve(param[0], param, nullptr); + if (ret != 0) { + cout << "execl: " << testCase << " failed, " << strerror(errno) << endl; + exit(0); + } + } + + ret = waitpid(pid, &status, 0); + if (ret != pid) { + cout << "waitpid failed, " << strerror(errno) << endl; + return; + } + + if (WEXITSTATUS(status) == 0) { + return; + } + + if (g_testsuitesFailedCount >= TEST_CASE_FAILED_COUNT_MAX) { + cout << "[UNITTEST_RUN] Failure cases more than upper limit!\n"; + return; + } + + size = strlen(testCase) + 1; + g_testsuitesFailedCase[g_testsuitesFailedCount] = static_cast(malloc(size)); + if (g_testsuitesFailedCase[g_testsuitesFailedCount] == nullptr) { + cout << "[UNITTEST_RUN] malloc failed!\n"; + return; + } + + if (memcpy_s(g_testsuitesFailedCase[g_testsuitesFailedCount], size, testCase, size) != EOK) { + cout << "[UNITTEST_RUN] memcpy failed!\n"; + return; + } + g_testsuitesFailedCount++; +} + +static void RunAllTestCase(vector files, struct TestsuitesParam *param) +{ + const char *testMode = nullptr; + const char *smokeTest = "door.bin"; + const char *unittestRun = g_param.testsuitesBin; + int unittestRunLen = strlen(unittestRun); + int smokeTestLen = strlen(smokeTest); + char *execParam[TEST_PARAM_NUM]; + + if (param->testMode == TEST_CASE_MODE_SMOKE) { + testMode = "door.bin"; + } else { + testMode = ".bin"; + } + + for (size_t i = 0; i < files.size(); i++) { + int fileLen = strlen(files[i].c_str()); + if (fileLen >= unittestRunLen) { + if (strcmp((char *)(files[i].c_str() + (fileLen - unittestRunLen)), unittestRun) == 0) { + continue; + } + } + + if (strcmp(testMode, smokeTest) == 0) { + if (!TestsuitesIsSmoke(files[i], fileLen, smokeTest, smokeTestLen)) { + continue; + } + } else { + if (TestsuitesIsSmoke(files[i], fileLen, smokeTest, smokeTestLen)) { + continue; + } + } + + (void)memset_s(execParam, sizeof(char *) * TEST_PARAM_NUM, 0, sizeof(char *) * TEST_PARAM_NUM); + execParam[0] = (char *)files[i].c_str(); + RunCase(files[i].c_str(), execParam); + } +} + +static void TestsuitesToolsUsage(void) +{ + cout << "Usage: " << endl; + cout << "liteos_unittest_run.bin [testsuites_dir] [options]" << endl; + cout << "options:" << endl; + cout << " -r [1-1000] --- The number of repeated runs of the test program." << endl; + cout << " -m [smoke/full] --- Run the smoke or full test case in this directory." << endl; + cout << " -t [case] [args] -t ... --- Runs the specified executable program name." << endl; +} + +static int TestsuitesDirFormat(char *testDir, int len) +{ + if (memcpy_s(g_param.testsuitesDir, TEST_PATH_MAX, testDir, len + 1) != EOK) { + cout << "testsuites dir: " << strerror(ENAMETOOLONG) << endl; + return -1; + } + + char *end = g_param.testsuitesDir + len; + while ((testDir != end) && (*end == '/')) { + *end = '\0'; + end--; + len--; + } + + g_param.testsuitesDirLen = len; + if (len <= 0) { + return -1; + } + return 0; +} + +static int GetTestsuitesToolsName(const char *argv[]) +{ + const char *testBin = static_cast(argv[0]); + const char *end = testBin + strlen(testBin); + while (*end != '/') { + end--; + } + end++; + + if (memcpy_s(g_param.testsuitesBin, TEST_PATH_MAX, end, strlen(end)) != EOK) { + cout << "testsuites dir: " << strerror(ENAMETOOLONG) << endl; + return -1; + } + return 0; +} + +static int ParseTestCaseAndParam(int argc, const char *argv[], int *index) +{ + int j; + + (*index)++; + if (*index >= argc) { + return -1; + } + + if (g_param.testCaseNum >= TEST_DEFAULT_CASE_MAX) { + return -1; + } + + g_param.testCase[g_param.testCaseNum] = (struct TestCase *)malloc(sizeof(struct TestCase)); + if (g_param.testCase[g_param.testCaseNum] == nullptr) { + cout << "test case " << strerror(ENOMEM) << endl; + return -1; + } + (void)memset_s(g_param.testCase[g_param.testCaseNum], sizeof(struct TestCase), 0, sizeof(struct TestCase)); + struct TestCase *testCase = g_param.testCase[g_param.testCaseNum]; + testCase->caseLen = strlen(argv[*index]); + if (memcpy_s(testCase->bin, TEST_PATH_MAX, argv[*index], testCase->caseLen + 1) != EOK) { + testCase->caseLen = 0; + cout << "test case " << strerror(ENAMETOOLONG) << endl; + return -1; + } + testCase->param[0] = testCase->bin; + (*index)++; + for (j = 1; (j < TEST_PARAM_NUM) && (*index < argc) && (strncmp("-t", argv[*index], TEST_T_PARAM_LEN) != 0); j++) { + testCase->param[j] = (char *)argv[*index]; + (*index)++; + } + g_param.testCaseNum++; + if (((*index) < argc) && (strncmp("-t", argv[*index], TEST_T_PARAM_LEN) == 0)) { + (*index)--; + } + return 0; +} + +static int TestsuitesParamCheck(int argc, const char *argv[]) +{ + int ret; + unsigned int mask = 0; + g_param.testMode = TEST_CASE_MODE_FULL; + g_param.repeat = 1; + ret = TestsuitesDirFormat((char *)argv[1], strlen(argv[1])); + if (ret < 0) { + return -1; + } + + for (int i = 2; i < argc; i++) { /* 2: param index */ + if (strcmp("-m", argv[i]) == 0) { + i++; + if (i >= argc) { + return -1; + } + mask |= TEST_CASE_FLAGS_ALL; + if (strcmp("smoke", argv[i]) == 0) { + g_param.testMode = TEST_CASE_MODE_SMOKE; + } else if (strcmp("full", argv[i]) == 0) { + g_param.testMode = TEST_CASE_MODE_FULL; + } else { + return -1; + } + } else if (strcmp("-t", argv[i]) == 0) { + mask |= TEST_CASE_FLAGS_SPECIFY; + ret = ParseTestCaseAndParam(argc, argv, &i); + if (ret < 0) { + return ret; + } + } else if (strcmp("-r", argv[i]) == 0) { + i++; + if (i >= argc) { + return -1; + } + g_param.repeat = atoi(argv[i]); + if ((g_param.repeat <= 0) || (g_param.repeat > 1000)) { /* 1000: repeat limit */ + return -1; + } + } + } + + if (((mask & TEST_CASE_FLAGS_ALL) != 0) && ((mask & TEST_CASE_FLAGS_SPECIFY) != 0)) { + cout << "Invalid parameter combination" << endl; + return -1; + } + return 0; +} + +static void IsCase(vector files, struct TestCase *testCase) +{ + for (size_t i = 0; i < files.size(); i++) { + string file = files[i]; + int fileLen = strlen(file.c_str()); + if (fileLen <= testCase->caseLen) { + continue; + } + + const string &suffix = file.c_str() + (fileLen - testCase->caseLen); + if (strcmp(suffix.c_str(), testCase->bin) != 0) { + continue; + } + + if (memcpy_s(testCase->bin, TEST_PATH_MAX, file.c_str(), fileLen + 1) != EOK) { + testCase->caseLen = 0; + return; + } + testCase->caseLen = fileLen; + g_param.testCaseNum++; + return; + } + cout << "liteos_unittest_run.bin: not find test case: " << testCase->bin << endl; + return; +} + +static int FindTestCase(vector files) +{ + int count = g_param.testCaseNum; + g_param.testCaseNum = 0; + + for (int i = 0; i < count; i++) { + IsCase(files, g_param.testCase[i]); + } + + if (g_param.testCaseNum == 0) { + cout << "Not find test case !" << endl; + return -1; + } + return 0; +} + +static void FreeTestCaseMem(void) +{ + for (int index = 0; index < g_param.testCaseNum; index++) { + free(g_param.testCase[index]); + g_param.testCase[index] = nullptr; + } +} + +static void ShowTestLog(int count) +{ + cout << "[UNITTEST_RUN] Repeats: " << count << " Succeed count: " + << g_testsuitesCount - g_testsuitesFailedCount + << " Failed count: " << g_testsuitesFailedCount << endl; + + if (g_testsuitesFailedCount == 0) { + return; + } + cout << "[UNITTEST_RUN] Failed testcase: " << endl; + for (int i = 0; i < g_testsuitesFailedCount; i++) { + cout << "[" << i << "] -> " << g_testsuitesFailedCase[i] << endl; + free(g_testsuitesFailedCase[i]); + g_testsuitesFailedCase[i] = nullptr; + } +} + +int main(int argc, const char *argv[]) +{ + int ret; + int count = 0; + + if ((argc < 2) || (argv == nullptr)) { /* 2: param index */ + cout << argv[0] << ": " << strerror(EINVAL) << endl; + return -1; + } + + if ((strcmp("--h", argv[1]) == 0) || (strcmp("--help", argv[1]) == 0)) { + TestsuitesToolsUsage(); + return 0; + } + + (void)memset_s(&g_param, sizeof(struct TestsuitesParam), 0, sizeof(struct TestsuitesParam)); + + ret = GetTestsuitesToolsName(argv); + if (ret < 0) { + return -1; + } + + ret = TestsuitesParamCheck(argc, argv); + if (ret < 0) { + cout << strerror(EINVAL) << endl; + FreeTestCaseMem(); + return -1; + } + + vector files = GetAllTestsuites(g_param.testsuitesDir); + + if (g_param.testCaseNum != 0) { + ret = FindTestCase(files); + if (ret < 0) { + files.clear(); + FreeTestCaseMem(); + return -1; + } + } + + while (count < g_param.repeat) { + if (g_param.testCaseNum == 0) { + RunAllTestCase(files, &g_param); + } else { + for (int index = 0; index < g_param.testCaseNum; index++) { + RunCase(g_param.testCase[index]->bin, g_param.testCase[index]->param); + } + } + count++; + } + files.clear(); + FreeTestCaseMem(); + ShowTestLog(count); + return 0; +}