Commit 0a8fe34b11

Jonathan Marler <johnnymarler@gmail.com>
2022-07-02 18:36:39
add test to ignore sigpipe
1 parent c02ced4
Changed files (8)
lib
test
link
macho
dead_strip_dylibs
src
standalone
lib/std/Build/EmulatableRunStep.zig
@@ -26,7 +26,7 @@ builder: *std.Build,
 exe: *CompileStep,
 
 /// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
-expected_exit_code: ?u8 = 0,
+expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 },
 
 /// Override this field to modify the environment
 env_map: ?*EnvMap,
@@ -131,7 +131,7 @@ fn make(step: *Step) !void {
     try RunStep.runCommand(
         argv_list.items,
         self.builder,
-        self.expected_exit_code,
+        self.expected_term,
         self.stdout_action,
         self.stderr_action,
         .Inherit,
lib/std/Build/RunStep.zig
@@ -35,7 +35,7 @@ stderr_action: StdIoAction = .inherit,
 stdin_behavior: std.ChildProcess.StdIo = .Inherit,
 
 /// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
-expected_exit_code: ?u8 = 0,
+expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 },
 
 /// Print the command before running it
 print: bool,
@@ -289,7 +289,7 @@ fn make(step: *Step) !void {
     try runCommand(
         argv_list.items,
         self.builder,
-        self.expected_exit_code,
+        self.expected_term,
         self.stdout_action,
         self.stderr_action,
         self.stdin_behavior,
@@ -303,10 +303,55 @@ fn make(step: *Step) !void {
     }
 }
 
+fn formatTerm(
+    term: ?std.ChildProcess.Term,
+    comptime fmt: []const u8,
+    options: std.fmt.FormatOptions,
+    writer: anytype,
+) !void {
+    _ = fmt;
+    _ = options;
+    if (term) |t| switch (t) {
+        .Exited => |code| try writer.print("exited with code {}", .{code}),
+        .Signal => |sig| try writer.print("terminated with signal {}", .{sig}),
+        .Stopped => |sig| try writer.print("stopped with signal {}", .{sig}),
+        .Unknown => |code| try writer.print("terminated for unknown reason with code {}", .{code}),
+    } else {
+        try writer.writeAll("exited with any code");
+    }
+}
+fn fmtTerm(term: ?std.ChildProcess.Term) std.fmt.Formatter(formatTerm) {
+    return .{ .data = term };
+}
+
+fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term) bool {
+    return if (expected) |e| switch (e) {
+        .Exited => |expected_code| switch (actual) {
+            .Exited => |actual_code| expected_code == actual_code,
+            else => false,
+        },
+        .Signal => |expected_sig| switch (actual) {
+            .Signal => |actual_sig| expected_sig == actual_sig,
+            else => false,
+        },
+        .Stopped => |expected_sig| switch (actual) {
+            .Stopped => |actual_sig| expected_sig == actual_sig,
+            else => false,
+        },
+        .Unknown => |expected_code| switch (actual) {
+            .Unknown => |actual_code| expected_code == actual_code,
+            else => false,
+        },
+    } else switch (actual) {
+        .Exited => true,
+        else => false,
+    };
+}
+
 pub fn runCommand(
     argv: []const []const u8,
     builder: *std.Build,
-    expected_exit_code: ?u8,
+    expected_term: ?std.ChildProcess.Term,
     stdout_action: StdIoAction,
     stderr_action: StdIoAction,
     stdin_behavior: std.ChildProcess.StdIo,
@@ -368,32 +413,14 @@ pub fn runCommand(
         return err;
     };
 
-    switch (term) {
-        .Exited => |code| blk: {
-            const expected_code = expected_exit_code orelse break :blk;
-
-            if (code != expected_code) {
-                if (builder.prominent_compile_errors) {
-                    std.debug.print("Run step exited with error code {} (expected {})\n", .{
-                        code,
-                        expected_code,
-                    });
-                } else {
-                    std.debug.print("The following command exited with error code {} (expected {}):\n", .{
-                        code,
-                        expected_code,
-                    });
-                    printCmd(cwd, argv);
-                }
-
-                return error.UnexpectedExitCode;
-            }
-        },
-        else => {
-            std.debug.print("The following command terminated unexpectedly:\n", .{});
+    if (!termMatches(expected_term, term)) {
+        if (builder.prominent_compile_errors) {
+            std.debug.print("Run step {} (expected {})\n", .{ fmtTerm(term), fmtTerm(expected_term) });
+        } else {
+            std.debug.print("The following command {} (expected {}):\n", .{ fmtTerm(term), fmtTerm(expected_term) });
             printCmd(cwd, argv);
-            return error.UncleanExit;
-        },
+        }
+        return error.UnexpectedExit;
     }
 
     switch (stderr_action) {
lib/std/os.zig
@@ -7074,6 +7074,8 @@ pub const keep_sigpipe: bool = if (@hasDecl(root, "keep_sigpipe"))
 else
     false;
 
+fn noopSigHandler(_: c_int) callconv(.C) void {}
+
 /// This function will tell the kernel to ignore SIGPIPE rather than terminate
 /// the process.  This function is automatically called in `start.zig` before
 /// `main`.  This behavior can be disabled by adding this to your root module:
@@ -7092,12 +7094,13 @@ else
 pub fn maybeIgnoreSigpipe() void {
     if (have_sigpipe_support and !keep_sigpipe) {
         const act = Sigaction{
-            .handler = .{ .sigaction = SIG.IGN },
+            // We set handler to a noop function instead of SIG.IGN so we don't leak our
+            // signal disposition to a child process
+            .handler = .{ .handler = noopSigHandler },
             .mask = empty_sigset,
-            .flags = SA.SIGINFO,
+            .flags = 0,
         };
-        sigaction(SIG.PIPE, &act, null) catch |err| std.debug.panic("ignore SIGPIPE failed with '{s}'" ++
-            ", add `pub const keep_sigpipe = true;` to your root module" ++
-            " or adjust have_sigpipe_support in std/os.zig", .{@errorName(err)});
+        sigaction(SIG.PIPE, &act, null) catch |err|
+            std.debug.panic("failed to install noop SIGPIPE handler with '{s}'", .{@errorName(err)});
     }
 }
test/link/macho/dead_strip_dylibs/build.zig
@@ -29,7 +29,7 @@ pub fn build(b: *std.Build) void {
         exe.dead_strip_dylibs = true;
 
         const run_cmd = exe.run();
-        run_cmd.expected_exit_code = @bitCast(u8, @as(i8, -2)); // should fail
+        run_cmd.expected_term = .{ .Exited = @bitCast(u8, @as(i8, -2)) }; // should fail
         test_step.dependOn(&run_cmd.step);
     }
 }
test/src/compare_output.zig
@@ -168,7 +168,7 @@ pub const CompareOutputContext = struct {
                 run.addArgs(case.cli_args);
                 run.stderr_action = .ignore;
                 run.stdout_action = .ignore;
-                run.expected_exit_code = 126;
+                run.expected_term = .{ .Exited = 126 };
 
                 self.step.dependOn(&run.step);
             },
test/standalone/sigpipe/breakpipe.zig
@@ -0,0 +1,21 @@
+const std = @import("std");
+const build_options = @import("build_options");
+
+pub usingnamespace if (build_options.keep_sigpipe) struct {
+    pub const keep_sigpipe = true;
+} else struct {
+    // intentionally not setting keep_sigpipe to ensure the default behavior is equivalent to false
+};
+
+pub fn main() !void {
+    const pipe = try std.os.pipe();
+    std.os.close(pipe[0]);
+    _ = std.os.write(pipe[1], "a") catch |err| switch (err) {
+        error.BrokenPipe => {
+            try std.io.getStdOut().writer().writeAll("BrokenPipe\n");
+            std.os.exit(123);
+        },
+        else => |e| return e,
+    };
+    unreachable;
+}
test/standalone/sigpipe/build.zig
@@ -0,0 +1,35 @@
+const std = @import("std");
+const os = std.os;
+
+pub fn build(b: *std.build.Builder) !void {
+    const test_step = b.step("test", "Run the tests");
+
+    // This test runs "breakpipe" as a child process and that process
+    // depends on inheriting a SIGPIPE disposition of "default".
+    {
+        const act = os.Sigaction{
+            .handler = .{ .handler = os.SIG.DFL },
+            .mask = os.empty_sigset,
+            .flags = 0,
+        };
+        try os.sigaction(os.SIG.PIPE, &act, null);
+    }
+
+    for ([_]bool{ false, true }) |keep_sigpipe| {
+        const options = b.addOptions();
+        options.addOption(bool, "keep_sigpipe", keep_sigpipe);
+        const exe = b.addExecutable(.{
+            .name = "breakpipe",
+            .root_source_file = .{ .path = "breakpipe.zig" },
+        });
+        exe.addOptions("build_options", options);
+        const run = exe.run();
+        if (keep_sigpipe) {
+            run.expected_term = .{ .Signal = std.os.SIG.PIPE };
+        } else {
+            run.stdout_action = .{ .expect_exact = "BrokenPipe\n" };
+            run.expected_term = .{ .Exited = 123 };
+        }
+        test_step.dependOn(&run.step);
+    }
+}
test/standalone.zig
@@ -84,6 +84,9 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
         cases.addBuildFile("test/standalone/pie/build.zig", .{});
     }
     cases.addBuildFile("test/standalone/issue_12706/build.zig", .{});
+    if (std.os.have_sigpipe_support) {
+        cases.addBuildFile("test/standalone/sigpipe/build.zig", .{});
+    }
 
     // Ensure the development tools are buildable. Alphabetically sorted.
     // No need to build `tools/spirv/grammar.zig`.