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