Drive cargo llvm-cov line coverage from current baseline to 100%, using
coverage/lcov.info and coverage/target/llvm-cov/html/ as generated work
lists
Exact source locations belong in generated reports. Do not pin source line numbers in this plan; they go stale after edits and test additions. Anchor work on files, functions, branches, and observable behavior
Use line coverage as target metric. Function coverage helps find untouched routines, but hides missed match arms inside already-hit functions
Refreshed 2026-06-26 from CI line coverage (pg17) job (run 28219022837).
- Target: 89.49% line coverage (17602 / 19670), 2068 missed lines from
cargo llvm-cov report --summary-only. Use this denominator for--fail-under-lines - Secondary: 88.36% function coverage (1639 / 1855), region 88.51% (27228 / 30762). llvm-cov counts each source function once and marks it hit if any instantiation ran. Use only for prioritising untouched functions
- Ignore:
lcov.infoFN/FNDArows. lcov export emits oneFNper monomorphization per crate, so generic functions and closures are counted repeatedly. Collapse by(file, start-line)to reproduce llvm-cov shape before drawing any conclusion from FN counts
Real work is 2068 missed executable lines, not the inflated monomorphized
FN count.
Regenerate locally with CI coverage recipe, PG 17 and ClickHouse running, see
.github/workflows/ci.yml coverage: job:
cargo llvm-cov clean --workspace
WALSHADOW_USE_LOCAL=1 cargo llvm-cov --workspace --all-targets --locked --no-report -- --nocapture
cargo llvm-cov report --summary-only
cargo llvm-cov report --lcov --output-path coverage/lcov.infoMissed-line counts from the 2026-06-26 run (Lines column), main blockers:
| area | missed lines | work |
|---|---|---|
src/bin/* |
476 | decide coverage denominator, then test or exclude binaries (stream 461, filter 11, classify 4) |
ch_emitter.rs |
185 | config arms, CH connection/retry, flush, observer, debug variants |
heap_decoder.rs |
224 | value matrix, partial tuple, varlena, dropped-column arms |
xact_buffer.rs |
228 | snapshot, TOAST, spill/detoast, observer-error paths |
oracle.rs + ch_ddl.rs + shadow_* |
306 | live control-plane and schema-event paths |
| remaining library tail | 649 | fixture gaps, pure helpers, defensive error arms |
| total | 2068 | summary TOTAL row denominator |
Treat this as denominator pressure, not function backlog. heap_decoder.rs is
98.44% function-covered but still misses 224 lines; bin/stream.rs alone
misses 461 lines (pipeline/inserter.rs 26, queueing_record_sink.rs 37,
shadow_stream.rs 72 are the next library blockers). Broad match arms and the
stream binary dominate
Literal 100% line coverage needs one denominator decision for CLI binaries, decoder/emitter matrix tests, and deterministic fault injection for defensive OS and transport paths. No residual glue can remain under literal 100% gate
Use existing in-proc and fixture paths. Extend
tests/common/inproc_harness.rs, fixture replay, or module-local tests
- Pipeline tail variants: batcher deadline trip, inserter
send_with_retry- reconnect arms, ack collector
Trailing/ gap cases,RecordSinkon_idle/on_close, andDisplay/fmt
- reconnect arms, ack collector
wal_streamhelper sinks: exerciseCountingRecordSink::on_record,CollectingBytesSinkchunk / segment-boundary handling, andRecordhelpers. Delete helpers if dead after confirming no non-test callerfilter_segment.rserror closures: feed corrupt records throughtests/filter_round_trip.rs, including badpage_magicand unparseable body pathsfpi.rsdecompress errors: corrupt LZ4 and ZSTD FPI fixtures to exercise both error closuresch_ddl.rsrename/drop-column logic: drive ALTER RENAME and DROP COLUMN through existing DDL test path intests/ddl_replicates.rsxact_buffer.rsdetoast and observer errors: drive truncated/bad TOAST through the subxact / large-xact harnesses and inject observer failures where callbacks already exist- Live-client accessors: once DDL harness builds a
DdlApplicator, assertconfigandconfig_mut. Holds a liveAsyncClient, so pure unit construction is not practical queueing_record_sink.rsworker-stopped branch: build sink with innerRecordSinkthat errors onon_record, push batch so worker exits, thenflushagain sotx.sendfails and "worker stopped" error surfaces. "Already closed" arm remains Tier D because publicclose()consumesself
Extend existing WALSHADOW_USE_LOCAL e2e tests. Keep live-state cases in
existing harnesses rather than adding binaries
shadow_catalog.rs::seed_from_sourceand dependent closures: drive viatests/catalog_seed.rs/tests/shadow_catalog.rsch_emitter.rs::reconnect: explicit CH drop mid-streamshadow_stream.rslisteners and COPYDATA enqueue path: drive walshadow walsender throughtests/walsender_pg18_walreceiver.rs/tests/shadow_lifecycle.rssource_feed.rsconnect paths: exercise unix, TCP, and TLS transports oncebackfill_bootstrap.rsdrain arms andbackup_source*start / finish / tablespace symlink: extendtests/bootstrap_*_e2e.rswith tablespace-bearing source- Catalog-bound accessors: assert
shadow_cataloglast_observed_replayandxact_bufferwith_subxact_tracker/subxact_trackerwhere existing catalog e2e tests already have realShadowCatalog preflight.rs::runBadShadowVersionarm: point validator at shadow returning non-integerserver_version_num, or extract parse into a pure helper and unit-test it
Remaining misses are executable CLI lines, match arms inside called decoder functions, and defensive error closures. Each missed executable line needs one outcome: execute it in CI, refactor it into testable code, or remove it from configured denominator
- CLI denominator, 476 missed lines (
stream461,filter11,classify4).filter/classifyare near the floor;streamis the whole decision. Choose coverage or exclusion explicitly.streamcoverage means bootstrap off/direct/object-store paths, CH emitter + DDL setup, metrics-only observer, oracle wrapper on/off, retention loop, object-store WAL fetch, shadow startup success/failure, and shutdown hooks. Prefer moving pure helpers into library modules before testing - Decoder/value matrix,
heap_decoder.rs. Varlena short / 4-byte / unsupported-tag / invalid-UTF-8 / truncation, dropped columns, missing defaults per mapped type, prefix/suffix partial-tuple exits, and the array-elementCharmapping (char[]fixture). Do not exclude product decoder code - Emitter/DDL/config arms across
ch_emitter.rsandch_ddl.rs. Config parser errors and coercions (port, budgets, retry fields, namespace/table/ column shape errors,attnumoverflow),ColumnBufdebug variants, idle/close/deadline flush variants, retry and reconnect success/failure, DDL rename/drop/type-change branches, and mapping mutation. Use unit, in-proc, and live-CH harnesses - Live control plane. Validation hard-error,
seed_from_sourceempty/known/new relation/not-found/error paths, TCP walsender listener, COPYDATA enqueue error, TCP/TLS source transports, direct/object-store bootstrap, tablespace symlink, object-store WAL fetch, and shadow lifecycle. Extend existing e2e tests where state is real; add injectable clients or failing readers/writers where deterministic faults are needed - Defensive OS/transport/fault lines. Add explicit fault injection for
remaining
with_contextclosures, fs failures, socket handoffs, malformed impossible states, and lock/fault paths. Known examples includemetrics.rslistener accept failure andqueueing_record_sink.rs"already closed"ok_or_else, unreachable from public API becauseclose()consumesself. Stablerustc 1.96pluscargo-llvm-cov 0.8.7does not expose stable line-level#[coverage(off)]; cargo-llvm-cov documents it ascoverage_nightlygated. On stable, practical exclusions are report-level file regexes, so prefer small injection points over excluding product files
- True source 100% line coverage: no report exclusions. Requires tests or fault injection for every item above, including all binaries and all defensive failures
- Practical 100% line gate: exclude any intentional non-product files (none
currently); cover or refactor existing
binaries; add decoder/emitter/control-plane matrix tests; add deterministic
fault injection; gate configured denominator with
--fail-under-lines 100 - High-water floor: if stream/CLI or defensive product-file exclusions are accepted, state scope in CI and docs. This means "100% of configured denominator", not source line coverage
- Finish Tier B fixture and in-proc tests
- Land config parser and DDL fixtures from Tier D, dense coverage work
- Extend Tier C live e2e tests
- Decide binary denominator, cover or exclude CLI paths
- Add decoder matrix, emitter/DDL matrix, then fault injection for remaining defensive lines
- Add
--fail-under-lines Nto CI coverage job once baseline is accepted, then raise it after each tier
After each tier, rerun llvm-cov recipe, diff summary, and regenerate
coverage/lcov.info so work list shrinks visibly. Do not add tests that only
walk closures; every test must assert observable behavior