]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
testsuite: add content and return-code assertions
authorAndrew Tridgell <andrew@tridgell.net>
Sun, 24 May 2026 22:14:04 +0000 (08:14 +1000)
committerAndrew Tridgell <andrew@tridgell.net>
Mon, 25 May 2026 21:43:00 +0000 (07:43 +1000)
Several tests proved only that rsync exited cleanly (or that a file merely
exists), so a no-op/short transfer would pass:

  protected-regular  compare the dst bytes to the source after --inplace.
  00-hello           re-assert one/two were copied on the RSYNC_OLD_ARGS=1
                     env-var path (the explicit --old-args case already did).
  missing            check the dry-run's exit status in test 1.
  mkpath             compare transferred bytes (not just existence) and add a
                     negative control: a transfer WITHOUT --mkpath must fail
                     and create no intermediate path.
  size-filter        compare each kept file's content to its source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
testsuite/00-hello_test.py
testsuite/missing_test.py
testsuite/mkpath_test.py
testsuite/protected-regular_test.py
testsuite/size-filter_test.py

index 22b2e0e68d4207b73393f2886c7c8e36417619d1..312f35394249bbd6ff9ab19de9928f75c0c159d2 100644 (file)
@@ -97,3 +97,8 @@ try:
     )
 finally:
     os.chdir(saved)
+
+# check=True only proves a zero exit; confirm the env-var path actually copied
+# both files (as the explicit --old-args case above does).
+if not (TODIR / 'one').is_file() or not (TODIR / 'two').is_file():
+    test_fail("RSYNC_OLD_ARGS=1 copy of 'one two' failed")
index 2d0a8ef9f49ec1c43d9395484cc4d3163396eb3c..c0a600fe35406f9b87dac9b549194776d6758419 100644 (file)
@@ -31,6 +31,8 @@ def run_capture(*args):
 out_path = TMPDIR / 'out1'
 proc = run_capture('-n', '-r', '--ignore-non-existing', '-vv',
                    f'{FROMDIR}/', f'{TODIR}/')
+if proc.returncode != 0:
+    test_fail(f"test 1 failed: dry-run errored (rc={proc.returncode})")
 out_path.write_text(proc.stdout)
 for line in proc.stdout.splitlines():
     if 'not creating new' in line and 'subdir/file' in line:
index e257d67a2be6ad9a246d7cbdfe171f7ad1f61c7a..f72cf986c2606f1c10df7053445f8d1bedd57f79 100644 (file)
@@ -4,6 +4,7 @@
 # Test the rsync --mkpath option: it should create any missing intermediate
 # destination directories rather than erroring out.
 
+import filecmp
 import os
 import shutil
 from pathlib import Path
@@ -25,11 +26,20 @@ os.chdir(TMPDIR)
 deep_dir = Path('to/foo/bar/baz/down/deep')
 
 
-def assert_file(path: Path, label: str) -> None:
+def assert_file(path: Path, label: str, src: str = 'from/text') -> None:
     if not path.is_file():
         test_fail(f"{label}: {path} not found")
+    if not filecmp.cmp(path, src, shallow=False):
+        test_fail(f"{label}: {path} content differs from {src}")
 
 
+# Negative control: without --mkpath, a missing intermediate path must fail and
+# create nothing -- otherwise the --mkpath successes below prove nothing.
+rmtree('to/foo')
+proc = run_rsync('-ai', 'from/text', str(deep_dir / 'new'), check=False)
+if proc.returncode == 0 or (deep_dir / 'new').exists():
+    test_fail("a transfer WITHOUT --mkpath created the missing intermediate path")
+
 # Create several levels of dest dir (file destination — final component
 # is the new filename).
 run_rsync('-aiv', '--mkpath', 'from/text', str(deep_dir / 'new'))
@@ -53,7 +63,8 @@ rmtree('to/foo')
 
 # Multiple source args (whole directory) — bare dest name.
 run_rsync('-aiv', '--mkpath', 'from/', str(deep_dir))
-assert_file(deep_dir / 'extra', "'extra' file in deep dir (multi-source, no trailing slash)")
+assert_file(deep_dir / 'extra', "'extra' file in deep dir (multi-source, no trailing slash)",
+            src='from/extra')
 rmtree('to/foo')
 
 # Multiple source args (whole directory) — dest with trailing slash.
index f3e0485f0a9774385e2b48c62b724b6baa4c937a..b7d9825822939e0451f5e14e031b25ddfd356038 100644 (file)
@@ -12,7 +12,7 @@ import subprocess
 import sys
 from pathlib import Path
 
-from rsyncfns import TMPDIR, run_rsync, test_skipped
+from rsyncfns import TMPDIR, run_rsync, test_fail, test_skipped
 
 
 pr_path = Path('/proc/sys/fs/protected_regular')
@@ -71,3 +71,10 @@ print(f"Contents of {workdir}:")
 subprocess.run(['ls', '-al', str(workdir)])
 
 run_rsync('--inplace', str(workdir / 'src'), str(workdir / 'dst'))
+
+# A zero exit isn't enough: confirm --inplace actually wrote the source bytes
+# into the protected destination (a no-op/short write would also exit 0).
+dst_content = (workdir / 'dst').read_text()
+if dst_content != "Source\n":
+    test_fail(f"--inplace did not write the source content into the protected "
+              f"dst: got {dst_content!r}")
index ce52868938cc07ba2133905c52b21f8405d32878..4fa3a5007fab25f7bbb3bff801102d643049d41d 100644 (file)
@@ -10,7 +10,7 @@ import os
 
 from rsyncfns import (
     FROMDIR, TODIR,
-    assert_exists, assert_not_exists, make_data_file, rmtree, run_rsync,
+    assert_not_exists, assert_same, make_data_file, rmtree, run_rsync,
 )
 
 src = FROMDIR
@@ -30,21 +30,25 @@ def seed():
 
 
 # --- --max-size keeps only the small files at every level -------------------
+# Compare content (not just existence) so a kept file is proven to be the
+# transferred source, not an empty/stale placeholder.
 seed()
 run_rsync('-a', '--max-size=1000', f'{src}/', f'{TODIR}/')
-cur = TODIR
+dcur, scur = TODIR, src
 for lvl in range(4):
-    assert_exists(cur / f'small{lvl}', label=f'--max-size kept small L{lvl}')
-    assert_not_exists(cur / f'large{lvl}', label=f'--max-size dropped large L{lvl}')
-    cur = cur / f'd{lvl + 1}'
+    assert_same(dcur / f'small{lvl}', scur / f'small{lvl}',
+                label=f'--max-size kept small L{lvl}')
+    assert_not_exists(dcur / f'large{lvl}', label=f'--max-size dropped large L{lvl}')
+    dcur, scur = dcur / f'd{lvl + 1}', scur / f'd{lvl + 1}'
 
 # --- --min-size keeps only the large files at every level -------------------
 seed()
 run_rsync('-a', '--min-size=1000', f'{src}/', f'{TODIR}/')
-cur = TODIR
+dcur, scur = TODIR, src
 for lvl in range(4):
-    assert_exists(cur / f'large{lvl}', label=f'--min-size kept large L{lvl}')
-    assert_not_exists(cur / f'small{lvl}', label=f'--min-size dropped small L{lvl}')
-    cur = cur / f'd{lvl + 1}'
+    assert_same(dcur / f'large{lvl}', scur / f'large{lvl}',
+                label=f'--min-size kept large L{lvl}')
+    assert_not_exists(dcur / f'small{lvl}', label=f'--min-size dropped small L{lvl}')
+    dcur, scur = dcur / f'd{lvl + 1}', scur / f'd{lvl + 1}'
 
 print("size-filter: --max-size / --min-size select correctly at depth")