Testing
Layers
| Layer | Where | What it proves |
|---|---|---|
| Unit tests | #[cfg(test)] modules beside the code | Parsing, planning, rendering, config validation, range logic |
| Registry and mapper tests | src/registry/tests.rs with src/testutil.rs | Resolution, caching, single flight, revalidation, stale-if-error, mapper failures, and remote media, against in-process mapper and origin servers |
| Router tests | src/http/tests.rs | Real routing, middleware, headers, and streaming, through tower::ServiceExt::oneshot (no socket) |
| Decode test | http::tests::ffmpeg_decodes_hls_and_dash_presentations | FFmpeg plays the HLS and DASH output over a real TCP server |
| Process tests | tests/cli.rs, tests/package.rs | The compiled binary runs; package output is well-formed and deterministic |
| Conformance | tests/conformance.rs | Playlist grammar, timelines, fMP4 box arithmetic, HLS/DASH byte identity, and decoding, against the running binary |
| Performance | benches/budgets.rs | The TDD 0001 latency, memory, and concurrency budgets (make bench) |
| Fuzzing | fuzz/fuzz_targets/media_pipeline.rs | Parsing, planning, and fragment preparation survive arbitrary bytes |
Run everything CI runs with make ci (format check, cargo check, Clippy with warnings denied, all tests, fuzz-target compile, rustdoc with warnings denied). make test runs only the tests.
Fixtures
Committed under tests/fixtures/, generated by tests/fixtures/generate.sh and tests/fixtures/generate-variants.sh with FFmpeg (make fixtures) from synthetic sources, so there are no licensing concerns:
| File | Purpose |
|---|---|
h264-aac.mp4 | Main fixture: 320x180, 30 fps, 3 seconds, keyframe every second, two B-frames, 48 kHz AAC |
h264-aac.ffprobe.json | FFprobe packet dump: the ground truth the index is compared against |
h264-aac-moov-last.mp4 | moov after mdat |
h264-aac-edit-list.mp4 | Edit lists written by -c copy remuxing |
h264-aac-default-edits.mp4 | FFmpeg’s default output: an edit list per track for B-frame delay and AAC priming |
h264-aac-audio-delay.mp4 | Audio starts half a second late, so its track has a leading empty edit |
h264-aac-two-audio.mp4 | Two audio tracks, tagged eng and spa |
h264-aac-timecode.mp4 | An extra tmcd track that must be skipped |
h264-aac-anamorphic.mp4 | Non-square pixels and tagged colour: the sample entry carries pasp and colr |
h264-aac-quicktime.mov | QuickTime: versioned mp4a entries with a wave box, and a qt brand |
hevc-aac.mp4 | HEVC video (hvc1) |
vp9-opus.mp4, av1-aac.mp4 | VP9 with Opus, and AV1 with AAC |
h264-ac3.mp4, h264-eac3.mp4, h264-flac.mp4 | AC-3, E-AC-3, and FLAC audio |
aac-only.m4a, aac-two-tracks-only.m4a | Audio only, with FFmpeg’s default edit list, and two tagged tracks |
h264-mp3.mp4 | MP3 in MP4: an mp4a entry that must be rejected by its audio object type |
h264-aac-fragmented*.mp4 | The progressive fixture remuxed with -c copy into fragmented layouts: two tracks per moof, explicit base offsets (legacy), one track per moof (CMAF), a sidx, version 1 trun with negative composition offsets, and (patched by generate-variants.sh) a timeline starting at 100 seconds |
h264-video-only.mp4 | No audio track |
h264-aac-44100-stereo.mp4 | Different audio parameters |
h264-variable-timing.mp4 | Two frame-duration classes |
FFmpeg is needed only to regenerate fixtures and to run the decode test, which skips itself with a message when ffmpeg is not installed. CI installs it.
Techniques used here
-
Real servers, not fakes.
MockMapperandMockOrigininsrc/testutil.rsare Axum servers on ephemeral ports. The mapper can be told to answer a given status, delay, a raw body, or require a token; the origin can ignore ranges, advertise a chosen validator, change it mid-test, and counts requests and bytes served. Tests then assert on observable behavior: mapper call counts, origin range requests, response statuses, and metrics. -
Mutation checks. Removing single flight or the DNS address filter makes the concurrency and SSRF tests fail, which is how those tests were validated.
-
Oracle comparison:
sample_index_matches_ffprobe_packetschecks every sample’s offset, size, timestamps, and keyframe flag against FFprobe. -
Equivalence with the progressive file: every fragmented fixture holds the same packets as
h264-aac.mp4, and a test compares each sample’s size, duration, sync flag, and payload bytes, and its timing. -
Second implementation:
agrees_with_an_independent_implementation_on_every_samplecompares the in-tree parser with themp4crate (a dev-dependency only) on seven fixtures: timing, sync flags, and the payload bytes at each sample’s offset and size. -
Verbatim init segments: the init tests assert that the
stsdbytes equal the source’s, sopasp,colr, andavcCsurvive; the conformance suite then compares aspect ratio and colour reported by FFprobe for the source and the reassembled track. -
Deterministic mutation:
corrupted_metadata_never_panics_the_pipelinecorrupts a few random bytes of each fixture’smoovthousands of times and runs parse, plan, init writing, and fragment preparation over each. Any result is fine except a panic or hang. It runs in everycargo test; the fuzz target below is the deeper version. -
Byte mutation instead of new fixtures: tests copy
moovfrom the fixture and patch a few bytes to make an invalid input (find_typelocates a box by name). Seerejects_multiple_sample_descriptionsandrejects_run_length_entries_that_claim_more_samples_than_stsz. -
State injection for limits: HTTP tests build an
AppStatewith small limits, then hold semaphores (segment_jobs,request_slots) to force503paths, or shrinkstream_chunk_bytesandresponse_idle_timeout_msto exercise the idle-client path. -
Versioned URLs in tests: the helper
versioned(path)appends the real?v=value that media routes require. -
Determinism:
tests/package.rsruns the packager twice and compares every output byte.
Fuzzing
The fuzz crate (fuzz/) is separate and depends on the library. Its target writes the fuzz input to a temporary file and calls segmentor::fuzzing::exercise_media_pipeline, which runs parse, plan, init-segment writing, and fragment preparation and ignores expected failures; the fuzzer looks for panics, hangs, and runaway allocation. make fuzz-check compiles the target on stable; make fuzz runs a campaign on nightly with the fixtures as seeds.
Adding a test
- Put unit tests in the module’s
testsblock using the fixtures above. - For a new rejection rule, mutate fixture bytes rather than adding a binary.
- For a new route or header behavior, add a router test with
oneshotinhttp/tests.rs. - Anything that changes fragment or playlist bytes should also keep the FFmpeg decode test and
tests/package.rsgreen.
Not covered yet
The vendor HLS and DASH validators and browser playback tests; see Protocol conformance. Performance results and their limits are in Performance budgets.
cargo test builds the benchmark binary but skips its run unless invoked with --bench, so a minute-long load test never runs as part of the test suite.