Testing Standard
Testing is a landing requirement, not a follow-up. No crate is considered done until it meets this standard, and CI enforces the mechanical parts.
Per-crate requirements
Every crate carries:
- Unit tests for each public function and the non-obvious private ones, under the workspace 85% per-file coverage ratchet (coverage may only rise).
- Property tests (
proptest) wherever there is a round-trip, an invariant, or a parser/encoder. Serialization, decode/encode, cursor arithmetic, type-mapping compatibility, and DAG locator propagation all qualify. - Snapshot tests (
insta) for config parsing and any generated artefact (DDL, schema translation, contract resolution output). - Doc tests on public API examples, so the docs cannot drift from behaviour.
Sans-io crates (twg-codec-*, twg-proto-flatten, twg-proto-schema,
twg-proto-decode, twg-wire-sasl, twg-type-map,
twg-contract-core) must test entirely without a runtime or containers —
that testability is the point of the sans-io discipline, and a container
dependency creeping into their tests is a layering smell.
End-to-end requirements
The twg-e2e crate holds cross-crate scenarios and regression tests:
- Scenarios (
tests/scenarios.rs) exercise realistic full-path flows through Testcontainers (Kafka, Pulsar, Postgres, Unity Catalog OSS). They are skipped gracefully when Docker is unavailable, so the workspace still builds and unit tests still run without it. - Regressions (
tests/regressions.rs) encode known-hard cases as permanent assertions. Each entry corresponds to a specific failure mode; once written it never leaves.
The Zerobus sink is the one integration with no open-source double, so its contract is tested against a hand-rolled fake implementing the sink trait. The fake enforces the behaviours learned in production — mid-stream schema changes rejected, the SDK breaking-surface pinned — so the contract is verified even though the real service is not in CI. A thin smoke suite gated on real credentials runs outside PR CI.
Benchmarks as gates, not curiosities
Where a decision rests on performance, the benchmark is part of the decision and runs in CI as a regression gate — not a one-off measurement quoted in a document.
The protobuf decode strategy is the current case. A zero-copy descriptor-driven
parser is to be vendored and extended rather than depended upon (see
twg-codec-protobuf), and that divergence is only justified if it pays: the
upstream parser is the baseline to beat, measured on our hardware and our
schemas, with prost-reflect as the reference floor. Include a wide schema
(100+ fields) — that is where reflection-based decoders collapse and where the
gain is largest, so it is the case that decides the question. If the
fused-into-ArrayBuilders path does not beat parse-then-walk by a real margin,
the fusion is not worth maintaining.
Criterion, committed baselines, and a CI failure on regression beyond a stated
tolerance. See BENCHMARKING.md for how to run it and, more
usefully, for the cheaper premise check to do before implementing anything.
The regression list is a debt ledger
Every item in tests/regressions.rs is currently a comment. Turning each into an
executable, passing test is tracked work — a crate that touches the relevant area
may not land while its regression is still a comment. See
SCENARIOS.md for the authoritative scenario and regression
catalogue with rationale.