Files
rsync/testsuite/hands_test.py
Andrew Tridgell 1f689ec0c2 testsuite: rewrite the shell testsuite in Python
Replace the entire shell-based testsuite with Python. runtests.py
already drove the suite (it had replaced runtests.sh earlier); this
converts all 60 test scripts from *.test shell to *_test.py and adds
testsuite/rsyncfns.py as the shared helper module -- the Python
counterpart of the now-removed rsync.fns.

runtests.py:
  * Discovers and runs both *.test and *_test.py; dispatches the
    Python tests via the same python3 that runs the harness.
  * Extends PYTHONPATH so tests can `import rsyncfns`.

testsuite/rsyncfns.py provides everything the ports need:
  * environment wiring (scratchdir / srcdir / TOOLDIR / RSYNC /
    TLS_ARGS, and HOME pointed at the per-test scratch dir);
  * result reporting -- test_fail / test_skipped / test_xfail mapping
    to the 0 / 1 / 77 / 78 exit-code convention;
  * the transfer-and-verify helpers checkit, checkdiff, verify_dirs,
    rsync_ls_lR, check_perms and the v_filt output filter;
  * fixture builders hands_setup, build_symlinks, build_rsyncd_conf,
    make_data_file, cp_p / cp_touch, makepath / rmtree.

All 60 tests are converted, including the four split-variant tests
that share one source via a Makefile-built symlink (chown/chown-fake,
devices/devices-fake, xattrs/xattrs-hlink, exclude/exclude-lsh);
Makefile.in's CHECK_SYMLINKS now points at the *_test.py names.

The dead rsync.fns shell library is removed.

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

64 lines
2.3 KiB
Python

#!/usr/bin/env python3
# Python rewrite of testsuite/hands.test.
#
# The canonical end-to-end transfer test: build a richly-populated source
# tree via hands_setup() then run a series of rsync invocations covering
# basic operation, hard links, single-file copies, --no-whole-file delta
# updates and --delete cleanup. After each run the source and destination
# tree listings must match exactly.
import os
import shutil
from rsyncfns import FROMDIR, TMPDIR, TODIR, checkit, hands_setup, run_rsync
hands_setup()
DEBUG_OPTS = "--debug=all0,deltasum0"
# 1. basic operation
print("Test basic operation:")
checkit(['-av', f'{FROMDIR}/', str(TODIR)], FROMDIR, TODIR)
# 2. hard links — link filelist into dir/ then transfer with -H so the
# receiver should recreate the link relationship.
os.link(FROMDIR / 'filelist', FROMDIR / 'dir' / 'filelist')
print("Test hard links:")
checkit(['-avH', '--bwlimit=0', DEBUG_OPTS, f'{FROMDIR}/', str(TODIR)], FROMDIR, TODIR)
# 3. one file — delete the destination 'text' and re-sync; only it should
# transfer, everything else stays uptodate.
(TODIR / 'text').unlink()
print("Test one file:")
checkit(['-avH', DEBUG_OPTS, f'{FROMDIR}/', str(TODIR)], FROMDIR, TODIR)
# 4. extra data — append to destination then re-sync with --no-whole-file so
# the rsync delta algorithm has to repair it.
with open(TODIR / 'text', 'a') as f:
f.write("extra line\n")
print("Test extra data:")
checkit(['-avH', DEBUG_OPTS, '--no-whole-file', f'{FROMDIR}/', str(TODIR)], FROMDIR, TODIR)
# 5. --delete — add a stray file on the destination and confirm --delete
# removes it.
shutil.copy(FROMDIR / 'text', TODIR / 'ThisShouldGo')
print("Test --delete:")
checkit(['--delete', '-avH', DEBUG_OPTS, f'{FROMDIR}/', str(TODIR)], FROMDIR, TODIR)
# 6. globbed copy without recursion — wipe and re-sync top-level entries by
# glob, then a final empty pass to compare listings.
os.chdir(TMPDIR)
shutil.rmtree('to', ignore_errors=True)
for entry in TMPDIR.glob('from/*dir'):
if entry.is_dir():
shutil.rmtree(entry, ignore_errors=True)
else:
entry.unlink()
# Replicate `rsync -av from/* to/` — list the from/ children explicitly.
sources = sorted(str(p) for p in (TMPDIR / 'from').iterdir())
run_rsync('-av', *sources, 'to/')
checkit(['-av', '--exclude=*', 'from/', 'to/'], FROMDIR, TODIR)