Skip to content

Commit de3fafc

Browse files
test(cli): budget the six remaining cli test files from 13 s up; spawn the source entry with node in three (#28616)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XE1ashwufQRjetDvaJEkX2H)_ Part of #23181 (slice 28). The card stays open for later slices: this one takes only the six remaining `packages/cli` files at 13 s and over in slice 23's table. Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:`packages/cli` 里 CI 读数 13 s 以上、还没有预算头的 6 个测试文件,每个在第 2 行加一行 `// test-budget:` 头(metadata-type-schema-gate 30 s,其余 5 个 20 s)。其中 3 个文件原来经 `node_modules/.bin/tsx` 包装层起 CLI 源码入口,现在改成直接用 `process.execPath` 起同一个 `bin/run-dev.js`,与 metadata-type-schema-gate 已有写法一致。 - **为什么改**:卡 #23181 的测试提速职责:超过 10 s 的测试文件要么提速,要么按裁决 6181973363 B 声明预算。逐个文件数过:每次 spawn 都是不同的(命令、参数、夹具)组合,没有可合并的重复;schema-migration-plugins 没有 spawn,地板是冷导入 `@objectstack/runtime`(本地约 9.9 s)。所以都走预算头,能省的只有 tsx 包装层。 - **风险与回滚**:只改测试文件,不发布任何东西(cli 的 `files[]` 只有 dist、README、CHANGELOG);没有删、没有放宽任何断言,用例名和数量不变;回滚即 revert。 - **先看**:`packages/cli/test/validate-lint-mapping-connector-source.test.ts` 与 `packages/cli/test/info-detail-package-fold.test.ts`(tsx → `process.execPath`);`packages/cli/test/emit-json-pipe.test.ts`(只改了末尾那一次 CLI spawn,四个 harness 和对照仍走 tsx)。 - **两分钟验证**:`pnpm --filter @objectstack/cli exec vitest run test/validate-lint-mapping-connector-source.test.ts test/info-detail-package-fold.test.ts` → 10/10 通过。 - **需要你做的**:无,席位落地。 ## What changes The six files and their slowest reading over the five scheduled `ci.yml` runs `41199450735`, `41217827347`, `41237094739`, `41255489006`, `41271604267` (slice 23's offender table, default reporter's per-file module line in the Test Core shard logs): | file | CI max | tests | spawns / boots | distinct triples | cause | fix | |:--|--:|--:|:--|--:|:--|:--| | `test/metadata-type-schema-gate.test.ts` | 16.1 s (41217827347) | 8 | 4 source-CLI spawns, already on `process.execPath` | 4 | one spawn per (command, fixture): `os validate` on the control, planted-key and `lg` stacks, `os build` on the planted key (its no-artifact claim). About 2.6 s each locally; the in-process cases take under 0.2 s together | header 30 | | `src/utils/schema-migration-plugins.test.ts` | 15.9 s (41271604267) | 52 | no spawn, no boot | n/a | a cold import: the composition imports `@objectstack/runtime` for the AppPlugin it composes, as `os serve` does. A probe timed it at 9.86 s under this package's vitest config, plus 3.94 s for the module under test, 1.21 s for plugin-auth (aliased to its src) and 0.45 s for service-automation. Each lands on whichever case triggers it first (the "host config LOADS" case reads 9.9 s); no case repeats it | header 20 | | `test/validate-lint-mapping-connector-source.test.ts` | 15.7 s (41217827347) | 3 | 3 source-CLI spawns through the `tsx` wrapper | 3 | `os validate --json` and `os lint --json` on the card stack, `os lint --json` on the control stack. The wrapper adds about 1.3 s per spawn | spawn with `process.execPath`; header 20 | | `test/scaffold-test-script-passes.test.ts` | 14.9 s (41217827347) | 9 | 8 spawns of the emitted projects' own scripts | 8 | five vitest `test` runs (init plugin, its empty-suite control, create plugin fresh, after `build`, its empty-suite control) and three tsc runs (`build`, `typecheck` clean, `typecheck` with the planted error). Each runs on its own fixture state | header 20 | | `test/info-detail-package-fold.test.ts` | 14.8 s (41217827347) | 7 | 6 source-CLI spawns through the `tsx` wrapper, already in one `beforeAll` | 6 | `os info --json` and `os info` on the option-B, top-level and two-package projects | spawn with `process.execPath`; header 20 | | `test/emit-json-pipe.test.ts` | 13.5 s (41271604267) | 7 | 6 children: 4 tsx harnesses, the tsx `console.log` control, 1 source-CLI `os validate --json` through the wrapper | 6 | each harness writes through `emitJson` and ends a different way; the in-process `isExitSignal` case pays the cold import of `format.ts` and the spec barrel (about 2.3 s) | the CLI spawn uses `process.execPath`; header 20 | **Why the floor is distinct real work.** No file repeats a (command, flags, fixture) triple, and the two that spawn the most already share their setup (`info-detail` in one `beforeAll`; `scaffold` materialises each fixture once per case, from in-process renderers). Nothing reads a file through a child that could become an in-process read. So every file takes the ruling 6181973363 letter B header: N = the slowest CI reading × 1.25, rounded up to 10 s (16.1 → 30; 15.9, 15.7, 14.9, 14.8, 13.5 → 20). Class `spawns the real producer once per single-cause fixture` for the five spawning files, `whole-package import` for `schema-migration-plugins` (the precedent is the lint headers that name a cold barrel import as that class). All six parse with `readTestBudget` (`scripts/measure-test-shard-timings.mjs`): line 2, the class and seconds above. **The one lever inside the floor.** Three files spawned `bin/run-dev.js` through `node_modules/.bin/tsx`. `metadata-type-schema-gate`, `authoring-rule-command-parity` and `format-zod-union` already run the same entry with `process.execPath`: same entry file, same `src/`, same `childEnv()`, same arguments. The three now do the same. `emit-json-pipe`'s four harnesses and its control keep `tsx`: they import `src/utils/format.ts` directly and need its loader, and the pipe they pin is the one the tsx child writes to. Not changed: no assertion, case name, case count, timeout, tier, project or helper. No source file. ## Tests - **A/B** for the three files whose code changed. Base copied beside head as `zzbase.*` (`git show c658320:…`), one box, one lock hold, N=3. Each side ran in its own vitest invocation (the three files together, `--maxWorkers=1`) so neither side inherits the other's transform cache, and the order alternated (head first, base first, head first). Reading: the default reporter's per-file line. Copies deleted before the gate run. | file | base (s) | head (s) | change | |:--|:--|:--|--:| | `validate-lint-mapping-connector-source` | 11.44, 12.11, 11.09 | 8.47, 8.53, 8.61 | −26.1% (mean 11.55 → 8.54) | | `info-detail-package-fold` | 10.73, 10.96, 10.69 | 7.75, 8.08, 7.99 | −26.4% (mean 10.79 → 7.94) | | `emit-json-pipe` | 12.72, 13.36, 12.76 | 11.94, 13.23, 12.11 | −4.0% (mean 12.95 → 12.43), within the run-to-run spread | Every run exit 0, 17/17 tests per side, same names. `emit-json-pipe` moved one spawn of six, and it runs first in its invocation, so it pays the cold transform of the spec graph; its header carries it. - **All six on head**, one run with `test/vitest-tiers-partition.test.ts`: 7 files, 108/108 passed. Case counts are the same as the base run (8, 52, 3, 9, 7, 7), and the tier partition pin is green, so the three edited files stay in `integration`. - **Header-only files** (`metadata-type-schema-gate`, `schema-migration-plugins`, `scaffold-test-script-passes`): no A/B, per the order. - `pnpm --filter @objectstack/cli typecheck` (tsc and `check:test-typecheck`) exit 0, with `NODE_OPTIONS=--max-old-space-size=4096`. - No ablation: no tested behaviour changed (test-only). ## Acceptance notes - **`schema-migration-plugins`' 9.9 s is a config cost, not a test cost.** `@objectstack/runtime` resolves to its `dist/`, but this package's `vitest.config.ts` aliases several packages that the runtime graph reaches to their `src/`, so vite transforms them on the first import. The lever is that shared config, which every cli file reads. It is outside this slice's file surface; noted, not filed. Carrier: none. - **`skip-changeset`.** Test-only. `@objectstack/cli`'s `files[]` is `dist`, `README.md`, `CHANGELOG.md`; its build emits no `*.test.ts` into `dist/`. No `files[]` content changes. ## Verification All readings on head `2af0e899`: the fix commit `ea81aa2f` plus one merge of `origin/main` after slice 23 (#28581) landed, as the order asks. The A/B and the six-file run above were taken on `ea81aa2f`; the merge touches none of the six files. - **Derived gate families:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` → 52 families, each run with its exit code captured before any pipe, after a full `pnpm exec turbo run build --concurrency=2` (118 tasks, exit 0). All 52 exit 0. `--ran` reconciliation: `✓ dispatch-gates --ran: 52 derived famil(ies) accounted for — 52 run, 0 NOT-MEASURED`. On the pre-merge head, before the full build, `check:dual-build-cjs-loads` and `check:i18n-coverage` read exit 3 (no full build, a container prerequisite) and the other 50 exit 0. - **cli unit tier** (`pnpm --filter @objectstack/cli exec vitest run --project unit`): 362/362 files, 5155/5155 tests. - **Hand gates:** `pnpm exec eslint --no-inline-config` over the six files exit 0 (`@objectstack/cli` has no `lint` script; the repo-wide `pnpm lint` is CI's); `node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD` exit 0; `node scripts/check-nul-bytes.mjs` exit 0. - **CI** families (Test Core with the cli integration tier, lint farm, type-check lanes) are read off this PR's check-runs by the seat. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01FEkQ5pfFsY9QCnzGXyBnPP Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0c03ce2 commit de3fafc

6 files changed

Lines changed: 20 additions & 8 deletions

‎packages/cli/src/utils/schema-migration-plugins.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
// test-budget: 20 — whole-package import: the composition under test imports `@objectstack/runtime` cold for the AppPlugin it composes, as `os serve` does, and that one import is about 9.9 s of the file locally (plugin-auth's src and service-automation follow on their own triggers); every case is in process, no spawn; slowest CI reading 15.9 s (run 41271604267) of the five scheduled ci.yml runs 41199450735 to 41271604267 (slice 23's offender table), x 1.25, rounded up
23

34
import { describe, it, expect, afterEach, beforeEach } from 'vitest';
45
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';

‎packages/cli/test/emit-json-pipe.test.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
// test-budget: 20 — spawns the real producer once per single-cause fixture: six children, one per shape — four tsx harnesses that each write through emitJson and end a different way, the console.log control, and one source-CLI `os validate --json` over the 900-issue config — plus the in-process import of format.ts and the spec barrel it reads; slowest CI reading 13.5 s (run 41271604267) of the five scheduled ci.yml runs 41199450735 to 41271604267 (slice 23's offender table), x 1.25, rounded up
23

34
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
45
import { execFile, spawn } from 'node:child_process';
@@ -201,7 +202,9 @@ describe('os validate --json over a pipe', () => {
201202
const { stdout, code } = await (async () => {
202203
try {
203204
const { stdout } = await execFileP(
204-
TSX,
205+
// The source entry, spawned with this node binary rather than
206+
// through the `tsx` wrapper the harnesses above need.
207+
process.execPath,
205208
[resolve(REPO_CLI, 'bin/run-dev.js'), 'validate', join(configDir, 'objectstack.config.ts'), '--json'],
206209
{ maxBuffer: 64 * 1024 * 1024, cwd: REPO_CLI, env: childEnv() },
207210
);

‎packages/cli/test/info-detail-package-fold.test.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
// test-budget: 20 — spawns the real producer once per single-cause fixture: six source-CLI spawns in one beforeAll, one per distinct (project, face) pair (`os info --json` and `os info` on the option-B, top-level and two-package projects); slowest CI reading 14.8 s (run 41217827347) of the five scheduled ci.yml runs 41199450735 to 41271604267 (slice 23's offender table), x 1.25, rounded up
23

34
/**
45
* #17790 — `os info`'s four DETAIL reads went through the top level alone, so
@@ -64,9 +65,12 @@ import { fileURLToPath } from 'node:url';
6465
import { childEnv } from './helpers/serve-process.js';
6566

6667
const HERE = resolve(fileURLToPath(import.meta.url), '..');
67-
/** The SOURCE entry — this suite must not depend on `packages/cli/dist`. */
68+
/**
69+
* The SOURCE entry — this suite must not depend on `packages/cli/dist`. Spawned
70+
* with this node binary rather than through the `tsx` wrapper, as
71+
* `metadata-type-schema-gate.test.ts` does: same entry, same `src/`.
72+
*/
6873
const CLI = resolve(HERE, '../bin/run-dev.js');
69-
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
7074

7175
const MANIFEST = {
7276
id: 'com.example.ob',
@@ -172,7 +176,7 @@ interface Run {
172176
function runInfo(cwd: string, argv: readonly string[]): Promise<Run> {
173177
return new Promise((resolvePromise) => {
174178
execFile(
175-
TSX,
179+
process.execPath,
176180
[CLI, 'info', ...argv],
177181
{ cwd, maxBuffer: 32 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) },
178182
(err, stdout, stderr) => {

‎packages/cli/test/metadata-type-schema-gate.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
// test-budget: 30 — spawns the real producer once per single-cause fixture: four source-CLI spawns, one per distinct (command, fixture) pair (`os validate` on the control, the planted key and the `lg` breakpoint stacks; `os build` on the planted key, for its no-artifact claim); slowest CI reading 16.1 s (run 41217827347) of the five scheduled ci.yml runs 41199450735 to 41271604267 (slice 23's offender table), x 1.25, rounded up
23

34
/**
45
* Does the CLI hold metadata to the SAME schema the write path does? (#5000)

‎packages/cli/test/scaffold-test-script-passes.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
// test-budget: 20 — spawns the real producer once per single-cause fixture: eight spawns of the emitted projects' own scripts, each on its own fixture state (five `test` runs through vitest: init plugin, its empty-suite control, create plugin fresh, after `build`, and its empty-suite control; three tsc runs: `build`, `typecheck` clean and `typecheck` with the planted error); slowest CI reading 14.9 s (run 41217827347) of the five scheduled ci.yml runs 41199450735 to 41271604267 (slice 23's offender table), x 1.25, rounded up
23

34
/**
45
* PIN (#25175) — an `os init` template that declares a `test` script ships a

‎packages/cli/test/validate-lint-mapping-connector-source.test.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
// test-budget: 20 — spawns the real producer once per single-cause fixture: three source-CLI spawns, one per distinct (command, fixture) pair (`os validate --json` and `os lint --json` on the card stack, `os lint --json` on the control stack); slowest CI reading 15.7 s (run 41217827347) of the five scheduled ci.yml runs 41199450735 to 41271604267 (slice 23's offender table), x 1.25, rounded up
23

34
/**
45
* [#21127] `os validate` and `os lint` judge a stack whose mapping authors
@@ -22,7 +23,9 @@
2223
* is advisory. (The control was `object.externalSharingModel`, `planned` +
2324
* `authorWarn`, until #23268 retired that key; the parse now refuses it.)
2425
*
25-
* The CLI runs through `bin/run-dev.js` (source, via tsx); `@objectstack/lint`
26+
* The CLI runs through `bin/run-dev.js` (the source entry, spawned with this
27+
* node binary rather than through the `tsx` wrapper, as
28+
* `metadata-type-schema-gate.test.ts` does); `@objectstack/lint`
2629
* resolves through `exports` to `dist/`, and both read the ledger JSON the spec
2730
* package ships under `liveness/`.
2831
*/
@@ -38,8 +41,7 @@ import { defineStackSource, linkSpec } from './helpers/define-stack-fixture.js';
3841

3942
const HERE = resolve(fileURLToPath(import.meta.url), '..');
4043
const CLI = resolve(HERE, '../bin/run-dev.js');
41-
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
42-
/** A cold `tsx` spawn of the CLI source entry runs well past vitest's 5 s default. */
44+
/** A cold spawn of the CLI source entry runs well past vitest's 5 s default. */
4345
const SPAWN_TIMEOUT_MS = 120_000;
4446

4547
/** The lint's ledger-integrity error — the whole answer both doors gave before the fix. */
@@ -54,7 +56,7 @@ interface Run {
5456
function runCli(args: string[], cwd: string): Promise<Run> {
5557
return new Promise((resolvePromise) => {
5658
execFile(
57-
TSX,
59+
process.execPath,
5860
[CLI, ...args],
5961
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1' }) },
6062
(err, stdout, stderr) => {

0 commit comments

Comments
 (0)