1*d9497217SMartin Matuska# Unit tests 2*d9497217SMartin Matuska 3*d9497217SMartin Matuska> [!NOTE] 4*d9497217SMartin Matuska> 5*d9497217SMartin Matuska> This document is a draft. It will be updated as we gain experience writing 6*d9497217SMartin Matuska> and running unit tests. 7*d9497217SMartin Matuska 8*d9497217SMartin MatuskaThis directory contains a unit testing framework for OpenZFS, and a collection 9*d9497217SMartin Matuskaof unit tests. 10*d9497217SMartin Matuska 11*d9497217SMartin Matuska## Building and running 12*d9497217SMartin Matuska 13*d9497217SMartin MatuskaThe unit tests are built by default as part of the regular userspace build, so 14*d9497217SMartin Matuskayou probably don’t have to do anything else. 15*d9497217SMartin Matuska 16*d9497217SMartin MatuskaThe easiest way to run the tests is to run `make unit`, which will run all the 17*d9497217SMartin Matuskaavailable tests. 18*d9497217SMartin Matuska 19*d9497217SMartin Matuska``` 20*d9497217SMartin Matuska$ make unit 21*d9497217SMartin Matuska UNITTEST tests/unit/test_zap 22*d9497217SMartin MatuskaRunning test suite with seed 0x9d36890b... 23*d9497217SMartin Matuskazap.mock_microzap_sanity [ OK ] [ 0.00001088 / 0.00000939 CPU ] 24*d9497217SMartin Matuskazap.mock_fatzap_sanity [ OK ] [ 0.00004281 / 0.00004257 CPU ] 25*d9497217SMartin Matuskazap.zap_basic 26*d9497217SMartin Matuska type=micro [ OK ] [ 0.00001899 / 0.00001893 CPU ] 27*d9497217SMartin Matuska type=fat [ OK ] [ 0.00004174 / 0.00004135 CPU ] 28*d9497217SMartin Matuska4 of 4 (100%) tests successful, 0 (0%) test skipped. 29*d9497217SMartin Matuska``` 30*d9497217SMartin Matuska 31*d9497217SMartin MatuskaRunning a single test binary is possible with the `T=` param to `make unit`. 32*d9497217SMartin Matuska 33*d9497217SMartin Matuska``` 34*d9497217SMartin Matuska$ make unit T=zap 35*d9497217SMartin Matuska UNITTEST tests/unit/test_zap 36*d9497217SMartin Matuska ... 37*d9497217SMartin Matuska``` 38*d9497217SMartin Matuska 39*d9497217SMartin MatuskaThe test binaries are just normal programs in `./tests/unit`, and can be run 40*d9497217SMartin Matuskadirectly. This is useful for debugging with `gdb`. 41*d9497217SMartin Matuska 42*d9497217SMartin Matuska``` 43*d9497217SMartin Matuska$ ./tests/unit/test_zap 44*d9497217SMartin MatuskaRunning test suite with seed 0x18e131ac... 45*d9497217SMartin Matuska... 46*d9497217SMartin Matuska``` 47*d9497217SMartin Matuska 48*d9497217SMartin MatuskaThe test framework provides various options for controlling how the tests are 49*d9497217SMartin Matuskarun. Add the `--help` switch for more info. If using the make rule, options can 50*d9497217SMartin Matuskabe passed via the `TOPT=` param. 51*d9497217SMartin Matuska 52*d9497217SMartin Matuska### Building just for tests 53*d9497217SMartin Matuska 54*d9497217SMartin MatuskaRecommended “minimum” build for just the unit tests, with additional debug to 55*d9497217SMartin Matuskaassist with understanding issues. 56*d9497217SMartin Matuska 57*d9497217SMartin Matuska``` 58*d9497217SMartin Matuska./configure \ 59*d9497217SMartin Matuska --with-config=user \ 60*d9497217SMartin Matuska --enable-debug --enable-debuginfo \ 61*d9497217SMartin Matuska --disable-sysvinit --disable-systemd --disable-pam --disable-pyzfs 62*d9497217SMartin Matuskamake -j$(nproc) 63*d9497217SMartin Matuska``` 64*d9497217SMartin Matuska 65*d9497217SMartin MatuskaTODO: add `--with-config=unit` that disables _everything_ not needed for the 66*d9497217SMartin Matuskatests 67*d9497217SMartin Matuska 68*d9497217SMartin Matuska### Generating a coverage report 69*d9497217SMartin Matuska 70*d9497217SMartin MatuskaIf `configure` was run with `--enable-code-coverage`, then two additional build 71*d9497217SMartin Matuskatargets are available that will run the requested tests and produce a report. 72*d9497217SMartin Matuska 73*d9497217SMartin MatuskaThe `unit-coverage` target runs `scripts/coverage_report.pl` to produce a 74*d9497217SMartin Matuskacoverage summary directly in text immediately after the test output, and is 75*d9497217SMartin Matuskagood for inclusion in log files and other build system output. 76*d9497217SMartin Matuska 77*d9497217SMartin Matuska``` 78*d9497217SMartin Matuska$ make unit-coverage T=zap 79*d9497217SMartin Matuska UNITTEST tests/unit/test_zap 80*d9497217SMartin MatuskaRunning test suite with seed 0xf51efca9... 81*d9497217SMartin Matuskazap.mock_microzap_sanity [ OK ] [ 0.00000941 / 0.00000834 CPU ] 82*d9497217SMartin Matuskazap.mock_fatzap_sanity [ OK ] [ 0.00005782 / 0.00005766 CPU ] 83*d9497217SMartin Matuska... 84*d9497217SMartin Matuskazap.cursor_release_one 85*d9497217SMartin Matuska type=micro [ OK ] [ 0.00001705 / 0.00001681 CPU ] 86*d9497217SMartin Matuska type=fat [ OK ] [ 0.00004748 / 0.00004738 CPU ] 87*d9497217SMartin Matuska30 of 30 (100%) tests successful, 0 (0%) test skipped. 88*d9497217SMartin MatuskaCoverage: test_zap | By line | By branch | By function 89*d9497217SMartin Matuska | Rate% Total Hit | Rate% Total Hit | Rate% Total Hit 90*d9497217SMartin Matuskamodule/zfs/u8_textprep.c | 0.0% 802 0 | 0.0% 510 0 | 0.0% 12 0 91*d9497217SMartin Matuskamodule/zfs/zap.c | 33.9% 610 207 | 31.1% 238 74 | 23.0% 74 17 92*d9497217SMartin Matuskamodule/zfs/zap_fat.c | 47.1% 665 313 | 29.8% 446 133 | 62.2% 37 23 93*d9497217SMartin Matuskamodule/zfs/zap_impl.c | 57.8% 232 134 | 39.7% 146 58 | 72.0% 25 18 94*d9497217SMartin Matuskamodule/zfs/zap_leaf.c | 60.9% 466 284 | 41.2% 216 89 | 78.3% 23 18 95*d9497217SMartin Matuskamodule/zfs/zap_micro.c | 68.9% 238 164 | 41.5% 142 59 | 92.9% 14 13 96*d9497217SMartin Matuska``` 97*d9497217SMartin Matuska 98*d9497217SMartin MatuskaThe `unit-coverage-html` will use `lcov` and `genhtml` to generate an 99*d9497217SMartin Matuskainteractive HTML report that also can show the specific source lines that are 100*d9497217SMartin Matuskacovered. 101*d9497217SMartin Matuska 102*d9497217SMartin Matuska``` 103*d9497217SMartin Matuska$ make unit-coverage-html T=zap 104*d9497217SMartin Matuska UNITTEST tests/unit/test_zap 105*d9497217SMartin MatuskaRunning test suite with seed 0x485bf2e2... 106*d9497217SMartin Matuskazap.mock_microzap_sanity [ OK ] [ 0.00000935 / 0.00000794 CPU ] 107*d9497217SMartin Matuskazap.mock_fatzap_sanity [ OK ] [ 0.00006050 / 0.00006025 CPU ] 108*d9497217SMartin Matuska... 109*d9497217SMartin Matuskazap.cursor_release_one 110*d9497217SMartin Matuska type=micro [ OK ] [ 0.00001785 / 0.00001767 CPU ] 111*d9497217SMartin Matuska type=fat [ OK ] [ 0.00005262 / 0.00005250 CPU ] 112*d9497217SMartin Matuska30 of 30 (100%) tests successful, 0 (0%) test skipped. 113*d9497217SMartin Matuskacoverage results: 114*d9497217SMartin Matuskafile:///home/robn/code/zfs-unit/tests/unit/tests/unit/test_zap_coverage/index.ht 115*d9497217SMartin Matuskaml 116*d9497217SMartin Matuska``` 117*d9497217SMartin Matuska 118*d9497217SMartin MatuskaCurrently the coverage data will only be regenerated when the test binary 119*d9497217SMartin Matuskaitself changes. To force it, use `make unit-clean-local` to remove the coverage 120*d9497217SMartin Matuskadata. 121*d9497217SMartin Matuska 122*d9497217SMartin Matuska## Guidance for test writers 123*d9497217SMartin Matuska 124*d9497217SMartin Matuska### Top five 125*d9497217SMartin Matuska 126*d9497217SMartin Matuska* Only bring in the source files under test. 127*d9497217SMartin Matuska* Use mocks to create the test scenario, then interrogate them to understand 128*d9497217SMartin Matuskathe result. 129*d9497217SMartin Matuska* Prefer more smaller tests over fewer bigger ones. 130*d9497217SMartin Matuska* Use coverage reports to guide test development. 131*d9497217SMartin Matuska* Do the simplest possible thing. 132*d9497217SMartin Matuska 133*d9497217SMartin Matuska### Test structure 134*d9497217SMartin Matuska 135*d9497217SMartin MatuskaTests should be as simple and as readable as possible. When a test fails, we 136*d9497217SMartin Matuskawant to avoid the possibility that it could be the test itself at fault rather 137*d9497217SMartin Matuskathan the system under test. 138*d9497217SMartin Matuska 139*d9497217SMartin Matuska* Aim for one source file per subsystem or source concept (eg ZAP). 140*d9497217SMartin Matuska* Aim for one test function per API call or logical behaviour 141*d9497217SMartin Matuska * Each “version” or “mode” of an API call or behaviour is a separate test 142*d9497217SMartin Matuska * Don’t test more than one thing in the same test; a test shouldn’t rely on 143*d9497217SMartin Matuska state or results from an earlier test 144*d9497217SMartin Matuska* Use test parameters for “class“ or ”vtable” -type APIs, where each 145*d9497217SMartin Matuska implementation should respond to API calls the same way 146*d9497217SMartin Matuska 147*d9497217SMartin Matuska### Build system 148*d9497217SMartin Matuska 149*d9497217SMartin MatuskaThe build setup `tests/unit/Makefile.am` is very similar to the other 150*d9497217SMartin Matuskauserspace, however it has a couple of differences to make the run and coverage 151*d9497217SMartin Matuskatargets work more smoothly. 152*d9497217SMartin Matuska 153*d9497217SMartin Matuska* Name the test program `test_foo`. Almost always, you will have one source 154*d9497217SMartin Matuska file with the actual tests in it, called `test_foo.c`. 155*d9497217SMartin Matuska* Add the program to `UNIT_TESTS`. `noinst_PROGRAMS` will be populated from it, 156*d9497217SMartin Matuska but this gives a specific name the run and coverage targets can use to 157*d9497217SMartin Matuska resolve the `T=` parameter to a specific test. 158*d9497217SMartin Matuska* List the source files under test in `nodist_%C%_test_foo_SOURCES`, and the 159*d9497217SMartin Matuska source files for the test itself in `%C%_test_foo_SOURCES`. This is 160*d9497217SMartin Matuska important, as the coverage targets use `nodist_%C%_ ... _SOURCES` as the list 161*d9497217SMartin Matuska of objects to include in the coverage output. 162*d9497217SMartin Matuska 163*d9497217SMartin Matuska### Mocks 164*d9497217SMartin Matuska 165*d9497217SMartin MatuskaA “mock” struct is a fake version of some data structure that the subsystem 166*d9497217SMartin Matuskaunder test will accept and use as though it was a real one. 167*d9497217SMartin Matuska 168*d9497217SMartin Matuska* Make mock structs opaque. All uses from the test suite should be through 169*d9497217SMartin Matuska specific named accessor functions. 170*d9497217SMartin Matuska* Name a mock struct for the struct it is mimicking, prefixed with `mock_`. eg 171*d9497217SMartin Matuska `mock_dnode_t` is the mock for `dnode_t`. 172*d9497217SMartin Matuska* Access functions should be named for the struct, eg the function to create a 173*d9497217SMartin Matuska `mock_dnode_t` is `mock_dnode_t *mock_dnode_create(...)`. 174*d9497217SMartin Matuska* `mock_*` functions should always use the mock type name in its signature, 175*d9497217SMartin Matuska never the original. 176*d9497217SMartin Matuska* The mock object should always be directly castable to its real type and 177*d9497217SMartin Matuska vice-versa, ie a `mock_dnode_t *` is always usable wherever a `dnode_t *` 178*d9497217SMartin Matuska is (within the domain of the subsystem under test). 179*d9497217SMartin Matuska 180*d9497217SMartin MatuskaThis guidance pushes the programmer towards being explicit at the possible 181*d9497217SMartin Matuskaexpense of concision. This is in service of keeping the tests reliable; in 182*d9497217SMartin Matuskaparticular, if mocks require explicit casting to use, then there’s far less 183*d9497217SMartin Matuskachance of either a mock or a real object being used incorrectly in the test, 184*d9497217SMartin Matuskawhich can be confusing. 185*d9497217SMartin Matuska 186*d9497217SMartin Matuska### Unit testing framework 187*d9497217SMartin Matuska 188*d9497217SMartin Matuska[µnit](https://nemequ.github.io/munit/) (aka munit) is the unit test framework. 189*d9497217SMartin MatuskaIt is a relatively niche choice, and arguably abandoned by upstream, but is 190*d9497217SMartin Matuskawell constructed with a thoughtful feature set and some useful properties: 191*d9497217SMartin Matuska 192*d9497217SMartin Matuska* Just two source files we can easily carry in the repo. 193*d9497217SMartin Matuska* Portable, including to Windows. 194*d9497217SMartin Matuska* Each test is run in a forked process, so a test failure will not corrupt the 195*d9497217SMartin Matuska rest of the test suite run 196*d9497217SMartin Matuska* Parameterised tests. 197*d9497217SMartin Matuska* A large suite of assertions and other useful functions that make it easy to 198*d9497217SMartin Matuska integrate with. 199*d9497217SMartin Matuska 200*d9497217SMartin MatuskaAll OpenZFS unit tests are ultimately targeting munit, so its expected that 201*d9497217SMartin Matuskathey will use various features as needed. However, we also supply our own 202*d9497217SMartin Matuskafacilities to extend those in useful ways. 203*d9497217SMartin Matuska 204*d9497217SMartin Matuska#### Local extensions 205*d9497217SMartin Matuska 206*d9497217SMartin Matuska`unit.h` provides a handful of macros. The majority of these are aliases for 207*d9497217SMartin Matuskathe much longer munit names for same function, eg `unit_true(n)` is an alias 208*d9497217SMartin Matuskafor `munit_assert_true(n)`, `unit_eq(a,b)` is an alias for 209*d9497217SMartin Matuska`munit_assert_uint64(a, ==, b)`, and so on. These are there so that the 210*d9497217SMartin Matuskaassertions do not dominate the test visually, as we want it to be easier to 211*d9497217SMartin Matuskafocus on the details. 212*d9497217SMartin Matuska 213*d9497217SMartin MatuskaSimilarly, the `UINT_TEST` and `UNIT_PARAM` macros exist to help with test 214*d9497217SMartin Matuskadefinition, as the casts are a little complicated. 215*d9497217SMartin Matuska 216*d9497217SMartin MatuskaThe goal is to keep this set relatively small, but all of munit is there for 217*d9497217SMartin Matuskause, so do extend it if necessary. 218