7.8 KiB
pspautotests
This test suite is located in the pspautotests submodule/subdirectory at the root of the repo. Its tests/ subdirectory contains tests arranged by category (audio/, cpu/, gpu/, threads/, etc.).
The runner script test.py at the repo root contains two lists: tests_good (known-passing regression tests) and tests_next (tests that don't yet pass — move things here → tests_good by fixing PPSSPP).
Tests are compiled as PRX binaries (a variation on ELF) which PPSSPP can load. They also have an .expected file alongside them, containing the reference output from running on a real PSP via PSPLink. The goal is for PPSSPPHeadless's output to match.
Prerequisites
- Initialize the submodule (if not done):
git submodule update --init - The PRX binaries and
.expectedfiles are already committed inside the submodule — no need to compile tests yourself just to run them. - To build PPSSPPHeadless itself, see the next section.
Building PPSSPPHeadless
Windows (Visual Studio)
Open Windows/PPSSPP.sln in Visual Studio and build the PPSSPPHeadless project, or use MSBuild directly:
MSBuild.exe -noAutoResponse Windows/PPSSPP.sln -target:PPSSPPHeadless -property:Configuration=Debug -property:Platform=x64 -maxCpuCount
The built executable will be at Windows/x64/Debug/PPSSPPHeadless.exe.
CMake-based builds do exist but are not the recommended way on Windows — stick with the VS solution.
Linux / macOS
Use ./b.sh --debug, then find the executable under build/ (e.g. build/Debug/PPSSPPHeadless).
Running tests
Using test.py (recommended)
Make sure Python is available. On Windows the Microsoft Store alias may interfere; use py or the full path to python.exe.
- Run all good tests:
py test.py -g - Run all broken/next tests:
py test.py -b - Run all tests (good + next):
py test.py - Run a specific test (or space-separated list):
py test.py -g cpu/cpu_alu/cpu_alu - Run a group of tests by prefix match (add
-m):py test.py -g -m audio/atrac
Direct headless invocation
Windows/x64/Debug/PPSSPPHeadless.exe --root pspautotests/tests/../ --compare --timeout=5 --graphics=software pspautotests/tests/audio/atrac/addstreamdata.prx
Instead of a single PRX, you can pass a directory (e.g. pspautotests/tests/threads/mbx/...) to run all tests under it, recursively.
Key flags:
--root— points to the directory abovetests/so the headless can find the expected directory layout.--compare— enables output comparison against.expectedfiles.--timeout=N— seconds per test before killing it (default 5).--graphics=software— uses software GPU backend (required for headless; no real GPU available).
What you'll see
Passing test:
pspautotests/tests/audio/atrac/addstreamdata.prx:
audio/atrac/addstreamdata - passed!
1 tests passed, 0 tests failed, 0 tests missing.
Failing test:
pspautotests/tests/threads/mbx/refer/refer.prx:
O hi 0 prio=00 next=OTHER hi 1 prio=00 next=ITSELF ...
E hi 1 prio=00 next=ITSELF ...
+ ...
0 tests passed, 1 tests failed, 0 tests missing.
Failed tests:
threads/mbx/refer/refer
Lines prefixed with O are from the .expected file (real PSP), E is what PPSSPP produced, and + means a match.
The diff is line-by-line, so an O line followed by an E line at the same conceptual position means PPSSPP produced different output at that spot. A + line means both outputs agreed on that line.
Workflow for fixing a test
- Pick a test from
tests_nextintest.py. - Run it with headless to confirm failure and see what differs (
OvsElines). - Read the test source (
.c/.cpp) and the.expectedfile to understand the API being tested. The.expectedfile was recorded from a real PSP — it's the ground truth. The test source reveals what syscalls are made and in what order. Sometimes the test deliberately corrupts state to probe kernel error handling. - Form a hypothesis: look for a systematic pattern in the diffs (wrong order, wrong error code, missing output). Cross-reference with multiple expected files that exercise the same API — they may reveal the PSP's real behavior from different angles.
- Make changes to PPSSPP's HLE or other core code. Do not make super-targeted changes just to fix the test — instead, fix the underlying issue in a way that would also make sense on real PSP hardware.
- Rebuild PPSSPPHeadless and re-run the test.
- Run the full
tests_goodsuite (py test.py -g) to check for regressions. A correct fix should not break any previously passing tests. - Rinse and repeat until it passes, then move it from
tests_nexttotests_goodintest.py.
Tips from experience
- The diff output compares the full text output line-by-line. To see PPSSPP's raw output
without the diff overlay, omit
--compare:There's another trick too, --print-equal-lines, which prints matching lines with a '=' prefix, so you can see the full output with context.Windows/x64/Debug/PPSSPPHeadless.exe --root pspautotests/tests/../ --timeout=5 --graphics=software path/to/test.prx - Tests can show contradictory expected outputs at first glance. For example, the mbx/send
test expected file shows FIFO message order (normal sends), while the mbx/refer test shows
LIFO (after corruption) — because
sceKernelReferMbxStatuson a real PSP updatesfirstMessageduring traversal, changing the apparent head. Understanding the expected file's behavior often requires reading multiple related tests together. - When searching for the underlying issue, trace through the HLE implementation with the test's syscall sequence. Check whether the kernel writes into PSP-visible memory — if so, test code can corrupt those values, and the PSP kernel may have specific handling for that.
- Pointer addresses differ between PSP and PPSSPP. A test that prints raw kernel pointers
(heap addresses, TLS block addresses, etc.) will always have mismatched expected output
because PSPLink shifts memory layout. Fix by changing the test to print offsets from a
base address instead of absolute addresses. After changing the test, run it on hardware
to generate a new
.expected. - The diff notation:
Oline = present in PPSSPP's output but not in expected (PPSSPP-only).Eline = present in expected but not in PPSSPP output (expected-only).+line = context line (shown around diffs for context).=line (with--print-equal-lines) = matching line.[r]/[x]prefix = rescheduling occurred / did not occur since last line.
- When a function is a stub (
UNIMPLin log), the expected output is a good specification for what to implement. Look at multiple test cases in the expected file to understand the full range of valid and invalid inputs, return codes, and side effects. - Time-dependent tests (RTC, timezone conversions) depend on the host machine's clock
and timezone. The PSP's
sceRtcParseDateTimewas a stub — the expected file showed exactly which RFC 3339 / RFC 2822 formats are accepted and which are rejected (return -1).
Troubleshooting
- "Python was not found" on Windows — the Microsoft Store alias is interfering. Use
py(the Python launcher) or the full path, e.g."C:/Users/.../AppData/Local/Programs/Python/Python314/python.exe" test.py -g. Or disable the alias in Settings > Apps > Advanced app settings > App execution aliases. - No PRX files found — run
git submodule update --initfrom the repo root to fetch thepspautotestssubmodule. - PPSSPPHeadless exits immediately / "CPU not started" — the test PRX may be missing or the
--rootpath is wrong. Ensure--rootpoints to the parent oftests/. - MSBuild error MSB1008 — the
MSBuild.rspresponse file may be interfering. Always pass-noAutoResponseon Windows builds.