diff --git a/testing/cmocka/0004-cmocka-xml-report-and-generate-case-and-suite-tool.patch b/testing/cmocka/0004-cmocka-xml-report-and-generate-case-and-suite-tool.patch
new file mode 100644
index 000000000..5a91130ff
--- /dev/null
+++ b/testing/cmocka/0004-cmocka-xml-report-and-generate-case-and-suite-tool.patch
@@ -0,0 +1,249 @@
+diff --git a/src/cmocka.c b/src/cmocka.c
+index ede5b22..ec47f4e 100644
+--- a/src/cmocka.c
++++ cmocka/src/cmocka.c
+@@ -2532,6 +2532,7 @@ static void cmprintf_group_finish_xml(const char *group_name,
+ if (fp == NULL) {
+ fp = fopen(buf, "w");
+ if (fp != NULL) {
++ xml_printed = 0;
+ file_append = 1;
+ file_opened = 1;
+ } else {
+@@ -2554,13 +2555,15 @@ static void cmprintf_group_finish_xml(const char *group_name,
+ }
+
+ if (!xml_printed || (file_opened && !file_append)) {
+- fprintf(fp, "\n");
++ fprintf(fp, "\n\n");
+ if (!file_opened) {
+ xml_printed = 1;
+ }
++ } else {
++ fseek(fp, strlen("\n") * -1, SEEK_END);
++ ftruncate(fileno(fp), ftell(fp));
+ }
+
+- fprintf(fp, "\n");
+ fprintf(fp, " \n",
+ group_name,
+diff --git a/tool/cmocka_implement.py b/tool/cmocka_implement.py
+new file mode 100644
+index 0000000..11d2842
+--- /dev/null
++++ cmocka/tool/cmocka_implement.py
+@@ -0,0 +1,213 @@
++# -*- coding: utf-8 -*-
++import os
++import re
++import typer
++import copy
++import traceback
++
++TESTSUITE_TEMPLATE = """
++/*
++ * Copyright (C) 2023 Xiaomi Corporation
++ *
++ * 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.
++ */
++
++/****************************************************************************
++ * Included Files
++ ****************************************************************************/
++
++#include
++#include
++#include
++#include
++#include
++#include
++
++/****************************************************************************
++ * Name: cmocka_{suite_file}_main
++ ****************************************************************************/
++
++int main(int argc, char* argv[])
++{
++
++ /* Add Test Cases */
++ const struct CMUnitTest {suite_name}[] = {
++ cmocka_unit_test_setup_teardown(write case name here, NULL, NULL),
++ };
++
++ /* Run Test cases */
++ cmocka_run_group_tests({suite_name}, NULL, NULL);
++
++ printf("hello cmocka auto-tests\\n");
++
++ return 0;
++}
++"""
++
++TESTCASE_TEMPLATE = """
++/****************************************************************************
++ * Included Files
++ ****************************************************************************/
++#include
++#include
++#include
++#include
++#include
++#include
++
++
++/****************************************************************************
++ * Name: {case_file}
++ * Description: Testing for scene "describe scene here".
++ * The detail test steps are as following:
++ * 1. describe step 1 here
++ * 2. describe step 2 here
++ * 3. describe step 3 here
++ ****************************************************************************/
++
++void {case_name}(FAR void **state)
++{
++ printf("case: {case_name}\\n");
++ assert(true);
++}
++"""
++
++
++class CmockaGen:
++
++ def __init__(self, path):
++ self.path = path
++ self.suite_path = None
++ self.suite_file = None
++ self.suite_name = None
++ self.case_path = None
++ self.case_file = None
++ self.case_name = None
++
++ def check_path(self):
++ if not self.path:
++ print("request correct path option")
++ return 1
++ if not os.path.exists(self.path):
++ os.makedirs(self.path)
++ return 0
++
++ def check_suite_option(self, suite_option):
++ if not suite_option:
++ return 1
++ opts = suite_option.split("::")
++ if len(opts) != 2:
++ print("suite option must like aaa/bbb/ccc.c::VelaAutoTestSuite")
++ return 1
++ else:
++ self.suite_path = opts[0]
++ self.suite_name = opts[1]
++ paths = self.suite_path.split("/")
++ if not paths[-1].endswith(".c"):
++ print("suite option must like aaa/bbb/ccc.c::VelaAutoTestSuite")
++ return 1
++ else:
++ self.suite_file = paths[-1]
++ return 0
++
++ def generate_suite(self):
++ content = copy.deepcopy(TESTSUITE_TEMPLATE)
++ file_without_ext = self.suite_file.replace(".c", "")
++ content = content.replace("{suite_file}", file_without_ext)
++ content = content.replace("{suite_name}", self.suite_name)
++ full_path = os.path.join(self.path, self.suite_path)
++ dir_path = os.path.dirname(full_path)
++ if not os.path.exists(dir_path):
++ os.makedirs(dir_path)
++ with open(full_path, "w") as fl:
++ fl.write(content)
++
++ def check_case_option(self, case_option):
++ if not case_option:
++ return 1
++ opts = case_option.split("::")
++ if len(opts) != 2:
++ print("case option must like aaa/bbb/ccc.c::test_playback_uv_01")
++ return 1
++ else:
++ self.case_path = opts[0]
++ self.case_name = opts[1]
++ if not self.case_name.startswith("test"):
++ print("case function name start with 'test'")
++ return 1
++ paths = self.case_path.split("/")
++ if not paths[-1].endswith(".c"):
++ print("case option must like aaa/bbb/ccc.c::VelaAutoTestcase")
++ return 1
++ file_parts = paths[-1].split("_")
++ pattern = r"[0-9]{2,3}\.c$"
++ if file_parts[0] != "test":
++ print("case file name must start with 'test'")
++ return 1
++ elif not re.search(pattern, file_parts[-1]):
++ print("case file name must end with '00-99' or '000-999'")
++ return 1
++ else:
++ self.case_file = paths[-1]
++ return 0
++
++ def generate_case(self):
++ content = copy.deepcopy(TESTCASE_TEMPLATE)
++ content = content.replace("{case_file}", self.case_file)
++ content = content.replace("{case_name}", self.case_name)
++ full_path = os.path.join(self.path, self.case_path)
++ dir_path = os.path.dirname(full_path)
++ if not os.path.exists(dir_path):
++ os.makedirs(dir_path)
++ with open(full_path, "w") as fl:
++ fl.write(content)
++
++ def main(self, suite_option, case_option):
++ try:
++ if self.check_path():
++ return
++ if not self.check_suite_option(suite_option):
++ self.generate_suite()
++ print("generate suite success")
++ if not self.check_case_option(case_option):
++ self.generate_case()
++ print("generate case success")
++ except Exception as e:
++ traceback.print_exc()
++
++
++app = typer.Typer()
++
++@app.command()
++def main(
++ path: str = typer.Option("", help="where to gnerate suite/case file"),
++ suite: str = typer.Option(
++ default="",
++ help="suite file name and suite function name, path/suite::name, eg aaa/bbb/ccc.c::VelaAutoTestcase"
++ ),
++ case: str = typer.Option(
++ default="",
++ help="case file name and case function name, path/case::function, eg ddd/eee/fff.c::test_playback_uv_01"
++ ),
++):
++ """
++ :param path: where to gnerate suite/case file
++ :param suite: suite file name and suite function name
++ :param case: case file name and case function name
++ :return:
++ """
++ gen = CmockaGen(path)
++ gen.main(suite, case)
++
++if __name__ == '__main__':
++ app()
diff --git a/testing/cmocka/Makefile b/testing/cmocka/Makefile
index d887ef180..f31627831 100644
--- a/testing/cmocka/Makefile
+++ b/testing/cmocka/Makefile
@@ -42,6 +42,7 @@ cmocka.zip:
$(Q) patch -p0 < 0001-cmocka.c-Reduce-the-call-stack-consumption-of-printf.patch
$(Q) patch -p0 < 0002-cmocka-feature-to-forwarding-cmocka-log-message-to-c.patch
$(Q) patch -p0 < 0003-cmocka-update-method-for-strmatch-to-regex-and-add-list-all-testcases-function.patch
+ $(Q) patch -p0 < 0004-cmocka-xml-report-and-generate-case-and-suite-tool.patch
context:: cmocka.zip
diff --git a/testing/cmocka/cmocka_main.c b/testing/cmocka/cmocka_main.c
index b13dbbd84..14251a478 100644
--- a/testing/cmocka/cmocka_main.c
+++ b/testing/cmocka/cmocka_main.c
@@ -57,6 +57,8 @@ static void cm_usage(void)
"name matches B pattern\n"
" --suite C only run suites where PROGNAME "
"matches C pattern\n"
+ " --output-path use xml report instead of standard "
+ "output\n"
"Example: cmocka --suite mm|sched "
"--test Test* --skip TestNuttxMm0[123]\n\n";
printf("%s", mesg);
@@ -92,6 +94,7 @@ int main(int argc, FAR char *argv[])
FAR char *bypass[argc + 1];
FAR char *suite = NULL;
FAR char *skip = NULL;
+ FAR char *xml_path = NULL;
int num_bypass = 1;
int ret;
int i;
@@ -116,6 +119,10 @@ int main(int argc, FAR char *argv[])
{
list_tests = 1;
}
+ else if (strcmp("--output-path", argv[i]) == 0)
+ {
+ xml_path = argv[++i];
+ }
else if (strcmp("--test", argv[i]) == 0)
{
testcase = argv[++i];
@@ -136,10 +143,17 @@ int main(int argc, FAR char *argv[])
cmocka_set_test_filter(NULL);
cmocka_set_skip_filter(NULL);
+ cmocka_set_message_output(CM_OUTPUT_STDOUT);
cmocka_set_list_test(list_tests);
if (list_tests == 0)
{
+ if (xml_path != NULL)
+ {
+ setenv("CMOCKA_XML_FILE", xml_path, 1);
+ cmocka_set_message_output(CM_OUTPUT_XML);
+ }
+
cmocka_set_test_filter(testcase);
cmocka_set_skip_filter(skip);
}