// This file is part of the uutils coreutils package. // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. #![allow(clippy::borrow_as_ptr)] use crate::common::util::TestScenario; use regex::Regex; #[cfg(target_os = "linux")] use std::fmt::Write; // tests for basic tee functionality. // inspired by: // https://github.com/coreutils/coreutils/tests/misc/tee.sh // spell-checker:ignore nopipe #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } #[test] fn test_short_help_is_long_help() { // I can't believe that this test is necessary. let help_short = new_ucmd!() .arg("-h") .succeeds() .no_stderr() .stdout_str() .to_owned(); new_ucmd!() .arg("--help") .succeeds() .no_stderr() .stdout_is(help_short); } #[test] fn test_tee_processing_multiple_operands() { // POSIX says: "Processing of at least 13 file operands shall be supported." let content = "tee_sample_content"; for n in [1, 2, 12, 13] { let files = (1..=n).map(|x| x.to_string()).collect::>(); let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&files) .pipe_in(content) .succeeds() .stdout_is(content); for file in &files { assert!(at.file_exists(file)); assert_eq!(at.read(file), content); } } } #[test] fn test_tee_treat_minus_as_filename() { // Ensure tee treats '-' as the name of a file, as mandated by POSIX. let (at, mut ucmd) = at_and_ucmd!(); let content = "tee_sample_content"; let file = "-"; ucmd.arg("-").pipe_in(content).succeeds().stdout_is(content); assert!(at.file_exists(file)); assert_eq!(at.read(file), content); } #[test] fn test_tee_append() { let (at, mut ucmd) = at_and_ucmd!(); let content = "tee_sample_content"; let file = "tee_out"; at.touch(file); at.write(file, content); assert_eq!(at.read(file), content); ucmd.arg("-a") .arg(file) .pipe_in(content) .succeeds() .stdout_is(content); assert!(at.file_exists(file)); assert_eq!(at.read(file), content.repeat(2)); } #[test] #[cfg(target_os = "linux")] fn test_tee_no_more_writeable_1() { // equals to 'tee /dev/full out2 /dev/full File { use libc::c_int; use std::os::unix::io::FromRawFd; let mut fds: [c_int; 2] = [0, 0]; assert!( (unsafe { libc::pipe(&mut fds as *mut c_int) } == 0), "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..=100_000).fold(String::new(), |mut output, x| { let _ = writeln!(output, "{x}"); output }); #[allow(deprecated)] let output = proc .ignore_stdin_write_error() .set_stdin(Stdio::piped()) .run_no_wait() .pipe_in_and_wait_with_output(content.as_bytes()); (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: {contents}\n Compare: {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_nopipe_3_shortcut() { 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_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_pipe_error_exit_nopipe_shortcut() { let (at, mut ucmd) = at_and_ucmd!(); let file_out_a = "tee_file_out_a"; let proc = ucmd .arg("--output-error=exit-nop") .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()); } }