// This file is part of the uutils coreutils package. // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // // spell-checker:ignore regfile parentdir use uutests::util::{TestScenario, get_root_path}; use uutests::{at_and_ucmd, new_ucmd, path_concat, util_name}; static GIBBERISH: &str = "supercalifragilisticexpialidocious"; #[cfg(not(windows))] static NOT_A_DIRECTORY: &str = "Not a directory"; #[cfg(windows)] static NOT_A_DIRECTORY: &str = "The directory name is invalid."; #[test] fn test_no_args() { new_ucmd!() .fails_with_code(1) .no_stdout() .stderr_contains("readlink: missing operand"); } #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails_with_code(1); } #[test] fn test_resolve() { let (at, mut ucmd) = at_and_ucmd!(); at.touch("foo"); at.symlink_file("foo", "bar"); ucmd.arg("bar").succeeds().stdout_contains("foo\n"); } #[test] fn test_canonicalize() { let (at, mut ucmd) = at_and_ucmd!(); let actual = ucmd.arg("-f").arg(".").succeeds().stdout_move_str(); let expect = at.root_dir_resolved() + "\n"; println!("actual: {actual:?}"); println!("expect: {expect:?}"); assert_eq!(actual, expect); } #[test] fn test_canonicalize_existing() { let (at, mut ucmd) = at_and_ucmd!(); let actual = ucmd.arg("-e").arg(".").succeeds().stdout_move_str(); let expect = at.root_dir_resolved() + "\n"; println!("actual: {actual:?}"); println!("expect: {expect:?}"); assert_eq!(actual, expect); } #[test] fn test_canonicalize_missing() { let (at, mut ucmd) = at_and_ucmd!(); let actual = ucmd.arg("-m").arg(GIBBERISH).succeeds().stdout_move_str(); let expect = path_concat!(at.root_dir_resolved(), GIBBERISH) + "\n"; println!("actual: {actual:?}"); println!("expect: {expect:?}"); assert_eq!(actual, expect); } #[test] #[cfg(unix)] fn test_canonicalize_symlink_before_parentdir() { // GNU readlink follows the symlink first and only then evaluates `..`. // Logical resolution would collapse `link/..` up front and return the current directory instead. let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("real"); at.mkdir("real/sub"); at.relative_symlink_dir("real/sub", "link"); let actual = ucmd.args(&["-f", "link/.."]).succeeds().stdout_move_str(); let expect = format!("{}/real\n", at.root_dir_resolved()); assert_eq!(actual, expect); } #[test] fn test_long_redirection_to_current_dir() { let (at, mut ucmd) = at_and_ucmd!(); // Create a 256-character path to current directory let dir = path_concat!(".", ..128); let actual = ucmd .arg("-n") .arg("-m") .arg(dir) .succeeds() .stdout_move_str(); let expect = at.root_dir_resolved(); println!("actual: {actual:?}"); println!("expect: {expect:?}"); assert_eq!(actual, expect); } #[test] fn test_long_redirection_to_root() { // Create a 255-character path to root let dir = path_concat!("..", ..85); let actual = new_ucmd!() .arg("-n") .arg("-m") .arg(dir) .succeeds() .stdout_move_str(); let expect = get_root_path(); println!("actual: {actual:?}"); println!("expect: {expect:?}"); assert_eq!(actual, expect); } #[test] fn test_symlink_to_itself_verbose() { let (at, mut ucmd) = at_and_ucmd!(); at.relative_symlink_file("a", "a"); ucmd.args(&["-ev", "a"]) .fails_with_code(1) .stderr_contains("Too many levels of symbolic links"); } #[test] #[cfg(not(windows))] fn test_posixly_correct_regular_file() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("regfile"); scene .ucmd() .env("POSIXLY_CORRECT", "1") .arg("regfile") .fails_with_code(1) .stderr_contains("Invalid argument") .no_stdout(); // GNU behavior is to ignore -s and -q when POSIXLY_CORRECT is set. scene .ucmd() .env("POSIXLY_CORRECT", "1") .args(&["-s", "regfile"]) .fails_with_code(1) .stderr_contains("Invalid argument") .no_stdout(); scene .ucmd() .env("POSIXLY_CORRECT", "1") .args(&["-q", "regfile"]) .fails_with_code(1) .stderr_contains("Invalid argument") .no_stdout(); } #[test] fn test_trailing_slash_regular_file() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("regfile"); scene .ucmd() .args(&["-ev", "./regfile/"]) .fails_with_code(1) .stderr_contains(NOT_A_DIRECTORY) .no_stdout(); scene .ucmd() .args(&["-e", "./regfile"]) .succeeds() .stdout_contains("regfile"); } #[test] fn test_trailing_slash_symlink_to_regular_file() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("regfile"); at.relative_symlink_file("regfile", "link"); scene .ucmd() .args(&["-ev", "./link/"]) .fails_with_code(1) .stderr_contains(NOT_A_DIRECTORY) .no_stdout(); scene .ucmd() .args(&["-e", "./link"]) .succeeds() .stdout_contains("regfile"); scene .ucmd() .args(&["-e", "./link/more"]) .fails_with_code(1) .no_stdout(); } #[test] fn test_trailing_slash_directory() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir("directory"); for query in ["./directory", "./directory/"] { scene .ucmd() .args(&["-e", query]) .succeeds() .stdout_contains("directory"); } } #[test] fn test_trailing_slash_symlink_to_directory() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir("directory"); at.relative_symlink_dir("directory", "link"); for query in ["./link", "./link/"] { scene .ucmd() .args(&["-e", query]) .succeeds() .stdout_contains("directory"); } scene .ucmd() .args(&["-ev", "./link/more"]) .fails_with_code(1) .stderr_contains("No such file or directory") .no_stdout(); } #[test] fn test_trailing_slash_symlink_to_missing() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir("subdir"); at.relative_symlink_file("missing", "link"); at.relative_symlink_file("subdir/missing", "link2"); for query in [ "missing", "./missing/", "link", "./link/", "link/more", "link2", "./link2/", "link2/more", ] { scene .ucmd() .args(&["-ev", query]) .fails_with_code(1) .stderr_contains("No such file or directory") .no_stdout(); } } #[test] fn test_canonicalize_trailing_slash_regfile() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("regfile"); at.relative_symlink_file("regfile", "link1"); for name in ["regfile", "link1"] { scene .ucmd() .args(&["-f", name]) .succeeds() .stdout_contains("regfile"); scene .ucmd() .args(&["-fv", &format!("./{name}/")]) .fails_with_code(1) .stderr_contains(NOT_A_DIRECTORY) .no_stdout(); scene .ucmd() .args(&["-fv", &format!("{name}/more")]) .fails_with_code(1) .stderr_contains(NOT_A_DIRECTORY) .no_stdout(); scene .ucmd() .args(&["-fv", &format!("./{name}/more/")]) .fails_with_code(1) .stderr_contains(NOT_A_DIRECTORY) .no_stdout(); } } #[test] fn test_canonicalize_trailing_slash_subdir() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir("subdir"); at.relative_symlink_dir("subdir", "link2"); for name in ["subdir", "link2"] { scene .ucmd() .args(&["-f", name]) .succeeds() .stdout_contains("subdir"); scene .ucmd() .args(&["-f", &format!("./{name}/")]) .succeeds() .stdout_contains("subdir"); scene .ucmd() .args(&["-f", &format!("{name}/more")]) .succeeds() .stdout_contains(path_concat!("subdir", "more")); scene .ucmd() .args(&["-f", &format!("./{name}/more/")]) .succeeds() .stdout_contains(path_concat!("subdir", "more")); scene .ucmd() .args(&["-f", &format!("{name}/more/more2")]) .fails_with_code(1) .no_stdout(); scene .ucmd() .args(&["-f", &format!("./{name}/more/more2/")]) .fails_with_code(1) .no_stdout(); } } #[test] fn test_canonicalize_trailing_slash_missing() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.relative_symlink_file("missing", "link3"); for name in ["missing", "link3"] { scene .ucmd() .args(&["-f", name]) .succeeds() .stdout_contains("missing"); scene .ucmd() .args(&["-f", &format!("./{name}/")]) .succeeds() .stdout_contains("missing"); scene .ucmd() .args(&["-f", &format!("{name}/more")]) .fails_with_code(1) .no_stdout(); scene .ucmd() .args(&["-f", &format!("./{name}/more/")]) .fails_with_code(1) .no_stdout(); } } #[test] fn test_canonicalize_trailing_slash_subdir_missing() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir("subdir"); at.relative_symlink_file("subdir/missing", "link4"); for query in ["link4", "./link4/"] { scene .ucmd() .args(&["-f", query]) .succeeds() .stdout_contains(path_concat!("subdir", "missing")); } for query in ["link4/more", "./link4/more/"] { scene .ucmd() .args(&["-f", query]) .fails_with_code(1) .no_stdout(); } } #[test] fn test_canonicalize_trailing_slash_symlink_loop() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.relative_symlink_file("link5", "link5"); for query in ["link5", "./link5/", "link5/more", "./link5/more/"] { scene .ucmd() .args(&["-f", query]) .fails_with_code(1) .no_stdout(); } } #[test] #[cfg(not(windows))] fn test_delimiters() { new_ucmd!() .args(&["--zero", "-n", "-m", "/a"]) .succeeds() .stdout_only("/a"); new_ucmd!() .args(&["-n", "-m", "/a"]) .succeeds() .stdout_only("/a"); new_ucmd!() .args(&["--zero", "-m", "/a"]) .succeeds() .stdout_only("/a\0"); new_ucmd!() .args(&["-m", "/a"]) .succeeds() .stdout_only("/a\n"); new_ucmd!() .args(&["--zero", "-n", "-m", "/a", "/a"]) .succeeds() .stderr_contains("ignoring --no-newline with multiple arguments") .stdout_is("/a\0/a\0"); new_ucmd!() .args(&["-n", "-m", "/a", "/a"]) .succeeds() .stderr_contains("ignoring --no-newline with multiple arguments") .stdout_is("/a\n/a\n"); } #[test] #[cfg(target_os = "linux")] fn test_readlink_non_utf8_paths() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let file_name = "target_file"; at.touch(file_name); let non_utf8_bytes = b"symlink_\xFF\xFE"; let non_utf8_name = OsStr::from_bytes(non_utf8_bytes); std::os::unix::fs::symlink(at.plus_as_string(file_name), at.plus(non_utf8_name)).unwrap(); // Test that readlink handles non-UTF-8 symlink names without crashing let result = scene.ucmd().arg(non_utf8_name).succeeds(); let output = result.stdout_str_lossy(); assert!(output.contains(file_name)); } #[test] fn test_verbose_or_silent() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("regfile"); scene .ucmd() .arg("regfile") .fails_with_code(1) .no_stderr() .no_stdout(); scene .ucmd() .args(&["-v", "regfile"]) .fails_with_code(1) .stderr_contains( #[cfg(not(windows))] "Invalid argument", #[cfg(windows)] "regfile: The file or directory is not a reparse point.", ) .no_stdout(); scene .ucmd() .args(&["-vs", "regfile"]) .fails_with_code(1) .no_stderr() .no_stdout(); scene .ucmd() .args(&["-sv", "regfile"]) .fails_with_code(1) .stderr_contains( #[cfg(not(windows))] "Invalid argument", #[cfg(windows)] "regfile: The file or directory is not a reparse point.", ) .no_stdout(); }