From bb556bf481ad66fef0befb1122ccbe2ed8e1450f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 19 Sep 2026 12:20:48 -0600 Subject: [PATCH] CI: run pspautotests on all four CPU backends The headless tests only ever exercised the JIT, since that's what headless defaults to. Run all four on the Linux runners, and add an arm64 Linux lane so the arm64 JIT is covered too - nothing else in the matrix tested it. test.py scales the wall clock to the backend instead of raising it for everyone: the interpreter needs 20s for gpu/rendertarget/copy, which does over a million guest-side vsprintf calls, while a hang under the JIT is still caught in five seconds. The frametest report artifact needs a per-OS name now that two Linux legs upload one. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build.yml | 29 +++++++++++++++++++++++++++-- test.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82d6f160f1..974091b14b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -228,6 +228,15 @@ jobs: args: ./b.sh --headless --unittest id: gcc-normal + # Gives the headless tests an arm64 host to run the CPU backends on - the arm64 JIT is a + # different backend from the x86-64 one, and nothing else in the matrix tests it on Linux. + - os: ubuntu-26.04-arm + extra: test + cc: clang + cxx: clang++ + args: ./b.sh --headless --unittest + id: clang-arm64 + - os: ubuntu-26.04 extra: android cc: clang @@ -456,7 +465,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-26.04, macos-latest] + os: [ubuntu-26.04, ubuntu-26.04-arm, macos-latest] runs-on: ${{ matrix.os }} needs: build @@ -501,9 +510,25 @@ jobs: run: ./PPSSPPUnitTest ALL - name: Execute headless tests + if: runner.os != 'Linux' working-directory: ${{ env.GITHUB_WORKSPACE }} run: python test.py -g --graphics=software + # The Linux runners carry the CPU backend coverage, on both x86-64 and arm64. Elsewhere the + # default (JIT) is enough - the backends are shared code, and four runs everywhere adds up. + # test.py picks a wall clock to match the backend, so the slower ones don't time out. + - name: Execute headless tests (all CPU backends) + if: runner.os == 'Linux' + working-directory: ${{ env.GITHUB_WORKSPACE }} + run: | + fail=0 + for cpu in jit jit-ir ir interpreter; do + echo "::group::CPU backend: $cpu" + python test.py -g --graphics=software --cpu=$cpu || fail=1 + echo "::endgroup::" + done + exit $fail + - name: Execute frametests if: runner.os == 'Linux' working-directory: ${{ env.GITHUB_WORKSPACE }} @@ -513,7 +538,7 @@ jobs: uses: actions/upload-artifact@v7 if: runner.os == 'Linux' && always() with: - name: frametest-report + name: frametest-report-${{ matrix.os }} path: frametests/out/ test-headless-alpine: diff --git a/test.py b/test.py index 23025d1638..80f3291f41 100755 --- a/test.py +++ b/test.py @@ -40,6 +40,17 @@ PPSSPP_EXE = None TEST_ROOT = "pspautotests/tests/" TIMEOUT = 5 +# The slower CPU backends need a longer wall clock on the CPU-heavy tests - the interpreter runs +# gpu/rendertarget/copy in about 4.5s against the JIT's 0.15s, since that test does over a million +# guest-side vsprintf calls. Scale the timeout per backend rather than raising it for everyone, so +# a genuine hang under the JIT is still caught in five seconds. +CPU_TIMEOUTS = { + 'interpreter': 20, + 'ir': 10, + 'jit': 5, + 'jit-ir': 5, +} + class Command(object): def __init__(self, cmd, data = None): self.cmd = cmd @@ -573,9 +584,22 @@ def init(): print("PPSSPPHeadless executable missing, please build one.") sys.exit(1) +def cpu_backend(args): + # Which backend headless will end up on, given the args we hand through to it. Headless defaults + # to the JIT, and a later flag overrides an earlier one, like its own parsing in Core/CmdLine.cpp. + short_flags = {'-i': 'interpreter', '-r': 'ir', '-j': 'jit', '-J': 'jit-ir'} + backend = 'jit' + for arg in args: + if arg in short_flags: + backend = short_flags[arg] + elif arg.startswith('--cpu='): + backend = arg[len('--cpu='):] + return backend + def run_tests(test_list, args): global PPSSPP_EXE, TIMEOUT returncode = 0 + timeout = CPU_TIMEOUTS.get(cpu_backend(args), TIMEOUT) test_filenames = [] for test in test_list: @@ -589,11 +613,11 @@ def run_tests(test_list, args): if len(test_filenames): # TODO: Maybe --compare should detect --graphics? - cmdline = [PPSSPP_EXE, '--root', TEST_ROOT + '../', '--compare', '--timeout-wall=' + str(TIMEOUT), '@-'] + cmdline = [PPSSPP_EXE, '--root', TEST_ROOT + '../', '--compare', '--timeout-wall=' + str(timeout), '@-'] cmdline.extend([i for i in args if i not in ['-g', '-m', '-b']]) c = Command(cmdline, '\n'.join(test_filenames)) - returncode = c.run(TIMEOUT * len(test_filenames)) + returncode = c.run(timeout * len(test_filenames)) print("Ran " + ' '.join(cmdline))