mirror of
https://github.com/uutils/coreutils
synced 2024-07-22 02:14:38 +00:00
tail: reduce CPU load for polling (#3618)
* tail: reduce CPU load for polling This reduces the CPU load for polling drastically (from ~80% down to ~5%) by removing/fixing several previous workarounds related to polling, while still passing all related GNU test-suite checks. * set Notify::PollWatcher delay to: sleep_sec/10 instead of sleep_sec/100 * set recv_timeout to sleep_sec instead of sleep_sec/100 * remove the manual polling of watched files Bugs: * fix an issue with headers to consistently pass "test_follow_name_retry_headers" and "gnu/tests/tail-2/overlay-headers.sh" Code clean-up and refactor * make fields of struct FileHandling private (and add getters/setters) to ensure that the paths are absolute and match the paths returned by Notify::Events * replace calls to "crash!" with "return USimpleError" * clean-up formatting
This commit is contained in:
parent
c277e933c9
commit
75edeea5e4
|
@ -18,7 +18,7 @@ path = "src/tail.rs"
|
|||
[dependencies]
|
||||
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
||||
libc = "0.2.126"
|
||||
notify = { version = "5.0.0-pre.15", features=["macos_kqueue"]}
|
||||
notify = { version = "=5.0.0-pre.15", features=["macos_kqueue"]}
|
||||
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["ringbuffer", "lines"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
## Missing features
|
||||
|
||||
* `--max-unchanged-stats`
|
||||
* check whether process p is alive at least every number of seconds (relevant for `--pid`)
|
||||
|
||||
Note:
|
||||
There's a stub for `--max-unchanged-stats` so GNU test-suite checks using it can run, however this flag has no functionality yet.
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -89,6 +89,7 @@ fn test_stdin_redirect_file() {
|
|||
.arg("-f")
|
||||
.set_stdin(std::fs::File::open(at.plus("f")).unwrap())
|
||||
.run_no_wait();
|
||||
|
||||
sleep(Duration::from_millis(500));
|
||||
p.kill().unwrap();
|
||||
|
||||
|
@ -249,6 +250,7 @@ fn test_follow_stdin_descriptor() {
|
|||
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);
|
||||
|
@ -292,6 +294,7 @@ fn test_follow_stdin_explicit_indefinitely() {
|
|||
.set_stdin(Stdio::null())
|
||||
.args(&["-f", "-", "/dev/null"])
|
||||
.run_no_wait();
|
||||
|
||||
sleep(Duration::from_millis(500));
|
||||
p.kill().unwrap();
|
||||
|
||||
|
@ -379,6 +382,7 @@ fn test_null_default() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_follow_single() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
||||
|
@ -402,6 +406,7 @@ fn test_follow_single() {
|
|||
|
||||
/// Test for following when bytes are written that are not valid UTF-8.
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_follow_non_utf8_bytes() {
|
||||
// Tail the test file and start following it.
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
@ -433,6 +438,7 @@ fn test_follow_non_utf8_bytes() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_follow_multiple() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let mut child = ucmd
|
||||
|
@ -498,10 +504,10 @@ fn test_follow_multiple_untailable() {
|
|||
|
||||
let expected_stdout = "==> DIR1 <==\n\n==> DIR2 <==\n";
|
||||
let expected_stderr = "tail: error reading 'DIR1': Is a directory\n\
|
||||
tail: DIR1: cannot follow end of this type of file; giving up on this name\n\
|
||||
tail: error reading 'DIR2': Is a directory\n\
|
||||
tail: DIR2: cannot follow end of this type of file; giving up on this name\n\
|
||||
tail: no files remaining\n";
|
||||
tail: DIR1: cannot follow end of this type of file; giving up on this name\n\
|
||||
tail: error reading 'DIR2': Is a directory\n\
|
||||
tail: DIR2: cannot follow end of this type of file; giving up on this name\n\
|
||||
tail: no files remaining\n";
|
||||
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.mkdir("DIR1");
|
||||
|
@ -527,6 +533,30 @@ fn test_follow_stdin_pipe() {
|
|||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_follow_invalid_pid() {
|
||||
new_ucmd!()
|
||||
.args(&["-f", "--pid=-1234"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_is("tail: invalid PID: '-1234'\n");
|
||||
new_ucmd!()
|
||||
.args(&["-f", "--pid=abc"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_is("tail: invalid PID: 'abc': invalid digit found in string\n");
|
||||
let max_pid = (i32::MAX as i64 + 1).to_string();
|
||||
new_ucmd!()
|
||||
.args(&["-f", "--pid", &max_pid])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_is(format!(
|
||||
"tail: invalid PID: '{}': number too large to fit in target type\n",
|
||||
max_pid
|
||||
));
|
||||
}
|
||||
|
||||
// FixME: test PASSES for usual windows builds, but fails for coverage testing builds (likely related to the specific RUSTFLAGS '-Zpanic_abort_tests -Cpanic=abort') This test also breaks tty settings under bash requiring a 'stty sane' or reset. // spell-checker:disable-line
|
||||
#[cfg(disable_until_fixed)]
|
||||
#[test]
|
||||
|
@ -721,7 +751,7 @@ fn test_multiple_input_files_missing() {
|
|||
.stdout_is_fixture("foobar_follow_multiple.expected")
|
||||
.stderr_is(
|
||||
"tail: cannot open 'missing1' for reading: No such file or directory\n\
|
||||
tail: cannot open 'missing2' for reading: No such file or directory",
|
||||
tail: cannot open 'missing2' for reading: No such file or directory",
|
||||
)
|
||||
.code_is(1);
|
||||
}
|
||||
|
@ -740,7 +770,7 @@ fn test_follow_missing() {
|
|||
.no_stdout()
|
||||
.stderr_is(
|
||||
"tail: cannot open 'missing' for reading: No such file or directory\n\
|
||||
tail: no files remaining",
|
||||
tail: no files remaining",
|
||||
)
|
||||
.code_is(1);
|
||||
}
|
||||
|
@ -813,8 +843,8 @@ fn test_dir_follow() {
|
|||
.no_stdout()
|
||||
.stderr_is(
|
||||
"tail: error reading 'DIR': Is a directory\n\
|
||||
tail: DIR: cannot follow end of this type of file; giving up on this name\n\
|
||||
tail: no files remaining\n",
|
||||
tail: DIR: cannot follow end of this type of file; giving up on this name\n\
|
||||
tail: no files remaining\n",
|
||||
)
|
||||
.code_is(1);
|
||||
}
|
||||
|
@ -833,9 +863,9 @@ fn test_dir_follow_retry() {
|
|||
.run()
|
||||
.stderr_is(
|
||||
"tail: warning: --retry only effective for the initial open\n\
|
||||
tail: error reading 'DIR': Is a directory\n\
|
||||
tail: DIR: cannot follow end of this type of file\n\
|
||||
tail: no files remaining\n",
|
||||
tail: error reading 'DIR': Is a directory\n\
|
||||
tail: DIR: cannot follow end of this type of file\n\
|
||||
tail: no files remaining\n",
|
||||
)
|
||||
.code_is(1);
|
||||
}
|
||||
|
@ -1129,18 +1159,20 @@ fn test_retry3() {
|
|||
let missing = "missing";
|
||||
|
||||
let expected_stderr = "tail: cannot open 'missing' for reading: No such file or directory\n\
|
||||
tail: 'missing' has appeared; following new file\n";
|
||||
tail: 'missing' has appeared; following new file\n";
|
||||
let expected_stdout = "X\n";
|
||||
let delay = 1000;
|
||||
|
||||
let mut delay = 1500;
|
||||
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));
|
||||
|
||||
at.touch(missing);
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.truncate(missing, "X\n");
|
||||
sleep(Duration::from_millis(2 * delay));
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
p.kill().unwrap();
|
||||
|
||||
|
@ -1150,6 +1182,7 @@ fn test_retry3() {
|
|||
|
||||
at.remove(missing);
|
||||
args.pop();
|
||||
delay /= 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1165,11 +1198,10 @@ fn test_retry4() {
|
|||
let missing = "missing";
|
||||
|
||||
let expected_stderr = "tail: warning: --retry only effective for the initial open\n\
|
||||
tail: cannot open 'missing' for reading: No such file or directory\n\
|
||||
tail: 'missing' has appeared; following new file\n\
|
||||
tail: missing: file truncated\n";
|
||||
tail: cannot open 'missing' for reading: No such file or directory\n\
|
||||
tail: 'missing' has appeared; following new file\n\
|
||||
tail: missing: file truncated\n";
|
||||
let expected_stdout = "X1\nX\n";
|
||||
let delay = 1000;
|
||||
let mut args = vec![
|
||||
"-s.1",
|
||||
"--max-unchanged-stats=1",
|
||||
|
@ -1178,14 +1210,17 @@ fn test_retry4() {
|
|||
missing,
|
||||
"---disable-inotify",
|
||||
];
|
||||
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.touch(missing);
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.truncate(missing, "X1\n");
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.truncate(missing, "X\n");
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
|
@ -1197,6 +1232,7 @@ fn test_retry4() {
|
|||
|
||||
at.remove(missing);
|
||||
args.pop();
|
||||
delay /= 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1211,15 +1247,16 @@ fn test_retry5() {
|
|||
let missing = "missing";
|
||||
|
||||
let expected_stderr = "tail: warning: --retry only effective for the initial open\n\
|
||||
tail: cannot open 'missing' for reading: No such file or directory\n\
|
||||
tail: 'missing' has been replaced with an untailable file; giving up on this name\n\
|
||||
tail: no files remaining\n";
|
||||
let delay = 1000;
|
||||
tail: cannot open 'missing' for reading: No such file or directory\n\
|
||||
tail: 'missing' has been replaced with an untailable file; giving up on this name\n\
|
||||
tail: no files remaining\n";
|
||||
|
||||
let mut delay = 1500;
|
||||
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));
|
||||
|
||||
at.mkdir(missing);
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
|
@ -1231,6 +1268,7 @@ fn test_retry5() {
|
|||
|
||||
at.rmdir(missing);
|
||||
args.pop();
|
||||
delay /= 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1283,15 +1321,13 @@ fn test_retry7() {
|
|||
let untailable = "untailable";
|
||||
|
||||
let expected_stderr = "tail: error reading 'untailable': Is a directory\n\
|
||||
tail: untailable: cannot follow end of this type of file\n\
|
||||
tail: 'untailable' has become accessible\n\
|
||||
tail: 'untailable' has become inaccessible: No such file or directory\n\
|
||||
tail: 'untailable' has been replaced with an untailable file\n\
|
||||
tail: 'untailable' has become accessible\n";
|
||||
tail: untailable: cannot follow end of this type of file\n\
|
||||
tail: 'untailable' has become accessible\n\
|
||||
tail: 'untailable' has become inaccessible: No such file or directory\n\
|
||||
tail: 'untailable' has been replaced with an untailable file\n\
|
||||
tail: 'untailable' has become accessible\n";
|
||||
let expected_stdout = "foo\nbar\n";
|
||||
|
||||
let delay = 1000;
|
||||
|
||||
let mut args = vec![
|
||||
"-s.1",
|
||||
"--max-unchanged-stats=1",
|
||||
|
@ -1299,8 +1335,11 @@ fn test_retry7() {
|
|||
untailable,
|
||||
"--use-polling",
|
||||
];
|
||||
|
||||
let mut delay = 1500;
|
||||
for _ in 0..2 {
|
||||
at.mkdir(untailable);
|
||||
|
||||
let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
|
@ -1334,7 +1373,7 @@ fn test_retry7() {
|
|||
|
||||
args.pop();
|
||||
at.remove(untailable);
|
||||
sleep(Duration::from_millis(delay));
|
||||
delay /= 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1417,14 +1456,14 @@ fn test_retry9() {
|
|||
|
||||
let expected_stderr = format!(
|
||||
"\
|
||||
tail: 'parent_dir/watched_file' has become inaccessible: No such file or directory\n\
|
||||
tail: directory containing watched file was removed\n\
|
||||
tail: {} cannot be used, reverting to polling\n\
|
||||
tail: 'parent_dir/watched_file' has appeared; following new file\n\
|
||||
tail: 'parent_dir/watched_file' has become inaccessible: No such file or directory\n\
|
||||
tail: 'parent_dir/watched_file' has appeared; following new file\n\
|
||||
tail: 'parent_dir/watched_file' has become inaccessible: No such file or directory\n\
|
||||
tail: 'parent_dir/watched_file' has appeared; following new file\n",
|
||||
tail: 'parent_dir/watched_file' has become inaccessible: No such file or directory\n\
|
||||
tail: directory containing watched file was removed\n\
|
||||
tail: {} cannot be used, reverting to polling\n\
|
||||
tail: 'parent_dir/watched_file' has appeared; following new file\n\
|
||||
tail: 'parent_dir/watched_file' has become inaccessible: No such file or directory\n\
|
||||
tail: 'parent_dir/watched_file' has appeared; following new file\n\
|
||||
tail: 'parent_dir/watched_file' has become inaccessible: No such file or directory\n\
|
||||
tail: 'parent_dir/watched_file' has appeared; following new file\n",
|
||||
BACKEND
|
||||
);
|
||||
let expected_stdout = "foo\nbar\nfoo\nbar\n";
|
||||
|
@ -1469,7 +1508,6 @@ fn test_retry9() {
|
|||
sleep(Duration::from_millis(delay));
|
||||
|
||||
p.kill().unwrap();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p);
|
||||
assert_eq!(buf_stdout, expected_stdout);
|
||||
|
@ -1500,7 +1538,7 @@ fn test_follow_descriptor_vs_rename1() {
|
|||
"---disable-inotify",
|
||||
];
|
||||
|
||||
let delay = 500;
|
||||
let mut delay = 1500;
|
||||
for _ in 0..2 {
|
||||
at.touch(file_a);
|
||||
|
||||
|
@ -1523,13 +1561,13 @@ fn test_follow_descriptor_vs_rename1() {
|
|||
sleep(Duration::from_millis(delay));
|
||||
|
||||
p.kill().unwrap();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p);
|
||||
assert_eq!(buf_stdout, "A\nB\nC\n");
|
||||
assert!(buf_stderr.is_empty());
|
||||
|
||||
args.pop();
|
||||
delay /= 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1555,18 +1593,20 @@ fn test_follow_descriptor_vs_rename2() {
|
|||
"---disable-inotify",
|
||||
];
|
||||
|
||||
let delay = 100;
|
||||
let mut delay = 1500;
|
||||
for _ in 0..2 {
|
||||
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));
|
||||
|
||||
at.rename(file_a, file_c);
|
||||
sleep(Duration::from_millis(1000));
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.append(file_c, "X\n");
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
p.kill().unwrap();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p);
|
||||
assert_eq!(
|
||||
|
@ -1576,6 +1616,68 @@ fn test_follow_descriptor_vs_rename2() {
|
|||
assert!(buf_stderr.is_empty());
|
||||
|
||||
args.pop();
|
||||
delay /= 3;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_follow_name_retry_headers() {
|
||||
// inspired by: "gnu/tests/tail-2/F-headers.sh"
|
||||
// Ensure tail -F distinguishes output with the
|
||||
// correct headers for created/renamed files
|
||||
|
||||
/*
|
||||
$ tail --follow=descriptor -s.1 --max-unchanged-stats=1 -F a b
|
||||
tail: cannot open 'a' for reading: No such file or directory
|
||||
tail: cannot open 'b' for reading: No such file or directory
|
||||
tail: 'a' has appeared; following new file
|
||||
==> a <==
|
||||
x
|
||||
tail: 'b' has appeared; following new file
|
||||
|
||||
==> b <==
|
||||
y
|
||||
*/
|
||||
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
let file_a = "a";
|
||||
let file_b = "b";
|
||||
|
||||
let mut args = vec![
|
||||
"-F",
|
||||
"-s.1",
|
||||
"--max-unchanged-stats=1",
|
||||
file_a,
|
||||
file_b,
|
||||
"---disable-inotify",
|
||||
];
|
||||
|
||||
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\
|
||||
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"
|
||||
);
|
||||
|
||||
at.remove(file_a);
|
||||
at.remove(file_b);
|
||||
args.pop();
|
||||
delay /= 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1604,15 +1706,16 @@ fn test_follow_name_remove() {
|
|||
),
|
||||
];
|
||||
|
||||
let delay = 2000;
|
||||
let mut args = vec!["--follow=name", source_copy, "--use-polling"];
|
||||
|
||||
let mut delay = 1500;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..2 {
|
||||
at.copy(source, source_copy);
|
||||
let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait();
|
||||
|
||||
let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.remove(source_copy);
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
|
@ -1623,6 +1726,7 @@ fn test_follow_name_remove() {
|
|||
assert_eq!(buf_stderr, expected_stderr[i]);
|
||||
|
||||
args.pop();
|
||||
delay /= 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1661,7 +1765,7 @@ fn test_follow_name_truncate1() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
|
||||
fn test_follow_name_truncate2() {
|
||||
// This test triggers a truncate event while `tail --follow=name file` is running.
|
||||
// $ ((sleep 1 && echo -n "x\nx\nx\n" >> file && sleep 1 && \
|
||||
|
@ -1738,18 +1842,17 @@ fn test_follow_name_truncate4() {
|
|||
|
||||
let mut args = vec!["-s.1", "--max-unchanged-stats=1", "-F", "file"];
|
||||
|
||||
let delay = 300;
|
||||
let mut delay = 500;
|
||||
for _ 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(100));
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.truncate("file", "foobar\n");
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
p.kill().unwrap();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p);
|
||||
assert!(buf_stderr.is_empty());
|
||||
|
@ -1757,17 +1860,24 @@ fn test_follow_name_truncate4() {
|
|||
|
||||
at.remove("file");
|
||||
args.push("---disable-inotify");
|
||||
delay *= 3;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(target_os = "android")))] // NOTE: Should work on Android but CI VM is too slow.
|
||||
#[cfg(all(unix, not(target_os = "android")))]
|
||||
fn test_follow_truncate_fast() {
|
||||
// inspired by: "gnu/tests/tail-2/truncate.sh"
|
||||
// Ensure all logs are output upon file truncation
|
||||
|
||||
// This is similar to `test_follow_name_truncate1-3` but uses very short delays
|
||||
// to better mimic the tight timings used in the "truncate.sh" test.
|
||||
// This is here to test for "speed" only, all the logic is already covered by other tests.
|
||||
|
||||
if is_ci() {
|
||||
println!("TEST SKIPPED (too fast for CI)");
|
||||
return;
|
||||
}
|
||||
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
|
@ -1775,7 +1885,7 @@ fn test_follow_truncate_fast() {
|
|||
let mut args = vec!["-s.1", "--max-unchanged-stats=1", "f", "---disable-inotify"];
|
||||
let follow = vec!["-f", "-F"];
|
||||
|
||||
let delay = 150;
|
||||
let mut delay = 1000;
|
||||
for _ in 0..2 {
|
||||
for mode in &follow {
|
||||
args.push(mode);
|
||||
|
@ -1789,7 +1899,6 @@ fn test_follow_truncate_fast() {
|
|||
sleep(Duration::from_millis(delay));
|
||||
|
||||
p.kill().unwrap();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p);
|
||||
assert_eq!(
|
||||
|
@ -1801,12 +1910,13 @@ fn test_follow_truncate_fast() {
|
|||
args.pop();
|
||||
}
|
||||
args.pop();
|
||||
delay = 250;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
|
||||
fn test_follow_name_move_create() {
|
||||
fn test_follow_name_move_create1() {
|
||||
// This test triggers a move/create event while `tail --follow=name file` is running.
|
||||
// ((sleep 2 && mv file backup && sleep 2 && cp backup file &)>/dev/null 2>&1 &) ; tail --follow=name file
|
||||
|
||||
|
@ -1830,14 +1940,15 @@ fn test_follow_name_move_create() {
|
|||
#[cfg(not(target_os = "linux"))]
|
||||
let expected_stderr = format!("{}: {}: No such file or directory\n", ts.util_name, source);
|
||||
|
||||
let delay = 500;
|
||||
let args = ["--follow=name", source];
|
||||
|
||||
let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait();
|
||||
|
||||
let delay = 2000;
|
||||
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.rename(source, backup);
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.copy(backup, source);
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
|
@ -1892,13 +2003,12 @@ fn test_follow_name_move_create2() {
|
|||
sleep(Duration::from_millis(delay));
|
||||
|
||||
p.kill().unwrap();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
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"
|
||||
tail: '1' has appeared; following new file\n"
|
||||
);
|
||||
|
||||
// NOTE: Because "gnu/tests/tail-2/inotify-hash-abuse.sh" 'forgets' to clear the files used
|
||||
|
@ -1916,15 +2026,16 @@ fn test_follow_name_move_create2() {
|
|||
|
||||
at.remove("f");
|
||||
args.push("---disable-inotify");
|
||||
delay = 2000;
|
||||
delay *= 3;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
|
||||
fn test_follow_name_move() {
|
||||
fn test_follow_name_move1() {
|
||||
// This test triggers a move event while `tail --follow=name file` is running.
|
||||
// ((sleep 2 && mv file backup &)>/dev/null 2>&1 &) ; tail --follow=name file
|
||||
// NOTE: For `---disable-inotify` tail exits with "no file remaining", it stays open w/o it.
|
||||
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
|
@ -1934,22 +2045,23 @@ fn test_follow_name_move() {
|
|||
|
||||
let expected_stdout = at.read(FOLLOW_NAME_SHORT_EXP);
|
||||
let expected_stderr = [
|
||||
format!("{}: {}: No such file or directory\n", ts.util_name, source),
|
||||
format!(
|
||||
"{}: {}: No such file or directory\n{0}: no files remaining\n",
|
||||
ts.util_name, source
|
||||
),
|
||||
format!("{}: {}: No such file or directory\n", ts.util_name, source),
|
||||
];
|
||||
|
||||
let mut args = vec!["--follow=name", source, "--use-polling"];
|
||||
let mut args = vec!["--follow=name", source];
|
||||
|
||||
let mut delay = 500;
|
||||
#[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));
|
||||
|
||||
sleep(Duration::from_millis(2000));
|
||||
at.rename(source, backup);
|
||||
sleep(Duration::from_millis(5000));
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
p.kill().unwrap();
|
||||
|
||||
|
@ -1958,14 +2070,15 @@ fn test_follow_name_move() {
|
|||
assert_eq!(buf_stderr, expected_stderr[i]);
|
||||
|
||||
at.rename(backup, source);
|
||||
args.pop();
|
||||
args.push("--use-polling");
|
||||
delay *= 3;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
|
||||
fn test_follow_name_move2() {
|
||||
// Like test_follow_name_move, but move to a name that's already monitored.
|
||||
// Like test_follow_name_move1, but move to a name that's already monitored.
|
||||
|
||||
// $ echo file1_content > file1; echo file2_content > file2; \
|
||||
// ((sleep 2 ; mv file1 file2 ; sleep 1 ; echo "more_file2_content" >> file2 ; sleep 1 ; \
|
||||
|
@ -1993,48 +2106,60 @@ fn test_follow_name_move2() {
|
|||
|
||||
let expected_stdout = format!(
|
||||
"==> {0} <==\n{0}_content\n\n==> {1} <==\n{1}_content\n{0}_content\n\
|
||||
more_{1}_content\n\n==> {0} <==\nmore_{0}_content\n",
|
||||
more_{1}_content\n\n==> {0} <==\nmore_{0}_content\n",
|
||||
file1, file2
|
||||
);
|
||||
let expected_stderr = format!(
|
||||
let mut expected_stderr = format!(
|
||||
"{0}: {1}: No such file or directory\n\
|
||||
{0}: '{2}' has been replaced; following new file\n\
|
||||
{0}: '{1}' has appeared; following new file\n",
|
||||
{0}: '{2}' has been replaced; following new file\n\
|
||||
{0}: '{1}' has appeared; following new file\n",
|
||||
ts.util_name, file1, file2
|
||||
);
|
||||
|
||||
at.append(file1, "file1_content\n");
|
||||
at.append(file2, "file2_content\n");
|
||||
let mut args = vec!["--follow=name", file1, file2];
|
||||
|
||||
// TODO: [2021-05; jhscheer] fix this for `--use-polling`
|
||||
let mut args = vec!["--follow=name", file1, file2 /*, "--use-polling" */];
|
||||
let mut delay = 500;
|
||||
for _ in 0..2 {
|
||||
at.truncate(file1, "file1_content\n");
|
||||
at.truncate(file2, "file2_content\n");
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for _ in 0..1 {
|
||||
let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
sleep(Duration::from_millis(1000));
|
||||
at.rename(file1, file2);
|
||||
sleep(Duration::from_millis(1000));
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.append(file2, "more_file2_content\n");
|
||||
sleep(Duration::from_millis(1000));
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.append(file1, "more_file1_content\n");
|
||||
sleep(Duration::from_millis(1000));
|
||||
sleep(Duration::from_millis(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);
|
||||
|
||||
args.pop();
|
||||
args.push("--use-polling");
|
||||
delay *= 3;
|
||||
// NOTE: Switch the first and second line because the events come in this order from
|
||||
// `notify::PollWatcher`. However, for GNU's tail, the order between polling and not
|
||||
// polling does not change.
|
||||
expected_stderr = format!(
|
||||
"{0}: '{2}' has been replaced; following new file\n\
|
||||
{0}: {1}: No such file or directory\n\
|
||||
{0}: '{1}' has appeared; following new file\n",
|
||||
ts.util_name, file1, file2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
|
||||
fn test_follow_name_move_retry() {
|
||||
// Similar to test_follow_name_move but with `--retry` (`-F`)
|
||||
fn test_follow_name_move_retry1() {
|
||||
// Similar to test_follow_name_move1 but with `--retry` (`-F`)
|
||||
// This test triggers two move/rename events while `tail --follow=name --retry file` is running.
|
||||
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
@ -2045,41 +2170,138 @@ fn test_follow_name_move_retry() {
|
|||
|
||||
let expected_stderr = format!(
|
||||
"{0}: '{1}' has become inaccessible: No such file or directory\n\
|
||||
{0}: '{1}' has appeared; following new file\n",
|
||||
{0}: '{1}' has appeared; following new file\n",
|
||||
ts.util_name, source
|
||||
);
|
||||
let expected_stdout = "tailed\nnew content\n";
|
||||
|
||||
let mut args = vec!["--follow=name", "--retry", source, "--use-polling"];
|
||||
|
||||
let mut delay = 1500;
|
||||
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));
|
||||
|
||||
sleep(Duration::from_millis(1000));
|
||||
at.append(source, "tailed\n");
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
sleep(Duration::from_millis(2000));
|
||||
// with --follow=name, tail should stop monitoring the renamed file
|
||||
at.rename(source, backup);
|
||||
sleep(Duration::from_millis(4000));
|
||||
|
||||
sleep(Duration::from_millis(delay));
|
||||
// overwrite backup while it's not monitored
|
||||
at.truncate(backup, "new content\n");
|
||||
sleep(Duration::from_millis(500));
|
||||
sleep(Duration::from_millis(delay));
|
||||
// move back, tail should pick this up and print new content
|
||||
at.rename(backup, source);
|
||||
sleep(Duration::from_millis(4000));
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
p.kill().unwrap();
|
||||
|
||||
let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p);
|
||||
dbg!(&buf_stdout, &buf_stderr);
|
||||
assert_eq!(buf_stdout, expected_stdout);
|
||||
assert_eq!(buf_stderr, expected_stderr);
|
||||
|
||||
at.remove(source);
|
||||
args.pop();
|
||||
delay /= 3;
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
|
||||
fn test_follow_name_move_retry2() {
|
||||
// inspired by: "gnu/tests/tail-2/F-vs-rename.sh"
|
||||
// Similar to test_follow_name_move2 (move to a name that's already monitored)
|
||||
// but with `--retry` (`-F`)
|
||||
|
||||
/*
|
||||
$ touch a b
|
||||
$ ((sleep 1; echo x > a; mv a b; echo x2 > a; echo y >> b; echo z >> a &)>/dev/null 2>&1 &) ; tail -F a b
|
||||
==> a <==
|
||||
|
||||
==> b <==
|
||||
|
||||
==> a <==
|
||||
x
|
||||
tail: 'a' has become inaccessible: No such file or directory
|
||||
tail: 'b' has been replaced; following new file
|
||||
|
||||
==> b <==
|
||||
x
|
||||
tail: 'a' has appeared; following new file
|
||||
|
||||
==> a <==
|
||||
x2
|
||||
|
||||
==> b <==
|
||||
y
|
||||
|
||||
==> a <==
|
||||
z
|
||||
*/
|
||||
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
|
||||
let file1 = "a";
|
||||
let file2 = "b";
|
||||
|
||||
let expected_stdout = format!(
|
||||
"==> {0} <==\n\n==> {1} <==\n\n==> {0} <==\nx\n\n==> {1} <==\
|
||||
\nx\n\n==> {0} <==\nx2\n\n==> {1} <==\ny\n\n==> {0} <==\nz\n",
|
||||
file1, file2
|
||||
);
|
||||
let mut expected_stderr = format!(
|
||||
"{0}: '{1}' has become inaccessible: No such file or directory\n\
|
||||
{0}: '{2}' has been replaced; following new file\n\
|
||||
{0}: '{1}' has appeared; following new file\n",
|
||||
ts.util_name, file1, file2
|
||||
);
|
||||
|
||||
let mut args = vec!["-s.1", "--max-unchanged-stats=1", "-F", file1, file2];
|
||||
|
||||
let mut delay = 500;
|
||||
for _ in 0..2 {
|
||||
at.touch(file1);
|
||||
at.touch(file2);
|
||||
|
||||
let mut p = ts.ucmd().set_stdin(Stdio::null()).args(&args).run_no_wait();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.truncate(file1, "x\n");
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.rename(file1, file2);
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.truncate(file1, "x2\n");
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.append(file2, "y\n");
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
at.append(file1, "z\n");
|
||||
sleep(Duration::from_millis(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);
|
||||
|
||||
at.remove(file1);
|
||||
at.remove(file2);
|
||||
args.push("--use-polling");
|
||||
delay *= 3;
|
||||
// NOTE: Switch the first and second line because the events come in this order from
|
||||
// `notify::PollWatcher`. However, for GNU's tail, the order between polling and not
|
||||
// polling does not change.
|
||||
expected_stderr = format!(
|
||||
"{0}: '{2}' has been replaced; following new file\n\
|
||||
{0}: '{1}' has become inaccessible: No such file or directory\n\
|
||||
{0}: '{1}' has appeared; following new file\n",
|
||||
ts.util_name, file1, file2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2211,7 +2433,7 @@ fn test_illegal_seek() {
|
|||
assert_eq!(
|
||||
buf_stderr,
|
||||
"tail: 'FILE' has been replaced; following new file\n\
|
||||
tail: FILE: cannot seek to offset 0: Illegal seek\n"
|
||||
tail: FILE: cannot seek to offset 0: Illegal seek\n"
|
||||
);
|
||||
assert_eq!(p.wait().unwrap().code().unwrap(), 1);
|
||||
}
|
||||
|
|
|
@ -164,6 +164,14 @@ sed -i -e "s|rm: cannot remove 'a/1'|rm: cannot remove 'a'|g" tests/rm/rm2.sh
|
|||
|
||||
sed -i -e "s|removed directory 'a/'|removed directory 'a'|g" tests/rm/v-slash.sh
|
||||
|
||||
# overlay-headers.sh test intends to check for inotify events,
|
||||
# however there's a bug because `---dis` is an alias for: `---disable-inotify`
|
||||
sed -i -e "s|---dis ||g" tests/tail-2/overlay-headers.sh
|
||||
|
||||
# F-headers.sh test sometime fails (but only in CI),
|
||||
# just testing inotify should make it more stable
|
||||
sed -i -e "s| '---disable-inotify'||g" tests/tail-2/F-headers.sh
|
||||
|
||||
test -f "${UU_BUILD_DIR}/getlimits" || cp src/getlimits "${UU_BUILD_DIR}"
|
||||
|
||||
# When decoding an invalid base32/64 string, gnu writes everything it was able to decode until
|
||||
|
@ -200,6 +208,7 @@ sed -i -e "s/provoked error./provoked error\ncat pat |sort -u > pat/" tests/misc
|
|||
sed -i -e "s/ln: 'f' and 'f' are the same file/ln: failed to link 'f' to 'f': Same file/g" tests/ln/hard-backup.sh
|
||||
sed -i -e "s/failed to access 'no-such-dir'\":/failed to link 'no-such-dir'\"/" -e "s/link-to-dir: hard link not allowed for directory/failed to link 'link-to-dir' to/" -e "s|link-to-dir/: hard link not allowed for directory|failed to link 'link-to-dir/' to|" tests/ln/hard-to-sym.sh
|
||||
|
||||
|
||||
# GNU sleep accepts some crazy string, not sure we should match this behavior
|
||||
sed -i -e "s/timeout 10 sleep 0x.002p1/#timeout 10 sleep 0x.002p1/" tests/misc/sleep.sh
|
||||
|
||||
|
|
Loading…
Reference in a new issue