Repository navigation
Commit 87027c5
test(cli): budget the os dev / os serve / package spawn files, drop the tsx launcher from two (#28601)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XHtyVkHSaxV7iEzXvwKNU1u)_
Part of #23181 (slice 25, the `os dev` / `os serve` / package family).
The card stays open for the other slices.
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:`packages/cli/test` 里 6 个 `os dev` / `os serve` /
包相关的集成测试文件,各加一行第 2 行 `// test-budget:` 头(80 / 50 / 40 / 40 / 30 / 30
s);其中 2 个文件(package-restart-acceptance、package-owned-command-readers)把
spawn 方式从 `node_modules/.bin/tsx bin/run-dev.js` 改成 `process.execPath
bin/run-dev.js`,同一个入口、同一份 `src/`,每次 spawn 少起一个 node 进程,本机 A/B 各快约 34%。
- **为什么改**:卡 #23181 的测试提速职责:超过 10 s 的测试文件要么提速,要么按裁决 6181973363 B 声明预算。这
6 个文件里每次 boot/spawn 都是该文件要钉住的主体(重启、多次 boot
抓竞态、每条腿一个输入),没有重复的(命令、参数、夹具)三元组可合并,所以地板是真实工作,走 budget 头。
- **风险与回滚**:只改测试文件,不发布任何东西(cli 的 `files[]` 只有
dist、README、CHANGELOG);断言一条没删、没放宽,超时、层、项目都没动;回滚即 revert。
-
**先看**:`packages/cli/test/package-restart-acceptance.integration.test.ts`(spawn
改为
`process.execPath`);`packages/cli/test/package-owned-command-readers.test.ts`(同上);其余
4 个文件只加了头。
- **两分钟验证**:`pnpm --filter @objectstack/cli exec vitest run --project
integration test/package-owned-command-readers.test.ts` → 8/8 通过。
- **需要你做的**:无,席位落地。
## What changes
Slice 23's offender table
(`pm-orders/server/evidence/23181-s23/offenders-after-s23.md` in the
project files) reads these six files over 10 s with no `// test-budget:`
header. Readings are the default reporter's per-file module line in the
Test Core shard logs of the five scheduled `ci.yml` runs `41199450735`,
`41217827347`, `41237094739`, `41255489006`, `41271604267`:
| file | 0735 | 7347 | 4739 | 9006 | 4267 | header |
|:--|--:|--:|--:|--:|--:|--:|
| `dev-boot-output-order.integration.test.ts` | 59.0 | **59.4** | 41.9 |
53.6 | 47.2 | 80 |
| `package-restart-acceptance.integration.test.ts` | 19.7 | 27.2 | 21.9
| 34.4 | **35.1** | 50 |
| `package-owned-command-readers.test.ts` | 31.1 | 19.3 | 23.6 | 30.2 |
**31.3** | 40 |
| `dev-organization-ownership-stale-column.integration.test.ts` | 28.9 |
**29.0** | 18.1 | 25.2 | 21.1 | 40 |
| `dev-standalone-self-heal.integration.test.ts` | 12.5 | 16.7 | 13.5 |
**22.9** | 22.6 | 30 |
| `serve-runtime-state-project-key.test.ts` | **19.7** | 19.0 | 12.0 |
17.5 | 14.8 | 30 |
**Cause, per file.** Every file already runs its boots or spawns once,
in a `beforeAll` (or, for the runtime-state file, in the cases that read
them). I counted them against distinct (command, flags, fixture)
triples:
| file | tests | boots / spawns | distinct triples | cause | fix |
|:--|--:|:--|--:|:--|:--|
| `dev-boot-output-order` | 3 | 5 boots of the built `os dev`
(supervisor + `serve` child) under a real pty | 5 (fresh database each)
| the boots are the subject: the order defect was a race one boot can
miss; each boot also holds 2 s after the block and ends with a typed
Ctrl+C | header 80 |
| `package-restart-acceptance` | 5 | 2 boots of the source `os serve
--dev` on one database file | 2 | the restart is the subject; each spawn
went through the tsx binary, whose launcher (`tsx/dist/cli.mjs`) starts
a second node process for the same entry | `process.execPath` + header
50 |
| `package-owned-command-readers` | 8 | 6 source-CLI spawns, two alive
at once | 6 (doctor, `diff --json`, `migrate meta --json` over the
two-package config and its one-package control) | one spawn per distinct
pair; same tsx launcher cost | `process.execPath` + header 40 |
| `dev-organization-ownership-stale-column` | 5 | 2 boots of the built
`os dev` on one database file (the second runs the upgrade ceremony) | 2
| the fresh boot creates the tables the stale shape is staged on; the
second boot is the subject | header 40 |
| `dev-standalone-self-heal` | 4 | 3 boots of the built CLI on one
database file | 3 (fresh `os dev`, restart over the staged index,
production `os serve`) | each boot is a leg the file pins | header 30 |
| `serve-runtime-state-project-key` | 12 | 3 child processes publishing
the state file | 3 (project A, project B, a second boot of A) | each
child loads the real writer from `src/commands/serve.ts` through tsx,
about 4 s of module load apiece; the file's own import of `serve.ts` is
about 16 s locally. Distinct pids and a clean exit per child are the
subject | header 30 |
**The tsx launcher.** `node_modules/.bin/tsx X` runs `tsx/dist/cli.mjs`
in one node process, which then spawns a second node process for `X`
(checked: under `tsx -e`, `process.ppid` is a `node …/tsx/dist/cli.mjs`
process). `bin/run-dev.js` runs straight under `process.execPath`, as
slice 12 already does for other spawn files
(`serve-no-auth-secret-refusal`, `dev-standalone-self-heal`,
`union-fold-command-parity`): the same entry, the same `src/`, the same
`NODE_ENV` that `run-dev.js` pins. Measured without the test manifest,
`node bin/run-dev.js diff --help` took 5.1 to 5.2 s against 8.9 to 10.2
s through the tsx binary.
I tried the same move on `serve-runtime-state-project-key` (`node
--import tsx/dist/loader.mjs CHILD`) and reverted it: about 4.0 to 4.3 s
per child either way, because the cost there is loading `serve.ts`, not
the launcher. Making the child lighter would mean moving the state
writer out of `serve.ts`, a source change outside this slice.
**Headers** (ruling 6181973363 letter B, class `spawns the real producer
once per single-cause fixture`): N = the slowest of the five CI readings
× 1.25, rounded up to 10 s. The two files whose spawn changed keep their
pre-change CI reading; a shard-timings refresh resizes them. All six
parse with `readTestBudget` (`scripts/measure-test-shard-timings.mjs`):
80, 50, 40, 40, 30, 30, each at line 2.
Not changed: no assertion, case name, timeout, tier or project. No test
helper (`test/helpers/**` is untouched). No source file.
## Tests
- **A/B** (one box, one lock hold, base copied beside head as
`zzbase-*`, interleaved, N=3, `--project integration --maxWorkers=1`,
the test manifest global setup on as in CI's queue tier; vitest's
`tests` figure, and the whole `Duration` in brackets; copies deleted
before the gate run):
| file | base (s) | head (s) | change |
|:--|:--|:--|--:|
| `package-restart-acceptance` | 23.51, 23.06, 23.24 (32.98, 32.57,
32.91) | 15.19, 15.55, 15.34 (24.65, 24.98, 24.72) | −34% (mean 23.27 →
15.36) |
| `package-owned-command-readers` | 16.97, 17.18, 16.88 (29.32, 29.94,
29.59) | 11.02, 11.25, 11.46 (23.09, 24.06, 24.15) | −34% (mean 17.01 →
11.24) |
Every run exit 0; restart-acceptance 5/5 and owned-command-readers 8/8
on both sides, same names.
- **Header-only files**, run once on head in the same lock hold:
`dev-boot-output-order` 3/3 (tests 46.02 s),
`dev-organization-ownership-stale-column` 5/5 (21.79 s),
`dev-standalone-self-heal` 4/4 (17.17 s),
`serve-runtime-state-project-key` 12/12 (17.86 s, import 16.53 s).
- No ablation: no tested behaviour changed (test-only).
- `pnpm --filter @objectstack/cli typecheck` (tsc +
`check:test-typecheck`) exit 0 on `9a33975d`, with
`NODE_OPTIONS=--max-old-space-size=4096`.
## Acceptance notes
- **`skip-changeset`.** Test-only. `@objectstack/cli`'s `files[]` is
`dist`, `README.md`, `CHANGELOG.md`, and its build does not emit
`test/**` into `dist/`, so no `files[]` content changes.
- `serve-runtime-state-project-key` and `package-owned-command-readers`
sit in `vitest-tiers.ts`'s `integration` tier already (they spawn);
nothing moved.
## Verification
All readings on head `9a33975d` (my commit `eb79e981` plus one merge of
`origin/main` after slice 23, #28581, landed; that merge touches none of
these six files).
- **Derived gate families:** `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` → 48 families, each run
with its exit code captured before any pipe, all exit 0. On the first
round (before the merge) two were container prerequisites, re-read after
meeting them: `check-plugin-teardown-shape --self-test` exit 3 (shallow
clone) → `git fetch --depth=1` of its pinned fixture commit → exit 0;
`check:dual-build-cjs-loads` exit 3 (no full build) → `pnpm exec turbo
run build --concurrency=2` (118/118) → exit 0. The whole set then re-ran
on `9a33975d`. `--ran` reconciliation: `✓ dispatch-gates --ran: 48
derived famil(ies) accounted for — 48 run, 0 NOT-MEASURED (a DERIVED
zero — all 48 recorded an exit code and none of them is 3)`. List with
codes: `pm-orders/server/evidence/23181-s25/gates-ran-9a33975d.txt` in
the project files.
- **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.
- **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_01FEkQ5peeAMkJqa2CHQj7ia
Co-authored-by: Claude <noreply@anthropic.com>1 parent 519089e commit 87027c5
6 files changed
Lines changed: 13 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
35 | 36 | | |
36 | 37 | | |
37 | 38 | | |
38 | | - | |
| 39 | + | |
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
| |||
76 | 77 | | |
77 | 78 | | |
78 | 79 | | |
| 80 | + | |
| 81 | + | |
79 | 82 | | |
80 | | - | |
| 83 | + | |
81 | 84 | | |
82 | 85 | | |
83 | 86 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
63 | 64 | | |
64 | 65 | | |
65 | 66 | | |
66 | | - | |
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| |||
110 | 110 | | |
111 | 111 | | |
112 | 112 | | |
113 | | - | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
114 | 116 | | |
115 | 117 | | |
116 | 118 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
0 commit comments