1# Running Tests
2
3## Setting Up Your Environment
4
5For the purposes of this tutorial, we'll be setting up an environment in Docker.
6Docker is handy because it's fairly portable, and won't interfere with the rest
7of your machine setup. It also offers a strong guarantee that you're testing
8the same way that others working on the project are. Finally, we can get away
9with using the same Docker image that's run by the OpenBMC continuous
10integration bot, so we have even more confidence that we're running relevant
11tests the way they'd be run upstream.
12
13### Install Docker
14
15Installation of Docker CE (Community Edition) varies by platform, and may differ
16in your organization. But the
17[Docker Docs](https://docs.docker.com/v17.12/install) are a good place to
18start looking.
19
20Check that the installation was successful by running `sudo docker run
21hello-world`.
22
23### Download OpenBMC Continuous Integration Image
24
25You'll want a couple of different repositories, so start by making a place for
26it all to go, and clone the CI scripts:
27
28```shell
29mkdir openbmc-ci-tests
30cd openbmc-ci-tests
31git clone https://github.com/openbmc/openbmc-build-scripts.git
32```
33
34### Add `phosphor-host-ipmid`
35
36We also need to put a copy of the project you want to test against here. But
37you've probably got a copy checked out already, so we're going to make a
38*git worktree*. You can read more about those by running `git help worktree`,
39but the basic idea is that it's like having a second copy of your repo - but the
40second copy is in sync with your main copy, knows about your local branches, and
41protects you from checking out the same branch in two places.
42
43Your new worktree doesn't know about any untracked files you have in your main
44worktree, so you should get in the habit of committing everything you want to
45run tests against. However, because of the implementation of
46`run-unit-test-docker.sh`, you can't run the CI with untracked changes anyways,
47so this isn't the worst thing in the world. (If you make untracked changes in
48your testing worktree, it's easy to update a commit with those.)
49
50Note the placeholders in the following steps; modify the commands to match your
51directory layout.
52
53```shell
54cd /my/dir/for/phosphor-host-ipmid
55git worktree add /path/to/openbmc-ci-tests/phosphor-host-ipmid
56```
57
58Now, if you `cd /path/to/openbmc-ci-tests`, you should see a directory
59`phosphor-host-ipmid/`, and if you enter it and run `git status` you will see
60that you're likely on a new branch named `phosphor-host-ipmid`. This is just for
61convenience, since you can't check out a branch in your worktree that's already
62checked out somewhere else; you can safely ignore or delete that branch later.
63
64However, Git won't be able to figure out how to get to your main worktree
65(`/my/dir/for/phosphor-host-ipmid`), so we'll need to mount it when we run. Open
66up `/path/to/openbmc-ci-tests/openbmc-build-scripts/run-unit-test-docker.sh` and
67find where we call `docker run`, way down at the bottom. Add an additional
68argument, remembering to escape the newline ('\'):
69
70```shell
71PHOSPHOR_IPMI_HOST_PATH="/my/dir/for/phosphor-host-ipmid"
72
73docker run --blah-blah-existing-flags \
74  -v ${PHOSPHOR_IPMI_HOST_PATH}:${PHOSPHOR_IPMI_HOST_PATH} \
75  -other \
76  -args
77```
78
79Then commit this, so you can make sure not to lose it if you update the scripts
80repo:
81
82```shell
83cd openbmc-build-scripts
84git add run-unit-test-docker.sh
85git commit -m "mount phosphor-host-ipmid"
86```
87
88NOTE: There are other ways to do this besides a worktree; other approaches
89trade the cruft of mounting extra paths to the Docker container for different
90cruft:
91
92You can create a local upstream:
93```shell
94cd openbmc-ci-tests
95mkdir phosphor-host-ipmid
96cd phosphor-host-ipmid
97git init
98cd /my/dir/for/phosphor-host-ipmid
99git remote add /path/to/openbmc-ci-tests/phosphor-host-ipmid ci
100git push ci
101```
102This method would require you to push your topic branch to `ci` and then `git
103checkout` the appropriate branch every time you switched topics:
104```shell
105cd /my/dir/for/phosphor-host-ipmid
106git commit -m "my changes to be tested"
107git push ci
108cd /path/to/openbmc-ci-tests/phosphor-host-ipmid
109git checkout topic-branch
110```
111
112You can also create a symlink from your Git workspace into `openbmc-ci-tests/`.
113This is especially not recommended, since you won't be able to work on your code
114in parallel while the tests run, and since the CI scripts are unhappy when you
115have untracked changes - which you're likely to have during active development.
116
117## Building and Running
118
119The OpenBMC CI scripts take care of the build for you, and run the test suite.
120Build and run like so:
121
122```shell
123sudo WORKSPACE=$(pwd) UNIT_TEST_PKG=phosphor-host-ipmid \
124  ./openbmc-build-scripts/run-unit-test-docker.sh
125```
126
127The first run will take a long time! But afterwards it shouldn't be so bad, as
128many parts of the Docker container are already downloaded and configured.
129
130## Reading Output
131
132Your results will appear in
133`openbmc-ci-tests/phosphor-host-ipmid/test/test-suite.log`, as well as being
134printed to `stdout`. You will also see other `.log` files generated for each
135test file, for example `sample_unittest.log`. All these `*.log` files are
136human-readable and can be examined to determine why something failed
137
138# Writing Tests
139
140Now that you've got an easy working environment for running tests, let's write
141some new ones.
142
143## Setting Up Your Environment
144
145In `/my/dir/for/phosphor-host-ipmid`, create a new branch based on `master` (or
146your current patchset). For this tutorial, let's call it `sensorhandler-tests`.
147
148```shell
149git checkout -b sensorhandler-tests master
150```
151
152This creates a new branch `sensorhandler-tests` which is based on `master`, then
153checks out that branch for you to start hacking.
154
155## Write Some Tests
156
157For this tutorial, we'll be adding some basic unit testing of the struct
158accessors for `get_sdr::GetSdrReq`, just because they're fairly simple. The text
159of the struct and accessors is recreated here:
160
161```c++
162/**
163 * Get SDR
164 */
165namespace get_sdr
166{
167
168struct GetSdrReq
169{
170    uint8_t reservation_id_lsb;
171    uint8_t reservation_id_msb;
172    uint8_t record_id_lsb;
173    uint8_t record_id_msb;
174    uint8_t offset;
175    uint8_t bytes_to_read;
176} __attribute__((packed));
177
178namespace request
179{
180
181inline uint8_t get_reservation_id(GetSdrReq* req)
182{
183    return (req->reservation_id_lsb + (req->reservation_id_msb << 8));
184};
185
186inline uint16_t get_record_id(GetSdrReq* req)
187{
188    return (req->record_id_lsb + (req->record_id_msb << 8));
189};
190
191} // namespace request
192
193...
194} // namespace get_sdr
195```
196
197We'll create the tests in `test/sensorhandler_unittest.cpp`; go ahead and start
198that file with your editor.
199
200First, include the header you want to test, as well as the GTest header:
201
202```c++
203#include <sensorhandler.hpp>
204
205#include <gtest/gtest.h>
206```
207
208Let's plan the test cases we care about before we build any additional
209scaffolding. We've only got two functions - `get_reservation_id()` and
210`get_record_id()`. We want to test:
211
212- "Happy path" - in an ideal case, everything works correctly
213- Error handling - when given bad input, things break as expected
214- Edge cases - when given extremes (e.g. very large or very small numbers),
215  things work correctly or break as expected
216
217For `get_reservation_id()`:
218
219```c++
220TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_HappyPath)
221{
222}
223
224TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_NullInputDies)
225{
226}
227
228TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_Uint16MaxWorksCorrectly)
229{
230}
231```
232
233For `get_record_id()`, we have pretty much the same set of tests:
234
235```c++
236TEST(SensorHandlerTest, GetSdrReq_get_record_id_HappyPath)
237{
238}
239
240TEST(SensorHandlerTest, GetSdrReq_get_record_id_NullInputDies)
241{
242}
243
244TEST(SensorHandlerTest, GetSdrReq_get_record_id_Uint16MaxWorksCorrectly)
245{
246}
247```
248
249In the case of these two methods, there's really not much else to test. Some
250types of edge cases - like overflow/underflow - are prevented by C++'s strong
251typing; other types - like passing the incorrect type - are impossible to
252insulate against because it's possible to cast anything to a `GetSdrReq*` if we
253want. Since these are particularly boring, they make a good example for a
254tutorial like this; in practice, tests you write will likely be for more
255complicated code! We'll talk more about this in the Best Practices section
256below.
257
258Let's implement the `get_reservation_id()` items first. The implementations for
259`get_record_id()` will be identical, so we won't cover them here.
260
261For the happy path, we want to make it very clear that the test value we're
262using is within range, so we express it in binary. We also want to be able to
263ensure that the MSB and LSB are being combined in the correct order, so we make
264sure that the MSB and LSB values are different (don't use `0x3333` as the
265expected ID here). Finally, we want it to be obvious to the reader if we have
266populated the `GetSdrReq` incorrectly, so we've labeled all the fields. Since we
267are only testing one operation, it's okay to use either `ASSERT_EQ` or
268`EXPECT_EQ`; more on that in the Best Practices section.
269
270```c++
271TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_HappyPath)
272{
273    uint16_t expected_id = 0x1234; // Expected ID spans both bytes.
274    GetSdrReq input = {0x34,  // Reservation ID LSB
275                       0x12,  // Reservation ID MSB
276                       0x00,  // Record ID LSB
277                       0x00,  // Record ID MSB
278                       0x00,  // Offset
279                       0x00}; // Bytes to Read
280
281    uint16_t actual = get_sdr::request::get_reservation_id(&input);
282    ASSERT_EQ(actual, expected_id);
283}
284```
285
286We don't expect that our `GetSdrReq` pointer will ever be null; in this case,
287the null pointer validation is done much, much earlier. So it's okay for us to
288specify that in the unlikely case we're given a null pointer, we die. We don't
289really care what the output message is.
290
291```c++
292TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_NullInputDies)
293{
294    ASSERT_DEATH(get_sdr::request::get_reservation_id(nullptr), ".*");
295}
296```
297
298Finally, while negative values are taken care of by C++'s type system, we can at
299least check that our code still works happily with `UINT16_MAX`. This test is
300similar to the happy path test, but uses an edge value instead.
301
302```c++
303TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_Uint16MaxWorksCorrectly)
304{
305    uint16_t expected_id = 0xFFFF; // Expected ID spans both bytes.
306    GetSdrReq input = {0xFF,  // Reservation ID LSB
307                       0xFF,  // Reservation ID MSB
308                       0x00,  // Record ID LSB
309                       0x00,  // Record ID MSB
310                       0x00,  // Offset
311                       0x00}; // Bytes to Read
312
313    uint16_t actual = get_sdr::request::get_reservation_id(&input);
314    ASSERT_EQ(actual, expected_id);
315}
316```
317
318The `get_record_id()` tests are identical, except that they are testing the
319Record ID field. They will not be duplicated here.
320
321Finally, we'll need to add the new tests to `test/Makefile.am`. You can mimic other
322existing test setups:
323
324```make
325# Build/add sensorhandler_unittest to test suite
326sensorhandler_unittest_CPPFLAGS = \
327    -Igtest \
328    $(GTEST_CPPFLAGS) \
329    $(AM_CPPFLAGS)
330sensorhandler_unittest_CXXFLAGS = \
331    $(PTHREAD_CFLAGS) \
332    $(CODE_COVERAGE_CXXFLAGS) \
333    $(CODE_COVERAGE_CFLAGS) \
334    -DBOOST_COROUTINES_NO_DEPRECATION_WARNING
335sensorhandler_unittest_LDFLAGS = \
336    -lgtest_main \
337    -lgtest \
338    -pthread \
339    $(OESDK_TESTCASE_FLAGS) \
340    $(CODE_COVERAGE_LDFLAGS)
341sensorhandler_unittest_SOURCES = \
342    %reldir%/sensorhandler_unittest.cpp
343check_PROGRAMS += %reldir%/sensorhandler_unittest
344```
345
346## Run the New Tests
347
348Commit your test changes. Then, you'll want to checkout the
349`sensorhandler-tests` branch in your CI worktree, which will involve releasing
350it from your main worktree:
351
352```shell
353cd /my/dir/for/phosphor-host-ipmid
354git add test/sensorhandler_unittest.cpp
355git commit -s
356git checkout master   # Here you can use any branch except sensorhandler-tests
357cd /path/to/openbmc-ci-tests/phosphor-host-ipmid
358git checkout sensorhandler-tests
359```
360
361Now you can run the test suite as described earlier in the document. If you see
362a linter error when you run, you can actually apply the cleaned-up code easily:
363
364```shell
365cd ./phosphor-host-ipmid
366git diff    # Examine the proposed changes
367git add -u  # Apply the proposed changes
368git commit --amend
369```
370
371(If you will need to apply the proposed changes to multiple commits, you can do
372this with interactive rebase, which won't be described here.)
373
374## Best Practices
375
376While a good unit test can ensure your code's stability, a flaky or
377poorly-written unit test can make life harder for contributors down the road.
378Some things to remember:
379
380Include both positive (happy-path) and negative (error) testing in your
381testbench. It's not enough to know that the code works when it's supposed to; we
382also need to know that it fails gracefully when something goes wrong. Applying
383edge-case testing helps us find bugs that may take years to occur (and
384reproduce!) in the field.
385
386Keep your tests small. Avoid branching - if you feel a desire to, instead
387explore that codepath in another test. The best tests are easy to read and
388understand.
389
390When a test fails, it's useful if the test is named in such a way that you can
391tell _what it's supposed to do_ and _when_. That way you can be certain whether
392the change you made really broke it or not. A good pattern is
393`Object_NameCircumstanceResult` - for example,
394`FooFactory_OutOfBoundsNameThrowsException`. From the name, it's very clear that
395when some "name" is out of bounds, an exception should be thrown. (What "name"
396is should be clear from the context of the function in `FooFactory`.)
397
398Don't test other people's code. Make sure to limit the test assertions to the
399code under test. For example, don't test what happens if you give a bad input to
400`sdbusplus` when you're supposed to be testing `phosphor-host-ipmid`.
401
402However, don't trust other people's code, either! Try to test _how you respond_
403when a service fails unexpectedly. Rather than checking if `sdbusplus` fails on
404bad inputs, check whether you handle an `sdbusplus` failure gracefully. You can
405use GMock for this kind of testing.
406
407Think about testing when you write your business logic code. Concepts like
408dependency injection and small functions make your code more testable - you'll
409be thanking yourself later when you're writing tests.
410
411Finally, you're very likely to find bugs while writing tests, especially if it's
412for code that wasn't previously unit-tested. It's okay to check in a bugfix
413along with a test that verifies the fix worked, if you're only doing one test
414and one bugfix. If you're checking in a large suite of tests, do the bugfixes in
415independent commits which your test suite commit is based on:
416
417master -> fix Foo.Abc() -> fix Foo.Def() -> Fix Foo.Ghi() -> test Foo class
418
419## Sending for Review
420
421You can send your test update and any associated bugfixes for review to Gerrit
422as you would send any other change. For the `Tested:` field in the commit
423message, you can state that you ran the new unit tests written.
424
425# Reviewing Tests
426
427## Best Practices
428
429## Quickly Running At Home
430
431# Credits
432
433Thanks very much to Patrick Venture for his prior work putting together
434documentation on this topic internal to Google.
435