mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Implement tee -p and --output-error
This has the following behaviours. On Unix: - The default is to exit on pipe errors, and warn on other errors. - "--output-error=warn" means to warn on all errors - "--output-error", "--output-error=warn-nopipe" and "-p" all mean that pipe errors are suppressed, all other errors warn. - "--output-error=exit" means to warn and exit on all errors. - "--output-error=exit-nopipe" means to suppress pipe errors, and to warn and exit on all other errors. On non-Unix platforms, all pipe behaviours are ignored, so the default is effectively "--output-error=warn" and "warn-nopipe" is identical. The only meaningful option is "--output-error=exit" which is identical to "--output-error=exit-nopipe" on these platforms. Note that warnings give a non-zero exit code, but do not halt writing to non-erroring targets.
This commit is contained in:
parent
37b754f462
commit
a360504574
2 changed files with 521 additions and 17 deletions
|
@ -4,6 +4,8 @@ use crate::common::util::*;
|
|||
// inspired by:
|
||||
// https://github.com/coreutils/coreutils/tests/misc/tee.sh
|
||||
|
||||
// spell-checker:ignore nopipe
|
||||
|
||||
#[test]
|
||||
fn test_tee_processing_multiple_operands() {
|
||||
// POSIX says: "Processing of at least 13 file operands shall be supported."
|
||||
|
@ -98,3 +100,356 @@ fn test_tee_no_more_writeable_2() {
|
|||
// assert_eq!(at.read(file_out_b), content);
|
||||
// assert!(result.stderr.contains("No space left on device"));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux_only {
|
||||
use crate::common::util::*;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::process::Output;
|
||||
use std::thread;
|
||||
|
||||
fn make_broken_pipe() -> File {
|
||||
use libc::c_int;
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
let mut fds: [c_int; 2] = [0, 0];
|
||||
if unsafe { libc::pipe(&mut fds as *mut c_int) } != 0 {
|
||||
panic!("Failed to create pipe");
|
||||
}
|
||||
|
||||
// Drop the read end of the pipe
|
||||
let _ = unsafe { File::from_raw_fd(fds[0]) };
|
||||
|
||||
// Make the write end of the pipe into a Rust File
|
||||
unsafe { File::from_raw_fd(fds[1]) }
|
||||
}
|
||||
|
||||
fn run_tee(proc: &mut UCommand) -> (String, Output) {
|
||||
let content = (1..=100000).map(|x| format!("{}\n", x)).collect::<String>();
|
||||
|
||||
let mut prog = proc.run_no_wait();
|
||||
|
||||
let mut stdin = prog
|
||||
.stdin
|
||||
.take()
|
||||
.unwrap_or_else(|| panic!("Could not take child process stdin"));
|
||||
|
||||
let c = content.clone();
|
||||
let thread = thread::spawn(move || {
|
||||
let _ = stdin.write_all(c.as_bytes());
|
||||
});
|
||||
|
||||
let output = prog.wait_with_output().unwrap();
|
||||
|
||||
thread.join().unwrap();
|
||||
|
||||
(content, output)
|
||||
}
|
||||
|
||||
fn expect_success(output: &Output) {
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"Command was expected to succeed.\nstdout = {}\n stderr = {}",
|
||||
std::str::from_utf8(&output.stdout).unwrap(),
|
||||
std::str::from_utf8(&output.stderr).unwrap(),
|
||||
);
|
||||
assert!(
|
||||
output.stderr.is_empty(),
|
||||
"Unexpected data on stderr.\n stderr = {}",
|
||||
std::str::from_utf8(&output.stderr).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
fn expect_failure(output: &Output, message: &str) {
|
||||
assert!(
|
||||
!output.status.success(),
|
||||
"Command was expected to fail.\nstdout = {}\n stderr = {}",
|
||||
std::str::from_utf8(&output.stdout).unwrap(),
|
||||
std::str::from_utf8(&output.stderr).unwrap(),
|
||||
);
|
||||
assert!(
|
||||
std::str::from_utf8(&output.stderr)
|
||||
.unwrap()
|
||||
.contains(message),
|
||||
"Expected to see error message fragment {} in stderr, but did not.\n stderr = {}",
|
||||
message,
|
||||
std::str::from_utf8(&output.stderr).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
fn expect_silent_failure(output: &Output) {
|
||||
assert!(
|
||||
!output.status.success(),
|
||||
"Command was expected to fail.\nstdout = {}\n stderr = {}",
|
||||
std::str::from_utf8(&output.stdout).unwrap(),
|
||||
std::str::from_utf8(&output.stderr).unwrap(),
|
||||
);
|
||||
assert!(
|
||||
output.stderr.is_empty(),
|
||||
"Unexpected data on stderr.\n stderr = {}",
|
||||
std::str::from_utf8(&output.stderr).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
fn expect_correct(name: &str, at: &AtPath, contents: &str) {
|
||||
assert!(at.file_exists(name));
|
||||
let compare = at.read(name);
|
||||
assert_eq!(compare, contents);
|
||||
}
|
||||
|
||||
fn expect_short(name: &str, at: &AtPath, contents: &str) {
|
||||
assert!(at.file_exists(name));
|
||||
let compare = at.read(name);
|
||||
assert!(
|
||||
compare.len() < contents.len(),
|
||||
"Too many bytes ({}) written to {} (should be a short count from {})",
|
||||
compare.len(),
|
||||
name,
|
||||
contents.len()
|
||||
);
|
||||
assert!(contents.starts_with(&compare),
|
||||
"Expected truncated output to be a prefix of the correct output, but it isn't.\n Correct: {}\n Compare: {}",
|
||||
contents,
|
||||
compare);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_error_default() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd.arg(file_out_a).set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_silent_failure(&output);
|
||||
expect_short(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_error_warn_nopipe_1() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("-p")
|
||||
.arg(file_out_a)
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_success(&output);
|
||||
expect_correct(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_error_warn_nopipe_2() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("--output-error")
|
||||
.arg(file_out_a)
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_success(&output);
|
||||
expect_correct(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_error_warn_nopipe_3() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("--output-error=warn-nopipe")
|
||||
.arg(file_out_a)
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_success(&output);
|
||||
expect_correct(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_error_warn() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("--output-error=warn")
|
||||
.arg(file_out_a)
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_failure(&output, "Broken pipe");
|
||||
expect_correct(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_error_exit() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("--output-error=exit")
|
||||
.arg(file_out_a)
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_failure(&output, "Broken pipe");
|
||||
expect_short(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pipe_error_exit_nopipe() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("--output-error=exit-nopipe")
|
||||
.arg(file_out_a)
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_success(&output);
|
||||
expect_correct(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_space_error_default() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd.arg(file_out_a).arg("/dev/full");
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_failure(&output, "No space left");
|
||||
expect_correct(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_space_error_warn_nopipe_1() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("-p")
|
||||
.arg(file_out_a)
|
||||
.arg("/dev/full")
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_failure(&output, "No space left");
|
||||
expect_correct(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_space_error_warn_nopipe_2() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("--output-error")
|
||||
.arg(file_out_a)
|
||||
.arg("/dev/full")
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_failure(&output, "No space left");
|
||||
expect_correct(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_space_error_warn_nopipe_3() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("--output-error=warn-nopipe")
|
||||
.arg(file_out_a)
|
||||
.arg("/dev/full")
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_failure(&output, "No space left");
|
||||
expect_correct(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_space_error_warn() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("--output-error=warn")
|
||||
.arg(file_out_a)
|
||||
.arg("/dev/full")
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_failure(&output, "No space left");
|
||||
expect_correct(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_space_error_exit() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("--output-error=exit")
|
||||
.arg(file_out_a)
|
||||
.arg("/dev/full")
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_failure(&output, "No space left");
|
||||
expect_short(file_out_a, &at, content.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_space_error_exit_nopipe() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
let file_out_a = "tee_file_out_a";
|
||||
|
||||
let proc = ucmd
|
||||
.arg("--output-error=exit-nopipe")
|
||||
.arg(file_out_a)
|
||||
.arg("/dev/full")
|
||||
.set_stdout(make_broken_pipe());
|
||||
|
||||
let (content, output) = run_tee(proc);
|
||||
|
||||
expect_failure(&output, "No space left");
|
||||
expect_short(file_out_a, &at, content.as_str());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue