runtests: add --rsync-bin2 / --expect-result for version-mixing tests

Let the suite run with two rsync binaries so the current build can be
tested against the actual old code of a previous release, rather than
only forcing the current binary to speak an old protocol (check29/30).

  --rsync-bin2 PATH  exports RSYNC_PEER, the binary used for the SERVER
                     side of two-sided transfers (the daemon process and
                     the remote-shell --rsync-path target). Defaults to
                     RSYNC, so single-binary runs are byte-for-byte
                     unchanged.
  --expect-result F  the manifest's listed tests ARE the run set; each
                     test's actual outcome (pass/skip/fail/xfail) is
                     compared to its expected one and any mismatch --
                     including an unexpected pass (xpass) -- fails the
                     run. --expect-skipped and the default exit logic
                     are untouched.

rsyncfns gains the RSYNC_PEER global and launches the daemon with it
(start_rsyncd / start_test_daemon, the latter with an optional rsync_cmd
override used by the reverse-direction test); the remote-shell tests
pass --rsync-path={RSYNC_PEER}. All no-ops when no peer is selected.

Direction is fixed: the current binary always drives (only it
understands the new test scripts); the old binary is only ever the
server/daemon side.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andrew Tridgell
2026-05-31 21:00:51 +10:00
parent c0219caf15
commit e21cdabd71
10 changed files with 171 additions and 45 deletions

View File

@@ -60,6 +60,11 @@ def parse_args():
help='Per-test timeout in seconds (default: 300)')
p.add_argument('--rsync-bin', default=None, metavar='PATH',
help='Path to rsync binary (default: ./rsync)')
p.add_argument('--rsync-bin2', default=None, metavar='PATH',
help='Path to a second ("peer") rsync binary used for the '
'daemon side and remote-shell --rsync-path. Lets the '
'suite mix two rsync versions over the wire. Default: '
'same as --rsync-bin (no version mixing).')
p.add_argument('--tooldir', default=None, metavar='DIR',
help='Tool/build directory (default: cwd)')
p.add_argument('--srcdir', default=None, metavar='DIR',
@@ -68,6 +73,13 @@ def parse_args():
help='Force protocol version (adds --protocol=VER to rsync)')
p.add_argument('--expect-skipped', default=None, metavar='LIST',
help='Comma-separated list of expected-skipped tests')
p.add_argument('--expect-result', default=None, metavar='FILE',
help='Path to an expected-outcome manifest (one '
'"<testname> <pass|skip|fail|xfail>" per line). When '
'set, ONLY the tests listed in FILE are run, and each '
"test's actual outcome is compared against its "
'expected one; any mismatch (including an unexpected '
'pass) fails the run. Used for version-mixing CI.')
p.add_argument('--use-tcp', action='store_true',
help='Run daemon tests against a real rsyncd bound to '
'127.0.0.1 (non-default). The default is the secure '
@@ -208,6 +220,44 @@ def collect_tests(suitedir, patterns):
return tests
_VALID_OUTCOMES = ('pass', 'skip', 'fail', 'xfail')
def parse_expect_result(path):
"""Parse an expected-outcome manifest into {testbase: outcome}.
One "<testname> <outcome>" entry per line; '#' comments and blank lines
are ignored. outcome is one of pass|skip|fail|xfail. The set of listed
tests doubles as the run set (see main()). Exits 2 on a malformed file.
"""
expect = {}
with open(path) as f:
for lineno, raw in enumerate(f, 1):
line = raw.split('#', 1)[0].strip()
if not line:
continue
fields = line.split()
if len(fields) != 2 or fields[1] not in _VALID_OUTCOMES:
sys.stderr.write(
f"{path}:{lineno}: expected '<testname> "
f"<{'|'.join(_VALID_OUTCOMES)}>', got: {raw.rstrip()}\n"
)
sys.exit(2)
expect[fields[0]] = fields[1]
return expect
def outcome_of(result):
"""Map a per-test exit code to an outcome string."""
if result == 0:
return 'pass'
if result == 77:
return 'skip'
if result == 78:
return 'xfail'
return 'fail'
def build_rsync_cmd(rsync_bin, args, scratchbase):
"""Build the RSYNC command string for tests."""
parts = []
@@ -339,6 +389,12 @@ def main():
if rsync_bin and not os.path.isabs(rsync_bin):
rsync_bin = os.path.abspath(rsync_bin)
# Optional second ("peer") binary for the daemon / remote-shell side, so a
# run can mix two rsync versions. Defaults to rsync_bin -> no mixing.
rsync_bin2 = args.rsync_bin2 or os.environ.get('rsync_bin2') or rsync_bin
if rsync_bin2 and not os.path.isabs(rsync_bin2):
rsync_bin2 = os.path.abspath(rsync_bin2)
suitedir = os.path.join(srcdir, 'testsuite')
scratchbase = os.path.join(os.environ.get('scratchbase', tooldir), 'testtmp')
os.makedirs(scratchbase, exist_ok=True)
@@ -347,10 +403,14 @@ def main():
tls_args = get_tls_args(os.path.join(tooldir, 'config.h'))
setfacl_nodef = find_setfacl_nodef(scratchbase)
rsync_cmd = build_rsync_cmd(rsync_bin, args, scratchbase)
rsync_peer_cmd = build_rsync_cmd(rsync_bin2, args, scratchbase)
if not os.path.isfile(rsync_bin):
sys.stderr.write(f"rsync_bin {rsync_bin} is not a file\n")
sys.exit(2)
if not os.path.isfile(rsync_bin2):
sys.stderr.write(f"rsync_bin2 {rsync_bin2} is not a file\n")
sys.exit(2)
if not os.path.isdir(srcdir):
sys.stderr.write(f"srcdir {srcdir} is not a directory\n")
sys.exit(2)
@@ -378,6 +438,8 @@ def main():
print('=' * 60)
print(f'{sys.argv[0]} running in {tooldir}')
print(f' rsync_bin={rsync_cmd}')
if rsync_peer_cmd != rsync_cmd:
print(f' rsync_peer={rsync_peer_cmd}')
print(f' srcdir={srcdir}')
print(f' TLS_ARGS={tls_args}')
print(f' testuser={testuser}')
@@ -407,6 +469,7 @@ def main():
'TOOLDIR': tooldir,
'srcdir': srcdir,
'RSYNC': rsync_cmd,
'RSYNC_PEER': rsync_peer_cmd,
'TLS_ARGS': tls_args,
'RUNSHFLAGS': '-e',
'scratchbase': scratchbase,
@@ -443,6 +506,29 @@ def main():
print(f"Excluding {before - len(tests)} test(s) matching: "
f"{', '.join(excl)}")
# An expected-result manifest defines BOTH the run set (its keys) and the
# expected per-test outcome (its values). Used for version-mixing runs.
expect = parse_expect_result(args.expect_result) if args.expect_result else None
if expect is not None:
have = {_testbase(t) for t in tests}
unknown = sorted(k for k in expect if k not in have)
if unknown:
sys.stderr.write(
"runtests.py: --expect-result lists test(s) with no matching "
f"test file (ignored): {', '.join(unknown)}\n"
)
tests = [t for t in tests if _testbase(t) in expect]
full_run = False
def _cls(outcome):
"""Equivalence class for outcome comparison: fail and xfail both just
mean 'broke', so a manifest 'fail' matches an actual fail OR xfail."""
return 'broken' if outcome in ('fail', 'xfail') else outcome
def mismatch(testbase, actual):
"""True if actual outcome disagrees with the manifest expectation."""
return expect is not None and _cls(expect[testbase]) != _cls(actual)
# Record test order for consistent skipped-list output
test_order = {_testbase(t): i for i, t in enumerate(tests)}
@@ -450,31 +536,33 @@ def main():
failed = 0
skipped = 0
skipped_list = []
outcomes = {} # testbase -> actual outcome string ('pass'/'skip'/'fail'/'xfail')
def process_result(tr):
"""Process a TestResult and update counters. Returns True if test failed."""
"""Process a TestResult and update counters. Returns True if the test
should count as a failure for --stop-on-fail purposes."""
nonlocal passed, failed, skipped
with _print_lock:
if tr.output:
print(tr.output)
scratchdir = os.path.join(scratchbase, tr.testbase)
oc = outcome_of(tr.result)
outcomes[tr.testbase] = oc
if tr.result == 0:
passed += 1
if not args.preserve_scratch and os.path.isdir(scratchdir):
subprocess.run(['rm', '-rf', scratchdir], capture_output=True)
return False
elif tr.result == 77:
skipped_list.append(tr.testbase)
skipped += 1
if not args.preserve_scratch and os.path.isdir(scratchdir):
subprocess.run(['rm', '-rf', scratchdir], capture_output=True)
return False
elif tr.result == 78:
failed += 1
return True
else:
failed += 1
return True
if tr.result in (0, 77) and not args.preserve_scratch \
and os.path.isdir(scratchdir):
subprocess.run(['rm', '-rf', scratchdir], capture_output=True)
# With a manifest, only a mismatch is a "failure" (an expected fail is
# fine); without one, any non-pass/non-skip result is a failure.
if expect is not None:
return mismatch(tr.testbase, oc)
return tr.result not in (0, 77)
if args.parallel > 1:
# Parallel execution
@@ -541,6 +629,25 @@ def main():
if vg_errors > 0:
print(f' {vg_errors} valgrind error(s) found (see logs in {scratchbase})')
if expect is not None:
# Version-mixing mode: the run is judged purely on whether each test's
# actual outcome matched its manifest expectation. An expected 'fail'
# is fine; an UNEXPECTED pass (xpass) or any other divergence is not.
mismatches = []
for tb in sorted(expect, key=lambda x: test_order.get(x, 1 << 30)):
actual = outcomes.get(tb, 'notrun')
if actual == 'notrun' or mismatch(tb, actual):
mismatches.append((tb, expect[tb], actual))
if mismatches:
print('----- expected-result mismatches:')
for tb, want, got in mismatches:
tag = ' (xpass)' if _cls(want) == 'broken' and got == 'pass' else ''
print(f' {tb}: expected {want}, got {got}{tag}')
print('-' * 60)
exit_code = len(mismatches) + vg_errors
print(f'overall result is {exit_code}')
sys.exit(exit_code)
skipped_str = ','.join(sorted(skipped_list, key=lambda x: test_order.get(x, 0)))
if full_run and args.expect_skipped != 'IGNORE':
print('----- skipped results:')

View File

@@ -8,7 +8,7 @@
import os
from rsyncfns import (
FROMDIR, RSYNC, SRCDIR, TODIR,
FROMDIR, RSYNC, RSYNC_PEER, SRCDIR, TODIR,
checkit, run_rsync, test_fail,
)
@@ -39,7 +39,7 @@ def append_line(line: str) -> None:
def copy_weird(args: list, src_host: str, dst_host: str) -> None:
checkit(
[*args, f'--rsync-path={RSYNC}',
[*args, f'--rsync-path={RSYNC_PEER}',
f'{src_host}{weird_dir}/',
f'{dst_host}{TODIR / weird_name}'],
FROMDIR, TODIR,
@@ -70,7 +70,7 @@ print('test6')
saved = os.getcwd()
os.chdir(FROMDIR)
try:
run_rsync('-ai', '--old-args', f'--rsync-path={RSYNC}',
run_rsync('-ai', '--old-args', f'--rsync-path={RSYNC_PEER}',
'lh:one two', f'{TODIR}/')
finally:
os.chdir(saved)
@@ -91,7 +91,7 @@ from rsyncfns import rsync_argv
os.chdir(FROMDIR)
try:
subprocess.run(
rsync_argv('-ai', f'--rsync-path={RSYNC}',
rsync_argv('-ai', f'--rsync-path={RSYNC_PEER}',
'lh:one two', f'{TODIR}/'),
env=env, check=True,
)

View File

@@ -11,7 +11,7 @@ import shutil
import time
from rsyncfns import (
CHKDIR, FROMDIR, RSYNC, SCRATCHDIR, SRCDIR, TMPDIR, TODIR,
CHKDIR, FROMDIR, RSYNC, RSYNC_PEER, SCRATCHDIR, SRCDIR, TMPDIR, TODIR,
checkit, hands_setup, rmtree, run_rsync, test_fail,
)
@@ -73,7 +73,7 @@ for maybe_inplace in ([], ['--inplace']):
for srchost in ('', 'localhost:'):
desthost = 'localhost:' if not srchost else ''
rmtree(TODIR)
checkit(['-ave', SSH, f'--rsync-path={RSYNC}', *maybe_inplace,
checkit(['-ave', SSH, f'--rsync-path={RSYNC_PEER}', *maybe_inplace,
f'--copy-dest={alt3dir}',
f'{srchost}{FROMDIR}/', f'{desthost}{TODIR}/'],
FROMDIR, TODIR)

View File

@@ -10,7 +10,7 @@ import os
import subprocess
from rsyncfns import (
CHKFILE, FROMDIR, OUTFILE, RSYNC, SCRATCHDIR, SRCDIR, TODIR,
CHKFILE, FROMDIR, OUTFILE, RSYNC, RSYNC_PEER, SCRATCHDIR, SRCDIR, TODIR,
build_rsyncd_conf, get_rootuid, get_testuid, makepath,
rsync_argv, run_rsync, start_test_daemon, test_fail,
)
@@ -78,7 +78,7 @@ def run_and_check(args, label, capture_stderr=False):
# Module list via the lsh.sh stand-in.
rsync_path = f"{RSYNC}{(' ' + ' '.join(confopt)) if confopt else ''}"
rsync_path = f"{RSYNC_PEER}{(' ' + ' '.join(confopt)) if confopt else ''}"
out = run_and_check(
['-ve', SSH, f'--rsync-path={rsync_path}', 'localhost::'],
"module list via lsh.sh",

View File

@@ -15,7 +15,7 @@ import subprocess
import sys
from rsyncfns import (
CHKDIR, FROMDIR, RSYNC, SCRATCHDIR, SRCDIR, TMPDIR, TODIR,
CHKDIR, FROMDIR, RSYNC, RSYNC_PEER, SCRATCHDIR, SRCDIR, TMPDIR, TODIR,
all_plus, allspace, dots,
checkdiff, checkit, makepath, rsync_argv, run_rsync, test_fail,
)
@@ -26,7 +26,7 @@ os.environ['CVSIGNORE'] = '*.junk'
script_name = os.path.basename(sys.argv[0] if sys.argv[0] else __file__)
if 'lsh' in script_name:
os.environ['RSYNC_RSH'] = str(SRCDIR / 'support' / 'lsh.sh')
rpath = [f'--rsync-path={RSYNC}']
rpath = [f'--rsync-path={RSYNC_PEER}']
host = 'lh:'
else:
rpath = []
@@ -116,7 +116,7 @@ excl.write_text(
# --- main checks ------------------------------------------------------------
# Start with a check of --prune-empty-dirs.
run_rsync('-av', f'--rsync-path={RSYNC}',
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
'-f', '-_foo/too/', '-f', '-_foo/down/',
'-f', '-_foo/and/', '-f', '-_new/',
f'{host}{FROMDIR}/', f'{CHKDIR}/')
@@ -162,7 +162,7 @@ for f in (CHKDIR / 'bar' / 'down' / 'to' / 'foo').glob('file[235-9]'):
(up2 / 'dst-newness').touch()
# Un-tweak the directory times in our first (weak) exclude test.
run_rsync('-av', f'--rsync-path={RSYNC}',
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
'--existing', '--include=*/', '--exclude=*',
f'{host}{FROMDIR}/', f'{CHKDIR}/')
@@ -181,7 +181,7 @@ for f in (CHKDIR / 'bar' / 'down' / 'to' / 'foo').glob('*.junk'):
(CHKDIR / 'bar' / 'down' / 'to' / 'home-cvs-exclude').unlink()
(CHKDIR / 'mid' / 'one-in-one-out').unlink()
run_rsync('-av', f'--rsync-path={RSYNC}',
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
'--existing', '--filter=exclude,! */',
f'{host}{FROMDIR}/', f'{CHKDIR}/')
@@ -205,7 +205,7 @@ from rsyncfns import cp_touch
cp_touch(FROMDIR / 'bar' / 'down' / 'to' / 'foo' / 'to',
CHKDIR / 'bar' / 'down' / 'to' / 'foo')
run_rsync('-av', f'--rsync-path={RSYNC}',
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
'--existing', '-f', 'show .filt*', '-f', 'hide,! */', '--del',
f'{host}{FROMDIR}/', f'{TODIR}/')
@@ -213,7 +213,7 @@ run_rsync('-av', f'--rsync-path={RSYNC}',
cp_touch(TODIR / 'bar' / 'down' / 'to' / 'bar' / 'baz' / 'nodel.deep',
CHKDIR / 'bar' / 'down' / 'to' / 'bar' / 'baz')
run_rsync('-av', f'--rsync-path={RSYNC}',
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
'--existing', '--filter=-! */',
f'{host}{FROMDIR}/', f'{CHKDIR}/')
@@ -255,7 +255,7 @@ verify_dirs(CHKDIR, TODIR, label="dir-merge + merge-from-stdin")
(CHKDIR / 'bar' / 'down' / 'to' / 'bar' / '.filt2').unlink()
(CHKDIR / 'mid' / '.filt').unlink()
run_rsync('-av', f'--rsync-path={RSYNC}',
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
'--existing', '--include=*/', '--exclude=*',
f'{host}{FROMDIR}/', f'{CHKDIR}/')
@@ -275,15 +275,15 @@ verify_dirs(CHKDIR, TODIR, label="delete-before with merge")
"+ file3\n*.bak\n"
)
run_rsync('-av', f'--rsync-path={RSYNC}',
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
'--del', f'{host}{FROMDIR}/', f'{CHKDIR}/')
(CHKDIR / 'bar' / 'down' / 'to' / 'foo' / 'file1.bak').unlink()
(CHKDIR / 'bar' / 'down' / 'to' / 'foo' / 'file3').unlink()
(CHKDIR / 'bar' / 'down' / 'to' / 'foo' / '+ file3').unlink()
run_rsync('-av', f'--rsync-path={RSYNC}',
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
'--existing', '--filter=-! */',
f'{host}{FROMDIR}/', f'{CHKDIR}/')
run_rsync('-av', f'--rsync-path={RSYNC}',
run_rsync('-av', f'--rsync-path={RSYNC_PEER}',
'--delete-excluded', '--exclude=*',
f'{host}{FROMDIR}/', f'{TODIR}/')
@@ -293,7 +293,7 @@ checkit(['-avv', *rpath, '-f', 'dir-merge,-_.excl',
# Combine with --relative.
relative_opts = ['--relative', '--chmod=Du+w', '--copy-unsafe-links']
run_rsync('-av', f'--rsync-path={RSYNC}', *relative_opts,
run_rsync('-av', f'--rsync-path={RSYNC_PEER}', *relative_opts,
f'{host}{FROMDIR}/foo', f'{CHKDIR}/')
shutil.rmtree(str(CHKDIR) + str(FROMDIR) + '/foo/down', ignore_errors=True)
run_rsync('-av', *relative_opts, '--existing', '--filter=-! */',

View File

@@ -6,7 +6,7 @@
# files-host / src-host / dest-host placement combinations.
from rsyncfns import (
CHKDIR, FROMDIR, RSYNC, SCRATCHDIR, SRCDIR, TODIR,
CHKDIR, FROMDIR, RSYNC, RSYNC_PEER, SCRATCHDIR, SRCDIR, TODIR,
checkit, hands_setup, rmtree, run_rsync,
)
@@ -44,7 +44,7 @@ for filehost in ('', 'localhost:'):
rmtree(TODIR)
checkit(
['-avse', SSH, f'--rsync-path={RSYNC}',
['-avse', SSH, f'--rsync-path={RSYNC_PEER}',
f'--files-from={filehost}{filelist}',
f'{srchost}{SCRATCHDIR}', f'{desthost}{TODIR}/'],
CHKDIR, TODIR,

View File

@@ -12,7 +12,7 @@ import shutil
import subprocess
from rsyncfns import (
CHKDIR, FROMDIR, OUTFILE, RSYNC, SRCDIR, TODIR,
CHKDIR, FROMDIR, OUTFILE, RSYNC, RSYNC_PEER, SRCDIR, TODIR,
checkit, makepath, rsync_argv, test_fail, test_skipped,
)
@@ -64,7 +64,7 @@ for x in chars:
os.link(name1, FROMDIR / 'subdir' / 'down' / 'deep' / 'new-file')
(TODIR / 'text').unlink()
checkit(['-aHivve', SSH, '--debug=HLINK5', f'--rsync-path={RSYNC}',
checkit(['-aHivve', SSH, '--debug=HLINK5', f'--rsync-path={RSYNC_PEER}',
f'{FROMDIR}/', f'localhost:{TODIR}/'], FROMDIR, TODIR)
# --link-dest and --copy-dest should also keep hard-linked entries.

View File

@@ -64,6 +64,15 @@ os.umask(0o022)
os.environ['HOME'] = str(SCRATCHDIR)
RSYNC = _required('RSYNC') # full command line, possibly with valgrind/protocol
# The "peer" rsync command -- used for the SERVER side of two-sided transfers
# (the daemon process; the remote-shell --rsync-path target). The runner sets
# RSYNC_PEER to a second binary when invoked with --rsync-bin2, letting a run
# mix two rsync versions over the wire. When no second binary was selected,
# RSYNC_PEER == RSYNC, so every consumer below behaves exactly as before and
# single-binary runs are unchanged. Use .get (not _required) so a test invoked
# by hand without the runner still works.
RSYNC_PEER = os.environ.get('RSYNC_PEER', RSYNC)
# TLS_ARGS controls how the 'tls' helper formats listings (e.g. --atimes,
# -l, -L). Tests that exercise non-default rsync features (atimes, etc.)
# assign to rsyncfns.TLS_ARGS before calling checkit / rsync_ls_lR.
@@ -232,7 +241,7 @@ def _stop_rsyncd(proc) -> 'None':
pass
def start_rsyncd(conf_path, port: int) -> 'subprocess.Popen':
def start_rsyncd(conf_path, port: int, rsync_cmd: str = None) -> 'subprocess.Popen':
"""Spawn `rsync --daemon --no-detach --address=127.0.0.1 --port=N
--config=conf` and return the Popen handle after the port is accepting
connections.
@@ -245,10 +254,16 @@ def start_rsyncd(conf_path, port: int) -> 'subprocess.Popen':
the test process doesn't strand the daemon either. The caller is expected
to have already claim_ports()'d `port`.
rsync_cmd selects the binary to run as the daemon; it defaults to
RSYNC_PEER (the peer side of a two-sided run), so ordinary daemon tests
get current-client <-> peer-daemon. The reverse-direction test passes
rsync_cmd=RSYNC to put the current build on the daemon side and drive with
the old client.
This is only ever reached from start_test_daemon() in --use-tcp mode; the
default (pipe) mode never starts a listening daemon.
"""
argv = shlex.split(RSYNC) + [
argv = shlex.split(rsync_cmd or RSYNC_PEER) + [
'--daemon', '--no-detach',
'--address=127.0.0.1',
f'--port={port}',
@@ -282,9 +297,12 @@ def start_rsyncd(conf_path, port: int) -> 'subprocess.Popen':
test_fail(f"rsyncd never listened on 127.0.0.1:{port}: {last_err}")
def start_test_daemon(conf_path, port: int) -> str:
def start_test_daemon(conf_path, port: int, rsync_cmd: str = None) -> str:
"""Bring up the test daemon and return a URL prefix for client commands.
rsync_cmd selects the daemon-side binary (default RSYNC_PEER); pass
rsync_cmd=RSYNC for the reverse-direction test (current daemon, old client).
This is the single seam every daemon test uses. The transport depends on
the mode the runner selected:
@@ -302,11 +320,12 @@ def start_test_daemon(conf_path, port: int) -> str:
Build URLs as f"{prefix}module/path". `port` is only used (and claimed)
in --use-tcp mode.
"""
daemon_cmd = rsync_cmd or RSYNC_PEER
if USE_TCP:
claim_ports(port)
start_rsyncd(conf_path, port)
start_rsyncd(conf_path, port, daemon_cmd)
return f'rsync://localhost:{port}/'
os.environ['RSYNC_CONNECT_PROG'] = f'{RSYNC} --config={conf_path} --daemon'
os.environ['RSYNC_CONNECT_PROG'] = f'{daemon_cmd} --config={conf_path} --daemon'
return 'rsync://localhost/'

View File

@@ -38,17 +38,17 @@ print(f"Using remote shell: {SSH}")
hands_setup()
# RSYNC may be a multi-word command line; pass it through --rsync-path.
from rsyncfns import RSYNC
from rsyncfns import RSYNC, RSYNC_PEER
def _basic():
checkit(['-avH', '-e', SSH, f'--rsync-path={RSYNC}',
checkit(['-avH', '-e', SSH, f'--rsync-path={RSYNC_PEER}',
f'{FROMDIR}/', f'localhost:{TODIR}'], FROMDIR, TODIR)
def _delete_after_rename():
shutil.move(str(TODIR / 'text'), str(TODIR / 'ThisShouldGo'))
checkit(['--delete', '-avH', '-e', SSH, f'--rsync-path={RSYNC}',
checkit(['--delete', '-avH', '-e', SSH, f'--rsync-path={RSYNC_PEER}',
f'{FROMDIR}/', f'localhost:{TODIR}'], FROMDIR, TODIR)

View File

@@ -18,7 +18,7 @@ import subprocess
import time
from rsyncfns import (
RSYNC, SCRATCHDIR, SRCDIR, TMPDIR,
RSYNC, SCRATCHDIR, RSYNC_PEER, SRCDIR, TMPDIR,
make_data_file, resolve_beneath_supported, rsync_argv, test_fail,
test_skipped,
)
@@ -59,7 +59,7 @@ def push(*args, label: str) -> None:
os.chdir(srcbase)
try:
proc = subprocess.run(
rsync_argv(f'--rsync-path={RSYNC}', *args),
rsync_argv(f'--rsync-path={RSYNC_PEER}', *args),
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
)
print(proc.stdout, end='')