|
5 | 5 | branches: [main] |
6 | 6 | pull_request: |
7 | 7 | workflow_dispatch: |
| 8 | + # Nightly pollution-detector run (see test-one-process below). |
| 9 | + schedule: |
| 10 | + - cron: "17 7 * * *" |
8 | 11 |
|
9 | 12 | concurrency: |
10 | 13 | group: ci-${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }} |
@@ -78,31 +81,29 @@ jobs: |
78 | 81 | run: bun run build |
79 | 82 |
|
80 | 83 | # The suite is sharded so the slowest slice, not the whole suite, sets the |
81 | | - # wall clock. The old ./src leg (387 files, ~88s local) is split into three |
82 | | - # path-disjoint shards measured at ~30s (src-a: tui, 146 files), ~46s |
83 | | - # (src-b: agent + subagent, 90 files), and ~45s (src-c: everything else in |
84 | | - # src, 153 files); the ./tests leg stays whole because ./evals ./scripts |
85 | | - # finish in ~1s and a leg of their own would be all setup overhead. |
86 | | - # Every shard still goes through check:projects-dir-guard: the |
87 | | - # guard forwards these path filters to the suite it wraps, and the union of |
88 | | - # the shards' filters is exactly ./src ./tests ./evals ./scripts, so the gate covers |
89 | | - # the same tests as before, all of them sandboxed. |
| 84 | + # wall clock. Four time-balanced shards split the same |
| 85 | + # ./src ./tests ./evals ./scripts union via bun's --shard=k/4, balanced by |
| 86 | + # the checked-in per-file durations in scripts/ci-timings.json (--timings). |
| 87 | + # Every shard still goes through check:projects-dir-guard: the guard |
| 88 | + # forwards the path union plus the shard flags to the suite it wraps, so the |
| 89 | + # gate covers the same tests as before, all of them sandboxed. |
| 90 | + # |
| 91 | + # Timings refresh policy: regenerate scripts/ci-timings.json by running the |
| 92 | + # full union locally with --update-timings (same seeded flags as `test`): |
| 93 | + # bun run check:projects-dir-guard ./src ./tests ./evals ./scripts \ |
| 94 | + # --timings=./scripts/ci-timings.json --update-timings |
| 95 | + # Regen when the slowest shard's Test step skews more than ~20% above a |
| 96 | + # quarter of the one-process suite time (shards drifting apart means the |
| 97 | + # timings no longer describe the suite), or proactively whenever slow files |
| 98 | + # land. A scheduled refresh artifact is a future option, not current setup. |
90 | 99 | test: |
91 | 100 | runs-on: ubuntu-latest |
92 | 101 | strategy: |
93 | | - # A red shard must not cancel the others; every result is the signal. |
| 102 | + # A red shard must not cancel the others; all results are the signal. |
94 | 103 | fail-fast: false |
95 | 104 | matrix: |
96 | | - shard: |
97 | | - - name: src-a |
98 | | - paths: ./src/tui |
99 | | - - name: src-b |
100 | | - paths: ./src/agent ./src/subagent |
101 | | - - name: src-c |
102 | | - paths: ./src/auth ./src/changelog ./src/config ./src/cost ./src/crash ./src/exec ./src/logging ./src/mcp ./src/perf ./src/permission ./src/plugins ./src/provider ./src/session ./src/shell ./src/telemetry ./src/tools ./src/trust ./src/upgrade ./src/util ./src/web ./src/config.test.ts ./src/context-compactor.test.ts ./src/director.test.ts ./src/inference-abort.test.ts ./src/inference-error-message.test.ts ./src/inference-gateway-error.test.ts ./src/list-dir.test.ts ./src/pricing-fetcher.test.ts ./src/pricing-metadata.test.ts ./src/profiles.test.ts ./src/prompts.test.ts ./src/renderer.test.ts ./src/settings.test.ts ./src/state.test.ts |
103 | | - - name: tests-evals-and-scripts |
104 | | - paths: ./tests ./evals ./scripts |
105 | | - name: test (${{ matrix.shard.name }}) |
| 105 | + shard: ["1/4", "2/4", "3/4", "4/4"] |
| 106 | + name: test (${{ matrix.shard }}) |
106 | 107 | steps: |
107 | 108 | - name: Checkout |
108 | 109 | uses: actions/checkout@v4 |
@@ -131,19 +132,67 @@ jobs: |
131 | 132 | - name: Install dependencies |
132 | 133 | run: bun install --frozen-lockfile |
133 | 134 |
|
134 | | - # The same script the local `bun run check` gate runs, with the shard's |
135 | | - # path filters forwarded through the guard to the suite. The guard |
136 | | - # routes a filtered run through test:paths, which carries the same |
137 | | - # seeded flags as the `test` script; bun test filters are additive, so |
138 | | - # appending filters to `bun run test` could not narrow it. Randomized |
139 | | - # order catches tests that only pass in the default file order (shared |
140 | | - # module-level state, an unrestored global mock, a leaked env var). |
| 135 | + # The same script the local `bun run check` gate runs, with the full |
| 136 | + # path union plus the shard's --shard/--timings flags forwarded through |
| 137 | + # the guard to the suite. The guard routes a filtered run through |
| 138 | + # test:paths, which carries the same seeded flags as the `test` script; |
| 139 | + # bun test filters are additive, so appending filters to `bun run test` |
| 140 | + # could not narrow it. --shard splits by file (balanced by --timings), |
| 141 | + # so every shard covers the same union and the four shards together |
| 142 | + # cover the whole suite. Randomized order catches tests that only pass |
| 143 | + # in the default file order (shared module-level state, an unrestored |
| 144 | + # global mock, a leaked env var). |
141 | 145 | # The seed stays 424242 in every shard rather than varying per shard: |
142 | 146 | # the shards already run disjoint file sets, and a fixed seed keeps |
143 | 147 | # any failure reproducible locally with the same |
144 | | - # `bun run test:paths <paths>`. |
| 148 | + # `bun run test:paths <paths> --shard=k/4 |
| 149 | + # --timings=./scripts/ci-timings.json`. |
145 | 150 | - name: Test |
146 | | - run: bun run check:projects-dir-guard ${{ matrix.shard.paths }} |
| 151 | + run: bun run check:projects-dir-guard ./src ./tests ./evals ./scripts --shard=${{ matrix.shard }} --timings=./scripts/ci-timings.json |
| 152 | + |
| 153 | + # Cross-shard pollution detector: the shards above split the path union, |
| 154 | + # but the union is not the isolation domain — a mock.module leak across |
| 155 | + # files fails in the one-process suite yet passes when the files land in |
| 156 | + # different shards (CL-6967). This job reruns the whole union in one |
| 157 | + # process with no filters, exactly like the local `bun run check` gate. |
| 158 | + # Nightly (plus manual workflow_dispatch), not per-PR: the one-process |
| 159 | + # suite takes ~2 minutes on CI and would put that back on the PR wall |
| 160 | + # clock this sharding removes. Non-blocking (continue-on-error) so a slow |
| 161 | + # or flaky full run cannot hold the gate; a real pollution failure still |
| 162 | + # shows up red for triage. |
| 163 | + test-one-process: |
| 164 | + name: test (one-process pollution detector) |
| 165 | + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' |
| 166 | + continue-on-error: true |
| 167 | + runs-on: ubuntu-latest |
| 168 | + steps: |
| 169 | + - name: Checkout |
| 170 | + uses: actions/checkout@v4 |
| 171 | + |
| 172 | + - name: Setup Node |
| 173 | + uses: actions/setup-node@v4 |
| 174 | + with: |
| 175 | + node-version: "24" |
| 176 | + |
| 177 | + - name: Setup Bun |
| 178 | + uses: oven-sh/setup-bun@v2 |
| 179 | + with: |
| 180 | + bun-version: "1.3.14" |
| 181 | + |
| 182 | + - name: Install ripgrep |
| 183 | + run: sudo apt-get install -y ripgrep |
| 184 | + |
| 185 | + - name: Cache dependencies |
| 186 | + uses: actions/cache@v4 |
| 187 | + with: |
| 188 | + path: node_modules |
| 189 | + key: bun-${{ hashFiles('bun.lock') }} |
| 190 | + |
| 191 | + - name: Install dependencies |
| 192 | + run: bun install --frozen-lockfile |
| 193 | + |
| 194 | + - name: Test |
| 195 | + run: bun run check:projects-dir-guard |
147 | 196 |
|
148 | 197 | # protect-main still requires the pre-restructure check names. These jobs |
149 | 198 | # exist only to publish those contexts after the real work succeeds. |
|
0 commit comments