From 33c79841832b575c2dacc7ec07ac9536798be4e3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Jul 2024 16:03:11 -0700 Subject: [PATCH] add std.testing.random_seed closes #17609 --- lib/compiler/build_runner.zig | 5 ++--- lib/compiler/test_runner.zig | 3 +++ lib/std/Build.zig | 1 + lib/std/Build/Step/Run.zig | 7 ++++++- lib/std/Random/ziggurat.zig | 4 +++- lib/std/hash_map.zig | 4 ++-- lib/std/heap/arena_allocator.zig | 2 +- lib/std/math/big/rational.zig | 2 +- lib/std/os/linux/IoUring.zig | 4 ++-- lib/std/priority_dequeue.zig | 6 +++--- lib/std/sort.zig | 4 ++-- lib/std/testing.zig | 6 +++++- src/main.zig | 5 ++++- 13 files changed, 35 insertions(+), 18 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 1b13881586..f06ae7d4b9 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -97,7 +97,6 @@ pub fn main() !void { var max_rss: u64 = 0; var skip_oom_steps = false; var color: Color = .auto; - var seed: u32 = 0; var prominent_compile_errors = false; var help_menu = false; var steps_menu = false; @@ -188,7 +187,7 @@ pub fn main() !void { } else if (mem.eql(u8, arg, "--seed")) { const next_arg = nextArg(args, &arg_idx) orelse fatalWithHint("expected u32 after '{s}'", .{arg}); - seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| { + graph.random_seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| { fatal("unable to parse seed '{s}' as unsigned 32-bit integer: {s}\n", .{ next_arg, @errorName(err), }); @@ -371,7 +370,7 @@ pub fn main() !void { } const gpa = arena; - prepare(gpa, arena, builder, targets.items, &run, seed) catch |err| switch (err) { + prepare(gpa, arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) { error.UncleanExit => process.exit(1), else => return err, }; diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index e05bc957b2..478728455b 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -24,6 +24,9 @@ pub fn main() void { for (args[1..]) |arg| { if (std.mem.eql(u8, arg, "--listen=-")) { listen = true; + } else if (std.mem.startsWith(u8, arg, "--seed=")) { + std.testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch + @panic("unable to parse --seed command line argument"); } else { @panic("unrecognized command line argument"); } diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 20c79176e4..f76e3263cb 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -121,6 +121,7 @@ pub const Graph = struct { /// Information about the native target. Computed before build() is invoked. host: ResolvedTarget, incremental: ?bool = null, + random_seed: u32 = 0, }; const AvailableDeps = []const struct { []const u8, []const u8 }; diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 0712ce8083..ff0c5e418a 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -185,8 +185,13 @@ pub fn setName(run: *Run, name: []const u8) void { } pub fn enableTestRunnerMode(run: *Run) void { + const b = run.step.owner; + const arena = b.allocator; run.stdio = .zig_test; - run.addArgs(&.{"--listen=-"}); + run.addArgs(&.{ + std.fmt.allocPrint(arena, "--seed=0x{x}", .{b.graph.random_seed}) catch @panic("OOM"), + "--listen=-", + }); } pub fn addArtifactArg(run: *Run, artifact: *Step.Compile) void { diff --git a/lib/std/Random/ziggurat.zig b/lib/std/Random/ziggurat.zig index 2bed6a065b..682fef07f9 100644 --- a/lib/std/Random/ziggurat.zig +++ b/lib/std/Random/ziggurat.zig @@ -126,7 +126,9 @@ pub fn norm_zero_case(random: Random, u: f64) f64 { } } -test "normal dist sanity" { +test "normal dist smoke test" { + // Hardcode 0 as the seed because it's possible a seed exists that fails + // this test. var prng = Random.DefaultPrng.init(0); const random = prng.random(); diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index d7d58fd7d8..e3b3e58814 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -1935,7 +1935,7 @@ test "put and remove loop in random order" { while (i < size) : (i += 1) { try keys.append(i); } - var prng = std.Random.DefaultPrng.init(0); + var prng = std.Random.DefaultPrng.init(std.testing.random_seed); const random = prng.random(); while (i < iterations) : (i += 1) { @@ -1967,7 +1967,7 @@ test "remove one million elements in random order" { keys.append(i) catch unreachable; } - var prng = std.Random.DefaultPrng.init(0); + var prng = std.Random.DefaultPrng.init(std.testing.random_seed); const random = prng.random(); random.shuffle(u32, keys.items); diff --git a/lib/std/heap/arena_allocator.zig b/lib/std/heap/arena_allocator.zig index 8ba3cc9826..3cff6b439f 100644 --- a/lib/std/heap/arena_allocator.zig +++ b/lib/std/heap/arena_allocator.zig @@ -250,7 +250,7 @@ test "reset with preheating" { var arena_allocator = ArenaAllocator.init(std.testing.allocator); defer arena_allocator.deinit(); // provides some variance in the allocated data - var rng_src = std.Random.DefaultPrng.init(19930913); + var rng_src = std.Random.DefaultPrng.init(std.testing.random_seed); const random = rng_src.random(); var rounds: usize = 25; while (rounds > 0) { diff --git a/lib/std/math/big/rational.zig b/lib/std/math/big/rational.zig index 774e0c1314..bbfea9de74 100644 --- a/lib/std/math/big/rational.zig +++ b/lib/std/math/big/rational.zig @@ -594,7 +594,7 @@ test "toFloat" { test "set/to Float round-trip" { var a = try Rational.init(testing.allocator); defer a.deinit(); - var prng = std.Random.DefaultPrng.init(0x5EED); + var prng = std.Random.DefaultPrng.init(std.testing.random_seed); const random = prng.random(); var i: usize = 0; while (i < 512) : (i += 1) { diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index 50d87f3722..c7717524a9 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -3995,7 +3995,7 @@ test "ring mapped buffers recv" { defer fds.close(); // for random user_data in sqe/cqe - var Rnd = std.Random.DefaultPrng.init(0); + var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed); var rnd = Rnd.random(); var round: usize = 4; // repeat send/recv cycle round times @@ -4081,7 +4081,7 @@ test "ring mapped buffers multishot recv" { defer fds.close(); // for random user_data in sqe/cqe - var Rnd = std.Random.DefaultPrng.init(0); + var Rnd = std.Random.DefaultPrng.init(std.testing.random_seed); var rnd = Rnd.random(); var round: usize = 4; // repeat send/recv cycle round times diff --git a/lib/std/priority_dequeue.zig b/lib/std/priority_dequeue.zig index 7b03beb170..520288be78 100644 --- a/lib/std/priority_dequeue.zig +++ b/lib/std/priority_dequeue.zig @@ -866,7 +866,7 @@ test "shrinkAndFree" { } test "fuzz testing min" { - var prng = std.Random.DefaultPrng.init(0x12345678); + var prng = std.Random.DefaultPrng.init(std.testing.random_seed); const random = prng.random(); const test_case_count = 100; @@ -895,7 +895,7 @@ fn fuzzTestMin(rng: std.Random, comptime queue_size: usize) !void { } test "fuzz testing max" { - var prng = std.Random.DefaultPrng.init(0x87654321); + var prng = std.Random.DefaultPrng.init(std.testing.random_seed); const random = prng.random(); const test_case_count = 100; @@ -924,7 +924,7 @@ fn fuzzTestMax(rng: std.Random, queue_size: usize) !void { } test "fuzz testing min and max" { - var prng = std.Random.DefaultPrng.init(0x87654321); + var prng = std.Random.DefaultPrng.init(std.testing.random_seed); const random = prng.random(); const test_case_count = 100; diff --git a/lib/std/sort.zig b/lib/std/sort.zig index ba7b0f0d89..d3e6f9c16d 100644 --- a/lib/std/sort.zig +++ b/lib/std/sort.zig @@ -223,7 +223,7 @@ test "stable sort" { } test "stable sort fuzz testing" { - var prng = std.Random.DefaultPrng.init(0x12345678); + var prng = std.Random.DefaultPrng.init(std.testing.random_seed); const random = prng.random(); const test_case_count = 10; @@ -408,7 +408,7 @@ test "sort with context in the middle of a slice" { } test "sort fuzz testing" { - var prng = std.Random.DefaultPrng.init(0x12345678); + var prng = std.Random.DefaultPrng.init(std.testing.random_seed); const random = prng.random(); const test_case_count = 10; diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 190182db70..6f29fbd613 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1,8 +1,12 @@ const std = @import("std.zig"); const builtin = @import("builtin"); - +const assert = std.debug.assert; const math = std.math; +/// Provides deterministic randomness in unit tests. +/// Initialized on startup. Read-only after that. +pub var random_seed: u32 = 0; + pub const FailingAllocator = @import("testing/failing_allocator.zig").FailingAllocator; /// This should only be used in temporary test programs. diff --git a/src/main.zig b/src/main.zig index ae34b083ae..07e8d4eaf8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4282,7 +4282,10 @@ fn runOrTest( defer argv.deinit(); if (test_exec_args.len == 0) { - try argv.append(exe_path); + try argv.appendSlice(&.{ + exe_path, + try std.fmt.allocPrint(arena, "--seed=0x{x}", .{std.crypto.random.int(u32)}), + }); } else { for (test_exec_args) |arg| { try argv.append(arg orelse exe_path);