PLAN-bench phase 06: integration tests¶
Prompt¶
Before responding to questions or discussion points in this
document, explore the instar codebase thoroughly. Read relevant
source files, understand existing patterns (tests/base.py's
InstarTestBase and runner helpers, tests/test_bitmap.py and
tests/test_map.py as the structural exemplars, the
KNOWN_*_DIVERGENCES registry idiom), and ground your answers in
what the code actually does today. The behavioural contract being
pinned is the one already captured empirically: the phase-01
plan's 1e capture (+ Supplement 2), the phase-04 plan's smoke
results, and the phase-05 plan's write verification — read all
three capture sections before writing a single assertion. Do not
speculate when you could read instead.
Phase plans live alongside the master plan
(PLAN-bench.md) in docs/plans/. This is the
sixth of eight phases; phases 1-5 delivered a working
instar bench (reads on all five formats, writes on raw+qcow2)
whose deterministic surface was smoke-verified against qemu-img
10.0.8 but is not yet guarded by the test suite.
I prefer one commit per logical change, and at minimum one commit per phase. Each commit should be self-contained: it should build, pass tests, and have a clear commit message explaining what changed and why.
Situation¶
This phase converts the 4c/5c smoke matrices into
tests/test_bench.py so the contract survives future changes,
and formalizes KNOWN_BENCH_DIVERGENCES. Test-infrastructure
facts (surveyed):
- Runner: stestr over testtools (NOT pytest); a new
tests/test_bench.pyis auto-discovered viatests/.stestr.conf— zero registration.make test-integrationruns the suite (16-way on this host; tests must be order-free and self-contained —tempfile.TemporaryDirectoryper test). File-scoped run:cd tests && ../.venv/bin/stestr run test_bench. - Base class:
InstarTestBase(tests/base.py:39);get_instar_binary()(:315, env override thensrc/target/release/instar); runner helpers return(stdout, stderr, returncode)triples; per-subprocess timeouts (bitmap uses 120 s for guest launches). - KVM guard:
_require_kvm()idiom (test_bitmap.py:94-102) —skipTestwhen/dev/kvmis not accessible. Every bench test that launches the guest needs it; pure-validation tests do not (they fail host-side, pre-launch, and are cheap). - Message comparison idiom: substring assertions
(
assertIn), never whole-line equality — instar wraps errors asError: "<msg>", qemu prefixesqemu-img: <msg>; the core text is identical for the parity messages. Header lines ARE whole-line byte-comparable (stdout, no wrappers). - Divergence registry precedent:
KNOWN_MAP_DIVERGENCES(test_map.py:168) is the richest —dict[key, (scope, reason)]— and pairs with aTestMapDivergenceRegressionclass (:638) whose tests assert the divergence STILL EXISTS, so a silent fix fails loudly and forces a registry update. Bench adopts both halves. - Fixtures:
qemu-img create+qemu-iowrites in a TemporaryDirectory (test_dd.py:160-186pattern-image idiom;test_bitmap.py:158-172create idiom). qemu-io and qemu-img are hard dependencies of the suite. No Python helper exists for the 55AA raw trick — the test file replicates it:qemu-io -f raw -c 'write -P 0x55 510 1' -c 'write -P 0xaa 511 1'on a truncated file (mirrorscripts/create-external-data-testdata.sh:46-49). - Speed: guest launch dominates (~seconds), the request loop
does not (
-c 100completes in ~1 ms) — keep counts at 100, images ≤ 64 MiB. Budget: ~30 guest-launching tests + ~20 launch-free validation tests parallelized 16-way is well within suite norms. - JSON tests:
json.loads(stdout)+ key asserts (test_measure.py:145idiom).
Mission¶
1. Module docstring: the OQ13 decisions, recorded¶
tests/test_bench.py opens with the suite's scope contract:
- Timing values are NEVER asserted — the completion line is
checked against
^Run completed in \d+\.\d{3} seconds\.$(shape),elapsed-secondsin JSON only for presence/type. - No cross-version baselines (OQ13 resolved): the
deterministic header depends only on arguments, so a baseline
matrix would record only the uncomparable timing line. There
is deliberately no
test_bench_baselines.pyand noexpected-outputs/bench-*tree; parity is asserted live against the host's qemu-img instead. - Divergences are asserted, not silenced (the map contract): a
failing cross-validation NOT registered in
KNOWN_BENCH_DIVERGENCESis a real regression; a registered divergence that stops diverging fails the regression class.
2. KNOWN_BENCH_DIVERGENCES¶
Map-style dict[str, tuple[str, str]] (key → (scope, reason)),
with entries — sourced from the 4c/5c captures, wording tightened
during implementation:
depth-serialized—-dechoed in the header for parity but executed serially; JSON reportseffective-depth: 1. (Registry-documented; no regression test — it is a semantic property, verified via the JSON field test.)wrap-rule-10-0-8— instar uses master's% (image_size - bufsize)wrap; qemu 10.0.8% image_sizecan EIO near EOF (5c raw×(d) captured both live). Regression-testable both ways.cache-modes-refused—-t≠ writeback refused; qemu runs.aio-refused—-i <any>refused; qemu runs.native-aio-refused—-nrefused by instar; qemu also fails but for its own unrelated reason (both-fail-differently).image-opts-refused—--image-optsalone refused; qemu runs. (--image-opts -fmutual-exclusion is parity, not a divergence.)bufsize-cap-2mib—-sin (2 MiB, 2147483647] refused; qemu runs.help-hint-names-instar— second line of the filename-count error saysinstar bench --help.zero-byte-early-failure— instar fails at chain discovery before any header; qemu prints the header thenFailed request.write-formats-limited—-won vmdk/vhd/vhdx refused; qemu writes them.secure-raw-detection— headerless raw (no MBR/known signature) refused as unknown format (instar-wide security posture); qemu benches it. (This is why the raw fixtures carry the 55AA signature.)
TestBenchDivergenceRegression re-verifies the qemu side (or
the instar refusal) for entries 2-7, 9-11 so silent drift is
caught; entries that need -w or KVM guard accordingly.
3. Test classes and their content¶
All in one file; a BenchTestBase(InstarTestBase) carries the
helpers: run_instar_bench(*args), run_qemu_bench(*args)
(both triples, 120 s timeouts), make_raw_mbr(dir, size),
make_qcow2(...), make_populated_qcow2(...) (qemu-io
write -P 0xbb 0 8M), make_overlay(...) (backing with 0xbb),
make_compressed_qcow2(...) (compressible content then
convert -c; assert compressed clusters exist via check/map),
sha256(path), header_line(stdout),
COMPLETION_RE.
- TestBenchHeaderParity (KVM; ~11 tests = the 4c rows):
identical args on identical fixtures, instar vs qemu:
header line byte-equal, completion shape on both, exit 0/0.
Rows: raw defaults /
-o 1k/-S 0/-s 65537/-d 1; qcow2 plain / backing / compressed; vmdk; vhd; vhdx. - TestBenchValidation (no KVM needed; ~18 tests): the
corrected §2 table from the phase-04 plan — for each: instar
stderr contains the pinned literal, exit 1; where parity is
claimed, qemu is run with the same argv and its stderr must
contain the same core text (live parity, the no-baselines
answer). Rows: echo forms ×7, range forms ×7 (incl. the
suffix-overflow route for
-s/-o), the 2 MiB cap, the two cross-option rules,--flush-interval 0without-waccepted (exit 0), filename count (0 and 2 filenames, both lines),--image-opts -fmutual exclusion. - TestBenchReadBehaviour (KVM; ~4):
-opast EOF → header on stdout thenFailed request: Input/output error, exit 1, both tools (raw fixture, in-bounds-free window); wrap-window test on a small image (the phase-1 10240-byte vector: instar exits 0 under the master rule — paired with the divergence regression asserting qemu 10.0.8 EIOs); reads on an overlay and compressed qcow2 return success (content path exercised). - TestBenchWrite (KVM; ~10 = the 5c matrix, thinned):
raw
-wbyte-identical to qemu twin (sha256); fresh qcow2 —qemu-img compareidentical + check clean + disk growth; populated qcow2 — fast path, no growth; overlay — compare + check + the COW spot-byte via qemu-io read (0xbbbeside the window);--flush-interval 50 -d 1→ JSONflushes-issued== 2 (and the flush line present on both tools);--no-drainaccepted; cluster-straddling-s 131072 -o 32768on qcow2 — compare + check. - TestBenchWriteRefusals (KVM; ~7): internal snapshot, refcount_bits=1, compressed qcow2, vmdk, vhd, vhdx, LUKS — refusal message substring + sha256 unchanged.
- TestBenchJson (KVM; ~3): full schema key-set + key order
irrelevant but presence exact;
effective-depth == 1;write/pattern/no-drainreflect args;elapsed-secondsis a float> 0; derived rates present and positive; no timing equality anywhere. - TestBenchDivergenceRegression (per §2).
Total ≈ 55 tests, ≈ 35 guest launches (+ qemu twins, which are near-instant).
4. What phase 6 does not do¶
No baselines (§1). No timing thresholds or perf assertions of any kind (the perf-tracking consumer is Future work's CI job, explicitly not this suite). No new fixtures in instar-testdata — everything is synthesized per-test (the suite must not add push traffic to the testdata repo for bench). No fuzz targets (phase 7).
Steps¶
| Step | Effort | Model | Isolation | Brief for sub-agent |
|---|---|---|---|---|
| 6a | high | sonnet | none | Write tests/test_bench.py in full per Mission §1-§3: module docstring with the OQ13 contract, KNOWN_BENCH_DIVERGENCES with the 11 entries, BenchTestBase helpers (incl. the in-file 55AA raw builder — two qemu-io writes at 510/511), and the seven test classes. Every pinned literal comes from the capture sections of the phase-01/04/05 plans — copy, don't paraphrase. Iterate with file-scoped stestr (cd tests && ../.venv/bin/stestr run test_bench) until 100% pass on this host; report the pass count and wall-clock. High effort for volume and for the discipline of matching capture text exactly; the design is fully specified. Commit 1. |
| 6b | medium | sonnet | none | Full-suite integration: make test-integration end-to-end (the whole suite, 16-way — proves bench tests are order-free and add no cross-test interference); the suite is zero-fail on healthy testdata, so ANY failure anywhere is investigated, not waved off. Record the bench-file test count, suite totals, and wall-clock delta in a ## Captured suite results (step 6b) section here; master plan row → Complete; index update; pre-commit. Commit 2. |
Captured suite results (step 6b)¶
Run on 2026-07-07 on the 16-core reference host (healthy
../instar-testdata at a34ca1bf2, git-lfs content present),
via make test-integration from the repo root (stestr, 16-way,
--exclude-regex test_info_malicious).
Bench file (from step 6a, for reference)¶
tests/test_bench.py: 62 tests in seven classes (TestBenchHeaderParity,TestBenchValidation,TestBenchReadBehaviour,TestBenchWrite,TestBenchWriteRefusals,TestBenchJson,TestBenchDivergenceRegression), plusKNOWN_BENCH_DIVERGENCESwith 11 entries.- File-scoped run (
cd tests && ../.venv/bin/stestr run test_bench): 62/62 pass in ~2.4 s wall-clock.
Full-suite totals¶
| Metric | Value |
|---|---|
| Ran | 2476 tests (was ~2414 pre-bench; +62 = exactly the bench file) |
| Passed | 1919 |
| Failed | 0 (also 0 expected-fail, 0 unexpected-success) |
| Skipped | 557 |
| Wall-clock | 13m 0s (time make test-integration, includes the instar build check) |
| stestr-reported | 775.5 s; sum of per-test execute time 7319.7 s across 16 workers |
Order-freedom and interference¶
All 62 bench tests passed inside the full parallel run, scheduled across 10 different workers ({6}-{15}) rather than a single partition — the file is order-free and introduced no cross-test interference. No bench test behaved differently under full-suite parallelism than it did file-scoped in 6a.
Incidents¶
None. Zero failures anywhere in the suite. The known trap on
this host — convert-compressed timeouts under external parallel
load — did not occur (all test_convert compressed rows passed,
worst single test ~3.5 s). The 557 skips were audited and are all
pre-existing families unrelated to bench, none originating in
tests/test_bench.py: 201 × oslo.utils not installed (optional
dependency), the KNOWN_MAP_DIVERGENCES / scanner / writer
divergence registries, baseline rows whose oracle exits non-zero,
measure's documented monolithicFlat-source refusal, and
qemu-side resize-unsupported-format rows.