Testing and evidence
Conformance, simulation, concurrency, property tests, golden and interop vectors, fuzz targets, and compiled docs.
On this page
What to call
testing::conformance/conformance_withwith aConfig: the suite a store must pass (featuretesting).testing::independent_handles: the same races through two handles on one database.BrokenStorewith aFault: stores the suite must catch;Flaky: a store that fails writes like a network.TestWitness: a C2SP witness in memory (withwitness).
just ci # everything CI runs, in CI order (Docker for PostgreSQL)Conformance: one contract, every store
The same public suite runs against MemoryStore, SQLite and PostgreSQL, and a
third-party store checks itself the same way. Each check runs on a fresh store
from your factory:
- tables: one row per rejection code (plus precedence, replays, reversals,
clearings, closing), each asserting the store answers exactly what the
in-memory
Journalanswers, field for field; - simulation: seeded workloads of valid and mutated operations with retries
of used keys, reads compared with an independent model, store invariants and
an
audit()at the end; by default 8 seeds of 200 steps; - concurrency: real tasks racing appends, batches, retries, draws on limited accounts, a seal against an append into its period, memo erasures and hold resolutions;
- independent handles: the same races through two handles on one database, for a lock that must hold across processes.
A failure names the check, seed and step, and how to rerun it:
DOUBLEENTRY_SEED=<seed> DOUBLEENTRY_CHECK=<check> cargo test ….
use doubleentry::store::MemoryStore;
use doubleentry::testing::{Config, conformance_with};
// A third-party store: `conformance(|| async { MyStore::fresh().await })`.
let config = Config { check: Some(String::from("tables/1")), ..Config::default() };
let report = conformance_with(|| async { MemoryStore::new() }, config).await?;
assert!(report.checks.iter().all(|(name, _)| name.starts_with("tables/1")));
The suite is itself tested: BrokenStore has eight faults (forgotten keys, no
lock, stale balances, a late seal, lost memos, resolved holds kept open, through
ignored, no open holds by account), and a test asserts the suite fails on
each.
just conformance # memory, SQLite, PostgreSQL (Docker)Property tests
proptest checks laws over input spaces, 1 024 to 100 000 cases each: every
record kind round-trips its encoding and refuses damage, random bytes never panic
the decoder, a full entry and seal fit the leaf limit, allocations conserve
their total, export pairs re-sum to the entry, report filters partition the
balance, holds follow their model.
cargo test --all-features --test properties --test record_properties --test modelGolden and RFC 6962 vectors
tests/vectors/golden.txt pins the leaf bytes and hash of every record kind, a
reference log root, the entry id, memo commitment, draft fingerprint and
balance-tree constants. It is written by an independent reference encoder
(tests/vectors/ref_encoder.py), and tests/golden.rs compares the crate with
it byte for byte. tests/log.rs checks the RFC 6962 test-tree vectors, compares
every proof up to 64 leaves with a recursive reference, and requires every
deformed proof to fail.
C2SP interop, both ways
Vectors generated by the Go reference implementations are committed and read on
every test run; just interop (CI job, needs Go) regenerates them and has Go
verify the crate's checkpoints, requests, bundles, policies and tiles, including
through the stock witness. Details: Transparency.
just golden # crate vectors and the committed Go vectors, no Go needed
just interop # Go → crate and crate → GoFuzz targets
Every parser of hostile input has a cargo-fuzz target, compiled in CI
(just fuzz-build):
| Target | Input |
|---|---|
record | leaf bytes into Record::decode |
note | signed notes and checkpoints |
witness_response | a witness's add-checkpoint reply |
policy | tlog-policy text |
bundle | tlog-proof bundles |
serde | JSON into every public Deserialize type |
values | text into every value parsed from a string |
just fuzz record 120 # run one target for 120 s (nightly, cargo-fuzz)Compiled documentation
Every Rust block on this site, in the README and in the rustdoc is compiled, and
run unless it needs a server; the programs under examples/ run and assert
their outcomes. A docs guard refuses removed API names, checks every docs.rs
link against the reviewed public API, and checks that every test the
GoBD matrix cites exists.
just site-verify && just docs-stale && just examplesLimits
- No formal verification: properties are tested, not proven.
- Concurrency checks run real races, not a model checker.