- --delete-delay: state that the reported count advances on actual removal while --max-delete is charged at plan/snapshot time (defer_add/planned). A new differential shows rsync instead charges on actual removals and recursively removes a queued directory, so the row moves to Caveat (matrix 111/13/33) and the residual is pinned by tests. - Add a deterministic unit test (plan-time budget charge), a FastSync integration test (byte-barrier refill + --max-delete), and an rsync differential for a refilled deferred directory. - Soften the --fuzzy summary: the tree is byte-exact by design, so it is pinned by the threshold suite, not a byte-level differential. - README: describe what -m parity actually selects; fix the allowlist example to the real max_delete entry. - xdist-safe delete-timing fixture names (timing and --threads mode). - Renumber the duplicate HANDOFF item 9 to 10; add the missing final newline to test_checksum.c. - Expose ignore_errors_allows_delete and unit-test the deletion gate without a privileged source directory; update the stale deleted-count doc comment. No production behavior changes.
85 lines
3.5 KiB
Markdown
85 lines
3.5 KiB
Markdown
# Integration tests
|
|
|
|
The integration suite drives the built `build/server` and `build/client`
|
|
against local corpora. Unit tests live in `tests/` (the custom C framework);
|
|
the Python suite here covers the full transfer pipeline, transports, features,
|
|
and rsync parity.
|
|
|
|
## Running
|
|
|
|
```bash
|
|
# Full suite (excludes privilege-dependent tests on CI runners)
|
|
python3 -m pytest tests/integration/ -n 4 --dist=load -m "not setpriv"
|
|
|
|
# Fast PR subset only
|
|
python3 -m pytest tests/integration/ -n 4 --dist=load -m ci
|
|
```
|
|
|
|
The tests expect `build/server` and `build/client` (configure/build with CMake
|
|
first); `common.py` derives `BUILD_DIR` from the repository root.
|
|
|
|
## Differential rsync-parity gate
|
|
|
|
`test_differential_parity.py` runs the **same** transfer with real
|
|
`rsync 3.4.1` and with FastSync over separate destinations, then compares:
|
|
|
|
- the destination trees — relative paths, file content hashes, symlink
|
|
targets, modes (where the case is about perms), and hard-link grouping;
|
|
- the normalized stdout for output-oriented flags (`-i`,
|
|
`--out-format=...`, `--stats`), after stripping volatile fields
|
|
(timings, rates, wire byte counts) and directory-only itemize lines that
|
|
FastSync's recursive scanner documents as absent.
|
|
|
|
FastSync mirrors the absolute source path under its receive root (see
|
|
`get_dest_received_dir`); the harness normalizes that layout (and the
|
|
`-R`/`--files-from` layouts) before comparing.
|
|
|
|
```bash
|
|
# Fast subset that guards the ✅ surface on pull requests
|
|
python3 -m pytest tests/integration/test_differential_parity.py -n 4 --dist=load -m parity_ci
|
|
|
|
# Full differential case table (`_CASES`): every row is marked `parity`, and a
|
|
# case with an allowlisted residual in `parity_caveats.py` is included too.
|
|
python3 -m pytest tests/integration/test_differential_parity.py -n 4 --dist=load -m parity
|
|
```
|
|
|
|
`-m parity` selects only the `_CASES` table in this module. Differential
|
|
coverage for options outside that table (`--temp-dir`, `--delay-updates`,
|
|
`--dry-run`, `--fuzzy`, the basis-dir options, `-M` over daemon/TCP, and
|
|
receiver filter-protect) lives in dedicated modules (`test_option_parity.py`,
|
|
`test_parity_blockers.py`, `test_parity_quickwins.py`, ...) and is not part of
|
|
this gate. The suite skips cleanly when `rsync` is not installed.
|
|
|
|
## Allowlist (`parity_caveats.py`)
|
|
|
|
`parity_caveats.py` is the single data-driven allowlist of known differences.
|
|
Each entry maps a case id to the aspects that may differ (`tree`, `stdout`,
|
|
`extra`, `rc`) and cites the governing row in `RSYNC_COMPAT.md`:
|
|
|
|
```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.",
|
|
},
|
|
}
|
|
```
|
|
|
|
A differential mismatch in an aspect that is **not** listed fails the gate with
|
|
a readable tree/stdout diff.
|
|
|
|
If a case is allowlisted but now matches rsync, the gate emits a loud warning
|
|
naming the stale entry — that is the parity burn-down signal. Run with
|
|
`FASTSYNC_PARITY_STRICT=1` to make stale entries fail instead (the full CI
|
|
parity job sets this). To add a residual:
|
|
|
|
1. Reproduce it with `-m parity` and read the failure's tree/stdout diff.
|
|
2. Confirm it is a documented `⚠️`/`❌` residual (or get the `✅` row
|
|
reclassified) and cite the row.
|
|
3. Add the case id and aspect(s) to `CAVEATS`, keeping the reason concise.
|
|
|
|
Do not allowlist an undocumented divergence from a `✅` row — fix it or get the
|
|
row reclassified first.
|