Skip to content

Commit 532107c

Browse files
committed
Merge branch 'test/ut_script' into 'master'
test: use the tiny-test-fw to run the unit test in CI See merge request idf/esp-idf!1558
2 parents 22dcdce + 5214a06 commit 532107c

File tree

12 files changed

+792
-168
lines changed

12 files changed

+792
-168
lines changed

.gitlab-ci.yml

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -446,22 +446,21 @@ assign_test:
446446
- components/idf_test/*/CIConfigs
447447
- components/idf_test/*/TC.sqlite
448448
- $EXAMPLE_CONFIG_OUTPUT_PATH
449+
- tools/unit-test-app/output
449450
expire_in: 1 mos
450451
before_script: *add_gitlab_key_before
451452
script:
452453
# first move test bins together: test_bins/CHIP_SDK/TestApp/bin_files
453454
- mkdir -p $OUTPUT_BIN_PATH
454-
# copy and rename folder name to "UT_config"
455-
- for CONFIG in $(ls $UT_BIN_PATH); do cp -r "$UT_BIN_PATH/$CONFIG" "$OUTPUT_BIN_PATH/UT_$CONFIG"; done
456455
- cp -r SSC/ssc_bin/* $OUTPUT_BIN_PATH
457456
# assign example tests
458457
- python $TEST_FW_PATH/CIAssignExampleTest.py $IDF_PATH/examples $IDF_PATH/.gitlab-ci.yml $EXAMPLE_CONFIG_OUTPUT_PATH
458+
# assign unit test cases
459+
- python $TEST_FW_PATH/CIAssignUnitTest.py $IDF_PATH/components/idf_test/unit_test/TestCaseAll.yml $IDF_PATH/.gitlab-ci.yml $IDF_PATH/components/idf_test/unit_test/CIConfigs
459460
# clone test script to assign tests
460461
- git clone $TEST_SCRIPT_REPOSITORY
461462
- cd auto_test_script
462463
- python $CHECKOUT_REF_SCRIPT auto_test_script
463-
# assign unit test cases
464-
- python CIAssignTestCases.py -t $IDF_PATH/components/idf_test/unit_test -c $IDF_PATH/.gitlab-ci.yml -b $IDF_PATH/test_bins
465464
# assgin integration test cases
466465
- python CIAssignTestCases.py -t $IDF_PATH/components/idf_test/integration_test -c $IDF_PATH/.gitlab-ci.yml -b $IDF_PATH/test_bins
467466

@@ -493,6 +492,17 @@ assign_test:
493492
# run test
494493
- python Runner.py $TEST_CASE_PATH -c $CONFIG_FILE
495494

495+
.unit_test_template: &unit_test_template
496+
<<: *example_test_template
497+
stage: unit_test
498+
dependencies:
499+
- assign_test
500+
variables:
501+
TEST_FW_PATH: "$CI_PROJECT_DIR/tools/tiny-test-fw"
502+
TEST_CASE_PATH: "$CI_PROJECT_DIR/tools/unit-test-app"
503+
CONFIG_FILE: "$CI_PROJECT_DIR/components/idf_test/unit_test/CIConfigs/$CI_JOB_NAME.yml"
504+
LOG_PATH: "$CI_PROJECT_DIR/TEST_LOGS"
505+
496506
.test_template: &test_template
497507
stage: test
498508
when: on_success
@@ -530,18 +540,6 @@ assign_test:
530540
# run test
531541
- python CIRunner.py -l "$LOG_PATH/$CI_JOB_NAME" -c $CONFIG_FILE -e $LOCAL_ENV_CONFIG_PATH -t $TEST_CASE_FILE_PATH -m $MODULE_UPDATE_FILE
532542

533-
# template for unit test jobs
534-
.unit_test_template: &unit_test_template
535-
<<: *test_template
536-
allow_failure: false
537-
stage: unit_test
538-
variables:
539-
LOCAL_ENV_CONFIG_PATH: "$CI_PROJECT_DIR/ci-test-runner-configs/$CI_RUNNER_DESCRIPTION/ESP32_IDF"
540-
LOG_PATH: "$CI_PROJECT_DIR/$CI_COMMIT_SHA"
541-
TEST_CASE_FILE_PATH: "$CI_PROJECT_DIR/components/idf_test/unit_test"
542-
MODULE_UPDATE_FILE: "$CI_PROJECT_DIR/components/idf_test/ModuleDefinition.yml"
543-
CONFIG_FILE: "$CI_PROJECT_DIR/components/idf_test/unit_test/CIConfigs/$CI_JOB_NAME.yml"
544-
545543
nvs_compatible_test:
546544
<<: *test_template
547545
artifacts:

components/driver/test/test_adc2.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ static esp_err_t event_handler(void *ctx, system_event_t *event)
4040
return ESP_OK;
4141
}
4242

43-
TEST_CASE("adc2 work with wifi","[adc]")
43+
TEST_CASE("adc2 work with wifi","[adc][ignore]")
4444
{
4545
int read_raw;
4646
int target_value;

tools/tiny-test-fw/CIAssignExampleTest.py

Lines changed: 3 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -22,147 +22,16 @@
2222
import re
2323
import argparse
2424

25-
import yaml
26-
2725
test_fw_path = os.getenv("TEST_FW_PATH")
2826
if test_fw_path:
2927
sys.path.insert(0, test_fw_path)
3028

31-
from Utility import CaseConfig, SearchCases, GitlabCIJob
32-
33-
34-
class Group(object):
35-
36-
MAX_EXECUTION_TIME = 30
37-
MAX_CASE = 15
38-
SORT_KEYS = ["env_tag"]
39-
40-
def __init__(self, case):
41-
self.execution_time = 0
42-
self.case_list = [case]
43-
self.filters = dict(zip(self.SORT_KEYS, [case.case_info[x] for x in self.SORT_KEYS]))
44-
45-
def accept_new_case(self):
46-
"""
47-
check if allowed to add any case to this group
48-
49-
:return: True or False
50-
"""
51-
max_time = (sum([x.case_info["execution_time"] for x in self.case_list]) < self.MAX_EXECUTION_TIME)
52-
max_case = (len(self.case_list) < self.MAX_CASE)
53-
return max_time and max_case
54-
55-
def add_case(self, case):
56-
"""
57-
add case to current group
58-
59-
:param case: test case
60-
:return: True if add succeed, else False
61-
"""
62-
added = False
63-
if self.accept_new_case():
64-
for key in self.filters:
65-
if case.case_info[key] != self.filters[key]:
66-
break
67-
else:
68-
self.case_list.append(case)
69-
added = True
70-
return added
71-
72-
def output(self):
73-
"""
74-
output data for job configs
75-
76-
:return: {"Filter": case filter, "CaseConfig": list of case configs for cases in this group}
77-
"""
78-
output_data = {
79-
"Filter": self.filters,
80-
"CaseConfig": [{"name": x.case_info["name"]} for x in self.case_list],
81-
}
82-
return output_data
83-
29+
from Utility.CIAssignTest import AssignTest
8430

85-
class AssignTest(object):
86-
"""
87-
Auto assign tests to CI jobs.
88-
89-
:param test_case: path of test case file(s)
90-
:param ci_config_file: path of ``.gitlab-ci.yml``
91-
"""
9231

32+
class CIExampleAssignTest(AssignTest):
9333
CI_TEST_JOB_PATTERN = re.compile(r"^example_test_.+")
9434

95-
def __init__(self, test_case, ci_config_file):
96-
self.test_cases = self._search_cases(test_case)
97-
self.jobs = self._parse_gitlab_ci_config(ci_config_file)
98-
99-
def _parse_gitlab_ci_config(self, ci_config_file):
100-
101-
with open(ci_config_file, "r") as f:
102-
ci_config = yaml.load(f)
103-
104-
job_list = list()
105-
for job_name in ci_config:
106-
if self.CI_TEST_JOB_PATTERN.search(job_name) is not None:
107-
job_list.append(GitlabCIJob.Job(ci_config[job_name], job_name))
108-
return job_list
109-
110-
@staticmethod
111-
def _search_cases(test_case, case_filter=None):
112-
"""
113-
:param test_case: path contains test case folder
114-
:param case_filter: filter for test cases
115-
:return: filtered test case list
116-
"""
117-
test_methods = SearchCases.Search.search_test_cases(test_case)
118-
return CaseConfig.filter_test_cases(test_methods, case_filter if case_filter else dict())
119-
120-
def _group_cases(self):
121-
"""
122-
separate all cases into groups according group rules. each group will be executed by one CI job.
123-
124-
:return: test case groups.
125-
"""
126-
groups = []
127-
for case in self.test_cases:
128-
for group in groups:
129-
# add to current group
130-
if group.add_case(case):
131-
break
132-
else:
133-
# create new group
134-
groups.append(Group(case))
135-
return groups
136-
137-
def assign_cases(self):
138-
"""
139-
separate test cases to groups and assign test cases to CI jobs.
140-
141-
:raise AssertError: if failed to assign any case to CI job.
142-
:return: None
143-
"""
144-
failed_to_assign = []
145-
test_groups = self._group_cases()
146-
for group in test_groups:
147-
for job in self.jobs:
148-
if job.match_group(group):
149-
job.assign_group(group)
150-
break
151-
else:
152-
failed_to_assign.append(group)
153-
assert not failed_to_assign
154-
155-
def output_configs(self, output_path):
156-
"""
157-
158-
:param output_path: path to output config files for each CI job
159-
:return: None
160-
"""
161-
if not os.path.exists(output_path):
162-
os.makedirs(output_path)
163-
for job in self.jobs:
164-
job.output_config(output_path)
165-
16635

16736
if __name__ == '__main__':
16837
parser = argparse.ArgumentParser()
@@ -174,6 +43,6 @@ def output_configs(self, output_path):
17443
help="output path of config files")
17544
args = parser.parse_args()
17645

177-
assign_test = AssignTest(args.test_case, args.ci_config_file)
46+
assign_test = CIExampleAssignTest(args.test_case, args.ci_config_file)
17847
assign_test.assign_cases()
17948
assign_test.output_configs(args.output_path)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""
2+
Command line tool to assign unit tests to CI test jobs.
3+
"""
4+
5+
import re
6+
import os
7+
import sys
8+
import argparse
9+
10+
import yaml
11+
12+
test_fw_path = os.getenv("TEST_FW_PATH")
13+
if test_fw_path:
14+
sys.path.insert(0, test_fw_path)
15+
16+
from Utility import CIAssignTest
17+
18+
19+
class Group(CIAssignTest.Group):
20+
SORT_KEYS = ["Test App", "SDK", "test environment"]
21+
MAX_CASE = 30
22+
ATTR_CONVERT_TABLE = {
23+
"execution_time": "execution time"
24+
}
25+
26+
@staticmethod
27+
def _get_case_attr(case, attr):
28+
if attr in Group.ATTR_CONVERT_TABLE:
29+
attr = Group.ATTR_CONVERT_TABLE[attr]
30+
return case[attr]
31+
32+
@staticmethod
33+
def _get_ut_config(test_app):
34+
# we format test app "UT_ + config" when parsing test cases
35+
# now we need to extract config
36+
assert test_app[:3] == "UT_"
37+
return test_app[3:]
38+
39+
def _create_extra_data(self):
40+
case_data = []
41+
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+
})
54+
return case_data
55+
56+
def output(self):
57+
"""
58+
output data for job configs
59+
60+
:return: {"Filter": case filter, "CaseConfig": list of case configs for cases in this group}
61+
"""
62+
output_data = {
63+
# we don't need filter for test function, as UT uses a few test functions for all cases
64+
"CaseConfig": [
65+
{
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(),
68+
}
69+
]
70+
}
71+
return output_data
72+
73+
74+
class UnitTestAssignTest(CIAssignTest.AssignTest):
75+
CI_TEST_JOB_PATTERN = re.compile(r"^UT_.+")
76+
77+
def __init__(self, test_case_path, ci_config_file):
78+
CIAssignTest.AssignTest.__init__(self, test_case_path, ci_config_file, case_group=Group)
79+
80+
@staticmethod
81+
def _search_cases(test_case_path, case_filter=None):
82+
"""
83+
For unit test case, we don't search for test functions.
84+
The unit test cases is stored in a yaml file which is created in job build-idf-test.
85+
"""
86+
87+
with open(test_case_path, "r") as f:
88+
raw_data = yaml.load(f)
89+
test_cases = raw_data["test cases"]
90+
if case_filter:
91+
for key in case_filter:
92+
filtered_cases = []
93+
for case in test_cases:
94+
try:
95+
# bot converts string to lower case
96+
if isinstance(case[key], str):
97+
_value = case[key].lower()
98+
else:
99+
_value = case[key]
100+
if _value in case_filter[key]:
101+
filtered_cases.append(case)
102+
except KeyError:
103+
# case don't have this key, regard as filter success
104+
filtered_cases.append(case)
105+
test_cases = filtered_cases
106+
return test_cases
107+
108+
109+
if __name__ == '__main__':
110+
parser = argparse.ArgumentParser()
111+
parser.add_argument("test_case",
112+
help="test case folder or file")
113+
parser.add_argument("ci_config_file",
114+
help="gitlab ci config file")
115+
parser.add_argument("output_path",
116+
help="output path of config files")
117+
args = parser.parse_args()
118+
119+
assign_test = UnitTestAssignTest(args.test_case, args.ci_config_file)
120+
assign_test.assign_cases()
121+
assign_test.output_configs(args.output_path)

tools/tiny-test-fw/DUT.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def write(self, data, eol="\r\n", flush=True):
316316
if flush:
317317
self.data_cache.flush()
318318
# do write if cache
319-
if data:
319+
if data is not None:
320320
self._port_write(data + eol if eol else data)
321321

322322
@_expect_lock
@@ -557,9 +557,9 @@ def _format_data(data):
557557
:return: formatted data (str)
558558
"""
559559
timestamp = time.time()
560-
timestamp = "{}:{}".format(time.strftime("%m-%d %H:%M:%S", time.localtime(timestamp)),
561-
str(timestamp % 1)[2:5])
562-
formatted_data = "[{}]:\r\n{}\r\n".format(timestamp, _decode_data(data))
560+
timestamp = "[{}:{}]".format(time.strftime("%m-%d %H:%M:%S", time.localtime(timestamp)),
561+
str(timestamp % 1)[2:5])
562+
formatted_data = timestamp.encode() + b"\r\n" + data + b"\r\n"
563563
return formatted_data
564564

565565
def _port_open(self):
@@ -571,11 +571,13 @@ def _port_close(self):
571571
def _port_read(self, size=1):
572572
data = self.port_inst.read(size)
573573
if data:
574-
with open(self.log_file, "a+") as _log_file:
574+
with open(self.log_file, "ab+") as _log_file:
575575
_log_file.write(self._format_data(data))
576576
return data
577577

578578
def _port_write(self, data):
579+
if isinstance(data, str):
580+
data = data.encode()
579581
self.port_inst.write(data)
580582

581583
@classmethod

0 commit comments

Comments
 (0)