Files
rsync/testsuite/delay-updates-deep_test.py
Andrew Tridgell d6124a82a4 testsuite: cross-directory/temp/backup/dest coverage at depth
Fill the highest-restructure-risk gap: options that do two-directory / rename /
outside-tree work, asserted at >=3 levels deep with the aux tree kept outside
the main tree, and asserting the option's specific property rather than just
tree equality (which the ported tests already cover).

  alt-dest-deep  --link-dest hardlinks unchanged files (same inode), --copy-dest
                 copies (never links), --compare-dest omits unchanged files;
                 ref tree outside both src and dest.
  temp-dir       cross-dir temp->final rename at depth; temp dir left clean; a
                 missing --temp-dir fails (so the option is proven consulted).
  partial        --partial keeps the partial in the dest file; relative
                 --partial-dir stages per-directory at depth (pre-seed +
                 interrupt/resume); absolute --partial-dir writes the partial
                 outside the tree.
  inplace        --inplace keeps the destination inode across a delta update;
                 the default temp+rename path replaces it.
  append         --append completes truncated files tail-only; --append-verify
                 repairs a corrupted prefix (protocol >= 30).
  backup-deep    --suffix saves <name>S beside the new file; --backup-dir
                 relocates old files to a parallel deep tree outside the dest
                 and captures deletions under --delete.

All green on master and under --protocol=29/30.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:31:52 +10:00

63 lines
2.1 KiB
Python

#!/usr/bin/env python3
"""Property-level coverage of --delay-updates at depth.
--delay-updates writes each updated file into a per-directory staging dir
(.~tmp~) during the transfer and only renames them into place at the very end,
so an interrupted run never leaves a half-written file visible. The staging dir
sits inside each destination directory, so the staging write and the
end-of-run rename are parent-directory operations the resolver restructure
rewrites; the ported delay-updates_test.py only exercises the tree root, so
this companion drives a >=3-deep tree.
Asserts, at every level of the tree:
* a --delay-updates transfer reproduces the source and leaves no .~tmp~
staging directory behind;
* a stale file pre-planted in a deep .~tmp~ staging dir is overwritten
cleanly and the staging dir is removed.
"""
import os
from rsyncfns import (
FROMDIR, TODIR,
assert_same, make_tree, rmtree, run_rsync, test_fail, walk_dirs,
walk_files,
)
src = FROMDIR
deepdir = os.path.join('d1', 'd2', 'd3')
def no_staging_left():
leftover = [p for p in walk_dirs(TODIR) if p.name == '.~tmp~']
if leftover:
test_fail(f"--delay-updates left staging dirs behind: {leftover}")
# --- initial --delay-updates over a deep tree -------------------------------
rmtree(src)
rmtree(TODIR)
make_tree(src, depth=3, data=True, data_size=4096)
rels = [p.relative_to(src) for p in walk_files(src)]
run_rsync('-a', '--delay-updates', f'{src}/', f'{TODIR}/')
for rel in rels:
assert_same(TODIR / rel, src / rel, label=f'delay-updates initial {rel}')
no_staging_left()
# --- update every file, with a stale staging file planted deep --------------
for rel in rels:
with open(src / rel, 'ab') as f:
f.write(b'\nupdated content\n')
stage = TODIR / deepdir / '.~tmp~'
stage.mkdir(parents=True, exist_ok=True)
(stage / 'f3').write_bytes(b'stale staged junk\n') # must be overwritten
run_rsync('-a', '--delay-updates', f'{src}/', f'{TODIR}/')
for rel in rels:
assert_same(TODIR / rel, src / rel, label=f'delay-updates update {rel}')
no_staging_left()
print("delay-updates-deep: staging + clean overwrite verified at depth")