diff --git a/.github/workflows/almalinux-8-build.yml b/.github/workflows/almalinux-8-build.yml index a0c474e0b..8eb4598c3 100644 --- a/.github/workflows/almalinux-8-build.yml +++ b/.github/workflows/almalinux-8-build.yml @@ -67,7 +67,7 @@ jobs: # crtimes-not-supported skip matches the other Linux jobs; # daemon-chroot-acl and proxy-response-line-too-long skip because # the default (secure) transport opens no listening socket. - run: RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt,@testsuite/skiplist/almalinux-8.txt make check + run: RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/common.txt,@testsuite/skiplist/linux.txt make check - name: check (TCP daemon transport) # Second run exercising the real loopback-TCP daemon path. run: ./runtests.py --rsync-bin="$PWD/rsync" --use-tcp -j 8 diff --git a/testsuite/COVERAGE.md b/testsuite/COVERAGE.md index 48dd50d6a..470d87a09 100644 --- a/testsuite/COVERAGE.md +++ b/testsuite/COVERAGE.md @@ -90,7 +90,7 @@ Status legend: ✓ property asserted · `~` shallow / by an existing ported test | -x, --one-file-system | — | — | — | ✗ (needs a mount boundary) | | --preallocate / --fsync | — | — | — | ✗ | | -B, --block-size | — | — | — | ✗ | -| --max-alloc | max-alloc-zero-rejected*new*, daemon-max-alloc-zero*new* | — | — | ✓ zero rejected locally and when forwarded to the daemon (needs an old client) | +| --max-alloc | max-alloc-zero | — | — | ✓ zero resolves to each peer's supported ceiling; values above the limit are rejected | ### Filtering | option | test(s) | depth | x-dir | notes / gap | diff --git a/testsuite/fleettest.json.example b/testsuite/fleettest.json.example index 32c0da09a..d8b3b299b 100644 --- a/testsuite/fleettest.json.example +++ b/testsuite/fleettest.json.example @@ -40,7 +40,8 @@ "--disable-zstd", "--disable-md2man", "--disable-xxhash", - "--disable-lz4" + "--disable-lz4", + "--disable-idn" ] }, { @@ -53,7 +54,8 @@ "--disable-zstd", "--disable-md2man", "--disable-xxhash", - "--disable-lz4" + "--disable-lz4", + "--disable-idn" ] }, { @@ -74,7 +76,8 @@ "--disable-zstd", "--disable-md2man", "--disable-xxhash", - "--disable-lz4" + "--disable-lz4", + "--disable-idn" ] }, { @@ -87,7 +90,8 @@ "--disable-zstd", "--disable-md2man", "--disable-xxhash", - "--disable-lz4" + "--disable-lz4", + "--disable-idn" ] }, { @@ -103,9 +107,8 @@ "--disable-zstd", "--disable-lz4" ], - "_expect_skip_extra_comment": "daemon-max-alloc-zero needs the old static client (git-archive export-ignore'd); protected-regular's fs.protected_regular path is not exercised on this box.", + "_expect_skip_extra_comment": "protected-regular's fs.protected_regular path is not exercised on this box.", "expect_skip_extra": [ - "daemon-max-alloc-zero", "protected-regular" ] }, @@ -116,10 +119,6 @@ "workflow": "ubuntu-22.04-build.yml", "configure_flags": [ "--with-rrsync" - ], - "_expect_skip_extra_comment": "The symlink-race tests now run on every platform (one portable race-free resolver), so only daemon-max-alloc-zero skips here: it needs the old static client, which git-archive omits (.gitattributes export-ignore) from the source-only fleet push.", - "expect_skip_extra": [ - "daemon-max-alloc-zero" ] }, { @@ -135,10 +134,6 @@ ], "configure_flags": [ "--with-rrsync" - ], - "_expect_skip_extra_comment": "daemon-max-alloc-zero needs the old static client, which git-archive omits (.gitattributes export-ignore) from the source-only fleet push.", - "expect_skip_extra": [ - "daemon-max-alloc-zero" ] }, { @@ -154,10 +149,6 @@ ], "configure_flags": [ "--with-rrsync" - ], - "_expect_skip_extra_comment": "daemon-max-alloc-zero needs the old static client, which git-archive omits (.gitattributes export-ignore) from the source-only fleet push.", - "expect_skip_extra": [ - "daemon-max-alloc-zero" ] }, { @@ -173,10 +164,6 @@ ], "configure_flags": [ "--with-rrsync" - ], - "_expect_skip_extra_comment": "daemon-max-alloc-zero needs the old static client, which git-archive omits (.gitattributes export-ignore) from the source-only fleet push.", - "expect_skip_extra": [ - "daemon-max-alloc-zero" ] }, { @@ -187,10 +174,9 @@ "configure_flags": [ "--with-rrsync" ], - "_expect_skip_extra_comment": "Box-specific, not workflow-specific: this VM has no separate filesystem for a cross-device temp dir, and daemon-max-alloc-zero needs the old static client that the source-only fleet push omits (.gitattributes export-ignore), as on ubuntu-2004. fs.protected_regular is enabled in /etc/sysctl.d/90-rsync-fleettest.conf so protected-regular runs here rather than skipping.", + "_expect_skip_extra_comment": "Box-specific, not workflow-specific: this VM has no separate filesystem for a cross-device temp dir. fs.protected_regular is enabled in /etc/sysctl.d/90-rsync-fleettest.conf so protected-regular runs here rather than skipping.", "expect_skip_extra": [ - "chmod-temp-dir", - "daemon-max-alloc-zero" + "chmod-temp-dir" ] }, { @@ -284,11 +270,12 @@ ] }, { - "_comment": "Cygwin: non-root plain user (no sudo), binary is rsync.exe.", + "_comment": "Cygwin: non-root plain user (no sudo), binary is rsync.exe. The workflow installs only the python39 package, so pin python3.9 here too: the box's default python3 (3.12) has a working AF_UNIX socketpair, which makes daemon-stdin-local-socket run where cygwin.txt expects it to skip. Keep python39 current: the xattr tests need the os.getxattr that newer Cygwin builds (3.9.25+) expose.", "name": "cygwin", "ssh_host": "win11", "workflow": "cygwin-build.yml", "rsync_bin": "rsync.exe", + "python": "python3.9", "privilege": "user", "configure_flags": [ "--with-rrsync" diff --git a/testsuite/malicious-sender-delete-scope_test.py b/testsuite/malicious-sender-delete-scope_test.py index 34f109430..fcd1fde3b 100644 --- a/testsuite/malicious-sender-delete-scope_test.py +++ b/testsuite/malicious-sender-delete-scope_test.py @@ -73,8 +73,16 @@ # exactly the threat model. mal_src = SCRATCHDIR / 'mal-rsync' rmtree(mal_src) -shutil.copytree(SRCDIR, mal_src, symlinks=True, - ignore=shutil.ignore_patterns('testtmp', '.git', 'auto-build-save')) +try: + shutil.copytree(SRCDIR, mal_src, symlinks=True, + ignore=shutil.ignore_patterns('testtmp', '.git', 'auto-build-save')) +except shutil.Error as e: + # Cygwin's python 3.9 has os.listxattr, and copystat() on a fresh symlink + # fails with EACCES there. The link itself was created, so only a + # non-symlink error means the copy is incomplete. + if any(not (os.path.islink(src) and os.path.islink(dst)) + for src, dst, _why in e.args[0]): + raise flist_c = mal_src / 'flist.c' PATCH_OLD = "xflags = XMIT_TOP_DIR | XMIT_NO_CONTENT_DIR;" diff --git a/testsuite/pseudo-paths-daemon_test.py b/testsuite/pseudo-paths-daemon_test.py index daab15fd2..f6d2d869e 100644 --- a/testsuite/pseudo-paths-daemon_test.py +++ b/testsuite/pseudo-paths-daemon_test.py @@ -21,7 +21,10 @@ test_skipped('bash is not installed') raise SystemExit(0) -probe_bash = subprocess.run([bash, '-c', 'echo "probe" > >(cat > /dev/null)'], capture_output=True) +# runtests.py exports POSIXLY_CORRECT=1, and bash before 5.1 disables process +# substitution in POSIX mode -- the very syntax bash is required for here. +bash_env = {k: v for k, v in os.environ.items() if k != 'POSIXLY_CORRECT'} +probe_bash = subprocess.run([bash, '-c', 'echo "probe" > >(cat > /dev/null)'], env=bash_env, capture_output=True) if probe_bash.returncode != 0: test_skipped('bash process substitution is not supported on this system') raise SystemExit(0) @@ -86,7 +89,7 @@ def kill_daemon(proc): cmd_host = f"{rsync_bin} --daemon --no-detach --config={conf_host} --port={port_host} --address=127.0.0.1 > >(cat > {out_host}) 2> {err_host} < /dev/null" # Executes directly as the host user - daemon_host = subprocess.Popen([bash, '-c', cmd_host], start_new_session=True) + daemon_host = subprocess.Popen([bash, '-c', cmd_host], env=bash_env, start_new_session=True) try: # Bounded readiness polling (Wait for daemon to bind to the port) @@ -190,7 +193,7 @@ def kill_daemon(proc): cmd_ns = f"{ns_rsync_bin} --daemon --no-detach --config={conf_ns} --port={port_ns} --address=127.0.0.1 > >(cat > {out_ns}) 2> {err_ns} < /dev/null" namespace_cmd = launcher + unshare_argv + [bash, '-c', cmd_ns] - daemon_ns = subprocess.Popen(namespace_cmd, stdin=subprocess.DEVNULL, start_new_session=True) + daemon_ns = subprocess.Popen(namespace_cmd, env=bash_env, stdin=subprocess.DEVNULL, start_new_session=True) try: for _ in range(50): diff --git a/testsuite/pseudo-paths_test.py b/testsuite/pseudo-paths_test.py index 65dce2459..2c0a5b25c 100644 --- a/testsuite/pseudo-paths_test.py +++ b/testsuite/pseudo-paths_test.py @@ -21,10 +21,14 @@ if bash is None: test_skipped('bash is unavailable, cannot test process substitution') +# runtests.py exports POSIXLY_CORRECT=1, and bash before 5.1 disables process +# substitution in POSIX mode -- the very syntax bash is required for here. +bash_env = {k: v for k, v in os.environ.items() if k != 'POSIXLY_CORRECT'} # Verify the host bash actually supports process substitution probe = subprocess.run( [bash, '-c', 'echo "probe" > >(cat > /dev/null)'], - capture_output=True + capture_output=True, + env=bash_env, ) if probe.returncode != 0: test_skipped('bash process substitution is not supported on this system') @@ -54,6 +58,7 @@ proc = subprocess.run( [bash, '-c', bash_script], capture_output=True, + env=bash_env, text=True, timeout=10, ) @@ -91,6 +96,7 @@ proc_confined = subprocess.run( [bash, '-c', bash_script_confined], capture_output=True, + env=bash_env, text=True, timeout=10, ) @@ -122,6 +128,7 @@ proc_trailing = subprocess.run( [bash, '-c', trailing_script], capture_output=True, + env=bash_env, text=True, timeout=10, ) @@ -148,7 +155,7 @@ # 3A: --files-from=/dev/stdin # Piping printf directly into rsync forces /dev/stdin to resolve to pipe:[N] stdin_script = f'printf "stdin_test.txt\\n" | {rsync_base_cmd} --files-from=/dev/stdin {src_path} {dest_path}' -proc_stdin = subprocess.run([bash, '-c', stdin_script], capture_output=True, text=True, timeout=10) +proc_stdin = subprocess.run([bash, '-c', stdin_script], env=bash_env, capture_output=True, text=True, timeout=10) if proc_stdin.returncode != 0: test_fail(f'rsync failed to read --files-from=/dev/stdin (rc={proc_stdin.returncode}, stderr={proc_stdin.stderr.strip()!r})') @@ -172,7 +179,7 @@ symlink_list.symlink_to('/dev/stdin') symlink_script = f'printf "stdin_test.txt\\n" | {rsync_base_cmd} --files-from={shlex.quote(str(symlink_list))} {src_path} {dest_path}' -proc_symlink = subprocess.run([bash, '-c', symlink_script], capture_output=True, text=True, timeout=10) +proc_symlink = subprocess.run([bash, '-c', symlink_script], env=bash_env, capture_output=True, text=True, timeout=10) if proc_symlink.returncode != 0: test_fail(f'rsync failed with --files-from=rsync.list -> /dev/stdin (rc={proc_symlink.returncode}, stderr={proc_symlink.stderr.strip()!r})') @@ -206,7 +213,7 @@ # Run rsync using --files-from targeting the spoofed path. spoofed_script = f'cd {shlex.quote(str(base))} && {rsync_base_cmd} --files-from=dev/fd/99 {src_path} {dest_path}' -proc_spoofed = subprocess.run([bash, '-c', spoofed_script], capture_output=True, text=True, timeout=10) +proc_spoofed = subprocess.run([bash, '-c', spoofed_script], env=bash_env, capture_output=True, text=True, timeout=10) stderr_lower = proc_spoofed.stderr.lower() @@ -300,6 +307,7 @@ proc_unshare_4a = subprocess.run( unshare_cmd_4a, capture_output=True, + env=bash_env, text=True, timeout=10, ) @@ -338,6 +346,7 @@ proc_unshare_4b = subprocess.run( unshare_cmd_4b, capture_output=True, + env=bash_env, text=True, timeout=10, ) diff --git a/testsuite/read-batch-pipe_test.py b/testsuite/read-batch-pipe_test.py index b40638313..505678a7e 100644 --- a/testsuite/read-batch-pipe_test.py +++ b/testsuite/read-batch-pipe_test.py @@ -19,9 +19,13 @@ if bash is None: test_skipped('bash is unavailable, cannot test process substitution') +# runtests.py exports POSIXLY_CORRECT=1, and bash before 5.1 disables process +# substitution in POSIX mode -- the very syntax bash is required for here. +bash_env = {k: v for k, v in os.environ.items() if k != 'POSIXLY_CORRECT'} # Verify the host bash actually supports process substitution probe = subprocess.run( [bash, '-c', 'cat <(echo "probe")'], + env=bash_env, capture_output=True) if probe.returncode != 0: @@ -57,6 +61,7 @@ proc_read = subprocess.run( [bash, '-c', bash_script], capture_output=True, + env=bash_env, text=True, timeout=10, ) diff --git a/testsuite/skiplist/README.md b/testsuite/skiplist/README.md index 2e822eff8..31dd457ed 100644 --- a/testsuite/skiplist/README.md +++ b/testsuite/skiplist/README.md @@ -23,7 +23,6 @@ different tests merge cleanly. | file | contents | | --- | --- | | `common.txt` | skipped on every platform that runs the oracle — mostly `require_tcp` / `require_asan` tests, which the default stdio-pipe `make check` cannot satisfy | -| `almalinux-8.txt` | AlmaLinux 8 container additions | | `linux.txt` | Linux-only additions | | `macos.txt` | macOS-only additions | | `cygwin.txt` | Cygwin-only additions | diff --git a/testsuite/skiplist/almalinux-8.txt b/testsuite/skiplist/almalinux-8.txt deleted file mode 100644 index c814b9563..000000000 --- a/testsuite/skiplist/almalinux-8.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Tests expected to SKIP. One name per line, '#' starts a comment; the file -# must stay sorted and duplicate-free (runtests.py enforces both). Referenced -# from a workflow as RSYNC_EXPECT_SKIPPED=@testsuite/skiplist/[,@...]. -# See testsuite/skiplist/README.md. -# -# AlmaLinux 8 container additions to common.txt and linux.txt. - -pseudo-paths # Bash process substitution is unavailable in the AlmaLinux 8 container -pseudo-paths-daemon -read-batch-pipe diff --git a/testsuite/skiplist/macos.txt b/testsuite/skiplist/macos.txt index 6a82dbf6a..7ba8d589d 100644 --- a/testsuite/skiplist/macos.txt +++ b/testsuite/skiplist/macos.txt @@ -21,7 +21,7 @@ open-noatime partial-protected-regular-retry-linux preallocate protected-regular -pseudo-paths # dynamically skips on runners lacking bash process substitution +pseudo-paths # Linux-only: procfs pseudo-paths pseudo-paths-daemon read-batch-pipe readonly-partial-abort-mode-regression #