diff --git a/lib/compiler/Maker.zig b/lib/compiler/Maker.zig index 739ddd0bb8183c8d9040f73fc11c1341054ff828..61b99159cecbfc9603b04e716d97ba448a431248 100644 --- a/lib/compiler/Maker.zig +++ b/lib/compiler/Maker.zig @@ -4125,8 +4125,7 @@ const Fork = struct { fork.arena_allocator = .init(gpa); const arena = fork.arena_allocator.allocator(); - var error_bundle: std.zig.ErrorBundle.Wip = undefined; - try error_bundle.init(gpa); + var error_bundle: std.zig.ErrorBundle.Wip = try .init(gpa); defer error_bundle.deinit(); const manifest_path = try fork.path.join(arena, Package.Manifest.basename); @@ -4227,8 +4226,7 @@ fn loadManifest( errdefer manifest.deinit(gpa); if (manifest.errors.len > 0) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); const src_path = try wip_errors.addString(Package.Manifest.basename); diff --git a/lib/compiler/Maker/Fetch.zig b/lib/compiler/Maker/Fetch.zig index 5d3e80e25ee3becf1b308aa4917fe6a5c92781b2..21b7b2597c3e33cf7f10e31c82c82bbf052d2785 100644 --- a/lib/compiler/Maker/Fetch.zig +++ b/lib/compiler/Maker/Fetch.zig @@ -527,7 +527,7 @@ pub fn run(f: *Fetch) RunError!void { const arena = f.arena.allocator(); const gpa = f.arena.child_allocator; - try eb.init(gpa); + eb.* = try .init(gpa); // Check the global zig package cache to see if the hash already exists. If // so, load, parse, and validate the build.zig.zon file therein, and skip diff --git a/lib/compiler/Maker/Step/Run.zig b/lib/compiler/Maker/Step/Run.zig index 58bf2c7b8818fec5f1473a648b92c5b2af2a656f..784f84eef95e7aeb912c097a4b0781def6a2ddcf 100644 --- a/lib/compiler/Maker/Step/Run.zig +++ b/lib/compiler/Maker/Step/Run.zig @@ -861,6 +861,7 @@ fn waitZigTest( .{ builtin.zig_version_string, body }, ); }, + .error_bundle => step.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body), .test_metadata => { // `metadata` would only be populated if we'd already seen a `test_metadata`, but we // only request it once (and importantly, we don't re-request it if we kill and @@ -920,10 +921,12 @@ fn waitZigTest( const name = md.testName(tr_hdr.index); const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); stderr.tossBuffered(); - if (stderr_bytes.len == 0) { - try step.addError(maker, "'{s}' failed without output", .{name}); - } else { + if (stderr_bytes.len > 0) { try step.addError(maker, "'{s}' failed:\n{s}", .{ name, stderr_bytes }); + } else if (step.result_error_bundle.errorMessageCount() > 0) { + try step.addError(maker, "'{s}' errored", .{name}); + } else { + try step.addError(maker, "'{s}' failed without output", .{name}); } } else if (leak_count > 0) { const name = md.testName(tr_hdr.index); diff --git a/lib/compiler/resinator/main.zig b/lib/compiler/resinator/main.zig index 7ba7f7445cdb930783a3aa2bac9307b0367c4b6d..1331c68aa8ae193b6bfab3e20c3ce88228e3a525 100644 --- a/lib/compiler/resinator/main.zig +++ b/lib/compiler/resinator/main.zig @@ -790,8 +790,7 @@ fn cliDiagnosticsToErrorBundle( ) !ErrorBundle { @branchHint(.cold); - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(gpa); + var bundle: ErrorBundle.Wip = try .init(gpa); errdefer bundle.deinit(); try bundle.addRootErrorMessage(.{ @@ -837,8 +836,7 @@ fn diagnosticsToErrorBundle( ) !ErrorBundle { @branchHint(.cold); - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(gpa); + var bundle: ErrorBundle.Wip = try .init(gpa); errdefer bundle.deinit(); var msg_buf: std.Io.Writer.Allocating = .init(gpa); @@ -919,8 +917,7 @@ fn diagnosticsToErrorBundle( fn errorStringToErrorBundle(allocator: Allocator, comptime format: []const u8, args: anytype) !ErrorBundle { @branchHint(.cold); - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(allocator); + var bundle: ErrorBundle.Wip = try .init(allocator); errdefer bundle.deinit(); try bundle.addRootErrorMessage(.{ .msg = try bundle.printString(format, args), diff --git a/lib/compiler/util.zig b/lib/compiler/util.zig index 05570abc3bfd9cfc1c9c3afaf602ee0b615bb78b..9e17e996a47fe572f121788a7c0c6f4868e8192f 100644 --- a/lib/compiler/util.zig +++ b/lib/compiler/util.zig @@ -10,8 +10,7 @@ pub fn aroDiagnosticsToErrorBundle( ) !ErrorBundle { @branchHint(.cold); - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(gpa); + var bundle: ErrorBundle.Wip = try .init(gpa); errdefer bundle.deinit(); if (fail_msg) |msg| { diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index c8797f26c68bdc41778f6360d75e591c78e10396..06b5b523a8fefcc0427b3900fad4af0bd34ac891 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -849,27 +849,14 @@ pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 { } while (true) { const content_len = r.end - r.seek; - if (r.buffer.len - content_len == 0) break; try fillMore(r); + if (r.buffer.len - content_len == 0) return error.StreamTooLong; const seek = r.seek; const contents = r.buffer[0..r.end]; if (std.mem.findScalarPos(u8, contents, seek + content_len, delimiter)) |end| { return contents[seek .. end + 1]; } } - // It might or might not be end of stream. There is no more buffer space - // left to disambiguate. If `StreamTooLong` was added to `RebaseError` then - // this logic could be replaced by removing the exit condition from the - // above while loop. That error code would represent when `buffer` capacity - // is too small for an operation, replacing the current use of asserts. - var failing_writer = Writer.failing; - while (r.vtable.stream(r, &failing_writer, .limited(1))) |n| { - assert(n == 0); - } else |err| switch (err) { - error.WriteFailed => return error.StreamTooLong, - error.ReadFailed => |e| return e, - error.EndOfStream => |e| return e, - } } /// Returns a slice of the next bytes of buffered data from the stream until diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 286654f1bd123b8fc6732cc70a5017e970d00f68..5c6bfa52f700385cf2be4894b3f870d9b34cff81 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -727,8 +727,7 @@ pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *Io.File.Reader) ![ } pub fn printAstErrorsToStderr(gpa: Allocator, io: Io, tree: Ast, path: []const u8, color: Color) !void { - var wip_errors: ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors); diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index d6fde276f1e9fb328d262b90a91f0112c45afb77..4ce1af8fdee40685a819298333d4248ddb9abfa0 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -360,22 +360,25 @@ pub const Wip = struct { extra: std.ArrayList(u32), root_list: std.ArrayList(MessageIndex), - pub fn init(wip: *Wip, gpa: Allocator) !void { - wip.* = .{ + pub fn init(gpa: Allocator) !Wip { + var wip: Wip = .{ .gpa = gpa, .string_bytes = .empty, .extra = .empty, .root_list = .empty, }; + errdefer wip.deinit(); // So that 0 can be used to indicate a null string. try wip.string_bytes.append(gpa, 0); - assert(0 == try addExtra(wip, ErrorMessageList{ + assert(0 == try wip.addExtra(ErrorMessageList{ .len = 0, .start = 0, .compile_log_text = 0, })); + + return wip; } pub fn deinit(wip: *Wip) void { @@ -773,7 +776,7 @@ pub const Wip = struct { u32 => @field(extra, field_name), MessageIndex => @backingInt(@field(extra, field_name)), SourceLocationIndex => @backingInt(@field(extra, field_name)), - else => @compileError("bad field type"), + else => @compileError("bad field type: " ++ @typeName(field_type)), }; i += 1; } diff --git a/src/Compilation.zig b/src/Compilation.zig index a2e962fde90f0b6873335c12a2e566f8b9980bdb..4fd5ef2fe7bb6b4e66a2acbd4a65783c5ff94c2a 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3838,8 +3838,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) error{OutOfMemory}!ErrorBundle { const gpa = comp.gpa; const io = comp.io; - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(gpa); + var bundle: ErrorBundle.Wip = try .init(gpa); defer bundle.deinit(); for (comp.failed_c_objects.values()) |diag_bundle| { @@ -5442,8 +5441,7 @@ fn reportRetryableWin32ResourceError( win32_resource.status = .failure_retryable; - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(comp.gpa); + var bundle: ErrorBundle.Wip = try .init(comp.gpa); errdefer bundle.deinit(); try bundle.addRootErrorMessage(.{ .msg = try bundle.printString("{s}", .{@errorName(err)}), @@ -6830,8 +6828,7 @@ fn failCObjWithOwnedDiagBundle( fn failWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, comptime format: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } { @branchHint(.cold); - var bundle: ErrorBundle.Wip = undefined; - try bundle.init(comp.gpa); + var bundle: ErrorBundle.Wip = try .init(comp.gpa); errdefer bundle.deinit(); try bundle.addRootErrorMessage(.{ .msg = try bundle.printString(format, args), diff --git a/src/Sema.zig b/src/Sema.zig index a1d30147758234ed15ba84cbe71a85494ac574aa..e209f453cae9ceb95b5d222fb281d5e3bff7da2e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2705,8 +2705,7 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg assert(sema.err == null); if (build_options.enable_debug_extensions and comp.debug_compile_errors) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - wip_errors.init(gpa) catch @panic("out of memory"); + var wip_errors = std.zig.ErrorBundle.Wip.init(gpa) catch @panic("out of memory"); Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory"); std.debug.print("compile error during Sema:\n", .{}); var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory"); diff --git a/src/fmt.zig b/src/fmt.zig index 769e9ab3fc1e007b70d413868f103d386f4e0419..8bb5582cbea0e506e66a6b1c52f381b81222bc2e 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -130,8 +130,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! defer zir.deinit(gpa); if (zir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try wip_errors.addZirErrorMessages(zir, tree, source_code, ""); var error_bundle = try wip_errors.toOwnedBundle(""); @@ -144,8 +143,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! defer zoir.deinit(gpa); if (zoir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try wip_errors.addZoirErrorMessages(zoir, tree, source_code, ""); var error_bundle = try wip_errors.toOwnedBundle(""); @@ -333,8 +331,7 @@ fn fmtPathFile( defer zir.deinit(gpa); if (zir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path); var error_bundle = try wip_errors.toOwnedBundle(""); @@ -348,8 +345,7 @@ fn fmtPathFile( defer zoir.deinit(gpa); if (zoir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try wip_errors.addZoirErrorMessages(zoir, tree, source_code, file_path); var error_bundle = try wip_errors.toOwnedBundle(""); diff --git a/src/link.zig b/src/link.zig index 6b2d2eecdf7f644ddaa94b71a701de983173fda5..e0e32c3a8326d7adeaf21e7507f833b7036d247a 100644 --- a/src/link.zig +++ b/src/link.zig @@ -2586,8 +2586,7 @@ fn resolvePathInputLib( const ld_script_result = LdScript.parse(gpa, &diags, test_path, ld_script_bytes.items); if (diags.hasErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(gpa); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(gpa); defer wip_errors.deinit(); try diags.addMessagesToBundle(&wip_errors, null); diff --git a/src/main.zig b/src/main.zig index f09b81e0ff7ecaaa19d5cec8ed7a852dca376f46..39df0ee67a6f2c79aaebce2ac8de1d22dfbe7f55 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5659,8 +5659,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map: const zir = try AstGen.generate(arena, tree); if (zir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(arena); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena); try wip_errors.addZirErrorMessages(zir, tree, source, display_path); var error_bundle = try wip_errors.toOwnedBundle(""); try error_bundle.renderToStderr(io, .{}, color); @@ -5730,8 +5729,7 @@ fn cmdAstCheck(arena: Allocator, io: Io, args: []const []const u8, environ_map: .zon => { const zoir = try ZonGen.generate(arena, tree, .{}); if (zoir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(arena); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena); try wip_errors.addZoirErrorMessages(zoir, tree, source, display_path); var error_bundle = try wip_errors.toOwnedBundle(""); error_bundle.renderToStderr(io, .{}, color) catch {}; @@ -5824,8 +5822,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map const old_zir = try AstGen.generate(arena, old_tree); if (old_zir.loweringFailed()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(arena); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena); try wip_errors.addZirErrorMessages(old_zir, old_tree, old_source, old_source_path); var error_bundle = try wip_errors.toOwnedBundle(""); error_bundle.renderToStderr(io, .{}, color) catch {}; @@ -5836,8 +5833,7 @@ fn cmdChangelist(arena: Allocator, io: Io, args: []const []const u8, environ_map const new_zir = try AstGen.generate(arena, new_tree); if (new_zir.loweringFailed()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(arena); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena); try wip_errors.addZirErrorMessages(new_zir, new_tree, new_source, new_source_path); var error_bundle = try wip_errors.toOwnedBundle(""); error_bundle.renderToStderr(io, .{}, color) catch {}; diff --git a/test/src/Runner.zig b/test/src/Runner.zig index 599f7b001aaf38e642c1e8bf5e231f0dd0b522b1..158d657ad37ba2b5f73183d9e709eae0be431cbb 100644 --- a/test/src/Runner.zig +++ b/test/src/Runner.zig @@ -1,274 +1,5 @@ +const assert = std.debug.assert; const std = @import("std"); - -pub fn main(init: std.process.Init) !void { - 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 target_args: std.ArrayList([]const u8) = .empty; - defer target_args.deinit(init.gpa); - var keep_src = false; - var enable_qemu = false; - var enable_wine = false; - var enable_wasmtime = false; - var enable_darling = false; - - 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, - .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, - .enable_qemu = false, - .enable_wine = false, - .enable_wasmtime = false, - .enable_darling = false, - }, - .host = try std.zig.system.resolveTargetQuery(init.io, .{}), - .server = .{ - .in = &stdin.interface, - .out = &stdout.interface, - }, - }; - defer runner.deinit(); - return runner.protocol(); - } else if (std.mem.eql(u8, arg, "--zig")) { - zig_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{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 '{s}'", .{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 target_args.append( - init.gpa, - try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after '{s}'", .{arg})), - ); - } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| { - try target_args.append(init.gpa, try arena.dupe(u8, target)); - } else if (std.mem.eql(u8, arg, "--keep-src")) { - keep_src = true; - } else if (std.mem.eql(u8, arg, "-fqemu")) { - enable_qemu = true; - } else if (std.mem.eql(u8, arg, "-fwine")) { - enable_wine = true; - } else if (std.mem.eql(u8, arg, "-fwasmtime")) { - enable_wasmtime = true; - } else if (std.mem.eql(u8, arg, "-fdarling")) { - enable_darling = true; - } else { - if (test_path_arg) |_| fatal("unknown arg '{s}'", .{arg}); - test_path_arg = arg; - } - } - - const test_path = test_path_arg orelse fatal("missing 'path/to/test'", .{}); - const zig_path = zig_path_arg orelse fatal("missing '--zig=path/to/zig'", .{}); - const lib_path = lib_path_arg orelse fatal("missing '--lib=path/to/lib'", .{}); - - const cwd: std.Io.Dir = .cwd(); - const test_file = try cwd.openFile(init.io, test_path, .{ - .allow_directory = true, - }); - const zig_exe = try cwd.openFile(init.io, zig_path, .{}); - defer zig_exe.close(init.io); - const lib_dir = try cwd.openDir(init.io, lib_path, .{}); - defer lib_dir.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; - }); - var src_dir = try cwd.createDirPathOpen(init.io, src_dir_path, .{}); - defer { - src_dir.close(init.io); - if (!keep_src) cwd.deleteTree(init.io, src_dir_path) catch |err| { - std.log.warn("failed to delete tree '{s}': {t}", .{ src_dir_path, err }); - }; - } - - var stdout_buffer: [512]u8 = undefined; - var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer); - const stdout = &stdout_fw.interface; - - var child = try std.process.spawn(init.io, .{ - .argv = &.{ self, "--listen=-" }, - .stdin = .pipe, - .stdout = .pipe, - .inherit_dirs = &.{ lib_dir, src_dir }, - .inherit_files = &.{ zig_exe, test_file }, - }); - - 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, - }; - { - var args: std.ArrayList(u8) = .empty; - defer args.deinit(init.gpa); - const Arg = std.zig.Client.Message.Arg; - - try args.append(init.gpa, @backingInt(@as(Arg, switch ((try test_file.stat(init.io)).kind) { - else => unreachable, - .file => .input_file, - .directory => .input_dir, - }))); - try args.appendSlice(init.gpa, @ptrCast(&test_file.handle)); - - try args.append(init.gpa, @backingInt(Arg.prefix)); - try args.appendSlice(init.gpa, "--zig="); - try args.append(init.gpa, 0); - - try args.append(init.gpa, @backingInt(Arg.input_file)); - try args.appendSlice(init.gpa, @ptrCast(&zig_exe.handle)); - - try args.append(init.gpa, @backingInt(Arg.prefix)); - try args.appendSlice(init.gpa, "--lib="); - try args.append(init.gpa, 0); - - try args.append(init.gpa, @backingInt(Arg.input_dir)); - try args.appendSlice(init.gpa, @ptrCast(&lib_dir.handle)); - - try args.append(init.gpa, @backingInt(Arg.prefix)); - try args.appendSlice(init.gpa, "--src="); - try args.append(init.gpa, 0); - - try args.append(init.gpa, @backingInt(Arg.output_dir)); - try args.appendSlice(init.gpa, @ptrCast(&src_dir.handle)); - - for (target_args.items) |target_arg| { - try args.append(init.gpa, @backingInt(Arg.string)); - try args.appendSlice(init.gpa, "--target"); - try args.append(init.gpa, 0); - - try args.append(init.gpa, @backingInt(Arg.string)); - try args.appendSlice(init.gpa, target_arg); - try args.append(init.gpa, 0); - } - - if (enable_qemu) { - try args.append(init.gpa, @backingInt(Arg.string)); - try args.appendSlice(init.gpa, "-fqemu"); - try args.append(init.gpa, 0); - } - if (enable_wine) { - try args.append(init.gpa, @backingInt(Arg.string)); - try args.appendSlice(init.gpa, "-fwine"); - try args.append(init.gpa, 0); - } - if (enable_wasmtime) { - try args.append(init.gpa, @backingInt(Arg.string)); - try args.appendSlice(init.gpa, "-fwasmtime"); - try args.append(init.gpa, 0); - } - if (enable_darling) { - try args.append(init.gpa, @backingInt(Arg.string)); - try args.appendSlice(init.gpa, "-fdarling"); - try args.append(init.gpa, 0); - } - - try client.serveMessageHeader(.{ - .tag = .args, - .bytes_len = @intCast(args.items.len), - }); - try client.out.writeAll(args.items); - - 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 body = try arena.alloc(u8, hdr.bytes_len); - try client.in.readSliceAll(body); - if (!std.mem.eql(u8, @import("builtin").zig_version_string, body)) return fatal( - "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", - .{ @import("builtin").zig_version_string, body }, - ); - }, - .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); - 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); - } - _ = try child.wait(init.io); -} -fn fatal(comptime fmt: []const u8, args: anytype) noreturn { - std.log.err(fmt, args); - std.process.exit(1); -} - const Runner = @This(); gpa: std.mem.Allocator, @@ -278,6 +9,7 @@ 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 { @@ -312,24 +44,34 @@ const Args = struct { fn deinit(runner: *Runner) void { runner.prog_node.end(); runner.args.deinit(runner.gpa); + runner.eb_wip.deinit(); runner.* = undefined; } -pub fn protocol(runner: *Runner) !void { +const Fallible = error{AlreadyReported} || std.mem.Allocator.Error || std.Io.Cancelable; +fn fail(runner: *Runner, comptime fmt: []const u8, args: anytype) Fallible { + return runner.failString(try runner.eb_wip.printString(fmt, args)); +} +fn failString(runner: *Runner, msg: std.zig.ErrorBundle.String) Fallible { + try runner.eb_wip.addRootErrorMessage(.{ + .msg = msg, + }); + return error.AlreadyReported; +} + +pub const ProtocolError = Fallible || std.Io.Reader.Error || std.Io.Writer.Error; +pub fn runServer(runner: *Runner) ProtocolError { try runner.server.serveStringMessage(.zig_version, @import("builtin").zig_version_string); while (true) { _ = runner.arena.reset(.retain_capacity); const arena = runner.arena.allocator(); const hdr = try runner.server.receiveMessage(); switch (hdr.tag) { - else => { - std.log.warn("unsupported message: {t}\n", .{hdr.tag}); - std.process.exit(1); - }, + 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); - try runner.server.in.readSliceAll(args_body); + runner.server.in.readSliceAll(args_body) catch unreachable; var state: enum { positional, zig, lib, src, target } = .positional; var input_dir: std.zig.Server.Message.InputDir = .cwd; var args_body_offset: usize = 0; @@ -392,32 +134,30 @@ pub fn protocol(runner: *Runner) !void { }; switch (state) { .positional => { - std.debug.assert(runner.args.@"test" == null); + assert(runner.args.@"test" == null); runner.args.@"test" = .{ .input_dir = input_dir, .dir = dir }; - std.debug.assert(runner.args.manifest_file == null); + assert(runner.args.manifest_file == null); const manifest_path = "manifest"; - runner.args.manifest_file = - try dir.openFile(runner.io, manifest_path, .{}); - - try runner.server.serveMessageHeader(.{ - .tag = .discovered_inputs, - .bytes_len = @sizeOf(std.zig.Server.Message.InputDir) + - manifest_path.len + 1, - }); - try runner.server.out.writeInt(u32, @backingInt( - input_dir, - ), .little); - try runner.server.out.writeAll(manifest_path); - try runner.server.out.writeByte(0); - try runner.server.out.flush(); + 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); }, .zig => unreachable, .lib => { - std.debug.assert(runner.args.lib_dir == null); + assert(runner.args.lib_dir == null); runner.args.lib_dir = dir; }, .src => { - std.debug.assert(runner.args.src_dir == null); + assert(runner.args.src_dir == null); runner.args.src_dir = dir; }, .target => unreachable, @@ -434,11 +174,11 @@ pub fn protocol(runner: *Runner) !void { }; switch (state) { .positional => { - std.debug.assert(runner.args.manifest_file == null); + assert(runner.args.manifest_file == null); runner.args.manifest_file = file; }, .zig => { - std.debug.assert(runner.args.zig_exe == null); + assert(runner.args.zig_exe == null); runner.args.zig_exe = file; }, .lib => unreachable, @@ -460,29 +200,199 @@ pub fn protocol(runner: *Runner) !void { }); }, .run_test => { - const test_index = try runner.server.receiveBody_u32(); + const test_index = runner.server.receiveBody_u32() catch unreachable; try runner.server.serveBodylessMessage(.test_started); try runner.server.serveTestResults(.{ .index = test_index, .flags = .{ - .status = if (runner.testOne(test_index)) .pass else |err| switch (err) { - else => status: { - std.log.err("{t}\n", .{err}); - if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace); + .status = if (runner.testOne(test_index)) + .pass + else |err| status: switch (err) { + error.Canceled, + error.OutOfMemory, + error.ReadFailed, + error.WriteFailed, + => |e| return e, + error.AlreadyReported => { + var eb = try runner.eb_wip.toOwnedBundle(""); + defer eb.deinit(runner.gpa); + assert(eb.errorMessageCount() > 0); // already reported what? + try runner.server.serveErrorBundle(.error_bundle, eb); + const eb_wip: std.zig.ErrorBundle.Wip = try .init(runner.gpa); + runner.eb_wip.deinit(); + runner.eb_wip = eb_wip; break :status .fail; }, - error.SkipZigTest => .skip, + error.SkipTest => .skip, }, .fuzz = false, .log_err_count = 0, .leak_count = 0, }, }); + assert(runner.eb_wip.root_list.items.len == 0); // failed to report }, } } } -const DelimitedReader = struct { + +fn discoverInput( + 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, + ) Fallible { + 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, + ) Fallible { + 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, @@ -528,251 +438,376 @@ const DelimitedReader = struct { } }; -fn testOne(runner: *const Runner, test_index: u32) !void { +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, +} || Fallible || std.Io.Reader.ShortError || std.Io.Writer.Error; +fn testOne(runner: *Runner, test_index: u32) TestError!void { const target_query = std.mem.sliceTo(runner.args.target_bytes.items[runner.args.targets.items[test_index]..], 0); - const src_dir = try (runner.args.src_dir orelse return error.MissingSrcDir).createDirPathOpen( - runner.io, - target_query, - .{}, - ); - const manifest_file = runner.args.manifest_file orelse return error.MissingManifestArg; + 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 allow_skip = true; + 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 .. std.Io.Dir.readLinkAbsolute( + runner.io, + try runner.arena.allocator().print("/proc/self/fd/{d}", .{manifest_file.handle}), + &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(); + } var skip_delimiter = false; - var compiler: ?Compiler = null; - var update_prog_node: std.Progress.Node = .none; - defer update_prog_node.end(); - var update_mtime = std.Io.Clock.real.now(runner.io); - var update_num: usize = 0; - while (manifest_fr.interface.takeSentinel('\n')) |untrimmed_line| { - const line = std.mem.trimEnd(u8, untrimmed_line, "\r"); + while (update.manifest_sr.interface.takeSentinel('\n')) |line| { var line_it = std.mem.tokenizeScalar(u8, line, ' '); - const cmd_str = std.mem.cutPrefix(u8, line_it.next() orelse continue, "#").?; + const cmd_src_loc = line_it.next() orelse continue; if (skip_delimiter) { - std.debug.assert(std.mem.eql(u8, cmd_str, "}")); + 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( - enum { todo, skip, exe, lib, obj, write, delete, update, check }, - cmd_str, - ) orelse return error.UnknownCommand; - var maybe_arg = line_it.next(); + 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 { + var contents_impl: union(enum) { + ending, dr: DelimitedReader, fr: std.Io.File.Reader, - } = undefined; + } = .ending; const contents_r: *std.Io.Reader = contents_r: switch (cmd) { .skip, .delete => { - if (line_it.next()) |_| return error.UnexpectedArg; + const unexpected = line_it.rest(); + if (unexpected.len > 0) return update.manifest_sr.fail( + unexpected, + "unexpected argument {q}", + .{unexpected}, + ); break :contents_r .ending; }, else => { - const contents_path = contents_path: { - const line_rest = line_it.rest(); - if (line_rest.len > 0) break :contents_path line_rest; - const contents_path = maybe_arg orelse break :contents_r .ending; - maybe_arg = null; - break :contents_path contents_path; - }; + const contents_path = line_it.rest(); + if (contents_path.len == 0) break :contents_r .ending; + if (std.mem.eql(u8, contents_path, "{")) { - contents_impl = .{ .dr = .init(&manifest_fr.interface, "#}", &contents_buffer) }; + 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 error.ContentsFileInFileTest; - try runner.server.serveMessageHeader(.{ - .tag = .discovered_inputs, - .bytes_len = @intCast(@sizeOf(std.zig.Server.Message.InputDir) + - contents_path.len + 1), - }); - try runner.server.out.writeInt(u32, @backingInt(args_test.input_dir), .little); - try runner.server.out.writeAll(contents_path); - try runner.server.out.writeByte(0); - try runner.server.out.flush(); + 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 = try args_test.dir.openFile(runner.io, 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; }, }; - 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}", .{ - cmd, maybe_arg orelse "", - }) catch &name_buffer, 0); - defer cmd_prog_node.end(); - - cmd: switch (cmd) { - .todo => _ = try contents_r.discardRemaining(), - .skip => if (!allow_skip) - return error.NonInitialSkipCommand // #skip must appear first - else if (std.mem.eql(u8, target_query, maybe_arg orelse return error.MissingArg)) - return error.SkipZigTest, - .exe, .lib, .obj => try runner.spawnCompiler(&compiler, src_dir, switch (cmd) { - else => unreachable, - .exe => .Exe, - .lib => .Lib, - .obj => .Obj, - }, contents_r, target: { - const backend_split = std.mem.findScalarLast(u8, target_query, '-') orelse - return error.TargetMissingBackend; - const mode_split = - std.mem.findScalarLast(u8, target_query[0..backend_split], '-') orelse - return error.TargetMissingMode; - const triple = target_query[0..mode_split]; - const backend = std.meta.stringToEnum( - Compiler.Target.Backend, - target_query[backend_split + 1 ..], - ) orelse return error.TargetMissingBackend; - const mode = std.meta.stringToEnum( - Compiler.Target.Mode, - target_query[mode_split + 1 .. backend_split], - ) orelse return error.TargetMissingMode; - break :target .{ - .triple = triple, - .resolved = try std.zig.system.resolveTargetQuery( - runner.io, - try std.Build.parseTargetQuery(.{ - .arch_os_abi = triple, - .object_format = switch (backend) { - .sema, .selfhosted, .llvm => null, - .cbe => "c", - }, - }), - ), - .mode = mode, - .backend = backend, - }; - }), - .write => { - const file = try src_dir.createFile( - runner.io, - maybe_arg orelse return error.MissingFileArg, - .{}, - ); - defer file.close(runner.io); - var fw = file.writer(runner.io, &.{}); - _ = try contents_r.streamRemaining(&fw.interface); - try file.setTimestamps(runner.io, .{ .modify_timestamp = .{ .new = update_mtime } }); + runner.handleCommand(&update, cmd_src_loc, cmd, arg, contents_r) catch |err| switch (err) { + error.Canceled, + error.OutOfMemory, + error.AlreadyReported, + error.SkipTest, + error.WriteFailed, + => |e| return e, + error.ReadFailed => switch (contents_impl) { + .ending => unreachable, + .dr => return runner.fail("unable to read manifest: {t}", .{manifest_fr.err.?}), + .fr => |fr| return runner.fail("unable to read contents file: {t}", .{fr.err.?}), }, - .delete => try src_dir.deleteFile( - runner.io, - maybe_arg orelse return error.MissingFileArg, - ), - .update => { - const comp = &(compiler orelse return error.MissingCompiler); - update_prog_node.end(); - update_prog_node = runner.prog_node.start( - switch (try contents_r.readSliceShort(&name_buffer)) { - 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, - ); - _ = try contents_r.discardRemaining(); - if (comp.state != .idle) return error.MissingCheck; - try comp.client.serveBodylessMessage(.update); - comp.state = .update; - update_mtime = update_mtime.addDuration(.fromSeconds(2)); - update_num += 1; - }, - .check => { - const check = std.meta.stringToEnum( - Compiler.Check, - maybe_arg orelse return error.MissingCheckArg, - ) orelse return error.UnknownCheck; - const expected = try contents_r.allocRemaining(runner.gpa, .unlimited); - defer runner.gpa.free(expected); - - const comp = &(compiler orelse return error.MissingCompiler); - if (comp.state != .update) return error.MissingUpdate; - const stderr = comp.mr.reader(1); - while (try comp.receiveMessage()) |message| switch (message.tag) { - .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, src_dir, digest); - }, - .error_bundle => { - const error_bundle = - try std.zig.Server.allocErrorBundle(runner.arena.allocator(), message.body); - switch (check) { - .errors => { - var error_aw: std.Io.Writer.Allocating = .init(runner.gpa); - defer error_aw.deinit(); - try error_bundle.renderToWriter(.{ - .include_reference_trace = false, - .include_source_line = false, - .include_log_text = true, - }, &error_aw.writer); - try std.testing.expectEqualStrings(expected, error_aw.written()); - }, - .stdout, .exit, .lldb => if (error_bundle.errorMessageCount() > 0) { - try error_bundle.renderToStderr(runner.io, .{}, .auto); - fatal("unexpected compile errors", .{}); - }, - } - comp.state = .idle; - break :cmd; - }, - else => {}, // Ignore other messages, - }; - - const buffered_stderr = stderr.buffered(); - if (buffered_stderr.len > 0) { - if (comp.allow_compiler_stderr) { - std.log.info("stderr:\n{s}", .{buffered_stderr}); - } else { - fatal("unexpected stderr:\n{s}", .{buffered_stderr}); - } - } - - try comp.exit(); - fatal("compiler failed to send terminating error_bundle", .{}); - }, - } - std.debug.assert(try contents_r.discardRemaining() == 0); - switch (cmd) { - .todo, .skip => {}, - else => allow_skip = false, - } + }; } else |err| switch (err) { - else => |e| return e, - error.EndOfStream => if (skip_delimiter) return error.MissingDelimiter, + error.ReadFailed => |e| return e, + error.StreamTooLong => unreachable, + error.EndOfStream => if (skip_delimiter) + return runner.fail("manifest missing \"#}}\" delimiter", .{}), } - if (compiler) |*comp| { - if (comp.state != .idle) return error.MissingCheck; + 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, +) 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 => try runner.spawnCompiler(&update.compiler, update.src_dir, switch (cmd) { + else => unreachable, + .exe => .Exe, + .lib => .Lib, + .obj => .Obj, + }, contents_r, target: { + const backend_split = std.mem.findScalarLast(u8, update.target_query, '-') orelse + return runner.fail("target {q} missing query", .{update.target_query}); + const mode_split = + std.mem.findScalarLast(u8, update.target_query[0..backend_split], '-') orelse + return runner.fail("target {q} missing mode", .{update.target_query}); + const triple = update.target_query[0..mode_split]; + const backend_str = update.target_query[backend_split + 1 ..]; + 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 mode_str = update.target_query[mode_split + 1 .. backend_split]; + const mode = std.meta.stringToEnum(Compiler.Target.Mode, mode_str) orelse + return runner.fail("target {q} unknown mode {q}", .{ update.target_query, mode_str }); + 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 }), + }, + .mode = mode, + .backend = backend, + }; + }), + .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 = file.writer(runner.io, &.{}); + _ = try contents_r.streamRemaining(&fw.interface); + 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 (try contents_r.readSliceShort(&name_buffer)) { + 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, + ); + _ = try contents_r.discardRemaining(); + if (comp.state != .idle) + return update.manifest_sr.fail(cmd_src_loc, "missing \"#check\"", .{}); + try comp.client.serveBodylessMessage(.update); + 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, error.ReadFailed => |e| return e, + 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(); + try eb.renderToWriter(.{ + .include_reference_trace = false, + .include_source_line = false, + .include_log_text = true, + }, &error_aw.writer); + std.testing.expectEqualStrings( + expected, + error_aw.written(), + ) catch |err| switch (err) { + error.TestExpectedEqual => return runner.fail( + "errors did not match expected", + .{}, + ), + }; + }, + .stdout, .exit, .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(try contents_r.discardRemaining() == 0); + switch (cmd) { + .todo, .skip => {}, + else => update.allow_skip = false, + } +} fn spawnCompiler( - runner: *const Runner, + runner: *Runner, compiler: *?Compiler, src_dir: std.Io.Dir, output_mode: std.lang.OutputMode, args_r: *std.Io.Reader, target: Compiler.Target, -) !void { +) (Fallible || std.Io.Reader.ShortError)!void { const gpa = runner.gpa; const arena = runner.arena.allocator(); if (compiler.*) |*comp| { @@ -804,18 +839,20 @@ fn spawnCompiler( .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..try std.Io.Dir.readLinkAbsolute( + } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. std.Io.Dir.readLinkAbsolute( runner.io, - try arena.print("/proc/self/fd/{d}", .{ - (runner.args.lib_dir orelse return error.MissingLibDir).handle, - }), + try arena.print("/proc/self/fd/{d}", .{(runner.args.lib_dir orelse + return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{})).handle}), &path_buffer, - )] }); + ) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return runner.fail("unable to get lib path: {t}", .{e}), + }] }); var need: usize = 1; var root_name: ?[]const u8 = null; while (true) { const done = if (args_r.fill(need)) false else |err| switch (err) { - else => |e| return e, + error.ReadFailed => |e| return e, error.EndOfStream => true, }; const buffered = args_r.buffered(); @@ -854,15 +891,19 @@ fn spawnCompiler( }) catch &name_buffer, 0); errdefer comp_prog_node.end(); - const child = try std.process.spawn(runner.io, .{ - .exe = .{ .file = runner.args.zig_exe orelse return error.MissingZigExe }, + 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, + else => |e| return runner.fail("unable to spawn compiler: {t}", .{e}), + }; compiler.* = .{ .runner = runner, .prog_node = comp_prog_node, @@ -886,7 +927,7 @@ fn spawnCompiler( comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface }; } const Compiler = struct { - runner: *const Runner, + runner: *Runner, prog_node: std.Progress.Node, allow_compiler_stderr: bool, output_mode: std.lang.OutputMode, @@ -927,7 +968,7 @@ const Compiler = struct { }; }; - fn receiveMessage(comp: *Compiler) !?struct { + fn receiveMessage(comp: *Compiler) Fallible!?struct { tag: std.zig.Server.Message.Tag, body: []const u8, } { @@ -935,9 +976,10 @@ const Compiler = struct { &comp.mr, .none, ) catch |err| switch (err) { + error.Canceled, error.OutOfMemory => |e| return e, error.Timeout => unreachable, error.EndOfStream => return null, - else => |e| return e, + 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); @@ -945,7 +987,9 @@ const Compiler = struct { if (comp.allow_compiler_stderr) { std.log.info("{t} stderr:\n{s}", .{ header.tag, stderr.buffered() }); } else { - fatal("{t} unexpected stderr:\n{s}", .{ header.tag, stderr.buffered() }); + return comp.runner.fail("{t} unexpected stderr:\n{s}", .{ + header.tag, stderr.buffered(), + }); } stderr.tossBuffered(); } @@ -959,7 +1003,7 @@ const Compiler = struct { expected: []const u8, src_dir: std.Io.Dir, digest: *const std.Build.Cache.BinDigest, - ) !void { + ) Fallible!void { const runner = comp.runner; const gpa = runner.gpa; const arena = runner.arena.allocator(); @@ -1021,7 +1065,10 @@ const Compiler = struct { }) catch &name_buffer, 0); defer run_prog_node.end(); - const bin_file = try src_dir.openFile(io, bin_path, .{}); + 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); switch (check) { .errors => unreachable, @@ -1033,13 +1080,14 @@ const Compiler = struct { .progress_node = run_prog_node, }) catch |err| if (executor) |_| { // Chances are the foreign executor isn't available. Skip this evaluation. - std.log.warn("skipping execution of '{s}' via executor for foreign target '{s}': {t}", .{ - bin_path, - comp.target.triple, - err, - }); + std.log.warn( + "skipping execution of '{s}' via executor for foreign target '{s}': {t}", + .{ bin_path, comp.target.triple, err }, + ); return; - } else fatal("failed to run the generated executable '{s}': {t}", .{ bin_path, err }); + } else return runner.fail("unable to run the generated executable '{s}': {t}", .{ + bin_path, err, + }); defer { gpa.free(result.stdout); gpa.free(result.stderr); @@ -1048,20 +1096,46 @@ const Compiler = struct { .exited => |code| switch (check) { .errors, .lldb => unreachable, .stdout => { - if (code != 0) fatal("generated executable '{s}' failed with code {d}", .{ bin_path, code }); - try std.testing.expectEqualStrings(expected, result.stdout); + if (code != 0) return runner.fail( + "generated executable '{s}' 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; - try std.testing.expectEqualStrings( + 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", + .{}, + ), + }; }, }, - .signal => |sig| fatal("generated executable '{s}' terminated with signal {t}", .{ bin_path, sig }), - .stopped => |sig| fatal("generated executable '{s}' stopped with signal {t}", .{ bin_path, sig }), - .unknown => fatal("generated executable '{s}' terminated unexpectedly", .{bin_path}), + .signal => |sig| return runner.fail( + "generated executable '{s}' terminated with signal {t}", + .{ bin_path, sig }, + ), + .stopped => |sig| return runner.fail( + "generated executable '{s}' stopped with signal {t}", + .{ bin_path, sig }, + ), + .unknown => return runner.fail( + "generated executable '{s}' terminated unexpectedly", + .{bin_path}, + ), } if (executor == null and result.stderr.len > 0) { std.log.err("generated executable '{s}' had unexpected stderr:\n{s}", .{ @@ -1073,12 +1147,12 @@ const Compiler = struct { } } - fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) ![]const u8 { + fn compileC(parent_comp: *Compiler, src_dir: std.Io.Dir, c_path: []const u8) Fallible![]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); - try parent_comp.runner.spawnCompiler(&compiler, src_dir, parent_comp.output_mode, &args_r, .{ + 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; @@ -1087,12 +1161,18 @@ const Compiler = struct { }, .mode = .whole, .backend = null, - }); + }) catch |err| switch (err) { + error.Canceled, error.OutOfMemory, error.AlreadyReported => |e| return e, + error.ReadFailed => unreachable, // .fixed + }; const comp = &compiler.?; - try comp.client.serveBodylessMessage(.update); + comp.client.serveBodylessMessage(.update) catch |err| switch (err) { + error.WriteFailed => return runner.fail("unable to send message: {t}", .{comp.fw.err.?}), + }; 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 = @@ -1108,40 +1188,421 @@ const Compiler = struct { out_path = try std.Io.Dir.path.join(arena, &.{ out_dir, comp.out_name }); }, .error_bundle => { - const error_bundle = try std.zig.Server.allocErrorBundle(arena, message.body); - - if (error_bundle.errorMessageCount() > 0) { - try error_bundle.renderToStderr(runner.io, .{}, .auto); - fatal("unexpected compile errors", .{}); + 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; }, - else => {}, // Ignore other messages, }; try comp.exit(); return out_path.?; } - fn exit(comp: *Compiler) !void { - const io = comp.runner.io; + fn exit(comp: *Compiler) Fallible!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| fatal("failed to send exit: {t}", .{e}), + else => |e| return comp.runner.fail("unable to send exit: {t}", .{e}), }, }; - comp.child.stdin.?.close(io); + comp.child.stdin.?.close(runner.io); comp.child.stdin = null; while (try comp.receiveMessage()) |_| {} - if (comp.client.in.bufferedLen() > 0) return error.EndOfStream; - const term = comp.child.wait(io) catch |err| fatal("child process failed: {t}", .{err}); + 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) fatal("compiler failed with code {d}", .{code}), - .signal => |sig| fatal("compiler terminated with signal {t}", .{sig}), - .stopped => |sig| fatal("compiler stopped unexpectedly with signal {t}", .{sig}), - .unknown => fatal("compiler terminated unexpectedly", .{}), + .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), + keep_src: bool, + enable_qemu: bool, + enable_wine: bool, + enable_wasmtime: bool, + enable_darling: 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 args: ClientArgs = .{ + .test_file = undefined, + .test_kind = undefined, + .zig_exe = undefined, + .lib_dir = undefined, + .src_dir = undefined, + .targets = .empty, + .keep_src = false, + .enable_qemu = false, + .enable_wine = false, + .enable_wasmtime = false, + .enable_darling = 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, + .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, + .enable_qemu = false, + .enable_wine = false, + .enable_wasmtime = false, + .enable_darling = 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.ReadFailed => fatal("unable to receive message: {t}", .{stdin.err.?}), + error.EndOfStream => fatal("no more messages", .{}), + error.WriteFailed => fatal("unable to send message: {t}", .{stdout.err.?}), + 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 '{s}'", .{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 '{s}'", .{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 '{s}'", .{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, "--keep-src")) { + args.keep_src = true; + } else if (std.mem.eql(u8, arg, "-fqemu")) { + args.enable_qemu = 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, "-fdarling")) { + args.enable_darling = true; + } else { + if (test_path_arg) |_| fatal("unknown arg '{s}'", .{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); + + 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 child = std.process.spawn(init.io, .{ + .argv = &.{ self, "--listen=-" }, + .stdin = .pipe, + .stdout = .pipe, + .inherit_dirs = &.{ args.lib_dir, args.src_dir }, + .inherit_files = &.{ args.zig_exe, args.test_file }, + }) 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 => fatal("unable to receive message: {t}", .{child_reader.err.?}), + error.EndOfStream => fatal("no more messages", .{}), + error.WriteFailed => fatal("unable to send message: {t}", .{child_writer.err.?}), + }; + _ = 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.enable_qemu) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-fqemu"); + 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.enable_darling) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "-fdarling"); + 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 expected = @import("builtin").zig_version_string; + const actual = try client.in.take(hdr.bytes_len); + if (!std.mem.eql(u8, expected, actual)) { + try stderr.print( + "error: zig version mismatch compiler test runner vs compiler: {q} vs {q}\n", + .{ expected, actual }, + ); + 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/tools/gen_spirv_spec.zig b/tools/gen_spirv_spec.zig index b9967d378570ede07fa26cfc2aa7870ba9d6fed7..769f09936e66547da537be43125564991a803e04 100644 --- a/tools/gen_spirv_spec.zig +++ b/tools/gen_spirv_spec.zig @@ -86,8 +86,7 @@ pub fn main(init: std.process.Init) !void { var zir = try std.zig.AstGen.generate(arena, tree); if (zir.hasCompileErrors()) { - var wip_errors: std.zig.ErrorBundle.Wip = undefined; - try wip_errors.init(arena); + var wip_errors: std.zig.ErrorBundle.Wip = try .init(arena); defer wip_errors.deinit(); try wip_errors.addZirErrorMessages(zir, tree, output, ""); var error_bundle = try wip_errors.toOwnedBundle("");