--- /dev/null
+#!/usr/bin/env python3
+"""Coverage of the comparison/skip options at depth: -c, -I, --size-only,
+--modify-window.
+
+These decide WHETHER a file is transferred. Each is checked on a file several
+levels deep using a "stealth change" (same size, same mtime, different content)
+that the default quick check deliberately skips.
+"""
+
+import os
+
+from rsyncfns import (
+ FROMDIR, TODIR,
+ assert_same, make_tree, rmtree, run_rsync, test_fail,
+)
+
+src = FROMDIR
+deep = os.path.join('d1', 'd2', 'd3', 'f3')
+
+
+def seed():
+ rmtree(src)
+ rmtree(TODIR)
+ make_tree(src, depth=3, data=True, data_size=4096)
+ run_rsync('-a', f'{src}/', f'{TODIR}/') # dest == src
+
+
+def stealth_change():
+ """Change the deep source file's content but restore the destination's
+ size+mtime, so the quick check sees them as equal."""
+ st = os.stat(TODIR / deep)
+ data = bytearray((src / deep).read_bytes())
+ data[0] ^= 0xFF # same length, new content
+ (src / deep).write_bytes(bytes(data))
+ os.utime(src / deep, (st.st_atime, st.st_mtime))
+
+
+# --- the default quick check skips a stealth change; -c and -I catch it ------
+seed()
+stealth_change()
+run_rsync('-a', f'{src}/', f'{TODIR}/')
+if (TODIR / deep).read_bytes() == (src / deep).read_bytes():
+ test_fail("default quick check unexpectedly transferred a same-size, "
+ "same-mtime change (test setup is wrong)")
+
+run_rsync('-a', '-c', f'{src}/', f'{TODIR}/')
+assert_same(TODIR / deep, src / deep, label='-c caught stealth change')
+
+seed()
+stealth_change()
+run_rsync('-a', '-I', f'{src}/', f'{TODIR}/')
+assert_same(TODIR / deep, src / deep, label='-I caught stealth change')
+
+# --- --size-only skips a same-size change even when the mtime differs --------
+def samesize_newmtime():
+ data = bytearray((src / deep).read_bytes())
+ data[0] ^= 0xFF # same size, new content
+ (src / deep).write_bytes(bytes(data))
+ st = os.stat(src / deep)
+ os.utime(src / deep, (st.st_atime, st.st_mtime + 100)) # mtime differs
+
+
+seed()
+samesize_newmtime()
+run_rsync('-a', '--size-only', f'{src}/', f'{TODIR}/')
+if (TODIR / deep).read_bytes() == (src / deep).read_bytes():
+ test_fail("--size-only transferred a same-size file (should have skipped)")
+
+# Contrast on a fresh tree: the default DOES transfer it (mtime differs).
+# (Re-seed because --size-only above updated the dest mtime to match.)
+seed()
+samesize_newmtime()
+run_rsync('-a', f'{src}/', f'{TODIR}/')
+assert_same(TODIR / deep, src / deep, label='default caught mtime change')
+
+# --- --modify-window absorbs a small mtime difference -----------------------
+# Both runs are dry-run (-ain): a real run would update the dest mtime to match
+# the source, leaving the --modify-window run nothing to absorb (vacuous).
+seed()
+st = os.stat(TODIR / deep)
+os.utime(src / deep, (st.st_atime, st.st_mtime + 1)) # 1s newer, same content
+p = run_rsync('-ain', f'{src}/', f'{TODIR}/', capture_output=True)
+if 'f3' not in p.stdout:
+ test_fail("a 1s mtime change was not itemized without --modify-window")
+p = run_rsync('-ain', '--modify-window=2', f'{src}/', f'{TODIR}/',
+ capture_output=True)
+if 'f3' in p.stdout:
+ test_fail("--modify-window=2 did not absorb a 1s mtime difference")
+
+print("compare: -c / -I / --size-only / --modify-window verified at depth")
--- /dev/null
+#!/usr/bin/env python3
+"""Breadth coverage of the algorithm-selection options at depth:
+--compress-choice / --compress-level / --skip-compress and
+--checksum-choice / --checksum-seed.
+
+Compression and checksum choice don't change the result, so each available
+algorithm is exercised for a clean, byte-identical transfer of a >=3-deep tree
+(proving the option parses, negotiates and doesn't corrupt data).
+"""
+
+import json
+
+from rsyncfns import (
+ FROMDIR, TODIR,
+ assert_same, make_tree, rmtree, run_rsync, walk_files,
+)
+
+src = FROMDIR
+vv = json.loads(run_rsync('-VV', check=True, capture_output=True).stdout)
+compressors = [a for a in vv.get('compress_list', []) if a != 'none']
+checksums = [a for a in vv.get('checksum_list', []) if a != 'none']
+
+
+def fresh():
+ rmtree(src)
+ rmtree(TODIR)
+ make_tree(src, depth=3, data=True, data_size=4096)
+ return [p.relative_to(src) for p in walk_files(src)]
+
+
+def verify(rels, label):
+ for rel in rels:
+ assert_same(TODIR / rel, src / rel, label=f'{label} {rel}')
+
+
+# --- --compress-choice for every advertised compressor ----------------------
+for algo in compressors:
+ rels = fresh()
+ run_rsync('-az', f'--compress-choice={algo}', f'{src}/', f'{TODIR}/')
+ verify(rels, f'--compress-choice={algo}')
+
+# --- --compress-level -------------------------------------------------------
+rels = fresh()
+run_rsync('-az', '--compress-level=9', f'{src}/', f'{TODIR}/')
+verify(rels, '--compress-level=9')
+
+# --- --skip-compress (the file must still arrive intact) --------------------
+rels = fresh()
+(src / 'd1' / 'd2' / 'x.gz').write_bytes(b'\x1f\x8b' + b'pseudo gzip body ' * 64)
+run_rsync('-az', '--skip-compress=gz', f'{src}/', f'{TODIR}/')
+assert_same(TODIR / 'd1' / 'd2' / 'x.gz', src / 'd1' / 'd2' / 'x.gz',
+ label='--skip-compress gz')
+
+# --- --checksum-choice for every advertised checksum ------------------------
+for algo in checksums:
+ rels = fresh()
+ run_rsync('-a', '-c', f'--checksum-choice={algo}', f'{src}/', f'{TODIR}/')
+ verify(rels, f'--checksum-choice={algo}')
+
+# --- --checksum-seed --------------------------------------------------------
+rels = fresh()
+run_rsync('-a', '-c', '--checksum-seed=12345', f'{src}/', f'{TODIR}/')
+verify(rels, '--checksum-seed')
+
+print("compress-options: compress/checksum algorithm selection verified at depth")
--- /dev/null
+#!/usr/bin/env python3
+"""Breadth coverage of the output / reporting options.
+
+These options control rsync's OUTPUT, not its path handling, so they are
+checked for the documented output shape rather than at depth:
+ --version, --help, --itemize-changes (-i), --dry-run (-n), --stats,
+ --out-format, --list-only, --quiet (-q), --progress, -h, -8.
+"""
+
+import subprocess
+
+from rsyncfns import (
+ FROMDIR, TODIR,
+ assert_not_exists, make_tree, rmtree, rsync_argv, test_fail,
+)
+
+src = FROMDIR
+
+
+def out(*args):
+ return subprocess.run(rsync_argv(*args), capture_output=True, text=True)
+
+
+# --- --version / --help -----------------------------------------------------
+p = out('--version')
+if p.returncode != 0 or 'protocol version' not in p.stdout:
+ test_fail(f"--version output unexpected:\n{p.stdout}")
+p = out('--help')
+help_txt = p.stdout + p.stderr
+if 'rsync' not in help_txt or 'Usage' not in help_txt:
+ test_fail("--help did not print usage")
+
+rmtree(src)
+rmtree(TODIR)
+make_tree(src, depth=2)
+
+# --- --itemize-changes: a new file shows the create itemization -------------
+p = out('-ai', f'{src}/', f'{TODIR}/')
+if '>f+++++++++' not in p.stdout:
+ test_fail(f"--itemize-changes missing create line:\n{p.stdout}")
+
+# --- --dry-run lists but does not create ------------------------------------
+rmtree(TODIR)
+p = out('-ain', f'{src}/', f'{TODIR}/')
+if '>f+++++++++' not in p.stdout:
+ test_fail("--dry-run itemize output missing")
+assert_not_exists(TODIR / 'f0', label='--dry-run created a file')
+
+# --- --stats prints the summary block ---------------------------------------
+rmtree(TODIR)
+p = out('-a', '--stats', f'{src}/', f'{TODIR}/')
+if 'Number of files:' not in p.stdout or 'Total file size:' not in p.stdout:
+ test_fail(f"--stats output missing expected lines:\n{p.stdout}")
+
+# --- --out-format=%n emits bare filenames -----------------------------------
+rmtree(TODIR)
+p = out('-a', '--out-format=%n', f'{src}/', f'{TODIR}/')
+if 'f0' not in p.stdout:
+ test_fail(f"--out-format=%n did not emit filenames:\n{p.stdout}")
+
+# --- --list-only lists the source without copying ---------------------------
+# Pass a destination too: without --list-only this transfer would populate
+# TODIR, so the assert_not_exists below actually proves the "without copying"
+# property rather than being vacuously true for a destination-less command.
+rmtree(TODIR)
+p = out('--list-only', '-r', f'{src}/', f'{TODIR}/')
+if 'f0' not in p.stdout:
+ test_fail(f"--list-only did not list files:\n{p.stdout}")
+assert_not_exists(TODIR / 'f0', label='--list-only copied a file')
+
+# --- --quiet suppresses normal stdout ---------------------------------------
+rmtree(TODIR)
+p = out('-a', '-q', f'{src}/', f'{TODIR}/')
+if p.stdout.strip() != '':
+ test_fail(f"--quiet produced stdout: {p.stdout!r}")
+
+# --- --progress shows a percentage ------------------------------------------
+rmtree(TODIR)
+p = out('-a', '--progress', f'{src}/', f'{TODIR}/')
+if '100%' not in p.stdout:
+ test_fail(f"--progress did not show a percentage:\n{p.stdout}")
+
+# --- -h / -8 do not break a transfer ----------------------------------------
+rmtree(TODIR)
+p = out('-a', '-h', '-8', '--stats', f'{src}/', f'{TODIR}/')
+if p.returncode != 0:
+ test_fail(f"-h/-8 broke the transfer:\n{p.stderr}")
+
+print("output-options: version/help/-i/-n/--stats/--out-format/--list-only/"
+ "-q/--progress/-h/-8 verified")