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