Files
deepseek-harness/scripts/coverage-fork-diagnostics.spec.ts
T
Tianyi CuiandChinesezjc 9bc34fa962 ci: reuse reviewed coverage fork exit diagnostics
Repeated native Windows coverage runs stopped a Vitest fork while a JSONL
migration suite was unfinished. Vitest's generic unexpected-exit error
discarded the process exit code and signal, preventing classification of
the failure despite identifying the missing test file from the inventory.

Reuse the public ForksPoolWorker diagnostic implementation and protocol
fixtures from independently reviewed PR #4388, pinned at
77e215ff76711ec75ed8aa08aa5b414780e539ae. The runtime helper is unchanged.
It reports PID, project, worker identity, outstanding file request, code,
and signal before stock Vitest reports the original failure. It clears
ownership only on the typed testfileFinished response, suppresses expected
stop, and leaves cancellation and failure handling with the original pool.

Wire that existing pool into generated coverage partition configs. Keep
the original worker counts, partition scheduling, test timeouts, coverage
thresholds, and failure verdicts. No environment, raw argv, or test object
is serialized. This commit adds diagnostic evidence; it does not claim to
repair the still-unidentified Windows crash. PR #4388 remains the separate
reviewable owner; no duplicate diagnostic framework or PR is introduced.

Adapt the copied fixtures to the current no-new-unknown-assertions rule:
declare the private preload exit function, validate consumed logger fields
with the public logger type, and assign matcher/rejection values to typed
unknown locals without assertions. Add a concise bilingual testing note.

Validation:
- Diagnostic suite: 10 passed, including a real configured Vitest fork
  exiting 23, stock-pool negative control, successful silent shutdown,
  unexpected exit 0, early exit, independent forks, and signal metadata.
- Coverage coordinator suite: 51 passed.
- Targeted lint and focused strict TypeScript check passed.
- pnpm exec tsc -b tsconfig.host.json passed.
- verify-no-unknown-casts passed; 1430 existing assertions unchanged.
- doc-sync passed 40 gates; the two initially failing gates were repaired
  and rerun successfully: doc-typecheck and verify-doc-budgets.
- Translation pairing and git diff --check passed.

Refs #4387, #4388, #4394.

Co-authored-by: Chinesezjc <[email protected]>
2026-10-06 10:17:44 +08:00

278 lines
12 KiB
TypeScript

import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { runGate } from './run-gates.ts'
import { PassThrough } from 'node:stream'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { ForksPoolWorker } from 'vitest/node'
import type { PoolOptions, PoolWorker, TestProject, WorkerRequest, WorkerResponse } from 'vitest/node'
import { coverageForkPool } from './coverage-fork-diagnostics.ts'
const roots: string[] = []
const cleanups: Array<() => Promise<void>> = []
afterEach(async () => {
const cleanup = cleanups.splice(0)
const directories = roots.splice(0)
const results = await Promise.allSettled(cleanup.map(dispose => dispose()))
vi.restoreAllMocks()
for (const directory of directories) await rm(directory, { recursive: true, force: true })
const errors: unknown[] = []
for (const result of results) {
if (result.status === 'rejected') errors.push(result.reason)
}
if (errors.length > 0) throw new AggregateError(errors, 'fork fixture cleanup failed')
})
async function fixture(exitCode: number, early = false): Promise<string> {
const root = await mkdtemp(join(tmpdir(), 'dsh-coverage-fork-'))
roots.push(root)
await mkdir(join(root, 'workers'))
// The public pool's distPath selects a plain Node protocol fixture, not a nested Vitest run.
await writeFile(join(root, 'workers/forks.js'), early ? `process.exit(${exitCode})\n` : `
process.on('message', (message) => {
if (message.type === 'run' || message.type === 'collect') process.send({ kind: 'ready', pid: process.pid })
if (message.type === 'cancel') process.exit(${exitCode})
if (message.type === 'stop') process.exit(0)
})
`)
return root
}
function poolOptions(root: string) {
const outputStream = new PassThrough()
const errorStream = new PassThrough()
const error = vi.fn<(message: string) => void>()
// ForksPoolWorker needs only the logger and project name from the Vitest project.
const logger: Pick<TestProject['vitest']['logger'], 'outputStream' | 'errorStream' | 'error'> = {
outputStream, errorStream, error,
}
const project = { name: 'fixture', vitest: { logger } } as TestProject
const options: PoolOptions = {
distPath: root, project, method: 'run', environment: { name: 'node', options: null },
execArgv: [], env: {},
}
return { options, error, outputStream, errorStream }
}
async function start(root: string, diagnostic = true) {
const { options, error, outputStream, errorStream } = poolOptions(root)
const pool: PoolWorker = diagnostic ? coverageForkPool.createPoolWorker(options) : new ForksPoolWorker(options)
let started = false
const closed = Promise.withResolvers<undefined>()
cleanups.push(async () => {
if (started) {
await pool.stop()
await closed.promise
}
outputStream.destroy()
errorStream.destroy()
})
await pool.start()
started = true
pool.on('close', () => { closed.resolve(undefined) })
const exited = new Promise<number | null>((resolve) => { pool.on('exit', resolve) })
const ready = new Promise<number>((resolve) => {
pool.on('message', (message: unknown) => {
const response = message as { kind: string; pid: number }
if (response.kind === 'ready') resolve(response.pid)
})
})
return { pool, ready, exited, error }
}
function request(filepath: string, workerId: number): Extract<WorkerRequest, { type: 'run' }> {
return {
__vitest_worker_request__: true, type: 'run',
context: { files: [{ filepath }], workerId, providedContext: {}, environment: { name: 'node', options: null } },
}
}
const cancel: WorkerRequest = { __vitest_worker_request__: true, type: 'cancel' }
const stop: WorkerRequest = { __vitest_worker_request__: true, type: 'stop' }
async function configuredPool(pool: 'stock' | 'diagnostic', outcome: 'exit' | 'pass', signal: AbortSignal) {
const cacheDir = await mkdtemp(join(tmpdir(), 'dsh-configured-fork-cache-'))
roots.push(cacheDir)
const controller = new AbortController()
const result = runGate({
id: `fork-${pool}`, label: 'fork fixture', displayCommand: 'Vitest pool fixture',
command: process.execPath,
args: [resolve('node_modules/vitest/vitest.mjs'), 'run', '--config', 'scripts/fixtures/coverage-fork.config.ts'],
env: { DSH_TEST_FORK_POOL: pool, DSH_TEST_FORK_OUTCOME: outcome, DSH_TEST_FORK_CACHE: cacheDir },
}, AbortSignal.any([signal, controller.signal]))
let settled = false
cleanups.push(async () => {
if (!settled) controller.abort()
await result
})
try {
return await result
} finally {
settled = true
}
}
describe('coverage fork configuration entry', () => {
it('preserves the real Vitest failure and adds metadata before its error report', async ({ signal }) => {
const [stock, diagnostic] = await Promise.all([
configuredPool('stock', 'exit', signal),
configuredPool('diagnostic', 'exit', signal),
])
for (const result of [stock, diagnostic]) {
expect(result.aborted).not.toBe(true)
expect(result.error).toBeUndefined()
expect(result.signalCode).toBeNull()
expect(result.exitCode).toBe(1)
expect(result.output.map(chunk => chunk.text).join('')).toContain('Worker exited unexpectedly')
}
const output = diagnostic.output.map(chunk => chunk.text).join('')
expect(stock.output.map(chunk => chunk.text).join('')).not.toContain('coverage-worker-exit:')
const encoded = /coverage-worker-exit: (\{[^\r\n]*\})/u.exec(output)?.[1]
expect(encoded).toBeDefined()
const record = JSON.parse(encoded!) as {
pid: number
project: string
workerId: number
files: string[]
exitCode: number
signalCode: null
}
const pid = Number(/coverage-fixture-pid: (\d+)/u.exec(output)?.[1])
const anyNumber: unknown = expect.any(Number)
const anyString: unknown = expect.any(String)
expect(record).toEqual({
pid, project: 'configured-fork', workerId: anyNumber,
files: [anyString], exitCode: 23, signalCode: null,
})
expect(record.files.map(file => file.replaceAll('\\', '/')))
.toEqual([resolve('scripts/fixtures/coverage-fork.fixture.ts').replaceAll('\\', '/')])
expect(output.indexOf('coverage-worker-exit:')).toBeLessThan(output.indexOf('Worker exited unexpectedly'))
})
it('keeps a successful configured run silent and successful', async ({ signal }) => {
const result = await configuredPool('diagnostic', 'pass', signal)
expect(result.aborted).not.toBe(true)
expect(result.error).toBeUndefined()
expect(result.signalCode).toBeNull()
expect(result.exitCode).toBe(0)
expect(result.output.map(chunk => chunk.text).join('')).not.toContain('coverage-worker-exit:')
})
})
describe('coverage fork exit callback', () => {
it.each([
['native receiver', { pid: 41 }, 41],
['unavailable receiver', undefined, null],
] as const)('retains a signal with %s and silences expected shutdown', async (_name, receiver, pid) => {
let exitListener: ((value: unknown) => void) | undefined
vi.spyOn(ForksPoolWorker.prototype, 'start').mockResolvedValue(undefined)
vi.spyOn(ForksPoolWorker.prototype, 'stop').mockResolvedValue(undefined)
vi.spyOn(ForksPoolWorker.prototype, 'send').mockImplementation(() => undefined)
vi.spyOn(ForksPoolWorker.prototype, 'off').mockImplementation(() => undefined)
vi.spyOn(ForksPoolWorker.prototype, 'on').mockImplementation((event, callback) => {
if (event === 'exit') exitListener = callback
})
const { options, error, outputStream, errorStream } = poolOptions('/unused')
const pool = coverageForkPool.createPoolWorker(options)
cleanups.push(async () => {
await pool.stop()
outputStream.destroy()
errorStream.destroy()
})
await pool.start()
pool.send(request('/signal.spec.ts', 9))
if (exitListener === undefined) throw new Error('exit observer was not registered')
Reflect.apply(exitListener, receiver, [null, 'SIGTERM'])
expect(error).toHaveBeenCalledWith(`coverage-worker-exit: ${JSON.stringify({
pid, project: 'fixture', workerId: 9, files: ['/signal.spec.ts'], exitCode: null, signalCode: 'SIGTERM',
})}`)
error.mockClear()
pool.send(stop)
expect(() => { Reflect.apply(exitListener!, undefined, [null, 'SIGTERM']) }).not.toThrow()
expect(error).not.toHaveBeenCalled()
})
})
describe('coverage fork exit diagnostics', () => {
it('attributes a later collect request instead of an earlier run request', async () => {
const instance = await start(await fixture(23))
instance.pool.send(request('/earlier.spec.ts', 4))
const pid = await instance.ready
instance.pool.send({ ...request('/collected.spec.ts', 5), type: 'collect' })
const unrelated = { type: 'testfileFinished' }
expect(instance.pool.deserialize(unrelated)).toBe(unrelated)
expect(instance.pool.deserialize(null)).toBeNull()
instance.pool.send(cancel)
expect(await instance.exited).toBe(23)
expect(instance.error).toHaveBeenCalledWith(`coverage-worker-exit: ${JSON.stringify({
pid, project: 'fixture', workerId: 5, files: ['/collected.spec.ts'], exitCode: 23, signalCode: null,
})}`)
})
it('does not attribute a post-completion exit to a finished file', async () => {
const instance = await start(await fixture(23))
instance.pool.send(request('/finished.spec.ts', 6))
const pid = await instance.ready
const finished = {
__vitest_worker_response__: true, type: 'testfileFinished',
} satisfies Extract<WorkerResponse, { type: 'testfileFinished' }>
expect(instance.pool.deserialize(finished)).toBe(finished)
instance.pool.send(cancel)
expect(await instance.exited).toBe(23)
expect(instance.error).toHaveBeenCalledWith(`coverage-worker-exit: ${JSON.stringify({
pid, project: 'fixture', workerId: null, files: [], exitCode: 23, signalCode: null,
})}`)
})
it('records the exact owner while an independent ready fork remains alive', async () => {
const root = await fixture(23)
const [first, second] = await Promise.all([start(root), start(root)])
first.pool.send(request('/first.spec.ts', 1))
second.pool.send(request('/second.spec.ts', 2))
const [firstPid, secondPid] = await Promise.all([first.ready, second.ready])
expect(firstPid).not.toBe(secondPid)
first.pool.send(cancel)
expect(await first.exited).toBe(23)
expect(first.error.mock.calls).toEqual([[`coverage-worker-exit: ${JSON.stringify({
pid: firstPid, project: 'fixture', workerId: 1, files: ['/first.spec.ts'], exitCode: 23, signalCode: null,
})}`]])
expect(second.error).not.toHaveBeenCalled()
expect(() => process.kill(secondPid, 0)).not.toThrow()
second.pool.send(stop)
expect(await second.exited).toBe(0)
expect(second.error).not.toHaveBeenCalled()
})
it('retains an unexpected zero exit instead of interpreting it as suite success', async () => {
const instance = await start(await fixture(0))
instance.pool.send(request('/unfinished.spec.ts', 3))
const pid = await instance.ready
instance.pool.send(cancel)
expect(await instance.exited).toBe(0)
expect(instance.error).toHaveBeenCalledWith(`coverage-worker-exit: ${JSON.stringify({
pid, project: 'fixture', workerId: 3, files: ['/unfinished.spec.ts'], exitCode: 0, signalCode: null,
})}`)
})
it('records an exit before any file request without inventing a file owner', async () => {
const instance = await start(await fixture(31, true))
expect(await instance.exited).toBe(31)
expect(instance.error).toHaveBeenCalledOnce()
const record = JSON.parse(instance.error.mock.calls[0]![0].slice('coverage-worker-exit: '.length)) as Record<string, unknown>
const anyNumber: unknown = expect.any(Number)
expect(record).toEqual({
pid: anyNumber, project: 'fixture', workerId: null, files: [], exitCode: 31, signalCode: null,
})
})
it('observes the same child failure without diagnostics under the stock pool negative control', async () => {
const instance = await start(await fixture(23), false)
instance.pool.send(request('/stock.spec.ts', 4))
await instance.ready
instance.pool.send(cancel)
expect(await instance.exited).toBe(23)
expect(instance.error).not.toHaveBeenCalled()
})
})