Skip to content

Commit add806c

Browse files
test(ui): read the Test UI shard width from ci.yml instead of spelling /16 (#26516)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi92nQVkpVWAJTFvTHUJtyJt8)_ Part of #26443 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:两个 ui 选择器自测不再写死 `/16`,改为调用 `scripts/ci-shard-matrix.mjs` 的 `shardTotal(ci.yml, 'test-ui')` 读出 Test UI 的分片数。 - 为什么改:分片数只该住在 ci.yml 一处;以后调整 Test UI 矩阵宽度,不必再去 ui 测试里找第二份 `16`。 - 风险与回滚:仅测试文件,无发布面;ci.yml 只读不改。回滚即 revert 本 PR。 - 先看:`ui/scripts/__tests__/select-affected-tests.test.ts`(宽度断言那一段)、`ui/scripts/__tests__/select-affected-tests-wiring.test.ts`(RUN_HEADER 与三处 `--shard=1/N`)。 - 两分钟验证:`cd ui && pnpm exec vitest run scripts/__tests__/select-affected-tests-wiring.test.ts scripts/__tests__/select-affected-tests.test.ts`,预期 2 个文件 49 个用例全绿。 - 需要你做的:无,席位落地。 ## What changes Test-only, two files under `ui/scripts/__tests__/` (both edits are the card's deliverable, no other test was forced to move): - `select-affected-tests-wiring.test.ts`: `RUN_HEADER` and the three `--shard=1/16` / `shard 1 of 16` expectations now use `SHARDS = shardTotal(ci.yml text, UI_JOBS.test)`. The workflow text is read once, before the header is built. - `select-affected-tests.test.ts` ("keeps the matrix width and the full-suite command for the full battery"): the literal `shard: [1, ..., 16]` list and the three `/16` spellings are replaced by `shardTotal(rootCi(), UI_JOBS.test)`; the run-step header is now asserted present (the old `job.indexOf(...)` slice silently fell back to the job's last character when the header was missing). Both import the reader as `../../../scripts/ci-shard-matrix.mjs` and read ci.yml through the files' existing root resolution (`workspaceRoot()` / `rootCi()`). `.github/workflows/ci.yml` and `scripts/ci-shard-matrix.mjs` are untouched. ## Tests At head 9507724: `pnpm exec vitest run` on the two files: `Test Files 2 passed (2)`, `Tests 49 passed (49)` (os-verify-lock VERDICT command-exit 0). Ablation (on-disk ci.yml mutation through `scripts/ablation-replace.mjs`, restored by trap; blob back to HEAD b1014f8 and `git diff HEAD` empty after each leg): <details><summary>Leg A: test-ui matrix list alone resized to 12, /16 left in the name and run step</summary> New tests: `Tests 1 failed | 38 passed`, both files red. The wiring file refuses at load: `ci.yml has no step headed "- name: Run the ui tests (shard ${{ matrix.shard }}/12)"`; the selector file fails "keeps the matrix width...". So the tests still require the run step to agree with the matrix. </details> <details><summary>Leg B: the whole test-ui job resized to 12 (list, all four /16, "of 16.")</summary> `node scripts/ci-shard-matrix.mjs --job test-ui` answers 12. New tests: `Test Files 2 passed`, `Tests 49 passed`: they follow the workflow. The origin/main versions of the same two files against the same mutated ci.yml: `Tests 1 failed | 38 passed`, both files red: they stayed pinned at 16. </details> ## Acceptance notes - The ui `select-affected-tests` selector reaches a root `scripts/` change only through its always-on set; these two files live in `ui/scripts/__tests__/`, which that set covers, so a later change to `scripts/ci-shard-matrix.mjs` still re-runs them. - The prose "the full sixteen-shard suite" in the wiring file's header is history (what every PR ran before objectui#12214's fix) and is left as written. - The card's acceptance grep over these two files (`git grep -nE "/(3|8|16)\b"`) has zero hits. Over all of `ui/scripts/__tests__/*.ts` the remaining shard-shaped hits are prose (`Test (shard N/16)` in the headers of check-eager-closure-budget, check-skill-examples and check-test-path-roots, which name objectui's retired job) and check-test-shard-results' own synthetic 8-shard roster, which reads no workflow. None copies a live ci.yml size, and all are outside this card's file surface. ## Verification `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at 9507724 derived 18 commands; all 18 run locally, exit codes recorded before any pipe. `--ran`: "18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN". <details><summary>Per-command exit codes</summary> 17 of 18 exit 0: check-closing-keyword-parity (scan and --self-test), check-comment-mask-corpus, check:cross-package-test-inputs, check:driver-memory-census, check:engine-double-contract, check:gitlink-declared, check:nul-bytes, check:published-files, check:query-options-erasure, check:refd-timer-probe, check:tier-file-adoption, check:type-check-coverage, check:type-check-debt, check:undeclared-dep-imports, check:watch-hint-literal, check:where-matcher. `pnpm --dir ui exec vitest run scripts/` exit 1, not caused by this diff: - first run: 16 files red, every one `Cannot find package '@objectstack/spec/...'` (the container had not built the seven ui deps that CI's Test UI builds first); - after `turbo run build` of those seven closures (VERDICT command-exit 0): `Test Files 1 failed | 177 passed | 2 skipped`, `Tests 1 failed | 5096 passed`. The one red is a 15 s timeout in `check-i18n-dead-keys.test.ts` ("keeps every key it makes reachable OUT of both tiers") on the shared box; that file alone: `Tests 134 passed (134)`, VERDICT command-exit 0. Both changed files are green in that run. </details> CI check-runs: in progress at the time of writing; Test UI is the authority for the suite. --- _Generated by [Claude Code](https://claude.ai/code/session_01ADHkmRRgsu9coZffDuXRpz)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4e644f0 commit add806c

2 files changed

Lines changed: 24 additions & 11 deletions

File tree

‎ui/scripts/__tests__/select-affected-tests-wiring.test.ts‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import os from 'node:os';
55
import path from 'node:path';
66
import { fileURLToPath } from 'node:url';
77

8+
import { shardTotal } from '../../../scripts/ci-shard-matrix.mjs';
89
import { workspaceRoot } from '../workspace-root.mjs';
10+
import { UI_JOBS } from './helpers/root-ci';
911

1012
/**
1113
* objectui#13350 — the two steps of the `Test UI` job that make the affected
@@ -14,7 +16,7 @@ import { workspaceRoot } from '../workspace-root.mjs';
1416
* runs this tree's suite; the objectui copy under `ui/.github/` runs nowhere.
1517
*
1618
* `Select the tests this change affects` (id `select`) appends the selector's
17-
* verdict line to `$GITHUB_OUTPUT`; `Run the ui tests (shard N/16)` reads one
19+
* verdict line to `$GITHUB_OUTPUT`; `Run the ui tests (shard N/M)` reads one
1820
* output of that step into an env var and branches on it. The runner splits each appended
1921
* line at its first `=`, so the line `mode=affected` is the output `mode` with
2022
* the value `affected`. From objectui#12214 until this card the run step
@@ -33,7 +35,10 @@ import { workspaceRoot } from '../workspace-root.mjs';
3335
* `env:` names, and reads which command the run step chose off a `pnpm`
3436
* stand-in that records its argv.
3537
* Textual rather than YAML-parsed, for the reason `markdown-test-inputs.test.ts`
36-
* gives: what is under test is the bytes CI runs.
38+
* gives: what is under test is the bytes CI runs. The matrix width M is not
39+
* spelled here either: the monorepo's `scripts/ci-shard-matrix.mjs` reads it
40+
* off the job's own `shard:` list, so resizing the matrix moves every `/M`
41+
* below with it.
3742
*
3843
* affected a `pull_request` and a `merge_group` event hand vitest exactly the
3944
* filters the selector wrote.
@@ -49,9 +54,12 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../
4954
/** Where the workflow runs: its steps' working directory, and the home of `.github/`. */
5055
const workflowRoot = workspaceRoot(repoRoot);
5156
const CI_YML = path.join(workflowRoot, '.github', 'workflows', 'ci.yml');
57+
const YAML = fs.readFileSync(CI_YML, 'utf8');
58+
/** The `test-ui` matrix width, read from the workflow by the one reader of shard sizes. */
59+
const SHARDS = shardTotal(YAML, UI_JOBS.test);
5260

5361
const SELECT_HEADER = ' - name: Select the tests this change affects\n';
54-
const RUN_HEADER = ' - name: Run the ui tests (shard ${{ matrix.shard }}/16)\n';
62+
const RUN_HEADER = ` - name: Run the ui tests (shard \${{ matrix.shard }}/${SHARDS})\n`;
5563
/** The output reference a run step's `env:` value is, when it reads the select step. */
5664
const SELECT_OUTPUT = /^\$\{\{ steps\.select\.outputs\.([A-Za-z0-9_-]+) \}\}$/;
5765

@@ -91,7 +99,6 @@ function readStep(yaml: string, header: string): Step {
9199
return { at, id, env, run: `${body.join('\n').trimEnd()}\n` };
92100
}
93101

94-
const YAML = fs.readFileSync(CI_YML, 'utf8');
95102
const SELECT = readStep(YAML, SELECT_HEADER);
96103
const RUN = readStep(YAML, RUN_HEADER);
97104

@@ -288,8 +295,8 @@ describe('the affected speed, both steps run as ci.yml writes them (objectui#133
288295
const shard = runShard({ name: 'pull_request', base: BASE_REF }, real.temp, real.outputs);
289296
expect(shard.argv.slice(0, AFFECTED_ARGV.length)).toEqual(AFFECTED_ARGV);
290297
expect(shard.argv.slice(-real.filters.length)).toEqual(real.filters);
291-
expect(shard.argv).toContain('--shard=1/16');
292-
expect(shard.log).toContain(`Affected speed: ${real.filters.length} vitest filter(s), shard 1 of 16.`);
298+
expect(shard.argv).toContain(`--shard=1/${SHARDS}`);
299+
expect(shard.log).toContain(`Affected speed: ${real.filters.length} vitest filter(s), shard 1 of ${SHARDS}.`);
293300
});
294301

295302
it('a merge_group build hands vitest the same filters', () => {
@@ -306,7 +313,7 @@ describe('the affected speed, both steps run as ci.yml writes them (objectui#133
306313
const event: Event = { name: 'push', base: '' };
307314
const shard = runShard(event, temp, runSelect(event, temp, replay).outputs);
308315
expect(shard.argv.slice(0, FULL_ARGV.length)).toEqual(FULL_ARGV);
309-
expect(shard.argv).toContain('--shard=1/16');
316+
expect(shard.argv).toContain(`--shard=1/${SHARDS}`);
310317
expect(shard.argv.filter((arg) => real.filters.includes(arg))).toEqual([]);
311318
});
312319

‎ui/scripts/__tests__/select-affected-tests.test.ts‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
selectAffectedTests,
2020
trackedFiles,
2121
} from '../select-affected-tests.mjs';
22+
import { shardTotal } from '../../../scripts/ci-shard-matrix.mjs';
2223
import { jobBlock, rootCi, triggerBlock, UI_JOBS, uiRun, withoutComments } from './helpers/root-ci';
2324

2425
/**
@@ -447,9 +448,14 @@ describe("the root ci.yml's Test UI job wires the selector fail-open (objectui#1
447448
});
448449

449450
it('keeps the matrix width and the full-suite command for the full battery', () => {
450-
const run = job.slice(job.indexOf('- name: Run the ui tests (shard ${{ matrix.shard }}/16)'));
451-
expect(job).toContain('shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]');
452-
expect(run).toContain(`${uiRun('test')} --shard=` + '${{ matrix.shard }}/16');
453-
expect(run).toContain('pnpm --dir ui exec vitest run --shard=${{ matrix.shard }}/16 --reporter=default --reporter=github-actions "${filters[@]}"');
451+
// The width is the job's own `shard:` list, read by the monorepo's one reader of
452+
// shard sizes (it refuses any other spelling); every `/N` the job's run step
453+
// spells must be that width.
454+
const shards = shardTotal(rootCi(), UI_JOBS.test);
455+
const header = `- name: Run the ui tests (shard \${{ matrix.shard }}/${shards})`;
456+
expect(job).toContain(header);
457+
const run = job.slice(job.indexOf(header));
458+
expect(run).toContain(`${uiRun('test')} --shard=\${{ matrix.shard }}/${shards}`);
459+
expect(run).toContain(`pnpm --dir ui exec vitest run --shard=\${{ matrix.shard }}/${shards} --reporter=default --reporter=github-actions "\${filters[@]}"`);
454460
});
455461
});

0 commit comments

Comments
 (0)