test(parity): assert max_delete invariants, empty the parity allowlist
CI / lint (pull_request) Successful in 1m40s
CI / parity-full (pull_request) Skipped
CI / sanitizers (address) (pull_request) Skipped
CI / sanitizers (undefined) (pull_request) Skipped
CI / fuzz-build (pull_request) Skipped
CI / coverage (pull_request) Skipped
CI / valgrind (pull_request) Skipped
CI / parity-fast (pull_request) Successful in 18s
CI / build-and-test (pull_request) Successful in 52s

The max_delete case allowlisted the tree aspect because the surviving extras after a partial --max-delete abort are deletion-order dependent. Under FASTSYNC_PARITY_STRICT a run where the orders coincide was reported as a stale entry (CI failure), while removing the entry made the order-dependent tree mismatch fail. Add a per-case 'compare_tree' flag: max_delete now asserts rc=25 plus the survivor count via extra_check instead of exact tree identity, so the allowlist can be empty. Burn-down reached zero.
This commit is contained in:
2026-09-18 22:28:38 +02:00
parent 596a039454
commit 3545d88905
4 changed files with 12 additions and 21 deletions
+2 -6
View File
@@ -58,12 +58,8 @@ Each entry maps a case id to the aspects that may differ (`tree`, `stdout`,
```python
CAVEATS = {
"max_delete": {
"tree": "which destination extras survive a partial --max-delete abort "
"is deletion-order dependent and unspecified; rc=25 and the "
"number of survivors match rsync. ref: RSYNC_COMPAT.md "
"`--max-delete=NUM` row.",
},
# no known residuals at present -- the burn-down reached zero
# "some_case_id": {"tree": "documented residual ... ref: RSYNC_COMPAT.md ..."},
}
```
+1 -13
View File
@@ -24,19 +24,7 @@ re-triaged when the row moves.
"""
# case id -> {aspect: "reason (ref: RSYNC_COMPAT.md ...)"}
CAVEATS = {
# --max-delete stops the extras walk part-way and exits 25 in both
# implementations; which of the remaining extras survives depends on
# deletion order, which neither tool specifies. The exit code and the
# number of survivors match (asserted implicitly by the harness's rc
# comparison and the one-for-one diff below).
"max_delete": {
"tree": "which destination extras survive a partial --max-delete abort "
"is deletion-order dependent and unspecified; rc=25 and the "
"number of survivors match rsync. ref: RSYNC_COMPAT.md "
"`--max-delete=NUM` row.",
},
}
CAVEATS = {}
# Accepted aspect names (guards against typos in this file).
ASPECTS = ("tree", "stdout", "extra", "rc")
+7 -1
View File
@@ -92,6 +92,10 @@ class Case:
# rsync receives ``src + "/"``; FastSync mirrors the path it is given, so a
# trailing-slash-sensitive case must hand FastSync the same form.
fs_src_suffix: str = ""
# Some cases have an unspecified result (e.g. which extras survive a
# partial --max-delete abort): assert the case-specific invariants via
# extra_check and skip the exact-tree comparison.
compare_tree: bool = True
ci: bool = False
ref: str = ""
@@ -422,6 +426,7 @@ def run_differential( # noqa: PLR0913 (explicit scenario parameters)
extra_check: Optional[Callable] = None,
files_from: Optional[Tuple[str, ...]] = None,
fs_src_suffix: str = "",
compare_tree: bool = True,
) -> Dict[str, object]:
"""Run one rsync/FastSync pair and return the diff aspects.
@@ -466,7 +471,7 @@ def run_differential( # noqa: PLR0913 (explicit scenario parameters)
"fastsync_rc": fs_result.returncode,
"rsync_stderr": rs.stderr,
"fastsync_stderr": fs_result.stderr or fs_result.stdout,
"tree": tree_diff(rroot, froot, _View()),
"tree": tree_diff(rroot, froot, _View()) if compare_tree else [],
"stdout": [],
"extra": [],
}
@@ -491,6 +496,7 @@ def execute_case(case: Case, server) -> Dict[str, object]:
compare_modes=case.compare_modes, compare_hardlinks=case.compare_hardlinks,
ignore_paths=case.ignore_paths, extra_check=case.extra_check,
files_from=case.files_from, fs_src_suffix=case.fs_src_suffix,
compare_tree=case.compare_tree,
)
@@ -207,7 +207,8 @@ _CASES = [
seed=seed_delete_excluded, server_args=DELETE, ref="--delete-excluded"),
H.Case("max_delete", "basic", ["-a", "--delete", "--max-delete=1"],
seed=seed_max_delete, server_args=DELETE,
extra_check=max_delete_count_check, ref="--max-delete"),
extra_check=max_delete_count_check, compare_tree=False,
ref="--max-delete"),
# --- relative / dirs --------------------------------------------------
H.Case("relative_general", "basic", ["-a", "-R"], layout=H.MIRROR_ABS,