| ... | ... | @@ -630,6 +630,8 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void { |
| 630 | 630 | |
| 631 | 631 | pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath { |
| 632 | 632 | assert(run.stdio != .inherit); |
| 633 | assert(run.stdio != .zig_test); |
| 634 | |
| 633 | 635 | const b = run.step.owner; |
| 634 | 636 | |
| 635 | 637 | if (run.captured_stderr) |captured| return .{ .generated = .{ .file = &captured.output.generated_file } }; |
| ... | ... | @@ -649,6 +651,8 @@ pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa |
| 649 | 651 | |
| 650 | 652 | pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath { |
| 651 | 653 | assert(run.stdio != .inherit); |
| 654 | assert(run.stdio != .zig_test); |
| 655 | |
| 652 | 656 | const b = run.step.owner; |
| 653 | 657 | |
| 654 | 658 | if (run.captured_stdout) |captured| return .{ .generated = .{ .file = &captured.output.generated_file } }; |
| ... | ... | @@ -1224,7 +1228,7 @@ fn runCommand( |
| 1224 | 1228 | |
| 1225 | 1229 | var env_map = run.env_map orelse &b.graph.env_map; |
| 1226 | 1230 | |
| 1227 | | const result = spawnChildAndCollect(run, argv, env_map, has_side_effects, options, fuzz_context) catch |err| term: { |
| 1231 | const opt_generic_result = spawnChildAndCollect(run, argv, env_map, has_side_effects, options, fuzz_context) catch |err| term: { |
| 1228 | 1232 | // InvalidExe: cpu arch mismatch |
| 1229 | 1233 | // FileNotFound: can happen with a wrong dynamic linker path |
| 1230 | 1234 | if (err == error.InvalidExe or err == error.FileNotFound) interpret: { |
| ... | ... | @@ -1365,34 +1369,34 @@ fn runCommand( |
| 1365 | 1369 | |
| 1366 | 1370 | break :term spawnChildAndCollect(run, interp_argv.items, env_map, has_side_effects, options, fuzz_context) catch |e| { |
| 1367 | 1371 | if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; |
| 1368 | | |
| 1372 | if (e == error.MakeFailed) return error.MakeFailed; // error already reported |
| 1369 | 1373 | return step.fail("unable to spawn interpreter {s}: {s}", .{ |
| 1370 | 1374 | interp_argv.items[0], @errorName(e), |
| 1371 | 1375 | }); |
| 1372 | 1376 | }; |
| 1373 | 1377 | } |
| 1378 | if (err == error.MakeFailed) return error.MakeFailed; // error already reported |
| 1374 | 1379 | |
| 1375 | 1380 | return step.fail("failed to spawn and capture stdio from {s}: {s}", .{ argv[0], @errorName(err) }); |
| 1376 | 1381 | }; |
| 1377 | 1382 | |
| 1378 | | step.result_duration_ns = result.elapsed_ns; |
| 1379 | | step.result_peak_rss = result.peak_rss; |
| 1380 | | step.test_results = result.stdio.test_results; |
| 1381 | | if (result.stdio.test_metadata) |tm| { |
| 1382 | | run.cached_test_metadata = tm.toCachedTestMetadata(); |
| 1383 | | if (options.web_server) |ws| ws.updateTimeReportRunTest( |
| 1384 | | run, |
| 1385 | | &run.cached_test_metadata.?, |
| 1386 | | tm.ns_per_test, |
| 1387 | | ); |
| 1388 | | } |
| 1389 | | |
| 1390 | 1383 | const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; |
| 1391 | 1384 | |
| 1392 | | if (fuzz_context != null) { |
| 1393 | | try step.handleChildProcessTerm(result.term, cwd, final_argv); |
| 1385 | const generic_result = opt_generic_result orelse { |
| 1386 | assert(run.stdio == .zig_test); |
| 1387 | // Specific errors have already been reported. All we need to do is detect those and |
| 1388 | // report the general "test failed" error, which includes the command argv. |
| 1389 | if (!step.test_results.isSuccess() or step.result_error_msgs.items.len > 0) { |
| 1390 | return step.fail( |
| 1391 | "the following test command failed:\n{s}", |
| 1392 | .{try Step.allocPrintCmd(arena, cwd, final_argv)}, |
| 1393 | ); |
| 1394 | } |
| 1394 | 1395 | return; |
| 1395 | | } |
| 1396 | }; |
| 1397 | |
| 1398 | assert(fuzz_context == null); |
| 1399 | assert(run.stdio != .zig_test); |
| 1396 | 1400 | |
| 1397 | 1401 | // Capture stdout and stderr to GeneratedFile objects. |
| 1398 | 1402 | const Stream = struct { |
| ... | ... | @@ -1402,11 +1406,11 @@ fn runCommand( |
| 1402 | 1406 | for ([_]Stream{ |
| 1403 | 1407 | .{ |
| 1404 | 1408 | .captured = run.captured_stdout, |
| 1405 | | .bytes = result.stdio.stdout, |
| 1409 | .bytes = generic_result.stdout, |
| 1406 | 1410 | }, |
| 1407 | 1411 | .{ |
| 1408 | 1412 | .captured = run.captured_stderr, |
| 1409 | | .bytes = result.stdio.stderr, |
| 1413 | .bytes = generic_result.stderr, |
| 1410 | 1414 | }, |
| 1411 | 1415 | }) |stream| { |
| 1412 | 1416 | if (stream.captured) |captured| { |
| ... | ... | @@ -1436,9 +1440,10 @@ fn runCommand( |
| 1436 | 1440 | } |
| 1437 | 1441 | |
| 1438 | 1442 | switch (run.stdio) { |
| 1443 | .zig_test => unreachable, |
| 1439 | 1444 | .check => |checks| for (checks.items) |check| switch (check) { |
| 1440 | 1445 | .expect_stderr_exact => |expected_bytes| { |
| 1441 | | if (!mem.eql(u8, expected_bytes, result.stdio.stderr.?)) { |
| 1446 | if (!mem.eql(u8, expected_bytes, generic_result.stderr.?)) { |
| 1442 | 1447 | return step.fail( |
| 1443 | 1448 | \\ |
| 1444 | 1449 | \\========= expected this stderr: ========= |
| ... | ... | @@ -1449,13 +1454,13 @@ fn runCommand( |
| 1449 | 1454 | \\{s} |
| 1450 | 1455 | , .{ |
| 1451 | 1456 | expected_bytes, |
| 1452 | | result.stdio.stderr.?, |
| 1457 | generic_result.stderr.?, |
| 1453 | 1458 | try Step.allocPrintCmd(arena, cwd, final_argv), |
| 1454 | 1459 | }); |
| 1455 | 1460 | } |
| 1456 | 1461 | }, |
| 1457 | 1462 | .expect_stderr_match => |match| { |
| 1458 | | if (mem.indexOf(u8, result.stdio.stderr.?, match) == null) { |
| 1463 | if (mem.indexOf(u8, generic_result.stderr.?, match) == null) { |
| 1459 | 1464 | return step.fail( |
| 1460 | 1465 | \\ |
| 1461 | 1466 | \\========= expected to find in stderr: ========= |
| ... | ... | @@ -1466,13 +1471,13 @@ fn runCommand( |
| 1466 | 1471 | \\{s} |
| 1467 | 1472 | , .{ |
| 1468 | 1473 | match, |
| 1469 | | result.stdio.stderr.?, |
| 1474 | generic_result.stderr.?, |
| 1470 | 1475 | try Step.allocPrintCmd(arena, cwd, final_argv), |
| 1471 | 1476 | }); |
| 1472 | 1477 | } |
| 1473 | 1478 | }, |
| 1474 | 1479 | .expect_stdout_exact => |expected_bytes| { |
| 1475 | | if (!mem.eql(u8, expected_bytes, result.stdio.stdout.?)) { |
| 1480 | if (!mem.eql(u8, expected_bytes, generic_result.stdout.?)) { |
| 1476 | 1481 | return step.fail( |
| 1477 | 1482 | \\ |
| 1478 | 1483 | \\========= expected this stdout: ========= |
| ... | ... | @@ -1483,13 +1488,13 @@ fn runCommand( |
| 1483 | 1488 | \\{s} |
| 1484 | 1489 | , .{ |
| 1485 | 1490 | expected_bytes, |
| 1486 | | result.stdio.stdout.?, |
| 1491 | generic_result.stdout.?, |
| 1487 | 1492 | try Step.allocPrintCmd(arena, cwd, final_argv), |
| 1488 | 1493 | }); |
| 1489 | 1494 | } |
| 1490 | 1495 | }, |
| 1491 | 1496 | .expect_stdout_match => |match| { |
| 1492 | | if (mem.indexOf(u8, result.stdio.stdout.?, match) == null) { |
| 1497 | if (mem.indexOf(u8, generic_result.stdout.?, match) == null) { |
| 1493 | 1498 | return step.fail( |
| 1494 | 1499 | \\ |
| 1495 | 1500 | \\========= expected to find in stdout: ========= |
| ... | ... | @@ -1500,69 +1505,45 @@ fn runCommand( |
| 1500 | 1505 | \\{s} |
| 1501 | 1506 | , .{ |
| 1502 | 1507 | match, |
| 1503 | | result.stdio.stdout.?, |
| 1508 | generic_result.stdout.?, |
| 1504 | 1509 | try Step.allocPrintCmd(arena, cwd, final_argv), |
| 1505 | 1510 | }); |
| 1506 | 1511 | } |
| 1507 | 1512 | }, |
| 1508 | 1513 | .expect_term => |expected_term| { |
| 1509 | | if (!termMatches(expected_term, result.term)) { |
| 1514 | if (!termMatches(expected_term, generic_result.term)) { |
| 1510 | 1515 | return step.fail("the following command {f} (expected {f}):\n{s}", .{ |
| 1511 | | fmtTerm(result.term), |
| 1516 | fmtTerm(generic_result.term), |
| 1512 | 1517 | fmtTerm(expected_term), |
| 1513 | 1518 | try Step.allocPrintCmd(arena, cwd, final_argv), |
| 1514 | 1519 | }); |
| 1515 | 1520 | } |
| 1516 | 1521 | }, |
| 1517 | 1522 | }, |
| 1518 | | .zig_test => { |
| 1519 | | const prefix: []const u8 = p: { |
| 1520 | | if (result.stdio.test_metadata) |tm| { |
| 1521 | | if (tm.next_index > 0 and tm.next_index <= tm.names.len) { |
| 1522 | | const name = tm.testName(tm.next_index - 1); |
| 1523 | | break :p b.fmt("while executing test '{s}', ", .{name}); |
| 1524 | | } |
| 1525 | | } |
| 1526 | | break :p ""; |
| 1527 | | }; |
| 1528 | | const expected_term: std.process.Child.Term = .{ .Exited = 0 }; |
| 1529 | | if (!termMatches(expected_term, result.term)) { |
| 1530 | | return step.fail("{s}the following command {f} (expected {f}):\n{s}", .{ |
| 1531 | | prefix, |
| 1532 | | fmtTerm(result.term), |
| 1533 | | fmtTerm(expected_term), |
| 1534 | | try Step.allocPrintCmd(arena, cwd, final_argv), |
| 1535 | | }); |
| 1536 | | } |
| 1537 | | if (!result.stdio.test_results.isSuccess()) { |
| 1538 | | return step.fail( |
| 1539 | | "{s}the following test command failed:\n{s}", |
| 1540 | | .{ prefix, try Step.allocPrintCmd(arena, cwd, final_argv) }, |
| 1541 | | ); |
| 1542 | | } |
| 1543 | | }, |
| 1544 | 1523 | else => { |
| 1545 | 1524 | // On failure, print stderr if captured. |
| 1546 | | const bad_exit = switch (result.term) { |
| 1525 | const bad_exit = switch (generic_result.term) { |
| 1547 | 1526 | .Exited => |code| code != 0, |
| 1548 | 1527 | .Signal, .Stopped, .Unknown => true, |
| 1549 | 1528 | }; |
| 1550 | 1529 | |
| 1551 | | if (bad_exit) if (result.stdio.stderr) |err| { |
| 1530 | if (bad_exit) if (generic_result.stderr) |err| { |
| 1552 | 1531 | try step.addError("stderr:\n{s}", .{err}); |
| 1553 | 1532 | }; |
| 1554 | 1533 | |
| 1555 | | try step.handleChildProcessTerm(result.term, cwd, final_argv); |
| 1534 | try step.handleChildProcessTerm(generic_result.term, cwd, final_argv); |
| 1556 | 1535 | }, |
| 1557 | 1536 | } |
| 1558 | 1537 | } |
| 1559 | 1538 | |
| 1560 | | const ChildProcResult = struct { |
| 1539 | const EvalZigTestResult = struct { |
| 1540 | test_results: Step.TestResults, |
| 1541 | test_metadata: ?TestMetadata, |
| 1542 | }; |
| 1543 | const EvalGenericResult = struct { |
| 1561 | 1544 | term: std.process.Child.Term, |
| 1562 | | elapsed_ns: u64, |
| 1563 | | peak_rss: usize, |
| 1564 | | |
| 1565 | | stdio: StdIoResult, |
| 1545 | stdout: ?[]const u8, |
| 1546 | stderr: ?[]const u8, |
| 1566 | 1547 | }; |
| 1567 | 1548 | |
| 1568 | 1549 | fn spawnChildAndCollect( |
| ... | ... | @@ -1572,7 +1553,7 @@ fn spawnChildAndCollect( |
| 1572 | 1553 | has_side_effects: bool, |
| 1573 | 1554 | options: Step.MakeOptions, |
| 1574 | 1555 | fuzz_context: ?FuzzContext, |
| 1575 | | ) !ChildProcResult { |
| 1556 | ) !?EvalGenericResult { |
| 1576 | 1557 | const b = run.step.owner; |
| 1577 | 1558 | const arena = b.allocator; |
| 1578 | 1559 | |
| ... | ... | @@ -1613,121 +1594,237 @@ fn spawnChildAndCollect( |
| 1613 | 1594 | child.stdin_behavior = .Pipe; |
| 1614 | 1595 | } |
| 1615 | 1596 | |
| 1616 | | const inherit = child.stdout_behavior == .Inherit or child.stderr_behavior == .Inherit; |
| 1617 | | |
| 1618 | | if (run.stdio != .zig_test and !run.disable_zig_progress and !inherit) { |
| 1619 | | child.progress_node = options.progress_node; |
| 1620 | | } |
| 1621 | | |
| 1622 | | const term, const result, const elapsed_ns = t: { |
| 1597 | if (run.stdio == .zig_test) { |
| 1598 | var timer = try std.time.Timer.start(); |
| 1599 | const res = try evalZigTest(run, &child, options, fuzz_context); |
| 1600 | run.step.result_duration_ns = timer.read(); |
| 1601 | run.step.test_results = res.test_results; |
| 1602 | if (res.test_metadata) |tm| { |
| 1603 | run.cached_test_metadata = tm.toCachedTestMetadata(); |
| 1604 | if (options.web_server) |ws| ws.updateTimeReportRunTest( |
| 1605 | run, |
| 1606 | &run.cached_test_metadata.?, |
| 1607 | tm.ns_per_test, |
| 1608 | ); |
| 1609 | } |
| 1610 | return null; |
| 1611 | } else { |
| 1612 | const inherit = child.stdout_behavior == .Inherit or child.stderr_behavior == .Inherit; |
| 1613 | if (!run.disable_zig_progress and !inherit) { |
| 1614 | child.progress_node = options.progress_node; |
| 1615 | } |
| 1623 | 1616 | if (inherit) std.debug.lockStdErr(); |
| 1624 | 1617 | defer if (inherit) std.debug.unlockStdErr(); |
| 1618 | var timer = try std.time.Timer.start(); |
| 1619 | const res = try evalGeneric(run, &child); |
| 1620 | run.step.result_duration_ns = timer.read(); |
| 1621 | return .{ .term = res.term, .stdout = res.stdout, .stderr = res.stderr }; |
| 1622 | } |
| 1623 | } |
| 1624 | |
| 1625 | const StdioPollEnum = enum { stdout, stderr }; |
| 1626 | |
| 1627 | fn evalZigTest( |
| 1628 | run: *Run, |
| 1629 | child: *std.process.Child, |
| 1630 | options: Step.MakeOptions, |
| 1631 | fuzz_context: ?FuzzContext, |
| 1632 | ) !EvalZigTestResult { |
| 1633 | const gpa = run.step.owner.allocator; |
| 1634 | const arena = run.step.owner.allocator; |
| 1635 | |
| 1636 | // We will update this every time a child runs. |
| 1637 | run.step.result_peak_rss = 0; |
| 1638 | |
| 1639 | var result: EvalZigTestResult = .{ |
| 1640 | .test_results = .{ |
| 1641 | .test_count = 0, |
| 1642 | .skip_count = 0, |
| 1643 | .fail_count = 0, |
| 1644 | .crash_count = 0, |
| 1645 | .timeout_count = 0, |
| 1646 | .leak_count = 0, |
| 1647 | .log_err_count = 0, |
| 1648 | }, |
| 1649 | .test_metadata = null, |
| 1650 | }; |
| 1625 | 1651 | |
| 1652 | while (true) { |
| 1626 | 1653 | try child.spawn(); |
| 1627 | | errdefer { |
| 1654 | var poller = std.Io.poll(gpa, StdioPollEnum, .{ |
| 1655 | .stdout = child.stdout.?, |
| 1656 | .stderr = child.stderr.?, |
| 1657 | }); |
| 1658 | var child_killed = false; |
| 1659 | defer if (!child_killed) { |
| 1628 | 1660 | _ = child.kill() catch {}; |
| 1629 | | } |
| 1661 | poller.deinit(); |
| 1662 | run.step.result_peak_rss = @max( |
| 1663 | run.step.result_peak_rss, |
| 1664 | child.resource_usage_statistics.getMaxRss() orelse 0, |
| 1665 | ); |
| 1666 | }; |
| 1630 | 1667 | |
| 1631 | | // We need to report `error.InvalidExe` *now* if applicable. |
| 1632 | 1668 | try child.waitForSpawn(); |
| 1633 | 1669 | |
| 1634 | | var timer = try std.time.Timer.start(); |
| 1670 | switch (try pollZigTest( |
| 1671 | run, |
| 1672 | child, |
| 1673 | options, |
| 1674 | fuzz_context, |
| 1675 | &poller, |
| 1676 | &result.test_metadata, |
| 1677 | &result.test_results, |
| 1678 | )) { |
| 1679 | .write_failed => |err| { |
| 1680 | // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured |
| 1681 | // all available stderr to make our error output as useful as possible. |
| 1682 | while (try poller.poll()) {} |
| 1683 | run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered()); |
| 1684 | |
| 1685 | // Clean up everything and wait for the child to exit. |
| 1686 | child.stdin.?.close(); |
| 1687 | child.stdin = null; |
| 1688 | poller.deinit(); |
| 1689 | child_killed = true; |
| 1690 | const term = try child.wait(); |
| 1691 | run.step.result_peak_rss = @max( |
| 1692 | run.step.result_peak_rss, |
| 1693 | child.resource_usage_statistics.getMaxRss() orelse 0, |
| 1694 | ); |
| 1635 | 1695 | |
| 1636 | | const result = if (run.stdio == .zig_test) |
| 1637 | | try evalZigTest(run, &child, options, fuzz_context) |
| 1638 | | else |
| 1639 | | try evalGeneric(run, &child); |
| 1696 | try run.step.addError("unable to write stdin ({t}); test process unexpectedly {f}", .{ err, fmtTerm(term) }); |
| 1697 | return result; |
| 1698 | }, |
| 1699 | .no_poll => |no_poll| { |
| 1700 | // This might be a success (we requested exit and the child dutifully closed stdout) or |
| 1701 | // a crash of some kind. Either way, the child will terminate by itself -- wait for it. |
| 1702 | const stderr_owned = try arena.dupe(u8, poller.reader(.stderr).buffered()); |
| 1703 | poller.reader(.stderr).tossBuffered(); |
| 1704 | |
| 1705 | // Clean up everything and wait for the child to exit. |
| 1706 | child.stdin.?.close(); |
| 1707 | child.stdin = null; |
| 1708 | poller.deinit(); |
| 1709 | child_killed = true; |
| 1710 | const term = try child.wait(); |
| 1711 | run.step.result_peak_rss = @max( |
| 1712 | run.step.result_peak_rss, |
| 1713 | child.resource_usage_statistics.getMaxRss() orelse 0, |
| 1714 | ); |
| 1640 | 1715 | |
| 1641 | | break :t .{ try child.wait(), result, timer.read() }; |
| 1642 | | }; |
| 1716 | if (no_poll.active_test_index) |test_index| { |
| 1717 | // A test was running, so this is definitely a crash. Report it against that |
| 1718 | // test, and continue to the next test. |
| 1719 | result.test_metadata.?.ns_per_test[test_index] = no_poll.ns_elapsed; |
| 1720 | result.test_results.crash_count += 1; |
| 1721 | try run.step.addError("'{s}' {f}{s}{s}", .{ |
| 1722 | result.test_metadata.?.testName(test_index), |
| 1723 | fmtTerm(term), |
| 1724 | if (stderr_owned.len != 0) " with stderr:\n" else "", |
| 1725 | std.mem.trim(u8, stderr_owned, "\n"), |
| 1726 | }); |
| 1727 | continue; |
| 1728 | } |
| 1643 | 1729 | |
| 1644 | | return .{ |
| 1645 | | .stdio = result, |
| 1646 | | .term = term, |
| 1647 | | .elapsed_ns = elapsed_ns, |
| 1648 | | .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0, |
| 1649 | | }; |
| 1730 | // Report an error if the child terminated uncleanly or if we were still trying to run more tests. |
| 1731 | run.step.result_stderr = stderr_owned; |
| 1732 | const tests_done = result.test_metadata != null and result.test_metadata.?.next_index == std.math.maxInt(u32); |
| 1733 | if (!tests_done or !termMatches(.{ .Exited = 0 }, term)) { |
| 1734 | try run.step.addError("test process unexpectedly {f}", .{fmtTerm(term)}); |
| 1735 | } |
| 1736 | return result; |
| 1737 | }, |
| 1738 | .timeout => |timeout| { |
| 1739 | const stderr = poller.reader(.stderr).buffered(); |
| 1740 | poller.reader(.stderr).tossBuffered(); |
| 1741 | if (timeout.active_test_index) |test_index| { |
| 1742 | // A test was running. Report the timeout against that test, and continue on to |
| 1743 | // the next test. |
| 1744 | result.test_metadata.?.ns_per_test[test_index] = timeout.ns_elapsed; |
| 1745 | result.test_results.timeout_count += 1; |
| 1746 | try run.step.addError("'{s}' timed out after {D}{s}{s}", .{ |
| 1747 | result.test_metadata.?.testName(test_index), |
| 1748 | timeout.ns_elapsed, |
| 1749 | if (stderr.len != 0) " with stderr:\n" else "", |
| 1750 | std.mem.trim(u8, stderr, "\n"), |
| 1751 | }); |
| 1752 | continue; |
| 1753 | } |
| 1754 | // Just log an error and let the child be killed. |
| 1755 | run.step.result_stderr = try arena.dupe(u8, stderr); |
| 1756 | return run.step.fail("test runner failed to respond for {D}", .{timeout.ns_elapsed}); |
| 1757 | }, |
| 1758 | } |
| 1759 | comptime unreachable; |
| 1760 | } |
| 1650 | 1761 | } |
| 1651 | 1762 | |
| 1652 | | const StdIoResult = struct { |
| 1653 | | stdout: ?[]const u8, |
| 1654 | | stderr: ?[]const u8, |
| 1655 | | test_results: Step.TestResults, |
| 1656 | | test_metadata: ?TestMetadata, |
| 1657 | | }; |
| 1658 | | |
| 1659 | | fn evalZigTest( |
| 1763 | /// Polls stdout of a Zig test process until a termination condition is reached: |
| 1764 | /// * A write fails, indicating the child unexpectedly closed stdin |
| 1765 | /// * A test (or a response from the test runner) times out |
| 1766 | /// * `poll` fails, indicating the child closed stdout and stderr |
| 1767 | fn pollZigTest( |
| 1660 | 1768 | run: *Run, |
| 1661 | 1769 | child: *std.process.Child, |
| 1662 | 1770 | options: Step.MakeOptions, |
| 1663 | 1771 | fuzz_context: ?FuzzContext, |
| 1664 | | ) !StdIoResult { |
| 1772 | poller: *std.Io.Poller(StdioPollEnum), |
| 1773 | opt_metadata: *?TestMetadata, |
| 1774 | results: *Step.TestResults, |
| 1775 | ) !union(enum) { |
| 1776 | write_failed: anyerror, |
| 1777 | no_poll: struct { |
| 1778 | active_test_index: ?u32, |
| 1779 | ns_elapsed: u64, |
| 1780 | }, |
| 1781 | timeout: struct { |
| 1782 | active_test_index: ?u32, |
| 1783 | ns_elapsed: u64, |
| 1784 | }, |
| 1785 | } { |
| 1665 | 1786 | const gpa = run.step.owner.allocator; |
| 1666 | 1787 | const arena = run.step.owner.allocator; |
| 1667 | 1788 | |
| 1668 | | const PollEnum = enum { stdout, stderr }; |
| 1669 | | |
| 1670 | | var poller = std.Io.poll(gpa, PollEnum, .{ |
| 1671 | | .stdout = child.stdout.?, |
| 1672 | | .stderr = child.stderr.?, |
| 1673 | | }); |
| 1674 | | defer poller.deinit(); |
| 1789 | var sub_prog_node: ?std.Progress.Node = null; |
| 1790 | defer if (sub_prog_node) |n| n.end(); |
| 1675 | 1791 | |
| 1676 | | // If this is `true`, we avoid ever entering the polling loop below, because the stdin pipe has |
| 1677 | | // somehow already closed; instead, we go straight to capturing stderr in case it has anything |
| 1678 | | // useful. |
| 1679 | | const first_write_failed = if (fuzz_context) |fctx| failed: { |
| 1680 | | switch (fctx.fuzz.mode) { |
| 1792 | if (fuzz_context) |ctx| { |
| 1793 | assert(opt_metadata.* == null); // fuzz processes are never restarted |
| 1794 | switch (ctx.fuzz.mode) { |
| 1681 | 1795 | .forever => { |
| 1682 | | const instance_id = 0; // will be used by mutiprocess forever fuzzing |
| 1683 | | sendRunFuzzTestMessage(child.stdin.?, fctx.unit_test_index, .forever, instance_id) catch |err| { |
| 1684 | | try run.step.addError("unable to write stdin: {s}", .{@errorName(err)}); |
| 1685 | | break :failed true; |
| 1686 | | }; |
| 1687 | | break :failed false; |
| 1796 | sendRunFuzzTestMessage( |
| 1797 | child.stdin.?, |
| 1798 | ctx.unit_test_index, |
| 1799 | .forever, |
| 1800 | 0, // instance ID; will be used by multiprocess forever fuzzing in the future |
| 1801 | ) catch |err| return .{ .write_failed = err }; |
| 1688 | 1802 | }, |
| 1689 | 1803 | .limit => |limit| { |
| 1690 | | sendRunFuzzTestMessage(child.stdin.?, fctx.unit_test_index, .iterations, limit.amount) catch |err| { |
| 1691 | | try run.step.addError("unable to write stdin: {s}", .{@errorName(err)}); |
| 1692 | | break :failed true; |
| 1693 | | }; |
| 1694 | | break :failed false; |
| 1804 | sendRunFuzzTestMessage( |
| 1805 | child.stdin.?, |
| 1806 | ctx.unit_test_index, |
| 1807 | .iterations, |
| 1808 | limit.amount, |
| 1809 | ) catch |err| return .{ .write_failed = err }; |
| 1695 | 1810 | }, |
| 1696 | 1811 | } |
| 1697 | | } else failed: { |
| 1812 | } else if (opt_metadata.*) |*md| { |
| 1813 | // Previous unit test process died or was killed; we're continuing where it left off |
| 1814 | requestNextTest(child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; |
| 1815 | } else { |
| 1816 | // Running unit tests normally |
| 1698 | 1817 | run.fuzz_tests.clearRetainingCapacity(); |
| 1699 | | sendMessage(child.stdin.?, .query_test_metadata) catch |err| { |
| 1700 | | try run.step.addError("unable to write stdin: {s}", .{@errorName(err)}); |
| 1701 | | break :failed true; |
| 1702 | | }; |
| 1703 | | break :failed false; |
| 1704 | | }; |
| 1705 | | |
| 1706 | | var fail_count: u32 = 0; |
| 1707 | | var skip_count: u32 = 0; |
| 1708 | | var leak_count: u32 = 0; |
| 1709 | | var timeout_count: u32 = 0; |
| 1710 | | var test_count: u32 = 0; |
| 1711 | | var log_err_count: u32 = 0; |
| 1712 | | |
| 1713 | | var metadata: ?TestMetadata = null; |
| 1714 | | var coverage_id: ?u64 = null; |
| 1715 | | |
| 1716 | | var test_is_running = false; |
| 1818 | sendMessage(child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err }; |
| 1819 | } |
| 1717 | 1820 | |
| 1718 | | // String allocated into `gpa`. Owned by this function while it runs, then moved to the `Step`. |
| 1719 | | var result_stderr: []u8 = &.{}; |
| 1720 | | defer run.step.result_stderr = result_stderr; |
| 1821 | var active_test_index: ?u32 = null; |
| 1721 | 1822 | |
| 1722 | 1823 | // `null` means this host does not support `std.time.Timer`. This timer is `reset()` whenever we |
| 1723 | | // toggle `test_is_running`, i.e. whenever a test starts or finishes. |
| 1724 | | var timer: ?std.time.Timer = std.time.Timer.start() catch t: { |
| 1725 | | std.log.warn("std.time.Timer not supported on host; test timeouts will be ignored", .{}); |
| 1726 | | break :t null; |
| 1727 | | }; |
| 1824 | // change `active_test_index`, i.e. whenever a test starts or finishes. |
| 1825 | var timer: ?std.time.Timer = std.time.Timer.start() catch null; |
| 1728 | 1826 | |
| 1729 | | var sub_prog_node: ?std.Progress.Node = null; |
| 1730 | | defer if (sub_prog_node) |n| n.end(); |
| 1827 | var coverage_id: ?u64 = null; |
| 1731 | 1828 | |
| 1732 | 1829 | // This timeout is used when we're waiting on the test runner itself rather than a user-specified |
| 1733 | 1830 | // test. For instance, if the test runner leaves this much time between us requesting a test to |
| ... | ... | @@ -1735,12 +1832,10 @@ fn evalZigTest( |
| 1735 | 1832 | // *should* never happen, but could in theory be caused by some very unlucky IB in a test. |
| 1736 | 1833 | const response_timeout_ns = 30 * std.time.ns_per_s; |
| 1737 | 1834 | |
| 1738 | | const any_write_failed = first_write_failed or poll: while (true) { |
| 1739 | | // These are scoped inside the loop because we sometimes respawn the child and recreate |
| 1740 | | // `poller` which invaldiates these readers. |
| 1741 | | const stdout = poller.reader(.stdout); |
| 1742 | | const stderr = poller.reader(.stderr); |
| 1835 | const stdout = poller.reader(.stdout); |
| 1836 | const stderr = poller.reader(.stderr); |
| 1743 | 1837 | |
| 1838 | while (true) { |
| 1744 | 1839 | const Header = std.zig.Server.Message.Header; |
| 1745 | 1840 | |
| 1746 | 1841 | // This block is exited when `stdout` contains enough bytes for a `Header`. |
| ... | ... | @@ -1753,15 +1848,21 @@ fn evalZigTest( |
| 1753 | 1848 | // Always `null` if `timer` is `null`. |
| 1754 | 1849 | const opt_timeout_ns: ?u64 = ns: { |
| 1755 | 1850 | if (timer == null) break :ns null; |
| 1756 | | if (!test_is_running) break :ns response_timeout_ns; |
| 1851 | if (active_test_index == null) break :ns response_timeout_ns; |
| 1757 | 1852 | break :ns options.unit_test_timeout_ns; |
| 1758 | 1853 | }; |
| 1759 | 1854 | |
| 1760 | 1855 | if (opt_timeout_ns) |timeout_ns| { |
| 1761 | 1856 | const remaining_ns = timeout_ns -| timer.?.read(); |
| 1762 | | if (!try poller.pollTimeout(remaining_ns)) break :poll false; |
| 1857 | if (!try poller.pollTimeout(remaining_ns)) return .{ .no_poll = .{ |
| 1858 | .active_test_index = active_test_index, |
| 1859 | .ns_elapsed = if (timer) |*t| t.read() else 0, |
| 1860 | } }; |
| 1763 | 1861 | } else { |
| 1764 | | if (!try poller.poll()) break :poll false; |
| 1862 | if (!try poller.poll()) return .{ .no_poll = .{ |
| 1863 | .active_test_index = active_test_index, |
| 1864 | .ns_elapsed = if (timer) |*t| t.read() else 0, |
| 1865 | } }; |
| 1765 | 1866 | } |
| 1766 | 1867 | |
| 1767 | 1868 | if (stdout.buffered().len >= @sizeOf(Header)) { |
| ... | ... | @@ -1769,73 +1870,29 @@ fn evalZigTest( |
| 1769 | 1870 | break :header_ready; |
| 1770 | 1871 | } |
| 1771 | 1872 | |
| 1772 | | const timeout_ns = opt_timeout_ns orelse continue; |
| 1773 | | const cur_ns = timer.?.read(); |
| 1774 | | if (cur_ns < timeout_ns) continue; |
| 1775 | | |
| 1776 | | // There was a timeout. |
| 1777 | | |
| 1778 | | if (!test_is_running) { |
| 1779 | | // The child stopped responding while *not* running a test. To avoid getting into |
| 1780 | | // a loop if something's broken, don't retry; just report an error and stop. |
| 1781 | | try run.step.addError("test runner failed to respond for {D}", .{cur_ns}); |
| 1782 | | break :poll false; |
| 1783 | | } |
| 1784 | | |
| 1785 | | // A test has probably just gotten stuck. We'll report an error, then just kill the |
| 1786 | | // child and continue with the next test in the list. |
| 1787 | | |
| 1788 | | const md = &metadata.?; |
| 1789 | | const test_index = md.next_index - 1; |
| 1790 | | |
| 1791 | | timeout_count += 1; |
| 1792 | | try run.step.addError( |
| 1793 | | "'{s}' timed out after {D}", |
| 1794 | | .{ md.testName(test_index), cur_ns }, |
| 1795 | | ); |
| 1796 | | if (stderr.buffered().len > 0) { |
| 1797 | | const new_bytes = stderr.buffered(); |
| 1798 | | const old_len = result_stderr.len; |
| 1799 | | result_stderr = try gpa.realloc(result_stderr, old_len + new_bytes.len); |
| 1800 | | @memcpy(result_stderr[old_len..], new_bytes); |
| 1873 | if (opt_timeout_ns) |timeout_ns| { |
| 1874 | const cur_ns = timer.?.read(); |
| 1875 | if (cur_ns >= timeout_ns) return .{ .timeout = .{ |
| 1876 | .active_test_index = active_test_index, |
| 1877 | .ns_elapsed = cur_ns, |
| 1878 | } }; |
| 1801 | 1879 | } |
| 1802 | | |
| 1803 | | _ = try child.kill(); |
| 1804 | | // Respawn the test runner. There's a double-cleanup if this fails, but that's |
| 1805 | | // fine because our caller's `kill` will just return `error.AlreadyTerminated`. |
| 1806 | | try child.spawn(); |
| 1807 | | try child.waitForSpawn(); |
| 1808 | | |
| 1809 | | // After respawning the child, we must update the poller's streams. |
| 1810 | | poller.deinit(); |
| 1811 | | poller = std.Io.poll(gpa, PollEnum, .{ |
| 1812 | | .stdout = child.stdout.?, |
| 1813 | | .stderr = child.stderr.?, |
| 1814 | | }); |
| 1815 | | |
| 1816 | | test_is_running = false; |
| 1817 | | md.ns_per_test[test_index] = timer.?.lap(); |
| 1818 | | |
| 1819 | | requestNextTest(child.stdin.?, md, &sub_prog_node) catch |err| { |
| 1820 | | try run.step.addError("unable to write stdin: {s}", .{@errorName(err)}); |
| 1821 | | break :poll true; |
| 1822 | | }; |
| 1823 | | |
| 1824 | | continue :poll; // continue work with the new (respawned) child |
| 1880 | continue; |
| 1825 | 1881 | } |
| 1826 | 1882 | // There is definitely a header available now -- read it. |
| 1827 | 1883 | const header = stdout.takeStruct(Header, .little) catch unreachable; |
| 1828 | 1884 | |
| 1829 | | while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll false; |
| 1885 | while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) return .{ .no_poll = .{ |
| 1886 | .active_test_index = active_test_index, |
| 1887 | .ns_elapsed = if (timer) |*t| t.read() else 0, |
| 1888 | } }; |
| 1830 | 1889 | const body = stdout.take(header.bytes_len) catch unreachable; |
| 1831 | 1890 | switch (header.tag) { |
| 1832 | 1891 | .zig_version => { |
| 1833 | | if (!std.mem.eql(u8, builtin.zig_version_string, body)) { |
| 1834 | | return run.step.fail( |
| 1835 | | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", |
| 1836 | | .{ builtin.zig_version_string, body }, |
| 1837 | | ); |
| 1838 | | } |
| 1892 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) return run.step.fail( |
| 1893 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", |
| 1894 | .{ builtin.zig_version_string, body }, |
| 1895 | ); |
| 1839 | 1896 | }, |
| 1840 | 1897 | .test_metadata => { |
| 1841 | 1898 | assert(fuzz_context == null); |
| ... | ... | @@ -1843,14 +1900,14 @@ fn evalZigTest( |
| 1843 | 1900 | // `metadata` would only be populated if we'd already seen a `test_metadata`, but we |
| 1844 | 1901 | // only request it once (and importantly, we don't re-request it if we kill and |
| 1845 | 1902 | // restart the test runner). |
| 1846 | | assert(metadata == null); |
| 1903 | assert(opt_metadata.* == null); |
| 1847 | 1904 | |
| 1848 | 1905 | const TmHdr = std.zig.Server.Message.TestMetadata; |
| 1849 | | const tm_hdr = @as(*align(1) const TmHdr, @ptrCast(body)); |
| 1850 | | test_count = tm_hdr.tests_len; |
| 1906 | const tm_hdr: *align(1) const TmHdr = @ptrCast(body); |
| 1907 | results.test_count = tm_hdr.tests_len; |
| 1851 | 1908 | |
| 1852 | | const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)]; |
| 1853 | | const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)]; |
| 1909 | const names_bytes = body[@sizeOf(TmHdr)..][0 .. results.test_count * @sizeOf(u32)]; |
| 1910 | const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. results.test_count * @sizeOf(u32)]; |
| 1854 | 1911 | const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len]; |
| 1855 | 1912 | |
| 1856 | 1913 | const names = std.mem.bytesAsSlice(u32, names_bytes); |
| ... | ... | @@ -1863,68 +1920,70 @@ fn evalZigTest( |
| 1863 | 1920 | for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src; |
| 1864 | 1921 | |
| 1865 | 1922 | options.progress_node.setEstimatedTotalItems(names.len); |
| 1866 | | metadata = .{ |
| 1923 | opt_metadata.* = .{ |
| 1867 | 1924 | .string_bytes = try arena.dupe(u8, string_bytes), |
| 1868 | | .ns_per_test = try arena.alloc(u64, test_count), |
| 1925 | .ns_per_test = try arena.alloc(u64, results.test_count), |
| 1869 | 1926 | .names = names_aligned, |
| 1870 | 1927 | .expected_panic_msgs = expected_panic_msgs_aligned, |
| 1871 | 1928 | .next_index = 0, |
| 1872 | 1929 | .prog_node = options.progress_node, |
| 1873 | 1930 | }; |
| 1874 | | @memset(metadata.?.ns_per_test, std.math.maxInt(u64)); |
| 1931 | @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64)); |
| 1875 | 1932 | |
| 1876 | | test_is_running = false; |
| 1933 | active_test_index = null; |
| 1877 | 1934 | if (timer) |*t| t.reset(); |
| 1878 | 1935 | |
| 1879 | | requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node) catch |err| { |
| 1880 | | try run.step.addError("unable to write stdin: {s}", .{@errorName(err)}); |
| 1881 | | break :poll true; |
| 1882 | | }; |
| 1936 | requestNextTest(child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; |
| 1883 | 1937 | }, |
| 1884 | 1938 | .test_started => { |
| 1885 | | test_is_running = true; |
| 1939 | active_test_index = opt_metadata.*.?.next_index - 1; |
| 1886 | 1940 | if (timer) |*t| t.reset(); |
| 1887 | 1941 | }, |
| 1888 | 1942 | .test_results => { |
| 1889 | 1943 | assert(fuzz_context == null); |
| 1890 | | const md = &metadata.?; |
| 1944 | const md = &opt_metadata.*.?; |
| 1891 | 1945 | |
| 1892 | 1946 | const TrHdr = std.zig.Server.Message.TestResults; |
| 1893 | 1947 | const tr_hdr: *align(1) const TrHdr = @ptrCast(body); |
| 1894 | | fail_count +|= @intFromBool(tr_hdr.flags.fail); |
| 1895 | | skip_count +|= @intFromBool(tr_hdr.flags.skip); |
| 1896 | | leak_count +|= @intFromBool(tr_hdr.flags.leak); |
| 1897 | | log_err_count +|= tr_hdr.flags.log_err_count; |
| 1948 | assert(tr_hdr.index == active_test_index); |
| 1949 | |
| 1950 | switch (tr_hdr.flags.status) { |
| 1951 | .pass => {}, |
| 1952 | .skip => results.skip_count +|= 1, |
| 1953 | .fail => results.fail_count +|= 1, |
| 1954 | } |
| 1955 | const leak_count = tr_hdr.flags.leak_count; |
| 1956 | const log_err_count = tr_hdr.flags.log_err_count; |
| 1957 | results.leak_count +|= leak_count; |
| 1958 | results.log_err_count +|= log_err_count; |
| 1898 | 1959 | |
| 1899 | 1960 | if (tr_hdr.flags.fuzz) try run.fuzz_tests.append(gpa, tr_hdr.index); |
| 1900 | 1961 | |
| 1901 | | if (tr_hdr.flags.fail or tr_hdr.flags.leak or tr_hdr.flags.log_err_count > 0) { |
| 1962 | if (tr_hdr.flags.status == .fail) { |
| 1902 | 1963 | const name = std.mem.sliceTo(md.testName(tr_hdr.index), 0); |
| 1903 | | const stderr_contents = stderr.buffered(); |
| 1904 | | stderr.toss(stderr_contents.len); |
| 1905 | | const msg = std.mem.trim(u8, stderr_contents, "\n"); |
| 1906 | | const label = if (tr_hdr.flags.fail) |
| 1907 | | "failed" |
| 1908 | | else if (tr_hdr.flags.leak) |
| 1909 | | "leaked" |
| 1910 | | else if (tr_hdr.flags.log_err_count > 0) |
| 1911 | | "logged errors" |
| 1912 | | else |
| 1913 | | unreachable; |
| 1914 | | if (msg.len > 0) { |
| 1915 | | try run.step.addError("'{s}' {s}: {s}", .{ name, label, msg }); |
| 1964 | const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); |
| 1965 | stderr.tossBuffered(); |
| 1966 | if (stderr_bytes.len == 0) { |
| 1967 | try run.step.addError("'{s}' failed without output", .{name}); |
| 1916 | 1968 | } else { |
| 1917 | | try run.step.addError("'{s}' {s}", .{ name, label }); |
| 1969 | try run.step.addError("'{s}' failed:\n{s}", .{ name, stderr_bytes }); |
| 1918 | 1970 | } |
| 1971 | } else if (leak_count > 0) { |
| 1972 | const name = std.mem.sliceTo(md.testName(tr_hdr.index), 0); |
| 1973 | const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); |
| 1974 | stderr.tossBuffered(); |
| 1975 | try run.step.addError("'{s}' leaked {d} allocations:\n{s}", .{ name, leak_count, stderr_bytes }); |
| 1976 | } else if (log_err_count > 0) { |
| 1977 | const name = std.mem.sliceTo(md.testName(tr_hdr.index), 0); |
| 1978 | const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); |
| 1979 | stderr.tossBuffered(); |
| 1980 | try run.step.addError("'{s}' logged {d} errors:\n{s}", .{ name, log_err_count, stderr_bytes }); |
| 1919 | 1981 | } |
| 1920 | 1982 | |
| 1921 | | test_is_running = false; |
| 1983 | active_test_index = null; |
| 1922 | 1984 | if (timer) |*t| md.ns_per_test[tr_hdr.index] = t.lap(); |
| 1923 | 1985 | |
| 1924 | | requestNextTest(child.stdin.?, md, &sub_prog_node) catch |err| { |
| 1925 | | try run.step.addError("unable to write stdin: {s}", .{@errorName(err)}); |
| 1926 | | break :poll true; |
| 1927 | | }; |
| 1986 | requestNextTest(child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; |
| 1928 | 1987 | }, |
| 1929 | 1988 | .coverage_id => { |
| 1930 | 1989 | const fuzz = fuzz_context.?.fuzz; |
| ... | ... | @@ -1961,39 +2020,7 @@ fn evalZigTest( |
| 1961 | 2020 | }, |
| 1962 | 2021 | else => {}, // ignore other messages |
| 1963 | 2022 | } |
| 1964 | | }; |
| 1965 | | |
| 1966 | | if (any_write_failed) { |
| 1967 | | // The compiler unexpectedly closed stdin; something is very wrong and has probably crashed. |
| 1968 | | // We want to make sure we've captured all of stderr so that it's logged below. |
| 1969 | | while (try poller.poll()) {} |
| 1970 | 2023 | } |
| 1971 | | |
| 1972 | | const stderr = poller.reader(.stderr); |
| 1973 | | if (stderr.buffered().len > 0) { |
| 1974 | | const new_bytes = stderr.buffered(); |
| 1975 | | const old_len = result_stderr.len; |
| 1976 | | result_stderr = try gpa.realloc(result_stderr, old_len + new_bytes.len); |
| 1977 | | @memcpy(result_stderr[old_len..], new_bytes); |
| 1978 | | } |
| 1979 | | |
| 1980 | | // Send EOF to stdin. |
| 1981 | | child.stdin.?.close(); |
| 1982 | | child.stdin = null; |
| 1983 | | |
| 1984 | | return .{ |
| 1985 | | .stdout = null, |
| 1986 | | .stderr = null, |
| 1987 | | .test_results = .{ |
| 1988 | | .test_count = test_count, |
| 1989 | | .fail_count = fail_count, |
| 1990 | | .skip_count = skip_count, |
| 1991 | | .leak_count = leak_count, |
| 1992 | | .timeout_count = timeout_count, |
| 1993 | | .log_err_count = log_err_count, |
| 1994 | | }, |
| 1995 | | .test_metadata = metadata, |
| 1996 | | }; |
| 1997 | 2024 | } |
| 1998 | 2025 | |
| 1999 | 2026 | const TestMetadata = struct { |
| ... | ... | @@ -2077,10 +2104,15 @@ fn sendRunFuzzTestMessage( |
| 2077 | 2104 | try file.writeAll(full_msg); |
| 2078 | 2105 | } |
| 2079 | 2106 | |
| 2080 | | fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult { |
| 2107 | fn evalGeneric(run: *Run, child: *std.process.Child) !EvalGenericResult { |
| 2081 | 2108 | const b = run.step.owner; |
| 2082 | 2109 | const arena = b.allocator; |
| 2083 | 2110 | |
| 2111 | try child.spawn(); |
| 2112 | errdefer _ = child.kill() catch {}; |
| 2113 | |
| 2114 | try child.waitForSpawn(); |
| 2115 | |
| 2084 | 2116 | switch (run.stdin) { |
| 2085 | 2117 | .bytes => |bytes| { |
| 2086 | 2118 | child.stdin.?.writeAll(bytes) catch |err| { |
| ... | ... | @@ -2170,11 +2202,12 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult { |
| 2170 | 2202 | } |
| 2171 | 2203 | }; |
| 2172 | 2204 | |
| 2205 | run.step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0; |
| 2206 | |
| 2173 | 2207 | return .{ |
| 2208 | .term = try child.wait(), |
| 2174 | 2209 | .stdout = stdout_bytes, |
| 2175 | 2210 | .stderr = stderr_bytes, |
| 2176 | | .test_results = .{}, |
| 2177 | | .test_metadata = null, |
| 2178 | 2211 | }; |
| 2179 | 2212 | } |
| 2180 | 2213 | |