mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-05-24 23:05:52 -04:00
testsuite: filtering coverage at depth
Assert exactly which entries are/aren't transferred, deep in the tree:
filter-depth --exclude/--include precedence on files at every level, and
a -F per-directory .rsync-filter loaded from a deep dir that
applies to that subtree only (not above it).
cvs-exclude -C built-in cruft patterns (*.o, *~) at every level plus a
deep per-directory .cvsignore scoped to its subtree.
size-filter --max-size / --min-size select the right files all the way
down.
files-from-depth --files-from selects only the listed deep paths (implied
parents created); --from0 NUL-delimited; --exclude-from /
--include-from filter at depth.
(--existing / --ignore-existing are covered in delete-deep_test.py.)
Green on master and under --protocol=29/30.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
46
testsuite/cvs-exclude_test.py
Normal file
46
testsuite/cvs-exclude_test.py
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Coverage of -C / --cvs-exclude at depth.
|
||||
|
||||
-C ignores the usual CVS cruft (object files, core, editor backups, VCS dirs,
|
||||
...) and also honours a per-directory .cvsignore. Verify both the built-in
|
||||
patterns and a deep .cvsignore on a >=3-level tree.
|
||||
"""
|
||||
|
||||
from rsyncfns import (
|
||||
FROMDIR, TODIR,
|
||||
assert_exists, assert_not_exists, makepath, rmtree, run_rsync,
|
||||
)
|
||||
|
||||
src = FROMDIR
|
||||
rmtree(src)
|
||||
rmtree(TODIR)
|
||||
makepath(src / 'd1' / 'd2' / 'd3')
|
||||
|
||||
# A real file plus default-CVS-cruft at every level.
|
||||
cur = src
|
||||
for lvl in range(4):
|
||||
(cur / f'real{lvl}.c').write_text('code\n')
|
||||
(cur / f'obj{lvl}.o').write_text('obj\n') # *.o is built-in cruft
|
||||
(cur / f'back{lvl}~').write_text('backup\n') # *~ is built-in cruft
|
||||
cur = cur / f'd{lvl + 1}'
|
||||
|
||||
# A per-directory .cvsignore deep in the tree adds "*.junk" for that subtree.
|
||||
(src / 'd1' / 'd2' / '.cvsignore').write_text('*.junk\n')
|
||||
(src / 'd1' / 'd2' / 'local.junk').write_text('j\n')
|
||||
(src / 'top.junk').write_text('j\n') # not covered by that .cvsignore
|
||||
|
||||
run_rsync('-aC', f'{src}/', f'{TODIR}/')
|
||||
|
||||
cur = TODIR
|
||||
for lvl in range(4):
|
||||
assert_exists(cur / f'real{lvl}.c', label=f'-C kept real L{lvl}')
|
||||
assert_not_exists(cur / f'obj{lvl}.o', label=f'-C dropped *.o L{lvl}')
|
||||
assert_not_exists(cur / f'back{lvl}~', label=f'-C dropped *~ L{lvl}')
|
||||
cur = cur / f'd{lvl + 1}'
|
||||
|
||||
# .cvsignore is scoped to its directory subtree.
|
||||
assert_not_exists(TODIR / 'd1' / 'd2' / 'local.junk',
|
||||
label='-C deep .cvsignore applied')
|
||||
assert_exists(TODIR / 'top.junk', label='-C deep .cvsignore not applied above')
|
||||
|
||||
print("cvs-exclude: built-in patterns + deep .cvsignore honoured at depth")
|
||||
68
testsuite/files-from-depth_test.py
Normal file
68
testsuite/files-from-depth_test.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Coverage of --files-from, -0/--from0, --exclude-from, --include-from at depth.
|
||||
|
||||
--files-from selects exactly the listed source-relative paths (creating their
|
||||
implied parent dirs); --from0 makes the list NUL-delimited; --exclude-from /
|
||||
--include-from read filter patterns from a file. All resolve names several
|
||||
levels deep.
|
||||
"""
|
||||
|
||||
from rsyncfns import (
|
||||
FROMDIR, SCRATCHDIR, TODIR,
|
||||
assert_exists, assert_not_exists, assert_same, make_tree, rmtree,
|
||||
run_rsync,
|
||||
)
|
||||
|
||||
src = FROMDIR
|
||||
listed = ['d1/f1', 'd1/d2/d3/f3']
|
||||
unlisted = ['f0', 'd1/d2/f2']
|
||||
|
||||
|
||||
def seed():
|
||||
rmtree(src)
|
||||
rmtree(TODIR)
|
||||
make_tree(src, depth=3)
|
||||
|
||||
|
||||
# --- --files-from selects only the listed deep paths ------------------------
|
||||
seed()
|
||||
lf = SCRATCHDIR / 'files.lst'
|
||||
lf.write_text('\n'.join(listed) + '\n')
|
||||
run_rsync('-a', f'--files-from={lf}', f'{src}/', f'{TODIR}/')
|
||||
for rel in listed:
|
||||
assert_same(TODIR / rel, src / rel, label=f'--files-from {rel}')
|
||||
for rel in unlisted:
|
||||
assert_not_exists(TODIR / rel, label=f'--files-from excluded {rel}')
|
||||
|
||||
# --- --from0: the same list, NUL-delimited ----------------------------------
|
||||
rmtree(TODIR)
|
||||
lf0 = SCRATCHDIR / 'files0.lst'
|
||||
lf0.write_bytes(b'\0'.join(p.encode() for p in listed) + b'\0')
|
||||
run_rsync('-a', '--from0', f'--files-from={lf0}', f'{src}/', f'{TODIR}/')
|
||||
for rel in listed:
|
||||
assert_same(TODIR / rel, src / rel, label=f'--from0 {rel}')
|
||||
for rel in unlisted:
|
||||
assert_not_exists(TODIR / rel, label=f'--from0 excluded {rel}')
|
||||
|
||||
# --- --exclude-from drops matching files at depth ---------------------------
|
||||
seed()
|
||||
(src / 'a.skip').write_text('s\n')
|
||||
(src / 'd1' / 'd2' / 'a.skip').write_text('s\n')
|
||||
ef = SCRATCHDIR / 'excl.lst'
|
||||
ef.write_text('*.skip\n')
|
||||
run_rsync('-a', f'--exclude-from={ef}', f'{src}/', f'{TODIR}/')
|
||||
assert_not_exists(TODIR / 'a.skip', label='--exclude-from top')
|
||||
assert_not_exists(TODIR / 'd1' / 'd2' / 'a.skip', label='--exclude-from deep')
|
||||
assert_same(TODIR / 'd1' / 'd2' / 'f2', src / 'd1' / 'd2' / 'f2',
|
||||
label='--exclude-from kept others')
|
||||
|
||||
# --- --include-from keeps only matching files at depth ----------------------
|
||||
seed()
|
||||
(src / 'd1' / 'd2' / 'k.keepme').write_text('k\n')
|
||||
inc = SCRATCHDIR / 'inc.lst'
|
||||
inc.write_text('*/\n*.keepme\n')
|
||||
run_rsync('-a', f'--include-from={inc}', '--exclude=*', f'{src}/', f'{TODIR}/')
|
||||
assert_exists(TODIR / 'd1' / 'd2' / 'k.keepme', label='--include-from kept')
|
||||
assert_not_exists(TODIR / 'd1' / 'd2' / 'f2', label='--include-from excluded rest')
|
||||
|
||||
print("files-from-depth: --files-from/--from0/--exclude-from/--include-from at depth")
|
||||
71
testsuite/filter-depth_test.py
Normal file
71
testsuite/filter-depth_test.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Coverage of --exclude / --include / --filter / -F at depth.
|
||||
|
||||
The interesting case for the resolver restructure is a per-directory merge file
|
||||
(-F reads .rsync-filter from each directory as it descends): the rule set is
|
||||
loaded from a file several levels deep and must apply to that directory and
|
||||
below, but not above. Also check plain --exclude / --include precedence on
|
||||
files spread through the tree.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from rsyncfns import (
|
||||
FROMDIR, TODIR,
|
||||
assert_exists, assert_not_exists, assert_same, makepath, rmtree, run_rsync,
|
||||
)
|
||||
|
||||
src = FROMDIR
|
||||
|
||||
|
||||
def seed_ext():
|
||||
"""A tree with a .log and a .txt at every level."""
|
||||
rmtree(src)
|
||||
rmtree(TODIR)
|
||||
cur = src
|
||||
for lvl in range(4):
|
||||
cur.mkdir(parents=True, exist_ok=True)
|
||||
(cur / f'keep{lvl}.txt').write_text(f'txt {lvl}\n')
|
||||
(cur / f'drop{lvl}.log').write_text(f'log {lvl}\n')
|
||||
cur = cur / f'd{lvl + 1}'
|
||||
|
||||
|
||||
# --- --exclude drops matching files at every level --------------------------
|
||||
seed_ext()
|
||||
run_rsync('-a', '--exclude=*.log', f'{src}/', f'{TODIR}/')
|
||||
cur = TODIR
|
||||
for lvl in range(4):
|
||||
assert_exists(cur / f'keep{lvl}.txt', label=f'--exclude kept txt L{lvl}')
|
||||
assert_not_exists(cur / f'drop{lvl}.log', label=f'--exclude dropped log L{lvl}')
|
||||
cur = cur / f'd{lvl + 1}'
|
||||
|
||||
# --- --include before --exclude='*' keeps only .txt at every level ----------
|
||||
seed_ext()
|
||||
run_rsync('-a', '--include=*/', '--include=*.txt', '--exclude=*',
|
||||
f'{src}/', f'{TODIR}/')
|
||||
cur = TODIR
|
||||
for lvl in range(4):
|
||||
assert_exists(cur / f'keep{lvl}.txt', label=f'--include txt L{lvl}')
|
||||
assert_not_exists(cur / f'drop{lvl}.log', label=f'--include excluded log L{lvl}')
|
||||
cur = cur / f'd{lvl + 1}'
|
||||
|
||||
# --- -F per-directory merge file loaded from a deep directory ---------------
|
||||
# .rsync-filter at d1/d2 excludes "secret*" for d1/d2 and below only.
|
||||
rmtree(src)
|
||||
rmtree(TODIR)
|
||||
makepath(src / 'd1' / 'd2' / 'd3')
|
||||
for rel in ('secret.top', 'd1/secret.mid', 'd1/d2/secret.deep',
|
||||
'd1/d2/d3/secret.deeper'):
|
||||
(src / rel).write_text('x\n')
|
||||
(src / 'd1' / 'd2' / '.rsync-filter').write_text('- secret*\n')
|
||||
|
||||
run_rsync('-aF', f'{src}/', f'{TODIR}/')
|
||||
# Above the merge file: not affected.
|
||||
assert_exists(TODIR / 'secret.top', label='-F above merge dir')
|
||||
assert_exists(TODIR / 'd1' / 'secret.mid', label='-F above merge dir')
|
||||
# At and below the merge file: excluded.
|
||||
assert_not_exists(TODIR / 'd1' / 'd2' / 'secret.deep', label='-F at merge dir')
|
||||
assert_not_exists(TODIR / 'd1' / 'd2' / 'd3' / 'secret.deeper',
|
||||
label='-F below merge dir')
|
||||
|
||||
print("filter-depth: --exclude/--include precedence and -F per-dir merge at depth")
|
||||
50
testsuite/size-filter_test.py
Normal file
50
testsuite/size-filter_test.py
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Coverage of --max-size / --min-size at depth.
|
||||
|
||||
A small and a large file at every level; --max-size must transfer only the
|
||||
small ones and --min-size only the large ones, the selection holding all the
|
||||
way down the tree.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from rsyncfns import (
|
||||
FROMDIR, TODIR,
|
||||
assert_exists, assert_not_exists, make_data_file, rmtree, run_rsync,
|
||||
)
|
||||
|
||||
src = FROMDIR
|
||||
SMALL = 500
|
||||
LARGE = 5000
|
||||
|
||||
|
||||
def seed():
|
||||
rmtree(src)
|
||||
rmtree(TODIR)
|
||||
cur = src
|
||||
for lvl in range(4):
|
||||
cur.mkdir(parents=True, exist_ok=True)
|
||||
make_data_file(cur / f'small{lvl}', SMALL)
|
||||
make_data_file(cur / f'large{lvl}', LARGE)
|
||||
cur = cur / f'd{lvl + 1}'
|
||||
|
||||
|
||||
# --- --max-size keeps only the small files at every level -------------------
|
||||
seed()
|
||||
run_rsync('-a', '--max-size=1000', f'{src}/', f'{TODIR}/')
|
||||
cur = TODIR
|
||||
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}'
|
||||
|
||||
# --- --min-size keeps only the large files at every level -------------------
|
||||
seed()
|
||||
run_rsync('-a', '--min-size=1000', f'{src}/', f'{TODIR}/')
|
||||
cur = TODIR
|
||||
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}'
|
||||
|
||||
print("size-filter: --max-size / --min-size select correctly at depth")
|
||||
Reference in New Issue
Block a user