Repository navigation
Commit 95df345
ci(test-ui): attribute a disk-reading test to the packages its fs reads name (#31567)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} -->
_Requested by **Steedos** · [project
thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9Ubb77KgkSa7rZ7QmbAP6hZdVB)_
Part-of #31520 (slice 2 of 3 planned; the card closes only when the
empty-diff count is under 200 and the three-PR shard reading is on it)
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:Test UI
的选测脚本现在能读懂测试里用字面路径写出的磁盘读取,把这类测试归到它所读的包上,只在那些包被改动时才跑;路径读不清的测试仍然每次必跑。空改动下的必跑集从
579 个文件降到 519 个。
- 为什么改:394 个「读磁盘」的测试此前一律每次必跑,其中很多只读自己包里的文件;路径解析器已在
`scripts/check-cross-package-test-inputs.mjs` 里,本 PR 直接导入它,没有复制。
- 风险与回滚:风险是某个测试读了一个包,但它变了时没被选中。规则是「读不清就必跑」,另用 34
个单包改动逐一比对旧新两版:没有新增选中;1,284 处去掉的选中都对照了导入闭包和运行时实际读取的文件。回滚即还原这三个文件。
- 先看:`ui/scripts/select-affected-tests.mjs` 里的
`readRadius`(什么情况下仍判为必跑),然后是外层门禁里新增的 `reads` 字段和一条 self-test。
- 两分钟验证:`cd ui && node scripts/select-affected-tests.mjs --base
origin/main --explain`,预期 `always-on test files ...: 519`;`node
scripts/check-cross-package-test-inputs.mjs --self-test`,预期 262 条全过。
- 需要你做的:无,席位落地。
## What changes
Item 2b of the selector's header, **bounded disk readers**. Up to now, a
test whose import closure touched `fs` was always-on. Now the selector
asks the outer gate's path walker, `scanPathExpressions` in
`scripts/check-cross-package-test-inputs.mjs`, where each read in that
file points. If every read names a literal path inside a workspace
package, those packages join the test's input packages from slice 1, and
the test is selected only when one of them is affected.
The rule is strict and fail-closed. `readRadius` returns null, which
keeps the test always-on, when any of these hold:
- the tree is standalone (no outer walker), or the file has no owning
package;
- the file uses `import.meta.glob`, `process.cwd(`, `createRequire`,
`require.resolve` or `require('fs')`;
- it imports a disk module other than `fs`/`node:fs`, or a library that
reads the disk itself (`typescript`, `ts-morph`, `eslint`, `vite`,
`esbuild`, `rollup`, `tailwindcss`, `postcss`);
- an `fs` import is aliased or is not a known read or write;
- a default or namespace `fs` binding is used other than as a member
call;
- the walker sees a walked root;
- the number of spelled read calls differs from the reads the walker
resolved;
- any read is `globSync`, unresolved, vendored, or lands outside every
package.
The selector imports the walker; it does not copy it. Slice 2's
amendment allows one additive edit in that file, and this PR makes
exactly that edit: `scanPathExpressions` is exported, its result gains a
`reads` field (each call with its resolved path, or null), and one
`--self-test` case pins it. `RECOGNISED_PATH_SPELLINGS` and the
AGENTS.md mirror are untouched.
Empty-diff count, `cd ui && node scripts/select-affected-tests.mjs
--base origin/main --explain`:
| head | always-on | selected of tracked |
|---|---:|---:|
| `f923bc1e` (main after slice 1, branch base) | 579 | 579 of 6,658 |
| `9544e4e9` (this PR, `origin/main` `6f9f0086` merged in) | 519 | 519
of 6,658 |
`origin/main` has no change under `ui/scripts/` or in the walker file
since `f923bc1e`, so main's count is still 579. Of the 60 freed tests,
45 read only their own package and 15 read another package by a named
path. The plan estimated about 510. Nine tests that the walker could
bound stay always-on because they import `typescript` or `vite`, which
read files the walker cannot see (see Tests).
## Why
This is slice 2 of the plan the `domain:ui#3` seat approved on #31520:
class (b) of the card's classification, disk readers whose reads resolve
inside named packages. It ships in the strict, fail-closed variant the
seat chose; the lenient one was declined. Slice 3 (`scripts/` and
`eslint-rules/` as owner directories) follows as a separate PR.
## Tests
In `ui/scripts/__tests__/select-affected-tests.test.ts`, the fixture
gains six disk-reading tests in `packages/c/src/`, built by a new
`reader()` helper.
- Positive controls. A test that reads `fixtures/x.json` in its own
package is no longer always-on, and a change in its package selects it.
A test that reads `a/src/index.ts` by a named path is selected through
`@fx/a` when `@fx/a` changes.
- Negative controls:
- the own-package reader is not selected by an `@fx/a` change;
- the cross-package reader is not selected by an `@fx/registry` change;
- readers that use `process.cwd()`, a path held in a loop variable, an
aliased `readFileSync as rf`, or `typescript` stay always-on with "reads
the disk or spawns a process";
- with `walker: null` (the standalone tree), both bounded readers stay
always-on.
- Seat condition 2, the walker control: on the live tree, a change to
`scripts/check-cross-package-test-inputs.mjs` (arriving as `outside`)
selects the same mode and the same filters as a change to
`ui/scripts/select-affected-tests.mjs`, and those filters include the
selector's own test file. Today both runs give 519 filters, because
`scripts/__tests__` is always-on. Once slice 3 makes `scripts/` an owner
directory, this control is what keeps an edit to the walker from going
untested.
- The outer gate's new self-test case scans `readFileSync(join(HERE,
'..', 'index.ts'))` and `readFileSync(someVariable)`. It pins two reads:
the first resolved to `packages/create-objectstack/index.ts`, the second
null.
<details><summary>Ablation: the radius disabled, the positive controls
red</summary>
Run through `node scripts/ablation-replace.mjs --file
ui/scripts/select-affected-tests.mjs --anchor ' if (!walker) return
null;\n' --replacement ' return null;\n'`, so `readRadius` refuses every
file. The anchor went from 1 to 0 and the blob from `65a5eea7` to
`d2024e6e`. `pnpm --dir ui exec vitest run
scripts/__tests__/select-affected-tests.test.ts`: 2 failed, 48 passed.
The failures were the two positive controls, "frees a test whose reads
stay in its own package, and selects it with that package" and "frees a
test that reads another package by a named path, and selects it when
that package changes". After the restore the blob was `65a5eea7` again,
equal to HEAD, and `git diff HEAD` was empty.
</details>
<details><summary>Exactness sweep: no test loses a selection it
needs</summary>
For each workspace package with a tracked `src/` file, the selector at
`f923bc1e` and this one were each run on a one-file change in that
package. Across the 34 affected-mode runs (core, i18n, test-support and
types are full-run triggers under both versions), 1,284 test-file
selections were dropped and 0 were added. All the drops belong to the 60
freed tests.
Each dropped (test, change) pair was checked three ways: the test's
relative closure has no file in an affected package's directory, it
imports no affected package by name, and its runtime reads hit no
affected directory. The runtime reads came from a separate pass that ran
the freed tests under an `fs` tracer, attributing each read to its test
file. All 60 freed tests are in that trace.
The check flagged one test, 20 times:
`packages/components/src/__tests__/chart-primitives-removed-7397.test.ts`.
The flag was a false match. Its closure includes
`packages/components/src/index.css`, and a comment there names
`@object-ui/app-shell/styles.css`; the checker parsed that comment as an
import. CSS is not script, and the selector does not parse it.
The runtime trace also caught a real miss during development:
`packages/types/src/__tests__/anyschema-declared-node-types-11478.test.ts`
reads the root `tsconfig.json` and spec through the `typescript`
program, where the walker cannot see. That led to the library rule. It
keeps that test and eight others like it always-on, which is why the
count is 519 and not 510.
</details>
## Acceptance notes
- Seat condition 1: `node scripts/check-cross-package-test-inputs.mjs`
exited 0 before and after, with the same verdict line both times ("OK:
32 package(s) read outside themselves, all declared … 4590 test file(s)
import a workspace sibling by bare specifier over 474 package pair(s),
every one declared."). Its `--self-test` exited 0 before (261 cases) and
after (262 cases).
- The shard count does not move here either. Test UI's matrix is still
`test-ui 16 literal`, and the seat has taken that question to the
maintainer. It would be a `ci.yml` change, which is out of scope.
- After this slice, 334 of the 519 always-on files are disk readers the
strict rule cannot bound, and 183 are outside every package. Slice 3
targets about 80 of the 183. Under 200 still needs the further decision
recorded on the card.
## Verification
Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `9544e4e9`: 43 families. All 43 were run
locally on that head and reconciled with `--ran`, which reported "43
derived, 43 run, 0 NOT-MEASURED, 0 UNRUN", with every exit code
recorded. `node scripts/check-sdui-manifest.mjs` was also run, as the ui
seat requires on every card. So were the five roster gates the
derivation marks as keeping their roster under `scripts/`.
<details><summary>Gate exit codes at 9544e4e</summary>
```text
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs :: exit 0
node scripts/check-declaration-mirrors.mjs --self-test :: exit 0
node scripts/check-published-list-mirrors.mjs :: exit 0
node scripts/check-published-list-mirrors.mjs --self-test :: exit 0
node scripts/check-self-test-wired.mjs :: exit 0
node scripts/check-self-test-wired.mjs --self-test :: exit 0
node scripts/check-self-test-workflow-commands.mjs :: exit 0
node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0
node scripts/check-whole-set-label-write.mjs :: exit 0
node scripts/check-whole-set-label-write.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:pm-dispatch-gates :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:pnpm-flag-spellings :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:scripts-symbol-anchors :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:i18n-stale-fill :: exit 0
pnpm check:published-readme-exports :: exit 0
node scripts/check-dts-references.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
```
</details>
- `pnpm --dir ui exec vitest run scripts/` (under the verify lock): 170
test files passed and 2 were skipped, the skips being existing
conditional skips. 5,008 tests passed.
- No changeset: `ui/` is the private `object-ui-monorepo` root,
`ui/scripts/` ships in no package, and the repo-root `scripts/` file is
unpublished, so this PR carries `skip-changeset`.
- CI runs the rest: the PR's required check-runs.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QsR2jREy1UXbgGdQRBvZji)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c0fb794 commit 95df345
3 files changed
Lines changed: 306 additions & 17 deletions
File tree
- scripts
- ui/scripts
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1244 | 1244 | | |
1245 | 1245 | | |
1246 | 1246 | | |
1247 | | - | |
| 1247 | + | |
1248 | 1248 | | |
1249 | 1249 | | |
| 1250 | + | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
1250 | 1257 | | |
1251 | 1258 | | |
1252 | 1259 | | |
| |||
1308 | 1315 | | |
1309 | 1316 | | |
1310 | 1317 | | |
| 1318 | + | |
1311 | 1319 | | |
1312 | 1320 | | |
1313 | 1321 | | |
| |||
1335 | 1343 | | |
1336 | 1344 | | |
1337 | 1345 | | |
1338 | | - | |
| 1346 | + | |
1339 | 1347 | | |
1340 | 1348 | | |
1341 | 1349 | | |
| |||
3341 | 3349 | | |
3342 | 3350 | | |
3343 | 3351 | | |
| 3352 | + | |
| 3353 | + | |
| 3354 | + | |
| 3355 | + | |
| 3356 | + | |
| 3357 | + | |
| 3358 | + | |
| 3359 | + | |
| 3360 | + | |
| 3361 | + | |
| 3362 | + | |
| 3363 | + | |
| 3364 | + | |
| 3365 | + | |
| 3366 | + | |
| 3367 | + | |
| 3368 | + | |
| 3369 | + | |
| 3370 | + | |
| 3371 | + | |
| 3372 | + | |
| 3373 | + | |
| 3374 | + | |
| 3375 | + | |
| 3376 | + | |
| 3377 | + | |
| 3378 | + | |
| 3379 | + | |
| 3380 | + | |
| 3381 | + | |
| 3382 | + | |
| 3383 | + | |
| 3384 | + | |
| 3385 | + | |
| 3386 | + | |
| 3387 | + | |
| 3388 | + | |
| 3389 | + | |
| 3390 | + | |
| 3391 | + | |
| 3392 | + | |
| 3393 | + | |
| 3394 | + | |
| 3395 | + | |
| 3396 | + | |
| 3397 | + | |
| 3398 | + | |
| 3399 | + | |
| 3400 | + | |
| 3401 | + | |
| 3402 | + | |
| 3403 | + | |
| 3404 | + | |
| 3405 | + | |
| 3406 | + | |
| 3407 | + | |
| 3408 | + | |
3344 | 3409 | | |
3345 | 3410 | | |
3346 | 3411 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
| 16 | + | |
15 | 17 | | |
16 | 18 | | |
17 | 19 | | |
| |||
48 | 50 | | |
49 | 51 | | |
50 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
51 | 62 | | |
52 | 63 | | |
53 | 64 | | |
| |||
107 | 118 | | |
108 | 119 | | |
109 | 120 | | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
110 | 131 | | |
111 | 132 | | |
112 | 133 | | |
| |||
220 | 241 | | |
221 | 242 | | |
222 | 243 | | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
223 | 294 | | |
224 | 295 | | |
225 | 296 | | |
| |||
435 | 506 | | |
436 | 507 | | |
437 | 508 | | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
438 | 523 | | |
439 | 524 | | |
440 | 525 | | |
| |||
0 commit comments