culvert-async's test suite is built around deterministic register-access tests. All tests run against in-memory transport implementations; no real hardware is required at any point.
Each client module contains tests immediately below the code it covers. They are culvert's
tests for the same module, run against the async client: #[pollster::test] with
async fn bodies, .await on each Scdc call. They therefore check the same register
bits as culvert's, through culvert-async's own reads and writes — which is what this crate
adds; the encoding and decoding behind them is culvert::codec's.
They use TestTransport, an async 256-byte register array transport defined in
src/client/test_transport.rs, with two constructors:
TestTransport::new()— succeeds on all operations; used for happy-path tests.TestTransport::failing_after(n)— succeeds for the firstnoperations then returnsErr(()); used to exercise every?error branch.TestTransport::log()— the first 16 operations, in order, so a test can pin which registers a method reads and writes, in what order and how often. Aread_blockis oneOp::ReadBlock(reg, len), counted once byfailing_after(an empty one not at all), so a test pins each multi-register method to one transaction.
The unit tests use a single instantiation (Scdc<TestTransport>) on purpose: one
concrete type avoids LLVM counting per-monomorphisation ? branches as uncovered. The
integration tests in tests/ add their own transports, which the coverage run includes;
their ? error branches are covered by the unit tests' instantiation.
Tests run under #[pollster::test], keeping the executor dependency out of the library and
avoiding any runtime choice.
culvert's tests/scdc.rs made async: the client's operations through the crate root,
against SimulatedScdc, a 256-byte register array implementing culvert-async's
re-exported ScdcTransport. Each test pre-loads register state, runs operations and checks
both the returned values and the registers. One more test names every type and trait the
crate root re-exports, so a re-export that goes missing fails to compile, and one runs
every operation generic over a SendScdcTransport, passing each future through a function
that requires Send, so it compiles only if every operation could run on a multi-threaded
executor.
culvert's regression tests for the torn CED read, made async: a sink whose lane 0 counter
ticks while it is read, with a checksum kept correct for its current value. Read one
register at a time, 0x00FF ticking to 0x0100 reads back as 0x01FF with a
ChecksumMismatch. Read as one block from a snapshotting sink, one that holds its
registers still during the transaction (Block; the read_block contract leaves that to
the sink), it is 0x00FF with no warning. A sweep over starting counts shows a counter
ticking by one per byte never passes the checksum (a larger jump can).
The CED unit tests (src/client/ced.rs) match CedWarnings by their fields rather than
building them: the variants are #[non_exhaustive], so outside culvert they can be
matched but not constructed, as a caller meets them.
When compiled with --features plumbob-async, two more sets of tests run, both culvert's
plumbob feature tests made async:
- Unit tests (
src/client/plumbob_client.rs) call each method through theplumbob_async::ScdcClienttrait and assert on register bits: all sevenUpdate_0flags, every request value (and 0x9 passed on asLtpReq::Reserved), all sixSource_Test_Configurationbits, each flag clear writing only its own bit,Config_1encoding, culvert's FFE-levels check, and transport errors. - End-to-end tests (
tests/plumbob_async.rs) run plumbob-async'sFrlTrainerthroughScdc<T>against a sink simulated at the register level: a full training run, a rate drop, a timeout and exhausted rates ending in TMDS,FLT_no_timeout, an undefined request ignored and returned as aTrainingWarning, a 3-lane link whose unused lane 3 reports an undefined value,FRL_startandFLT_updateset together (recordingFrlStartWithRetrain), a DDC NACK mid-LTS:3 still ending in LTS:L, a retrain resuming from no pattern, theUpdate_0flags plumbob does not handle left set, and the FFE maximum limited per rate. The simulated transport waits (returnsPendingonce) on every other operation, so training runs over I/O that really suspends. Sendfutures (alsotests/plumbob_async.rs): a function generic overSendScdcTransportandSendHdmiPhymust return aSendfuture that trains through the plainScdc<T>, runs LTS:L and trains again with events, so it compiles only if training throughScdc<T>could be spawned on a multi-threaded executor. It runs over the same simulated sink, behind aMutex(the transport twin isSync).
examples/scdc/main.rs is culvert's example with an async transport, driven by
pollster::block_on. It exercises the public API against a simulated sink; its output is
identical to culvert's example, which is a quick check that both clients read the same
register map.
cargo run --example scdcCI measures line coverage with cargo-llvm-cov over the default features and the
plumbob-async feature, merged into one report, so the adapter is covered too. The baseline
is stored in .coverage-baseline (100%); CI fails if coverage drops more than 0.1% below
it.
Scdc<T> runs identically against simulated and real ScdcTransport implementations.
A test that cannot run with an in-memory transport does not belong in this repository.
Hardware is never a test dependency: pollster drives the tests without a Tokio or Embassy
runtime.