From 15e7c0447ebd3696eb63b20fb4b35233eb8dce4e Mon Sep 17 00:00:00 2001 From: Zen Dodd Date: Sat, 10 Oct 2026 09:16:06 +1000 Subject: [PATCH] ci: enforce BSD and Solaris profiles --- .github/workflows/freebsd-build.yml | 8 +++-- .github/workflows/netbsd-build.yml | 8 +++-- .github/workflows/openbsd-build.yml | 12 ++++--- .github/workflows/solaris-build.yml | 8 +++-- testsuite/fleettest.py | 15 ++++++--- testsuite/tests/workflow-profile_test.py | 40 ++++++++++++++++++++---- 6 files changed, 66 insertions(+), 25 deletions(-) diff --git a/.github/workflows/freebsd-build.yml b/.github/workflows/freebsd-build.yml index 32de3e78..faa5698e 100644 --- a/.github/workflows/freebsd-build.yml +++ b/.github/workflows/freebsd-build.yml @@ -51,12 +51,13 @@ jobs: ./configure --with-rrsync \ -disable-zstd --disable-md2man --disable-xxhash --disable-lz4 --disable-idn \ --enable-roll-simd --enable-roll-asm --enable-md5-asm - make + make check-progs ./rsync --version - make check - ./testsuite/runtests.py --rsync-bin=`pwd`/rsync --use-tcp -j 8 + RSYNC_TEST_PROFILES=pipe,non-asan,root,self-peer,freebsd,no-idn,no-xxhash,no-zstd-threads ./testsuite/runtests.py --rsync-bin="$PWD/rsync" --receipt=test-results/freebsd-pipe.json -j 8 + RSYNC_TEST_PROFILES=non-asan,root,self-peer,freebsd,no-idn,no-xxhash,no-zstd-threads ./testsuite/runtests.py --rsync-bin="$PWD/rsync" --use-tcp --receipt=test-results/freebsd-tcp.json -j 8 ./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true - name: save artifact + if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: retention-days: 45 @@ -69,3 +70,4 @@ jobs: rsyncd.conf.5 rrsync.1 rrsync + test-results/*.json diff --git a/.github/workflows/netbsd-build.yml b/.github/workflows/netbsd-build.yml index 4ac4675e..8d86b785 100644 --- a/.github/workflows/netbsd-build.yml +++ b/.github/workflows/netbsd-build.yml @@ -50,12 +50,13 @@ jobs: run: | uname -a ./configure --with-rrsync --disable-zstd --disable-md2man --disable-xxhash --disable-lz4 --disable-idn - make + make check-progs ./rsync --version - make check - ./testsuite/runtests.py --rsync-bin=`pwd`/rsync --use-tcp -j 8 + RSYNC_TEST_PROFILES=pipe,non-asan,root,self-peer,netbsd,no-idn,no-xxhash,no-zstd-threads ./testsuite/runtests.py --rsync-bin="$PWD/rsync" --receipt=test-results/netbsd-pipe.json -j 8 + RSYNC_TEST_PROFILES=non-asan,root,self-peer,netbsd,no-idn,no-xxhash,no-zstd-threads ./testsuite/runtests.py --rsync-bin="$PWD/rsync" --use-tcp --receipt=test-results/netbsd-tcp.json -j 8 ./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true - name: save artifact + if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: retention-days: 45 @@ -68,3 +69,4 @@ jobs: rsyncd.conf.5 rrsync.1 rrsync + test-results/*.json diff --git a/.github/workflows/openbsd-build.yml b/.github/workflows/openbsd-build.yml index 0b7b177e..19d328e0 100644 --- a/.github/workflows/openbsd-build.yml +++ b/.github/workflows/openbsd-build.yml @@ -51,7 +51,7 @@ jobs: export AUTOCONF_VERSION=2.71 export AUTOMAKE_VERSION=1.16 ./configure --with-rrsync --disable-zstd --disable-md2man --disable-xxhash --disable-lz4 --disable-idn - make + make check-progs ./rsync --version # The flipper (symlink-race) tests are excluded on OpenBSD, as on the # fleet's OpenBSD box: this kernel has a connect()-under-rename-load @@ -60,18 +60,19 @@ jobs: # dev-notes/openbsd-connect-lost-wakeup-report.txt); the protections # they exercise are verified on the Linux/BSD boxes. export RSYNC_EXCLUDE=acl-symlink-race,sender-readlink-atfd,sender-remove-source-secure - make check + RSYNC_TEST_PROFILES=pipe,non-asan,root,self-peer,openbsd,no-idn,no-xxhash,no-zstd-threads ./testsuite/runtests.py --rsync-bin="$PWD/rsync" --receipt=test-results/openbsd-pipe.json -j 8 # The --use-tcp daemon tests run at -j2 here (vs -j8 elsewhere): this # job runs inside a nested VM, and at -j8 the many concurrent loopback # daemons occasionally lose a connection-handshake timing race under # that resource pressure, hanging one test to the 300s timeout. It is # an environment artifact, not an rsync bug (the handshake is # deadlock-free and unreproducible elsewhere, even pinned to 1 CPU at - # -j8); -j2 keeps the VM from over-subscribing. The pipe `make check` - # above stays at the default parallelism. - ./testsuite/runtests.py --rsync-bin=`pwd`/rsync --use-tcp -j 2 + # -j8); -j2 keeps the VM from over-subscribing. The pipe pass above + # stays at the default parallelism. + RSYNC_TEST_PROFILES=non-asan,root,self-peer,openbsd,no-idn,no-xxhash,no-zstd-threads ./testsuite/runtests.py --rsync-bin="$PWD/rsync" --use-tcp --receipt=test-results/openbsd-tcp.json -j 2 ./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true - name: save artifact + if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: retention-days: 45 @@ -84,3 +85,4 @@ jobs: rsyncd.conf.5 rrsync.1 rrsync + test-results/*.json diff --git a/.github/workflows/solaris-build.yml b/.github/workflows/solaris-build.yml index a76736a8..80a00f8e 100644 --- a/.github/workflows/solaris-build.yml +++ b/.github/workflows/solaris-build.yml @@ -49,12 +49,13 @@ jobs: run: | uname -a ./configure --with-rrsync -disable-zstd --disable-md2man --disable-xxhash --disable-lz4 --disable-idn - make + make check-progs ./rsync --version - make check - ./testsuite/runtests.py --rsync-bin=`pwd`/rsync --use-tcp -j 8 + RSYNC_TEST_PROFILES=pipe,non-asan,root,self-peer,solaris,no-idn,no-xxhash,no-zstd-threads ./testsuite/runtests.py --rsync-bin="$PWD/rsync" --receipt=test-results/solaris-pipe.json -j 8 + RSYNC_TEST_PROFILES=non-asan,root,self-peer,solaris,no-idn,no-xxhash,no-zstd-threads ./testsuite/runtests.py --rsync-bin="$PWD/rsync" --use-tcp --receipt=test-results/solaris-tcp.json -j 8 ./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true - name: save artifact + if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: retention-days: 45 @@ -67,3 +68,4 @@ jobs: rsyncd.conf.5 rrsync.1 rrsync + test-results/*.json diff --git a/testsuite/fleettest.py b/testsuite/fleettest.py index 38f1326e..0b9a6be5 100755 --- a/testsuite/fleettest.py +++ b/testsuite/fleettest.py @@ -302,13 +302,18 @@ def push_argv(target: Target, staging: str) -> list[str]: def parse_workflow_profiles(workflow: str, make_target: str = "check") -> str | None: path = WORKFLOWS / workflow try: - text = path.read_text() + lines = path.read_text().splitlines() except OSError: return None - rx = re.compile(r"RSYNC_TEST_PROFILES=(\S+).*?\s+make\s+" - + re.escape(make_target) + r"'?\s*$", re.M) - match = rx.search(text) - return match.group(1) if match else None + make_call = re.compile(r'\bmake\s+' + re.escape(make_target) + r'(?:\s|[\'\"]|$)') + for line in lines: + match = re.search(r'RSYNC_TEST_PROFILES=(\S+)', line) + if not match: + continue + direct = make_target == 'check' and 'runtests.py' in line and '--use-tcp' not in line + if direct or make_call.search(line): + return match.group(1) + return None def tcp_profiles(workflow: str) -> str | None: diff --git a/testsuite/tests/workflow-profile_test.py b/testsuite/tests/workflow-profile_test.py index 246a778e..6b31002e 100644 --- a/testsuite/tests/workflow-profile_test.py +++ b/testsuite/tests/workflow-profile_test.py @@ -22,6 +22,13 @@ PROFILE_ASSIGNMENT = re.compile( r'RSYNC_TEST_PROFILES=(\S*\$\{\{\s*matrix\.profiles\s*\}\}\S*|\S+)') MATRIX_PROFILE = re.compile(r'\$\{\{\s*matrix\.profiles\s*\}\}') MATRIX_PROFILE_VALUE = re.compile(r'^\s+profiles:\s+(\S+)\s*$') +PLATFORM_PROFILES = {'non-asan', 'root', 'self-peer', 'no-idn', 'no-xxhash', 'no-zstd-threads'} +PLATFORM_WORKFLOWS = { + 'freebsd-build.yml': PLATFORM_PROFILES | {'freebsd'}, + 'netbsd-build.yml': PLATFORM_PROFILES | {'netbsd'}, + 'openbsd-build.yml': PLATFORM_PROFILES | {'openbsd'}, + 'solaris-build.yml': PLATFORM_PROFILES | {'solaris'}, +} def profile_specs(line, matrix_profiles): @@ -52,14 +59,27 @@ for path in workflows: matrix_profiles = sorted(match.group(1) for line in lines if (match := MATRIX_PROFILE_VALUE.match(line))) profile_lines = [line for line in lines if 'RSYNC_TEST_PROFILES=' in line] - for line in profile_lines: - for value in profile_specs(line, matrix_profiles): - for name in value.split(','): - runtests.load_profile(SRC / 'testsuite' / 'profiles' / f'{name}.json', known_tests) - references += 1 + specs = [value for line in profile_lines for value in profile_specs(line, matrix_profiles)] + names = {name for value in specs for name in value.split(',')} + for value in specs: + for name in value.split(','): + runtests.load_profile(SRC / 'testsuite' / 'profiles' / f'{name}.json', known_tests) + references += 1 + + required = PLATFORM_WORKFLOWS.get(path.name) + if required: + missing = required - names + if missing: + test_fail(f'{path.name}: missing profiles: {", ".join(sorted(missing))}') + if not any('test-results/*.json' in line for line in lines): + test_fail(f'{path.name}: profile receipts are not retained') + if not any('if: always()' in line for line in lines): + test_fail(f'{path.name}: failed profile receipts are not retained') pipe = next((line for line in profile_lines - if line.rstrip().endswith('make check') or line.rstrip().endswith("make check'")), None) + if '--use-tcp' not in line and ('runtests.py' in line + or line.rstrip().endswith('make check') + or line.rstrip().endswith("make check'"))), None) if not pipe: continue tcp = next((line for line in profile_lines if '--use-tcp' in line), None) @@ -73,6 +93,8 @@ for path in workflows: actual = {tuple(value.split(',')) for value in profile_specs(tcp, matrix_profiles)} if actual != expected: test_fail(f'{path.name}: TCP profiles differ from pipe profiles') + if required and ('--receipt=' not in pipe or '--receipt=' not in tcp): + test_fail(f'{path.name}: profiled transport has no receipt') if workflows and not references: test_fail('no workflow profile references found') @@ -137,5 +159,11 @@ if workflows: for target in fleet: if error := fleettest.validate_target_capabilities(target): test_fail(error) + required = PLATFORM_WORKFLOWS.get(target.workflow) + if required: + pipe_profiles = set(fleettest.target_profiles(target, 'pipe').split(',')) + tcp_profiles = set(fleettest.target_profiles(target, 'tcp').split(',')) + if pipe_profiles != required | {'pipe'} or tcp_profiles != required: + test_fail(f'{target.name}: fleet profiles differ from workflow') print(f'ok: {references} workflow profile references')