1Unit Testing in {IDF_TARGET_NAME}
2=================================
3:link_to_translation:`zh_CN:[中文]`
4
5ESP-IDF provides the following methods to test software.
6
7- Target based tests using a central unit test application which runs on the {IDF_TARGET_PATH_NAME}. These tests use the `Unity <https://www.throwtheswitch.org/unity>` unit test framework. They can be integrated into an ESP-IDF component by placing them in the component's ``test`` subdirectory. For the most part, this document is about target based tests.
8- Linux-host based unit tests in which all the hardware is abstracted via mocks. Linux-host based tests are still under development and only a small fraction of IDF components support them, currently. They are covered here: :doc:`target based unit testing <linux-host-testing>`.
9
10Normal Test Cases
11------------------
12
13Unit tests are located in the ``test`` subdirectory of a component. Tests are written in C, and a single C source file can contain multiple test cases. Test files start with the word "test".
14
15Each test file should include the ``unity.h`` header and the header for the C module to be tested.
16
17Tests are added in a function in the C file as follows:
18
19.. code-block:: c
20
21    TEST_CASE("test name", "[module name]"
22    {
23            // Add test here
24    }
25
26- The first argument is a descriptive name for the test.
27- The second argument is an identifier in square brackets. Identifiers are used to group related test, or tests with specific properties.
28
29.. note::
30    There is no need to add a main function with ``UNITY_BEGIN()`` and ``​UNITY_END()`` in each test case. ``unity_platform.c`` will run ``UNITY_BEGIN()`` autonomously, and run the test cases, then call ``​UNITY_END()``.
31
32The ``test`` subdirectory should contain a :ref:`component CMakeLists.txt <component-directories>`, since they are themselves components (i.e., a test component). ESP-IDF uses the Unity test framework located in the ``unity`` component. Thus, each test component should specify the ``unity`` component as a component requirement using the ``REQUIRES`` argument. Normally, components :ref:`should list their sources manually <cmake-file-globbing>`; for component tests however, this requirement is relaxed and the use of the ``SRC_DIRS`` argument in ``idf_component_register`` is advised.
33
34Overall, the minimal ``test`` subdirectory ``CMakeLists.txt`` file should contain the following:
35
36.. code:: cmake
37
38    idf_component_register(SRC_DIRS "."
39                           INCLUDE_DIRS "."
40                           REQUIRES unity)
41
42See http://www.throwtheswitch.org/unity for more information about writing tests in Unity.
43
44
45Multi-device Test Cases
46-------------------------
47
48The normal test cases will be executed on one DUT (Device Under Test). However, components that require some form of communication (e.g., GPIO, SPI) require another device to communicate with, thus cannot be tested normal test cases. Multi-device test cases involve writing multiple test functions, and running them on multiple DUTs.
49
50The following is an example of a multi-device test case:
51
52.. code-block:: c
53
54    void gpio_master_test()
55    {
56        gpio_config_t slave_config = {
57                .pin_bit_mask = 1 << MASTER_GPIO_PIN,
58                .mode = GPIO_MODE_INPUT,
59        };
60        gpio_config(&slave_config);
61        unity_wait_for_signal("output high level");
62        TEST_ASSERT(gpio_get_level(MASTER_GPIO_PIN) == 1);
63    }
64
65    void gpio_slave_test()
66    {
67        gpio_config_t master_config = {
68                .pin_bit_mask = 1 << SLAVE_GPIO_PIN,
69                .mode = GPIO_MODE_OUTPUT,
70        };
71        gpio_config(&master_config);
72        gpio_set_level(SLAVE_GPIO_PIN, 1);
73        unity_send_signal("output high level");
74    }
75
76    TEST_CASE_MULTIPLE_DEVICES("gpio multiple devices test example", "[driver]", gpio_master_test, gpio_slave_test);
77
78The macro ``TEST_CASE_MULTIPLE_DEVICES`` is used to declare a multi-device test case.
79
80- The first argument is test case name.
81- The second argument is test case description.
82- From the third argument, up to 5 test functions can be defined, each function will be the entry point of tests running on each DUT.
83
84Running test cases from different DUTs could require synchronizing between DUTs. We provide ``unity_wait_for_signal`` and ``unity_send_signal`` to support synchronizing with UART. As the scenario in the above example, the slave should get GPIO level after master set level. DUT UART console will prompt and user interaction is required:
85
86DUT1 (master) console::
87
88    Waiting for signal: [output high level]!
89    Please press "Enter" key to once any board send this signal.
90
91DUT2 (slave) console::
92
93    Send signal: [output high level]!
94
95Once the signal is sent from DUT2, you need to press "Enter" on DUT1, then DUT1 unblocks from ``unity_wait_for_signal`` and starts to change GPIO level.
96
97
98Multi-stage Test Cases
99-----------------------
100
101The normal test cases are expected to finish without reset (or only need to check if reset happens). Sometimes we expect to run some specific tests after certain kinds of reset. For example, we want to test if the reset reason is correct after a wake up from deep sleep. We need to create a deep-sleep reset first and then check the reset reason. To support this, we can define multi-stage test cases, to group a set of test functions::
102
103    static void trigger_deepsleep(void)
104    {
105        esp_sleep_enable_timer_wakeup(2000);
106        esp_deep_sleep_start();
107    }
108
109    void check_deepsleep_reset_reason()
110    {
111        soc_reset_reason_t reason = esp_rom_get_reset_reason(0);
112        TEST_ASSERT(reason == RESET_REASON_CORE_DEEP_SLEEP);
113    }
114
115    TEST_CASE_MULTIPLE_STAGES("reset reason check for deepsleep", "[{IDF_TARGET_PATH_NAME}]", trigger_deepsleep, check_deepsleep_reset_reason);
116
117Multi-stage test cases present a group of test functions to users. It needs user interactions (select cases and select different stages) to run the case.
118
119Tests For Different Targets
120------------------------------
121
122Some tests (especially those related to hardware) cannot run on all targets. Below is a guide how to make your unit tests run on only specified targets.
123
1241. Wrap your test code by ``!(TEMPORARY_)DISABLED_FOR_TARGETS()`` macros and place them either in the original test file, or separate the code into files grouped by functions, but make sure all these files will be processed by the compiler. E.g.::
125
126      #if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32, ESP8266)
127      TEST_CASE("a test that is not ready for esp32 and esp8266 yet", "[]")
128      {
129      }
130      #endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32, ESP8266)
131
132Once you need one of the tests to be compiled on a specified target, just modify the targets in the disabled list. It's more encouraged to use some general conception that can be described in ``soc_caps.h`` to control the disabling of tests. If this is done but some of the tests are not ready yet, use both of them (and remove ``!(TEMPORARY_)DISABLED_FOR_TARGETS()`` later). E.g.: ::
133
134      #if SOC_SDIO_SLAVE_SUPPORTED
135      #if !TEMPORARY_DISABLED_FOR_TARGETS(ESP64)
136      TEST_CASE("a sdio slave tests that is not ready for esp64 yet", "[sdio_slave]")
137      {
138          //available for esp32 now, and will be available for esp64 in the future
139      }
140      #endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP64)
141      #endif //SOC_SDIO_SLAVE_SUPPORTED
142
1432. For test code that you are 100% for sure that will not be supported (e.g. no peripheral at all), use ``DISABLED_FOR_TARGETS``; for test code that should be disabled temporarily, or due to lack of runners, etc., use ``TEMPORARY_DISABLED_FOR_TARGETS``.
144
145Some old ways of disabling unit tests for targets, that have obvious disadvantages, are deprecated:
146
147- DON'T put the test code under ``test/target`` folder and use CMakeLists.txt to choose one of the target folder. This is prevented because test code is more likely to be reused than the implementations. If you put something into ``test/esp32`` just to avoid building it on esp32s2, it's hard to make the code tidy if you want to enable the test again on esp32s3.
148
149- DON'T use ``CONFIG_IDF_TARGET_xxx`` macros to disable the test items any more. This makes it harder to track disabled tests and enable them again. Also, a black-list style ``#if !disabled`` is preferred to white-list style ``#if CONFIG_IDF_TARGET_xxx``, since you will not silently disable cases when new targets are added in the future. But for test implementations, it's allowed to use ``#if CONFIG_IDF_TARGET_xxx`` to pick one of the implementation code.
150
151  - Test item: some items that will be performed on some targets, but skipped on other targets. E.g.
152
153    There are three test items SD 1-bit, SD 4-bit and SDSPI. For ESP32-S2, which doesn't have SD host, among the tests only SDSPI is enabled on ESP32-S2.
154
155  - Test implementation: some code will always happen, but in different ways. E.g.
156
157    There is no SDIO PKT_LEN register on ESP8266. If you want to get the length from the slave as a step in the test process, you can have different implementation code protected by ``#if CONFIG_IDF_TARGET_`` reading in different ways.
158
159    But please avoid using ``#else`` macro. When new target is added, the test case will fail at building stage, so that the maintainer will be aware of this, and choose one of the implementations explicitly.
160
161Building Unit Test App
162----------------------
163
164Follow the setup instructions in the top-level esp-idf README. Make sure that ``IDF_PATH`` environment variable is set to point to the path of esp-idf top-level directory.
165
166Change into ``tools/unit-test-app`` directory to configure and build it:
167
168* ``idf.py menuconfig`` - configure unit test app.
169* ``idf.py -T all build`` - build unit test app with tests for each component having tests in the ``test`` subdirectory.
170* ``idf.py -T "xxx yyy" build`` - build unit test app with tests for some space-separated specific components (For instance: ``idf.py -T heap build`` - build unit tests only for ``heap`` component directory).
171* ``idf.py -T all -E "xxx yyy" build`` - build unit test app with all unit tests, except for unit tests of some components (For instance: ``idf.py -T all -E "ulp mbedtls" build`` - build all unit tests excludes ``ulp`` and ``mbedtls`` components).
172
173.. note::
174
175    Due to inherent limitations of Windows command prompt, following syntax has to be used in order to build unit-test-app with multiple components: ``idf.py -T xxx -T yyy build`` or with escaped quotes: ``idf.py -T \`"xxx yyy\`" build`` in PowerShell or ``idf.py -T \^"ssd1306 hts221\^" build`` in Windows command prompt.
176
177When the build finishes, it will print instructions for flashing the chip. You can simply run ``idf.py flash`` to flash all build output.
178
179You can also run ``idf.py -T all flash`` or ``idf.py -T xxx flash`` to build and flash. Everything needed will be rebuilt automatically before flashing.
180
181Use menuconfig to set the serial port for flashing.
182
183Running Unit Tests
184------------------
185
186After flashing reset the {IDF_TARGET_NAME} and it will boot the unit test app.
187
188When unit test app is idle, press "Enter" will make it print test menu with all available tests::
189
190    Here's the test menu, pick your combo:
191    (1)     "esp_ota_begin() verifies arguments" [ota]
192    (2)     "esp_ota_get_next_update_partition logic" [ota]
193    (3)     "Verify bootloader image in flash" [bootloader_support]
194    (4)     "Verify unit test app image" [bootloader_support]
195    (5)     "can use new and delete" [cxx]
196    (6)     "can call virtual functions" [cxx]
197    (7)     "can use static initializers for non-POD types" [cxx]
198    (8)     "can use std::vector" [cxx]
199    (9)     "static initialization guards work as expected" [cxx]
200    (10)    "global initializers run in the correct order" [cxx]
201    (11)    "before scheduler has started, static initializers work correctly" [cxx]
202    (12)    "adc2 work with wifi" [adc]
203    (13)    "gpio master/slave test example" [ignore][misc][test_env=UT_T2_1][multi_device]
204            (1)     "gpio_master_test"
205            (2)     "gpio_slave_test"
206    (14)    "SPI Master clockdiv calculation routines" [spi]
207    (15)    "SPI Master test" [spi][ignore]
208    (16)    "SPI Master test, interaction of multiple devs" [spi][ignore]
209    (17)    "SPI Master no response when switch from host1 (SPI2) to host2 (SPI3)" [spi]
210    (18)    "SPI Master DMA test, TX and RX in different regions" [spi]
211    (19)    "SPI Master DMA test: length, start, not aligned" [spi]
212    (20)    "reset reason check for deepsleep" [{IDF_TARGET_PATH_NAME}][test_env=UT_T2_1][multi_stage]
213            (1)     "trigger_deepsleep"
214            (2)     "check_deepsleep_reset_reason"
215
216The normal case will print the case name and description. Master-slave cases will also print the sub-menu (the registered test function names).
217
218Test cases can be run by inputting one of the following:
219
220- Test case name in quotation marks to run a single test case
221
222- Test case index to run a single test case
223
224- Module name in square brackets to run all test cases for a specific module
225
226- An asterisk to run all test cases
227
228``[multi_device]`` and ``[multi_stage]`` tags tell the test runner whether a test case is a multiple devices or multiple stages of test case. These tags are automatically added by ```TEST_CASE_MULTIPLE_STAGES`` and ``TEST_CASE_MULTIPLE_DEVICES`` macros.
229
230After you select a multi-device test case, it will print sub-menu::
231
232    Running gpio master/slave test example...
233    gpio master/slave test example
234            (1)     "gpio_master_test"
235            (2)     "gpio_slave_test"
236
237You need to input a number to select the test running on the DUT.
238
239Similar to multi-device test cases, multi-stage test cases will also print sub-menu::
240
241    Running reset reason check for deepsleep...
242    reset reason check for deepsleep
243            (1)     "trigger_deepsleep"
244            (2)     "check_deepsleep_reset_reason"
245
246First time you execute this case, input ``1`` to run first stage (trigger deepsleep). After DUT is rebooted and able to run test cases, select this case again and input ``2`` to run the second stage. The case only passes if the last stage passes and all previous stages trigger reset.
247
248
249.. _cache-compensated-timer:
250
251Timing Code with Cache Compensated Timer
252-----------------------------------------
253
254Instructions and data stored in external memory (e.g. SPI Flash and SPI RAM) are accessed through the CPU's unified instruction and data cache. When code or data is in cache, access is very fast (i.e., a cache hit).
255
256However, if the instruction or data is not in cache, it needs to be fetched from external memory (i.e., a cache miss). Access to external memory is significantly slower, as the CPU must execute stall cycles whilst waiting for the instruction or data to be retrieved from external memory. This can cause the overall code execution speed to vary depending on the number of cache hits or misses.
257
258Code and data placements can vary between builds, and some arrangements may be more favorable with regards to cache access (i.e., minimizing cache misses). This can technically affect execution speed, however these factors are usually irrelevant as their effect 'average out' over the device's operation.
259
260The effect of the cache on execution speed, however, can be relevant in benchmarking scenarios (especially micro benchmarks). There might be some variability in measured time between runs and between different builds. A technique for eliminating for some of the variability is to place code and data in instruction or data RAM (IRAM/DRAM), respectively. The CPU can access IRAM and DRAM directly, eliminating the cache out of the equation. However, this might not always be viable as the size of IRAM and DRAM is limited.
261
262The cache compensated timer is an alternative to placing the code/data to be benchmarked in IRAM/DRAM. This timer uses the processor's internal event counters in order to determine the amount of time spent on waiting for code/data in case of a cache miss, then subtract that from the recorded wall time.
263
264  .. code-block:: c
265
266    // Start the timer
267    ccomp_timer_start();
268
269    // Function to time
270    func_code_to_time();
271
272    // Stop the timer, and return the elapsed time in microseconds relative to
273    // ccomp_timer_start
274    int64_t t = ccomp_timer_stop();
275
276
277One limitation of the cache compensated timer is that the task that benchmarked functions should be pinned to a core. This is due to each core having its own event counters that are independent of each other. For example, if ``ccomp_timer_start`` gets called on one core, put to sleep by the scheduler, wakes up, and gets rescheduled on the other core, then the corresponding ``ccomp_timer_stop`` will be invalid.
278
279Mocks
280-----
281
282.. note::
283    Currently, mocking is only possible with some selected components when running on the Linux host. In the future, we plan to make essential components in IDF mockable. This will also include mocking when running on the {IDF_TARGET_NAME}.
284
285One of the biggest problems regarding unit testing of embedded systems are the strong hardware dependencies. Running unit tests directly on the {IDF_TARGET_NAME} can be especially difficult for higher layer components for the following reasons:
286
287- Decreased test reliability due to lower layer components and/or hardware setup.
288- Increased difficulty in testing edge cases due to limitations of lower layer components and/or hardware setup
289- Increased difficulty in identifying the root cause due to the large number of dependencies influencing the behavior
290
291When testing a particular component, (i.e., the component under test), software mocking allows the dependencies of the component under test to be substituted (i.e., mocked) entirely in software. To allow software mocking, ESP-IDF integrates the `CMock <https://www.throwtheswitch.org/cmock>`_ mocking framework as a component. With the addition of some CMake functions in the ESP-IDF's build system, it is possible to conveniently mock the entirety (or a part of) an IDF component.
292
293Ideally, all components that the component under test is dependent on should be mocked, thus allowing the test environment complete control over all interactions with the component under test. However, if mocking all dependent components becomes too complex or too tedious (e.g. because you need to mock too many function calls) you have the following options:
294
295.. list::
296    - Include more "real" IDF code in the tests. This may work but increases the dependency on the "real" code's behavior. Furthermore, once a test fails, you may not know if the failure is in your actual code under tests or the "real" IDF code.
297    - Re-evaluate the design of the code under test and attempt to reduce its dependencies by dividing the code under test into more manageable components. This may seem burdensome but it is common knowledge that unit tests often expose software design weaknesses. Fixing design weaknesses will not only help with unit testing in the short term, but will help future code maintenance as well.
298
299Refer to :component_file:`cmock/CMock/docs/CMock_Summary.md` for more details on how CMock works and how to create and use mocks.
300
301Requirements
302^^^^^^^^^^^^
303The following requirements are necessary to generate the mocks:
304
305.. list::
306    - Installed ESP-IDF with all its requirements
307    - ``ruby``
308    - On the Linux target, which is the only target where mocking currently works, ``libbsd`` is required, too
309
310Mock a Component
311^^^^^^^^^^^^^^^^
312
313To create a mock version of a component, called a *component mock*, the component needs to be overwritten in a particular way. Overriding a component entails creating a component with the exact same name as the original component, then let the build system discover it later than the original component (see `Multiple components with the same name <cmake-components-same-name>` for more details).
314
315In the component mock, the following parts are specified:
316
317.. list::
318    - The headers providing the functions to generate mocks for
319    - Include paths of the aforementioned headers
320    - Dependencies of the mock component (this is necessary e.g. if the headers include files from other components)
321
322All these parts have to be specified using the IDF build system function ``idf_component_mock``. You can use the IDF build system function ``idf_component_get_property`` with the tag ``COMPONENT_OVERRIDEN_DIR`` to access the component directory of the original component and then register the mock component parts using ``idf_component_mock``:
323
324.. code:: none
325
326    idf_component_get_property(original_component_dir <original-component-name> COMPONENT_OVERRIDEN_DIR)
327    ...
328    idf_component_mock(INCLUDE_DIRS "${original_component_dir}/include"
329        REQUIRES freertos
330        MOCK_HEADER_FILES ${original_component_dir}/include/header_containing_functions_to_mock.h)
331
332The component mock also requires a separate ``mock`` directory containing a ``mock_config.yaml`` file that configures CMock. A simple ``mock_config.yaml`` could look like this:
333
334  .. code-block:: yaml
335
336    :cmock:
337      :plugins:
338        - expect
339        - expect_any_args
340
341For more details about the CMock configuration yaml file, have a look at :component_file:`cmock/CMock/docs/CMock_Summary.md`.
342
343Note that the component mock does not have to mock the original component in its entirety. As long as the test project's dependencies and dependencies of other code to the original components are satisfied by the component mock, partial mocking is adequate. In fact, most of the component mocks in IDF in ``tools/mocks`` are only partially mocking the original component.
344
345Examples of component mocks can be found under :idf:`tools/mocks` in the IDF directory. General information on how to *override an IDF component* can be found under the section "Multiple components with the same name" in the :doc:`IDF build system documentation`</api-guides/build-system>`.
346
347Adjustments in Unit Test
348^^^^^^^^^^^^^^^^^^^^^^^^
349
350The unit test needs to inform the cmake build system to mock dependent components (i.e., it needs to override the original component with the mock component). This is done by either placing the component mock into the project's ``components`` directory or adding the mock component's directory using the following line in the project's root ``CMakeLists.txt``:
351
352  .. code:: cmake
353
354    list(APPEND EXTRA_COMPONENT_DIRS "<mock_component_dir>")
355
356Both methods will override existing components in ESP-IDF with the component mock. The latter is particularly convenient if you use component mocks that are already supplied by IDF.
357
358Users should refer to the ``esp_event`` host-based unit test and its :component_file:`esp_event/host_test/esp_event_unit_test/CMakeLists.txt` as an example of a component mock.
359