Files
ps5upload/TESTING.md
T
PhantomPtr 5954c8adde 6.0.0: AVA1 replaces FTX2 — one encrypted connection for everything (#374)
See CHANGELOG.md (6.0.0). Full history of the work: tag ftx2-last and branch history archived in ps5upload-docs.
2026-10-05 00:25:05 -07:00

8.9 KiB
Raw Blame History

Testing

Four layers, cheapest first. Push a check down to the cheapest layer that can hold it — a host self-test runs in milliseconds and cannot wedge a console, which a hardware test very much can.

Layer Command Needs
1. Host gate npm run validate nothing
2. Payload build + self-tests npm run validate:full PS5_PAYLOAD_SDK
3. Live console npm run validate:hardware a PS5 with the payload loaded
4. Fresh-install matrix manual, per release clean VMs

Layer 1 — the host gate

npm run validate      # or: make quality

Runs, and fails on any of: version drift against VERSION; script syntax (Node, Bash, Python, and PowerShell when pwsh is present); the script inventory audit; git whitespace (git diff --check); engine fmt, engine clippy (-D warnings) and engine tests; desktop cargo check, desktop clippy and desktop tests; client typecheck, client lint, client tests and client vite build; and i18n coverage.

Two of these catch things the others structurally cannot, and both have caught real regressions:

  • engine fmt fails on formatting no test exercises. Run cargo fmt --all in engine/ before assuming a failure is real.
  • client typecheck covers test files too. Vitest does not typecheck, so a test can pass while calling a function with the wrong argument types.

Running a narrower check and assuming the wider one follows is the most common way to burn a cycle here.

Layer 2 — payload build and self-tests

export PS5_PAYLOAD_SDK=/opt/ps5-payload-sdk
npm run validate:full          # or: make quality-full / make test-payload

Builds payload/ps5upload.elf and the DPI daemon, checks both are PS5 ELFs with valid gzip resources, then runs every host self-test with -Wall -Wextra -Werror:

Self-test Pins
hw_guard Recovering from a faulting Sony getter without losing the helper
ptrace_recovery Timeout recovery never resuming injected registers
timed_init Bounded one-time init never starting a second initializer
appdb_scan Reading app.db as real SQLite records, not printable runs
ftp_format PASV/EPSV/LIST reply shapes clients parse strictly
ftp_lifecycle Session registration, generations, stop-to-kill
sdk_param param.json rewrite, and reporting when nothing changed
elf_param Finding SDK fields via program headers, not stray magic bytes
sdk_changer_file Patching only tracked sources; durable backups

The SDK version is pinned in scripts/ps5-sdk.env (currently v0.43) and verified by checksum. Local installers and CI read the same file.

Adding payload logic? If it can be separated from the console, put it header-only in payload/include/ and give it a self-test here. That is the established convention and the reason the list above exists.

Layer 3 — live console

Nothing here is tied to one network. Every entry point takes an address:

export PS5_HOST=192.168.1.50            # console IP (loader + payload)
export PS5_ADDR=192.168.1.50            # console IP; the engine owns the port (9120)

make send-payload PS5_HOST=$PS5_HOST
npm run smoke:hardware                  # or: node tests/smoke-hardware.mjs
npm run validate:hardware               # host gate + make validate

Keep your own addresses in a gitignored local file rather than editing committed defaults — those stay generic on purpose.

Command Does
make validate Build, send, wait for :9120, smoke, sweep, write bench/reports/<ts>-sweep.{json,md}
make validate-xl Adds the 200k-file stress profile
node tests/smoke-hardware.mjs --no-spawn-engine Against an engine already running

Clean up test uploads afterwards:

curl -X POST http://127.0.0.1:19113/api/ps5/cleanup \
  -H 'content-type: application/json' \
  -d '{"addr":"'"$PS5_HOST"'","path":"/data/ps5upload/tests/manual"}'

Use a live console when touching: the payload C runtime, AVA1 (payload/ava1/), transfer/reconcile/resume, storage, mount, cleanup, the file browser, volume commands, or anything performance-sensitive.

Hardware testing has teeth

A payload bug can leave a console needing a power cycle — ports stay open but every connection resets, so payload takeover cannot recover it. That has happened, from a stack overflow in a file-transfer handler. Before testing a payload change:

  • Prefer a read-only probe first; confirm the payload still answers /api/ps5/status between steps.
  • Some reads (temperatures, power telemetry) ptrace-pause the system UI. They are safe on demand and unsafe on a loop.
  • Expect blanks that are not bugs: SoC clock, SoC power, CPU usage, fan duty, per-drive sensors and the console date/time are not exposed by retail firmware.

Layer 4 — fresh-install matrix, per release

CI proves the binaries build. It does not prove they launch on a machine that has never been a dev box. Every artifact has a historical "ships green, fails on fresh install" failure:

Platform Failure caught before Guard now in tree
Windows x64 / arm64 VCRUNTIME140.dll not found .cargo/config.toml sets +crt-static
Windows x64 / arm64 Explorer "Extract All" rejects the zip publish.yml packs via pwsh Compress-Archive
Linux x64 / arm64 AppImage needs FUSE ships PS5Upload.sh with APPIMAGE_EXTRACT_AND_RUN=1
Linux (any) glibc too new built on ubuntu-24.04 (glibc 2.39)
macOS x64 / arm64 Gatekeeper on first run documented; right-click → Open

Before tagging: unzip with the OS's own tool and launch on a clean Windows x64 VM, Windows arm64, Ubuntu 24.04, arm64 Linux, macOS arm64 and macOS x64. Sideload the APK on a real Android device.

See v2.5.1 and v2.5.2 for the "noticed after release, patched same day" playbook.

Coverage reports

npm run coverage            # or: make coverage
make coverage-client        # one side only
make coverage-engine

Outputs client/coverage/index.html, coverage/engine/html/index.html and coverage/engine/lcov.info. Rust coverage needs cargo-llvm-cov (npm run coverage -- --install-tools).

Cross-platform

CI checks the core crate for every shipped target (x86_64/aarch64 × linux-gnu, apple-darwin, pc-windows-msvc) and runs the desktop Rust suite natively on ubuntu-24.04, macos-14 and windows-2022 — that native matrix is what exercises the #[cfg(target_os = ...)] paths (keep-awake, update/download handling, launcher, sidecar extraction). A macOS dev box generally cannot build the Windows/Linux targets, so CI is the source of truth there.

Android

make android-deps      # verify SDK / JDK 17 / NDK / rustup target
make android-build     # debug APK, no device needed
make android-deploy    # build + install on connected devices
make run-android       # run on a device with live reload

Android links the engine in-process rather than spawning a sidecar, so engine startup failures surface differently — check the in-app log. .rar is desktop-only (the UnRAR C dependency is excluded).

Self-hosted engine

make docker-engine        # build the image
make docker-engine-run    # run it; exposes :19113

CI publishes the same image to GHCR on every release tag. The API is unauthenticated and can read, write and delete files on the console — LAN only, never the internet.

i18n

npm run i18n:check        # gate: fails on any non-allowlisted miss
npm run i18n:report       # same, always prints the per-language summary

client/src/i18n/locales/en.ts is the source of truth. A new tr() key must exist there or the gate fails. Then either translate it into the 18 locales or add it to scripts/i18n-known-missing.json as a deliberate, scoped deferral.

Do not run i18n:bootstrap. It rewrites the allowlist to the current state, silencing every gap at once — including whatever you were about to forget.

Script hygiene

npm run scripts:check     # syntax
npm run scripts:audit     # inventory

A lab or debug script with no caller is intentional, not dead code. Only remove generated artifacts, or scripts whose replacement is documented.

Multi-volume .rar

Multi-part archives work in both debug and release builds.

This needed a fix: upstream unrar 0.5.8 reads ~8 KB out of a much smaller buffer every time it crosses a volume boundary, which aborted any debug build that touched a multi-part .rar and was a latent out-of-bounds read in release. 0.5.8 is the latest release, so the crate is vendored at third_party/unrar with the fix and both workspaces redirect to it via [patch.crates-io].

See third_party/unrar/README.md for the patch and docs/unrar-upstream-bug.md for the report to send upstream. If you bump the crate, re-apply the hunk or drop the vendoring once upstream ships the fix.

The shipped .rar fixtures are all single-volume, so they never exercise this — verifying it needs a real multi-part archive.