Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions tests/by-util/test_cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading