发现于 ADR-0116 的收尾验证(#4131 / PR #4163、#4185)。与那些改动无关,是一个独立的测试基础设施问题。
现象
packages/plugins/plugin-audit/src/audit-writers.test.ts 里的
× localizes verb + object label to the workspace locale (zh-CN) 20094ms
- 单独跑
pnpm --filter @objectstack/plugin-audit test:4 文件 / 46 用例全过。
- 全仓
pnpm test 下必失败,我这里连续两次复现(20347ms / 20094ms),都是撞 20s 的 testTimeout,不是断言失败。
机制(已定位)
罪魁是 helper 里的动态导入:
// audit-writers.test.ts:544
async function makeI18n() {
const { createMemoryI18n } = await import('@objectstack/core');
const { AuditTranslations } = await import('./translations/index.js');
...
}
关键证据是同文件内相邻用例的耗时对比:
| 用例 |
耗时 |
localizes verb + object label …(zh-CN) ← 全文件第一个调用 makeI18n() |
20094ms(超时) |
localizes the generic update fallback ← 紧随其后,调用同一个 helper |
104ms |
falls back to the object def label, then English… |
1ms |
也就是说付出代价的只有第一次冷加载 @objectstack/core(一个 110KB+ 的 barrel,带大量传递依赖):模块解析 + 转换在 turbo 把十几个包的 vitest worker 同时压满时超过 20 秒;之后命中缓存就回到毫秒级。单独跑时机器不拥塞,冷加载够快,所以永远看不到。
为什么值得修而不是忍
这个用例目前把"全仓 pnpm test 是否绿"变成了一个取决于机器负载的问题——本地看到的红灯与代码质量无关,会持续消耗每个跑全量测试的人的注意力(我这次就为它多花了两轮排查)。而且它是"沉默的负载探测器":机器越忙越红,最容易在最需要可信信号的时候骗人。
可能的修法(未定,列给接手的人)
- 把动态导入提到模块顶层的静态 import——最直接。
createMemoryI18n 与 AuditTranslations 都没有需要延迟加载的理由(不是可选依赖、不是循环依赖规避——这点需要确认后再动)。
- 给这个文件或这个用例单独放宽
testTimeout——治标,且把负载敏感性留在原地。
- 在
beforeAll 里预热一次导入,让冷加载成本不计入任一用例的超时预算。
倾向 1,但要先确认当初写成 await import() 是不是在规避某个循环依赖(plugin-audit → core 方向上应该不存在,但值得查一下再改)。
复现
pnpm test # 全仓并行 → plugin-audit 必红
pnpm --filter @objectstack/plugin-audit test # 单独 → 46/46 绿
注意:pnpm test 2>&1 | grep ... 这类写法拿到的退出码是管道末端命令的,会把这次失败显示成"成功"——排查时请直接取 pnpm test 自身的退出码(我第一轮就是这么误判为全绿的)。
发现于 ADR-0116 的收尾验证(#4131 / PR #4163、#4185)。与那些改动无关,是一个独立的测试基础设施问题。
现象
packages/plugins/plugin-audit/src/audit-writers.test.ts里的pnpm --filter @objectstack/plugin-audit test:4 文件 / 46 用例全过。pnpm test下必失败,我这里连续两次复现(20347ms / 20094ms),都是撞 20s 的testTimeout,不是断言失败。机制(已定位)
罪魁是 helper 里的动态导入:
关键证据是同文件内相邻用例的耗时对比:
localizes verb + object label …(zh-CN)← 全文件第一个调用makeI18n()localizes the generic update fallback← 紧随其后,调用同一个 helperfalls back to the object def label, then English…也就是说付出代价的只有第一次冷加载
@objectstack/core(一个 110KB+ 的 barrel,带大量传递依赖):模块解析 + 转换在 turbo 把十几个包的 vitest worker 同时压满时超过 20 秒;之后命中缓存就回到毫秒级。单独跑时机器不拥塞,冷加载够快,所以永远看不到。为什么值得修而不是忍
这个用例目前把"全仓
pnpm test是否绿"变成了一个取决于机器负载的问题——本地看到的红灯与代码质量无关,会持续消耗每个跑全量测试的人的注意力(我这次就为它多花了两轮排查)。而且它是"沉默的负载探测器":机器越忙越红,最容易在最需要可信信号的时候骗人。可能的修法(未定,列给接手的人)
createMemoryI18n与AuditTranslations都没有需要延迟加载的理由(不是可选依赖、不是循环依赖规避——这点需要确认后再动)。testTimeout——治标,且把负载敏感性留在原地。beforeAll里预热一次导入,让冷加载成本不计入任一用例的超时预算。倾向 1,但要先确认当初写成
await import()是不是在规避某个循环依赖(plugin-audit→core方向上应该不存在,但值得查一下再改)。复现
注意:
pnpm test 2>&1 | grep ...这类写法拿到的退出码是管道末端命令的,会把这次失败显示成"成功"——排查时请直接取pnpm test自身的退出码(我第一轮就是这么误判为全绿的)。