authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-10-06 11:16:27+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-10-06 11:16:27+01:00
log008bb1f1201a4b4987bf00de9daf46185aa9292d
treefa5017a9988957b1a71838ea9fe7d388f6619541
parent516cb5a5e86bb9d30c16d0692e3b9eb706812b42
parent90db7677212f8331733a661615490d37c7bf75d2
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #21518 from mlugg/incremental-ci

incr-check enhancements, and CI for incremental test cases

13 files changed, 354 insertions(+), 79 deletions(-)

build.zig+4
......@@ -577,6 +577,10 @@ pub fn build(b: *std.Build) !void {
577577 } else {
578578 update_mingw_step.dependOn(&b.addFail("The -Dmingw-src=... option is required for this step").step);
579579 }
580
581 const test_incremental_step = b.step("test-incremental", "Run the incremental compilation test cases");
582 try tests.addIncrementalTests(b, test_incremental_step);
583 test_step.dependOn(test_incremental_step);
580584}
581585
582586fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
lib/std/io.zig+162-30
......@@ -442,6 +442,7 @@ pub fn poll(
442442 .overlapped = [1]windows.OVERLAPPED{
443443 mem.zeroes(windows.OVERLAPPED),
444444 } ** enum_fields.len,
445 .small_bufs = undefined,
445446 .active = .{
446447 .count = 0,
447448 .handles_buf = undefined,
......@@ -481,6 +482,7 @@ pub fn Poller(comptime StreamEnum: type) type {
481482 windows: if (is_windows) struct {
482483 first_read_done: bool,
483484 overlapped: [enum_fields.len]windows.OVERLAPPED,
485 small_bufs: [enum_fields.len][128]u8,
484486 active: struct {
485487 count: math.IntFittingRange(0, enum_fields.len),
486488 handles_buf: [enum_fields.len]windows.HANDLE,
......@@ -534,24 +536,31 @@ pub fn Poller(comptime StreamEnum: type) type {
534536 const bump_amt = 512;
535537
536538 if (!self.windows.first_read_done) {
537 // Windows Async IO requires an initial call to ReadFile before waiting on the handle
539 var already_read_data = false;
538540 for (0..enum_fields.len) |i| {
539541 const handle = self.windows.active.handles_buf[i];
540 switch (try windowsAsyncRead(
542 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
541543 handle,
542544 &self.windows.overlapped[i],
543545 &self.fifos[i],
546 &self.windows.small_bufs[i],
544547 bump_amt,
545548 )) {
546 .pending => {
549 .populated, .empty => |state| {
550 if (state == .populated) already_read_data = true;
547551 self.windows.active.handles_buf[self.windows.active.count] = handle;
548552 self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i));
549553 self.windows.active.count += 1;
550554 },
551555 .closed => {}, // don't add to the wait_objects list
556 .closed_populated => {
557 // don't add to the wait_objects list, but we did already get data
558 already_read_data = true;
559 },
552560 }
553561 }
554562 self.windows.first_read_done = true;
563 if (already_read_data) return true;
555564 }
556565
557566 while (true) {
......@@ -576,32 +585,35 @@ pub fn Poller(comptime StreamEnum: type) type {
576585
577586 const active_idx = status - windows.WAIT_OBJECT_0;
578587
579 const handle = self.windows.active.handles_buf[active_idx];
580588 const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]);
581 var read_bytes: u32 = undefined;
582 if (0 == windows.kernel32.GetOverlappedResult(
583 handle,
584 &self.windows.overlapped[stream_idx],
585 &read_bytes,
586 0,
587 )) switch (windows.GetLastError()) {
588 .BROKEN_PIPE => {
589 const handle = self.windows.active.handles_buf[active_idx];
590
591 const overlapped = &self.windows.overlapped[stream_idx];
592 const stream_fifo = &self.fifos[stream_idx];
593 const small_buf = &self.windows.small_bufs[stream_idx];
594
595 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
596 .success => |n| n,
597 .closed => {
589598 self.windows.active.removeAt(active_idx);
590599 continue;
591600 },
592 else => |err| return windows.unexpectedError(err),
601 .aborted => unreachable,
593602 };
603 try stream_fifo.write(small_buf[0..num_bytes_read]);
594604
595 self.fifos[stream_idx].update(read_bytes);
596
597 switch (try windowsAsyncRead(
605 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
598606 handle,
599 &self.windows.overlapped[stream_idx],
600 &self.fifos[stream_idx],
607 overlapped,
608 stream_fifo,
609 small_buf,
601610 bump_amt,
602611 )) {
603 .pending => {},
604 .closed => self.windows.active.removeAt(active_idx),
612 .empty => {}, // irrelevant, we already got data from the small buffer
613 .populated => {},
614 .closed,
615 .closed_populated, // identical, since we already got data from the small buffer
616 => self.windows.active.removeAt(active_idx),
605617 }
606618 return true;
607619 }
......@@ -654,25 +666,145 @@ pub fn Poller(comptime StreamEnum: type) type {
654666 };
655667}
656668
657fn windowsAsyncRead(
669/// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful
670/// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For
671/// compatibility, we point it to this dummy variables, which we never otherwise access.
672/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
673var win_dummy_bytes_read: u32 = undefined;
674
675/// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before
676/// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data
677/// is available. `handle` must have no pending asynchronous operation.
678fn windowsAsyncReadToFifoAndQueueSmallRead(
658679 handle: windows.HANDLE,
659680 overlapped: *windows.OVERLAPPED,
660681 fifo: *PollFifo,
682 small_buf: *[128]u8,
661683 bump_amt: usize,
662) !enum { pending, closed } {
684) !enum { empty, populated, closed_populated, closed } {
685 var read_any_data = false;
663686 while (true) {
664 const buf = try fifo.writableWithSize(bump_amt);
665 var read_bytes: u32 = undefined;
666 const read_result = windows.kernel32.ReadFile(handle, buf.ptr, math.cast(u32, buf.len) orelse math.maxInt(u32), &read_bytes, overlapped);
667 if (read_result == 0) return switch (windows.GetLastError()) {
668 .IO_PENDING => .pending,
669 .BROKEN_PIPE => .closed,
670 else => |err| windows.unexpectedError(err),
687 const fifo_read_pending = while (true) {
688 const buf = try fifo.writableWithSize(bump_amt);
689 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);
690
691 if (0 == windows.kernel32.ReadFile(
692 handle,
693 buf.ptr,
694 buf_len,
695 &win_dummy_bytes_read,
696 overlapped,
697 )) switch (windows.GetLastError()) {
698 .IO_PENDING => break true,
699 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
700 else => |err| return windows.unexpectedError(err),
701 };
702
703 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
704 .success => |n| n,
705 .closed => return if (read_any_data) .closed_populated else .closed,
706 .aborted => unreachable,
707 };
708
709 read_any_data = true;
710 fifo.update(num_bytes_read);
711
712 if (num_bytes_read == buf_len) {
713 // We filled the buffer, so there's probably more data available.
714 continue;
715 } else {
716 // We didn't fill the buffer, so assume we're out of data.
717 // There is no pending read.
718 break false;
719 }
671720 };
672 fifo.update(read_bytes);
721
722 if (fifo_read_pending) cancel_read: {
723 // Cancel the pending read into the FIFO.
724 _ = windows.kernel32.CancelIo(handle);
725
726 // We have to wait for the handle to be signalled, i.e. for the cancellation to complete.
727 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {
728 windows.WAIT_OBJECT_0 => {},
729 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),
730 else => unreachable,
731 }
732
733 // If it completed before we canceled, make sure to tell the FIFO!
734 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) {
735 .success => |n| n,
736 .closed => return if (read_any_data) .closed_populated else .closed,
737 .aborted => break :cancel_read,
738 };
739 read_any_data = true;
740 fifo.update(num_bytes_read);
741 }
742
743 // Try to queue the 1-byte read.
744 if (0 == windows.kernel32.ReadFile(
745 handle,
746 small_buf,
747 small_buf.len,
748 &win_dummy_bytes_read,
749 overlapped,
750 )) switch (windows.GetLastError()) {
751 .IO_PENDING => {
752 // 1-byte read pending as intended
753 return if (read_any_data) .populated else .empty;
754 },
755 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
756 else => |err| return windows.unexpectedError(err),
757 };
758
759 // We got data back this time. Write it to the FIFO and run the main loop again.
760 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
761 .success => |n| n,
762 .closed => return if (read_any_data) .closed_populated else .closed,
763 .aborted => unreachable,
764 };
765 try fifo.write(small_buf[0..num_bytes_read]);
766 read_any_data = true;
673767 }
674768}
675769
770/// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation.
771/// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected).
772///
773/// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the
774/// operation immediately returns data:
775/// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially
776/// erroneous results."
777/// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...]
778/// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to
779/// get the actual number of bytes read."
780/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
781fn windowsGetReadResult(
782 handle: windows.HANDLE,
783 overlapped: *windows.OVERLAPPED,
784 allow_aborted: bool,
785) !union(enum) {
786 success: u32,
787 closed,
788 aborted,
789} {
790 var num_bytes_read: u32 = undefined;
791 if (0 == windows.kernel32.GetOverlappedResult(
792 handle,
793 overlapped,
794 &num_bytes_read,
795 0,
796 )) switch (windows.GetLastError()) {
797 .BROKEN_PIPE => return .closed,
798 .OPERATION_ABORTED => |err| if (allow_aborted) {
799 return .aborted;
800 } else {
801 return windows.unexpectedError(err);
802 },
803 else => |err| return windows.unexpectedError(err),
804 };
805 return .{ .success = num_bytes_read };
806}
807
676808/// Given an enum, returns a struct with fields of that enum, each field
677809/// representing an I/O stream for polling.
678810pub fn PollFiles(comptime StreamEnum: type) type {
test/incremental/add_decl+1
......@@ -1,5 +1,6 @@
11#target=x86_64-linux-selfhosted
22#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
34#update=initial version
45#file=main.zig
56const std = @import("std");
test/incremental/add_decl_namespaced+1
......@@ -1,5 +1,6 @@
11#target=x86_64-linux-selfhosted
22#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
34#update=initial version
45#file=main.zig
56const std = @import("std");
test/incremental/delete_comptime_decls+1
......@@ -1,5 +1,6 @@
11#target=x86_64-linux-selfhosted
22#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
34#update=initial version
45#file=main.zig
56pub fn main() void {}
test/incremental/hello+1
......@@ -1,5 +1,6 @@
11#target=x86_64-linux-selfhosted
22#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
34#update=initial version
45#file=main.zig
56const std = @import("std");
test/incremental/modify_inline_fn+1
......@@ -1,5 +1,6 @@
11#target=x86_64-linux-selfhosted
22#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
34#update=initial version
45#file=main.zig
56const std = @import("std");
test/incremental/move_src+1
......@@ -1,5 +1,6 @@
11#target=x86_64-linux-selfhosted
22#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
34#update=initial version
45#file=main.zig
56const std = @import("std");
test/incremental/remove_enum_field+1
......@@ -1,5 +1,6 @@
11#target=x86_64-linux-selfhosted
22#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
34#update=initial version
45#file=main.zig
56const MyEnum = enum(u8) {
test/incremental/type_becomes_comptime_only+1
......@@ -1,5 +1,6 @@
11#target=x86_64-linux-selfhosted
22#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
34#update=initial version
45#file=main.zig
56const SomeType = u32;
test/incremental/unreferenced_error+1
......@@ -1,5 +1,6 @@
11#target=x86_64-linux-selfhosted
22#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
34#update=initial version
45#file=main.zig
56const std = @import("std");
test/tests.zig+28
......@@ -1509,3 +1509,31 @@ pub fn addDebuggerTests(b: *std.Build, options: DebuggerContext.Options) ?*Step
15091509 });
15101510 return step;
15111511}
1512
1513pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {
1514 const incr_check = b.addExecutable(.{
1515 .name = "incr-check",
1516 .root_source_file = b.path("tools/incr-check.zig"),
1517 .target = b.graph.host,
1518 .optimize = .Debug,
1519 });
1520
1521 var dir = try b.build_root.handle.openDir("test/incremental", .{ .iterate = true });
1522 defer dir.close();
1523
1524 var it = try dir.walk(b.graph.arena);
1525 while (try it.next()) |entry| {
1526 if (entry.kind != .file) continue;
1527
1528 const run = b.addRunArtifact(incr_check);
1529 run.setName(b.fmt("incr-check '{s}'", .{entry.basename}));
1530
1531 run.addArg(b.graph.zig_exe);
1532 run.addFileArg(b.path("test/incremental/").path(b, entry.path));
1533 run.addArgs(&.{ "--zig-lib-dir", b.fmt("{}", .{b.graph.zig_lib_directory}) });
1534
1535 run.addCheck(.{ .expect_term = .{ .Exited = 0 } });
1536
1537 test_step.dependOn(&run.step);
1538 }
1539}
tools/incr-check.zig+151-49
......@@ -1,11 +1,12 @@
11const std = @import("std");
2const fatal = std.process.fatal;
32const Allocator = std.mem.Allocator;
43const Cache = std.Build.Cache;
54
6const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-link] [--zig-cc-binary /path/to/zig]";
5const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-link] [--preserve-tmp] [--zig-cc-binary /path/to/zig]";
76
87pub fn main() !void {
8 const fatal = std.process.fatal;
9
910 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
1011 defer arena_instance.deinit();
1112 const arena = arena_instance.allocator();
......@@ -16,6 +17,7 @@ pub fn main() !void {
1617 var opt_cc_zig: ?[]const u8 = null;
1718 var debug_zcu = false;
1819 var debug_link = false;
20 var preserve_tmp = false;
1921
2022 var arg_it = try std.process.argsWithAllocator(arena);
2123 _ = arg_it.skip();
......@@ -27,6 +29,8 @@ pub fn main() !void {
2729 debug_zcu = true;
2830 } else if (std.mem.eql(u8, arg, "--debug-link")) {
2931 debug_link = true;
32 } else if (std.mem.eql(u8, arg, "--preserve-tmp")) {
33 preserve_tmp = true;
3034 } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) {
3135 opt_cc_zig = arg_it.next() orelse fatal("expect arg after '--zig-cc-binary'\n{s}", .{usage});
3236 } else {
......@@ -48,15 +52,29 @@ pub fn main() !void {
4852 const input_file_bytes = try std.fs.cwd().readFileAlloc(arena, input_file_name, std.math.maxInt(u32));
4953 const case = try Case.parse(arena, input_file_bytes);
5054
55 // Check now: if there are any targets using the `cbe` backend, we need the lib dir.
56 if (opt_lib_dir == null) {
57 for (case.targets) |target| {
58 if (target.backend == .cbe) {
59 fatal("'--zig-lib-dir' requried when using backend 'cbe'", .{});
60 }
61 }
62 }
63
5164 const prog_node = std.Progress.start(.{});
5265 defer prog_node.end();
5366
5467 const rand_int = std.crypto.random.int(u64);
5568 const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);
56 const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{});
57
58 const child_prog_node = prog_node.start("zig build-exe", 0);
59 defer child_prog_node.end();
69 var tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{});
70 defer {
71 tmp_dir.close();
72 if (!preserve_tmp) {
73 std.fs.cwd().deleteTree(tmp_dir_path) catch |err| {
74 std.log.warn("failed to delete tree '{s}': {s}", .{ tmp_dir_path, @errorName(err) });
75 };
76 }
77 }
6078
6179 // Convert paths to be relative to the cwd of the subprocess.
6280 const resolved_zig_exe = try std.fs.path.relative(arena, tmp_dir_path, zig_exe);
......@@ -65,10 +83,21 @@ pub fn main() !void {
6583 else
6684 null;
6785
86 const host = try std.zig.system.resolveTargetQuery(.{});
87
6888 const debug_log_verbose = debug_zcu or debug_link;
6989
7090 for (case.targets) |target| {
71 std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) });
91 const target_prog_node = node: {
92 var name_buf: [std.Progress.Node.max_name_len]u8 = undefined;
93 const name = std.fmt.bufPrint(&name_buf, "{s}-{s}", .{ target.query, @tagName(target.backend) }) catch &name_buf;
94 break :node prog_node.start(name, case.updates.len);
95 };
96 defer target_prog_node.end();
97
98 if (debug_log_verbose) {
99 std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) });
100 }
72101
73102 var child_args: std.ArrayListUnmanaged([]const u8) = .empty;
74103 try child_args.appendSlice(arena, &.{
......@@ -81,7 +110,7 @@ pub fn main() !void {
81110 "--cache-dir",
82111 ".local-cache",
83112 "--global-cache-dir",
84 ".global_cache",
113 ".global-cache",
85114 "--listen=-",
86115 });
87116 if (opt_resolved_lib_dir) |resolved_lib_dir| {
......@@ -100,11 +129,14 @@ pub fn main() !void {
100129 try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" });
101130 }
102131
132 const zig_prog_node = target_prog_node.start("zig build-exe", 0);
133 defer zig_prog_node.end();
134
103135 var child = std.process.Child.init(child_args.items, arena);
104136 child.stdin_behavior = .Pipe;
105137 child.stdout_behavior = .Pipe;
106138 child.stderr_behavior = .Pipe;
107 child.progress_node = child_prog_node;
139 child.progress_node = zig_prog_node;
108140 child.cwd_dir = tmp_dir;
109141 child.cwd = tmp_dir_path;
110142
......@@ -121,7 +153,7 @@ pub fn main() !void {
121153 "-target",
122154 target.query,
123155 "-I",
124 opt_resolved_lib_dir orelse fatal("'--zig-lib-dir' required when using backend 'cbe'", .{}),
156 opt_resolved_lib_dir.?, // verified earlier
125157 "-o",
126158 });
127159 }
......@@ -129,11 +161,13 @@ pub fn main() !void {
129161 var eval: Eval = .{
130162 .arena = arena,
131163 .case = case,
164 .host = host,
132165 .target = target,
133166 .tmp_dir = tmp_dir,
134167 .tmp_dir_path = tmp_dir_path,
135168 .child = &child,
136169 .allow_stderr = debug_log_verbose,
170 .preserve_tmp_on_fatal = preserve_tmp,
137171 .cc_child_args = &cc_child_args,
138172 };
139173
......@@ -146,7 +180,7 @@ pub fn main() !void {
146180 defer poller.deinit();
147181
148182 for (case.updates) |update| {
149 var update_node = prog_node.start(update.name, 0);
183 var update_node = target_prog_node.start(update.name, 0);
150184 defer update_node.end();
151185
152186 if (debug_log_verbose) {
......@@ -160,18 +194,20 @@ pub fn main() !void {
160194
161195 try eval.end(&poller);
162196
163 waitChild(&child);
197 waitChild(&child, &eval);
164198 }
165199}
166200
167201const Eval = struct {
168202 arena: Allocator,
203 host: std.Target,
169204 case: Case,
170205 target: Case.Target,
171206 tmp_dir: std.fs.Dir,
172207 tmp_dir_path: []const u8,
173208 child: *std.process.Child,
174209 allow_stderr: bool,
210 preserve_tmp_on_fatal: bool,
175211 /// When `target.backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary.
176212 /// The arguments `out.c in.c` must be appended before spawning the subprocess.
177213 cc_child_args: *std.ArrayListUnmanaged([]const u8),
......@@ -186,12 +222,12 @@ const Eval = struct {
186222 .sub_path = full_contents.name,
187223 .data = full_contents.bytes,
188224 }) catch |err| {
189 fatal("failed to update '{s}': {s}", .{ full_contents.name, @errorName(err) });
225 eval.fatal("failed to update '{s}': {s}", .{ full_contents.name, @errorName(err) });
190226 };
191227 }
192228 for (update.deletes) |doomed_name| {
193229 eval.tmp_dir.deleteFile(doomed_name) catch |err| {
194 fatal("failed to delete '{s}': {s}", .{ doomed_name, @errorName(err) });
230 eval.fatal("failed to delete '{s}': {s}", .{ doomed_name, @errorName(err) });
195231 };
196232 }
197233 }
......@@ -233,7 +269,7 @@ const Eval = struct {
233269 if (eval.allow_stderr) {
234270 std.log.info("error_bundle included stderr:\n{s}", .{stderr_data});
235271 } else {
236 fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data});
272 eval.fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data});
237273 }
238274 }
239275 if (result_error_bundle.errorMessageCount() != 0) {
......@@ -252,7 +288,7 @@ const Eval = struct {
252288 if (eval.allow_stderr) {
253289 std.log.info("emit_digest included stderr:\n{s}", .{stderr_data});
254290 } else {
255 fatal("emit_digest included unexpected stderr:\n{s}", .{stderr_data});
291 eval.fatal("emit_digest included unexpected stderr:\n{s}", .{stderr_data});
256292 }
257293 }
258294
......@@ -268,14 +304,7 @@ const Eval = struct {
268304 const name = std.fs.path.stem(std.fs.path.basename(eval.case.root_source_file));
269305 const bin_name = try std.zig.binNameAlloc(arena, .{
270306 .root_name = name,
271 .target = try std.zig.system.resolveTargetQuery(try std.Build.parseTargetQuery(.{
272 .arch_os_abi = eval.target.query,
273 .object_format = switch (eval.target.backend) {
274 .sema => unreachable,
275 .selfhosted, .llvm => null,
276 .cbe => "c",
277 },
278 })),
307 .target = eval.target.resolved,
279308 .output_mode = .Exe,
280309 });
281310 const bin_path = try std.fs.path.join(arena, &.{ result_dir, bin_name });
......@@ -296,16 +325,15 @@ const Eval = struct {
296325 if (eval.allow_stderr) {
297326 std.log.info("update '{s}' included stderr:\n{s}", .{ update.name, stderr_data });
298327 } else {
299 fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data });
328 eval.fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data });
300329 }
301330 }
302331
303 waitChild(eval.child);
304 fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name});
332 waitChild(eval.child, eval);
333 eval.fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name});
305334 }
306335
307336 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
308 _ = eval;
309337 switch (update.outcome) {
310338 .unknown => return,
311339 .compile_errors => |expected_errors| {
......@@ -317,7 +345,7 @@ const Eval = struct {
317345 .stdout, .exit_code => {
318346 const color: std.zig.Color = .auto;
319347 error_bundle.renderToStdErr(color.renderOptions());
320 fatal("update '{s}': unexpected compile errors", .{update.name});
348 eval.fatal("update '{s}': unexpected compile errors", .{update.name});
321349 },
322350 }
323351 }
......@@ -325,7 +353,7 @@ const Eval = struct {
325353 fn checkSuccessOutcome(eval: *Eval, update: Case.Update, opt_emitted_path: ?[]const u8, prog_node: std.Progress.Node) !void {
326354 switch (update.outcome) {
327355 .unknown => return,
328 .compile_errors => fatal("expected compile errors but compilation incorrectly succeeded", .{}),
356 .compile_errors => eval.fatal("expected compile errors but compilation incorrectly succeeded", .{}),
329357 .stdout, .exit_code => {},
330358 }
331359 const emitted_path = opt_emitted_path orelse {
......@@ -344,27 +372,73 @@ const Eval = struct {
344372 },
345373 };
346374
375 var argv_buf: [2][]const u8 = undefined;
376 const argv: []const []const u8, const is_foreign: bool = switch (std.zig.system.getExternalExecutor(
377 eval.host,
378 &eval.target.resolved,
379 .{ .link_libc = eval.target.backend == .cbe },
380 )) {
381 .bad_dl, .bad_os_or_cpu => {
382 // This binary cannot be executed on this host.
383 if (eval.allow_stderr) {
384 std.log.warn("skipping execution because host '{s}' cannot execute binaries for foreign target '{s}'", .{
385 try eval.host.zigTriple(eval.arena),
386 try eval.target.resolved.zigTriple(eval.arena),
387 });
388 }
389 return;
390 },
391 .native, .rosetta => argv: {
392 argv_buf[0] = binary_path;
393 break :argv .{ argv_buf[0..1], false };
394 },
395 .qemu, .wine, .wasmtime, .darling => |executor_cmd| argv: {
396 argv_buf[0] = executor_cmd;
397 argv_buf[1] = binary_path;
398 break :argv .{ argv_buf[0..2], true };
399 },
400 };
401
402 const run_prog_node = prog_node.start("run generated executable", 0);
403 defer run_prog_node.end();
404
347405 const result = std.process.Child.run(.{
348406 .allocator = eval.arena,
349 .argv = &.{binary_path},
407 .argv = argv,
350408 .cwd_dir = eval.tmp_dir,
351409 .cwd = eval.tmp_dir_path,
352410 }) catch |err| {
353 fatal("update '{s}': failed to run the generated executable '{s}': {s}", .{
411 if (is_foreign) {
412 // Chances are the foreign executor isn't available. Skip this evaluation.
413 if (eval.allow_stderr) {
414 std.log.warn("update '{s}': skipping execution of '{s}' via executor for foreign target '{s}': {s}", .{
415 update.name,
416 binary_path,
417 try eval.target.resolved.zigTriple(eval.arena),
418 @errorName(err),
419 });
420 }
421 return;
422 }
423 eval.fatal("update '{s}': failed to run the generated executable '{s}': {s}", .{
354424 update.name, binary_path, @errorName(err),
355425 });
356426 };
357 if (result.stderr.len != 0) {
427
428 // Some executors (looking at you, Wine) like throwing some stderr in, just for fun.
429 // Therefore, we'll ignore stderr when using a foreign executor.
430 if (!is_foreign and result.stderr.len != 0) {
358431 std.log.err("update '{s}': generated executable '{s}' had unexpected stderr:\n{s}", .{
359432 update.name, binary_path, result.stderr,
360433 });
361434 }
435
362436 switch (result.term) {
363437 .Exited => |code| switch (update.outcome) {
364438 .unknown, .compile_errors => unreachable,
365439 .stdout => |expected_stdout| {
366440 if (code != 0) {
367 fatal("update '{s}': generated executable '{s}' failed with code {d}", .{
441 eval.fatal("update '{s}': generated executable '{s}' failed with code {d}", .{
368442 update.name, binary_path, code,
369443 });
370444 }
......@@ -373,12 +447,13 @@ const Eval = struct {
373447 .exit_code => |expected_code| try std.testing.expectEqual(expected_code, result.term.Exited),
374448 },
375449 .Signal, .Stopped, .Unknown => {
376 fatal("update '{s}': generated executable '{s}' terminated unexpectedly", .{
450 eval.fatal("update '{s}': generated executable '{s}' terminated unexpectedly", .{
377451 update.name, binary_path,
378452 });
379453 },
380454 }
381 if (result.stderr.len != 0) std.process.exit(1);
455
456 if (!is_foreign and result.stderr.len != 0) std.process.exit(1);
382457 }
383458
384459 fn requestUpdate(eval: *Eval) !void {
......@@ -390,7 +465,7 @@ const Eval = struct {
390465 }
391466
392467 fn end(eval: *Eval, poller: *Poller) !void {
393 requestExit(eval.child);
468 requestExit(eval.child, eval);
394469
395470 const Header = std.zig.Server.Message.Header;
396471 const stdout = poller.fifo(.stdout);
......@@ -410,7 +485,7 @@ const Eval = struct {
410485
411486 if (stderr.readableLength() > 0) {
412487 const stderr_data = try stderr.toOwnedSlice();
413 fatal("unexpected stderr:\n{s}", .{stderr_data});
488 eval.fatal("unexpected stderr:\n{s}", .{stderr_data});
414489 }
415490 }
416491
......@@ -430,7 +505,7 @@ const Eval = struct {
430505 .cwd = eval.tmp_dir_path,
431506 .progress_node = child_prog_node,
432507 }) catch |err| {
433 fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{
508 eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{
434509 update.name, c_path, @errorName(err),
435510 });
436511 };
......@@ -441,7 +516,7 @@ const Eval = struct {
441516 update.name, result.stderr,
442517 });
443518 }
444 fatal("update '{s}': zig cc for '{s}' failed with code {d}", .{
519 eval.fatal("update '{s}': zig cc for '{s}' failed with code {d}", .{
445520 update.name, c_path, code,
446521 });
447522 },
......@@ -451,12 +526,22 @@ const Eval = struct {
451526 update.name, result.stderr,
452527 });
453528 }
454 fatal("update '{s}': zig cc for '{s}' terminated unexpectedly", .{
529 eval.fatal("update '{s}': zig cc for '{s}' terminated unexpectedly", .{
455530 update.name, c_path,
456531 });
457532 },
458533 }
459534 }
535
536 fn fatal(eval: *Eval, comptime fmt: []const u8, args: anytype) noreturn {
537 eval.tmp_dir.close();
538 if (!eval.preserve_tmp_on_fatal) {
539 std.fs.cwd().deleteTree(eval.tmp_dir_path) catch |err| {
540 std.log.warn("failed to delete tree '{s}': {s}", .{ eval.tmp_dir_path, @errorName(err) });
541 };
542 }
543 std.process.fatal(fmt, args);
544 }
460545};
461546
462547const Case = struct {
......@@ -466,6 +551,7 @@ const Case = struct {
466551
467552 const Target = struct {
468553 query: []const u8,
554 resolved: std.Target,
469555 backend: Backend,
470556 const Backend = enum {
471557 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
......@@ -511,6 +597,8 @@ const Case = struct {
511597 };
512598
513599 fn parse(arena: Allocator, bytes: []const u8) !Case {
600 const fatal = std.process.fatal;
601
514602 var targets: std.ArrayListUnmanaged(Target) = .empty;
515603 var updates: std.ArrayListUnmanaged(Update) = .empty;
516604 var changes: std.ArrayListUnmanaged(FullContents) = .empty;
......@@ -521,18 +609,32 @@ const Case = struct {
521609 if (std.mem.startsWith(u8, line, "#")) {
522610 var line_it = std.mem.splitScalar(u8, line, '=');
523611 const key = line_it.first()[1..];
524 const val = line_it.rest();
612 const val = std.mem.trimRight(u8, line_it.rest(), "\r"); // windows moment
525613 if (val.len == 0) {
526614 fatal("line {d}: missing value", .{line_n});
527615 } else if (std.mem.eql(u8, key, "target")) {
528616 const split_idx = std.mem.lastIndexOfScalar(u8, val, '-') orelse
529617 fatal("line {d}: target does not include backend", .{line_n});
618
530619 const query = val[0..split_idx];
620
531621 const backend_str = val[split_idx + 1 ..];
532622 const backend: Target.Backend = std.meta.stringToEnum(Target.Backend, backend_str) orelse
533623 fatal("line {d}: invalid backend '{s}'", .{ line_n, backend_str });
624
625 const parsed_query = std.Build.parseTargetQuery(.{
626 .arch_os_abi = query,
627 .object_format = switch (backend) {
628 .sema, .selfhosted, .llvm => null,
629 .cbe => "c",
630 },
631 }) catch fatal("line {d}: invalid target query '{s}'", .{ line_n, query });
632
633 const resolved = try std.zig.system.resolveTargetQuery(parsed_query);
634
534635 try targets.append(arena, .{
535636 .query = query,
637 .resolved = resolved,
536638 .backend = backend,
537639 });
538640 } else if (std.mem.eql(u8, key, "update")) {
......@@ -603,7 +705,7 @@ const Case = struct {
603705 }
604706};
605707
606fn requestExit(child: *std.process.Child) void {
708fn requestExit(child: *std.process.Child, eval: *Eval) void {
607709 if (child.stdin == null) return;
608710
609711 const header: std.zig.Client.Message.Header = .{
......@@ -612,7 +714,7 @@ fn requestExit(child: *std.process.Child) void {
612714 };
613715 child.stdin.?.writeAll(std.mem.asBytes(&header)) catch |err| switch (err) {
614716 error.BrokenPipe => {},
615 else => fatal("failed to send exit: {s}", .{@errorName(err)}),
717 else => eval.fatal("failed to send exit: {s}", .{@errorName(err)}),
616718 };
617719
618720 // Send EOF to stdin.
......@@ -620,11 +722,11 @@ fn requestExit(child: *std.process.Child) void {
620722 child.stdin = null;
621723}
622724
623fn waitChild(child: *std.process.Child) void {
624 requestExit(child);
625 const term = child.wait() catch |err| fatal("child process failed: {s}", .{@errorName(err)});
725fn waitChild(child: *std.process.Child, eval: *Eval) void {
726 requestExit(child, eval);
727 const term = child.wait() catch |err| eval.fatal("child process failed: {s}", .{@errorName(err)});
626728 switch (term) {
627 .Exited => |code| if (code != 0) fatal("compiler failed with code {d}", .{code}),
628 .Signal, .Stopped, .Unknown => fatal("compiler terminated unexpectedly", .{}),
729 .Exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}),
730 .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}),
629731 }
630732}