Repository navigation
ci: shard measured Go server tests and split parallel checks (#445) - #446
Conversation
Review — CS-pve-agent3 PR#446 — head f030d44Dom: REQUEST CHANGES Evidence tags: [T] I ran it myself (local scratch archives of head / merged tree / base), [A] GitHub Actions API or downloaded CI artifacts, [K] code reading. The design is solid. The fail-closed gates, exact-once execution, receipts and coverage merge all hold up, and the speed-up is real. Two things block a merge: the manifest is already stale against current Findings
Answers to the focus pointsFail-closed gate. The gate is fail-closed. Coverage of tests. Each Coverage merge. The merge is correct, and the percentage is unchanged. I re-merged the three downloaded shard profiles independently in Python: 14,229 identical blocks, all statement counts equal, 0 counter mismatches against the uploaded Fork guards. 9 in Measured times. The "before" numbers are accurate. I re-derived them via
The Go critical path drops well below the ~19-minute server step. E2E is now the critical path. [A] Complexity and maintenance. Yes, the manifest has to be regenerated or extended by hand, and none of that is documented (Findings 1 and 2). It is otherwise fairly heavy: about 600 lines of runner, report and inventory code with receipts, SHA bindings and attempt logic. That is defensible for fail-closed CI, but only if the day-to-day "I added a test" path is a one-liner. Always-checks. There is no production code change: no Tests run (merged tree
|
| ID | Mutation | Check | Result |
|---|---|---|---|
| M1 | New TestReviewerMutantUnassigned in cmd/server, not in manifest |
--validate |
red: unassigned Go test: TestReviewerMutantUnassigned |
| M2 | go-test gate also accepts skipped |
test-ci-parallel-gates.js + Go fork-guard test |
red (both; Go reports 8 gate failures) |
| M3 | Drop go-server-2 from build-and-publish.needs |
JS + Go guard tests | red (both) |
| M4 | coverageStats: count > 0n → >= 0n |
test-ci-go-shards.js |
survives (Finding 3) |
| M5 | Drop the "every selected test completed" loop in verifyTestJSON |
test-ci-go-shards.js |
red |
| M6 | Rename advert_intervals_test.go (test moves file) |
--validate |
red: test source mismatch |
| M7 | Coverage merge uses max instead of sum | test-ci-go-shards.js |
red |
| M8 | Drop ^…$ anchors from testPattern |
test-ci-go-shards.js |
red |
| (natural) | Current master + head |
--validate |
red (Finding 1) |
Green controls on unmodified head: 18/18, 57 + 33 gate cases, and the Go fork-guard test all pass.
Not verified
- I did not run the shards under
-racelocally. Execution evidence comes from the CI artifacts (receipts,test.jsonl, profiles) of attempt 2, which I re-verified offline. - I did not run the full sharded E2E suite (3 shards). I ran only the core Playwright smoke, since the PR has no frontend or server changes.
- I did not exercise the cancellation and rerun-failed-jobs paths (earlier-attempt receipts) on GitHub. I read them in code and their unit tests only.
- I did not check branch-protection settings (whether "require up-to-date branch" would have caught Finding 1).
- I did not reproduce the PR's local arm64 timing samples.
Head was f030d440 at both the start and the end of this review (git ls-remote).
) The merged-profile fixtures covered every block, so counting a block with count >= 0 as covered (mutant M4 in the PR #446 review) passed all cases. A third block now stays at count 0 in every shard; the merge must report 5/8 statements, not 8/8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…445) Only js-test needs full history, for the XSS --diff step. The guard, Go shard, ingestor, library, script and go-test aggregate jobs copied fetch-depth: 0 from the old serial job. The gate test now pins this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new func Test... in cmd/server no longer needs a hand-written manifest entry. Manifest v2 pins only the slowest entries to shards 1..N-1 (go test -run); shard N is the catch-all and runs everything else with go test -skip of the pinned names. The five #444 tests, and any later ones, run there automatically. The guard now checks that every active Test, Fuzz seed target and Example is selected by exactly one shard, and the aggregator checks that the receipts together ran each active entry once. Stale pins select nothing and are only reported. Patterns over 96 KiB are refused (Linux MAX_ARG_STRLEN is 128 KiB per argv string); with 2/9 pins they are under 0.5 KiB. scripts/rebalance-go-server-shards.js regenerates the manifest from CI go test -json artifacts. The checked-in weights come from the GitHub runners of run 38529224242 attempts 1 and 2 (not local arm64): 2 pins on shard 1, 9 on shard 2, 2,483 entries in the catch-all, projected 319 / 325 / 299 test seconds. AGENTS.md documents when and how to rebalance, and the go-test summary prints the per-shard test seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d5f14aa merged the same origin/master (89adc5b) into f030d44 and re-measured the v1 manifest locally (linux/arm64). Its tree differs from 71eae69 only in scripts/go-server-shards.json, which the catch-all change (4d9acd5) replaces with the v2 manifest generated from GitHub runner timings, so that file is kept as v2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rapport — CS-pve-agent1 PR#446 runde-2 — head 4312030Review feedback addressed (commit Evidence tags: [T] I ran it myself, [A] GitHub Actions API or downloaded CI artifacts, [K] code reading. Commits, in order, all ordinary with no rebase, amend or force-push:
Unchanged: 9 fork guards in Concurrent pushWhile I was working, the remote branch moved from Tests (tree
|
| Finding | Mutation | Check | Result |
|---|---|---|---|
| 1 | The catch-all selects only entries explicitly pinned to it (old semantics) | test-ci-go-shards.js, --validate |
red: empty active Go shard 3: regenerate with scripts/rebalance-go-server-shards.js … |
| 2 | The catch-all -skip omits shard 2's pins (a test would run twice) |
test-ci-go-shards.js |
red |
| 2 | Rebalance puts the next entry in the heaviest shard | test-ci-go-shards.js |
red |
| 3 | coverageStats > 0n → >= 0n (M4) |
test-ci-go-shards.js |
red (it survived 18/18 on the old fixture) |
| 4 | Rebalance keeps only the first run's sample | test-ci-go-shards.js |
red |
| 5 | The pattern-length assertion is disabled | test-ci-go-shards.js |
red |
| 8 | fetch-depth: 0 reintroduced in the shard steps |
test-ci-parallel-gates.js |
red: go-server-1 must use the default shallow checkout |
Green control on unmodified head: 26/26, 57 + 33, and --validate OK (2,494 entries, 2 / 9 / 2,483).
CI per job: run 38551184270, attempt 1, head 4312030d [A]
Conclusion: success. 15 jobs succeeded and 3 were skipped as expected (Release Artifacts, Publish Badges & Summary, Deploy Staging). No reruns. The known flakies #271 and #301 did not occur.
| Job | Result | Duration |
|---|---|---|
| 📋 Verify Go Test Assignments | success | 66 s |
| ✅ Go Server Tests (1/3) | success | 577 s (test step 555 s) |
| ✅ Go Server Tests (2/3) | success | 479 s (test step 453 s) |
| ✅ Go Server Tests (3/3), catch-all | success | 436 s (test step 415 s) |
| ✅ Go Ingestor Tests | success | 225 s |
| ✅ Go Libraries & CLI | success | 84 s |
| ✅ JavaScript Unit Tests & Lint | success | 122 s |
| ✅ Script & Container Unit Checks | success | 69 s |
| ✅ Go Build & Test (aggregate) | success | 35 s |
| 🧱 Validate Docker Image | success | 81 s |
| 🎭 Playwright E2E Tests (1/3) | success | 640 s |
| 🎭 Playwright E2E Tests (2/3) | success | 654 s |
| 🎭 Playwright E2E Tests (3/3) | success | 654 s |
| 📊 Verify E2E Shards & Coverage | success | 18 s |
| 🏗️ Build & Publish Docker Image | success | 13 s |
| 📦 Release Artifacts / 📝 Publish Badges & Summary / 🚀 Deploy Staging | skipped | — |
From the go-coverage report: 2,494 unique entries, each run once (2,485 PASS + 9 skip), with selections -run:2 / -run:9 / -skip:2483. Test seconds are 476.6 / 372.7 / 333.0. Coverage is 25,976/28,554 = 90.97%, with the 91.0% badge unchanged. The Go span is 614 s and the whole pipeline 11:31. Runner time was 69.22 min.
Not verified
- I did not exercise the rerun-failed-jobs path for the new
selectionFlagreceipt field on GitHub. Earlier-attempt receipts from before this change do not carry it, but they could only exist for an older head, which the run/head check already rejects [K]. - I did not compare against branch protection settings.
Review — CS-Macmini PR#446 — head 4312030Dom: APPROVE med nits Evidence tags: [T] I ran it myself (scratch archives of head / merged tree / base, local servers on ports 13801–13802), [A] Actions API or downloaded CI artifacts, [K] code reading. Round 2 closes all six points of the previous review. The catch-all design removes the whole class of problem behind blocker 1 rather than patching the symptom, and I verified that twice over: Findings
No blocking findings. Nothing above needs to hold up the merge. Answers to the pointsBlocker 1 — manifest current against Maintainability — catch-all, exactly-once guard, rebalance script. All three hold up under my own mutants.
M4 killed. Balance from runner times; Gate fail-closed, fork guards, Merged coverage equals
Exactly equal, with only two single-statement blocks differing in direction (one each way, netting zero) — ordinary coverage nondeterminism, not a merge artifact. Independently of that I re-merged the three downloaded CI shard profiles in Python: identical block sets, 14 229 blocks, all statement counts equal, 0 counter mismatches against the uploaded merged profile, 25 976 / 28 554 = 90.971 %. It is a true union (shard 3 alone 90.93 %, shards 1 and 2 alone 4.37 % and 6.57 %). The Always-checks. No production change: Issue acceptance. The timing data is in the PR as the manifest's Tests runOn the merged tree against
Mutants (reviewer-authored, scratch only)
Green controls on the unmutated merged tree: 26/26, 57+33, Not verified
Head was |
Relates to #445
Summary
The
cmd/serverGo tests (race and coverage) run in three parallel jobs instead of one serial step. Ingestor coverage and its ForceReconnect race check, the small Go libraries/CLI/anomaly checks, the JavaScript units/lint/XSS checks, and the shell/container/proto checks now run as independent jobs. The existing E2E shards, fixtures and ports are unchanged. No benchmark, active fuzzing, short mode, retry, new skip or test-workload reduction is introduced.Draft only, auto-merge off. No ready marking, merge, issue closure, settings changes, upstream writes or staging/production contact.
How sharding works (manifest v2, catch-all)
scripts/go-server-shards.jsonpins only the slowest entries: shard 1 and shard 2 run their pins withgo test -run '^(…)$'.go test -skip '^(all pinned names)$'.func Test…needs no manifest edit. It runs in shard 3. The five ops: bundled Mosquitto opt-in, normalised DISABLE_MOSQUITTO, pprof loopback default (#441) #444 tests (compose_optin_441_test.go) are not in the manifest and ran there on CI.Exactly-once guard
📋 Verify Go Test Assignmentsjob checks that every active entry from the standard-library AST inventory is selected by exactly one shard. It also checks that the inventory equals the race-enabledgo test -listset.go test -jsonoutput contains exactly its expected set, each entry once.-runor-skip).Rebalancing: when and how (also in AGENTS.md, "Go server test shards")
The
✅ Go Build & Testsummary prints the test seconds per shard. Rebalance when one of these holds:--validatereports stale pins.The script averages each Test's top-level
go test -jsonElapsedon the GitHub runners. It fills the catch-all with the cheapest entries up to 1/3 of the total and pins the rest longest-first to the lightest pinned shard. The output is deterministic, with one pin per line, and records the SHA-256 of every source file.Weights come from GitHub runners
The checked-in weights come from run 38529224242 attempts 1 and 2 on the GitHub runners. Attempt 1's JSON was taken from the job logs and attempt 2's from the
go-server-*artifacts. The local arm64 measurements are no longer used. The projection is 319 / 325 / 299 test seconds.Argument length (
MAX_ARG_STRLEN)go testpasses the pattern to the test binary as one argv string (-test.run=…/-test.skip=…), and Linux limits one argv string to 128 KiB. Only pinned names go into a pattern, so the patterns are now 92 / 354 / 443 bytes, compared with about 34 KB each before. The guard refuses any pattern over 96 KiB.Fail-closed gates (unchanged by this round)
go-testrequires a literalsuccessfrom all eight Go/split jobs, including the guard.🏗️ Build & Publish Docker Imagerequires those eight jobs, the Go aggregate and the five E2E/report/image prerequisites: fourteen in total.deploy.ymland 1 inrelease-fast-path.yml. Nosquad-*.yml, other workflow or repository setting changes.js-testkeepsfetch-depth: 0, for the XSS--diffstep. The split jobs use the default shallow checkout.Measurements (GitHub Actions)
"Go span" runs from the first Go job start to
go-testcompletion. "Runner minutes" is the sum of start-to-completion times of the non-skipped jobs, not billed minutes.f030d440)f030d440)d5f14aa0)4312030d)TestHotStartup_ConcurrentQueryDuringBackgroundLoad, took 320 / 281 / 194 / 454 s in these four runs. It sits on shard 1 with a single ~20 s companion, so shard 1 cannot finish faster than that test. A rebalance over all four runs keeps the same structure (checked, not committed). The remaining spread is that test's runner variance, not the assignment.Local verification (tree
2c21da81)-race, plus aggregation: 2,494 unique entries, 2,485 PASS and 9 SKIP, 91.0%.--runtime-listpasses.cmd/ingestorsuite: PASS.go vetoncmd/server: clean.sh test-all.sh: 248 passed, 0 failed.test-ci-go-shards.js: 26/26, including a realgo testexact-once run of the catch-all on a fixture module.node test-frontend-helpers.js: 713 passed, 0 failed.🤖 Generated with Claude Code