xref: /aosp_15_r20/external/coreboot/Documentation/tutorial/part3.md (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1# Writing unit tests for coreboot
2
3## Introduction
4General thoughts about unit testing coreboot can be found in
5[Unit-testing coreboot](../technotes/2020-03-unit-testing-coreboot.md).
6Additionally, [code coverage](../technotes/2021-05-code-coverage.md)
7support is available for unit tests.
8
9This document aims to guide developers through the process of adding and
10writing unit tests for coreboot modules.
11
12As an example of unit-under-test, `src/device/i2c.c` (referred hereafter
13as UUT "Unit Under Test") will be used. This is simple module, thus it
14should be easy for the reader to focus solely on the testing logic,
15without the need to spend too much time on digging deeply into the
16source code details and flow of operations. That being said, a good
17understanding of what the unit-under-test is doing is crucial for
18writing unit tests.
19
20This tutorial should also be helpful for developers who want to follow
21[TDD](https://en.wikipedia.org/wiki/Test-driven_development). Even
22though TDD has a different work flow of building tests first, followed
23by the code that satisfies them, the process of writing tests and adding
24them to the tree is the same.
25
26## Analysis of unit-under-test
27First of all, it is necessary to precisely establish what we want to
28test in a particular module. Usually this will be an externally exposed
29API, which can be used by other modules.
30
31```{eval-rst}
32.. admonition:: i2c-test example
33
34   In case of our UUT, API consist of two methods:
35
36   .. code-block:: c
37
38     int i2c_read_field(unsigned int bus, uint8_t chip, uint8_t reg,
39                 uint8_t *data, uint8_t mask, uint8_t shift)
40     int i2c_write_field(unsigned int bus, uint8_t chip, uint8_t reg,
41                 uint8_t data, uint8_t mask, uint8_t shift)
42
43   For sake of simplicity, let's focus on `i2c_read_field` in this
44   document.
45```
46
47Once the API is defined, the next question is __what__ this API is doing
48(or what it will be doing in case of TDD). In other words, what outputs
49we are expecting from particular functions, when providing particular
50input parameters.
51
52```{eval-rst}
53.. admonition:: i2c-test example
54
55   .. code-block:: c
56
57     int i2c_read_field(unsigned int bus, uint8_t chip, uint8_t reg,
58                 uint8_t *data, uint8_t mask, uint8_t shift)
59
60   This is a method which means to read content of register `reg` from
61   i2c device on i2c `bus` and slave address `chip`, applying bit `mask`
62   and offset `shift` to it. Returned data should be placed in `data`.
63```
64
65The next step is to determine all external dependencies of UUT in order
66to mock them out. Usually we want to isolate the UUT as much as
67possible, so that the test result depends __only__ on the behavior of
68UUT and not on the other modules. While some software dependencies may
69be hard to be mock (for example due to complicated dependencies) and
70thus should be simply linked into the test binaries, all hardware
71dependencies need to be mocked out, since in the user-space host
72environment, target hardware is not available.
73
74```{eval-rst}
75.. admonition:: i2c-test example
76
77   `i2c_read_field` is calling `i2c_readb`, which eventually invokes
78   `i2c_transfer`. This method simply calls `platform_i2c_transfer`. The
79   last function in the chain is a hardware-touching one, and defined
80   separately for different SOCs. It is responsible for issuing
81   transactions on the i2c bus.  For the purpose of writing unit test,
82   we should mock this function.
83```
84
85## Adding new tests
86In order to keep the tree clean, the `tests/` directory should mimic the
87`src/` directory, so that test harness code is placed in a location
88corresponding to UUT. Furthermore, the naming convention is to add the
89suffix `-test` to the UUT name when creating a new test harness file.
90
91```{eval-rst}
92.. admonition:: i2c-test example
93
94   Considering that UUT is `src/device/i2c.c`, test file should be named
95   `tests/device/i2c-test.c`. When adding a new test file, it needs to
96   be registered with the coreboot unit testing infrastructure.
97```
98
99Every directory under `tests/` should contain a Makefile.mk, similar to
100what can be seen under the `src/`. Register a new test in Makefile.mk,
101by __appending__ test name to the `tests-y` variable.
102
103```{eval-rst}
104.. admonition:: i2c-test example
105
106   .. code-block:: c
107
108     tests-y += i2c-test
109```
110
111Next step is to list all source files, which should be linked together
112in order to create test binary. Usually a tests requires only two files
113- UUT and test harness code, but sometimes more is needed to provide the
114test environment.  Source files are registered in `<test_name>-srcs`
115variable.
116
117```{eval-rst}
118.. admonition:: i2c-test example
119
120   .. code-block:: c
121
122     i2c-test-srcs += tests/device/i2c-test.c
123     i2c-test-srcs += src/device/i2c.c
124```
125
126Above minimal configuration is a basis for further work. One can try to
127build and run test binary either by invoking `make
128tests/<test_dir>/<test_name>` or by running all unit tests (whole suite)
129for coreboot `make unit-tests`.
130
131```{eval-rst}
132.. admonition:: i2c-test example
133
134   .. code-block:: c
135
136     make tests/device/i2c-test
137
138   or
139
140   .. code-block:: c
141
142     make unit-tests
143```
144
145When trying to build test binary, one can often see the linker complaining
146about `undefined reference` for a couple of symbols. This is one of the
147solutions to determine all external dependencies of UUT - iteratively
148build test and resolve errors one by one. At this step, developer should
149decide either it's better to add an extra module to provide necessary
150definitions or rather mock such dependency. A quick guide about adding
151mocks is provided later in this doc.
152
153## Writing new tests
154In coreboot, [Cmocka](https://cmocka.org/) is used as unit test
155framework. The project has exhaustive [API
156documentation](https://api.cmocka.org/). Let's see how we may
157incorporate it when writing tests.
158
159### Assertions
160Testing the UUT consists of calling the functions in the UUT and
161comparing the returned values to the expected values. Cmocka implements
162[a set of assert
163macros](https://api.cmocka.org/group__cmocka__asserts.html) to compare a
164value with an expected value. If the two values do not match, the test
165fails with an error message.
166
167```{eval-rst}
168.. admonition:: i2c-test example
169
170   In our example, the simplest test is to call UUT for reading our fake
171   devices registers and do all calculation in the test harness itself.
172   At the end, let's compare integers with `assert_int_equal`.
173
174   .. code-block:: c
175
176     #define MASK        0x3
177     #define SHIFT        0x1
178
179     static void i2c_read_field_test(void **state)
180     {
181             int bus, slave, reg;
182             int i, j;
183             uint8_t buf;
184
185             mock_expect_params_platform_i2c_transfer();
186
187             /* Read particular bits in all registers in all devices, then compare
188                with expected value. */
189             for (i = 0; i < ARRAY_SIZE(i2c_ex_devs); i++)
190                     for (j = 0; j < ARRAY_SIZE(i2c_ex_devs[0].regs); j++) {
191                             i2c_read_field(i2c_ex_devs[i].bus,
192                                     i2c_ex_devs[i].slave,
193                                     i2c_ex_devs[i].regs[j].reg,
194                                     &buf, MASK, SHIFT);
195                             assert_int_equal((i2c_ex_devs[i].regs[j].data &
196                                     (MASK << SHIFT)) >> SHIFT, buf);
197                     };
198     }
199```
200
201### Mocks
202
203#### Overview
204Many coreboot modules are low level software that touch hardware
205directly.  Because of this, one of the most important and challenging
206part of writing tests is to design and implement mocks. A mock is a
207software component which implements the API of another component so that
208the test can verify that certain functions are called (or not called),
209verify the parameters passed to those functions, and specify the return
210values from those functions. Mocks are especially useful when the API to
211be implemented is one that accesses hardware components.
212
213When writing a mock, the developer implements the same API as the module
214being mocked. Such a mock may, for example, register a set of driver
215methods. Behind this API, there is usually a simulation of real
216hardware.
217
218```{eval-rst}
219.. admonition:: i2c-test example
220
221   For purpose of our i2c test, we may introduce two i2c devices with
222   set of registers, which simply are structs in memory.
223
224   .. code-block:: c
225
226     /* Simulate two i2c devices, both on bus 0, each with three uint8_t regs
227        implemented. */
228     typedef struct {
229             uint8_t reg;
230             uint8_t data;
231     } i2c_ex_regs_t;
232
233     typedef struct {
234             unsigned int bus;
235             uint8_t slave;
236             i2c_ex_regs_t regs[3];
237     } i2c_ex_devs_t;
238
239     i2c_ex_devs_t i2c_ex_devs[] = {
240             {.bus = 0, .slave = 0xA, .regs = {
241                     {.reg = 0x0, .data = 0xB},
242                     {.reg = 0x1, .data = 0x6},
243                     {.reg = 0x2, .data = 0xF},
244             } },
245             {.bus = 0, .slave = 0x3, .regs = {
246                     {.reg = 0x0, .data = 0xDE},
247                     {.reg = 0x1, .data = 0xAD},
248                     {.reg = 0x2, .data = 0xBE},
249             } },
250     };
251
252   These fake devices will be accessed instead of hardware ones:
253
254   .. code-block:: c
255
256             reg = tmp->buf[0];
257
258             /* Find object for requested device */
259             for (i = 0; i < ARRAY_SIZE(i2c_ex_devs); i++, i2c_dev++)
260                     if (i2c_ex_devs[i].slave == tmp->slave) {
261                             i2c_dev = &i2c_ex_devs[i];
262                             break;
263                     }
264
265             if (i2c_dev == NULL)
266                     return -1;
267
268             /* Write commands */
269             if (tmp->len > 1) {
270                     i2c_dev->regs[reg].data = tmp->buf[1];
271             };
272
273             /* Read commands */
274             for (i = 0; i < count; i++, tmp++)
275                     if (tmp->flags & I2C_M_RD) {
276                             *(tmp->buf) = i2c_dev->regs[reg].data;
277                     };
278```
279
280Cmocka uses a feature that gcc provides for breaking dependencies at the
281link time. It is possible to override implementation of some function,
282with the method from test harness. This allows test harness to take
283control of execution from binary (during the execution of test), and
284stimulate UUT as required without changing the source code.
285
286coreboot unit test infrastructure supports overriding of functions at
287link time.  This is as simple as adding a `name_of_function` to be
288mocked into <test_name>-mocks variable in Makefile.mk. The result is
289that the test's implementation of that function is called instead of
290coreboot's.
291
292```{eval-rst}
293.. admonition:: i2c-test example
294
295   .. code-block:: c
296
297     i2c-test-mocks += platform_i2c_transfer
298
299   Now, dev can write own implementation of `platform_i2c_transfer`.
300   This implementation instead of accessing real i2c bus, will
301   write/read from fake structs.
302
303   .. code-block:: c
304
305     int platform_i2c_transfer(unsigned int bus, struct i2c_msg
306                 *segments, int count)
307     {
308     }
309```
310
311#### Checking mock's arguments
312A test can verify the parameters provided by the UUT to the mock
313function. The developer may also verify that number of calls to mock is
314correct and the order of calls to particular mocks is as expected (See
315[this](https://api.cmocka.org/group__cmocka__call__order.html)). The
316Cmocka macros for checking parameters are described
317[here](https://api.cmocka.org/group__cmocka__param.html). In general, in
318mock function, one makes a call to `check_expected(<param_name>)` and in
319the corresponding test function, `expect*()` macro, with description
320which parameter in which mock should have particular value, or be inside
321a described range.
322
323```{eval-rst}
324.. admonition:: i2c-test example
325
326   In our example, we may want to check that `platform_i2c_transfer` is
327   fed with a number of segments bigger than 0, each segment has flags
328   which are in the supported range and each segment has a buf which is
329   non-NULL. We are expecting such values for _every_ call, thus the
330   last parameter in `expect*` macros is -1.
331
332   .. code-block:: c
333
334     static void mock_expect_params_platform_i2c_transfer(void)
335     {
336             unsigned long int expected_flags[] = {0, I2C_M_RD,
337                     I2C_M_TEN, I2C_M_RECV_LEN, I2C_M_NOSTART};
338
339             /* Flags should always be only within supported range */
340             expect_in_set_count(platform_i2c_transfer, segments->flags,
341                     expected_flags, -1);
342
343             expect_not_value_count(platform_i2c_transfer, segments->buf,
344                     NULL, -1);
345
346             expect_in_range_count(platform_i2c_transfer, count, 1,
347                     INT_MAX, -1);
348     }
349
350   And the checks below should be added to our mock
351
352   .. code-block:: c
353
354             check_expected(count);
355
356             for (i = 0; i < count; i++, segments++) {
357                     check_expected_ptr(segments->buf);
358                     check_expected(segments->flags);
359             }
360```
361
362#### Instrument mocks
363It is possible for the test function to instrument what the mock will
364return to the UUT. This can be done by using the `will_return*()` and
365`mock()` macros.  These are described in [the Mock Object
366section](https://api.cmocka.org/group__cmocka__mock.html) of the Cmocka
367API documentation.
368
369```{eval-rst}
370.. admonition:: Example
371
372   There is an non-coreboot example for using Cmocka available
373   `here <https://lwn.net/Articles/558106/>`_.
374```
375
376### Test runner
377Finally, the developer needs to implement the test `main()` function.
378All tests should be registered there and the cmocka test runner invoked.
379All methods for invoking Cmocka test are described
380[here](https://api.cmocka.org/group__cmocka__exec.html).
381
382```{eval-rst}
383.. admonition:: i2c-test example
384
385   We don't need any extra setup and teardown functions for i2c-test, so
386   let's simply register the test for `i2c_read_field` and return from
387   main the output of Cmocka's runner (it returns number of tests
388   that failed).
389
390   .. code-block:: c
391
392     int main(void)
393     {
394             const struct CMUnitTest tests[] = {
395                     cmocka_unit_test(i2c_read_field_test),
396             };
397
398             return cb_run_group_tests(tests, NULL, NULL);
399     }
400```
401