Testing

September 6, 2026 · View on GitHub

pip install -e . pytest
python -m pytest -q               # 107 tests: unit + contract + integration + evals
python evals/run.py               # 9 measurement eval cases with expected values and tolerances
python evals/contract_evals.py    # 17 contract eval cases

FFmpeg is required. Integration tests and evals are not skipped when it is missing; the session fails, so a green run always means the real ffmpeg / ffprobe were exercised. CI (.github/workflows/tests.yml) runs the whole suite on Linux (Python 3.9, 3.11), Windows (choco ffmpeg) and macOS (brew ffmpeg).

Fixtures (tests/fixtures/generate.py)

Nothing binary is committed; fixtures are generated with ffmpeg into a temporary directory (a few hundred KB, ~2 s):

fixtureconstructionused for
av.mp46 s testsrc2 320x180 @25 H.264 + mono AAC 48 kHz; 1 kHz tone amplitude 0.1 only in t=2..5 sprobe, video, silence (leading 0–2, trailing 5–end), integrity PASS, cache, budget, CLI
video_only.mp44 s video, no audiono-audio paths (UNSUPPORTED_FORMAT for audio kinds), integrity without frames shortfall
stereo.wav3 s pcm_s24le 48 kHz stereo, same tone on both channelsaudio_format, loudness −20.0 LUFS
mono.wav3 s pcm_s24le 44.1 kHz mono, 440 Hzaudio_format, integrity audio-only
silence.wav2 s digital silenceentirely_silent, loudness below the absolute gate
multi.mp42 video (320x180 @25, 160x90 @30) + 2 audio (mono 48 kHz eng, stereo 44.1 kHz jpn)stream ordinals, per-stream selection
loud.wav5 s continuous 1 kHz tone amplitude 0.1 → −23.0 LUFS, −20.0 dBTP, LRA 0loudness eval
short.mp40.4 s (10 frames)short video
scenes.mp4three 2 s hard-cut segmentsscene cuts at 2.0 and 4.0 s
corrupt.mp4av.mp4 with 3000 bytes zeroed inside mdatintegrity FAIL

Unit tests (tests/test_unit.py, no ffmpeg)

contract ↔ registry agreement (no undeclared or unimplemented tools) · AnalysisRequest validation · command / argv rejection · kind validation · parameter validation (unknown, type, range, NaN) · probe / stream / video (CFR, VFR, unknown) / audio parsers · silence parser + classification · ebur128 parser (incl. -inf) · integrity status (PASS / WARN / FAIL) · scdet parser · malformed result · wrong asset / kind / analysis id / source · deterministic identity (parameter order, asset / version / parameter changes) · cache hit / miss / relabel / disabled · cache invalidation by asset, analyzer version, parameters · tampered and unreadable cache entries · timeout (real subprocess) · budget (calls, total seconds, cache hits free) · unsupported analyzer · dry-run runs nothing and still validates · path policy (roots, symlink escape, directories, NUL, workspace writes, argv shape) · no shell in source · secret leakage (env value, secret-looking key, child environment) · command / argv leakage · error model.

Contract tests (tests/test_contract.py)

schema validator · contract JSON self-consistency (sections, schema versions, error / exit tables, budget / cache vocab) · tools and kinds derived from the registry (tool_spec(analyzer) equality, coverage of all 10 kinds, capability names) · consumer rules from tests/contract/agent_skill_package_contract.json (video-production-agent SkillPackage / ToolSpec / Observation / capability-name rules) · request schema ↔ AnalysisRequest agreement (incl. strategy / budget / cache_policy rejection) · response / result / observation schema roundtrip through engine.run (ok, partial, error, dry-run, rejected document) · exit code table · process-group kill of a grandchild · executable path not configurable · OS registry rules from tests/contract/os_registry_contract.json (provides shape, one id per kind, documented collision published, denylist superset, recursive rejection) · error classes in results and contract · doctor per-Capability AVAILABLE / MISSING · check_contract accepts the live contract · self-conformance (conformance.py, SKILL_SPEC.md section 8): all 8 checks PASS or honestly NOT_IMPLEMENTED, and each fallible check is proven to actually detect a broken implementation (a validator that stops rejecting, a leaky PathPolicy stub, a real shell=True planted in a scanned file) · CLI smoke · 21 drift fixtures (tests/contract/cases: valid, unsupported schema, missing / extra tool, tool / capability / kind / kind_to_tool / version / schema-version / schema / invocation / provenance / exit-code mismatch, provides missing / renamed / bad lifecycle / extra kind, denylist narrowed, error class drift) · contract --check CLI (file, stdin, unreadable) · no confidence / judgement vocabulary in analyzers · batch identity independent of order.

Integration tests (tests/test_integration.py, real ffmpeg)

MP4 probe · video stream analysis (CFR, frame count, short file) · audio stream analysis (stereo / mono) · silence (leading / trailing, entirely silent, identity changes with threshold) · loudness (−23.0 / −20.0 LUFS, silent input) · integrity (PASS, FAIL on corruption, video-only, audio-only) · multiple streams (ordinals, per-stream video / audio / loudness, out-of-range ordinal) · no-audio video · scene detection · timing / duration · cache hit skips the analyzer (execution log: first run executed ffmpeg, second run executed nothing, a new engine reuses the disk cache) · cache policies (bypass, onlyCACHE_MISS) · budget and timeout with real processes · timeout leaves no ffmpeg process and no cache entry (/proc scan on Linux) · determinism across runs · all 10 kinds on real multi-stream media, response validated against the published schemas, second batch entirely from cache · CLI smoke (doctor, probe, analyze multi-kind, cache hit, dry-run JSON / text, error inside the JSON document vs stderr, partial batch with budget error, run with request file, argv rejection, --allowed-input, contract) · run - over stdin with a batch and budget (partial status, per-result errors, exactly one stdout document), invalid JSON on stdin, unknown budget field · invalid-input matrix over stdin (invalid JSON, missing asset / kind, unknown kind, invalid / unknown parameter, path traversal, nonexistent file, non-media file, command field, executable override → one parseable error response each, stderr empty) · absolute vs relative input (one identity) and --allowed-input$ · \text{timeout} \text{inside} \text{a} \text{batch} \text{keeps} \text{the} \text{protocol} (\text{partial} \text{response}, \text{others} \text{served}, \text{no} \text{cache} \text{entry}, \text{no} \text{ffmpeg} \text{left}) · **\text{real}-\text{media} \text{matrix}**: \text{all} 10 \text{kinds} \times 10 \text{fixtures} \text{validated} \text{against} \text{the} \text{schemas}, \text{expected} $UNSUPPORTED_FORMAT for audio kinds on video-only and video kinds on audio-only media, second pass served from cache except the never-cached errors · determinism across two processes · non-ASCII (Japanese) file name over cp1252 / cp932 / ascii pipes: JSON protocol, human mode, stdin request and error documents all survive.

Contract evals (evals/contract_evals.py, tests/test_evals.py)

C01 contract JSON valid · C02 declared tools exist · C03 declared kinds exist · C04 observation schema valid · C05 request / response roundtrip · C06 unsupported field rejection (request, parameter, output_policy, batch budget) · C07 malicious command rejected · C08 path traversal rejected (input and cache dir) · C09 credential leakage rejected · C10 cache hit produces the identical observation with zero analyzer calls (real ffmpeg) · C11 contract / registry consistency over every drift fixture · C12 invalid input JSON over run - · C13 timeout process tree (real ffmpeg) · C14 batch identity · C15 no-audio media · C16 deterministic result across engines (real ffmpeg) · C17 cache invalidation (content, version, parameters, corruption).

Measurement evals (evals/cases/*.json, evals/run.py, tests/test_evals.py)

Each case names its fixture, kind, parameters, the derivation of the expected values and a list of expectations (eq, approx with tolerance, gte, lte, startswith) on paths in the Observation. Cases: known media probe, known video format (stream 1 of a multi-stream file), known audio format, known silence, known loudness (mono and stereo), integrity PASS and FAIL, known scene cuts. Tolerances: 0.05 s for container / segment times, 0.2 LU / dB for loudness, exact for codecs / counts / dimensions.