authorgravatar for johnnymarler@gmail.comJonathan Marler <johnnymarler@gmail.com> 2022-07-02 10:36:39-06:00
committergravatar for johnnymarler@gmail.comJonathan Marler <johnnymarler@gmail.com> 2023-02-17 15:25:36-07:00
log0a8fe34b11f7a44fd7f744bf4332353a5e7bfcdf
tree150b057095fdf8278b5d1155287b1d28339be6cd
parentc02ced4d346656abefa4cccfbdebf5dd27b326e5

add test to ignore sigpipe


8 files changed, 126 insertions(+), 37 deletions(-)

lib/std/Build/EmulatableRunStep.zig+2-2
......@@ -26,7 +26,7 @@ builder: *std.Build,
2626exe: *CompileStep,
2727
2828/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
29expected_exit_code: ?u8 = 0,
29expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 },
3030
3131/// Override this field to modify the environment
3232env_map: ?*EnvMap,
......@@ -131,7 +131,7 @@ fn make(step: *Step) !void {
131131 try RunStep.runCommand(
132132 argv_list.items,
133133 self.builder,
134 self.expected_exit_code,
134 self.expected_term,
135135 self.stdout_action,
136136 self.stderr_action,
137137 .Inherit,
lib/std/Build/RunStep.zig+55-28
......@@ -35,7 +35,7 @@ stderr_action: StdIoAction = .inherit,
3535stdin_behavior: std.ChildProcess.StdIo = .Inherit,
3636
3737/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
38expected_exit_code: ?u8 = 0,
38expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 },
3939
4040/// Print the command before running it
4141print: bool,
......@@ -289,7 +289,7 @@ fn make(step: *Step) !void {
289289 try runCommand(
290290 argv_list.items,
291291 self.builder,
292 self.expected_exit_code,
292 self.expected_term,
293293 self.stdout_action,
294294 self.stderr_action,
295295 self.stdin_behavior,
......@@ -303,10 +303,55 @@ fn make(step: *Step) !void {
303303 }
304304}
305305
306fn formatTerm(
307 term: ?std.ChildProcess.Term,
308 comptime fmt: []const u8,
309 options: std.fmt.FormatOptions,
310 writer: anytype,
311) !void {
312 _ = fmt;
313 _ = options;
314 if (term) |t| switch (t) {
315 .Exited => |code| try writer.print("exited with code {}", .{code}),
316 .Signal => |sig| try writer.print("terminated with signal {}", .{sig}),
317 .Stopped => |sig| try writer.print("stopped with signal {}", .{sig}),
318 .Unknown => |code| try writer.print("terminated for unknown reason with code {}", .{code}),
319 } else {
320 try writer.writeAll("exited with any code");
321 }
322}
323fn fmtTerm(term: ?std.ChildProcess.Term) std.fmt.Formatter(formatTerm) {
324 return .{ .data = term };
325}
326
327fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term) bool {
328 return if (expected) |e| switch (e) {
329 .Exited => |expected_code| switch (actual) {
330 .Exited => |actual_code| expected_code == actual_code,
331 else => false,
332 },
333 .Signal => |expected_sig| switch (actual) {
334 .Signal => |actual_sig| expected_sig == actual_sig,
335 else => false,
336 },
337 .Stopped => |expected_sig| switch (actual) {
338 .Stopped => |actual_sig| expected_sig == actual_sig,
339 else => false,
340 },
341 .Unknown => |expected_code| switch (actual) {
342 .Unknown => |actual_code| expected_code == actual_code,
343 else => false,
344 },
345 } else switch (actual) {
346 .Exited => true,
347 else => false,
348 };
349}
350
306351pub fn runCommand(
307352 argv: []const []const u8,
308353 builder: *std.Build,
309 expected_exit_code: ?u8,
354 expected_term: ?std.ChildProcess.Term,
310355 stdout_action: StdIoAction,
311356 stderr_action: StdIoAction,
312357 stdin_behavior: std.ChildProcess.StdIo,
......@@ -368,32 +413,14 @@ pub fn runCommand(
368413 return err;
369414 };
370415
371 switch (term) {
372 .Exited => |code| blk: {
373 const expected_code = expected_exit_code orelse break :blk;
374
375 if (code != expected_code) {
376 if (builder.prominent_compile_errors) {
377 std.debug.print("Run step exited with error code {} (expected {})\n", .{
378 code,
379 expected_code,
380 });
381 } else {
382 std.debug.print("The following command exited with error code {} (expected {}):\n", .{
383 code,
384 expected_code,
385 });
386 printCmd(cwd, argv);
387 }
388
389 return error.UnexpectedExitCode;
390 }
391 },
392 else => {
393 std.debug.print("The following command terminated unexpectedly:\n", .{});
416 if (!termMatches(expected_term, term)) {
417 if (builder.prominent_compile_errors) {
418 std.debug.print("Run step {} (expected {})\n", .{ fmtTerm(term), fmtTerm(expected_term) });
419 } else {
420 std.debug.print("The following command {} (expected {}):\n", .{ fmtTerm(term), fmtTerm(expected_term) });
394421 printCmd(cwd, argv);
395 return error.UncleanExit;
396 },
422 }
423 return error.UnexpectedExit;
397424 }
398425
399426 switch (stderr_action) {
lib/std/os.zig+8-5
......@@ -7074,6 +7074,8 @@ pub const keep_sigpipe: bool = if (@hasDecl(root, "keep_sigpipe"))
70747074else
70757075 false;
70767076
7077fn noopSigHandler(_: c_int) callconv(.C) void {}
7078
70777079/// This function will tell the kernel to ignore SIGPIPE rather than terminate
70787080/// the process. This function is automatically called in `start.zig` before
70797081/// `main`. This behavior can be disabled by adding this to your root module:
......@@ -7092,12 +7094,13 @@ else
70927094pub fn maybeIgnoreSigpipe() void {
70937095 if (have_sigpipe_support and !keep_sigpipe) {
70947096 const act = Sigaction{
7095 .handler = .{ .sigaction = SIG.IGN },
7097 // We set handler to a noop function instead of SIG.IGN so we don't leak our
7098 // signal disposition to a child process
7099 .handler = .{ .handler = noopSigHandler },
70967100 .mask = empty_sigset,
7097 .flags = SA.SIGINFO,
7101 .flags = 0,
70987102 };
7099 sigaction(SIG.PIPE, &act, null) catch |err| std.debug.panic("ignore SIGPIPE failed with '{s}'" ++
7100 ", add `pub const keep_sigpipe = true;` to your root module" ++
7101 " or adjust have_sigpipe_support in std/os.zig", .{@errorName(err)});
7103 sigaction(SIG.PIPE, &act, null) catch |err|
7104 std.debug.panic("failed to install noop SIGPIPE handler with '{s}'", .{@errorName(err)});
71027105 }
71037106}
test/link/macho/dead_strip_dylibs/build.zig+1-1
......@@ -29,7 +29,7 @@ pub fn build(b: *std.Build) void {
2929 exe.dead_strip_dylibs = true;
3030
3131 const run_cmd = exe.run();
32 run_cmd.expected_exit_code = @bitCast(u8, @as(i8, -2)); // should fail
32 run_cmd.expected_term = .{ .Exited = @bitCast(u8, @as(i8, -2)) }; // should fail
3333 test_step.dependOn(&run_cmd.step);
3434 }
3535}
test/src/compare_output.zig+1-1
......@@ -168,7 +168,7 @@ pub const CompareOutputContext = struct {
168168 run.addArgs(case.cli_args);
169169 run.stderr_action = .ignore;
170170 run.stdout_action = .ignore;
171 run.expected_exit_code = 126;
171 run.expected_term = .{ .Exited = 126 };
172172
173173 self.step.dependOn(&run.step);
174174 },
test/standalone.zig+3
......@@ -84,6 +84,9 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
8484 cases.addBuildFile("test/standalone/pie/build.zig", .{});
8585 }
8686 cases.addBuildFile("test/standalone/issue_12706/build.zig", .{});
87 if (std.os.have_sigpipe_support) {
88 cases.addBuildFile("test/standalone/sigpipe/build.zig", .{});
89 }
8790
8891 // Ensure the development tools are buildable. Alphabetically sorted.
8992 // No need to build `tools/spirv/grammar.zig`.
test/standalone/sigpipe/breakpipe.zig created+21
......@@ -0,0 +1,21 @@
1const std = @import("std");
2const build_options = @import("build_options");
3
4pub usingnamespace if (build_options.keep_sigpipe) struct {
5 pub const keep_sigpipe = true;
6} else struct {
7 // intentionally not setting keep_sigpipe to ensure the default behavior is equivalent to false
8};
9
10pub fn main() !void {
11 const pipe = try std.os.pipe();
12 std.os.close(pipe[0]);
13 _ = std.os.write(pipe[1], "a") catch |err| switch (err) {
14 error.BrokenPipe => {
15 try std.io.getStdOut().writer().writeAll("BrokenPipe\n");
16 std.os.exit(123);
17 },
18 else => |e| return e,
19 };
20 unreachable;
21}
test/standalone/sigpipe/build.zig created+35
......@@ -0,0 +1,35 @@
1const std = @import("std");
2const os = std.os;
3
4pub fn build(b: *std.build.Builder) !void {
5 const test_step = b.step("test", "Run the tests");
6
7 // This test runs "breakpipe" as a child process and that process
8 // depends on inheriting a SIGPIPE disposition of "default".
9 {
10 const act = os.Sigaction{
11 .handler = .{ .handler = os.SIG.DFL },
12 .mask = os.empty_sigset,
13 .flags = 0,
14 };
15 try os.sigaction(os.SIG.PIPE, &act, null);
16 }
17
18 for ([_]bool{ false, true }) |keep_sigpipe| {
19 const options = b.addOptions();
20 options.addOption(bool, "keep_sigpipe", keep_sigpipe);
21 const exe = b.addExecutable(.{
22 .name = "breakpipe",
23 .root_source_file = .{ .path = "breakpipe.zig" },
24 });
25 exe.addOptions("build_options", options);
26 const run = exe.run();
27 if (keep_sigpipe) {
28 run.expected_term = .{ .Signal = std.os.SIG.PIPE };
29 } else {
30 run.stdout_action = .{ .expect_exact = "BrokenPipe\n" };
31 run.expected_term = .{ .Exited = 123 };
32 }
33 test_step.dependOn(&run.step);
34 }
35}