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 the
8same way that others working on the project are. Finally, we can get away with
9using the same Docker image that's run by the OpenBMC continuous integration
10bot, so we have even more confidence that we're running relevant tests the way
11they'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 start
18looking.
19
20Check that the installation was successful by running
21`sudo docker run hello-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 _git
38worktree_. You can read more about those by running `git help worktree`, but the
39basic idea is that it's like having a second copy of your repo - but the second
40copy 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 trade
89the cruft of mounting extra paths to the Docker container for different cruft:
90
91You can create a local upstream:
92
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```
102
103This method would require you to push your topic branch to `ci` and then
104`git checkout` the appropriate branch every time you switched topics:
105
106```shell
107cd /my/dir/for/phosphor-host-ipmid
108git commit -m "my changes to be tested"
109git push ci
110cd /path/to/openbmc-ci-tests/phosphor-host-ipmid
111git checkout topic-branch
112```
113
114You can also create a symlink from your Git workspace into `openbmc-ci-tests/`.
115This is especially not recommended, since you won't be able to work on your code
116in parallel while the tests run, and since the CI scripts are unhappy when you
117have untracked changes - which you're likely to have during active development.
118
119## Building and Running
120
121The OpenBMC CI scripts take care of the build for you, and run the test suite.
122Build and run like so:
123
124```shell
125sudo WORKSPACE=$(pwd) UNIT_TEST_PKG=phosphor-host-ipmid \
126  ./openbmc-build-scripts/run-unit-test-docker.sh
127```
128
129The first run will take a long time! But afterwards it shouldn't be so bad, as
130many parts of the Docker container are already downloaded and configured.
131
132## Reading Output
133
134Your results will appear in
135`openbmc-ci-tests/phosphor-host-ipmid/test/test-suite.log`, as well as being
136printed to `stdout`. You will also see other `.log` files generated for each
137test file, for example `sample_unittest.log`. All these `*.log` files are
138human-readable and can be examined to determine why something failed
139
140# Writing Tests
141
142Now that you've got an easy working environment for running tests, let's write
143some new ones.
144
145## Setting Up Your Environment
146
147In `/my/dir/for/phosphor-host-ipmid`, create a new branch based on `master` (or
148your current patchset). For this tutorial, let's call it `sensorhandler-tests`.
149
150```shell
151git checkout -b sensorhandler-tests master
152```
153
154This creates a new branch `sensorhandler-tests` which is based on `master`, then
155checks out that branch for you to start hacking.
156
157## Write Some Tests
158
159For this tutorial, we'll be adding some basic unit testing of the struct
160accessors for `get_sdr::GetSdrReq`, just because they're fairly simple. The text
161of the struct and accessors is recreated here:
162
163```c++
164/**
165 * Get SDR
166 */
167namespace get_sdr
168{
169
170struct GetSdrReq
171{
172    uint8_t reservation_id_lsb;
173    uint8_t reservation_id_msb;
174    uint8_t record_id_lsb;
175    uint8_t record_id_msb;
176    uint8_t offset;
177    uint8_t bytes_to_read;
178} __attribute__((packed));
179
180namespace request
181{
182
183inline uint8_t get_reservation_id(GetSdrReq* req)
184{
185    return (req->reservation_id_lsb + (req->reservation_id_msb << 8));
186};
187
188inline uint16_t get_record_id(GetSdrReq* req)
189{
190    return (req->record_id_lsb + (req->record_id_msb << 8));
191};
192
193} // namespace request
194
195...
196} // namespace get_sdr
197```
198
199We'll create the tests in `test/sensorhandler_unittest.cpp`; go ahead and start
200that file with your editor.
201
202First, include the header you want to test, as well as the GTest header:
203
204```c++
205#include <sensorhandler.hpp>
206
207#include <gtest/gtest.h>
208```
209
210Let's plan the test cases we care about before we build any additional
211scaffolding. We've only got two functions - `get_reservation_id()` and
212`get_record_id()`. We want to test:
213
214- "Happy path" - in an ideal case, everything works correctly
215- Error handling - when given bad input, things break as expected
216- Edge cases - when given extremes (e.g. very large or very small numbers),
217  things work correctly or break as expected
218
219For `get_reservation_id()`:
220
221```c++
222TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_HappyPath)
223{
224}
225
226TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_NullInputDies)
227{
228}
229
230TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_Uint16MaxWorksCorrectly)
231{
232}
233```
234
235For `get_record_id()`, we have pretty much the same set of tests:
236
237```c++
238TEST(SensorHandlerTest, GetSdrReq_get_record_id_HappyPath)
239{
240}
241
242TEST(SensorHandlerTest, GetSdrReq_get_record_id_NullInputDies)
243{
244}
245
246TEST(SensorHandlerTest, GetSdrReq_get_record_id_Uint16MaxWorksCorrectly)
247{
248}
249```
250
251In the case of these two methods, there's really not much else to test. Some
252types of edge cases - like overflow/underflow - are prevented by C++'s strong
253typing; other types - like passing the incorrect type - are impossible to
254insulate against because it's possible to cast anything to a `GetSdrReq*` if we
255want. Since these are particularly boring, they make a good example for a
256tutorial like this; in practice, tests you write will likely be for more
257complicated code! We'll talk more about this in the Best Practices section
258below.
259
260Let's implement the `get_reservation_id()` items first. The implementations for
261`get_record_id()` will be identical, so we won't cover them here.
262
263For the happy path, we want to make it very clear that the test value we're
264using is within range, so we express it in binary. We also want to be able to
265ensure that the MSB and LSB are being combined in the correct order, so we make
266sure that the MSB and LSB values are different (don't use `0x3333` as the
267expected ID here). Finally, we want it to be obvious to the reader if we have
268populated the `GetSdrReq` incorrectly, so we've labeled all the fields. Since we
269are only testing one operation, it's okay to use either `ASSERT_EQ` or
270`EXPECT_EQ`; more on that in the Best Practices section.
271
272```c++
273TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_HappyPath)
274{
275    uint16_t expected_id = 0x1234; // Expected ID spans both bytes.
276    GetSdrReq input = {0x34,  // Reservation ID LSB
277                       0x12,  // Reservation ID MSB
278                       0x00,  // Record ID LSB
279                       0x00,  // Record ID MSB
280                       0x00,  // Offset
281                       0x00}; // Bytes to Read
282
283    uint16_t actual = get_sdr::request::get_reservation_id(&input);
284    ASSERT_EQ(actual, expected_id);
285}
286```
287
288We don't expect that our `GetSdrReq` pointer will ever be null; in this case,
289the null pointer validation is done much, much earlier. So it's okay for us to
290specify that in the unlikely case we're given a null pointer, we die. We don't
291really care what the output message is.
292
293```c++
294TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_NullInputDies)
295{
296    ASSERT_DEATH(get_sdr::request::get_reservation_id(nullptr), ".*");
297}
298```
299
300Finally, while negative values are taken care of by C++'s type system, we can at
301least check that our code still works happily with `UINT16_MAX`. This test is
302similar to the happy path test, but uses an edge value instead.
303
304```c++
305TEST(SensorHandlerTest, GetSdrReq_get_reservation_id_Uint16MaxWorksCorrectly)
306{
307    uint16_t expected_id = 0xFFFF; // Expected ID spans both bytes.
308    GetSdrReq input = {0xFF,  // Reservation ID LSB
309                       0xFF,  // Reservation ID MSB
310                       0x00,  // Record ID LSB
311                       0x00,  // Record ID MSB
312                       0x00,  // Offset
313                       0x00}; // Bytes to Read
314
315    uint16_t actual = get_sdr::request::get_reservation_id(&input);
316    ASSERT_EQ(actual, expected_id);
317}
318```
319
320The `get_record_id()` tests are identical, except that they are testing the
321Record ID field. They will not be duplicated here.
322
323Finally, we'll need to add the new tests to `test/Makefile.am`. You can mimic
324other existing test setups:
325
326```make
327# Build/add sensorhandler_unittest to test suite
328sensorhandler_unittest_CPPFLAGS = \
329    -Igtest \
330    $(GTEST_CPPFLAGS) \
331    $(AM_CPPFLAGS)
332sensorhandler_unittest_CXXFLAGS = \
333    $(PTHREAD_CFLAGS) \
334    $(CODE_COVERAGE_CXXFLAGS) \
335    $(CODE_COVERAGE_CFLAGS) \
336    -DBOOST_COROUTINES_NO_DEPRECATION_WARNING
337sensorhandler_unittest_LDFLAGS = \
338    -lgtest_main \
339    -lgtest \
340    -pthread \
341    $(OESDK_TESTCASE_FLAGS) \
342    $(CODE_COVERAGE_LDFLAGS)
343sensorhandler_unittest_SOURCES = \
344    %reldir%/sensorhandler_unittest.cpp
345check_PROGRAMS += %reldir%/sensorhandler_unittest
346```
347
348## Run the New Tests
349
350Commit your test changes. Then, you'll want to checkout the
351`sensorhandler-tests` branch in your CI worktree, which will involve releasing
352it from your main worktree:
353
354```shell
355cd /my/dir/for/phosphor-host-ipmid
356git add test/sensorhandler_unittest.cpp
357git commit -s
358git checkout master   # Here you can use any branch except sensorhandler-tests
359cd /path/to/openbmc-ci-tests/phosphor-host-ipmid
360git checkout sensorhandler-tests
361```
362
363Now you can run the test suite as described earlier in the document. If you see
364a linter error when you run, you can actually apply the cleaned-up code easily:
365
366```shell
367cd ./phosphor-host-ipmid
368git diff    # Examine the proposed changes
369git add -u  # Apply the proposed changes
370git commit --amend
371```
372
373(If you will need to apply the proposed changes to multiple commits, you can do
374this with interactive rebase, which won't be described here.)
375
376## Best Practices
377
378While a good unit test can ensure your code's stability, a flaky or
379poorly-written unit test can make life harder for contributors down the road.
380Some things to remember:
381
382Include both positive (happy-path) and negative (error) testing in your
383testbench. It's not enough to know that the code works when it's supposed to; we
384also need to know that it fails gracefully when something goes wrong. Applying
385edge-case testing helps us find bugs that may take years to occur (and
386reproduce!) in the field.
387
388Keep your tests small. Avoid branching - if you feel a desire to, instead
389explore that codepath in another test. The best tests are easy to read and
390understand.
391
392When a test fails, it's useful if the test is named in such a way that you can
393tell _what it's supposed to do_ and _when_. That way you can be certain whether
394the change you made really broke it or not. A good pattern is
395`Object_NameCircumstanceResult` - for example,
396`FooFactory_OutOfBoundsNameThrowsException`. From the name, it's very clear that
397when some "name" is out of bounds, an exception should be thrown. (What "name"
398is should be clear from the context of the function in `FooFactory`.)
399
400Don't test other people's code. Make sure to limit the test assertions to the
401code under test. For example, don't test what happens if you give a bad input to
402`sdbusplus` when you're supposed to be testing `phosphor-host-ipmid`.
403
404However, don't trust other people's code, either! Try to test _how you respond_
405when a service fails unexpectedly. Rather than checking if `sdbusplus` fails on
406bad inputs, check whether you handle an `sdbusplus` failure gracefully. You can
407use GMock for this kind of testing.
408
409Think about testing when you write your business logic code. Concepts like
410dependency injection and small functions make your code more testable - you'll
411be thanking yourself later when you're writing tests.
412
413Finally, you're very likely to find bugs while writing tests, especially if it's
414for code that wasn't previously unit-tested. It's okay to check in a bugfix
415along with a test that verifies the fix worked, if you're only doing one test
416and one bugfix. If you're checking in a large suite of tests, do the bugfixes in
417independent commits which your test suite commit is based on:
418
419master -> fix Foo.Abc() -> fix Foo.Def() -> Fix Foo.Ghi() -> test Foo class
420
421## Sending for Review
422
423You can send your test update and any associated bugfixes for review to Gerrit
424as you would send any other change. For the `Tested:` field in the commit
425message, you can state that you ran the new unit tests written.
426
427# Reviewing Tests
428
429Tests are written primarily to be read. So when you review a test, you're the
430first customer of that test!
431
432## Best Practices
433
434First, all the best practices listed above for writing tests are things you
435should check for and encourage when you're reading tests.
436
437Next, you should ensure that you can tell what's going on when you read the
438test. If it's not clear to you, it's not going to be clear to someone else, and
439the test is more prone to error - ask!
440
441Finally, think about what's _not_ being tested. If there's a case you're curious
442about and it isn't covered, you should mention it to the committer.
443
444## Quickly Running At Home
445
446Now that you've got a handy setup as described earlier in this document, you can
447quickly download and run the tests yourself. Within the Gerrit change, you
448should be able to find a button that says "Download", which will give you
449commands for various types of downloads into an existing Git repo. Use
450"Checkout", for example:
451
452```shell
453cd openbmc-ci-tests/phosphor-host-ipmid
454git fetch "https://gerrit.openbmc-project.xyz/openbmc/phosphor-host-ipmid" \
455  refs/changes/43/23043/1 && git checkout FETCH_HEAD
456```
457
458This won't disturb the rest of your Git repo state, and will put your CI
459worktree into detached-HEAD mode pointing to the commit that's under review. You
460can then run your tests normally, and even make changes and push again if the
461commit was abandoned or otherwise left to rot by its author.
462
463Doing so can be handy in a number of scenarios:
464
465- Jenkins isn't responding
466- The Jenkins build is broken for a reason beyond the committer's control
467- The committer doesn't have "Ok-To-Test" permission, and you don't have
468  permission to grant it to them
469
470# Credits
471
472Thanks very much to Patrick Venture for his prior work putting together
473documentation on this topic internal to Google.
474