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;
+}