Skip to content

Commit fee844d

Browse files
committed
unit-test-app: support multi stages test
1 parent 6acb38a commit fee844d

File tree

9 files changed

+278
-49
lines changed

9 files changed

+278
-49
lines changed

docs/api-guides/unit-tests.rst

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,30 @@ DUT2 (slave) console::
8484
Once the signal is set from DUT2, you need to press "Enter" on DUT1, then DUT1 unblocks from ``unity_wait_for_signal`` and starts to change GPIO level.
8585

8686

87+
Add multiple stages test cases
88+
-------------------------------
89+
90+
The normal test cases are expected to finish without reset (or only need to check if reset happens). Sometimes we want to run some specific test after certain kinds of reset.
91+
For example, we want to test if reset reason is correct after wakeup from deep sleep. We need to create deep sleep reset first and then check the reset reason.
92+
To support this, we can define multiple stages test case, to group a set of test functions together::
93+
94+
static void trigger_deepsleep(void)
95+
{
96+
esp_sleep_enable_timer_wakeup(2000);
97+
esp_deep_sleep_start();
98+
}
99+
100+
void check_deepsleep_reset_reason()
101+
{
102+
RESET_REASON reason = rtc_get_reset_reason(0);
103+
TEST_ASSERT(reason == DEEPSLEEP_RESET);
104+
}
105+
106+
TEST_CASE_MULTIPLE_STAGES("reset reason check for deepsleep", "[esp32]", trigger_deepsleep, check_deepsleep_reset_reason);
107+
108+
Multiple stages test cases present a group of test functions to users. It need user interactions (select case and select different stages) to run the case.
109+
110+
87111
Building unit test app
88112
----------------------
89113

@@ -123,7 +147,7 @@ When unit test app is idle, press "Enter" will make it print test menu with all
123147
(10) "global initializers run in the correct order" [cxx]
124148
(11) "before scheduler has started, static initializers work correctly" [cxx]
125149
(12) "adc2 work with wifi" [adc]
126-
(13) "gpio master/slave test example" [ignore][misc][test_env=UT_T2_1]
150+
(13) "gpio master/slave test example" [ignore][misc][test_env=UT_T2_1][multi_device]
127151
(1) "gpio_master_test"
128152
(2) "gpio_slave_test"
129153
(14) "SPI Master clockdiv calculation routines" [spi]
@@ -132,6 +156,9 @@ When unit test app is idle, press "Enter" will make it print test menu with all
132156
(17) "SPI Master no response when switch from host1 (HSPI) to host2 (VSPI)" [spi]
133157
(18) "SPI Master DMA test, TX and RX in different regions" [spi]
134158
(19) "SPI Master DMA test: length, start, not aligned" [spi]
159+
(20) "reset reason check for deepsleep" [esp32][test_env=UT_T2_1][multi_stage]
160+
(1) "trigger_deepsleep"
161+
(2) "check_deepsleep_reset_reason"
135162

136163
Normal case will print the case name and description. Master slave cases will also print the sub-menu (the registered test function names).
137164

@@ -145,11 +172,25 @@ Test cases can be run by inputting one of the following:
145172

146173
- An asterisk to run all test cases
147174

148-
After you select multiple devices test case, it will print sub menu::
175+
``[multi_device]`` and ``[multi_stage]`` tags tell the test runner whether a test case is a multiple devices or multiple stages test case.
176+
These tags are automatically added by ```TEST_CASE_MULTIPLE_STAGES`` and ``TEST_CASE_MULTIPLE_DEVICES`` macros.
177+
178+
After you select a multiple devices test case, it will print sub menu::
149179

150180
Running gpio master/slave test example...
151181
gpio master/slave test example
152182
(1) "gpio_master_test"
153183
(2) "gpio_slave_test"
154184

155185
You need to input number to select the test running on the DUT.
186+
187+
Similar to multiple devices test cases, multiple stages test cases will also print sub menu::
188+
189+
Running reset reason check for deepsleep...
190+
reset reason check for deepsleep
191+
(1) "trigger_deepsleep"
192+
(2) "check_deepsleep_reset_reason"
193+
194+
First time you execute this case, input ``1`` to run first stage (trigger deepsleep).
195+
After DUT is rebooted and able to run test cases, select this case again and input ``2`` to run the second stage.
196+
The case only passes if the last stage passes and all previous stages trigger reset.

tools/tiny-test-fw/CIAssignUnitTest.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818

1919
class Group(CIAssignTest.Group):
20-
SORT_KEYS = ["Test App", "SDK", "test environment"]
20+
SORT_KEYS = ["Test App", "SDK", "test environment", "multi_device", "multi_stage"]
21+
CI_JOB_MATCH_KEYS = ["Test App", "SDK", "test environment"]
2122
MAX_CASE = 30
2223
ATTR_CONVERT_TABLE = {
2324
"execution_time": "execution time"
@@ -36,35 +37,53 @@ def _get_ut_config(test_app):
3637
assert test_app[:3] == "UT_"
3738
return test_app[3:]
3839

39-
def _create_extra_data(self):
40+
def _create_extra_data(self, test_function):
4041
case_data = []
4142
for case in self.case_list:
42-
if self._get_case_attr(case, "cmd set") == "multiple_devices_case":
43-
case_data.append({
44-
"config": self._get_ut_config(self._get_case_attr(case, "Test App")),
45-
"name": self._get_case_attr(case, "summary"),
46-
"child case num": self._get_case_attr(case, "child case num")
47-
})
48-
else:
49-
case_data.append({
50-
"config": self._get_ut_config(self._get_case_attr(case, "Test App")),
51-
"name": self._get_case_attr(case, "summary"),
52-
"reset": self._get_case_attr(case, "reset") ,
53-
})
43+
one_case_data = {
44+
"config": self._get_ut_config(self._get_case_attr(case, "Test App")),
45+
"name": self._get_case_attr(case, "summary"),
46+
"reset": self._get_case_attr(case, "reset"),
47+
}
48+
49+
if test_function in ["run_multiple_devices_cases", "run_multiple_stage_cases"]:
50+
try:
51+
one_case_data["child case num"] = self._get_case_attr(case, "child case num")
52+
except KeyError as e:
53+
print("multiple devices/stages cases must contains at least two test functions")
54+
print("case name: {}".format(one_case_data["name"]))
55+
raise e
56+
57+
case_data.append(one_case_data)
5458
return case_data
5559

60+
def _map_test_function(self):
61+
"""
62+
determine which test function to use according to current test case
63+
64+
:return: test function name to use
65+
"""
66+
if self.filters["multi_device"] == "Yes":
67+
test_function = "run_multiple_devices_cases"
68+
elif self.filters["multi_stage"] == "Yes":
69+
test_function = "run_multiple_stage_cases"
70+
else:
71+
test_function = "run_unit_test_cases"
72+
return test_function
73+
5674
def output(self):
5775
"""
5876
output data for job configs
5977
6078
:return: {"Filter": case filter, "CaseConfig": list of case configs for cases in this group}
6179
"""
80+
test_function = self._map_test_function()
6281
output_data = {
6382
# we don't need filter for test function, as UT uses a few test functions for all cases
6483
"CaseConfig": [
6584
{
66-
"name": self.case_list[0]["cmd set"] if isinstance(self.case_list[0]["cmd set"], str) else self.case_list[0]["cmd set"][0],
67-
"extra_data": self._create_extra_data(),
85+
"name": test_function,
86+
"extra_data": self._create_extra_data(test_function),
6887
}
6988
]
7089
}

tools/tiny-test-fw/Utility/CIAssignTest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,18 @@ class Group(object):
5353
MAX_EXECUTION_TIME = 30
5454
MAX_CASE = 15
5555
SORT_KEYS = ["env_tag"]
56+
# Matching CI job rules could be different from the way we want to group test cases.
57+
# For example, when assign unit test cases, different test cases need to use different test functions.
58+
# We need to put them into different groups.
59+
# But these groups can be assigned to jobs with same tags, as they use the same test environment.
60+
CI_JOB_MATCH_KEYS = SORT_KEYS
5661

5762
def __init__(self, case):
5863
self.execution_time = 0
5964
self.case_list = [case]
6065
self.filters = dict(zip(self.SORT_KEYS, [self._get_case_attr(case, x) for x in self.SORT_KEYS]))
66+
self.ci_job_match_keys = dict(zip(self.CI_JOB_MATCH_KEYS,
67+
[self._get_case_attr(case, x) for x in self.CI_JOB_MATCH_KEYS]))
6168

6269
@staticmethod
6370
def _get_case_attr(case, attr):

tools/tiny-test-fw/Utility/GitlabCIJob.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def match_group(self, group):
4141
if "case group" in self:
4242
# this job is already assigned
4343
break
44-
for value in group.filters.values():
44+
for value in group.ci_job_match_keys.values():
4545
if value not in self["tags"]:
4646
break
4747
else:

tools/unit-test-app/components/unity/include/unity_config.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,34 @@ void unity_run_all_tests();
106106
static void UNITY_TEST_UID(test_func_) (void)
107107

108108

109+
/*
110+
* Multiple stages test cases will handle the case that test steps are separated by DUT reset.
111+
* e.g: we want to verify some function after SW reset, WDT reset or deep sleep reset.
112+
*
113+
* First argument is a free-form description,
114+
* second argument is (by convention) a list of identifiers, each one in square brackets.
115+
* subsequent arguments are names test functions separated by reset.
116+
* e.g:
117+
* TEST_CASE_MULTIPLE_STAGES("run light sleep after deep sleep","[sleep]", goto_deepsleep, light_sleep_after_deep_sleep_wakeup);
118+
* */
119+
120+
#define TEST_CASE_MULTIPLE_STAGES(name_, desc_, ...) \
121+
UNITY_TEST_FN_SET(__VA_ARGS__); \
122+
static void __attribute__((constructor)) UNITY_TEST_UID(test_reg_helper_) () \
123+
{ \
124+
static struct test_desc_t UNITY_TEST_UID(test_desc_) = { \
125+
.name = name_, \
126+
.desc = desc_"[multi_stage]", \
127+
.fn = UNITY_TEST_UID(test_functions), \
128+
.file = __FILE__, \
129+
.line = __LINE__, \
130+
.test_fn_count = PP_NARG(__VA_ARGS__), \
131+
.test_fn_name = UNITY_TEST_UID(test_fn_name), \
132+
.next = NULL \
133+
}; \
134+
unity_testcase_register( & UNITY_TEST_UID(test_desc_) ); \
135+
}
136+
109137
/*
110138
* First argument is a free-form description,
111139
* second argument is (by convention) a list of identifiers, each one in square brackets.
@@ -120,7 +148,7 @@ void unity_run_all_tests();
120148
{ \
121149
static struct test_desc_t UNITY_TEST_UID(test_desc_) = { \
122150
.name = name_, \
123-
.desc = desc_, \
151+
.desc = desc_"[multi_device]", \
124152
.fn = UNITY_TEST_UID(test_functions), \
125153
.file = __FILE__, \
126154
.line = __LINE__, \
@@ -130,6 +158,7 @@ void unity_run_all_tests();
130158
}; \
131159
unity_testcase_register( & UNITY_TEST_UID(test_desc_) ); \
132160
}
161+
133162
/**
134163
* Note: initialization of test_desc_t fields above has to be done exactly
135164
* in the same order as the fields are declared in the structure.

tools/unit-test-app/components/unity/unity_platform.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,13 @@ void unity_testcase_register(struct test_desc_t* desc)
145145
}
146146
}
147147

148-
/* print the multiple devices case name and its sub-menu
148+
/* print the multiple function case name and its sub-menu
149149
* e.g:
150150
* (1) spi master/slave case
151151
* (1)master case
152152
* (2)slave case
153153
* */
154-
static void print_multiple_devices_test_menu(const struct test_desc_t* test_ms)
154+
static void print_multiple_function_test_menu(const struct test_desc_t* test_ms)
155155
{
156156
unity_printf("%s\n", test_ms->name);
157157
for (int i = 0; i < test_ms->test_fn_count; i++)
@@ -160,12 +160,12 @@ static void print_multiple_devices_test_menu(const struct test_desc_t* test_ms)
160160
}
161161
}
162162

163-
void multiple_devices_option(const struct test_desc_t* test_ms)
163+
void multiple_function_option(const struct test_desc_t* test_ms)
164164
{
165165
int selection;
166166
char cmdline[256] = {0};
167167

168-
print_multiple_devices_test_menu(test_ms);
168+
print_multiple_function_test_menu(test_ms);
169169
while(strlen(cmdline) == 0)
170170
{
171171
/* Flush anything already in the RX buffer */
@@ -175,7 +175,7 @@ void multiple_devices_option(const struct test_desc_t* test_ms)
175175
UartRxString((uint8_t*) cmdline, sizeof(cmdline) - 1);
176176
if(strlen(cmdline) == 0) {
177177
/* if input was newline, print a new menu */
178-
print_multiple_devices_test_menu(test_ms);
178+
print_multiple_function_test_menu(test_ms);
179179
}
180180
}
181181
selection = atoi((const char *) cmdline) - 1;
@@ -194,7 +194,7 @@ static void unity_run_single_test(const struct test_desc_t* test)
194194
if(test->test_fn_count == 1) {
195195
UnityDefaultTestRun(test->fn[0], test->name, test->line);
196196
} else {
197-
multiple_devices_option(test);
197+
multiple_function_option(test);
198198
}
199199
}
200200

tools/unit-test-app/tools/TagDefinition.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,10 @@ test_env:
88
omitted: "UT_T1_1"
99
reset:
1010
default: "POWERON_RESET"
11-
omitted: " "
11+
omitted: " "
12+
multi_device:
13+
default: "Yes"
14+
omitted: "No"
15+
multi_stage:
16+
default: "Yes"
17+
omitted: "No"

tools/unit-test-app/tools/UnitTestParser.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ def parse_test_cases_from_elf(self, elf_file, app_name):
103103
self.test_env_tags.update({tc["test environment"]: [tc["ID"]]})
104104

105105
if function_count > 1:
106-
tc.update({"cmd set": "multiple_devices_case",
107-
"child case num": function_count})
108-
del tc['reset']
106+
tc.update({"child case num": function_count})
109107

110108
# only add cases need to be executed
111109
test_cases.append(tc)
@@ -191,7 +189,9 @@ def parse_one_test_case(self, name, description, file_name, app_name):
191189
"test environment": prop["test_env"],
192190
"reset": prop["reset"],
193191
"sub module": self.module_map[prop["module"]]['sub module'],
194-
"summary": name})
192+
"summary": name,
193+
"multi_device": prop["multi_device"],
194+
"multi_stage": prop["multi_stage"]})
195195
return test_case
196196

197197
def dump_test_cases(self, test_cases):

0 commit comments

Comments
 (0)