-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
expose random seed to the test runner #17609
Comments
Does this affect all tests or would it only affect tests which touch the seed? I would expect tests which don't make use of the seed to remain cached unless the user forces a re-run of everything. |
I think this is a really good point. It makes sense to maximize the amount of caching that will still be happening. Would it be possible to still cache the "seeded" tests, and only use the cache if the seed is the same when re-run? |
if the seed is a decl/build_option then zig's lazy analysis then this will ensure the caching is preserved as only the tests using the seed will touch it and be affected by its changes |
yes yes please I love this soooo much, we need this a tone at TigerBeetle! Sorry for being overly enthusiastic here, it's just such a brilliant solution to the "reproduciblity vs true randomness" dilemma in randomized tests, beautiful!
I think, with the server setup, we can display the seed even when the tests crashes due to assert? That would be very helpful, as a lot of failures are not "clean", and inability to print the seed during panic makes it hard to implement this feature today "in userspace". |
Perhaps the seed could be provided in the build summary for test steps? Would that address the concern? If not could you provide an example shell session where the seed would be problematically missing? |
So what we do today is test "k_way_merge: fuzz" {
const seed = std.crypto.random.int(u64);
errdefer std.debug.print("\nTEST FAILED: seed = {}\n", .{seed});
var prng = std.rand.DefaultPrng.init(seed);
const random = prng.random();
// Body of the test //
} This works if the body of the tests looks like try std.testing.expectEqual(random.int(u8), 0); This doesn't work if your test looks rather like std.debug.assert(random.int(u8) == 0); Here's the printout for that case today: run test: error: thread 20615 panic: reached unreachable code
/home/matklad/p/tb/work/zig/lib/std/debug.zig:343:14: 0x227ecc in assert (test)
if (!ok) unreachable; // assertion failure
^
/home/matklad/p/tb/work/src/lsm/k_way_merge.zig:470:21: 0x22f22d in test.k_way_merge: fuzz (test)
std.debug.assert(random.int(u8) == 0);
^
/home/matklad/p/tb/work/zig/lib/test_runner.zig:100:29: 0x22d829 in mainServer (test)
test_fn.func() catch |err| switch (err) {
^
/home/matklad/p/tb/work/zig/lib/test_runner.zig:34:26: 0x22775a in main (test)
return mainServer() catch @panic("internal test runner failure");
^
/home/matklad/p/tb/work/zig/lib/std/start.zig:564:22: 0x2270c9 in main (test)
root.main();
^
???:?:?: 0x7f5dc0b76acd in ??? (libc.so.6)
Unwind information for `libc.so.6:0x7f5dc0b76acd` was not available, trace may be incomplete
run test: error: while executing test 'test.k_way_merge: fuzz', the following command terminated with signal 6 (expected exited with code 0):
/home/matklad/p/tb/work/zig-cache/o/485906540d0b75cec548d0afb33c79b5/test --listen=-
Build Summary: 6/8 steps succeeded; 1 failed; 2/2 tests passed (disable with --summary none)
test transitive failure
└─ run test failure
error: the following build command failed with exit code 1:
/home/matklad/p/tb/work/zig-cache/o/d668bfb7357d37d5edbea66cab8648cc/build /home/matklad/p/tb/work/zig/zig /home/matklad/p/tb/work /home/matklad/p/tb/work/zig-cache /home/matklad/.cache/zig test -Dtest-filter=k_way_merge: fuzz And most of the tests where we use randomization are of the second kind, where we don't check for specific expected values, but rather rely on internal assertions to verify invariant. (that is, the There are some unsatisfactory ways we could fix this today:
The fundamental problem here is that the seed is created inside the process, and, if that process is itself dying, you can't reliably print the seed. That's why I really love this solution, where the seed is explicitly passed in by the parent process --- the parent process should have the ability to print the seed even if the child process dies abruptly (or, indeed, if it gets into an infinite loop).
Ideally, I'd love to see the seed only when there's a test failure. It's too easy to "just print more helpful info", but this dynamics leads to software which prints pages of output by default, making the output useless due to bad singnal/noise ratio. |
Thanks for the example. I think this would already be solved by the original proposal. Example: const std = @import("std");
const assert = std.debug.assert;
test "example" {
assert(false);
}
The only edit I made to this copy-pasted output here is adding In your example, it would show up here:
|
In #14940, the Zig compiler passes a 32-bit integer
--seed
parameter to the build runner for shuffling dependency traversal order. If something goes wrong, the user can repeat the same operation with the same seed because the seed will be printed on the command line.This same concept can be useful for unit tests which want to use randomness but also need reproducibility.
This proposal is to expose the seed to std.testing so that unit tests can obtain it and use it.
This would also provide a way to resolve one area of confusion for users which is the fact that unit tests get cached. Some users are confused why unit tests would be cached. Explicitly having this seed would make everything make sense. If you give the same seed, then the test runs are considered cached. If you give a different seed, it's a new run.
Example code:
If this is implemented then it will be important for the seed to be displayed if the tests fail. This is already the case, as shown here:
This proposal would mean that last line would include, for example,
--seed 0xdeadbeef
.The text was updated successfully, but these errors were encountered: