master
 1const std = @import("std");
 2
 3pub fn main() !void {
 4    // make sure safety checks are enabled even in release modes
 5    var gpa_state = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
 6    defer if (gpa_state.deinit() != .ok) {
 7        @panic("found memory leaks");
 8    };
 9    const gpa = gpa_state.allocator();
10
11    var it = try std.process.argsWithAllocator(gpa);
12    defer it.deinit();
13    _ = it.next() orelse unreachable; // skip binary name
14    const child_path, const needs_free = child_path: {
15        const child_path = it.next() orelse unreachable;
16        const cwd_path = it.next() orelse break :child_path .{ child_path, false };
17        // If there is a third argument, it is the current CWD somewhere within the cache directory.
18        // In that case, modify the child path in order to test spawning a path with a leading `..` component.
19        break :child_path .{ try std.fs.path.relative(gpa, cwd_path, child_path), true };
20    };
21    defer if (needs_free) gpa.free(child_path);
22
23    var threaded: std.Io.Threaded = .init(gpa);
24    defer threaded.deinit();
25    const io = threaded.io();
26
27    var child = std.process.Child.init(&.{ child_path, "hello arg" }, gpa);
28    child.stdin_behavior = .Pipe;
29    child.stdout_behavior = .Pipe;
30    child.stderr_behavior = .Inherit;
31    try child.spawn();
32    const child_stdin = child.stdin.?;
33    try child_stdin.writeAll("hello from stdin"); // verified in child
34    child_stdin.close();
35    child.stdin = null;
36
37    const hello_stdout = "hello from stdout";
38    var buf: [hello_stdout.len]u8 = undefined;
39    var stdout_reader = child.stdout.?.readerStreaming(io, &.{});
40    const n = try stdout_reader.interface.readSliceShort(&buf);
41    if (!std.mem.eql(u8, buf[0..n], hello_stdout)) {
42        testError("child stdout: '{s}'; want '{s}'", .{ buf[0..n], hello_stdout });
43    }
44
45    switch (try child.wait()) {
46        .Exited => |code| {
47            const child_ok_code = 42; // set by child if no test errors
48            if (code != child_ok_code) {
49                testError("child exit code: {d}; want {d}", .{ code, child_ok_code });
50            }
51        },
52        else => |term| testError("abnormal child exit: {}", .{term}),
53    }
54    if (parent_test_error) return error.ParentTestError;
55
56    // Check that FileNotFound is consistent across platforms when trying to spawn an executable that doesn't exist
57    const missing_child_path = try std.mem.concat(gpa, u8, &.{ child_path, "_intentionally_missing" });
58    defer gpa.free(missing_child_path);
59    try std.testing.expectError(error.FileNotFound, std.process.Child.run(.{ .allocator = gpa, .argv = &.{missing_child_path} }));
60}
61
62var parent_test_error = false;
63
64fn testError(comptime fmt: []const u8, args: anytype) void {
65    var stderr_writer = std.fs.File.stderr().writer(&.{});
66    const stderr = &stderr_writer.interface;
67    stderr.print("PARENT TEST ERROR: ", .{}) catch {};
68    stderr.print(fmt, args) catch {};
69    if (fmt[fmt.len - 1] != '\n') {
70        stderr.writeByte('\n') catch {};
71    }
72    parent_test_error = true;
73}