mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-05-24 23:05:52 -04:00
testsuite: cover more path/file-operation code (syscall.c, util1.c, delete.c)
Target previously-uncovered functions in the path/file-operation files the
resolver restructure touches, confirmed hit under coverage:
preallocate --preallocate (syscall.c do_fallocate) and sparse hole-punching
via --preallocate --sparse and --inplace --sparse (do_punch_hole),
on a file several levels deep.
fuzzy-basis --fuzzy basis selection with similar-named candidates and no
exact match, so the generator scores them (util1.c fuzzy_distance).
delete-deep add a --backup --delete case so removing an extraneous
backup-suffixed file consults delete.c is_backup_file.
preallocate probes --preallocate support up front and skips where it is
unavailable: macOS, the *BSDs and Solaris build without fallocate/posix_fallocate
(and FALLOC_FL_PUNCH_HOLE is Linux-only), and reject the option outright. It runs
on Linux and Cygwin. fuzzy-basis and delete-deep are plain local transfers with
no skips. All 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:
2
.github/workflows/macos-build.yml
vendored
2
.github/workflows/macos-build.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
# chown-fake / devices-fake / xattrs / xattrs-hlink now RUN on macOS
|
||||
# (rsyncfns.py drives xattrs via the `xattr` command), verified on a
|
||||
# real macOS host, so they're no longer in the skip set.
|
||||
run: sudo RSYNC_EXPECT_SKIPPED=acls-default,acls-depth,chmod-temp-dir,daemon-access-ip,daemon-chroot-acl,dir-sgid,open-noatime,protected-regular,proxy-response-line-too-long,simd-checksum,sparse make check
|
||||
run: sudo RSYNC_EXPECT_SKIPPED=acls-default,acls-depth,chmod-temp-dir,daemon-access-ip,daemon-chroot-acl,dir-sgid,open-noatime,preallocate,protected-regular,proxy-response-line-too-long,simd-checksum,sparse make check
|
||||
- name: check (TCP daemon transport)
|
||||
# Second run with daemon tests over a real loopback rsyncd; the default
|
||||
# 'make check' above uses the secure stdio-pipe transport.
|
||||
|
||||
@@ -12,8 +12,8 @@ import os
|
||||
|
||||
from rsyncfns import (
|
||||
FROMDIR, TODIR,
|
||||
assert_not_exists, assert_same, make_tree, makepath, rmtree, run_rsync,
|
||||
test_fail, walk_files,
|
||||
assert_exists, assert_not_exists, assert_same, make_tree, makepath, rmtree,
|
||||
run_rsync, test_fail, walk_files,
|
||||
)
|
||||
|
||||
src = FROMDIR
|
||||
@@ -82,4 +82,26 @@ if (TODIR / 'd1' / 'f1').read_text() != "KEEP THIS\n":
|
||||
test_fail("--ignore-existing overwrote an existing deep file")
|
||||
assert_same(TODIR / 'f0', src / 'f0', label='--ignore-existing created new file')
|
||||
|
||||
print("delete-deep: delete family, max-delete, existing/ignore-existing at depth")
|
||||
# --- --backup --delete consults is_backup_file -------------------------------
|
||||
# Under --backup with no --backup-dir, an extraneous file is backed up to
|
||||
# <name>~ before removal, but a name that already ends in the backup suffix is
|
||||
# unlinked directly rather than re-backed-up to <name>~~ (delete.c
|
||||
# is_backup_file). rsync auto-protects *~ from deletion, so without an explicit
|
||||
# "risk" rule the suffixed file is never even a deletion candidate and that
|
||||
# branch is unreachable; the R rule un-protects it so the branch actually runs.
|
||||
rels = seed_src()
|
||||
fresh_dest()
|
||||
d = TODIR / 'd1' / 'd2'
|
||||
(d / 'plain').write_text("extraneous\n") # normal -> backed up to plain~
|
||||
(d / 'stale~').write_text("already a backup\n") # suffixed -> unlinked, no stale~~
|
||||
run_rsync('-a', '-b', '--delete', '--filter=R *~', f'{src}/', f'{TODIR}/')
|
||||
assert_not_exists(d / 'plain', label='--backup --delete removed extraneous')
|
||||
assert_exists(d / 'plain~', label='--backup backed up the extraneous file')
|
||||
assert_not_exists(d / 'stale~', label='--backup --delete removed suffixed orphan')
|
||||
assert_not_exists(d / 'stale~~',
|
||||
label='is_backup_file: already-suffixed file unlinked, not re-backed-up')
|
||||
for rel in rels:
|
||||
assert_same(TODIR / rel, src / rel, label=f'--backup --delete kept {rel}')
|
||||
|
||||
print("delete-deep: delete family, max-delete, existing/ignore-existing, "
|
||||
"backup-delete at depth")
|
||||
|
||||
37
testsuite/fuzzy-basis_test.py
Normal file
37
testsuite/fuzzy-basis_test.py
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Coverage of --fuzzy basis selection scoring (util1.c fuzzy_distance).
|
||||
|
||||
When the destination has no exact match for a source file, --fuzzy makes the
|
||||
generator score the same-directory candidates by name similarity (fuzzy_distance)
|
||||
and use the closest as the delta basis. Set this up at depth with several
|
||||
similar-named candidates so the scorer actually runs.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from rsyncfns import (
|
||||
FROMDIR, TODIR,
|
||||
assert_same, make_data_file, makepath, rmtree, run_rsync,
|
||||
)
|
||||
|
||||
src = FROMDIR
|
||||
deepdir = os.path.join('d1', 'd2')
|
||||
newfile = os.path.join(deepdir, 'archive-v2.tar')
|
||||
|
||||
rmtree(src)
|
||||
rmtree(TODIR)
|
||||
makepath(src / deepdir, TODIR / deepdir)
|
||||
|
||||
make_data_file(src / newfile, 300_000)
|
||||
base = (src / newfile).read_bytes()
|
||||
|
||||
# Destination has NO 'archive-v2.tar', but several similar-named candidates that
|
||||
# are mostly identical to it -- so fuzzy must score them by name distance.
|
||||
(TODIR / deepdir / 'archive-v1.tar').write_bytes(base[:280_000] + b'older tail data')
|
||||
(TODIR / deepdir / 'archive-old.tar').write_bytes(base[:200_000])
|
||||
(TODIR / deepdir / 'unrelated.dat').write_bytes(b'nothing alike' * 1000)
|
||||
|
||||
run_rsync('-a', '--fuzzy', '--no-whole-file', f'{src}/', f'{TODIR}/')
|
||||
assert_same(TODIR / newfile, src / newfile, label='fuzzy result')
|
||||
|
||||
print("fuzzy-basis: --fuzzy candidate scoring (fuzzy_distance) verified at depth")
|
||||
75
testsuite/preallocate_test.py
Normal file
75
testsuite/preallocate_test.py
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Coverage of the file-allocation syscalls in syscall.c at depth:
|
||||
do_fallocate (--preallocate) and do_punch_hole (sparse writes).
|
||||
|
||||
These are receiver-side file operations the resolver restructure also touches.
|
||||
Where the filesystem lacks fallocate/punch-hole the calls warn and the transfer
|
||||
still completes, so the content assertions hold regardless; the coverage is
|
||||
gained wherever the kernel supports them.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from rsyncfns import (
|
||||
FROMDIR, TODIR,
|
||||
assert_same, make_data_file, makepath, rmtree, run_rsync, test_skipped,
|
||||
)
|
||||
|
||||
src = FROMDIR
|
||||
deep = os.path.join('d1', 'd2', 'd3', 'f')
|
||||
|
||||
# --preallocate needs fallocate/posix_fallocate, and do_punch_hole needs
|
||||
# FALLOC_FL_PUNCH_HOLE -- both Linux (and Cygwin) features. macOS, the *BSDs and
|
||||
# Solaris build without preallocation and reject the option outright ("prealloc-
|
||||
# ation is not supported"), so probe once with a trivial transfer and skip the
|
||||
# whole test where it's unavailable.
|
||||
rmtree(src)
|
||||
rmtree(TODIR)
|
||||
makepath(src)
|
||||
(src / 'probe').write_text("x\n")
|
||||
if run_rsync('-a', '--preallocate', f'{src}/', f'{TODIR}/',
|
||||
check=False, capture_output=True).returncode != 0:
|
||||
test_skipped("--preallocate not supported on this platform")
|
||||
|
||||
|
||||
def seed_plain(size=1_000_000):
|
||||
rmtree(src)
|
||||
rmtree(TODIR)
|
||||
makepath(src / 'd1' / 'd2' / 'd3')
|
||||
make_data_file(src / deep, size)
|
||||
|
||||
|
||||
def seed_holey(head=4096, hole=2 * 1024 * 1024, tail=4096):
|
||||
rmtree(src)
|
||||
rmtree(TODIR)
|
||||
makepath(src / 'd1' / 'd2' / 'd3')
|
||||
with open(src / deep, 'wb') as f:
|
||||
f.write(os.urandom(head))
|
||||
f.write(b'\0' * hole) # a real zero run for the sparse writer
|
||||
f.write(os.urandom(tail))
|
||||
|
||||
|
||||
# --- --preallocate: do_fallocate on the receiver ----------------------------
|
||||
seed_plain()
|
||||
run_rsync('-a', '--preallocate', f'{src}/', f'{TODIR}/')
|
||||
assert_same(TODIR / deep, src / deep, label='--preallocate content')
|
||||
|
||||
# --- --preallocate --sparse on a holey file: do_fallocate + do_punch_hole ---
|
||||
seed_holey()
|
||||
run_rsync('-a', '--preallocate', '--sparse', f'{src}/', f'{TODIR}/')
|
||||
assert_same(TODIR / deep, src / deep, label='--preallocate --sparse content')
|
||||
|
||||
# --- --inplace --sparse update that introduces a zero run: do_punch_hole ----
|
||||
# (sparse_end's updating_basis_or_equiv branch punches the hole in place.)
|
||||
seed_plain()
|
||||
run_rsync('-a', f'{src}/', f'{TODIR}/') # dest starts fully populated
|
||||
data = bytearray((src / deep).read_bytes())
|
||||
data[200_000:800_000] = b'\0' * 600_000 # same size, new zero run
|
||||
(src / deep).write_bytes(bytes(data))
|
||||
st = os.stat(src / deep)
|
||||
os.utime(src / deep, (st.st_atime, st.st_mtime + 100)) # force a delta update
|
||||
run_rsync('-a', '--inplace', '--sparse', '--no-whole-file', f'{src}/', f'{TODIR}/')
|
||||
assert_same(TODIR / deep, src / deep, label='--inplace --sparse content')
|
||||
|
||||
print("preallocate: --preallocate (do_fallocate) + sparse hole-punching "
|
||||
"(do_punch_hole) verified at depth")
|
||||
Reference in New Issue
Block a user