Testing

Tau uses four distinct testing layers. Each one finds a different class of bug; together they provide confidence across correctness, input coverage, emergent system behaviour, and crash recovery.


Layer 1: Example-Based Unit Tests

Location: #[cfg(test)] mod tests block at the bottom of every source file.

What they test: Specific, known-correct behaviours with a fixed shape. Wire protocol responses, error message strings, parse failures, WAL checksum mismatches, auth rejection sequences. These are behaviours where the output is fully determined by the input and any change is a regression.

Coverage:

  • Parser rejects malformed input and accepts valid input
  • Executor returns the correct Output variant for each statement
  • Permission checks fire on the correct conditions
  • WAL replay reconstructs the same in-memory state as a direct write
  • WAL rotation archives the pre-rotation file for point-in-time recovery
  • Connection manager accepts and rejects connections as expected

How to run:

cargo nextest run --release                        # all tests (preferred)
cargo nextest run --release --lib                  # libtau unit tests only
cargo nextest run --release -p libdst              # DST framework tests
cargo nextest run --release -p dst                 # Tau DST driver tests
cargo nextest run --release -E 'binary(tau)'       # server tests only
cargo nextest run --release -E 'binary(tauctl)'    # tauctl tests only
cargo test --release                               # fallback if nextest is not installed

Layer 2: Property-Based Tests (Hegel / Hypothesis)

Location: #[hegel::test] in the same mod tests blocks.

What they test: Invariants that must hold for any input, not just a chosen example. Hegel draws randomised inputs from typed generators, runs each property hundreds of times, and shrinks failures to the smallest possible reproducer.

Coverage:

  • Tau::new(s, e, v).contains(t) iff s <= t < e, for any s, e, t
  • Layer::at(t) matches a linear scan over the same taus
  • Value::encode / Value::decode roundtrip for every variant
  • compact_layers preserves all query results
  • Auth Perm display / parse roundtrip
  • handle_query never panics on arbitrary input strings
  • Parse failure responses always start with ERR parse:
  • Response::parse never panics on arbitrary UTF-8 text
  • Response::display → parse roundtrip for VAL, RANGE, and NAMES variants
  • WAL rotation archive is replayable and contains pre-rotation layer state
  • libdst behavior-tree guards, deterministic scheduler, shrink correctness, and Tau oracle compaction / TTL properties

How to run:

cargo nextest run --release   # Hegel runs inline alongside example tests

All such tests are named pbt_* for easy log filtering. Hegel auto-installs a Python shim (~/.cache/hegel) on first run. Each property runs 100+ randomised cases by default. Use HEGEL_MAX_EXAMPLES=500 to increase the draw count.


Layer 3: Deterministic Simulation Testing (libdst + dst)

Location: crates/libdst (framework), crates/dst (Tau driver binary).

What they test: End-to-end agreement between libtau::Executor and an independent reference oracle (no libtau code) under random workloads — including derived lenses, TTL, multi-database switching, WAL replay, and truncated-WAL recovery. Divergences are structured (Divergence) with step index, description, and expected/got values. Failing traces are minimised via delta-debug shrinking.

How to run:

cargo run --release --bin dst -- --seed 42
RUST_LOG=warn cargo run --release --bin dst -- --ci --seed 42
cargo test -p libdst -p dst

See Deterministic Simulation Testing for architecture and operation tables.


Layer 4: Fuzz Testing (cargo-fuzz / LibFuzzer)

Location: crates/fuzztau/fuzz_targets/

What they test: Crash-freedom and panic-freedom under arbitrary byte inputs — the class of bug that neither deterministic unit tests nor property tests reliably find, because the fault depends on specific byte sequences the author never imagined.

Fuzz targets exercise the untrusted-input surfaces:

TargetEntry pointWhat it finds
parselibtau::parsePanics / OOMs / loops in the TauQL nom parser
wirelibtau::Response::parseWire response decoder (different grammar, splitting, integer parsing)
value_decodelibtau::Value::decodeValue codec used inside VAL / RANGE wire segments (escapes, tags)
perm_parselibtau::Perm::parsePermission bitmap parser (CRUDA, *, -) used by wire GRANTS and users file
parse_literallibtau::parse_literalSingle-literal parser used by bulk/COPY paths

Seed corpus: crates/fuzztau/seeds/{parse,wire,value_decode,perm_parse,parse_literal}/ — small committed sets of valid + boundary cases. The working corpus grows in the gitignored crates/fuzztau/corpus/ and can be minimised with cargo fuzz cmin.

Prerequisites: a nightly Rust toolchain (rustup toolchain install nightly).

How to run:

# Bootstrap working corpus from committed seeds (recommended for long runs)
mkdir -p crates/fuzztau/corpus/wire && cp crates/fuzztau/seeds/wire/* crates/fuzztau/corpus/wire/ 2>/dev/null || true
mkdir -p crates/fuzztau/corpus/parse && cp crates/fuzztau/seeds/parse/* crates/fuzztau/corpus/parse/ 2>/dev/null || true

# Run a target (ctrl-c to stop; add -- -max_total_time=N to bound the session)
cargo +nightly fuzz run --fuzz-dir crates/fuzztau wire crates/fuzztau/corpus/wire
cargo +nightly fuzz run --fuzz-dir crates/fuzztau parse crates/fuzztau/corpus/parse
cargo +nightly fuzz run --fuzz-dir crates/fuzztau value_decode crates/fuzztau/corpus/value_decode
cargo +nightly fuzz run --fuzz-dir crates/fuzztau perm_parse crates/fuzztau/corpus/perm_parse
cargo +nightly fuzz run --fuzz-dir crates/fuzztau parse_literal crates/fuzztau/corpus/parse_literal

# Minimise a corpus after a long run (keeps high coverage, drops redundant inputs)
cargo +nightly fuzz cmin --fuzz-dir crates/fuzztau wire  crates/fuzztau/corpus/wire
cargo +nightly fuzz cmin --fuzz-dir crates/fuzztau parse crates/fuzztau/corpus/parse

Crash inputs are saved to crates/fuzztau/artifacts/<target>/. Reproduce:

cargo +nightly fuzz run --fuzz-dir crates/fuzztau wire crates/fuzztau/artifacts/wire/<filename>

Summary

LayerWhat it catchesWhen to run
Unit testsRegressions on known-shape behaviourAlways (CI)
Hegel PBTInvariant violations across random inputsAlways (inline with unit tests)
libdst / dstSUT vs reference divergence under emergent workloadsCI (after nextest, before Docker)
cargo-fuzzPanics and crashes from adversarial byte sequences on parsers/codecsOn demand / scheduled CI