diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index e8f6765cb..c5d1f9390 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -4101,6 +4101,110 @@ fn test_cp_dest_no_permissions() { .stderr_contains("denied"); } +/// Test readonly destination behavior with reflink options +#[cfg(any(target_os = "linux", target_os = "macos"))] +#[test] +fn test_cp_readonly_dest_with_reflink() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("source.txt", "source content"); + at.write("readonly_dest_auto.txt", "original content"); + at.write("readonly_dest_always.txt", "original content"); + at.set_readonly("readonly_dest_auto.txt"); + at.set_readonly("readonly_dest_always.txt"); + + // Test reflink=auto + ts.ucmd() + .args(&["--reflink=auto", "source.txt", "readonly_dest_auto.txt"]) + .fails() + .stderr_contains("readonly_dest_auto.txt"); + + // Test reflink=always + ts.ucmd() + .args(&["--reflink=always", "source.txt", "readonly_dest_always.txt"]) + .fails() + .stderr_contains("readonly_dest_always.txt"); + + assert_eq!(at.read("readonly_dest_auto.txt"), "original content"); + assert_eq!(at.read("readonly_dest_always.txt"), "original content"); +} + +/// Test readonly destination behavior in recursive directory copy +#[test] +fn test_cp_readonly_dest_recursive() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("source_dir"); + at.mkdir("dest_dir"); + at.write("source_dir/file.txt", "source content"); + at.write("dest_dir/file.txt", "original content"); + at.set_readonly("dest_dir/file.txt"); + + ts.ucmd().args(&["-r", "source_dir", "dest_dir"]).succeeds(); + + assert_eq!(at.read("dest_dir/file.txt"), "original content"); +} + +/// Test copying to readonly file when another file exists +#[test] +fn test_cp_readonly_dest_with_existing_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("source.txt", "source content"); + at.write("readonly_dest.txt", "original content"); + at.write("other_file.txt", "other content"); + at.set_readonly("readonly_dest.txt"); + + ts.ucmd() + .args(&["source.txt", "readonly_dest.txt"]) + .fails() + .stderr_contains("readonly_dest.txt") + .stderr_contains("denied"); + + assert_eq!(at.read("readonly_dest.txt"), "original content"); + assert_eq!(at.read("other_file.txt"), "other content"); +} + +/// Test readonly source file (should work fine) +#[test] +fn test_cp_readonly_source() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("readonly_source.txt", "source content"); + at.write("dest.txt", "dest content"); + at.set_readonly("readonly_source.txt"); + + ts.ucmd() + .args(&["readonly_source.txt", "dest.txt"]) + .succeeds(); + + assert_eq!(at.read("dest.txt"), "source content"); +} + +/// Test readonly source and destination (should fail) +#[test] +fn test_cp_readonly_source_and_dest() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("readonly_source.txt", "source content"); + at.write("readonly_dest.txt", "original content"); + at.set_readonly("readonly_source.txt"); + at.set_readonly("readonly_dest.txt"); + + ts.ucmd() + .args(&["readonly_source.txt", "readonly_dest.txt"]) + .fails() + .stderr_contains("readonly_dest.txt") + .stderr_contains("denied"); + + assert_eq!(at.read("readonly_dest.txt"), "original content"); +} + #[test] #[cfg(all(unix, not(target_os = "freebsd"), not(target_os = "openbsd")))] fn test_cp_attributes_only() {