- Why is there a
zig test
subcommand? How are tests run by the build system different than tests run by thezig test command
? - What is the default test runner? Where is the source code for the default test runner?
- Are there alternative test runners / frameworks?
- Does the default test runner run tests in parallel? How?
- How does the test runner monitor its own sub-processes?
- How does the default test runner know when to cache a test result?
- How do I generate a test report? What test reports are supported?
- How does the test runner determine which tests should run?
- Why doesn’t my test run when I put it inside a struct?
- Can I run tests for other architectures? Using QEMU / wine?
- How do I skip certain tests based on the compile time information, like the target architecture?
- Why can’t I write a function definition within a test block? I can write a struct definition within a test block?
- What is the difference between a doc test and a regular test?
- Can I write a doc test for a symbol from another file?
- Why is there a test keyword?
- Why doesn’t a test block require an ending semi-colon (“;”)?
- Is a test a declaration, a function, an expression?
- Can I use compile time reflection on test declarations?
- Are tests optimized? What is the runtime safety of tests?
Quotes are from the docs.
-
During the build,
test
declarations found while resolving the given Zig source file are included for the default test runner to run and report on. -
Probably because your struct is never used, and therefore will not be resolved, as above.
-
Probably by using the
-Dtarget
flag, the same as for an executable. -
test { if (builtin.target.os.tag == .windows) return; // ... }
-
For the same reason you cannot write a function within a function.
-
A doctest, like a doc comment, serves as documentation for the associated declaration, and will appear in the generated documentation for the declaration.
-
I don’t see why you couldn’t.
-
To distinguish tests from regular functions.
-
Why would it?
-
Test declarations are similar to Functions: they have a return type and a block of code. The implicit return type of
test
is the Error Union Typeanyerror!void
, and it cannot be changed. When a Zig source file is not built using the zig test tool, the test declarations are omitted from the build. -
I don’t think so.
-
For
zig build test
, use-Doptimize
. Forzig test
, use-O
, both default toDebug
.
- or return error.SkipZigTest for explicit skip, it will be displayed in the test statistics (assuming default test runner is used):
if (builtin.target.os.tag == .windows) return error.SkipZigTest;
(1). zig test
tests only the specified file. This can be helpful when you are writing something that isn’t reachable from your root yet, but you want to test. zig build test
requires a test step and modules that it references. It will only run tests reachable from the modules you specify, like @n0s4 clarified in 8 and 9.
- I don’t think there are any alternative test runners (yet).
- As far as I have seen, it runs tests serially.
(6). I don’t think there is any caching of test results. All tests are run.
I’m pretty sure test results are cached, look at the --summary all
output after the tests have been ran the first time:
$ zig build test --summary all
Build Summary: 5/5 steps succeeded; 4/4 tests passed
test success
├─ run test 1 passed 927us MaxRSS:1M
│ └─ zig test Debug native cached 19ms MaxRSS:33M
└─ run test 3 passed 997us MaxRSS:1M
└─ zig test Debug native cached 19ms MaxRSS:33M
- I don’t know for sure, but I think that test results are always cached, and are only re-run when the source changes.
Yes. I work mainly with microcontrollers, which means all the development is cross platform. For the large amount of code that is platform independent, I modified the standard test runner to integrate onto my microcontroller platform, which was not difficult. The simple test runner is just that. Now I can run test cases both on the microcontroller, where it really counts, and on my development platform, where it is really convenient and at any optimization level. It’s stuff like this that makes working with Zig such a pleasure.
The build system invokes zig test
to run tests; it passes a special flag --listen=-
to enable a communication protocol between the build system and the test runner, then the test results are displayed by the build system.
It is the main
function of the test runner that is compiled with the tests.
The source of the test runner is lib/compiler/test_runner.zig
No, the tests run sequentially by the test runner.
The build system invokes the build steps in parallel.
The test runner is the sub-process that runs the tests.
The build system invokes the test runner.
The test runner does not cache anything.
The build system caches the test runner. Set the flag has_side_effects
in the test run build step to rerun the tests when there are no changes.
const run_tests = b.addRunArtifact(tests);
run_tests.has_side_effects = true;
The build system displays information about the tests according to the --summary
flag (new
and all
are the most useful values).
@import("builtin").test_functions
returns the list of test functions.
The list is populated from the tests of the root test module namespace and all other tests that are in referenced namespaces.
Because the struct namespace is not referenced.
Yes, the build system can run the test runner under various emulators:
-fdarling, -fno-darling Integration with system-installed Darling to
execute macOS programs on Linux hosts
(default: no)
-fqemu, -fno-qemu Integration with system-installed QEMU to execute
foreign-architecture programs on Linux hosts
(default: no)
--glibc-runtimes [path] Enhances QEMU integration by providing glibc built
for multiple foreign architectures, allowing
execution of non-native programs that link with glibc.
-frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
ARM64 macOS hosts. (default: no)
-fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
execute WASI binaries. (default: no)
-fwine, -fno-wine Integration with system-installed Wine to execute
Windows programs on Linux hosts. (default: no)
Return error.SkipZigTest
from your tests.
The doc tests are about defined symbols (functions or declarations) that are displayed as example code in the generated documentation.
Neither a function nor a declaration. It is a test (it is implemented as a function that accepts no arguments and returns anyerror!void
).
Yes, using @import("builtin").test_functions
. Note that this is not just a list of test names, you can also invoke the test function.
This is unfortunate. Some test errors are not always reproducible and can depend on some information (like current date-and-time) that is not part of any file. There should be a way to force-rerun tests when so desired.
You also could set the has_side_effects to true or false based on some build-option.
IMO tests should be deterministic, and not rely on any environment conditions. I don’t know what kind of system you would test that relies on system time, but if you had to test it then simulating the time via some form of dependency injection is what I would try to do.
It is common to have random data generation for fuzzing and property testing.
But that is still deterministic and repeatable once you store the seed value for the random generator that was used in the test run.
Yes, I agree. It is also best to have a mock for current time and anything else not deterministic that turns the tests to deterministic.
Unless you read from network or from dev/random
I would call this repeatable rather than deterministic though. It’s still a test which fails randomly, rather than always passing or always failing.
When stochastic tests like fuzzers fail, it’s good to make the failure into a deterministic test so that it fails immediately if the fix regresses.