I’ve missed it in release notes, can’t ind it here on ziggit and my googling fails.
To be honest, as much as I love Zig, it’s been unclear to me how to make sure all my tests will run, and I’ve sort of given up and settled on using this black magic incantation in my root.zig:
test {
// pardon my French...
std.testing.refAllDeclsRecursive(@This());
}
but in 0.16 it looks like it’s been banished?
I repent, may thee show me the righteous way?
It’s been deleted. You could, if you wanted, copy the definition of it from an old version of Zig.
To ensure that a test runs, the type (or file) containing it needs to be analyzed by the compiler during the compilation of the tests.
So this means that
// root.zig
const Struct = struct {
test {
if (true) return error.TestFailed;
}
};
test {
if (true) return;
}
will result in 1 test running with 0 failures.
One way to force analysis of a type (or file) during compilation of tests is to discard it in a test.
test "ref" {
_ = Struct; // 3 tests, 1 failure
}
Another way to force analysis is to reference it unconditionally:
// at container scope
comptime {
_ = Struct; // 2 tests, 1 failure (without the "ref" code)
}
Explicitly listing out the types you want referenced in your tests can be tedious, but it’s code that can be generated or written only once. std.testing.refAllDeclsRecursive has some slightly odd footguns: for example, if you accidentally redeclare a type from std, e.g. pub const Io = std.Io, suddenly when you run the tests you start running the tests for the Zig standard library, which is probably not a desirable result. I think this is one of the reasons it was removed.
10 Likes
To elaborate a bit on my context; I have utility lib which contains several modules:
const std = @import("std");
pub const argv = @import("argv.zig");
pub const dbg = @import("dbg.zig");
pub const env = @import("env.zig");
pub const exit = @import("exit.zig");
pub const fs = @import("fs.zig");
pub const global = @import("global.zig");
pub const mem = @import("mem.zig");
pub const prof = @import("prof.zig");
pub const sh = @import("sh.zig");
pub const store = @import("store.zig");
pub const termcolors = @import("termcolors.zig");
pub const testing = @import("testing.zig");
pub const timestamp = @import("timestamp.zig");
pub const txt = @import("txt.zig");
pub const typing = @import("typing.zig");
test {
std.testing.refAllDeclsRecursive(@This());
}
Now simply replacing refAllDeclsRecursive() with refAllDecls() just led to zig test reporting success without running any tests.
It looks like zig wants me to do:
const std = @import("std");
pub const argv = @import("argv.zig");
pub const dbg = @import("dbg.zig");
pub const env = @import("env.zig");
pub const exit = @import("exit.zig");
pub const fs = @import("fs.zig");
pub const global = @import("global.zig");
pub const mem = @import("mem.zig");
pub const prof = @import("prof.zig");
pub const sh = @import("sh.zig");
pub const store = @import("store.zig");
pub const termcolors = @import("termcolors.zig");
pub const testing = @import("testing.zig");
pub const timestamp = @import("timestamp.zig");
pub const txt = @import("txt.zig");
pub const typing = @import("typing.zig");
test {
std.testing.refAllDecls(argv);
std.testing.refAllDecls(dbg);
std.testing.refAllDecls(env);
std.testing.refAllDecls(exit);
std.testing.refAllDecls(fs);
std.testing.refAllDecls(global);
std.testing.refAllDecls(mem);
std.testing.refAllDecls(prof);
std.testing.refAllDecls(sh);
std.testing.refAllDecls(store);
std.testing.refAllDecls(termcolors);
std.testing.refAllDecls(testing);
std.testing.refAllDecls(timestamp);
std.testing.refAllDecls(txt);
std.testing.refAllDecls(typing);
}
which is rather tedious and error prone, if I add a module and forget to list it explicitly in root, then it’s untested.
That said, I understand that what I’m doing is not ideal: In my build.zig I’m exposing all these modules as one module, ie. single monolithic dependency. I only benefited from refAllDeclsRecursve() because of that.
Eventually I might want to split them into separate modules/dependencies; then a larger version of this tedium will need to be done inside build.zig.
Thanks, it seems that in my case, porting the code from old zig would be a good quick fix.
Or just accepting the tedium of having to add the reference into root.zig “twice” every time.
This should work fine, and it’s exactly what the standard library does. Here’s a simple example:
const std = @import("std");
pub const foo = @import("foo.zig");
pub const bar = @import("bar.zig");
test {
std.testing.refAllDecls(@This());
}
foo.zig:
test "something" {
return error.FooTestFailure;
}
bar.zig:
test "something else" {
return error.BarTestFailure;
}
> zig test src/root.zig
2/3 foo.test.something...FAIL (FooTestFailure)
C:\Users\Ryan\Programming\zig\tmp\init\src\foo.zig:2:5: 0x7ff77bd24b5e in test.something (test_zcu.obj)
return error.FooTestFailure;
^
3/3 bar.test.something else...FAIL (BarTestFailure)
C:\Users\Ryan\Programming\zig\tmp\init\src\bar.zig:2:5: 0x7ff77bd24b3e in test.something else (test_zcu.obj)
return error.BarTestFailure;
^
1 passed; 0 skipped; 2 failed.
With build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const mod = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
const mod_tests = b.addTest(.{
.root_module = mod,
});
const run_mod_tests = b.addRunArtifact(mod_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
}
> zig build test
test
└─ run test 1 pass, 2 fail (3 total)
error: 'foo.test.something' failed:
C:\Users\Ryan\Programming\zig\tmp\init\src\foo.zig:2:5: 0x7ff7cf8c4b5e in test.something (test_zcu.obj)
return error.FooTestFailure;
^
error: 'bar.test.something else' failed:
C:\Users\Ryan\Programming\zig\tmp\init\src\bar.zig:2:5: 0x7ff7cf8c4b3e in test.something else (test_zcu.obj)
return error.BarTestFailure;
^
My guess is that you don’t have any actual tests in those imported files and therefore you’d need to put a test { refAllDecls(@This()); } block in those imported files, too (or just generally ensure that the stuff you want to test is actually referenced in a test block in some way).
7 Likes