Skip to content

test: cut the golden fixture's duplication and fold three VM tests into one (#312) - #318

Merged
lionello merged 2 commits into
masterfrom
test/312-optimize-tests
Aug 21, 2026
Merged

test: cut the golden fixture's duplication and fold three VM tests into one (#312)#318
lionello merged 2 commits into
masterfrom
test/312-optimize-tests

Conversation

@defangdevs

@defangdevs defangdevs commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

Two independent reductions for #312, one per commit.

1. tests/golden stores identical rendered bytes once.

I measured what actually drives the fixture diff rather than guessing. The
web golden config is the vm config plus the web overlay, so 12 of the 16
files vm rendered were byte-for-byte copies of web's
— including the three
biggest payloads (supervisor 46 KB, session CLI 21 KB, codex remote control
12 KB) — and AGENTS.robot.md was a third copy of the seeded guide. So a
two-line edit to one generated script landed twice in tests/golden. That is
the multiplier behind "the changes in tests/golden is often way more than the
code being changed".

bin/golden-snapshot.py now deduplicates at render time: the first path in
sorted order owns the bytes, and every other path that renders them is one line
in tests/golden/DUPLICATES. Fixture goes from 67 files to 55.

Replayed over every commit since 2026-07-20 that touched the fixture, that is
28% less golden churn overall — 20-50% on anything touching a shared payload
or the guide, and 0% on web-only changes, which is correct because nothing was
duplicated there:

commit golden lines before after
fix(sessions): move the supervisor's observations out of sessions.json 701 412
fix(sessions): lock sessions.json across every read-modify-write 520 351
docs(agents): type slash commands into your own pane 552 340
fix(sessions): follow /clear's session-id rotation on respawn 185 94
fix(webhook): launch claude with --channels 82 41
feat(web): download a session's transcript from its row (web-only) 333 333

No coverage is given up: the check still diffs the whole rendered snapshot, so a
copy that stops matching drops out of DUPLICATES and reappears as its own file
in the same diff. The header in DUPLICATES says exactly that, so a
disappearing line does not read as a cleanup.

2. Three VM tests that were the same test become one.

web-fail2ban.nix, self-serve-domain.nix and download-files.nix had the
same node definition — same services.agent-box block, same 12-line
password-hash activation script, same curl-only client node — and differed only
in the Caddyfile they lib.mkForce-swapped in. That is one capability per file
rather than one test per capability, and it spent three guest boots on three
routing shapes of one vhost.

They are now tests/web-surface.nix: one guest, one client, three named
subtest() blocks, every assertion and every hard-won comment carried over.
366 lines become 261, and CI goes from 7 VM checks to 5 — one --max-jobs 3
wave less.

Merging also adds coverage. The fail2ban subtest runs last (it bans the
client), which puts it after the self-serve subtest's systemctl reload caddy.service — so its "correct password still works" check now proves the
{$WEB_PASSWORD_HASH_AGENT} placeholder survives a reload. Neither split test
could see that: fail2ban never reloaded, and the self-serve Caddyfile carried no
placeholder.

What I deliberately did not touch

tests/webhook.nix (1949 lines) and tests/sessions.nix (1682) are the files
with the one-stanza-per-issue growth pattern, and an earlier comment on #312
proposed regrouping them into subtest() blocks. I decided against it here:

  • They are not padding. 552 and 505 of those lines are comments explaining why
    an assertion exists, and the rest is ~380 dense assertions each. Coverage is
    what makes them long, and I could not find assertions worth deleting.
  • The payoff is navigability only, and the price is a ~3600-line diff — the very
    thing this issue is about.
  • refactor(module): systemd template units, %i = user (#154 Phase 3) #295 (Phase 3) and feat(agents): add opencode support #315 both edit those two files right now. Reordering them
    underneath would be a merge conflict for no behavior gain.

Regrouping them into subtest() blocks is worth doing once #295 lands, as a
pure-move commit with no assertion changes, so the diff is reviewable as a move.

A further lever, if you want it (not in this PR)

The remaining golden churn is inherent: a payload's bytes are in the fixture, so
an intentional change to modules/src/* shows up once in the source and once in
the fixture. It can be cut to near zero by recording payloads as
sha256 + the normalized store references they contain instead of full bytes. A
hash is an equally strong byte-stability lock (a no-op refactor still shows zero
diff), and the reference list still catches wiring changes — but you lose the
readable "here is how the rendered config changed" diff that #154 Phase 0 was
built for, and #295 is the current consumer of exactly that. Your call; I did not
want to remove a review capability you designed in while a refactor is mid-flight.

Closes #312

Test plan

  • nix build .#checks.aarch64-linux.golden-snapshot — passes with the
    deduplicated fixture (and still passed before, so the fixture is
    cross-arch stable either way)
  • All aarch64-native checks green: assemble-module-escaping,
    download-route, golden-snapshot, module-generated-up-to-date,
    module-single-file, multi-user, webhook-route
  • nix eval .#checks.x86_64-linux.web-surface.drvPath evaluates; the three
    removed check names no longer resolve; settings-page,
    memory-protection, sessions, webhook still evaluate
  • check-testscript.sh tests/web-surface.nix — the driver's own ty and
    ruff --select F gates on the testScript pass
  • caddy adapt accepts the merged Caddyfile both with and without a
    ~/sites snippet present (empty import glob is a warning, as before)
  • full CI — the VM tests are x86-only, so web-surface gets its first real
    boot here

🤖 Generated with Claude Code

https://claude.ai/code/session_01AMbiL3ux2XbUTrGQqP15U1

defangdevs and others added 2 commits August 21, 2026 19:00
The web golden config is the vm config plus the web overlay, so 12 of the
16 files vm rendered were byte-for-byte copies of web's — including the
three biggest payloads (supervisor, session CLI, codex remote control) —
and AGENTS.robot.md was a third copy of the guide. A two-line edit to one
generated script therefore landed twice in the fixture, which is why the
tests/golden diff on a small change read as a multiple of the change.

Deduplicate at render time: the first path in sorted order owns the bytes
and every other path that renders them is one line in DUPLICATES. Measured
against real history this drops 20-50% of the golden churn on any commit
that touches a shared payload or the seeded guide, and 0% on web-only
changes, which is correct — nothing was duplicated there.

No coverage is given up. The check still diffs the whole rendered snapshot
against the fixture, so a copy that stops matching drops out of DUPLICATES
and reappears as its own file in the same diff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMbiL3ux2XbUTrGQqP15U1
…jail (#312)

tests/web-fail2ban.nix, tests/self-serve-domain.nix and
tests/download-files.nix were three tests with the same node definition —
same services.agent-box block, the same 12-line password-hash activation
script, the same curl-only client node — differing only in the Caddyfile
they lib.mkForce-swapped in and the assertions they then ran. That is one
capability per file rather than one test per capability, and it cost three
guest boots to check three routing shapes of the same vhost.

Fold them into tests/web-surface.nix: one guest, one client, three named
subtests, every assertion and every hard-won comment carried over. CI goes
from 7 VM checks to 5, which at --max-jobs 3 is one wave less.

Ordering is deliberate. The fail2ban subtest bans the client at the
firewall, so it runs last; running it after the self-serve subtest's
`systemctl reload caddy.service` also means its "correct password still
works" check now proves the {$WEB_PASSWORD_HASH_AGENT} placeholder survives
a reload — the exact sequence a real agent puts a box through, and
something neither split test could see, because fail2ban never reloaded and
the self-serve Caddyfile carried no placeholder.

The merged Caddyfile keeps all three shapes the module emits (per-user
snippet import, authenticated /<user>/downloads/ handle, authenticated
catch-all); `caddy adapt` accepts it both with and without a snippet
present. The credential-less 401 the downloads subtest takes still cannot
score against the jail: the failregex requires "Authorization":["REDACTED"],
so only a supplied wrong credential counts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMbiL3ux2XbUTrGQqP15U1
@github-project-automation github-project-automation Bot moved this to Backlog in Agent-Box Aug 21, 2026
@lionello
lionello merged commit 50f9296 into master Aug 21, 2026
1 check passed
@lionello
lionello deleted the test/312-optimize-tests branch August 21, 2026 19:25
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Agent-Box Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Optimize tests

2 participants