Test a parser
This page covers testing one parser: unit tests, the corpus, coverage and scaling. For making a parser fast, see Make a parser fast and memory-bounded.
Run the tests
cargo test -p poka-<ext>
cargo build -p poka-<ext> --target wasm32-wasip1 --profile wasm-check
A --profile wasm-check build (Cargo.toml) proves the crate compiles for wasm in a fraction of the release
build's time; build with --release whenever the wasm is installed, measured or shipped.
Run cargo from inside the repo, so that it reads the repo's
.cargo/config.toml and its shared target directory.
Test on the corpus
Corpus tests read real files from poka-samples, expected as a sibling
checkout (../poka-samples). Set POKA_SAMPLES if yours lives elsewhere. The
tests fail loudly when the sample set shrinks below its floor, because a
coverage claim depends on the samples still being there.
Put a
tests/samples.rsin the crate that parses every sample of the format.Run every crate whose samples are present:
python3 scripts/corpus-tests.pyIt prints pass, fail or skip per crate. Pre-push runs it for the crates you changed, and for a shared crate those crates depend on: a corpus suite that lives in a shared crate (poka-sheet-common holds the ones for
.xls,.xlsxand.xlsm) names the extensions it serves in that crate's Cargo.toml, under[package.metadata.poka.corpus] suffixes, because it has noplugin.jsonto route them from.Sweep every sample of the format, not the one in the bug report. A defect often lives only in the files nobody opened.
Never include_bytes! a poka-samples file unless it sits behind
#[cfg(feature = "corpus-tests")]. CI clones this repo alone, and
pre-commit rejects the bare form.
Prove the coverage
A parser that reads every corpus file without error can still drop a construct, and you do not notice, because what it drops never looks missing. Assert coverage with the strongest evidence the format offers:
- A formal schema, if the format publishes one. Check that every construct the corpus uses is one the schema declares.
- The file's own arithmetic.
#Nodesthen the nodes, a declared element count then the elements, a chunk length then the chunk. The file disagrees with a mis-parse, not your test. - Exhaustiveness over the corpus. Account for every line, record or chunk, with nothing left over.
- An exhaustive tiling, when the format publishes nothing. Divide the file into contiguous, non-overlapping regions that cover it end to end, and name each one. A test can answer yes or no to that; a percentage drifts silently when a parser starts losing bytes.
A region can be named without being interpreted. "The numeric fields of the record before this one" is an attribution, not a reading. Report both numbers and keep them apart: how much is accounted for, and how much of that carries a meaning. Never average them into one figure.
An other or unknown bucket is good design, because it keeps what you do
not model. Add a test that asserts it is empty over the corpus, or it becomes
the place where coverage gaps hide.
Prove the asymptotics
Corpus benchmarks find constant factors. They do not find quadratics, because
no corpus file is big enough. Give every lookup you make sub-quadratic a
tests/scaling.rs, written as a ratio of two input sizes and not as a wall
clock. The worked recipe is in
Make a parser fast and memory-bounded.
Each parser also has a tests/bench.rs that parses the whole corpus
in-process:
cargo test --release -p poka-<ext> --test bench -- --nocapture
Use that rather than the wall clock of cargo test, most of which is
compilation and file I/O.
Run the repo checks
python3 scripts/validate-parser-plugin.py
python3 scripts/test-manifests.py
python3 scripts/check-parser-invariants.py
python3 scripts/scorecard.py --check
Pre-commit and pre-push run these for you once you set
git config core.hooksPath .githooks. The process side (hooks, what CI runs
and what stays local) is in CONTRIBUTING.md.
See also
- Write a parser
SPEC.md— the contract the validators check.