fix(parity): rsync bwlimit units, --info categories, --ignore-errors deletion semantics
- --bwlimit: faithful port of rsync 3.4.1 parse_size_arg (default KiB/s, binary K/M/G/T/P, decimal KB/MB, KiB/MiB, decimals, 0 = unlimited, 512-byte floor, (size+512)/1024 quantization). Unit tests + docs. - --info: wire del/remove/name/flist/nonreg/progress to real FastSync events in rsync's line format (deleting PATH, sender removed NAME, name lines, 'sending incremental file list', skipping non-regular file "NAME"); name no longer aliases copy; --info=progress drives the progress path and report_stats. - --ignore-errors: match rsync's default -- a source I/O error skips deletion unless --ignore-errors, while the readable tree still transfers and the run exits 23. Covers all delete timings and both send paths.
This commit is contained in:
@@ -4463,11 +4463,11 @@ class TestDeletePolicy:
|
||||
@pytest.mark.parametrize("mt", [False, True])
|
||||
@pytest.mark.setpriv
|
||||
def test_ignore_errors_keeps_deletion_active_on_scan_error(self, mt):
|
||||
"""A source I/O error (unreadable subdirectory) aborts the run so no
|
||||
deletion happens by default; --ignore-errors continues, still transfers
|
||||
the readable tree and still deletes, single-threaded and under -m. Run
|
||||
as an unprivileged user so the mode-000 directory is genuinely
|
||||
unreadable."""
|
||||
"""rsync's --ignore-errors semantics: a source I/O error (unreadable
|
||||
subdirectory) makes the run continue and transfer the readable tree, but
|
||||
the default suppresses deletion ("IO error encountered -- skipping file
|
||||
deletion"); --ignore-errors lets deletion proceed. Both exit 23. Run as
|
||||
an unprivileged user so the mode-000 directory is genuinely unreadable."""
|
||||
if os.geteuid() != 0 or shutil.which("setpriv") is None:
|
||||
pytest.skip("requires root + setpriv to drop privileges for the client")
|
||||
tag = f"ioerr_{os.getpid()}_{mt}"
|
||||
@@ -4487,11 +4487,15 @@ class TestDeletePolicy:
|
||||
try:
|
||||
os.chmod(os.path.join(source, "locked"), 0)
|
||||
|
||||
# Default: scan error aborts the run; nothing is deleted.
|
||||
# Default: the scan continues past the unreadable dir and the
|
||||
# readable tree transfers, but deletion is skipped (exit 23).
|
||||
self._write(os.path.join(received, "extra.txt"), b"extra\n")
|
||||
flags = ["--delete"] + (["--threads"] if mt else [])
|
||||
result = self._run_client_as_nobody(source, dest, server.port, flags)
|
||||
assert result.returncode != 0, "unreadable source dir did not fail the run"
|
||||
assert result.returncode == 23, \
|
||||
f"unreadable source dir should exit 23 (got {result.returncode})"
|
||||
assert os.path.exists(os.path.join(received, "top.txt")), \
|
||||
"readable tree did not transfer past the I/O error"
|
||||
assert os.path.exists(os.path.join(received, "extra.txt")), \
|
||||
"default run deleted although the scan hit an I/O error"
|
||||
|
||||
@@ -4499,6 +4503,8 @@ class TestDeletePolicy:
|
||||
self._write(os.path.join(received, "extra.txt"), b"extra\n")
|
||||
flags = ["--delete", "--ignore-errors"] + (["--threads"] if mt else [])
|
||||
result = self._run_client_as_nobody(source, dest, server.port, flags)
|
||||
assert result.returncode == 23, \
|
||||
f"--ignore-errors run should still exit 23 (got {result.returncode})"
|
||||
assert not os.path.exists(os.path.join(received, "extra.txt")), \
|
||||
f"--ignore-errors did not keep deletion active: {result.stderr[:300]}"
|
||||
assert not os.path.exists(os.path.join(received, "locked")), \
|
||||
|
||||
Reference in New Issue
Block a user