ReviewOS

also looking at this

zig-utils/zig-test-framework

fix: enforce bounded parallel test execution

#21
Merged glennmichael123 wants to merge codex/issue-15-bounded-parallel into main
3 files +478 -176
src/test_runner.zigmodified+20-37
Changes to src/test_runner.zig
@@ -79,57 +79,35 @@ pub const TestRunner = struct {
7979 return RunnerError.NoTestsFound;
8080 }
8181
82 // Notify reporter of run start
83 try current_reporter.onRunStart(total_tests);
84
8582 // Run tests in parallel or sequential based on options
8683 if (self.options.parallel) {
8784 const parallel_opts = parallel.ParallelOptions{
8885 .enabled = true,
8986 .n_jobs = self.options.n_jobs,
87 .filter = self.options.filter,
9088 };
9189
92 _ = parallel.runTestsParallel(
90 const all_passed = try parallel.runTestsParallel(
9391 self.allocator,
9492 self.registry,
9593 current_reporter,
9694 parallel_opts,
97 ) catch |err| {
98 std.debug.print("Parallel execution failed: {any}, falling back to sequential\n", .{err});
99 // Fall back to sequential execution
100 for (self.registry.root_suites.items) |test_suite| {
101 try self.runSuite(test_suite, current_reporter);
102 if (self.options.bail and self.results.failed > 0) {
103 break;
104 }
105 }
106 return false;
107 };
108
109 // Update results from parallel execution
110 self.results.total = 0;
111 self.results.passed = 0;
112 self.results.failed = 0;
113 self.results.skipped = 0;
95 );
11496
11597 for (self.registry.root_suites.items) |test_suite| {
116 for (test_suite.tests.items) |test_case| {
117 self.results.total += 1;
118 switch (test_case.status) {
119 .passed => self.results.passed += 1,
120 .failed => self.results.failed += 1,
121 .skipped => self.results.skipped += 1,
122 else => {},
123 }
124 }
98 try self.addSuiteResults(test_suite);
12599 }
126 } else {
127 // Sequential execution (original behavior)
128 for (self.registry.root_suites.items) |test_suite| {
129 try self.runSuite(test_suite, current_reporter);
130 if (self.options.bail and self.results.failed > 0) {
131 break;
132 }
100
101 try stdout_writer.interface.flush();
102 return all_passed and self.results.failed == 0;
103 }
104
105 // Sequential execution (original behavior)
106 try current_reporter.onRunStart(total_tests);
107 for (self.registry.root_suites.items) |test_suite| {
108 try self.runSuite(test_suite, current_reporter);
109 if (self.options.bail and self.results.failed > 0) {
110 break;
133111 }
134112 }
135113
@@ -142,6 +120,11 @@ pub const TestRunner = struct {
142120 return self.results.failed == 0;
143121 }
144122
123 fn addSuiteResults(self: *Self, test_suite: *suite.TestSuite) !void {
124 for (test_suite.tests.items) |*test_case| try self.results.addTest(test_case);
125 for (test_suite.suites.items) |nested| try self.addSuiteResults(nested);
126 }
127
145128 /// Run a single test suite
146129 fn runSuite(self: *Self, test_suite: *suite.TestSuite, rep: *reporter_mod.Reporter) !void {
147130 // Skip if marked as skip or if has_only and this isn't marked as only