test(delete): cover per-dir timings for files-from scope, max-delete, excluded protection, refuse-delete, type conflicts

This commit is contained in:
2026-09-16 23:29:25 +02:00
parent e771cc9da6
commit a5083776da
2 changed files with 71 additions and 8 deletions
+35 -8
View File
@@ -3820,10 +3820,11 @@ class TestDeleteTiming:
assert _read_file(os.path.join(received, "sub", "deep.txt")) == b"deeply nested file\n"
assert not os.path.exists(extra), "--delete-delay did not remove the extra"
def test_early_flag_respected_when_server_refuses_delete(self, shared_server):
"""With an --allow-delete-less server the client's early timing still
completes (no deadlock on the pre-delete ack) and simply never deletes,
exactly like the plain server policy."""
@pytest.mark.parametrize("flag", ["--delete-before", "--delete-during", "--delete-delay"])
def test_early_flag_respected_when_server_refuses_delete(self, flag, shared_server):
"""With an --allow-delete-less server the client's timing still completes
(no deadlock on the pre-delete ack, no per-directory deletion) and simply
never deletes, exactly like the plain server policy."""
source = self._seed("refused")
dest = os.path.join(TEST_DATA_DIR, "deltiming_refused_dst")
clean_dir(dest)
@@ -3833,9 +3834,9 @@ class TestDeleteTiming:
extra = os.path.join(received, "extra.txt")
with open(extra, "wb") as fh:
fh.write(b"extra file")
result, _ = run_client(source, dest, flags=["--delete-before"], port=shared_server.port)
result, _ = run_client(source, dest, flags=[flag], port=shared_server.port)
assert result.returncode == 0, \
f"--delete-before against a refuse-delete server failed: {result.stderr[:300]}"
f"{flag} against a refuse-delete server failed: {result.stderr[:300]}"
assert os.path.exists(extra), "unauthorized delete removed an extra file"
@@ -3930,6 +3931,31 @@ class TestDeleteScope:
finally:
server.stop()
@pytest.mark.parametrize("mt", [False, True])
@pytest.mark.parametrize("timing", ["--delete-during", "--delete-delay"])
@pytest.mark.ci
def test_files_from_per_dir_timing_confined_to_listed_dirs(self, mt, timing):
"""The per-directory timings honor the same --files-from scope: an extra
inside a listed directory is removed, while unlisted siblings and the
receive-root extra survive."""
source, dest, received, server = self._seed(f"pd_{timing.strip('-')}_{mt}")
try:
listed = _write_rel_list(b"listed.txt\nsub/\n")
flags = ["--files-from", listed, timing] + (["--threads"] if mt else [])
result, _ = run_client(source, dest, flags=flags, port=server.port)
assert result.returncode == 0, f"{timing} delete failed: {result.stderr[:300]}"
assert not os.path.exists(os.path.join(received, "sub", "extra.txt")), \
f"{timing} did not delete the in-scope extra"
assert os.path.isfile(os.path.join(received, "sub", "x.txt"))
assert os.path.exists(os.path.join(received, "unlisted.txt")), \
f"{timing} deleted an unlisted path (data loss)"
assert os.path.exists(os.path.join(received, "other", "c.txt")), \
f"{timing} deleted an unlisted sibling directory (data loss)"
assert os.path.exists(os.path.join(received, "rootextra.txt")), \
f"{timing} deleted the receive-root extra (data loss)"
finally:
server.stop()
class TestDeleteExtraneousSymlinks:
"""#290 (3): --delete unlinks extraneous destination symlinks (never follows
@@ -4017,7 +4043,8 @@ class TestDeletePolicy:
@pytest.mark.parametrize("mt", [False, True])
@pytest.mark.parametrize("timing",
["--delete", "--delete-before", "--delete-after", "--delete-delay"])
["--delete", "--delete-before", "--delete-after", "--delete-delay",
"--delete-during"])
def test_delete_protects_excluded_by_default_and_delete_excluded_removes(self, mt, timing):
"""rsync parity: with a --delete timing the destination mirror path whose
source was excluded survives (protected by default); --delete-excluded
@@ -4101,7 +4128,7 @@ class TestDeletePolicy:
"--delete-excluded did not remove the excluded dir subtree"
@pytest.mark.parametrize("mt", [False, True])
@pytest.mark.parametrize("timing", ["--delete", "--delete-before"])
@pytest.mark.parametrize("timing", ["--delete", "--delete-before", "--delete-during"])
def test_max_delete_exceeded_deletes_up_to_cap_and_exits_25(self, mt, timing):
"""rsync parity: --max-delete=N deletes up to N extras, skips the rest and
still succeeds as a transfer, exiting 25 with a diagnostic."""