From ad90634a18f327cbb8b6ffb03d3b8fa9f490e3f8 Mon Sep 17 00:00:00 2001 From: jkooe <62494645+jkooe@users.noreply.github.com> Date: Mon, 12 Oct 2026 03:17:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(test):=20=E5=85=9C=E5=BA=95=E5=AE=88?= =?UTF-8?q?=E5=8D=AB=E7=9A=84=E7=89=87=E6=AE=B5=E6=8E=92=E9=99=A4=E5=9C=A8?= =?UTF-8?q?=20Windows=20=E4=B8=8A=E5=A4=B1=E6=95=88=EF=BC=88=E8=B7=A8?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E8=B7=AF=E5=BE=84=E5=88=86=E9=9A=94=E7=AC=A6?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI(Windows E2E,run 38166982975)在「单元测试」步骤红: not ok 102 - 兜底:capabilities 下命中坐标信号的新文件必须引用 PS_DPI_AWARE - apps\agent\src\capabilities\win-ps\prelude-image.ts(命中 "CopyFromScreen") 而本机 macOS 全绿 —— 又是一例「本地全绿 ≠ Windows 绿」。 根因:`rel = relative(ROOT, full)` 在 Windows 返回 `apps\agent\…`,而排除规则写的是 `rel.includes('capabilities/win-ps/')`(**硬编码 "/")→ 在 Windows 上恒为 false → 片段文件被误报成违规。排除本身是**对的**(prelude-* / body-* 由 WIN_HELPER_PRELUDE 在最前面统一注入 DPI 感知,其序位已由第 5 条断言),错的只是判断依赖了平台分隔符。 修法: - 新增 `toPosix()`:把任意平台路径归一为 posix 形态(附注释说明为何必须显式归一); - 把「是否排除」收敛成纯函数 `isWinPsFragment(relPath, name)`,**函数内自带归一化** —— 调用方传什么形态都正确,且可在本地直接断言; - `walk` 内改用归一化后的 `rel` 并调用该函数。 新增守卫(第 9 条):直接喂 Windows 反斜杠路径断言排除逻辑,把这类差异钉进 「本地可验」的范围。已用 `path.win32.relative()` 在本地**真实复现** CI 场景 (修复前 false → 误报;修复后 true → 正确排除),并注入故障实测该守卫会红。 测试:dpi 守卫 9/9;pnpm test:unit 291/291。 --- tests/unit/dpi-awareness.test.mjs | 54 +++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/tests/unit/dpi-awareness.test.mjs b/tests/unit/dpi-awareness.test.mjs index 85bb318..af8b455 100644 --- a/tests/unit/dpi-awareness.test.mjs +++ b/tests/unit/dpi-awareness.test.mjs @@ -34,6 +34,25 @@ import { PS_DPI_AWARE } from '../../apps/agent/dist/util/dpi.js'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '../..'); const read = (p) => readFileSync(join(ROOT, p), 'utf8'); +/** + * 把任意平台的路径归一到 posix 形态。 + * + * ⚠️ 必须显式归一:`path.relative()` 在 Windows 返回 `apps\agent\…`, + * 而在 macOS/Linux 返回 `apps/agent/…`。任何用 `/` 硬编码的 `includes()` 判断 + * 在 Windows 上恒为 false —— 本地永远绿,CI 才红(2026-10-12 真例)。 + */ +export const toPosix = (p) => p.replaceAll('\\', '/'); + +/** + * `capabilities/win-ps/` 下的片段文件是否应被兜底扫描排除。 + * + * 这些片段(`prelude-*` / `body-*`)不自带 DPI 感知,而是由 `WIN_HELPER_PRELUDE` + * 在拼接时于**最前面**统一注入 —— 其序位已由「助手前置段:DPI 感知必须最先执行」断言。 + * 因此排除它们是正确的,**但判断本身不得依赖平台分隔符**(函数内自行归一)。 + */ +export const isWinPsFragment = (relPath, name) => + toPosix(relPath).includes('capabilities/win-ps/') && /^(prelude|body)-/.test(name); + /** 断言:脚本里注入了 DPI 感知,且**早于**第一个依赖它的调用。 */ function assertDpiInjectedFirst(script, { before, label }) { assert.ok(script.includes(PS_DPI_AWARE), `${label}:未注入 PS_DPI_AWARE`); @@ -129,9 +148,9 @@ test('兜底:capabilities 下命中坐标信号的新文件必须引用 PS_DPI continue; } if (!name.endsWith('.ts') || name.endsWith('.d.ts')) continue; - const rel = relative(ROOT, full); + const rel = toPosix(relative(ROOT, full)); // 片段文件由 WIN_HELPER_PRELUDE 统一提供 DPI 感知(上面已断言其序位) - if (rel.includes('capabilities/win-ps/') && /prelude-|body-/.test(name)) continue; + if (isWinPsFragment(rel, name)) continue; const src = readFileSync(full, 'utf8'); const hit = COORD_SIGNALS.find((s) => src.includes(s)); if (hit && !src.includes('PS_DPI_AWARE')) offenders.push(`${rel}(命中 "${hit}")`); @@ -146,3 +165,34 @@ test('兜底:capabilities 下命中坐标信号的新文件必须引用 PS_DPI offenders.map((o) => ` - ${o}`).join('\n'), ); }); + +/** + * 跨平台回归:排除逻辑必须与路径分隔符无关。 + * + * 首版写的是 `rel.includes('capabilities/win-ps/')` —— 在 Windows 上 `rel` 形如 + * `apps\agent\…`,该判断**恒为 false** → 片段文件被误报成违规,于是 + * **本地 macOS 全绿、CI 才红**(2026-10-12 真例:`prelude-image.ts` 命中 + * `CopyFromScreen` 被点名)。本测试直接喂 Windows 形态路径,把这类差异 + * 钉进「本地可验」的范围,不再依赖真的跑在 Windows 上才发现。 + */ +test('兜底守卫:片段文件排除逻辑必须与路径分隔符无关', () => { + const win = 'apps\\agent\\src\\capabilities\\win-ps\\prelude-image.ts'; + const posix = 'apps/agent/src/capabilities/win-ps/prelude-image.ts'; + + assert.equal(toPosix(win), posix, 'toPosix 必须把反斜杠归一为斜杠'); + assert.equal(isWinPsFragment(win, 'prelude-image.ts'), true, 'Windows 反斜杠路径必须被识别为片段'); + assert.equal(isWinPsFragment(posix, 'prelude-image.ts'), true, 'posix 路径必须被识别为片段'); + assert.equal(isWinPsFragment('apps\\agent\\src\\capabilities\\win-ps\\body-list.ts', 'body-list.ts'), true); + + // 反向:非片段、或不在 win-ps 目录下的一律**不得**被排除(否则兜底形同虚设) + assert.equal( + isWinPsFragment('apps\\agent\\src\\capabilities\\screen.ts', 'screen.ts'), + false, + 'capabilities 顶层文件不得被排除', + ); + assert.equal( + isWinPsFragment('apps\\agent\\src\\capabilities\\win-ps\\helper.ts', 'helper.ts'), + false, + 'win-ps 下的非片段文件(无 prelude-/body- 前缀)不得被排除', + ); +});