authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-03 14:51:11+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-03 14:52:11+00:00
logd024d9f00566968fd3aa85e0e6a0e8907016977c
treeafd1f92145e999b66704f2a2549ce99cf5438c81
parentc9fa8e46df21cdf66b291fb3dfdcef1b6a1d3cab
signaturelock-open Commit is signed but in an unrecognized format.

std.Build: crashes in the test runner are fatal errors


1 files changed, 45 insertions(+), 44 deletions(-)

lib/std/Build/Step/Run.zig+45-44
......@@ -1539,10 +1539,6 @@ fn runCommand(
15391539 }
15401540}
15411541
1542const EvalZigTestResult = struct {
1543 test_results: Step.TestResults,
1544 test_metadata: ?TestMetadata,
1545};
15461542const EvalGenericResult = struct {
15471543 term: std.process.Child.Term,
15481544 stdout: ?[]const u8,
......@@ -1605,21 +1601,8 @@ fn spawnChildAndCollect(
16051601
16061602 if (run.stdio == .zig_test) {
16071603 var timer = try std.time.Timer.start();
1608 const res = try evalZigTest(run, &child, options, fuzz_context);
1609 run.step.result_duration_ns = timer.read();
1610 run.step.test_results = res.test_results;
1611 if (res.test_metadata) |tm| {
1612 run.cached_test_metadata = tm.toCachedTestMetadata();
1613 if (options.web_server) |ws| {
1614 if (b.graph.time_report) {
1615 ws.updateTimeReportRunTest(
1616 run,
1617 &run.cached_test_metadata.?,
1618 tm.ns_per_test,
1619 );
1620 }
1621 }
1622 }
1604 defer run.step.result_duration_ns = timer.read();
1605 try evalZigTest(run, &child, options, fuzz_context);
16231606 return null;
16241607 } else {
16251608 const inherit = child.stdout_behavior == .Inherit or child.stderr_behavior == .Inherit;
......@@ -1675,7 +1658,7 @@ fn evalZigTest(
16751658 child: *std.process.Child,
16761659 options: Step.MakeOptions,
16771660 fuzz_context: ?FuzzContext,
1678) !EvalZigTestResult {
1661) !void {
16791662 const step_owner = run.step.owner;
16801663 const gpa = step_owner.allocator;
16811664 const arena = step_owner.allocator;
......@@ -1684,18 +1667,16 @@ fn evalZigTest(
16841667 // We will update this every time a child runs.
16851668 run.step.result_peak_rss = 0;
16861669
1687 var result: EvalZigTestResult = .{
1688 .test_results = .{
1689 .test_count = 0,
1690 .skip_count = 0,
1691 .fail_count = 0,
1692 .crash_count = 0,
1693 .timeout_count = 0,
1694 .leak_count = 0,
1695 .log_err_count = 0,
1696 },
1697 .test_metadata = null,
1670 var test_results: Step.TestResults = .{
1671 .test_count = 0,
1672 .skip_count = 0,
1673 .fail_count = 0,
1674 .crash_count = 0,
1675 .timeout_count = 0,
1676 .leak_count = 0,
1677 .log_err_count = 0,
16981678 };
1679 var test_metadata: ?TestMetadata = null;
16991680
17001681 while (true) {
17011682 try child.spawn(io);
......@@ -1721,8 +1702,8 @@ fn evalZigTest(
17211702 options,
17221703 fuzz_context,
17231704 &poller,
1724 &result.test_metadata,
1725 &result.test_results,
1705 &test_metadata,
1706 &test_results,
17261707 )) {
17271708 .write_failed => |err| {
17281709 // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured
......@@ -1741,8 +1722,9 @@ fn evalZigTest(
17411722 child.resource_usage_statistics.getMaxRss() orelse 0,
17421723 );
17431724
1744 try run.step.addError("unable to write stdin ({t}); test process unexpectedly {f}", .{ err, fmtTerm(term) });
1745 return result;
1725 // The individual unit test results are irrelevant: the test runner itself broke!
1726 // Fail immediately without populating `s.test_results`.
1727 return run.step.fail("unable to write stdin ({t}); test process unexpectedly {f}", .{ err, fmtTerm(term) });
17461728 },
17471729 .no_poll => |no_poll| {
17481730 // This might be a success (we requested exit and the child dutifully closed stdout) or
......@@ -1764,10 +1746,10 @@ fn evalZigTest(
17641746 if (no_poll.active_test_index) |test_index| {
17651747 // A test was running, so this is definitely a crash. Report it against that
17661748 // test, and continue to the next test.
1767 result.test_metadata.?.ns_per_test[test_index] = no_poll.ns_elapsed;
1768 result.test_results.crash_count += 1;
1749 test_metadata.?.ns_per_test[test_index] = no_poll.ns_elapsed;
1750 test_results.crash_count += 1;
17691751 try run.step.addError("'{s}' {f}{s}{s}", .{
1770 result.test_metadata.?.testName(test_index),
1752 test_metadata.?.testName(test_index),
17711753 fmtTerm(term),
17721754 if (stderr_owned.len != 0) " with stderr:\n" else "",
17731755 std.mem.trim(u8, stderr_owned, "\n"),
......@@ -1777,11 +1759,28 @@ fn evalZigTest(
17771759
17781760 // Report an error if the child terminated uncleanly or if we were still trying to run more tests.
17791761 run.step.result_stderr = stderr_owned;
1780 const tests_done = result.test_metadata != null and result.test_metadata.?.next_index == std.math.maxInt(u32);
1762 const tests_done = test_metadata != null and test_metadata.?.next_index == std.math.maxInt(u32);
17811763 if (!tests_done or !termMatches(.{ .Exited = 0 }, term)) {
1782 try run.step.addError("test process unexpectedly {f}", .{fmtTerm(term)});
1764 // The individual unit test results are irrelevant: the test runner itself broke!
1765 // Fail immediately without populating `s.test_results`.
1766 return run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)});
1767 }
1768
1769 // We're done with all of the tests! Commit the test results and return.
1770 run.step.test_results = test_results;
1771 if (test_metadata) |tm| {
1772 run.cached_test_metadata = tm.toCachedTestMetadata();
1773 if (options.web_server) |ws| {
1774 if (run.step.owner.graph.time_report) {
1775 ws.updateTimeReportRunTest(
1776 run,
1777 &run.cached_test_metadata.?,
1778 tm.ns_per_test,
1779 );
1780 }
1781 }
17831782 }
1784 return result;
1783 return;
17851784 },
17861785 .timeout => |timeout| {
17871786 const stderr = poller.reader(.stderr).buffered();
......@@ -1789,10 +1788,10 @@ fn evalZigTest(
17891788 if (timeout.active_test_index) |test_index| {
17901789 // A test was running. Report the timeout against that test, and continue on to
17911790 // the next test.
1792 result.test_metadata.?.ns_per_test[test_index] = timeout.ns_elapsed;
1793 result.test_results.timeout_count += 1;
1791 test_metadata.?.ns_per_test[test_index] = timeout.ns_elapsed;
1792 test_results.timeout_count += 1;
17941793 try run.step.addError("'{s}' timed out after {D}{s}{s}", .{
1795 result.test_metadata.?.testName(test_index),
1794 test_metadata.?.testName(test_index),
17961795 timeout.ns_elapsed,
17971796 if (stderr.len != 0) " with stderr:\n" else "",
17981797 std.mem.trim(u8, stderr, "\n"),
......@@ -1801,6 +1800,8 @@ fn evalZigTest(
18011800 }
18021801 // Just log an error and let the child be killed.
18031802 run.step.result_stderr = try arena.dupe(u8, stderr);
1803 // The individual unit test results in `results` are irrelevant: the test runner
1804 // is broken! Fail immediately without populating `s.test_results`.
18041805 return run.step.fail("test runner failed to respond for {D}", .{timeout.ns_elapsed});
18051806 },
18061807 }