//
// This file is part of the uutils coreutils package.
//
// (c) mahkoh (ju.orth [at] gmail [dot] com)
// (c) Daniel Rocco <drocco@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//

// spell-checker:ignore (words) egid euid pseudofloat

use crate::common::util::TestScenario;
use std::thread::sleep;

#[test]
fn test_empty_test_equivalent_to_false() {
    new_ucmd!().run().code_is(1);
}

#[test]
fn test_empty_string_is_false() {
    new_ucmd!().arg("").run().code_is(1);
}

#[test]
fn test_solo_not() {
    new_ucmd!().arg("!").succeeds();
}

#[test]
fn test_solo_and_or_or_is_a_literal() {
    // /bin/test '' -a '' => 1; so test(1) must interpret `-a` by itself as
    // a literal string
    new_ucmd!().arg("-a").succeeds();
    new_ucmd!().arg("-o").succeeds();
}

#[test]
fn test_some_literals() {
    let scenario = TestScenario::new(util_name!());
    let tests = [
        "a string",
        "(",
        ")",
        "-",
        "--",
        "-0",
        "-f",
        "--help",
        "--version",
        "-eq",
        "-lt",
        "-ef",
        "[",
    ];

    for test in &tests {
        scenario.ucmd().arg(test).succeeds();
    }

    // run the inverse of all these tests
    for test in &tests {
        scenario.ucmd().arg("!").arg(test).run().code_is(1);
    }
}

#[test]
fn test_double_not_is_false() {
    new_ucmd!().args(&["!", "!"]).run().code_is(1);
}

#[test]
fn test_and_not_is_false() {
    new_ucmd!().args(&["-a", "!"]).run().code_is(1);
}

#[test]
fn test_not_and_is_false() {
    // `-a` is a literal here & has nonzero length
    new_ucmd!().args(&["!", "-a"]).run().code_is(1);
}

#[test]
fn test_not_and_not_succeeds() {
    new_ucmd!().args(&["!", "-a", "!"]).succeeds();
}

#[test]
fn test_simple_or() {
    new_ucmd!().args(&["foo", "-o", ""]).succeeds();
}

#[test]
fn test_negated_or() {
    new_ucmd!()
        .args(&["!", "foo", "-o", "bar"])
        .run()
        .code_is(1);
    new_ucmd!().args(&["foo", "-o", "!", "bar"]).succeeds();
    new_ucmd!()
        .args(&["!", "foo", "-o", "!", "bar"])
        .run()
        .code_is(1);
}

#[test]
fn test_string_length_of_nothing() {
    // odd but matches GNU, which must interpret -n as a literal here
    new_ucmd!().arg("-n").succeeds();
}

#[test]
fn test_string_length_of_empty() {
    new_ucmd!().args(&["-n", ""]).run().code_is(1);

    // STRING equivalent to -n STRING
    new_ucmd!().arg("").run().code_is(1);
}

#[test]
fn test_nothing_is_empty() {
    // -z is a literal here and has nonzero length
    new_ucmd!().arg("-z").succeeds();
}

#[test]
fn test_zero_len_of_empty() {
    new_ucmd!().args(&["-z", ""]).succeeds();
}

#[test]
fn test_zero_len_equals_zero_len() {
    new_ucmd!().args(&["", "=", ""]).succeeds();
}

#[test]
fn test_zero_len_not_equals_zero_len_is_false() {
    new_ucmd!().args(&["", "!=", ""]).run().code_is(1);
}

#[test]
fn test_double_equal_is_string_comparison_op() {
    // undocumented but part of the GNU test suite
    new_ucmd!().args(&["t", "==", "t"]).succeeds();
    new_ucmd!().args(&["t", "==", "f"]).run().code_is(1);
}

#[test]
fn test_string_comparison() {
    let scenario = TestScenario::new(util_name!());
    let tests = [
        ["foo", "!=", "bar"],
        ["contained\nnewline", "=", "contained\nnewline"],
        ["(", "=", "("],
        ["(", "!=", ")"],
        ["(", "!=", "="],
        ["!", "=", "!"],
        ["=", "=", "="],
    ];

    for test in &tests {
        scenario.ucmd().args(&test[..]).succeeds();
    }

    // run the inverse of all these tests
    for test in &tests {
        scenario.ucmd().arg("!").args(&test[..]).run().code_is(1);
    }
}

#[test]
#[ignore = "fixme: error reporting"]
fn test_dangling_string_comparison_is_error() {
    new_ucmd!()
        .args(&["missing_something", "="])
        .run()
        .code_is(2)
        .stderr_is("test: missing argument after '='");
}

#[test]
fn test_string_operator_is_literal_after_bang() {
    let scenario = TestScenario::new(util_name!());
    let tests = [
        ["!", "="],
        ["!", "!="],
        ["!", "-eq"],
        ["!", "-ne"],
        ["!", "-lt"],
        ["!", "-le"],
        ["!", "-gt"],
        ["!", "-ge"],
        ["!", "-ef"],
        ["!", "-nt"],
        ["!", "-ot"],
    ];

    for test in &tests {
        scenario.ucmd().args(&test[..]).run().code_is(1);
    }
}

#[test]
fn test_a_bunch_of_not() {
    new_ucmd!()
        .args(&["!", "", "!=", "", "-a", "!", "", "!=", ""])
        .succeeds();
}

#[test]
fn test_pseudofloat_equal() {
    // string comparison; test(1) doesn't support comparison of actual floats
    new_ucmd!().args(&["123.45", "=", "123.45"]).succeeds();
}

#[test]
fn test_pseudofloat_not_equal() {
    // string comparison; test(1) doesn't support comparison of actual floats
    new_ucmd!().args(&["123.45", "!=", "123.450"]).succeeds();
}

#[test]
fn test_negative_arg_is_a_string() {
    new_ucmd!().arg("-12345").succeeds();
    new_ucmd!().arg("--qwert").succeeds(); // spell-checker:disable-line
}

#[test]
fn test_some_int_compares() {
    let scenario = TestScenario::new(util_name!());

    let tests = [
        ["0", "-eq", "0"],
        ["0", "-ne", "1"],
        ["421", "-lt", "3720"],
        ["0", "-le", "0"],
        ["11", "-gt", "10"],
        ["1024", "-ge", "512"],
        ["9223372036854775806", "-le", "9223372036854775807"],
    ];

    for test in &tests {
        scenario.ucmd().args(&test[..]).succeeds();
    }

    // run the inverse of all these tests
    for test in &tests {
        scenario.ucmd().arg("!").args(&test[..]).run().code_is(1);
    }
}

#[test]
#[ignore = "fixme: evaluation error (code 1); GNU returns 0"]
fn test_values_greater_than_i64_allowed() {
    new_ucmd!()
        .args(&["9223372036854775808", "-gt", "0"])
        .succeeds();
}

#[test]
fn test_negative_int_compare() {
    let scenario = TestScenario::new(util_name!());

    let tests = [
        ["-1", "-eq", "-1"],
        ["-1", "-ne", "-2"],
        ["-3720", "-lt", "-421"],
        ["-10", "-le", "-10"],
        ["-21", "-gt", "-22"],
        ["-128", "-ge", "-256"],
        ["-9223372036854775808", "-le", "-9223372036854775807"],
    ];

    for test in &tests {
        scenario.ucmd().args(&test[..]).succeeds();
    }

    // run the inverse of all these tests
    for test in &tests {
        scenario.ucmd().arg("!").args(&test[..]).run().code_is(1);
    }
}

#[test]
fn test_float_inequality_is_error() {
    new_ucmd!()
        .args(&["123.45", "-ge", "6"])
        .run()
        .code_is(2)
        .stderr_is("test: invalid integer '123.45'\n");
}

#[test]
#[cfg(not(windows))]
fn test_invalid_utf8_integer_compare() {
    use std::ffi::OsStr;
    use std::os::unix::ffi::OsStrExt;

    let source = [0x66, 0x6f, 0x80, 0x6f];
    let arg = OsStr::from_bytes(&source[..]);

    new_ucmd!()
        .args(&[OsStr::new("123"), OsStr::new("-ne"), arg])
        .run()
        .code_is(2)
        .stderr_is("test: invalid integer $'fo\\x80o'\n");

    new_ucmd!()
        .args(&[arg, OsStr::new("-eq"), OsStr::new("456")])
        .run()
        .code_is(2)
        .stderr_is("test: invalid integer $'fo\\x80o'\n");
}

#[test]
#[cfg(unix)]
fn test_file_is_itself() {
    new_ucmd!()
        .args(&["regular_file", "-ef", "regular_file"])
        .succeeds();
}

#[test]
#[cfg(not(any(target_env = "musl", target_os = "android")))]
fn test_file_is_newer_than_and_older_than_itself() {
    // odd but matches GNU
    new_ucmd!()
        .args(&["regular_file", "-nt", "regular_file"])
        .run()
        .code_is(1);
    new_ucmd!()
        .args(&["regular_file", "-ot", "regular_file"])
        .run()
        .code_is(1);
}

#[test]
fn test_non_existing_files() {
    let scenario = TestScenario::new(util_name!());

    let result = scenario
        .ucmd()
        .args(&["newer_file", "-nt", "regular_file"])
        .fails();
    assert!(result.stderr().is_empty());
}

#[test]
#[cfg(unix)]
fn test_same_device_inode() {
    let scenario = TestScenario::new(util_name!());
    let at = &scenario.fixtures;

    scenario.cmd("touch").arg("regular_file").succeeds();
    scenario.cmd("touch").arg("regular_file_second").succeeds();

    at.symlink_file("regular_file", "symlink");

    scenario
        .ucmd()
        .args(&["regular_file", "-ef", "regular_file_second"])
        .fails();

    scenario
        .ucmd()
        .args(&["regular_file", "-ef", "symlink"])
        .succeeds();
}

#[test]
#[cfg(not(any(target_env = "musl", target_os = "android")))]
// musl: creation time is not available on this platform currently
fn test_newer_file() {
    let scenario = TestScenario::new(util_name!());

    scenario.fixtures.touch("regular_file");
    sleep(std::time::Duration::from_millis(1000));
    scenario.fixtures.touch("newer_file");

    scenario
        .ucmd()
        .args(&["newer_file", "-nt", "regular_file"])
        .succeeds();
    scenario
        .ucmd()
        .args(&["newer_file", "-ot", "regular_file"])
        .succeeds();
}

#[test]
fn test_file_exists() {
    new_ucmd!().args(&["-e", "regular_file"]).succeeds();
}

#[test]
fn test_nonexistent_file_does_not_exist() {
    new_ucmd!()
        .args(&["-e", "nonexistent_file"])
        .run()
        .code_is(1);
}

#[test]
fn test_nonexistent_file_is_not_regular() {
    new_ucmd!()
        .args(&["-f", "nonexistent_file"])
        .run()
        .code_is(1);
}

#[test]
fn test_file_exists_and_is_regular() {
    new_ucmd!().args(&["-f", "regular_file"]).succeeds();
}

#[test]
#[cfg(not(windows))] // FIXME: implement on Windows
fn test_file_is_readable() {
    new_ucmd!().args(&["-r", "regular_file"]).succeeds();
}

#[test]
#[cfg(not(windows))] // FIXME: implement on Windows
fn test_file_is_not_readable() {
    let scenario = TestScenario::new(util_name!());
    let mut ucmd = scenario.ucmd();
    let mut chmod = scenario.cmd("chmod");

    scenario.fixtures.touch("crypto_file");
    chmod.args(&["u-r", "crypto_file"]).succeeds();

    ucmd.args(&["!", "-r", "crypto_file"]).succeeds();
}

#[test]
#[cfg(not(windows))] // FIXME: implement on Windows
fn test_file_is_writable() {
    new_ucmd!().args(&["-w", "regular_file"]).succeeds();
}

#[test]
#[cfg(not(windows))] // FIXME: implement on Windows
fn test_file_is_not_writable() {
    let scenario = TestScenario::new(util_name!());
    let mut ucmd = scenario.ucmd();
    let mut chmod = scenario.cmd("chmod");

    scenario.fixtures.touch("immutable_file");
    chmod.args(&["u-w", "immutable_file"]).succeeds();

    ucmd.args(&["!", "-w", "immutable_file"]).succeeds();
}

#[test]
fn test_file_is_not_executable() {
    #[cfg(unix)]
    let (at, mut ucmd) = at_and_ucmd!();
    #[cfg(not(unix))]
    let (_, mut ucmd) = at_and_ucmd!();

    // WSL creates executable files by default, so if we are on unix, make sure
    // to set make it non-executable.
    // Files on other targets are non-executable by default.
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let metadata = std::fs::metadata(at.plus("regular_file")).unwrap();
        let mut permissions = metadata.permissions();

        // The conversion is useless on some platforms and casts from u16 to
        // u32 on others
        #[allow(clippy::useless_conversion)]
        permissions.set_mode(permissions.mode() & !u32::from(libc::S_IXUSR));
        std::fs::set_permissions(at.plus("regular_file"), permissions).unwrap();
    }
    ucmd.args(&["!", "-x", "regular_file"]).succeeds();
}

#[test]
#[cfg(not(windows))] // FIXME: implement on Windows
fn test_file_is_executable() {
    let scenario = TestScenario::new(util_name!());
    let mut chmod = scenario.cmd("chmod");

    chmod.args(&["u+x", "regular_file"]).succeeds();

    scenario.ucmd().args(&["-x", "regular_file"]).succeeds();
}

#[test]
fn test_is_not_empty() {
    new_ucmd!().args(&["-s", "non_empty_file"]).succeeds();
}

#[test]
fn test_nonexistent_file_size_test_is_false() {
    new_ucmd!()
        .args(&["-s", "nonexistent_file"])
        .run()
        .code_is(1);
}

#[test]
fn test_not_is_not_empty() {
    new_ucmd!().args(&["!", "-s", "regular_file"]).succeeds();
}

#[test]
#[cfg(not(windows))]
fn test_symlink_is_symlink() {
    let scenario = TestScenario::new(util_name!());
    let at = &scenario.fixtures;

    at.symlink_file("regular_file", "symlink");

    // FIXME: implement on Windows
    scenario.ucmd().args(&["-h", "symlink"]).succeeds();
    scenario.ucmd().args(&["-L", "symlink"]).succeeds();
}

#[test]
fn test_file_is_not_symlink() {
    let scenario = TestScenario::new(util_name!());

    scenario
        .ucmd()
        .args(&["!", "-h", "regular_file"])
        .succeeds();
    scenario
        .ucmd()
        .args(&["!", "-L", "regular_file"])
        .succeeds();
}

#[test]
fn test_nonexistent_file_is_not_symlink() {
    let scenario = TestScenario::new(util_name!());

    scenario
        .ucmd()
        .args(&["!", "-h", "nonexistent_file"])
        .succeeds();
    scenario
        .ucmd()
        .args(&["!", "-L", "nonexistent_file"])
        .succeeds();
}

#[test]
// FixME: freebsd fails with 'chmod: sticky_file: Inappropriate file type or format'
// Windows has no concept of sticky bit
#[cfg(not(any(windows, target_os = "freebsd")))]
fn test_file_is_sticky() {
    let scenario = TestScenario::new(util_name!());
    let mut ucmd = scenario.ucmd();
    let mut chmod = scenario.cmd("chmod");

    scenario.fixtures.touch("sticky_file");
    chmod.args(&["+t", "sticky_file"]).succeeds();

    ucmd.args(&["-k", "sticky_file"]).succeeds();
}

#[test]
fn test_file_is_not_sticky() {
    new_ucmd!().args(&["-k", "regular_file"]).run().code_is(1);
}

#[test]
fn test_solo_empty_parenthetical_is_error() {
    new_ucmd!().args(&["(", ")"]).run().code_is(2);
}

#[test]
fn test_parenthesized_literal() {
    let scenario = TestScenario::new(util_name!());
    let tests = [
        "a string",
        "(",
        ")",
        "-",
        "--",
        "-0",
        "-f",
        "--help",
        "--version",
        "-e",
        "-t",
        "!",
        "-n",
        "-z",
        "[",
        "-a",
        "-o",
    ];

    for test in &tests {
        scenario.ucmd().arg("(").arg(test).arg(")").succeeds();
    }

    // run the inverse of all these tests
    for test in &tests {
        scenario
            .ucmd()
            .arg("!")
            .arg("(")
            .arg(test)
            .arg(")")
            .run()
            .code_is(1);
    }
}

#[test]
fn test_parenthesized_op_compares_literal_parenthesis() {
    // ensure we aren’t treating this case as “string length of literal equal
    // sign”
    new_ucmd!().args(&["(", "=", ")"]).run().code_is(1);
}

#[test]
fn test_parenthesized_string_comparison() {
    let scenario = TestScenario::new(util_name!());
    let tests = [
        ["(", "foo", "!=", "bar", ")"],
        ["(", "contained\nnewline", "=", "contained\nnewline", ")"],
        ["(", "(", "=", "(", ")"],
        ["(", "(", "!=", ")", ")"],
        ["(", "!", "=", "!", ")"],
        ["(", "=", "=", "=", ")"],
    ];

    for test in &tests {
        scenario.ucmd().args(&test[..]).succeeds();
    }

    // run the inverse of all these tests
    for test in &tests {
        scenario.ucmd().arg("!").args(&test[..]).run().code_is(1);
    }
}

#[test]
fn test_parenthesized_right_parenthesis_as_literal() {
    new_ucmd!().args(&["(", "-f", ")", ")"]).run().code_is(1);
}

#[test]
#[cfg(not(windows))]
fn test_file_owned_by_euid() {
    new_ucmd!().args(&["-O", "regular_file"]).succeeds();
}

#[test]
#[cfg(not(windows))]
fn test_nonexistent_file_not_owned_by_euid() {
    new_ucmd!()
        .args(&["-O", "nonexistent_file"])
        .run()
        .code_is(1);
}

#[test]
#[cfg(all(not(windows), not(target_os = "freebsd")))]
fn test_file_not_owned_by_euid() {
    new_ucmd!()
        .args(&["-f", "/bin/sh", "-a", "!", "-O", "/bin/sh"])
        .succeeds();
}

#[test]
#[cfg(all(not(windows), not(target_os = "freebsd")))]
fn test_file_owned_by_egid() {
    new_ucmd!().args(&["-G", "regular_file"]).succeeds();
}

#[test]
#[cfg(not(windows))]
fn test_nonexistent_file_not_owned_by_egid() {
    new_ucmd!()
        .args(&["-G", "nonexistent_file"])
        .run()
        .code_is(1);
}

#[test]
#[cfg(all(not(windows), not(target_os = "freebsd")))]
fn test_file_not_owned_by_egid() {
    new_ucmd!()
        .args(&["-f", "/bin/sh", "-a", "!", "-G", "/bin/sh"])
        .succeeds();
}

#[test]
fn test_op_precedence_and_or_1() {
    new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds();
}

#[test]
fn test_op_precedence_and_or_1_overridden_by_parentheses() {
    new_ucmd!()
        .args(&["(", " ", "-o", "", ")", "-a", ""])
        .run()
        .code_is(1);
}

#[test]
fn test_op_precedence_and_or_2() {
    new_ucmd!()
        .args(&["", "-a", "", "-o", " ", "-a", " "])
        .succeeds();
}

#[test]
fn test_op_precedence_and_or_2_overridden_by_parentheses() {
    new_ucmd!()
        .args(&["", "-a", "(", "", "-o", " ", ")", "-a", " "])
        .run()
        .code_is(1);
}

#[test]
fn test_negated_boolean_precedence() {
    let scenario = TestScenario::new(util_name!());

    let tests = [
        vec!["!", "(", "foo", ")", "-o", "bar"],
        vec!["!", "", "-o", "", "-a", ""],
        vec!["!", "(", "", "-a", "", ")", "-o", ""],
    ];

    for test in &tests {
        scenario.ucmd().args(&test[..]).succeeds();
    }

    let negative_tests = [
        vec!["!", "-n", "", "-a", ""],
        vec!["", "-a", "", "-o", ""],
        vec!["!", "", "-a", "", "-o", ""],
        vec!["!", "(", "", "-a", "", ")", "-a", ""],
    ];

    for test in &negative_tests {
        scenario.ucmd().args(&test[..]).run().code_is(1);
    }
}

#[test]
fn test_bang_bool_op_precedence() {
    // For a Boolean combination of two literals, bang inverts the entire expression
    new_ucmd!().args(&["!", "", "-a", ""]).succeeds();
    new_ucmd!().args(&["!", "", "-o", ""]).succeeds();

    new_ucmd!()
        .args(&["!", "a value", "-o", "another value"])
        .run()
        .code_is(1);

    // Introducing a UOP — even one that is equivalent to a bare string — causes
    // bang to invert only the first term
    new_ucmd!()
        .args(&["!", "-n", "", "-a", ""])
        .run()
        .code_is(1);
    new_ucmd!()
        .args(&["!", "", "-a", "-n", ""])
        .run()
        .code_is(1);

    // for compound Boolean expressions, bang inverts the _next_ expression
    // only, not the entire compound expression
    new_ucmd!()
        .args(&["!", "", "-a", "", "-a", ""])
        .run()
        .code_is(1);

    // parentheses can override this
    new_ucmd!()
        .args(&["!", "(", "", "-a", "", "-a", "", ")"])
        .succeeds();
}

#[test]
fn test_inverted_parenthetical_bool_op_precedence() {
    // For a Boolean combination of two literals, bang inverts the entire expression
    new_ucmd!()
        .args(&["!", "a value", "-o", "another value"])
        .run()
        .code_is(1);

    // only the parenthetical is inverted, not the entire expression
    new_ucmd!()
        .args(&["!", "(", "a value", ")", "-o", "another value"])
        .succeeds();
}

#[test]
#[ignore = "fixme: error reporting"]
fn test_dangling_parenthesis() {
    new_ucmd!()
        .args(&["(", "(", "a", "!=", "b", ")", "-o", "-n", "c"])
        .run()
        .code_is(2);
    new_ucmd!()
        .args(&["(", "(", "a", "!=", "b", ")", "-o", "-n", "c", ")"])
        .succeeds();
}

#[test]
fn test_complicated_parenthesized_expression() {
    new_ucmd!()
        .args(&[
            "(", "(", "!", "(", "a", "=", "b", ")", "-o", "c", "=", "d", ")", "-a", "(", "q", "!=",
            "r", ")", ")",
        ])
        .succeeds();
}

#[test]
fn test_erroneous_parenthesized_expression() {
    new_ucmd!()
        .args(&["a", "!=", "(", "b", "-a", "b", ")", "!=", "c"])
        .run()
        .code_is(2)
        .stderr_is("test: extra argument 'b'\n");
}

#[test]
fn test_or_as_filename() {
    new_ucmd!().args(&["x", "-a", "-z", "-o"]).run().code_is(1);
}

#[test]
#[ignore = "TODO: Busybox has this working"]
fn test_filename_or_with_equal() {
    new_ucmd!()
        .args(&["-f", "=", "a", "-o", "b"])
        .run()
        .code_is(0);
}

#[test]
#[ignore = "GNU considers this an error"]
fn test_string_length_and_nothing() {
    new_ucmd!().args(&["-n", "a", "-a"]).run().code_is(2);
}

#[test]
fn test_bracket_syntax_success() {
    let scenario = TestScenario::new("[");
    let mut ucmd = scenario.ucmd();

    ucmd.args(&["1", "-eq", "1", "]"]).succeeds();
}

#[test]
fn test_bracket_syntax_failure() {
    let scenario = TestScenario::new("[");
    let mut ucmd = scenario.ucmd();

    ucmd.args(&["1", "-eq", "2", "]"]).run().code_is(1);
}

#[test]
fn test_bracket_syntax_missing_right_bracket() {
    let scenario = TestScenario::new("[");
    let mut ucmd = scenario.ucmd();

    // Missing closing bracket takes precedence over other possible errors.
    ucmd.args(&["1", "-eq"])
        .run()
        .code_is(2)
        .stderr_is("[: missing ']'\n");
}

#[test]
fn test_bracket_syntax_help() {
    let scenario = TestScenario::new("[");
    let mut ucmd = scenario.ucmd();

    ucmd.arg("--help").succeeds().stdout_contains("Usage:");
}

#[test]
fn test_bracket_syntax_version() {
    let scenario = TestScenario::new("[");
    let mut ucmd = scenario.ucmd();

    ucmd.arg("--version")
        .succeeds()
        .stdout_matches(&r"\[ \d+\.\d+\.\d+".parse().unwrap());
}

#[test]
#[allow(non_snake_case)]
#[cfg(unix)]
fn test_file_N() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;
    scene.ucmd().args(&["-N", "regular_file"]).fails();
    // The file will have different create/modified data
    // so, test -N will return 0
    sleep(std::time::Duration::from_millis(1000));
    at.touch("regular_file");
    scene.ucmd().args(&["-N", "regular_file"]).succeeds();
}

#[test]
fn test_long_integer() {
    let scene = TestScenario::new(util_name!());
    scene
        .ucmd()
        .args(&["18446744073709551616", "-eq", "0"])
        .fails();
    scene
        .ucmd()
        .args(&["-9223372036854775809", "-ge", "18446744073709551616"])
        .fails();
    scene
        .ucmd()
        .args(&[
            "'('",
            "-9223372036854775809",
            "-ge",
            "18446744073709551616",
            "')'",
        ])
        .fails();
}

#[test]
fn test_missing_argument_after() {
    let mut ucmd = new_ucmd!();

    let result = ucmd.args(&["(", "foo"]).fails();
    result.no_stdout();
    assert_eq!(result.exit_status().code().unwrap(), 2);
    assert_eq!(
        result.stderr_str().trim(),
        "test: missing argument after 'foo'"
    );
}
