diff --git a/.github/workflows/build-installers.yml b/.github/workflows/build-installers.yml index 7a22640..1c81cdb 100644 --- a/.github/workflows/build-installers.yml +++ b/.github/workflows/build-installers.yml @@ -3,12 +3,20 @@ name: build-installers # 触发: # - push tag `v*` → 构建安装包并发布到 Release # - push main → 只跑 test job(防「镜像漂移/坏改动」静默合入 main) +# - PR → main → 只跑 test job(构建类 job 有 if 条件,PR 上不会执行) # - 手动触发 → 只构建并上传 Actions artifact(用于验证) # -# 为什么加 push main:此前只有 tag 与 PR 触发测试,直接 push main 不跑任何 CI, +# 为什么加 push main:此前只有 tag 触发测试,直接 push main 不跑任何 CI, # 而本仓有「根目录 ↔ plugins/zcode-advisor/ 双副本」结构,忘同步就会把旧代码合入。 # 只跑 test(构建类 job 已加 if 条件,不在 push main 时执行):快、无网络依赖,够堵住漂移。 # +# 为什么还要加 pull_request:push main 是「事后」,红灯只在已经坏在 main 上之后才亮。 +# 2026-10 实测踩过:PR #14/#15 的 ocr-review 适配里,GNU timeout 分支在 Linux 上返回 +# 124(macOS 没有 timeout,走进程组回退返回 143),测试只断言了 137|143,于是本机全绿、 +# 合并后 main 两次变红。PR 阶段跑一遍 test,这类「只在 Linux 成立」的差异就拦在合并前。 +# 注:构建类 job 都带 `startsWith(github.ref,'refs/tags/v') || event_name == 'workflow_dispatch'` +# 守卫,PR 上只剩 test 这一个 job,代价就是一条 ubuntu 单元测试。 +# # 工具链选择依据(2026-09 查证 actions/runner-images): # - windows-2022 预装 NSIS 3.10,可直接产出 setup.exe(windows-2025 未预装 NSIS,只有 InnoSetup) # - macos-15-intel 为 Intel(x64) runner;macos-15 为 Apple Silicon(arm64) runner @@ -18,6 +26,8 @@ on: branches: [main] tags: - 'v*' + pull_request: + branches: [main] workflow_dispatch: inputs: mac_arch: diff --git a/test/ocr-review.test.js b/test/ocr-review.test.js index 9c13e8f..5675ecf 100644 --- a/test/ocr-review.test.js +++ b/test/ocr-review.test.js @@ -476,10 +476,14 @@ test('ocr-review.sh:链文件存在但零条合法条目时同样退回快照' assert.match(r.stdout, COMPLETED_RE); }); -// 单跳超时:本机 macOS + bash 3.2 没有 GNU timeout,run_with_deadline 用「自成进程组 + -// 整组 TERM」实现。这里锁住两个行为:① 挂起的一跳会被掐掉并降级;② **孙进程**也必须死 +// 单跳超时:run_with_deadline 有两条实现路径,走哪条取决于本机有没有 GNU timeout。 +// - 有 GNU timeout(CI 的 ubuntu-latest 就有):`timeout "$secs" "$@"`,到点自杀返回 124; +// - 没有(macOS + bash 3.2,本机开发环境):「自成进程组 + 整组 TERM」,返回 137/143。 +// 两条路径都必须满足同样的两个行为:① 挂起的一跳会被掐掉并降级;② **孙进程**也必须死 // (用 perl alarm 或裸 kill 只杀直接子进程,ocr 派生的子进程会漏成孤儿)。 -test('ocr-review.sh:单跳挂起会被强杀并降级(退出码 137/143 透传)', skipUnlessDeadline, () => { +// 所以断言只认「脚本判定为被强制终止」的那组退出码(脚本的 case 也是 124|137|143), +// 不锁死某一个平台的码 —— 曾经只写 137|143,结果 Linux CI 打印 124 就红了。 +test('ocr-review.sh:单跳挂起会被强杀并降级(退出码 124/137/143 透传)', skipUnlessDeadline, () => { const sb = mkSandbox(); const childPidFile = path.join(sb.root, 'grandchild.pid'); const state = path.join(sb.root, 'state'); @@ -510,7 +514,9 @@ printf '%s\\n' '${JSON.stringify(COMPLETE_JSON)}' > "$out" assert.strictEqual(r.code, 0, '第一跳超时后应降级到第二跳并成功'); // 超时提示走 stdout(与「尝试 provider=…」同一路),且必须带上真实退出码, // 否则看起来和「普通失败」一模一样,排查时会误判成 provider 坏了。 - assert.match(r.stdout, /上限被强制终止(退出码 (137|143))/, '超时必须明说并带上退出码'); + // 124 = GNU timeout 到点自杀的退出码;143/137 = 进程组被 TERM/KILL 打掉。 + // 这里只要求「是脚本认定为被强制终止的码」,不锁死平台。 + assert.match(r.stdout, /上限被强制终止(退出码 (124|137|143))/, '超时必须明说并带上退出码'); assert.match(r.stdout, /成功:fake \/ m1/, '应降级到下一跳'); // 孙进程必须已被回收(组信号生效)。给它一点时间完成 TERM→KILL。