From 11958a306f30268b17454a25bc365e67aad84efd Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sun, 13 Sep 2026 06:31:29 -0400 Subject: [PATCH] test: implement new runner --- build.zig | 21 + lib/compiler/Maker/Step.zig | 5 +- lib/compiler/Maker/Step/Run.zig | 615 ++++++++++++++---- lib/std/Build.zig | 5 +- lib/std/Build/Configuration.zig | 3 +- lib/std/Build/Serialize.zig | 2 +- lib/std/Build/Step/Run.zig | 23 +- lib/std/Io/Reader/Limited.zig | 6 +- lib/std/Io/Threaded.zig | 67 +- lib/std/bit_set.zig | 9 +- lib/std/process.zig | 2 + lib/std/zig/Client.zig | 28 + .../change_panic_handler_explicit/0-main.zig | 46 ++ .../change_panic_handler_explicit/1-main.zig | 46 ++ .../change_panic_handler_explicit/2-main.zig | 46 ++ .../change_panic_handler_explicit/manifest | 23 + test/incremental2/hello | 28 + test/src/runner.zig | 279 ++++++++ test/standalone/build.zig | 1 + test/tests.zig | 87 ++- tools/runner-check.zig | 151 +++++ 21 files changed, 1323 insertions(+), 170 deletions(-) create mode 100644 test/incremental2/change_panic_handler_explicit/0-main.zig create mode 100644 test/incremental2/change_panic_handler_explicit/1-main.zig create mode 100644 test/incremental2/change_panic_handler_explicit/2-main.zig create mode 100644 test/incremental2/change_panic_handler_explicit/manifest create mode 100644 test/incremental2/hello create mode 100644 test/src/runner.zig create mode 100644 tools/runner-check.zig diff --git a/build.zig b/build.zig index 6b94dd1d195da678e7ef1335d68464e8fd9dcadc..df065484e061352b3f5a33eebcc3c969d7d87b93 100644 --- a/build.zig +++ b/build.zig @@ -804,6 +804,27 @@ pub fn build(b: *std.Build) !void { .skip_wasm = skip_wasm, .max_rss = 4_300_000_000, })) |test_libc_nsz_step| test_step.dependOn(test_libc_nsz_step); + + const runner = b.addExecutable(.{ + .name = "runner", + .root_module = b.createModule(.{ + .root_source_file = b.path("test/src/runner.zig"), + .target = b.graph.host, + }), + }); + test_step.dependOn(try tests.addNewIncrementalTests(b, runner, .{ + .test_filters = test_filters, + .test_target_filters = test_target_filters, + .skip_non_native = skip_non_native, + .skip_wasm = skip_wasm, + .skip_freebsd = skip_freebsd, + .skip_netbsd = skip_netbsd, + .skip_openbsd = skip_openbsd, + .skip_windows = skip_windows, + .skip_darwin = skip_darwin, + .skip_linux = skip_linux, + .skip_llvm = skip_llvm, + })); } fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { diff --git a/lib/compiler/Maker/Step.zig b/lib/compiler/Maker/Step.zig index f52640d0a490cb148467672db759d1051765a12a..97537845a7d52f2e35cbaddd999c40f4fdc6da0e 100644 --- a/lib/compiler/Maker/Step.zig +++ b/lib/compiler/Maker/Step.zig @@ -270,7 +270,10 @@ pub fn make( .compile => break :t null, .run => { const run_flags: Configuration.Step.Run.Flags = @bitCast(flags); - if (run_flags.stdio == .zig_test) break :t null; + switch (run_flags.stdio) { + .infer_from_args, .inherit, .check => {}, + .zig_test, .protocol => break :t null, + } }, else => {}, } diff --git a/lib/compiler/Maker/Step/Run.zig b/lib/compiler/Maker/Step/Run.zig index 7f5afff5cbb1968bb25537a716a715a9efb20506..0dc9b7acd60aba42460f5e326cd0143dd740693f 100644 --- a/lib/compiler/Maker/Step/Run.zig +++ b/lib/compiler/Maker/Step/Run.zig @@ -73,18 +73,24 @@ pub fn make( man.hash.add(conf_run.flags.color); man.hash.add(conf_run.flags.disable_zig_progress); - var any_dep_files = false; + var any_discovered_inputs = switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check, .zig_test => false, + .protocol => true, + }; var any_output_args = false; var any_cli_positionals = false; - for (conf_run.args.slice) |arg_index| { + for (switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check, .zig_test => conf_run.args.slice, + .protocol => conf_run.args.slice[0..1], // arguments communicated over protocol + }) |arg_index| { const arg = arg_index.get(conf); try argv_list.ensureUnusedCapacity(gpa, 1); switch (arg.flags.tag) { .string => { - const prefix = arg.prefix.value.?.slice(conf); - argv_list.appendAssumeCapacity(prefix); - man.hash.addBytesZ(prefix); + const string = arg.prefix.value.?.slice(conf); + argv_list.appendAssumeCapacity(string); + man.hash.addBytesZ(string); }, .path_file => { const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; @@ -118,14 +124,14 @@ pub fn make( result.writer.writeAll(prefix) catch return error.OutOfMemory; const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| - return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err }); + return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err }); defer file.close(io); var file_reader = file.reader(io, &.{}); _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { error.ReadFailed => switch (file_reader.err.?) { error.Canceled => |e| return e, - else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }), + else => |e| return step.fail(maker, "failed to read from {qf}: {t}", .{ file_path, e }), }, error.WriteFailed => return error.OutOfMemory, }; @@ -133,8 +139,8 @@ pub fn make( argv_list.appendAssumeCapacity(result.written()); man.hash.addBytesZ(prefix); - man.hash.addBytesZ(suffix); _ = try man.addInputPath(file_path, .{}); + man.hash.addBytesZ(suffix); }, .artifact => { const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; @@ -168,7 +174,7 @@ pub fn make( man.hash.addBytesZ(suffix); man.hash.add(arg.flags.dep_file); - any_dep_files = any_dep_files or arg.flags.dep_file; + if (arg.flags.dep_file) any_discovered_inputs = true; any_output_args = true; // Add a placeholder into the argument list because we need the @@ -176,9 +182,10 @@ pub fn make( // object directory is computed. try output_placeholders.append(gpa, .{ .index = @intCast(argv_list.items.len), + .offset = 0, .arg_index = arg_index, }); - argv_list.items.len += 1; + _ = argv_list.addOneAssumeCapacity(); }, .passthru => { any_cli_positionals = true; @@ -195,14 +202,285 @@ pub fn make( } } - man.hash.add(conf_run.flags.test_runner_mode); - if (conf_run.flags.test_runner_mode) { - const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false); - - try argv_list.ensureUnusedCapacity(gpa, 3); - argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string})); - argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed})); - argv_list.appendAssumeCapacity("--listen=-"); + var owned_dirs: std.bit_set.Dynamic = .{}; + var inherit_dirs: std.ArrayList(Io.Dir) = .empty; + var owned_files: std.bit_set.Dynamic = .{}; + var inherit_files: std.ArrayList(Io.File) = .empty; + var protocol_args: std.ArrayList(u8) = .empty; + defer { + for (inherit_dirs.items, 0..) |inherit_dir, inherit_dir_index| + if (owned_dirs.isSet(inherit_dir_index)) inherit_dir.close(io); + for (inherit_files.items, 0..) |inherit_file, inherit_file_index| + if (owned_files.isSet(inherit_file_index)) inherit_file.close(io); + owned_dirs.deinit(gpa); + inherit_dirs.deinit(gpa); + owned_files.deinit(gpa); + inherit_files.deinit(gpa); + protocol_args.deinit(gpa); + } + switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check => {}, + .zig_test => { + const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false); + + try argv_list.ensureUnusedCapacity(gpa, 3); + argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string})); + argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed})); + argv_list.appendAssumeCapacity("--listen=-"); + }, + .protocol => for (conf_run.args.slice[1..]) |arg_index| { + const arg = arg_index.get(conf); + switch (arg.flags.tag) { + .string => { + const string = arg.prefix.value.?.slice(conf); + + man.hash.addBytesZ(string); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + string.len + 1); + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string)); + protocol_args.appendSliceAssumeCapacity(string); + protocol_args.appendAssumeCapacity(0); + }, + .path_file => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); + + try owned_files.resize(gpa, owned_files.bit_length + 1, true); + try inherit_files.ensureUnusedCapacity(gpa, 1); + const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| + return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err }); + inherit_files.appendAssumeCapacity(file); + + man.hash.addBytesZ(prefix); + _ = try man.addInputPath(file_path, .{}); + man.hash.addBytesZ(suffix); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.File.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file)); + const file_handle: *align(1) Io.File.Handle = + @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle))); + file_handle.* = file.handle; + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .path_directory => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const dir_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); + + const has_sub_path = dir_path.sub_path.len > 0; + try owned_dirs.resize(gpa, owned_dirs.bit_length + 1, has_sub_path); + try inherit_dirs.ensureUnusedCapacity(gpa, 1); + const dir = if (has_sub_path) + dir_path.root_dir.handle.openDir(io, dir_path.sub_path, .{ + .iterate = true, + }) catch |err| return step.fail(maker, "unable to open input dir {qf}: {t}", .{ dir_path, err }) + else + dir_path.root_dir.handle; + inherit_dirs.appendAssumeCapacity(dir); + + man.hash.addBytesZ(prefix); + man.hash.addOptionalBytes(dir_path.root_dir.path); + man.hash.addBytes(dir_path.sub_path); + man.hash.addBytesZ(suffix); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.Dir.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_dir)); + const dir_handle: *align(1) Io.Dir.Handle = + @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.Dir.Handle))); + dir_handle.* = dir.handle; + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .file_content => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); + + try owned_files.resize(gpa, owned_files.bit_length + 1, true); + try inherit_files.ensureUnusedCapacity(gpa, 1); + const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| + return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err }); + inherit_files.appendAssumeCapacity(file); + + man.hash.addBytesZ(prefix); + _ = try man.addInputPath(file_path, .{}); + man.hash.addBytesZ(suffix); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.File.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file_content)); + const file_handle: *align(1) Io.File.Handle = + @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle))); + file_handle.* = file.handle; + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .artifact => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const producer_index = arg.producer.value.?; + const producer_step = producer_index.ptr(conf); + const producer = producer_step.extended.get(conf.extra).compile; + const producer_make_comp_step = maker.stepByIndex(producer_index); + const producer_make_comp = &producer_make_comp_step.extended.compile; + + const file_path = producer_make_comp.installed_path orelse + maker.generatedPath(producer.generated_bin.value.?); + + try owned_files.resize(gpa, owned_files.bit_length + 1, true); + try inherit_files.ensureUnusedCapacity(gpa, 1); + const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| + return step.fail(maker, "unable to open input artifact {qf}: {t}", .{ file_path, err }); + inherit_files.appendAssumeCapacity(file); + + man.hash.addBytesZ(prefix); + _ = try man.addInputPath(file_path, .{}); + man.hash.addBytesZ(suffix); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.File.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.input_file)); + const file_handle: *align(1) Io.File.Handle = + @ptrCast(protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle))); + file_handle.* = file.handle; + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .output_file => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const basename = arg.basename.value.?.slice(conf); + + man.hash.addBytesZ(prefix); + man.hash.addBytesZ(basename); + man.hash.addBytesZ(suffix); + assert(!arg.flags.dep_file); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.File.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.output_file)); + + // Add a placeholder into the argument list because we need the + // manifest hash to be updated with all arguments before the + // object directory is computed. + try owned_files.resize(gpa, owned_files.bit_length + 1, false); + try inherit_files.ensureUnusedCapacity(gpa, 1); + try output_placeholders.append(gpa, .{ + .index = @intCast(inherit_files.items.len), + .offset = @intCast(protocol_args.items.len), + .arg_index = arg_index, + }); + _ = inherit_files.addOneAssumeCapacity(); + + _ = protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.File.Handle)); + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .output_directory => { + const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; + const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; + const basename = arg.basename.value.?.slice(conf); + + man.hash.addBytesZ(prefix); + man.hash.addBytesZ(basename); + man.hash.addBytesZ(suffix); + assert(!arg.flags.dep_file); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + prefix.len + 1 + + 1 + @sizeOf(Io.Dir.Handle) + + 1 + suffix.len + 1); + if (prefix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.prefix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.output_dir)); + + // Add a placeholder into the argument list because we need the + // manifest hash to be updated with all arguments before the + // object directory is computed. + try owned_dirs.resize(gpa, owned_dirs.bit_length + 1, false); + try inherit_dirs.ensureUnusedCapacity(gpa, 1); + try output_placeholders.append(gpa, .{ + .index = @intCast(inherit_dirs.items.len), + .offset = @intCast(protocol_args.items.len), + .arg_index = arg_index, + }); + _ = inherit_dirs.addOneAssumeCapacity(); + + _ = protocol_args.addManyAsArrayAssumeCapacity(@sizeOf(Io.Dir.Handle)); + if (suffix.len > 0) { + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.suffix)); + protocol_args.appendSliceAssumeCapacity(prefix); + protocol_args.appendAssumeCapacity(0); + } + }, + .passthru => if (maker.run_args) |run_args| { + man.hash.addListOfBytes(run_args); + + for (run_args) |run_arg| { + try protocol_args.ensureUnusedCapacity(gpa, 1 + run_arg.len + 1); + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string)); + protocol_args.appendSliceAssumeCapacity(run_arg); + protocol_args.appendAssumeCapacity(0); + } + }, + .enable_darling => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_darling, arg.prefix.value, arg.suffix.value), + .enable_qemu => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_qemu, arg.prefix.value, arg.suffix.value), + .enable_rosetta => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_rosetta, arg.prefix.value, arg.suffix.value), + .enable_wasmtime => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_wasmtime, arg.prefix.value, arg.suffix.value), + .enable_wine => try thirdPartyToggleProtocol(gpa, &man.hash, &protocol_args, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value), + } + }, } switch (conf_run.stdin.u) { @@ -227,7 +505,7 @@ pub fn make( } switch (conf_run.flags.stdio) { - .infer_from_args, .inherit, .zig_test => {}, + .infer_from_args, .inherit, .zig_test, .protocol => {}, .check => { man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else ""); man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else ""); @@ -256,7 +534,7 @@ pub fn make( conf_run.captured_stdout.value == null and conf_run.captured_stderr.value == null, .inherit => true, - .check, .zig_test => false, + .check, .zig_test, .protocol => false, }; if (!has_side_effects and try step.cacheHitWatched(maker, &man, progress_node)) { @@ -268,13 +546,22 @@ pub fn make( return; } - if (!any_dep_files) { + if (!any_discovered_inputs) { // We already know the final output paths; use them directly. const digest = if (has_side_effects) man.hash.final() else man.missDigestHex(); const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest; try populateGeneratedStdIo(maker, &conf_run, &digest); - try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, argv_list.items); - try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, output_dir_path, null); + try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check, .zig_test => .{ .argv = argv_list.items }, + .protocol => .{ .protocol = .{ + .owned_dirs = &owned_dirs, + .inherit_dirs = inherit_dirs.items, + .owned_files = &owned_files, + .inherit_files = inherit_files.items, + .args = protocol_args.items, + } }, + }); + try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, inherit_dirs.items, inherit_files.items, protocol_args.items, has_side_effects, output_dir_path, null); if (!has_side_effects) try step.finalizeManifestAndWatch(maker, &man); return; } @@ -284,8 +571,17 @@ pub fn make( io.random(@ptrCast(&rand_int)); const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); - try populateGeneratedPathsCreateDirs(arena, run_index, maker, tmp_dir_path, output_placeholders.items, argv_list.items); - try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, tmp_dir_path, null); + try populateGeneratedPathsCreateDirs(arena, run_index, maker, tmp_dir_path, output_placeholders.items, switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check, .zig_test => .{ .argv = argv_list.items }, + .protocol => .{ .protocol = .{ + .owned_dirs = &owned_dirs, + .inherit_dirs = inherit_dirs.items, + .owned_files = &owned_files, + .inherit_files = inherit_files.items, + .args = protocol_args.items, + } }, + }); + try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, inherit_dirs.items, inherit_files.items, protocol_args.items, has_side_effects, tmp_dir_path, null); for (output_placeholders.items) |placeholder| { const arg = placeholder.arg_index.get(conf); @@ -296,10 +592,10 @@ pub fn make( var diagnostic: Cache.DepTokenizer.Token = undefined; man.addInputDepFile(generated_path, &diagnostic) catch |err| switch (err) { error.OutOfMemory, error.Canceled => |e| return e, - error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{ + error.InvalidDepFile => return step.fail(maker, "failed adding dep file {qf} to cache: {f}", .{ generated_path, diagnostic, }), - else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{ + else => |e| return step.fail(maker, "failed adding dep file {qf} to cache: {t}", .{ generated_path, e, }), }; @@ -307,13 +603,13 @@ pub fn make( var diagnostic: Cache.Manifest.AddDiscoveredDepFileDiagnostic = undefined; man.addDiscoveredDepFile(generated_path, &diagnostic) catch |err| switch (err) { error.OutOfMemory, error.Canceled => |e| return e, - error.InvalidDepFile => return step.fail(maker, "failed adding dep file {f} to cache: {f}", .{ + error.InvalidDepFile => return step.fail(maker, "failed adding dep file {qf} to cache: {f}", .{ generated_path, diagnostic.dep_tokenizer, }), - error.FileSystemFailure => return step.fail(maker, "failed adding a path from dep file {f} to cache: {f}", .{ + error.FileSystemFailure => return step.fail(maker, "failed adding a path from dep file {qf} to cache: {f}", .{ generated_path, diagnostic.add_discovered_path, }), - else => |e| return step.fail(maker, "failed adding dep file {f} to cache: {t}", .{ + else => |e| return step.fail(maker, "failed adding dep file {qf} to cache: {t}", .{ generated_path, e, }), }; @@ -342,7 +638,7 @@ pub fn make( ) catch |err| switch (err) { error.DirNotEmpty => { dst_path.root_dir.handle.deleteTree(io, dst_path.sub_path) catch |del_err| - return step.fail(maker, "failed to remove tree {f}: {t}", .{ dst_path, del_err }); + return step.fail(maker, "failed to remove tree {qf}: {t}", .{ dst_path, del_err }); Dir.rename( tmp_path.root_dir.handle, @@ -350,11 +646,11 @@ pub fn make( dst_path.root_dir.handle, dst_path.sub_path, io, - ) catch |retry_err| return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{ + ) catch |retry_err| return step.fail(maker, "failed to rename directory {qf} to {qf}: {t}", .{ tmp_path, dst_path, retry_err, }); }, - else => return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{ + else => return step.fail(maker, "failed to rename directory {qf} to {qf}: {t}", .{ tmp_path, dst_path, err, }), }; @@ -386,19 +682,30 @@ fn thirdPartyToggle( enable: ?Configuration.String, disable: ?Configuration.String, ) void { - if (setting) { - if (enable) |string| { - const slice = string.slice(conf); - if (man_hash) |h| h.addBytesZ(slice); - argv_list.appendAssumeCapacity(slice); - } - } else { - if (disable) |string| { - const slice = string.slice(conf); - if (man_hash) |h| h.addBytesZ(slice); - argv_list.appendAssumeCapacity(slice); - } - } + const string = (if (setting) enable else disable) orelse return; + const slice = string.slice(conf); + if (man_hash) |h| h.addBytesZ(slice); + argv_list.appendAssumeCapacity(slice); +} + +fn thirdPartyToggleProtocol( + gpa: Allocator, + man_hash: *Cache.HashHelper, + protocol_args: *std.ArrayList(u8), + conf: *const Configuration, + setting: bool, + enable: ?Configuration.String, + disable: ?Configuration.String, +) Allocator.Error!void { + const string = (if (setting) enable else disable) orelse return; + const slice = string.slice(conf); + + man_hash.addBytesZ(slice); + + try protocol_args.ensureUnusedCapacity(gpa, 1 + slice.len + 1); + protocol_args.appendAssumeCapacity(@backingInt(std.zig.Client.Message.Arg.string)); + protocol_args.appendSliceAssumeCapacity(slice); + protocol_args.appendAssumeCapacity(0); } /// Reads stdout of a Zig test process until a termination condition is reached: @@ -412,6 +719,7 @@ fn waitZigTest( maker: *Maker, child: *process.Child, progress_node: std.Progress.Node, + protocol_args: []const u8, multi_reader: *Io.File.MultiReader, opt_metadata: *?TestMetadata, results: *Step.TestResults, @@ -444,6 +752,14 @@ fn waitZigTest( .out = &stdin_writer.interface, }; + if (protocol_args.len > 0) { + try client.serveMessageHeader(.{ + .tag = .args, + .bytes_len = @intCast(protocol_args.len), + }); + try client.out.writeAll(protocol_args); + } + if (opt_metadata.*) |*md| { // Previous unit test process died or was killed; we're continuing where it left off requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; @@ -492,6 +808,7 @@ fn waitZigTest( var body_r: std.Io.Reader = .fixed(body); switch (header.tag) { + else => {}, // ignore other messages .zig_version => { if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail( maker, @@ -583,7 +900,6 @@ fn waitZigTest( requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; }, - else => {}, // ignore other messages } } } @@ -820,6 +1136,7 @@ const FuzzTestRunner = struct { } switch (header.tag) { + else => {}, // ignore other messages .zig_version => { if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail( maker, @@ -882,7 +1199,6 @@ const FuzzTestRunner = struct { f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer)); } }, - else => {}, // ignore other messages } instance.message.clearRetainingCapacity(); @@ -1146,6 +1462,7 @@ fn evalZigTest( maker: *Maker, progress_node: std.Progress.Node, spawn_options: process.SpawnOptions, + protocol_args: []const u8, fuzz_context: ?FuzzContext, ) !void { if (fuzz_context != null) { @@ -1194,6 +1511,7 @@ fn evalZigTest( maker, &child, progress_node, + protocol_args, &multi_reader, &test_metadata, &test_results, @@ -1402,7 +1720,7 @@ fn evalGeneric( var write_buffer: [1024]u8 = undefined; var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer); _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { - error.ReadFailed => return step.fail(maker, "failed to read from {f}: {t}", .{ + error.ReadFailed => return step.fail(maker, "failed to read from {qf}: {t}", .{ path, file_reader.err.?, }), error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{ @@ -1490,6 +1808,7 @@ fn evalGeneric( const IndexedOutput = struct { index: u32, + offset: u32, arg_index: Configuration.Step.Run.Arg.Index, }; @@ -1516,13 +1835,16 @@ pub fn rerunInFuzzMode( var argv_list: std.ArrayList([]const u8) = .empty; defer argv_list.deinit(gpa); - for (conf_run.args.slice) |arg_index| { + for (switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check, .zig_test => conf_run.args.slice, + .protocol => @panic("protocol fuzz unimplemented"), + }) |arg_index| { const arg = arg_index.get(conf); try argv_list.ensureUnusedCapacity(gpa, 1); switch (arg.flags.tag) { .string => { - const prefix = arg.prefix.value.?.slice(conf); - argv_list.appendAssumeCapacity(prefix); + const string = arg.prefix.value.?.slice(conf); + argv_list.appendAssumeCapacity(string); }, .path_file => { const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; @@ -1550,14 +1872,14 @@ pub fn rerunInFuzzMode( result.writer.writeAll(prefix) catch return error.OutOfMemory; const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| - return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err }); + return step.fail(maker, "unable to open input file {qf}: {t}", .{ file_path, err }); defer file.close(io); var file_reader = file.reader(io, &.{}); _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { error.ReadFailed => switch (file_reader.err.?) { error.Canceled => |e| return e, - else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }), + else => |e| return step.fail(maker, "failed to read from {qf}: {t}", .{ file_path, e }), }, error.WriteFailed => return error.OutOfMemory, }; @@ -1591,14 +1913,17 @@ pub fn rerunInFuzzMode( .enable_wine => thirdPartyToggle(null, &argv_list, conf, graph.enable_wine, arg.prefix.value, arg.suffix.value), } } + switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check => {}, + .zig_test => { + const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false); - if (conf_run.flags.test_runner_mode) { - const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }, false); - - try argv_list.ensureUnusedCapacity(gpa, 3); - argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string})); - argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed})); - argv_list.appendAssumeCapacity("--listen=-"); + try argv_list.ensureUnusedCapacity(gpa, 3); + argv_list.appendAssumeCapacity(try arena.print("--cache-dir={s}", .{cache_dir_string})); + argv_list.appendAssumeCapacity(try arena.print("--seed=0x{x}", .{graph.random_seed})); + argv_list.appendAssumeCapacity("--listen=-"); + }, + .protocol => unreachable, } step.clearFailedCommand(gpa); @@ -1607,7 +1932,7 @@ pub fn rerunInFuzzMode( var rand_int: u64 = undefined; io.random(@ptrCast(&rand_int)); const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); - try runCommand(arena, run, run_index, maker, prog_node, argv_list.items, has_side_effects, tmp_dir_path, .{ + try runCommand(arena, run, run_index, maker, prog_node, argv_list.items, &.{}, &.{}, &.{}, has_side_effects, tmp_dir_path, .{ .fuzz = fuzz, }); } @@ -1633,7 +1958,16 @@ fn populateGeneratedPathsCreateDirs( maker: *Maker, output_dir_path: []const u8, output_placeholders: []const IndexedOutput, - argv: [][]const u8, + populate: union(enum) { + argv: [][]const u8, + protocol: struct { + owned_dirs: *std.bit_set.Dynamic, + inherit_dirs: []Io.Dir, + owned_files: *std.bit_set.Dynamic, + inherit_files: []Io.File, + args: []u8, + }, + }, ) !void { const step = maker.stepByIndex(run_index); const conf = &maker.scanned_config.configuration; @@ -1653,16 +1987,52 @@ fn populateGeneratedPathsCreateDirs( const create_path: Path = .{ .root_dir = cache_root, .sub_path = switch (arg.flags.tag) { + else => unreachable, .output_file => Dir.path.dirname(generated_path.sub_path).?, .output_directory => generated_path.sub_path, - else => unreachable, }, }; create_path.root_dir.handle.createDirPath(io, create_path.sub_path) catch |err| - return step.fail(maker, "unable to make path {f}: {t}", .{ create_path, err }); + return step.fail(maker, "unable to make path {qf}: {t}", .{ create_path, err }); - const arg_output_path = try convertPathArg(arena, run_index, maker, generated_path, arg.flags.make_absolute); - argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix }); + switch (populate) { + .argv => |argv| { + const arg_output_path = try convertPathArg(arena, run_index, maker, generated_path, arg.flags.make_absolute); + argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix }); + }, + .protocol => |protocol| switch (arg.flags.tag) { + else => unreachable, + .output_file => { + const file = generated_path.root_dir.handle.createFile( + io, + generated_path.sub_path, + .{ .read = true }, + ) catch |err| return step.fail(maker, "unable to create output file {qf}: {t}", .{ + generated_path, err, + }); + protocol.inherit_files[placeholder.index] = file; + protocol.owned_files.set(placeholder.index); + const file_handle: *align(1) Io.File.Handle = + @ptrCast(protocol.args[placeholder.offset..][0..@sizeOf(Io.File.Handle)]); + file_handle.* = file.handle; + }, + .output_directory => { + assert(generated_path.sub_path.len > 0); + const dir = generated_path.root_dir.handle.openDir( + io, + generated_path.sub_path, + .{ .iterate = true }, + ) catch |err| return step.fail(maker, "unable to open output dir {qf}: {t}", .{ + generated_path, err, + }); + protocol.inherit_dirs[placeholder.index] = dir; + protocol.owned_dirs.set(placeholder.index); + const dir_handle: *align(1) Io.Dir.Handle = + @ptrCast(protocol.args[placeholder.offset..][0..@sizeOf(Io.Dir.Handle)]); + dir_handle.* = dir.handle; + }, + }, + } } } @@ -1711,6 +2081,9 @@ fn runCommand( maker: *Maker, progress_node: std.Progress.Node, argv: []const []const u8, + inherit_dirs: []const Io.Dir, + inherit_files: []const Io.File, + protocol_args: []const u8, has_side_effects: bool, output_dir_path: []const u8, fuzz_context: ?FuzzContext, @@ -1777,10 +2150,14 @@ fn runCommand( progress_node, argv, &environ_map, + inherit_dirs, + inherit_files, + protocol_args, has_side_effects, fuzz_context, ) catch |err| term: { switch (err) { + else => {}, error.InvalidExe, // cpu arch mismatch error.FileNotFound, // can happen with a wrong dynamic linker path => interpret: { @@ -1928,6 +2305,9 @@ fn runCommand( progress_node, interp_argv.items, &environ_map, + inherit_dirs, + inherit_files, + protocol_args, has_side_effects, fuzz_context, ) catch |e| { @@ -1937,21 +2317,21 @@ fn runCommand( }; }, error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e, - else => {}, } return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err }); }; - const generic_result = opt_generic_result orelse { - assert(conf_run.flags.stdio == .zig_test); - // Specific errors have already been reported, and test results are populated. All we need - // to do is report step failure if any test failed. - if (!step.test_results.isSuccess()) return error.MakeFailed; - return; + const generic_result = switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check => opt_generic_result.?, + .zig_test, .protocol => { + assert(opt_generic_result == null); + // Specific errors have already been reported, and test results are populated. All we need + // to do is report step failure if any test failed. + if (!step.test_results.isSuccess()) return error.MakeFailed; + return; + }, }; - assert(fuzz_context == null); - assert(conf_run.flags.stdio != .zig_test); // Capture stdout and stderr to GeneratedFile objects. const Stream = struct { @@ -1978,7 +2358,7 @@ fn runCommand( const sub_path_parent = output_path.dirname().?; sub_path_parent.root_dir.handle.createDirPath(io, sub_path_parent.sub_path) catch |err| - return step.fail(maker, "unable to make path {f}: {t}", .{ sub_path_parent, err }); + return step.fail(maker, "unable to make path {qf}: {t}", .{ sub_path_parent, err }); const data = switch (stream.trim_whitespace) { .none => stream.bytes.?, @@ -1989,12 +2369,12 @@ fn runCommand( output_path.root_dir.handle.writeFile(io, .{ .sub_path = output_path.sub_path, .data = data, - }) catch |err| return step.fail(maker, "unable to write file {f}: {t}", .{ output_path, err }); + }) catch |err| return step.fail(maker, "unable to write file {qf}: {t}", .{ output_path, err }); } } switch (conf_run.flags.stdio) { - .zig_test => unreachable, + .zig_test, .protocol => unreachable, .check => { if (conf_run.expect_stderr_exact.value) |bytes| { const expected_bytes = bytes.slice(conf); @@ -2186,6 +2566,9 @@ fn spawnChildAndCollect( progress_node: std.Progress.Node, argv: []const []const u8, environ_map: *EnvMap, + inherit_dirs: []const Io.Dir, + inherit_files: []const Io.File, + protocol_args: []const u8, has_side_effects: bool, fuzz_context: ?FuzzContext, ) !?EvalGenericResult { @@ -2199,7 +2582,10 @@ fn spawnChildAndCollect( if (fuzz_context != null) { assert(!has_side_effects); - assert(conf_run.flags.stdio == .zig_test); + switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check => unreachable, + .zig_test, .protocol => {}, + } } const child_cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd| @@ -2237,20 +2623,22 @@ fn spawnChildAndCollect( .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore, .inherit => .inherit, .check => .ignore, - .zig_test => .pipe, + .zig_test, .protocol => .pipe, }, .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) { .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore, .inherit => .inherit, .check => if (checksContainStdout(&conf_run)) .pipe else .ignore, - .zig_test => .pipe, + .zig_test, .protocol => .pipe, }, .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) { .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .pipe, .inherit => if (maker.protocol_server == null) .inherit else .pipe, .check => .pipe, - .zig_test => .pipe, + .zig_test, .protocol => .pipe, }, + .inherit_dirs = inherit_dirs, + .inherit_files = inherit_files, }; if (maker.protocol_server != null) { @@ -2263,35 +2651,38 @@ fn spawnChildAndCollect( assert(spawn_options.stderr != .inherit); } - if (conf_run.flags.stdio == .zig_test) { - try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?); - const started: Io.Clock.Timestamp = .now(io, .awake); - const result = evalZigTest(run, run_index, maker, progress_node, spawn_options, fuzz_context) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| e, - }; - step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); - try result; - return null; - } else { - const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit; - if (!conf_run.flags.disable_zig_progress and !inherit) { - spawn_options.progress_node = progress_node; - } - const terminal_mode: Io.Terminal.Mode = if (inherit) m: { - const stderr = try io.lockStderr(&.{}, graph.stderr_mode); - break :m stderr.terminal_mode; - } else .no_color; - defer if (inherit) io.unlockStderr(); - try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode); + switch (conf_run.flags.stdio) { + .infer_from_args, .inherit, .check => { + const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit; + if (!conf_run.flags.disable_zig_progress and !inherit) { + spawn_options.progress_node = progress_node; + } + const terminal_mode: Io.Terminal.Mode = if (inherit) m: { + const stderr = try io.lockStderr(&.{}, graph.stderr_mode); + break :m stderr.terminal_mode; + } else .no_color; + defer if (inherit) io.unlockStderr(); + try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode); - const started: Io.Clock.Timestamp = .now(io, .awake); - const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) { - error.Canceled => |e| return e, - else => |e| e, - }; - step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); - return try result; + const started: Io.Clock.Timestamp = .now(io, .awake); + const result = evalGeneric(arena, run_index, maker, spawn_options) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| e, + }; + step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); + return try result; + }, + .zig_test, .protocol => { + try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?); + const started: Io.Clock.Timestamp = .now(io, .awake); + const result = evalZigTest(run, run_index, maker, progress_node, spawn_options, protocol_args, fuzz_context) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| e, + }; + step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); + try result; + return null; + }, } } @@ -2341,7 +2732,7 @@ fn setColorEnvironmentVariables( .auto => { const capture_stderr = conf_run.captured_stderr.value != null or switch (conf_run.flags.stdio) { .check => checksContainStderr(conf_run), - .infer_from_args, .inherit, .zig_test => false, + .infer_from_args, .inherit, .zig_test, .protocol => false, }; if (capture_stderr) { continue :color .disable; diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 47993f66d4edb8905c6c497288825c103e80ba9a..730c6bcba58f9c216929acd6157f30c497508a6b 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -768,9 +768,8 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run { const run_step = Step.Run.create(b, step_name); run_step.producer = exe; + run_step.addArtifactArg(exe); if (exe.kind == .@"test") { - run_step.addArtifactArg(exe); - const test_server_mode: bool = s: { if (exe.test_runner) |r| break :s r.mode == .server; if (exe.use_llvm == false) { @@ -802,8 +801,6 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run { // communicate failure via its exit code. run_step.expectExitCode(0); } - } else { - run_step.addArtifactArg(exe); } return run_step; diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index efcd9d86ed32f16d6cb1b24427f098fbcbf94747..f0c3ff44d92678e6fbdf945e9d12883b75f95049 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -665,7 +665,7 @@ pub const Step = extern struct { pub const Tag = enum(u2) { none, bytes, lazy_path }; }; pub const TrimWhitespace = enum(u2) { none, all, leading, trailing }; - pub const StdIo = enum(u2) { infer_from_args, inherit, check, zig_test }; + pub const StdIo = enum(u3) { infer_from_args, inherit, check, zig_test, protocol }; pub const ExpectTermStatus = enum(u2) { exited, signal, stopped, unknown }; @@ -675,7 +675,6 @@ pub const Step = extern struct { skip_foreign_checks: bool, failing_to_execute_foreign_is_an_error: bool, has_side_effects: bool, - test_runner_mode: bool, color: Color, stdin: StdIn.Tag, stdio: StdIo, diff --git a/lib/std/Build/Serialize.zig b/lib/std/Build/Serialize.zig index eae93e66e46515fc5d5f3e33634966cc6cc9bbaf..802bf4cbcff33db51ca5c406828e5325bb707895 100644 --- a/lib/std/Build/Serialize.zig +++ b/lib/std/Build/Serialize.zig @@ -462,13 +462,13 @@ pub fn write(b: *std.Build, wc: *Configuration.Wip, writer: *std.Io.Writer) !voi .skip_foreign_checks = run.skip_foreign_checks, .failing_to_execute_foreign_is_an_error = run.failing_to_execute_foreign_is_an_error, .has_side_effects = run.has_side_effects, - .test_runner_mode = run.test_runner_mode, .color = run.color, .stdio = switch (run.stdio) { .infer_from_args => .infer_from_args, .inherit => .inherit, .check => .check, .zig_test => .zig_test, + .protocol => .protocol, }, .stdin = switch (run.stdin) { .none => .none, diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 7d271e10e1461606574b96a6966332975b39b706..e8a26ea36ba163c115df3be63abed8db84ec59d2 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -92,7 +92,6 @@ captured_stdout: ?*CapturedStdIo, captured_stderr: ?*CapturedStdIo, has_side_effects: bool, -test_runner_mode: bool = false, /// If this Run step was produced by a Compile step, it is tracked here. producer: ?*Step.Compile, @@ -127,7 +126,10 @@ pub const StdIo = union(enum) { check: std.ArrayList(Check), /// This Run step is running a zig unit test binary and will communicate /// extra metadata over the IPC protocol. + /// Deprecated, use `.protocol`. zig_test, + /// This Run step is running a binary that will communicate over the IPC protocol. + protocol, pub const Check = union(enum) { expect_stderr_exact: []const u8, @@ -243,10 +245,13 @@ pub fn setName(run: *Run, name: []const u8) void { run.rename_step_with_output_arg = false; } +/// Deprecated, use `enableProtocolMode`. pub fn enableTestRunnerMode(run: *Run) void { - if (run.test_runner_mode) return; run.stdio = .zig_test; - run.test_runner_mode = true; +} + +pub fn enableProtocolMode(run: *Run) void { + run.stdio = .protocol; } pub const ArgOptions = struct { @@ -750,8 +755,10 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void { } pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath { - assert(run.stdio != .inherit); - assert(run.stdio != .zig_test); + switch (run.stdio) { + .infer_from_args, .check => {}, + .inherit, .zig_test, .protocol => unreachable, + } const b = run.step.owner; const graph = b.graph; @@ -771,8 +778,10 @@ pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa } pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath { - assert(run.stdio != .inherit); - assert(run.stdio != .zig_test); + switch (run.stdio) { + .infer_from_args, .check => {}, + .inherit, .zig_test, .protocol => unreachable, + } const b = run.step.owner; const graph = b.graph; diff --git a/lib/std/Io/Reader/Limited.zig b/lib/std/Io/Reader/Limited.zig index bc372de06b1e023638f648c8cbd7533f3fd7dec3..fc1e219176dfa8b5ffb37ba6d2759a34f3d2f69b 100644 --- a/lib/std/Io/Reader/Limited.zig +++ b/lib/std/Io/Reader/Limited.zig @@ -28,8 +28,7 @@ pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited { fn stream(r: *Reader, w: *Writer, limit: Limit) Reader.StreamError!usize { const l: *Limited = @fieldParentPtr("interface", r); if (l.remaining == .nothing) return error.EndOfStream; - const combined_limit = limit.min(l.remaining); - const n = try l.unlimited.stream(w, combined_limit); + const n = try l.unlimited.stream(w, limit.min(l.remaining)); l.remaining = l.remaining.subtract(n).?; return n; } @@ -53,8 +52,7 @@ test stream { fn discard(r: *Reader, limit: Limit) Reader.Error!usize { const l: *Limited = @fieldParentPtr("interface", r); if (l.remaining == .nothing) return error.EndOfStream; - const combined_limit = limit.min(l.remaining); - const n = try l.unlimited.discard(combined_limit); + const n = try l.unlimited.discard(limit.min(l.remaining)); l.remaining = l.remaining.subtract(n).?; return n; } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3b688ecc93c36631fa0c0bad423155abb8020eeb..58216a3497264988026f05449f5df4450f11e49f 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -4462,7 +4462,7 @@ fn dirCreateFilePosix( var fl_flags: usize = fl: { const syscall: Syscall = try .start(); while (true) { - const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); + const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(u32, 0)); switch (posix.errno(rc)) { .SUCCESS => { syscall.finish(); @@ -5058,7 +5058,7 @@ fn dirOpenFilePosix( var fl_flags: usize = fl: { const syscall: Syscall = try .start(); while (true) { - const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); + const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(u32, 0)); switch (posix.errno(rc)) { .SUCCESS => { syscall.finish(); @@ -15230,16 +15230,12 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr; - const prog_fileno = 3; - comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno); - const env_block = env_block: { - const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ - .zig_progress_fd = prog_fd, + .zig_progress_fd = prog_pipe[1], }); break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ - .zig_progress_fd = prog_fd, + .zig_progress_fd = prog_pipe[1], }); }; @@ -15267,23 +15263,42 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked; const ep1 = err_pipe[1]; - setUpChildIo(options.stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); - setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); - setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); - + // Must happen befor clobbering fds below. switch (options.cwd) { .inherit => {}, - .dir => |cwd| { - fchdir(cwd.handle) catch |err| forkBail(ep1, err); - }, - .path => |cwd| { - chdir(cwd) catch |err| forkBail(ep1, err); - }, + .dir => |cwd| fchdir(cwd.handle) catch |err| forkBail(ep1, err), + .path => |cwd| chdir(cwd) catch |err| forkBail(ep1, err), } - // Must happen after fchdir above, the cwd file descriptor might be - // equal to prog_fileno and be clobbered by this dup2 call. - if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| forkBail(ep1, err); + for (options.inherit_dirs) |dir| switch (posix.errno( + posix.system.fcntl(dir.handle, posix.F.SETFD, @as(u32, 0)), + )) { + .SUCCESS => {}, + else => |err| forkBail(ep1, posix.unexpectedErrno(err)), + }; + for (options.inherit_files) |file| switch (posix.errno( + posix.system.fcntl(file.handle, posix.F.SETFD, @as(u32, 0)), + )) { + .SUCCESS => {}, + else => |err| forkBail(ep1, posix.unexpectedErrno(err)), + }; + if (prog_pipe[1] != -1) switch (posix.errno( + posix.system.fcntl(prog_pipe[1], posix.F.SETFD, @as(u32, 0)), + )) { + .SUCCESS => {}, + else => |err| forkBail(ep1, posix.unexpectedErrno(err)), + }; + for ( + [_]process.SpawnOptions.StdIo{ options.stdin, options.stdout, options.stderr }, + [_]posix.fd_t{ stdin_pipe[0], stdout_pipe[1], stderr_pipe[1] }, + [_]posix.fd_t{ posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO }, + ) |stdio, pipe_fd, std_fd| switch (stdio) { + .pipe => dup2(pipe_fd, std_fd) catch |err| forkBail(ep1, err), + .close => closeFd(std_fd), + .inherit => {}, + .ignore => dup2(dev_null_fd, std_fd) catch |err| forkBail(ep1, err), + .file => |file| dup2(file.handle, std_fd) catch |err| forkBail(ep1, err), + }; if (options.gid) |gid| { switch (posix.errno(posix.system.setregid(gid, gid))) { @@ -15746,16 +15761,6 @@ fn destroyPipe(pipe: [2]posix.fd_t) void { if (pipe[0] != pipe[1]) closeFd(pipe[1]); } -fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { - switch (stdio) { - .pipe => try dup2(pipe_fd, std_fileno), - .close => closeFd(std_fileno), - .inherit => {}, - .ignore => try dup2(dev_null_fd, std_fileno), - .file => |file| try dup2(file.handle, std_fileno), - } -} - fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { const t: *Threaded = @ptrCast(@alignCast(userdata)); diff --git a/lib/std/bit_set.zig b/lib/std/bit_set.zig index f3e477cff91fb5ea7da76b9235ac7d3cfa47d512..b758697d63d6f6e288520e8d3d881a79a8a66d85 100644 --- a/lib/std/bit_set.zig +++ b/lib/std/bit_set.zig @@ -723,13 +723,8 @@ pub const Dynamic = struct { // That slot holds the size of the true allocation, which // is needed by Zig's allocator interface in case a shrink // fails. - - // Don't modify this value. Ideally it would go in const data so - // modifications would cause a bus error, but the only way - // to discard a const qualifier is through intFromPtr, which - // cannot currently round trip at comptime. - var empty_masks_data = [_]MaskInt{ 0, undefined }; - const empty_masks_ptr = empty_masks_data[1..2]; + const empty_masks_data = [_]MaskInt{ 0, undefined }; + const empty_masks_ptr = @constCast(empty_masks_data[1..2]); /// Creates a bit set with no elements present. /// If bit_length is not zero, deinit must eventually be called. diff --git a/lib/std/process.zig b/lib/std/process.zig index 651d854edb623b25437e418640fa54f7f4e6c67a..716c02f6f0875f524f830566ed099bf35dcb013b 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -390,6 +390,8 @@ pub const SpawnOptions = struct { stdin: StdIo = .inherit, stdout: StdIo = .inherit, stderr: StdIo = .inherit, + inherit_dirs: []const Dir = &.{}, + inherit_files: []const File = &.{}, /// Set to true to obtain rusage information for the child process. /// Depending on the target platform and implementation status, the diff --git a/lib/std/zig/Client.zig b/lib/std/zig/Client.zig index cedda191b99042affaba76eec838fd9f332abdbb..d2902a53cbe30a37a0b32ee4184e0081df8185dc 100644 --- a/lib/std/zig/Client.zig +++ b/lib/std/zig/Client.zig @@ -60,6 +60,7 @@ pub const Message = struct { start_fuzzing, /// The message body has the same format as in Server. new_fuzz_input, + args, /// Asks the server to run a list of steps. /// Body is a `BuildSteps`. @@ -69,6 +70,33 @@ pub const Message = struct { _, }; + pub const Arg = enum(u8) { + /// Trailing: + /// * string: [:0]const u8, + string, + /// Trailing: + /// * string: [:0]const u8, + prefix, + /// Trailing: + /// * string: [:0]const u8, + suffix, + /// Trailing: + /// * handle: Handle, + input_file, + /// Trailing: + /// * handle: Handle, + input_dir, + /// Trailing: + /// * handle: Handle, + input_file_content, + /// Trailing: + /// * handle: Handle, + output_file, + /// Trailing: + /// * handle: Handle, + output_dir, + }; + /// Trailing: /// * step_indices: [step_count]std.Build.Configuration.Step.Index, pub const BuildSteps = extern struct { diff --git a/test/incremental2/change_panic_handler_explicit/0-main.zig b/test/incremental2/change_panic_handler_explicit/0-main.zig new file mode 100644 index 0000000000000000000000000000000000000000..608b4a8f1beac504fd564bc89705d2c676076b7b --- /dev/null +++ b/test/incremental2/change_panic_handler_explicit/0-main.zig @@ -0,0 +1,46 @@ +pub fn main() !u8 { + var a: u8 = undefined; + a = 255; + _ = a + 1; + return 1; +} +const no_panic = std.debug.no_panic; +pub const panic = struct { + pub const call = myPanic; + pub fn integerOverflow() noreturn { + @panic("integer overflow"); + } + pub const sentinelMismatch = no_panic.sentinelMismatch; + pub const unwrapError = no_panic.unwrapError; + pub const outOfBounds = no_panic.outOfBounds; + pub const startGreaterThanEnd = no_panic.startGreaterThanEnd; + pub const inactiveUnionField = no_panic.inactiveUnionField; + pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder; + pub const reachedUnreachable = no_panic.reachedUnreachable; + pub const unwrapNull = no_panic.unwrapNull; + pub const castToNull = no_panic.castToNull; + pub const incorrectAlignment = no_panic.incorrectAlignment; + pub const invalidErrorCode = no_panic.invalidErrorCode; + pub const unexpectedErrorCode = no_panic.unexpectedErrorCode; + pub const integerOutOfBounds = no_panic.integerOutOfBounds; + pub const shlOverflow = no_panic.shlOverflow; + pub const shrOverflow = no_panic.shrOverflow; + pub const divideByZero = no_panic.divideByZero; + pub const exactDivisionRemainder = no_panic.exactDivisionRemainder; + pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds; + pub const corruptSwitch = no_panic.corruptSwitch; + pub const shiftRhsTooBig = no_panic.shiftRhsTooBig; + pub const invalidEnumValue = no_panic.invalidEnumValue; + pub const forLenMismatch = no_panic.forLenMismatch; + pub const copyLenMismatch = no_panic.copyLenMismatch; + pub const memcpyAlias = no_panic.memcpyAlias; + pub const noreturnReturned = no_panic.noreturnReturned; + pub const loadUninstantiableType = no_panic.loadUninstantiableType; +}; +fn myPanic(msg: []const u8, _: ?usize) noreturn { + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + stdout_writer.interface.print("panic message: {s}\n", .{msg}) catch {}; + std.process.exit(0); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental2/change_panic_handler_explicit/1-main.zig b/test/incremental2/change_panic_handler_explicit/1-main.zig new file mode 100644 index 0000000000000000000000000000000000000000..a1a8742a597b78f18e449dbc60348c4974bb5874 --- /dev/null +++ b/test/incremental2/change_panic_handler_explicit/1-main.zig @@ -0,0 +1,46 @@ +pub fn main() !u8 { + var a: u8 = undefined; + a = 255; + _ = a + 1; + return 1; +} +const no_panic = std.debug.no_panic; +pub const panic = struct { + pub const call = myPanic; + pub fn integerOverflow() noreturn { + @panic("integer overflow"); + } + pub const sentinelMismatch = no_panic.sentinelMismatch; + pub const unwrapError = no_panic.unwrapError; + pub const outOfBounds = no_panic.outOfBounds; + pub const startGreaterThanEnd = no_panic.startGreaterThanEnd; + pub const inactiveUnionField = no_panic.inactiveUnionField; + pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder; + pub const reachedUnreachable = no_panic.reachedUnreachable; + pub const unwrapNull = no_panic.unwrapNull; + pub const castToNull = no_panic.castToNull; + pub const incorrectAlignment = no_panic.incorrectAlignment; + pub const invalidErrorCode = no_panic.invalidErrorCode; + pub const unexpectedErrorCode = no_panic.unexpectedErrorCode; + pub const integerOutOfBounds = no_panic.integerOutOfBounds; + pub const shlOverflow = no_panic.shlOverflow; + pub const shrOverflow = no_panic.shrOverflow; + pub const divideByZero = no_panic.divideByZero; + pub const exactDivisionRemainder = no_panic.exactDivisionRemainder; + pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds; + pub const corruptSwitch = no_panic.corruptSwitch; + pub const shiftRhsTooBig = no_panic.shiftRhsTooBig; + pub const invalidEnumValue = no_panic.invalidEnumValue; + pub const forLenMismatch = no_panic.forLenMismatch; + pub const copyLenMismatch = no_panic.copyLenMismatch; + pub const memcpyAlias = no_panic.memcpyAlias; + pub const noreturnReturned = no_panic.noreturnReturned; + pub const loadUninstantiableType = no_panic.loadUninstantiableType; +}; +fn myPanic(msg: []const u8, _: ?usize) noreturn { + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + stdout_writer.interface.print("new panic message: {s}\n", .{msg}) catch {}; + std.process.exit(0); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental2/change_panic_handler_explicit/2-main.zig b/test/incremental2/change_panic_handler_explicit/2-main.zig new file mode 100644 index 0000000000000000000000000000000000000000..9b6143b2fa2dc39db42a6bdccc82acaf428986b7 --- /dev/null +++ b/test/incremental2/change_panic_handler_explicit/2-main.zig @@ -0,0 +1,46 @@ +pub fn main() !u8 { + var a: u8 = undefined; + a = 255; + _ = a + 1; + return 1; +} +const no_panic = std.debug.no_panic; +pub const panic = struct { + pub const call = myPanicNew; + pub fn integerOverflow() noreturn { + @panic("integer overflow"); + } + pub const sentinelMismatch = no_panic.sentinelMismatch; + pub const unwrapError = no_panic.unwrapError; + pub const outOfBounds = no_panic.outOfBounds; + pub const startGreaterThanEnd = no_panic.startGreaterThanEnd; + pub const inactiveUnionField = no_panic.inactiveUnionField; + pub const sliceCastLenRemainder = no_panic.sliceCastLenRemainder; + pub const reachedUnreachable = no_panic.reachedUnreachable; + pub const unwrapNull = no_panic.unwrapNull; + pub const castToNull = no_panic.castToNull; + pub const incorrectAlignment = no_panic.incorrectAlignment; + pub const invalidErrorCode = no_panic.invalidErrorCode; + pub const unexpectedErrorCode = no_panic.unexpectedErrorCode; + pub const integerOutOfBounds = no_panic.integerOutOfBounds; + pub const shlOverflow = no_panic.shlOverflow; + pub const shrOverflow = no_panic.shrOverflow; + pub const divideByZero = no_panic.divideByZero; + pub const exactDivisionRemainder = no_panic.exactDivisionRemainder; + pub const integerPartOutOfBounds = no_panic.integerPartOutOfBounds; + pub const corruptSwitch = no_panic.corruptSwitch; + pub const shiftRhsTooBig = no_panic.shiftRhsTooBig; + pub const invalidEnumValue = no_panic.invalidEnumValue; + pub const forLenMismatch = no_panic.forLenMismatch; + pub const copyLenMismatch = no_panic.copyLenMismatch; + pub const memcpyAlias = no_panic.memcpyAlias; + pub const noreturnReturned = no_panic.noreturnReturned; + pub const loadUninstantiableType = no_panic.loadUninstantiableType; +}; +fn myPanicNew(msg: []const u8, _: ?usize) noreturn { + var stdout_writer = std.Io.File.stdout().writerStreaming(io, &.{}); + stdout_writer.interface.print("third panic message: {s}\n", .{msg}) catch {}; + std.process.exit(0); +} +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); diff --git a/test/incremental2/change_panic_handler_explicit/manifest b/test/incremental2/change_panic_handler_explicit/manifest new file mode 100644 index 0000000000000000000000000000000000000000..2a873447048a981cefea243c2a33c8e2e323dd7b --- /dev/null +++ b/test/incremental2/change_panic_handler_explicit/manifest @@ -0,0 +1,23 @@ +#exe { +-Mroot=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#} +#check stdout { +new panic message: integer overflow +#} + +#write main.zig 2-main.zig +#update { +change the panic handler function value#} +#check stdout { +third panic message: integer overflow +#} diff --git a/test/incremental2/hello b/test/incremental2/hello new file mode 100644 index 0000000000000000000000000000000000000000..74a45d2a8b81afebe334968569949b6c82d4c397 --- /dev/null +++ b/test/incremental2/hello @@ -0,0 +1,28 @@ +#exe { +-Mroot=main.zig +#} + +#write main.zig { +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); +pub fn main() !void { + try std.Io.File.stdout().writeStreamingAll(io, "good morning\n"); +} +#} +#update +#check stdout { +good morning +#} + +#write main.zig { +const std = @import("std"); +const io = std.Io.Threaded.global_single_threaded.io(); +pub fn main() !void { + try std.Io.File.stdout().writeStreamingAll(io, "おはようございます\n"); +} +#} +#update { +change the string#} +#check stdout { +おはようございます +#} diff --git a/test/src/runner.zig b/test/src/runner.zig new file mode 100644 index 0000000000000000000000000000000000000000..2b5778a8553bc0adb0b348f9e964a762e38c6154 --- /dev/null +++ b/test/src/runner.zig @@ -0,0 +1,279 @@ +const std = @import("std"); +const Args = struct { + test_dir: ?std.Io.Dir, + manifest_file: ?std.Io.File, + zig_file: ?std.Io.File, + lib_dir: ?std.Io.Dir, + src_dir: ?std.Io.Dir, +}; +pub fn main(init: std.process.Init) !void { + var stdin_buffer: [512]u8 = undefined; + var stdout_buffer: [512]u8 = undefined; + var stderr_buffer: [512]u8 = undefined; + var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer); + var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer); + var stderr: std.Io.File.Writer = .initStreaming(.stderr(), init.io, &stderr_buffer); + var server: std.zig.Server = .{ + .in = &stdin.interface, + .out = &stdout.interface, + }; + try server.serveStringMessage(.zig_version, @import("builtin").zig_version_string); + var args: Args = .{ + .test_dir = null, + .manifest_file = null, + .zig_file = null, + .lib_dir = null, + .src_dir = null, + }; + while (true) { + const hdr = try server.receiveMessage(); + switch (hdr.tag) { + else => { + std.debug.print("unsupported message: {t}\n", .{hdr.tag}); + std.process.exit(1); + }, + .exit => { + std.process.exit(0); + }, + .args => { + const args_body = try init.arena.allocator().alloc(u8, hdr.bytes_len); + try server.in.readSliceAll(args_body); + var state: enum { positional, zig, lib, src } = .positional; + 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; + 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; + _ = string; + }, + .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; + 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 + unreachable; + continue; + }, + .suffix => unreachable, + .input_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: std.Io.Dir = .{ + .handle = dir_handle.*, + }; + switch (state) { + .positional => { + std.debug.assert(args.test_dir == null); + args.test_dir = dir; + std.debug.assert(args.manifest_file == null); + args.manifest_file = try dir.openFile(init.io, "manifest", .{}); + }, + .zig => unreachable, + .lib => { + std.debug.assert(args.lib_dir == null); + args.lib_dir = dir; + }, + .src => { + std.debug.assert(args.src_dir == null); + args.src_dir = dir; + }, + } + }, + .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: std.Io.File = .{ + .handle = file_handle.*, + .flags = .{ .nonblocking = false }, + }; + switch (state) { + .positional => { + std.debug.assert(args.manifest_file == null); + args.manifest_file = file; + }, + .zig => { + std.debug.assert(args.zig_file == null); + args.zig_file = file; + }, + .lib, .src => unreachable, + } + }, + } + state = .positional; + } + }, + .query_test_metadata => try server.serveTestMetadata(.{ + .names = &.{0}, + .expected_panic_msgs = &.{0}, + .string_bytes = "runner\x00", + }), + .run_test => { + const test_index = try server.receiveBody_u32(); + try server.serveBodylessMessage(.test_started); + const test_result = runTest(init.io, &stderr.interface, &args); + try stderr.flush(); + try server.serveTestResults(.{ + .index = test_index, + .flags = .{ + .status = if (test_result) .pass else |err| switch (err) { + else => status: { + if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace); + break :status .fail; + }, + error.SkipZigTest => .skip, + }, + .fuzz = false, + .log_err_count = 0, + .leak_count = 0, + }, + }); + }, + } + } +} +fn runTest(io: std.Io, stderr: *std.Io.Writer, args: *const Args) anyerror!void { + const manifest_file = args.manifest_file orelse return error.MissingManifestArg; + const src_dir = args.src_dir orelse return error.MissingSrcDir; + var manifest_buffer: [512]u8 = undefined; + var manifest_fr = manifest_file.reader(io, &manifest_buffer); + var skip_delimiter = false; + var update_mtime = std.Io.Clock.real.now(io); + var update_num: usize = 0; + while (manifest_fr.interface.takeSentinel('\n')) |untrimmed_line| { + const line = std.mem.trimEnd(u8, untrimmed_line, "\r"); + var line_it = std.mem.tokenizeScalar(u8, line, ' '); + const cmd_str = std.mem.cutPrefix(u8, line_it.next() orelse continue, "#").?; + if (skip_delimiter) { + std.debug.assert(std.mem.eql(u8, cmd_str, "}")); + skip_delimiter = false; + continue; + } + const cmd = std.meta.stringToEnum( + enum { obj, exe, lib, write, delete, update, check }, + cmd_str, + ) orelse return error.UnknownCommand; + var maybe_arg = line_it.next(); + var contents_file: ?std.Io.File = null; + defer if (contents_file) |file| file.close(io); + var contents_impl: union { + dr: DelimitedReader, + fr: std.Io.File.Reader, + } = undefined; + const contents_r: *std.Io.Reader = contents_r: { + switch (cmd) { + else => {}, + .delete => break :contents_r .ending, + } + 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; + }; + if (std.mem.eql(u8, contents_path, "{")) { + contents_impl = .{ .dr = .init(&manifest_fr.interface, "#}", &.{}) }; + skip_delimiter = true; + break :contents_r &contents_impl.dr.interface; + } + const test_dir = args.test_dir orelse return error.ContentsFileInFileTest; + contents_file = try test_dir.openFile(io, contents_path, .{}); + contents_impl = .{ .fr = contents_file.?.reader(io, &.{}) }; + break :contents_r &contents_impl.fr.interface; + }; + switch (cmd) { + else => { + try stderr.print("{t} {s} {{\n", .{ cmd, maybe_arg orelse "" }); + _ = try contents_r.streamRemaining(stderr); + try stderr.writeAll("\n}\n"); + }, + .write => { + const file = try src_dir.createFile(io, maybe_arg orelse return error.MissingArg, .{}); + defer file.close(io); + var fw = file.writer(io, &.{}); + _ = try contents_r.streamRemaining(&fw.interface); + try file.setTimestamps(io, .{ .modify_timestamp = .{ .new = update_mtime } }); + }, + .delete => try src_dir.deleteFile(io, maybe_arg orelse return error.MissingArg), + .update => { + var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; + const name = 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], + }; + _ = try contents_r.discardRemaining(); + try stderr.print("update({q})\n", .{name}); + update_mtime = update_mtime.addDuration(.fromSeconds(2)); + update_num += 1; + }, + } + std.debug.assert(try contents_r.discardRemaining() == 0); + } else |err| switch (err) { + else => |e| return e, + error.EndOfStream => if (skip_delimiter) return error.MissingDelimiter, + } +} +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; + } +}; diff --git a/test/standalone/build.zig b/test/standalone/build.zig index ab4262c8b90686517cf32f90b7b99263c72d3969..93488b0ad94b3126ae8d76ea73f9f64612e96120 100644 --- a/test/standalone/build.zig +++ b/test/standalone/build.zig @@ -44,6 +44,7 @@ pub fn build(b: *std.Build) void { "../../tools/generate_linux_syscalls.zig", "../../tools/process_headers.zig", "../../tools/migrate_langref.zig", + "../../tools/runner-check.zig", "../../tools/unskip_spirv_tests.zig", "../../tools/update-linux-headers.zig", "../../tools/update_clang_options.zig", diff --git a/test/tests.zig b/test/tests.zig index ff475109fb084df3c5948f05c7d9b0f0a4ffd0b5..db2973e413b4e1babb231a79435a6cd1b8f3cd07 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -3365,7 +3365,7 @@ pub fn addIncrementalTests( var it = try dir.walk(b.graph.arena); while (try it.next(io)) |entry| { - if (std.mem.endsWith(u8, entry.basename, ".swp")) continue; + if (isEditorFileName(entry.basename)) continue; for (options.test_filters) |test_filter| { if (std.mem.find(u8, entry.path, test_filter)) |_| break; @@ -3435,6 +3435,91 @@ pub fn addIncrementalTests( } } +pub fn addNewIncrementalTests( + b: *std.Build, + runner: *std.Build.Step.Compile, + options: IncrementalTestOptions, +) !*Step { + const tests_step = b.step("test-new-incremental", "Run the new incremental compilation test cases"); + + const tests_path = b.path("test/incremental2"); + b.dependOnDirectoryContents(tests_path); + + var tests_dir = try b.root.openDir(b.graph.io, "test/incremental2", .{ .iterate = true }); + defer tests_dir.close(b.graph.io); + var test_it = tests_dir.iterate(); + while (try test_it.next(b.graph.io)) |@"test"| { + const test_path = tests_path.path(b, @"test".name); + switch (@"test".kind) { + else => continue, + .file => if (isEditorFileName(@"test".name)) continue, + .directory => {}, + } + + for (incremental_targets) |test_target| { + const resolved_target = b.resolveTargetQuery(test_target.target); + + if (options.skip_non_native and !isNative(&resolved_target, &b.graph.host.result)) + continue; + + const target = &resolved_target.result; + + if (options.skip_wasm and target.cpu.arch.isWasm()) continue; + + if (options.skip_freebsd and target.os.tag == .freebsd) continue; + if (options.skip_netbsd and target.os.tag == .netbsd) continue; + if (options.skip_openbsd and target.os.tag == .openbsd) continue; + if (options.skip_windows and target.os.tag == .windows) continue; + if (options.skip_darwin and target.os.tag.isDarwin()) continue; + if (options.skip_linux and target.os.tag == .linux) continue; + + if (options.skip_llvm and test_target.backend == .llvm) continue; + + const target_str = b.fmt("{s}-{t}", .{ + resolved_target.query.zigTriple(b.allocator) catch @panic("OOM"), + test_target.backend, + }); + + if (options.test_target_filters.len > 0) { + for (options.test_target_filters) |filter| { + if (std.mem.find(u8, target_str, filter) != null) break; + } else continue; + } + + const run = b.addRunArtifact(runner); + run.setName(@"test".name); + run.enableProtocolMode(); + + switch (@"test".kind) { + else => continue, + .file => run.addFileArg(test_path), + .directory => run.addDirectoryArg(test_path), + } + run.addPrefixedFileArg("--zig=", .zig_exe); + run.addPrefixedDirectoryArg("--lib=", .zig_lib); + _ = run.addPrefixedOutputDirectoryArg("--src=", "src"); + run.addArgs(&.{ "--target", target_str }); + + run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc + + run.addThirdPartyEnabledArgDarling(.{ .enabled = "-fdarling" }); + run.addThirdPartyEnabledArgQemu(.{ .enabled = "-fqemu" }); + run.addThirdPartyEnabledArgRosetta(.{ .enabled = "-frosetta" }); + run.addThirdPartyEnabledArgWasmtime(.{ .enabled = "-fwasmtime" }); + run.addThirdPartyEnabledArgWine(.{ .enabled = "-fwine" }); + + tests_step.dependOn(&run.step); + } + } + + return tests_step; +} + +fn isEditorFileName(name: []const u8) bool { + return (std.mem.startsWith(u8, name, "#") and std.mem.endsWith(u8, name, "#")) or + std.mem.endsWith(u8, name, "~") or std.mem.endsWith(u8, name, ".swp"); +} + pub fn addLlvmIrTests(b: *std.Build, options: LlvmIrContext.Options) ?*Step { const step = b.step("test-llvm-ir", "Run the LLVM IR tests"); diff --git a/tools/runner-check.zig b/tools/runner-check.zig new file mode 100644 index 0000000000000000000000000000000000000000..0291d93814f99242322930cf3722f4d10af3c418 --- /dev/null +++ b/tools/runner-check.zig @@ -0,0 +1,151 @@ +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 keep_src = false; + + var arg_it = try init.minimal.args.iterateAllocator(arena); + std.debug.assert(arg_it.skip()); + while (arg_it.next()) |arg| { + if (std.mem.eql(u8, arg, "--zig")) { + zig_path_arg = arg_it.next() orelse fail("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 fail("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, "--keep-src")) { + keep_src = true; + } else { + if (test_path_arg) |_| fail("unknown arg '{s}'", .{arg}); + test_path_arg = arg; + } + } + + const test_path = test_path_arg orelse fail("missing 'path/to/test'", .{}); + const zig_path = zig_path_arg orelse fail("missing '--zig=path/to/zig'", .{}); + const lib_path = lib_path_arg orelse fail("missing '--lib=path/to/lib'", .{}); + + const test_file = try std.Io.Dir.cwd().openFile(init.io, test_path, .{ + .allow_directory = true, + }); + const zig_file = try std.Io.Dir.cwd().openFile(init.io, zig_path, .{}); + defer zig_file.close(init.io); + const lib_dir = try std.Io.Dir.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 std.Io.Dir.cwd().createDirPathOpen(init.io, src_dir_path, .{}); + defer { + src_dir.close(init.io); + if (!keep_src) std.Io.Dir.cwd().deleteTree(init.io, src_dir_path) catch |err| { + std.log.warn("failed to delete tree '{s}': {t}", .{ src_dir_path, err }); + }; + } + + var child = try std.process.spawn(init.io, .{ + .argv = &.{ + zig_path, + "run", + try std.fs.path.resolveAlloc(arena, &.{ lib_path, "..", "test", "src", "runner.zig" }), + }, + .stdin = .pipe, + .stdout = .pipe, + .inherit_dirs = &.{ lib_dir, src_dir }, + .inherit_files = &.{ zig_file, test_file }, + }); + + var stdout_buffer: [512]u8 = undefined; + var stdout_reader = child.stdout.?.readerStreaming(init.io, &stdout_buffer); + var stdin_buffer: [512]u8 = undefined; + var stdin_writer = child.stdin.?.writerStreaming(init.io, &stdin_buffer); + var client: std.zig.Client = .{ + .in = &stdout_reader.interface, + .out = &stdin_writer.interface, + }; + { + const Arg = std.zig.Client.Message.Arg; + var args: std.ArrayList(u8) = .initBuffer(try arena.alloc(u8, 0 + + @sizeOf(Arg) + @sizeOf(std.Io.File.Handle) + + @sizeOf(Arg) + "--zig=\x00".len + + @sizeOf(Arg) + @sizeOf(std.Io.File.Handle) + + @sizeOf(Arg) + "--lib=\x00".len + + @sizeOf(Arg) + @sizeOf(std.Io.Dir.Handle) + + @sizeOf(Arg) + "--src=\x00".len + + @sizeOf(Arg) + @sizeOf(std.Io.Dir.Handle))); + + args.appendAssumeCapacity(@backingInt(@as(Arg, switch ((try test_file.stat(init.io)).kind) { + else => unreachable, + .file => .input_file, + .directory => .input_dir, + }))); + args.appendSliceAssumeCapacity(@ptrCast(&test_file.handle)); + + args.appendAssumeCapacity(@backingInt(Arg.prefix)); + args.appendSliceAssumeCapacity("--zig="); + args.appendAssumeCapacity(0); + + args.appendAssumeCapacity(@backingInt(Arg.input_file)); + args.appendSliceAssumeCapacity(@ptrCast(&zig_file.handle)); + + args.appendAssumeCapacity(@backingInt(Arg.prefix)); + args.appendSliceAssumeCapacity("--lib="); + args.appendAssumeCapacity(0); + + args.appendAssumeCapacity(@backingInt(Arg.input_dir)); + args.appendSliceAssumeCapacity(@ptrCast(&lib_dir.handle)); + + args.appendAssumeCapacity(@backingInt(Arg.prefix)); + args.appendSliceAssumeCapacity("--src="); + args.appendAssumeCapacity(0); + + args.appendAssumeCapacity(@backingInt(Arg.output_dir)); + args.appendSliceAssumeCapacity(@ptrCast(&src_dir.handle)); + + std.debug.assert(args.unusedCapacitySlice().len == 0); + + try client.serveMessageHeader(.{ + .tag = .args, + .bytes_len = @intCast(args.items.len), + }); + try client.out.writeAll(args.items); + + 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 fail( + "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", + .{ @import("builtin").zig_version_string, body }, + ); + }, + .test_metadata => { + try client.in.discardAll(hdr.bytes_len); + try client.serveRunTest(0); + }, + .test_results => { + try client.in.discardAll(hdr.bytes_len); + break; + }, + } + } + try client.serveBodylessMessage(.exit); + } + _ = try child.wait(init.io); +} +fn fail(comptime fmt: []const u8, args: anytype) noreturn { + std.log.err(fmt, args); + std.process.exit(1); +} -- 2.54.0