diff --git a/runtests.py b/runtests.py index 3a1c74852..574da01b0 100755 --- a/runtests.py +++ b/runtests.py @@ -191,35 +191,31 @@ def prep_scratch(scratchdir, srcdir, tooldir, setfacl_nodef): def _is_test_path(path): - base = os.path.basename(path) - return base.endswith('.test') or base.endswith(_PY_TEST_SUFFIX) + return os.path.basename(path).endswith(_PY_TEST_SUFFIX) def _testbase(path): """Strip the test extension to get the canonical test name.""" base = os.path.basename(path) - if base.endswith('.test'): - return base[:-len('.test')] if base.endswith(_PY_TEST_SUFFIX): return base[:-len(_PY_TEST_SUFFIX)] return base def collect_tests(suitedir, patterns): - """Collect test scripts (.test or _test.py) matching the given patterns.""" + """Collect test scripts (_test.py) matching the given patterns.""" if not patterns: - candidates = (glob.glob(os.path.join(suitedir, '*.test')) - + glob.glob(os.path.join(suitedir, '*' + _PY_TEST_SUFFIX))) + candidates = glob.glob(os.path.join(suitedir, '*' + _PY_TEST_SUFFIX)) tests = sorted(p for p in candidates if _is_test_path(p)) else: seen = set() tests = [] for pat in patterns: # Accept either bare name ("mkpath"), explicit extension, or glob. - if pat.endswith('.test') or pat.endswith('.py'): + if pat.endswith('.py'): pats = [pat] else: - pats = [pat + '.test', pat + _PY_TEST_SUFFIX] + pats = [pat + _PY_TEST_SUFFIX] for p in pats: for m in sorted(glob.glob(os.path.join(suitedir, p))): if _is_test_path(m) and m not in seen: diff --git a/testsuite/fleettest.py b/testsuite/fleettest.py index a4f3f2ab8..b200604a1 100755 --- a/testsuite/fleettest.py +++ b/testsuite/fleettest.py @@ -83,7 +83,11 @@ # source tree these point at, so it must be run from inside an rsync checkout # or given --repo PATH. REPO = Path.cwd() -WORKFLOWS = REPO / ".github" / "workflows" +# Source tree providing the test suite (runtests.py + testsuite/). Defaults to +# REPO; --testsuite-repo decouples it so one tree is built and another's suite is +# run against the result. +TESTSUITE_REPO = REPO +WORKFLOWS = TESTSUITE_REPO / ".github" / "workflows" # Fleet config (overridable with --fleet): ~/.fleettest.json is tried first, then # fleettest.json next to this script. The example template sits next to the @@ -815,18 +819,35 @@ def main() -> int: help="report per-target wall-clock (push/build/test) to find " "the slowest target") ap.add_argument("--repo", help="rsync source tree to build (default: cwd)") + ap.add_argument("--testsuite-repo", + help="rsync tree to take runtests.py + testsuite/ from " + "(default: --repo). Build one tree and run another's test " + "suite against it, e.g. --repo ../rsync-v3.4 --testsuite-repo .") ap.add_argument("--fleet", help="fleet config JSON (default: ~/.fleettest.json, " "else fleettest.json next to this script)") ap.add_argument("--list", action="store_true", help="list targets and exit") args = ap.parse_args() - global REPO, WORKFLOWS + global REPO, WORKFLOWS, TESTSUITE_REPO REPO = Path(args.repo).resolve() if args.repo else Path.cwd() - WORKFLOWS = REPO / ".github" / "workflows" - if not args.cleanup and not (REPO / "runtests.py").is_file(): - print(f"{REPO} is not an rsync source tree (no runtests.py); " - f"run from inside a checkout or pass --repo", file=sys.stderr) - return 2 + TESTSUITE_REPO = Path(args.testsuite_repo).resolve() if args.testsuite_repo else REPO + # The expected-skip lists travel with the suite, so read workflows from the + # tree that provides the tests. + WORKFLOWS = TESTSUITE_REPO / ".github" / "workflows" + if not args.cleanup: + # The Python test suite (runtests.py + testsuite/) comes from + # TESTSUITE_REPO, so that is where runtests.py must live. The build tree + # (REPO) only has to be a buildable rsync source -- it may be an older + # release whose runtests.py predates the Python suite, or lacks it. + if not (TESTSUITE_REPO / "runtests.py").is_file(): + print(f"{TESTSUITE_REPO} has no runtests.py; run from inside a " + f"checkout or pass --testsuite-repo a tree with the Python " + f"test suite", file=sys.stderr) + return 2 + if not (REPO / "rsync.h").is_file(): + print(f"{REPO} is not an rsync source tree (no rsync.h); " + f"run from inside a checkout or pass --repo", file=sys.stderr) + return 2 if args.fleet: config_path = Path(args.fleet).resolve() @@ -905,6 +926,19 @@ def main() -> int: print(f"git archive failed: {ar.stderr}", file=sys.stderr) return 2 + # --testsuite-repo: overlay another tree's runtests.py + testsuite/ onto + # the built source (merge, no delete). Build REPO's rsync, but run + # TESTSUITE_REPO's suite against it. The leftover .test files from REPO + # are ignored by a Python runtests.py (it globs *_test.py). + if TESTSUITE_REPO != REPO: + ov = subprocess.run( + f"git -C {TESTSUITE_REPO} archive HEAD -- runtests.py testsuite " + f"| tar -x -C {staging}", + shell=True, capture_output=True, text=True) + if ov.returncode != 0: + print(f"testsuite overlay archive failed: {ov.stderr}", file=sys.stderr) + return 2 + # Tests that opt into the non-root pass (same for every target). args.nonroot_tests = discover_nonroot_tests(Path(staging) / "testsuite")