authorgravatar for alex@cloudware.ioalex <alex@cloudware.io> 2022-10-02 14:42:20+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-07-25 10:57:45-07:00
logabd960873cfa3404f48b4ee2b4a3aabb451bbc39
tree642fc385a78567fcf6e2de4ef79f3b34a835157f
parent737b366279984b5725673ef94b0e144ac5eca98e

test/standalone: reinstate std.ChildProcess tests

67d5bfef removed std.ChildProcess tests, suggesting to make them standalone instead. This commit does exactly that after the bug creating SIGPIPE in ReleaseFast is no more with LLVM 15.0.5. Thanks to @x1ddos for the idea with the compile artifacts and PR improvements.

5 files changed, 141 insertions(+), 0 deletions(-)

lib/test_runner.zig+1
......@@ -1,3 +1,4 @@
1//! Default test runner for unit tests.
12const std = @import("std");
23const io = std.io;
34const builtin = @import("builtin");
test/standalone.zig+4
......@@ -151,6 +151,10 @@ pub const build_cases = [_]BuildCase{
151151 // .build_root = "test/standalone/issue_12588",
152152 // .import = @import("standalone/issue_12588/build.zig"),
153153 //},
154 .{
155 .build_root = "test/standalone/child_process",
156 .import = @import("standalone/child_process/build.zig"),
157 },
154158 .{
155159 .build_root = "test/standalone/embed_generated_file",
156160 .import = @import("standalone/embed_generated_file/build.zig"),
test/standalone/child_process/build.zig created+31
......@@ -0,0 +1,31 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4pub fn build(b: *std.Build) void {
5 const test_step = b.step("test", "Test it");
6 b.default_step = test_step;
7
8 const optimize: std.builtin.OptimizeMode = .Debug;
9 const target: std.zig.CrossTarget = .{};
10
11 if (builtin.os.tag == .wasi) return;
12
13 const child = b.addExecutable(.{
14 .name = "child",
15 .root_source_file = .{ .path = "child.zig" },
16 .optimize = optimize,
17 .target = target,
18 });
19
20 const main = b.addExecutable(.{
21 .name = "main",
22 .root_source_file = .{ .path = "main.zig" },
23 .optimize = optimize,
24 .target = target,
25 });
26 const run = b.addRunArtifact(main);
27 run.addArtifactArg(child);
28 run.expectExitCode(0);
29
30 test_step.dependOn(&run.step);
31}
test/standalone/child_process/child.zig created+50
......@@ -0,0 +1,50 @@
1const std = @import("std");
2
3// 42 is expected by parent; other values result in test failure
4var exit_code: u8 = 42;
5
6pub fn main() !void {
7 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
8 const arena = arena_state.allocator();
9 try run(arena);
10 arena_state.deinit();
11 std.process.exit(exit_code);
12}
13
14fn run(allocator: std.mem.Allocator) !void {
15 var args = try std.process.argsWithAllocator(allocator);
16 defer args.deinit();
17 _ = args.next() orelse unreachable; // skip binary name
18
19 // test cmd args
20 const hello_arg = "hello arg";
21 const a1 = args.next() orelse unreachable;
22 if (!std.mem.eql(u8, a1, hello_arg)) {
23 testError("first arg: '{s}'; want '{s}'", .{ a1, hello_arg });
24 }
25 if (args.next()) |a2| {
26 testError("expected only one arg; got more: {s}", .{a2});
27 }
28
29 // test stdout pipe; parent verifies
30 try std.io.getStdOut().writer().writeAll("hello from stdout");
31
32 // test stdin pipe from parent
33 const hello_stdin = "hello from stdin";
34 var buf: [hello_stdin.len]u8 = undefined;
35 const stdin = std.io.getStdIn().reader();
36 const n = try stdin.readAll(&buf);
37 if (!std.mem.eql(u8, buf[0..n], hello_stdin)) {
38 testError("stdin: '{s}'; want '{s}'", .{ buf[0..n], hello_stdin });
39 }
40}
41
42fn testError(comptime fmt: []const u8, args: anytype) void {
43 const stderr = std.io.getStdErr().writer();
44 stderr.print("CHILD TEST ERROR: ", .{}) catch {};
45 stderr.print(fmt, args) catch {};
46 if (fmt[fmt.len - 1] != '\n') {
47 stderr.writeByte('\n') catch {};
48 }
49 exit_code = 1;
50}
test/standalone/child_process/main.zig created+55
......@@ -0,0 +1,55 @@
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 = it.next() orelse unreachable;
15
16 var child = std.ChildProcess.init(&.{ child_path, "hello arg" }, gpa);
17 child.stdin_behavior = .Pipe;
18 child.stdout_behavior = .Pipe;
19 child.stderr_behavior = .Inherit;
20 try child.spawn();
21 const child_stdin = child.stdin.?;
22 try child_stdin.writer().writeAll("hello from stdin"); // verified in child
23 child_stdin.close();
24 child.stdin = null;
25
26 const hello_stdout = "hello from stdout";
27 var buf: [hello_stdout.len]u8 = undefined;
28 const n = try child.stdout.?.reader().readAll(&buf);
29 if (!std.mem.eql(u8, buf[0..n], hello_stdout)) {
30 testError("child stdout: '{s}'; want '{s}'", .{ buf[0..n], hello_stdout });
31 }
32
33 switch (try child.wait()) {
34 .Exited => |code| {
35 const child_ok_code = 42; // set by child if no test errors
36 if (code != child_ok_code) {
37 testError("child exit code: {d}; want {d}", .{ code, child_ok_code });
38 }
39 },
40 else => |term| testError("abnormal child exit: {}", .{term}),
41 }
42 return if (parent_test_error) error.ParentTestError else {};
43}
44
45var parent_test_error = false;
46
47fn testError(comptime fmt: []const u8, args: anytype) void {
48 const stderr = std.io.getStdErr().writer();
49 stderr.print("PARENT TEST ERROR: ", .{}) catch {};
50 stderr.print(fmt, args) catch {};
51 if (fmt[fmt.len - 1] != '\n') {
52 stderr.writeByte('\n') catch {};
53 }
54 parent_test_error = true;
55}