unit: a unit testing framework

This commit establishes a unit test framework for OpenZFS, and
integrates it into the build.

It includes:
- the "munit" unit test framework (munit.c, munit.h)
- some light extensions to munit and glue for OpenZFS (unit.c, unit.h)
- make targets for running tests and generating coverage reports
- a document explaining the what, how and why

This is a first step; I expect we will extend all of this as we use it
more places and gain experience with it.

Sponsored-by: TrueNAS
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Rob Norris <rob.norris@truenas.com>
Closes #18564
This commit is contained in:
Rob Norris
2026-05-02 17:37:12 +10:00
committed by Brian Behlendorf
parent accb2b418e
commit 82b33c0034
9 changed files with 3431 additions and 0 deletions
+1
View File
@@ -138,6 +138,7 @@ cstyle:
! -path './include/sys/lua/*' \
! -path './module/lua/l*.[ch]' \
! -path './module/zfs/lz4.c' \
! -path './tests/unit/munit.[ch]' \
$(cstyle_line)
filter_executable = -exec test -x '{}' \; -print
+1
View File
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: CDDL-1.0
include $(srcdir)/%D%/unit/Makefile.am
include $(srcdir)/%D%/zfs-tests/Makefile.am
+2
View File
@@ -0,0 +1,2 @@
/test_*.info
/test_*_coverage
+60
View File
@@ -0,0 +1,60 @@
# SPDX-License-Identifier: CDDL-1.0
# libunit.la includes munit and any additional tools that apply to all tests
libunit_la_CFLAGS = $(AM_CFLAGS)
noinst_LTLIBRARIES += libunit.la
libunit_la_SOURCES = \
%D%/munit.c \
%D%/munit.h \
%D%/unit.c \
%D%/unit.h
# all test binaries
UNIT_TESTS =
noinst_PROGRAMS = $(UNIT_TESTS)
# test run and coverage targets below
PHONY += unit unit-coverage
_unit_run_%: %D%/%
@echo " UNITTEST $<" ; $<
_unit_coverage_%: _unit_run_%
@${LCOV} --quiet --capture \
--test-name $(subst _unit_coverage_, , $@) \
--directory $(top_srcdir) \
--output-file \
%D%/$(join $(subst _unit_coverage_, , $@), .info) \
$(addprefix --include , $(call $(join \
$(subst _unit_coverage_, nodist_%C%_, $@), _SOURCES)))
@${GENHTML} --quiet \
%D%/$(join $(subst _unit_coverage_, , $@), .info) \
--output-directory \
%D%/$(join $(subst _unit_coverage_, , $@), _coverage)
@echo "coverage results:" \
"file://$(realpath %D%)/$(join $(subst _unit_coverage_, , $@), _coverage)/index.html"
_UNIT_ALL_TARGETS = $(notdir $(UNIT_TESTS))
_UNIT_FIND_TARGET = \
$(foreach cmd, $(UNIT_TESTS), \
$(if $(filter $(join test_, $(1)), $(notdir $(cmd))), \
$(notdir $(cmd))))
_UNIT_TARGETS = $(if $(T), \
$(call _UNIT_FIND_TARGET, $(T)), $(call _UNIT_ALL_TARGETS))
unit: $(addprefix _unit_run_, $(_UNIT_TARGETS))
@$(if $^, true, echo "ERROR: couldn't find unit test: $(T)" && false)
if CODE_COVERAGE_ENABLED
unit-coverage: $(addprefix _unit_coverage_, $(_UNIT_TARGETS))
@$(if $^, true, echo "ERROR: couldn't find unit test: $(T)" && false)
else
unit-coverage:
@echo "unit test coverage not enabled."
@echo "re-run configure with --enable-code-coverage"
@false
endif
+189
View File
@@ -0,0 +1,189 @@
# Unit tests
> [!NOTE]
>
> This document is a draft. It will be updated as we gain experience writing
> and running unit tests.
This directory contains a unit testing framework for OpenZFS, and a collection
of unit tests.
## Building and running
The unit tests are built by default as part of the regular userspace build, so
you probably dont have to do anything else.
The easiest way to run the tests is to run `make unit`, which will run all the
available tests.
```
$ make unit
UNITTEST tests/unit/test_zap
Running test suite with seed 0x9d36890b...
zap.mock_microzap_sanity [ OK ] [ 0.00001088 / 0.00000939 CPU ]
zap.mock_fatzap_sanity [ OK ] [ 0.00004281 / 0.00004257 CPU ]
zap.zap_basic
type=micro [ OK ] [ 0.00001899 / 0.00001893 CPU ]
type=fat [ OK ] [ 0.00004174 / 0.00004135 CPU ]
4 of 4 (100%) tests successful, 0 (0%) test skipped.
```
Running a single test binary is possible with the `T=` param to `make unit`.
```
$ make unit T=zap
UNITTEST tests/unit/test_zap
...
```
The test binaries are just normal programs in `./tests/unit`, and can be run
directly. This is useful for debugging with `gdb`.
```
$ ./tests/unit/test_zap
Running test suite with seed 0x18e131ac...
...
```
### Building just for tests
Recommended “minimum” build for just the unit tests, with additional debug to
assist with understanding issues.
```
./configure \
--with-config=user \
--enable-debug --enable-debuginfo \
--disable-sysvinit --disable-systemd --disable-pam --disable-pyzfs
make -j$(nproc)
```
TODO: add `--with-config=unit` that disables _everything_ not needed for the tests
### Generating a coverage report
If `configure` was run with `--enable-code-coverage`, then an additional
`unit-coverage` target is available, which will run the requested tests, then
run `lcov` and `genhtml` to produce a HTML coverage report:
```
$ make unit-coverage T=zap
UNITTEST tests/unit/test_zap
Running test suite with seed 0xe461208d...
zap.mock_microzap_sanity [ OK ] [ 0.00000933 / 0.00000773 CPU ]
zap.mock_fatzap_sanity [ OK ] [ 0.00004685 / 0.00004612 CPU ]
zap.zap_basic
type=micro [ OK ] [ 0.00002579 / 0.00002484 CPU ]
type=fat [ OK ] [ 0.00004093 / 0.00004038 CPU ]
4 of 4 (100%) tests successful, 0 (0%) test skipped.
lcov: WARNING: (inconsistent) /home/robn/code/zfs-unit/module/zfs/u8_textprep.c:1104: unexecuted block on non-branch line with non-zero hit count. Use "geninfo --rc geninfo_unexecuted_blocks=1 to set count to zero.
(use "lcov --ignore-errors inconsistent,inconsistent ..." to suppress this warning)
Message summary:
1 warning message:
inconsistent: 1
Overall coverage rate:
source files: 6
lines.......: 42.3% (1270 of 3002 lines)
functions...: 42.0% (76 of 181 functions)
Message summary:
no messages were reported
coverage results: file://tests/unit/test_zap_coverage/index.html
```
TODO: improve the overall structure to make this less noisy.
## Guidance for test writers
### Top five
* Only bring in the source files under test.
* Use mocks to create the test scenario, then interrogate them to understand the result.
* Prefer more smaller tests over fewer bigger ones.
* Use coverage reports to guide test development.
* Do the simplest possible thing.
### Test structure
Tests should be as simple and as readable as possible. When a test fails, we
want to avoid the possibility that it could be the test itself at fault rather
than the system under test.
* Aim for one source file per subsystem or source concept (eg ZAP).
* Aim for one test function per API call or logical behaviour
* Each “version” or “mode” of an API call or behaviour is a separate test
* Dont test more than one thing in the same test; a test shouldnt rely on
state or results from an earlier test
* Use test parameters for “class“ or ”vtable” -type APIs, where each
implementation should respond to API calls the same way
### Build system
The build setup `tests/unit/Makefile.am` is very similar to the other
userspace, however it has a couple of differences to make the run and coverage
targets work more smoothly.
* Name the test program `test_foo`. Almost always, you will have one source
file with the actual tests in it, called `test_foo.c`.
* Add the program to `UNIT_TESTS`. `noinst_PROGRAMS` will be populated from it,
but this gives a specific name the run and coverage targets can use to
resolve the `T=` parameter to a specific test.
* List the source files under test in `nodist_%C%_test_foo_SOURCES`, and the
source files for the test itself in `%C%_test_foo_SOURCES`. This is
important, as the coverage targets use `nodist_%C%_ ... _SOURCES` as the list
of objects to include in the coverage output.
### Mocks
A “mock” struct is a fake version of some data structure that the subsystem
under test will accept and use as though it was a real one.
* Make mock structs opaque. All uses from the test suite should be through
specific named accessor functions.
* Name a mock struct for the struct it is mimicking, prefixed with `mock_`. eg
`mock_dnode_t` is the mock for `dnode_t`.
* Access functions should be named for the struct, eg the function to create a
`mock_dnode_t` is `mock_dnode_t *mock_dnode_create(...)`.
* `mock_*` functions should always use the mock type name in its signature,
never the original.
* The mock object should always be directly castable to its real type and
vice-versa, ie a `mock_dnode_t *` is always usable wherever a `dnode_t *`
is (within the domain of the subsystem under test).
This guidance pushes the programmer towards being explicit at the possible
expense of concision. This is in service of keeping the tests reliable; in
particular, if mocks require explicit casting to use, then theres far less
chance of either a mock or a real object being used incorrectly in the test,
which can be confusing.
### Unit testing framework
[µnit](https://nemequ.github.io/munit/) (aka munit) is the unit test framework.
It is a relatively niche choice, and arguably abandoned by upstream, but is
well constructed with a thoughtful feature set and some useful properties:
* Just two source files we can easily carry in the repo.
* Portable, including to Windows.
* Each test is run in a forked process, so a test failure will not corrupt the
rest of the test suite run
* Parameterised tests.
* A large suite of assertions and other useful functions that make it easy to
integrate with.
All OpenZFS unit tests are ultimately targeting munit, so its expected that
they will use various features as needed. However, we also supply our own
facilities to extend those in useful ways.
#### Local extensions
`unit.h` provides a handful of macros. The majority of these are aliases for
the much longer munit names for same function, eg `unit_true(n)` is an alias
for `munit_assert_true(n)`, `unit_eq(a,b)` is an alias for
`munit_assert_uint64(a, ==, b)`, and so on. These are there so that the
assertions do not dominate the test visually, as we want it to be easier to
focus on the details.
Similarly, the `UINT_TEST` and `UNIT_PARAM` macros exist to help with test
definition, as the casts are a little complicated.
The goal is to keep this set relatively small, but all of munit is there for
use, so do extend it if necessary.
+2458
View File
File diff suppressed because it is too large Load Diff
+575
View File
@@ -0,0 +1,575 @@
// SPDX-License-Identifier: MIT
/* µnit Testing Framework
* Copyright (c) 2013-2017 Evan Nemerson <evan@nemerson.com>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef MUNIT_H
#define MUNIT_H
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#define MUNIT_VERSION(major, minor, revision) \
(((major) << 16) | ((minor) << 8) | (revision))
#define MUNIT_CURRENT_VERSION MUNIT_VERSION(0, 4, 1)
#if defined(_MSC_VER) && (_MSC_VER < 1600)
# define munit_int8_t __int8
# define munit_uint8_t unsigned __int8
# define munit_int16_t __int16
# define munit_uint16_t unsigned __int16
# define munit_int32_t __int32
# define munit_uint32_t unsigned __int32
# define munit_int64_t __int64
# define munit_uint64_t unsigned __int64
#else
# include <stdint.h>
# define munit_int8_t int8_t
# define munit_uint8_t uint8_t
# define munit_int16_t int16_t
# define munit_uint16_t uint16_t
# define munit_int32_t int32_t
# define munit_uint32_t uint32_t
# define munit_int64_t int64_t
# define munit_uint64_t uint64_t
#endif
#if defined(_MSC_VER) && (_MSC_VER < 1800)
# if !defined(PRIi8)
# define PRIi8 "i"
# endif
# if !defined(PRIi16)
# define PRIi16 "i"
# endif
# if !defined(PRIi32)
# define PRIi32 "i"
# endif
# if !defined(PRIi64)
# define PRIi64 "I64i"
# endif
# if !defined(PRId8)
# define PRId8 "d"
# endif
# if !defined(PRId16)
# define PRId16 "d"
# endif
# if !defined(PRId32)
# define PRId32 "d"
# endif
# if !defined(PRId64)
# define PRId64 "I64d"
# endif
# if !defined(PRIx8)
# define PRIx8 "x"
# endif
# if !defined(PRIx16)
# define PRIx16 "x"
# endif
# if !defined(PRIx32)
# define PRIx32 "x"
# endif
# if !defined(PRIx64)
# define PRIx64 "I64x"
# endif
# if !defined(PRIu8)
# define PRIu8 "u"
# endif
# if !defined(PRIu16)
# define PRIu16 "u"
# endif
# if !defined(PRIu32)
# define PRIu32 "u"
# endif
# if !defined(PRIu64)
# define PRIu64 "I64u"
# endif
#else
# include <inttypes.h>
#endif
#if !defined(munit_bool)
# if defined(bool)
# define munit_bool bool
# elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
# define munit_bool _Bool
# else
# define munit_bool int
# endif
#endif
#if defined(__cplusplus)
extern "C" {
#endif
#if defined(__GNUC__)
# define MUNIT_LIKELY(expr) (__builtin_expect((expr), 1))
# define MUNIT_UNLIKELY(expr) (__builtin_expect((expr), 0))
# define MUNIT_UNUSED __attribute__((__unused__))
#else
# define MUNIT_LIKELY(expr) (expr)
# define MUNIT_UNLIKELY(expr) (expr)
# define MUNIT_UNUSED
#endif
#if !defined(_WIN32)
# define MUNIT_SIZE_MODIFIER "z"
# define MUNIT_CHAR_MODIFIER "hh"
# define MUNIT_SHORT_MODIFIER "h"
#else
# if defined(_M_X64) || defined(__amd64__)
# define MUNIT_SIZE_MODIFIER "I64"
# else
# define MUNIT_SIZE_MODIFIER ""
# endif
# define MUNIT_CHAR_MODIFIER ""
# define MUNIT_SHORT_MODIFIER ""
#endif
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
# define MUNIT_NO_RETURN _Noreturn
#elif defined(__GNUC__)
# define MUNIT_NO_RETURN __attribute__((__noreturn__))
#elif defined(_MSC_VER)
# define MUNIT_NO_RETURN __declspec(noreturn)
#else
# define MUNIT_NO_RETURN
#endif
#if defined(_MSC_VER) && (_MSC_VER >= 1500)
# define MUNIT_PUSH_DISABLE_MSVC_C4127_ \
__pragma(warning(push)) __pragma(warning(disable : 4127))
# define MUNIT_POP_DISABLE_MSVC_C4127_ __pragma(warning(pop))
#else
# define MUNIT_PUSH_DISABLE_MSVC_C4127_
# define MUNIT_POP_DISABLE_MSVC_C4127_
#endif
typedef enum {
MUNIT_LOG_DEBUG,
MUNIT_LOG_INFO,
MUNIT_LOG_WARNING,
MUNIT_LOG_ERROR
} MunitLogLevel;
#if defined(__GNUC__) && !defined(__MINGW32__)
# define MUNIT_PRINTF(string_index, first_to_check) \
__attribute__((format(printf, string_index, first_to_check)))
#else
# define MUNIT_PRINTF(string_index, first_to_check)
#endif
MUNIT_PRINTF(4, 5)
void munit_logf_ex(MunitLogLevel level, const char *filename, int line,
const char *format, ...);
#define munit_logf(level, format, ...) \
munit_logf_ex(level, __FILE__, __LINE__, format, __VA_ARGS__)
#define munit_log(level, msg) munit_logf(level, "%s", msg)
MUNIT_NO_RETURN
MUNIT_PRINTF(3, 4)
void munit_errorf_ex(const char *filename, int line, const char *format, ...);
#define munit_errorf(format, ...) \
munit_errorf_ex(__FILE__, __LINE__, format, __VA_ARGS__)
#define munit_error(msg) munit_errorf("%s", msg)
#define munit_assert(expr) \
do { \
if (!MUNIT_LIKELY(expr)) { \
munit_error("assertion failed: " #expr); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_true(expr) \
do { \
if (!MUNIT_LIKELY(expr)) { \
munit_error("assertion failed: " #expr " is not true"); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_false(expr) \
do { \
if (!MUNIT_LIKELY(!(expr))) { \
munit_error("assertion failed: " #expr " is not false"); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_type_full(prefix, suffix, T, fmt, a, op, b) \
do { \
T munit_tmp_a_ = (a); \
T munit_tmp_b_ = (b); \
if (!(munit_tmp_a_ op munit_tmp_b_)) { \
munit_errorf("assertion failed: %s %s %s (" prefix "%" fmt suffix \
" %s " prefix "%" fmt suffix ")", \
#a, #op, #b, munit_tmp_a_, #op, munit_tmp_b_); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_type(T, fmt, a, op, b) \
munit_assert_type_full("", "", T, fmt, a, op, b)
#define munit_assert_char(a, op, b) \
munit_assert_type_full("'\\x", "'", char, "02" MUNIT_CHAR_MODIFIER "x", a, \
op, b)
#define munit_assert_uchar(a, op, b) \
munit_assert_type_full("'\\x", "'", unsigned char, \
"02" MUNIT_CHAR_MODIFIER "x", a, op, b)
#define munit_assert_short(a, op, b) \
munit_assert_type(short, MUNIT_SHORT_MODIFIER "d", a, op, b)
#define munit_assert_ushort(a, op, b) \
munit_assert_type(unsigned short, MUNIT_SHORT_MODIFIER "u", a, op, b)
#define munit_assert_int(a, op, b) munit_assert_type(int, "d", a, op, b)
#define munit_assert_uint(a, op, b) \
munit_assert_type(unsigned int, "u", a, op, b)
#define munit_assert_long(a, op, b) munit_assert_type(long int, "ld", a, op, b)
#define munit_assert_ulong(a, op, b) \
munit_assert_type(unsigned long int, "lu", a, op, b)
#define munit_assert_llong(a, op, b) \
munit_assert_type(long long int, "lld", a, op, b)
#define munit_assert_ullong(a, op, b) \
munit_assert_type(unsigned long long int, "llu", a, op, b)
#define munit_assert_size(a, op, b) \
munit_assert_type(size_t, MUNIT_SIZE_MODIFIER "u", a, op, b)
#define munit_assert_ssize(a, op, b) \
munit_assert_type(ssize_t, MUNIT_SIZE_MODIFIER "d", a, op, b)
#define munit_assert_float(a, op, b) munit_assert_type(float, "f", a, op, b)
#define munit_assert_double(a, op, b) munit_assert_type(double, "g", a, op, b)
#define munit_assert_ptr(a, op, b) \
munit_assert_type(const void *, "p", a, op, b)
#define munit_assert_int8(a, op, b) \
munit_assert_type(munit_int8_t, PRIi8, a, op, b)
#define munit_assert_uint8(a, op, b) \
munit_assert_type(munit_uint8_t, PRIu8, a, op, b)
#define munit_assert_int16(a, op, b) \
munit_assert_type(munit_int16_t, PRIi16, a, op, b)
#define munit_assert_uint16(a, op, b) \
munit_assert_type(munit_uint16_t, PRIu16, a, op, b)
#define munit_assert_int32(a, op, b) \
munit_assert_type(munit_int32_t, PRIi32, a, op, b)
#define munit_assert_uint32(a, op, b) \
munit_assert_type(munit_uint32_t, PRIu32, a, op, b)
#define munit_assert_int64(a, op, b) \
munit_assert_type(munit_int64_t, PRIi64, a, op, b)
#define munit_assert_uint64(a, op, b) \
munit_assert_type(munit_uint64_t, PRIu64, a, op, b)
#define munit_assert_ptrdiff(a, op, b) \
munit_assert_type(ptrdiff_t, "td", a, op, b)
#define munit_assert_enum(T, a, op, b) munit_assert_type(T, "d", a, op, b)
#define munit_assert_double_equal(a, b, precision) \
do { \
const double munit_tmp_a_ = (a); \
const double munit_tmp_b_ = (b); \
const double munit_tmp_diff_ = ((munit_tmp_a_ - munit_tmp_b_) < 0) \
? -(munit_tmp_a_ - munit_tmp_b_) \
: (munit_tmp_a_ - munit_tmp_b_); \
if (MUNIT_UNLIKELY(munit_tmp_diff_ > 1e-##precision)) { \
munit_errorf("assertion failed: %s == %s (%0." #precision \
"g == %0." #precision "g)", \
#a, #b, munit_tmp_a_, munit_tmp_b_); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) MUNIT_POP_DISABLE_MSVC_C4127_
#include <string.h>
#define munit_assert_string_equal(a, b) \
do { \
const char *munit_tmp_a_ = (a); \
const char *munit_tmp_b_ = (b); \
if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) != 0)) { \
munit_hexdump_diff(stderr, munit_tmp_a_, strlen(munit_tmp_a_), \
munit_tmp_b_, strlen(munit_tmp_b_)); \
munit_errorf("assertion failed: string %s == %s (\"%s\" == \"%s\")", #a, \
#b, munit_tmp_a_, munit_tmp_b_); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_string_not_equal(a, b) \
do { \
const char *munit_tmp_a_ = (a); \
const char *munit_tmp_b_ = (b); \
if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) == 0)) { \
munit_errorf("assertion failed: string %s != %s (\"%s\" == \"%s\")", #a, \
#b, munit_tmp_a_, munit_tmp_b_); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_memory_equal(size, a, b) \
do { \
const unsigned char *munit_tmp_a_ = (const unsigned char *)(a); \
const unsigned char *munit_tmp_b_ = (const unsigned char *)(b); \
const size_t munit_tmp_size_ = (size); \
if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) != \
0) { \
size_t munit_tmp_pos_; \
for (munit_tmp_pos_ = 0; munit_tmp_pos_ < munit_tmp_size_; \
munit_tmp_pos_++) { \
if (munit_tmp_a_[munit_tmp_pos_] != munit_tmp_b_[munit_tmp_pos_]) { \
munit_hexdump_diff(stderr, munit_tmp_a_, size, munit_tmp_b_, size); \
munit_errorf("assertion failed: memory %s == %s, at offset " \
"%" MUNIT_SIZE_MODIFIER "u", \
#a, #b, munit_tmp_pos_); \
break; \
} \
} \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_memn_equal(a, a_size, b, b_size) \
do { \
const unsigned char *munit_tmp_a_ = (const unsigned char *)(a); \
const unsigned char *munit_tmp_b_ = (const unsigned char *)(b); \
const size_t munit_tmp_a_size_ = (a_size); \
const size_t munit_tmp_b_size_ = (b_size); \
if (MUNIT_UNLIKELY(munit_tmp_a_size_ != munit_tmp_b_size_) || \
MUNIT_UNLIKELY(munit_tmp_a_size_ && memcmp(munit_tmp_a_, munit_tmp_b_, \
munit_tmp_a_size_)) != 0) { \
munit_hexdump_diff(stderr, munit_tmp_a_, munit_tmp_a_size_, \
munit_tmp_b_, munit_tmp_b_size_); \
munit_errorf("assertion failed: memory %s == %s", #a, #b); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_memory_not_equal(size, a, b) \
do { \
const unsigned char *munit_tmp_a_ = (const unsigned char *)(a); \
const unsigned char *munit_tmp_b_ = (const unsigned char *)(b); \
const size_t munit_tmp_size_ = (size); \
if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) == \
0) { \
munit_errorf("assertion failed: memory %s != %s (%zu bytes)", #a, #b, \
munit_tmp_size_); \
} \
MUNIT_PUSH_DISABLE_MSVC_C4127_ \
} while (0) MUNIT_POP_DISABLE_MSVC_C4127_
#define munit_assert_ptr_equal(a, b) munit_assert_ptr(a, ==, b)
#define munit_assert_ptr_not_equal(a, b) munit_assert_ptr(a, !=, b)
#define munit_assert_null(ptr) munit_assert_ptr(ptr, ==, NULL)
#define munit_assert_not_null(ptr) munit_assert_ptr(ptr, !=, NULL)
#define munit_assert_ptr_null(ptr) munit_assert_ptr(ptr, ==, NULL)
#define munit_assert_ptr_not_null(ptr) munit_assert_ptr(ptr, !=, NULL)
/*** Memory allocation ***/
void *munit_malloc_ex(const char *filename, int line, size_t size);
#define munit_malloc(size) munit_malloc_ex(__FILE__, __LINE__, (size))
#define munit_new(type) ((type *)munit_malloc(sizeof(type)))
#define munit_calloc(nmemb, size) munit_malloc((nmemb) * (size))
#define munit_newa(type, nmemb) ((type *)munit_calloc((nmemb), sizeof(type)))
/*** Random number generation ***/
void munit_rand_seed(munit_uint32_t seed);
munit_uint32_t munit_rand_uint32(void);
int munit_rand_int_range(int min, int max);
double munit_rand_double(void);
void munit_rand_memory(size_t size, munit_uint8_t *buffer);
/*** Tests and Suites ***/
typedef enum {
/* Test successful */
MUNIT_OK,
/* Test failed */
MUNIT_FAIL,
/* Test was skipped */
MUNIT_SKIP,
/* Test failed due to circumstances not intended to be tested
* (things like network errors, invalid parameter value, failure to
* allocate memory in the test harness, etc.). */
MUNIT_ERROR
} MunitResult;
typedef struct {
char *name;
char **values;
} MunitParameterEnum;
typedef struct {
char *name;
char *value;
} MunitParameter;
const char *munit_parameters_get(const MunitParameter params[],
const char *key);
typedef enum {
MUNIT_TEST_OPTION_NONE = 0,
MUNIT_TEST_OPTION_SINGLE_ITERATION = 1 << 0,
MUNIT_TEST_OPTION_TODO = 1 << 1
} MunitTestOptions;
typedef MunitResult (*MunitTestFunc)(const MunitParameter params[],
void *user_data_or_fixture);
typedef void *(*MunitTestSetup)(const MunitParameter params[], void *user_data);
typedef void (*MunitTestTearDown)(void *fixture);
typedef struct {
const char *name;
MunitTestFunc test;
MunitTestSetup setup;
MunitTestTearDown tear_down;
MunitTestOptions options;
MunitParameterEnum *parameters;
} MunitTest;
typedef enum { MUNIT_SUITE_OPTION_NONE = 0 } MunitSuiteOptions;
typedef struct MunitSuite_ MunitSuite;
struct MunitSuite_ {
const char *prefix;
const MunitTest *tests;
const MunitSuite *suites;
unsigned int iterations;
MunitSuiteOptions options;
};
int munit_suite_main(const MunitSuite *suite, void *user_data, int argc,
char *const *argv);
/* Note: I'm not very happy with this API; it's likely to change if I
* figure out something better. Suggestions welcome. */
typedef struct MunitArgument_ MunitArgument;
struct MunitArgument_ {
char *name;
munit_bool (*parse_argument)(const MunitSuite *suite, void *user_data,
int *arg, int argc, char *const *argv);
void (*write_help)(const MunitArgument *argument, void *user_data);
};
int munit_suite_main_custom(const MunitSuite *suite, void *user_data, int argc,
char *const *argv, const MunitArgument arguments[]);
#if defined(MUNIT_ENABLE_ASSERT_ALIASES)
# define assert_true(expr) munit_assert_true(expr)
# define assert_false(expr) munit_assert_false(expr)
# define assert_char(a, op, b) munit_assert_char(a, op, b)
# define assert_uchar(a, op, b) munit_assert_uchar(a, op, b)
# define assert_short(a, op, b) munit_assert_short(a, op, b)
# define assert_ushort(a, op, b) munit_assert_ushort(a, op, b)
# define assert_int(a, op, b) munit_assert_int(a, op, b)
# define assert_uint(a, op, b) munit_assert_uint(a, op, b)
# define assert_long(a, op, b) munit_assert_long(a, op, b)
# define assert_ulong(a, op, b) munit_assert_ulong(a, op, b)
# define assert_llong(a, op, b) munit_assert_llong(a, op, b)
# define assert_ullong(a, op, b) munit_assert_ullong(a, op, b)
# define assert_size(a, op, b) munit_assert_size(a, op, b)
# define assert_ssize(a, op, b) munit_assert_ssize(a, op, b)
# define assert_float(a, op, b) munit_assert_float(a, op, b)
# define assert_double(a, op, b) munit_assert_double(a, op, b)
# define assert_ptr(a, op, b) munit_assert_ptr(a, op, b)
# define assert_int8(a, op, b) munit_assert_int8(a, op, b)
# define assert_uint8(a, op, b) munit_assert_uint8(a, op, b)
# define assert_int16(a, op, b) munit_assert_int16(a, op, b)
# define assert_uint16(a, op, b) munit_assert_uint16(a, op, b)
# define assert_int32(a, op, b) munit_assert_int32(a, op, b)
# define assert_uint32(a, op, b) munit_assert_uint32(a, op, b)
# define assert_int64(a, op, b) munit_assert_int64(a, op, b)
# define assert_uint64(a, op, b) munit_assert_uint64(a, op, b)
# define assert_ptrdiff(a, op, b) munit_assert_ptrdiff(a, op, b)
# define assert_enum(T, a, op, b) munit_assert_enum(T, a, op, b)
# define assert_double_equal(a, b, precision) \
munit_assert_double_equal(a, b, precision)
# define assert_string_equal(a, b) munit_assert_string_equal(a, b)
# define assert_string_not_equal(a, b) munit_assert_string_not_equal(a, b)
# define assert_memory_equal(size, a, b) munit_assert_memory_equal(size, a, b)
# define assert_memn_equal(a, a_size, b, b_size) \
munit_assert_memn_equal(a, a_size, b, b_size)
# define assert_memory_not_equal(size, a, b) \
munit_assert_memory_not_equal(size, a, b)
# define assert_ptr_equal(a, b) munit_assert_ptr_equal(a, b)
# define assert_ptr_not_equal(a, b) munit_assert_ptr_not_equal(a, b)
# define assert_ptr_null(ptr) munit_assert_null_equal(ptr)
# define assert_ptr_not_null(ptr) munit_assert_not_null(ptr)
# define assert_null(ptr) munit_assert_null(ptr)
# define assert_not_null(ptr) munit_assert_not_null(ptr)
#endif /* defined(MUNIT_ENABLE_ASSERT_ALIASES) */
#define munit_void_test_decl(func) \
void func(void); \
\
static inline MunitResult wrap_##func(const MunitParameter params[], \
void *fixture) { \
(void)params; \
(void)fixture; \
\
func(); \
return MUNIT_OK; \
}
#define munit_void_test(func) \
{"/" #func, wrap_##func, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}
#define munit_test_end() {NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}
int munit_hexdump(FILE *fp, const void *data, size_t datalen);
int munit_hexdump_diff(FILE *fp, const void *a, size_t alen, const void *b,
size_t blen);
#if defined(__cplusplus)
}
#endif
#endif /* !defined(MUNIT_H) */
#if defined(MUNIT_ENABLE_ASSERT_ALIASES)
#if defined(assert)
# undef assert
#endif
#define assert(expr) munit_assert(expr)
#endif
+85
View File
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: CDDL-1.0
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/
/*
* Copyright (c) 2026, TrueNAS.
*/
/* Core stubs, applicable to all test suites. */
#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/zfs_debug.h>
#include "munit.h"
/*
* SET_ERROR() expands to __set_error() in debug builds. It's an
* under-the-hood tracing aid in production; a no-op is fine.
*/
void
__set_error(const char *file, const char *func, int line, int err)
{
(void) file; (void) func; (void) line; (void) err;
}
/* Plumb logging and debug into munit for convenience. */
/* dprintf() checks zfs_flags and calls __dprintf() in debug builds. */
int zfs_dbgmsg_enable = 1;
int zfs_flags = ZFS_DEBUG_DPRINTF;
/* Log dprintf() to MUNIT_LOG_DEBUG. */
void
__dprintf(boolean_t dprint, const char *file, const char *func,
int line, const char *fmt, ...)
{
char buf[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof (buf), fmt, ap);
va_end(ap);
munit_logf_ex(MUNIT_LOG_DEBUG, NULL, 0, "%s%s:%d [%s]: %s",
dprint ? "dprintf: " : "", file, line, func, buf);
}
/* Log cmn_err() to MUNIT_LOG_INFO or WARNING, abort test on CE_PANIC. */
void
cmn_err(int ce, const char *fmt, ...)
{
if (ce == CE_IGNORE)
return;
char buf[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof (buf), fmt, ap);
va_end(ap);
switch (ce) {
case CE_WARN:
munit_logf_ex(MUNIT_LOG_WARNING, NULL, 0, "%s", buf);
break;
case CE_PANIC:
munit_errorf_ex(NULL, 0, "PANIC: %s", buf);
break;
default:
munit_logf_ex(MUNIT_LOG_INFO, NULL, 0, "%s", buf);
break;
}
}
+60
View File
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: CDDL-1.0
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/
/*
* Copyright (c) 2026, TrueNAS.
*/
#ifndef UNIT_H
#define UNIT_H
#include "munit.h"
/* test/suite definition helpers */
/* single element in a MunitTest array */
#define _UNIT_TEST(name, func, params, ...) \
{ (name), (func), NULL, NULL, MUNIT_TEST_OPTION_NONE, \
(MunitParameterEnum*)(params) }
#define UNIT_TEST(name, func, ...) \
_UNIT_TEST(name, func, ##__VA_ARGS__, NULL)
/* single element in a MunitParameterEnum array */
#define UNIT_PARAM(name, ...) \
{ (char *)(name), (char **)(const char *[]) { __VA_ARGS__, NULL } }
/* shortcut for truthy tests */
#define unit_true(a) munit_assert_true(a)
#define unit_false(a) munit_assert_false(a)
/* shortcut for zero test */
#define unit_zero(a) munit_assert_uint64((a), ==, 0)
/* shortcuts for integer comparisons */
#define _unit_op(a, op, b) munit_assert_uint64((a), op, (b))
#define unit_eq(a, b) _unit_op((a), ==, (b))
#define unit_ne(a, b) _unit_op((a), !=, (b))
#define unit_le(a, b) _unit_op((a), <=, (b))
#define unit_ge(a, b) _unit_op((a), >=, (b))
#define unit_lt(a, b) _unit_op((a), <, (b))
#define unit_gt(a, b) _unit_op((a), >, (b))
/* shortcuts for string comparisons */
#define unit_str_eq(a, b) munit_assert_string_equal(a, b)
#define unit_str_ne(a, b) munit_assert_string_not_equal(a, b)
/* shortcuts for error-returning function call */
#define unit_ok(a) munit_assert_int((a), ==, 0)
#define unit_err(a, e) munit_assert_int((a), ==, (e))
#endif /* UNIT_H */