Running the tests¶
Goal¶
Know which command runs which suite, what each proves, and what can only run on a Mac. Target names below were checked against the Makefiles, Cargo.toml, studio/Package.swift, tools/ci_build.sh
and .github/workflows/tests.yml. Results marked "measured" were produced on a Linux box (no Metal, no Xcode) while writing this page.
CI (.github/workflows/tests.yml, on every pull request and push to main, plus manual dispatch) has these jobs:
| Job | Runner | Command(s) | Blocking |
|---|---|---|---|
docs |
ubuntu-latest | python3 tools/check_docs.py |
yes |
rust |
macos-26 | cargo test --workspace --locked |
yes |
native |
macos-26 | make -C input test, make -C audio test, make -C frontend test, make -C gfx lib adapter, make -C gfx tests, make -C gfx test, make -C runtime test test-services |
yes, except make -C gfx test (continue-on-error: a virtual-Mac runner may lack a usable GPU; it stays a local gate) |
studio |
macos-26 | swift test --package-path studio |
yes |
ios-smoke |
macos-26 | SDK=iphonesimulator tools/build_ios_deps.sh, SDK=iphonesimulator tools/build_ios.sh (compile and link only) |
no (continue-on-error until the a32 sources are compiled by tools/build_ios.sh) |
Not in CI: anything that needs game files, keys or firmware (boots of the reference titles), swift test UI automation, signing, make -C runtime test-host, cargo clippy.
Quick matrix: Linux or macOS¶
| Suite | Command | Linux | macOS |
|---|---|---|---|
| Docs | python3 tools/check_docs.py |
yes (CI runs it on Linux) | yes |
| Rust workspace | cargo test --workspace --locked |
yes (measured: every test binary reports ok) | yes (CI) |
| Rust lints | cargo clippy --workspace |
runs; measured: fails with 4 clippy::never_loop errors in napple-build (firmware.rs, legacy.rs, pipeline.rs x2). Not part of CI |
same code, same lints |
| Runtime unit tests | make -C runtime test |
no | yes (CI) |
| Runtime service tests | make -C runtime test-services |
no (see tools/linux-check for the portable subset) |
yes (CI) |
| Runtime host tests | make -C runtime test-host |
measured: does not build (src/threads.c uses Mach APIs); only test_ios_dylib builds |
yes (run by tools/ci_build.sh, not by tests.yml) |
| Service logic on Linux | sh tools/linux-check/run-tests.sh, sh tools/linux-check/check-syntax.sh |
yes | not needed |
| gfx (Metal) | make -C gfx test |
no | yes (local gate; CI non-blocking) |
| gfx without Metal | sh gfx/tests/t_getters_linux.sh, sh gfx/tests/t_xlate_linux.sh |
yes | the first delegates to the Makefile; the second also runs there |
| Audio | make -C audio test |
no (CoreAudio, AudioToolbox, Homebrew opus) |
yes (CI) |
| Input | make -C input test |
no through the Makefile (it links GameController and uses clock_gettime_nsec_np); tests/test_hid.c compiles alone with src/hid_shm.c |
yes (CI) |
| Frontend | make -C frontend test |
no (Cocoa) | yes (CI) |
| Studio | swift test --package-path studio |
no (platform macOS 26) | yes (CI) |
| Full build + tests | tools/ci_build.sh [dir] |
no | yes |
Rust (cargo test, clippy)¶
- Workspace members (
Cargo.toml):napple-core,napple-cli,napple-rewrite,napple-compat,napple-sigs,napple-build. Unit tests sit next to the code (#[cfg(test)]); integration tests incrates/napple-build/tests/(e2e.rs,cancel.rs,golden/),crates/napple-core/tests/ingest.rs,crates/napple-rewrite/tests/. None needs a real game: fixtures are synthetic. cargo test --workspace --lockedis the CI command (--locked:Cargo.lockmust not change). One package:cargo test -p napple-sigs(40 tests, offline). On a shared machine setCARGO_TARGET_DIRto keeptarget/out of the tree.cargo clippy --workspace: see the matrix; fix new warnings you introduce, and do not treat the four existingnever_looperrors as yours.
Runtime (macOS)¶
make -C runtime builds runtime/build/napple-runtime; it needs the other libraries built first: make -C input && make -C audio && make -C frontend && make -C gfx lib adapter (order of tools/ci_build.sh;
GFX=napple is the default, GFX=stub selects gfx_stub.c). debug=1 builds with -O0.
- make -C runtime test builds and runs build/tests/test_media, test_ipc (CMIF, domains, TIPC, sm), test_fs_journal, test_hid_service, test_timescale; each prints a JSON line {"test":...,"checked":N,"failed":M} and the target stops at the first failure.
- make -C runtime test-services builds every runtime/tests/test_svc_*.c (32 files today) plus test_hid_depth (macOS only: hid.c needs Mach and the input library) and runs them. A test #includes the service source it exercises; the object of that
source is left out of the link (runtime/tests/services.mk). How to write one: add-ipc-service.md.
- make -C runtime test-host builds the portable kernel, loader and service code against the stand-ins in runtime/tests/host/ and runs test_svc_async, test_kernel_ipc, test_loader, test_nvdrv, test_fs_savedata, test_fs_journal,
test_ios_dylib, test_kernel_sched. The Makefile describes it as "any POSIX host, e.g. Linux", but src/threads.c (in HOST_KERNEL) contains Mach calls in the NAPPLE_PCPROF sampler, so on the Linux box used for this page only test_ios_dylib
builds (make BUILD=<dir> <dir>/host/test_ios_dylib). Treat it as a macOS target. HOST_CC picks the compiler. How to write one: add-svc.md.
- Manual tools, not wired to any target: runtime/tests/a32/ (test_module.c AArch32 loader against a real data dir, a32_boot.c, fuzz.py interpreter fuzzing against Unicorn) and runtime/tests/a32jit/ (difftest.c lifter versus interpreter,
ittest.c, bench.c; build lines are in their headers; tools/build_a32jit.sh builds the lifted jit).
Linux subset: tools/linux-check¶
runtime/ only builds on macOS; tools/linux-check checks the portable service code on Linux (details in tools/linux-check/README.md):
- sh tools/linux-check/run-tests.sh [runtime/tests/test_svc_x.c ...] compiles each test_svc_*.c with every src/services/*_core.c and ipc_mock.c (a stand-in for ipc.c, kernel.c, log.c) and runs it. Measured: 29 of 32 pass with "failed":0 (33 of 36 after sim-online added test_svc_online, test_svc_irs_sim, test_svc_amiibo, test_svc_nfp);
test_svc_fs and test_svc_fs_dataid do not link (romfs_map.c is not part of the script), test_svc_time does not link (host_symbols_register). Sanitizers:
ASAN_OPTIONS=detect_leaks=0 CC="gcc -fsanitize=address,undefined" sh tools/linux-check/run-tests.sh.
- sh tools/linux-check/check-syntax.sh [file.c ...] runs -fsyntax-only with gcc and clang over src/services/*.c with stub headers (measured: "syntax check: all clean", with warnings in hid.c, ncm.c; skips fs.c, nvdrv.c).
Passing says the C is well formed and the core logic matches the test; it says nothing about the macOS build or a game.
Graphics (gfx/)¶
make -C gfx lib adapterbuildsgfx/libnapple_gfx.aandgfx/build/obj/adapter/gfx_napple.o(what the runtime links).make -C gfx testsbuilds everytests/t_*.candt_*.mtogfx/build/tests/.make -C gfx testruns them withMTL_DEBUG_LAYER=1 MTL_SHADER_VALIDATION=0, thent_triangletwice with a disk cache (T_CACHE_PASS=1|2) andt_pipe_stress(pass 1, then pass 2 with 1 and 4 threads andNAPPLE_GFX_NO_ARCHIVE=1), and ends withALL TESTS PASSED. They are headless Metal probes: they need a Mac with a GPU and need no game data. One test:make -C gfx build/tests/t_state && gfx/build/tests/t_state.- Linux:
sh gfx/tests/t_getters_linux.sh(measured33 checks, 0 failed: set/get round trips of NVN state and sampler getters, fromapi_state.candapi_texture.c) andsh gfx/tests/t_xlate_linux.sh(measuredt_xlate: 50 Metal compile checks skipped,t_xlate: ok: the Maxwell translator's text assertions, no Metal compile). Writing them: add-nvn-function.md, add-maxwell-shader-op.md. - Corpus runs of real shaders:
NAPPLE_SHADER_CORPUS=<dir>witht_xlate; the corpus lives outside the repository.
Audio, input, frontend (macOS)¶
make -C audio testrunstest_audren test_effects test_reverb3d test_review test_audin test_audout test_hwopus test_engine test_threads test_speed(needs Homebrewopus,OPUS=overrides the prefix).make -C audio tsanandmake -C audio asanrebuild subsets with ThreadSanitizer / AddressSanitizer (not in CI).make -C audio demoplays a sine wave.make -C input testrunstest_hid test_bindings test_motion test_input(-Werrorbuild). The pure HID writer test also compiles on Linux by hand:gcc -std=gnu11 -Iinput/include -Iinput/src input/tests/test_hid.c input/src/hid_shm.c -lm -o /tmp/t && /tmp/t(measured: 199 checks, 0 failed).make -C input demois an interactive pad demo.make -C frontend testrunstest_settingsandtest_window(creates the frontend without showing the window and runs the real AppKit loop).tests/test_ios_logic.cis not part of the Makefiletesttarget.
Studio (macOS 26)¶
swift test --package-path studio runs NappleStudioTests (studio/Tests/NappleStudioTests/StudioTests.swift, LibraryTests.swift) against NappleStudioCore, the testable logic of the SwiftUI app (studio/Package.swift, platform .macOS("26.0")).
UI automation is not run in CI. studio/build_app.sh builds the app (part of tools/ci_build.sh).
Docs¶
python3 tools/check_docs.py [--root <Napple dir>] (no dependencies, no network, exit 0 = ok) checks that every relative link and #anchor in the Markdown files resolves, every document under docs/ is reachable from docs/index.md (no orphans;
docs/archive/changes/* is history and only needs a line in its README), the required files exist (docs/index.md, docs/architecture/overview.md, docs/guides/README.md, docs/reference/cli.md, docs/reference/environment-variables.md,
docs/reference/glossary.md, docs/process/README.md, docs/archive/changes/README.md, docs/symptoms.md), code fences are closed, docs/symptoms.md rows have four non-empty cells, and every reference title has docs/titles/<id>.md.
Run it after every docs change; the docs job fails the pull request otherwise.
Whole build: tools/ci_build.sh¶
tools/ci_build.sh [output-dir] (macOS, Apple Silicon): cargo build --release -p napple-cli --locked, make in input, audio, frontend, gfx lib adapter, runtime, packaging, then (unless NAPPLE_SKIP_TESTS=1; failures only warn)
cargo test --release --locked && make -C runtime test-host, then studio/build_app.sh and studio/packaging/make_release.sh into dist/. Env: NAPPLE_VERSION, NAPPLE_BUILD, NAPPLE_SIGN_IDENTITY.
Needs Xcode command line tools, rustup, brew install opus libvpx. .claude/skills/napple-latest/napple_latest.sh runs the same commands plus a status survey.
iOS¶
SDK=iphonesimulator tools/build_ios_deps.sh && SDK=iphonesimulator tools/build_ios.sh builds runtime/build-ios-sim/napple-runtime for the Simulator (compile and link; nothing is run). See ios.md.
Titles¶
Booting the reference titles needs the user's own game files and keys, so it is never automated: use debugging.md, section 8, and the pages in titles/. A passing boot is exit 0, thousands of frames and no CRASH.
End-to-end (tools/e2e/run.sh)¶
tools/e2e/run.sh <las|smo|acnh|mk8d|all> ties the whole product together on a Mac that has the Testing Material, a Studio keyset (~/Library/Application Support/Napple/keysets/22.1)
and this checkout's CLI, runtime, launcher and applet library built (cargo build --release -p napple-cli, make -C runtime applet-lib, make -C packaging). Per title it
builds the .app through Studio's own build path (napple-studio-build: KeysetManager + NappleCLI.build, in-place data mode, updates and DLC as planned), then runs the
steps below and prints a PASS / FAIL / KNOWN / REPORT / N/A table plus summary.json and summary.md (default folder $TMPDIR/napple-e2e, --out to change it):
| Step | Passes when |
|---|---|
| build | Studio's pipeline produced the .app |
| bundle | codesign --verify passes, Contents/Frameworks/libnapple-applet.dylib is there, the A32 jit dylib is embedded for the 32-bit title (MK8D) |
| camera-mic | NSCameraUsageDescription and NSMicrophoneUsageDescription in Info.plist, the camera and audio-input entitlements, no App Sandbox |
| mii-db | (first title) the real Mii Maker applet, driven by NAPPLE_INPUT_SCRIPT and NAPPLE_SWKBD_ANSWER, adds a Mii to the shared MiiDatabase.dat; every later boot reads the same file |
| boot | the launcher starts the app headless, exit 0, no CRASH, at least a frame floor |
| audio | the PCM counter line ([napple-audio] PCM: ...) saw samples above the noise floor |
| input | the scripted presses reached the guest (input: script: press) |
| amiibo | NAPPLE_AMIIBO=<model id>,3000,12000: placed and removed, a virtual amiibo in the store (N/A for a title that never opens nfp:user) |
| save | a save was committed, and a second boot with the same save root mounts it again |
| overlay | NAPPLE_FE_OVERLAY + NAPPLE_FE_VIEWSHOT render the overlay tabs to PNGs (a windowed run: the window appears briefly) |
| golden | tools/golden routes against the approved goldens: report only, golden diffs are not deterministic run to run and never fail the table |
All steps share one system data root (<out>/support, via NAPPLE_SUPPORT_DIR, see system data folders). Known issues are tagged KNOWN, not
FAIL, with the reason: ACNH's intermittent guest Break at sdk+0xc80730 right after a headless start (retried once). A .app is about 6 GB: apps are deleted after each title
unless --keep-apps. Nothing from the Testing Material is copied into the repository.
Which tests to run for a change¶
| You changed | Run |
|---|---|
crates/* |
cargo test --workspace --locked |
knownSignatures/ |
napple sigs validate <file>, cargo test -p napple-sigs (add-signatures.md) |
runtime/src/services/* |
make -C runtime test-services (Mac) or sh tools/linux-check/run-tests.sh (Linux) |
runtime/src/kernel.c, threads.c, ipc.c, memory.c, ksession.c |
make -C runtime test test-host (Mac) |
gfx/src/api_*.c, mtl_*.m |
make -C gfx test (Mac); sh gfx/tests/t_getters_linux.sh for state getters |
gfx/src/shader/* |
sh gfx/tests/t_xlate_linux.sh, then make -C gfx test |
audio/, input/, frontend/ |
the matching make -C <dir> test |
studio/ |
swift test --package-path studio (use --scratch-path "$TMPDIR/..." on ~/Documents: File Provider xattrs break its codesign step) |
| anything that crosses Studio, the runtime and packaging | tools/e2e/run.sh <title> (end-to-end, needs the Testing Material) |
any *.md |
python3 tools/check_docs.py |
compat/matrix.json, docs/titles/*, the coverage snapshot |
python3 -I tools/support/gen_support.py (regenerates the support tables), then python3 tools/check_docs.py |