Testing strategy
There is no specification for most of what orbistoun implements. The semantics are undocumented, so "is this correct?" rarely has a cheap answer, and the test strategy is built around that.
Sources of ground truth
Four sources, in order of preference. A change that cannot be justified from one of them says so in its commit message.
1. FreeBSD source
The target kernel is FreeBSD-derived, and much of libkernel is POSIX
under vendor naming. FreeBSD source is lawful, citable and the strongest
reference available. When implementing a libkernel function, find the
FreeBSD analogue first and name it in a comment. This is why
orbistoun-kernel needs less guesswork than any other
crate.
2. Framebuffer diffing
For the GPU layer: render a frame, compare it numerically against a reference, and get a number. It is the one cheap, mechanical correctness signal in the codebase, which makes the GPU crates the best target for tooling and automation.
3. The guest itself
A one-bit oracle per call site. Return success: does the guest
proceed? Return an error: does it bail? The answer is bisectable, and
StubPolicy is runtime data so that a question costs an edit
to a TOML file and a relaunch, not a rebuild.
Each query costs a boot, and it constrains only behaviour the guest observes and checks, so it converges on "correct for this title" rather than correct. Use a prior (a FreeBSD analogue, a name, observed argument usage) to choose what to try first rather than bisecting blind.
4. Instruction test suites
SingleStepTests and ProcessorTests give
per-instruction JSON with full pre- and post-state for retro CPUs. They
do not cover this target. Their use here is validating tooling: an
automated approach that cannot pass a suite where the answers are known
is not trusted where they are not.
What gets tested
The high-value targets are the pure ones with concrete contracts, written test-first:
| Crate | What is pinned |
|---|---|
orbistoun-nid |
Hash stability, suffix sensitivity, unknown-NID handling |
orbistoun-mem |
ABI alignment rules, overlap detection in both directions |
orbistoun-hle |
NID resolution, policy override isolation, loud-by-default |
orbistoun-elf |
Truncation, bad magic, wrong class, honest failure on vendor data |
orbistoun-core |
Error-code round-tripping, placeholder/real code separation |
orbistoun-libc |
ISO C and POSIX behaviour at the edges, against a real specification |
orbistoun-shader |
Decode against a reference disassembler, and against bytes that are not instructions: random, degenerate, truncated, ragged |
orbistoun-translate |
Per-instruction behaviour, executed and compared rather than asserted structurally |
orbistoun-gpu |
Submission handling: a shader that will not translate is reported, a window that runs out refuses |
orbistoun-names |
A generated index has a specific answer; a confirmed name round-trips through the hash |
orbistoun-report |
The progress verdict, and that a differing-conditions comparison is labelled as measuring a settings change |
orbistoun-probe |
Every captured transcript parses, and nothing grades above an assumption without an asserted target |
Every test states the property it protects in a comment. A test survives a refactor that preserves the contract and fails when the contract changes.
Tested lightly on purpose: the CLI's output formatting, and anything whose failure is cosmetic. Effort goes to the layers where a wrong answer is silent.
Vacuous loops
A check that iterates over what the code produced and validates each
item passes when the code produces nothing. The loop body never runs,
every assertion inside it is vacuously satisfied, and the report is
green. every_decoded_operand_appears_in_the_reference in
orbistoun-shader has this shape: an instruction that
decodes to an empty operand list produces no comparisons.
The shape to watch for is a test whose assertions all live inside a loop over produced data. It checks that what was produced is right, and says nothing about whether anything was produced.
The converse inventory is the rule for such a test. Not "at least one item", which drifts into meaninglessness, but an exact list: the set of cases producing nothing is exactly this written-down list, each entry with its reason. Closing a gap fails until the entry is deleted, and opening one fails until it is added and justified. For any "for each X found, check X" test, ask what it reports when nothing is found; if the answer is "it passes", write the converse.
A measurement lands as a fixture
A hardware measurement becomes a test before it becomes behaviour. The test names the request and the sweep rows it rests on, and asserts the measured value - a return code, a structure's bytes, a texel's offset, a frame's hash. A change that disagrees with the hardware then fails the gate instead of waiting to be noticed in a run.
A measured value printed rather than asserted is a defect:
tests/oracle_gl_cube.rs in orbistoun-gpu holds
the console's two frame hashes and is the case this rule exists for.
Validation separate from effect
orbistoun-mem separates validation from mapping:
validate() is a pure function over the ABI rules, and the
effectful reserve() calls it first. The rules are fully
testable without touching the host address space.
Prefer that shape wherever it fits: a pure decision function plus a thin effectful wrapper. Where most effects are hard to test, it keeps coverage meaningful.
Running
./bin/orbistoun test # the suite, under nextest when installed
cargo nextest run --workspace # directly
cargo test --doc --workspace # doctests, which nextest does not runnextest runs each test in its own process, in parallel. It does not
run doctests, so the gate runs them separately; the
guest_module! contract is a doctest.
./bin/orbistoun check runs both, then re-runs the
device-dependent tests with output shown (BUILDING.md).
Coverage
cargo llvm-cov --ignore-run-fail continues past a
failing test binary but records nothing from it. The profile is written
when a process exits normally, and a libtest failure exits
by a path that does not write it. Every file covered only by that binary
reports 0.00%, and every file covered partly by it reports
whatever other binaries reached.
A zero in a coverage report therefore means either "nothing tests
this" or "the binary that tests this failed". Fix the suite before
reading a coverage number, or skip the failing test explicitly with
-- --skip <name>, which keeps the rest of that
binary's profile. A baseline taken while any test fails is not a
baseline.