README.md
1# Unit Test App
2
3ESP-IDF unit tests are run using Unit Test App. The app can be built with the unit tests for a specific component. Unit tests are in `test` subdirectories of respective components.
4
5# Building Unit Test App
6
7## CMake
8
9* Follow the setup instructions in the top-level esp-idf README.
10* Set IDF_PATH environment variable to point to the path to the esp-idf top-level directory.
11* Change into `tools/unit-test-app` directory
12* `idf.py menuconfig` to configure the Unit Test App.
13* `idf.py -T <component> -T <component> ... build` with `component` set to names of the components to be included in the test app. Or `idf.py -T all build` to build the test app with all the tests for components having `test` subdirectory.
14* Follow the printed instructions to flash, or run `idf.py -p PORT flash`.
15* Unit test have a few preset sdkconfigs. It provides command `idf.py ut-clean-config_name` and `idf.py ut-build-config_name` (where `config_name` is the file name under `unit-test-app/configs` folder) to build with preset configs. For example, you can use `idf.py -T all ut-build-default` to build with config file `unit-test-app/configs/default`. Built binary for this config will be copied to `unit-test-app/output/config_name` folder.
16
17## Legacy GNU Make
18
19* Follow the setup instructions in the top-level esp-idf README.
20* Set IDF_PATH environment variable to point to the path to the esp-idf top-level directory.
21* Change into `tools/unit-test-app` directory
22* `make menuconfig` to configure the Unit Test App.
23* `make TEST_COMPONENTS=` with `TEST_COMPONENTS` set to names of the components to be included in the test app. Or `make TESTS_ALL=1` to build the test app with all the tests for components having `test` subdirectory.
24* Follow the printed instructions to flash, or run `make flash`.
25* Unit test have a few preset sdkconfigs. It provides command `make ut-clean-config_name` and `make ut-build-config_name` (where `config_name` is the file name under `unit-test-app/configs` folder) to build with preset configs. For example, you can use `make ut-build-default TESTS_ALL=1` to build with config file `unit-test-app/configs/default`. Built binary for this config will be copied to `unit-test-app/output/config_name` folder.
26
27# Flash Size
28
29The unit test partition table assumes a 4MB flash size. When testing `-T all` or `TESTS_ALL=1` (Legacy GNU Make) or, this additional factory app partition size is required.
30
31If building unit tests to run on a smaller flash size, edit `partition_table_unit_tests_app.csv` and use `-T <component> <component> ...` or `TEST_COMPONENTS=` (Legacy GNU Make) or instead of `-T all` or `TESTS_ALL` if tests don't fit in a smaller factory app partition (exact size will depend on configured options).
32
33# Running Unit Tests
34
35The unit test loader will prompt by showing a menu of available tests to run:
36
37* Type a number to run a single test.
38* `*` to run all tests.
39* `[tagname]` to run tests with "tag"
40* `![tagname]` to run tests without "tag" (`![ignore]` is very useful as it runs all CI-enabled tests.)
41* `"test name here"` to run test with given name
42
43# Testing Unit Tests with CI
44
45## CI Test Flow for Unit Test
46
47Unit test uses 3 stages in CI: `build`, `assign_test`, `unit_test`.
48
49### Build Stage:
50
51`build_esp_idf_tests` job will build all UT configs and parse test cases form built elf files. Built binary (`tools/unit-test-app/output`) and parsed cases (`components/idf_test/unit_test/TestCaseAll.yml`) will be saved as artifacts.
52
53When we add new test case, it will construct a structure to save case data during build. We'll parse the test case from this structure. The description (defined in test case: `TEST_CASE("name", "description")`) is used to extend test case definition. The format of test description is a list of tags:
54
551. first tag is always group of test cases, it's mandatory
562. the rest tags should be [type=value]. Tags could have default value and omitted value. For example, reset tag default value is "POWERON_RESET", omitted value is "" (do not reset) :
57 * "[reset]" equal to [reset=POWERON_RESET]
58 * if reset tag doesn't exist, then it equals to [reset=""]
593. the `[leaks]` tag is used to disable the leak checking. A specific maximum memory leakage can be set as follows: `[leaks=500]`. This allows no more than 500 bytes of heap to be leaked. Also there is a special function to set the critical level of leakage not through a tag, just directly in the test code ``test_utils_set_critical_leak_level()``.
60
61The priority of using leakage level is as follows:
62
631. Setting by tag `[leaks=500]`.
642. Setting by ``test_utils_set_critical_leak_level()`` function.
653. Setting by default leakage in Kconfig ``CONFIG_UNITY_CRITICAL_LEAK_LEVEL_GENERAL``.
66
67Tests marked as `[leaks]` or `[leaks=xxx]` reset the device after completion (or after each stage in multistage tests).
68
69`TagDefinition.yml` defines how we should parse the description. In `TagDefinition.yml`, we declare the tags we are interested in, their default value and omitted value. Parser will parse the properities of test cases according to this file, and add them as test case attributes.
70
71We will build unit-test-app with different sdkconfigs. Some config items requires specific board to run. For example, if `CONFIG_ESP32_SPIRAM_SUPPORT` is enabled, then unit test app must run on board supports PSRAM. `ConfigDependency.yml` is used to define the mapping between sdkconfig items and tags. The tags will be saved as case attributes, used to select jobs and runners. In the previous example, `psram` tag is generated, will only select jobs and runners also contains `psram` tag.
72
73### Assign Test Stage:
74
75`assign_test` job will try to assign all cases to test jobs defined in `.gitlab-ci.yml`, according to test environment and tags. For each job, one config file with same name of test job will be generated in `components/idf_test/unit_test/CIConfigs/`(this folder will be passed to test jobs as artifacts). These config files will tell test jobs which cases it need to run, and pass some extra configs (like if the case will reset) of test case to runner.
76
77Please check related document in tiny-test-fw for details.
78
79### Unit Test Stage:
80
81All jobs in `unit_test` stage will run job according to unit test configs. Then unit test jobs will use tiny-test-fw runner to run the test cases. The test logs will be saved as artifacts.
82
83Unit test jobs will do reset before running each case (because some cases do not cleanup when failed). This makes test cases independent with each other during execution.
84
85## Handle Unit Test CI Issues
86
87### 1. Assign Test Failures
88
89Gitlab CI do not support create jobs at runtime. We must maunally add all jobs to CI config file. To make test running in parallel, we limit the number of cases running on each job. When add new unit test cases, it could exceed the limitation that current unit test jobs support. In this case, assign test job will raise error, remind you to add jobs to `.gitlab-ci.yml`.
90
91```
92Too many test cases vs jobs to run. Please add the following jobs to .gitlab-ci.yml with specific tags:
93* Add job with: UT_T1_1, ESP32_IDF, psram
94* Add job with: UT_T1_1, ESP32_IDF
95```
96
97The above is an example of error message in assign test job. In this case, please add the following jobs in `.gitlab-ci.yml`:
98
99```
100UT_001_25:
101 <<: *unit_test_template
102 tags:
103 - ESP32_IDF
104 - UT_T1_1
105
106UT_004_09:
107 <<: *unit_test_template
108 tags:
109 - ESP32_IDF
110 - UT_T1_1
111 - psram
112```
113
114The naming rule of jobs are `UT` + `job type index` + `job index`. Each combination of tags is a different job type.
115
116### 2. Debugging Failed Cases
117
118First you can check the logs. It's saved as unit test job artifacts. You can download from the test job page.
119
120If you want to reproduce locally, you need to:
121
1221. Download artifacts of `build_esp_idf_tests`. The built binary is in `tools/unit-test-app/output` folder.
123 * Built binary in CI could be slightly different from locally built binary with the same revision, some cases might only fails with CI built binary.
1242. Check the following print in CI job to get the config name: `Running unit test for config: config_name`. Then flash the binary of this config to your board.
1253. Run the failed case on your board (refer to Running Unit Tests section).
126 * There're some special UT cases (multiple stages case, multiple devices cases) which requires user interaction:
127 * You can refer to [unit test document](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/unit-tests.html#running-unit-tests) to run test manually.
128 * Or, you can use `tools/unit-test-app/unit_test.py` to run the test cases (see below)
129
130## Running unit tests on local machine by `unit_test.py`
131
132First, install Python dependencies and export the Python path where the IDF CI Python modules are found:
133
134```bash
135pip install -r $IDF_PATH/tools/ci/python_packages/tiny_test_fw/requirements.txt
136export PYTHONPATH=$IDF_PATH/tools/ci/python_packages
137```
138
139Change to the unit test app directory, configure the app as needed and build it in the default "build" directory. For example:
140
141```bash
142cd $IDF_PATH/tools/unit-test-app
143idf.py ut-apply-config-psram
144idf.py build -T vfs
145```
146
147(Instead of these steps, you can do whatever is needed to configure & build a unit test app with the tests and config that you need.)
148
149### run a single test case by name
150
151```bash
152
153./unit_test.py "UART can do select()"
154```
155
156unit_test.py script will flash the unit test binary from the (default) build directory, then run the test case.
157
158### Run a single test case twice
159
160```bash
161./unit_test.py -r 2 "UART can do select()"
162```
163
164### run multiple unit test cases
165
166```bash
167./unit_test.py "UART can do select()" "concurrent selects work"
168```
169
170### run a multi-stage test (type of test and child case numbers are autodetected)
171
172```bash
173./unit_test.py "check a time after wakeup from deep sleep"
174```
175
176### run a list of different unit tests (one simple and one multi-stage test)
177
178```bash
179./unit_test.py "concurrent selects work" "check a time after wakeup from deep sleep"
180```
181
182### Use custom environment config file
183
184```bash
185./unit_test.py -e /tmp/EnvConfigTemplate.yml "UART can do select()"
186```
187
188Note: No sample YAML file is currently available.
189
190### use custom application binary
191
192```bash
193./unit_test.py -b /tmp/app.bin "UART can do select()"
194```
195
196Note: This option doesn't currently work without an EnvConfigTemplate also supplied, use the default unit-test-app binaries only.
197
198### add some options for unit tests
199
200```bash
201./unit_test.py "UART can do select()",timeout:10 "concurrent selects work",config:release,env_tag:UT_T2_1
202```
203
204Note: Setting the `config` and `env_tag` values doesn't significantly change anything but the console log output, the same binary is used.
205