| author | |
| committer | |
| log | abd960873cfa3404f48b4ee2b4a3aabb451bbc39 |
| tree | 642fc385a78567fcf6e2de4ef79f3b34a835157f |
| parent | 737b366279984b5725673ef94b0e144ac5eca98e |
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. | |
| 1 | 2 | const std = @import("std"); |
| 2 | 3 | const io = std.io; |
| 3 | 4 | const builtin = @import("builtin"); |
test/standalone.zig+4| ... | ... | @@ -151,6 +151,10 @@ pub const build_cases = [_]BuildCase{ |
| 151 | 151 | // .build_root = "test/standalone/issue_12588", |
| 152 | 152 | // .import = @import("standalone/issue_12588/build.zig"), |
| 153 | 153 | //}, |
| 154 | .{ | |
| 155 | .build_root = "test/standalone/child_process", | |
| 156 | .import = @import("standalone/child_process/build.zig"), | |
| 157 | }, | |
| 154 | 158 | .{ |
| 155 | 159 | .build_root = "test/standalone/embed_generated_file", |
| 156 | 160 | .import = @import("standalone/embed_generated_file/build.zig"), |
test/standalone/child_process/build.zig created+31| ... | ... | @@ -0,0 +1,31 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | ||
| 4 | pub 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 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | // 42 is expected by parent; other values result in test failure | |
| 4 | var exit_code: u8 = 42; | |
| 5 | ||
| 6 | pub 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 | ||
| 14 | fn 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 | ||
| 42 | fn 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 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | pub 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 | ||
| 45 | var parent_test_error = false; | |
| 46 | ||
| 47 | fn 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 | } |