diff --git a/build.zig b/build.zig index bcd0f50975e906c61a9e2649e933ac8350da5d43..f2b6c5c9b6d2a3a560b31c5605367bd776ec3f60 100644 --- a/build.zig +++ b/build.zig @@ -793,7 +793,7 @@ pub fn build(b: *std.Build) !void { const runner = b.addExecutable(.{ .name = "runner", .root_module = b.createModule(.{ - .root_source_file = b.path("test/src/Runner.zig"), + .root_source_file = b.path("test/Runner.zig"), .target = b.graph.host, }), }); @@ -813,17 +813,15 @@ pub fn build(b: *std.Build) !void { .lldb = b.option([]const u8, "lldb", "path to lldb binary"), }; - const test_incremental_step = b.step("test-incremental", "Run the incremental compilation tests"); - try tests.addRunnerTests(b, runner, test_incremental_step, "test/incremental", tests.incremental_matrix, runner_opts); - test_step.dependOn(test_incremental_step); - const test_debugger_step = b.step("test-debugger", "Run the debugger tests"); - if (runner_opts.gdb == null and runner_opts.lldb == null) { - test_debugger_step.dependOn(&b.addFail("test-debugger requires -Dgdb and/or -Dlldb").step); - } else { - try tests.addRunnerTests(b, runner, test_debugger_step, "test/debugger", tests.debugger_matrix, runner_opts); + if (runner_opts.gdb != null or runner_opts.lldb != null) { + try tests.addRunnerTests(b, runner, test_debugger_step, "test/debugger", @import("test/debugger/matrix.zon"), runner_opts); test_step.dependOn(test_debugger_step); - } + } else test_debugger_step.dependOn(&b.addFail("test-debugger requires -Dgdb and/or -Dlldb").step); + + const test_incremental_step = b.step("test-incremental", "Run the incremental compilation tests"); + try tests.addRunnerTests(b, runner, test_incremental_step, "test/incremental", @import("test/incremental/matrix.zon"), runner_opts); + test_step.dependOn(test_incremental_step); } fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { diff --git a/lib/compiler/Maker/Package/Manifest.zig b/lib/compiler/Maker/Package/Manifest.zig index 7dd507a20c8ce9836b04ba2d8e4822e569ef5e91..dd7fbd86f8c6d1ae3f4e003921a42fdf6e2a67dc 100644 --- a/lib/compiler/Maker/Package/Manifest.zig +++ b/lib/compiler/Maker/Package/Manifest.zig @@ -494,95 +494,9 @@ const Parse = struct { }; switch (result) { .success => {}, - .failure => |err| try p.appendStrLitError(err, token, bytes, offset), - } - } - - /// TODO: try to DRY this with AstGen.failWithStrLitError - fn appendStrLitError( - p: *Parse, - err: std.zig.string_literal.Error, - token: Ast.TokenIndex, - bytes: []const u8, - offset: u32, - ) Allocator.Error!void { - const raw_string = bytes[offset..]; - switch (err) { - .invalid_escape_character => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "invalid escape character: '{c}'", - .{raw_string[bad_index]}, - ); - }, - .expected_hex_digit => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "expected hex digit, found '{c}'", - .{raw_string[bad_index]}, - ); - }, - .empty_unicode_escape_sequence => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "empty unicode escape sequence", - .{}, - ); - }, - .expected_hex_digit_or_rbrace => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "expected hex digit or '}}', found '{c}'", - .{raw_string[bad_index]}, - ); - }, - .invalid_unicode_codepoint => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "unicode escape does not correspond to a unicode code point", - .{}, - ); - }, - .expected_lbrace => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "expected '{{', found '{c}", - .{raw_string[bad_index]}, - ); - }, - .expected_rbrace => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "expected '}}', found '{c}", - .{raw_string[bad_index]}, - ); - }, - .expected_single_quote => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "expected single quote ('), found '{c}", - .{raw_string[bad_index]}, - ); - }, - .invalid_character => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "invalid byte in string or character literal: '{c}'", - .{raw_string[bad_index]}, - ); - }, - .empty_char_literal => { - try p.appendErrorOff(token, offset, "empty character literal", .{}); - }, + .failure => |err| try p.appendErrorOff(token, @intCast(offset + err.offset()), "{f}", .{ + err.fmt(bytes[offset..]), + }), } } diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index c51c2ab77c62faf84057d25f2739e37ece9fd0d6..95a293c2b86d68f3de2ee292b4df3d50666f9709 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -38,6 +38,11 @@ pub const Error = union(enum) { invalid_character: usize, /// `''`. Not returned for string literals. empty_char_literal, + /// The input ended before the end of the literal. + end_of_input: usize, + /// The literal ended before the end of the input. There was an otherwise successful + /// parse of a prefix of the input of length given by this payload written to the writer. + end_of_literal: usize, const FormatMessage = struct { err: Error, @@ -83,6 +88,12 @@ pub const Error = union(enum) { .empty_char_literal => try writer.writeAll( "empty character literal", ), + .end_of_input => try writer.writeAll( + "expected end of string literal", + ), + .end_of_literal => try writer.writeAll( + "unexpected end of string literal", + ), } } @@ -104,6 +115,8 @@ pub const Error = union(enum) { .expected_rbrace, .expected_single_quote, .invalid_character, + .end_of_input, + .end_of_literal, => |n| n, .empty_char_literal => 0, }; @@ -339,13 +352,16 @@ pub fn parseWrite(writer: *Writer, bytes: []const u8) Writer.Error!Result { try writer.writeAll(buf[0..len]); } else { try writer.writeByte(@as(u8, @intCast(codepoint))); + if (index == bytes.len) return .{ .failure = .{ .end_of_input = index } }; } }, .failure => |err| return .{ .failure = err }, } }, '\n' => return .{ .failure = .{ .invalid_character = index } }, - '"' => return .success, + '"' => return if (index < bytes.len - 1) .{ + .failure = .{ .end_of_literal = index }, + } else .success, else => { try writer.writeByte(b); index += 1; diff --git a/test/Runner.zig b/test/Runner.zig new file mode 100644 index 0000000000000000000000000000000000000000..d614036096d5de25984438bc00314d696d1409b0 --- /dev/null +++ b/test/Runner.zig @@ -0,0 +1,2240 @@ +const assert = std.debug.assert; +const builtin = @import("builtin"); +const std = @import("std"); +const Runner = @This(); + +gpa: std.mem.Allocator, +arena: *std.heap.ArenaAllocator, +environ_map: std.process.Environ.Map, +io: std.Io, +prog_node: std.Progress.Node, +args: Args, +host: std.Target, +server: std.zig.Server, +eb_wip: std.zig.ErrorBundle.Wip, + +const Args = struct { + @"test": ?struct { + input_dir: std.zig.Server.Message.InputDir, + dir: std.Io.Dir, + }, + manifest_file: ?std.Io.File, + zig_exe: ?std.Io.File, + lib_dir: ?std.Io.Dir, + src_dir: ?std.Io.Dir, + targets: std.ArrayList(u32), + target_bytes: std.ArrayList(u8), + + libc_runtimes_dir: ?std.Io.Dir, + enable_darling: bool, + enable_qemu: bool, + enable_rosetta: bool, + enable_wasmtime: bool, + enable_wine: bool, + + gdb_exe: ?std.Io.File, + lldb_exe: ?std.Io.File, + + quiet: bool, + + fn deinit(args: *Args, gpa: std.mem.Allocator) void { + args.targets.deinit(gpa); + args.target_bytes.deinit(gpa); + args.* = undefined; + } + + fn addTarget(args: *Args, gpa: std.mem.Allocator, target: []const u8) std.mem.Allocator.Error!void { + try args.target_bytes.ensureUnusedCapacity(gpa, target.len + 1); + try args.targets.append(gpa, @intCast(args.target_bytes.items.len)); + args.target_bytes.appendSliceAssumeCapacity(target); + args.target_bytes.appendAssumeCapacity(0); + } +}; + +fn deinit(runner: *Runner) void { + runner.environ_map.deinit(); + runner.prog_node.end(); + runner.args.deinit(runner.gpa); + runner.eb_wip.deinit(); + runner.* = undefined; +} + +const FailError = error{AlreadyReported} || std.mem.Allocator.Error || std.Io.Cancelable; +fn fail(runner: *Runner, comptime fmt: []const u8, args: anytype) FailError { + return runner.failString(try runner.eb_wip.printString(fmt, args)); +} +fn failString(runner: *Runner, msg: std.zig.ErrorBundle.String) FailError { + try runner.eb_wip.addRootErrorMessage(.{ .msg = msg }); + return error.AlreadyReported; +} + +pub const ProtocolError = error{ + ServerReadFailed, + ServerWriteFailed, + ServerEndOfStream, +} || FailError; +pub fn runServer(runner: *Runner) ProtocolError { + runner.server.serveStringMessage(.zig_version, builtin.zig_version_string) catch |err| switch (err) { + error.WriteFailed => return error.ServerWriteFailed, + }; + while (true) { + _ = runner.arena.reset(.retain_capacity); + const arena = runner.arena.allocator(); + const hdr = runner.server.receiveMessage() catch |err| switch (err) { + error.ReadFailed => return error.ServerReadFailed, + error.EndOfStream => return error.ServerEndOfStream, + }; + switch (hdr.tag) { + else => |tag| return runner.fail("unsupported message: {t}", .{tag}), + .exit => std.process.exit(0), + .args => { + const args_body = try arena.alloc(u8, hdr.bytes_len); + runner.server.in.readSliceAll(args_body) catch unreachable; + const State = enum { + positional, + @"--zig", + @"--lib", + @"--src", + @"--target", + @"--libc-runtimes", + @"--gdb", + @"--lldb", + }; + var state: State = .positional; + const state_expected: std.enums.EnumArray( + State, + enum { path, dir, file, string }, + ) = .init(.{ + .positional = .path, + .@"--zig" = .file, + .@"--lib" = .dir, + .@"--src" = .dir, + .@"--target" = .string, + .@"--libc-runtimes" = .dir, + .@"--gdb" = .file, + .@"--lldb" = .file, + }); + var input_dir: std.zig.Server.Message.InputDir = .cwd; + var args_body_offset: usize = 0; + while (args_body.len - args_body_offset > 0) { + const arg: std.zig.Client.Message.Arg = + @fromBackingInt(args_body[args_body_offset]); + args_body_offset += 1; + arg: switch (arg) { + .string => { + const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?; + const string = args_body[args_body_offset..end]; + args_body_offset = end + 1; + switch (state) { + .positional => if (std.mem.eql(u8, string, "--target")) { + state = .@"--target"; + continue; + } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| { + try runner.args.addTarget(runner.gpa, target); + } else if (std.mem.eql(u8, string, "--libc-runtimes")) { + state = .@"--libc-runtimes"; + continue; + } else if (std.mem.eql(u8, string, "-fdarling")) { + runner.args.enable_darling = true; + } else if (std.mem.eql(u8, string, "-fqemu")) { + runner.args.enable_qemu = true; + } else if (std.mem.eql(u8, string, "-frosetta")) { + runner.args.enable_rosetta = true; + } else if (std.mem.eql(u8, string, "-fwasmtime")) { + runner.args.enable_wasmtime = true; + } else if (std.mem.eql(u8, string, "-fwine")) { + runner.args.enable_wine = true; + } else if (std.mem.eql(u8, string, "--gdb")) { + state = .@"--gdb"; + continue; + } else if (std.mem.eql(u8, string, "--lldb")) { + state = .@"--lldb"; + continue; + } else if (std.mem.eql(u8, string, "--quiet")) { + runner.args.quiet = true; + }, + .@"--target" => try runner.args.addTarget(runner.gpa, string), + else => return runner.fail( + "\"{t}\" expected {t}, got {t}", + .{ state, state_expected.get(state), arg }, + ), + } + }, + .prefix => { + const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?; + const string = args_body[args_body_offset..end]; + args_body_offset = end + 1; + assert(state == .positional); + state = if (std.mem.eql(u8, string, "--zig=")) + .@"--zig" + else if (std.mem.eql(u8, string, "--lib=")) + .@"--lib" + else if (std.mem.eql(u8, string, "--src=")) + .@"--src" + else if (std.mem.eql(u8, string, "--libc-runtimes=")) + .@"--libc-runtimes" + else if (std.mem.eql(u8, string, "--gdb=")) + .@"--gdb" + else if (std.mem.eql(u8, string, "--lldb=")) + .@"--lldb" + else + return runner.fail("unsupported arg prefix: {q}", .{string}); + continue; + }, + .suffix => { + const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?; + const string = args_body[args_body_offset..end]; + args_body_offset = end + 1; + return runner.fail("unsupported arg suffix: {q}", .{string}); + }, + .input_dir => { + input_dir = @fromBackingInt(@backingInt(input_dir) + 1); + continue :arg .output_dir; + }, + .output_dir => { + const dir_handle: *align(1) const std.Io.Dir.Handle = @ptrCast( + args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)], + ); + args_body_offset += @sizeOf(std.Io.Dir.Handle); + const dir = runner.io.vtable.inheritParentDir( + runner.io.userdata, + dir_handle.*, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail( + "unable to inherit parent dir: {t}", + .{e}, + ), + }; + switch (state) { + .positional => { + if (runner.args.@"test" != null or + runner.args.manifest_file != null) return runner.fail( + "{t} specified multiple times", + .{state}, + ); + runner.args.@"test" = .{ .input_dir = input_dir, .dir = dir }; + const manifest_path = "manifest"; + runner.args.manifest_file = dir.openFile( + runner.io, + manifest_path, + .{}, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail( + "unable to open manifest file: {t}", + .{e}, + ), + }; + try runner.discoverInput(input_dir, manifest_path); + }, + .@"--lib" => { + if (runner.args.lib_dir != null) return runner.fail( + "\"{t}\" specified multiple times", + .{state}, + ); + runner.args.lib_dir = dir; + }, + .@"--src" => { + if (runner.args.src_dir != null) return runner.fail( + "\"{t}\" specified multiple times", + .{state}, + ); + runner.args.src_dir = dir; + }, + .@"--libc-runtimes" => { + if (runner.args.libc_runtimes_dir != null) return runner.fail( + "\"{t}\" specified multiple times", + .{state}, + ); + runner.args.libc_runtimes_dir = dir; + }, + else => return runner.fail( + "\"{t}\" expected {t}, got {t}", + .{ state, state_expected.get(state), arg }, + ), + } + }, + .input_file, .input_file_content, .output_file => { + const file_handle: *align(1) const std.Io.File.Handle = @ptrCast( + args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)], + ); + args_body_offset += @sizeOf(std.Io.File.Handle); + const file = runner.io.vtable.inheritParentFile( + runner.io.userdata, + file_handle.*, + .{ .nonblocking = false }, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail( + "unable to inherit parent file: {t}", + .{e}, + ), + }; + switch (state) { + .positional => { + if (runner.args.@"test" != null or + runner.args.manifest_file != null) return runner.fail( + "{t} specified multiple times", + .{state}, + ); + runner.args.manifest_file = file; + }, + .@"--zig" => { + if (runner.args.zig_exe != null) return runner.fail( + "\"{t}\" specified multiple times", + .{state}, + ); + runner.args.zig_exe = file; + }, + .@"--gdb" => { + if (runner.args.gdb_exe != null) return runner.fail( + "\"{t}\" specified multiple times", + .{state}, + ); + runner.args.gdb_exe = file; + }, + .@"--lldb" => { + if (runner.args.lldb_exe != null) return runner.fail( + "\"{t}\" specified multiple times", + .{state}, + ); + runner.args.lldb_exe = file; + }, + else => return runner.fail( + "\"{t}\" expected {t}, got {t}", + .{ state, state_expected.get(state), arg }, + ), + } + }, + } + state = .positional; + } + }, + .query_test_metadata => { + const expected_panic_msgs = try arena.alloc(u32, runner.args.targets.items.len); + @memset(expected_panic_msgs, 0); + runner.server.serveTestMetadata(.{ + .names = runner.args.targets.items, + .expected_panic_msgs = expected_panic_msgs, + .string_bytes = runner.args.target_bytes.items, + }) catch |err| switch (err) { + error.WriteFailed => return error.ServerWriteFailed, + }; + }, + .run_test => { + const test_index = runner.server.receiveBody_u32() catch unreachable; + runner.server.serveBodylessMessage(.test_started) catch |err| switch (err) { + error.WriteFailed => return error.ServerWriteFailed, + }; + runner.server.serveTestResults(.{ + .index = test_index, + .flags = .{ + .status = if (runner.testOne(test_index)) + .pass + else |err| status: switch (err) { + error.Canceled, + error.OutOfMemory, + error.ServerWriteFailed, + => |e| return e, + error.AlreadyReported => { + var eb = try runner.eb_wip.toOwnedBundle(""); + defer eb.deinit(runner.gpa); + assert(eb.errorMessageCount() > 0); // already reported what? + runner.server.serveErrorBundle( + .error_bundle, + eb, + ) catch |server_err| switch (server_err) { + error.WriteFailed => return error.ServerWriteFailed, + }; + const eb_wip: std.zig.ErrorBundle.Wip = try .init(runner.gpa); + runner.eb_wip.deinit(); + runner.eb_wip = eb_wip; + break :status .fail; + }, + error.SkipTest => .skip, + }, + .fuzz = false, + .log_err_count = 0, + .leak_count = 0, + }, + }) catch |err| switch (err) { + error.WriteFailed => return error.ServerWriteFailed, + }; + assert(runner.eb_wip.root_list.items.len == 0); // failed to report + }, + } + } +} + +fn discoverInput( + runner: *Runner, + dir: std.zig.Server.Message.InputDir, + sub_path: []const u8, +) error{ServerWriteFailed}!void { + return runner.discoverInputInner(dir, sub_path) catch |err| switch (err) { + error.WriteFailed => return error.ServerWriteFailed, + }; +} +fn discoverInputInner( + runner: *Runner, + dir: std.zig.Server.Message.InputDir, + sub_path: []const u8, +) std.Io.Writer.Error!void { + try runner.server.serveMessageHeader(.{ + .tag = .discovered_inputs, + .bytes_len = @intCast(@sizeOf(std.zig.Server.Message.InputDir) + + sub_path.len + 1), + }); + try runner.server.out.writeInt(u32, @backingInt(dir), .little); + try runner.server.out.writeAll(sub_path); + try runner.server.out.writeByte(0); + try runner.server.out.flush(); +} + +pub const SourceReader = struct { + const Io = std.Io; + + backing: *std.Io.Reader, + line_aw: std.Io.Writer.Allocating, + interface: std.Io.Reader, + line: u32, + eb_wip: *std.zig.ErrorBundle.Wip, + src_path: std.zig.ErrorBundle.String, + + pub fn init( + reader: *Io.Reader, + gpa: std.mem.Allocator, + eb_wip: *std.zig.ErrorBundle.Wip, + src_path: std.zig.ErrorBundle.String, + ) SourceReader { + return .{ + .backing = reader, + .line_aw = .init(gpa), + .interface = .{ + .vtable = &.{ + .stream = stream, + .discard = discard, + .readVec = readVec, + .rebase = rebase, + }, + .buffer = &.{}, + .seek = 0, + .end = 0, + }, + .line = 0, + .eb_wip = eb_wip, + .src_path = src_path, + }; + } + fn deinit(s: *SourceReader) void { + s.line_aw.deinit(); + } + fn stream(r: *Io.Reader, _: *Io.Writer, _: Io.Limit) Io.Reader.StreamError!usize { + const s: *SourceReader = @fieldParentPtr("interface", r); + try s.bufferLine(); + return 0; + } + fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { + const s: *SourceReader = @fieldParentPtr("interface", r); + const remaining = r.buffered(); + if (remaining.len > 0) { + const n = limit.minInt(remaining.len); + r.toss(n); + return n; + } + try s.bufferLine(); + return 0; + } + fn readVec(r: *Io.Reader, _: [][]u8) Io.Reader.Error!usize { + const s: *SourceReader = @fieldParentPtr("interface", r); + try s.bufferLine(); + return 0; + } + fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void { + const s: *SourceReader = @fieldParentPtr("interface", r); + defer s.updateBuffer(); + s.line_aw.ensureUnusedCapacity(capacity) catch |err| switch (err) { + error.OutOfMemory => return error.ReadFailed, + }; + } + fn updateBuffer(s: *SourceReader) void { + s.interface.buffer = s.line_aw.writer.buffer; + s.interface.end = s.line_aw.writer.end; + } + fn bufferLine(s: *SourceReader) Io.Reader.Error!void { + defer s.updateBuffer(); + { + const written = s.line_aw.written(); + const start = if (std.mem.findScalarLast(u8, written[0..s.interface.seek], '\n')) |newline| + newline + 1 + else + 0; + s.line += @intCast(std.mem.countScalar(u8, written[0..start], '\n')); + const preserve = written[start..]; + @memmove(written[0..preserve.len], preserve); + s.line_aw.shrinkRetainingCapacity(preserve.len); + s.interface.seek -= start; + } + const n = s.backing.streamDelimiterEnding(&s.line_aw.writer, '\n') catch |err| switch (err) { + error.ReadFailed => |e| return e, + error.WriteFailed => return error.ReadFailed, + }; + if (std.mem.endsWith(u8, s.line_aw.written(), "\r")) s.line_aw.writer.end -= 1; + const buffered = s.backing.buffered(); + if (n == 0 and buffered.len == 0) return error.EndOfStream; + if (std.mem.startsWith(u8, buffered, "\n")) { + s.line_aw.writer.writeByte('\n') catch |err| switch (err) { + error.WriteFailed => return error.ReadFailed, + }; + s.backing.toss(1); + } + } + fn fail( + s: *SourceReader, + src_loc: []const u8, + comptime fmt: []const u8, + args: anytype, + ) FailError { + return s.failString(src_loc, try s.eb_wip.printString(fmt, args)); + } + fn failString( + s: *SourceReader, + src_loc: []const u8, + msg: std.zig.ErrorBundle.String, + ) FailError { + try s.eb_wip.addRootErrorMessage(.{ .msg = msg, .src_loc = try s.srcLoc(src_loc) }); + return error.AlreadyReported; + } + fn srcLoc( + s: *SourceReader, + src_loc: []const u8, + ) std.mem.Allocator.Error!std.zig.ErrorBundle.SourceLocationIndex { + const line: u32, const column: u32, const source_line = src_loc: { + const buffer = s.interface.buffer[0..s.interface.end]; + const pos = src_loc.ptr - buffer.ptr; + const end = std.mem.findScalarPos(u8, buffer, pos, '\n') orelse buffer.len; + const start = + if (std.mem.findScalarLast(u8, buffer[0..end], '\n')) |newline| newline + 1 else 0; + break :src_loc .{ + @intCast(s.line + std.mem.countScalar(u8, buffer[0..start], '\n')), + @intCast(pos - start), + buffer[start..end], + }; + }; + return s.eb_wip.addSourceLocation(.{ + .src_path = s.src_path, + .line = line, + .column = column, + .span_start = column, + .span_main = column, + .span_end = @intCast(column + src_loc.len), + .source_line = try s.eb_wip.addString(source_line), + }); + } +}; +pub const DelimitedReader = struct { + const Io = std.Io; + + unlimited: *Io.Reader, + remaining: Io.Limit, + delimiter: []const u8, + interface: Io.Reader, + + pub fn init(reader: *Io.Reader, delimiter: []const u8, buffer: []u8) DelimitedReader { + return .{ + .unlimited = reader, + .remaining = .nothing, + .delimiter = delimiter, + .interface = .{ + .vtable = &.{ + .stream = stream, + .discard = discard, + }, + .buffer = buffer, + .seek = 0, + .end = 0, + }, + }; + } + fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { + const d: *DelimitedReader = @fieldParentPtr("interface", r); + const block = try d.unlimited.peekGreedy(d.delimiter.len); + d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse + block.len - d.delimiter.len + 1); + if (d.remaining == .nothing) return error.EndOfStream; + const n = try d.unlimited.stream(w, limit.min(d.remaining)); + d.remaining = d.remaining.subtract(n).?; + return n; + } + fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { + const d: *DelimitedReader = @fieldParentPtr("interface", r); + const block = try d.unlimited.peekGreedy(d.delimiter.len); + d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse + block.len - d.delimiter.len + 1); + if (d.remaining == .nothing) return error.EndOfStream; + const n = try d.unlimited.discard(limit.min(d.remaining)); + d.remaining = d.remaining.subtract(n).?; + return n; + } +}; + +const Update = struct { + target_query: []const u8, + manifest_sr: SourceReader, + compiler: ?Compiler, + src_dir: std.Io.Dir, + eb_wip: *std.zig.ErrorBundle.Wip, + cmd_src_loc: std.zig.ErrorBundle.SourceLocationIndex, + src_loc: ?std.zig.ErrorBundle.SourceLocationIndex, + num: usize, + name: []const u8, + prog_node: std.Progress.Node, + mtime: std.Io.Timestamp, + allow_skip: bool, + + fn checkMatches( + update: *const Update, + what: []const u8, + expected: []const u8, + actual: []const u8, + ) FailError!void { + var expected_match_it = std.mem.splitSequence(u8, expected, "#..."); + var pos: usize = 0; + while (expected_match_it.next()) |expected_match| : (pos += expected_match.len) { + var context_len: usize = expected_match.len; + pos = while (true) : (context_len -= 1) break std.mem.findPos( + u8, + actual, + pos, + expected_match[0..context_len], + ) orelse continue; + const actual_match = actual[pos..]; + std.testing.expectEqualStrings( + expected_match, + actual_match[0..@min(actual_match.len, expected_match.len)], + ) catch |err| switch (err) { + error.TestExpectedEqual => { + std.testing.failPrint("\n=============== full output: =========\n", .{}); + std.testing.failPrintWithVisibleNewlines(actual_match); + std.testing.failPrint("\n======================================\n", .{}); + return update.fail("{s} did not match expected", .{what}); + }, + }; + } + } + + fn fail(update: *const Update, comptime fmt: []const u8, args: anytype) FailError { + return update.failString(try update.eb_wip.printString(fmt, args)); + } + fn failString(update: *const Update, msg: std.zig.ErrorBundle.String) FailError { + var notes_buffer: [1]std.zig.ErrorBundle.ErrorMessage = undefined; + var notes_len: u32 = 0; + if (update.src_loc) |src_loc| { + notes_buffer[notes_len] = .{ + .msg = try update.eb_wip.addString("after this update"), + .src_loc = src_loc, + }; + notes_len += 1; + } + try update.eb_wip.addRootErrorMessageWithNotes(.{ + .msg = msg, + .src_loc = update.cmd_src_loc, + .notes_len = 1, + }, notes_buffer[0..notes_len]); + return error.AlreadyReported; + } +}; + +const TestError = error{ + SkipTest, +} || FailError; +fn testOne(runner: *Runner, test_index: u32) (error{ServerWriteFailed} || TestError)!void { + const target_query = + std.mem.sliceTo(runner.args.target_bytes.items[runner.args.targets.items[test_index]..], 0); + const manifest_file = runner.args.manifest_file orelse + return runner.fail("missing \"path/to/test\" arg", .{}); + var manifest_buffer: [512]u8 = undefined; + var manifest_fr = manifest_file.reader(runner.io, &manifest_buffer); + var update: Update = .{ + .target_query = target_query, + .manifest_sr = .init(&manifest_fr.interface, runner.gpa, &runner.eb_wip, src_path_string: { + var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + const src_path = src_path: { + break :src_path path_buffer[0 .. manifest_file.realPath( + runner.io, + &path_buffer, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => break :src_path "manifest", + }]; + }; + break :src_path_string try runner.eb_wip.addString(src_path); + }), + .compiler = null, + .src_dir = (runner.args.src_dir orelse + return runner.fail("missing \"--src=path/to/src\" arg", .{})).createDirPathOpen( + runner.io, + target_query, + .{}, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to create {q}: {t}", .{ target_query, e }), + }, + .eb_wip = &runner.eb_wip, + .cmd_src_loc = undefined, + .src_loc = null, + .num = 0, + .name = undefined, + .prog_node = .none, + .mtime = .now(runner.io, .real), + .allow_skip = true, + }; + defer { + update.manifest_sr.deinit(); + update.src_dir.close(runner.io); + update.prog_node.end(); + } + + // hack around `std.Io.Reader` interface bugs by ensuring the + // buffer is larger than the longest supported manifest line + try update.manifest_sr.line_aw.ensureTotalCapacityPrecise(512); + update.manifest_sr.updateBuffer(); + + var skip_delimiter = false; + while (update.manifest_sr.interface.takeSentinel('\n')) |line| { + var line_it = std.mem.tokenizeScalar(u8, line, ' '); + const cmd_src_loc = line_it.next() orelse continue; + if (skip_delimiter) { + assert(std.mem.eql(u8, cmd_src_loc, "#}")); + skip_delimiter = false; + const unexpected = line_it.rest(); + if (unexpected.len > 0) + return update.manifest_sr.fail(unexpected, "unexpected argument {q}", .{unexpected}); + continue; + } + update.cmd_src_loc = try update.manifest_sr.srcLoc(cmd_src_loc); + const cmd = std.meta.stringToEnum(Command, std.mem.cutPrefix(u8, cmd_src_loc, "#") orelse + return update.manifest_sr.fail(cmd_src_loc, "expected command", .{})) orelse + return update.manifest_sr.fail(cmd_src_loc, "unknown command", .{}); + const arg = switch (cmd) { + .todo, .exe, .lib, .obj, .update => null, + .skip, .write, .delete, .check => line_it.next() orelse + return update.manifest_sr.fail(line_it.rest()[0..0], "missing argument", .{}), + }; + var contents_file: ?std.Io.File = null; + defer if (contents_file) |file| file.close(runner.io); + var contents_buffer: [512]u8 = undefined; + var contents_impl: union(enum) { + r: std.Io.Reader, + dr: DelimitedReader, + fr: std.Io.File.Reader, + } = undefined; + const contents_r: *std.Io.Reader = contents_r: switch (cmd) { + .skip, .delete => { + const unexpected = line_it.rest(); + if (unexpected.len > 0) return update.manifest_sr.fail( + unexpected, + "unexpected argument {q}", + .{unexpected}, + ); + contents_impl = .{ .r = .ending_instance }; + break :contents_r &contents_impl.r; + }, + else => { + const contents = line_it.rest(); + if (contents.len == 0) { + contents_impl = .{ .r = .ending_instance }; + break :contents_r &contents_impl.r; + } + contents: switch (contents[0]) { + else => return update.manifest_sr.fail(contents, "invalid contents syntax", .{}), + '"' => { + if (contents.len < 2 or contents[contents.len - 1] != '"') continue :contents 0; + var contents_fw: std.Io.Writer = .fixed(&contents_buffer); + switch (std.zig.string_literal.parseWrite( + &contents_fw, + contents, + ) catch |err| switch (err) { + error.WriteFailed => return update.manifest_sr.fail( + contents, + "contents string too long, use inline or file syntax instead", + .{}, + ), + }) { + .success => { + contents_impl = .{ .r = .fixed(contents_fw.buffered()) }; + break :contents_r &contents_impl.r; + }, + .failure => |err| return update.manifest_sr.fail( + contents[err.offset()..][0..0], + "{f}", + .{err.fmt(contents)}, + ), + } + }, + '{' => { + if (contents.len != 1) continue :contents 0; + + contents_impl = .{ + .dr = .init(&update.manifest_sr.interface, "#}", &contents_buffer), + }; + skip_delimiter = true; + break :contents_r &contents_impl.dr.interface; + }, + '@' => { + const args_test = runner.args.@"test" orelse return update.manifest_sr.fail( + contents, + "contents file requires directory test", + .{}, + ); + const contents_path = contents[1..]; + try runner.discoverInput(args_test.input_dir, contents_path); + + contents_file = args_test.dir.openFile( + runner.io, + contents_path, + .{}, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail( + contents_path, + "unable to open contents file: {t}", + .{e}, + ), + }; + contents_impl = .{ .fr = contents_file.?.reader(runner.io, &contents_buffer) }; + break :contents_r &contents_impl.fr.interface; + }, + } + }, + }; + + runner.handleCommand(&update, cmd_src_loc, cmd, arg, contents_r) catch |err| switch (err) { + error.Canceled, + error.OutOfMemory, + error.AlreadyReported, + error.SkipTest, + => |e| return e, + error.ContentsReadFailed => switch (contents_impl) { + .r => unreachable, + .dr => switch (manifest_fr.err orelse return error.OutOfMemory) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to read manifest: {t}", .{e}), + }, + .fr => |fr| switch (fr.err.?) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to read contents file: {t}", .{e}), + }, + }, + }; + } else |err| switch (err) { + error.ReadFailed => switch (manifest_fr.err orelse return error.OutOfMemory) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to read manifest: {t}", .{e}), + }, + error.StreamTooLong => unreachable, + error.EndOfStream => { + const final_line = update.manifest_sr.interface.buffered(); + switch (final_line.len) { + 0 => if (skip_delimiter) return update.manifest_sr.fail( + final_line[0..0], + "missing \"#}}\" delimiter", + .{}, + ), + else => return update.manifest_sr.fail( + final_line[final_line.len..], + "missing trailing newline", + .{}, + ), + } + }, + } + if (update.compiler) |*comp| { + if (comp.state != .idle) return update.fail("missing \"#check\"", .{}); + try comp.exit(); + } +} +const Command = enum { todo, skip, exe, lib, obj, write, delete, update, check }; +fn handleCommand( + runner: *Runner, + update: *Update, + cmd_src_loc: []const u8, + cmd: Command, + arg: ?[]const u8, + contents_r: *std.Io.Reader, +) (error{ContentsReadFailed} || TestError)!void { + var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; + const cmd_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "#{t}{s}{s}", .{ + cmd, if (arg) |_| " " else "", arg orelse "", + }) catch &name_buffer, 0); + defer cmd_prog_node.end(); + + cmd: switch (cmd) { + .todo => _ = contents_r.discardRemaining() catch |err| switch (err) { + error.ReadFailed => {}, + }, + .skip => if (!update.allow_skip) return update.manifest_sr.fail( + cmd_src_loc, + "\"#skip\" must appear before other commands", + .{}, + ) else if (std.mem.eql(u8, update.target_query, arg.?)) return error.SkipTest, + .exe, .lib, .obj => runner.spawnCompiler(&update.compiler, update.src_dir, switch (cmd) { + else => unreachable, + .exe => .Exe, + .lib => .Lib, + .obj => .Obj, + }, contents_r, target: { + var component_it = std.mem.splitBackwardsScalar(u8, update.target_query, '-'); + const pic_str = component_it.next() orelse + return update.fail("target {q} missing pic", .{update.target_query}); + const pic = std.meta.stringToEnum(Compiler.Target.Pic, pic_str) orelse + return update.fail("target {q} unknown pic {q}", .{ update.target_query, pic_str }); + const linker_str = component_it.next() orelse + return update.fail("target {q} missing linker", .{update.target_query}); + const linker = std.meta.stringToEnum(Compiler.Target.Linker, linker_str) orelse + return update.fail("target {q} unknown linker {q}", .{ + update.target_query, linker_str, + }); + const backend_str = component_it.next() orelse + return update.fail("target {q} missing backend", .{update.target_query}); + const backend = std.meta.stringToEnum(Compiler.Target.Backend, backend_str) orelse + return update.fail("target {q} unknown backend {q}", .{ + update.target_query, backend_str, + }); + const cache_mode_str = component_it.next() orelse + return update.fail("target {q} missing cache mode", .{update.target_query}); + const cache_mode = std.meta.stringToEnum(Compiler.Target.CacheMode, cache_mode_str) orelse + return update.fail("target {q} unknown cache mode {q}", .{ + update.target_query, cache_mode_str, + }); + const triple = component_it.rest(); + break :target .{ + .triple = triple, + .resolved = std.zig.system.resolveTargetQuery( + runner.io, + std.Build.parseTargetQuery(.{ + .arch_os_abi = triple, + .object_format = switch (backend) { + .sema, .selfhosted, .llvm => null, + .cbe => "c", + }, + }) catch |err| switch (err) { + error.ParseFailed => return update.fail("unable to parse triple {q}", .{ + triple, + }), + }, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to resolve target {q}: {t}", .{ triple, e }), + }, + .cache_mode = cache_mode, + .backend = backend, + .linker = linker, + .pic = pic, + }; + }) catch |err| switch (err) { + error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e, + error.ReadFailed => return error.ContentsReadFailed, + }, + .write => { + const file = update.src_dir.createFile(runner.io, arg.?, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail(arg.?, "unable to create file: {t}", .{e}), + }; + defer file.close(runner.io); + var fw_buffer: [512]u8 = undefined; + var fw = file.writer(runner.io, &fw_buffer); + _ = contents_r.streamRemaining(&fw.interface) catch |err| switch (err) { + error.ReadFailed => return error.ContentsReadFailed, + error.WriteFailed => switch (fw.err.?) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail( + arg.?, + "unable to write file: {t}", + .{e}, + ), + }, + }; + fw.flush() catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail( + arg.?, + "unable to write file: {t}", + .{e}, + ), + }; + file.setTimestamps(runner.io, .{ .modify_timestamp = .{ + .new = update.mtime, + } }) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail( + arg.?, + "unable to update file timestamps: {t}", + .{e}, + ), + }; + }, + .delete => update.src_dir.deleteFile(runner.io, arg.?) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.manifest_sr.fail(arg.?, "unable to delete file: {t}", .{e}), + }, + .update => { + update.src_loc = update.cmd_src_loc; + const comp = &(update.compiler orelse return update.manifest_sr.fail( + cmd_src_loc, + "missing \"#exe\", \"#lib\", or \"#obj\"", + .{}, + )); + update.prog_node.end(); + const arena = runner.arena.allocator(); + update.name = switch (contents_r.readSliceShort(&name_buffer) catch |err| switch (err) { + error.ReadFailed => return error.ContentsReadFailed, + }) { + 0 => switch (update.num) { + 0 => "initial update", + else => |num| try arena.print("update {d}", .{num}), + }, + else => |name_len| try arena.dupe(u8, name_buffer[0..name_len]), + }; + update.prog_node = runner.prog_node.start(update.name, 0); + _ = contents_r.discardRemaining() catch |err| switch (err) { + error.ReadFailed => return error.ContentsReadFailed, + }; + if (comp.state != .idle) + return update.manifest_sr.fail(cmd_src_loc, "missing \"#check\"", .{}); + comp.client.serveBodylessMessage(.update) catch |err| switch (err) { + error.WriteFailed => switch (comp.fw.err.?) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to send message to compiler: {t}", .{e}), + }, + }; + comp.state = .update; + update.mtime = update.mtime.addDuration(.fromSeconds(2)); + update.num += 1; + }, + .check => { + const check = std.meta.stringToEnum(Compiler.Check, arg.?) orelse + return update.manifest_sr.fail(arg.?, "unknown check", .{}); + const expected = + contents_r.allocRemaining(runner.gpa, .unlimited) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.ReadFailed => return error.ContentsReadFailed, + error.StreamTooLong => unreachable, // .unlimited + }; + defer runner.gpa.free(expected); + + const comp = &(update.compiler orelse return update.manifest_sr.fail( + cmd_src_loc, + "missing \"#exe\", \"#lib\", or \"#obj\"", + .{}, + )); + if (comp.state != .update) + return update.manifest_sr.fail(cmd_src_loc, "missing \"#update\"", .{}); + const stderr = comp.mr.reader(1); + while (try comp.receiveMessage()) |message| switch (message.tag) { + else => {}, // Ignore other messages, + .config => { + var body_r: std.Io.Reader = .fixed(message.body); + comp.config = body_r.takeStruct(std.zig.Server.Message.Config, .little) catch + unreachable; + }, + .emit_digest => { + var body_r: std.Io.Reader = .fixed(message.body); + _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch + unreachable; + const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch + unreachable; + try comp.checkSuccess(update, check, expected, digest); + }, + .error_bundle => { + const eb = std.zig.Server.allocErrorBundle( + runner.arena.allocator(), + message.body, + ) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.EndOfStream => return update.fail("error bundle truncated", .{}), + }; + switch (check) { + .errors => { + var error_aw: std.Io.Writer.Allocating = .init(runner.gpa); + defer error_aw.deinit(); + eb.renderToWriter(.{ + .include_reference_trace = false, + .include_source_line = false, + .include_log_text = true, + }, &error_aw.writer) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + }; + const actual = error_aw.written(); + std.mem.replaceScalar(u8, actual, '\\', '/'); + std.testing.expectEqualStrings(expected, actual) catch |err| switch (err) { + error.TestExpectedEqual => return update.fail( + "errors did not match expected", + .{}, + ), + }; + }, + .stdout, .exit, .gdb, .lldb => if (eb.errorMessageCount() > 0) { + eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) { + error.Canceled => {}, + else => {}, + }; + return update.fail("unexpected compile errors", .{}); + }, + } + comp.state = .idle; + break :cmd; + }, + }; + + const stderr_buffered = stderr.buffered(); + if (stderr_buffered.len > 0) { + if (comp.allow_compiler_stderr) { + std.log.info("#update {q} stderr:\n{s}", .{ update.name, stderr_buffered }); + } else return update.fail("unexpected stderr:\n{s}", .{stderr_buffered}); + } + + try comp.exit(); + return update.fail("compiler failed to send terminating error_bundle", .{}); + }, + } + assert(contents_r.discardRemaining() catch |err| switch (err) { + error.ReadFailed => return error.ContentsReadFailed, + } == 0); + switch (cmd) { + .todo, .skip => {}, + else => update.allow_skip = false, + } +} + +fn spawnCompiler( + runner: *Runner, + compiler: *?Compiler, + src_dir: std.Io.Dir, + output_mode: std.lang.OutputMode, + args_r: *std.Io.Reader, + target: Compiler.Target, +) (std.Io.Reader.ShortError || TestError)!void { + const gpa = runner.gpa; + const arena = runner.arena.allocator(); + if (compiler.*) |*comp| { + try comp.exit(); + compiler.* = null; + } + + var argv: std.ArrayList([]const u8) = .empty; + defer argv.deinit(gpa); + try argv.appendSlice(gpa, &.{ + "zig", + switch (output_mode) { + .Obj => "build-obj", + .Exe => "build-exe", + .Lib => "build-lib", + }, + "-target", + target.triple, + "--cache-dir", + ".zig-cache", + }); + switch (target.cache_mode) { + .whole => {}, + .incremental => try argv.append(gpa, "-fincremental"), + } + var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + if (target.backend) |b| switch (b) { + .sema => try argv.append(gpa, "-fno-emit-bin"), + .selfhosted => try argv.append(gpa, "-fno-llvm"), + .llvm => try argv.append(gpa, "-fllvm"), + .cbe => try argv.append(gpa, "-ofmt=c"), + } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. (runner.args.lib_dir orelse + return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{})) + .realPath(runner.io, &path_buffer) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to get lib path: {t}", .{e}), + }] }); + switch (target.linker) { + .lld => try argv.appendSlice(gpa, &.{ "-flld", "-fno-new-linker" }), + .old => try argv.appendSlice(gpa, &.{ "-fno-lld", "-fno-new-linker" }), + .new => try argv.appendSlice(gpa, &.{ "-fno-lld", "-fnew-linker" }), + } + switch (target.pic) { + .nopic => try argv.appendSlice(gpa, &.{ "-fno-PIC", "-fno-PIE" }), + .pic => try argv.appendSlice(gpa, &.{ "-fPIC", "-fno-PIE" }), + .pie => try argv.appendSlice(gpa, &.{ "-fPIC", "-fPIE" }), + } + + var need: usize = 1; + var state: enum { positional, @"--name" } = .positional; + var root_name: ?[]const u8 = null; + while (true) { + const done = if (args_r.fill(need)) false else |err| switch (err) { + error.ReadFailed => |e| return e, + error.EndOfStream => true, + }; + const buffered = args_r.buffered(); + const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len; + const end = std.mem.findAnyPos(u8, buffered, start, "\n ") orelse buffered.len; + if (end - start > 0) { + const arg = try arena.dupe(u8, buffered[start..end]); + switch (state) { + .positional => if (std.mem.eql(u8, arg, "--name")) { + state = .@"--name"; + } else if (std.mem.cutPrefix(u8, arg, "-M")) |module| { + var module_name_it = std.mem.splitScalar(u8, module, '='); + const module_name = module_name_it.next().?; + root_name = root_name orelse module_name; + }, + .@"--name" => { + root_name = arg; + state = .positional; + }, + } + try argv.append(gpa, arg); + args_r.toss(end); + need = 1; + } else { + args_r.toss(start); + need = buffered.len - start + 1; + } + if (done) break; + } + try argv.append(gpa, "--listen=-"); + + const out_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ + .root_name = root_name orelse "root", + .cpu_arch = target.resolved.cpu.arch, + .os_tag = target.resolved.os.tag, + .ofmt = target.resolved.ofmt, + .abi = target.resolved.abi, + .output_mode = output_mode, + }); + + var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; + const comp_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "compile {s}", .{ + out_name, + }) catch &name_buffer, 0); + errdefer comp_prog_node.end(); + + const child = std.process.spawn(runner.io, .{ + .exe = .{ .file = runner.args.zig_exe orelse + return runner.fail("missing \"--zig=path/to/zig\" arg", .{}) }, + .argv = argv.items, + .cwd = .{ .dir = src_dir }, + .progress_node = comp_prog_node, + .stdin = .pipe, + .stdout = .pipe, + .stderr = .pipe, + }) catch |err| switch (err) { + error.Canceled, error.OutOfMemory => |e| return e, + error.OperationUnsupported => return error.SkipTest, + else => |e| return runner.fail("unable to spawn compiler: {t}", .{e}), + }; + compiler.* = .{ + .runner = runner, + .prog_node = comp_prog_node, + .allow_compiler_stderr = true, + .output_mode = output_mode, + .state = .idle, + .child = child, + .fw = undefined, + .mr_buffer = undefined, + .mr = undefined, + .client = undefined, + .target = target, + .out_name = out_name, + .config = null, + }; + const comp = &compiler.*.?; + comp.fw = child.stdin.?.writerStreaming(runner.io, &.{}); + comp.mr.init(gpa, runner.io, comp.mr_buffer.toStreams(), &.{ + child.stdout.?, child.stderr.?, + }); + comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface }; +} +const Compiler = struct { + runner: *Runner, + prog_node: std.Progress.Node, + allow_compiler_stderr: bool, + output_mode: std.lang.OutputMode, + state: enum { idle, update }, + child: std.process.Child, + fw: std.Io.File.Writer, + mr_buffer: std.Io.File.MultiReader.Buffer(2), + mr: std.Io.File.MultiReader, + client: std.zig.Client, + target: Target, + out_name: []const u8, + config: ?std.zig.Server.Message.Config, + + const Target = struct { + triple: []const u8, + resolved: std.Target, + cache_mode: CacheMode, + backend: ?Backend, + linker: Linker, + pic: Pic, + + const CacheMode = enum { whole, incremental }; + + const Backend = enum { + /// Run semantic analysis only. Runtime output will not be tested, but we still verify + /// that compilation succeeds. Corresponds to `-fno-emit-bin`. + sema, + /// Use the self-hosted code generation backend for this target. + /// Corresponds to `-fno-llvm -fno-lld`. + selfhosted, + /// Use the LLVM backend. + /// Corresponds to `-fllvm -flld`. + llvm, + /// Use the C backend. The output is compiled with `zig cc`. + /// Corresponds to `-ofmt=c`. + cbe, + }; + + const Linker = enum { old, new, lld }; + + const Pic = enum { nopic, pic, pie }; + }; + + fn receiveMessage(comp: *Compiler) FailError!?struct { + tag: std.zig.Server.Message.Tag, + body: []const u8, + } { + const header = comp.client.receiveMessageWithMultiReader( + &comp.mr, + .none, + ) catch |err| switch (err) { + error.Canceled, error.OutOfMemory => |e| return e, + error.Timeout => unreachable, + error.EndOfStream => return null, + else => |e| return comp.runner.fail("unable to receive message: {t}", .{e}), + }; + const body = comp.client.in.take(header.bytes_len) catch unreachable; + const stderr = comp.mr.reader(1); + if (stderr.bufferedLen() > 0) { + if (comp.allow_compiler_stderr) { + std.log.info("{t} stderr:\n{s}", .{ header.tag, stderr.buffered() }); + } else { + return comp.runner.fail("{t} unexpected stderr:\n{s}", .{ + header.tag, stderr.buffered(), + }); + } + stderr.tossBuffered(); + } + return .{ .tag = header.tag, .body = body }; + } + + const Check = enum { errors, stdout, exit, gdb, lldb }; + fn checkSuccess( + comp: *Compiler, + update: *const Update, + check: Check, + expected: []const u8, + digest: *const std.Build.Cache.BinDigest, + ) TestError!void { + const runner = comp.runner; + const gpa = runner.gpa; + const arena = runner.arena.allocator(); + const io = runner.io; + const target = &comp.target.resolved; + const config = &comp.config.?; + var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + + // After a failure while running a script, the debugger starts accepting commands from stdin, and + // because it is empty, the debugger exits normally with status 0. Choose a non-zero status to + // return from the debugger script instead to detect it running to completion and indicate success. + const debug_success = 99; + + const tmp_dir_path = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ "tmp"; + update.src_dir.createDirPath(io, tmp_dir_path) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to create dir path {q}: {t}", .{ + tmp_dir_path, e, + }), + }; + + var argv: std.ArrayList([]const u8) = .initBuffer(try arena.alloc([]const u8, 5 + 4)); + var environ_map: std.process.Environ.Map = .init(gpa); + defer environ_map.deinit(); + try environ_map.putAll(&runner.environ_map); + var script_buffer: [512]u8 = undefined; + const script_path, const script_file, var script_fw = script: switch (check) { + .errors => return, + .stdout, .exit => .{ undefined, null, undefined }, + .gdb => { + const script_path = tmp_dir_path ++ std.Io.Dir.path.sep_str ++ "gdb"; + argv.appendSliceAssumeCapacity(&.{ "gdb", "--batch", "--command", script_path, "--args" }); + const script_file = + update.src_dir.createFile(io, script_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to create debugger script {q}: {t}", .{ + script_path, e, + }), + }; + errdefer script_file.close(io); + var script_fw = script_file.writer(io, &script_buffer); + script_fw.interface.writeAll( + \\set remotetimeout 0 + \\ + \\ + ) catch |err| switch (err) { + error.WriteFailed => switch (script_fw.err.?) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to write debugger script {q}: {t}", .{ + script_path, e, + }), + }, + }; + break :script .{ script_path, script_file, script_fw }; + }, + .lldb => { + const script_path = tmp_dir_path ++ std.Io.Dir.path.sep_str ++ "lldb"; + argv.appendSliceAssumeCapacity(&.{ "lldb", "--batch", "--source", script_path, "--" }); + try environ_map.put("LANG", "C.UTF-8"); // affects output formatting + const script_file = + update.src_dir.createFile(io, script_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to create debugger script {q}: {t}", .{ + script_path, e, + }), + }; + errdefer script_file.close(io); + var script_fw = script_file.writer(io, &script_buffer); + script_fw.interface.writeAll( + \\settings set frame-format 'frame #${frame.index}:{ ${module.file.basename}{\`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${line.file.basename}:${line.number}{:${line.column}}}{${function.is-optimized} [opt]}{${frame.is-artificial} [artificial]}\n' + \\settings set plugin.process.gdb-remote.packet-timeout 0 + \\settings set stop-line-count-after 0 + \\settings set stop-line-count-before 0 + \\ + ) catch |err| switch (err) { + error.WriteFailed => switch (script_fw.err.?) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to write debugger script {q}: {t}", .{ + script_path, e, + }), + }, + }; + break :script .{ script_path, script_file, script_fw }; + }, + }; + defer if (script_file) |file| file.close(io); + + const out_dir_path = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ + "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*); + const out_path = try std.Io.Dir.path.join(arena, &.{ out_dir_path, comp.out_name }); + const bin_path = switch (comp.target.backend.?) { + .sema => return, + .selfhosted, .llvm => out_path, + .cbe => try comp.compileC(update.src_dir, out_path), + }; + + const need_cross_libc = + target.os.tag == .linux and config.flags.link_libc and config.flags.link_mode == .dynamic; + const use_executor = use_executor: switch (std.zig.system.getExternalExecutor(io, target, .{ + .host_cpu_arch = runner.host.cpu.arch, + .host_os_tag = runner.host.os.tag, + .qemu_fixes_dl = need_cross_libc and runner.args.libc_runtimes_dir != null, + .link_mode = config.flags.link_mode, + .link_libc = config.flags.link_libc, + })) { + .bad_dl, .bad_os_or_cpu => { + // This binary cannot be executed on this host. + if (!runner.args.quiet) std.log.warn("skipping execution because host {q} cannot " ++ + "execute binaries for foreign target {q}", .{ + try runner.host.zigTriple(arena), comp.target.triple, + }); + return; + }, + .native => false, + .darling => |executor| if (runner.args.enable_darling) { + argv.appendAssumeCapacity(executor); + break :use_executor true; + } else continue :use_executor .bad_os_or_cpu, + .qemu => |executor| if (runner.args.enable_qemu) { + argv.appendAssumeCapacity(executor); + if (need_cross_libc) { + const libc_runtimes_dir = runner.args.libc_runtimes_dir orelse + continue :use_executor .bad_os_or_cpu; + const libc_runtimes_path = path_buffer[0 .. libc_runtimes_dir.realPath( + io, + &path_buffer, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to get libc runtimes path: {t}", .{e}), + }]; + argv.appendSliceAssumeCapacity(&.{ "-L", try std.Io.Dir.path.join(arena, &.{ + libc_runtimes_path, + try if (target.isGnuLibC()) std.zig.target.glibcRuntimeTriple( + arena, + target.cpu.arch, + target.os.tag, + target.abi, + ) else if (target.isMuslLibC()) std.zig.target.muslRuntimeTriple( + arena, + target.cpu.arch, + target.abi, + ) else unreachable, + }) }); + } + break :use_executor true; + } else continue :use_executor .bad_os_or_cpu, + .rosetta => if (runner.args.enable_rosetta) + true + else + continue :use_executor .bad_os_or_cpu, + .wine => |executor| if (runner.args.enable_wine) { + if (!environ_map.contains("WINEDEBUG")) try environ_map.put("WINEDEBUG", "-all"); + argv.appendAssumeCapacity(executor); + break :use_executor true; + } else continue :use_executor .bad_os_or_cpu, + .wasmtime => |executor| if (runner.args.enable_wasmtime) { + if (!environ_map.contains("WASMTIME_BACKTRACE_DETAILS")) + try environ_map.put("WASMTIME_BACKTRACE_DETAILS", "1"); + argv.appendSliceAssumeCapacity(&.{ executor, "--dir=.", "-Sinherit-env" }); + break :use_executor true; + } else continue :use_executor .bad_os_or_cpu, + }; + argv.appendAssumeCapacity(bin_path); + + if (!runner.args.quiet) std.log.info("{f}", .{std.zig.SubprocessCommand{ + .argv = argv.items, + .cwd = cwd_path: { + break :cwd_path path_buffer[0 .. update.src_dir.realPath( + io, + &path_buffer, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => break :cwd_path null, + }]; + }, + .parent_env = &runner.environ_map, + .child_env = &environ_map, + }}); + + var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; + const run_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "run {s}", .{ + comp.out_name, + }) catch &name_buffer, 0); + defer run_prog_node.end(); + + const bin_file = update.src_dir.openFile(io, bin_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to open {q}: {t}", .{ bin_path, e }), + }; + defer bin_file.close(io); + const result = std.process.run(gpa, io, .{ + .exe = exe: switch (check) { + .errors => unreachable, + .stdout, .exit => if (use_executor) .search else .{ .file = bin_file }, + .gdb, .lldb => { + if (use_executor) { + if (!runner.args.quiet) std.log.warn( + "skipping debugging of {q} via executor for foreign target {q}", + .{ bin_path, comp.target.triple }, + ); + return; + } + + var line_it = std.mem.splitScalar(u8, expected, '\n'); + while (line_it.next()) |line| if (std.mem.cutPrefix(u8, line, switch (check) { + else => unreachable, + .gdb => "(gdb) ", + .lldb => "(lldb) ", + })) |cmd| script_fw.interface.print( + \\{s} + \\ + , .{cmd}) catch |err| switch (err) { + error.WriteFailed => switch (script_fw.err.?) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to write debugger script {q}: {t}", .{ + script_path, e, + }), + }, + }; + script_fw.interface.print( + \\ + \\quit {d} + \\ + , .{debug_success}) catch |err| switch (err) { + error.WriteFailed => switch (script_fw.err.?) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to write debugger script {q}: {t}", .{ + script_path, e, + }), + }, + }; + script_fw.flush() catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return update.fail("unable to write debugger script {q}: {t}", .{ + script_path, e, + }), + }; + break :exe .{ .file = switch (check) { + else => unreachable, + .gdb => runner.args.gdb_exe, + .lldb => runner.args.lldb_exe, + } orelse { + if (!runner.args.quiet) std.log.warn( + "skipping debugging of {q} because of missing \"--{t}=path/to/{[1]t}\" arg", + .{ bin_path, check }, + ); + return; + } }; + }, + }, + .argv = argv.items, + .cwd = .{ .dir = update.src_dir }, + .environ_map = &environ_map, + .progress_node = run_prog_node, + }) catch |err| if (use_executor) { + // Chances are the foreign executor isn't available. Skip this evaluation. + if (!runner.args.quiet) std.log.warn( + "skipping execution of {q} via executor for foreign target {q}: {t}", + .{ bin_path, comp.target.triple, err }, + ); + return; + } else return update.fail("unable to run the generated executable {q}: {t}", .{ + bin_path, err, + }); + defer { + gpa.free(result.stdout); + gpa.free(result.stderr); + } + switch (result.term) { + .exited => |code| switch (check) { + .errors => unreachable, + .stdout => { + if (code != 0) return update.fail( + "generated executable {q} failed with code {d}", + .{ bin_path, code }, + ); + std.testing.expectEqualStrings( + expected, + result.stdout, + ) catch |err| switch (err) { + error.TestExpectedEqual => return update.fail( + "stdout did not match expected", + .{}, + ), + }; + }, + .exit => { + var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 = + undefined; + std.testing.expectEqualStrings( + expected, + std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable, + ) catch |err| switch (err) { + error.TestExpectedEqual => return update.fail( + "exit code did not match expected", + .{}, + ), + }; + }, + .gdb, .lldb => { + if (code != debug_success) return update.fail( + "debugging {q} failed with code {d}", + .{ bin_path, code }, + ); + try update.checkMatches("stdout", expected, result.stdout); + }, + }, + .signal => |sig| return update.fail( + "generated executable {q} terminated with signal {t}", + .{ bin_path, sig }, + ), + .stopped => |sig| return update.fail( + "generated executable {q} stopped with signal {t}", + .{ bin_path, sig }, + ), + .unknown => return update.fail( + "generated executable {q} terminated unexpectedly", + .{bin_path}, + ), + } + if (!use_executor and result.stderr.len > 0) { + std.log.err("#update {q} generated executable {q} had unexpected stderr:\n{s}", .{ + update.name, bin_path, result.stderr, + }); + } + } + + fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) TestError![]const u8 { + const runner = parent_comp.runner; + const arena = runner.arena.allocator(); + var compiler: ?Compiler = null; + var args_r: std.Io.Reader = .fixed(c_path); + parent_comp.runner.spawnCompiler(&compiler, src_dir, parent_comp.output_mode, &args_r, .{ + .triple = parent_comp.target.triple, + .resolved = resolved: { + var resolved = parent_comp.target.resolved; + resolved.ofmt = .default(resolved.os.tag, resolved.cpu.arch); + break :resolved resolved; + }, + .cache_mode = .whole, + .backend = null, + .linker = .lld, + .pic = parent_comp.target.pic, + }) catch |err| switch (err) { + error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e, + error.ReadFailed => unreachable, // .fixed + }; + const comp = &compiler.?; + comp.client.serveBodylessMessage(.update) catch |err| switch (err) { + error.WriteFailed => switch (comp.fw.err.?) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to send message: {t}", .{e}), + }, + }; + comp.state = .update; + var out_path: ?[]const u8 = null; + while (try comp.receiveMessage()) |message| switch (message.tag) { + else => {}, // Ignore other messages, + .config => { + var body_r: std.Io.Reader = .fixed(message.body); + comp.config = + body_r.takeStruct(std.zig.Server.Message.Config, .little) catch unreachable; + }, + .emit_digest => { + var body_r: std.Io.Reader = .fixed(message.body); + _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; + const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch unreachable; + + const out_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ + "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*); + out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name }); + }, + .error_bundle => { + const eb = std.zig.Server.allocErrorBundle( + runner.arena.allocator(), + message.body, + ) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.EndOfStream => return runner.fail("error bundle truncated", .{}), + }; + if (eb.errorMessageCount() > 0) { + try runner.eb_wip.addBundleAsRoots(eb); + return runner.fail("unexpected compile errors", .{}); + } + comp.state = .idle; + break; + }, + }; + try comp.exit(); + return out_path.?; + } + + fn exit(comp: *Compiler) FailError!void { + const runner = comp.runner; + comp.client.serveBodylessMessage(.exit) catch |err| switch (err) { + error.WriteFailed => switch (comp.fw.err.?) { + error.Canceled => |e| return e, + error.BrokenPipe => {}, + else => |e| return comp.runner.fail("unable to send exit: {t}", .{e}), + }, + }; + comp.child.stdin.?.close(runner.io); + comp.child.stdin = null; + while (try comp.receiveMessage()) |_| {} + if (comp.client.in.bufferedLen() > 0) return runner.fail("unable to receive messages", .{}); + const term = comp.child.wait(runner.io) catch |err| + return runner.fail("child process failed: {t}", .{err}); + comp.prog_node.end(); + switch (term) { + .exited => |code| if (code != 0) + return runner.fail("compiler failed with code {d}", .{code}), + .signal => |sig| return runner.fail("compiler terminated with signal {t}", .{sig}), + .stopped => |sig| return runner.fail("compiler stopped unexpectedly with signal {t}", .{ + sig, + }), + .unknown => return runner.fail("compiler terminated unexpectedly", .{}), + } + } +}; + +const ClientArgs = struct { + test_file: std.Io.File, + test_kind: std.Io.File.Kind, + zig_exe: std.Io.File, + lib_dir: std.Io.Dir, + src_dir: std.Io.Dir, + targets: std.ArrayList([]const u8), + + libc_runtimes_dir: ?std.Io.Dir, + enable_darling: bool, + enable_qemu: bool, + enable_rosetta: bool, + enable_wine: bool, + enable_wasmtime: bool, + + gdb_exe: ?std.Io.File, + lldb_exe: ?std.Io.File, + + quiet: bool, + keep_src: bool, +}; +pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelable)!u8 { + const fatal = struct { + fn fatal(comptime fmt: []const u8, args: anytype) noreturn { + std.log.err(fmt, args); + std.process.exit(1); + } + }.fatal; + const arena = init.arena.allocator(); + + var test_path_arg: ?[]const u8 = null; + var zig_path_arg: ?[]const u8 = null; + var lib_path_arg: ?[]const u8 = null; + var libc_runtimes_path_arg: ?[]const u8 = null; + var gdb_path_arg: ?[]const u8 = null; + var lldb_path_arg: ?[]const u8 = null; + var args: ClientArgs = .{ + .test_file = undefined, + .test_kind = undefined, + .zig_exe = undefined, + .lib_dir = undefined, + .src_dir = undefined, + .targets = .empty, + + .libc_runtimes_dir = null, + .enable_darling = false, + .enable_qemu = false, + .enable_rosetta = false, + .enable_wine = false, + .enable_wasmtime = false, + + .gdb_exe = null, + .lldb_exe = null, + + .quiet = false, + .keep_src = false, + }; + defer args.targets.deinit(init.gpa); + + var arg_it = try init.minimal.args.iterateAllocator(arena); + const self = arg_it.next().?; + while (arg_it.next()) |arg| { + if (std.mem.eql(u8, arg, "--listen=-")) { + var stdin_buffer: [512]u8 = undefined; + var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer); + var stdout_buffer: [512]u8 = undefined; + var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer); + var runner: Runner = .{ + .gpa = init.gpa, + .arena = init.arena, + .environ_map = init.minimal.environ.createMap(init.gpa) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => |e| fatal("unable to get environment: {t}", .{e}), + }, + .io = init.io, + .prog_node = std.Progress.start(init.io, .{}), + .args = .{ + .@"test" = null, + .manifest_file = null, + .zig_exe = null, + .lib_dir = null, + .src_dir = null, + .targets = .empty, + .target_bytes = .empty, + + .libc_runtimes_dir = null, + .enable_darling = false, + .enable_qemu = false, + .enable_rosetta = false, + .enable_wasmtime = false, + .enable_wine = false, + + .gdb_exe = null, + .lldb_exe = null, + + .quiet = false, + }, + .host = std.zig.system.resolveTargetQuery(init.io, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to resolve host target: {t}", .{e}), + }, + .server = .{ + .in = &stdin.interface, + .out = &stdout.interface, + }, + .eb_wip = try .init(init.gpa), + }; + defer runner.deinit(); + switch (runner.runServer()) { + error.Canceled, error.OutOfMemory => |e| return e, + error.ServerReadFailed => switch (stdin.err.?) { + error.Canceled => |e| return e, + else => |e| fatal("unable to receive message: {t}", .{e}), + }, + error.ServerWriteFailed => switch (stdout.err.?) { + error.Canceled => |e| return e, + else => |e| fatal("unable to send message: {t}", .{e}), + }, + error.ServerEndOfStream => fatal("no more messages", .{}), + error.AlreadyReported => { + var eb = try runner.eb_wip.toOwnedBundle(""); + defer eb.deinit(init.gpa); + eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) { + error.Canceled => |e| return e, + else => {}, + }; + std.process.exit(1); + }, + } + } else if (std.mem.eql(u8, arg, "--zig")) { + zig_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); + } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| { + zig_path_arg = zig_path; + } else if (std.mem.eql(u8, arg, "--lib")) { + lib_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); + } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| { + lib_path_arg = lib_path; + } else if (std.mem.eql(u8, arg, "--target")) { + try args.targets.append( + init.gpa, + try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after {q}", .{arg})), + ); + } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| { + try args.targets.append(init.gpa, try arena.dupe(u8, target)); + } else if (std.mem.eql(u8, arg, "--libc-runtimes")) { + libc_runtimes_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); + } else if (std.mem.cutPrefix(u8, arg, "--libc-runtimes=")) |libc_runtimes_path| { + libc_runtimes_path_arg = libc_runtimes_path; + } else if (std.mem.eql(u8, arg, "-fdarling")) { + args.enable_darling = true; + } else if (std.mem.eql(u8, arg, "-fqemu")) { + args.enable_qemu = true; + } else if (std.mem.eql(u8, arg, "-frosetta")) { + args.enable_rosetta = true; + } else if (std.mem.eql(u8, arg, "-fwine")) { + args.enable_wine = true; + } else if (std.mem.eql(u8, arg, "-fwasmtime")) { + args.enable_wasmtime = true; + } else if (std.mem.eql(u8, arg, "--gdb")) { + gdb_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); + } else if (std.mem.cutPrefix(u8, arg, "--gdb=")) |gdb_path| { + gdb_path_arg = gdb_path; + } else if (std.mem.eql(u8, arg, "--lldb")) { + lldb_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); + } else if (std.mem.cutPrefix(u8, arg, "--lldb=")) |lldb_path| { + lldb_path_arg = lldb_path; + } else if (std.mem.eql(u8, arg, "--quiet")) { + args.quiet = true; + } else if (std.mem.eql(u8, arg, "--keep-src")) { + args.keep_src = true; + } else { + if (test_path_arg) |_| fatal("unexpected arg {q}", .{arg}); + test_path_arg = arg; + } + } + + const test_path = test_path_arg orelse fatal("missing \"path/to/test\" arg", .{}); + const zig_path = zig_path_arg orelse fatal("missing \"--zig=path/to/zig\" arg", .{}); + const lib_path = lib_path_arg orelse fatal("missing \"--lib=path/to/lib\" arg", .{}); + + const cwd: std.Io.Dir = .cwd(); + args.test_file = cwd.openFile(init.io, test_path, .{ + .allow_directory = true, + }) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to open {q}: {t}", .{ test_path, e }), + }; + defer args.test_file.close(init.io); + args.test_kind = (args.test_file.stat(init.io) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to stat {q}: {t}", .{ test_path, e }), + }).kind; + args.zig_exe = cwd.openFile(init.io, zig_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to open {q}: {t}", .{ zig_path, e }), + }; + defer args.zig_exe.close(init.io); + args.lib_dir = cwd.openDir(init.io, lib_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }), + }; + defer args.lib_dir.close(init.io); + if (libc_runtimes_path_arg) |libc_runtimes_path| args.libc_runtimes_dir = cwd.openDir( + init.io, + libc_runtimes_path, + .{}, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }), + }; + defer if (args.libc_runtimes_dir) |libc_runtimes_dir| libc_runtimes_dir.close(init.io); + if (gdb_path_arg) |gdb_path| args.gdb_exe = cwd.openFile(init.io, gdb_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to open {q}: {t}", .{ gdb_path, e }), + }; + defer if (args.gdb_exe) |gdb_exe| gdb_exe.close(init.io); + if (lldb_path_arg) |lldb_path| args.lldb_exe = cwd.openFile(init.io, lldb_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to open {q}: {t}", .{ lldb_path, e }), + }; + defer if (args.lldb_exe) |lldb_exe| lldb_exe.close(init.io); + + const src_dir_path = "src_" ++ std.fmt.hex(rand_int: { + var rand_int: u64 = undefined; + init.io.random(@ptrCast(&rand_int)); + break :rand_int rand_int; + }); + args.src_dir = cwd.createDirPathOpen(init.io, src_dir_path, .{}) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to create {q}: {t}", .{ src_dir_path, e }), + }; + defer { + args.src_dir.close(init.io); + if (!args.keep_src) cwd.deleteTree(init.io, src_dir_path) catch |err| { + std.log.warn("unable to delete tree {q}: {t}", .{ src_dir_path, err }); + }; + } + + var inherit_dirs: std.ArrayList(std.Io.Dir) = .initBuffer(try arena.alloc(std.Io.Dir, 3)); + var inherit_files: std.ArrayList(std.Io.File) = .initBuffer(try arena.alloc(std.Io.File, 4)); + inherit_files.appendSliceAssumeCapacity(&.{ args.test_file, args.zig_exe }); + inherit_dirs.appendSliceAssumeCapacity(&.{ args.lib_dir, args.src_dir }); + if (args.libc_runtimes_dir) |libc_runtimes_dir| inherit_dirs.appendAssumeCapacity(libc_runtimes_dir); + if (args.gdb_exe) |gdb_exe| inherit_files.appendAssumeCapacity(gdb_exe); + if (args.lldb_exe) |lldb_exe| inherit_files.appendAssumeCapacity(lldb_exe); + var child = std.process.spawn(init.io, .{ + .argv = &.{ self, "--listen=-" }, + .stdin = .pipe, + .stdout = .pipe, + .inherit_dirs = inherit_dirs.items, + .inherit_files = inherit_files.items, + }) catch |err| switch (err) { + error.Canceled, error.OutOfMemory => |e| return e, + else => |e| fatal("unable to spawn runner: {t}", .{e}), + }; + + var child_reader_buffer: [512]u8 = undefined; + var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer); + var child_writer_buffer: [512]u8 = undefined; + var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer); + var client: std.zig.Client = .{ + .in = &child_reader.interface, + .out = &child_writer.interface, + }; + const success = runClient(init.gpa, arena, init.io, &client, &args) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.ReadFailed => switch (child_reader.err.?) { + error.Canceled => |e| return e, + else => |e| fatal("unable to receive message: {t}", .{e}), + }, + error.WriteFailed => switch (child_writer.err.?) { + error.Canceled => |e| return e, + else => |e| fatal("unable to send message: {t}", .{e}), + }, + error.EndOfStream => fatal("no more messages", .{}), + }; + _ = child.wait(init.io) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| fatal("unable to wait for runner: {t}", .{e}), + }; + return if (success) 0 else 1; +} +fn runClient( + gpa: std.mem.Allocator, + arena: std.mem.Allocator, + io: std.Io, + client: *std.zig.Client, + args: *const ClientArgs, +) (std.mem.Allocator.Error || std.Io.Reader.Error || std.Io.Writer.Error)!bool { + var stdout_buffer: [512]u8 = undefined; + var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer); + const stdout = &stdout_fw.interface; + var stderr_buffer: [512]u8 = undefined; + var stderr_fw: std.Io.File.Writer = .initStreaming(.stderr(), io, &stderr_buffer); + const stderr = &stderr_fw.interface; + + var argv: std.ArrayList(u8) = .empty; + defer argv.deinit(gpa); + const Arg = std.zig.Client.Message.Arg; + + try argv.append(gpa, @backingInt(@as(Arg, switch (args.test_kind) { + else => unreachable, + .file => .input_file, + .directory => .input_dir, + }))); + try argv.appendSlice(gpa, @ptrCast(&args.test_file.handle)); + + try argv.append(gpa, @backingInt(Arg.prefix)); + try argv.appendSlice(gpa, "--zig="); + try argv.append(gpa, 0); + try argv.append(gpa, @backingInt(Arg.input_file)); + try argv.appendSlice(gpa, @ptrCast(&args.zig_exe.handle)); + + try argv.append(gpa, @backingInt(Arg.prefix)); + try argv.appendSlice(gpa, "--lib="); + try argv.append(gpa, 0); + try argv.append(gpa, @backingInt(Arg.input_dir)); + try argv.appendSlice(gpa, @ptrCast(&args.lib_dir.handle)); + + try argv.append(gpa, @backingInt(Arg.prefix)); + try argv.appendSlice(gpa, "--src="); + try argv.append(gpa, 0); + try argv.append(gpa, @backingInt(Arg.output_dir)); + try argv.appendSlice(gpa, @ptrCast(&args.src_dir.handle)); + + for (args.targets.items) |target| { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "--target"); + try argv.append(gpa, 0); + + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, target); + try argv.append(gpa, 0); + } + + if (args.libc_runtimes_dir) |libc_runtimes_dir| { + try argv.append(gpa, @backingInt(Arg.prefix)); + try argv.appendSlice(gpa, "--libc-runtimes="); + try argv.append(gpa, 0); + try argv.append(gpa, @backingInt(Arg.input_dir)); + try argv.appendSlice(gpa, @ptrCast(&libc_runtimes_dir.handle)); + } + if (args.enable_darling) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-fdarling"); + try argv.append(gpa, 0); + } + if (args.enable_qemu) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-fqemu"); + try argv.append(gpa, 0); + } + if (args.enable_rosetta) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-frosetta"); + try argv.append(gpa, 0); + } + if (args.enable_wine) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-fwine"); + try argv.append(gpa, 0); + } + if (args.enable_wasmtime) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-fwasmtime"); + try argv.append(gpa, 0); + } + + if (args.gdb_exe) |gdb_exe| { + try argv.append(gpa, @backingInt(Arg.prefix)); + try argv.appendSlice(gpa, "--gdb="); + try argv.append(gpa, 0); + try argv.append(gpa, @backingInt(Arg.input_file)); + try argv.appendSlice(gpa, @ptrCast(&gdb_exe.handle)); + } + if (args.lldb_exe) |lldb_exe| { + try argv.append(gpa, @backingInt(Arg.prefix)); + try argv.appendSlice(gpa, "--lldb="); + try argv.append(gpa, 0); + try argv.append(gpa, @backingInt(Arg.input_file)); + try argv.appendSlice(gpa, @ptrCast(&lldb_exe.handle)); + } + + if (args.quiet) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "--quiet"); + try argv.append(gpa, 0); + } + + try client.serveMessageHeader(.{ + .tag = .args, + .bytes_len = @intCast(argv.items.len), + }); + try client.out.writeAll(argv.items); + + var success = true; + var metadata: struct { + index: u32, + len: u32, + names: []u32, + expected_panic_msgs: []u32, + string_bytes: []u8, + + fn name(m: *@This()) []const u8 { + return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0); + } + + fn expectedPanicMsg(m: *@This()) ?[:0]const u8 { + return switch (m.expected_panic_msgs[m.index - 1]) { + 0 => null, + else => |offset| m.string_bytes[offset..std.mem.findScalarPos( + u8, + m.string_bytes, + offset, + 0, + ).? :0], + }; + } + + fn next(m: *@This()) ?u32 { + if (m.len - m.index == 0) return null; + defer m.index += 1; + return m.index; + } + } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} }; + try client.serveBodylessMessage(.query_test_metadata); + while (true) { + const hdr = try client.receiveMessage(); + switch (hdr.tag) { + else => try client.in.discardAll(hdr.bytes_len), + .zig_version => { + const actual_version = try client.in.take(hdr.bytes_len); + if (!std.mem.eql(u8, builtin.zig_version_string, actual_version)) { + try stderr.print( + "error: zig version mismatch compiler test runner vs compiler: {q} vs {q}\n", + .{ builtin.zig_version_string, actual_version }, + ); + try stderr.flush(); + std.process.exit(1); + } + }, + .error_bundle => { + const body = try arena.alloc(u8, hdr.bytes_len); + try client.in.readSliceAll(body); + const eb = std.zig.Server.allocErrorBundle(arena, body) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + error.EndOfStream => { + try stderr.writeAll("error bundle truncated\n"); + try stderr.flush(); + std.process.exit(1); + }, + }; + success = false; + eb.renderToStderr(io, .{}, .auto) catch {}; + }, + .test_metadata => { + const tm_hdr = + try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little); + metadata = .{ + .index = 0, + .len = tm_hdr.tests_len, + .names = try arena.alloc(u32, tm_hdr.tests_len), + .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len), + .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len), + }; + try client.in.readSliceEndian(u32, metadata.names, .little); + try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little); + try client.in.readSliceAll(metadata.string_bytes); + try client.serveRunTest(metadata.next() orelse break); + }, + .test_results => { + const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little); + switch (tr.flags.status) { + .pass, .skip => {}, + .fail => success = false, + } + try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() }); + try stdout.flush(); + try client.serveRunTest(metadata.next() orelse break); + }, + } + } + try client.serveBodylessMessage(.exit); + return success; +} diff --git a/test/debugger/basic/manifest b/test/debugger/basic/manifest index bddaec73e0f6d7e5ab469b041e0b5f229937fefb..79beee569dc10bb57f7fd99675f8b33c29055a48 100644 --- a/test/debugger/basic/manifest +++ b/test/debugger/basic/manifest @@ -3,6 +3,6 @@ -Mroot=basic.zig #} -#write basic.zig basic.zig +#write basic.zig @basic.zig #update -#check lldb lldb +#check lldb @lldb diff --git a/test/debugger/enums b/test/debugger/enums index d6cbcf24334ee9ea2122cb88143139c22f59d701..dfd6375be0cdad75c30e4c0f41ba1ad9ce87adcb 100644 --- a/test/debugger/enums +++ b/test/debugger/enums @@ -1,6 +1,4 @@ -#todo { -passes, but prints errors -#} +#todo "passes, but prints errors" #skip x86_64-linux-none-whole-selfhosted-new-nopic #skip x86_64-linux-none-whole-selfhosted-new-pic #skip x86_64-linux-none-incremental-selfhosted-new-nopic diff --git a/test/debugger/errors b/test/debugger/errors index 156118c3addabd6b5ce13fe8bbdcb3a3dd580c92..8a8143330ac40d161c7c2f229a6deeb263c6bc66 100644 --- a/test/debugger/errors +++ b/test/debugger/errors @@ -1,6 +1,4 @@ -#todo { -passes, but prints errors -#} +#todo "passes, but prints errors" #skip x86_64-linux-none-whole-selfhosted-new-nopic #skip x86_64-linux-none-whole-selfhosted-new-pic #skip x86_64-linux-none-incremental-selfhosted-new-nopic diff --git a/test/debugger/inline_call/manifest b/test/debugger/inline_call/manifest index 77b906f21de307fc4e2219ec965df5f08d3b5a19..f23cf902c8e1da1a5b670744003ae295b4283a01 100644 --- a/test/debugger/inline_call/manifest +++ b/test/debugger/inline_call/manifest @@ -1,6 +1,4 @@ -#todo { -implement inline line info in lldb -#} +#todo "implement inline line info in lldb" #skip x86_64-linux-none-incremental-selfhosted-new-nopic #skip x86_64-linux-none-incremental-selfhosted-new-pic @@ -10,9 +8,9 @@ implement inline line info in lldb -Mmodule=mod0.zig #} -#write root0.zig root0.zig -#write root1.zig root1.zig -#write mod0.zig mod0.zig -#write mod1.zig mod1.zig +#write root0.zig @root0.zig +#write root1.zig @root1.zig +#write mod0.zig @mod0.zig +#write mod1.zig @mod1.zig #update -#check lldb lldb +#check lldb @lldb diff --git a/test/debugger/matrix.zon b/test/debugger/matrix.zon new file mode 100644 index 0000000000000000000000000000000000000000..a3b36d803468d510b7a6af7c64c90efb3e37060b --- /dev/null +++ b/test/debugger/matrix.zon @@ -0,0 +1,68 @@ +.{ + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, + }, + .cache_mode = .whole, + .backend = .selfhosted, + .linker = .old, + .pic = .nopic, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, + }, + .cache_mode = .whole, + .backend = .selfhosted, + .linker = .old, + .pic = .pic, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, + }, + .cache_mode = .whole, + .backend = .selfhosted, + .linker = .new, + .pic = .nopic, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, + }, + .cache_mode = .whole, + .backend = .selfhosted, + .linker = .new, + .pic = .pic, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, + }, + .cache_mode = .incremental, + .backend = .selfhosted, + .linker = .new, + .pic = .nopic, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, + }, + .cache_mode = .incremental, + .backend = .selfhosted, + .linker = .new, + .pic = .pic, + }, +} diff --git a/test/debugger/multi_array_list/manifest b/test/debugger/multi_array_list/manifest index 2503522bee1531df3cd11ce44cfec94b2510def5..233cff5ffd14881cd2c1104ced977e0e8c05beb8 100644 --- a/test/debugger/multi_array_list/manifest +++ b/test/debugger/multi_array_list/manifest @@ -3,6 +3,6 @@ -Mroot=main.zig #} -#write main.zig main.zig +#write main.zig @main.zig #update -#check lldb lldb +#check lldb @lldb diff --git a/test/debugger/step_single_stmt_loops/manifest b/test/debugger/step_single_stmt_loops/manifest index d7284fe12466167a022443945763760b7655e352..abbda7d27e5fd6b8d34ac9da6922d5476a3bc4a7 100644 --- a/test/debugger/step_single_stmt_loops/manifest +++ b/test/debugger/step_single_stmt_loops/manifest @@ -3,6 +3,6 @@ -Mroot=step_single_stmt_loops.zig #} -#write step_single_stmt_loops.zig step_single_stmt_loops.zig +#write step_single_stmt_loops.zig @step_single_stmt_loops.zig #update -#check lldb lldb +#check lldb @lldb diff --git a/test/debugger/unions b/test/debugger/unions index 435b8ec7cf02c239392c7867d0489d8b7eb26e66..786ba275cbf92d418ac4db98301ed24c3214217b 100644 --- a/test/debugger/unions +++ b/test/debugger/unions @@ -1,6 +1,4 @@ -#todo { -passes, but prints errors -#} +#todo "passes, but prints errors" #skip x86_64-linux-none-whole-selfhosted-new-nopic #skip x86_64-linux-none-whole-selfhosted-new-pic #skip x86_64-linux-none-incremental-selfhosted-new-nopic diff --git a/test/incremental/add_decl b/test/incremental/add_decl index 5832015217c571a79a2bc5ca0ab45abf91d7a156..27a5e921dfccf9cd098f9aefee14ae7f6850ebfa 100644 --- a/test/incremental/add_decl +++ b/test/incremental/add_decl @@ -24,8 +24,7 @@ const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -add new declaration#} +#update "add new declaration" #check stdout { good morning #} @@ -39,8 +38,7 @@ const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -reference new declaration#} +#update "reference new declaration" #check stdout { good evening #} @@ -54,8 +52,7 @@ const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -reference missing declaration#} +#update "reference missing declaration" #check errors { main.zig:3:52: error: use of undeclared identifier 'qux' #} @@ -70,8 +67,7 @@ const bar = "good evening\n"; const qux = "good night\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -add missing declaration#} +#update "add missing declaration" #check stdout { good night #} @@ -84,8 +80,7 @@ pub fn main() !void { const qux = "good night\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -remove unused declarations#} +#update "remove unused declarations" #check stdout { good night #} diff --git a/test/incremental/add_decl_namespaced b/test/incremental/add_decl_namespaced index 30bf6a81e9abb3449ad1575a20022b7d6f0d279f..30314032b97331b5f6f3f7c362fb90a128f1a8ca 100644 --- a/test/incremental/add_decl_namespaced +++ b/test/incremental/add_decl_namespaced @@ -24,8 +24,7 @@ const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -add new declaration#} +#update "add new declaration" #check stdout { good morning #} @@ -39,8 +38,7 @@ const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -reference new declaration#} +#update "reference new declaration" #check stdout { good evening #} @@ -54,8 +52,7 @@ const foo = "good morning\n"; const bar = "good evening\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -reference missing declaration#} +#update "reference missing declaration" #check errors { main.zig:3:59: error: root source file struct 'main' has no member named 'qux' main.zig:1:1: note: struct declared here @@ -71,8 +68,7 @@ const bar = "good evening\n"; const qux = "good night\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -add missing declaration#} +#update "add missing declaration" #check stdout { good night #} @@ -85,8 +81,7 @@ pub fn main() !void { const qux = "good night\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -remove unused declarations#} +#update "remove unused declarations" #check stdout { good night #} diff --git a/test/incremental/add_decls_around_bad_field_expr b/test/incremental/add_decls_around_bad_field_expr index b084be50e0df8e4ee192f39c658595baff996a4d..539ba9bf3dd1d4cd7d276184cc3ad499af1bfca3 100644 --- a/test/incremental/add_decls_around_bad_field_expr +++ b/test/incremental/add_decls_around_bad_field_expr @@ -32,8 +32,7 @@ pub fn main() void { _ = @as(@This(), undefined); } #} -#update { -add more decls around error#} +#update "add more decls around error" #check errors { main.zig:4:15: error: root source file struct 'main' has no member named 'does_not_exist' main.zig:1:1: note: struct declared here diff --git a/test/incremental/add_field_and_nested_struct_uses_changed_decl b/test/incremental/add_field_and_nested_struct_uses_changed_decl index 731d34837bc82b9b3ffac774943f53ffad77d468..acabf972c4e3ba0470e2f2be83f262a69a70041b 100644 --- a/test/incremental/add_field_and_nested_struct_uses_changed_decl +++ b/test/incremental/add_field_and_nested_struct_uses_changed_decl @@ -14,8 +14,7 @@ const S = struct { }; #} #update -#check stdout { -#} +#check stdout #write main.zig { pub fn main() void { @@ -29,7 +28,5 @@ const S = struct { bar: u32, }; #} -#update { -add field to outer struct, change decl used by inner struct#} -#check stdout { -#} +#update "add field to outer struct, change decl used by inner struct" +#check stdout diff --git a/test/incremental/add_remove_struct_fields/manifest b/test/incremental/add_remove_struct_fields/manifest index 2099e73cef2d89db7446cfe1c9f3a82057332248..b0b067f9c212b7de14af1b54031ed03a62eecb05 100644 --- a/test/incremental/add_remove_struct_fields/manifest +++ b/test/incremental/add_remove_struct_fields/manifest @@ -2,30 +2,27 @@ -Mroot=main.zig #} -#write main.zig 0/main.zig +#write main.zig @0/main.zig #update #check stdout { 1 100 #} -#write main.zig 1/main.zig -#update { -add a field#} +#write main.zig @1/main.zig +#update "add a field" #check stdout { 2 100 #} -#write main.zig 2/main.zig -#update { -remove all fields#} +#write main.zig @2/main.zig +#update "remove all fields" #check errors { main.zig:15:24: error: no field named 'x' in struct 'main.S' main.zig:1:11: note: struct declared here #} -#write main.zig 3/main.zig -#update { -remove reference to non-existent field#} +#write main.zig @3/main.zig +#update "remove reference to non-existent field" #check stdout { 0 #} diff --git a/test/incremental/add_remove_toplevel_fields/manifest b/test/incremental/add_remove_toplevel_fields/manifest index 569ee7527b51e75d6a214d260b4206b1c5dd2974..c991cd84b336d667312b14ed9c39783a1a97d10b 100644 --- a/test/incremental/add_remove_toplevel_fields/manifest +++ b/test/incremental/add_remove_toplevel_fields/manifest @@ -2,30 +2,27 @@ -Mroot=main.zig #} -#write main.zig 0/main.zig +#write main.zig @0/main.zig #update #check stdout { 1 100 #} -#write main.zig 1/main.zig -#update { -add a field#} +#write main.zig @1/main.zig +#update "add a field" #check stdout { 2 100 #} -#write main.zig 2/main.zig -#update { -remove all fields#} +#write main.zig @2/main.zig +#update "remove all fields" #check errors { main.zig:15:24: error: no field named 'x' in struct 'main' main.zig:1:1: note: struct declared here #} -#write main.zig 3/main.zig -#update { -remove reference to non-existent field#} +#write main.zig @3/main.zig +#update "remove reference to non-existent field" #check stdout { 0 #} diff --git a/test/incremental/analysis_error_and_syntax_error b/test/incremental/analysis_error_and_syntax_error index 5a2246ee8a8cbc2da6512e70830d8927a676d75b..5f2d849420ca99419966333cbf999b33653cfacc 100644 --- a/test/incremental/analysis_error_and_syntax_error +++ b/test/incremental/analysis_error_and_syntax_error @@ -16,8 +16,7 @@ main.zig:2:5: error: uh oh pub fn main() !void { @compileError("uh oh"); #} -#update { -add parse error#} +#update "add parse error" #check errors { main.zig:3:1: error: expected statement, found 'EOF' #} @@ -27,8 +26,7 @@ pub fn main() !void { @compileError("uh oh"); } #} -#update { -fix parse error#} +#update "fix parse error" #check errors { main.zig:2:5: error: uh oh #} @@ -37,8 +35,7 @@ main.zig:2:5: error: uh oh pub fn main() !void { @compileError("uh oh"); #} -#update { -add parse error again#} +#update "add parse error again" #check errors { main.zig:3:1: error: expected statement, found 'EOF' #} @@ -47,8 +44,7 @@ main.zig:3:1: error: expected statement, found 'EOF' pub fn main() !void { //@compileError("uh oh"); #} -#update { -comment @compileError call#} +#update "comment @compileError call" #check errors { main.zig:3:1: error: expected statement, found 'EOF' #} @@ -58,7 +54,5 @@ pub fn main() !void { //@compileError("uh oh"); } #} -#update { -fix parse error again#} -#check stdout { -#} +#update "fix parse error again" +#check stdout diff --git a/test/incremental/bad_import b/test/incremental/bad_import index d93a9ed6309f963c310a82f0813d0905a564cc10..dcf0bbccd81a23ebab163ae4f6b342a890727504 100644 --- a/test/incremental/bad_import +++ b/test/incremental/bad_import @@ -26,8 +26,7 @@ comptime { _ = @import("this_is/not_real.zig"); } #} -#update { -change bad import#} +#update "change bad import" #check errors { this_is/not_real.zig:1:1: error: unable to load "not_real.zig": FileNotFound foo.zig:2:17: note: file imported here @@ -41,8 +40,7 @@ pub fn main() !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -remove import of 'foo.zig'#} +#update "remove import of 'foo.zig'" #check stdout { success #} diff --git a/test/incremental/change_bitpack_backing_int b/test/incremental/change_bitpack_backing_int index a5a2b81ce3c3df699043da2fdbd06e7718627ec8..6530ae9c66ae83e3b92085e5e9eca2d5cd3238fd 100644 --- a/test/incremental/change_bitpack_backing_int +++ b/test/incremental/change_bitpack_backing_int @@ -38,8 +38,7 @@ comptime { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -make backing types signed#} +#update "make backing types signed" #check errors { main.zig:5:5: error: found compile log statement main.zig:8:5: note: also here @@ -62,8 +61,7 @@ comptime { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -make backing types too small#} +#update "make backing types too small" #check errors { main.zig:1:20: error: backing integer bit width does not match total bit width of fields main.zig:1:27: note: backing integer 'i5' has bit width '5' @@ -87,8 +85,7 @@ comptime { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -make backing types too big#} +#update "make backing types too big" #check errors { main.zig:1:20: error: backing integer bit width does not match total bit width of fields main.zig:1:27: note: backing integer 'u10' has bit width '10' diff --git a/test/incremental/change_container_kind b/test/incremental/change_container_kind index b83abc6ed79414180e28bf393af94cb708bb377e..55b0f788db78ed1c470bca7020b4062da7f79a1b 100644 --- a/test/incremental/change_container_kind +++ b/test/incremental/change_container_kind @@ -12,8 +12,7 @@ pub fn main() void { #write foo.zig { pub const T = struct { x: u8 }; #} -#update { -T is struct#} +#update "T is struct" #check errors { main.zig:3:5: error: found compile log statement @@ -25,8 +24,7 @@ Compile Log Output: #write foo.zig { pub const T = union {}; #} -#update { -T is union#} +#update "T is union" #check errors { main.zig:3:5: error: found compile log statement @@ -38,8 +36,7 @@ Compile Log Output: #write foo.zig { pub const T = enum {}; #} -#update { -T is enum#} +#update "T is enum" #check errors { main.zig:3:5: error: found compile log statement @@ -51,8 +48,7 @@ Compile Log Output: #write foo.zig { pub const T = opaque {}; #} -#update { -T is opaque#} +#update "T is opaque" #check errors { main.zig:3:5: error: found compile log statement @@ -64,8 +60,7 @@ Compile Log Output: #write foo.zig { pub const T = packed struct {}; #} -#update { -T is packed struct#} +#update "T is packed struct" #check errors { main.zig:3:5: error: found compile log statement @@ -77,8 +72,7 @@ Compile Log Output: #write foo.zig { pub const T = packed union { x: void }; // packed unions need at least 1 field #} -#update { -T is packed union#} +#update "T is packed union" #check errors { main.zig:3:5: error: found compile log statement @@ -90,8 +84,7 @@ Compile Log Output: #write foo.zig { pub const T = extern struct {}; #} -#update { -T is extern struct#} +#update "T is extern struct" #check errors { main.zig:3:5: error: found compile log statement @@ -103,8 +96,7 @@ Compile Log Output: #write foo.zig { pub const T = extern union { x: void }; // extern unions need at least 1 field #} -#update { -T is extern union#} +#update "T is extern union" #check errors { main.zig:3:5: error: found compile log statement diff --git a/test/incremental/change_embed_file b/test/incremental/change_embed_file index d50101dc476973ab1974d4ae82fca3cebffed359..8ef936e7e6b96d8d967850c11e3748050628ccd4 100644 --- a/test/incremental/change_embed_file +++ b/test/incremental/change_embed_file @@ -21,15 +21,13 @@ Hello, World! #write string.txt { Hello again, World! #} -#update { -change file contents#} +#update "change file contents" #check stdout { Hello again, World! #} #delete string.txt -#update { -delete file#} +#update "delete file" #check errors { main.zig:2:27: error: failed opening "string.txt": FileNotFound #} @@ -42,8 +40,7 @@ pub fn main() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -remove reference to file#} +#update "remove reference to file" #check stdout { a hardcoded string #} @@ -56,8 +53,7 @@ pub fn main() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -re-introduce reference to file#} +#update "re-introduce reference to file" #check errors { main.zig:2:27: error: failed opening "string.txt": FileNotFound #} @@ -65,8 +61,7 @@ main.zig:2:27: error: failed opening "string.txt": FileNotFound #write string.txt { We're back, World! #} -#update { -recreate file#} +#update "recreate file" #check stdout { We're back, World! #} diff --git a/test/incremental/change_enum_tag_type/manifest b/test/incremental/change_enum_tag_type/manifest index 682c83a67ed4a3d9ced7ba413b3c8e2ddfab4559..e26e630b28561e1339b22813e0ea99e760c79d91 100644 --- a/test/incremental/change_enum_tag_type/manifest +++ b/test/incremental/change_enum_tag_type/manifest @@ -2,29 +2,26 @@ -Mroot=main.zig #} -#write main.zig 0/main.zig +#write main.zig @0/main.zig #update #check stdout { a #} -#write main.zig 1/main.zig -#update { -too many enum fields#} +#write main.zig @1/main.zig +#update "too many enum fields" #check errors { main.zig:7:5: error: enum tag value '4' too large for type 'u2' #} -#write main.zig 2/main.zig -#update { -increase tag size#} +#write main.zig @2/main.zig +#update "increase tag size" #check stdout { a #} -#write main.zig 3/main.zig -#update { -specify tag directly#} +#write main.zig @3/main.zig +#update "specify tag directly" #check errors { main.zig:9:5: error: found compile log statement @@ -32,9 +29,8 @@ Compile Log Output: @as(type, u3) #} -#write main.zig 4/main.zig -#update { -change directly-specified tag type#} +#write main.zig @4/main.zig +#update "change directly-specified tag type" #check errors { main.zig:9:5: error: found compile log statement diff --git a/test/incremental/change_exports/manifest b/test/incremental/change_exports/manifest index 41716cd32cadf13dca51971fbab9703bc1530ade..9d38a16b9d18a1a07c11f23b765a68eae510c7e0 100644 --- a/test/incremental/change_exports/manifest +++ b/test/incremental/change_exports/manifest @@ -2,50 +2,43 @@ -Mroot=main.zig #} -#write main.zig 0/main.zig +#write main.zig @0/main.zig #update #check stdout { 123 #} -#write main.zig 1/main.zig -#update { -add conflict#} +#write main.zig @1/main.zig +#update "add conflict" #check errors { main.zig:6:5: error: exported symbol collision: foo main.zig:1:1: note: other symbol here #} -#write main.zig 2/main.zig -#update { -resolve conflict#} +#write main.zig @2/main.zig +#update "resolve conflict" #check stdout { 123 456 #} -#write main.zig 3/main.zig -#update { -put exports in decl#} +#write main.zig @3/main.zig +#update "put exports in decl" #check stdout { 123 456 #} -#write main.zig 4/main.zig -#update { -remove reference to exporting decl#} -#check stdout { -#} +#write main.zig @4/main.zig +#update "remove reference to exporting decl" +#check stdout -#write main.zig 5/main.zig -#update { -mark consts as export#} +#write main.zig @5/main.zig +#update "mark consts as export" #check stdout { 123 456 #} -#write main.zig 6/main.zig -#update { -reintroduce reference to exporting decl, introducing conflict#} +#write main.zig @6/main.zig +#update "reintroduce reference to exporting decl, introducing conflict" #check errors { main.zig:5:5: error: exported symbol collision: bar main.zig:2:1: note: other symbol here diff --git a/test/incremental/change_fn_type b/test/incremental/change_fn_type index b16811fbc937aa183fd80bfe7b0eb59690e089b7..448050823cba202d0d4cc2b73f855472e7e53674 100644 --- a/test/incremental/change_fn_type +++ b/test/incremental/change_fn_type @@ -29,8 +29,7 @@ fn foo(x: i64) !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -change function type#} +#update "change function type" #check stdout { 123 #} @@ -46,8 +45,7 @@ fn foo(x: i64) !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -change function argument#} +#update "change function argument" #check stdout { -42 #} diff --git a/test/incremental/change_generic_line_number b/test/incremental/change_generic_line_number index cda09859f9c849649b5b40c66d9d16e94101cf34..561fc8b59cf326545a17288f8c96e59148d796f0 100644 --- a/test/incremental/change_generic_line_number +++ b/test/incremental/change_generic_line_number @@ -39,8 +39,7 @@ pub fn main() !void { try Printer("bar\n").print(); } #} -#update { -change line number#} +#update "change line number" #check stdout { foo bar diff --git a/test/incremental/change_line_number b/test/incremental/change_line_number index ed8e24b009356b452dd71d17d7ca510e0df285d2..a6ec89fb4f80da9697bf660fc65695abb31a7ad1 100644 --- a/test/incremental/change_line_number +++ b/test/incremental/change_line_number @@ -22,8 +22,7 @@ pub fn main() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -change line number#} +#update "change line number" #check stdout { foo #} diff --git a/test/incremental/change_module b/test/incremental/change_module index 4c24df6e4dd2c3f410d2d0a555e5a05a2d94d592..a1fc17b6777e5113d0f3bfbd169b9fb9b2627f01 100644 --- a/test/incremental/change_module +++ b/test/incremental/change_module @@ -41,8 +41,7 @@ comptime { //_ = @import("other.zig"); } #} -#update { -change module of other.zig#} +#update "change module of other.zig" #check errors { other.zig:2:5: error: found compile log statement @@ -61,8 +60,7 @@ comptime { _ = @import("other.zig"); } #} -#update { -put other.zig in both modules#} +#update "put other.zig in both modules" #check errors { other.zig:1:1: error: file exists in modules 'root' and 'foo' other.zig:1:1: note: files must belong to only one module @@ -81,7 +79,5 @@ comptime { //_ = @import("other.zig"); } #} -#update { -put other.zig in no modules#} -#check stdout { -#} +#update "put other.zig in no modules" +#check stdout diff --git a/test/incremental/change_panic_handler b/test/incremental/change_panic_handler index 3407de9bade926e20542b9342c91e2d223a019da..67abb5aac76dddfe33c6b6206107772227805f9d 100644 --- a/test/incremental/change_panic_handler +++ b/test/incremental/change_panic_handler @@ -39,8 +39,7 @@ fn myPanic(msg: []const u8, _: ?usize) noreturn { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -change the panic handler body#} +#update "change the panic handler body" #check stdout { new panic message: integer overflow #} @@ -61,8 +60,7 @@ fn myPanicNew(msg: []const u8, _: ?usize) noreturn { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -change the panic handler function value#} +#update "change the panic handler function value" #check stdout { third panic message: integer overflow #} diff --git a/test/incremental/change_panic_handler_explicit/manifest b/test/incremental/change_panic_handler_explicit/manifest index 3388f912ec3c0f6f111bf0750283c4c94a2b720f..201b78dc20cda57f266ddf66facd3bd359320d20 100644 --- a/test/incremental/change_panic_handler_explicit/manifest +++ b/test/incremental/change_panic_handler_explicit/manifest @@ -2,22 +2,20 @@ -Mroot=main.zig #} -#write main.zig 0/main.zig +#write main.zig @0/main.zig #update #check stdout { panic message: integer overflow #} -#write main.zig 1/main.zig -#update { -change the panic handler body#} +#write main.zig @1/main.zig +#update "change the panic handler body" #check stdout { new panic message: integer overflow #} -#write main.zig 2/main.zig -#update { -change the panic handler function value#} +#write main.zig @2/main.zig +#update "change the panic handler function value" #check stdout { third panic message: integer overflow #} diff --git a/test/incremental/change_reify_struct_field_type b/test/incremental/change_reify_struct_field_type index 92a41faee42808ef4b2338a1bdac241507e659fe..808fb39b701a603fb39d1c6f93f18585a51e1cf2 100644 --- a/test/incremental/change_reify_struct_field_type +++ b/test/incremental/change_reify_struct_field_type @@ -13,8 +13,7 @@ const Enum = enum { foo, bar }; const std = @import("std"); #} #update -#check stdout { -foo#} +#check stdout "foo" #write main.zig { fn getEnum(s: @Struct(.auto, null, &.{"field"}, &.{Enum}, &.{.{}})) Enum { @@ -26,7 +25,5 @@ pub fn main(init: std.process.Init) !void { const Enum = enum { foo, bar }; const std = @import("std"); #} -#update { -change field type#} -#check stdout { -bar#} +#update "change field type" +#check stdout "bar" diff --git a/test/incremental/change_shift_op b/test/incremental/change_shift_op index bcd0ff1d2f61b4aeb305a350bc64a8b64b76fbed..85160205dc368156fb1aa9860d7d03f1e6ce2038 100644 --- a/test/incremental/change_shift_op +++ b/test/incremental/change_shift_op @@ -29,8 +29,7 @@ fn foo(x: u16) !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -change to right shift#} +#update "change to right shift" #check stdout { 0x130 #} diff --git a/test/incremental/change_struct_same_fields b/test/incremental/change_struct_same_fields index 348a443dd555223482c96f6ae0ccd7ed5a48c459..a8a4533ef420b1922dbf12322ee6e62e238ea497 100644 --- a/test/incremental/change_struct_same_fields +++ b/test/incremental/change_struct_same_fields @@ -39,8 +39,7 @@ fn foo(val: *const S) !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -change struct layout#} +#update "change struct layout" #check stdout { 100 200 #} @@ -61,8 +60,7 @@ fn foo(val: *const S) !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -change values#} +#update "change values" #check stdout { 1234 5678 #} diff --git a/test/incremental/change_union_tag_type b/test/incremental/change_union_tag_type index dbbbc94c1e9bebbfdd1b98743de8a85763e725a5..8ec3714735fd783b31a3e1e4e70f47e169b7b129 100644 --- a/test/incremental/change_union_tag_type +++ b/test/incremental/change_union_tag_type @@ -29,8 +29,7 @@ pub fn main(init: std.process.Init) !void { } const std = @import("std"); #} -#update { -change tag type#} +#update "change tag type" #check errors { main.zig:3:24: error: no field named 'a' in enum 'main.B' main.zig:2:11: note: enum declared here @@ -47,8 +46,7 @@ pub fn main(init: std.process.Init) !void { } const std = @import("std"); #} -#update { -change field name to match new tag type#} +#update "change field name to match new tag type" #check stdout { b #} diff --git a/test/incremental/change_zon_file b/test/incremental/change_zon_file index 53b7c82c84703475b71d4495a716041858d27cee..5082d01e796df554957f7c280aba8d55d8913e0e 100644 --- a/test/incremental/change_zon_file +++ b/test/incremental/change_zon_file @@ -21,15 +21,13 @@ Hello, World! #write message.zon { "Hello again, World!\n" #} -#update { -change ZON file contents#} +#update "change ZON file contents" #check stdout { Hello again, World! #} #delete message.zon -#update { -delete file#} +#update "delete file" #check errors { message.zon:1:1: error: unable to load "message.zon": FileNotFound main.zig:2:37: note: file imported here @@ -43,8 +41,7 @@ pub fn main() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -remove reference to ZON file#} +#update "remove reference to ZON file" #check errors { message.zon:1:1: error: unable to load "message.zon": FileNotFound main.zig:2:37: note: file imported here @@ -53,8 +50,7 @@ main.zig:2:37: note: file imported here #write message.zon { "We're back, World!\n" #} -#update { -recreate ZON file#} +#update "recreate ZON file" #check stdout { a hardcoded string #} @@ -67,8 +63,7 @@ pub fn main() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -re-introduce reference to ZON file#} +#update "re-introduce reference to ZON file" #check stdout { We're back, World! #} diff --git a/test/incremental/change_zon_file_no_result_type b/test/incremental/change_zon_file_no_result_type index dd63226cbaa21d113d2ff5f7f43484d616a5d956..201c5dfbde118d39f731ae3b3127431bfa25d868 100644 --- a/test/incremental/change_zon_file_no_result_type +++ b/test/incremental/change_zon_file_no_result_type @@ -26,8 +26,7 @@ Hello, World! .b_number = 0, } #} -#update { -change ZON file#} +#update "change ZON file" #check stdout { Hello again, World! #} diff --git a/test/incremental/compile_error_then_log b/test/incremental/compile_error_then_log index 44dd98a1734dc9fdc2c5618fa58d8fdcceea8ce9..867ad231eb1c8ba756007266ccbd374eaa30940c 100644 --- a/test/incremental/compile_error_then_log +++ b/test/incremental/compile_error_then_log @@ -11,8 +11,7 @@ comptime { @compileLog("this is a log"); } #} -#update { -compile error#} +#update "compile error" #check errors { main.zig:3:5: error: this is an error @@ -29,8 +28,7 @@ comptime { @compileLog("this is a log"); } #} -#update { -remove the compile error#} +#update "remove the compile error" #check errors { main.zig:6:5: error: found compile log statement diff --git a/test/incremental/compile_log b/test/incremental/compile_log index d4445f3ca80aaac2262e70b94ec2f8acaf751603..ec2dd9e1065f437e5d3a1f31d9b2744a31b78e7d 100644 --- a/test/incremental/compile_log +++ b/test/incremental/compile_log @@ -9,8 +9,7 @@ pub fn main() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -no compile log#} +#update "no compile log" #check stdout { Hello, World! #} @@ -23,8 +22,7 @@ pub fn main() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -add compile log#} +#update "add compile log" #check errors { main.zig:4:5: error: found compile log statement @@ -39,8 +37,7 @@ pub fn main() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -remove compile log#} +#update "remove compile log" #check stdout { Hello, World! #} diff --git a/test/incremental/delete_comptime_decls b/test/incremental/delete_comptime_decls index ffae08f3d8d7a26173aefdb4c398895eb26d420a..ff13f6c8a77146890361297218d155eeedc47b67 100644 --- a/test/incremental/delete_comptime_decls +++ b/test/incremental/delete_comptime_decls @@ -44,8 +44,7 @@ comptime { _ = y; } #} -#update { -delete and modify comptime decls#} +#update "delete and modify comptime decls" #check errors { main.zig:6:16: error: slice of null pointer #} diff --git a/test/incremental/dependency_on_type_of_inferred_global b/test/incremental/dependency_on_type_of_inferred_global index 73d36a7254759c0f98720617aaf3848cc8703070..517424ed02ba2b4ebb3571797f381d68a673a4f8 100644 --- a/test/incremental/dependency_on_type_of_inferred_global +++ b/test/incremental/dependency_on_type_of_inferred_global @@ -15,8 +15,7 @@ comptime { pub fn main() void {} #} #update -#check stdout { -#} +#check stdout #write main.zig { const foo = @as(u16, 123); @@ -30,8 +29,7 @@ comptime { } pub fn main() void {} #} -#update { -change the type#} +#update "change the type" #check errors { main.zig:8:37: error: reached unreachable code #} diff --git a/test/incremental/do_nothing b/test/incremental/do_nothing index 213f7f1892d8fa96dbb9313054d862513956ce66..ace818eacc418576e6fb787f736a29070ebf0967 100644 --- a/test/incremental/do_nothing +++ b/test/incremental/do_nothing @@ -1,6 +1,4 @@ -#todo { -it'd be great if we could actually check that no analysis happened! -#} +#todo "it'd be great if we could actually check that no analysis happened!" #exe { -Mroot=main.zig #} @@ -25,8 +23,7 @@ const O = opaque { }; #} #update -#check stdout { -#} +#check stdout #write main.zig { pub fn main() void { @@ -47,7 +44,5 @@ const O = opaque { } }; #} -#update { -do literally nothing#} -#check stdout { -#} +#update "do literally nothing" +#check stdout diff --git a/test/incremental/fix_astgen_failure b/test/incremental/fix_astgen_failure index feb078359a513becff249d9e5e89a88a2e243352..df25736b059af01381b17f12e8d9f6eceba3a163 100644 --- a/test/incremental/fix_astgen_failure +++ b/test/incremental/fix_astgen_failure @@ -12,8 +12,7 @@ pub fn hello() !void { try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n"); } #} -#update { -error#} +#update "error" #check errors { foo.zig:2:9: error: use of undeclared identifier 'std' #} @@ -25,8 +24,7 @@ pub fn hello() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -fix the error#} +#update "fix the error" #check stdout { Hello, World! #} @@ -38,8 +36,7 @@ pub fn hello() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -add new error#} +#update "add new error" #check errors { foo.zig:3:52: error: use of undeclared identifier 'hello_str' #} @@ -52,8 +49,7 @@ pub fn hello() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -fix the new error#} +#update "fix the new error" #check stdout { Hello, World! Again! #} diff --git a/test/incremental/function_becomes_inline b/test/incremental/function_becomes_inline index 7d64be329579442c284c149e6342bcc0d3105c36..0a68a41407d4dd311b3b969bc4c157cdf90deaa3 100644 --- a/test/incremental/function_becomes_inline +++ b/test/incremental/function_becomes_inline @@ -12,8 +12,7 @@ fn foo() !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -non-inline version#} +#update "non-inline version" #check stdout { Hello, World! #} @@ -28,8 +27,7 @@ inline fn foo() !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -make function inline#} +#update "make function inline" #check stdout { Hello, World! #} @@ -44,8 +42,7 @@ inline fn foo() !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -change string#} +#update "change string" #check stdout { Hello, `inline` World! #} diff --git a/test/incremental/hello b/test/incremental/hello index 74a45d2a8b81afebe334968569949b6c82d4c397..8aab04829d520e382ed5bbbe156462a844a09585 100644 --- a/test/incremental/hello +++ b/test/incremental/hello @@ -21,8 +21,7 @@ pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n"); } #} -#update { -change the string#} +#update "change the string" #check stdout { おはようございます #} diff --git a/test/incremental/increase_array_field_length b/test/incremental/increase_array_field_length index 7b0eb9c165ff1a1b43206624569303b820d658ac..81d9d1ce8251658382c8c566241a84cc721f1e24 100644 --- a/test/incremental/increase_array_field_length +++ b/test/incremental/increase_array_field_length @@ -35,8 +35,7 @@ pub fn main(init: std.process.Init) !void { } const std = @import("std"); #} -#update { -increase array length#} +#update "increase array length" #check stdout { arr[999] = 234 #} diff --git a/test/incremental/make_decl_pub b/test/incremental/make_decl_pub index 59a54ece02b3850083fdd6c86c63d901cd346157..8055c5b7dfc9419740d46f19e50787c136eabc14 100644 --- a/test/incremental/make_decl_pub +++ b/test/incremental/make_decl_pub @@ -28,8 +28,7 @@ pub fn hello() !void { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -make hello pub#} +#update "make hello pub" #check stdout { Hello, World! #} diff --git a/test/incremental/matrix.zon b/test/incremental/matrix.zon new file mode 100644 index 0000000000000000000000000000000000000000..7c239ec719c4d5d5ccaf2b0e96cc305633f7d38f --- /dev/null +++ b/test/incremental/matrix.zon @@ -0,0 +1,61 @@ +.{ + // Avoid adding more CBE or LLVM targets without good reason: they're a lot slower than others + // to run due to the output (C source code or LLVM IR) being built non-incrementally (by Clang + // or LLVM). We just have a couple here to make sure that it works. + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .musl, + }, + .cache_mode = .incremental, + .backend = .cbe, + .linker = .old, + .pic = .nopic, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, + }, + .cache_mode = .incremental, + .backend = .llvm, + .linker = .lld, + .pic = .nopic, + }, + + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .none, + }, + .cache_mode = .incremental, + .backend = .selfhosted, + .linker = .new, + .pic = .nopic, + }, + .{ + .target = .{ + .cpu_arch = .x86_64, + .os_tag = .windows, + .abi = .none, + }, + .cache_mode = .incremental, + .backend = .selfhosted, + .linker = .old, + .pic = .pic, + }, + .{ + .target = .{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + .abi = .none, + }, + .cache_mode = .incremental, + .backend = .selfhosted, + .linker = .old, + .pic = .nopic, + }, +} diff --git a/test/incremental/modify_inline_fn b/test/incremental/modify_inline_fn index 956a47a6da82a984edfb3cc9ebf788759c0c61a5..fba956d321e27b0d631e4473736c275f4e0f922c 100644 --- a/test/incremental/modify_inline_fn +++ b/test/incremental/modify_inline_fn @@ -29,8 +29,7 @@ inline fn getStr() []const u8 { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -change the string#} +#update "change the string" #check stdout { bar #} diff --git a/test/incremental/move_src b/test/incremental/move_src index b385a312dbb5d55b6f7605aee556abbda5f9ade4..1de952a6b9b31864ee273238aa3c5f177c30a4ef 100644 --- a/test/incremental/move_src +++ b/test/incremental/move_src @@ -36,8 +36,7 @@ fn bar() u32 { } const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -add newline#} +#update "add newline" #check stdout { 8 123 #} diff --git a/test/incremental/pointer_to_decl_with_astgen_error b/test/incremental/pointer_to_decl_with_astgen_error index 87d07ca1abe476a8bc0be5a2abfa2b127736127f..1ac7e90c460749db94e2e8e3209ee3921b75d20a 100644 --- a/test/incremental/pointer_to_decl_with_astgen_error +++ b/test/incremental/pointer_to_decl_with_astgen_error @@ -9,8 +9,7 @@ pub fn main() void { } #} #update -#check stdout { -#} +#check stdout #write main.zig { const foo = something; @@ -18,8 +17,7 @@ pub fn main() void { _ = &foo; } #} -#update { -introduce use of undeclared identifier#} +#update "introduce use of undeclared identifier" #check errors { main.zig:1:13: error: use of undeclared identifier 'something' #} @@ -30,7 +28,5 @@ pub fn main() void { _ = &foo; } #} -#update { -revert use of undeclared identifier#} -#check stdout { -#} +#update "revert use of undeclared identifier" +#check stdout diff --git a/test/incremental/recursive_function_becomes_non_recursive b/test/incremental/recursive_function_becomes_non_recursive index 442748a079c7bc0ca8a97fc726a661e86546acd1..df5bc83da9a5fd2a7376517f7a2737e51ae59d33 100644 --- a/test/incremental/recursive_function_becomes_non_recursive +++ b/test/incremental/recursive_function_becomes_non_recursive @@ -31,8 +31,7 @@ fn foo(recurse: bool) !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -eliminate recursion and change argument#} +#update "eliminate recursion and change argument" #check stdout { x==1 #} diff --git a/test/incremental/remove_enum_field b/test/incremental/remove_enum_field index 67b0db55b9d597270216eee728189fbd0a638833..049d98746163611f72e824f908016f466a5404b7 100644 --- a/test/incremental/remove_enum_field +++ b/test/incremental/remove_enum_field @@ -31,8 +31,7 @@ pub fn main() !void { const std = @import("std"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -remove enum field#} +#update "remove enum field" #check errors { main.zig:7:68: error: enum 'main.MyEnum' has no member named 'foo' main.zig:1:16: note: enum declared here diff --git a/test/incremental/remove_function_with_changed_callee b/test/incremental/remove_function_with_changed_callee index d0a04678922d9ef841393ad2903469baf6ed7ca6..3bfa9bca0705c4cbdfcad7690d08d81871a40c10 100644 --- a/test/incremental/remove_function_with_changed_callee +++ b/test/incremental/remove_function_with_changed_callee @@ -28,8 +28,7 @@ pub fn main() !void { } #} #update -#check stdout { -0#} +#check stdout "0" #write main.zig { //! This update must preserve `bar` `InternPool.Index` value, while changing its wasm function type. @@ -54,7 +53,5 @@ pub fn main() !void { try bar(.one); } #} -#update { -remove foo and change bar wasm function type#} -#check stdout { -1#} +#update "remove foo and change bar wasm function type" +#check stdout "1" diff --git a/test/incremental/remove_invalid_union_backing_enum b/test/incremental/remove_invalid_union_backing_enum index 8a2099afb4ebe92697ff76f773b53abb4eb8b871..f93c81b5b157b04e89d21506a6977cecfd44d0b1 100644 --- a/test/incremental/remove_invalid_union_backing_enum +++ b/test/incremental/remove_invalid_union_backing_enum @@ -33,7 +33,5 @@ pub fn main() void { _ = u; } #} -#update { -remove invalid backing enum#} -#check stdout { -#} +#update "remove invalid backing enum" +#check stdout diff --git a/test/incremental/struct_reification_becomes_anon_init b/test/incremental/struct_reification_becomes_anon_init index bd790cdeb9445aab9834c64f0a5fa683974b6d95..c317b0dd749874f22f175b79a7ce326067f21a6e 100644 --- a/test/incremental/struct_reification_becomes_anon_init +++ b/test/incremental/struct_reification_becomes_anon_init @@ -23,8 +23,7 @@ pub fn main(init: std.process.Init) !void { } const std = @import("std"); #} -#update { -change to `@TypeOf` anonymous init#} +#update "change to `@TypeOf` anonymous init" #check stdout { anon_field #} diff --git a/test/incremental/tag_name b/test/incremental/tag_name index 05991657cdbfd920ee24d094bb8db93ada706d17..d948d102f0ebdbec84de2460c29bea3d0f4f53e3 100644 --- a/test/incremental/tag_name +++ b/test/incremental/tag_name @@ -11,8 +11,7 @@ pub fn main() !void { } #} #update -#check stdout { -first#} +#check stdout "first" #write main.zig { const std = @import("std"); @@ -22,10 +21,8 @@ pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum)); } #} -#update { -no change#} -#check stdout { -first#} +#update "no change" +#check stdout "first" #write main.zig { const std = @import("std"); @@ -35,7 +32,5 @@ pub fn main() !void { try std.Io.File.stdout().writeStreamingAll(io, @tagName(some_enum)); } #} -#update { -swap fields#} -#check stdout { -first#} +#update "swap fields" +#check stdout "first" diff --git a/test/incremental/temporary_analysis_error_in_generic_signature b/test/incremental/temporary_analysis_error_in_generic_signature index dbb3b6407eba276b9654775979353b53d9cea452..9b9292f4accc103d90dff97eaba552d8da9e522b 100644 --- a/test/incremental/temporary_analysis_error_in_generic_signature +++ b/test/incremental/temporary_analysis_error_in_generic_signature @@ -33,8 +33,7 @@ fn bar(comptime x: u8) @This().FooAlias { } const std = @import("std"); #} -#update { -change generic signature to use non-existent member#} +#update "change generic signature to use non-existent member" #check errors { main.zig:8:31: error: root source file struct 'main' has no member named 'FooAlias' main.zig:1:1: note: struct declared here @@ -54,8 +53,7 @@ fn bar(comptime x: u8) @This().FooAlias { } const std = @import("std"); #} -#update { -add that member, fixing the error#} +#update "add that member, fixing the error" #check stdout { Z #} diff --git a/test/incremental/temporary_parse_error b/test/incremental/temporary_parse_error index 9687ac4844b79133391f85addd09e71a44c8f6cd..033e5f581d207e24f12f000c4d35275368fbbca9 100644 --- a/test/incremental/temporary_parse_error +++ b/test/incremental/temporary_parse_error @@ -7,14 +7,12 @@ const std = @import("std"); pub fn main() !void {} #} #update -#check stdout { -#} +#check stdout #write main.zig { pub fn main() !void { #} -#update { -introduce parse error#} +#update "introduce parse error" #check errors { main.zig:2:1: error: expected statement, found 'EOF' #} @@ -22,7 +20,5 @@ main.zig:2:1: error: expected statement, found 'EOF' #write main.zig { pub fn main() !void {} #} -#update { -fix parse error#} -#check stdout { -#} +#update "fix parse error" +#check stdout diff --git a/test/incremental/type_becomes_comptime_only b/test/incremental/type_becomes_comptime_only index f174faacd788e3e5d4720c9badb1cb2e5f91c9ea..15a9d51176bca515225026f3ae85c0ef8197d6f6 100644 --- a/test/incremental/type_becomes_comptime_only +++ b/test/incremental/type_becomes_comptime_only @@ -14,8 +14,7 @@ pub fn main() void { } #} #update -#check stdout { -#} +#check stdout #write main.zig { const SomeType = comptime_int; @@ -28,10 +27,8 @@ pub fn main() void { s.foo(); } #} -#update { -make S comptime-only#} -#check stdout { -#} +#update "make S comptime-only" +#check stdout #write main.zig { const SomeType = u16; @@ -44,7 +41,5 @@ pub fn main() void { s.foo(); } #} -#update { -make S runtime again#} -#check stdout { -#} +#update "make S runtime again" +#check stdout diff --git a/test/incremental/type_dependency_loop b/test/incremental/type_dependency_loop index 85de8ae8391015b85194357423522046466cc042..1a5b1e2a49f467ebbea3420e3f8793db99628427 100644 --- a/test/incremental/type_dependency_loop +++ b/test/incremental/type_dependency_loop @@ -24,10 +24,8 @@ pub fn main() void { _ = B; } #} -#update { -remove reference to dependency loop#} -#check stdout { -#} +#update "remove reference to dependency loop" +#check stdout #write main.zig { pub const A = struct { b: B }; @@ -36,10 +34,8 @@ pub fn main() void { _ = B; } #} -#update { -change dependency loop without fixing it#} -#check stdout { -#} +#update "change dependency loop without fixing it" +#check stdout #write main.zig { pub const A = struct { b: B }; @@ -48,8 +44,7 @@ pub fn main() void { _ = @as(B, undefined); } #} -#update { -reference dependency loop again#} +#update "reference dependency loop again" #check errors { error: dependency loop with length 2 main.zig:2:43: note: type 'main.B' depends on type 'main.A' for alignment query here @@ -64,7 +59,5 @@ pub fn main() void { _ = @as(B, undefined); } #} -#update { -fix dependency loop#} -#check stdout { -#} +#update "fix dependency loop" +#check stdout diff --git a/test/incremental/unreferenced_error b/test/incremental/unreferenced_error index 0f5f65feab5c98985d105d06db1e2086cd6f0470..fc63d66a96462524e35e8cba041fe4dd20cbc159 100644 --- a/test/incremental/unreferenced_error +++ b/test/incremental/unreferenced_error @@ -23,8 +23,7 @@ pub fn main() !void { const a = @compileError("bad a"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -introduce compile error#} +#update "introduce compile error" #check errors { main.zig:5:11: error: bad a #} @@ -38,8 +37,7 @@ const a = @compileError("bad a"); const b = "Hi there!\n"; const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -remove error reference#} +#update "remove error reference" #check stdout { Hi there! #} @@ -53,8 +51,7 @@ const a = "Back to a\n"; const b = @compileError("bad b"); const io = std.Io.Threaded.global_single_threaded.io(); #} -#update { -introduce and remove reference to error#} +#update "introduce and remove reference to error" #check stdout { Back to a #} diff --git a/test/src/Runner.zig b/test/src/Runner.zig deleted file mode 100644 index 66e204b4c5d1799ccbbd3ad956cfff1d72b635f6..0000000000000000000000000000000000000000 --- a/test/src/Runner.zig +++ /dev/null @@ -1,2141 +0,0 @@ -const assert = std.debug.assert; -const builtin = @import("builtin"); -const std = @import("std"); -const Runner = @This(); - -gpa: std.mem.Allocator, -arena: *std.heap.ArenaAllocator, -environ_map: std.process.Environ.Map, -io: std.Io, -prog_node: std.Progress.Node, -args: Args, -host: std.Target, -server: std.zig.Server, -eb_wip: std.zig.ErrorBundle.Wip, - -const Args = struct { - @"test": ?struct { - input_dir: std.zig.Server.Message.InputDir, - dir: std.Io.Dir, - }, - manifest_file: ?std.Io.File, - zig_exe: ?std.Io.File, - lib_dir: ?std.Io.Dir, - src_dir: ?std.Io.Dir, - targets: std.ArrayList(u32), - target_bytes: std.ArrayList(u8), - - libc_runtimes_dir: ?std.Io.Dir, - enable_darling: bool, - enable_qemu: bool, - enable_rosetta: bool, - enable_wasmtime: bool, - enable_wine: bool, - - gdb_exe: ?std.Io.File, - lldb_exe: ?std.Io.File, - - quiet: bool, - - fn deinit(args: *Args, gpa: std.mem.Allocator) void { - args.targets.deinit(gpa); - args.target_bytes.deinit(gpa); - args.* = undefined; - } - - fn addTarget(args: *Args, gpa: std.mem.Allocator, target: []const u8) std.mem.Allocator.Error!void { - try args.target_bytes.ensureUnusedCapacity(gpa, target.len + 1); - try args.targets.append(gpa, @intCast(args.target_bytes.items.len)); - args.target_bytes.appendSliceAssumeCapacity(target); - args.target_bytes.appendAssumeCapacity(0); - } -}; - -fn deinit(runner: *Runner) void { - runner.environ_map.deinit(); - runner.prog_node.end(); - runner.args.deinit(runner.gpa); - runner.eb_wip.deinit(); - runner.* = undefined; -} - -const FailError = error{AlreadyReported} || std.mem.Allocator.Error || std.Io.Cancelable; -fn fail(runner: *Runner, comptime fmt: []const u8, args: anytype) FailError { - return runner.failString(try runner.eb_wip.printString(fmt, args)); -} -fn failString(runner: *Runner, msg: std.zig.ErrorBundle.String) FailError { - try runner.eb_wip.addRootErrorMessage(.{ - .msg = msg, - }); - return error.AlreadyReported; -} - -pub const ProtocolError = error{ - ServerReadFailed, - ServerWriteFailed, - ServerEndOfStream, -} || FailError; -pub fn runServer(runner: *Runner) ProtocolError { - runner.server.serveStringMessage(.zig_version, builtin.zig_version_string) catch |err| switch (err) { - error.WriteFailed => return error.ServerWriteFailed, - }; - while (true) { - _ = runner.arena.reset(.retain_capacity); - const arena = runner.arena.allocator(); - const hdr = runner.server.receiveMessage() catch |err| switch (err) { - error.ReadFailed => return error.ServerReadFailed, - error.EndOfStream => return error.ServerEndOfStream, - }; - switch (hdr.tag) { - else => |tag| return runner.fail("unsupported message: {t}", .{tag}), - .exit => std.process.exit(0), - .args => { - const args_body = try arena.alloc(u8, hdr.bytes_len); - runner.server.in.readSliceAll(args_body) catch unreachable; - const State = enum { - positional, - @"--zig", - @"--lib", - @"--src", - @"--target", - @"--libc-runtimes", - @"--gdb", - @"--lldb", - }; - var state: State = .positional; - const state_expected: std.enums.EnumArray( - State, - enum { path, dir, file, string }, - ) = .init(.{ - .positional = .path, - .@"--zig" = .file, - .@"--lib" = .dir, - .@"--src" = .dir, - .@"--target" = .string, - .@"--libc-runtimes" = .dir, - .@"--gdb" = .file, - .@"--lldb" = .file, - }); - var input_dir: std.zig.Server.Message.InputDir = .cwd; - var args_body_offset: usize = 0; - while (args_body.len - args_body_offset > 0) { - const arg: std.zig.Client.Message.Arg = - @fromBackingInt(args_body[args_body_offset]); - args_body_offset += 1; - arg: switch (arg) { - .string => { - const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?; - const string = args_body[args_body_offset..end]; - args_body_offset = end + 1; - switch (state) { - .positional => if (std.mem.eql(u8, string, "--target")) { - state = .@"--target"; - continue; - } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| { - try runner.args.addTarget(runner.gpa, target); - } else if (std.mem.eql(u8, string, "--libc-runtimes")) { - state = .@"--libc-runtimes"; - continue; - } else if (std.mem.eql(u8, string, "-fdarling")) { - runner.args.enable_darling = true; - } else if (std.mem.eql(u8, string, "-fqemu")) { - runner.args.enable_qemu = true; - } else if (std.mem.eql(u8, string, "-frosetta")) { - runner.args.enable_rosetta = true; - } else if (std.mem.eql(u8, string, "-fwasmtime")) { - runner.args.enable_wasmtime = true; - } else if (std.mem.eql(u8, string, "-fwine")) { - runner.args.enable_wine = true; - } else if (std.mem.eql(u8, string, "--gdb")) { - state = .@"--gdb"; - continue; - } else if (std.mem.eql(u8, string, "--lldb")) { - state = .@"--lldb"; - continue; - } else if (std.mem.eql(u8, string, "--quiet")) { - runner.args.quiet = true; - }, - .@"--target" => try runner.args.addTarget(runner.gpa, string), - else => return runner.fail( - "\"{t}\" expected {t}, got {t}", - .{ state, state_expected.get(state), arg }, - ), - } - }, - .prefix => { - const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?; - const string = args_body[args_body_offset..end]; - args_body_offset = end + 1; - assert(state == .positional); - state = if (std.mem.eql(u8, string, "--zig=")) - .@"--zig" - else if (std.mem.eql(u8, string, "--lib=")) - .@"--lib" - else if (std.mem.eql(u8, string, "--src=")) - .@"--src" - else if (std.mem.eql(u8, string, "--libc-runtimes=")) - .@"--libc-runtimes" - else if (std.mem.eql(u8, string, "--gdb=")) - .@"--gdb" - else if (std.mem.eql(u8, string, "--lldb=")) - .@"--lldb" - else - return runner.fail("unsupported arg prefix: {q}", .{string}); - continue; - }, - .suffix => { - const end = std.mem.findScalarPos(u8, args_body, args_body_offset, 0).?; - const string = args_body[args_body_offset..end]; - args_body_offset = end + 1; - return runner.fail("unsupported arg suffix: {q}", .{string}); - }, - .input_dir => { - input_dir = @fromBackingInt(@backingInt(input_dir) + 1); - continue :arg .output_dir; - }, - .output_dir => { - const dir_handle: *align(1) const std.Io.Dir.Handle = @ptrCast( - args_body[args_body_offset..][0..@sizeOf(std.Io.Dir.Handle)], - ); - args_body_offset += @sizeOf(std.Io.Dir.Handle); - const dir = runner.io.vtable.inheritParentDir( - runner.io.userdata, - dir_handle.*, - ) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail( - "unable to inherit parent dir: {t}", - .{e}, - ), - }; - switch (state) { - .positional => { - if (runner.args.@"test" != null or - runner.args.manifest_file != null) return runner.fail( - "{t} specified multiple times", - .{state}, - ); - runner.args.@"test" = .{ .input_dir = input_dir, .dir = dir }; - const manifest_path = "manifest"; - runner.args.manifest_file = dir.openFile( - runner.io, - manifest_path, - .{}, - ) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail( - "unable to open manifest file: {t}", - .{e}, - ), - }; - try runner.discoverInput(input_dir, manifest_path); - }, - .@"--lib" => { - if (runner.args.lib_dir != null) return runner.fail( - "\"{t}\" specified multiple times", - .{state}, - ); - runner.args.lib_dir = dir; - }, - .@"--src" => { - if (runner.args.src_dir != null) return runner.fail( - "\"{t}\" specified multiple times", - .{state}, - ); - runner.args.src_dir = dir; - }, - .@"--libc-runtimes" => { - if (runner.args.libc_runtimes_dir != null) return runner.fail( - "\"{t}\" specified multiple times", - .{state}, - ); - runner.args.libc_runtimes_dir = dir; - }, - else => return runner.fail( - "\"{t}\" expected {t}, got {t}", - .{ state, state_expected.get(state), arg }, - ), - } - }, - .input_file, .input_file_content, .output_file => { - const file_handle: *align(1) const std.Io.File.Handle = @ptrCast( - args_body[args_body_offset..][0..@sizeOf(std.Io.File.Handle)], - ); - args_body_offset += @sizeOf(std.Io.File.Handle); - const file = runner.io.vtable.inheritParentFile( - runner.io.userdata, - file_handle.*, - .{ .nonblocking = false }, - ) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail( - "unable to inherit parent file: {t}", - .{e}, - ), - }; - switch (state) { - .positional => { - if (runner.args.@"test" != null or - runner.args.manifest_file != null) return runner.fail( - "{t} specified multiple times", - .{state}, - ); - runner.args.manifest_file = file; - }, - .@"--zig" => { - if (runner.args.zig_exe != null) return runner.fail( - "\"{t}\" specified multiple times", - .{state}, - ); - runner.args.zig_exe = file; - }, - .@"--gdb" => { - if (runner.args.gdb_exe != null) return runner.fail( - "\"{t}\" specified multiple times", - .{state}, - ); - runner.args.gdb_exe = file; - }, - .@"--lldb" => { - if (runner.args.lldb_exe != null) return runner.fail( - "\"{t}\" specified multiple times", - .{state}, - ); - runner.args.lldb_exe = file; - }, - else => return runner.fail( - "\"{t}\" expected {t}, got {t}", - .{ state, state_expected.get(state), arg }, - ), - } - }, - } - state = .positional; - } - }, - .query_test_metadata => { - const expected_panic_msgs = try arena.alloc(u32, runner.args.targets.items.len); - @memset(expected_panic_msgs, 0); - runner.server.serveTestMetadata(.{ - .names = runner.args.targets.items, - .expected_panic_msgs = expected_panic_msgs, - .string_bytes = runner.args.target_bytes.items, - }) catch |err| switch (err) { - error.WriteFailed => return error.ServerWriteFailed, - }; - }, - .run_test => { - const test_index = runner.server.receiveBody_u32() catch unreachable; - runner.server.serveBodylessMessage(.test_started) catch |err| switch (err) { - error.WriteFailed => return error.ServerWriteFailed, - }; - runner.server.serveTestResults(.{ - .index = test_index, - .flags = .{ - .status = if (runner.testOne(test_index)) - .pass - else |err| status: switch (err) { - error.Canceled, - error.OutOfMemory, - error.ServerWriteFailed, - => |e| return e, - error.AlreadyReported => { - var eb = try runner.eb_wip.toOwnedBundle(""); - defer eb.deinit(runner.gpa); - assert(eb.errorMessageCount() > 0); // already reported what? - runner.server.serveErrorBundle( - .error_bundle, - eb, - ) catch |server_err| switch (server_err) { - error.WriteFailed => return error.ServerWriteFailed, - }; - const eb_wip: std.zig.ErrorBundle.Wip = try .init(runner.gpa); - runner.eb_wip.deinit(); - runner.eb_wip = eb_wip; - break :status .fail; - }, - error.SkipTest => .skip, - }, - .fuzz = false, - .log_err_count = 0, - .leak_count = 0, - }, - }) catch |err| switch (err) { - error.WriteFailed => return error.ServerWriteFailed, - }; - assert(runner.eb_wip.root_list.items.len == 0); // failed to report - }, - } - } -} - -fn discoverInput( - runner: *Runner, - dir: std.zig.Server.Message.InputDir, - sub_path: []const u8, -) error{ServerWriteFailed}!void { - return runner.discoverInputInner(dir, sub_path) catch |err| switch (err) { - error.WriteFailed => return error.ServerWriteFailed, - }; -} -fn discoverInputInner( - runner: *Runner, - dir: std.zig.Server.Message.InputDir, - sub_path: []const u8, -) std.Io.Writer.Error!void { - try runner.server.serveMessageHeader(.{ - .tag = .discovered_inputs, - .bytes_len = @intCast(@sizeOf(std.zig.Server.Message.InputDir) + - sub_path.len + 1), - }); - try runner.server.out.writeInt(u32, @backingInt(dir), .little); - try runner.server.out.writeAll(sub_path); - try runner.server.out.writeByte(0); - try runner.server.out.flush(); -} - -pub const SourceReader = struct { - const Io = std.Io; - - backing: *std.Io.Reader, - line_aw: std.Io.Writer.Allocating, - interface: std.Io.Reader, - line: u32, - eb_wip: *std.zig.ErrorBundle.Wip, - src_path: std.zig.ErrorBundle.String, - - pub fn init( - reader: *Io.Reader, - gpa: std.mem.Allocator, - eb_wip: *std.zig.ErrorBundle.Wip, - src_path: std.zig.ErrorBundle.String, - ) SourceReader { - return .{ - .backing = reader, - .line_aw = .init(gpa), - .interface = .{ - .vtable = &.{ - .stream = stream, - .discard = discard, - .readVec = readVec, - .rebase = rebase, - }, - .buffer = &.{}, - .seek = 0, - .end = 0, - }, - .line = 0, - .eb_wip = eb_wip, - .src_path = src_path, - }; - } - fn deinit(s: *SourceReader) void { - s.line_aw.deinit(); - } - fn stream(r: *Io.Reader, _: *Io.Writer, _: Io.Limit) Io.Reader.StreamError!usize { - const s: *SourceReader = @fieldParentPtr("interface", r); - try s.bufferLine(); - return 0; - } - fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { - const s: *SourceReader = @fieldParentPtr("interface", r); - const remaining = r.buffered(); - if (remaining.len > 0) { - const n = limit.minInt(remaining.len); - r.toss(n); - return n; - } - try s.bufferLine(); - return 0; - } - fn readVec(r: *Io.Reader, _: [][]u8) Io.Reader.Error!usize { - const s: *SourceReader = @fieldParentPtr("interface", r); - try s.bufferLine(); - return 0; - } - fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void { - const s: *SourceReader = @fieldParentPtr("interface", r); - defer s.updateBuffer(); - s.line_aw.ensureUnusedCapacity(capacity) catch |err| switch (err) { - error.OutOfMemory => return error.ReadFailed, - }; - } - fn updateBuffer(s: *SourceReader) void { - s.interface.buffer = s.line_aw.writer.buffer; - s.interface.end = s.line_aw.writer.end; - } - fn bufferLine(s: *SourceReader) Io.Reader.Error!void { - defer s.updateBuffer(); - { - const written = s.line_aw.written(); - const start = if (std.mem.findScalarLast(u8, written[0..s.interface.seek], '\n')) |newline| - newline + 1 - else - 0; - s.line += @intCast(std.mem.countScalar(u8, written[0..start], '\n')); - const preserve = written[start..]; - @memmove(written[0..preserve.len], preserve); - s.line_aw.shrinkRetainingCapacity(preserve.len); - s.interface.seek -= start; - } - const n = s.backing.streamDelimiterEnding(&s.line_aw.writer, '\n') catch |err| switch (err) { - error.ReadFailed => |e| return e, - error.WriteFailed => return error.ReadFailed, - }; - if (std.mem.endsWith(u8, s.line_aw.written(), "\r")) s.line_aw.writer.end -= 1; - const buffered = s.backing.buffered(); - if (n == 0 and buffered.len == 0) return error.EndOfStream; - if (std.mem.startsWith(u8, buffered, "\n")) { - s.line_aw.writer.writeByte('\n') catch |err| switch (err) { - error.WriteFailed => return error.ReadFailed, - }; - s.backing.toss(1); - } - } - fn fail( - s: *SourceReader, - src_loc: []const u8, - comptime fmt: []const u8, - args: anytype, - ) FailError { - return s.failString(src_loc, try s.eb_wip.printString(fmt, args)); - } - fn failString( - s: *SourceReader, - src_loc: []const u8, - msg: std.zig.ErrorBundle.String, - ) FailError { - const line: u32, const column: u32, const source_line = src_loc: { - const pos = src_loc.ptr - s.interface.buffer.ptr; - const end = std.mem.findScalarPos(u8, s.interface.buffer, pos, '\n') orelse - s.interface.buffer.len; - const start = if (std.mem.findScalarLast(u8, s.interface.buffer[0..end], '\n')) |newline| - newline + 1 - else - 0; - break :src_loc .{ - @intCast(s.line + std.mem.countScalar(u8, s.interface.buffer[0..start], '\n')), - @intCast(pos - start), - s.interface.buffer[start..end], - }; - }; - try s.eb_wip.addRootErrorMessage(.{ - .msg = msg, - .src_loc = try s.eb_wip.addSourceLocation(.{ - .src_path = s.src_path, - .line = line, - .column = column, - .span_start = column, - .span_main = column, - .span_end = @intCast(column + src_loc.len), - .source_line = try s.eb_wip.addString(source_line), - }), - }); - return error.AlreadyReported; - } -}; -pub const DelimitedReader = struct { - const Io = std.Io; - - unlimited: *Io.Reader, - remaining: Io.Limit, - delimiter: []const u8, - interface: Io.Reader, - - pub fn init(reader: *Io.Reader, delimiter: []const u8, buffer: []u8) DelimitedReader { - return .{ - .unlimited = reader, - .remaining = .nothing, - .delimiter = delimiter, - .interface = .{ - .vtable = &.{ - .stream = stream, - .discard = discard, - }, - .buffer = buffer, - .seek = 0, - .end = 0, - }, - }; - } - fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { - const d: *DelimitedReader = @fieldParentPtr("interface", r); - const block = try d.unlimited.peekGreedy(d.delimiter.len); - d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse - block.len - d.delimiter.len + 1); - if (d.remaining == .nothing) return error.EndOfStream; - const n = try d.unlimited.stream(w, limit.min(d.remaining)); - d.remaining = d.remaining.subtract(n).?; - return n; - } - fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { - const d: *DelimitedReader = @fieldParentPtr("interface", r); - const block = try d.unlimited.peekGreedy(d.delimiter.len); - d.remaining = .limited(std.mem.findPos(u8, block, d.remaining.toInt().?, d.delimiter) orelse - block.len - d.delimiter.len + 1); - if (d.remaining == .nothing) return error.EndOfStream; - const n = try d.unlimited.discard(limit.min(d.remaining)); - d.remaining = d.remaining.subtract(n).?; - return n; - } -}; - -const Update = struct { - target_query: []const u8, - manifest_sr: SourceReader, - compiler: ?Compiler, - src_dir: std.Io.Dir, - prog_node: std.Progress.Node, - mtime: std.Io.Timestamp, - num: usize, - allow_skip: bool, -}; - -const TestError = error{ - SkipTest, -} || FailError; -fn testOne(runner: *Runner, test_index: u32) (error{ServerWriteFailed} || TestError)!void { - const target_query = - std.mem.sliceTo(runner.args.target_bytes.items[runner.args.targets.items[test_index]..], 0); - const manifest_file = runner.args.manifest_file orelse - return runner.fail("missing \"path/to/test\" arg", .{}); - var manifest_buffer: [512]u8 = undefined; - var manifest_fr = manifest_file.reader(runner.io, &manifest_buffer); - var update: Update = .{ - .target_query = target_query, - .manifest_sr = .init(&manifest_fr.interface, runner.gpa, &runner.eb_wip, src_path_string: { - var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; - const src_path = src_path: { - break :src_path path_buffer[0 .. manifest_file.realPath( - runner.io, - &path_buffer, - ) catch |err| switch (err) { - error.Canceled => |e| return e, - else => break :src_path "manifest", - }]; - }; - break :src_path_string try runner.eb_wip.addString(src_path); - }), - .compiler = null, - .src_dir = (runner.args.src_dir orelse - return runner.fail("missing \"--src=path/to/src\" arg", .{})).createDirPathOpen( - runner.io, - target_query, - .{}, - ) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to create {q}: {t}", .{ target_query, e }), - }, - .prog_node = .none, - .mtime = .now(runner.io, .real), - .num = 0, - .allow_skip = true, - }; - defer { - update.manifest_sr.deinit(); - update.src_dir.close(runner.io); - update.prog_node.end(); - } - - // hack around `std.Io.Reader` interface bugs by ensuring the - // buffer is larger than the longest supported manifest line - try update.manifest_sr.line_aw.ensureTotalCapacityPrecise(512); - update.manifest_sr.updateBuffer(); - - var skip_delimiter = false; - while (update.manifest_sr.interface.takeSentinel('\n')) |line| { - var line_it = std.mem.tokenizeScalar(u8, line, ' '); - const cmd_src_loc = line_it.next() orelse continue; - if (skip_delimiter) { - assert(std.mem.eql(u8, cmd_src_loc, "#}")); - skip_delimiter = false; - const unexpected = line_it.rest(); - if (unexpected.len > 0) - return update.manifest_sr.fail(unexpected, "unexpected argument {q}", .{unexpected}); - continue; - } - const cmd = std.meta.stringToEnum(Command, std.mem.cutPrefix(u8, cmd_src_loc, "#") orelse - return update.manifest_sr.fail(cmd_src_loc, "expected command", .{})) orelse - return update.manifest_sr.fail(cmd_src_loc, "unknown command", .{}); - const arg = switch (cmd) { - .todo, .exe, .lib, .obj, .update => null, - .skip, .write, .delete, .check => line_it.next() orelse - return update.manifest_sr.fail(line_it.rest()[0..0], "missing argument", .{}), - }; - var contents_file: ?std.Io.File = null; - defer if (contents_file) |file| file.close(runner.io); - var contents_buffer: [512]u8 = undefined; - var contents_impl: union(enum) { - ending: std.Io.Reader, - dr: DelimitedReader, - fr: std.Io.File.Reader, - } = undefined; - const contents_r: *std.Io.Reader = contents_r: switch (cmd) { - .skip, .delete => { - const unexpected = line_it.rest(); - if (unexpected.len > 0) return update.manifest_sr.fail( - unexpected, - "unexpected argument {q}", - .{unexpected}, - ); - contents_impl = .{ .ending = .ending_instance }; - break :contents_r &contents_impl.ending; - }, - else => { - const contents_path = line_it.rest(); - if (contents_path.len == 0) { - contents_impl = .{ .ending = .ending_instance }; - break :contents_r &contents_impl.ending; - } - - if (std.mem.eql(u8, contents_path, "{")) { - contents_impl = .{ - .dr = .init(&update.manifest_sr.interface, "#}", &contents_buffer), - }; - skip_delimiter = true; - break :contents_r &contents_impl.dr.interface; - } - - const args_test = runner.args.@"test" orelse return update.manifest_sr.fail( - contents_path, - "contents file requires directory test", - .{}, - ); - try runner.discoverInput(args_test.input_dir, contents_path); - - contents_file = - args_test.dir.openFile(runner.io, contents_path, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return update.manifest_sr.fail( - contents_path, - "unable to open contents file: {t}", - .{e}, - ), - }; - contents_impl = .{ .fr = contents_file.?.reader(runner.io, &contents_buffer) }; - break :contents_r &contents_impl.fr.interface; - }, - }; - - runner.handleCommand(&update, cmd_src_loc, cmd, arg, contents_r) catch |err| switch (err) { - error.Canceled, - error.OutOfMemory, - error.AlreadyReported, - error.SkipTest, - => |e| return e, - error.ContentsReadFailed => switch (contents_impl) { - .ending => unreachable, - .dr => switch (manifest_fr.err orelse return error.OutOfMemory) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to read manifest: {t}", .{e}), - }, - .fr => |fr| switch (fr.err.?) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to read contents file: {t}", .{e}), - }, - }, - }; - } else |err| switch (err) { - error.ReadFailed => switch (manifest_fr.err orelse return error.OutOfMemory) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to read manifest: {t}", .{e}), - }, - error.StreamTooLong => unreachable, - error.EndOfStream => if (skip_delimiter) - return runner.fail("manifest missing \"#}}\" delimiter", .{}), - } - if (update.compiler) |*comp| { - if (comp.state != .idle) return runner.fail("missing \"#check\"", .{}); - try comp.exit(); - } -} -const Command = enum { todo, skip, exe, lib, obj, write, delete, update, check }; -fn handleCommand( - runner: *Runner, - update: *Update, - cmd_src_loc: []const u8, - cmd: Command, - arg: ?[]const u8, - contents_r: *std.Io.Reader, -) (error{ContentsReadFailed} || TestError)!void { - var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; - const cmd_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "#{t}{s}{s}", .{ - cmd, if (arg) |_| " " else "", arg orelse "", - }) catch &name_buffer, 0); - defer cmd_prog_node.end(); - - cmd: switch (cmd) { - .todo => _ = contents_r.discardRemaining() catch |err| switch (err) { - error.ReadFailed => {}, - }, - .skip => if (!update.allow_skip) return update.manifest_sr.fail( - cmd_src_loc, - "\"#skip\" must appear before other commands", - .{}, - ) else if (std.mem.eql(u8, update.target_query, arg.?)) return error.SkipTest, - .exe, .lib, .obj => runner.spawnCompiler(&update.compiler, update.src_dir, switch (cmd) { - else => unreachable, - .exe => .Exe, - .lib => .Lib, - .obj => .Obj, - }, contents_r, target: { - var component_it = std.mem.splitBackwardsScalar(u8, update.target_query, '-'); - const pic_str = component_it.next() orelse - return runner.fail("target {q} missing pic", .{update.target_query}); - const pic = std.meta.stringToEnum(Compiler.Target.Pic, pic_str) orelse - return runner.fail("target {q} unknown pic {q}", .{ update.target_query, pic_str }); - const linker_str = component_it.next() orelse - return runner.fail("target {q} missing linker", .{update.target_query}); - const linker = std.meta.stringToEnum(Compiler.Target.Linker, linker_str) orelse - return runner.fail("target {q} unknown linker {q}", .{ update.target_query, linker_str }); - const backend_str = component_it.next() orelse - return runner.fail("target {q} missing backend", .{update.target_query}); - const backend = std.meta.stringToEnum(Compiler.Target.Backend, backend_str) orelse - return runner.fail("target {q} unknown backend {q}", .{ update.target_query, backend_str }); - const cache_mode_str = component_it.next() orelse - return runner.fail("target {q} missing cache mode", .{update.target_query}); - const cache_mode = std.meta.stringToEnum(Compiler.Target.CacheMode, cache_mode_str) orelse - return runner.fail("target {q} unknown cache mode {q}", .{ update.target_query, cache_mode_str }); - const triple = component_it.rest(); - break :target .{ - .triple = triple, - .resolved = std.zig.system.resolveTargetQuery( - runner.io, - std.Build.parseTargetQuery(.{ - .arch_os_abi = triple, - .object_format = switch (backend) { - .sema, .selfhosted, .llvm => null, - .cbe => "c", - }, - }) catch |err| switch (err) { - error.ParseFailed => return runner.fail("unable to parse triple {q}", .{ - triple, - }), - }, - ) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to resolve target {q}: {t}", .{ triple, e }), - }, - .cache_mode = cache_mode, - .backend = backend, - .linker = linker, - .pic = pic, - }; - }) catch |err| switch (err) { - error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e, - error.ReadFailed => return error.ContentsReadFailed, - }, - .write => { - const file = update.src_dir.createFile(runner.io, arg.?, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return update.manifest_sr.fail(arg.?, "unable to create file: {t}", .{e}), - }; - defer file.close(runner.io); - var fw_buffer: [512]u8 = undefined; - var fw = file.writer(runner.io, &fw_buffer); - _ = contents_r.streamRemaining(&fw.interface) catch |err| switch (err) { - error.ReadFailed => return error.ContentsReadFailed, - error.WriteFailed => switch (fw.err.?) { - error.Canceled => |e| return e, - else => |e| return update.manifest_sr.fail( - arg.?, - "unable to write file: {t}", - .{e}, - ), - }, - }; - fw.flush() catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return update.manifest_sr.fail( - arg.?, - "unable to write file: {t}", - .{e}, - ), - }; - file.setTimestamps(runner.io, .{ .modify_timestamp = .{ - .new = update.mtime, - } }) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return update.manifest_sr.fail( - arg.?, - "unable to update file timestamps: {t}", - .{e}, - ), - }; - }, - .delete => update.src_dir.deleteFile(runner.io, arg.?) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return update.manifest_sr.fail(arg.?, "unable to delete file: {t}", .{e}), - }, - .update => { - const comp = &(update.compiler orelse return update.manifest_sr.fail( - cmd_src_loc, - "missing \"#exe\", \"#lib\", or \"#obj\"", - .{}, - )); - update.prog_node.end(); - update.prog_node = runner.prog_node.start( - switch (contents_r.readSliceShort(&name_buffer) catch |err| switch (err) { - error.ReadFailed => return error.ContentsReadFailed, - }) { - 0 => switch (update.num) { - 0 => "initial update", - else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch - &name_buffer, - }, - else => |name_len| name_buffer[0..name_len], - }, - 0, - ); - _ = contents_r.discardRemaining() catch |err| switch (err) { - error.ReadFailed => return error.ContentsReadFailed, - }; - if (comp.state != .idle) - return update.manifest_sr.fail(cmd_src_loc, "missing \"#check\"", .{}); - comp.client.serveBodylessMessage(.update) catch |err| switch (err) { - error.WriteFailed => switch (comp.fw.err.?) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to send message to compiler: {t}", .{e}), - }, - }; - comp.state = .update; - update.mtime = update.mtime.addDuration(.fromSeconds(2)); - update.num += 1; - }, - .check => { - const check = std.meta.stringToEnum(Compiler.Check, arg.?) orelse - return update.manifest_sr.fail(arg.?, "unknown check", .{}); - const expected = - contents_r.allocRemaining(runner.gpa, .unlimited) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.ReadFailed => return error.ContentsReadFailed, - error.StreamTooLong => unreachable, // .unlimited - }; - defer runner.gpa.free(expected); - - const comp = &(update.compiler orelse return update.manifest_sr.fail( - cmd_src_loc, - "missing \"#exe\", \"#lib\", or \"#obj\"", - .{}, - )); - if (comp.state != .update) - return update.manifest_sr.fail(cmd_src_loc, "missing \"#update\"", .{}); - const stderr = comp.mr.reader(1); - while (try comp.receiveMessage()) |message| switch (message.tag) { - else => {}, // Ignore other messages, - .config => { - var body_r: std.Io.Reader = .fixed(message.body); - comp.config = body_r.takeStruct(std.zig.Server.Message.Config, .little) catch - unreachable; - }, - .emit_digest => { - var body_r: std.Io.Reader = .fixed(message.body); - _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch - unreachable; - const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch - unreachable; - try comp.checkSuccess(check, expected, update.src_dir, digest); - }, - .error_bundle => { - const eb = std.zig.Server.allocErrorBundle( - runner.arena.allocator(), - message.body, - ) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.EndOfStream => return runner.fail("error bundle truncated", .{}), - }; - switch (check) { - .errors => { - var error_aw: std.Io.Writer.Allocating = .init(runner.gpa); - defer error_aw.deinit(); - eb.renderToWriter(.{ - .include_reference_trace = false, - .include_source_line = false, - .include_log_text = true, - }, &error_aw.writer) catch |err| switch (err) { - error.WriteFailed => return error.OutOfMemory, - }; - const actual = error_aw.written(); - std.mem.replaceScalar(u8, actual, '\\', '/'); - std.testing.expectEqualStrings(expected, actual) catch |err| switch (err) { - error.TestExpectedEqual => return runner.fail( - "errors did not match expected", - .{}, - ), - }; - }, - .stdout, .exit, .gdb, .lldb => if (eb.errorMessageCount() > 0) { - eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) { - error.Canceled => {}, - else => {}, - }; - return runner.fail("unexpected compile errors", .{}); - }, - } - comp.state = .idle; - break :cmd; - }, - }; - - const buffered_stderr = stderr.buffered(); - if (buffered_stderr.len > 0) { - if (comp.allow_compiler_stderr) { - std.log.info("stderr:\n{s}", .{buffered_stderr}); - } else { - return runner.fail("unexpected stderr:\n{s}", .{buffered_stderr}); - } - } - - try comp.exit(); - return runner.fail("compiler failed to send terminating error_bundle", .{}); - }, - } - assert(contents_r.discardRemaining() catch |err| switch (err) { - error.ReadFailed => return error.ContentsReadFailed, - } == 0); - switch (cmd) { - .todo, .skip => {}, - else => update.allow_skip = false, - } -} - -fn spawnCompiler( - runner: *Runner, - compiler: *?Compiler, - src_dir: std.Io.Dir, - output_mode: std.lang.OutputMode, - args_r: *std.Io.Reader, - target: Compiler.Target, -) (std.Io.Reader.ShortError || TestError)!void { - const gpa = runner.gpa; - const arena = runner.arena.allocator(); - if (compiler.*) |*comp| { - try comp.exit(); - compiler.* = null; - } - - var argv: std.ArrayList([]const u8) = .empty; - defer argv.deinit(gpa); - try argv.appendSlice(gpa, &.{ - "zig", - switch (output_mode) { - .Obj => "build-obj", - .Exe => "build-exe", - .Lib => "build-lib", - }, - "-target", - target.triple, - "--cache-dir", - ".zig-cache", - }); - switch (target.cache_mode) { - .whole => {}, - .incremental => try argv.append(gpa, "-fincremental"), - } - var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; - if (target.backend) |b| switch (b) { - .sema => try argv.append(gpa, "-fno-emit-bin"), - .selfhosted => try argv.append(gpa, "-fno-llvm"), - .llvm => try argv.append(gpa, "-fllvm"), - .cbe => try argv.append(gpa, "-ofmt=c"), - } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. (runner.args.lib_dir orelse - return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{})) - .realPath(runner.io, &path_buffer) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to get lib path: {t}", .{e}), - }] }); - switch (target.linker) { - .lld => try argv.appendSlice(gpa, &.{ "-flld", "-fno-new-linker" }), - .old => try argv.appendSlice(gpa, &.{ "-fno-lld", "-fno-new-linker" }), - .new => try argv.appendSlice(gpa, &.{ "-fno-lld", "-fnew-linker" }), - } - switch (target.pic) { - .nopic => try argv.appendSlice(gpa, &.{ "-fno-PIC", "-fno-PIE" }), - .pic => try argv.appendSlice(gpa, &.{ "-fPIC", "-fno-PIE" }), - .pie => try argv.appendSlice(gpa, &.{ "-fPIC", "-fPIE" }), - } - - var need: usize = 1; - var state: enum { positional, @"--name" } = .positional; - var root_name: ?[]const u8 = null; - while (true) { - const done = if (args_r.fill(need)) false else |err| switch (err) { - error.ReadFailed => |e| return e, - error.EndOfStream => true, - }; - const buffered = args_r.buffered(); - const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len; - const end = std.mem.findAnyPos(u8, buffered, start, "\n ") orelse buffered.len; - if (end - start > 0) { - const arg = try arena.dupe(u8, buffered[start..end]); - switch (state) { - .positional => if (std.mem.eql(u8, arg, "--name")) { - state = .@"--name"; - } else if (std.mem.cutPrefix(u8, arg, "-M")) |module| { - var module_name_it = std.mem.splitScalar(u8, module, '='); - const module_name = module_name_it.next().?; - root_name = root_name orelse module_name; - }, - .@"--name" => { - root_name = arg; - state = .positional; - }, - } - try argv.append(gpa, arg); - args_r.toss(end); - need = 1; - } else { - args_r.toss(start); - need = buffered.len - start + 1; - } - if (done) break; - } - try argv.append(gpa, "--listen=-"); - - const out_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ - .root_name = root_name orelse "root", - .cpu_arch = target.resolved.cpu.arch, - .os_tag = target.resolved.os.tag, - .ofmt = target.resolved.ofmt, - .abi = target.resolved.abi, - .output_mode = output_mode, - }); - - var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; - const comp_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "compile {s}", .{ - out_name, - }) catch &name_buffer, 0); - errdefer comp_prog_node.end(); - - const child = std.process.spawn(runner.io, .{ - .exe = .{ .file = runner.args.zig_exe orelse - return runner.fail("missing \"--zig=path/to/zig\" arg", .{}) }, - .argv = argv.items, - .cwd = .{ .dir = src_dir }, - .progress_node = comp_prog_node, - .stdin = .pipe, - .stdout = .pipe, - .stderr = .pipe, - }) catch |err| switch (err) { - error.Canceled, error.OutOfMemory => |e| return e, - error.OperationUnsupported => return error.SkipTest, - else => |e| return runner.fail("unable to spawn compiler: {t}", .{e}), - }; - compiler.* = .{ - .runner = runner, - .prog_node = comp_prog_node, - .allow_compiler_stderr = true, - .output_mode = output_mode, - .state = .idle, - .child = child, - .fw = undefined, - .mr_buffer = undefined, - .mr = undefined, - .client = undefined, - .target = target, - .out_name = out_name, - .config = null, - }; - const comp = &compiler.*.?; - comp.fw = child.stdin.?.writerStreaming(runner.io, &.{}); - comp.mr.init(gpa, runner.io, comp.mr_buffer.toStreams(), &.{ - child.stdout.?, child.stderr.?, - }); - comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface }; -} -const Compiler = struct { - runner: *Runner, - prog_node: std.Progress.Node, - allow_compiler_stderr: bool, - output_mode: std.lang.OutputMode, - state: enum { idle, update }, - child: std.process.Child, - fw: std.Io.File.Writer, - mr_buffer: std.Io.File.MultiReader.Buffer(2), - mr: std.Io.File.MultiReader, - client: std.zig.Client, - target: Target, - out_name: []const u8, - config: ?std.zig.Server.Message.Config, - - const Target = struct { - triple: []const u8, - resolved: std.Target, - cache_mode: CacheMode, - backend: ?Backend, - linker: Linker, - pic: Pic, - - const CacheMode = enum { whole, incremental }; - - const Backend = enum { - /// Run semantic analysis only. Runtime output will not be tested, but we still verify - /// that compilation succeeds. Corresponds to `-fno-emit-bin`. - sema, - /// Use the self-hosted code generation backend for this target. - /// Corresponds to `-fno-llvm -fno-lld`. - selfhosted, - /// Use the LLVM backend. - /// Corresponds to `-fllvm -flld`. - llvm, - /// Use the C backend. The output is compiled with `zig cc`. - /// Corresponds to `-ofmt=c`. - cbe, - }; - - const Linker = enum { old, new, lld }; - - const Pic = enum { nopic, pic, pie }; - }; - - fn receiveMessage(comp: *Compiler) FailError!?struct { - tag: std.zig.Server.Message.Tag, - body: []const u8, - } { - const header = comp.client.receiveMessageWithMultiReader( - &comp.mr, - .none, - ) catch |err| switch (err) { - error.Canceled, error.OutOfMemory => |e| return e, - error.Timeout => unreachable, - error.EndOfStream => return null, - else => |e| return comp.runner.fail("unable to receive message: {t}", .{e}), - }; - const body = comp.client.in.take(header.bytes_len) catch unreachable; - const stderr = comp.mr.reader(1); - if (stderr.bufferedLen() > 0) { - if (comp.allow_compiler_stderr) { - std.log.info("{t} stderr:\n{s}", .{ header.tag, stderr.buffered() }); - } else { - return comp.runner.fail("{t} unexpected stderr:\n{s}", .{ - header.tag, stderr.buffered(), - }); - } - stderr.tossBuffered(); - } - return .{ .tag = header.tag, .body = body }; - } - - const Check = enum { errors, stdout, exit, gdb, lldb }; - fn checkSuccess( - comp: *Compiler, - check: Check, - expected: []const u8, - src_dir: std.Io.Dir, - digest: *const std.Build.Cache.BinDigest, - ) TestError!void { - const runner = comp.runner; - const gpa = runner.gpa; - const arena = runner.arena.allocator(); - const io = runner.io; - const target = &comp.target.resolved; - const config = &comp.config.?; - var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; - - // After a failure while running a script, the debugger starts accepting commands from stdin, and - // because it is empty, the debugger exits normally with status 0. Choose a non-zero status to - // return from the debugger script instead to detect it running to completion and indicate success. - const debug_success = 99; - - const tmp_dir_path = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ "tmp"; - src_dir.createDirPath(io, tmp_dir_path) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to create dir path {q}: {t}", .{ - tmp_dir_path, e, - }), - }; - - var argv: std.ArrayList([]const u8) = .initBuffer(try arena.alloc([]const u8, 5 + 4)); - var environ_map: std.process.Environ.Map = .init(gpa); - defer environ_map.deinit(); - try environ_map.putAll(&runner.environ_map); - var script_buffer: [512]u8 = undefined; - const script_path, const script_file, var script_fw = script: switch (check) { - .errors => return, - .stdout, .exit => .{ undefined, null, undefined }, - .gdb => { - const script_path = tmp_dir_path ++ std.Io.Dir.path.sep_str ++ "gdb"; - argv.appendSliceAssumeCapacity(&.{ "gdb", "--batch", "--command", script_path, "--args" }); - const script_file = src_dir.createFile(io, script_path, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to create debugger script {q}: {t}", .{ - script_path, e, - }), - }; - errdefer script_file.close(io); - var script_fw = script_file.writer(io, &script_buffer); - script_fw.interface.writeAll( - \\set remotetimeout 0 - \\ - \\ - ) catch |err| switch (err) { - error.WriteFailed => switch (script_fw.err.?) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to write debugger script {q}: {t}", .{ script_path, e }), - }, - }; - break :script .{ script_path, script_file, script_fw }; - }, - .lldb => { - const script_path = tmp_dir_path ++ std.Io.Dir.path.sep_str ++ "lldb"; - argv.appendSliceAssumeCapacity(&.{ "lldb", "--batch", "--source", script_path, "--" }); - try environ_map.put("LANG", "C.UTF-8"); // affects output formatting - const script_file = src_dir.createFile(io, script_path, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to create debugger script {q}: {t}", .{ - script_path, - e, - }), - }; - errdefer script_file.close(io); - var script_fw = script_file.writer(io, &script_buffer); - script_fw.interface.writeAll( - \\settings set frame-format 'frame #${frame.index}:{ ${module.file.basename}{\`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${line.file.basename}:${line.number}{:${line.column}}}{${function.is-optimized} [opt]}{${frame.is-artificial} [artificial]}\n' - \\settings set plugin.process.gdb-remote.packet-timeout 0 - \\settings set stop-line-count-after 0 - \\settings set stop-line-count-before 0 - \\ - ) catch |err| switch (err) { - error.WriteFailed => switch (script_fw.err.?) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to write debugger script {q}: {t}", .{ script_path, e }), - }, - }; - break :script .{ script_path, script_file, script_fw }; - }, - }; - defer if (script_file) |file| file.close(io); - - const out_dir_path = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ - "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*); - const out_path = try std.Io.Dir.path.join(arena, &.{ out_dir_path, comp.out_name }); - const bin_path = switch (comp.target.backend.?) { - .sema => return, - .selfhosted, .llvm => out_path, - .cbe => try comp.compileC(src_dir, out_path), - }; - - const need_cross_libc = - target.os.tag == .linux and config.flags.link_libc and config.flags.link_mode == .dynamic; - const use_executor = use_executor: switch (std.zig.system.getExternalExecutor(io, target, .{ - .host_cpu_arch = runner.host.cpu.arch, - .host_os_tag = runner.host.os.tag, - .qemu_fixes_dl = need_cross_libc and runner.args.libc_runtimes_dir != null, - .link_mode = config.flags.link_mode, - .link_libc = config.flags.link_libc, - })) { - .bad_dl, .bad_os_or_cpu => { - // This binary cannot be executed on this host. - if (!runner.args.quiet) std.log.warn("skipping execution because host {q} cannot " ++ - "execute binaries for foreign target {q}", .{ - try runner.host.zigTriple(arena), comp.target.triple, - }); - return; - }, - .native => false, - .darling => |executor| if (runner.args.enable_darling) { - argv.appendAssumeCapacity(executor); - break :use_executor true; - } else continue :use_executor .bad_os_or_cpu, - .qemu => |executor| if (runner.args.enable_qemu) { - argv.appendAssumeCapacity(executor); - if (need_cross_libc) { - const libc_runtimes_dir = runner.args.libc_runtimes_dir orelse - continue :use_executor .bad_os_or_cpu; - const libc_runtimes_path = path_buffer[0 .. libc_runtimes_dir.realPath( - io, - &path_buffer, - ) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to get libc runtimes path: {t}", .{e}), - }]; - argv.appendSliceAssumeCapacity(&.{ "-L", try std.Io.Dir.path.join(arena, &.{ - libc_runtimes_path, - try if (target.isGnuLibC()) std.zig.target.glibcRuntimeTriple( - arena, - target.cpu.arch, - target.os.tag, - target.abi, - ) else if (target.isMuslLibC()) std.zig.target.muslRuntimeTriple( - arena, - target.cpu.arch, - target.abi, - ) else unreachable, - }) }); - } - break :use_executor true; - } else continue :use_executor .bad_os_or_cpu, - .rosetta => if (runner.args.enable_rosetta) - true - else - continue :use_executor .bad_os_or_cpu, - .wine => |executor| if (runner.args.enable_wine) { - if (!environ_map.contains("WINEDEBUG")) try environ_map.put("WINEDEBUG", "-all"); - argv.appendAssumeCapacity(executor); - break :use_executor true; - } else continue :use_executor .bad_os_or_cpu, - .wasmtime => |executor| if (runner.args.enable_wasmtime) { - if (!environ_map.contains("WASMTIME_BACKTRACE_DETAILS")) - try environ_map.put("WASMTIME_BACKTRACE_DETAILS", "1"); - argv.appendSliceAssumeCapacity(&.{ executor, "--dir=.", "-Sinherit-env" }); - break :use_executor true; - } else continue :use_executor .bad_os_or_cpu, - }; - argv.appendAssumeCapacity(bin_path); - - if (!runner.args.quiet) std.log.info("{f}", .{std.zig.SubprocessCommand{ - .argv = argv.items, - .cwd = cwd_path: { - break :cwd_path path_buffer[0 .. src_dir.realPath(io, &path_buffer) catch |err| switch (err) { - error.Canceled => |e| return e, - else => break :cwd_path null, - }]; - }, - .parent_env = &runner.environ_map, - .child_env = &environ_map, - }}); - - var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; - const run_prog_node = runner.prog_node.start(std.mem.print(&name_buffer, "run {s}", .{ - comp.out_name, - }) catch &name_buffer, 0); - defer run_prog_node.end(); - - const bin_file = src_dir.openFile(io, bin_path, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to open {q}: {t}", .{ bin_path, e }), - }; - defer bin_file.close(io); - const result = std.process.run(gpa, io, .{ - .exe = exe: switch (check) { - .errors => unreachable, - .stdout, .exit => if (use_executor) .search else .{ .file = bin_file }, - .gdb, .lldb => { - if (use_executor) { - if (!runner.args.quiet) std.log.warn( - "skipping debugging of {q} via executor for foreign target {q}", - .{ bin_path, comp.target.triple }, - ); - return; - } - - var line_it = std.mem.splitScalar(u8, expected, '\n'); - while (line_it.next()) |line| if (std.mem.cutPrefix(u8, line, switch (check) { - else => unreachable, - .gdb => "(gdb) ", - .lldb => "(lldb) ", - })) |cmd| script_fw.interface.print( - \\{s} - \\ - , .{cmd}) catch |err| switch (err) { - error.WriteFailed => switch (script_fw.err.?) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to write debugger script {q}: {t}", .{ - script_path, e, - }), - }, - }; - script_fw.interface.print( - \\ - \\quit {d} - \\ - , .{debug_success}) catch |err| switch (err) { - error.WriteFailed => switch (script_fw.err.?) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to write debugger script {q}: {t}", .{ - script_path, e, - }), - }, - }; - script_fw.flush() catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to write debugger script {q}: {t}", .{ - script_path, e, - }), - }; - break :exe .{ .file = switch (check) { - else => unreachable, - .gdb => runner.args.gdb_exe, - .lldb => runner.args.lldb_exe, - } orelse { - if (!runner.args.quiet) std.log.warn( - "skipping debugging of {q} because of missing \"--{t}=path/to/{[1]t}\" arg", - .{ bin_path, check }, - ); - return; - } }; - }, - }, - .argv = argv.items, - .cwd = .{ .dir = src_dir }, - .environ_map = &environ_map, - .progress_node = run_prog_node, - }) catch |err| if (use_executor) { - // Chances are the foreign executor isn't available. Skip this evaluation. - if (!runner.args.quiet) std.log.warn( - "skipping execution of {q} via executor for foreign target {q}: {t}", - .{ bin_path, comp.target.triple, err }, - ); - return; - } else return runner.fail("unable to run the generated executable {q}: {t}", .{ - bin_path, err, - }); - defer { - gpa.free(result.stdout); - gpa.free(result.stderr); - } - switch (result.term) { - .exited => |code| switch (check) { - .errors => unreachable, - .stdout => { - if (code != 0) return runner.fail( - "generated executable {q} failed with code {d}", - .{ bin_path, code }, - ); - std.testing.expectEqualStrings( - expected, - result.stdout, - ) catch |err| switch (err) { - error.TestExpectedEqual => return runner.fail("stdout did not match expected", .{}), - }; - }, - .exit => { - var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 = undefined; - std.testing.expectEqualStrings( - expected, - std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable, - ) catch |err| switch (err) { - error.TestExpectedEqual => return runner.fail( - "exit code did not match expected", - .{}, - ), - }; - }, - .gdb, .lldb => { - if (code != debug_success) return runner.fail( - "debugging {q} failed with code {d}", - .{ bin_path, code }, - ); - var expected_match_it = std.mem.splitSequence(u8, expected, "#..."); - var pos: usize = 0; - while (expected_match_it.next()) |expected_match| : (pos += expected_match.len) { - var context_len: usize = expected_match.len; - pos = while (true) : (context_len -= 1) break std.mem.findPos( - u8, - result.stdout, - pos, - expected_match[0..context_len], - ) orelse continue; - const actual_match = result.stdout[pos..]; - std.testing.expectEqualStrings( - expected_match, - actual_match[0..@min(actual_match.len, expected_match.len)], - ) catch |err| switch (err) { - error.TestExpectedEqual => { - std.testing.failPrint("\n=============== full output: =========\n", .{}); - std.testing.failPrintWithVisibleNewlines(actual_match); - std.testing.failPrint("\n======================================\n", .{}); - return runner.fail("stdout did not match expected", .{}); - }, - }; - } - }, - }, - .signal => |sig| return runner.fail( - "generated executable {q} terminated with signal {t}", - .{ bin_path, sig }, - ), - .stopped => |sig| return runner.fail( - "generated executable {q} stopped with signal {t}", - .{ bin_path, sig }, - ), - .unknown => return runner.fail( - "generated executable {q} terminated unexpectedly", - .{bin_path}, - ), - } - if (!use_executor and result.stderr.len > 0) { - std.log.err("generated executable {q} had unexpected stderr:\n{s}", .{ - bin_path, result.stderr, - }); - } - } - - fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) TestError![]const u8 { - const runner = parent_comp.runner; - const arena = runner.arena.allocator(); - var compiler: ?Compiler = null; - var args_r: std.Io.Reader = .fixed(c_path); - parent_comp.runner.spawnCompiler(&compiler, src_dir, parent_comp.output_mode, &args_r, .{ - .triple = parent_comp.target.triple, - .resolved = resolved: { - var resolved = parent_comp.target.resolved; - resolved.ofmt = .default(resolved.os.tag, resolved.cpu.arch); - break :resolved resolved; - }, - .cache_mode = .whole, - .backend = null, - .linker = .lld, - .pic = parent_comp.target.pic, - }) catch |err| switch (err) { - error.Canceled, error.OutOfMemory, error.AlreadyReported, error.SkipTest => |e| return e, - error.ReadFailed => unreachable, // .fixed - }; - const comp = &compiler.?; - comp.client.serveBodylessMessage(.update) catch |err| switch (err) { - error.WriteFailed => switch (comp.fw.err.?) { - error.Canceled => |e| return e, - else => |e| return runner.fail("unable to send message: {t}", .{e}), - }, - }; - comp.state = .update; - var out_path: ?[]const u8 = null; - while (try comp.receiveMessage()) |message| switch (message.tag) { - else => {}, // Ignore other messages, - .config => { - var body_r: std.Io.Reader = .fixed(message.body); - comp.config = - body_r.takeStruct(std.zig.Server.Message.Config, .little) catch unreachable; - }, - .emit_digest => { - var body_r: std.Io.Reader = .fixed(message.body); - _ = body_r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; - const digest = body_r.takeArray(std.Build.Cache.bin_digest_len) catch unreachable; - - const out_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ - "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*); - out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name }); - }, - .error_bundle => { - const eb = std.zig.Server.allocErrorBundle( - runner.arena.allocator(), - message.body, - ) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.EndOfStream => return runner.fail("error bundle truncated", .{}), - }; - if (eb.errorMessageCount() > 0) { - try runner.eb_wip.addBundleAsRoots(eb); - return runner.fail("unexpected compile errors", .{}); - } - comp.state = .idle; - break; - }, - }; - try comp.exit(); - return out_path.?; - } - - fn exit(comp: *Compiler) FailError!void { - const runner = comp.runner; - comp.client.serveBodylessMessage(.exit) catch |err| switch (err) { - error.WriteFailed => switch (comp.fw.err.?) { - error.Canceled => |e| return e, - error.BrokenPipe => {}, - else => |e| return comp.runner.fail("unable to send exit: {t}", .{e}), - }, - }; - comp.child.stdin.?.close(runner.io); - comp.child.stdin = null; - while (try comp.receiveMessage()) |_| {} - if (comp.client.in.bufferedLen() > 0) return runner.fail("unable to receive messages", .{}); - const term = comp.child.wait(runner.io) catch |err| - return runner.fail("child process failed: {t}", .{err}); - comp.prog_node.end(); - switch (term) { - .exited => |code| if (code != 0) - return runner.fail("compiler failed with code {d}", .{code}), - .signal => |sig| return runner.fail("compiler terminated with signal {t}", .{sig}), - .stopped => |sig| return runner.fail("compiler stopped unexpectedly with signal {t}", .{ - sig, - }), - .unknown => return runner.fail("compiler terminated unexpectedly", .{}), - } - } -}; - -const ClientArgs = struct { - test_file: std.Io.File, - test_kind: std.Io.File.Kind, - zig_exe: std.Io.File, - lib_dir: std.Io.Dir, - src_dir: std.Io.Dir, - targets: std.ArrayList([]const u8), - - libc_runtimes_dir: ?std.Io.Dir, - enable_darling: bool, - enable_qemu: bool, - enable_rosetta: bool, - enable_wine: bool, - enable_wasmtime: bool, - - gdb_exe: ?std.Io.File, - lldb_exe: ?std.Io.File, - - quiet: bool, - keep_src: bool, -}; -pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelable)!u8 { - const fatal = struct { - fn fatal(comptime fmt: []const u8, args: anytype) noreturn { - std.log.err(fmt, args); - std.process.exit(1); - } - }.fatal; - const arena = init.arena.allocator(); - - var test_path_arg: ?[]const u8 = null; - var zig_path_arg: ?[]const u8 = null; - var lib_path_arg: ?[]const u8 = null; - var libc_runtimes_path_arg: ?[]const u8 = null; - var gdb_path_arg: ?[]const u8 = null; - var lldb_path_arg: ?[]const u8 = null; - var args: ClientArgs = .{ - .test_file = undefined, - .test_kind = undefined, - .zig_exe = undefined, - .lib_dir = undefined, - .src_dir = undefined, - .targets = .empty, - - .libc_runtimes_dir = null, - .enable_darling = false, - .enable_qemu = false, - .enable_rosetta = false, - .enable_wine = false, - .enable_wasmtime = false, - - .gdb_exe = null, - .lldb_exe = null, - - .quiet = false, - .keep_src = false, - }; - defer args.targets.deinit(init.gpa); - - var arg_it = try init.minimal.args.iterateAllocator(arena); - const self = arg_it.next().?; - while (arg_it.next()) |arg| { - if (std.mem.eql(u8, arg, "--listen=-")) { - var stdin_buffer: [512]u8 = undefined; - var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer); - var stdout_buffer: [512]u8 = undefined; - var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer); - var runner: Runner = .{ - .gpa = init.gpa, - .arena = init.arena, - .environ_map = init.minimal.environ.createMap(init.gpa) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - else => |e| fatal("unable to get environment: {t}", .{e}), - }, - .io = init.io, - .prog_node = std.Progress.start(init.io, .{}), - .args = .{ - .@"test" = null, - .manifest_file = null, - .zig_exe = null, - .lib_dir = null, - .src_dir = null, - .targets = .empty, - .target_bytes = .empty, - - .libc_runtimes_dir = null, - .enable_darling = false, - .enable_qemu = false, - .enable_rosetta = false, - .enable_wasmtime = false, - .enable_wine = false, - - .gdb_exe = null, - .lldb_exe = null, - - .quiet = false, - }, - .host = std.zig.system.resolveTargetQuery(init.io, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| fatal("unable to resolve host target: {t}", .{e}), - }, - .server = .{ - .in = &stdin.interface, - .out = &stdout.interface, - }, - .eb_wip = try .init(init.gpa), - }; - defer runner.deinit(); - switch (runner.runServer()) { - error.Canceled, error.OutOfMemory => |e| return e, - error.ServerReadFailed => switch (stdin.err.?) { - error.Canceled => |e| return e, - else => |e| fatal("unable to receive message: {t}", .{e}), - }, - error.ServerWriteFailed => switch (stdout.err.?) { - error.Canceled => |e| return e, - else => |e| fatal("unable to send message: {t}", .{e}), - }, - error.ServerEndOfStream => fatal("no more messages", .{}), - error.AlreadyReported => { - var eb = try runner.eb_wip.toOwnedBundle(""); - defer eb.deinit(init.gpa); - eb.renderToStderr(runner.io, .{}, .auto) catch |err| switch (err) { - error.Canceled => |e| return e, - else => {}, - }; - std.process.exit(1); - }, - } - } else if (std.mem.eql(u8, arg, "--zig")) { - zig_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); - } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| { - zig_path_arg = zig_path; - } else if (std.mem.eql(u8, arg, "--lib")) { - lib_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); - } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| { - lib_path_arg = lib_path; - } else if (std.mem.eql(u8, arg, "--target")) { - try args.targets.append( - init.gpa, - try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after {q}", .{arg})), - ); - } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| { - try args.targets.append(init.gpa, try arena.dupe(u8, target)); - } else if (std.mem.eql(u8, arg, "--libc-runtimes")) { - libc_runtimes_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); - } else if (std.mem.cutPrefix(u8, arg, "--libc-runtimes=")) |libc_runtimes_path| { - libc_runtimes_path_arg = libc_runtimes_path; - } else if (std.mem.eql(u8, arg, "-fdarling")) { - args.enable_darling = true; - } else if (std.mem.eql(u8, arg, "-fqemu")) { - args.enable_qemu = true; - } else if (std.mem.eql(u8, arg, "-frosetta")) { - args.enable_rosetta = true; - } else if (std.mem.eql(u8, arg, "-fwine")) { - args.enable_wine = true; - } else if (std.mem.eql(u8, arg, "-fwasmtime")) { - args.enable_wasmtime = true; - } else if (std.mem.eql(u8, arg, "--gdb")) { - gdb_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); - } else if (std.mem.cutPrefix(u8, arg, "--gdb=")) |gdb_path| { - gdb_path_arg = gdb_path; - } else if (std.mem.eql(u8, arg, "--lldb")) { - lldb_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); - } else if (std.mem.cutPrefix(u8, arg, "--lldb=")) |lldb_path| { - lldb_path_arg = lldb_path; - } else if (std.mem.eql(u8, arg, "--quiet")) { - args.quiet = true; - } else if (std.mem.eql(u8, arg, "--keep-src")) { - args.keep_src = true; - } else { - if (test_path_arg) |_| fatal("unexpected arg {q}", .{arg}); - test_path_arg = arg; - } - } - - const test_path = test_path_arg orelse fatal("missing \"path/to/test\" arg", .{}); - const zig_path = zig_path_arg orelse fatal("missing \"--zig=path/to/zig\" arg", .{}); - const lib_path = lib_path_arg orelse fatal("missing \"--lib=path/to/lib\" arg", .{}); - - const cwd: std.Io.Dir = .cwd(); - args.test_file = cwd.openFile(init.io, test_path, .{ - .allow_directory = true, - }) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| fatal("unable to open {q}: {t}", .{ test_path, e }), - }; - defer args.test_file.close(init.io); - args.test_kind = (args.test_file.stat(init.io) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| fatal("unable to stat {q}: {t}", .{ test_path, e }), - }).kind; - args.zig_exe = cwd.openFile(init.io, zig_path, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| fatal("unable to open {q}: {t}", .{ zig_path, e }), - }; - defer args.zig_exe.close(init.io); - args.lib_dir = cwd.openDir(init.io, lib_path, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }), - }; - defer args.lib_dir.close(init.io); - if (libc_runtimes_path_arg) |libc_runtimes_path| args.libc_runtimes_dir = cwd.openDir( - init.io, - libc_runtimes_path, - .{}, - ) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| fatal("unable to open {q}: {t}", .{ lib_path, e }), - }; - defer if (args.libc_runtimes_dir) |libc_runtimes_dir| libc_runtimes_dir.close(init.io); - if (gdb_path_arg) |gdb_path| args.gdb_exe = cwd.openFile(init.io, gdb_path, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| fatal("unable to open {q}: {t}", .{ gdb_path, e }), - }; - defer if (args.gdb_exe) |gdb_exe| gdb_exe.close(init.io); - if (lldb_path_arg) |lldb_path| args.lldb_exe = cwd.openFile(init.io, lldb_path, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| fatal("unable to open {q}: {t}", .{ lldb_path, e }), - }; - defer if (args.lldb_exe) |lldb_exe| lldb_exe.close(init.io); - - const src_dir_path = "src_" ++ std.fmt.hex(rand_int: { - var rand_int: u64 = undefined; - init.io.random(@ptrCast(&rand_int)); - break :rand_int rand_int; - }); - args.src_dir = cwd.createDirPathOpen(init.io, src_dir_path, .{}) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| fatal("unable to create {q}: {t}", .{ src_dir_path, e }), - }; - defer { - args.src_dir.close(init.io); - if (!args.keep_src) cwd.deleteTree(init.io, src_dir_path) catch |err| { - std.log.warn("unable to delete tree {q}: {t}", .{ src_dir_path, err }); - }; - } - - var inherit_dirs: std.ArrayList(std.Io.Dir) = .initBuffer(try arena.alloc(std.Io.Dir, 3)); - var inherit_files: std.ArrayList(std.Io.File) = .initBuffer(try arena.alloc(std.Io.File, 4)); - inherit_files.appendSliceAssumeCapacity(&.{ args.test_file, args.zig_exe }); - inherit_dirs.appendSliceAssumeCapacity(&.{ args.lib_dir, args.src_dir }); - if (args.libc_runtimes_dir) |libc_runtimes_dir| inherit_dirs.appendAssumeCapacity(libc_runtimes_dir); - if (args.gdb_exe) |gdb_exe| inherit_files.appendAssumeCapacity(gdb_exe); - if (args.lldb_exe) |lldb_exe| inherit_files.appendAssumeCapacity(lldb_exe); - var child = std.process.spawn(init.io, .{ - .argv = &.{ self, "--listen=-" }, - .stdin = .pipe, - .stdout = .pipe, - .inherit_dirs = inherit_dirs.items, - .inherit_files = inherit_files.items, - }) catch |err| switch (err) { - error.Canceled, error.OutOfMemory => |e| return e, - else => |e| fatal("unable to spawn runner: {t}", .{e}), - }; - - var child_reader_buffer: [512]u8 = undefined; - var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer); - var child_writer_buffer: [512]u8 = undefined; - var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer); - var client: std.zig.Client = .{ - .in = &child_reader.interface, - .out = &child_writer.interface, - }; - const success = runClient(init.gpa, arena, init.io, &client, &args) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.ReadFailed => switch (child_reader.err.?) { - error.Canceled => |e| return e, - else => |e| fatal("unable to receive message: {t}", .{e}), - }, - error.WriteFailed => switch (child_writer.err.?) { - error.Canceled => |e| return e, - else => |e| fatal("unable to send message: {t}", .{e}), - }, - error.EndOfStream => fatal("no more messages", .{}), - }; - _ = child.wait(init.io) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| fatal("unable to wait for runner: {t}", .{e}), - }; - return if (success) 0 else 1; -} -fn runClient( - gpa: std.mem.Allocator, - arena: std.mem.Allocator, - io: std.Io, - client: *std.zig.Client, - args: *const ClientArgs, -) (std.mem.Allocator.Error || std.Io.Reader.Error || std.Io.Writer.Error)!bool { - var stdout_buffer: [512]u8 = undefined; - var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer); - const stdout = &stdout_fw.interface; - var stderr_buffer: [512]u8 = undefined; - var stderr_fw: std.Io.File.Writer = .initStreaming(.stderr(), io, &stderr_buffer); - const stderr = &stderr_fw.interface; - - var argv: std.ArrayList(u8) = .empty; - defer argv.deinit(gpa); - const Arg = std.zig.Client.Message.Arg; - - try argv.append(gpa, @backingInt(@as(Arg, switch (args.test_kind) { - else => unreachable, - .file => .input_file, - .directory => .input_dir, - }))); - try argv.appendSlice(gpa, @ptrCast(&args.test_file.handle)); - - try argv.append(gpa, @backingInt(Arg.prefix)); - try argv.appendSlice(gpa, "--zig="); - try argv.append(gpa, 0); - try argv.append(gpa, @backingInt(Arg.input_file)); - try argv.appendSlice(gpa, @ptrCast(&args.zig_exe.handle)); - - try argv.append(gpa, @backingInt(Arg.prefix)); - try argv.appendSlice(gpa, "--lib="); - try argv.append(gpa, 0); - try argv.append(gpa, @backingInt(Arg.input_dir)); - try argv.appendSlice(gpa, @ptrCast(&args.lib_dir.handle)); - - try argv.append(gpa, @backingInt(Arg.prefix)); - try argv.appendSlice(gpa, "--src="); - try argv.append(gpa, 0); - try argv.append(gpa, @backingInt(Arg.output_dir)); - try argv.appendSlice(gpa, @ptrCast(&args.src_dir.handle)); - - for (args.targets.items) |target| { - try argv.append(gpa, @backingInt(Arg.string)); - try argv.appendSlice(gpa, "--target"); - try argv.append(gpa, 0); - - try argv.append(gpa, @backingInt(Arg.string)); - try argv.appendSlice(gpa, target); - try argv.append(gpa, 0); - } - - if (args.libc_runtimes_dir) |libc_runtimes_dir| { - try argv.append(gpa, @backingInt(Arg.prefix)); - try argv.appendSlice(gpa, "--libc-runtimes="); - try argv.append(gpa, 0); - try argv.append(gpa, @backingInt(Arg.input_dir)); - try argv.appendSlice(gpa, @ptrCast(&libc_runtimes_dir.handle)); - } - if (args.enable_darling) { - try argv.append(gpa, @backingInt(Arg.string)); - try argv.appendSlice(gpa, "-fdarling"); - try argv.append(gpa, 0); - } - if (args.enable_qemu) { - try argv.append(gpa, @backingInt(Arg.string)); - try argv.appendSlice(gpa, "-fqemu"); - try argv.append(gpa, 0); - } - if (args.enable_rosetta) { - try argv.append(gpa, @backingInt(Arg.string)); - try argv.appendSlice(gpa, "-frosetta"); - try argv.append(gpa, 0); - } - if (args.enable_wine) { - try argv.append(gpa, @backingInt(Arg.string)); - try argv.appendSlice(gpa, "-fwine"); - try argv.append(gpa, 0); - } - if (args.enable_wasmtime) { - try argv.append(gpa, @backingInt(Arg.string)); - try argv.appendSlice(gpa, "-fwasmtime"); - try argv.append(gpa, 0); - } - - if (args.gdb_exe) |gdb_exe| { - try argv.append(gpa, @backingInt(Arg.prefix)); - try argv.appendSlice(gpa, "--gdb="); - try argv.append(gpa, 0); - try argv.append(gpa, @backingInt(Arg.input_file)); - try argv.appendSlice(gpa, @ptrCast(&gdb_exe.handle)); - } - if (args.lldb_exe) |lldb_exe| { - try argv.append(gpa, @backingInt(Arg.prefix)); - try argv.appendSlice(gpa, "--lldb="); - try argv.append(gpa, 0); - try argv.append(gpa, @backingInt(Arg.input_file)); - try argv.appendSlice(gpa, @ptrCast(&lldb_exe.handle)); - } - - if (args.quiet) { - try argv.append(gpa, @backingInt(Arg.string)); - try argv.appendSlice(gpa, "--quiet"); - try argv.append(gpa, 0); - } - - try client.serveMessageHeader(.{ - .tag = .args, - .bytes_len = @intCast(argv.items.len), - }); - try client.out.writeAll(argv.items); - - var success = true; - var metadata: struct { - index: u32, - len: u32, - names: []u32, - expected_panic_msgs: []u32, - string_bytes: []u8, - - fn name(m: *@This()) []const u8 { - return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0); - } - - fn expectedPanicMsg(m: *@This()) ?[:0]const u8 { - return switch (m.expected_panic_msgs[m.index - 1]) { - 0 => null, - else => |offset| m.string_bytes[offset..std.mem.findScalarPos( - u8, - m.string_bytes, - offset, - 0, - ).? :0], - }; - } - - fn next(m: *@This()) ?u32 { - if (m.len - m.index == 0) return null; - defer m.index += 1; - return m.index; - } - } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} }; - try client.serveBodylessMessage(.query_test_metadata); - while (true) { - const hdr = try client.receiveMessage(); - switch (hdr.tag) { - else => try client.in.discardAll(hdr.bytes_len), - .zig_version => { - const actual_version = try client.in.take(hdr.bytes_len); - if (!std.mem.eql(u8, builtin.zig_version_string, actual_version)) { - try stderr.print( - "error: zig version mismatch compiler test runner vs compiler: {q} vs {q}\n", - .{ builtin.zig_version_string, actual_version }, - ); - try stderr.flush(); - std.process.exit(1); - } - }, - .error_bundle => { - const body = try arena.alloc(u8, hdr.bytes_len); - try client.in.readSliceAll(body); - const eb = std.zig.Server.allocErrorBundle(arena, body) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - error.EndOfStream => { - try stderr.writeAll("error bundle truncated\n"); - try stderr.flush(); - std.process.exit(1); - }, - }; - success = false; - eb.renderToStderr(io, .{}, .auto) catch {}; - }, - .test_metadata => { - const tm_hdr = - try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little); - metadata = .{ - .index = 0, - .len = tm_hdr.tests_len, - .names = try arena.alloc(u32, tm_hdr.tests_len), - .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len), - .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len), - }; - try client.in.readSliceEndian(u32, metadata.names, .little); - try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little); - try client.in.readSliceAll(metadata.string_bytes); - try client.serveRunTest(metadata.next() orelse break); - }, - .test_results => { - const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little); - switch (tr.flags.status) { - .pass, .skip => {}, - .fail => success = false, - } - try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() }); - try stdout.flush(); - try client.serveRunTest(metadata.next() orelse break); - }, - } - } - try client.serveBodylessMessage(.exit); - return success; -} diff --git a/test/tests.zig b/test/tests.zig index ba0079b752ffb808a2cebe89fb5776e1663aa34f..c65cbcee859aee2911ac5bef1027a5db5485bddf 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2201,137 +2201,6 @@ const RunnerTarget = struct { pic: enum { nopic, pic, pie }, }; -pub const incremental_matrix: []const RunnerTarget = &.{ - // Avoid adding more CBE or LLVM targets without good reason: they're a lot slower than others - // to run due to the output (C source code or LLVM IR) being built non-incrementally (by Clang - // or LLVM). We just have a couple here to make sure that it works. - .{ - .target = .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .musl, - }, - .cache_mode = .incremental, - .backend = .cbe, - .linker = .old, - .pic = .nopic, - }, - .{ - .target = .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .none, - }, - .cache_mode = .incremental, - .backend = .llvm, - .linker = .lld, - .pic = .nopic, - }, - - .{ - .target = .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .none, - }, - .cache_mode = .incremental, - .backend = .selfhosted, - .linker = .new, - .pic = .nopic, - }, - .{ - .target = .{ - .cpu_arch = .x86_64, - .os_tag = .windows, - .abi = .none, - }, - .cache_mode = .incremental, - .backend = .selfhosted, - .linker = .old, - .pic = .pic, - }, - .{ - .target = .{ - .cpu_arch = .wasm32, - .os_tag = .wasi, - .abi = .none, - }, - .cache_mode = .incremental, - .backend = .selfhosted, - .linker = .old, - .pic = .nopic, - }, -}; - -pub const debugger_matrix: []const RunnerTarget = &.{ - .{ - .target = .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .none, - }, - .cache_mode = .whole, - .backend = .selfhosted, - .linker = .old, - .pic = .nopic, - }, - .{ - .target = .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .none, - }, - .cache_mode = .whole, - .backend = .selfhosted, - .linker = .old, - .pic = .pic, - }, - .{ - .target = .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .none, - }, - .cache_mode = .whole, - .backend = .selfhosted, - .linker = .new, - .pic = .nopic, - }, - .{ - .target = .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .none, - }, - .cache_mode = .whole, - .backend = .selfhosted, - .linker = .new, - .pic = .pic, - }, - .{ - .target = .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .none, - }, - .cache_mode = .incremental, - .backend = .selfhosted, - .linker = .new, - .pic = .nopic, - }, - .{ - .target = .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .none, - }, - .cache_mode = .incremental, - .backend = .selfhosted, - .linker = .new, - .pic = .pic, - }, -}; - pub fn addStackTraceTests(b: *std.Build, options: StackTracesContext.Options) *Step { const step = b.step("test-stack-traces", "Run the stack trace tests"); @@ -3224,21 +3093,24 @@ pub fn addRunnerTests( defer tests_dir.close(b.graph.io); var test_it = tests_dir.iterate(); while (try test_it.next(b.graph.io)) |@"test"| { + switch (@"test".kind) { + else => continue, + .file => { + if (std.mem.eql(u8, @"test".name, "matrix.zon")) continue; + if (isEditorFileName(@"test".name)) continue; + }, + .directory => {}, + } + for (options.test_filters) |filter| { if (std.mem.find(u8, @"test".name, filter) != null) break; } else if (options.test_filters.len > 0) continue; - const test_path = tests_path.path(b, @"test".name); - switch (@"test".kind) { - else => continue, - .file => if (isEditorFileName(@"test".name)) continue, - .directory => {}, - } - const run = b.addRunArtifact(runner); run.setName(@"test".name); run.enableProtocolMode(); + const test_path = tests_path.path(b, @"test".name); switch (@"test".kind) { else => continue, .file => run.addFileArg(test_path),