| author | |
| committer | |
| log | 488c15320b6e2848dd61245303e75f7577313ff2 |
| tree | 6201cb5b097c9b04705335f70f1430353c02f466 |
| parent | 8a6475c4f79374975be61ac7be13e164956d1a5c |
9 files changed, 762 insertions(+), 354 deletions(-)
lib/compiler/Maker/Step/Run.zig+21-19| ... | ... | @@ -231,6 +231,7 @@ pub fn make( |
| 231 | 231 | argv_list.appendAssumeCapacity("--listen=-"); |
| 232 | 232 | }, |
| 233 | 233 | .protocol => { |
| 234 | argv_list.appendAssumeCapacity("--listen=-"); | |
| 234 | 235 | try input_dirs.append(gpa, if (conf_run.cwd.value) |lazy_cwd| |
| 235 | 236 | try maker.resolveLazyPathIndex(arena, lazy_cwd, run_index) |
| 236 | 237 | else |
| ... | ... | @@ -742,7 +743,8 @@ fn waitZigTest( |
| 742 | 743 | run_index: Configuration.Step.Index, |
| 743 | 744 | maker: *Maker, |
| 744 | 745 | child: *process.Child, |
| 745 | progress_node: std.Progress.Node, | |
| 746 | tests_prog_node: std.Progress.Node, | |
| 747 | test_prog_node: std.Progress.Node, | |
| 746 | 748 | protocol_args: []const u8, |
| 747 | 749 | input_dirs: []const Cache.Path, |
| 748 | 750 | man: ?*Cache.Manifest, |
| ... | ... | @@ -765,9 +767,6 @@ fn waitZigTest( |
| 765 | 767 | const io = graph.io; |
| 766 | 768 | const step = maker.stepByIndex(run_index); |
| 767 | 769 | |
| 768 | var sub_prog_node: ?std.Progress.Node = null; | |
| 769 | defer if (sub_prog_node) |n| n.end(); | |
| 770 | ||
| 771 | 770 | const stdout = multi_reader.reader(0); |
| 772 | 771 | const stderr = multi_reader.reader(1); |
| 773 | 772 | |
| ... | ... | @@ -788,7 +787,7 @@ fn waitZigTest( |
| 788 | 787 | |
| 789 | 788 | if (opt_metadata.*) |*md| { |
| 790 | 789 | // Previous unit test process died or was killed; we're continuing where it left off |
| 791 | requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; | |
| 790 | requestNextTest(&client, md, test_prog_node) catch |err| return .{ .write_failed = err }; | |
| 792 | 791 | } else { |
| 793 | 792 | // Running unit tests normally |
| 794 | 793 | run.fuzz_tests.clearRetainingCapacity(); |
| ... | ... | @@ -859,27 +858,27 @@ fn waitZigTest( |
| 859 | 858 | |
| 860 | 859 | const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable; |
| 861 | 860 | |
| 862 | progress_node.setEstimatedTotalItems(names.len); | |
| 861 | tests_prog_node.setEstimatedTotalItems(names.len); | |
| 863 | 862 | opt_metadata.* = .{ |
| 864 | 863 | .string_bytes = try arena.dupe(u8, string_bytes), |
| 865 | 864 | .ns_per_test = try arena.alloc(u64, results.test_count), |
| 866 | 865 | .names = names, |
| 867 | 866 | .expected_panic_msgs = expected_panic_msgs, |
| 868 | 867 | .next_index = 0, |
| 869 | .prog_node = progress_node, | |
| 870 | 868 | }; |
| 871 | 869 | @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64)); |
| 872 | 870 | |
| 873 | 871 | active_test_index = null; |
| 874 | 872 | last_update = .now(io, .awake); |
| 875 | 873 | |
| 876 | requestNextTest(&client, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; | |
| 874 | requestNextTest(&client, &opt_metadata.*.?, test_prog_node) catch |err| return .{ .write_failed = err }; | |
| 877 | 875 | }, |
| 878 | 876 | .test_started => { |
| 879 | 877 | active_test_index = opt_metadata.*.?.next_index - 1; |
| 880 | 878 | last_update = .now(io, .awake); |
| 881 | 879 | }, |
| 882 | 880 | .test_results => { |
| 881 | tests_prog_node.completeOne(); | |
| 883 | 882 | const md = &opt_metadata.*.?; |
| 884 | 883 | |
| 885 | 884 | const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable; |
| ... | ... | @@ -924,7 +923,7 @@ fn waitZigTest( |
| 924 | 923 | md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds); |
| 925 | 924 | last_update = now; |
| 926 | 925 | |
| 927 | requestNextTest(&client, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; | |
| 926 | requestNextTest(&client, md, test_prog_node) catch |err| return .{ .write_failed = err }; | |
| 928 | 927 | }, |
| 929 | 928 | .discovered_inputs => while (body_r.takeEnum( |
| 930 | 929 | std.zig.Server.Message.InputDir, |
| ... | ... | @@ -1549,6 +1548,7 @@ fn evalZigTest( |
| 1549 | 1548 | maker, |
| 1550 | 1549 | &child, |
| 1551 | 1550 | progress_node, |
| 1551 | spawn_options.progress_node, | |
| 1552 | 1552 | protocol_args, |
| 1553 | 1553 | input_dirs, |
| 1554 | 1554 | man, |
| ... | ... | @@ -1681,7 +1681,6 @@ const TestMetadata = struct { |
| 1681 | 1681 | expected_panic_msgs: []const u32, |
| 1682 | 1682 | string_bytes: []const u8, |
| 1683 | 1683 | next_index: u32, |
| 1684 | prog_node: std.Progress.Node, | |
| 1685 | 1684 | |
| 1686 | 1685 | fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata { |
| 1687 | 1686 | return .{ |
| ... | ... | @@ -1704,16 +1703,14 @@ pub const CachedTestMetadata = struct { |
| 1704 | 1703 | } |
| 1705 | 1704 | }; |
| 1706 | 1705 | |
| 1707 | fn requestNextTest(client: *std.zig.Client, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { | |
| 1706 | fn requestNextTest(client: *std.zig.Client, metadata: *TestMetadata, test_prog_node: std.Progress.Node) !void { | |
| 1708 | 1707 | while (metadata.next_index < metadata.names.len) { |
| 1709 | 1708 | const i = metadata.next_index; |
| 1710 | 1709 | metadata.next_index += 1; |
| 1711 | 1710 | |
| 1712 | 1711 | if (metadata.expected_panic_msgs[i] != 0) continue; |
| 1713 | 1712 | |
| 1714 | const name = metadata.testName(i); | |
| 1715 | if (sub_prog_node.*) |n| n.end(); | |
| 1716 | sub_prog_node.* = metadata.prog_node.start(name, 0); | |
| 1713 | test_prog_node.setName(metadata.testName(i)); | |
| 1717 | 1714 | |
| 1718 | 1715 | try client.serveRunTest(i); |
| 1719 | 1716 | return; |
| ... | ... | @@ -2664,9 +2661,9 @@ fn spawnChildAndCollect( |
| 2664 | 2661 | .cwd = child_cwd, |
| 2665 | 2662 | .environ_map = environ_map, |
| 2666 | 2663 | .request_resource_usage_statistics = true, |
| 2667 | .stdin = if (conf_run.stdin.u != .none) s: { | |
| 2664 | .stdin = if (conf_run.stdin.u != .none) stdin: { | |
| 2668 | 2665 | assert(conf_run.flags.stdio != .inherit); |
| 2669 | break :s .pipe; | |
| 2666 | break :stdin .pipe; | |
| 2670 | 2667 | } else switch (conf_run.flags.stdio) { |
| 2671 | 2668 | .infer_from_args => if (maker.protocol_server == null and has_side_effects) .inherit else .ignore, |
| 2672 | 2669 | .inherit => .inherit, |
| ... | ... | @@ -2691,10 +2688,10 @@ fn spawnChildAndCollect( |
| 2691 | 2688 | |
| 2692 | 2689 | if (maker.protocol_server != null) { |
| 2693 | 2690 | if (spawn_options.stdin == .inherit) { |
| 2694 | return step.fail(maker, "Cannot inherit stdin when running through over the build system protocol", .{}); | |
| 2691 | return step.fail(maker, "Cannot inherit stdin when running over the build system protocol", .{}); | |
| 2695 | 2692 | } |
| 2696 | 2693 | if (spawn_options.stdout == .inherit) { |
| 2697 | return step.fail(maker, "Cannot inherit stdout when running through over the build system protocol", .{}); | |
| 2694 | return step.fail(maker, "Cannot inherit stdout when running over the build system protocol", .{}); | |
| 2698 | 2695 | } |
| 2699 | 2696 | assert(spawn_options.stderr != .inherit); |
| 2700 | 2697 | } |
| ... | ... | @@ -2720,7 +2717,12 @@ fn spawnChildAndCollect( |
| 2720 | 2717 | step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); |
| 2721 | 2718 | return try result; |
| 2722 | 2719 | }, |
| 2723 | .zig_test, .protocol => { | |
| 2720 | .zig_test, .protocol => |stdio| { | |
| 2721 | spawn_options.progress_node = if (conf_run.flags.disable_zig_progress or fuzz_context != null) | |
| 2722 | .none | |
| 2723 | else | |
| 2724 | progress_node.start(@tagName(stdio), 0); | |
| 2725 | defer spawn_options.progress_node.end(); | |
| 2724 | 2726 | try setColorEnvironmentVariables(&conf_run, environ_map, graph.stderr_mode.?); |
| 2725 | 2727 | const started: Io.Clock.Timestamp = .now(io, .awake); |
| 2726 | 2728 | const result = evalZigTest( |
lib/std/Io.zig-18| ... | ... | @@ -213,9 +213,7 @@ pub const VTable = struct { |
| 213 | 213 | processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void, |
| 214 | 214 | processSetCurrentPath: *const fn (?*anyopaque, []const u8) std.process.SetCurrentPathError!void, |
| 215 | 215 | processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError, |
| 216 | processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError, | |
| 217 | 216 | processSpawn: *const fn (?*anyopaque, std.process.SpawnOptions) std.process.SpawnError!std.process.Child, |
| 218 | processSpawnPath: *const fn (?*anyopaque, Dir, std.process.SpawnOptions) std.process.SpawnError!std.process.Child, | |
| 219 | 217 | childWait: *const fn (?*anyopaque, *std.process.Child) std.process.Child.WaitError!std.process.Child.Term, |
| 220 | 218 | childKill: *const fn (?*anyopaque, *std.process.Child) void, |
| 221 | 219 | |
| ... | ... | @@ -2797,9 +2795,7 @@ pub const failing: std.Io = .{ |
| 2797 | 2795 | .processSetCurrentDir = failingProcessSetCurrentDir, |
| 2798 | 2796 | .processSetCurrentPath = failingProcessSetCurrentPath, |
| 2799 | 2797 | .processReplace = failingProcessReplace, |
| 2800 | .processReplacePath = failingProcessReplacePath, | |
| 2801 | 2798 | .processSpawn = failingProcessSpawn, |
| 2802 | .processSpawnPath = failingProcessSpawnPath, | |
| 2803 | 2799 | .childWait = unreachableChildWait, |
| 2804 | 2800 | .childKill = unreachableChildKill, |
| 2805 | 2801 | |
| ... | ... | @@ -3451,26 +3447,12 @@ pub fn failingProcessReplace(userdata: ?*anyopaque, options: std.process.Replace |
| 3451 | 3447 | return error.OperationUnsupported; |
| 3452 | 3448 | } |
| 3453 | 3449 | |
| 3454 | pub fn failingProcessReplacePath(userdata: ?*anyopaque, dir: Dir, options: std.process.ReplaceOptions) std.process.ReplaceError { | |
| 3455 | _ = userdata; | |
| 3456 | _ = dir; | |
| 3457 | _ = options; | |
| 3458 | return error.OperationUnsupported; | |
| 3459 | } | |
| 3460 | ||
| 3461 | 3450 | pub fn failingProcessSpawn(userdata: ?*anyopaque, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child { |
| 3462 | 3451 | _ = userdata; |
| 3463 | 3452 | _ = options; |
| 3464 | 3453 | return error.OperationUnsupported; |
| 3465 | 3454 | } |
| 3466 | 3455 | |
| 3467 | pub fn failingProcessSpawnPath(userdata: ?*anyopaque, dir: Dir, options: std.process.SpawnOptions) std.process.SpawnError!std.process.Child { | |
| 3468 | _ = userdata; | |
| 3469 | _ = dir; | |
| 3470 | _ = options; | |
| 3471 | return error.OperationUnsupported; | |
| 3472 | } | |
| 3473 | ||
| 3474 | 3456 | pub fn unreachableChildWait(userdata: ?*anyopaque, child: *std.process.Child) std.process.Child.WaitError!std.process.Child.Term { |
| 3475 | 3457 | _ = userdata; |
| 3476 | 3458 | _ = child; |
lib/std/Io/Threaded.zig+98-68| ... | ... | @@ -1901,9 +1901,7 @@ pub fn io(t: *Threaded) Io { |
| 1901 | 1901 | .processSetCurrentDir = processSetCurrentDir, |
| 1902 | 1902 | .processSetCurrentPath = processSetCurrentPath, |
| 1903 | 1903 | .processReplace = processReplace, |
| 1904 | .processReplacePath = processReplacePath, | |
| 1905 | 1904 | .processSpawn = processSpawn, |
| 1906 | .processSpawnPath = processSpawnPath, | |
| 1907 | 1905 | .childWait = childWait, |
| 1908 | 1906 | .childKill = childKill, |
| 1909 | 1907 | |
| ... | ... | @@ -15133,23 +15131,7 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces |
| 15133 | 15131 | }); |
| 15134 | 15132 | }; |
| 15135 | 15133 | |
| 15136 | return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); | |
| 15137 | } | |
| 15138 | ||
| 15139 | fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError { | |
| 15140 | if (!process.can_replace) return error.OperationUnsupported; | |
| 15141 | _ = userdata; | |
| 15142 | _ = dir; | |
| 15143 | _ = options; | |
| 15144 | @panic("TODO processReplacePath"); | |
| 15145 | } | |
| 15146 | ||
| 15147 | fn processSpawnPath(userdata: ?*anyopaque, dir: Dir, options: process.SpawnOptions) process.SpawnError!process.Child { | |
| 15148 | if (!process.can_spawn) return error.OperationUnsupported; | |
| 15149 | _ = userdata; | |
| 15150 | _ = dir; | |
| 15151 | _ = options; | |
| 15152 | @panic("TODO processSpawnPath"); | |
| 15134 | return posixExec(options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH); | |
| 15153 | 15135 | } |
| 15154 | 15136 | |
| 15155 | 15137 | const processSpawn = switch (native_os) { |
| ... | ... | @@ -15263,15 +15245,15 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 15263 | 15245 | if (Thread.current) |current_thread| current_thread.cancel_protection = .blocked; |
| 15264 | 15246 | const ep1 = err_pipe[1]; |
| 15265 | 15247 | |
| 15266 | // Must happen befor clobbering fds below. | |
| 15248 | // Must happen before clobbering fds below. | |
| 15267 | 15249 | switch (options.cwd) { |
| 15268 | 15250 | .inherit => {}, |
| 15269 | 15251 | .dir => |cwd| fchdir(cwd.handle) catch |err| forkBail(ep1, err), |
| 15270 | 15252 | .path => |cwd| chdir(cwd) catch |err| forkBail(ep1, err), |
| 15271 | 15253 | } |
| 15272 | 15254 | |
| 15273 | for (options.inherit_dirs) |dir| switch (posix.errno( | |
| 15274 | posix.system.fcntl(dir.handle, posix.F.SETFD, @as(u32, 0)), | |
| 15255 | for (options.inherit_dirs) |inherit_dir| switch (posix.errno( | |
| 15256 | posix.system.fcntl(inherit_dir.handle, posix.F.SETFD, @as(u32, 0)), | |
| 15275 | 15257 | )) { |
| 15276 | 15258 | .SUCCESS => {}, |
| 15277 | 15259 | else => |err| forkBail(ep1, posix.unexpectedErrno(err)), |
| ... | ... | @@ -15331,14 +15313,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp |
| 15331 | 15313 | } |
| 15332 | 15314 | |
| 15333 | 15315 | if (options.start_suspended) { |
| 15334 | switch (posix.errno(posix.system.kill(0, .STOP))) { | |
| 15316 | switch (posix.errno(posix.system.kill(posix.system.getpid(), .STOP))) { | |
| 15335 | 15317 | .SUCCESS => {}, |
| 15336 | 15318 | .PERM => forkBail(ep1, error.PermissionDenied), |
| 15337 | 15319 | else => forkBail(ep1, error.Unexpected), |
| 15338 | 15320 | } |
| 15339 | 15321 | } |
| 15340 | 15322 | |
| 15341 | const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); | |
| 15323 | const err = posixExec(options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH); | |
| 15342 | 15324 | forkBail(ep1, err); |
| 15343 | 15325 | } |
| 15344 | 15326 | |
| ... | ... | @@ -17042,61 +17024,109 @@ fn testArgvToCommandLineWindows(argv: []const []const u8, expected_cmd_line: []c |
| 17042 | 17024 | try std.testing.expectEqualStrings(expected_cmd_line, cmd_line); |
| 17043 | 17025 | } |
| 17044 | 17026 | |
| 17045 | fn posixExecv( | |
| 17046 | arg0_expand: process.ArgExpansion, | |
| 17047 | file: [*:0]const u8, | |
| 17048 | child_argv: [*:null]?[*:0]const u8, | |
| 17027 | fn posixExec( | |
| 17028 | exe: process.ReplaceOptions.Exe, | |
| 17029 | argv: [*:null]?[*:0]const u8, | |
| 17030 | expand_arg0: process.ArgExpansion, | |
| 17049 | 17031 | env_block: process.Environ.PosixBlock, |
| 17050 | 17032 | PATH: []const u8, |
| 17051 | 17033 | ) process.ReplaceError { |
| 17052 | const file_slice = std.mem.sliceTo(file, 0); | |
| 17053 | if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block); | |
| 17054 | ||
| 17055 | // Use of PATH_MAX here is valid as the path_buf will be passed | |
| 17056 | // directly to the operating system in posixExecvPath. | |
| 17057 | var path_buf: [posix.PATH_MAX]u8 = undefined; | |
| 17058 | var it = std.mem.tokenizeScalar(u8, PATH, ':'); | |
| 17059 | var seen_eacces = false; | |
| 17060 | var err: process.ReplaceError = error.FileNotFound; | |
| 17061 | ||
| 17062 | // In case of expanding arg0 we must put it back if we return with an error. | |
| 17063 | const prev_arg0 = child_argv[0]; | |
| 17064 | defer switch (arg0_expand) { | |
| 17065 | .expand => child_argv[0] = prev_arg0, | |
| 17066 | .no_expand => {}, | |
| 17067 | }; | |
| 17034 | const arg0_slice = std.mem.span(argv[0].?); | |
| 17035 | exe: switch (exe) { | |
| 17036 | .detect => continue :exe if (std.mem.findScalar(u8, arg0_slice, '/')) |_| .{ | |
| 17037 | .path = .cwd(), | |
| 17038 | } else .search, | |
| 17039 | .search => { | |
| 17040 | // Use of PATH_MAX here is valid as the path_buf will be passed | |
| 17041 | // directly to the operating system in posixExecveat. | |
| 17042 | var path_buf: [posix.PATH_MAX]u8 = undefined; | |
| 17043 | var it = std.mem.tokenizeScalar(u8, PATH, ':'); | |
| 17044 | var err: error{ AccessDenied, FileNotFound, NotDir } = error.FileNotFound; | |
| 17068 | 17045 | |
| 17069 | while (it.next()) |search_path| { | |
| 17070 | const path_len = search_path.len + file_slice.len + 1; | |
| 17071 | if (path_buf.len < path_len + 1) return error.NameTooLong; | |
| 17072 | @memcpy(path_buf[0..search_path.len], search_path); | |
| 17073 | path_buf[search_path.len] = '/'; | |
| 17074 | @memcpy(path_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); | |
| 17075 | path_buf[path_len] = 0; | |
| 17076 | const full_path = path_buf[0..path_len :0].ptr; | |
| 17077 | switch (arg0_expand) { | |
| 17078 | .expand => child_argv[0] = full_path, | |
| 17079 | .no_expand => {}, | |
| 17080 | } | |
| 17081 | err = posixExecvPath(full_path, child_argv, env_block); | |
| 17082 | switch (err) { | |
| 17083 | error.AccessDenied => seen_eacces = true, | |
| 17084 | error.FileNotFound, error.NotDir => {}, | |
| 17085 | else => |e| return e, | |
| 17086 | } | |
| 17046 | while (it.next()) |search_path| { | |
| 17047 | const path_len = search_path.len + arg0_slice.len + 1; | |
| 17048 | if (path_buf.len < path_len + 1) return error.NameTooLong; | |
| 17049 | @memcpy(path_buf[0..search_path.len], search_path); | |
| 17050 | path_buf[search_path.len] = '/'; | |
| 17051 | @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice); | |
| 17052 | path_buf[path_len] = 0; | |
| 17053 | const full_path = path_buf[0..path_len :0].ptr; | |
| 17054 | switch (expand_arg0) { | |
| 17055 | .expand => argv[0] = full_path, | |
| 17056 | .no_expand => {}, | |
| 17057 | } | |
| 17058 | switch (posixExecveat(posix.AT.FDCWD, full_path, argv, env_block)) { | |
| 17059 | error.AccessDenied, error.FileNotFound, error.NotDir => |e| switch (err) { | |
| 17060 | error.AccessDenied => {}, | |
| 17061 | error.FileNotFound, error.NotDir => err = e, | |
| 17062 | }, | |
| 17063 | else => |e| return e, | |
| 17064 | } | |
| 17065 | } | |
| 17066 | return err; | |
| 17067 | }, | |
| 17068 | .path => |path| return posixExecveat(path.handle, argv[0], argv, env_block), | |
| 17069 | .file => |file| return posixExecveat(file.handle, null, argv, env_block), | |
| 17070 | .explicit => |explicit| { | |
| 17071 | var path_buffer: [posix.PATH_MAX]u8 = undefined; | |
| 17072 | const path_posix = try pathToPosix(explicit.path, &path_buffer); | |
| 17073 | return posixExecveat(explicit.dir.handle, path_posix, argv, env_block); | |
| 17074 | }, | |
| 17087 | 17075 | } |
| 17088 | if (seen_eacces) return error.AccessDenied; | |
| 17089 | return err; | |
| 17090 | 17076 | } |
| 17091 | 17077 | |
| 17092 | 17078 | /// This function ignores PATH environment variable. |
| 17093 | pub fn posixExecvPath( | |
| 17094 | path: [*:0]const u8, | |
| 17095 | child_argv: [*:null]const ?[*:0]const u8, | |
| 17079 | pub fn posixExecveat( | |
| 17080 | dir: posix.fd_t, | |
| 17081 | path: ?[*:0]const u8, | |
| 17082 | argv: [*:null]const ?[*:0]const u8, | |
| 17096 | 17083 | env_block: process.Environ.PosixBlock, |
| 17097 | 17084 | ) process.ReplaceError { |
| 17098 | 17085 | try Thread.checkCancel(); |
| 17099 | switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) { | |
| 17086 | switch (err: { | |
| 17087 | if (path) |p| if (dir == posix.AT.FDCWD or Dir.path.isAbsoluteZ(p)) { | |
| 17088 | break :err posix.errno(posix.system.execve(p, argv, env_block.slice.ptr)); | |
| 17089 | }; | |
| 17090 | if (comptime native_os == .linux and (!builtin.link_libc or | |
| 17091 | (builtin.target.abi.isGnu() and builtin.target.os.version_range.linux.glibc.order( | |
| 17092 | .{ .major = 2, .minor = 34, .patch = 0 }, | |
| 17093 | ).compare(.gte)))) | |
| 17094 | { | |
| 17095 | switch (posix.errno(posix.system.execveat(dir, path orelse "", argv, env_block.slice.ptr, .{ | |
| 17096 | .EMPTY_PATH = path == null, | |
| 17097 | .SYMLINK_NOFOLLOW = false, | |
| 17098 | }))) { | |
| 17099 | else => |err| break :err err, | |
| 17100 | .NOSYS => {}, | |
| 17101 | } | |
| 17102 | } | |
| 17103 | var path_buffer: [posix.PATH_MAX:0]u8 = undefined; | |
| 17104 | var path_len = realPathPosix(dir, &path_buffer) catch |err| switch (err) { | |
| 17105 | else => |e| return e, | |
| 17106 | error.InputOutput, | |
| 17107 | error.FileTooBig, | |
| 17108 | error.NoSpaceLeft, | |
| 17109 | error.PathAlreadyExists, | |
| 17110 | error.SymLinkLoop, | |
| 17111 | error.NetworkNotFound, | |
| 17112 | error.PipeBusy, | |
| 17113 | error.AntivirusInterference, | |
| 17114 | => return error.FileNotFound, | |
| 17115 | error.DeviceBusy, | |
| 17116 | error.NoDevice, | |
| 17117 | error.UnrecognizedVolume, | |
| 17118 | => return error.FileSystem, | |
| 17119 | }; | |
| 17120 | if (path) |p| { | |
| 17121 | const path_slice = std.mem.span(p); | |
| 17122 | path_buffer[path_len] = Dir.path.sep; | |
| 17123 | path_len += 1; | |
| 17124 | @memcpy(path_buffer[path_len..][0..path_slice.len], path_slice); | |
| 17125 | path_len += path_slice.len; | |
| 17126 | } | |
| 17127 | path_buffer[path_len] = 0; | |
| 17128 | break :err posix.errno(posix.system.execve(&path_buffer, argv, env_block.slice.ptr)); | |
| 17129 | }) { | |
| 17100 | 17130 | .FAULT => |err| return errnoBug(err), // Bad pointer parameter. |
| 17101 | 17131 | .@"2BIG" => return error.SystemResources, |
| 17102 | 17132 | .MFILE => return error.ProcessFdQuotaExceeded, |
lib/std/c.zig+6| ... | ... | @@ -8809,6 +8809,11 @@ pub const O = switch (native_os) { |
| 8809 | 8809 | else => void, |
| 8810 | 8810 | }; |
| 8811 | 8811 | |
| 8812 | pub const EXECVEAT = switch (native_os) { | |
| 8813 | .linux => linux.EXECVEAT, | |
| 8814 | else => void, | |
| 8815 | }; | |
| 8816 | ||
| 8812 | 8817 | pub const MAP = switch (native_os) { |
| 8813 | 8818 | .linux => linux.MAP, |
| 8814 | 8819 | .emscripten => packed struct(u32) { |
| ... | ... | @@ -11045,6 +11050,7 @@ pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, n |
| 11045 | 11050 | pub extern "c" fn chdir(path: [*:0]const u8) c_int; |
| 11046 | 11051 | pub extern "c" fn fchdir(fd: fd_t) c_int; |
| 11047 | 11052 | pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int; |
| 11053 | pub extern "c" fn execveat(dirfd: fd_t, path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8, flags: EXECVEAT) c_int; | |
| 11048 | 11054 | pub extern "c" fn dup(fd: fd_t) c_int; |
| 11049 | 11055 | pub extern "c" fn dup2(old_fd: fd_t, new_fd: fd_t) c_int; |
| 11050 | 11056 | pub extern "c" fn dup3(old: c_int, new: c_int, flags: c_uint) c_int; |
lib/std/process.zig+23-29| ... | ... | @@ -282,37 +282,40 @@ pub const ReplaceError = error{ |
| 282 | 282 | } || Allocator.Error || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; |
| 283 | 283 | |
| 284 | 284 | pub const ReplaceOptions = struct { |
| 285 | exe: Exe = .detect, | |
| 285 | 286 | argv: []const []const u8, |
| 286 | 287 | expand_arg0: ArgExpansion = .no_expand, |
| 287 | 288 | /// Replaces the environment when provided. The PATH value from here is |
| 288 | 289 | /// never used to resolve `argv[0]`. |
| 289 | 290 | environ_map: ?*const Environ.Map = null, |
| 291 | ||
| 292 | pub const Exe = union(enum) { | |
| 293 | /// `argv[0]` is the name of the program to execute. If it is not already a | |
| 294 | /// file path (i.e. it contains '/'), it is resolved into a file path based on | |
| 295 | /// PATH from the parent environment. | |
| 296 | detect, | |
| 297 | /// `argv[0]` is the name of the program to execute, resolved into a file path | |
| 298 | /// based on PATH from the parent environment. | |
| 299 | search, | |
| 300 | /// `argv[0]` is the file path of the program to execute, relative to `Dir` payload. | |
| 301 | /// It is *always* treated as a file path, even if it does not contain '/'. | |
| 302 | path: Dir, | |
| 303 | /// `File` payload is the program to execute. | |
| 304 | file: File, | |
| 305 | /// `path` is the file path of the program to execute, relative to `dir`. | |
| 306 | /// It is *always* treated as a file path, even if it does not contain '/'. | |
| 307 | explicit: struct { dir: Dir, path: []const u8 }, | |
| 308 | }; | |
| 290 | 309 | }; |
| 291 | 310 | |
| 292 | 311 | /// Replaces the current process image with the executed process. If this |
| 293 | 312 | /// function succeeds, it does not return. |
| 294 | 313 | /// |
| 295 | /// `argv[0]` is the name of the process to replace the current one with. If it | |
| 296 | /// is not already a file path (i.e. it contains '/'), it is resolved into a | |
| 297 | /// file path based on PATH from the parent environment. | |
| 298 | /// | |
| 299 | 314 | /// It is illegal to call this function in a fork() child. |
| 300 | 315 | pub fn replace(io: Io, options: ReplaceOptions) ReplaceError { |
| 301 | 316 | return io.vtable.processReplace(io.userdata, options); |
| 302 | 317 | } |
| 303 | 318 | |
| 304 | /// Replaces the current process image with the executed process. If this | |
| 305 | /// function succeeds, it does not return. | |
| 306 | /// | |
| 307 | /// `argv[0]` is the file path of the process to replace the current one with, | |
| 308 | /// relative to `dir`. It is *always* treated as a file path, even if it does | |
| 309 | /// not contain '/'. | |
| 310 | /// | |
| 311 | /// It is illegal to call this function in a fork() child. | |
| 312 | pub fn replacePath(io: Io, dir: Io.Dir, options: ReplaceOptions) ReplaceError { | |
| 313 | return io.vtable.processReplacePath(io.userdata, dir, options); | |
| 314 | } | |
| 315 | ||
| 316 | 319 | pub const ArgExpansion = enum { expand, no_expand }; |
| 317 | 320 | |
| 318 | 321 | /// File name extensions supported natively by `CreateProcess()` on Windows. |
| ... | ... | @@ -368,6 +371,7 @@ pub const SpawnError = error{ |
| 368 | 371 | } || Io.File.OpenError || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; |
| 369 | 372 | |
| 370 | 373 | pub const SpawnOptions = struct { |
| 374 | exe: ReplaceOptions.Exe = .detect, | |
| 371 | 375 | argv: []const []const u8, |
| 372 | 376 | |
| 373 | 377 | /// Set to change the current working directory when spawning the child process. |
| ... | ... | @@ -385,7 +389,7 @@ pub const SpawnOptions = struct { |
| 385 | 389 | /// |
| 386 | 390 | /// The child's progress tree will be grafted into the parent's progress tree, |
| 387 | 391 | /// by substituting this node with the child's root node. |
| 388 | progress_node: std.Progress.Node = std.Progress.Node.none, | |
| 392 | progress_node: std.Progress.Node = .none, | |
| 389 | 393 | |
| 390 | 394 | stdin: StdIo = .inherit, |
| 391 | 395 | stdout: StdIo = .inherit, |
| ... | ... | @@ -445,27 +449,16 @@ pub const SpawnOptions = struct { |
| 445 | 449 | }; |
| 446 | 450 | |
| 447 | 451 | /// Creates a child process. |
| 448 | /// | |
| 449 | /// `argv[0]` is the name of the program to execute. If it is not already a | |
| 450 | /// file path (i.e. it contains '/'), it is resolved into a file path based on | |
| 451 | /// PATH from the parent environment. | |
| 452 | 452 | pub fn spawn(io: Io, options: SpawnOptions) SpawnError!Child { |
| 453 | 453 | return io.vtable.processSpawn(io.userdata, options); |
| 454 | 454 | } |
| 455 | 455 | |
| 456 | /// Creates a child process. | |
| 457 | /// | |
| 458 | /// `argv[0]` is the file path of the program to execute, relative to `dir`. It | |
| 459 | /// is *always* treated as a file path, even if it does not contain '/'. | |
| 460 | pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { | |
| 461 | return io.vtable.processSpawnPath(io.userdata, dir, options); | |
| 462 | } | |
| 463 | ||
| 464 | 456 | pub const RunError = error{ |
| 465 | 457 | StreamTooLong, |
| 466 | 458 | } || SpawnError || Io.File.MultiReader.UnendingError || Io.Timeout.Error; |
| 467 | 459 | |
| 468 | 460 | pub const RunOptions = struct { |
| 461 | exe: ReplaceOptions.Exe = .detect, | |
| 469 | 462 | argv: []const []const u8, |
| 470 | 463 | stderr_limit: Io.Limit = .unlimited, |
| 471 | 464 | stdout_limit: Io.Limit = .unlimited, |
| ... | ... | @@ -503,6 +496,7 @@ pub const RunResult = struct { |
| 503 | 496 | /// If it succeeds, the caller owns result.stdout and result.stderr memory. |
| 504 | 497 | pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { |
| 505 | 498 | var child = try spawn(io, .{ |
| 499 | .exe = options.exe, | |
| 506 | 500 | .argv = options.argv, |
| 507 | 501 | .cwd = options.cwd, |
| 508 | 502 | .environ_map = options.environ_map, |
test/src/runner.zig+613-41| ... | ... | @@ -1,11 +1,218 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | ||
| 3 | pub fn main(init: std.process.Init) !void { | |
| 4 | const arena = init.arena.allocator(); | |
| 5 | ||
| 6 | var test_path_arg: ?[]const u8 = null; | |
| 7 | var zig_path_arg: ?[]const u8 = null; | |
| 8 | var lib_path_arg: ?[]const u8 = null; | |
| 9 | var target_args: std.ArrayList([]const u8) = .empty; | |
| 10 | defer target_args.deinit(init.gpa); | |
| 11 | var keep_src = false; | |
| 12 | ||
| 13 | var arg_it = try init.minimal.args.iterateAllocator(arena); | |
| 14 | const self = arg_it.next().?; | |
| 15 | while (arg_it.next()) |arg| { | |
| 16 | if (std.mem.eql(u8, arg, "--listen=-")) { | |
| 17 | return protocol(init.gpa, init.arena, init.io); | |
| 18 | } else if (std.mem.eql(u8, arg, "--zig")) { | |
| 19 | zig_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg}); | |
| 20 | } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| { | |
| 21 | zig_path_arg = zig_path; | |
| 22 | } else if (std.mem.eql(u8, arg, "--lib")) { | |
| 23 | lib_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg}); | |
| 24 | } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| { | |
| 25 | lib_path_arg = lib_path; | |
| 26 | } else if (std.mem.eql(u8, arg, "--target")) { | |
| 27 | try target_args.append( | |
| 28 | init.gpa, | |
| 29 | try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after '{s}'", .{arg})), | |
| 30 | ); | |
| 31 | } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| { | |
| 32 | try target_args.append(init.gpa, try arena.dupe(u8, target)); | |
| 33 | } else if (std.mem.eql(u8, arg, "--keep-src")) { | |
| 34 | keep_src = true; | |
| 35 | } else { | |
| 36 | if (test_path_arg) |_| fatal("unknown arg '{s}'", .{arg}); | |
| 37 | test_path_arg = arg; | |
| 38 | } | |
| 39 | } | |
| 40 | ||
| 41 | const test_path = test_path_arg orelse fatal("missing 'path/to/test'", .{}); | |
| 42 | const zig_path = zig_path_arg orelse fatal("missing '--zig=path/to/zig'", .{}); | |
| 43 | const lib_path = lib_path_arg orelse fatal("missing '--lib=path/to/lib'", .{}); | |
| 44 | ||
| 45 | const test_file = try std.Io.Dir.cwd().openFile(init.io, test_path, .{ | |
| 46 | .allow_directory = true, | |
| 47 | }); | |
| 48 | const zig_exe = try std.Io.Dir.cwd().openFile(init.io, zig_path, .{}); | |
| 49 | defer zig_exe.close(init.io); | |
| 50 | const lib_dir = try std.Io.Dir.cwd().openDir(init.io, lib_path, .{}); | |
| 51 | defer lib_dir.close(init.io); | |
| 52 | ||
| 53 | const src_dir_path = "src_" ++ std.fmt.hex(rand_int: { | |
| 54 | var rand_int: u64 = undefined; | |
| 55 | init.io.random(@ptrCast(&rand_int)); | |
| 56 | break :rand_int rand_int; | |
| 57 | }); | |
| 58 | var src_dir = try std.Io.Dir.cwd().createDirPathOpen(init.io, src_dir_path, .{}); | |
| 59 | defer { | |
| 60 | src_dir.close(init.io); | |
| 61 | if (!keep_src) std.Io.Dir.cwd().deleteTree(init.io, src_dir_path) catch |err| { | |
| 62 | std.log.warn("fataled to delete tree '{s}': {t}", .{ src_dir_path, err }); | |
| 63 | }; | |
| 64 | } | |
| 65 | ||
| 66 | var stdout_buffer: [512]u8 = undefined; | |
| 67 | var stdout_fw: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer); | |
| 68 | const stdout = &stdout_fw.interface; | |
| 69 | ||
| 70 | var child = try std.process.spawn(init.io, .{ | |
| 71 | .argv = &.{ self, "--listen=-" }, | |
| 72 | .stdin = .pipe, | |
| 73 | .stdout = .pipe, | |
| 74 | .inherit_dirs = &.{ lib_dir, src_dir }, | |
| 75 | .inherit_files = &.{ zig_exe, test_file }, | |
| 76 | }); | |
| 77 | ||
| 78 | var child_reader_buffer: [512]u8 = undefined; | |
| 79 | var child_reader = child.stdout.?.readerStreaming(init.io, &child_reader_buffer); | |
| 80 | var child_writer_buffer: [512]u8 = undefined; | |
| 81 | var child_writer = child.stdin.?.writerStreaming(init.io, &child_writer_buffer); | |
| 82 | var client: std.zig.Client = .{ | |
| 83 | .in = &child_reader.interface, | |
| 84 | .out = &child_writer.interface, | |
| 85 | }; | |
| 86 | { | |
| 87 | var args: std.ArrayList(u8) = .empty; | |
| 88 | defer args.deinit(init.gpa); | |
| 89 | const Arg = std.zig.Client.Message.Arg; | |
| 90 | ||
| 91 | try args.append(init.gpa, @backingInt(@as(Arg, switch ((try test_file.stat(init.io)).kind) { | |
| 92 | else => unreachable, | |
| 93 | .file => .input_file, | |
| 94 | .directory => .input_dir, | |
| 95 | }))); | |
| 96 | try args.appendSlice(init.gpa, @ptrCast(&test_file.handle)); | |
| 97 | ||
| 98 | try args.append(init.gpa, @backingInt(Arg.prefix)); | |
| 99 | try args.appendSlice(init.gpa, "--zig="); | |
| 100 | try args.append(init.gpa, 0); | |
| 101 | ||
| 102 | try args.append(init.gpa, @backingInt(Arg.input_file)); | |
| 103 | try args.appendSlice(init.gpa, @ptrCast(&zig_exe.handle)); | |
| 104 | ||
| 105 | try args.append(init.gpa, @backingInt(Arg.prefix)); | |
| 106 | try args.appendSlice(init.gpa, "--lib="); | |
| 107 | try args.append(init.gpa, 0); | |
| 108 | ||
| 109 | try args.append(init.gpa, @backingInt(Arg.input_dir)); | |
| 110 | try args.appendSlice(init.gpa, @ptrCast(&lib_dir.handle)); | |
| 111 | ||
| 112 | try args.append(init.gpa, @backingInt(Arg.prefix)); | |
| 113 | try args.appendSlice(init.gpa, "--src="); | |
| 114 | try args.append(init.gpa, 0); | |
| 115 | ||
| 116 | try args.append(init.gpa, @backingInt(Arg.output_dir)); | |
| 117 | try args.appendSlice(init.gpa, @ptrCast(&src_dir.handle)); | |
| 118 | ||
| 119 | for (target_args.items) |target_arg| { | |
| 120 | try args.append(init.gpa, @backingInt(Arg.string)); | |
| 121 | try args.appendSlice(init.gpa, "--target"); | |
| 122 | try args.append(init.gpa, 0); | |
| 123 | ||
| 124 | try args.append(init.gpa, @backingInt(Arg.string)); | |
| 125 | try args.appendSlice(init.gpa, target_arg); | |
| 126 | try args.append(init.gpa, 0); | |
| 127 | } | |
| 128 | ||
| 129 | try client.serveMessageHeader(.{ | |
| 130 | .tag = .args, | |
| 131 | .bytes_len = @intCast(args.items.len), | |
| 132 | }); | |
| 133 | try client.out.writeAll(args.items); | |
| 134 | ||
| 135 | var metadata: struct { | |
| 136 | index: u32, | |
| 137 | len: u32, | |
| 138 | names: []u32, | |
| 139 | expected_panic_msgs: []u32, | |
| 140 | string_bytes: []u8, | |
| 141 | ||
| 142 | fn name(m: *@This()) []const u8 { | |
| 143 | return std.mem.sliceTo(m.string_bytes[m.names[m.index - 1]..], 0); | |
| 144 | } | |
| 145 | ||
| 146 | fn expectedPanicMsg(m: *@This()) ?[:0]const u8 { | |
| 147 | return switch (m.expected_panic_msgs[m.index - 1]) { | |
| 148 | 0 => null, | |
| 149 | else => |offset| m.string_bytes[offset..std.mem.findScalarPos( | |
| 150 | u8, | |
| 151 | m.string_bytes, | |
| 152 | offset, | |
| 153 | 0, | |
| 154 | ).? :0], | |
| 155 | }; | |
| 156 | } | |
| 157 | ||
| 158 | fn next(m: *@This()) ?u32 { | |
| 159 | if (m.len - m.index == 0) return null; | |
| 160 | defer m.index += 1; | |
| 161 | return m.index; | |
| 162 | } | |
| 163 | } = .{ .index = 0, .len = 0, .names = &.{}, .expected_panic_msgs = &.{}, .string_bytes = &.{} }; | |
| 164 | try client.serveBodylessMessage(.query_test_metadata); | |
| 165 | while (true) { | |
| 166 | const hdr = try client.receiveMessage(); | |
| 167 | switch (hdr.tag) { | |
| 168 | else => try client.in.discardAll(hdr.bytes_len), | |
| 169 | .zig_version => { | |
| 170 | const body = try arena.alloc(u8, hdr.bytes_len); | |
| 171 | try client.in.readSliceAll(body); | |
| 172 | if (!std.mem.eql(u8, @import("builtin").zig_version_string, body)) return fatal( | |
| 173 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", | |
| 174 | .{ @import("builtin").zig_version_string, body }, | |
| 175 | ); | |
| 176 | }, | |
| 177 | .test_metadata => { | |
| 178 | const tm_hdr = | |
| 179 | try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little); | |
| 180 | metadata = .{ | |
| 181 | .index = 0, | |
| 182 | .len = tm_hdr.tests_len, | |
| 183 | .names = try arena.alloc(u32, tm_hdr.tests_len), | |
| 184 | .expected_panic_msgs = try arena.alloc(u32, tm_hdr.tests_len), | |
| 185 | .string_bytes = try arena.alloc(u8, tm_hdr.string_bytes_len), | |
| 186 | }; | |
| 187 | try client.in.readSliceEndian(u32, metadata.names, .little); | |
| 188 | try client.in.readSliceEndian(u32, metadata.expected_panic_msgs, .little); | |
| 189 | try client.in.readSliceAll(metadata.string_bytes); | |
| 190 | try client.serveRunTest(metadata.next() orelse break); | |
| 191 | }, | |
| 192 | .test_results => { | |
| 193 | const tr = try client.in.takeStruct(std.zig.Server.Message.TestResults, .little); | |
| 194 | try stdout.print("[{t}] {s}\n", .{ tr.flags.status, metadata.name() }); | |
| 195 | try stdout.flush(); | |
| 196 | try client.serveRunTest(metadata.next() orelse break); | |
| 197 | }, | |
| 198 | } | |
| 199 | } | |
| 200 | try client.serveBodylessMessage(.exit); | |
| 201 | } | |
| 202 | _ = try child.wait(init.io); | |
| 203 | } | |
| 204 | fn fatal(comptime fmt: []const u8, args: anytype) noreturn { | |
| 205 | std.log.err(fmt, args); | |
| 206 | std.process.exit(1); | |
| 207 | } | |
| 208 | ||
| 2 | 209 | const Args = struct { |
| 3 | 210 | @"test": ?struct { |
| 4 | 211 | input_dir: std.zig.Server.Message.InputDir, |
| 5 | 212 | dir: std.Io.Dir, |
| 6 | 213 | }, |
| 7 | 214 | manifest_file: ?std.Io.File, |
| 8 | zig_file: ?std.Io.File, | |
| 215 | zig_exe: ?std.Io.File, | |
| 9 | 216 | lib_dir: ?std.Io.Dir, |
| 10 | 217 | src_dir: ?std.Io.Dir, |
| 11 | 218 | targets: std.ArrayList(u32), |
| ... | ... | @@ -23,15 +230,14 @@ const Args = struct { |
| 23 | 230 | args.target_bytes.appendAssumeCapacity(0); |
| 24 | 231 | } |
| 25 | 232 | }; |
| 26 | pub fn main(init: std.process.Init) !void { | |
| 27 | const arena = init.arena.allocator(); | |
| 233 | pub fn protocol(gpa: std.mem.Allocator, arena_impl: *std.heap.ArenaAllocator, io: std.Io) !void { | |
| 234 | const prog_node = std.Progress.start(io, .{}); | |
| 235 | defer prog_node.end(); | |
| 28 | 236 | |
| 29 | 237 | var stdin_buffer: [512]u8 = undefined; |
| 30 | 238 | var stdout_buffer: [512]u8 = undefined; |
| 31 | var stderr_buffer: [512]u8 = undefined; | |
| 32 | var stdin: std.Io.File.Reader = .initStreaming(.stdin(), init.io, &stdin_buffer); | |
| 33 | var stdout: std.Io.File.Writer = .initStreaming(.stdout(), init.io, &stdout_buffer); | |
| 34 | var stderr: std.Io.File.Writer = .initStreaming(.stderr(), init.io, &stderr_buffer); | |
| 239 | var stdin: std.Io.File.Reader = .initStreaming(.stdin(), io, &stdin_buffer); | |
| 240 | var stdout: std.Io.File.Writer = .initStreaming(.stdout(), io, &stdout_buffer); | |
| 35 | 241 | var server: std.zig.Server = .{ |
| 36 | 242 | .in = &stdin.interface, |
| 37 | 243 | .out = &stdout.interface, |
| ... | ... | @@ -40,16 +246,18 @@ pub fn main(init: std.process.Init) !void { |
| 40 | 246 | var args: Args = .{ |
| 41 | 247 | .@"test" = null, |
| 42 | 248 | .manifest_file = null, |
| 43 | .zig_file = null, | |
| 249 | .zig_exe = null, | |
| 44 | 250 | .lib_dir = null, |
| 45 | 251 | .src_dir = null, |
| 46 | 252 | .targets = .empty, |
| 47 | 253 | .target_bytes = .empty, |
| 48 | 254 | }; |
| 49 | defer args.deinit(init.gpa); | |
| 255 | defer args.deinit(gpa); | |
| 50 | 256 | |
| 51 | 257 | try server.serveStringMessage(.zig_version, @import("builtin").zig_version_string); |
| 52 | 258 | while (true) { |
| 259 | _ = arena_impl.reset(.retain_capacity); | |
| 260 | const arena = arena_impl.allocator(); | |
| 53 | 261 | const hdr = try server.receiveMessage(); |
| 54 | 262 | switch (hdr.tag) { |
| 55 | 263 | else => { |
| ... | ... | @@ -77,12 +285,12 @@ pub fn main(init: std.process.Init) !void { |
| 77 | 285 | state = .target; |
| 78 | 286 | continue; |
| 79 | 287 | } else if (std.mem.cutPrefix(u8, string, "--target=")) |target| { |
| 80 | try args.addTarget(init.gpa, target); | |
| 288 | try args.addTarget(gpa, target); | |
| 81 | 289 | }, |
| 82 | 290 | .zig => unreachable, |
| 83 | 291 | .lib => unreachable, |
| 84 | 292 | .src => unreachable, |
| 85 | .target => try args.addTarget(init.gpa, string), | |
| 293 | .target => try args.addTarget(gpa, string), | |
| 86 | 294 | } |
| 87 | 295 | }, |
| 88 | 296 | .prefix => { |
| ... | ... | @@ -118,7 +326,7 @@ pub fn main(init: std.process.Init) !void { |
| 118 | 326 | args.@"test" = .{ .input_dir = input_dir, .dir = dir }; |
| 119 | 327 | std.debug.assert(args.manifest_file == null); |
| 120 | 328 | const manifest_path = "manifest"; |
| 121 | args.manifest_file = try dir.openFile(init.io, manifest_path, .{}); | |
| 329 | args.manifest_file = try dir.openFile(io, manifest_path, .{}); | |
| 122 | 330 | |
| 123 | 331 | try server.serveMessageHeader(.{ |
| 124 | 332 | .tag = .discovered_inputs, |
| ... | ... | @@ -157,8 +365,8 @@ pub fn main(init: std.process.Init) !void { |
| 157 | 365 | args.manifest_file = file; |
| 158 | 366 | }, |
| 159 | 367 | .zig => { |
| 160 | std.debug.assert(args.zig_file == null); | |
| 161 | args.zig_file = file; | |
| 368 | std.debug.assert(args.zig_exe == null); | |
| 369 | args.zig_exe = file; | |
| 162 | 370 | }, |
| 163 | 371 | .lib => unreachable, |
| 164 | 372 | .src => unreachable, |
| ... | ... | @@ -180,16 +388,17 @@ pub fn main(init: std.process.Init) !void { |
| 180 | 388 | }, |
| 181 | 389 | .run_test => { |
| 182 | 390 | const test_index = try server.receiveBody_u32(); |
| 183 | const target = | |
| 391 | const target_query = | |
| 184 | 392 | std.mem.sliceTo(args.target_bytes.items[args.targets.items[test_index]..], 0); |
| 185 | 393 | try server.serveBodylessMessage(.test_started); |
| 186 | const test_result = runTest(init.io, &stderr.interface, &server, &args, target); | |
| 187 | try stderr.flush(); | |
| 394 | const test_result = | |
| 395 | runTest(gpa, arena, io, prog_node, &server, &args, target_query); | |
| 188 | 396 | try server.serveTestResults(.{ |
| 189 | 397 | .index = test_index, |
| 190 | 398 | .flags = .{ |
| 191 | 399 | .status = if (test_result) .pass else |err| switch (err) { |
| 192 | 400 | else => status: { |
| 401 | std.debug.print("{t}\n", .{err}); | |
| 193 | 402 | if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace); |
| 194 | 403 | break :status .fail; |
| 195 | 404 | }, |
| ... | ... | @@ -205,18 +414,49 @@ pub fn main(init: std.process.Init) !void { |
| 205 | 414 | } |
| 206 | 415 | } |
| 207 | 416 | fn runTest( |
| 417 | gpa: std.mem.Allocator, | |
| 418 | arena: std.mem.Allocator, | |
| 208 | 419 | io: std.Io, |
| 209 | stderr: *std.Io.Writer, | |
| 420 | prog_node: std.Progress.Node, | |
| 210 | 421 | server: *std.zig.Server, |
| 211 | 422 | args: *const Args, |
| 212 | target: []const u8, | |
| 423 | target_string: []const u8, | |
| 213 | 424 | ) !void { |
| 214 | 425 | const manifest_file = args.manifest_file orelse return error.MissingManifestArg; |
| 215 | 426 | const src_dir = args.src_dir orelse return error.MissingSrcDir; |
| 427 | const target: Compiler.Target = target: { | |
| 428 | const backend_split = std.mem.findScalarLast(u8, target_string, '-') orelse | |
| 429 | return error.TargetMissingBackend; | |
| 430 | const mode_split = | |
| 431 | std.mem.findScalarLast(u8, target_string[0..backend_split], '-') orelse | |
| 432 | return error.TargetMissingMode; | |
| 433 | const triple = target_string[0..mode_split]; | |
| 434 | const backend = std.meta.stringToEnum( | |
| 435 | Compiler.Target.Backend, | |
| 436 | target_string[backend_split + 1 ..], | |
| 437 | ) orelse return error.TargetMissingBackend; | |
| 438 | const mode = std.meta.stringToEnum( | |
| 439 | Compiler.Target.Mode, | |
| 440 | target_string[mode_split + 1 .. backend_split], | |
| 441 | ) orelse return error.TargetMissingMode; | |
| 442 | break :target .{ | |
| 443 | .triple = triple, | |
| 444 | .resolved = try std.zig.system.resolveTargetQuery(io, try std.Build.parseTargetQuery(.{ | |
| 445 | .arch_os_abi = triple, | |
| 446 | .object_format = switch (backend) { | |
| 447 | .sema, .selfhosted, .llvm => null, | |
| 448 | .cbe => "c", | |
| 449 | }, | |
| 450 | })), | |
| 451 | .mode = mode, | |
| 452 | .backend = backend, | |
| 453 | }; | |
| 454 | }; | |
| 216 | 455 | var manifest_buffer: [512]u8 = undefined; |
| 217 | 456 | var manifest_fr = manifest_file.reader(io, &manifest_buffer); |
| 218 | 457 | var allow_skip = true; |
| 219 | 458 | var skip_delimiter = false; |
| 459 | var compiler: ?Compiler = null; | |
| 220 | 460 | var update_mtime = std.Io.Clock.real.now(io); |
| 221 | 461 | var update_num: usize = 0; |
| 222 | 462 | while (manifest_fr.interface.takeSentinel('\n')) |untrimmed_line| { |
| ... | ... | @@ -229,12 +469,13 @@ fn runTest( |
| 229 | 469 | continue; |
| 230 | 470 | } |
| 231 | 471 | const cmd = std.meta.stringToEnum( |
| 232 | enum { skip, obj, exe, lib, write, delete, update, check }, | |
| 472 | enum { skip, exe, lib, obj, write, delete, update, check }, | |
| 233 | 473 | cmd_str, |
| 234 | 474 | ) orelse return error.UnknownCommand; |
| 235 | 475 | var maybe_arg = line_it.next(); |
| 236 | 476 | var contents_file: ?std.Io.File = null; |
| 237 | 477 | defer if (contents_file) |file| file.close(io); |
| 478 | var contents_buffer: [512]u8 = undefined; | |
| 238 | 479 | var contents_impl: union { |
| 239 | 480 | dr: DelimitedReader, |
| 240 | 481 | fr: std.Io.File.Reader, |
| ... | ... | @@ -253,7 +494,7 @@ fn runTest( |
| 253 | 494 | break :contents_path contents_path; |
| 254 | 495 | }; |
| 255 | 496 | if (std.mem.eql(u8, contents_path, "{")) { |
| 256 | contents_impl = .{ .dr = .init(&manifest_fr.interface, "#}", &.{}) }; | |
| 497 | contents_impl = .{ .dr = .init(&manifest_fr.interface, "#}", &contents_buffer) }; | |
| 257 | 498 | skip_delimiter = true; |
| 258 | 499 | break :contents_r &contents_impl.dr.interface; |
| 259 | 500 | } |
| ... | ... | @@ -270,43 +511,263 @@ fn runTest( |
| 270 | 511 | try server.out.flush(); |
| 271 | 512 | |
| 272 | 513 | contents_file = try @"test".dir.openFile(io, contents_path, .{}); |
| 273 | contents_impl = .{ .fr = contents_file.?.reader(io, &.{}) }; | |
| 514 | contents_impl = .{ .fr = contents_file.?.reader(io, &contents_buffer) }; | |
| 274 | 515 | break :contents_r &contents_impl.fr.interface; |
| 275 | 516 | }, |
| 276 | 517 | }; |
| 277 | switch (cmd) { | |
| 278 | else => { | |
| 279 | try stderr.print("{t} {s} {{\n", .{ cmd, maybe_arg orelse "" }); | |
| 280 | _ = try contents_r.streamRemaining(stderr); | |
| 281 | try stderr.writeAll("\n}\n"); | |
| 282 | }, | |
| 518 | const cmd_prog_node = cmd_prog_node: { | |
| 519 | var cmd_name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; | |
| 520 | break :cmd_prog_node prog_node.start(std.mem.print(&cmd_name_buffer, "#{t} {s}", .{ | |
| 521 | cmd, maybe_arg orelse "", | |
| 522 | }) catch &cmd_name_buffer, 0); | |
| 523 | }; | |
| 524 | defer cmd_prog_node.end(); | |
| 525 | cmd: switch (cmd) { | |
| 283 | 526 | .skip => if (!allow_skip) |
| 284 | 527 | return error.NonInitialSkipCommand // #skip must appear first |
| 285 | else if (std.mem.eql(u8, target, maybe_arg orelse return error.MissingArg)) | |
| 528 | else if (std.mem.eql(u8, target_string, maybe_arg orelse return error.MissingArg)) | |
| 286 | 529 | return error.SkipZigTest, |
| 530 | .exe, .lib, .obj => { | |
| 531 | if (compiler) |*comp| { | |
| 532 | try comp.exit(io); | |
| 533 | compiler = null; | |
| 534 | } | |
| 535 | var argv: std.ArrayList([]const u8) = .empty; | |
| 536 | defer argv.deinit(gpa); | |
| 537 | try argv.appendSlice(gpa, &.{ | |
| 538 | "zig", | |
| 539 | switch (cmd) { | |
| 540 | else => unreachable, | |
| 541 | .obj => "build-obj", | |
| 542 | .exe => "build-exe", | |
| 543 | .lib => "build-lib", | |
| 544 | }, | |
| 545 | "-target", | |
| 546 | target.triple, | |
| 547 | }); | |
| 548 | switch (target.mode) { | |
| 549 | .whole => {}, | |
| 550 | .incremental => try argv.append(gpa, "-fincremental"), | |
| 551 | } | |
| 552 | switch (target.backend) { | |
| 553 | .sema => try argv.append(gpa, "-fno-emit-bin"), | |
| 554 | .selfhosted => try argv.append(gpa, "-fno-llvm"), | |
| 555 | .llvm => try argv.append(gpa, "-fllvm"), | |
| 556 | .cbe => try argv.append(gpa, "-ofmt=c"), | |
| 557 | } | |
| 558 | try argv.append(gpa, "--listen=-"); | |
| 559 | var need: usize = 1; | |
| 560 | var root_name: ?[]const u8 = null; | |
| 561 | while (contents_r.peekGreedy(need)) |buffered| { | |
| 562 | const start = std.mem.findNone(u8, buffered, "\n ") orelse buffered.len; | |
| 563 | if (std.mem.findAnyPos(u8, buffered, start, "\n ")) |end| { | |
| 564 | const arg = try arena.dupe(u8, buffered[start..end]); | |
| 565 | if (std.mem.cutPrefix(u8, arg, "-M")) |module| { | |
| 566 | var module_name_it = std.mem.splitScalar(u8, module, '='); | |
| 567 | const module_name = module_name_it.next().?; | |
| 568 | root_name = root_name orelse module_name; | |
| 569 | } | |
| 570 | try argv.append(gpa, arg); | |
| 571 | contents_r.toss(end); | |
| 572 | need = 1; | |
| 573 | } else { | |
| 574 | contents_r.toss(start); | |
| 575 | need = buffered.len - start + 1; | |
| 576 | } | |
| 577 | } else |err| switch (err) { | |
| 578 | else => |e| return e, | |
| 579 | error.EndOfStream => {}, | |
| 580 | } | |
| 581 | const compile_prog_node = prog_node.start("initial update", 0); | |
| 582 | const child = try std.process.spawn(io, .{ | |
| 583 | .exe = .{ .file = args.zig_exe orelse return error.MissingZigExe }, | |
| 584 | .argv = argv.items, | |
| 585 | .cwd = .{ .dir = src_dir }, | |
| 586 | .progress_node = compile_prog_node, | |
| 587 | .stdin = .pipe, | |
| 588 | .stdout = .pipe, | |
| 589 | .stderr = .pipe, | |
| 590 | }); | |
| 591 | compiler = .{ | |
| 592 | .prog_node = compile_prog_node, | |
| 593 | .allow_compiler_stderr = true, | |
| 594 | .output_mode = switch (cmd) { | |
| 595 | else => unreachable, | |
| 596 | .exe => .Exe, | |
| 597 | .lib => .Lib, | |
| 598 | .obj => .Obj, | |
| 599 | }, | |
| 600 | .root_name = root_name orelse return error.MissingRootModule, | |
| 601 | .state = .idle, | |
| 602 | .child = child, | |
| 603 | .fw = undefined, | |
| 604 | .mr_buffer = undefined, | |
| 605 | .mr = undefined, | |
| 606 | .client = undefined, | |
| 607 | }; | |
| 608 | const comp = &compiler.?; | |
| 609 | comp.fw = child.stdin.?.writerStreaming(io, &.{}); | |
| 610 | comp.mr.init(gpa, io, comp.mr_buffer.toStreams(), &.{ | |
| 611 | child.stdout.?, child.stderr.?, | |
| 612 | }); | |
| 613 | comp.client = .{ .in = comp.mr.reader(0), .out = &comp.fw.interface }; | |
| 614 | }, | |
| 287 | 615 | .write => { |
| 288 | const file = try src_dir.createFile(io, maybe_arg orelse return error.MissingArg, .{}); | |
| 616 | const file = | |
| 617 | try src_dir.createFile(io, maybe_arg orelse return error.MissingFileArg, .{}); | |
| 289 | 618 | defer file.close(io); |
| 290 | 619 | var fw = file.writer(io, &.{}); |
| 291 | 620 | _ = try contents_r.streamRemaining(&fw.interface); |
| 292 | 621 | try file.setTimestamps(io, .{ .modify_timestamp = .{ .new = update_mtime } }); |
| 293 | 622 | }, |
| 294 | .delete => try src_dir.deleteFile(io, maybe_arg orelse return error.MissingArg), | |
| 623 | .delete => try src_dir.deleteFile(io, maybe_arg orelse return error.MissingFileArg), | |
| 295 | 624 | .update => { |
| 296 | var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; | |
| 297 | const name = switch (try contents_r.readSliceShort(&name_buffer)) { | |
| 298 | 0 => switch (update_num) { | |
| 299 | 0 => "initial update", | |
| 300 | else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch | |
| 301 | &name_buffer, | |
| 302 | }, | |
| 303 | else => |name_len| name_buffer[0..name_len], | |
| 304 | }; | |
| 625 | const comp = &(compiler orelse return error.MissingCompiler); | |
| 626 | name: { | |
| 627 | var name_buffer: [std.Progress.Node.max_name_len]u8 = undefined; | |
| 628 | comp.prog_node.setName(switch (try contents_r.readSliceShort(&name_buffer)) { | |
| 629 | 0 => switch (update_num) { | |
| 630 | 0 => break :name, | |
| 631 | else => |num| std.mem.print(&name_buffer, "update {d}", .{num}) catch | |
| 632 | &name_buffer, | |
| 633 | }, | |
| 634 | else => |name_len| name_buffer[0..name_len], | |
| 635 | }); | |
| 636 | } | |
| 305 | 637 | _ = try contents_r.discardRemaining(); |
| 306 | try stderr.print("update({q})\n", .{name}); | |
| 638 | if (comp.state != .idle) return error.MissingCheck; | |
| 639 | try comp.client.serveBodylessMessage(.update); | |
| 640 | comp.state = .update; | |
| 307 | 641 | update_mtime = update_mtime.addDuration(.fromSeconds(2)); |
| 308 | 642 | update_num += 1; |
| 309 | 643 | }, |
| 644 | .check => { | |
| 645 | const expected = try contents_r.allocRemaining(gpa, .unlimited); | |
| 646 | defer gpa.free(expected); | |
| 647 | const check = std.meta.stringToEnum( | |
| 648 | enum { errors, stdout, exit, lldb }, | |
| 649 | maybe_arg orelse return error.MissingCheckArg, | |
| 650 | ) orelse return error.UnknownCheck; | |
| 651 | ||
| 652 | const comp = &(compiler orelse return error.MissingCompiler); | |
| 653 | if (comp.state != .update) return error.MissingUpdate; | |
| 654 | const stderr = comp.mr.reader(1); | |
| 655 | while (true) { | |
| 656 | const header = comp.client.receiveMessageWithMultiReader( | |
| 657 | &comp.mr, | |
| 658 | .none, | |
| 659 | ) catch |err| switch (err) { | |
| 660 | error.Timeout => unreachable, | |
| 661 | error.EndOfStream => break, | |
| 662 | else => |e| return e, | |
| 663 | }; | |
| 664 | const body = comp.client.in.take(header.bytes_len) catch unreachable; | |
| 665 | switch (header.tag) { | |
| 666 | .error_bundle => { | |
| 667 | const error_bundle = try std.zig.Server.allocErrorBundle(arena, body); | |
| 668 | if (stderr.bufferedLen() > 0) { | |
| 669 | if (comp.allow_compiler_stderr) { | |
| 670 | std.log.info("error_bundle stderr:\n{s}", .{stderr.buffered()}); | |
| 671 | } else { | |
| 672 | fatal("error_bundle unexpected stderr:\n{s}", .{stderr.buffered()}); | |
| 673 | } | |
| 674 | stderr.tossBuffered(); | |
| 675 | } | |
| 676 | switch (check) { | |
| 677 | .errors => if (error_bundle.errorMessageCount() != 0) { | |
| 678 | @panic("TODO"); | |
| 679 | } else fatal("expected compile errors", .{}), | |
| 680 | .stdout, .exit, .lldb => if (error_bundle.errorMessageCount() != 0) { | |
| 681 | try error_bundle.renderToStderr(io, .{}, .auto); | |
| 682 | fatal("unexpected compile errors", .{}); | |
| 683 | }, | |
| 684 | } | |
| 685 | comp.state = .idle; | |
| 686 | break :cmd; | |
| 687 | }, | |
| 688 | .emit_digest => { | |
| 689 | var r: std.Io.Reader = .fixed(body); | |
| 690 | _ = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch | |
| 691 | unreachable; | |
| 692 | ||
| 693 | if (stderr.bufferedLen() > 0) { | |
| 694 | if (comp.allow_compiler_stderr) { | |
| 695 | std.log.info("emit_digest stderr:\n{s}", .{stderr.buffered()}); | |
| 696 | } else { | |
| 697 | fatal("emit_digest unexpected stderr:\n{s}", .{stderr.buffered()}); | |
| 698 | } | |
| 699 | stderr.tossBuffered(); | |
| 700 | } | |
| 701 | switch (target.backend) { | |
| 702 | .sema => continue, | |
| 703 | .selfhosted, .llvm, .cbe => {}, | |
| 704 | } | |
| 705 | switch (check) { | |
| 706 | .errors => continue, | |
| 707 | .stdout, .exit, .lldb => {}, | |
| 708 | } | |
| 709 | const digest = r.takeArray(std.Build.Cache.bin_digest_len) catch | |
| 710 | unreachable; | |
| 711 | const result_dir = ".zig-cache" ++ std.Io.Dir.path.sep_str ++ | |
| 712 | "o" ++ std.Io.Dir.path.sep_str ++ std.Build.Cache.binToHex(digest.*); | |
| 713 | const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ | |
| 714 | .root_name = comp.root_name, | |
| 715 | .cpu_arch = target.resolved.cpu.arch, | |
| 716 | .os_tag = target.resolved.os.tag, | |
| 717 | .ofmt = target.resolved.ofmt, | |
| 718 | .abi = target.resolved.abi, | |
| 719 | .output_mode = comp.output_mode, | |
| 720 | }); | |
| 721 | const bin_path = | |
| 722 | try std.Io.Dir.path.join(arena, &.{ result_dir, bin_name }); | |
| 723 | const bin_file = try std.Io.Dir.cwd().openFile(io, bin_path, .{}); | |
| 724 | defer bin_file.close(io); | |
| 725 | switch (check) { | |
| 726 | .errors => unreachable, | |
| 727 | .stdout, .exit => { | |
| 728 | const result = std.process.run(arena, io, .{ | |
| 729 | .exe = .{ .file = bin_file }, | |
| 730 | .argv = &.{bin_path}, | |
| 731 | }) catch continue; | |
| 732 | switch (result.term) { | |
| 733 | .exited => |code| switch (check) { | |
| 734 | .errors, .lldb => unreachable, | |
| 735 | .stdout => { | |
| 736 | if (code != 0) fatal("generated executable '{s}' failed with code {d}", .{ bin_path, code }); | |
| 737 | try std.testing.expectEqualStrings(expected, result.stdout); | |
| 738 | }, | |
| 739 | .exit => { | |
| 740 | var actual_code_buffer: [std.fmt.count("{d}", .{std.math.maxInt(u8)})]u8 = undefined; | |
| 741 | try std.testing.expectEqualStrings( | |
| 742 | expected, | |
| 743 | std.mem.print(&actual_code_buffer, "{d}", .{code}) catch unreachable, | |
| 744 | ); | |
| 745 | }, | |
| 746 | }, | |
| 747 | .signal => |sig| fatal("generated executable '{s}' terminated with signal {t}", .{ bin_path, sig }), | |
| 748 | .stopped => |sig| fatal("generated executable '{s}' stopped with signal {t}", .{ bin_path, sig }), | |
| 749 | .unknown => fatal("generated executable '{s}' terminated unexpectedly", .{bin_path}), | |
| 750 | } | |
| 751 | }, | |
| 752 | .lldb => {}, | |
| 753 | } | |
| 754 | }, | |
| 755 | else => {}, // Ignore other messages, | |
| 756 | } | |
| 757 | } | |
| 758 | ||
| 759 | const buffered_stderr = stderr.buffered(); | |
| 760 | if (buffered_stderr.len > 0) { | |
| 761 | if (comp.allow_compiler_stderr) { | |
| 762 | std.log.info("stderr:\n{s}", .{buffered_stderr}); | |
| 763 | } else { | |
| 764 | fatal("unexpected stderr:\n{s}", .{buffered_stderr}); | |
| 765 | } | |
| 766 | } | |
| 767 | ||
| 768 | try comp.exit(io); | |
| 769 | fatal("compiler failed to send terminating error_bundle", .{}); | |
| 770 | }, | |
| 310 | 771 | } |
| 311 | 772 | std.debug.assert(try contents_r.discardRemaining() == 0); |
| 312 | 773 | if (cmd != .skip) allow_skip = false; |
| ... | ... | @@ -314,6 +775,7 @@ fn runTest( |
| 314 | 775 | else => |e| return e, |
| 315 | 776 | error.EndOfStream => if (skip_delimiter) return error.MissingDelimiter, |
| 316 | 777 | } |
| 778 | if (compiler) |*comp| try comp.exit(io); | |
| 317 | 779 | } |
| 318 | 780 | const DelimitedReader = struct { |
| 319 | 781 | const Io = std.Io; |
| ... | ... | @@ -360,3 +822,113 @@ const DelimitedReader = struct { |
| 360 | 822 | return n; |
| 361 | 823 | } |
| 362 | 824 | }; |
| 825 | const Compiler = struct { | |
| 826 | prog_node: std.Progress.Node, | |
| 827 | allow_compiler_stderr: bool, | |
| 828 | output_mode: std.lang.OutputMode, | |
| 829 | root_name: []const u8, | |
| 830 | state: enum { idle, update }, | |
| 831 | child: std.process.Child, | |
| 832 | fw: std.Io.File.Writer, | |
| 833 | mr_buffer: std.Io.File.MultiReader.Buffer(2), | |
| 834 | mr: std.Io.File.MultiReader, | |
| 835 | client: std.zig.Client, | |
| 836 | ||
| 837 | const Target = struct { | |
| 838 | triple: []const u8, | |
| 839 | resolved: std.Target, | |
| 840 | mode: Mode, | |
| 841 | backend: Backend, | |
| 842 | ||
| 843 | const Mode = enum { | |
| 844 | whole, | |
| 845 | incremental, | |
| 846 | }; | |
| 847 | ||
| 848 | const Backend = enum { | |
| 849 | /// Run semantic analysis only. Runtime output will not be tested, but we still verify | |
| 850 | /// that compilation succeeds. Corresponds to `-fno-emit-bin`. | |
| 851 | sema, | |
| 852 | /// Use the self-hosted code generation backend for this target. | |
| 853 | /// Corresponds to `-fno-llvm -fno-lld`. | |
| 854 | selfhosted, | |
| 855 | /// Use the LLVM backend. | |
| 856 | /// Corresponds to `-fllvm -flld`. | |
| 857 | llvm, | |
| 858 | /// Use the C backend. The output is compiled with `zig cc`. | |
| 859 | /// Corresponds to `-ofmt=c`. | |
| 860 | cbe, | |
| 861 | }; | |
| 862 | }; | |
| 863 | ||
| 864 | fn update(comp: *Compiler, io: std.Io) !void { | |
| 865 | comp.client.serveBodylessMessage(.update) catch |err| switch (err) { | |
| 866 | error.WriteFailed => return comp.fw.err.?, | |
| 867 | }; | |
| 868 | while (true) { | |
| 869 | const header = comp.client.receiveMessageWithMultiReader( | |
| 870 | &comp.mr, | |
| 871 | .none, | |
| 872 | ) catch |err| switch (err) { | |
| 873 | error.Timeout => unreachable, | |
| 874 | error.EndOfStream => break, | |
| 875 | else => |e| return e, | |
| 876 | }; | |
| 877 | const body = comp.client.in.take(header.bytes_len) catch unreachable; | |
| 878 | switch (header.tag) { | |
| 879 | .error_bundle => { | |
| 880 | return; | |
| 881 | }, | |
| 882 | .emit_digest => { | |
| 883 | _ = body; | |
| 884 | }, | |
| 885 | else => {}, // Ignore other messages, | |
| 886 | } | |
| 887 | } | |
| 888 | ||
| 889 | const buffered_stderr = comp.mr.reader(1).buffered(); | |
| 890 | if (buffered_stderr.len > 0) { | |
| 891 | if (comp.allow_compiler_stderr) { | |
| 892 | std.log.info("stderr:\n{s}", .{buffered_stderr}); | |
| 893 | } else { | |
| 894 | fatal("unexpected stderr:\n{s}", .{buffered_stderr}); | |
| 895 | } | |
| 896 | } | |
| 897 | ||
| 898 | try comp.exit(io); | |
| 899 | fatal("compiler failed to send terminating error_bundle", .{}); | |
| 900 | } | |
| 901 | ||
| 902 | fn exit(comp: *Compiler, io: std.Io) !void { | |
| 903 | comp.client.serveBodylessMessage(.exit) catch |err| switch (err) { | |
| 904 | error.WriteFailed => switch (comp.fw.err.?) { | |
| 905 | error.BrokenPipe => {}, | |
| 906 | else => |e| fatal("failed to send exit: {t}", .{e}), | |
| 907 | }, | |
| 908 | }; | |
| 909 | comp.child.stdin.?.close(io); | |
| 910 | comp.child.stdin = null; | |
| 911 | while (true) { | |
| 912 | const header = comp.client.receiveMessageWithMultiReader( | |
| 913 | &comp.mr, | |
| 914 | .none, | |
| 915 | ) catch |err| switch (err) { | |
| 916 | error.Timeout => unreachable, | |
| 917 | error.EndOfStream => |e| { | |
| 918 | if (comp.client.in.bufferedLen() == 0) break; | |
| 919 | return e; | |
| 920 | }, | |
| 921 | else => |e| return e, | |
| 922 | }; | |
| 923 | try comp.client.in.discardAll(header.bytes_len); | |
| 924 | } | |
| 925 | const term = comp.child.wait(io) catch |err| fatal("child process failed: {t}", .{err}); | |
| 926 | comp.prog_node.end(); | |
| 927 | switch (term) { | |
| 928 | .exited => |code| if (code != 0) fatal("compiler failed with code {d}", .{code}), | |
| 929 | .signal => |sig| fatal("compiler terminated with signal {t}", .{sig}), | |
| 930 | .stopped => |sig| fatal("compiler stopped unexpectedly with signal {t}", .{sig}), | |
| 931 | .unknown => fatal("compiler terminated unexpectedly", .{}), | |
| 932 | } | |
| 933 | } | |
| 934 | }; |
test/standalone/build.zig-1| ... | ... | @@ -44,7 +44,6 @@ pub fn build(b: *std.Build) void { |
| 44 | 44 | "../../tools/generate_linux_syscalls.zig", |
| 45 | 45 | "../../tools/process_headers.zig", |
| 46 | 46 | "../../tools/migrate_langref.zig", |
| 47 | "../../tools/runner-check.zig", | |
| 48 | 47 | "../../tools/unskip_spirv_tests.zig", |
| 49 | 48 | "../../tools/update-linux-headers.zig", |
| 50 | 49 | "../../tools/update_clang_options.zig", |
test/tests.zig+1-1| ... | ... | @@ -3488,7 +3488,7 @@ pub fn addNewIncrementalTests( |
| 3488 | 3488 | |
| 3489 | 3489 | if (options.skip_llvm and test_target.backend == .llvm) continue; |
| 3490 | 3490 | |
| 3491 | const target_str = b.fmt("{s}-{t}", .{ | |
| 3491 | const target_str = b.fmt("{s}-incremental-{t}", .{ | |
| 3492 | 3492 | resolved_target.query.zigTriple(b.allocator) catch @panic("OOM"), |
| 3493 | 3493 | test_target.backend, |
| 3494 | 3494 | }); |
tools/runner-check.zig deleted-177| ... | ... | @@ -1,177 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | pub fn main(init: std.process.Init) !void { | |
| 3 | const arena = init.arena.allocator(); | |
| 4 | ||
| 5 | var test_path_arg: ?[]const u8 = null; | |
| 6 | var zig_path_arg: ?[]const u8 = null; | |
| 7 | var lib_path_arg: ?[]const u8 = null; | |
| 8 | var target_args: std.ArrayList([]const u8) = .empty; | |
| 9 | defer target_args.deinit(init.gpa); | |
| 10 | var keep_src = false; | |
| 11 | ||
| 12 | var arg_it = try init.minimal.args.iterateAllocator(arena); | |
| 13 | std.debug.assert(arg_it.skip()); | |
| 14 | while (arg_it.next()) |arg| { | |
| 15 | if (std.mem.eql(u8, arg, "--zig")) { | |
| 16 | zig_path_arg = arg_it.next() orelse fail("missing arg after '{s}'", .{arg}); | |
| 17 | } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| { | |
| 18 | zig_path_arg = zig_path; | |
| 19 | } else if (std.mem.eql(u8, arg, "--lib")) { | |
| 20 | lib_path_arg = arg_it.next() orelse fail("missing arg after '{s}'", .{arg}); | |
| 21 | } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| { | |
| 22 | lib_path_arg = lib_path; | |
| 23 | } else if (std.mem.eql(u8, arg, "--target")) { | |
| 24 | try target_args.append( | |
| 25 | init.gpa, | |
| 26 | try arena.dupe(u8, arg_it.next() orelse fail("missing arg after '{s}'", .{arg})), | |
| 27 | ); | |
| 28 | } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| { | |
| 29 | try target_args.append(init.gpa, try arena.dupe(u8, target)); | |
| 30 | } else if (std.mem.eql(u8, arg, "--keep-src")) { | |
| 31 | keep_src = true; | |
| 32 | } else { | |
| 33 | if (test_path_arg) |_| fail("unknown arg '{s}'", .{arg}); | |
| 34 | test_path_arg = arg; | |
| 35 | } | |
| 36 | } | |
| 37 | ||
| 38 | const test_path = test_path_arg orelse fail("missing 'path/to/test'", .{}); | |
| 39 | const zig_path = zig_path_arg orelse fail("missing '--zig=path/to/zig'", .{}); | |
| 40 | const lib_path = lib_path_arg orelse fail("missing '--lib=path/to/lib'", .{}); | |
| 41 | ||
| 42 | const test_file = try std.Io.Dir.cwd().openFile(init.io, test_path, .{ | |
| 43 | .allow_directory = true, | |
| 44 | }); | |
| 45 | const zig_file = try std.Io.Dir.cwd().openFile(init.io, zig_path, .{}); | |
| 46 | defer zig_file.close(init.io); | |
| 47 | const lib_dir = try std.Io.Dir.cwd().openDir(init.io, lib_path, .{}); | |
| 48 | defer lib_dir.close(init.io); | |
| 49 | ||
| 50 | const src_dir_path = "src_" ++ std.fmt.hex(rand_int: { | |
| 51 | var rand_int: u64 = undefined; | |
| 52 | init.io.random(@ptrCast(&rand_int)); | |
| 53 | break :rand_int rand_int; | |
| 54 | }); | |
| 55 | var src_dir = try std.Io.Dir.cwd().createDirPathOpen(init.io, src_dir_path, .{}); | |
| 56 | defer { | |
| 57 | src_dir.close(init.io); | |
| 58 | if (!keep_src) std.Io.Dir.cwd().deleteTree(init.io, src_dir_path) catch |err| { | |
| 59 | std.log.warn("failed to delete tree '{s}': {t}", .{ src_dir_path, err }); | |
| 60 | }; | |
| 61 | } | |
| 62 | ||
| 63 | var child = try std.process.spawn(init.io, .{ | |
| 64 | .argv = &.{ | |
| 65 | zig_path, | |
| 66 | "run", | |
| 67 | try std.fs.path.resolveAlloc(arena, &.{ lib_path, "..", "test", "src", "runner.zig" }), | |
| 68 | }, | |
| 69 | .stdin = .pipe, | |
| 70 | .stdout = .pipe, | |
| 71 | .inherit_dirs = &.{ lib_dir, src_dir }, | |
| 72 | .inherit_files = &.{ zig_file, test_file }, | |
| 73 | }); | |
| 74 | ||
| 75 | var stdout_buffer: [512]u8 = undefined; | |
| 76 | var stdout_reader = child.stdout.?.readerStreaming(init.io, &stdout_buffer); | |
| 77 | var stdin_buffer: [512]u8 = undefined; | |
| 78 | var stdin_writer = child.stdin.?.writerStreaming(init.io, &stdin_buffer); | |
| 79 | var client: std.zig.Client = .{ | |
| 80 | .in = &stdout_reader.interface, | |
| 81 | .out = &stdin_writer.interface, | |
| 82 | }; | |
| 83 | { | |
| 84 | var args: std.ArrayList(u8) = .empty; | |
| 85 | defer args.deinit(init.gpa); | |
| 86 | const Arg = std.zig.Client.Message.Arg; | |
| 87 | ||
| 88 | try args.append(init.gpa, @backingInt(@as(Arg, switch ((try test_file.stat(init.io)).kind) { | |
| 89 | else => unreachable, | |
| 90 | .file => .input_file, | |
| 91 | .directory => .input_dir, | |
| 92 | }))); | |
| 93 | try args.appendSlice(init.gpa, @ptrCast(&test_file.handle)); | |
| 94 | ||
| 95 | try args.append(init.gpa, @backingInt(Arg.prefix)); | |
| 96 | try args.appendSlice(init.gpa, "--zig="); | |
| 97 | try args.append(init.gpa, 0); | |
| 98 | ||
| 99 | try args.append(init.gpa, @backingInt(Arg.input_file)); | |
| 100 | try args.appendSlice(init.gpa, @ptrCast(&zig_file.handle)); | |
| 101 | ||
| 102 | try args.append(init.gpa, @backingInt(Arg.prefix)); | |
| 103 | try args.appendSlice(init.gpa, "--lib="); | |
| 104 | try args.append(init.gpa, 0); | |
| 105 | ||
| 106 | try args.append(init.gpa, @backingInt(Arg.input_dir)); | |
| 107 | try args.appendSlice(init.gpa, @ptrCast(&lib_dir.handle)); | |
| 108 | ||
| 109 | try args.append(init.gpa, @backingInt(Arg.prefix)); | |
| 110 | try args.appendSlice(init.gpa, "--src="); | |
| 111 | try args.append(init.gpa, 0); | |
| 112 | ||
| 113 | try args.append(init.gpa, @backingInt(Arg.output_dir)); | |
| 114 | try args.appendSlice(init.gpa, @ptrCast(&src_dir.handle)); | |
| 115 | ||
| 116 | for (target_args.items) |target_arg| { | |
| 117 | try args.append(init.gpa, @backingInt(Arg.string)); | |
| 118 | try args.appendSlice(init.gpa, "--target"); | |
| 119 | try args.append(init.gpa, 0); | |
| 120 | ||
| 121 | try args.append(init.gpa, @backingInt(Arg.string)); | |
| 122 | try args.appendSlice(init.gpa, target_arg); | |
| 123 | try args.append(init.gpa, 0); | |
| 124 | } | |
| 125 | ||
| 126 | try client.serveMessageHeader(.{ | |
| 127 | .tag = .args, | |
| 128 | .bytes_len = @intCast(args.items.len), | |
| 129 | }); | |
| 130 | try client.out.writeAll(args.items); | |
| 131 | ||
| 132 | var metadata: struct { | |
| 133 | index: u32, | |
| 134 | len: u32, | |
| 135 | ||
| 136 | fn next(m: *@This()) ?u32 { | |
| 137 | if (m.len - m.index == 0) return null; | |
| 138 | defer m.index += 1; | |
| 139 | return m.index; | |
| 140 | } | |
| 141 | } = .{ .index = 0, .len = 0 }; | |
| 142 | try client.serveBodylessMessage(.query_test_metadata); | |
| 143 | while (true) { | |
| 144 | const hdr = try client.receiveMessage(); | |
| 145 | switch (hdr.tag) { | |
| 146 | else => try client.in.discardAll(hdr.bytes_len), | |
| 147 | .zig_version => { | |
| 148 | const body = try arena.alloc(u8, hdr.bytes_len); | |
| 149 | try client.in.readSliceAll(body); | |
| 150 | if (!std.mem.eql(u8, @import("builtin").zig_version_string, body)) return fail( | |
| 151 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", | |
| 152 | .{ @import("builtin").zig_version_string, body }, | |
| 153 | ); | |
| 154 | }, | |
| 155 | .test_metadata => { | |
| 156 | const tm_hdr = | |
| 157 | try client.in.takeStruct(std.zig.Server.Message.TestMetadata, .little); | |
| 158 | try client.in.discardAll( | |
| 159 | hdr.bytes_len - @sizeOf(std.zig.Server.Message.TestMetadata), | |
| 160 | ); | |
| 161 | metadata = .{ .index = 0, .len = tm_hdr.tests_len }; | |
| 162 | try client.serveRunTest(metadata.next() orelse break); | |
| 163 | }, | |
| 164 | .test_results => { | |
| 165 | try client.in.discardAll(hdr.bytes_len); | |
| 166 | try client.serveRunTest(metadata.next() orelse break); | |
| 167 | }, | |
| 168 | } | |
| 169 | } | |
| 170 | try client.serveBodylessMessage(.exit); | |
| 171 | } | |
| 172 | _ = try child.wait(init.io); | |
| 173 | } | |
| 174 | fn fail(comptime fmt: []const u8, args: anytype) noreturn { | |
| 175 | std.log.err(fmt, args); | |
| 176 | std.process.exit(1); | |
| 177 | } |