-
Notifications
You must be signed in to change notification settings - Fork 886
ci: shard Go race tests into a round-robin 3-way matrix (PLT-959) #3874
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d0e45a3
8b92414
34d246c
ea6cf7c
ccafa3d
d8b3396
ec8c6ac
9cb1953
b88efd4
cedd009
b4793b4
f2a1122
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,11 +18,32 @@ env: | |||||||||||||||||||
| # state_db tests run in their own workflow (sei-db-tests.yml); exclude | ||||||||||||||||||||
| # that subtree everywhere in this workflow to avoid double-running them. | ||||||||||||||||||||
| STATE_DB_PKG_PREFIX: github.com/sei-protocol/sei-chain/sei-db/state_db | ||||||||||||||||||||
| # Number of race-detection shards. Matches `NUM_SPLIT` passed to | ||||||||||||||||||||
| # `make test-group-N` so shards are locally reproducible. | ||||||||||||||||||||
| NUM_SPLIT: 3 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| jobs: | ||||||||||||||||||||
| shard-indexes: | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] This provisions a full runner and ~30s of setup on every run purely to turn There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] This job provisions a full runner and shells out to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This whole job exists only to turn test:
strategy:
fail-fast: false
matrix:
shard: [0, 1, 2] # length must equal NUM_SPLIT below
...
run: make split-test-packages NUM_SPLIT=${{ strategy.job-total }}That removes an extra runner + queue wait from the critical path of every race run. If you keep the generator job, |
||||||||||||||||||||
| name: Generate race shard indexes | ||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||
| outputs: | ||||||||||||||||||||
| json: ${{ steps.generate-index-list.outputs.json }} | ||||||||||||||||||||
| steps: | ||||||||||||||||||||
| - id: generate-index-list | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| MAX_INDEX=$((${{ env.NUM_SPLIT }}-1)) | ||||||||||||||||||||
| INDEX_LIST=$(seq 0 ${MAX_INDEX}) | ||||||||||||||||||||
| INDEX_JSON=$(jq --null-input --compact-output '. |= [inputs]' <<< ${INDEX_LIST}) | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Two small hardening points in this step:
Optionally, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] |
||||||||||||||||||||
| echo "json=${INDEX_JSON}" >> "$GITHUB_OUTPUT" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| test: | ||||||||||||||||||||
| name: Race Detection | ||||||||||||||||||||
| name: "Race Detection (shard ${{ matrix.shard }})" | ||||||||||||||||||||
| runs-on: uci-default | ||||||||||||||||||||
| needs: shard-indexes | ||||||||||||||||||||
| strategy: | ||||||||||||||||||||
| fail-fast: false | ||||||||||||||||||||
| matrix: | ||||||||||||||||||||
| shard: ${{ fromJson(needs.shard-indexes.outputs.json) }} | ||||||||||||||||||||
| env: | ||||||||||||||||||||
| GOFLAGS: -race -tags=ledger,test_ledger_mock | ||||||||||||||||||||
| steps: | ||||||||||||||||||||
|
|
@@ -52,9 +73,27 @@ jobs: | |||||||||||||||||||
| - name: Go test | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||
| PKGS=$(go list ./... | grep -v "^${STATE_DB_PKG_PREFIX}") | ||||||||||||||||||||
| echo "$PKGS" | xargs go test \ | ||||||||||||||||||||
| -timeout=${{ env.GO_TEST_TIMEOUT }} | ||||||||||||||||||||
| make split-test-packages NUM_SPLIT=${{ env.NUM_SPLIT }} | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This step hand-reimplements the shard-file selection and
A There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [blocker] Each shard recomputes the split independently, so the three shards can end up with different partitions of the same package list — and the failure is silent.
When the partitions disagree, some packages run twice and some run zero times — with every shard green and Suggest computing the plan exactly once and sharing it: run There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [blocker] Each of the N matrix shards runs Divergence is reachable in normal operation:
Because round-robin and bin-packing produce completely unrelated partitions, the union of the shards is then neither complete nor disjoint: some packages run 2–3×, and some packages run zero times — with every shard green and the aggregate "Race Detection" check passing. That is a silent loss of race-detector coverage on the gate that exists to catch races, and it fails invisibly rather than loudly. Compute the plan once per run and distribute it: a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [blocker] Race CI no longer compiles packages that have no test files. The previous step built its list from
What still covers them, and what doesn't:
Net effect: a compile break in a test-less package gated behind Cheapest fix is a separate step in this job that keeps the old guarantee without affecting the split: - name: Build all packages
run: go build ./...(Codex flagged this as P1; I agree it's real, though the exposed surface is narrow.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Coverage regression vs. the previous command (raised by the Codex pass). The old step used The gap is narrower than it first looks: If you want the old guarantee back cheaply, add a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] |
||||||||||||||||||||
| SHARD_FILE=build/packages.txt.${{ matrix.shard }} | ||||||||||||||||||||
|
cursor[bot] marked this conversation as resolved.
|
||||||||||||||||||||
| PARALLEL=() | ||||||||||||||||||||
| if grep -qx "github.com/sei-protocol/sei-chain/occ_tests" "$SHARD_FILE"; then | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Note this isn't actually "preserving" prior behavior of this job: the old race job passed no Consider splitting the invocation instead: grep -vx "$OCC" "$SHARD_FILE" | xargs -r go test -timeout=...
grep -qx "$OCC" "$SHARD_FILE" && go test -parallel=1 -timeout=... "$OCC"( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Note also that the old single-job CI never passed Splitting the invocation keeps the constraint where it's actually needed: grep -vx "$OCC" "$SHARD_FILE" | xargs -r go test -timeout=${{ env.GO_TEST_TIMEOUT }}
if grep -qx "$OCC" "$SHARD_FILE"; then
go test -parallel=1 -timeout=${{ env.GO_TEST_TIMEOUT }} "$OCC"
fiThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This reimplements the Per AGENTS.md's "guard at the choke point, never at each caller": the shard-running policy should live in exactly one place. Suggest either invoking There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Two things here:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This applies Note that the previous CI step passed no Consider splitting the invocation instead — grep -vx "$OCC" "$SHARD_FILE" | xargs -r go test -timeout=...
if grep -qx "$OCC" "$SHARD_FILE"; then go test -parallel=1 -timeout=... "$OCC"; fiThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This applies Scoping it to the one package that needs it recovers that time: grep -vx "$OCC" "$SHARD_FILE" | xargs -r go test -timeout=${{ env.GO_TEST_TIMEOUT }}
if grep -qx "$OCC" "$SHARD_FILE"; then
go test -parallel=1 -timeout=${{ env.GO_TEST_TIMEOUT }} "$OCC"
fi |
||||||||||||||||||||
| echo "occ_tests present in this shard; forcing -parallel=1" | ||||||||||||||||||||
| PARALLEL=(-parallel=1) | ||||||||||||||||||||
| fi | ||||||||||||||||||||
| xargs go test "${PARALLEL[@]}" -timeout=${{ env.GO_TEST_TIMEOUT }} < "$SHARD_FILE" | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] This is only reachable when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Also note There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Add This interacts with the pre-touch loop the Makefile added at line 619: its comment says an empty shard file avoids "breaking There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] GNU |
||||||||||||||||||||
|
|
||||||||||||||||||||
| test-check: | ||||||||||||||||||||
| name: Race Detection | ||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||
| needs: test | ||||||||||||||||||||
| if: always() | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion]
if: ${{ !cancelled() }}The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit]
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion]
if: ${{ !cancelled() }}There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] |
||||||||||||||||||||
| steps: | ||||||||||||||||||||
| - name: Check shard results | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| if [[ "${{ needs.test.result }}" != "success" ]]; then | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] |
||||||||||||||||||||
| echo "One or more Race Detection shards failed" | ||||||||||||||||||||
| exit 1 | ||||||||||||||||||||
| fi | ||||||||||||||||||||
|
|
||||||||||||||||||||
| coverage: | ||||||||||||||||||||
| name: Coverage | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,12 +99,15 @@ make build # build the seid binary into ./build/seid | |
| make install # install seid into $GOBIN | ||
| ``` | ||
|
|
||
| Tests run with the race detector and coverage. CI shards them into groups; while | ||
| iterating, run a single package directly: | ||
| Tests run with the race detector and coverage. `go-test.yml`'s Race Detection job | ||
| shards into `NUM_SPLIT` (currently 3) parallel matrix jobs, round-robin split | ||
| (package `i` goes to shard `i % NUM_SPLIT`, not a contiguous chunk — see the | ||
| `split-test-packages` Makefile target). `make test-group-N` reproduces a given | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] "reproduces a given shard locally with the same package split" is stronger than what's actually guaranteed. Suggest either pinning the tags inside the Makefile target (so the list is tag-independent of the caller), or documenting the invocation as |
||
| shard locally with the same package split: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] "reproduces a given shard locally with the same package split" is precise and correct — worth keeping that qualifier, because the invocations genuinely differ: |
||
|
|
||
| ```bash | ||
| make test-group-0 # one CI test shard (race + coverage) | ||
| go test ./<pkg>/... # run a single package | ||
| NUM_SPLIT=3 make test-group-0 # reproduce CI race shard 0 locally | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] "reproduce CI race shard 0 locally" oversells what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] "reproduce CI race shard 0 locally" overstates what
Either add the tags and align the timeout in |
||
| go test ./<pkg>/... # run a single package | ||
| ``` | ||
|
|
||
| CI mirrors these checks: `.github/workflows/golangci.yml` runs golangci-lint | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -569,19 +569,60 @@ GO_TEST_FILES != find $(CURDIR) -name "*_test.go" | |
| # default to four splits by default | ||
| NUM_SPLIT ?= 4 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Default is 4 but CI is now pinned to 3, so a bare There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] The default is 4 but CI now runs 3. Anyone who runs |
||
|
|
||
| # state_db tests run in their own workflow (sei-db-tests.yml); exclude that | ||
| # subtree here too so local shards match the CI shards exactly. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] |
||
| STATE_DB_PKG_PREFIX := github.com/sei-protocol/sei-chain/sei-db/state_db | ||
|
|
||
| $(BUILDDIR): | ||
| mkdir -p $@ | ||
|
|
||
| # The format statement filters out all packages that don't have tests. | ||
| # Note we need to check for both in-package tests (.TestGoFiles) and | ||
| # out-of-package tests (.XTestGoFiles). | ||
| # Includes every package, not just ones with test files: `go test` on a | ||
| # package with no tests still compiles it (reported as "no test files"), | ||
| # which is how go-test.yml's Race Detection job also acts as a compile | ||
| # check under -race -tags=ledger,test_ledger_mock for the whole tree. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This comment justifies including test-less packages by pointing at the compile check CI gets "under |
||
| # Filtering to test-only packages here would silently drop that coverage. | ||
| $(BUILDDIR)/packages.txt:$(GO_TEST_FILES) $(BUILDDIR) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] The prerequisite list no longer matches the target's inputs. When Consider depending on all There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Prerequisites no longer match what the recipe reads (also raised by Codex). The Either widen the prerequisite to all There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] The contents of Before this change the rule emitted only packages with CI is unaffected (fresh checkout each run), but .PHONY: $(BUILDDIR)/packages.txtThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Switching to
The workflow got this right with @if grep -qx "$(TARGET_PACKAGE)" $(BUILDDIR)/packages.txt.$*; then \Separately (Codex's point, and it's a real one now): this target's only file prerequisite is |
||
| go list -f "{{ if (or .TestGoFiles .XTestGoFiles) }}{{ .ImportPath }}{{ end }}" ./... | sort > $@ | ||
| go list ./... > $@.tmp | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Non-atomic write leaves a poisoned cache on failure. If Build the whole thing into the temp file and rename it into place, so the target only ever appears complete: $(BUILDDIR)/packages.txt:$(GO_TEST_FILES) $(BUILDDIR)
go list ./... | grep -v "^$(STATE_DB_PKG_PREFIX)" | LC_ALL=C sort > $@.tmp
mv $@.tmp $@(Adding |
||
| grep -v "^$(STATE_DB_PKG_PREFIX)" $@.tmp | sort > $@ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Pin it: |
||
| @rm -f $@.tmp | ||
|
|
||
| TARGET_PACKAGE := github.com/sei-protocol/sei-chain/occ_tests | ||
|
|
||
| # Packages whose test suite alone regularly runs 1-4+ minutes under -race. | ||
| # Plain i%N round-robin assigns these by their position in the full, | ||
| # alphabetically-sorted package list, so several of them can land on the | ||
| # same shard by coincidence. Splitting them into their own round-robin | ||
| # pass, ahead of the rest of the list, guarantees consecutive heavy | ||
| # packages rotate across shards instead of clustering. Re-derive this list | ||
| # occasionally from a race job's `ok <pkg> <secs>s` log lines. | ||
| HEAVY_TEST_PACKAGES := \ | ||
| github.com/sei-protocol/sei-chain/sei-db/db_engine/litt/disktable \ | ||
| github.com/sei-protocol/sei-chain/sei-cosmos/storev2/rootmulti \ | ||
| github.com/sei-protocol/sei-chain/sei-db/db_engine/litt/test \ | ||
| github.com/sei-protocol/sei-chain/sei-ibc-go/modules/core/04-channel/keeper \ | ||
| github.com/sei-protocol/sei-chain/giga/tests \ | ||
| github.com/sei-protocol/sei-chain/sei-cosmos/x/staking/keeper \ | ||
| github.com/sei-protocol/sei-chain/evmrpc/tests \ | ||
| github.com/sei-protocol/sei-chain/sei-ibc-go/modules/core/03-connection/keeper \ | ||
| github.com/sei-protocol/sei-chain/sei-ibc-go/modules/apps/transfer/keeper \ | ||
| github.com/sei-protocol/sei-chain/sei-cosmos/x/bank/keeper | ||
|
|
||
| # Round-robin split: package i goes to shard i%N. | ||
| # Interleaving avoids dumping a whole cluster of alphabetically-adjacent | ||
| # (and often runtime-correlated, e.g. a module's many keeper packages) | ||
| # packages into one shard, unlike a straight `split -d -n l/N` chunk split. | ||
| # Pre-touch all N files first so a shard with zero packages (NUM_SPLIT > | ||
| # package count) still gets an (empty) file instead of breaking test-group-%. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] The comment's claim doesn't quite hold: an empty shard file doesn't stop |
||
| # HEAVY_TEST_PACKAGES is round-robined separately, and first, so its own | ||
| # i%N indexing can't collide with the coincidental clustering above. | ||
| split-test-packages:$(BUILDDIR)/packages.txt | ||
| split -d -n l/$(NUM_SPLIT) $< $<. | ||
| @for i in $$(seq 0 $$(($(NUM_SPLIT)-1))); do : > $(BUILDDIR)/packages.txt.$$i; done | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] The pre-touch loop truncates @rm -f $(BUILDDIR)/packages.txt.*
@for i in $$(seq 0 $$(($(NUM_SPLIT)-1))); do : > $(BUILDDIR)/packages.txt.$$i; doneThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Two staleness edges around shard files, both local-only:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Only shards |
||
| @printf '%s\n' $(HEAVY_TEST_PACKAGES) > $(BUILDDIR)/heavy-packages.txt | ||
| @grep -Fxf $(BUILDDIR)/heavy-packages.txt $< > $(BUILDDIR)/packages.txt.heavy || true | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Drift in @comm -23 <(sort $(BUILDDIR)/heavy-packages.txt) <(sort $<) \
| sed 's/^/warning: HEAVY_TEST_PACKAGES entry not found: /' >&2Separately, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] That matters: the list is written in what looks like descending-cost order (disktable, rootmulti, litt/test, …, bank/keeper), and the round-robin
i.e. shard 0 gets 4 heavy packages and the cost ordering is back to alphabetical accident — which is what the separate heavy pass was introduced to avoid. Swapping the arguments preserves the declared order while still filtering to packages that exist: @grep -Fxf $< $(BUILDDIR)/heavy-packages.txt > $(BUILDDIR)/packages.txt.heavy || true |
||
| @grep -Fxvf $(BUILDDIR)/heavy-packages.txt $< > $(BUILDDIR)/packages.txt.rest || true | ||
| @awk -v n=$(NUM_SPLIT) -v dir=$(BUILDDIR) '{print >> (dir "/packages.txt." (NR-1)%n)}' $(BUILDDIR)/packages.txt.heavy | ||
| @awk -v n=$(NUM_SPLIT) -v dir=$(BUILDDIR) '{print >> (dir "/packages.txt." (NR-1)%n)}' $(BUILDDIR)/packages.txt.rest | ||
| @rm -f $(BUILDDIR)/heavy-packages.txt $(BUILDDIR)/packages.txt.heavy $(BUILDDIR)/packages.txt.rest | ||
| test-group-%:split-test-packages | ||
| @echo "🔍 Checking for special package: $(TARGET_PACKAGE)" | ||
| @if grep -q "$(TARGET_PACKAGE)" $(BUILDDIR)/packages.txt.$*; then \ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This substring
Switching this to |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] This job exists only to convert the literal
NUM_SPLIT: 3(line 23) into[0,1,2], but it costs a full serialized runner acquisition (~20–40s) on the critical path of a PR that's optimizing wall-clock, plus aneeds:edge.Since
NUM_SPLITis a static literal in this same file (not derived from repo state the way a dynamically-discovered test-case count would be),matrix: shard: [0, 1, 2]inline is equivalent and free. The tradeoff is having to edit two places when changing the shard count — a comment onNUM_SPLITpointing at the matrix would cover that.