--- /dev/null
+#!/usr/bin/env python3
+
+# Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
+# Copyright (C) 2003-2022 Wayne Davison
+# Copyright (C) 2026 Andrew Tridgell
+#
+# Rewrite of runtests.sh in Python (runtests.sh is now deprecated).
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version
+# 2 as published by the Free Software Foundation.
+
+"""rsync test runner.
+
+Invokes test scripts from testsuite/ and reports results.
+Can be called by 'make check' or directly.
+
+Usage:
+ ./runtests.py [options] [TEST ...]
+
+Each TEST is a test name (e.g. 'delete') or glob pattern (e.g. 'xattr*').
+If no tests are specified, all tests are run.
+"""
+
+import argparse
+import glob
+import os
+import signal
+import subprocess
+import sys
+import time
+
+
+def parse_args():
+ p = argparse.ArgumentParser(description='Run rsync test suite')
+ p.add_argument('tests', nargs='*', metavar='TEST',
+ help='Test names or patterns to run (default: all)')
+ p.add_argument('--valgrind', action='store_true',
+ help='Run rsync under valgrind (logs to per-process files)')
+ p.add_argument('--valgrind-opts', default='', metavar='OPTS',
+ help='Extra valgrind options (e.g. "--leak-check=full")')
+ p.add_argument('--preserve-scratch', action='store_true',
+ help='Keep scratch directories after tests complete')
+ p.add_argument('--log-level', type=int, default=1, metavar='N',
+ help='Verbosity level 1-10 (default: 1)')
+ p.add_argument('--always-log', action='store_true',
+ help='Show test logs even for passing tests')
+ p.add_argument('--stop-on-fail', action='store_true',
+ help='Stop after first test failure')
+ p.add_argument('--timeout', type=int, default=300, metavar='SECS',
+ 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('--tooldir', default=None, metavar='DIR',
+ help='Tool/build directory (default: cwd)')
+ p.add_argument('--srcdir', default=None, metavar='DIR',
+ help='Source directory (default: script directory)')
+ p.add_argument('--protocol', type=int, default=None, metavar='VER',
+ 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')
+ return p.parse_args()
+
+
+def find_setfacl_nodef(scratchbase):
+ """Determine the setfacl command to remove default ACLs."""
+ for cmd in [
+ ['setacl', '-k', 'u::7,g::5,o:5', scratchbase],
+ ['setfacl', '-k', scratchbase],
+ ['setfacl', '-s', 'u::7,g::5,o:5', scratchbase],
+ ]:
+ try:
+ subprocess.run(cmd, capture_output=True, timeout=5)
+ return cmd[:2] if cmd[0] == 'setacl' else cmd[:2]
+ except (FileNotFoundError, subprocess.TimeoutExpired):
+ continue
+ # Also check if setfacl supports -k via --help
+ try:
+ r = subprocess.run(['setfacl', '--help'], capture_output=True, text=True, timeout=5)
+ if '-k,' in r.stdout or '-k,' in r.stderr:
+ return ['setfacl', '-k']
+ except (FileNotFoundError, subprocess.TimeoutExpired):
+ pass
+ return None
+
+
+def get_tls_args(config_h):
+ """Determine TLS_ARGS from config.h."""
+ args = ''
+ try:
+ with open(config_h) as f:
+ text = f.read()
+ if '#define HAVE_LUTIMES 1' in text:
+ args += ' -l'
+ if '#undef CHOWN_MODIFIES_SYMLINK' in text:
+ args += ' -L'
+ except FileNotFoundError:
+ pass
+ return args.strip()
+
+
+def read_shconfig(path):
+ """Read shell config variables from shconfig."""
+ env = {}
+ try:
+ with open(path) as f:
+ for line in f:
+ line = line.strip()
+ if line.startswith('#') or line.startswith('export') or not line:
+ continue
+ if '=' in line:
+ k, _, v = line.partition('=')
+ env[k.strip()] = v.strip().strip('"')
+ except FileNotFoundError:
+ pass
+ return env
+
+
+def get_testuser():
+ """Determine the current test user."""
+ for cmd in ['/usr/bin/whoami', '/usr/ucb/whoami', '/bin/whoami']:
+ if os.path.isfile(cmd):
+ try:
+ return subprocess.check_output([cmd], text=True).strip()
+ except subprocess.CalledProcessError:
+ pass
+ try:
+ return subprocess.check_output(['id', '-un'], text=True).strip()
+ except (FileNotFoundError, subprocess.CalledProcessError):
+ return os.environ.get('LOGNAME', os.environ.get('USER', 'UNKNOWN'))
+
+
+def prep_scratch(scratchdir, srcdir, tooldir, setfacl_nodef):
+ """Prepare a scratch directory for a test."""
+ if os.path.isdir(scratchdir):
+ subprocess.run(['chmod', '-R', 'u+rwX', scratchdir], capture_output=True)
+ subprocess.run(['rm', '-rf', scratchdir], capture_output=True)
+ os.makedirs(scratchdir, exist_ok=True)
+ if setfacl_nodef:
+ subprocess.run(setfacl_nodef + [scratchdir], capture_output=True)
+ try:
+ os.chmod(scratchdir, os.stat(scratchdir).st_mode & ~0o2000) # clear setgid
+ except OSError:
+ pass
+ # Symlink to source directory
+ src_link = os.path.join(scratchdir, 'src')
+ if not os.path.exists(src_link):
+ if os.path.isabs(srcdir):
+ os.symlink(srcdir, src_link)
+ else:
+ os.symlink(os.path.join(tooldir, srcdir), src_link)
+
+
+def collect_tests(suitedir, patterns):
+ """Collect test scripts matching the given patterns."""
+ if not patterns:
+ tests = sorted(glob.glob(os.path.join(suitedir, '*.test')))
+ else:
+ tests = []
+ for pat in patterns:
+ if not pat.endswith('.test'):
+ pat = pat + '.test'
+ matches = sorted(glob.glob(os.path.join(suitedir, pat)))
+ tests.extend(matches)
+ return tests
+
+
+def build_rsync_cmd(rsync_bin, args, extra_rsync_opts, scratchbase):
+ """Build the RSYNC command string for tests."""
+ parts = []
+ if args.valgrind:
+ vlog = os.path.join(scratchbase, 'valgrind.%p.log')
+ vopts = f'--log-file={vlog}'
+ if args.valgrind_opts:
+ vopts += ' ' + args.valgrind_opts
+ parts.append(f'valgrind {vopts}')
+ parts.append(rsync_bin)
+ if args.protocol is not None:
+ parts.append(f'--protocol={args.protocol}')
+ if extra_rsync_opts:
+ parts.extend(extra_rsync_opts)
+ return ' '.join(parts)
+
+
+def run_test(testscript, scratchdir, env, timeout):
+ """Run a single test script with timeout. Returns exit code."""
+ logfile = os.path.join(scratchdir, 'test.log')
+ try:
+ with open(logfile, 'w') as log:
+ proc = subprocess.run(
+ ['sh', '-e', testscript],
+ stdout=log, stderr=subprocess.STDOUT,
+ env=env, timeout=timeout,
+ cwd=env.get('TOOLDIR', '.')
+ )
+ return proc.returncode
+ except subprocess.TimeoutExpired:
+ sys.stderr.write(f"TIMEOUT: {testscript} took over {timeout} seconds\n")
+ return 1
+
+
+def main():
+ args = parse_args()
+
+ # Also accept legacy environment variables
+ if args.preserve_scratch or os.environ.get('preserve_scratch') == 'yes':
+ args.preserve_scratch = True
+ if args.log_level == 1:
+ args.log_level = int(os.environ.get('loglevel', '1'))
+ if args.expect_skipped is None:
+ args.expect_skipped = os.environ.get('RSYNC_EXPECT_SKIPPED', 'IGNORE')
+ if os.environ.get('whichtests'):
+ args.tests = [os.environ['whichtests']]
+
+ # Determine directories
+ tooldir = args.tooldir or os.environ.get('TOOLDIR') or os.getcwd()
+ script_path = os.path.dirname(os.path.abspath(__file__))
+ srcdir = args.srcdir or script_path
+ if not srcdir or srcdir == '.':
+ srcdir = tooldir
+ rsync_bin = args.rsync_bin or os.environ.get('rsync_bin') or os.path.join(tooldir, 'rsync')
+
+ suitedir = os.path.join(srcdir, 'testsuite')
+ scratchbase = os.path.join(os.environ.get('scratchbase', tooldir), 'testtmp')
+ os.makedirs(scratchbase, exist_ok=True)
+
+ # Read shconfig for ECHO_N/ECHO_C/ECHO_T, HOST_OS, etc.
+ shconfig = read_shconfig(os.path.join(tooldir, 'shconfig'))
+
+ # Determine TLS args and setfacl
+ tls_args = get_tls_args(os.path.join(tooldir, 'config.h'))
+ setfacl_nodef = find_setfacl_nodef(scratchbase)
+
+ # Collect extra rsync options from remaining argv (after --)
+ extra_rsync_opts = []
+
+ # Build RSYNC command
+ rsync_cmd = build_rsync_cmd(rsync_bin, args, extra_rsync_opts, scratchbase)
+
+ # Validate
+ 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.isdir(srcdir):
+ sys.stderr.write(f"srcdir {srcdir} is not a directory\n")
+ sys.exit(2)
+
+ testuser = get_testuser()
+
+ # Print header
+ print('=' * 60)
+ print(f'{sys.argv[0]} running in {tooldir}')
+ print(f' rsync_bin={rsync_cmd}')
+ print(f' srcdir={srcdir}')
+ print(f' TLS_ARGS={tls_args}')
+ print(f' testuser={testuser}')
+ print(f' os={subprocess.check_output(["uname", "-a"], text=True).strip()}')
+ print(f' preserve_scratch={"yes" if args.preserve_scratch else "no"}')
+ if args.valgrind:
+ print(f' valgrind=enabled (logs in valgrind.*.log)')
+ print(f' scratchbase={scratchbase}')
+
+ # Build environment for test scripts
+ # For Solaris compatibility
+ path = os.environ.get('PATH', '')
+ if os.path.isdir('/usr/xpg4/bin'):
+ path = '/usr/xpg4/bin:' + path
+
+ test_env = os.environ.copy()
+ test_env.update({
+ 'PATH': path,
+ 'POSIXLY_CORRECT': '1',
+ 'TOOLDIR': tooldir,
+ 'srcdir': srcdir,
+ 'RSYNC': rsync_cmd,
+ 'TLS_ARGS': tls_args,
+ 'RUNSHFLAGS': '-e',
+ 'scratchbase': scratchbase,
+ 'suitedir': suitedir,
+ 'TESTRUN_TIMEOUT': str(args.timeout),
+ 'HOME': scratchbase,
+ })
+ # Pass through shconfig values
+ for k, v in shconfig.items():
+ if v:
+ test_env[k] = v
+ # setfacl_nodef as a shell-friendly string
+ if setfacl_nodef:
+ test_env['setfacl_nodef'] = ' '.join(setfacl_nodef)
+ else:
+ test_env['setfacl_nodef'] = 'true'
+
+ if args.log_level > 8:
+ test_env['RUNSHFLAGS'] = '-e -x'
+
+ # Collect tests
+ tests = collect_tests(suitedir, args.tests)
+ full_run = len(args.tests) == 0
+
+ passed = 0
+ failed = 0
+ skipped = 0
+ skipped_list = []
+
+ for testscript in tests:
+ testbase = os.path.basename(testscript).replace('.test', '')
+ scratchdir = os.path.join(scratchbase, testbase)
+
+ prep_scratch(scratchdir, srcdir, tooldir, setfacl_nodef)
+
+ test_env['scratchdir'] = scratchdir
+
+ # Longer timeout for hardlinks test
+ timeout = 600 if 'hardlinks' in testbase else args.timeout
+
+ result = run_test(testscript, scratchdir, test_env, timeout)
+
+ logfile = os.path.join(scratchdir, 'test.log')
+
+ # Show log on failure or if always_log
+ if args.always_log or (result not in (0, 77, 78)):
+ print(f'----- {testbase} log follows')
+ try:
+ with open(logfile) as f:
+ print(f.read(), end='')
+ except FileNotFoundError:
+ pass
+ print(f'----- {testbase} log ends')
+ rsyncd_log = os.path.join(scratchdir, 'rsyncd.log')
+ if os.path.isfile(rsyncd_log):
+ print(f'----- {testbase} rsyncd.log follows')
+ with open(rsyncd_log) as f:
+ print(f.read(), end='')
+ print(f'----- {testbase} rsyncd.log ends')
+
+ if result == 0:
+ print(f'PASS {testbase}')
+ passed += 1
+ if not args.preserve_scratch and os.path.isdir(scratchdir):
+ subprocess.run(['rm', '-rf', scratchdir], capture_output=True)
+ elif result == 77:
+ whyfile = os.path.join(scratchdir, 'whyskipped')
+ why = ''
+ try:
+ with open(whyfile) as f:
+ why = f.read().strip()
+ except FileNotFoundError:
+ pass
+ print(f'SKIP {testbase} ({why})')
+ skipped_list.append(testbase)
+ skipped += 1
+ if not args.preserve_scratch and os.path.isdir(scratchdir):
+ subprocess.run(['rm', '-rf', scratchdir], capture_output=True)
+ elif result == 78:
+ print(f'XFAIL {testbase}')
+ failed += 1
+ else:
+ print(f'FAIL {testbase}')
+ failed += 1
+ if args.stop_on_fail:
+ break
+
+ # Check valgrind logs for errors
+ vg_errors = 0
+ if args.valgrind:
+ for vlog in sorted(glob.glob(os.path.join(scratchbase, 'valgrind.*.log'))):
+ try:
+ with open(vlog) as f:
+ content = f.read()
+ # Check for non-zero error summary
+ for line in content.splitlines():
+ if 'ERROR SUMMARY:' in line and 'ERROR SUMMARY: 0 errors' not in line:
+ vg_errors += 1
+ print(f'----- valgrind errors in {os.path.basename(vlog)}:')
+ print(content)
+ break
+ except FileNotFoundError:
+ pass
+
+ # Summary
+ print('-' * 60)
+ print('----- overall results:')
+ print(f' {passed} passed')
+ if failed > 0:
+ print(f' {failed} failed')
+ if skipped > 0:
+ print(f' {skipped} skipped')
+ if vg_errors > 0:
+ print(f' {vg_errors} valgrind error(s) found (see logs in {scratchbase})')
+
+ skipped_str = ','.join(skipped_list)
+ if full_run and args.expect_skipped != 'IGNORE':
+ print('----- skipped results:')
+ print(f' expected: {args.expect_skipped}')
+ print(f' got: {skipped_str}')
+ else:
+ skipped_str = ''
+ args.expect_skipped = ''
+
+ print('-' * 60)
+
+ exit_code = failed + vg_errors
+ if exit_code == 0 and skipped_str != args.expect_skipped:
+ exit_code = 1
+
+ print(f'overall result is {exit_code}')
+ sys.exit(exit_code)
+
+
+if __name__ == '__main__':
+ main()
+++ /dev/null
-#! /bin/sh
-
-# Copyright (C) 2001, 2002 by Martin Pool <mbp@samba.org>
-# Copyright (C) 2003-2022 Wayne Davison
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version
-# 2 as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-# -------------------------------------------------------------------------
-
-# rsync top-level test script -- this invokes all the other more
-# detailed tests in order. This script can either be called by `make
-# check' or `make installcheck'. `check' runs against the copies of
-# the program and other files in the build directory, and
-# `installcheck' against the installed copy of the program.
-
-# It can also be called on a single test file using a run like this:
-#
-# preserve_scratch=yes whichtests=itemize.test ./runtests.sh
-
-# In either case we need to also be able to find the source directory,
-# since we read test scripts and possibly other information from
-# there.
-
-# Whenever possible, informational messages are written to stdout and
-# error messages to stderr. They're separated out by the build farm
-# display scripts.
-
-# According to the GNU autoconf manual, the only valid place to set up
-# directory locations is through Make, since users are allowed to (try
-# to) change their mind on the Make command line. So, Make has to
-# pass in all the values we need.
-
-# For other configured settings we read ./config.sh, which tells us
-# about shell commands on this machine and similar things.
-
-# rsync_bin gives the location of the rsync binary. This is either
-# builddir/rsync if we're testing an uninstalled copy, or
-# install_prefix/bin/rsync if we're testing an installed copy. On the
-# build farm rsync will be installed, but into a scratch /usr.
-
-# srcdir gives the location of the source tree, which lets us find the
-# build scripts. At the moment we assume we are invoked from the
-# source directory.
-
-# This script must be invoked from the build directory.
-
-# A scratch directory, 'testtmp', is used in the build directory to
-# hold per-test subdirectories.
-
-# This script also uses the $loglevel environment variable. 1 is the
-# default value, and 10 the most verbose. You can set this from the
-# Make command line. It's also set by the build farm to give more
-# detail for failing builds.
-
-# -------------------------------------------------------------------------
-
-# NOTES FOR TEST CASES:
-
-# Each test case runs in its own shell.
-
-# Exit codes from tests:
-
-# 1 tests failed
-# 2 error in starting tests
-# 77 this test skipped (random value unlikely to happen by chance, same as
-# automake)
-
-# HOWEVER, the overall exit code to the farm is different: we return
-# the *number of tests that failed*, so that it will show up nicely in
-# the overall summary.
-
-# rsync.fns contains some general setup functions and definitions.
-
-# -------------------------------------------------------------------------
-
-# NOTES ON PORTABILITY:
-
-# Both this script and the Makefile have to be pretty conservative
-# about which Unix features they use.
-
-# We cannot count on Make exporting variables to commands, unless
-# they're explicitly given on the command line.
-
-# Also, we can't count on 'cp -a' or 'mkdir -p', although they're
-# pretty handy (see function makepath for the latter).
-
-# I think some of the GNU documentation suggests that we shouldn't
-# rely on shell functions. However, the Bash manual seems to say that
-# they're in POSIX 1003.2, and since the build farm relies on them
-# they're probably working on most machines we really care about.
-
-# You cannot use "function foo {" syntax, but must instead say "foo()
-# {", or it breaks on FreeBSD.
-
-# BSD machines tend not to have "head" or "seq".
-
-# You cannot do "export VAR=VALUE" all on one line; the export must be
-# separate from the assignment. (SCO SysV)
-
-# Don't rely on grep -q, as that doesn't work everywhere -- just redirect
-# stdout to /dev/null to keep it quiet.
-
-# -------------------------------------------------------------------------
-
-# STILL TO DO:
-
-# We need a good protection against tests that hang indefinitely.
-# Perhaps some combination of starting them in the background, wait,
-# and kill?
-
-# Perhaps we need a common way to cleanup tests. At the moment just
-# clobbering the directory when we're done should be enough.
-
-# If any of the targets fail, then (GNU?) Make returns 2, instead of
-# the return code from the failing command. This is fine, but it
-# means that the build farm just shows "2" for failed tests, not the
-# number of tests that actually failed. For more details we might
-# need to grovel through the log files to find a line saying how many
-# failed.
-
-
-set -e
-
-. "./shconfig"
-
-RUNSHFLAGS='-e'
-export RUNSHFLAGS
-
-# for Solaris
-if [ -d /usr/xpg4/bin ]; then
- PATH="/usr/xpg4/bin/:$PATH"
- export PATH
-fi
-
-if [ "x$loglevel" != x ] && [ "$loglevel" -gt 8 ]; then
- if set -x; then
- # If it doesn't work the first time, don't keep trying.
- RUNSHFLAGS="$RUNSHFLAGS -x"
- fi
-fi
-
-POSIXLY_CORRECT=1
-if test x"$TOOLDIR" = x; then
- TOOLDIR=`pwd`
-fi
-srcdir=`dirname $0`
-if test x"$srcdir" = x || test x"$srcdir" = x.; then
- srcdir="$TOOLDIR"
-fi
-if test x"$rsync_bin" = x; then
- rsync_bin="$TOOLDIR/rsync"
-fi
-
-# This allows the user to specify extra rsync options -- use carefully!
-RSYNC="$rsync_bin $*"
-#RSYNC="valgrind $rsync_bin $*"
-
-TLS_ARGS=''
-if grep -E '^#define HAVE_LUTIMES 1' config.h >/dev/null; then
- TLS_ARGS="$TLS_ARGS -l"
-fi
-if grep -E '#undef CHOWN_MODIFIES_SYMLINK' config.h >/dev/null; then
- TLS_ARGS="$TLS_ARGS -L"
-fi
-
-export POSIXLY_CORRECT TOOLDIR srcdir RSYNC TLS_ARGS
-
-echo "============================================================"
-echo "$0 running in $TOOLDIR"
-echo " rsync_bin=$RSYNC"
-echo " srcdir=$srcdir"
-echo " TLS_ARGS=$TLS_ARGS"
-
-if [ -f /usr/bin/whoami ]; then
- testuser=`/usr/bin/whoami`
-elif [ -f /usr/ucb/whoami ]; then
- testuser=`/usr/ucb/whoami`
-elif [ -f /bin/whoami ]; then
- testuser=`/bin/whoami`
-else
- testuser=`id -un 2>/dev/null || echo ${LOGNAME:-${USERNAME:-${USER:-'UNKNOWN'}}}`
-fi
-
-echo " testuser=$testuser"
-echo " os=`uname -a`"
-
-# It must be "yes", not just nonnull
-if [ "x$preserve_scratch" = xyes ]; then
- echo " preserve_scratch=yes"
-else
- echo " preserve_scratch=no"
-fi
-
-# Check if setacl/setfacl is around and if it supports the -k or -s option.
-if setacl -k u::7,g::5,o:5 testsuite 2>/dev/null; then
- setfacl_nodef='setacl -k'
-elif setfacl --help 2>&1 | grep ' -k,\|\[-[a-z]*k' >/dev/null; then
- setfacl_nodef='setfacl -k'
-elif setfacl -s u::7,g::5,o:5 testsuite 2>/dev/null; then
- setfacl_nodef='setfacl -s u::7,g::5,o:5'
-else
- # The "true" command runs successfully, but does nothing.
- setfacl_nodef=true
-fi
-
-export setfacl_nodef
-
-if [ ! -f "$rsync_bin" ]; then
- echo "rsync_bin $rsync_bin is not a file" >&2
- exit 2
-fi
-
-if [ ! -d "$srcdir" ]; then
- echo "srcdir $srcdir is not a directory" >&2
- exit 2
-fi
-
-expect_skipped="${RSYNC_EXPECT_SKIPPED-IGNORE}"
-skipped_list=''
-skipped=0
-missing=0
-passed=0
-failed=0
-
-# Directory that holds the other test subdirs. We create separate dirs
-# inside for each test case, so that they can be left behind in case of
-# failure to aid investigation. We don't remove the testtmp subdir at
-# the end so that it can be configured as a symlink to a filesystem that
-# has ACLs and xattr support enabled (if desired).
-scratchbase="${scratchbase:-$TOOLDIR}"/testtmp
-echo " scratchbase=$scratchbase"
-[ -d "$scratchbase" ] || mkdir "$scratchbase"
-
-suitedir="$srcdir/testsuite"
-TESTRUN_TIMEOUT=300
-
-export scratchdir suitedir TESTRUN_TIMEOUT
-
-prep_scratch() {
- [ -d "$scratchdir" ] && chmod -R u+rwX "$scratchdir" && rm -rf "$scratchdir"
- mkdir "$scratchdir"
- # Get rid of default ACLs and dir-setgid to avoid confusing some tests.
- $setfacl_nodef "$scratchdir" 2>/dev/null || true
- chmod g-s "$scratchdir"
- case "$srcdir" in
- /*) ln -s "$srcdir" "$scratchdir/src" ;;
- *) ln -s "$TOOLDIR/$srcdir" "$scratchdir/src" ;;
- esac
- return 0
-}
-
-maybe_discard_scratch() {
- [ x"$preserve_scratch" != xyes ] && [ -d "$scratchdir" ] && rm -rf "$scratchdir"
- return 0
-}
-
-if [ "x$whichtests" = x ]; then
- whichtests="*.test"
- full_run=yes
-else
- full_run=no
-fi
-
-for testscript in $suitedir/$whichtests; do
- testbase=`echo $testscript | sed -e 's!.*/!!' -e 's/.test\$//'`
- scratchdir="$scratchbase/$testbase"
-
- prep_scratch
-
- case "$testscript" in
- *hardlinks*) TESTRUN_TIMEOUT=600 ;;
- *) TESTRUN_TIMEOUT=300 ;;
- esac
-
- set +e
- "$TOOLDIR/"testrun $RUNSHFLAGS "$testscript" >"$scratchdir/test.log" 2>&1
- result=$?
- set -e
-
- if [ "x$always_log" = xyes ] || ( [ $result != 0 ] && [ $result != 77 ] && [ $result != 78 ] )
- then
- echo "----- $testbase log follows"
- cat "$scratchdir/test.log"
- echo "----- $testbase log ends"
- if [ -f "$scratchdir/rsyncd.log" ]; then
- echo "----- $testbase rsyncd.log follows"
- cat "$scratchdir/rsyncd.log"
- echo "----- $testbase rsyncd.log ends"
- fi
- fi
-
- case $result in
- 0)
- echo "PASS $testbase"
- passed=`expr $passed + 1`
- maybe_discard_scratch
- ;;
- 77)
- # backticks will fill the whole file onto one line, which is a feature
- whyskipped=`cat "$scratchdir/whyskipped"`
- echo "SKIP $testbase ($whyskipped)"
- skipped_list="$skipped_list,$testbase"
- skipped=`expr $skipped + 1`
- maybe_discard_scratch
- ;;
- 78)
- # It failed, but we expected that. don't dump out error logs,
- # because most users won't want to see them. But do leave
- # the working directory around.
- echo "XFAIL $testbase"
- failed=`expr $failed + 1`
- ;;
- *)
- echo "FAIL $testbase"
- failed=`expr $failed + 1`
- if [ "x$nopersist" = xyes ]; then
- exit 1
- fi
- esac
-done
-
-echo '------------------------------------------------------------'
-echo "----- overall results:"
-echo " $passed passed"
-[ "$failed" -gt 0 ] && echo " $failed failed"
-[ "$skipped" -gt 0 ] && echo " $skipped skipped"
-[ "$missing" -gt 0 ] && echo " $missing missing"
-if [ "$full_run" = yes ] && [ "$expect_skipped" != IGNORE ]; then
- skipped_list=`echo "$skipped_list" | sed 's/^,//'`
- echo "----- skipped results:"
- echo " expected: $expect_skipped"
- echo " got: $skipped_list"
-else
- skipped_list=''
- expect_skipped=''
-fi
-echo '------------------------------------------------------------'
-
-# OK, so expr exits with 0 if the result is neither null nor zero; and
-# 1 if the expression is null or zero. This is the opposite of what
-# we want, and if we just call expr then this script will always fail,
-# because -e is set.
-
-result=`expr $failed + $missing || true`
-if [ "$result" = 0 ] && [ "$skipped_list" != "$expect_skipped" ]; then
- result=1
-fi
-echo "overall result is $result"
-exit $result