| author | |
| committer | |
| log | 0a8fe34b11f7a44fd7f744bf4332353a5e7bfcdf |
| tree | 150b057095fdf8278b5d1155287b1d28339be6cd |
| parent | c02ced4d346656abefa4cccfbdebf5dd27b326e5 |
8 files changed, 126 insertions(+), 37 deletions(-)
lib/std/Build/EmulatableRunStep.zig+2-2| ... | @@ -26,7 +26,7 @@ builder: *std.Build, | ... | @@ -26,7 +26,7 @@ builder: *std.Build, |
| 26 | exe: *CompileStep, | 26 | exe: *CompileStep, |
| 27 | 27 | ||
| 28 | /// Set this to `null` to ignore the exit code for the purpose of determining a successful execution | 28 | /// Set this to `null` to ignore the exit code for the purpose of determining a successful execution |
| 29 | expected_exit_code: ?u8 = 0, | 29 | expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 }, |
| 30 | 30 | ||
| 31 | /// Override this field to modify the environment | 31 | /// Override this field to modify the environment |
| 32 | env_map: ?*EnvMap, | 32 | env_map: ?*EnvMap, |
| ... | @@ -131,7 +131,7 @@ fn make(step: *Step) !void { | ... | @@ -131,7 +131,7 @@ fn make(step: *Step) !void { |
| 131 | try RunStep.runCommand( | 131 | try RunStep.runCommand( |
| 132 | argv_list.items, | 132 | argv_list.items, |
| 133 | self.builder, | 133 | self.builder, |
| 134 | self.expected_exit_code, | 134 | self.expected_term, |
| 135 | self.stdout_action, | 135 | self.stdout_action, |
| 136 | self.stderr_action, | 136 | self.stderr_action, |
| 137 | .Inherit, | 137 | .Inherit, |
lib/std/Build/RunStep.zig+55-28| ... | @@ -35,7 +35,7 @@ stderr_action: StdIoAction = .inherit, | ... | @@ -35,7 +35,7 @@ stderr_action: StdIoAction = .inherit, |
| 35 | stdin_behavior: std.ChildProcess.StdIo = .Inherit, | 35 | stdin_behavior: std.ChildProcess.StdIo = .Inherit, |
| 36 | 36 | ||
| 37 | /// Set this to `null` to ignore the exit code for the purpose of determining a successful execution | 37 | /// Set this to `null` to ignore the exit code for the purpose of determining a successful execution |
| 38 | expected_exit_code: ?u8 = 0, | 38 | expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 }, |
| 39 | 39 | ||
| 40 | /// Print the command before running it | 40 | /// Print the command before running it |
| 41 | print: bool, | 41 | print: bool, |
| ... | @@ -289,7 +289,7 @@ fn make(step: *Step) !void { | ... | @@ -289,7 +289,7 @@ fn make(step: *Step) !void { |
| 289 | try runCommand( | 289 | try runCommand( |
| 290 | argv_list.items, | 290 | argv_list.items, |
| 291 | self.builder, | 291 | self.builder, |
| 292 | self.expected_exit_code, | 292 | self.expected_term, |
| 293 | self.stdout_action, | 293 | self.stdout_action, |
| 294 | self.stderr_action, | 294 | self.stderr_action, |
| 295 | self.stdin_behavior, | 295 | self.stdin_behavior, |
| ... | @@ -303,10 +303,55 @@ fn make(step: *Step) !void { | ... | @@ -303,10 +303,55 @@ fn make(step: *Step) !void { |
| 303 | } | 303 | } |
| 304 | } | 304 | } |
| 305 | 305 | ||
| 306 | fn 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 | } | ||
| 323 | fn fmtTerm(term: ?std.ChildProcess.Term) std.fmt.Formatter(formatTerm) { | ||
| 324 | return .{ .data = term }; | ||
| 325 | } | ||
| 326 | |||
| 327 | fn 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 | |||
| 306 | pub fn runCommand( | 351 | pub fn runCommand( |
| 307 | argv: []const []const u8, | 352 | argv: []const []const u8, |
| 308 | builder: *std.Build, | 353 | builder: *std.Build, |
| 309 | expected_exit_code: ?u8, | 354 | expected_term: ?std.ChildProcess.Term, |
| 310 | stdout_action: StdIoAction, | 355 | stdout_action: StdIoAction, |
| 311 | stderr_action: StdIoAction, | 356 | stderr_action: StdIoAction, |
| 312 | stdin_behavior: std.ChildProcess.StdIo, | 357 | stdin_behavior: std.ChildProcess.StdIo, |
| ... | @@ -368,32 +413,14 @@ pub fn runCommand( | ... | @@ -368,32 +413,14 @@ pub fn runCommand( |
| 368 | return err; | 413 | return err; |
| 369 | }; | 414 | }; |
| 370 | 415 | ||
| 371 | switch (term) { | 416 | if (!termMatches(expected_term, term)) { |
| 372 | .Exited => |code| blk: { | 417 | if (builder.prominent_compile_errors) { |
| 373 | const expected_code = expected_exit_code orelse break :blk; | 418 | std.debug.print("Run step {} (expected {})\n", .{ fmtTerm(term), fmtTerm(expected_term) }); |
| 374 | 419 | } else { | |
| 375 | if (code != expected_code) { | 420 | std.debug.print("The following command {} (expected {}):\n", .{ fmtTerm(term), fmtTerm(expected_term) }); |
| 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", .{}); | ||
| 394 | printCmd(cwd, argv); | 421 | printCmd(cwd, argv); |
| 395 | return error.UncleanExit; | 422 | } |
| 396 | }, | 423 | return error.UnexpectedExit; |
| 397 | } | 424 | } |
| 398 | 425 | ||
| 399 | switch (stderr_action) { | 426 | switch (stderr_action) { |
lib/std/os.zig+8-5| ... | @@ -7074,6 +7074,8 @@ pub const keep_sigpipe: bool = if (@hasDecl(root, "keep_sigpipe")) | ... | @@ -7074,6 +7074,8 @@ pub const keep_sigpipe: bool = if (@hasDecl(root, "keep_sigpipe")) |
| 7074 | else | 7074 | else |
| 7075 | false; | 7075 | false; |
| 7076 | 7076 | ||
| 7077 | fn noopSigHandler(_: c_int) callconv(.C) void {} | ||
| 7078 | |||
| 7077 | /// This function will tell the kernel to ignore SIGPIPE rather than terminate | 7079 | /// This function will tell the kernel to ignore SIGPIPE rather than terminate |
| 7078 | /// the process. This function is automatically called in `start.zig` before | 7080 | /// the process. This function is automatically called in `start.zig` before |
| 7079 | /// `main`. This behavior can be disabled by adding this to your root module: | 7081 | /// `main`. This behavior can be disabled by adding this to your root module: |
| ... | @@ -7092,12 +7094,13 @@ else | ... | @@ -7092,12 +7094,13 @@ else |
| 7092 | pub fn maybeIgnoreSigpipe() void { | 7094 | pub fn maybeIgnoreSigpipe() void { |
| 7093 | if (have_sigpipe_support and !keep_sigpipe) { | 7095 | if (have_sigpipe_support and !keep_sigpipe) { |
| 7094 | const act = Sigaction{ | 7096 | 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 }, | ||
| 7096 | .mask = empty_sigset, | 7100 | .mask = empty_sigset, |
| 7097 | .flags = SA.SIGINFO, | 7101 | .flags = 0, |
| 7098 | }; | 7102 | }; |
| 7099 | sigaction(SIG.PIPE, &act, null) catch |err| std.debug.panic("ignore SIGPIPE failed with '{s}'" ++ | 7103 | sigaction(SIG.PIPE, &act, null) catch |err| |
| 7100 | ", add `pub const keep_sigpipe = true;` to your root module" ++ | 7104 | std.debug.panic("failed to install noop SIGPIPE handler with '{s}'", .{@errorName(err)}); |
| 7101 | " or adjust have_sigpipe_support in std/os.zig", .{@errorName(err)}); | ||
| 7102 | } | 7105 | } |
| 7103 | } | 7106 | } |
test/link/macho/dead_strip_dylibs/build.zig+1-1| ... | @@ -29,7 +29,7 @@ pub fn build(b: *std.Build) void { | ... | @@ -29,7 +29,7 @@ pub fn build(b: *std.Build) void { |
| 29 | exe.dead_strip_dylibs = true; | 29 | exe.dead_strip_dylibs = true; |
| 30 | 30 | ||
| 31 | const run_cmd = exe.run(); | 31 | 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 |
| 33 | test_step.dependOn(&run_cmd.step); | 33 | test_step.dependOn(&run_cmd.step); |
| 34 | } | 34 | } |
| 35 | } | 35 | } |
test/src/compare_output.zig+1-1| ... | @@ -168,7 +168,7 @@ pub const CompareOutputContext = struct { | ... | @@ -168,7 +168,7 @@ pub const CompareOutputContext = struct { |
| 168 | run.addArgs(case.cli_args); | 168 | run.addArgs(case.cli_args); |
| 169 | run.stderr_action = .ignore; | 169 | run.stderr_action = .ignore; |
| 170 | run.stdout_action = .ignore; | 170 | run.stdout_action = .ignore; |
| 171 | run.expected_exit_code = 126; | 171 | run.expected_term = .{ .Exited = 126 }; |
| 172 | 172 | ||
| 173 | self.step.dependOn(&run.step); | 173 | self.step.dependOn(&run.step); |
| 174 | }, | 174 | }, |
test/standalone.zig+3| ... | @@ -84,6 +84,9 @@ pub fn addCases(cases: *tests.StandaloneContext) void { | ... | @@ -84,6 +84,9 @@ pub fn addCases(cases: *tests.StandaloneContext) void { |
| 84 | cases.addBuildFile("test/standalone/pie/build.zig", .{}); | 84 | cases.addBuildFile("test/standalone/pie/build.zig", .{}); |
| 85 | } | 85 | } |
| 86 | cases.addBuildFile("test/standalone/issue_12706/build.zig", .{}); | 86 | cases.addBuildFile("test/standalone/issue_12706/build.zig", .{}); |
| 87 | if (std.os.have_sigpipe_support) { | ||
| 88 | cases.addBuildFile("test/standalone/sigpipe/build.zig", .{}); | ||
| 89 | } | ||
| 87 | 90 | ||
| 88 | // Ensure the development tools are buildable. Alphabetically sorted. | 91 | // Ensure the development tools are buildable. Alphabetically sorted. |
| 89 | // No need to build `tools/spirv/grammar.zig`. | 92 | // No need to build `tools/spirv/grammar.zig`. |
test/standalone/sigpipe/breakpipe.zig created+21| ... | @@ -0,0 +1,21 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const build_options = @import("build_options"); | ||
| 3 | |||
| 4 | pub 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 | |||
| 10 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const os = std.os; | ||
| 3 | |||
| 4 | pub 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 | } | ||