authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-12 00:39:21-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-15 10:48:14-07:00
logede5dcffea5a3a5fc9fd14e4e180464633402fae
treedcf88812197be81cdb36b747d2b2b55c56d2192b
parentef5f8bd7c62f929b5cc210caa816ce4a8c8f8538

make the build runner and test runner talk to each other

std.Build.addTest creates a CompileStep as before, however, this kind of step no longer actually runs the unit tests. Instead it only compiles it, and one must additionally create a RunStep from the CompileStep in order to actually run the tests. RunStep gains integration with the default test runner, which now supports the standard --listen=- argument in order to communicate over stdin and stdout. It also reports test statistics; how many passed, failed, and leaked, as well as directly associating the relevant stderr with the particular test name that failed. This separation of CompileStep and RunStep means that `CompileStep.Kind.test_exe` is no longer needed, and therefore has been removed in this commit. * build runner: show unit test statistics in build summary * added Step.writeManifest since many steps want to treat it as a warning and emit the same message if it fails. * RunStep: fixed error message that prints the failed command printing the original argv and not the adjusted argv in case an interpreter was used. * RunStep: fixed not passing the command line arguments to the interpreter. * move src/Server.zig to std.zig.Server so that the default test runner can use it. * the simpler test runner function which is used by work-in-progress backends now no longer prints to stderr, which is necessary in order for the build runner to not print the stderr as a warning message.

30 files changed, 780 insertions(+), 373 deletions(-)

CMakeLists.txt+1-1
......@@ -518,6 +518,7 @@ set(ZIG_STAGE2_SOURCES
518518 "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig"
519519 "${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig"
520520 "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"
521 "${CMAKE_SOURCE_DIR}/lib/std/zig/Server.zig"
521522 "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"
522523 "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
523524 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"
......@@ -623,7 +624,6 @@ set(ZIG_STAGE2_SOURCES
623624 "${CMAKE_SOURCE_DIR}/src/print_targets.zig"
624625 "${CMAKE_SOURCE_DIR}/src/print_zir.zig"
625626 "${CMAKE_SOURCE_DIR}/src/register_manager.zig"
626 "${CMAKE_SOURCE_DIR}/src/Server.zig"
627627 "${CMAKE_SOURCE_DIR}/src/target.zig"
628628 "${CMAKE_SOURCE_DIR}/src/tracy.zig"
629629 "${CMAKE_SOURCE_DIR}/src/translate_c.zig"
lib/build_runner.zig+57-2
......@@ -416,6 +416,12 @@ fn runStepNames(
416416 }
417417 assert(run.memory_blocked_steps.items.len == 0);
418418
419 var test_skip_count: usize = 0;
420 var test_fail_count: usize = 0;
421 var test_pass_count: usize = 0;
422 var test_leak_count: usize = 0;
423 var test_count: usize = 0;
424
419425 var success_count: usize = 0;
420426 var skipped_count: usize = 0;
421427 var failure_count: usize = 0;
......@@ -425,6 +431,12 @@ fn runStepNames(
425431 defer compile_error_steps.deinit(gpa);
426432
427433 for (step_stack.keys()) |s| {
434 test_fail_count += s.test_results.fail_count;
435 test_skip_count += s.test_results.skip_count;
436 test_leak_count += s.test_results.leak_count;
437 test_pass_count += s.test_results.passCount();
438 test_count += s.test_results.test_count;
439
428440 switch (s.state) {
429441 .precheck_unstarted => unreachable,
430442 .precheck_started => unreachable,
......@@ -468,6 +480,11 @@ fn runStepNames(
468480 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
469481 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
470482
483 if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
484 if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {};
485 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
486 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
487
471488 if (run.enable_summary == null) {
472489 ttyconf.setColor(stderr, .Dim) catch {};
473490 stderr.writeAll(" (disable with -fno-summary)") catch {};
......@@ -566,6 +583,13 @@ fn printTreeStep(
566583 try ttyconf.setColor(stderr, .Green);
567584 if (s.result_cached) {
568585 try stderr.writeAll(" cached");
586 } else if (s.test_results.test_count > 0) {
587 const pass_count = s.test_results.passCount();
588 try stderr.writer().print(" {d} passed", .{pass_count});
589 if (s.test_results.skip_count > 0) {
590 try ttyconf.setColor(stderr, .Yellow);
591 try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count});
592 }
569593 } else {
570594 try stderr.writeAll(" success");
571595 }
......@@ -609,15 +633,46 @@ fn printTreeStep(
609633 },
610634
611635 .failure => {
612 try ttyconf.setColor(stderr, .Red);
613636 if (s.result_error_bundle.errorMessageCount() > 0) {
637 try ttyconf.setColor(stderr, .Red);
614638 try stderr.writer().print(" {d} errors\n", .{
615639 s.result_error_bundle.errorMessageCount(),
616640 });
641 try ttyconf.setColor(stderr, .Reset);
642 } else if (!s.test_results.isSuccess()) {
643 try stderr.writer().print(" {d}/{d} passed", .{
644 s.test_results.passCount(), s.test_results.test_count,
645 });
646 if (s.test_results.fail_count > 0) {
647 try stderr.writeAll(", ");
648 try ttyconf.setColor(stderr, .Red);
649 try stderr.writer().print("{d} failed", .{
650 s.test_results.fail_count,
651 });
652 try ttyconf.setColor(stderr, .Reset);
653 }
654 if (s.test_results.skip_count > 0) {
655 try stderr.writeAll(", ");
656 try ttyconf.setColor(stderr, .Yellow);
657 try stderr.writer().print("{d} skipped", .{
658 s.test_results.skip_count,
659 });
660 try ttyconf.setColor(stderr, .Reset);
661 }
662 if (s.test_results.leak_count > 0) {
663 try stderr.writeAll(", ");
664 try ttyconf.setColor(stderr, .Red);
665 try stderr.writer().print("{d} leaked", .{
666 s.test_results.leak_count,
667 });
668 try ttyconf.setColor(stderr, .Reset);
669 }
670 try stderr.writeAll("\n");
617671 } else {
672 try ttyconf.setColor(stderr, .Red);
618673 try stderr.writeAll(" failure\n");
674 try ttyconf.setColor(stderr, .Reset);
619675 }
620 try ttyconf.setColor(stderr, .Reset);
621676 },
622677 }
623678
lib/std/Build.zig+4-6
......@@ -531,7 +531,6 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
531531
532532pub const TestOptions = struct {
533533 name: []const u8 = "test",
534 kind: CompileStep.Kind = .@"test",
535534 root_source_file: FileSource,
536535 target: CrossTarget = .{},
537536 optimize: std.builtin.Mode = .Debug,
......@@ -542,7 +541,7 @@ pub const TestOptions = struct {
542541pub fn addTest(b: *Build, options: TestOptions) *CompileStep {
543542 return CompileStep.create(b, .{
544543 .name = options.name,
545 .kind = options.kind,
544 .kind = .@"test",
546545 .root_source_file = options.root_source_file,
547546 .target = options.target,
548547 .optimize = options.optimize,
......@@ -626,16 +625,15 @@ pub fn addSystemCommand(self: *Build, argv: []const []const u8) *RunStep {
626625/// Creates a `RunStep` with an executable built with `addExecutable`.
627626/// Add command line arguments with methods of `RunStep`.
628627pub fn addRunArtifact(b: *Build, exe: *CompileStep) *RunStep {
629 assert(exe.kind == .exe or exe.kind == .test_exe);
630
631628 // It doesn't have to be native. We catch that if you actually try to run it.
632629 // Consider that this is declarative; the run step may not be run unless a user
633630 // option is supplied.
634631 const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.name}));
635632 run_step.addArtifactArg(exe);
636633
637 if (exe.kind == .test_exe) {
638 run_step.addArg(b.zig_exe);
634 if (exe.kind == .@"test") {
635 run_step.stdio = .zig_test;
636 run_step.addArgs(&.{"--listen=-"});
639637 }
640638
641639 if (exe.vcpkg_bin_path) |path| {
lib/std/Build/CompileStep.zig+7-86
......@@ -289,7 +289,6 @@ pub const Kind = enum {
289289 lib,
290290 obj,
291291 @"test",
292 test_exe,
293292};
294293
295294pub const Linkage = enum { dynamic, static };
......@@ -328,7 +327,7 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep {
328327 .exe => "zig build-exe",
329328 .lib => "zig build-lib",
330329 .obj => "zig build-obj",
331 .test_exe, .@"test" => "zig test",
330 .@"test" => "zig test",
332331 },
333332 name_adjusted,
334333 @tagName(options.optimize),
......@@ -410,7 +409,7 @@ fn computeOutFileNames(self: *CompileStep) void {
410409 .output_mode = switch (self.kind) {
411410 .lib => .Lib,
412411 .obj => .Obj,
413 .exe, .@"test", .test_exe => .Exe,
412 .exe, .@"test" => .Exe,
414413 },
415414 .link_mode = if (self.linkage) |some| @as(std.builtin.LinkMode, switch (some) {
416415 .dynamic => .Dynamic,
......@@ -621,7 +620,7 @@ pub fn producesPdbFile(self: *CompileStep) bool {
621620 if (!self.target.isWindows() and !self.target.isUefi()) return false;
622621 if (self.target.getObjectFormat() == .c) return false;
623622 if (self.strip == true) return false;
624 return self.isDynamicLibrary() or self.kind == .exe or self.kind == .test_exe;
623 return self.isDynamicLibrary() or self.kind == .exe or self.kind == .@"test";
625624}
626625
627626pub fn linkLibC(self: *CompileStep) void {
......@@ -850,19 +849,19 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {
850849
851850pub fn setNamePrefix(self: *CompileStep, text: []const u8) void {
852851 const b = self.step.owner;
853 assert(self.kind == .@"test" or self.kind == .test_exe);
852 assert(self.kind == .@"test");
854853 self.name_prefix = b.dupe(text);
855854}
856855
857856pub fn setFilter(self: *CompileStep, text: ?[]const u8) void {
858857 const b = self.step.owner;
859 assert(self.kind == .@"test" or self.kind == .test_exe);
858 assert(self.kind == .@"test");
860859 self.filter = if (text) |t| b.dupe(t) else null;
861860}
862861
863862pub fn setTestRunner(self: *CompileStep, path: ?[]const u8) void {
864863 const b = self.step.owner;
865 assert(self.kind == .@"test" or self.kind == .test_exe);
864 assert(self.kind == .@"test");
866865 self.test_runner = if (path) |p| b.dupePath(p) else null;
867866}
868867
......@@ -938,7 +937,7 @@ pub fn getOutputLibSource(self: *CompileStep) FileSource {
938937/// Returns the generated header file.
939938/// This function can only be called for libraries or object files which have `emit_h` set.
940939pub fn getOutputHSource(self: *CompileStep) FileSource {
941 assert(self.kind != .exe and self.kind != .test_exe and self.kind != .@"test");
940 assert(self.kind != .exe and self.kind != .@"test");
942941 assert(self.emit_h);
943942 return .{ .generated = &self.output_h_path_source };
944943}
......@@ -1243,7 +1242,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
12431242 .exe => "build-exe",
12441243 .obj => "build-obj",
12451244 .@"test" => "test",
1246 .test_exe => "test",
12471245 };
12481246 try zig_args.append(cmd);
12491247
......@@ -1293,7 +1291,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
12931291
12941292 .other_step => |other| switch (other.kind) {
12951293 .exe => @panic("Cannot link with an executable build artifact"),
1296 .test_exe => @panic("Cannot link with an executable build artifact"),
12971294 .@"test" => @panic("Cannot link with a test"),
12981295 .obj => {
12991296 try zig_args.append(other.getOutputSource().getPath(b));
......@@ -1661,83 +1658,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
16611658 try zig_args.append("--test-cmd-bin");
16621659 }
16631660 }
1664 } else {
1665 const need_cross_glibc = self.target.isGnuLibC() and transitive_deps.is_linking_libc;
1666
1667 switch (b.host.getExternalExecutor(self.target_info, .{
1668 .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null,
1669 .link_libc = transitive_deps.is_linking_libc,
1670 })) {
1671 .native => {},
1672 .bad_dl, .bad_os_or_cpu => {
1673 try zig_args.append("--test-no-exec");
1674 },
1675 .rosetta => if (b.enable_rosetta) {
1676 try zig_args.append("--test-cmd-bin");
1677 } else {
1678 try zig_args.append("--test-no-exec");
1679 },
1680 .qemu => |bin_name| ok: {
1681 if (b.enable_qemu) qemu: {
1682 const glibc_dir_arg = if (need_cross_glibc)
1683 b.glibc_runtimes_dir orelse break :qemu
1684 else
1685 null;
1686 try zig_args.append("--test-cmd");
1687 try zig_args.append(bin_name);
1688 if (glibc_dir_arg) |dir| {
1689 // TODO look into making this a call to `linuxTriple`. This
1690 // needs the directory to be called "i686" rather than
1691 // "x86" which is why we do it manually here.
1692 const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
1693 const cpu_arch = self.target.getCpuArch();
1694 const os_tag = self.target.getOsTag();
1695 const abi = self.target.getAbi();
1696 const cpu_arch_name: []const u8 = if (cpu_arch == .x86)
1697 "i686"
1698 else
1699 @tagName(cpu_arch);
1700 const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{
1701 dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
1702 });
1703
1704 try zig_args.append("--test-cmd");
1705 try zig_args.append("-L");
1706 try zig_args.append("--test-cmd");
1707 try zig_args.append(full_dir);
1708 }
1709 try zig_args.append("--test-cmd-bin");
1710 break :ok;
1711 }
1712 try zig_args.append("--test-no-exec");
1713 },
1714 .wine => |bin_name| if (b.enable_wine) {
1715 try zig_args.append("--test-cmd");
1716 try zig_args.append(bin_name);
1717 try zig_args.append("--test-cmd-bin");
1718 } else {
1719 try zig_args.append("--test-no-exec");
1720 },
1721 .wasmtime => |bin_name| if (b.enable_wasmtime) {
1722 try zig_args.append("--test-cmd");
1723 try zig_args.append(bin_name);
1724 try zig_args.append("--test-cmd");
1725 try zig_args.append("--dir=.");
1726 try zig_args.append("--test-cmd-bin");
1727 } else {
1728 try zig_args.append("--test-no-exec");
1729 },
1730 .darling => |bin_name| if (b.enable_darling) {
1731 try zig_args.append("--test-cmd");
1732 try zig_args.append(bin_name);
1733 try zig_args.append("--test-cmd-bin");
1734 } else {
1735 try zig_args.append("--test-no-exec");
1736 },
1737 }
17381661 }
1739 } else if (self.kind == .test_exe) {
1740 try zig_args.append("--test-no-exec");
17411662 }
17421663
17431664 try self.appendModuleArgs(&zig_args);
lib/std/Build/InstallArtifactStep.zig+2-3
......@@ -32,12 +32,11 @@ pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep {
3232 .artifact = artifact,
3333 .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) {
3434 .obj => @panic("Cannot install a .obj build artifact."),
35 .@"test" => @panic("Cannot install a .test build artifact, use .test_exe instead."),
36 .exe, .test_exe => InstallDir{ .bin = {} },
35 .exe, .@"test" => InstallDir{ .bin = {} },
3736 .lib => InstallDir{ .lib = {} },
3837 },
3938 .pdb_dir = if (artifact.producesPdbFile()) blk: {
40 if (artifact.kind == .exe or artifact.kind == .test_exe) {
39 if (artifact.kind == .exe or artifact.kind == .@"test") {
4140 break :blk InstallDir{ .bin = {} };
4241 } else {
4342 break :blk InstallDir{ .lib = {} };
lib/std/Build/RunStep.zig+296-59
......@@ -92,6 +92,9 @@ pub const StdIo = union(enum) {
9292 /// Note that an explicit check for exit code 0 needs to be added to this
9393 /// list if such a check is desireable.
9494 check: std.ArrayList(Check),
95 /// This RunStep is running a zig unit test binary and will communicate
96 /// extra metadata over the IPC protocol.
97 zig_test,
9598
9699 pub const Check = union(enum) {
97100 expect_stderr_exact: []const u8,
......@@ -324,6 +327,7 @@ fn hasSideEffects(self: RunStep) bool {
324327 .infer_from_args => !self.hasAnyOutputArgs(),
325328 .inherit => true,
326329 .check => false,
330 .zig_test => false,
327331 };
328332}
329333
......@@ -366,11 +370,6 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {
366370}
367371
368372fn make(step: *Step, prog_node: *std.Progress.Node) !void {
369 // Unfortunately we have no way to collect progress from arbitrary programs.
370 // Perhaps in the future Zig could offer some kind of opt-in IPC mechanism that
371 // processes could use to supply progress updates.
372 _ = prog_node;
373
374373 const b = step.owner;
375374 const arena = b.allocator;
376375 const self = @fieldParentPtr(RunStep, "step", step);
......@@ -439,7 +438,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
439438 hashStdIo(&man.hash, self.stdio);
440439
441440 if (has_side_effects) {
442 try runCommand(self, argv_list.items, has_side_effects, null);
441 try runCommand(self, argv_list.items, has_side_effects, null, prog_node);
443442 return;
444443 }
445444
......@@ -492,8 +491,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
492491 argv_list.items[placeholder.index] = cli_arg;
493492 }
494493
495 try runCommand(self, argv_list.items, has_side_effects, &digest);
496 try man.writeManifest();
494 try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node);
495
496 try step.writeManifest(&man);
497497}
498498
499499fn formatTerm(
......@@ -546,6 +546,7 @@ fn runCommand(
546546 argv: []const []const u8,
547547 has_side_effects: bool,
548548 digest: ?*const [std.Build.Cache.hex_digest_len]u8,
549 prog_node: *std.Progress.Node,
549550) !void {
550551 const step = &self.step;
551552 const b = step.owner;
......@@ -554,7 +555,15 @@ fn runCommand(
554555 try step.handleChildProcUnsupported(self.cwd, argv);
555556 try Step.handleVerbose(step.owner, self.cwd, argv);
556557
557 const result = spawnChildAndCollect(self, argv, has_side_effects) catch |err| term: {
558 const allow_skip = switch (self.stdio) {
559 .check, .zig_test => self.skip_foreign_checks,
560 else => false,
561 };
562
563 var interp_argv = std.ArrayList([]const u8).init(b.allocator);
564 defer interp_argv.deinit();
565
566 const result = spawnChildAndCollect(self, argv, has_side_effects, prog_node) catch |err| term: {
558567 // InvalidExe: cpu arch mismatch
559568 // FileNotFound: can happen with a wrong dynamic linker path
560569 if (err == error.InvalidExe or err == error.FileNotFound) interpret: {
......@@ -566,10 +575,10 @@ fn runCommand(
566575 .artifact => |exe| exe,
567576 else => break :interpret,
568577 };
569 if (exe.kind != .exe) break :interpret;
570
571 var interp_argv = std.ArrayList([]const u8).init(b.allocator);
572 defer interp_argv.deinit();
578 switch (exe.kind) {
579 .exe, .@"test" => {},
580 else => break :interpret,
581 }
573582
574583 const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc;
575584 switch (b.host.getExternalExecutor(exe.target_info, .{
......@@ -577,14 +586,13 @@ fn runCommand(
577586 .link_libc = exe.is_linking_libc,
578587 })) {
579588 .native, .rosetta => {
580 if (self.stdio == .check and self.skip_foreign_checks)
581 return error.MakeSkipped;
582
589 if (allow_skip) return error.MakeSkipped;
583590 break :interpret;
584591 },
585592 .wine => |bin_name| {
586593 if (b.enable_wine) {
587594 try interp_argv.append(bin_name);
595 try interp_argv.appendSlice(argv);
588596 } else {
589597 return failForeign(self, "-fwine", argv[0], exe);
590598 }
......@@ -617,6 +625,8 @@ fn runCommand(
617625 try interp_argv.append("-L");
618626 try interp_argv.append(full_dir);
619627 }
628
629 try interp_argv.appendSlice(argv);
620630 } else {
621631 return failForeign(self, "-fqemu", argv[0], exe);
622632 }
......@@ -624,6 +634,7 @@ fn runCommand(
624634 .darling => |bin_name| {
625635 if (b.enable_darling) {
626636 try interp_argv.append(bin_name);
637 try interp_argv.appendSlice(argv);
627638 } else {
628639 return failForeign(self, "-fdarling", argv[0], exe);
629640 }
......@@ -632,13 +643,15 @@ fn runCommand(
632643 if (b.enable_wasmtime) {
633644 try interp_argv.append(bin_name);
634645 try interp_argv.append("--dir=.");
646 try interp_argv.append(argv[0]);
647 try interp_argv.append("--");
648 try interp_argv.appendSlice(argv[1..]);
635649 } else {
636650 return failForeign(self, "-fwasmtime", argv[0], exe);
637651 }
638652 },
639653 .bad_dl => |foreign_dl| {
640 if (self.stdio == .check and self.skip_foreign_checks)
641 return error.MakeSkipped;
654 if (allow_skip) return error.MakeSkipped;
642655
643656 const host_dl = b.host.dynamic_linker.get() orelse "(none)";
644657
......@@ -650,8 +663,7 @@ fn runCommand(
650663 , .{ host_dl, foreign_dl });
651664 },
652665 .bad_os_or_cpu => {
653 if (self.stdio == .check and self.skip_foreign_checks)
654 return error.MakeSkipped;
666 if (allow_skip) return error.MakeSkipped;
655667
656668 const host_name = try b.host.target.zigTriple(b.allocator);
657669 const foreign_name = try exe.target.zigTriple(b.allocator);
......@@ -667,11 +679,9 @@ fn runCommand(
667679 RunStep.addPathForDynLibsInternal(&self.step, b, exe);
668680 }
669681
670 try interp_argv.append(argv[0]);
671
672682 try Step.handleVerbose(step.owner, self.cwd, interp_argv.items);
673683
674 break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects) catch |e| {
684 break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| {
675685 return step.fail("unable to spawn {s}: {s}", .{
676686 interp_argv.items[0], @errorName(e),
677687 });
......@@ -683,6 +693,7 @@ fn runCommand(
683693
684694 step.result_duration_ns = result.elapsed_ns;
685695 step.result_peak_rss = result.peak_rss;
696 step.test_results = result.stdio.test_results;
686697
687698 // Capture stdout and stderr to GeneratedFile objects.
688699 const Stream = struct {
......@@ -693,13 +704,13 @@ fn runCommand(
693704 for ([_]Stream{
694705 .{
695706 .captured = self.captured_stdout,
696 .is_null = result.stdout_null,
697 .bytes = result.stdout,
707 .is_null = result.stdio.stdout_null,
708 .bytes = result.stdio.stdout,
698709 },
699710 .{
700711 .captured = self.captured_stderr,
701 .is_null = result.stderr_null,
702 .bytes = result.stderr,
712 .is_null = result.stdio.stderr_null,
713 .bytes = result.stdio.stderr,
703714 },
704715 }) |stream| {
705716 if (stream.captured) |output| {
......@@ -724,11 +735,13 @@ fn runCommand(
724735 }
725736 }
726737
738 const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items;
739
727740 switch (self.stdio) {
728741 .check => |checks| for (checks.items) |check| switch (check) {
729742 .expect_stderr_exact => |expected_bytes| {
730 assert(!result.stderr_null);
731 if (!mem.eql(u8, expected_bytes, result.stderr)) {
743 assert(!result.stdio.stderr_null);
744 if (!mem.eql(u8, expected_bytes, result.stdio.stderr)) {
732745 return step.fail(
733746 \\
734747 \\========= expected this stderr: =========
......@@ -739,14 +752,14 @@ fn runCommand(
739752 \\{s}
740753 , .{
741754 expected_bytes,
742 result.stderr,
743 try Step.allocPrintCmd(arena, self.cwd, argv),
755 result.stdio.stderr,
756 try Step.allocPrintCmd(arena, self.cwd, final_argv),
744757 });
745758 }
746759 },
747760 .expect_stderr_match => |match| {
748 assert(!result.stderr_null);
749 if (mem.indexOf(u8, result.stderr, match) == null) {
761 assert(!result.stdio.stderr_null);
762 if (mem.indexOf(u8, result.stdio.stderr, match) == null) {
750763 return step.fail(
751764 \\
752765 \\========= expected to find in stderr: =========
......@@ -757,14 +770,14 @@ fn runCommand(
757770 \\{s}
758771 , .{
759772 match,
760 result.stderr,
761 try Step.allocPrintCmd(arena, self.cwd, argv),
773 result.stdio.stderr,
774 try Step.allocPrintCmd(arena, self.cwd, final_argv),
762775 });
763776 }
764777 },
765778 .expect_stdout_exact => |expected_bytes| {
766 assert(!result.stdout_null);
767 if (!mem.eql(u8, expected_bytes, result.stdout)) {
779 assert(!result.stdio.stdout_null);
780 if (!mem.eql(u8, expected_bytes, result.stdio.stdout)) {
768781 return step.fail(
769782 \\
770783 \\========= expected this stdout: =========
......@@ -775,14 +788,14 @@ fn runCommand(
775788 \\{s}
776789 , .{
777790 expected_bytes,
778 result.stdout,
779 try Step.allocPrintCmd(arena, self.cwd, argv),
791 result.stdio.stdout,
792 try Step.allocPrintCmd(arena, self.cwd, final_argv),
780793 });
781794 }
782795 },
783796 .expect_stdout_match => |match| {
784 assert(!result.stdout_null);
785 if (mem.indexOf(u8, result.stdout, match) == null) {
797 assert(!result.stdio.stdout_null);
798 if (mem.indexOf(u8, result.stdio.stdout, match) == null) {
786799 return step.fail(
787800 \\
788801 \\========= expected to find in stdout: =========
......@@ -793,8 +806,8 @@ fn runCommand(
793806 \\{s}
794807 , .{
795808 match,
796 result.stdout,
797 try Step.allocPrintCmd(arena, self.cwd, argv),
809 result.stdio.stdout,
810 try Step.allocPrintCmd(arena, self.cwd, final_argv),
798811 });
799812 }
800813 },
......@@ -803,33 +816,46 @@ fn runCommand(
803816 return step.fail("the following command {} (expected {}):\n{s}", .{
804817 fmtTerm(result.term),
805818 fmtTerm(expected_term),
806 try Step.allocPrintCmd(arena, self.cwd, argv),
819 try Step.allocPrintCmd(arena, self.cwd, final_argv),
807820 });
808821 }
809822 },
810823 },
824 .zig_test => {
825 const expected_term: std.process.Child.Term = .{ .Exited = 0 };
826 if (!termMatches(expected_term, result.term)) {
827 return step.fail("the following command {} (expected {}):\n{s}", .{
828 fmtTerm(result.term),
829 fmtTerm(expected_term),
830 try Step.allocPrintCmd(arena, self.cwd, final_argv),
831 });
832 }
833 if (!result.stdio.test_results.isSuccess()) {
834 return step.fail(
835 "the following test command failed:\n{s}",
836 .{try Step.allocPrintCmd(arena, self.cwd, final_argv)},
837 );
838 }
839 },
811840 else => {
812 try step.handleChildProcessTerm(result.term, self.cwd, argv);
841 try step.handleChildProcessTerm(result.term, self.cwd, final_argv);
813842 },
814843 }
815844}
816845
817846const ChildProcResult = struct {
818 // These use boolean flags instead of optionals as a workaround for
819 // https://github.com/ziglang/zig/issues/14783
820 stdout: []const u8,
821 stderr: []const u8,
822 stdout_null: bool,
823 stderr_null: bool,
824847 term: std.process.Child.Term,
825848 elapsed_ns: u64,
826849 peak_rss: usize,
850
851 stdio: StdIoResult,
827852};
828853
829854fn spawnChildAndCollect(
830855 self: *RunStep,
831856 argv: []const []const u8,
832857 has_side_effects: bool,
858 prog_node: *std.Progress.Node,
833859) !ChildProcResult {
834860 const b = self.step.owner;
835861 const arena = b.allocator;
......@@ -848,16 +874,19 @@ fn spawnChildAndCollect(
848874 .infer_from_args => if (has_side_effects) .Inherit else .Close,
849875 .inherit => .Inherit,
850876 .check => .Close,
877 .zig_test => .Pipe,
851878 };
852879 child.stdout_behavior = switch (self.stdio) {
853880 .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
854881 .inherit => .Inherit,
855882 .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore,
883 .zig_test => .Pipe,
856884 };
857885 child.stderr_behavior = switch (self.stdio) {
858886 .infer_from_args => if (has_side_effects) .Inherit else .Pipe,
859887 .inherit => .Inherit,
860888 .check => .Pipe,
889 .zig_test => .Pipe,
861890 };
862891 if (self.captured_stdout != null) child.stdout_behavior = .Pipe;
863892 if (self.captured_stderr != null) child.stderr_behavior = .Pipe;
......@@ -871,6 +900,219 @@ fn spawnChildAndCollect(
871900 });
872901 var timer = try std.time.Timer.start();
873902
903 const result = if (self.stdio == .zig_test)
904 evalZigTest(self, &child, prog_node)
905 else
906 evalGeneric(self, &child);
907
908 const term = try child.wait();
909 const elapsed_ns = timer.read();
910
911 return .{
912 .stdio = try result,
913 .term = term,
914 .elapsed_ns = elapsed_ns,
915 .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0,
916 };
917}
918
919const StdIoResult = struct {
920 // These use boolean flags instead of optionals as a workaround for
921 // https://github.com/ziglang/zig/issues/14783
922 stdout: []const u8,
923 stderr: []const u8,
924 stdout_null: bool,
925 stderr_null: bool,
926 test_results: Step.TestResults,
927};
928
929fn evalZigTest(
930 self: *RunStep,
931 child: *std.process.Child,
932 prog_node: *std.Progress.Node,
933) !StdIoResult {
934 const gpa = self.step.owner.allocator;
935 const arena = self.step.owner.allocator;
936
937 var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
938 .stdout = child.stdout.?,
939 .stderr = child.stderr.?,
940 });
941 defer poller.deinit();
942
943 try sendMessage(child.stdin.?, .query_test_metadata);
944
945 const Header = std.zig.Server.Message.Header;
946
947 const stdout = poller.fifo(.stdout);
948 const stderr = poller.fifo(.stderr);
949
950 var fail_count: u32 = 0;
951 var skip_count: u32 = 0;
952 var leak_count: u32 = 0;
953 var test_count: u32 = 0;
954
955 var metadata: ?TestMetadata = null;
956
957 var sub_prog_node: ?std.Progress.Node = null;
958 defer if (sub_prog_node) |*n| n.end();
959
960 poll: while (try poller.poll()) {
961 while (true) {
962 const buf = stdout.readableSlice(0);
963 assert(stdout.readableLength() == buf.len);
964 if (buf.len < @sizeOf(Header)) continue :poll;
965 const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]);
966 const header_and_msg_len = header.bytes_len + @sizeOf(Header);
967 if (buf.len < header_and_msg_len) continue :poll;
968 const body = buf[@sizeOf(Header)..][0..header.bytes_len];
969 switch (header.tag) {
970 .zig_version => {
971 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
972 return self.step.fail(
973 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
974 .{ builtin.zig_version_string, body },
975 );
976 }
977 },
978 .test_metadata => {
979 const TmHdr = std.zig.Server.Message.TestMetadata;
980 const tm_hdr = @ptrCast(*align(1) const TmHdr, body);
981 test_count = tm_hdr.tests_len;
982
983 const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)];
984 const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)];
985 const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)];
986 const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len];
987
988 const names = std.mem.bytesAsSlice(u32, names_bytes);
989 const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes);
990 const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes);
991 const names_aligned = try arena.alloc(u32, names.len);
992 for (names_aligned, names) |*dest, src| dest.* = src;
993
994 const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len);
995 for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src;
996
997 const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len);
998 for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src;
999
1000 prog_node.setEstimatedTotalItems(names.len);
1001 metadata = .{
1002 .string_bytes = try arena.dupe(u8, string_bytes),
1003 .names = names_aligned,
1004 .async_frame_lens = async_frame_lens_aligned,
1005 .expected_panic_msgs = expected_panic_msgs_aligned,
1006 .next_index = 0,
1007 .prog_node = prog_node,
1008 };
1009
1010 try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node);
1011 },
1012 .test_results => {
1013 const md = metadata.?;
1014
1015 const TrHdr = std.zig.Server.Message.TestResults;
1016 const tr_hdr = @ptrCast(*align(1) const TrHdr, body);
1017 fail_count += @boolToInt(tr_hdr.flags.fail);
1018 skip_count += @boolToInt(tr_hdr.flags.skip);
1019 leak_count += @boolToInt(tr_hdr.flags.leak);
1020
1021 if (tr_hdr.flags.fail or tr_hdr.flags.leak) {
1022 const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0);
1023 const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n");
1024 const label = if (tr_hdr.flags.fail) "failed" else "leaked";
1025 if (msg.len > 0) {
1026 try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg });
1027 } else {
1028 try self.step.addError("'{s}' {s}", .{ name, label });
1029 }
1030 stderr.discard(msg.len);
1031 }
1032
1033 try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node);
1034 },
1035 else => {}, // ignore other messages
1036 }
1037 stdout.discard(header_and_msg_len);
1038 }
1039 }
1040
1041 if (stderr.readableLength() > 0) {
1042 const msg = std.mem.trim(u8, try stderr.toOwnedSlice(), "\n");
1043 if (msg.len > 0) try self.step.result_error_msgs.append(arena, msg);
1044 }
1045
1046 // Send EOF to stdin.
1047 child.stdin.?.close();
1048 child.stdin = null;
1049
1050 return .{
1051 .stdout = &.{},
1052 .stderr = &.{},
1053 .stdout_null = true,
1054 .stderr_null = true,
1055 .test_results = .{
1056 .test_count = test_count,
1057 .fail_count = fail_count,
1058 .skip_count = skip_count,
1059 .leak_count = leak_count,
1060 },
1061 };
1062}
1063
1064const TestMetadata = struct {
1065 names: []const u32,
1066 async_frame_lens: []const u32,
1067 expected_panic_msgs: []const u32,
1068 string_bytes: []const u8,
1069 next_index: u32,
1070 prog_node: *std.Progress.Node,
1071
1072 fn testName(tm: TestMetadata, index: u32) []const u8 {
1073 return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0);
1074 }
1075};
1076
1077fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
1078 while (metadata.next_index < metadata.names.len) {
1079 const i = metadata.next_index;
1080 metadata.next_index += 1;
1081
1082 if (metadata.async_frame_lens[i] != 0) continue;
1083 if (metadata.expected_panic_msgs[i] != 0) continue;
1084
1085 const name = metadata.testName(i);
1086 if (sub_prog_node.*) |*n| n.end();
1087 sub_prog_node.* = metadata.prog_node.start(name, 0);
1088
1089 try sendRunTestMessage(in, i);
1090 return;
1091 } else {
1092 try sendMessage(in, .exit);
1093 }
1094}
1095
1096fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
1097 const header: std.zig.Client.Message.Header = .{
1098 .tag = tag,
1099 .bytes_len = 0,
1100 };
1101 try file.writeAll(std.mem.asBytes(&header));
1102}
1103
1104fn sendRunTestMessage(file: std.fs.File, index: u32) !void {
1105 const header: std.zig.Client.Message.Header = .{
1106 .tag = .run_test,
1107 .bytes_len = 4,
1108 };
1109 const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index);
1110 try file.writeAll(full_msg);
1111}
1112
1113fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult {
1114 const arena = self.step.owner.allocator;
1115
8741116 if (self.stdin) |stdin| {
8751117 child.stdin.?.writeAll(stdin) catch |err| {
8761118 return self.step.fail("unable to write stdin: {s}", .{@errorName(err)});
......@@ -925,17 +1167,12 @@ fn spawnChildAndCollect(
9251167 }
9261168 }
9271169
928 const term = try child.wait();
929 const elapsed_ns = timer.read();
930
9311170 return .{
9321171 .stdout = stdout_bytes,
9331172 .stderr = stderr_bytes,
9341173 .stdout_null = stdout_null,
9351174 .stderr_null = stderr_null,
936 .term = term,
937 .elapsed_ns = elapsed_ns,
938 .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0,
1175 .test_results = .{},
9391176 };
9401177}
9411178
......@@ -966,7 +1203,7 @@ fn failForeign(
9661203 exe: *CompileStep,
9671204) error{ MakeFailed, MakeSkipped, OutOfMemory } {
9681205 switch (self.stdio) {
969 .check => {
1206 .check, .zig_test => {
9701207 if (self.skip_foreign_checks)
9711208 return error.MakeSkipped;
9721209
......@@ -987,7 +1224,7 @@ fn failForeign(
9871224
9881225fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void {
9891226 switch (stdio) {
990 .infer_from_args, .inherit => {},
1227 .infer_from_args, .inherit, .zig_test => {},
9911228 .check => |checks| for (checks.items) |check| {
9921229 hh.add(@as(std.meta.Tag(StdIo.Check), check));
9931230 switch (check) {
lib/std/Build/Step.zig+30-3
......@@ -35,11 +35,27 @@ result_cached: bool,
3535result_duration_ns: ?u64,
3636/// 0 means unavailable or not reported.
3737result_peak_rss: usize,
38test_results: TestResults,
3839
3940/// The return addresss associated with creation of this step that can be useful
4041/// to print along with debugging messages.
4142debug_stack_trace: [n_debug_stack_frames]usize,
4243
44pub const TestResults = struct {
45 fail_count: u32 = 0,
46 skip_count: u32 = 0,
47 leak_count: u32 = 0,
48 test_count: u32 = 0,
49
50 pub fn isSuccess(tr: TestResults) bool {
51 return tr.fail_count == 0 and tr.leak_count == 0;
52 }
53
54 pub fn passCount(tr: TestResults) u32 {
55 return tr.test_count - tr.fail_count - tr.skip_count;
56 }
57};
58
4359pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void;
4460
4561const n_debug_stack_frames = 4;
......@@ -134,6 +150,7 @@ pub fn init(options: Options) Step {
134150 .result_cached = false,
135151 .result_duration_ns = null,
136152 .result_peak_rss = 0,
153 .test_results = .{},
137154 };
138155}
139156
......@@ -152,6 +169,10 @@ pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkip
152169 },
153170 };
154171
172 if (!s.test_results.isSuccess()) {
173 return error.MakeFailed;
174 }
175
155176 if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) {
156177 const msg = std.fmt.allocPrint(arena, "memory usage peaked at {d} bytes, exceeding the declared upper bound of {d}", .{
157178 s.result_peak_rss, s.max_rss,
......@@ -346,9 +367,7 @@ pub fn evalZigProcess(
346367 s.result_cached = ebp_hdr.flags.cache_hit;
347368 result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
348369 },
349 _ => {
350 // Unrecognized message.
351 },
370 else => {}, // ignore other messages
352371 }
353372 stdout.discard(header_and_msg_len);
354373 }
......@@ -475,3 +494,11 @@ fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyer
475494 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
476495 return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path });
477496}
497
498pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void {
499 if (s.test_results.isSuccess()) {
500 man.writeManifest() catch |err| {
501 try s.addError("unable to write cache manifest: {s}", .{@errorName(err)});
502 };
503 }
504}
lib/std/Build/WriteFileStep.zig+1-1
......@@ -282,7 +282,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
282282 });
283283 }
284284
285 try man.writeManifest();
285 try step.writeManifest(&man);
286286}
287287
288288const std = @import("../std.zig");
lib/std/zig/Client.zig+7
......@@ -26,6 +26,13 @@ pub const Message = struct {
2626 /// swap.
2727 /// No body.
2828 hot_update,
29 /// Ask the test runner for metadata about all the unit tests that can
30 /// be run. Server will respond with a `test_metadata` message.
31 /// No body.
32 query_test_metadata,
33 /// Ask the test runner to run a particular test.
34 /// The message body is a u32 test index.
35 run_test,
2936
3037 _,
3138 };
lib/std/zig/Server.zig+200
......@@ -1,3 +1,7 @@
1in: std.fs.File,
2out: std.fs.File,
3receive_fifo: std.fifo.LinearFifo(u8, .Dynamic),
4
15pub const Message = struct {
26 pub const Header = extern struct {
37 tag: Tag,
......@@ -14,6 +18,11 @@ pub const Message = struct {
1418 progress,
1519 /// Body is a EmitBinPath.
1620 emit_bin_path,
21 /// Body is a TestMetadata
22 test_metadata,
23 /// Body is a TestResults
24 test_results,
25
1726 _,
1827 };
1928
......@@ -26,6 +35,33 @@ pub const Message = struct {
2635 string_bytes_len: u32,
2736 };
2837
38 /// Trailing:
39 /// * name: [tests_len]u32
40 /// - null-terminated string_bytes index
41 /// * async_frame_len: [tests_len]u32,
42 /// - 0 means not async
43 /// * expected_panic_msg: [tests_len]u32,
44 /// - null-terminated string_bytes index
45 /// - 0 means does not expect pani
46 /// * string_bytes: [string_bytes_len]u8,
47 pub const TestMetadata = extern struct {
48 string_bytes_len: u32,
49 tests_len: u32,
50 };
51
52 pub const TestResults = extern struct {
53 index: u32,
54 flags: Flags,
55
56 pub const Flags = packed struct(u8) {
57 fail: bool,
58 skip: bool,
59 leak: bool,
60
61 reserved: u5 = 0,
62 };
63 };
64
2965 /// Trailing:
3066 /// * the file system path the emitted binary can be found
3167 pub const EmitBinPath = extern struct {
......@@ -37,3 +73,167 @@ pub const Message = struct {
3773 };
3874 };
3975};
76
77pub const Options = struct {
78 gpa: Allocator,
79 in: std.fs.File,
80 out: std.fs.File,
81 zig_version: []const u8,
82};
83
84pub fn init(options: Options) !Server {
85 var s: Server = .{
86 .in = options.in,
87 .out = options.out,
88 .receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(options.gpa),
89 };
90 try s.serveStringMessage(.zig_version, options.zig_version);
91 return s;
92}
93
94pub fn deinit(s: *Server) void {
95 s.receive_fifo.deinit();
96 s.* = undefined;
97}
98
99pub fn receiveMessage(s: *Server) !InMessage.Header {
100 const Header = InMessage.Header;
101 const fifo = &s.receive_fifo;
102
103 while (true) {
104 const buf = fifo.readableSlice(0);
105 assert(fifo.readableLength() == buf.len);
106 if (buf.len >= @sizeOf(Header)) {
107 const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]);
108
109 if (buf.len - @sizeOf(Header) >= header.bytes_len) {
110 const result = header.*;
111 fifo.discard(@sizeOf(Header));
112 return result;
113 } else {
114 const needed = header.bytes_len - (buf.len - @sizeOf(Header));
115 const write_buffer = try fifo.writableWithSize(needed);
116 const amt = try s.in.read(write_buffer);
117 fifo.update(amt);
118 continue;
119 }
120 }
121
122 const write_buffer = try fifo.writableWithSize(256);
123 const amt = try s.in.read(write_buffer);
124 fifo.update(amt);
125 }
126}
127
128pub fn receiveBody_u32(s: *Server) !u32 {
129 const fifo = &s.receive_fifo;
130 const buf = fifo.readableSlice(0);
131 const result = @ptrCast(*align(1) const u32, buf[0..4]).*;
132 fifo.discard(4);
133 return result;
134}
135
136pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void {
137 return s.serveMessage(.{
138 .tag = tag,
139 .bytes_len = @intCast(u32, msg.len),
140 }, &.{msg});
141}
142
143pub fn serveMessage(
144 s: *const Server,
145 header: OutMessage.Header,
146 bufs: []const []const u8,
147) !void {
148 var iovecs: [10]std.os.iovec_const = undefined;
149 iovecs[0] = .{
150 .iov_base = @ptrCast([*]const u8, &header),
151 .iov_len = @sizeOf(OutMessage.Header),
152 };
153 for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| {
154 iovec.* = .{
155 .iov_base = buf.ptr,
156 .iov_len = buf.len,
157 };
158 }
159 try s.out.writevAll(iovecs[0 .. bufs.len + 1]);
160}
161
162pub fn serveEmitBinPath(
163 s: *Server,
164 fs_path: []const u8,
165 header: OutMessage.EmitBinPath,
166) !void {
167 try s.serveMessage(.{
168 .tag = .emit_bin_path,
169 .bytes_len = @intCast(u32, fs_path.len + @sizeOf(OutMessage.EmitBinPath)),
170 }, &.{
171 std.mem.asBytes(&header),
172 fs_path,
173 });
174}
175
176pub fn serveTestResults(
177 s: *Server,
178 msg: OutMessage.TestResults,
179) !void {
180 try s.serveMessage(.{
181 .tag = .test_results,
182 .bytes_len = @intCast(u32, @sizeOf(OutMessage.TestResults)),
183 }, &.{
184 std.mem.asBytes(&msg),
185 });
186}
187
188pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
189 const eb_hdr: OutMessage.ErrorBundle = .{
190 .extra_len = @intCast(u32, error_bundle.extra.len),
191 .string_bytes_len = @intCast(u32, error_bundle.string_bytes.len),
192 };
193 const bytes_len = @sizeOf(OutMessage.ErrorBundle) +
194 4 * error_bundle.extra.len + error_bundle.string_bytes.len;
195 try s.serveMessage(.{
196 .tag = .error_bundle,
197 .bytes_len = @intCast(u32, bytes_len),
198 }, &.{
199 std.mem.asBytes(&eb_hdr),
200 // TODO: implement @ptrCast between slices changing the length
201 std.mem.sliceAsBytes(error_bundle.extra),
202 error_bundle.string_bytes,
203 });
204}
205
206pub const TestMetadata = struct {
207 names: []const u32,
208 async_frame_sizes: []const u32,
209 expected_panic_msgs: []const u32,
210 string_bytes: []const u8,
211};
212
213pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void {
214 const header: OutMessage.TestMetadata = .{
215 .tests_len = @intCast(u32, test_metadata.names.len),
216 .string_bytes_len = @intCast(u32, test_metadata.string_bytes.len),
217 };
218 const bytes_len = @sizeOf(OutMessage.TestMetadata) +
219 3 * 4 * test_metadata.names.len + test_metadata.string_bytes.len;
220 return s.serveMessage(.{
221 .tag = .test_metadata,
222 .bytes_len = @intCast(u32, bytes_len),
223 }, &.{
224 std.mem.asBytes(&header),
225 // TODO: implement @ptrCast between slices changing the length
226 std.mem.sliceAsBytes(test_metadata.names),
227 std.mem.sliceAsBytes(test_metadata.async_frame_sizes),
228 std.mem.sliceAsBytes(test_metadata.expected_panic_msgs),
229 test_metadata.string_bytes,
230 });
231}
232
233const OutMessage = std.zig.Server.Message;
234const InMessage = std.zig.Client.Message;
235
236const Server = @This();
237const std = @import("std");
238const Allocator = std.mem.Allocator;
239const assert = std.debug.assert;
lib/test_runner.zig+127-45
......@@ -8,14 +8,130 @@ pub const std_options = struct {
88};
99
1010var log_err_count: usize = 0;
11var cmdline_buffer: [4096]u8 = undefined;
12var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);
1113
1214pub fn main() void {
13 if (builtin.zig_backend != .stage1 and
14 builtin.zig_backend != .stage2_llvm and
15 builtin.zig_backend != .stage2_c)
15 if (builtin.zig_backend == .stage2_wasm or
16 builtin.zig_backend == .stage2_x86_64 or
17 builtin.zig_backend == .stage2_aarch64)
1618 {
17 return main2() catch @panic("test failure");
19 return mainSimple() catch @panic("test failure");
20 }
21
22 const args = std.process.argsAlloc(fba.allocator()) catch
23 @panic("unable to parse command line args");
24
25 var listen = false;
26
27 for (args[1..]) |arg| {
28 if (std.mem.eql(u8, arg, "--listen=-")) {
29 listen = true;
30 } else {
31 @panic("unrecognized command line argument");
32 }
33 }
34
35 if (listen) {
36 return mainServer();
37 } else {
38 return mainTerminal();
39 }
40}
41
42fn mainServer() void {
43 return mainServerFallible() catch @panic("internal test runner failure");
44}
45
46fn mainServerFallible() !void {
47 var server = try std.zig.Server.init(.{
48 .gpa = fba.allocator(),
49 .in = std.io.getStdIn(),
50 .out = std.io.getStdOut(),
51 .zig_version = builtin.zig_version_string,
52 });
53 defer server.deinit();
54
55 while (true) {
56 const hdr = try server.receiveMessage();
57 switch (hdr.tag) {
58 .exit => {
59 return std.process.exit(0);
60 },
61 .query_test_metadata => {
62 std.testing.allocator_instance = .{};
63 defer if (std.testing.allocator_instance.deinit()) {
64 @panic("internal test runner memory leak");
65 };
66
67 var string_bytes: std.ArrayListUnmanaged(u8) = .{};
68 defer string_bytes.deinit(std.testing.allocator);
69 try string_bytes.append(std.testing.allocator, 0); // Reserve 0 for null.
70
71 const test_fns = builtin.test_functions;
72 const names = try std.testing.allocator.alloc(u32, test_fns.len);
73 defer std.testing.allocator.free(names);
74 const async_frame_sizes = try std.testing.allocator.alloc(u32, test_fns.len);
75 defer std.testing.allocator.free(async_frame_sizes);
76 const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len);
77 defer std.testing.allocator.free(expected_panic_msgs);
78
79 for (test_fns, names, async_frame_sizes, expected_panic_msgs) |test_fn, *name, *async_frame_size, *expected_panic_msg| {
80 name.* = @intCast(u32, string_bytes.items.len);
81 try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1);
82 string_bytes.appendSliceAssumeCapacity(test_fn.name);
83 string_bytes.appendAssumeCapacity(0);
84
85 async_frame_size.* = @intCast(u32, test_fn.async_frame_size orelse 0);
86 expected_panic_msg.* = 0;
87 }
88
89 try server.serveTestMetadata(.{
90 .names = names,
91 .async_frame_sizes = async_frame_sizes,
92 .expected_panic_msgs = expected_panic_msgs,
93 .string_bytes = string_bytes.items,
94 });
95 },
96
97 .run_test => {
98 std.testing.allocator_instance = .{};
99 const index = try server.receiveBody_u32();
100 const test_fn = builtin.test_functions[index];
101 if (test_fn.async_frame_size != null)
102 @panic("TODO test runner implement async tests");
103 var fail = false;
104 var skip = false;
105 var leak = false;
106 test_fn.func() catch |err| switch (err) {
107 error.SkipZigTest => skip = true,
108 else => {
109 fail = true;
110 if (@errorReturnTrace()) |trace| {
111 std.debug.dumpStackTrace(trace.*);
112 }
113 },
114 };
115 leak = std.testing.allocator_instance.deinit();
116 try server.serveTestResults(.{
117 .index = index,
118 .flags = .{
119 .fail = fail,
120 .skip = skip,
121 .leak = leak,
122 },
123 });
124 },
125
126 else => {
127 std.debug.print("unsupported message: {x}", .{@enumToInt(hdr.tag)});
128 std.process.exit(1);
129 },
130 }
18131 }
132}
133
134fn mainTerminal() void {
19135 const test_fn_list = builtin.test_functions;
20136 var ok_count: usize = 0;
21137 var skip_count: usize = 0;
......@@ -118,51 +234,17 @@ pub fn log(
118234 }
119235}
120236
121pub fn main2() anyerror!void {
122 var skipped: usize = 0;
123 var failed: usize = 0;
124 // Simpler main(), exercising fewer language features, so that stage2 can handle it.
237/// Simpler main(), exercising fewer language features, so that
238/// work-in-progress backends can handle it.
239pub fn mainSimple() anyerror!void {
240 //const stderr = std.io.getStdErr();
125241 for (builtin.test_functions) |test_fn| {
126242 test_fn.func() catch |err| {
127243 if (err != error.SkipZigTest) {
128 failed += 1;
129 } else {
130 skipped += 1;
244 //stderr.writeAll(test_fn.name) catch {};
245 //stderr.writeAll("\n") catch {};
246 return err;
131247 }
132248 };
133249 }
134 if (builtin.zig_backend == .stage2_wasm or
135 builtin.zig_backend == .stage2_x86_64 or
136 builtin.zig_backend == .stage2_aarch64 or
137 builtin.zig_backend == .stage2_llvm or
138 builtin.zig_backend == .stage2_c)
139 {
140 const passed = builtin.test_functions.len - skipped - failed;
141 const stderr = std.io.getStdErr();
142 writeInt(stderr, passed) catch {};
143 stderr.writeAll(" passed; ") catch {};
144 writeInt(stderr, skipped) catch {};
145 stderr.writeAll(" skipped; ") catch {};
146 writeInt(stderr, failed) catch {};
147 stderr.writeAll(" failed.\n") catch {};
148 }
149 if (failed != 0) {
150 return error.TestsFailed;
151 }
152}
153
154fn writeInt(stderr: std.fs.File, int: usize) anyerror!void {
155 const base = 10;
156 var buf: [100]u8 = undefined;
157 var a: usize = int;
158 var index: usize = buf.len;
159 while (true) {
160 const digit = a % base;
161 index -= 1;
162 buf[index] = std.fmt.digitToChar(@intCast(u8, digit), .lower);
163 a /= base;
164 if (a == 0) break;
165 }
166 const slice = buf[index..];
167 try stderr.writeAll(slice);
168250}
src/Server.zig deleted-113
......@@ -1,113 +0,0 @@
1in: std.fs.File,
2out: std.fs.File,
3receive_fifo: std.fifo.LinearFifo(u8, .Dynamic),
4
5pub const Options = struct {
6 gpa: Allocator,
7 in: std.fs.File,
8 out: std.fs.File,
9};
10
11pub fn init(options: Options) !Server {
12 var s: Server = .{
13 .in = options.in,
14 .out = options.out,
15 .receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(options.gpa),
16 };
17 try s.serveStringMessage(.zig_version, build_options.version);
18 return s;
19}
20
21pub fn deinit(s: *Server) void {
22 s.receive_fifo.deinit();
23 s.* = undefined;
24}
25
26pub fn receiveMessage(s: *Server) !InMessage.Header {
27 const Header = InMessage.Header;
28 const fifo = &s.receive_fifo;
29
30 while (true) {
31 const buf = fifo.readableSlice(0);
32 assert(fifo.readableLength() == buf.len);
33 if (buf.len >= @sizeOf(Header)) {
34 const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]);
35 if (header.bytes_len != 0)
36 return error.InvalidClientMessage;
37 const result = header.*;
38 fifo.discard(@sizeOf(Header));
39 return result;
40 }
41
42 const write_buffer = try fifo.writableWithSize(256);
43 const amt = try s.in.read(write_buffer);
44 fifo.update(amt);
45 }
46}
47
48pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void {
49 return s.serveMessage(.{
50 .tag = tag,
51 .bytes_len = @intCast(u32, msg.len),
52 }, &.{msg});
53}
54
55pub fn serveMessage(
56 s: *const Server,
57 header: OutMessage.Header,
58 bufs: []const []const u8,
59) !void {
60 var iovecs: [10]std.os.iovec_const = undefined;
61 iovecs[0] = .{
62 .iov_base = @ptrCast([*]const u8, &header),
63 .iov_len = @sizeOf(OutMessage.Header),
64 };
65 for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| {
66 iovec.* = .{
67 .iov_base = buf.ptr,
68 .iov_len = buf.len,
69 };
70 }
71 try s.out.writevAll(iovecs[0 .. bufs.len + 1]);
72}
73
74pub fn serveEmitBinPath(
75 s: *Server,
76 fs_path: []const u8,
77 header: std.zig.Server.Message.EmitBinPath,
78) !void {
79 try s.serveMessage(.{
80 .tag = .emit_bin_path,
81 .bytes_len = @intCast(u32, fs_path.len + @sizeOf(std.zig.Server.Message.EmitBinPath)),
82 }, &.{
83 std.mem.asBytes(&header),
84 fs_path,
85 });
86}
87
88pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
89 const eb_hdr: std.zig.Server.Message.ErrorBundle = .{
90 .extra_len = @intCast(u32, error_bundle.extra.len),
91 .string_bytes_len = @intCast(u32, error_bundle.string_bytes.len),
92 };
93 const bytes_len = @sizeOf(std.zig.Server.Message.ErrorBundle) +
94 4 * error_bundle.extra.len + error_bundle.string_bytes.len;
95 try s.serveMessage(.{
96 .tag = .error_bundle,
97 .bytes_len = @intCast(u32, bytes_len),
98 }, &.{
99 std.mem.asBytes(&eb_hdr),
100 // TODO: implement @ptrCast between slices changing the length
101 std.mem.sliceAsBytes(error_bundle.extra),
102 error_bundle.string_bytes,
103 });
104}
105
106const OutMessage = std.zig.Server.Message;
107const InMessage = std.zig.Client.Message;
108
109const Server = @This();
110const std = @import("std");
111const build_options = @import("build_options");
112const Allocator = std.mem.Allocator;
113const assert = std.debug.assert;
src/main.zig+5-16
......@@ -10,6 +10,7 @@ const ArrayList = std.ArrayList;
1010const Ast = std.zig.Ast;
1111const warn = std.log.warn;
1212const ThreadPool = std.Thread.Pool;
13const cleanExit = std.process.cleanExit;
1314
1415const tracy = @import("tracy.zig");
1516const Compilation = @import("Compilation.zig");
......@@ -26,7 +27,7 @@ const target_util = @import("target.zig");
2627const crash_report = @import("crash_report.zig");
2728const Module = @import("Module.zig");
2829const AstGen = @import("AstGen.zig");
29const Server = @import("Server.zig");
30const Server = std.zig.Server;
3031
3132pub const std_options = struct {
3233 pub const wasiCwd = wasi_cwd;
......@@ -3545,6 +3546,7 @@ fn serve(
35453546 .gpa = gpa,
35463547 .in = in,
35473548 .out = out,
3549 .zig_version = build_options.version,
35483550 });
35493551 defer server.deinit();
35503552
......@@ -3656,8 +3658,8 @@ fn serve(
36563658 );
36573659 }
36583660 },
3659 _ => {
3660 @panic("TODO unrecognized message from client");
3661 else => {
3662 fatal("unrecognized message from client: 0x{x}", .{@enumToInt(hdr.tag)});
36613663 },
36623664 }
36633665 }
......@@ -5624,19 +5626,6 @@ fn detectNativeTargetInfo(cross_target: std.zig.CrossTarget) !std.zig.system.Nat
56245626 return std.zig.system.NativeTargetInfo.detect(cross_target);
56255627}
56265628
5627/// Indicate that we are now terminating with a successful exit code.
5628/// In debug builds, this is a no-op, so that the calling code's
5629/// cleanup mechanisms are tested and so that external tools that
5630/// check for resource leaks can be accurate. In release builds, this
5631/// calls exit(0), and does not return.
5632pub fn cleanExit() void {
5633 if (builtin.mode == .Debug) {
5634 return;
5635 } else {
5636 process.exit(0);
5637 }
5638}
5639
56405629const usage_ast_check =
56415630 \\Usage: zig ast-check [file]
56425631 \\
src/objcopy.zig+5-4
......@@ -8,8 +8,8 @@ const assert = std.debug.assert;
88
99const main = @import("main.zig");
1010const fatal = main.fatal;
11const cleanExit = main.cleanExit;
12const Server = @import("Server.zig");
11const Server = std.zig.Server;
12const build_options = @import("build_options");
1313
1414pub fn cmdObjCopy(
1515 gpa: Allocator,
......@@ -116,6 +116,7 @@ pub fn cmdObjCopy(
116116 .gpa = gpa,
117117 .in = std.io.getStdIn(),
118118 .out = std.io.getStdOut(),
119 .zig_version = build_options.version,
119120 });
120121 defer server.deinit();
121122
......@@ -124,7 +125,7 @@ pub fn cmdObjCopy(
124125 const hdr = try server.receiveMessage();
125126 switch (hdr.tag) {
126127 .exit => {
127 return cleanExit();
128 return std.process.cleanExit();
128129 },
129130 .update => {
130131 if (seen_update) {
......@@ -144,7 +145,7 @@ pub fn cmdObjCopy(
144145 }
145146 }
146147 }
147 return cleanExit();
148 return std.process.cleanExit();
148149}
149150
150151const usage =
test/link/common_symbols/build.zig+1-1
......@@ -24,5 +24,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2424 });
2525 test_exe.linkLibrary(lib_a);
2626
27 test_step.dependOn(&test_exe.step);
27 test_step.dependOn(&test_exe.run().step);
2828}
test/link/common_symbols_alignment/build.zig+1-1
......@@ -24,5 +24,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2424 });
2525 test_exe.linkLibrary(lib_a);
2626
27 test_step.dependOn(&test_exe.step);
27 test_step.dependOn(&test_exe.run().step);
2828}
test/link/interdependent_static_c_libs/build.zig+1-1
......@@ -35,5 +35,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3535 test_exe.linkLibrary(lib_b);
3636 test_exe.addIncludePath(".");
3737
38 test_step.dependOn(&test_exe.step);
38 test_step.dependOn(&test_exe.run().step);
3939}
test/link/macho/tls/build.zig+4-1
......@@ -32,5 +32,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3232 test_exe.linkLibrary(lib);
3333 test_exe.linkLibC();
3434
35 test_step.dependOn(&test_exe.step);
35 const run = test_exe.run();
36 run.skip_foreign_checks = true;
37
38 test_step.dependOn(&run.step);
3639}
test/src/Cases.zig+4-7
......@@ -547,15 +547,12 @@ pub fn lowerToBuildSteps(
547547 parent_step.dependOn(&artifact.step);
548548 },
549549 .Execution => |expected_stdout| {
550 if (case.is_test) {
551 parent_step.dependOn(&artifact.step);
552 } else {
553 const run = b.addRunArtifact(artifact);
554 run.skip_foreign_checks = true;
550 const run = b.addRunArtifact(artifact);
551 run.skip_foreign_checks = true;
552 if (!case.is_test) {
555553 run.expectStdOutEqual(expected_stdout);
556
557 parent_step.dependOn(&run.step);
558554 }
555 parent_step.dependOn(&run.step);
559556 },
560557 .Header => @panic("TODO"),
561558 }
test/standalone/emit_asm_and_bin/build.zig+1-1
......@@ -11,5 +11,5 @@ pub fn build(b: *std.Build) void {
1111 main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") };
1212 main.emit_bin = .{ .emit_to = b.pathFromRoot("main") };
1313
14 test_step.dependOn(&main.step);
14 test_step.dependOn(&main.run().step);
1515}
test/standalone/global_linkage/build.zig+1-1
......@@ -28,5 +28,5 @@ pub fn build(b: *std.Build) void {
2828 main.linkLibrary(obj1);
2929 main.linkLibrary(obj2);
3030
31 test_step.dependOn(&main.step);
31 test_step.dependOn(&main.run().step);
3232}
test/standalone/issue_13970/build.zig+3-3
......@@ -17,7 +17,7 @@ pub fn build(b: *std.Build) void {
1717 test2.setTestRunner("src/main.zig");
1818 test3.setTestRunner("src/main.zig");
1919
20 test_step.dependOn(&test1.step);
21 test_step.dependOn(&test2.step);
22 test_step.dependOn(&test3.step);
20 test_step.dependOn(&test1.run().step);
21 test_step.dependOn(&test2.run().step);
22 test_step.dependOn(&test3.run().step);
2323}
test/standalone/main_pkg_path/build.zig+1-1
......@@ -9,5 +9,5 @@ pub fn build(b: *std.Build) void {
99 });
1010 test_exe.setMainPkgPath(".");
1111
12 test_step.dependOn(&test_exe.step);
12 test_step.dependOn(&test_exe.run().step);
1313}
test/standalone/options/build.zig+1-1
......@@ -20,5 +20,5 @@ pub fn build(b: *std.Build) void {
2020 options.addOption([]const u8, "string", b.option([]const u8, "string", "s").?);
2121
2222 const test_step = b.step("test", "Run unit tests");
23 test_step.dependOn(&main.step);
23 test_step.dependOn(&main.run().step);
2424}
test/standalone/pie/build.zig+1-1
......@@ -17,5 +17,5 @@ pub fn build(b: *std.Build) void {
1717 });
1818 main.pie = true;
1919
20 test_step.dependOn(&main.step);
20 test_step.dependOn(&main.run().step);
2121}
test/standalone/static_c_lib/build.zig+1-1
......@@ -21,5 +21,5 @@ pub fn build(b: *std.Build) void {
2121 test_exe.linkLibrary(foo);
2222 test_exe.addIncludePath(".");
2323
24 test_step.dependOn(&test_exe.step);
24 test_step.dependOn(&test_exe.run().step);
2525}
test/standalone/test_runner_module_imports/build.zig+1-1
......@@ -15,5 +15,5 @@ pub fn build(b: *std.Build) void {
1515 t.addModule("module2", module2);
1616
1717 const test_step = b.step("test", "Run unit tests");
18 test_step.dependOn(&t.step);
18 test_step.dependOn(&t.run().step);
1919}
test/standalone/test_runner_path/build.zig-1
......@@ -8,7 +8,6 @@ pub fn build(b: *std.Build) void {
88
99 const test_exe = b.addTest(.{
1010 .root_source_file = .{ .path = "test.zig" },
11 .kind = .test_exe,
1211 });
1312 test_exe.test_runner = "test_runner.zig";
1413
test/standalone/use_alias/build.zig+1-1
......@@ -12,5 +12,5 @@ pub fn build(b: *std.Build) void {
1212 });
1313 main.addIncludePath(".");
1414
15 test_step.dependOn(&main.step);
15 test_step.dependOn(&main.run().step);
1616}
test/tests.zig+16-11
......@@ -596,7 +596,7 @@ pub fn addStandaloneTests(
596596 });
597597 if (case.link_libc) exe.linkLibC();
598598
599 step.dependOn(&exe.step);
599 step.dependOn(&exe.run().step);
600600 }
601601 }
602602 }
......@@ -981,14 +981,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
981981 });
982982 const single_threaded_txt = if (test_target.single_threaded) "single" else "multi";
983983 const backend_txt = if (test_target.backend) |backend| @tagName(backend) else "default";
984 these_tests.setNamePrefix(b.fmt("{s}-{s}-{s}-{s}-{s}-{s} ", .{
985 options.name,
986 triple_prefix,
987 @tagName(test_target.optimize_mode),
988 libc_prefix,
989 single_threaded_txt,
990 backend_txt,
991 }));
992984 these_tests.single_threaded = test_target.single_threaded;
993985 these_tests.setFilter(options.test_filter);
994986 if (test_target.link_libc) {
......@@ -1014,7 +1006,18 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
10141006 },
10151007 };
10161008
1017 step.dependOn(&these_tests.step);
1009 const run = these_tests.run();
1010 run.skip_foreign_checks = true;
1011 run.setName(b.fmt("run test {s}-{s}-{s}-{s}-{s}-{s}", .{
1012 options.name,
1013 triple_prefix,
1014 @tagName(test_target.optimize_mode),
1015 libc_prefix,
1016 single_threaded_txt,
1017 backend_txt,
1018 }));
1019
1020 step.dependOn(&run.step);
10181021 }
10191022 return step;
10201023}
......@@ -1053,7 +1056,9 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S
10531056 @tagName(optimize_mode),
10541057 }));
10551058
1056 step.dependOn(&test_step.step);
1059 const run = test_step.run();
1060 run.skip_foreign_checks = true;
1061 step.dependOn(&run.step);
10571062 }
10581063 }
10591064 return step;