From 7490ad2420ccc6eefdc86caa3c8221237f518946 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 3 Jul 2026 17:34:52 +1000 Subject: [PATCH] testsuite: harden ki58/ki62 tests for the fleet Two of the integrated MC/DC-audit tests were environment-fragile: - ki58: asserted the exact string '100% done percentfile', but %f expands to the transfer-relative path (with leading dirs) when the source is an absolute path, so the basename assumption failed. Assert the literal-percent escape '100% done ' plus the file name instead (still RED on a broken %%: it emits '100%% done'). - ki62: killed daemon.kill() -- the listener -- but rsyncd forks a child per connection, so the child sender kept streaming and the receiver hung. Parse the transfer child's pid from the daemon log ('[pid] rsync on /') and kill that. killpg is not usable: the test daemon shares this test's process group. Fixes are unchanged; only the test drivers. --- testsuite/ki58-log-format-percent_test.py | 11 ++++++++--- testsuite/ki62-io-error-mask_test.py | 23 +++++++++++++++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/testsuite/ki58-log-format-percent_test.py b/testsuite/ki58-log-format-percent_test.py index 99bcc61f..826a7004 100644 --- a/testsuite/ki58-log-format-percent_test.py +++ b/testsuite/ki58-log-format-percent_test.py @@ -33,10 +33,15 @@ p = subprocess.run( if p.returncode != 0: test_fail(f"rsync exited {p.returncode}:\n{p.stderr}") -expected = '100% done percentfile' -if expected not in p.stdout: +# %f expands to the transfer-relative path, which includes leading directories +# when the source is addressed by an absolute path (as here) -- so don't pin the +# exact filename. The point of this test is the escape: "100% done " with a +# single literal percent followed by a space (a broken %% consumes the space or +# leaves a doubled percent), plus the transferred file name somewhere. +if '100% done ' not in p.stdout or 'percentfile' not in p.stdout: test_fail( - f"expected {expected!r} in --out-format output, got:\n{p.stdout}" + "expected '100% done ' (a single literal percent) and 'percentfile' in " + f"--out-format output, got:\n{p.stdout}" ) print("ki58-log-format-percent: %% literal-percent escape verified") diff --git a/testsuite/ki62-io-error-mask_test.py b/testsuite/ki62-io-error-mask_test.py index c0dc98f2..d57b4579 100644 --- a/testsuite/ki62-io-error-mask_test.py +++ b/testsuite/ki62-io-error-mask_test.py @@ -19,12 +19,15 @@ # Exit codes: 0 pass, 1 fail, 77 skip (killable daemon testing not possible -- # the test needs --use-tcp so it can SIGKILL the sender process directly). +import os +import re +import signal import subprocess import sys import time from rsyncfns import ( - FROMDIR, TODIR, claim_ports, make_data_file, makepath, rmtree, + FROMDIR, SCRATCHDIR, TODIR, claim_ports, make_data_file, makepath, rmtree, rsync_argv, start_rsyncd, test_fail, test_skipped, build_rsyncd_conf, USE_TCP, ) @@ -97,7 +100,23 @@ client = subprocess.Popen( # (the path that exercises the io_error -> exit-code mapping). time.sleep(2) -# Kill the sender (daemon) mid-transfer. +# Kill the sender mid-transfer. rsyncd forks a child per connection, so the +# actual sender is NOT `daemon` (the listener) -- killing only the listener +# leaves the child streaming and the receiver never sees EOF. The child logs +# "[pid] rsync on /" to the daemon log; kill that pid (plus the +# listener). The daemon shares this test's process group, so killpg is not an +# option (it would kill the test itself). +child_pids = [] +try: + logtext = (SCRATCHDIR / 'rsyncd.log').read_text() + child_pids = [int(pid) for pid in re.findall(r'\[(\d+)\] rsync on ', logtext)] +except OSError: + pass +for pid in child_pids: + try: + os.kill(pid, signal.SIGKILL) + except (ProcessLookupError, PermissionError): + pass if daemon.poll() is None: daemon.kill() try: