ReviewOS

also looking at this

zig-utils/zig-test-framework

Add reproducible randomized test order

#29
Merged glennmichael123 wants to merge codex/issue-14-random-order into main
18 files +487 -42

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

README.mdmodified+17-3
Changes to README.md
@@ -9,6 +9,7 @@ A modern, feature-rich testing framework for Zig inspired by Jest, Vitest, and B
99- **Async Test Support** - Full async test execution with concurrent and sequential modes
1010- **Timeout Handling** - Configurable timeouts at test, suite, and global levels with extension support
1111- **Retries & Repeats** - Re-run failures, stress-run selected tests, and report flaky outcomes
12- **Reproducible Randomization** - Shuffle suites and tests with replayable seeds
1213- **Familiar API** - Describe/it syntax similar to Jest and Vitest
1314- **Rich Assertions** - Comprehensive assertion library with `.expect()` and matchers
1415- **Error Assertions** - `toThrow()` and `toThrowError()` for testing error handling
@@ -102,6 +103,8 @@ This will automatically discover and run all `*.test.zig` files in the `tests` d
102103- `--retry <N>` - Retry a failed test or discovered test file up to N times
103104- `--repeat <N>` - Require N successful repetitions of every selected test
104105- `--fail-on-flaky` - Return a failing exit status when a retry recovers a failure
106- `--shuffle` - Randomize suites, tests, or discovered files with a generated seed
107- `--seed <N>` - Replay a randomized order; supplying a seed implies `--shuffle`
105108- `--shard-index <N>` - Run one one-based file shard
106109- `--shard-count <N>` - Set the total number of file shards
107110- `--no-color` - Disable color in child Zig test processes
@@ -139,6 +142,10 @@ zig-test --test-dir tests --retry 2 --fail-on-flaky
139142# Stress-run matching tests 100 times
140143zig-test --test-dir tests --filter parser --repeat 100
141144
145# Expose order dependencies, then replay the exact order with the printed seed
146zig-test --test-dir tests --shuffle
147zig-test --test-dir tests --seed 8675309
148
142149# Run the second of four deterministic file shards
143150zig-test --test-dir tests --shard-index 2 --shard-count 4
144151```
@@ -185,7 +192,9 @@ precedence over configured values.
185192 "filter": "database",
186193 "retries": 2,
187194 "repeat": 10,
188 "fail_on_flaky": true
195 "fail_on_flaky": true,
196 "shuffle": true,
197 "seed": 8675309
189198 },
190199 "sharding": {
191200 "index": 1,
@@ -204,6 +213,8 @@ limitations as their CLI equivalents; unsupported combinations fail explicitly.
204213
205214See [Retries, repeats, and flaky tests](docs/retries-and-repeats.md) for attempt
206215semantics, exit codes, reporter output, and programmatic configuration.
216See [Randomized test order](docs/randomized-order.md) for replay and ordering
217guarantees.
207218
208219### Parallel execution semantics
209220
@@ -212,7 +223,8 @@ and set `n_jobs`. A configured value is the exact maximum number of active test
212223workers; omitting it uses the host's logical CPU count, with a one-worker
213224fallback when detection is unavailable.
214225
215Suites and nested suites are visited in declaration order. `beforeAll` and
226Suites and nested suites are visited in the selected order: declaration order
227by default, or deterministic seed order when randomization is enabled. `beforeAll` and
216228`afterAll` run once on the coordinating thread around that suite and its nested
217229suites. Direct tests within a suite may overlap. Their inherited `beforeEach`
218230and `afterEach` hooks run on the same worker as the test, so per-test hook order
@@ -220,7 +232,7 @@ is preserved but hooks from different tests may overlap. Hook state and the
220232allocator passed to tests must therefore be thread-safe.
221233
222234Reporter callbacks never run concurrently: test results are emitted in
223declaration order after each parallel batch, making output and final totals
235the selected order after each parallel batch, making output and final totals
224236deterministic. A parallel batch is already scheduled as a unit, so `bail` cannot
225237cancel tests that have started.
226238
@@ -665,6 +677,8 @@ zig-test --no-color
665677| `--retry <N>` | `--retries <N>` | Retry each failed test up to N times |
666678| `--repeat <N>` | | Require N successful runs of each selected test |
667679| `--fail-on-flaky` | | Fail the run when a retry recovers a failure |
680| `--shuffle` | | Randomize suite, test, and discovered-file order |
681| `--seed <N>` | | Replay a randomized order; implies `--shuffle` |
668682| `--verbose` | | Enable verbose output |
669683| `--quiet` | `-q` | Minimal output |
670684| `--no-color` | | Disable colored output |
docs/execution-pipeline.mdmodified+6-2
Changes to docs/execution-pipeline.md
@@ -34,6 +34,9 @@ reporters, including the configured JUnit output path.
3434 child process.
3535- Every logical result owns ordered attempt history. A failure followed by a
3636 successful retry is classified as `flaky`, distinct from an ordinary pass.
37- Optional randomized execution uses one run seed across the selected plan.
38 The seed is carried by reporters and `TestResults`; see
39 [Randomized test order](randomized-order.md) for mode-specific guarantees.
3740- Summaries always come from `TestResults`, rather than executor-local counters.
3841
3942## Extension points
@@ -53,10 +56,11 @@ model without coupling the execution API to JSON.
5356Existing programmatic APIs remain source compatible:
5457
5558- `TestRunner.init`, `runTests`, and `runTestsWithOptions` are unchanged.
56- `RunnerOptions` gains optional reporter, timeout, retry, repeat, and flaky-exit
59- `RunnerOptions` gains optional reporter, timeout, retry, repeat, flaky-exit,
60 shuffle, and seed
5761 fields with backward-compatible defaults.
5862- `runDiscoveredTests` and `LoaderOptions` remain available; optional reporter,
59 JUnit path, timeout, retry, repeat, flaky-exit, and writer fields default to
63 JUnit path, timeout, retry, repeat, flaky-exit, shuffle, seed, and writer fields default to
6064 the previous spec behavior.
6165
6266CLI hosts can inject a stdout writer with `reporter_writer`. Embedded callers
docs/json-event-protocol.mdmodified+4-2
Changes to docs/json-event-protocol.md
@@ -35,8 +35,8 @@ events are emitted in attempt order immediately before their `test_end`.
3535
3636| Type | Data fields |
3737| --- | --- |
38| `run_start` | `total` |
39| `run_end` | `total`, `passed`, `flaky`, `failed`, `skipped`, `duration_ns` |
38| `run_start` | `total`, optional `random_seed` |
39| `run_end` | `total`, `passed`, `flaky`, `failed`, `skipped`, `duration_ns`, optional `random_seed` |
4040| `suite_start`, `suite_end` | `name` |
4141| `test_start` | `name`, optional `suite` |
4242| `test_end` | `name`, optional `suite`, `status`, `duration_ns`, optional `error_message` |
@@ -51,6 +51,8 @@ Output streams are `stdout` or `stderr`.
5151
5252Optional fields are serialized as `null` when no value is available. Durations
5353are unsigned integer nanoseconds. Percentages are numbers from 0 through 100.
54When randomized ordering is enabled, both run events carry the reproducible
55unsigned integer seed.
5456
5557## Compatibility policy
5658
docs/randomized-order.mdadded+58-0
Changes to docs/randomized-order.md
@@ -0,0 +1,58 @@
1# Randomized test order
2
3Randomized order is opt-in. Use `--shuffle` to generate a seed for the run:
4
5```bash
6zig-test --shuffle
7```
8
9The selected reporter prints the generated seed at run start. If a test fails,
10the human-readable reporters print a replay command as well. Pass that seed to
11reproduce the order; `--seed` implies `--shuffle`:
12
13```bash
14zig-test --seed 8675309
15```
16
17Keep every other order-affecting option the same when replaying a run, including
18the test set, filter, shard, execution mode, and suite structure.
19
20## Ordering guarantees
21
22Without `--shuffle` or `--seed`, execution retains declaration or discovery
23order.
24
25In sequential programmatic execution, a seed deterministically orders root
26suites, each suite's direct tests, and its nested suites. Lifecycle hooks keep
27their normal scope: `beforeAll` and `afterAll` still wrap a suite, while
28`beforeEach` and `afterEach` still wrap their selected test.
29
30In parallel programmatic execution, the seed determines suite traversal, each
31worker queue, and serialized reporter output. Workers claim tests from that
32queue in order, but operating-system scheduling means wall-clock start and
33completion timing can differ between runs. Reporter order remains reproducible.
34
35In discovery mode, sharding selects files first and randomization orders the
36files inside the selected shard. The seed does not change shard membership.
37Retries and repetitions stay attached to their logical test or file and do not
38trigger another shuffle.
39
40## Metadata
41
42The seed is stored as `random_seed` in JSON `run_start` and `run_end` events,
43live UI events, and test-history files. JUnit output records it as a
44`random_seed` property on each test suite. A non-randomized run reports a null
45seed in JSON-compatible formats and omits the JUnit property.
46
47Programmatic callers can use `RunnerOptions.shuffle` and `RunnerOptions.seed`,
48or the matching `LoaderOptions` fields. JSON configuration accepts the same
49values under `test`:
50
51```json
52{
53 "test": {
54 "shuffle": true,
55 "seed": 8675309
56 }
57}
58```