tests(cat,stdbuf): Add broken-pipe robustness tests (#4627)

Add test coverage for cat and stdbuf broken pipe handling:

**cat tests:**
- test_cat_broken_pipe_nonzero_and_message: Verify cat handles SIGPIPE
  without hanging or crashing and exits with nonzero status

**stdbuf tests:**
- test_permission_external_missing_lib: Handle missing external libstdbuf
- test_no_such_external_missing_lib: Error handling in external lib mode
- Guard existing tests with #[cfg(not(feature = "feat_external_libstdbuf"))]

These tests address write-errors.sh from GNU test suite (#4627) and improve
cross-platform robustness for stdbuf feat_external_libstdbuf builds.
This commit is contained in:
naoNao89
2025-10-03 16:39:55 +07:00
parent 9064619efd
commit 130893a19a
2 changed files with 55 additions and 0 deletions
+26
View File
@@ -18,6 +18,32 @@ use uutests::util::TestScenario;
use uutests::util::vec_of_size;
use uutests::util_name;
#[cfg(unix)]
// Verify cat handles a broken pipe on stdout without hanging or crashing and exits nonzero
#[test]
fn test_cat_broken_pipe_nonzero_and_message() {
use std::fs::File;
use std::os::unix::io::FromRawFd;
use uutests::new_ucmd;
unsafe {
let mut fds: [libc::c_int; 2] = [0, 0];
assert_eq!(libc::pipe(fds.as_mut_ptr()), 0, "Failed to create pipe");
// Close the read end to simulate a broken pipe on stdout
let read_end = File::from_raw_fd(fds[0]);
// Explicitly drop the read-end so writers see EPIPE instead of blocking on a full pipe
std::mem::drop(read_end);
let write_end = File::from_raw_fd(fds[1]);
let content = (0..10000).map(|_| "x").collect::<String>();
// On Unix, SIGPIPE should lead to a non-zero exit; ensure process exits and fails
new_ucmd!()
.set_stdout(write_end)
.pipe_in(content.as_bytes())
.fails();
}
}
#[test]
fn test_output_simple() {
new_ucmd!()
+29
View File
@@ -15,6 +15,7 @@ fn invalid_input() {
new_ucmd!().arg("-/").fails_with_code(125);
}
#[cfg(not(feature = "feat_external_libstdbuf"))]
#[test]
fn test_permission() {
new_ucmd!()
@@ -24,6 +25,23 @@ fn test_permission() {
.stderr_contains("Permission denied");
}
// TODO: Tests below are brittle when feat_external_libstdbuf is enabled and libstdbuf is not installed.
// Align stdbuf with GNU search order to enable deterministic testing without installation:
// 1) search for libstdbuf next to the stdbuf binary, 2) then in LIBSTDBUF_DIR, 3) then system locations.
// After implementing this, rework tests to provide a temporary symlink rather than depending on system state.
#[cfg(feature = "feat_external_libstdbuf")]
#[test]
fn test_permission_external_missing_lib() {
// When built with external libstdbuf, running stdbuf fails early if lib is not installed
new_ucmd!()
.arg("-o1")
.arg(".")
.fails_with_code(1)
.stderr_contains("External libstdbuf not found");
}
#[cfg(not(feature = "feat_external_libstdbuf"))]
#[test]
fn test_no_such() {
new_ucmd!()
@@ -33,6 +51,17 @@ fn test_no_such() {
.stderr_contains("No such file or directory");
}
#[cfg(feature = "feat_external_libstdbuf")]
#[test]
fn test_no_such_external_missing_lib() {
// With external lib mode and missing installation, stdbuf fails before spawning the command
new_ucmd!()
.arg("-o1")
.arg("no_such")
.fails_with_code(1)
.stderr_contains("External libstdbuf not found");
}
// Disabled on x86_64-unknown-linux-musl because the cross-rs Docker image for this target
// does not provide musl-compiled system utilities (like head), leading to dynamic linker errors
// when preloading musl-compiled libstdbuf.so into glibc-compiled binaries. Same thing for FreeBSD.