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))