mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
tee: add short-read regression test
Upstream commit 9f50c8b42 ("tee: fix input with sleep") already
contains the functional fix for short reads from a paused writer.
Keep the regression coverage so we do not reintroduce the bug.
The test writes one small chunk, waits long enough for a buggy tee to
exit, then writes a second chunk and asserts both stdout and the output
file receive both writes.
This commit is contained in:
committed by
Sylvestre Ledru
parent
b08e880c1b
commit
b17025b663
@@ -193,6 +193,64 @@ fn test_tee_output_not_buffered() {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tee_continues_after_short_read() {
|
||||
// Regression test: `tee` must keep reading until EOF even when the
|
||||
// first `read(2)` returns fewer bytes than its internal buffer. This
|
||||
// happens in any pipeline where the upstream writer pauses between
|
||||
// writes (e.g. a slow producer, a `sleep` in a shell pipeline, or a
|
||||
// service emitting log lines in bursts). Treating a short read as
|
||||
// end-of-file caused tee to exit prematurely and downstream producers
|
||||
// to die with SIGPIPE on their next write.
|
||||
//
|
||||
// Run in a separate thread so that the test fails via timeout rather
|
||||
// than hanging if a regression reintroduces the bug in a form where
|
||||
// tee blocks on read instead of exiting early.
|
||||
let handle = std::thread::spawn(move || {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file_out = "tee_short_read_out";
|
||||
|
||||
let mut child = ucmd
|
||||
.arg(file_out)
|
||||
.set_stdin(Stdio::piped())
|
||||
.set_stdout(Stdio::piped())
|
||||
.run_no_wait();
|
||||
|
||||
// First chunk — deliberately much smaller than tee's internal
|
||||
// buffer so that `read(2)` returns a short count.
|
||||
child.write_in(b"first\n");
|
||||
assert_eq!(&child.stdout_exact_bytes(6), b"first\n");
|
||||
|
||||
// Give a buggy implementation time to exit before we try to
|
||||
// write again.
|
||||
child.delay(50);
|
||||
|
||||
// Second chunk. A correctly-implemented tee is still reading
|
||||
// from stdin; a buggy one has already exited and this write
|
||||
// will either fail with EPIPE or never reach the output file.
|
||||
child.write_in(b"second\n");
|
||||
assert_eq!(&child.stdout_exact_bytes(7), b"second\n");
|
||||
|
||||
// `wait` closes stdin for us before waiting on the child.
|
||||
child.wait().unwrap().success();
|
||||
|
||||
assert_eq!(at.read(file_out), "first\nsecond\n");
|
||||
});
|
||||
|
||||
for _ in 0..500 {
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
if handle.is_finished() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
handle.is_finished(),
|
||||
"tee did not complete within the timeout"
|
||||
);
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "linux", not(wasi_runner)))]
|
||||
mod linux_only {
|
||||
use uutests::util::{AtPath, CmdResult, UCommand};
|
||||
|
||||
Reference in New Issue
Block a user