mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
tail: add some tweaks to pass more of GNU's testsuite checks related to -F
This commit is contained in:
parent
63e9cd9202
commit
18a06c310e
2 changed files with 95 additions and 48 deletions
|
@ -11,7 +11,7 @@
|
|||
// spell-checker:ignore (libs) kqueue
|
||||
// spell-checker:ignore (acronyms)
|
||||
// spell-checker:ignore (env/flags)
|
||||
// spell-checker:ignore (jargon)
|
||||
// spell-checker:ignore (jargon) tailable untailable
|
||||
// spell-checker:ignore (names)
|
||||
// spell-checker:ignore (shell/tools)
|
||||
// spell-checker:ignore (misc)
|
||||
|
@ -155,11 +155,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
if let Some(s) = matches.value_of(options::MAX_UNCHANGED_STATS) {
|
||||
settings.max_unchanged_stats = match s.parse::<u32>() {
|
||||
Ok(s) => s,
|
||||
Err(_) => crash!(
|
||||
Err(_) => {
|
||||
// TODO: add test for this
|
||||
crash!(
|
||||
1,
|
||||
"invalid maximum number of unchanged stats between opens: {}",
|
||||
s.quote()
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,6 +270,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
// Do an initial tail print of each path's content.
|
||||
// Add `path` to `files` map if `--follow` is selected.
|
||||
for path in &paths {
|
||||
let md = path.metadata().ok();
|
||||
if path.is_stdin() {
|
||||
if settings.verbose {
|
||||
if !first_header {
|
||||
|
@ -316,7 +320,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
first_header = false;
|
||||
let mut file = File::open(&path).unwrap();
|
||||
let md = file.metadata().ok();
|
||||
let mut reader;
|
||||
|
||||
if is_seekable(&mut file) && get_block_size(md.as_ref().unwrap()) > 0 {
|
||||
|
@ -349,7 +352,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
key.to_path_buf(),
|
||||
PathData {
|
||||
reader: None,
|
||||
metadata: None,
|
||||
metadata: md,
|
||||
display_name: path.to_path_buf(),
|
||||
},
|
||||
);
|
||||
|
@ -538,6 +541,7 @@ fn follow(files: &mut FileHandling, settings: &Settings) {
|
|||
} else if settings.follow.is_some() && settings.retry {
|
||||
if path.is_orphan() {
|
||||
orphans.push(path.to_path_buf());
|
||||
// TODO: add test for this
|
||||
} else {
|
||||
let parent = path.parent().unwrap();
|
||||
watcher
|
||||
|
@ -564,6 +568,7 @@ fn follow(files: &mut FileHandling, settings: &Settings) {
|
|||
if new_path.exists() {
|
||||
let display_name = files.map.get(new_path).unwrap().display_name.to_path_buf();
|
||||
if new_path.is_file() && files.map.get(new_path).unwrap().metadata.is_none() {
|
||||
// TODO: add test for this
|
||||
show_error!("{} has appeared; following new file", display_name.quote());
|
||||
if let Ok(new_path_canonical) = new_path.canonicalize() {
|
||||
files.update_metadata(&new_path_canonical, None);
|
||||
|
@ -607,6 +612,7 @@ fn follow(files: &mut FileHandling, settings: &Settings) {
|
|||
if files.map.contains_key(event_path) {
|
||||
// TODO: handle this case for --follow=name --retry
|
||||
let _ = watcher.unwatch(event_path);
|
||||
// TODO: add test for this
|
||||
show_error!(
|
||||
"{}: {}",
|
||||
files.map.get(event_path).unwrap().display_name.display(),
|
||||
|
@ -622,7 +628,7 @@ fn follow(files: &mut FileHandling, settings: &Settings) {
|
|||
Ok(Err(notify::Error {
|
||||
kind: notify::ErrorKind::MaxFilesWatch,
|
||||
..
|
||||
})) => crash!(1, "inotify resources exhausted"),
|
||||
})) => crash!(1, "inotify resources exhausted"), // NOTE: Cannot test this in the CICD.
|
||||
Ok(Err(e)) => crash!(1, "{:?}", e),
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => {
|
||||
_timeout_counter += 1;
|
||||
|
@ -680,9 +686,31 @@ fn handle_event(
|
|||
| EventKind::Modify(ModifyKind::Data(DataChange::Any)) => {
|
||||
if let Ok(new_md) = event_path.metadata() {
|
||||
if let Some(old_md) = &files.map.get(event_path).unwrap().metadata {
|
||||
if new_md.len() <= old_md.len()
|
||||
if new_md.is_file() && !old_md.is_file() {
|
||||
show_error!(
|
||||
"{} has appeared; following new file",
|
||||
display_name.quote()
|
||||
);
|
||||
files.update_metadata(event_path, None);
|
||||
files.reopen_file(event_path).unwrap();
|
||||
} else if !new_md.is_file() && old_md.is_file() {
|
||||
show_error!(
|
||||
"{} has been replaced with an untailable file",
|
||||
display_name.quote()
|
||||
);
|
||||
files.map.insert(
|
||||
event_path.to_path_buf(),
|
||||
PathData {
|
||||
reader: None,
|
||||
metadata: None,
|
||||
display_name,
|
||||
},
|
||||
);
|
||||
files.update_metadata(event_path, None);
|
||||
} else if new_md.len() <= old_md.len()
|
||||
&& new_md.modified().unwrap() != old_md.modified().unwrap()
|
||||
{
|
||||
// TODO: add test for this
|
||||
show_error!("{}: file truncated", display_name.display());
|
||||
files.update_metadata(event_path, None);
|
||||
files.reopen_file(event_path).unwrap();
|
||||
|
@ -696,6 +724,7 @@ fn handle_event(
|
|||
| EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
|
||||
if event_path.is_file() {
|
||||
if settings.follow.is_some() {
|
||||
// TODO: add test for this
|
||||
let msg = if settings.use_polling && !settings.retry {
|
||||
format!("{} has been replaced", display_name.quote())
|
||||
} else {
|
||||
|
@ -710,6 +739,7 @@ fn handle_event(
|
|||
// behavior and is the usual truncation operation for log files.
|
||||
files.reopen_file(event_path).unwrap();
|
||||
if settings.follow == Some(FollowMode::Name) && settings.retry {
|
||||
// TODO: add test for this
|
||||
// Path has appeared, it's not an orphan any more.
|
||||
orphans.retain(|path| path != event_path);
|
||||
}
|
||||
|
@ -730,6 +760,7 @@ fn handle_event(
|
|||
crash!(1, "{}", text::NO_FILES_REMAINING);
|
||||
}
|
||||
} else if settings.follow == Some(FollowMode::Name) {
|
||||
// TODO: add test for this
|
||||
files.update_metadata(event_path, None);
|
||||
show_error!("{} {}", display_name.quote(), msg);
|
||||
}
|
||||
|
@ -741,11 +772,15 @@ fn handle_event(
|
|||
EventKind::Remove(RemoveKind::File) | EventKind::Remove(RemoveKind::Any) => {
|
||||
if settings.follow == Some(FollowMode::Name) {
|
||||
if settings.retry {
|
||||
if let Some(old_md) = &files.map.get(event_path).unwrap().metadata {
|
||||
if old_md.is_file() {
|
||||
show_error!(
|
||||
"{} has become inaccessible: {}",
|
||||
display_name.quote(),
|
||||
text::NO_SUCH_FILE
|
||||
);
|
||||
}
|
||||
}
|
||||
if event_path.is_orphan() {
|
||||
if !orphans.contains(event_path) {
|
||||
show_error!("directory containing watched file was removed");
|
||||
|
@ -885,6 +920,7 @@ impl FileHandling {
|
|||
false
|
||||
}
|
||||
|
||||
// TODO: change to update_reader() without error return
|
||||
fn reopen_file(&mut self, path: &Path) -> Result<(), Error> {
|
||||
assert!(self.map.contains_key(path));
|
||||
if let Some(pd) = self.map.get_mut(path) {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile logfile
|
||||
// spell-checker:ignore (libs) kqueue
|
||||
// spell-checker:ignore (jargon) tailable untailable
|
||||
|
||||
extern crate tail;
|
||||
|
||||
|
@ -743,7 +744,7 @@ fn test_retry5() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
|
||||
fn test_retry6() {
|
||||
// gnu/tests/tail-2/retry.sh
|
||||
// Ensure that --follow=descriptor (without --retry) does *not* try
|
||||
|
@ -799,17 +800,27 @@ fn test_retry7() {
|
|||
|
||||
let delay = 1000;
|
||||
|
||||
let mut args = vec![
|
||||
"-s.1",
|
||||
"--max-unchanged-stats=1",
|
||||
"-F",
|
||||
untailable,
|
||||
"--use-polling",
|
||||
];
|
||||
for _ in 0..2 {
|
||||
at.mkdir(untailable);
|
||||
let mut p = ts.ucmd().arg("-F").arg(untailable).run_no_wait();
|
||||
let mut p = ts.ucmd().args(&args).run_no_wait();
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
// tail: 'untailable' has become accessible
|
||||
// or (The first is the common case, "has appeared" arises with slow rmdir.)
|
||||
// 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));
|
||||
|
||||
// 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));
|
||||
|
@ -828,6 +839,11 @@ fn test_retry7() {
|
|||
let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p);
|
||||
assert_eq!(buf_stdout, expected_stdout);
|
||||
assert_eq!(buf_stderr, expected_stderr);
|
||||
|
||||
args.pop();
|
||||
at.remove(untailable);
|
||||
sleep(Duration::from_millis(delay));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -899,7 +915,8 @@ fn test_retry9() {
|
|||
let parent_dir = parent_dir.to_str().unwrap();
|
||||
let user_path = user_path.to_str().unwrap();
|
||||
|
||||
let expected_stderr = format!("\
|
||||
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\
|
||||
|
@ -1184,7 +1201,7 @@ fn test_follow_name_truncate2() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[cfg(target_os = "linux")] // FIXME: fix this test for BSD/macOS
|
||||
fn test_follow_name_truncate3() {
|
||||
// Opening an empty file in truncate mode should not trigger a truncate event while.
|
||||
// $ rm -f logfile && touch logfile
|
||||
|
@ -1203,13 +1220,7 @@ fn test_follow_name_truncate3() {
|
|||
|
||||
let delay = 1000;
|
||||
sleep(Duration::from_millis(delay));
|
||||
use std::fs::OpenOptions;
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(at.plus(source))
|
||||
.unwrap();
|
||||
file.write_all(b"x\n").unwrap();
|
||||
at.truncate(source, "x\n");
|
||||
sleep(Duration::from_millis(delay));
|
||||
|
||||
p.kill().unwrap();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue