Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion testsuite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ setting a module-level `fleet_nonroot = True`, so the set is maintained in the
test files and new privilege-sensitive tests join automatically with no
fleet-config change.

A target with `"protocols": [30, 29]` runs one extra stdio-pipe pass per listed
version, each forcing that older wire version with `runtests --protocol=N` — the
fleet analogue of a workflow's `check30`/`check29` steps. The passes reuse the
same parsed `RSYNC_EXPECT_SKIPPED` list as the pipe run and show up as `protoNN`
columns in the report (and `--timing` breakdown). Targets that don't set
`protocols` show `-` there.

Run it from inside a checkout (it builds the current directory's HEAD; use
`--repo PATH` for another tree):

Expand All @@ -159,7 +166,7 @@ python3 testsuite/fleettest.py --timing # per-target wall-clock bre
```

`--timing` adds a per-target breakdown after the report — total wall-clock plus
the push / build / pipe / tcp / nonroot phases, sorted slowest-first. Targets
the push / build / pipe / tcp / protoNN / nonroot phases, sorted slowest-first. Targets
run in parallel, so the whole run is gated by the slowest one; the phase columns
show whether that target's hold-up is the push, the build, or a test pass.

Expand Down
12 changes: 9 additions & 3 deletions testsuite/fleettest.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
"this target mirrors), configure_flags. Optional (with defaults): make (\"make\"),",
"python (\"python3\"), rsync_bin (\"rsync\"; \"rsync.exe\" on Cygwin), privilege",
"(\"root\" | \"sudo\" | \"user\"), pipe_jobs/tcp_jobs (8), builddir (\"rsync-citest\",",
"relative to the remote $HOME), env_prefix, configure_pre, nonroot.",
"relative to the remote $HOME), env_prefix, configure_pre, nonroot, protocols.",
"",
"nonroot: true reruns -- as the non-root ssh user, after the sudo runs -- the",
"tests that declare `fleet_nonroot = True` at module level (so the set is",
"maintained in the test files, not here). Keys starting with \"_\" are comments.",
"See testsuite/README.md."
"maintained in the test files, not here).",
"",
"protocols: [30, 29] adds one extra stdio-pipe test pass per listed version,",
"each run with runtests --protocol=N (the fleet analogue of a workflow's",
"check30/check29 steps) and shown as a protoNN column. Keys starting with",
"\"_\" are comments. See testsuite/README.md."
],
"targets": [
{
Expand Down Expand Up @@ -70,11 +74,13 @@
"configure_flags": ["--with-rrsync"]
},
{
"_comment": "Modern Ubuntu (mirrors ubuntu-build.yml). protocols: [30, 29] also runs the workflow's check30/check29 passes as extra stdio-pipe runs.",
"name": "ubuntu-2604",
"ssh_host": "runner@ubuntu-2604",
"workflow": "ubuntu-build.yml",
"privilege": "sudo",
"nonroot": true,
"protocols": [30, 29],
"configure_flags": ["--with-rrsync"]
},
{
Expand Down
51 changes: 45 additions & 6 deletions testsuite/fleettest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
from the workflow (not hardcoded). The --use-tcp run never sets an expected-skip
list (matching the workflows), so only test FAILs matter there.

A target may also list older "protocols" (e.g. [30, 29]) in the fleet config:
each runs as an extra stdio-pipe pass with runtests --protocol=N (the fleet
analogue of a workflow's check30/check29 steps), using the same parsed skip list
as the pipe run, and shows up as a protoNN column in the report.

The fleet -- which machines, how to reach and build each -- is read from a JSON
config: ~/.fleettest.json if present, else fleettest.json next to this script,
or --fleet PATH. Copy the bundled fleettest.json.example to either location (or
Expand Down Expand Up @@ -128,6 +133,10 @@ class Target:
# user -- every test that declares `fleet_nonroot = True` (see
# discover_nonroot_tests). Mirrors a workflow's non-root check step.
nonroot: bool = False
# Older protocol versions to additionally exercise, each as a separate
# stdio-pipe pass with runtests --protocol=N (the fleet analogue of a
# workflow's check30/check29 steps). e.g. [30, 29]. Empty => proto pass off.
protocols: list[int] = dataclasses.field(default_factory=list)


def load_fleet(path: Path) -> list[Target]:
Expand Down Expand Up @@ -273,15 +282,18 @@ def build_script(t: Target) -> str:
)


def test_script(t: Target, transport: str, skip_csv: str | None, jobs: int) -> str:
def test_script(t: Target, transport: str, skip_csv: str | None, jobs: int,
protocol: int | None = None) -> str:
rb = f'--rsync-bin="$PWD/{t.rsync_bin}"'
tcp = " --use-tcp" if transport == "tcp" else ""
# protocol forces an older wire version (mirrors `make check30`/`check29`).
proto = f" --protocol={protocol}" if protocol is not None else ""
# PYTHONDONTWRITEBYTECODE: don't drop root-owned __pycache__/*.pyc into the
# tree (a sudo run would, breaking the next non-root push --delete).
env = "PYTHONDONTWRITEBYTECODE=1 "
if skip_csv:
env += f"RSYNC_EXPECT_SKIPPED={skip_csv} "
runtests = f'{t.python} runtests.py {rb}{tcp} -j {jobs}'
runtests = f'{t.python} runtests.py {rb}{tcp}{proto} -j {jobs}'
# env_prefix (e.g. a brew PATH) must reach the test too: some tests build a
# helper binary on the fly (a test may invoke `make`, which needs gawk etc.),
# so the build tools must be on PATH at test time.
Expand Down Expand Up @@ -436,6 +448,23 @@ def run_target(t: Target, args, staging: str) -> TargetResult:
log(f"[{t.name}] {transport} done "
f"({'ok' if res.transports[transport].ok else 'ISSUE'})")

# Extra older-protocol passes (mirroring the workflow's check30/check29
# steps): same stdio-pipe transport and skip list as `make check`, but with
# runtests --protocol=N forcing an older wire version. Only targets that list
# `protocols` opt in; skipped under --transport tcp (these are pipe runs).
if t.protocols and "pipe" in args.transports:
skip_csv = parse_workflow_skip(t.workflow)
jobs = args.jobs if args.jobs else t.pipe_jobs
for proto in t.protocols:
label = f"proto{proto}"
cmd = test_script(t, "pipe", skip_csv, jobs, protocol=proto)
t0 = time.monotonic()
r = run_on(t, cmd, timeout=2400)
res.timings[label] = time.monotonic() - t0
res.transports[label] = parse_transport(label, r, skip_csv is not None)
log(f"[{t.name}] {label} done "
f"({'ok' if res.transports[label].ok else 'ISSUE'})")

# Extra non-root pass (after the sudo runs) for targets that opt in, running
# the tests that declare `fleet_nonroot = True` (discovered in main()).
if t.nonroot and args.nonroot_tests:
Expand Down Expand Up @@ -479,9 +508,13 @@ def print_report(results: list[TargetResult], args, fleet: list[Target]) -> bool
by_name = {t.name: t for t in fleet}
order = {t.name: i for i, t in enumerate(fleet)}
results.sort(key=lambda r: order.get(r.target, 99))
# The 'nonroot' column appears only when some target ran a non-root pass;
# targets without one show "-" there (a neutral N/A, not a failure).
# protoNN columns appear only when some target ran that older-protocol pass;
# the 'nonroot' column only when some target ran a non-root pass. Targets
# without a given pass show "-" there (a neutral N/A, not a failure).
transports = list(args.transports)
protos = {k for r in results for k in r.transports if k.startswith("proto")}
# highest protocol first (proto30 before proto29), matching check30/check29.
transports += sorted(protos, key=lambda c: int(c[len("proto"):]), reverse=True)
if any("nonroot" in r.transports for r in results):
transports.append("nonroot")
ts = time.strftime("%Y-%m-%d %H:%M")
Expand Down Expand Up @@ -588,7 +621,11 @@ def print_timing(results: list[TargetResult]) -> None:
timed = [r for r in results if r.timings]
if not timed:
return
phases = [p for p in _TIMING_PHASES if any(p in r.timings for r in timed)]
# Insert any protoNN phases (highest first) just before nonroot, in run order.
protos = sorted({k for r in timed for k in r.timings if k.startswith("proto")},
key=lambda c: int(c[len("proto"):]), reverse=True)
order = [p for p in _TIMING_PHASES if p != "nonroot"] + protos + ["nonroot"]
phases = [p for p in order if any(p in r.timings for r in timed)]

def total(r: TargetResult) -> float:
# Failed-early targets have no "total"; sum the phases they did reach.
Expand Down Expand Up @@ -745,8 +782,10 @@ def main() -> int:
for t in fleet:
host = t.ssh_host or "(local)"
skip = parse_workflow_skip(t.workflow)
proto = (",".join(f"proto{p}" for p in t.protocols)
if t.protocols else "none")
print(f"{t.name:12} {host:18} {t.make:6} "
f"pipe-skip={'set' if skip else 'unset'}")
f"pipe-skip={'set' if skip else 'unset'} protocols={proto}")
return 0

chosen = fleet
Expand Down
Loading