Files
rsync/testsuite/files-from_test.py
Andrew Tridgell 0789709aeb testsuite: quote the rsync command everywhere a shell re-parses it
Second layer of the space-in-build-path work.  Quoting the Makefile got the
runner started; these are the places that then hand the binary's path to
something that splits on whitespace.

  - RSYNC_CONNECT_PROG is run by a shell.  This was the big one: an unquoted
    daemon command turned every daemon-mode test into
    "sh: 1: /path/to/ws: Permission denied".
  - RSYNC_RSH / --rsh is tokenised by rsync itself (do_cmd() in main.c, which
    honours ' and "), so support/lsh.sh needs quoting when srcdir has a space.
  - --rsync-path is a command line run by the REMOTE shell, so rsync passes it
    through unsplit and lsh.sh's eval re-parses it.
  - The generated rsync-shim scripts interpolate RSYNC into "#!/bin/sh\nexec
    ...", where it is shell syntax rather than an argv entry.

rsync_path_arg() and rsh_cmd() build those strings by splitting the command and
re-joining with shlex, so a plain path with a space comes back quoted while a
wrapper command ("valgrind ... /build/rsync") stays several words.

split_rsync_cmd() also has to cope with RSYNC once a test has appended options
to it -- chown-fake and friends do -- where the string is no longer a filename.
It now takes the longest leading run that names an existing file as the program
and splits only what follows.

In a directory with a space: 231 pass, 22 fail, from 0 able to run before the
first commit.  Unchanged in a normal path: 257 passed, 0 failed.
2026-08-02 08:13:25 +10:00

52 lines
1.7 KiB
Python

#!/usr/bin/env python3
# Python rewrite of testsuite/files-from.test.
#
# Verify that --files-from=LIST drives rsync correctly both for a plain
# local sync and across the lsh.sh "remote shell" with each of the four
# files-host / src-host / dest-host placement combinations.
from rsyncfns import (
CHKDIR, FROMDIR, RSYNC, RSYNC_PEER, SCRATCHDIR, SRCDIR, TODIR,
checkit, hands_setup, rmtree, run_rsync, rsync_path_arg, rsh_cmd,
)
SSH = rsh_cmd()
hands_setup()
# Files-from list: skip the contents of subsubdir but include subsubdir2/
# in full (the trailing slash on subsubdir2/ is what flips it).
filelist = SCRATCHDIR / 'filelist'
filelist.write_text(
"from/./\n"
"from/./dir/subdir\n"
"from/./dir/subdir/subsubdir\n"
"from/./dir/subdir/subsubdir2/\n"
"from/./dir/subdir/foobar.baz\n"
)
# chkdir is what we expect the transfer to produce: source minus
# dir/text and minus everything under subsubdir/.
run_rsync('-a', '--exclude=dir/text', '--exclude=subsubdir/**',
f'{FROMDIR}/', f'{CHKDIR}/')
# Local case.
checkit(['-av', f'--files-from={filelist}', str(SCRATCHDIR), f'{TODIR}/'],
CHKDIR, TODIR)
# All four combinations of files-host / source-host / dest-host across the
# lsh.sh "remote shell". In each loop iteration exactly one of source or
# dest is remote (matches the original test's branch logic).
for filehost in ('', 'localhost:'):
for srchost in ('', 'localhost:'):
desthost = 'localhost:' if not srchost else ''
rmtree(TODIR)
checkit(
['-avse', SSH, f'--rsync-path={rsync_path_arg()}',
f'--files-from={filehost}{filelist}',
f'{srchost}{SCRATCHDIR}', f'{desthost}{TODIR}/'],
CHKDIR, TODIR,
)