]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
replace runtests.sh with runtests.py
authorAndrew Tridgell <andrew@tridgell.net>
Wed, 22 Apr 2026 01:45:24 +0000 (11:45 +1000)
committerAndrew Tridgell <andrew@tridgell.net>
Wed, 22 Apr 2026 02:34:39 +0000 (12:34 +1000)
Rewrite the test runner in Python with proper command-line options
including --valgrind which directs valgrind output to per-process
log files so it doesn't interfere with test output comparisons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Makefile.in
runtests.py [new file with mode: 0755]
runtests.sh [deleted file]
testsuite/ssh-basic.test

index 6f188ee8ad728701fc9ec3a1b2837f67157fa941..c2fe775bea4929e12006777ddbf18da93b4d45e4 100644 (file)
@@ -313,15 +313,15 @@ test: check
 
 .PHONY: check
 check: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
-       rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh
+       $(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT)
 
 .PHONY: check29
 check29: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
-       rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh --protocol=29
+       $(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) --protocol=29
 
 .PHONY: check30
 check30: all $(CHECK_PROGS) $(CHECK_SYMLINKS)
-       rsync_bin=`pwd`/rsync$(EXEEXT) $(srcdir)/runtests.sh --protocol=30
+       $(srcdir)/runtests.py --rsync-bin=`pwd`/rsync$(EXEEXT) --protocol=30
 
 wildtest.o: wildtest.c t_stub.o lib/wildmatch.c rsync.h config.h
 wildtest$(EXEEXT): wildtest.o lib/compat.o lib/snprintf.o @BUILD_POPT@
@@ -350,7 +350,7 @@ testsuite/xattrs-hlink.test:
 
 .PHONY: installcheck
 installcheck: $(CHECK_PROGS) $(CHECK_SYMLINKS)
-       POSIXLY_CORRECT=1 TOOLDIR=`pwd` rsync_bin="$(bindir)/rsync$(EXEEXT)" srcdir="$(srcdir)" $(srcdir)/runtests.sh
+       $(srcdir)/runtests.py --rsync-bin="$(bindir)/rsync$(EXEEXT)" --srcdir="$(srcdir)" --tooldir=`pwd`
 
 # TODO: Add 'dist' target; need to know which files will be included
 
diff --git a/runtests.py b/runtests.py
new file mode 100755 (executable)
index 0000000..0945469
--- /dev/null
@@ -0,0 +1,411 @@
+#!/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()
diff --git a/runtests.sh b/runtests.sh
deleted file mode 100755 (executable)
index 0c463be..0000000
+++ /dev/null
@@ -1,360 +0,0 @@
-#! /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
index 1559ca231ec14cc384059b837b2d43445da19e6a..83f718054194470bb207b1d735905dc39861a92c 100644 (file)
@@ -6,7 +6,7 @@
 # This program is distributable under the terms of the GNU GPL (see
 # COPYING)
 
-# This script tests ssh, if possible.  It's called by runtests.sh
+# This script tests ssh, if possible.  It's called by runtests.py
 
 . "$suitedir/rsync.fns"