Skip to content

Commit 415d01c

Browse files
authored
cp: add readonly file regression tests (#9045)
* feat: add comprehensive readonly file regression tests for cp - Add 10 new test functions covering readonly destination behavior - Tests cover basic readonly copying, flag combinations, and edge cases - Include macOS-specific clonefile behavior tests - Ensure readonly file protection from PR #5261 cannot regress - Tests provide evidence for closing issue #5349 * perf: optimize readonly regression tests with batched I/O operations - Reduce file I/O overhead by batching file operations - Consolidate setup operations to minimize system calls - Improve test execution time from 0.44s to 0.27s (38% improvement) - Maintain comprehensive test coverage for readonly file behavior * fix: remove duplicate tests and trivial comments per PR feedback - Remove test_cp_readonly_dest_regression (duplicate of test_cp_dest_no_permissions) - Remove test_cp_readonly_dest_with_force (duplicate of test_cp_arg_force) - Remove test_cp_readonly_dest_with_remove_destination (duplicate of test_cp_arg_remove_destination) - Remove test_cp_macos_clonefile_readonly (duplicate of test_cp_existing_target) - Remove test_cp_normal_copy_still_works (duplicate of test_cp_existing_target) - Remove trivial performance comments from readonly tests - Keep existing proven tests per maintainer preferences - Keep unique readonly tests that provide additional coverage
1 parent ee9bd8b commit 415d01c

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed

tests/by-util/test_cp.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4101,6 +4101,110 @@ fn test_cp_dest_no_permissions() {
41014101
.stderr_contains("denied");
41024102
}
41034103

4104+
/// Test readonly destination behavior with reflink options
4105+
#[cfg(any(target_os = "linux", target_os = "macos"))]
4106+
#[test]
4107+
fn test_cp_readonly_dest_with_reflink() {
4108+
let ts = TestScenario::new(util_name!());
4109+
let at = &ts.fixtures;
4110+
4111+
at.write("source.txt", "source content");
4112+
at.write("readonly_dest_auto.txt", "original content");
4113+
at.write("readonly_dest_always.txt", "original content");
4114+
at.set_readonly("readonly_dest_auto.txt");
4115+
at.set_readonly("readonly_dest_always.txt");
4116+
4117+
// Test reflink=auto
4118+
ts.ucmd()
4119+
.args(&["--reflink=auto", "source.txt", "readonly_dest_auto.txt"])
4120+
.fails()
4121+
.stderr_contains("readonly_dest_auto.txt");
4122+
4123+
// Test reflink=always
4124+
ts.ucmd()
4125+
.args(&["--reflink=always", "source.txt", "readonly_dest_always.txt"])
4126+
.fails()
4127+
.stderr_contains("readonly_dest_always.txt");
4128+
4129+
assert_eq!(at.read("readonly_dest_auto.txt"), "original content");
4130+
assert_eq!(at.read("readonly_dest_always.txt"), "original content");
4131+
}
4132+
4133+
/// Test readonly destination behavior in recursive directory copy
4134+
#[test]
4135+
fn test_cp_readonly_dest_recursive() {
4136+
let ts = TestScenario::new(util_name!());
4137+
let at = &ts.fixtures;
4138+
4139+
at.mkdir("source_dir");
4140+
at.mkdir("dest_dir");
4141+
at.write("source_dir/file.txt", "source content");
4142+
at.write("dest_dir/file.txt", "original content");
4143+
at.set_readonly("dest_dir/file.txt");
4144+
4145+
ts.ucmd().args(&["-r", "source_dir", "dest_dir"]).succeeds();
4146+
4147+
assert_eq!(at.read("dest_dir/file.txt"), "original content");
4148+
}
4149+
4150+
/// Test copying to readonly file when another file exists
4151+
#[test]
4152+
fn test_cp_readonly_dest_with_existing_file() {
4153+
let ts = TestScenario::new(util_name!());
4154+
let at = &ts.fixtures;
4155+
4156+
at.write("source.txt", "source content");
4157+
at.write("readonly_dest.txt", "original content");
4158+
at.write("other_file.txt", "other content");
4159+
at.set_readonly("readonly_dest.txt");
4160+
4161+
ts.ucmd()
4162+
.args(&["source.txt", "readonly_dest.txt"])
4163+
.fails()
4164+
.stderr_contains("readonly_dest.txt")
4165+
.stderr_contains("denied");
4166+
4167+
assert_eq!(at.read("readonly_dest.txt"), "original content");
4168+
assert_eq!(at.read("other_file.txt"), "other content");
4169+
}
4170+
4171+
/// Test readonly source file (should work fine)
4172+
#[test]
4173+
fn test_cp_readonly_source() {
4174+
let ts = TestScenario::new(util_name!());
4175+
let at = &ts.fixtures;
4176+
4177+
at.write("readonly_source.txt", "source content");
4178+
at.write("dest.txt", "dest content");
4179+
at.set_readonly("readonly_source.txt");
4180+
4181+
ts.ucmd()
4182+
.args(&["readonly_source.txt", "dest.txt"])
4183+
.succeeds();
4184+
4185+
assert_eq!(at.read("dest.txt"), "source content");
4186+
}
4187+
4188+
/// Test readonly source and destination (should fail)
4189+
#[test]
4190+
fn test_cp_readonly_source_and_dest() {
4191+
let ts = TestScenario::new(util_name!());
4192+
let at = &ts.fixtures;
4193+
4194+
at.write("readonly_source.txt", "source content");
4195+
at.write("readonly_dest.txt", "original content");
4196+
at.set_readonly("readonly_source.txt");
4197+
at.set_readonly("readonly_dest.txt");
4198+
4199+
ts.ucmd()
4200+
.args(&["readonly_source.txt", "readonly_dest.txt"])
4201+
.fails()
4202+
.stderr_contains("readonly_dest.txt")
4203+
.stderr_contains("denied");
4204+
4205+
assert_eq!(at.read("readonly_dest.txt"), "original content");
4206+
}
4207+
41044208
#[test]
41054209
#[cfg(all(unix, not(target_os = "freebsd"), not(target_os = "openbsd")))]
41064210
fn test_cp_attributes_only() {

0 commit comments

Comments
 (0)