Files
rsync/testsuite/relative-implied_test.py
Andrew Tridgell 0d546ee3b4 testsuite: structure / recursion / link coverage at depth
Cover the structure and link options at >=3 levels and across directories,
asserting each option's specific effect:

  links            -l keeps a symlink, -L dereferences it, -k follows a
                   directory symlink -- all on a symlink several levels deep.
  dirs             -d copies the top layer (file + empty dir) without recursing.
  prune-empty-dirs -m drops empty chains and chains emptied by an exclude,
                   keeps populated ones.
  hardlinks-deep   -H preserves a hard link whose names live in different
                   directories at depth; without -H they become separate inodes.
  delete-deep      --delete removes a deep extraneous file/subtree; the four
                   delete-timing variants agree; --max-delete caps deletions;
                   --existing / --ignore-existing select/skip correctly.
  relative-implied -R mirrors an implied directory's mode at depth;
                   --no-implied-dirs does not (proto 30+).

Green on master and under --protocol=29/30 (the --no-implied-dirs sub-case is
gated to protocol >= 30, where multi-component sender paths are accepted).

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

54 lines
2.1 KiB
Python

#!/usr/bin/env python3
"""Coverage of -R implied directories and --no-implied-dirs at depth.
With -R the directories implied by a source path are recreated at the
destination AND have their attributes mirrored from the source. With
--no-implied-dirs those implied directories are still created as needed but get
default attributes instead of the source's. Verify the distinction on an
implied directory carrying a non-default mode, several levels deep.
"""
import os
import stat
from rsyncfns import (
SCRATCHDIR, TODIR,
assert_mode, assert_same, forced_protocol, makepath, rmtree, run_rsync,
test_fail,
)
base = SCRATCHDIR / 'rbase'
rmtree(base)
rmtree(TODIR)
makepath(base / 'a' / 'b' / 'c')
os.chmod(base / 'a' / 'b', 0o750) # distinctive mode on an implied dir
(base / 'a' / 'b' / 'c' / 'file').write_text("data\n")
os.chdir(base / 'a')
# -R: implied dirs b and c are recreated with the source's attributes.
run_rsync('-aR', 'b/c/file', f'{TODIR}/')
assert_mode(TODIR / 'b', 0o750, label='-R mirrors implied-dir mode')
assert_same(TODIR / 'b' / 'c' / 'file', base / 'a' / 'b' / 'c' / 'file',
label='-R deep file')
# --no-implied-dirs: implied dir b is created with default (not source) attrs.
# At protocol 29 the generator rejects a multi-component path that has no
# transmitted directory entries ("invalid path from sender"), so this half is
# protocol-30+.
proto = forced_protocol()
if proto is not None and proto < 30:
print(f"relative-implied: protocol {proto} -- skipping --no-implied-dirs "
"(the multi-component path is rejected by the proto-29 generator)")
else:
rmtree(TODIR)
run_rsync('-aR', '--no-implied-dirs', 'b/c/file', f'{TODIR}/')
m = stat.S_IMODE(os.stat(TODIR / 'b').st_mode)
if m == 0o750:
test_fail("--no-implied-dirs unexpectedly mirrored the source mode "
"0750 onto the implied directory")
assert_same(TODIR / 'b' / 'c' / 'file', base / 'a' / 'b' / 'c' / 'file',
label='--no-implied-dirs deep file')
print("relative-implied: -R mirrors implied-dir attrs; --no-implied-dirs does not")