Skip to content

Commit

Permalink
tail/args: Fix parsing when -F is used together with --retry or --follow
Browse files Browse the repository at this point in the history
  • Loading branch information
Joining7943 committed Apr 12, 2023
1 parent 08c5f3b commit 6dd28bb
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/uu/tail/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ fundu = { workspace=true }
windows-sys = { workspace=true, features = ["Win32_System_Threading", "Win32_Foundation"] }
winapi-util = { workspace=true }

[dev-dependencies]
rstest = "0.17.0"

[[bin]]
name = "tail"
path = "src/main.rs"
76 changes: 61 additions & 15 deletions src/uu/tail/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use crate::paths::Input;
use crate::{parse, platform, Quotable};
use clap::crate_version;
use clap::{parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
use clap::{Arg, ArgAction, ArgMatches, Command};
use fundu::DurationParser;
use is_terminal::IsTerminal;
use same_file::Handle;
Expand Down Expand Up @@ -182,19 +182,33 @@ impl Settings {
}

pub fn from(matches: &clap::ArgMatches) -> UResult<Self> {
let mut settings: Self = Self {
follow: if matches.get_flag(options::FOLLOW_RETRY) {
Some(FollowMode::Name)
} else if matches.value_source(options::FOLLOW) != Some(ValueSource::CommandLine) {
None
} else if matches.get_one::<String>(options::FOLLOW)
== Some(String::from("name")).as_ref()
let (follow, retry) = match (
matches.get_flag(options::FOLLOW_RETRY),
matches.get_one::<String>(options::FOLLOW),
) {
(true, Some(_))
// It's ok to use `index_of` instead of `indices_of` since FOLLOW_RETRY is allowed
// only once and FOLLOW overwrites itself
if matches.index_of(options::FOLLOW_RETRY) > matches.index_of(options::FOLLOW) =>
{
Some(FollowMode::Name)
} else {
Some(FollowMode::Descriptor)
},
retry: matches.get_flag(options::RETRY) || matches.get_flag(options::FOLLOW_RETRY),
(Some(FollowMode::Name), true)
}
(true, Some(value)) if value == "name" => (Some(FollowMode::Name), true),
(true, Some(_)) => (Some(FollowMode::Descriptor), true),
(true, None) => (Some(FollowMode::Name), true),
(false, Some(value)) if value == "name" => {
(Some(FollowMode::Name), matches.get_flag(options::RETRY))
}
(false, Some(_)) => (
Some(FollowMode::Descriptor),
matches.get_flag(options::RETRY),
),
(false, None) => (None, matches.get_flag(options::RETRY)),
};

let mut settings: Self = Self {
follow,
retry,
use_polling: matches.get_flag(options::USE_POLLING),
mode: FilterMode::from(matches)?,
verbose: matches.get_flag(options::verbosity::VERBOSE),
Expand Down Expand Up @@ -469,7 +483,7 @@ pub fn uu_app() -> Command {
Arg::new(options::FOLLOW)
.short('f')
.long(options::FOLLOW)
.default_value("descriptor")
.default_missing_value("descriptor")
.num_args(0..=1)
.require_equals(true)
.value_parser(["descriptor", "name"])
Expand Down Expand Up @@ -550,7 +564,6 @@ pub fn uu_app() -> Command {
Arg::new(options::FOLLOW_RETRY)
.short('F')
.help("Same as --follow=name --retry")
.overrides_with_all([options::RETRY, options::FOLLOW])
.action(ArgAction::SetTrue),
)
.arg(
Expand All @@ -570,6 +583,8 @@ pub fn uu_app() -> Command {

#[cfg(test)]
mod tests {
use rstest::rstest;

use crate::parse::ObsoleteArgs;

use super::*;
Expand Down Expand Up @@ -616,4 +631,35 @@ mod tests {
let result = Settings::from_obsolete_args(&args, Some(&"file".into()));
assert_eq!(result.follow, Some(FollowMode::Name));
}

#[rstest]
#[case::default(vec![], None, false)]
#[case::retry(vec!["--retry"], None, true)]
#[case::follow_long(vec!["--follow"], Some(FollowMode::Descriptor), false)]
#[case::follow_short(vec!["-f"], Some(FollowMode::Descriptor), false)]
#[case::follow_long_with_retry(vec!["--follow", "--retry"], Some(FollowMode::Descriptor), true)]
#[case::follow_short_with_retry(vec!["-f", "--retry"], Some(FollowMode::Descriptor), true)]
#[case::follow_overwrites_previous_selection_1(vec!["--follow=name", "--follow=descriptor"], Some(FollowMode::Descriptor), false)]
#[case::follow_overwrites_previous_selection_2(vec!["--follow=descriptor", "--follow=name"], Some(FollowMode::Name), false)]
#[case::big_f(vec!["-F"], Some(FollowMode::Name), true)]
#[case::big_f_with_retry_then_does_not_change(vec!["-F", "--retry"], Some(FollowMode::Name), true)]
#[case::big_f_with_follow_descriptor_then_change(vec!["-F", "--follow=descriptor"], Some(FollowMode::Descriptor), true)]
#[case::big_f_with_follow_short_then_change(vec!["-F", "-f"], Some(FollowMode::Descriptor), true)]
#[case::follow_descriptor_with_big_f_then_change(vec!["--follow=descriptor", "-F"], Some(FollowMode::Name), true)]
#[case::follow_short_with_big_f_then_change(vec!["-f", "-F"], Some(FollowMode::Name), true)]
#[case::big_f_with_follow_name_then_not_change(vec!["-F", "--follow=name"], Some(FollowMode::Name), true)]
#[case::follow_name_with_big_f_then_not_change(vec!["--follow=name", "-F"], Some(FollowMode::Name), true)]
#[case::big_f_with_multiple_long_follow(vec!["--follow=name", "-F", "--follow=descriptor"], Some(FollowMode::Descriptor), true)]
#[case::big_f_with_multiple_long_follow_name(vec!["--follow=name", "-F", "--follow=name"], Some(FollowMode::Name), true)]
#[case::big_f_with_multiple_short_follow(vec!["-f", "-F", "-f"], Some(FollowMode::Descriptor), true)]
fn test_parse_settings_follow_mode_and_retry(
#[case] args: Vec<&str>,
#[case] expected_follow_mode: Option<FollowMode>,
#[case] expected_retry: bool,
) {
let settings =
Settings::from(&uu_app().no_binary_name(true).get_matches_from(args)).unwrap();
assert_eq!(settings.follow, expected_follow_mode);
assert_eq!(settings.retry, expected_retry);
}
}
17 changes: 9 additions & 8 deletions tests/by-util/test_tail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4358,14 +4358,7 @@ fn test_follow_when_file_and_symlink_are_pointing_to_same_file_and_append_data()
.stdout_only(expected_stdout);
}

// Fails with:
// 'Assertion failed. Expected 'tail' to be running but exited with status=exit status: 1.
// stdout:
// stderr: tail: warning: --retry ignored; --retry is useful only when following
// tail: error reading 'dir': Is a directory
// '
#[test]
#[cfg(disabled_until_fixed)]
fn test_args_when_directory_given_shorthand_big_f_together_with_retry() {
let scene = TestScenario::new(util_name!());
let fixtures = &scene.fixtures;
Expand All @@ -4377,9 +4370,17 @@ fn test_args_when_directory_given_shorthand_big_f_together_with_retry() {
tail: {0}: cannot follow end of this type of file\n",
dirname
);

let mut child = scene.ucmd().args(&["-F", "--retry", "dir"]).run_no_wait();

child.make_assertion_with_delay(500).is_alive();
child
.kill()
.make_assertion()
.with_current_output()
.stderr_only(&expected_stderr);

let mut child = scene.ucmd().args(&["--retry", "-F", "dir"]).run_no_wait();

child.make_assertion_with_delay(500).is_alive();
child
.kill()
Expand Down

0 comments on commit 6dd28bb

Please sign in to comment.