mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
add test to ignore sigpipe
This commit is contained in:
parent
c02ced4d34
commit
0a8fe34b11
8 changed files with 126 additions and 37 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
21
test/standalone/sigpipe/breakpipe.zig
Normal file
21
test/standalone/sigpipe/breakpipe.zig
Normal file
|
|
@ -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;
|
||||
}
|
||||
35
test/standalone/sigpipe/build.zig
Normal file
35
test/standalone/sigpipe/build.zig
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue