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