Просмотр исходного кода

Merge pull request #3058 from deepseek-harness/fix/pwsh-local-dispose-status

fix(ci): consolidate windows/snapshot/e2e CI blocker fixes
Tianyi Cui 3 недель назад
Родитель
Сommit
f4d1d3fb25

+ 2 - 2
.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;
 # 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:
 # 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
 #   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

+ 1 - 0
.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 |
 | 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) |
 | 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 |
 | `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
 ### Membership contract
 
 

+ 1 - 0
.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`),本不在阈值口径内 |
 | typert generator 全部 6 个 spec | generator 自身 src | generator src 已整包 threshold-excluded(`vitest.config.ts`),本不在阈值口径内 |
 | 其中 tools-catalog.spec 额外 import | `typert-registry`、`tool-cordis` 的 src | 两包各自的测试独立满覆盖(focused coverage 实测无阈值错误) |
 | 其中 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 子进程 | 无需接 |
 | `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`),本不在阈值口径内 |
 
 
 ### 成员资格约定
 ### 成员资格约定
 
 

+ 12 - 5
packages/shell/pwsh-local/tests/executor.spec.ts

@@ -471,10 +471,12 @@ describe.skipIf(!hasPwsh)('process lifecycle ownership (the subprocess service,
     await managerFiber.dispose()
     await managerFiber.dispose()
     expect(() => process.kill(pid, 0)).toThrow()
     expect(() => process.kill(pid, 0)).toThrow()
     await proc.done
     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 () => {
   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.
     // A settled process was untouched; the live one was terminated and joined.
     expect(finished.status).toBe('completed')
     expect(finished.status).toBe('completed')
     await running.done
     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)
   })
   })
 })
 })

+ 10 - 5
packages/shell/tool-pwsh/tests/loader.spec.ts

@@ -12,7 +12,7 @@ import { join } from 'node:path'
 import { fileURLToPath } from 'node:url'
 import { fileURLToPath } from 'node:url'
 import { spawnSync } from 'node:child_process'
 import { spawnSync } from 'node:child_process'
 import { describe, expect, it } from 'vitest'
 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'
 import { resolvePwshPath } from '@deepseek-ai/dsh-pwsh-local'
 
 
 // The probe follows the executor's own resolution (Program Files installs on
 // 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', () => {
 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 () => {
   it('registers the pwsh surface and renders real foreground and background results', async () => {
     let report: PwshLoaderReport | undefined
     let report: PwshLoaderReport | undefined
     const { stderr } = await runLoaderSmoke({
     const { stderr } = await runLoaderSmoke({
@@ -46,9 +51,7 @@ describe.skipIf(!hasPwsh)('tool-pwsh through a real Loader composition', () => {
       libBinScript: driver,
       libBinScript: driver,
       configPath,
       configPath,
       tsconfigPath: repoTsconfig,
       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) => {
       inspect: async (cwd) => {
         report = JSON.parse(await readFile(join(cwd, 'pwsh-loader-report.json'), 'utf8')) as PwshLoaderReport
         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?.foregroundText).toBe('loader-ok\n')
     expect(report?.backgroundText).toContain('loader-bg-ok')
     expect(report?.backgroundText).toContain('loader-bg-ok')
     expect(report?.backgroundText).toContain('[status: completed, exit code: 0]')
     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)
 })
 })

+ 2 - 2
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', () => {
 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 = {
     const firstReport = {
       status: 'continue',
       status: 'continue',
       summary: 'ROUND_ONE_HANDOFF',
       summary: 'ROUND_ONE_HANDOFF',
@@ -115,7 +115,7 @@ describe('dsh-tool-ralph over the real spawn and worker-thread stack', () => {
     await parentHandle.dispose()
     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 = {
     const firstReport = {
       status: 'continue',
       status: 'continue',
       summary: 'ROUND_ONE_HANDOFF',
       summary: 'ROUND_ONE_HANDOFF',

+ 9 - 0
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/oxlint-contract.spec.ts', exclude: 'scripts/oxlint-contract.spec.ts' },
   { filter: 'scripts/change-scope.spec.ts', exclude: 'scripts/change-scope.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' },
   { 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',
+  },
 ]
 ]