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...@@ -518,6 +518,7 @@ set(ZIG_STAGE2_SOURCES
518 "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig"518 "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig"
519 "${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig"519 "${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig"
520 "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"520 "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"
521 "${CMAKE_SOURCE_DIR}/lib/std/zig/Server.zig"
521 "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"522 "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"
522 "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"523 "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
523 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"524 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"
...@@ -623,7 +624,6 @@ set(ZIG_STAGE2_SOURCES...@@ -623,7 +624,6 @@ set(ZIG_STAGE2_SOURCES
623 "${CMAKE_SOURCE_DIR}/src/print_targets.zig"624 "${CMAKE_SOURCE_DIR}/src/print_targets.zig"
624 "${CMAKE_SOURCE_DIR}/src/print_zir.zig"625 "${CMAKE_SOURCE_DIR}/src/print_zir.zig"
625 "${CMAKE_SOURCE_DIR}/src/register_manager.zig"626 "${CMAKE_SOURCE_DIR}/src/register_manager.zig"
626 "${CMAKE_SOURCE_DIR}/src/Server.zig"
627 "${CMAKE_SOURCE_DIR}/src/target.zig"627 "${CMAKE_SOURCE_DIR}/src/target.zig"
628 "${CMAKE_SOURCE_DIR}/src/tracy.zig"628 "${CMAKE_SOURCE_DIR}/src/tracy.zig"
629 "${CMAKE_SOURCE_DIR}/src/translate_c.zig"629 "${CMAKE_SOURCE_DIR}/src/translate_c.zig"
lib/build_runner.zig+57-2
...@@ -416,6 +416,12 @@ fn runStepNames(...@@ -416,6 +416,12 @@ fn runStepNames(
416 }416 }
417 assert(run.memory_blocked_steps.items.len == 0);417 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
419 var success_count: usize = 0;425 var success_count: usize = 0;
420 var skipped_count: usize = 0;426 var skipped_count: usize = 0;
421 var failure_count: usize = 0;427 var failure_count: usize = 0;
...@@ -425,6 +431,12 @@ fn runStepNames(...@@ -425,6 +431,12 @@ fn runStepNames(
425 defer compile_error_steps.deinit(gpa);431 defer compile_error_steps.deinit(gpa);
426432
427 for (step_stack.keys()) |s| {433 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
428 switch (s.state) {440 switch (s.state) {
429 .precheck_unstarted => unreachable,441 .precheck_unstarted => unreachable,
430 .precheck_started => unreachable,442 .precheck_started => unreachable,
...@@ -468,6 +480,11 @@ fn runStepNames(...@@ -468,6 +480,11 @@ fn runStepNames(
468 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};480 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
469 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};481 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
471 if (run.enable_summary == null) {488 if (run.enable_summary == null) {
472 ttyconf.setColor(stderr, .Dim) catch {};489 ttyconf.setColor(stderr, .Dim) catch {};
473 stderr.writeAll(" (disable with -fno-summary)") catch {};490 stderr.writeAll(" (disable with -fno-summary)") catch {};
...@@ -566,6 +583,13 @@ fn printTreeStep(...@@ -566,6 +583,13 @@ fn printTreeStep(
566 try ttyconf.setColor(stderr, .Green);583 try ttyconf.setColor(stderr, .Green);
567 if (s.result_cached) {584 if (s.result_cached) {
568 try stderr.writeAll(" cached");585 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 }
569 } else {593 } else {
570 try stderr.writeAll(" success");594 try stderr.writeAll(" success");
571 }595 }
...@@ -609,15 +633,46 @@ fn printTreeStep(...@@ -609,15 +633,46 @@ fn printTreeStep(
609 },633 },
610634
611 .failure => {635 .failure => {
612 try ttyconf.setColor(stderr, .Red);
613 if (s.result_error_bundle.errorMessageCount() > 0) {636 if (s.result_error_bundle.errorMessageCount() > 0) {
637 try ttyconf.setColor(stderr, .Red);
614 try stderr.writer().print(" {d} errors\n", .{638 try stderr.writer().print(" {d} errors\n", .{
615 s.result_error_bundle.errorMessageCount(),639 s.result_error_bundle.errorMessageCount(),
616 });640 });
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");
617 } else {671 } else {
672 try ttyconf.setColor(stderr, .Red);
618 try stderr.writeAll(" failure\n");673 try stderr.writeAll(" failure\n");
674 try ttyconf.setColor(stderr, .Reset);
619 }675 }
620 try ttyconf.setColor(stderr, .Reset);
621 },676 },
622 }677 }
623678
lib/std/Build.zig+4-6
...@@ -531,7 +531,6 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {...@@ -531,7 +531,6 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
531531
532pub const TestOptions = struct {532pub const TestOptions = struct {
533 name: []const u8 = "test",533 name: []const u8 = "test",
534 kind: CompileStep.Kind = .@"test",
535 root_source_file: FileSource,534 root_source_file: FileSource,
536 target: CrossTarget = .{},535 target: CrossTarget = .{},
537 optimize: std.builtin.Mode = .Debug,536 optimize: std.builtin.Mode = .Debug,
...@@ -542,7 +541,7 @@ pub const TestOptions = struct {...@@ -542,7 +541,7 @@ pub const TestOptions = struct {
542pub fn addTest(b: *Build, options: TestOptions) *CompileStep {541pub fn addTest(b: *Build, options: TestOptions) *CompileStep {
543 return CompileStep.create(b, .{542 return CompileStep.create(b, .{
544 .name = options.name,543 .name = options.name,
545 .kind = options.kind,544 .kind = .@"test",
546 .root_source_file = options.root_source_file,545 .root_source_file = options.root_source_file,
547 .target = options.target,546 .target = options.target,
548 .optimize = options.optimize,547 .optimize = options.optimize,
...@@ -626,16 +625,15 @@ pub fn addSystemCommand(self: *Build, argv: []const []const u8) *RunStep {...@@ -626,16 +625,15 @@ pub fn addSystemCommand(self: *Build, argv: []const []const u8) *RunStep {
626/// Creates a `RunStep` with an executable built with `addExecutable`.625/// Creates a `RunStep` with an executable built with `addExecutable`.
627/// Add command line arguments with methods of `RunStep`.626/// Add command line arguments with methods of `RunStep`.
628pub fn addRunArtifact(b: *Build, exe: *CompileStep) *RunStep {627pub fn addRunArtifact(b: *Build, exe: *CompileStep) *RunStep {
629 assert(exe.kind == .exe or exe.kind == .test_exe);
630
631 // It doesn't have to be native. We catch that if you actually try to run it.628 // It doesn't have to be native. We catch that if you actually try to run it.
632 // Consider that this is declarative; the run step may not be run unless a user629 // Consider that this is declarative; the run step may not be run unless a user
633 // option is supplied.630 // option is supplied.
634 const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.name}));631 const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.name}));
635 run_step.addArtifactArg(exe);632 run_step.addArtifactArg(exe);
636633
637 if (exe.kind == .test_exe) {634 if (exe.kind == .@"test") {
638 run_step.addArg(b.zig_exe);635 run_step.stdio = .zig_test;
636 run_step.addArgs(&.{"--listen=-"});
639 }637 }
640638
641 if (exe.vcpkg_bin_path) |path| {639 if (exe.vcpkg_bin_path) |path| {
lib/std/Build/CompileStep.zig+7-86
...@@ -289,7 +289,6 @@ pub const Kind = enum {...@@ -289,7 +289,6 @@ pub const Kind = enum {
289 lib,289 lib,
290 obj,290 obj,
291 @"test",291 @"test",
292 test_exe,
293};292};
294293
295pub const Linkage = enum { dynamic, static };294pub const Linkage = enum { dynamic, static };
...@@ -328,7 +327,7 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep {...@@ -328,7 +327,7 @@ pub fn create(owner: *std.Build, options: Options) *CompileStep {
328 .exe => "zig build-exe",327 .exe => "zig build-exe",
329 .lib => "zig build-lib",328 .lib => "zig build-lib",
330 .obj => "zig build-obj",329 .obj => "zig build-obj",
331 .test_exe, .@"test" => "zig test",330 .@"test" => "zig test",
332 },331 },
333 name_adjusted,332 name_adjusted,
334 @tagName(options.optimize),333 @tagName(options.optimize),
...@@ -410,7 +409,7 @@ fn computeOutFileNames(self: *CompileStep) void {...@@ -410,7 +409,7 @@ fn computeOutFileNames(self: *CompileStep) void {
410 .output_mode = switch (self.kind) {409 .output_mode = switch (self.kind) {
411 .lib => .Lib,410 .lib => .Lib,
412 .obj => .Obj,411 .obj => .Obj,
413 .exe, .@"test", .test_exe => .Exe,412 .exe, .@"test" => .Exe,
414 },413 },
415 .link_mode = if (self.linkage) |some| @as(std.builtin.LinkMode, switch (some) {414 .link_mode = if (self.linkage) |some| @as(std.builtin.LinkMode, switch (some) {
416 .dynamic => .Dynamic,415 .dynamic => .Dynamic,
...@@ -621,7 +620,7 @@ pub fn producesPdbFile(self: *CompileStep) bool {...@@ -621,7 +620,7 @@ pub fn producesPdbFile(self: *CompileStep) bool {
621 if (!self.target.isWindows() and !self.target.isUefi()) return false;620 if (!self.target.isWindows() and !self.target.isUefi()) return false;
622 if (self.target.getObjectFormat() == .c) return false;621 if (self.target.getObjectFormat() == .c) return false;
623 if (self.strip == true) return false;622 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";
625}624}
626625
627pub fn linkLibC(self: *CompileStep) void {626pub fn linkLibC(self: *CompileStep) void {
...@@ -850,19 +849,19 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {...@@ -850,19 +849,19 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {
850849
851pub fn setNamePrefix(self: *CompileStep, text: []const u8) void {850pub fn setNamePrefix(self: *CompileStep, text: []const u8) void {
852 const b = self.step.owner;851 const b = self.step.owner;
853 assert(self.kind == .@"test" or self.kind == .test_exe);852 assert(self.kind == .@"test");
854 self.name_prefix = b.dupe(text);853 self.name_prefix = b.dupe(text);
855}854}
856855
857pub fn setFilter(self: *CompileStep, text: ?[]const u8) void {856pub fn setFilter(self: *CompileStep, text: ?[]const u8) void {
858 const b = self.step.owner;857 const b = self.step.owner;
859 assert(self.kind == .@"test" or self.kind == .test_exe);858 assert(self.kind == .@"test");
860 self.filter = if (text) |t| b.dupe(t) else null;859 self.filter = if (text) |t| b.dupe(t) else null;
861}860}
862861
863pub fn setTestRunner(self: *CompileStep, path: ?[]const u8) void {862pub fn setTestRunner(self: *CompileStep, path: ?[]const u8) void {
864 const b = self.step.owner;863 const b = self.step.owner;
865 assert(self.kind == .@"test" or self.kind == .test_exe);864 assert(self.kind == .@"test");
866 self.test_runner = if (path) |p| b.dupePath(p) else null;865 self.test_runner = if (path) |p| b.dupePath(p) else null;
867}866}
868867
...@@ -938,7 +937,7 @@ pub fn getOutputLibSource(self: *CompileStep) FileSource {...@@ -938,7 +937,7 @@ pub fn getOutputLibSource(self: *CompileStep) FileSource {
938/// Returns the generated header file.937/// Returns the generated header file.
939/// This function can only be called for libraries or object files which have `emit_h` set.938/// This function can only be called for libraries or object files which have `emit_h` set.
940pub fn getOutputHSource(self: *CompileStep) FileSource {939pub 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");
942 assert(self.emit_h);941 assert(self.emit_h);
943 return .{ .generated = &self.output_h_path_source };942 return .{ .generated = &self.output_h_path_source };
944}943}
...@@ -1243,7 +1242,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -1243,7 +1242,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
1243 .exe => "build-exe",1242 .exe => "build-exe",
1244 .obj => "build-obj",1243 .obj => "build-obj",
1245 .@"test" => "test",1244 .@"test" => "test",
1246 .test_exe => "test",
1247 };1245 };
1248 try zig_args.append(cmd);1246 try zig_args.append(cmd);
12491247
...@@ -1293,7 +1291,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -1293,7 +1291,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
12931291
1294 .other_step => |other| switch (other.kind) {1292 .other_step => |other| switch (other.kind) {
1295 .exe => @panic("Cannot link with an executable build artifact"),1293 .exe => @panic("Cannot link with an executable build artifact"),
1296 .test_exe => @panic("Cannot link with an executable build artifact"),
1297 .@"test" => @panic("Cannot link with a test"),1294 .@"test" => @panic("Cannot link with a test"),
1298 .obj => {1295 .obj => {
1299 try zig_args.append(other.getOutputSource().getPath(b));1296 try zig_args.append(other.getOutputSource().getPath(b));
...@@ -1661,83 +1658,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -1661,83 +1658,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
1661 try zig_args.append("--test-cmd-bin");1658 try zig_args.append("--test-cmd-bin");
1662 }1659 }
1663 }1660 }
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 }
1738 }1661 }
1739 } else if (self.kind == .test_exe) {
1740 try zig_args.append("--test-no-exec");
1741 }1662 }
17421663
1743 try self.appendModuleArgs(&zig_args);1664 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 {...@@ -32,12 +32,11 @@ pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep {
32 .artifact = artifact,32 .artifact = artifact,
33 .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) {33 .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) {
34 .obj => @panic("Cannot install a .obj build artifact."),34 .obj => @panic("Cannot install a .obj build artifact."),
35 .@"test" => @panic("Cannot install a .test build artifact, use .test_exe instead."),35 .exe, .@"test" => InstallDir{ .bin = {} },
36 .exe, .test_exe => InstallDir{ .bin = {} },
37 .lib => InstallDir{ .lib = {} },36 .lib => InstallDir{ .lib = {} },
38 },37 },
39 .pdb_dir = if (artifact.producesPdbFile()) blk: {38 .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") {
41 break :blk InstallDir{ .bin = {} };40 break :blk InstallDir{ .bin = {} };
42 } else {41 } else {
43 break :blk InstallDir{ .lib = {} };42 break :blk InstallDir{ .lib = {} };
lib/std/Build/RunStep.zig+296-59
...@@ -92,6 +92,9 @@ pub const StdIo = union(enum) {...@@ -92,6 +92,9 @@ pub const StdIo = union(enum) {
92 /// Note that an explicit check for exit code 0 needs to be added to this92 /// Note that an explicit check for exit code 0 needs to be added to this
93 /// list if such a check is desireable.93 /// list if such a check is desireable.
94 check: std.ArrayList(Check),94 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
96 pub const Check = union(enum) {99 pub const Check = union(enum) {
97 expect_stderr_exact: []const u8,100 expect_stderr_exact: []const u8,
...@@ -324,6 +327,7 @@ fn hasSideEffects(self: RunStep) bool {...@@ -324,6 +327,7 @@ fn hasSideEffects(self: RunStep) bool {
324 .infer_from_args => !self.hasAnyOutputArgs(),327 .infer_from_args => !self.hasAnyOutputArgs(),
325 .inherit => true,328 .inherit => true,
326 .check => false,329 .check => false,
330 .zig_test => false,
327 };331 };
328}332}
329333
...@@ -366,11 +370,6 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {...@@ -366,11 +370,6 @@ fn checksContainStderr(checks: []const StdIo.Check) bool {
366}370}
367371
368fn make(step: *Step, prog_node: *std.Progress.Node) !void {372fn 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
374 const b = step.owner;373 const b = step.owner;
375 const arena = b.allocator;374 const arena = b.allocator;
376 const self = @fieldParentPtr(RunStep, "step", step);375 const self = @fieldParentPtr(RunStep, "step", step);
...@@ -439,7 +438,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -439,7 +438,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
439 hashStdIo(&man.hash, self.stdio);438 hashStdIo(&man.hash, self.stdio);
440439
441 if (has_side_effects) {440 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);
443 return;442 return;
444 }443 }
445444
...@@ -492,8 +491,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {...@@ -492,8 +491,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
492 argv_list.items[placeholder.index] = cli_arg;491 argv_list.items[placeholder.index] = cli_arg;
493 }492 }
494493
495 try runCommand(self, argv_list.items, has_side_effects, &digest);494 try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node);
496 try man.writeManifest();495
496 try step.writeManifest(&man);
497}497}
498498
499fn formatTerm(499fn formatTerm(
...@@ -546,6 +546,7 @@ fn runCommand(...@@ -546,6 +546,7 @@ fn runCommand(
546 argv: []const []const u8,546 argv: []const []const u8,
547 has_side_effects: bool,547 has_side_effects: bool,
548 digest: ?*const [std.Build.Cache.hex_digest_len]u8,548 digest: ?*const [std.Build.Cache.hex_digest_len]u8,
549 prog_node: *std.Progress.Node,
549) !void {550) !void {
550 const step = &self.step;551 const step = &self.step;
551 const b = step.owner;552 const b = step.owner;
...@@ -554,7 +555,15 @@ fn runCommand(...@@ -554,7 +555,15 @@ fn runCommand(
554 try step.handleChildProcUnsupported(self.cwd, argv);555 try step.handleChildProcUnsupported(self.cwd, argv);
555 try Step.handleVerbose(step.owner, self.cwd, argv);556 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: {
558 // InvalidExe: cpu arch mismatch567 // InvalidExe: cpu arch mismatch
559 // FileNotFound: can happen with a wrong dynamic linker path568 // FileNotFound: can happen with a wrong dynamic linker path
560 if (err == error.InvalidExe or err == error.FileNotFound) interpret: {569 if (err == error.InvalidExe or err == error.FileNotFound) interpret: {
...@@ -566,10 +575,10 @@ fn runCommand(...@@ -566,10 +575,10 @@ fn runCommand(
566 .artifact => |exe| exe,575 .artifact => |exe| exe,
567 else => break :interpret,576 else => break :interpret,
568 };577 };
569 if (exe.kind != .exe) break :interpret;578 switch (exe.kind) {
570579 .exe, .@"test" => {},
571 var interp_argv = std.ArrayList([]const u8).init(b.allocator);580 else => break :interpret,
572 defer interp_argv.deinit();581 }
573582
574 const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc;583 const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc;
575 switch (b.host.getExternalExecutor(exe.target_info, .{584 switch (b.host.getExternalExecutor(exe.target_info, .{
...@@ -577,14 +586,13 @@ fn runCommand(...@@ -577,14 +586,13 @@ fn runCommand(
577 .link_libc = exe.is_linking_libc,586 .link_libc = exe.is_linking_libc,
578 })) {587 })) {
579 .native, .rosetta => {588 .native, .rosetta => {
580 if (self.stdio == .check and self.skip_foreign_checks)589 if (allow_skip) return error.MakeSkipped;
581 return error.MakeSkipped;
582
583 break :interpret;590 break :interpret;
584 },591 },
585 .wine => |bin_name| {592 .wine => |bin_name| {
586 if (b.enable_wine) {593 if (b.enable_wine) {
587 try interp_argv.append(bin_name);594 try interp_argv.append(bin_name);
595 try interp_argv.appendSlice(argv);
588 } else {596 } else {
589 return failForeign(self, "-fwine", argv[0], exe);597 return failForeign(self, "-fwine", argv[0], exe);
590 }598 }
...@@ -617,6 +625,8 @@ fn runCommand(...@@ -617,6 +625,8 @@ fn runCommand(
617 try interp_argv.append("-L");625 try interp_argv.append("-L");
618 try interp_argv.append(full_dir);626 try interp_argv.append(full_dir);
619 }627 }
628
629 try interp_argv.appendSlice(argv);
620 } else {630 } else {
621 return failForeign(self, "-fqemu", argv[0], exe);631 return failForeign(self, "-fqemu", argv[0], exe);
622 }632 }
...@@ -624,6 +634,7 @@ fn runCommand(...@@ -624,6 +634,7 @@ fn runCommand(
624 .darling => |bin_name| {634 .darling => |bin_name| {
625 if (b.enable_darling) {635 if (b.enable_darling) {
626 try interp_argv.append(bin_name);636 try interp_argv.append(bin_name);
637 try interp_argv.appendSlice(argv);
627 } else {638 } else {
628 return failForeign(self, "-fdarling", argv[0], exe);639 return failForeign(self, "-fdarling", argv[0], exe);
629 }640 }
...@@ -632,13 +643,15 @@ fn runCommand(...@@ -632,13 +643,15 @@ fn runCommand(
632 if (b.enable_wasmtime) {643 if (b.enable_wasmtime) {
633 try interp_argv.append(bin_name);644 try interp_argv.append(bin_name);
634 try interp_argv.append("--dir=.");645 try interp_argv.append("--dir=.");
646 try interp_argv.append(argv[0]);
647 try interp_argv.append("--");
648 try interp_argv.appendSlice(argv[1..]);
635 } else {649 } else {
636 return failForeign(self, "-fwasmtime", argv[0], exe);650 return failForeign(self, "-fwasmtime", argv[0], exe);
637 }651 }
638 },652 },
639 .bad_dl => |foreign_dl| {653 .bad_dl => |foreign_dl| {
640 if (self.stdio == .check and self.skip_foreign_checks)654 if (allow_skip) return error.MakeSkipped;
641 return error.MakeSkipped;
642655
643 const host_dl = b.host.dynamic_linker.get() orelse "(none)";656 const host_dl = b.host.dynamic_linker.get() orelse "(none)";
644657
...@@ -650,8 +663,7 @@ fn runCommand(...@@ -650,8 +663,7 @@ fn runCommand(
650 , .{ host_dl, foreign_dl });663 , .{ host_dl, foreign_dl });
651 },664 },
652 .bad_os_or_cpu => {665 .bad_os_or_cpu => {
653 if (self.stdio == .check and self.skip_foreign_checks)666 if (allow_skip) return error.MakeSkipped;
654 return error.MakeSkipped;
655667
656 const host_name = try b.host.target.zigTriple(b.allocator);668 const host_name = try b.host.target.zigTriple(b.allocator);
657 const foreign_name = try exe.target.zigTriple(b.allocator);669 const foreign_name = try exe.target.zigTriple(b.allocator);
...@@ -667,11 +679,9 @@ fn runCommand(...@@ -667,11 +679,9 @@ fn runCommand(
667 RunStep.addPathForDynLibsInternal(&self.step, b, exe);679 RunStep.addPathForDynLibsInternal(&self.step, b, exe);
668 }680 }
669681
670 try interp_argv.append(argv[0]);
671
672 try Step.handleVerbose(step.owner, self.cwd, interp_argv.items);682 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| {
675 return step.fail("unable to spawn {s}: {s}", .{685 return step.fail("unable to spawn {s}: {s}", .{
676 interp_argv.items[0], @errorName(e),686 interp_argv.items[0], @errorName(e),
677 });687 });
...@@ -683,6 +693,7 @@ fn runCommand(...@@ -683,6 +693,7 @@ fn runCommand(
683693
684 step.result_duration_ns = result.elapsed_ns;694 step.result_duration_ns = result.elapsed_ns;
685 step.result_peak_rss = result.peak_rss;695 step.result_peak_rss = result.peak_rss;
696 step.test_results = result.stdio.test_results;
686697
687 // Capture stdout and stderr to GeneratedFile objects.698 // Capture stdout and stderr to GeneratedFile objects.
688 const Stream = struct {699 const Stream = struct {
...@@ -693,13 +704,13 @@ fn runCommand(...@@ -693,13 +704,13 @@ fn runCommand(
693 for ([_]Stream{704 for ([_]Stream{
694 .{705 .{
695 .captured = self.captured_stdout,706 .captured = self.captured_stdout,
696 .is_null = result.stdout_null,707 .is_null = result.stdio.stdout_null,
697 .bytes = result.stdout,708 .bytes = result.stdio.stdout,
698 },709 },
699 .{710 .{
700 .captured = self.captured_stderr,711 .captured = self.captured_stderr,
701 .is_null = result.stderr_null,712 .is_null = result.stdio.stderr_null,
702 .bytes = result.stderr,713 .bytes = result.stdio.stderr,
703 },714 },
704 }) |stream| {715 }) |stream| {
705 if (stream.captured) |output| {716 if (stream.captured) |output| {
...@@ -724,11 +735,13 @@ fn runCommand(...@@ -724,11 +735,13 @@ fn runCommand(
724 }735 }
725 }736 }
726737
738 const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items;
739
727 switch (self.stdio) {740 switch (self.stdio) {
728 .check => |checks| for (checks.items) |check| switch (check) {741 .check => |checks| for (checks.items) |check| switch (check) {
729 .expect_stderr_exact => |expected_bytes| {742 .expect_stderr_exact => |expected_bytes| {
730 assert(!result.stderr_null);743 assert(!result.stdio.stderr_null);
731 if (!mem.eql(u8, expected_bytes, result.stderr)) {744 if (!mem.eql(u8, expected_bytes, result.stdio.stderr)) {
732 return step.fail(745 return step.fail(
733 \\746 \\
734 \\========= expected this stderr: =========747 \\========= expected this stderr: =========
...@@ -739,14 +752,14 @@ fn runCommand(...@@ -739,14 +752,14 @@ fn runCommand(
739 \\{s}752 \\{s}
740 , .{753 , .{
741 expected_bytes,754 expected_bytes,
742 result.stderr,755 result.stdio.stderr,
743 try Step.allocPrintCmd(arena, self.cwd, argv),756 try Step.allocPrintCmd(arena, self.cwd, final_argv),
744 });757 });
745 }758 }
746 },759 },
747 .expect_stderr_match => |match| {760 .expect_stderr_match => |match| {
748 assert(!result.stderr_null);761 assert(!result.stdio.stderr_null);
749 if (mem.indexOf(u8, result.stderr, match) == null) {762 if (mem.indexOf(u8, result.stdio.stderr, match) == null) {
750 return step.fail(763 return step.fail(
751 \\764 \\
752 \\========= expected to find in stderr: =========765 \\========= expected to find in stderr: =========
...@@ -757,14 +770,14 @@ fn runCommand(...@@ -757,14 +770,14 @@ fn runCommand(
757 \\{s}770 \\{s}
758 , .{771 , .{
759 match,772 match,
760 result.stderr,773 result.stdio.stderr,
761 try Step.allocPrintCmd(arena, self.cwd, argv),774 try Step.allocPrintCmd(arena, self.cwd, final_argv),
762 });775 });
763 }776 }
764 },777 },
765 .expect_stdout_exact => |expected_bytes| {778 .expect_stdout_exact => |expected_bytes| {
766 assert(!result.stdout_null);779 assert(!result.stdio.stdout_null);
767 if (!mem.eql(u8, expected_bytes, result.stdout)) {780 if (!mem.eql(u8, expected_bytes, result.stdio.stdout)) {
768 return step.fail(781 return step.fail(
769 \\782 \\
770 \\========= expected this stdout: =========783 \\========= expected this stdout: =========
...@@ -775,14 +788,14 @@ fn runCommand(...@@ -775,14 +788,14 @@ fn runCommand(
775 \\{s}788 \\{s}
776 , .{789 , .{
777 expected_bytes,790 expected_bytes,
778 result.stdout,791 result.stdio.stdout,
779 try Step.allocPrintCmd(arena, self.cwd, argv),792 try Step.allocPrintCmd(arena, self.cwd, final_argv),
780 });793 });
781 }794 }
782 },795 },
783 .expect_stdout_match => |match| {796 .expect_stdout_match => |match| {
784 assert(!result.stdout_null);797 assert(!result.stdio.stdout_null);
785 if (mem.indexOf(u8, result.stdout, match) == null) {798 if (mem.indexOf(u8, result.stdio.stdout, match) == null) {
786 return step.fail(799 return step.fail(
787 \\800 \\
788 \\========= expected to find in stdout: =========801 \\========= expected to find in stdout: =========
...@@ -793,8 +806,8 @@ fn runCommand(...@@ -793,8 +806,8 @@ fn runCommand(
793 \\{s}806 \\{s}
794 , .{807 , .{
795 match,808 match,
796 result.stdout,809 result.stdio.stdout,
797 try Step.allocPrintCmd(arena, self.cwd, argv),810 try Step.allocPrintCmd(arena, self.cwd, final_argv),
798 });811 });
799 }812 }
800 },813 },
...@@ -803,33 +816,46 @@ fn runCommand(...@@ -803,33 +816,46 @@ fn runCommand(
803 return step.fail("the following command {} (expected {}):\n{s}", .{816 return step.fail("the following command {} (expected {}):\n{s}", .{
804 fmtTerm(result.term),817 fmtTerm(result.term),
805 fmtTerm(expected_term),818 fmtTerm(expected_term),
806 try Step.allocPrintCmd(arena, self.cwd, argv),819 try Step.allocPrintCmd(arena, self.cwd, final_argv),
807 });820 });
808 }821 }
809 },822 },
810 },823 },
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 },
811 else => {840 else => {
812 try step.handleChildProcessTerm(result.term, self.cwd, argv);841 try step.handleChildProcessTerm(result.term, self.cwd, final_argv);
813 },842 },
814 }843 }
815}844}
816845
817const ChildProcResult = struct {846const 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,
824 term: std.process.Child.Term,847 term: std.process.Child.Term,
825 elapsed_ns: u64,848 elapsed_ns: u64,
826 peak_rss: usize,849 peak_rss: usize,
850
851 stdio: StdIoResult,
827};852};
828853
829fn spawnChildAndCollect(854fn spawnChildAndCollect(
830 self: *RunStep,855 self: *RunStep,
831 argv: []const []const u8,856 argv: []const []const u8,
832 has_side_effects: bool,857 has_side_effects: bool,
858 prog_node: *std.Progress.Node,
833) !ChildProcResult {859) !ChildProcResult {
834 const b = self.step.owner;860 const b = self.step.owner;
835 const arena = b.allocator;861 const arena = b.allocator;
...@@ -848,16 +874,19 @@ fn spawnChildAndCollect(...@@ -848,16 +874,19 @@ fn spawnChildAndCollect(
848 .infer_from_args => if (has_side_effects) .Inherit else .Close,874 .infer_from_args => if (has_side_effects) .Inherit else .Close,
849 .inherit => .Inherit,875 .inherit => .Inherit,
850 .check => .Close,876 .check => .Close,
877 .zig_test => .Pipe,
851 };878 };
852 child.stdout_behavior = switch (self.stdio) {879 child.stdout_behavior = switch (self.stdio) {
853 .infer_from_args => if (has_side_effects) .Inherit else .Ignore,880 .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
854 .inherit => .Inherit,881 .inherit => .Inherit,
855 .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore,882 .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore,
883 .zig_test => .Pipe,
856 };884 };
857 child.stderr_behavior = switch (self.stdio) {885 child.stderr_behavior = switch (self.stdio) {
858 .infer_from_args => if (has_side_effects) .Inherit else .Pipe,886 .infer_from_args => if (has_side_effects) .Inherit else .Pipe,
859 .inherit => .Inherit,887 .inherit => .Inherit,
860 .check => .Pipe,888 .check => .Pipe,
889 .zig_test => .Pipe,
861 };890 };
862 if (self.captured_stdout != null) child.stdout_behavior = .Pipe;891 if (self.captured_stdout != null) child.stdout_behavior = .Pipe;
863 if (self.captured_stderr != null) child.stderr_behavior = .Pipe;892 if (self.captured_stderr != null) child.stderr_behavior = .Pipe;
...@@ -871,6 +900,219 @@ fn spawnChildAndCollect(...@@ -871,6 +900,219 @@ fn spawnChildAndCollect(
871 });900 });
872 var timer = try std.time.Timer.start();901 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
874 if (self.stdin) |stdin| {1116 if (self.stdin) |stdin| {
875 child.stdin.?.writeAll(stdin) catch |err| {1117 child.stdin.?.writeAll(stdin) catch |err| {
876 return self.step.fail("unable to write stdin: {s}", .{@errorName(err)});1118 return self.step.fail("unable to write stdin: {s}", .{@errorName(err)});
...@@ -925,17 +1167,12 @@ fn spawnChildAndCollect(...@@ -925,17 +1167,12 @@ fn spawnChildAndCollect(
925 }1167 }
926 }1168 }
9271169
928 const term = try child.wait();
929 const elapsed_ns = timer.read();
930
931 return .{1170 return .{
932 .stdout = stdout_bytes,1171 .stdout = stdout_bytes,
933 .stderr = stderr_bytes,1172 .stderr = stderr_bytes,
934 .stdout_null = stdout_null,1173 .stdout_null = stdout_null,
935 .stderr_null = stderr_null,1174 .stderr_null = stderr_null,
936 .term = term,1175 .test_results = .{},
937 .elapsed_ns = elapsed_ns,
938 .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0,
939 };1176 };
940}1177}
9411178
...@@ -966,7 +1203,7 @@ fn failForeign(...@@ -966,7 +1203,7 @@ fn failForeign(
966 exe: *CompileStep,1203 exe: *CompileStep,
967) error{ MakeFailed, MakeSkipped, OutOfMemory } {1204) error{ MakeFailed, MakeSkipped, OutOfMemory } {
968 switch (self.stdio) {1205 switch (self.stdio) {
969 .check => {1206 .check, .zig_test => {
970 if (self.skip_foreign_checks)1207 if (self.skip_foreign_checks)
971 return error.MakeSkipped;1208 return error.MakeSkipped;
9721209
...@@ -987,7 +1224,7 @@ fn failForeign(...@@ -987,7 +1224,7 @@ fn failForeign(
9871224
988fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void {1225fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void {
989 switch (stdio) {1226 switch (stdio) {
990 .infer_from_args, .inherit => {},1227 .infer_from_args, .inherit, .zig_test => {},
991 .check => |checks| for (checks.items) |check| {1228 .check => |checks| for (checks.items) |check| {
992 hh.add(@as(std.meta.Tag(StdIo.Check), check));1229 hh.add(@as(std.meta.Tag(StdIo.Check), check));
993 switch (check) {1230 switch (check) {
lib/std/Build/Step.zig+30-3
...@@ -35,11 +35,27 @@ result_cached: bool,...@@ -35,11 +35,27 @@ result_cached: bool,
35result_duration_ns: ?u64,35result_duration_ns: ?u64,
36/// 0 means unavailable or not reported.36/// 0 means unavailable or not reported.
37result_peak_rss: usize,37result_peak_rss: usize,
38test_results: TestResults,
3839
39/// The return addresss associated with creation of this step that can be useful40/// The return addresss associated with creation of this step that can be useful
40/// to print along with debugging messages.41/// to print along with debugging messages.
41debug_stack_trace: [n_debug_stack_frames]usize,42debug_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
43pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void;59pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void;
4460
45const n_debug_stack_frames = 4;61const n_debug_stack_frames = 4;
...@@ -134,6 +150,7 @@ pub fn init(options: Options) Step {...@@ -134,6 +150,7 @@ pub fn init(options: Options) Step {
134 .result_cached = false,150 .result_cached = false,
135 .result_duration_ns = null,151 .result_duration_ns = null,
136 .result_peak_rss = 0,152 .result_peak_rss = 0,
153 .test_results = .{},
137 };154 };
138}155}
139156
...@@ -152,6 +169,10 @@ pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkip...@@ -152,6 +169,10 @@ pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkip
152 },169 },
153 };170 };
154171
172 if (!s.test_results.isSuccess()) {
173 return error.MakeFailed;
174 }
175
155 if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) {176 if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) {
156 const msg = std.fmt.allocPrint(arena, "memory usage peaked at {d} bytes, exceeding the declared upper bound of {d}", .{177 const msg = std.fmt.allocPrint(arena, "memory usage peaked at {d} bytes, exceeding the declared upper bound of {d}", .{
157 s.result_peak_rss, s.max_rss,178 s.result_peak_rss, s.max_rss,
...@@ -346,9 +367,7 @@ pub fn evalZigProcess(...@@ -346,9 +367,7 @@ pub fn evalZigProcess(
346 s.result_cached = ebp_hdr.flags.cache_hit;367 s.result_cached = ebp_hdr.flags.cache_hit;
347 result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);368 result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
348 },369 },
349 _ => {370 else => {}, // ignore other messages
350 // Unrecognized message.
351 },
352 }371 }
353 stdout.discard(header_and_msg_len);372 stdout.discard(header_and_msg_len);
354 }373 }
...@@ -475,3 +494,11 @@ fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyer...@@ -475,3 +494,11 @@ fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyer
475 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";494 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
476 return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path });495 return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path });
477}496}
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 {...@@ -282,7 +282,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
282 });282 });
283 }283 }
284284
285 try man.writeManifest();285 try step.writeManifest(&man);
286}286}
287287
288const std = @import("../std.zig");288const std = @import("../std.zig");
lib/std/zig/Client.zig+7
...@@ -26,6 +26,13 @@ pub const Message = struct {...@@ -26,6 +26,13 @@ pub const Message = struct {
26 /// swap.26 /// swap.
27 /// No body.27 /// No body.
28 hot_update,28 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
30 _,37 _,
31 };38 };
lib/std/zig/Server.zig+200
...@@ -1,3 +1,7 @@...@@ -1,3 +1,7 @@
1in: std.fs.File,
2out: std.fs.File,
3receive_fifo: std.fifo.LinearFifo(u8, .Dynamic),
4
1pub const Message = struct {5pub const Message = struct {
2 pub const Header = extern struct {6 pub const Header = extern struct {
3 tag: Tag,7 tag: Tag,
...@@ -14,6 +18,11 @@ pub const Message = struct {...@@ -14,6 +18,11 @@ pub const Message = struct {
14 progress,18 progress,
15 /// Body is a EmitBinPath.19 /// Body is a EmitBinPath.
16 emit_bin_path,20 emit_bin_path,
21 /// Body is a TestMetadata
22 test_metadata,
23 /// Body is a TestResults
24 test_results,
25
17 _,26 _,
18 };27 };
1928
...@@ -26,6 +35,33 @@ pub const Message = struct {...@@ -26,6 +35,33 @@ pub const Message = struct {
26 string_bytes_len: u32,35 string_bytes_len: u32,
27 };36 };
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
29 /// Trailing:65 /// Trailing:
30 /// * the file system path the emitted binary can be found66 /// * the file system path the emitted binary can be found
31 pub const EmitBinPath = extern struct {67 pub const EmitBinPath = extern struct {
...@@ -37,3 +73,167 @@ pub const Message = struct {...@@ -37,3 +73,167 @@ pub const Message = struct {
37 };73 };
38 };74 };
39};75};
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 {...@@ -8,14 +8,130 @@ pub const std_options = struct {
8};8};
99
10var log_err_count: usize = 0;10var log_err_count: usize = 0;
11var cmdline_buffer: [4096]u8 = undefined;
12var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);
1113
12pub fn main() void {14pub fn main() void {
13 if (builtin.zig_backend != .stage1 and15 if (builtin.zig_backend == .stage2_wasm or
14 builtin.zig_backend != .stage2_llvm and16 builtin.zig_backend == .stage2_x86_64 or
15 builtin.zig_backend != .stage2_c)17 builtin.zig_backend == .stage2_aarch64)
16 {18 {
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 }
18 }131 }
132}
133
134fn mainTerminal() void {
19 const test_fn_list = builtin.test_functions;135 const test_fn_list = builtin.test_functions;
20 var ok_count: usize = 0;136 var ok_count: usize = 0;
21 var skip_count: usize = 0;137 var skip_count: usize = 0;
...@@ -118,51 +234,17 @@ pub fn log(...@@ -118,51 +234,17 @@ pub fn log(
118 }234 }
119}235}
120236
121pub fn main2() anyerror!void {237/// Simpler main(), exercising fewer language features, so that
122 var skipped: usize = 0;238/// work-in-progress backends can handle it.
123 var failed: usize = 0;239pub fn mainSimple() anyerror!void {
124 // Simpler main(), exercising fewer language features, so that stage2 can handle it.240 //const stderr = std.io.getStdErr();
125 for (builtin.test_functions) |test_fn| {241 for (builtin.test_functions) |test_fn| {
126 test_fn.func() catch |err| {242 test_fn.func() catch |err| {
127 if (err != error.SkipZigTest) {243 if (err != error.SkipZigTest) {
128 failed += 1;244 //stderr.writeAll(test_fn.name) catch {};
129 } else {245 //stderr.writeAll("\n") catch {};
130 skipped += 1;246 return err;
131 }247 }
132 };248 };
133 }249 }
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);
168}250}
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;...@@ -10,6 +10,7 @@ const ArrayList = std.ArrayList;
10const Ast = std.zig.Ast;10const Ast = std.zig.Ast;
11const warn = std.log.warn;11const warn = std.log.warn;
12const ThreadPool = std.Thread.Pool;12const ThreadPool = std.Thread.Pool;
13const cleanExit = std.process.cleanExit;
1314
14const tracy = @import("tracy.zig");15const tracy = @import("tracy.zig");
15const Compilation = @import("Compilation.zig");16const Compilation = @import("Compilation.zig");
...@@ -26,7 +27,7 @@ const target_util = @import("target.zig");...@@ -26,7 +27,7 @@ const target_util = @import("target.zig");
26const crash_report = @import("crash_report.zig");27const crash_report = @import("crash_report.zig");
27const Module = @import("Module.zig");28const Module = @import("Module.zig");
28const AstGen = @import("AstGen.zig");29const AstGen = @import("AstGen.zig");
29const Server = @import("Server.zig");30const Server = std.zig.Server;
3031
31pub const std_options = struct {32pub const std_options = struct {
32 pub const wasiCwd = wasi_cwd;33 pub const wasiCwd = wasi_cwd;
...@@ -3545,6 +3546,7 @@ fn serve(...@@ -3545,6 +3546,7 @@ fn serve(
3545 .gpa = gpa,3546 .gpa = gpa,
3546 .in = in,3547 .in = in,
3547 .out = out,3548 .out = out,
3549 .zig_version = build_options.version,
3548 });3550 });
3549 defer server.deinit();3551 defer server.deinit();
35503552
...@@ -3656,8 +3658,8 @@ fn serve(...@@ -3656,8 +3658,8 @@ fn serve(
3656 );3658 );
3657 }3659 }
3658 },3660 },
3659 _ => {3661 else => {
3660 @panic("TODO unrecognized message from client");3662 fatal("unrecognized message from client: 0x{x}", .{@enumToInt(hdr.tag)});
3661 },3663 },
3662 }3664 }
3663 }3665 }
...@@ -5624,19 +5626,6 @@ fn detectNativeTargetInfo(cross_target: std.zig.CrossTarget) !std.zig.system.Nat...@@ -5624,19 +5626,6 @@ fn detectNativeTargetInfo(cross_target: std.zig.CrossTarget) !std.zig.system.Nat
5624 return std.zig.system.NativeTargetInfo.detect(cross_target);5626 return std.zig.system.NativeTargetInfo.detect(cross_target);
5625}5627}
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
5640const usage_ast_check =5629const usage_ast_check =
5641 \\Usage: zig ast-check [file]5630 \\Usage: zig ast-check [file]
5642 \\5631 \\
src/objcopy.zig+5-4
...@@ -8,8 +8,8 @@ const assert = std.debug.assert;...@@ -8,8 +8,8 @@ const assert = std.debug.assert;
88
9const main = @import("main.zig");9const main = @import("main.zig");
10const fatal = main.fatal;10const fatal = main.fatal;
11const cleanExit = main.cleanExit;11const Server = std.zig.Server;
12const Server = @import("Server.zig");12const build_options = @import("build_options");
1313
14pub fn cmdObjCopy(14pub fn cmdObjCopy(
15 gpa: Allocator,15 gpa: Allocator,
...@@ -116,6 +116,7 @@ pub fn cmdObjCopy(...@@ -116,6 +116,7 @@ pub fn cmdObjCopy(
116 .gpa = gpa,116 .gpa = gpa,
117 .in = std.io.getStdIn(),117 .in = std.io.getStdIn(),
118 .out = std.io.getStdOut(),118 .out = std.io.getStdOut(),
119 .zig_version = build_options.version,
119 });120 });
120 defer server.deinit();121 defer server.deinit();
121122
...@@ -124,7 +125,7 @@ pub fn cmdObjCopy(...@@ -124,7 +125,7 @@ pub fn cmdObjCopy(
124 const hdr = try server.receiveMessage();125 const hdr = try server.receiveMessage();
125 switch (hdr.tag) {126 switch (hdr.tag) {
126 .exit => {127 .exit => {
127 return cleanExit();128 return std.process.cleanExit();
128 },129 },
129 .update => {130 .update => {
130 if (seen_update) {131 if (seen_update) {
...@@ -144,7 +145,7 @@ pub fn cmdObjCopy(...@@ -144,7 +145,7 @@ pub fn cmdObjCopy(
144 }145 }
145 }146 }
146 }147 }
147 return cleanExit();148 return std.process.cleanExit();
148}149}
149150
150const usage =151const 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...@@ -24,5 +24,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
24 });24 });
25 test_exe.linkLibrary(lib_a);25 test_exe.linkLibrary(lib_a);
2626
27 test_step.dependOn(&test_exe.step);27 test_step.dependOn(&test_exe.run().step);
28}28}
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...@@ -24,5 +24,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
24 });24 });
25 test_exe.linkLibrary(lib_a);25 test_exe.linkLibrary(lib_a);
2626
27 test_step.dependOn(&test_exe.step);27 test_step.dependOn(&test_exe.run().step);
28}28}
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...@@ -35,5 +35,5 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
35 test_exe.linkLibrary(lib_b);35 test_exe.linkLibrary(lib_b);
36 test_exe.addIncludePath(".");36 test_exe.addIncludePath(".");
3737
38 test_step.dependOn(&test_exe.step);38 test_step.dependOn(&test_exe.run().step);
39}39}
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...@@ -32,5 +32,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
32 test_exe.linkLibrary(lib);32 test_exe.linkLibrary(lib);
33 test_exe.linkLibC();33 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);
36}39}
test/src/Cases.zig+4-7
...@@ -547,15 +547,12 @@ pub fn lowerToBuildSteps(...@@ -547,15 +547,12 @@ pub fn lowerToBuildSteps(
547 parent_step.dependOn(&artifact.step);547 parent_step.dependOn(&artifact.step);
548 },548 },
549 .Execution => |expected_stdout| {549 .Execution => |expected_stdout| {
550 if (case.is_test) {550 const run = b.addRunArtifact(artifact);
551 parent_step.dependOn(&artifact.step);551 run.skip_foreign_checks = true;
552 } else {552 if (!case.is_test) {
553 const run = b.addRunArtifact(artifact);
554 run.skip_foreign_checks = true;
555 run.expectStdOutEqual(expected_stdout);553 run.expectStdOutEqual(expected_stdout);
556
557 parent_step.dependOn(&run.step);
558 }554 }
555 parent_step.dependOn(&run.step);
559 },556 },
560 .Header => @panic("TODO"),557 .Header => @panic("TODO"),
561 }558 }
test/standalone/emit_asm_and_bin/build.zig+1-1
...@@ -11,5 +11,5 @@ pub fn build(b: *std.Build) void {...@@ -11,5 +11,5 @@ pub fn build(b: *std.Build) void {
11 main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") };11 main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") };
12 main.emit_bin = .{ .emit_to = b.pathFromRoot("main") };12 main.emit_bin = .{ .emit_to = b.pathFromRoot("main") };
1313
14 test_step.dependOn(&main.step);14 test_step.dependOn(&main.run().step);
15}15}
test/standalone/global_linkage/build.zig+1-1
...@@ -28,5 +28,5 @@ pub fn build(b: *std.Build) void {...@@ -28,5 +28,5 @@ pub fn build(b: *std.Build) void {
28 main.linkLibrary(obj1);28 main.linkLibrary(obj1);
29 main.linkLibrary(obj2);29 main.linkLibrary(obj2);
3030
31 test_step.dependOn(&main.step);31 test_step.dependOn(&main.run().step);
32}32}
test/standalone/issue_13970/build.zig+3-3
...@@ -17,7 +17,7 @@ pub fn build(b: *std.Build) void {...@@ -17,7 +17,7 @@ pub fn build(b: *std.Build) void {
17 test2.setTestRunner("src/main.zig");17 test2.setTestRunner("src/main.zig");
18 test3.setTestRunner("src/main.zig");18 test3.setTestRunner("src/main.zig");
1919
20 test_step.dependOn(&test1.step);20 test_step.dependOn(&test1.run().step);
21 test_step.dependOn(&test2.step);21 test_step.dependOn(&test2.run().step);
22 test_step.dependOn(&test3.step);22 test_step.dependOn(&test3.run().step);
23}23}
test/standalone/main_pkg_path/build.zig+1-1
...@@ -9,5 +9,5 @@ pub fn build(b: *std.Build) void {...@@ -9,5 +9,5 @@ pub fn build(b: *std.Build) void {
9 });9 });
10 test_exe.setMainPkgPath(".");10 test_exe.setMainPkgPath(".");
1111
12 test_step.dependOn(&test_exe.step);12 test_step.dependOn(&test_exe.run().step);
13}13}
test/standalone/options/build.zig+1-1
...@@ -20,5 +20,5 @@ pub fn build(b: *std.Build) void {...@@ -20,5 +20,5 @@ pub fn build(b: *std.Build) void {
20 options.addOption([]const u8, "string", b.option([]const u8, "string", "s").?);20 options.addOption([]const u8, "string", b.option([]const u8, "string", "s").?);
2121
22 const test_step = b.step("test", "Run unit tests");22 const test_step = b.step("test", "Run unit tests");
23 test_step.dependOn(&main.step);23 test_step.dependOn(&main.run().step);
24}24}
test/standalone/pie/build.zig+1-1
...@@ -17,5 +17,5 @@ pub fn build(b: *std.Build) void {...@@ -17,5 +17,5 @@ pub fn build(b: *std.Build) void {
17 });17 });
18 main.pie = true;18 main.pie = true;
1919
20 test_step.dependOn(&main.step);20 test_step.dependOn(&main.run().step);
21}21}
test/standalone/static_c_lib/build.zig+1-1
...@@ -21,5 +21,5 @@ pub fn build(b: *std.Build) void {...@@ -21,5 +21,5 @@ pub fn build(b: *std.Build) void {
21 test_exe.linkLibrary(foo);21 test_exe.linkLibrary(foo);
22 test_exe.addIncludePath(".");22 test_exe.addIncludePath(".");
2323
24 test_step.dependOn(&test_exe.step);24 test_step.dependOn(&test_exe.run().step);
25}25}
test/standalone/test_runner_module_imports/build.zig+1-1
...@@ -15,5 +15,5 @@ pub fn build(b: *std.Build) void {...@@ -15,5 +15,5 @@ pub fn build(b: *std.Build) void {
15 t.addModule("module2", module2);15 t.addModule("module2", module2);
1616
17 const test_step = b.step("test", "Run unit tests");17 const test_step = b.step("test", "Run unit tests");
18 test_step.dependOn(&t.step);18 test_step.dependOn(&t.run().step);
19}19}
test/standalone/test_runner_path/build.zig-1
...@@ -8,7 +8,6 @@ pub fn build(b: *std.Build) void {...@@ -8,7 +8,6 @@ pub fn build(b: *std.Build) void {
88
9 const test_exe = b.addTest(.{9 const test_exe = b.addTest(.{
10 .root_source_file = .{ .path = "test.zig" },10 .root_source_file = .{ .path = "test.zig" },
11 .kind = .test_exe,
12 });11 });
13 test_exe.test_runner = "test_runner.zig";12 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 {...@@ -12,5 +12,5 @@ pub fn build(b: *std.Build) void {
12 });12 });
13 main.addIncludePath(".");13 main.addIncludePath(".");
1414
15 test_step.dependOn(&main.step);15 test_step.dependOn(&main.run().step);
16}16}
test/tests.zig+16-11
...@@ -596,7 +596,7 @@ pub fn addStandaloneTests(...@@ -596,7 +596,7 @@ pub fn addStandaloneTests(
596 });596 });
597 if (case.link_libc) exe.linkLibC();597 if (case.link_libc) exe.linkLibC();
598598
599 step.dependOn(&exe.step);599 step.dependOn(&exe.run().step);
600 }600 }
601 }601 }
602 }602 }
...@@ -981,14 +981,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {...@@ -981,14 +981,6 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
981 });981 });
982 const single_threaded_txt = if (test_target.single_threaded) "single" else "multi";982 const single_threaded_txt = if (test_target.single_threaded) "single" else "multi";
983 const backend_txt = if (test_target.backend) |backend| @tagName(backend) else "default";983 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 }));
992 these_tests.single_threaded = test_target.single_threaded;984 these_tests.single_threaded = test_target.single_threaded;
993 these_tests.setFilter(options.test_filter);985 these_tests.setFilter(options.test_filter);
994 if (test_target.link_libc) {986 if (test_target.link_libc) {
...@@ -1014,7 +1006,18 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {...@@ -1014,7 +1006,18 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
1014 },1006 },
1015 };1007 };
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);
1018 }1021 }
1019 return step;1022 return step;
1020}1023}
...@@ -1053,7 +1056,9 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S...@@ -1053,7 +1056,9 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S
1053 @tagName(optimize_mode),1056 @tagName(optimize_mode),
1054 }));1057 }));
10551058
1056 step.dependOn(&test_step.step);1059 const run = test_step.run();
1060 run.skip_foreign_checks = true;
1061 step.dependOn(&run.step);
1057 }1062 }
1058 }1063 }
1059 return step;1064 return step;