xref: /freebsd/sys/contrib/openzfs/tests/unit/README.md (revision d9497217456002b0ddad3cd319570d0b098daa29)
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