Commit abd960873c
Changed files (5)
test
lib/test_runner.zig
@@ -1,3 +1,4 @@
+//! Default test runner for unit tests.
const std = @import("std");
const io = std.io;
const builtin = @import("builtin");
test/standalone/child_process/build.zig
@@ -0,0 +1,31 @@
+const std = @import("std");
+const builtin = @import("builtin");
+
+pub fn build(b: *std.Build) void {
+ const test_step = b.step("test", "Test it");
+ b.default_step = test_step;
+
+ const optimize: std.builtin.OptimizeMode = .Debug;
+ const target: std.zig.CrossTarget = .{};
+
+ if (builtin.os.tag == .wasi) return;
+
+ const child = b.addExecutable(.{
+ .name = "child",
+ .root_source_file = .{ .path = "child.zig" },
+ .optimize = optimize,
+ .target = target,
+ });
+
+ const main = b.addExecutable(.{
+ .name = "main",
+ .root_source_file = .{ .path = "main.zig" },
+ .optimize = optimize,
+ .target = target,
+ });
+ const run = b.addRunArtifact(main);
+ run.addArtifactArg(child);
+ run.expectExitCode(0);
+
+ test_step.dependOn(&run.step);
+}
test/standalone/child_process/child.zig
@@ -0,0 +1,50 @@
+const std = @import("std");
+
+// 42 is expected by parent; other values result in test failure
+var exit_code: u8 = 42;
+
+pub fn main() !void {
+ var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+ const arena = arena_state.allocator();
+ try run(arena);
+ arena_state.deinit();
+ std.process.exit(exit_code);
+}
+
+fn run(allocator: std.mem.Allocator) !void {
+ var args = try std.process.argsWithAllocator(allocator);
+ defer args.deinit();
+ _ = args.next() orelse unreachable; // skip binary name
+
+ // test cmd args
+ const hello_arg = "hello arg";
+ const a1 = args.next() orelse unreachable;
+ if (!std.mem.eql(u8, a1, hello_arg)) {
+ testError("first arg: '{s}'; want '{s}'", .{ a1, hello_arg });
+ }
+ if (args.next()) |a2| {
+ testError("expected only one arg; got more: {s}", .{a2});
+ }
+
+ // test stdout pipe; parent verifies
+ try std.io.getStdOut().writer().writeAll("hello from stdout");
+
+ // test stdin pipe from parent
+ const hello_stdin = "hello from stdin";
+ var buf: [hello_stdin.len]u8 = undefined;
+ const stdin = std.io.getStdIn().reader();
+ const n = try stdin.readAll(&buf);
+ if (!std.mem.eql(u8, buf[0..n], hello_stdin)) {
+ testError("stdin: '{s}'; want '{s}'", .{ buf[0..n], hello_stdin });
+ }
+}
+
+fn testError(comptime fmt: []const u8, args: anytype) void {
+ const stderr = std.io.getStdErr().writer();
+ stderr.print("CHILD TEST ERROR: ", .{}) catch {};
+ stderr.print(fmt, args) catch {};
+ if (fmt[fmt.len - 1] != '\n') {
+ stderr.writeByte('\n') catch {};
+ }
+ exit_code = 1;
+}
test/standalone/child_process/main.zig
@@ -0,0 +1,55 @@
+const std = @import("std");
+
+pub fn main() !void {
+ // make sure safety checks are enabled even in release modes
+ var gpa_state = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
+ defer if (gpa_state.deinit() != .ok) {
+ @panic("found memory leaks");
+ };
+ const gpa = gpa_state.allocator();
+
+ var it = try std.process.argsWithAllocator(gpa);
+ defer it.deinit();
+ _ = it.next() orelse unreachable; // skip binary name
+ const child_path = it.next() orelse unreachable;
+
+ var child = std.ChildProcess.init(&.{ child_path, "hello arg" }, gpa);
+ child.stdin_behavior = .Pipe;
+ child.stdout_behavior = .Pipe;
+ child.stderr_behavior = .Inherit;
+ try child.spawn();
+ const child_stdin = child.stdin.?;
+ try child_stdin.writer().writeAll("hello from stdin"); // verified in child
+ child_stdin.close();
+ child.stdin = null;
+
+ const hello_stdout = "hello from stdout";
+ var buf: [hello_stdout.len]u8 = undefined;
+ const n = try child.stdout.?.reader().readAll(&buf);
+ if (!std.mem.eql(u8, buf[0..n], hello_stdout)) {
+ testError("child stdout: '{s}'; want '{s}'", .{ buf[0..n], hello_stdout });
+ }
+
+ switch (try child.wait()) {
+ .Exited => |code| {
+ const child_ok_code = 42; // set by child if no test errors
+ if (code != child_ok_code) {
+ testError("child exit code: {d}; want {d}", .{ code, child_ok_code });
+ }
+ },
+ else => |term| testError("abnormal child exit: {}", .{term}),
+ }
+ return if (parent_test_error) error.ParentTestError else {};
+}
+
+var parent_test_error = false;
+
+fn testError(comptime fmt: []const u8, args: anytype) void {
+ const stderr = std.io.getStdErr().writer();
+ stderr.print("PARENT TEST ERROR: ", .{}) catch {};
+ stderr.print(fmt, args) catch {};
+ if (fmt[fmt.len - 1] != '\n') {
+ stderr.writeByte('\n') catch {};
+ }
+ parent_test_error = true;
+}
test/standalone.zig
@@ -151,6 +151,10 @@ pub const build_cases = [_]BuildCase{
// .build_root = "test/standalone/issue_12588",
// .import = @import("standalone/issue_12588/build.zig"),
//},
+ .{
+ .build_root = "test/standalone/child_process",
+ .import = @import("standalone/child_process/build.zig"),
+ },
.{
.build_root = "test/standalone/embed_generated_file",
.import = @import("standalone/embed_generated_file/build.zig"),