Testing Guide
July 6, 2026 · View on GitHub
How duckDBSP is tested, how to run the suites, and what each layer covers. The single most important idea: differential testing — after every batch of changes, an incrementally maintained view must equal DuckDB's own answer for the same SQL. DuckDB is the oracle; the engine never gets to grade its own homework.
Running the suites
All tests build in test/build_test (32 binaries registered with
ctest):
cd test/build_test
cmake .. && make -j8
ctest # full suite, ~15-45s
./test_planner_frontend # the big differential suite on its own
Benchmarks and the soak test build alongside but are not part of ctest:
make bench_planner_eval soak_differential
./bench_planner_eval # throughput + RAM benches
SOAK_ROUNDS=60 ./soak_differential "[soak]" # randomized long-run churn
Sanitizer builds live in sibling directories with the same CMake setup:
cd test/build_asan # RelWithDebInfo + AddressSanitizer
ASAN_OPTIONS=detect_leaks=0 ./test_planner_frontend
# (leak detection off: the CDC manager is a deliberately leaked singleton)
cd test/build_tsan # ThreadSanitizer
./test_planner_frontend "[parallel],[spill],[shard]"
./test_thread_safety
The layers
1. Unit tests (test/unit/)
Direct exercises of one component: Z-set algebra and the dense FlatWeightMap (including property tests against an oracle map with hostile hash functions), the spill store (row codec round-trips, rebuild diffs, bucket-index property tests across compactions), native view classes, CDC manager locking, security validation.
2. Integration tests (test/integration/)
Everything through the real extension surface: table functions, CDC,
cascades, recovery, persistence, ACID behavior. The centerpiece is
test_planner_frontend.cpp — for every supported SQL shape it creates
a view, runs randomized insert/delete rounds (NULLs included by the
generators), and after each round compares the view's contents against
DuckDB executing the same SQL directly:
expected: SELECT * FROM (<view SQL>) ORDER BY ALL -- DuckDB itself
actual: SELECT * FROM dbsp_query('<view>') ORDER BY ALL
Coverage includes joins of all types, correlated subqueries, grouping sets, ordered and holistic aggregates, window functions over expressions, percentage limits, spill-mode variants (bounded top-K with forced refills, spilled arrangements with live migration), and parallel propagation.
3. Soak test (test/benchmarks/soak_differential.cpp)
Stacked views (outer join → aggregate → sort, NOT IN, recursive)
under hundreds of randomized delete-heavy rounds, differentially
checked every round. SOAK_ROUNDS scales the run.
4. Sanitizers
ASAN and TSAN builds run the same suites. TSAN specifically pins the concurrency claims: parallel view propagation, sharded join probes, and concurrent probes of one spilled shared arrangement.
5. Benchmarks (test/benchmarks/bench_planner_eval.cpp)
Throughput ledgers (filter/aggregate/join rows-per-second, cascade sync latency, captured-commit latency), spill-mode RAM/CPU trade (maxrss comparison), arrangement-sharing and sharded-probe contrasts. Perf-sensitive changes are gated on these staying inside their noise bands — regressions here have reverted otherwise-working designs.
Conventions
- Tests encode intent, not just behavior: when a formerly rejected SQL construct becomes supported, the test asserting the rejection is updated to assert the support (and this shows up in the commit).
- Randomized generators always include NULLs and duplicate values.
- New state machinery ships with a property test against a plain
in-memory oracle (see
test_zset.cpp,test_spill_store.cpp). - Bench numbers quoted in commits come from
test/build_test(release flags); sanitizer-build numbers are 20-30x slower and never quoted.
For questions or issues, see CONTRIBUTING.md.