Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions testsuite/longdir_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
# 175-char directory names, drop a couple of files in the leaf, and
# verify that --delete -avH still produces an identical destination.

import os
import subprocess

from rsyncfns import FROMDIR, TODIR, checkit, hands_setup, test_skipped
from rsyncfns import (FROMDIR, TODIR, checkit, hands_setup, make_text_file,
test_skipped)


hands_setup()
Expand All @@ -29,13 +27,8 @@
except OSError:
test_skipped("unable to create files in long directory")

# Drop some recognisably-varied content into the two leaf files.
(longdir / '1').write_text(subprocess.check_output(['date'], text=True))

listdir = '/etc' if os.access('/etc', os.R_OK) else '/'
out = subprocess.run(['ls', '-la', listdir], capture_output=True, text=True)
# ls exits 1 if it can't stat some entries (e.g. permission-denied files in
# /etc); the shell test silently accepts that. We do the same.
(longdir / '2').write_text(out.stdout)
# Drop predictable, self-contained content into the two leaf files.
make_text_file(longdir / '1', 50)
make_text_file(longdir / '2', 100)

checkit(['--delete', '-avH', f'{FROMDIR}/', str(TODIR)], FROMDIR, TODIR)
64 changes: 52 additions & 12 deletions testsuite/rsyncfns.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,26 @@ def make_data_file(path, size: int) -> 'None':
f.write(bytes(out))


def make_text_file(path, lines: int = 100) -> 'None':
"""Write a predictable, self-contained text file of `lines` lines.

This replaces the old habit of capturing `ls -l /etc` / `ls -l /bin`
(falling back to `ls /`) into the test tree. Those tied the fixtures
to the host filesystem layout: the directories are absent or
unreadable on Android/Termux and other minimal environments, where
`ls /` fails outright, and the captured content was never
reproducible. The output here is deterministic and depends on nothing
outside the suite, so every platform builds the identical fixture.
"""
content = ''.join(
"line %06d the quick brown fox jumps over the lazy dog %d %d\n"
% (i, (i * 31) % 97, (i * 131) % 89)
for i in range(1, lines + 1)
)
with open(str(path), 'w') as f:
f.write(content)


def get_testuid() -> int:
return os.getuid()

Expand Down Expand Up @@ -600,7 +620,29 @@ def xattr_dump(*paths) -> str:
for comparing a source tree against its rsync'd copy. The format only
needs to be self-consistent on a given OS (we never compare across OSes),
mirroring the per-OS xls() in the old xattrs.test."""
if _SYSTEM == 'Linux' or _CYGWIN:
if _SYSTEM == 'Linux':
# Read xattrs natively (symmetric with the os.setxattr used in
# xattr_set) so the suite needs no external getfattr. The attr
# package's CLI tools are frequently absent -- on Android/Termux
# and minimal CI images -- even when the filesystem itself supports
# user xattrs, in which case shelling out to getfattr would crash
# the test instead of exercising it. The output mimics "getfattr
# -d": a "# file:" header then sorted name="value" lines, files
# with no user xattrs omitted.
out = []
for p in paths:
sp = str(p)
names = sorted(n for n in os.listxattr(sp) if n.startswith('user.'))
if not names:
continue
out.append(f'# file: {sp}\n')
for n in names:
v = os.getxattr(sp, n).decode('utf-8', 'surrogateescape')
out.append(f'{n}="{v}"\n')
out.append('\n')
return ''.join(out)
if _CYGWIN:
# Python on Cygwin lacks os.*xattr, so use the CLI there.
return subprocess.check_output(
['getfattr', '-d', *(str(p) for p in paths)], text=True)
if _SYSTEM == 'Darwin':
Expand Down Expand Up @@ -677,11 +719,12 @@ def build_symlinks() -> 'None':


def hands_setup() -> 'None':
"""Equivalent of rsync.fns hands_setup: populate FROMDIR with a varied
tree of files and directories for the canonical 'hands' transfer test.
"""Populate FROMDIR with a varied tree of files and directories for the
canonical 'hands' transfer test.

Recreates the shell behavior bit-for-bit so the tls listings match
across the shell and Python halves of the suite during the transition.
All content is generated from within the suite (srcdir contents plus
make_text_file output) so the fixture is self-contained and
reproducible on every platform.
"""
rmtree(FROMDIR)
rmtree(TODIR)
Expand Down Expand Up @@ -717,15 +760,12 @@ def hands_setup() -> 'None':
(FROMDIR / 'dir' / 'subdir').mkdir(exist_ok=True)
(FROMDIR / 'dir' / 'subdir' / 'foobar.baz').write_text("some data\n")
(FROMDIR / 'dir' / 'subdir' / 'subsubdir').mkdir(exist_ok=True)

src_listdir = '/etc' if os.access('/etc', os.R_OK) else '/'
out = subprocess.run(['ls', '-ltr', src_listdir], capture_output=True, text=True)
(FROMDIR / 'dir' / 'subdir' / 'subsubdir' / 'etc-ltr-list').write_text(out.stdout)
# Predictable, self-contained fixture files (the names etc-ltr-list /
# bin-lt-list are kept because other tests reference them by name).
make_text_file(FROMDIR / 'dir' / 'subdir' / 'subsubdir' / 'etc-ltr-list', 120)

(FROMDIR / 'dir' / 'subdir' / 'subsubdir2').mkdir(exist_ok=True)
src_listdir = '/bin' if os.access('/bin', os.R_OK) else '/'
out = subprocess.run(['ls', '-lt', src_listdir], capture_output=True, text=True)
(FROMDIR / 'dir' / 'subdir' / 'subsubdir2' / 'bin-lt-list').write_text(out.stdout)
make_text_file(FROMDIR / 'dir' / 'subdir' / 'subsubdir2' / 'bin-lt-list', 200)


# --- listing / verification ------------------------------------------------
Expand Down
Loading