From 553b8c35da38e13cd664bfcee45135f319405a70 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Tue, 25 Aug 2026 17:11:34 +0800 Subject: [PATCH 1/3] test(pwsh-local): accept graceful SIGTERM exit as service-disposal death On Linux, pwsh may trap SIGTERM and exit cleanly when the subprocess service is disposed, so the handle status is 'completed' rather than 'killed'. The test already proved the process tree is gone via kill(pid,0); both statuses satisfy the contract. --- .../shell/pwsh-local/tests/executor.spec.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/shell/pwsh-local/tests/executor.spec.ts b/packages/shell/pwsh-local/tests/executor.spec.ts index e26211b268..53047a62c8 100644 --- a/packages/shell/pwsh-local/tests/executor.spec.ts +++ b/packages/shell/pwsh-local/tests/executor.spec.ts @@ -471,10 +471,12 @@ describe.skipIf(!hasPwsh)('process lifecycle ownership (the subprocess service, await managerFiber.dispose() expect(() => process.kill(pid, 0)).toThrow() await proc.done - // POSIX reports the kill as a signal; Windows reports a forced - // termination as exit 1 with no signal (indistinguishable from a crash), - // so the status stamp follows the platform's exit facts. - expect(proc.status).toBe(process.platform === 'win32' ? 'completed' : 'killed') + // Service disposal confirmed the tree is gone (kill(pid,0) throws above). + // On POSIX the stamp depends on whether the shell traps SIGTERM and exits + // cleanly (completed) or is killed by the signal (killed); Windows forced + // termination (taskkill, no signals) also stamps completed. Both mean the + // process no longer survives the service. + expect(['killed', 'completed']).toContain(proc.status) }) it('service disposal settles running handles and leaves settled ones untouched', async () => { @@ -493,6 +495,11 @@ describe.skipIf(!hasPwsh)('process lifecycle ownership (the subprocess service, // A settled process was untouched; the live one was terminated and joined. expect(finished.status).toBe('completed') await running.done - expect(running.status).toBe(process.platform === 'win32' ? 'completed' : 'killed') + // The live handle was terminated and joined; on POSIX the stamp depends + // on whether the shell traps SIGTERM and exits cleanly (completed) or is + // killed by the signal (killed); Windows forced termination (taskkill, no + // signals) also stamps completed. Both mean the process no longer + // survives the service. + expect(['killed', 'completed']).toContain(running.status) }) }) From a404edf3b1de8515dc6944023d6a9b286166f81b Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Tue, 25 Aug 2026 18:45:21 +0800 Subject: [PATCH 2/3] test: widen windows-hosted subprocess budgets in two web-stack specs The self-hosted Windows coverage pool (16 shards x 12 workers on 192 threads) pushes real subprocess boots past their vitest deadlines: the tool-pwsh Loader smoke reaches ~40s against a 30s process cap, and the tool-ralph worker-thread cases exceed the 5s default. Give the pwsh smoke a 90s process deadline (the subprocess keeps the assembled boot, the vitest deadline stays at 120s), and give the two un-budgeted ralph cases 30s each, matching the existing 20s quiescence case. --- packages/shell/tool-pwsh/tests/loader.spec.ts | 15 ++++++++++----- .../workflow/tool-ralph/tests/integration.spec.ts | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/shell/tool-pwsh/tests/loader.spec.ts b/packages/shell/tool-pwsh/tests/loader.spec.ts index 9a01315516..ab4c1f1925 100644 --- a/packages/shell/tool-pwsh/tests/loader.spec.ts +++ b/packages/shell/tool-pwsh/tests/loader.spec.ts @@ -12,7 +12,7 @@ import { join } from 'node:path' import { fileURLToPath } from 'node:url' import { spawnSync } from 'node:child_process' import { describe, expect, it } from 'vitest' -import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke' +import { runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke' import { resolvePwshPath } from '@deepseek-ai/dsh-pwsh-local' // The probe follows the executor's own resolution (Program Files installs on @@ -37,6 +37,11 @@ interface PwshLoaderReport { } describe.skipIf(!hasPwsh)('tool-pwsh through a real Loader composition', () => { + // Self-hosted Windows runners reach ~40s for this smoke under the full + // coverage load (measured on the 192-thread CI pool), against the + // 30s default process deadline. Give the subprocess headroom so the + // assembled boot completes instead of being SIGKILLed mid-load. + const processTimeoutMs = 90_000 it('registers the pwsh surface and renders real foreground and background results', async () => { let report: PwshLoaderReport | undefined const { stderr } = await runLoaderSmoke({ @@ -46,9 +51,7 @@ describe.skipIf(!hasPwsh)('tool-pwsh through a real Loader composition', () => { libBinScript: driver, configPath, tsconfigPath: repoTsconfig, - // The self-hosted Windows pool can take roughly 40 seconds to boot this - // real Loader composition under the full CI load. - processTimeoutMs: 90_000, + processTimeoutMs, inspect: async (cwd) => { report = JSON.parse(await readFile(join(cwd, 'pwsh-loader-report.json'), 'utf8')) as PwshLoaderReport }, @@ -62,5 +65,7 @@ describe.skipIf(!hasPwsh)('tool-pwsh through a real Loader composition', () => { expect(report?.foregroundText).toBe('loader-ok\n') expect(report?.backgroundText).toContain('loader-bg-ok') expect(report?.backgroundText).toContain('[status: completed, exit code: 0]') - }, LOADER_SMOKE_TEST_TIMEOUT_MS + 75_000) + // 15s of vitest headroom past the subprocess deadline, mirroring + // LOADER_SMOKE_TEST_TIMEOUT_MS's margin over its process window. + }, processTimeoutMs + 15_000) }) diff --git a/packages/workflow/tool-ralph/tests/integration.spec.ts b/packages/workflow/tool-ralph/tests/integration.spec.ts index e6de6c38ef..8b3629ffe7 100644 --- a/packages/workflow/tool-ralph/tests/integration.spec.ts +++ b/packages/workflow/tool-ralph/tests/integration.spec.ts @@ -35,7 +35,7 @@ async function mountRalph(script: MockScript, config: toolRalph.Config) { } describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => { - it('uses distinct empty-seed children, shared cwd, and only the prior bounded handoff', async () => { + it('uses distinct empty-seed children, shared cwd, and only the prior bounded handoff', { timeout: 30_000 }, async () => { const firstReport = { status: 'continue', summary: 'ROUND_ONE_HANDOFF', @@ -115,7 +115,7 @@ describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => { await parentHandle.dispose() }) - it('reports the failed round and last good handoff when a child fails', async () => { + it('reports the failed round and last good handoff when a child fails', { timeout: 30_000 }, async () => { const firstReport = { status: 'continue', summary: 'ROUND_ONE_HANDOFF', From 9bf6f4b43c2998336ef8f3f3aacceda7f34042a4 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Tue, 25 Aug 2026 21:46:31 +0800 Subject: [PATCH 3/3] perf(ci): move the transform corpus out of coverage partitions The full-corpus transform gate spawns one child that transforms and imports every built bundle, so it runs for 8-25 minutes as a single case and dominates one native Windows coverage partition, blowing its 900s budget under load. Move it to the coverage-exempt heavy gate, which runs it with its own worker budget instead of competing with the instrumented partitions. The package's src is threshold-excluded in vitest.config.ts, so the exemption carries no coverage; the exempt-heavy roster note records the entry. --- .../2026-07-31-coverage-exempt-heavy-suites.i18n.yaml | 4 ++-- .../process/2026-07-31-coverage-exempt-heavy-suites.md | 1 + .../2026-07-31-coverage-exempt-heavy-suites.zh.md | 1 + scripts/coverage-exempt.ts | 9 +++++++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.i18n.yaml b/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.i18n.yaml index 78ff70c1c1..929708a328 100644 --- a/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.md -2026-07-31-coverage-exempt-heavy-suites.md: 1f468a69321b451593a9279cfebc1b457fb08a47 -2026-07-31-coverage-exempt-heavy-suites.zh.md: 7e519f44c8321b6b99c04c6af56c4cfa5b641663 +2026-07-31-coverage-exempt-heavy-suites.md: c587950a4cf6e79180d381e61ade0b274dbed6e7 +2026-07-31-coverage-exempt-heavy-suites.zh.md: 74ab9b1510a1d3eafa2ae56da7667afa817737cd diff --git a/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.md b/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.md index 1f468a6932..c587950a4c 100644 --- a/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.md +++ b/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.md @@ -30,6 +30,7 @@ A suite contributes to coverage exactly when it executes measured files in-proce | All 6 typert generator specs | The generator's own src | Generator src is threshold-excluded as a package (`vitest.config.ts`) — outside the threshold scope to begin with | | tools-catalog.spec additionally imports | `typert-registry` and `tool-cordis` src | Each package's own tests cover them fully (verified with focused coverage runs, zero threshold errors) | | `scripts/install-lefthook.spec.ts`, `scripts/oxlint-contract.spec.ts`, `scripts/change-scope.spec.ts`, `scripts/translation-pairing-merge.spec.ts` | None — they test `scripts/` sources (never in `coverage.include`) and work by spawning child processes | Nothing to carry | +| `packages/experimental/webworker-runtime/tests/compile/transform-corpus.spec.ts` | None — it spawns a child process that transforms and imports every built bundle (Node's ESM loader is the oracle) | webworker-runtime src is threshold-excluded as a package (`vitest.config.ts`) — outside the threshold scope to begin with | ### Membership contract diff --git a/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.zh.md b/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.zh.md index 7e519f44c8..74ab9b1510 100644 --- a/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.zh.md +++ b/.agents/notes/implemented/process/2026-07-31-coverage-exempt-heavy-suites.zh.md @@ -30,6 +30,7 @@ Linux 覆盖率 CI 与原生 Windows CI 在插桩门禁内部使用 [job 内分 | typert generator 全部 6 个 spec | generator 自身 src | generator src 已整包 threshold-excluded(`vitest.config.ts`),本不在阈值口径内 | | 其中 tools-catalog.spec 额外 import | `typert-registry`、`tool-cordis` 的 src | 两包各自的测试独立满覆盖(focused coverage 实测无阈值错误) | | `scripts/install-lefthook.spec.ts`、`scripts/oxlint-contract.spec.ts`、`scripts/change-scope.spec.ts`、`scripts/translation-pairing-merge.spec.ts` | 无——被测对象是 `scripts/` 源码(从不在 coverage.include),执行方式是 spawn 子进程 | 无需接 | +| `packages/experimental/webworker-runtime/tests/compile/transform-corpus.spec.ts` | 无——spawn 子进程对全部已构建 bundle 做 transform 并 import(oracle 是 Node ESM loader) | webworker-runtime src 已整包 threshold-excluded(`vitest.config.ts`),本不在阈值口径内 | ### 成员资格约定 diff --git a/scripts/coverage-exempt.ts b/scripts/coverage-exempt.ts index eff6ca2b13..a8ea51c6f7 100644 --- a/scripts/coverage-exempt.ts +++ b/scripts/coverage-exempt.ts @@ -39,4 +39,13 @@ export const coverageExemptHeavySuites: readonly CoverageExemptSuite[] = [ { filter: 'scripts/oxlint-contract.spec.ts', exclude: 'scripts/oxlint-contract.spec.ts' }, { filter: 'scripts/change-scope.spec.ts', exclude: 'scripts/change-scope.spec.ts' }, { filter: 'scripts/translation-pairing-merge.spec.ts', exclude: 'scripts/translation-pairing-merge.spec.ts' }, + // Spawns the full-corpus transform gate in a child process (Node's ESM + // loader is its oracle), so no measured file executes in-process; the + // package src is threshold-excluded in vitest.config.ts. A single + // 900s-budget case; running it inside an instrumented partition exceeds + // the Windows partition budget under load. + { + filter: 'packages/experimental/webworker-runtime/tests/compile/transform-corpus.spec.ts', + exclude: 'packages/experimental/webworker-runtime/tests/compile/transform-corpus.spec.ts', + }, ]