From 982fb682e9ddf52b41288e1b23ddf526c29457ef Mon Sep 17 00:00:00 2001 From: Joining7943 <111500881+Joining7943@users.noreply.github.com> Date: Fri, 18 Nov 2022 01:25:43 +0100 Subject: [PATCH] tests: Use UChild in tests. Rename run_no_wait_child to run_no_wait and return UChild tests/tail: * test_stdin_redirect_file:. Test fails now when assert_alive()! The follow test `tail -f < file` where file's content is `foo` fails with: Assertion failed. Expected 'tail' to be running but exited with status=exit status: 0 I also tried on the command line and can confirm that tail isn't runnning when following by descriptor. The test is deactivated until the implementation is fixed. * test_follow_stdin_descriptor * test_follow_stdin_explicit_indefinitely. * test_follow_single * test_follow_non_utf8_bytes * test_follow_multiple * test_follow_name_multiple * test_follow_invalid_pid * test_single_big_args * test_retry3 * test_retry4 * test_retry5 * test_retry7 * test_retry8 * test_retry9 * test_follow_descriptor_vs_rename1 * test_follow_descriptor_vs_rename2 * test_follow_name_retry_headers * test_follow_name_remove * test_follow_name_truncate1 * test_follow_name_truncate2 * test_follow_name_truncate3 * test_follow_name_truncate4 * test_follow_truncate_fast * test_follow_name_move_create1 * test_follow_name_move_create2 * test_follow_name_move1 * test_follow_name_move2 * test_follow_name_move_retry1 * test_follow_name_move_retry2 * test_follow_inotify_only_regular * test_fifo * test_illegal_seek tests/cat: * test_dev_full * test_dev_full_show_all * test_dev_random * test_fifo_symlink tests/dd: * test_random_73k_test_lazy_fullblock * test_sync_delayed_reader tests/factor: * test_parallel tests/rm: * test_rm_force_prompts_order * test_rm_descend_directory * test_rm_prompts tests/seq: * the helper run method tests/sort: * test_sigpipe_panic tests/tee: * the helper run_tee method tests/tty: * test_tty module tests/yes: * the helper run method --- tests/by-util/test_cat.rs | 56 +-- tests/by-util/test_dd.rs | 23 +- tests/by-util/test_factor.rs | 4 +- tests/by-util/test_rm.rs | 57 +-- tests/by-util/test_seq.rs | 12 +- tests/by-util/test_sort.rs | 6 +- tests/by-util/test_tail.rs | 740 ++++++++++++++++++++--------------- tests/by-util/test_tee.rs | 21 +- tests/by-util/test_tty.rs | 24 +- tests/by-util/test_yes.rs | 13 +- tests/common/util.rs | 37 +- 11 files changed, 519 insertions(+), 474 deletions(-) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 1869105d0..6fef828ab 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -1,12 +1,11 @@ // spell-checker:ignore NOFILE use crate::common::util::*; -use std::fs::OpenOptions; -#[cfg(unix)] -use std::io::Read; - #[cfg(any(target_os = "linux", target_os = "android"))] use rlimit::Resource; +use std::fs::OpenOptions; +#[cfg(not(windows))] +use std::process::Stdio; #[test] fn test_output_simple() { @@ -87,8 +86,7 @@ fn test_fifo_symlink() { pipe.write_all(&data).unwrap(); }); - let output = proc.wait_with_output().unwrap(); - assert_eq!(&output.stdout, &data2); + proc.wait().unwrap().stdout_only_bytes(data2); thread.join().unwrap(); } @@ -395,17 +393,19 @@ fn test_squeeze_blank_before_numbering() { #[test] #[cfg(unix)] fn test_dev_random() { - let mut buf = [0; 2048]; #[cfg(any(target_os = "linux", target_os = "android"))] const DEV_RANDOM: &str = "/dev/urandom"; #[cfg(not(any(target_os = "linux", target_os = "android")))] const DEV_RANDOM: &str = "/dev/random"; - let mut proc = new_ucmd!().args(&[DEV_RANDOM]).run_no_wait(); - let mut proc_stdout = proc.stdout.take().unwrap(); - proc_stdout.read_exact(&mut buf).unwrap(); + let mut proc = new_ucmd!() + .set_stdout(Stdio::piped()) + .args(&[DEV_RANDOM]) + .run_no_wait(); + proc.make_assertion_with_delay(100).is_alive(); + let buf = proc.stdout_exact_bytes(2048); let num_zeroes = buf.iter().fold(0, |mut acc, &n| { if n == 0 { acc += 1; @@ -415,7 +415,7 @@ fn test_dev_random() { // The probability of more than 512 zero bytes is essentially zero if the // output is truly random. assert!(num_zeroes < 512); - proc.kill().unwrap(); + proc.kill(); } /// Reading from /dev/full should return an infinite amount of zero bytes. @@ -423,29 +423,35 @@ fn test_dev_random() { #[test] #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] fn test_dev_full() { - let mut buf = [0; 2048]; - let mut proc = new_ucmd!().args(&["/dev/full"]).run_no_wait(); - let mut proc_stdout = proc.stdout.take().unwrap(); + let mut proc = new_ucmd!() + .set_stdout(Stdio::piped()) + .args(&["/dev/full"]) + .run_no_wait(); let expected = [0; 2048]; - proc_stdout.read_exact(&mut buf).unwrap(); - assert_eq!(&buf[..], &expected[..]); - proc.kill().unwrap(); + proc.make_assertion_with_delay(100) + .is_alive() + .with_exact_output(2048, 0) + .stdout_only_bytes(expected); + proc.kill(); } #[test] #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] fn test_dev_full_show_all() { - let mut buf = [0; 2048]; - let mut proc = new_ucmd!().args(&["-A", "/dev/full"]).run_no_wait(); - let mut proc_stdout = proc.stdout.take().unwrap(); - proc_stdout.read_exact(&mut buf).unwrap(); - - let expected: Vec = (0..buf.len()) + let buf_len = 2048; + let mut proc = new_ucmd!() + .set_stdout(Stdio::piped()) + .args(&["-A", "/dev/full"]) + .run_no_wait(); + let expected: Vec = (0..buf_len) .map(|n| if n & 1 == 0 { b'^' } else { b'@' }) .collect(); - assert_eq!(&buf[..], &expected[..]); - proc.kill().unwrap(); + proc.make_assertion_with_delay(100) + .is_alive() + .with_exact_output(buf_len, 0) + .stdout_only_bytes(expected); + proc.kill(); } #[test] diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 03877d1d5..b7309dfce 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1036,11 +1036,12 @@ fn test_random_73k_test_lazy_fullblock() { sleep(Duration::from_millis(10)); } } - let output = child.wait_with_output().unwrap(); - assert!(output.status.success()); - - assert_eq!(&output.stdout, &data); - assert_eq!(&output.stderr, b"142+1 records in\n72+1 records out\n"); + child + .wait() + .unwrap() + .success() + .stdout_is_bytes(&data) + .stderr_is("142+1 records in\n72+1 records out\n"); } #[test] @@ -1381,9 +1382,6 @@ fn test_sync_delayed_reader() { sleep(Duration::from_millis(10)); } } - let output = child.wait_with_output().unwrap(); - assert!(output.status.success()); - // Expected output is 0xFFFFFFFF00000000FFFFFFFF00000000... let mut expected: [u8; 8 * 16] = [0; 8 * 16]; for i in 0..8 { @@ -1391,8 +1389,13 @@ fn test_sync_delayed_reader() { expected[16 * i + j] = 0xF; } } - assert_eq!(&output.stdout, &expected); - assert_eq!(&output.stderr, b"0+8 records in\n4+0 records out\n"); + + child + .wait() + .unwrap() + .success() + .stdout_is_bytes(expected) + .stderr_is("0+8 records in\n4+0 records out\n"); } /// Test for making a sparse copy of the input file. diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index 92abf5b79..a4f38dbac 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -52,7 +52,7 @@ fn test_parallel() { .open(tmp_dir.plus("output")) .unwrap(); - for mut child in (0..10) + for child in (0..10) .map(|_| { new_ucmd!() .set_stdout(output.try_clone().unwrap()) @@ -61,7 +61,7 @@ fn test_parallel() { }) .collect::>() { - assert_eq!(child.wait().unwrap().code().unwrap(), 0); + child.wait().unwrap().success(); } let result = TestScenario::new(util_name!()) diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index ee81cf8d9..7edf26367 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -357,8 +357,6 @@ fn test_rm_interactive_never() { fn test_rm_descend_directory() { // This test descends into each directory and deletes the files and folders inside of them // This test will have the rm process asks 6 question and us answering Y to them will delete all the files and folders - use std::io::Write; - use std::process::Child; // Needed for talking with stdin on platforms where CRLF or LF matters const END_OF_LINE: &str = if cfg!(windows) { "\r\n" } else { "\n" }; @@ -375,24 +373,15 @@ fn test_rm_descend_directory() { at.touch(file_1); at.touch(file_2); - let mut child: Child = scene.ucmd().arg("-ri").arg("a").run_no_wait(); + let mut child = scene.ucmd().arg("-ri").arg("a").run_no_wait(); + child.write_in(yes.as_bytes()).unwrap(); + child.write_in(yes.as_bytes()).unwrap(); + child.write_in(yes.as_bytes()).unwrap(); + child.write_in(yes.as_bytes()).unwrap(); + child.write_in(yes.as_bytes()).unwrap(); + child.write_in(yes.as_bytes()).unwrap(); - // Needed so that we can talk to the rm program - let mut child_stdin = child.stdin.take().unwrap(); - child_stdin.write_all(yes.as_bytes()).unwrap(); - child_stdin.flush().unwrap(); - child_stdin.write_all(yes.as_bytes()).unwrap(); - child_stdin.flush().unwrap(); - child_stdin.write_all(yes.as_bytes()).unwrap(); - child_stdin.flush().unwrap(); - child_stdin.write_all(yes.as_bytes()).unwrap(); - child_stdin.flush().unwrap(); - child_stdin.write_all(yes.as_bytes()).unwrap(); - child_stdin.flush().unwrap(); - child_stdin.write_all(yes.as_bytes()).unwrap(); - child_stdin.flush().unwrap(); - - child.wait_with_output().unwrap(); + child.wait().unwrap(); assert!(!at.dir_exists("a/b")); assert!(!at.dir_exists("a")); @@ -404,7 +393,6 @@ fn test_rm_descend_directory() { #[test] fn test_rm_prompts() { use std::io::Write; - use std::process::Child; // Needed for talking with stdin on platforms where CRLF or LF matters const END_OF_LINE: &str = if cfg!(windows) { "\r\n" } else { "\n" }; @@ -457,21 +445,15 @@ fn test_rm_prompts() { .arg(file_2) .succeeds(); - let mut child: Child = scene.ucmd().arg("-ri").arg("a").run_no_wait(); - - let mut child_stdin = child.stdin.take().unwrap(); + let mut child = scene.ucmd().arg("-ri").arg("a").run_no_wait(); for _ in 0..9 { - child_stdin.write_all(yes.as_bytes()).unwrap(); - child_stdin.flush().unwrap(); + child.write_in(yes.as_bytes()).unwrap(); } - let output = child.wait_with_output().unwrap(); + let result = child.wait().unwrap(); let mut trimmed_output = Vec::new(); - for string in String::from_utf8(output.stderr) - .expect("Couldn't convert output.stderr to string") - .split("rm: ") - { + for string in result.stderr_str().split("rm: ") { if !string.is_empty() { let trimmed_string = format!("rm: {}", string).trim().to_string(); trimmed_output.push(trimmed_string); @@ -491,9 +473,6 @@ fn test_rm_prompts() { #[test] fn test_rm_force_prompts_order() { - use std::io::Write; - use std::process::Child; - // Needed for talking with stdin on platforms where CRLF or LF matters const END_OF_LINE: &str = if cfg!(windows) { "\r\n" } else { "\n" }; @@ -507,15 +486,11 @@ fn test_rm_force_prompts_order() { at.touch(empty_file); // This should cause rm to prompt to remove regular empty file - let mut child: Child = scene.ucmd().arg("-fi").arg(empty_file).run_no_wait(); + let mut child = scene.ucmd().arg("-fi").arg(empty_file).run_no_wait(); + child.write_in(yes.as_bytes()).unwrap(); - let mut child_stdin = child.stdin.take().unwrap(); - child_stdin.write_all(yes.as_bytes()).unwrap(); - child_stdin.flush().unwrap(); - - let output = child.wait_with_output().unwrap(); - let string_output = - String::from_utf8(output.stderr).expect("Couldn't convert output.stderr to string"); + let result = child.wait().unwrap(); + let string_output = result.stderr_str(); assert_eq!( string_output.trim(), "rm: remove regular empty file 'empty'?" diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index a6fa36353..ad224fbc7 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -1,6 +1,6 @@ // spell-checker:ignore lmnop xlmnop use crate::common::util::*; -use std::io::Read; +use std::process::Stdio; #[test] fn test_invalid_arg() { @@ -595,12 +595,10 @@ fn test_width_floats() { /// Run `seq`, capture some of the output, close the pipe, and verify it. fn run(args: &[&str], expected: &[u8]) { let mut cmd = new_ucmd!(); - let mut child = cmd.args(args).run_no_wait(); - let mut stdout = child.stdout.take().unwrap(); - let mut buf = vec![0; expected.len()]; - stdout.read_exact(&mut buf).unwrap(); - drop(stdout); - assert!(child.wait().unwrap().success()); + let mut child = cmd.args(args).set_stdout(Stdio::piped()).run_no_wait(); + let buf = child.stdout_exact_bytes(expected.len()); + child.close_stdout(); + child.wait().unwrap().success(); assert_eq!(buf.as_slice(), expected); } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index fe63eb2c7..38dea5b72 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -976,7 +976,7 @@ fn test_sigpipe_panic() { let mut child = cmd.args(&["ext_sort.txt"]).run_no_wait(); // Dropping the stdout should not lead to an error. // The "Broken pipe" error should be silently ignored. - drop(child.stdout.take()); + child.close_stdout(); assert_eq!( String::from_utf8(child.wait_with_output().unwrap().stderr), Ok(String::new()) @@ -1137,7 +1137,7 @@ fn test_tmp_files_deleted_on_sigint() { "--buffer-size=1", // with a small buffer size `sort` will be forced to create a temporary directory very soon. "--temporary-directory=tmp_dir", ]); - let mut child = ucmd.run_no_wait(); + let child = ucmd.run_no_wait(); // wait a short amount of time so that `sort` can create a temporary directory. let mut timeout = Duration::from_millis(100); for _ in 0..5 { @@ -1152,7 +1152,7 @@ fn test_tmp_files_deleted_on_sigint() { // kill sort with SIGINT signal::kill(Pid::from_raw(child.id() as i32), signal::SIGINT).unwrap(); // wait for `sort` to exit - assert_eq!(child.wait().unwrap().code(), Some(2)); + child.wait().unwrap().code_is(2); // `sort` should have deleted the temporary directory again. assert!(read_dir(at.plus("tmp_dir")).unwrap().next().is_none()); } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 49e8b54f2..39c0ef052 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -13,11 +13,8 @@ use crate::common::random::*; use crate::common::util::*; use rand::distributions::Alphanumeric; use std::char::from_digit; -use std::io::Read; use std::io::Write; use std::process::Stdio; -use std::thread::sleep; -use std::time::Duration; use tail::chunks::BUFFER_SIZE as CHUNK_BUFFER_SIZE; static FOOBAR_TXT: &str = "foobar.txt"; @@ -30,6 +27,9 @@ static FOLLOW_NAME_SHORT_EXP: &str = "follow_name_short.expected"; #[allow(dead_code)] static FOLLOW_NAME_EXP: &str = "follow_name.expected"; +#[cfg(not(windows))] +const DEFAULT_SLEEP_INTERVAL_MILLIS: u64 = 1000; + #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); @@ -55,6 +55,8 @@ fn test_stdin_explicit() { } #[test] +// FIXME: the -f test fails with: Assertion failed. Expected 'tail' to be running but exited with status=exit status: 0 +#[cfg(disable_until_fixed)] #[cfg(not(target_vendor = "apple"))] // FIXME: for currently not working platforms fn test_stdin_redirect_file() { // $ echo foo > f @@ -89,17 +91,11 @@ fn test_stdin_redirect_file() { .set_stdin(std::fs::File::open(at.plus("f")).unwrap()) .run_no_wait(); - sleep(Duration::from_millis(500)); - - // Cleanup the process if it is still running. The result isn't important - // for the test, so it is ignored. - // NOTE: The result may be Error on windows with an Os error `Permission - // Denied` if the process already terminated: - let _ = p.kill(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert!(buf_stdout.eq("foo")); - assert!(buf_stderr.is_empty()); + p.make_assertion_with_delay(500).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stdout_only("foo"); } #[test] @@ -342,13 +338,12 @@ fn test_follow_stdin_descriptor() { let mut args = vec!["-f", "-"]; for _ in 0..2 { let mut p = ts.ucmd().args(&args).run_no_wait(); - sleep(Duration::from_millis(500)); - - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert!(buf_stdout.is_empty()); - assert!(buf_stderr.is_empty()); + p.make_assertion_with_delay(500).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .no_stderr() + .no_stdout(); args.pop(); } @@ -387,12 +382,12 @@ fn test_follow_stdin_explicit_indefinitely() { .args(&["-f", "-", "/dev/null"]) .run_no_wait(); - sleep(Duration::from_millis(500)); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert!(buf_stdout.eq("==> standard input <==")); - assert!(buf_stderr.eq("tail: warning: following standard input indefinitely is ineffective")); + p.make_assertion_with_delay(500).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stdout_is("==> standard input <==") + .stderr_is("tail: warning: following standard input indefinitely is ineffective"); // Also: // $ echo bar > foo @@ -482,16 +477,26 @@ fn test_follow_single() { .arg(FOOBAR_TXT) .run_no_wait(); - let expected = at.read("foobar_single_default.expected"); - assert_eq!(read_size(&mut child, expected.len()), expected); + let expected_fixture = "foobar_single_default.expected"; + + child + .make_assertion_with_delay(200) + .is_alive() + .with_current_output() + .stdout_only_fixture(expected_fixture); // We write in a temporary copy of foobar.txt let expected = "line1\nline2\n"; at.append(FOOBAR_TXT, expected); - assert_eq!(read_size(&mut child, expected.len()), expected); - - child.kill().unwrap(); + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .is_alive(); + child + .kill() + .make_assertion() + .with_current_output() + .stdout_only(expected); } /// Test for following when bytes are written that are not valid UTF-8. @@ -505,8 +510,12 @@ fn test_follow_non_utf8_bytes() { .set_stdin(Stdio::null()) .arg(FOOBAR_TXT) .run_no_wait(); - let expected = at.read("foobar_single_default.expected"); - assert_eq!(read_size(&mut child, expected.len()), expected); + + child + .make_assertion_with_delay(100) + .is_alive() + .with_current_output() + .stdout_only_fixture("foobar_single_default.expected"); // Now append some bytes that are not valid UTF-8. // @@ -521,10 +530,14 @@ fn test_follow_non_utf8_bytes() { // test, it is just a requirement of the current implementation. let expected = [0b10000000, b'\n']; at.append_bytes(FOOBAR_TXT, &expected); - let actual = read_size_bytes(&mut child, expected.len()); - assert_eq!(actual, expected.to_vec()); - child.kill().unwrap(); + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .with_current_output() + .stdout_only_bytes(expected); + + child.make_assertion().is_alive(); + child.kill(); } #[test] @@ -538,19 +551,30 @@ fn test_follow_multiple() { .arg(FOOBAR_2_TXT) .run_no_wait(); - let expected = at.read("foobar_follow_multiple.expected"); - assert_eq!(read_size(&mut child, expected.len()), expected); + child + .make_assertion_with_delay(500) + .is_alive() + .with_current_output() + .stdout_only_fixture("foobar_follow_multiple.expected"); let first_append = "trois\n"; at.append(FOOBAR_2_TXT, first_append); - assert_eq!(read_size(&mut child, first_append.len()), first_append); + + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .with_current_output() + .stdout_only(first_append); let second_append = "twenty\nthirty\n"; - let expected = at.read("foobar_follow_multiple_appended.expected"); at.append(FOOBAR_TXT, second_append); - assert_eq!(read_size(&mut child, expected.len()), expected); - child.kill().unwrap(); + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .with_current_output() + .stdout_only_fixture("foobar_follow_multiple_appended.expected"); + + child.make_assertion().is_alive(); + child.kill(); } #[test] @@ -564,19 +588,30 @@ fn test_follow_name_multiple() { .arg(FOOBAR_2_TXT) .run_no_wait(); - let expected = at.read("foobar_follow_multiple.expected"); - assert_eq!(read_size(&mut child, expected.len()), expected); + child + .make_assertion_with_delay(500) + .is_alive() + .with_current_output() + .stdout_only_fixture("foobar_follow_multiple.expected"); let first_append = "trois\n"; at.append(FOOBAR_2_TXT, first_append); - assert_eq!(read_size(&mut child, first_append.len()), first_append); + + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .with_current_output() + .stdout_only(first_append); let second_append = "twenty\nthirty\n"; - let expected = at.read("foobar_follow_multiple_appended.expected"); at.append(FOOBAR_TXT, second_append); - assert_eq!(read_size(&mut child, expected.len()), expected); - child.kill().unwrap(); + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .with_current_output() + .stdout_only_fixture("foobar_follow_multiple_appended.expected"); + + child.make_assertion().is_alive(); + child.kill(); } #[test] @@ -654,8 +689,6 @@ fn test_follow_invalid_pid() { ))] // FIXME: for currently not working platforms fn test_follow_with_pid() { use std::process::{Command, Stdio}; - use std::thread::sleep; - use std::time::Duration; let (at, mut ucmd) = at_and_ucmd!(); @@ -678,37 +711,45 @@ fn test_follow_with_pid() { .arg(FOOBAR_2_TXT) .run_no_wait(); - let expected = at.read("foobar_follow_multiple.expected"); - assert_eq!(read_size(&mut child, expected.len()), expected); + child + .make_assertion_with_delay(100) + .is_alive() + .with_current_output() + .stdout_only_fixture("foobar_follow_multiple.expected"); let first_append = "trois\n"; at.append(FOOBAR_2_TXT, first_append); - assert_eq!(read_size(&mut child, first_append.len()), first_append); + + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .with_current_output() + .stdout_only(first_append); let second_append = "twenty\nthirty\n"; - let expected = at.read("foobar_follow_multiple_appended.expected"); at.append(FOOBAR_TXT, second_append); - assert_eq!(read_size(&mut child, expected.len()), expected); + + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .is_alive() + .with_current_output() + .stdout_only_fixture("foobar_follow_multiple_appended.expected"); // kill the dummy process and give tail time to notice this dummy.kill().unwrap(); let _ = dummy.wait(); - sleep(Duration::from_secs(1)); + + child.delay(DEFAULT_SLEEP_INTERVAL_MILLIS); let third_append = "should\nbe\nignored\n"; at.append(FOOBAR_TXT, third_append); - let mut buffer: [u8; 1] = [0; 1]; - let result = child.stdout.as_mut().unwrap().read(&mut buffer); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), 0); - // On Unix, trying to kill a process that's already dead is fine; on Windows it's an error. - let result = child.kill(); - if cfg!(windows) { - assert!(result.is_err()); - } else { - assert!(result.is_ok()); - } + child + .make_assertion_with_delay(DEFAULT_SLEEP_INTERVAL_MILLIS) + .is_not_alive() + .with_current_output() + .no_stderr() + .no_stdout() + .success(); } #[test] @@ -1243,19 +1284,20 @@ fn test_retry3() { let mut args = vec!["--follow=name", "--retry", missing, "--use-polling"]; for _ in 0..2 { let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.touch(missing); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.truncate(missing, "X\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(expected_stderr) + .stdout_is(expected_stdout); at.remove(missing); args.pop(); @@ -1295,22 +1337,24 @@ fn test_retry4() { let mut delay = 1500; for _ in 0..2 { let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.touch(missing); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.truncate(missing, "X1\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.truncate(missing, "X\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(expected_stderr) + .stdout_is(expected_stdout); at.remove(missing); args.pop(); @@ -1342,16 +1386,18 @@ fn test_retry5() { let mut args = vec!["--follow=descriptor", "--retry", missing, "--use-polling"]; for _ in 0..2 { let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.mkdir(missing); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert!(buf_stdout.is_empty()); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_not_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_only(expected_stderr) + .failure(); at.rmdir(missing); args.pop(); @@ -1384,17 +1430,20 @@ fn test_retry6() { .run_no_wait(); let delay = 1000; - sleep(Duration::from_millis(delay)); + p.make_assertion_with_delay(delay).is_alive(); + at.truncate(missing, "Y\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); + at.truncate(existing, "X\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stdout_is(expected_stdout) + .stderr_is(expected_stderr); } #[test] @@ -1433,35 +1482,37 @@ fn test_retry7() { at.mkdir(untailable); let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); // tail: 'untailable' has become accessible // or (The first is the common case, "has appeared" arises with slow rmdir): // tail: 'untailable' has appeared; following new file at.rmdir(untailable); at.truncate(untailable, "foo\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); // NOTE: GNU's `tail` only shows "become inaccessible" // if there's a delay between rm and mkdir. // tail: 'untailable' has become inaccessible: No such file or directory at.remove(untailable); - sleep(Duration::from_millis(delay)); + p.delay(delay); // tail: 'untailable' has been replaced with an untailable file\n"; at.mkdir(untailable); - sleep(Duration::from_millis(delay)); + p.delay(delay); // full circle, back to the beginning at.rmdir(untailable); at.truncate(untailable, "bar\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(expected_stderr) + .stdout_is(expected_stdout); args.pop(); at.remove(untailable); @@ -1508,7 +1559,8 @@ fn test_retry8() { .arg("--max-unchanged-stats=1") .arg(user_path) .run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); // 'parent_dir/watched_file' is orphan // tail: cannot open 'parent_dir/watched_file' for reading: No such file or directory\n\ @@ -1516,24 +1568,25 @@ fn test_retry8() { // tail: 'parent_dir/watched_file' has appeared; following new file\n\ at.mkdir(parent_dir); // not an orphan anymore at.append(user_path, "foo\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); // tail: 'parent_dir/watched_file' has become inaccessible: No such file or directory\n\ at.remove(user_path); at.rmdir(parent_dir); // 'parent_dir/watched_file' is orphan *again* - sleep(Duration::from_millis(delay)); + p.delay(delay); // Since 'parent_dir/watched_file' is orphan, this needs to be picked up by polling // tail: 'parent_dir/watched_file' has appeared; following new file\n"; at.mkdir(parent_dir); // not an orphan anymore at.append(user_path, "bar\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(expected_stderr) + .stdout_is(expected_stdout); } #[test] @@ -1584,37 +1637,38 @@ fn test_retry9() { .arg(user_path) .run_no_wait(); - sleep(Duration::from_millis(delay)); + p.make_assertion_with_delay(delay).is_alive(); at.remove(user_path); at.rmdir(parent_dir); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.mkdir(parent_dir); at.truncate(user_path, "bar\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.remove(user_path); at.rmdir(parent_dir); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.mkdir(parent_dir); at.truncate(user_path, "foo\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.remove(user_path); at.rmdir(parent_dir); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.mkdir(parent_dir); at.truncate(user_path, "bar\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(expected_stderr) + .stdout_is(expected_stdout); } #[test] @@ -1650,28 +1704,29 @@ fn test_follow_descriptor_vs_rename1() { at.touch(file_a); let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.append(file_a, "A\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.rename(file_a, file_b); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.append(file_b, "B\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.rename(file_b, file_c); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.append(file_c, "C\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, "A\nB\nC\n"); - assert!(buf_stderr.is_empty()); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stdout_only("A\nB\nC\n"); args.pop(); delay /= 3; @@ -1709,22 +1764,20 @@ fn test_follow_descriptor_vs_rename2() { at.touch(file_a); at.touch(file_b); let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.rename(file_a, file_c); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.append(file_c, "X\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!( - buf_stdout, - "==> FILE_A <==\n\n==> FILE_B <==\n\n==> FILE_A <==\nX\n" - ); - assert!(buf_stderr.is_empty()); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stdout_only("==> FILE_A <==\n\n==> FILE_B <==\n\n==> FILE_A <==\nX\n"); args.pop(); delay /= 3; @@ -1773,22 +1826,27 @@ fn test_follow_name_retry_headers() { let mut delay = 1500; for _ in 0..2 { let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); - at.truncate(file_a, "x\n"); - sleep(Duration::from_millis(delay)); - at.truncate(file_b, "y\n"); - sleep(Duration::from_millis(delay)); - p.kill().unwrap(); - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, "\n==> a <==\nx\n\n==> b <==\ny\n"); - assert_eq!( - buf_stderr, - "tail: cannot open 'a' for reading: No such file or directory\n\ + p.make_assertion_with_delay(delay).is_alive(); + + at.truncate(file_a, "x\n"); + p.delay(delay); + + at.truncate(file_b, "y\n"); + p.delay(delay); + + let expected_stderr = "tail: cannot open 'a' for reading: No such file or directory\n\ tail: cannot open 'b' for reading: No such file or directory\n\ tail: 'a' has appeared; following new file\n\ - tail: 'b' has appeared; following new file\n" - ); + tail: 'b' has appeared; following new file\n"; + let expected_stdout = "\n==> a <==\nx\n\n==> b <==\ny\n"; + + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stdout_is(expected_stdout) + .stderr_is(expected_stderr); at.remove(file_a); at.remove(file_b); @@ -1830,16 +1888,28 @@ fn test_follow_name_remove() { at.copy(source, source_copy); let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.remove(source_copy); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr[i]); + if i == 0 { + p.make_assertion().is_not_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stdout_is(&expected_stdout) + .stderr_is(&expected_stderr[i]) + .failure(); + } else { + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stdout_is(&expected_stdout) + .stderr_is(&expected_stderr[i]); + } args.pop(); delay /= 3; @@ -1867,21 +1937,24 @@ fn test_follow_name_truncate1() { let args = ["--follow=name", source]; let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - let delay = 1000; + p.make_assertion().is_alive(); at.copy(source, backup); - sleep(Duration::from_millis(delay)); + p.delay(delay); + at.touch(source); // trigger truncate - sleep(Duration::from_millis(delay)); + p.delay(delay); + at.copy(backup, source); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(expected_stderr) + .stdout_is(expected_stdout); } #[test] @@ -1908,21 +1981,27 @@ fn test_follow_name_truncate2() { let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); let delay = 1000; + p.make_assertion().is_alive(); at.append(source, "x\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); + at.append(source, "x\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); + at.append(source, "x\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); + at.truncate(source, "x\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); + p.make_assertion().is_alive(); - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(expected_stderr) + .stdout_is(expected_stdout); } #[test] @@ -1945,15 +2024,16 @@ fn test_follow_name_truncate3() { let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); let delay = 1000; - sleep(Duration::from_millis(delay)); + p.make_assertion_with_delay(delay).is_alive(); + at.truncate(source, "x\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert!(buf_stderr.is_empty()); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stdout_only(expected_stdout); } #[test] @@ -1967,23 +2047,26 @@ fn test_follow_name_truncate4() { let mut args = vec!["-s.1", "--max-unchanged-stats=1", "-F", "file"]; let mut delay = 500; - for _ in 0..2 { + for i in 0..2 { at.append("file", "foobar\n"); let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.truncate("file", "foobar\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert!(buf_stderr.is_empty()); - assert_eq!(buf_stdout, "foobar\n"); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stdout_only("foobar\n"); at.remove("file"); - args.push("---disable-inotify"); + if i == 0 { + args.push("---disable-inotify"); + } delay *= 3; } } @@ -2017,19 +2100,17 @@ fn test_follow_truncate_fast() { at.truncate("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n"); let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + p.make_assertion_with_delay(delay).is_alive(); at.truncate("f", "11\n12\n13\n14\n15\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!( - buf_stdout, - "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n" - ); - assert_eq!(buf_stderr, "tail: f: file truncated\n"); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is("tail: f: file truncated\n") + .stdout_is("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n"); args.pop(); } @@ -2076,19 +2157,21 @@ fn test_follow_name_move_create1() { let args = ["--follow=name", source]; let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.rename(source, backup); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.copy(backup, source); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(expected_stderr) + .stdout_is(expected_stdout); } #[test] @@ -2125,27 +2208,19 @@ fn test_follow_name_move_create2() { ]; let mut delay = 500; - for _ in 0..2 { + for i in 0..2 { let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.truncate("9", "x\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.rename("1", "f"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.truncate("1", "a\n"); - sleep(Duration::from_millis(delay)); - - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!( - buf_stderr, - "tail: '1' has become inaccessible: No such file or directory\n\ - tail: '1' has appeared; following new file\n" - ); + p.delay(delay); // NOTE: Because "gnu/tests/tail-2/inotify-hash-abuse.sh" 'forgets' to clear the files used // during the first loop iteration, we also don't clear them to get the same side-effects. @@ -2154,14 +2229,25 @@ fn test_follow_name_move_create2() { // at.touch("1"); // at.remove("9"); // at.touch("9"); - if args.len() == 14 { - assert_eq!(buf_stdout, "a\nx\na\n"); + let expected_stdout = if args.len() == 14 { + "a\nx\na\n" } else { - assert_eq!(buf_stdout, "x\na\n"); - } + "x\na\n" + }; + let expected_stderr = "tail: '1' has become inaccessible: No such file or directory\n\ + tail: '1' has appeared; following new file\n"; + + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(expected_stderr) + .stdout_is(expected_stdout); at.remove("f"); - args.push("---disable-inotify"); + if i == 0 { + args.push("---disable-inotify"); + } delay *= 3; } } @@ -2199,16 +2285,28 @@ fn test_follow_name_move1() { #[allow(clippy::needless_range_loop)] for i in 0..2 { let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.rename(source, backup); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr[i]); + if i == 0 { + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(&expected_stderr[i]) + .stdout_is(&expected_stdout); + } else { + p.make_assertion().is_not_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(&expected_stderr[i]) + .stdout_is(&expected_stdout) + .failure(); + } at.rename(backup, source); args.push("--use-polling"); @@ -2270,23 +2368,23 @@ fn test_follow_name_move2() { at.truncate(file2, "file2_content\n"); let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + p.make_assertion_with_delay(delay).is_alive(); at.rename(file1, file2); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.append(file2, "more_file2_content\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.append(file1, "more_file1_content\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - println!("out:\n{}\nerr:\n{}", buf_stdout, buf_stderr); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(&expected_stderr) + .stdout_is(&expected_stdout); if i == 0 { args.push("--use-polling"); @@ -2334,26 +2432,28 @@ fn test_follow_name_move_retry1() { for _ in 0..2 { at.touch(source); let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + + p.make_assertion_with_delay(delay).is_alive(); at.append(source, "tailed\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); // with --follow=name, tail should stop monitoring the renamed file at.rename(source, backup); - sleep(Duration::from_millis(delay)); + p.delay(delay); // overwrite backup while it's not monitored at.truncate(backup, "new content\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); // move back, tail should pick this up and print new content at.rename(backup, source); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(&expected_stderr) + .stdout_is(expected_stdout); at.remove(source); args.pop(); @@ -2425,28 +2525,29 @@ fn test_follow_name_move_retry2() { at.touch(file2); let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait(); - sleep(Duration::from_millis(delay)); + p.make_assertion_with_delay(delay).is_alive(); at.truncate(file1, "x\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.rename(file1, file2); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.truncate(file1, "x2\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.append(file2, "y\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); at.append(file1, "z\n"); - sleep(Duration::from_millis(delay)); + p.delay(delay); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, expected_stdout); - assert_eq!(buf_stderr, expected_stderr); + p.make_assertion().is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(&expected_stderr) + .stdout_is(&expected_stdout); at.remove(file1); at.remove(file2); @@ -2481,23 +2582,13 @@ fn test_follow_inotify_only_regular() { .arg("-f") .arg("/dev/null") .run_no_wait(); - sleep(Duration::from_millis(200)); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert_eq!(buf_stdout, String::new()); - assert_eq!(buf_stderr, String::new()); -} - -fn take_stdout_stderr(p: &mut std::process::Child) -> (String, String) { - let mut buf_stdout = String::new(); - let mut p_stdout = p.stdout.take().unwrap(); - p_stdout.read_to_string(&mut buf_stdout).unwrap(); - let mut buf_stderr = String::new(); - let mut p_stderr = p.stderr.take().unwrap(); - p_stderr.read_to_string(&mut buf_stderr).unwrap(); - (buf_stdout, buf_stderr) + p.make_assertion_with_delay(200).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .no_stderr() + .no_stdout(); } #[test] @@ -2548,20 +2639,21 @@ fn test_fifo() { at.mkfifo("FIFO"); let mut p = ts.ucmd().arg("FIFO").run_no_wait(); - sleep(Duration::from_millis(500)); - p.kill().unwrap(); - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert!(buf_stdout.is_empty()); - assert!(buf_stderr.is_empty()); + p.make_assertion_with_delay(500).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .no_stderr() + .no_stdout(); for arg in ["-f", "-F"] { let mut p = ts.ucmd().arg(arg).arg("FIFO").run_no_wait(); - sleep(Duration::from_millis(500)); - p.kill().unwrap(); - - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - assert!(buf_stdout.is_empty()); - assert!(buf_stderr.is_empty()); + p.make_assertion_with_delay(500).is_alive(); + p.kill() + .make_assertion() + .with_all_output() + .no_stderr() + .no_stdout(); } } @@ -2579,20 +2671,20 @@ fn test_illegal_seek() { at.mkfifo("FIFO"); let mut p = ts.ucmd().arg("FILE").run_no_wait(); - sleep(Duration::from_millis(500)); - at.rename("FILE", "FIFO"); - sleep(Duration::from_millis(500)); + p.make_assertion_with_delay(500).is_alive(); - p.kill().unwrap(); - let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p); - dbg!(&buf_stdout, &buf_stderr); - assert_eq!(buf_stdout, "foo\n"); - assert_eq!( - buf_stderr, - "tail: 'FILE' has been replaced; following new file\n\ - tail: FILE: cannot seek to offset 0: Illegal seek\n" - ); - assert_eq!(p.wait().unwrap().code().unwrap(), 1); + at.rename("FILE", "FIFO"); + p.delay(500); + + p.make_assertion().is_alive(); + let expected_stderr = "tail: 'FILE' has been replaced; following new file\n\ + tail: FILE: cannot seek to offset 0: Illegal seek\n"; + p.kill() + .make_assertion() + .with_all_output() + .stderr_is(expected_stderr) + .stdout_is("foo\n") + .code_is(1); // is this correct? after kill the code is not meaningful. } #[test] @@ -2913,7 +3005,8 @@ fn test_pipe_when_lines_option_given_input_size_is_one_byte_greater_than_buffer_ // FIXME: windows: this test failed with timeout in the CI. Running this test in // a Windows VirtualBox image produces no errors. #[test] -#[cfg(not(target_os = "windows"))] +// TODO: switch back on +// #[cfg(not(target_os = "windows"))] fn test_pipe_when_lines_option_given_input_size_has_multiple_size_of_buffer_size() { let total_lines = 100; let random_string = RandomString::generate_with_delimiter( @@ -3220,7 +3313,8 @@ fn test_pipe_when_bytes_option_given_input_size_is_one_byte_greater_than_buffer_ // FIXME: windows: this test failed with timeout in the CI. Running this test in // a Windows VirtualBox image produces no errors. #[test] -#[cfg(not(target_os = "windows"))] +// TODO: switch back on +// #[cfg(not(target_os = "windows"))] fn test_pipe_when_bytes_option_given_input_size_has_multiple_size_of_buffer_size() { let random_string = RandomString::generate(AlphanumericNewline, CHUNK_BUFFER_SIZE * 3); let random_string = random_string.as_str(); diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index d37f1a3ba..f6a562dce 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -111,9 +111,7 @@ 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; @@ -133,22 +131,9 @@ mod linux_only { fn run_tee(proc: &mut UCommand) -> (String, Output) { let content = (1..=100000).map(|x| format!("{}\n", x)).collect::(); - - 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(); + let output = proc + .run_no_wait() + .pipe_in_and_wait_with_output(content.as_bytes()); (content, output) } diff --git a/tests/by-util/test_tty.rs b/tests/by-util/test_tty.rs index 09340d39c..726167673 100644 --- a/tests/by-util/test_tty.rs +++ b/tests/by-util/test_tty.rs @@ -26,37 +26,29 @@ fn test_dev_null_silent() { #[test] fn test_close_stdin() { let mut child = new_ucmd!().run_no_wait(); - drop(child.stdin.take()); - let output = child.wait_with_output().unwrap(); - assert_eq!(output.status.code(), Some(1)); - assert_eq!(std::str::from_utf8(&output.stdout), Ok("not a tty\n")); + child.close_stdin(); + child.wait().unwrap().code_is(1).stdout_is("not a tty\n"); } #[test] fn test_close_stdin_silent() { let mut child = new_ucmd!().arg("-s").run_no_wait(); - drop(child.stdin.take()); - let output = child.wait_with_output().unwrap(); - assert_eq!(output.status.code(), Some(1)); - assert!(output.stdout.is_empty()); + child.close_stdin(); + child.wait().unwrap().code_is(1).no_stdout(); } #[test] fn test_close_stdin_silent_long() { let mut child = new_ucmd!().arg("--silent").run_no_wait(); - drop(child.stdin.take()); - let output = child.wait_with_output().unwrap(); - assert_eq!(output.status.code(), Some(1)); - assert!(output.stdout.is_empty()); + child.close_stdin(); + child.wait().unwrap().code_is(1).no_stdout(); } #[test] fn test_close_stdin_silent_alias() { let mut child = new_ucmd!().arg("--quiet").run_no_wait(); - drop(child.stdin.take()); - let output = child.wait_with_output().unwrap(); - assert_eq!(output.status.code(), Some(1)); - assert!(output.stdout.is_empty()); + child.close_stdin(); + child.wait().unwrap().code_is(1).no_stdout(); } #[test] diff --git a/tests/by-util/test_yes.rs b/tests/by-util/test_yes.rs index 545ac2236..57303e6f8 100644 --- a/tests/by-util/test_yes.rs +++ b/tests/by-util/test_yes.rs @@ -1,5 +1,4 @@ -use std::io::Read; -use std::process::ExitStatus; +use std::process::{ExitStatus, Stdio}; #[cfg(unix)] use std::os::unix::process::ExitStatusExt; @@ -19,12 +18,10 @@ fn check_termination(result: &ExitStatus) { /// Run `yes`, capture some of the output, close the pipe, and verify it. fn run(args: &[&str], expected: &[u8]) { let mut cmd = new_ucmd!(); - let mut child = cmd.args(args).run_no_wait(); - let mut stdout = child.stdout.take().unwrap(); - let mut buf = vec![0; expected.len()]; - stdout.read_exact(&mut buf).unwrap(); - drop(stdout); - check_termination(&child.wait().unwrap()); + let mut child = cmd.args(args).set_stdout(Stdio::piped()).run_no_wait(); + let buf = child.stdout_exact_bytes(expected.len()); + child.close_stdout(); + check_termination(&child.wait_with_output().unwrap().status); assert_eq!(buf.as_slice(), expected); } diff --git a/tests/common/util.rs b/tests/common/util.rs index deb9105a6..da43a134e 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -1118,28 +1118,15 @@ impl UCommand { self } - // TODO: Accept a parameter `delay` which returns delayed from this method. Most use cases are - // with some kind of post delay. Without any delay, the output may be empty because we return - // immediately. Most of the time a delay of 1ms was already sufficient. - // TODO: rename this method after refactoring the tests to run_no_wait and merge with it - pub fn run_no_wait_child(&mut self) -> UChild { - let child = self.run_no_wait(); - UChild::new( - child, - self.bin_path.clone(), - self.util_name.clone(), - self.tmpd.clone(), - self.captured_stdout.take(), - self.captured_stderr.take(), - self.ignore_stdin_write_error, - ) - } + // TODO: Add convenience method run_no_wait_with_delay which accept a parameter `delay` which + // returns delayed from run_no_wait. A lot of use cases are with some kind of post delay. + // Without any delay, the output may be empty because we return immediately. /// Spawns the command, feeds the stdin if any, and returns the /// child process immediately. Do not use this method directly /// if you want to have stderr redirected to stdout. Use /// [`UCommand::run_no_wait_stderr_to_stdout`] instead. - pub fn run_no_wait(&mut self) -> Child { + pub fn run_no_wait(&mut self) -> UChild { assert!(!self.has_run, "{}", ALREADY_RUN); self.has_run = true; log_info("run", &self.comm_string); @@ -1204,7 +1191,15 @@ impl UCommand { } } - child + UChild::new( + child, + self.bin_path.clone(), + self.util_name.clone(), + self.tmpd.clone(), + self.captured_stdout.take(), + self.captured_stderr.take(), + self.ignore_stdin_write_error, + ) } /// Spawns the command, feeds the stdin if any, waits for the result @@ -1212,8 +1207,8 @@ impl UCommand { /// It is recommended that you instead use succeeds() or fails() pub fn run(&mut self) -> CmdResult { match self.bytes_into_stdin.take() { - Some(input) => self.run_no_wait_child().pipe_in_and_wait(input), - None => self.run_no_wait_child().wait().unwrap(), + Some(input) => self.run_no_wait().pipe_in_and_wait(input), + None => self.run_no_wait().wait().unwrap(), } } @@ -1223,7 +1218,7 @@ impl UCommand { /// with succeeds() or fails() pub fn run_piped_stdin>>(&mut self, input: T) -> CmdResult { self.bytes_into_stdin = None; - self.run_no_wait_child().pipe_in_and_wait(input) + self.run_no_wait().pipe_in_and_wait(input) } /// Spawns the command, feeds the stdin if any, waits for the result,