Skip to content

Commit f860610

Browse files
authored
tests(tee): Add GNU-compat write-error and broken-pipe tests (#4627) (#8797)
Add comprehensive test coverage for tee --output-error and broken pipe behavior: - test_output_error_flag_without_value_defaults_warn_nopipe: Verify default behavior - test_output_error_presence_only_broken_pipe_unix: Non-crash on SIGPIPE - test_broken_pipe_early_termination_stdout_only: Early termination robustness - test_write_failure_reports_error_and_nonzero_exit: Error reporting validation These tests address remaining gaps from GNU test suite tests/misc/tee.sh and tests/misc/write-errors.sh highlighted in #4627. Platform-specific guards (#[cfg(unix)], FreeBSD exclusion) ensure cross-platform compatibility.
1 parent 013a385 commit f860610

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

tests/by-util/test_tee.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,3 +623,93 @@ mod linux_only {
623623
assert!(result.stderr_str().contains("No space left on device"));
624624
}
625625
}
626+
627+
// Additional cross-platform tee tests to cover GNU compatibility around --output-error
628+
#[test]
629+
fn test_output_error_flag_without_value_defaults_warn_nopipe() {
630+
// When --output-error is present without an explicit value, it should default to warn-nopipe
631+
// We can't easily simulate a broken pipe across all platforms here, but we can ensure
632+
// the flag is accepted without error and basic tee functionality still works.
633+
let (at, mut ucmd) = at_and_ucmd!();
634+
let file_out = "tee_output_error_default.txt";
635+
let content = "abc";
636+
637+
let result = ucmd
638+
.arg("--output-error")
639+
.arg(file_out)
640+
.pipe_in(content)
641+
.succeeds();
642+
643+
result.stdout_is(content);
644+
assert!(at.file_exists(file_out));
645+
assert_eq!(at.read(file_out), content);
646+
}
647+
// Unix-only: presence-only --output-error should not crash on broken pipe.
648+
// Current implementation may exit zero; we only assert the process exits to avoid flakiness.
649+
// TODO: When semantics are aligned with GNU warn-nopipe, strengthen assertions here.
650+
#[cfg(all(unix, not(target_os = "freebsd")))]
651+
#[test]
652+
fn test_output_error_presence_only_broken_pipe_unix() {
653+
use std::fs::File;
654+
use std::os::unix::io::FromRawFd;
655+
656+
unsafe {
657+
let mut fds: [libc::c_int; 2] = [0, 0];
658+
assert_eq!(libc::pipe(fds.as_mut_ptr()), 0, "Failed to create pipe");
659+
// Close the read end to simulate a broken pipe on stdout
660+
let _read_end = File::from_raw_fd(fds[0]);
661+
let write_end = File::from_raw_fd(fds[1]);
662+
663+
let content = (0..10_000).map(|_| "x").collect::<String>();
664+
let result = new_ucmd!()
665+
.arg("--output-error") // presence-only flag
666+
.set_stdout(write_end)
667+
.pipe_in(content.as_bytes())
668+
.run();
669+
670+
// Assert that a status was produced (i.e., process exited) and no crash occurred.
671+
assert!(result.try_exit_status().is_some(), "process did not exit");
672+
}
673+
}
674+
675+
// Skip on FreeBSD due to repeated CI hangs in FreeBSD VM (see PR #8684)
676+
#[cfg(all(unix, not(target_os = "freebsd")))]
677+
#[test]
678+
fn test_broken_pipe_early_termination_stdout_only() {
679+
use std::fs::File;
680+
use std::os::unix::io::FromRawFd;
681+
682+
// Create a broken stdout by creating a pipe and dropping the read end
683+
unsafe {
684+
let mut fds: [libc::c_int; 2] = [0, 0];
685+
assert_eq!(libc::pipe(fds.as_mut_ptr()), 0, "Failed to create pipe");
686+
// Close the read end immediately to simulate a broken pipe
687+
let _read_end = File::from_raw_fd(fds[0]);
688+
let write_end = File::from_raw_fd(fds[1]);
689+
690+
let content = (0..10_000).map(|_| "x").collect::<String>();
691+
let mut proc = new_ucmd!();
692+
let result = proc
693+
.set_stdout(write_end)
694+
.ignore_stdin_write_error()
695+
.pipe_in(content.as_bytes())
696+
.run();
697+
698+
// GNU tee exits nonzero on broken pipe unless configured otherwise; implementation
699+
// details vary by mode, but we should not panic and should return an exit status.
700+
// Assert that a status was produced (i.e., process exited) and no crash occurred.
701+
assert!(result.try_exit_status().is_some(), "process did not exit");
702+
}
703+
}
704+
705+
#[test]
706+
fn test_write_failure_reports_error_and_nonzero_exit() {
707+
// Simulate a file open failure which should be reported via show_error and cause a failure
708+
let (at, mut ucmd) = at_and_ucmd!();
709+
// Create a directory and try to use it as an output file (open will fail)
710+
at.mkdir("out_dir");
711+
712+
let result = ucmd.arg("out_dir").pipe_in("data").fails();
713+
714+
assert!(!result.stderr_str().is_empty());
715+
}

0 commit comments

Comments
 (0)