summaryrefslogtreecommitdiff
path: root/Documentation/tutorial/part3.md
diff options
context:
space:
mode:
Diffstat (limited to 'Documentation/tutorial/part3.md')
-rw-r--r--Documentation/tutorial/part3.md284
1 files changed, 149 insertions, 135 deletions
diff --git a/Documentation/tutorial/part3.md b/Documentation/tutorial/part3.md
index dc487182c1..83b34088c3 100644
--- a/Documentation/tutorial/part3.md
+++ b/Documentation/tutorial/part3.md
@@ -1,31 +1,32 @@
# Writing unit tests for coreboot
## Introduction
-General thoughts about unit testing coreboot can be found in
-[Unit testing coreboot](../technotes/2020-03-unit-testing-coreboot.md).
-Additionally, [code coverage](../technotes/2021-05-code-coverage.md) support
-is available for unit tests.
-
-This document aims to guide developers through the process of adding and writing
-unit tests for coreboot modules.
-
-As an example of unit under test, `src/device/i2c.c` (referred hereafter as UUT
-"Unit Under Test") will be used. This is simple module, thus it should be easy
-for the reader to focus solely on the testing logic, without the need to spend
-too much time on digging deeply into the source code details and flow of
-operations. That being said, a good understanding of what the unit under test is
-doing is crucial for writing unit tests.
+General thoughts about unit testing coreboot can be found in [Unit
+testing coreboot](../technotes/2020-03-unit-testing-coreboot.md).
+Additionally, [code coverage](../technotes/2021-05-code-coverage.md)
+support is available for unit tests.
+
+This document aims to guide developers through the process of adding and
+writing unit tests for coreboot modules.
+
+As an example of unit under test, `src/device/i2c.c` (referred hereafter
+as UUT "Unit Under Test") will be used. This is simple module, thus it
+should be easy for the reader to focus solely on the testing logic,
+without the need to spend too much time on digging deeply into the
+source code details and flow of operations. That being said, a good
+understanding of what the unit under test is doing is crucial for
+writing unit tests.
This tutorial should also be helpful for developers who want to follow
-[TDD](https://en.wikipedia.org/wiki/Test-driven_development). Even though TDD
-has a different work flow of building tests first, followed by the code that
-satisfies them, the process of writing tests and adding them to the tree is the
-same.
+[TDD](https://en.wikipedia.org/wiki/Test-driven_development). Even
+though TDD has a different work flow of building tests first, followed
+by the code that satisfies them, the process of writing tests and adding
+them to the tree is the same.
-## Analysis of unit under test
-First of all, it is necessary to precisely establish what we want to test in a
-particular module. Usually this will be an externally exposed API, which can be
-used by other modules.
+## Analysis of unit under test First of all, it is necessary to
+precisely establish what we want to test in a particular module. Usually
+this will be an externally exposed API, which can be used by other
+modules.
```eval_rst
.. admonition:: i2c-test example
@@ -34,66 +35,70 @@ used by other modules.
.. code-block:: c
- int i2c_read_field(unsigned int bus, uint8_t chip, uint8_t reg, uint8_t *data,
- uint8_t mask, uint8_t shift)
- int i2c_write_field(unsigned int bus, uint8_t chip, uint8_t reg, uint8_t data,
- uint8_t mask, uint8_t shift)
+ int i2c_read_field(unsigned int bus, uint8_t chip, uint8_t reg,
+ uint8_t *data, uint8_t mask, uint8_t shift)
+ int i2c_write_field(unsigned int bus, uint8_t chip, uint8_t reg,
+ uint8_t data, uint8_t mask, uint8_t shift)
- For sake of simplicity, let's focus on `i2c_read_field` in this document.
+ For sake of simplicity, let's focus on `i2c_read_field` in this
+ document.
```
-Once the API is defined, the next question is __what__ this API is doing (or
-what it will be doing in case of TDD). In other words, what outputs we are
-expecting from particular functions, when providing particular input parameters.
+Once the API is defined, the next question is __what__ this API is doing
+(or what it will be doing in case of TDD). In other words, what outputs
+we are expecting from particular functions, when providing particular
+input parameters.
```eval_rst
.. admonition:: i2c-test example
.. code-block:: c
- int i2c_read_field(unsigned int bus, uint8_t chip, uint8_t reg, uint8_t *data,
- uint8_t mask, uint8_t shift)
+ int i2c_read_field(unsigned int bus, uint8_t chip, uint8_t reg,
+ uint8_t *data, uint8_t mask, uint8_t shift)
- This is a method which means to read content of register `reg` from i2c device
- on i2c `bus` and slave address `chip`, applying bit `mask` and offset `shift`
- to it. Returned data should be placed in `data`.
+ This is a method which means to read content of register `reg` from
+ i2c device on i2c `bus` and slave address `chip`, applying bit `mask`
+ and offset `shift` to it. Returned data should be placed in `data`.
```
-The next step is to determine all external dependencies of UUT in order to mock
-them out. Usually we want to isolate the UUT as much as possible, so that the
-test result depends __only__ on the behavior of UUT and not on the other
-modules. While some software dependencies may be hard to be mock (for example
-due to complicated dependencies) and thus should be simply linked into the test
-binaries, all hardware dependencies need to be mocked out, since in the
-user-space host environment, targets hardware is not available.
+The next step is to determine all external dependencies of UUT in order
+to mock them out. Usually we want to isolate the UUT as much as
+possible, so that the test result depends __only__ on the behavior of
+UUT and not on the other modules. While some software dependencies may
+be hard to be mock (for example due to complicated dependencies) and
+thus should be simply linked into the test binaries, all hardware
+dependencies need to be mocked out, since in the user-space host
+environment, targets hardware is not available.
```eval_rst
.. admonition:: i2c-test example
`i2c_read_field` is calling `i2c_readb`, which eventually invokes
- `i2c_transfer`. This method simply calls `platform_i2c_transfer`. The last
- function in the chain is a hardware-touching one, and defined separately for
- different SOCs. It is responsible for issuing transactions on the i2c bus.
- For the purpose of writing unit test, we should mock this function.
+ `i2c_transfer`. This method simply calls `platform_i2c_transfer`. The
+ last function in the chain is a hardware-touching one, and defined
+ separately for different SOCs. It is responsible for issuing
+ transactions on the i2c bus. For the purpose of writing unit test,
+ we should mock this function.
```
## Adding new tests
-In order to keep the tree clean, the `tests/` directory should mimic the `src/`
-directory, so that test harness code is placed in a location corresponding to
-UUT. Furthermore, the naming convention is to add the suffix `-test` to the UUT
-name when creating a new test harness file.
+In order to keep the tree clean, the `tests/` directory should mimic the
+`src/` directory, so that test harness code is placed in a location
+corresponding to UUT. Furthermore, the naming convention is to add the
+suffix `-test` to the UUT name when creating a new test harness file.
```eval_rst
.. admonition:: i2c-test example
Considering that UUT is `src/device/i2c.c`, test file should be named
- `tests/device/i2c-test.c`. When adding a new test file, it needs to be
- registered with the coreboot unit testing infrastructure.
+ `tests/device/i2c-test.c`. When adding a new test file, it needs to
+ be registered with the coreboot unit testing infrastructure.
```
-Every directory under `tests/` should contain a Makefile.inc, similar to what
-can be seen under the `src/`. Register a new test in Makefile.inc, by
-__appending__ test name to the `tests-y` variable.
+Every directory under `tests/` should contain a Makefile.inc, similar to
+what can be seen under the `src/`. Register a new test in Makefile.inc,
+by __appending__ test name to the `tests-y` variable.
```eval_rst
.. admonition:: i2c-test example
@@ -103,10 +108,11 @@ __appending__ test name to the `tests-y` variable.
tests-y += i2c-test
```
-Next step is to list all source files, which should be linked together in order
-to create test binary. Usually a tests requires only two files - UUT and test
-harness code, but sometimes more is needed to provide the test environment.
-Source files are registered in `<test_name>-srcs` variable.
+Next step is to list all source files, which should be linked together
+in order to create test binary. Usually a tests requires only two files
+- UUT and test harness code, but sometimes more is needed to provide the
+test environment. Source files are registered in `<test_name>-srcs`
+variable.
```eval_rst
.. admonition:: i2c-test example
@@ -117,9 +123,10 @@ Source files are registered in `<test_name>-srcs` variable.
i2c-test-srcs += src/device/i2c.c
```
-Above minimal configuration is a basis for further work. One can try to build
-and run test binary either by invoking `make tests/<test_dir>/<test_name>` or by
-running all unit tests (whole suite) for coreboot `make unit-tests`.
+Above minimal configuration is a basis for further work. One can try to
+build and run test binary either by invoking `make
+tests/<test_dir>/<test_name>` or by running all unit tests (whole suite)
+for coreboot `make unit-tests`.
```eval_rst
.. admonition:: i2c-test example
@@ -135,31 +142,34 @@ running all unit tests (whole suite) for coreboot `make unit-tests`.
make unit-tests
```
-When trying to build test binary, one can often see linker complains about
-`undefined reference` to couple of symbols. This is one of solutions to
-determine all external dependencies of UUT - iteratively build test and resolve
-errors one by one. At this step, developer should decide either it's better to
-add an extra module to provide necessary definitions or rather mock such
-dependency. Quick guide through adding mocks is provided later in this doc.
+When trying to build test binary, one can often see linker complains
+about `undefined reference` to couple of symbols. This is one of
+solutions to determine all external dependencies of UUT - iteratively
+build test and resolve errors one by one. At this step, developer should
+decide either it's better to add an extra module to provide necessary
+definitions or rather mock such dependency. Quick guide through adding
+mocks is provided later in this doc.
## Writing new tests
-In coreboot, [Cmocka](https://cmocka.org/) is used as unit test framework. The
-project has exhaustive [API documentation](https://api.cmocka.org/). Let's see
-how we may incorporate it when writing tests.
+In coreboot, [Cmocka](https://cmocka.org/) is used as unit test
+framework. The project has exhaustive [API
+documentation](https://api.cmocka.org/). Let's see how we may
+incorporate it when writing tests.
### Assertions
-Testing the UUT consists of calling the functions in the UUT and comparing the
-returned values to the expected values. Cmocka implements
-[a set of assert macros](https://api.cmocka.org/group__cmocka__asserts.html) to
-compare a value with an expected value. If the two values do not match, the test
+Testing the UUT consists of calling the functions in the UUT and
+comparing the returned values to the expected values. Cmocka implements
+[a set of assert
+macros](https://api.cmocka.org/group__cmocka__asserts.html) to compare a
+value with an expected value. If the two values do not match, the test
fails with an error message.
```eval_rst
.. admonition:: i2c-test example
- In our example, the simplest test is to call UUT for reading our fake devices
- registers and do all calculation in the test harness itself. At the end, let's
- compare integers with `assert_int_equal`.
+ In our example, the simplest test is to call UUT for reading our fake
+ devices registers and do all calculation in the test harness itself.
+ At the end, let's compare integers with `assert_int_equal`.
.. code-block:: c
@@ -191,24 +201,25 @@ fails with an error message.
### Mocks
#### Overview
-Many coreboot modules are low level software that touch hardware directly.
-Because of this, one of the most important and challenging part of
-writing tests is to design and implement mocks. A mock is a software component
-which implements the API of another component so that the test can verify that
-certain functions are called (or not called), verify the parameters passed to
-those functions, and specify the return values from those functions. Mocks are
-especially useful when the API to be implemented is one that accesses hardware
-components.
-
-When writing a mock, the developer implements the same API as the module being
-mocked. Such a mock may, for example, register a set of driver methods. Behind
-this API, there is usually a simulation of real hardware.
+Many coreboot modules are low level software that touch hardware
+directly. Because of this, one of the most important and challenging
+part of writing tests is to design and implement mocks. A mock is a
+software component which implements the API of another component so that
+the test can verify that certain functions are called (or not called),
+verify the parameters passed to those functions, and specify the return
+values from those functions. Mocks are especially useful when the API to
+be implemented is one that accesses hardware components.
+
+When writing a mock, the developer implements the same API as the module
+being mocked. Such a mock may, for example, register a set of driver
+methods. Behind this API, there is usually a simulation of real
+hardware.
```eval_rst
.. admonition:: i2c-test example
- For purpose of our i2c test, we may introduce two i2c devices with set of
- registers, which simply are structs in memory.
+ For purpose of our i2c test, we may introduce two i2c devices with
+ set of registers, which simply are structs in memory.
.. code-block:: c
@@ -266,16 +277,17 @@ this API, there is usually a simulation of real hardware.
};
```
-Cmocka uses a feature that gcc provides for breaking dependencies at the link
-time. It is possible to override implementation of some function, with the
-method from test harness. This allows test harness to take control of execution
-from binary (during the execution of test), and stimulate UUT as required
-without changing the source code.
+Cmocka uses a feature that gcc provides for breaking dependencies at the
+link time. It is possible to override implementation of some function,
+with the method from test harness. This allows test harness to take
+control of execution from binary (during the execution of test), and
+stimulate UUT as required without changing the source code.
-coreboot unit test infrastructure supports overriding of functions at link time.
-This is as simple as adding a `name_of_function` to be mocked into
-<test_name>-mocks variable in Makefile.inc. The result is that the test's
-implementation of that function is called instead of coreboot's.
+coreboot unit test infrastructure supports overriding of functions at
+link time. This is as simple as adding a `name_of_function` to be
+mocked into <test_name>-mocks variable in Makefile.inc. The result is
+that the test's implementation of that function is called instead of
+coreboot's.
```eval_rst
.. admonition:: i2c-test example
@@ -284,44 +296,45 @@ implementation of that function is called instead of coreboot's.
i2c-test-mocks += platform_i2c_transfer
- Now, dev can write own implementation of `platform_i2c_transfer`. This
- implementation instead of accessing real i2c bus, will write/read from
- fake structs.
+ Now, dev can write own implementation of `platform_i2c_transfer`.
+ This implementation instead of accessing real i2c bus, will
+ write/read from fake structs.
.. code-block:: c
- int platform_i2c_transfer(unsigned int bus, struct i2c_msg *segments,
- int count)
+ int platform_i2c_transfer(unsigned int bus, struct i2c_msg
+ *segments, int count)
{
}
```
#### Checking mock's arguments
-A test can verify the parameters provided by the UUT to the mock function. The
-developer may also verify that number of calls to mock is correct and the order
-of calls to particular mocks is as expected (See
-[this](https://api.cmocka.org/group__cmocka__call__order.html)). The Cmocka
-macros for checking parameters are described
-[here](https://api.cmocka.org/group__cmocka__param.html). In general, in mock
-function, one makes a call to `check_expected(<param_name>)` and in the
-corresponding test function, `expect*()` macro, with description which parameter
-in which mock should have particular value, or be inside a described range.
+A test can verify the parameters provided by the UUT to the mock
+function. The developer may also verify that number of calls to mock is
+correct and the order of calls to particular mocks is as expected (See
+[this](https://api.cmocka.org/group__cmocka__call__order.html)). The
+Cmocka macros for checking parameters are described
+[here](https://api.cmocka.org/group__cmocka__param.html). In general, in
+mock function, one makes a call to `check_expected(<param_name>)` and in
+the corresponding test function, `expect*()` macro, with description
+which parameter in which mock should have particular value, or be inside
+a described range.
```eval_rst
.. admonition:: i2c-test example
- In our example, we may want to check that `platform_i2c_transfer` is fed with
- number of segments bigger than 0, each segment has flags which are in
- supported range and each segment has buf which is non-NULL. We are expecting
- such values for _every_ call, thus the last parameter in `expect*` macros is
- -1.
+ In our example, we may want to check that `platform_i2c_transfer` is
+ fed with number of segments bigger than 0, each segment has flags
+ which are in supported range and each segment has buf which is
+ non-NULL. We are expecting such values for _every_ call, thus the
+ last parameter in `expect*` macros is -1.
.. code-block:: c
static void mock_expect_params_platform_i2c_transfer(void)
{
- unsigned long int expected_flags[] = {0, I2C_M_RD, I2C_M_TEN,
- I2C_M_RECV_LEN, I2C_M_NOSTART};
+ unsigned long int expected_flags[] = {0, I2C_M_RD,
+ I2C_M_TEN, I2C_M_RECV_LEN, I2C_M_NOSTART};
/* Flags should always be only within supported range */
expect_in_set_count(platform_i2c_transfer, segments->flags,
@@ -330,8 +343,8 @@ in which mock should have particular value, or be inside a described range.
expect_not_value_count(platform_i2c_transfer, segments->buf,
NULL, -1);
- expect_in_range_count(platform_i2c_transfer, count, 1, INT_MAX,
- -1);
+ expect_in_range_count(platform_i2c_transfer, count, 1,
+ INT_MAX, -1);
}
And the checks below should be added to our mock
@@ -347,11 +360,11 @@ in which mock should have particular value, or be inside a described range.
```
#### Instrument mocks
-It is possible for the test function to instrument what the mock will return to
-the UUT. This can be done by using the `will_return*()` and `mock()` macros.
-These are described in
-[the Mock Object section](https://api.cmocka.org/group__cmocka__mock.html) of
-the Cmocka API documentation.
+It is possible for the test function to instrument what the mock will
+return to the UUT. This can be done by using the `will_return*()` and
+`mock()` macros. These are described in [the Mock Object
+section](https://api.cmocka.org/group__cmocka__mock.html) of the Cmocka
+API documentation.
```eval_rst
.. admonition:: Example
@@ -361,17 +374,18 @@ the Cmocka API documentation.
```
### Test runner
-Finally, the developer needs to implement the test `main()` function. All tests
-should be registered there and cmocka test runner invoked. All methods for
-invoking Cmocka test are described
+Finally, the developer needs to implement the test `main()` function.
+All tests should be registered there and cmocka test runner invoked. All
+methods for invoking Cmocka test are described
[here](https://api.cmocka.org/group__cmocka__exec.html).
```eval_rst
.. admonition:: i2c-test example
- We don't need any extra setup and teardown functions for i2c-test, so let's
- simply register test for `i2c_read_field` and return from main value which is
- output of Cmocka's runner (it returns number of tests that failed).
+ We don't need any extra setup and teardown functions for i2c-test, so
+ let's simply register test for `i2c_read_field` and return from main
+ value which is output of Cmocka's runner (it returns number of tests
+ that failed).
.. code-block:: c