Golden tests are the primary integration test for the compiler: they take a real PureScript module, run it through the whole pipeline, and pin the results (the IR, the generated Lua, and — when the module is runnable — its actual output) against checked-in expected files. A change anywhere in the pipeline shows up as a diff in these files.
- Harness:
test/Language/PureScript/Backend/Lua/Golden/Spec.hs - Golden primitive (vendored, customised):
test/Test/Hspec/Golden.hs - Test modules:
test/ps/src/Golden/<Name>/Test.purs - Generated artifacts:
test/ps/output/Golden.<Name>.Test/ - Regenerate (accept structural goldens):
PSLUA_GOLDEN_ACCEPT=1 cabal test
Each test module Golden.<Name>.Test maps to a directory
test/ps/output/Golden.<Name>.Test/:
| File | Committed? | What it is |
|---|---|---|
corefn.json |
yes | CoreFn emitted by purs, driven by spago build via the no-op backend in spago.yaml. Its builtWith stamp tracks the purs version. |
golden.ir |
yes | Pretty-printed IR UberModule — structural, a pure function of the code. |
golden.lua |
yes | Generated Lua — structural. |
eval/golden.txt |
yes | Program stdout, normalised. The semantic oracle — hand-verified. Present only for runnable modules. |
actual.ir / actual.lua / eval/actual.txt |
no (git-ignored) | What the current run produced; written on every run for diffing. |
externs.cbor |
no (git-ignored) | purs build artifact. |
A module is treated as an application (entry main is called) when
eval/golden.txt exists, and as a module otherwise. That is the only thing
the eval file's presence controls, beyond enabling the eval check.
cabal test spec (matched by -m Goldens) runs four groups, in order:
- compile (
beforeAll_ compilePs) —spago buildintest/ps, producingcorefn.jsonfor every module (thebackendinspago.yamlmakes spago emit CoreFn). compiles corefn files to lua— for each module:compileCorefnreads CoreFn → builds the IRUberModule→ compares the pretty-printed IR againstgolden.ir.compileIrlowers IR → Lua → compares againstgolden.lua.
golden files should evaluate— for eacheval/golden.txt: runslua <golden.lua>, asserts exit 0, normalises stdout (strip/trim/drop blank lines), and compares againsteval/golden.txt.golden files should typecheck— runsluacheck --quiet --std minover the generatedgolden.luafiles.
Important — the harness re-implements the pipeline.
compileCorefn(Spec.hs) callsLinker.makeUberModule >>> optimizedUberModuleandcompileIrcallsLua.fromUberModule >>> optimizeChunkdirectly — they do not go throughBackend.compileModules. So any new IR pipeline pass must live insideoptimizedUberModule(the shared function), or the golden tests will silently bypass it — a pass wired only intoBackend.compileModuleswould run in a real build but stay invisible to these tests.
golden.ir and golden.lua are derived: regenerating them from the current
compiler is always "correct" by construction. eval/golden.txt is different —
it is the hand-verified expected behaviour. A passing eval test is the only
thing that proves a codegen change preserved semantics; a luacheck pass alone
does not (it only checks the Lua parses/lints).
Therefore: never regenerate eval/golden.txt from the compiler's current
output unless the program's behaviour legitimately changed and you have
re-verified it by hand. Doing so silently bakes a regression in as the new
"expected" value.
Accept the structural goldens in place; the eval oracle is left untouched:
PSLUA_GOLDEN_ACCEPT=1 cabal test specPSLUA_GOLDEN_ACCEPT rewrites a mismatching golden with the actual output and
passes — but only for goldens marked acceptable (golden.ir /
golden.lua, built with acceptableGolden). eval/golden.txt is built with
defaultGolden (acceptable = False) and is never auto-accepted, so it
keeps failing until you fix the regression or update it deliberately. Review the
resulting git diff before committing.
This replaces deleting goldens by hand. The equivalent manual workflow:
find test/ps/output -name golden.ir -delete
find test/ps/output -name golden.lua -delete
cabal test spec # recreates ir/lua; eval runs against the preserved oracleRe-verify the new behaviour by hand, then update eval/golden.txt for the
affected module(s) explicitly (edit it, or delete just that file and let the run
recreate it after you've confirmed the program is correct).
golden_reset deletes every file named golden.* in test/ps/output
(including eval/golden.txt) and reruns the suite to recreate them. Because it
nukes the oracle, only use it when the expected program output itself changed
across the board and you have re-verified it. For ordinary codegen churn, prefer
PSLUA_GOLDEN_ACCEPT=1 above.
- By default a mismatch prints a bounded summary: the first differing line
plus a small window of each side, the line counts, and the path to the full
actual.*file. This keeps a run with many mismatches cheap (it does not hold two full blobs and their diff per failure). - For the full expected/actual diff:
PSLUA_GOLDEN_FULL_DIFF=1 cabal test spec. - The complete actual output is always on disk next to the golden
(
actual.ir/actual.lua/eval/actual.txt) for manual diffing.
The test runner's RTS is capped in pslua.cabal
(-with-rtsopts=-N4 -c -M16g): bounding the parallel-GC core count and heap
turns a pathological run (many large mismatches) into a clean heap-overflow
rather than an OOM that takes the machine down. The bounded summary above is the
other half of keeping failures affordable.
- Create
test/ps/src/Golden/<Name>/Test.purs(moduleGolden.<Name>.Test). For a runnable test, give itmain :: Effect Unit. cd test/ps && spago build && cd ../..to emitcorefn.json.- For a runnable test, create the oracle by hand:
test/ps/output/Golden.<Name>.Test/eval/golden.txtwith the expected stdout, pluseval/.gitignorecontainingactual.txt. - Run
cabal test spec.golden.ir/golden.luaare created on first run (they pass — "first time execution"); the eval test compares the program's output against your oracle. - Review the generated
golden.ir/golden.lua, then commitTest.purs,corefn.json,golden.ir,golden.lua,eval/golden.txt,eval/.gitignore.