Files
deepseek-harness/scripts/coverage-partitions.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

733 lines
29 KiB
TypeScript

import { access, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
COVERAGE_PARTITION_MODE_ENV,
COVERAGE_PARTITIONS_ENV,
COVERAGE_TEST_TIMEOUT_ENV,
CoveragePartitionCoordinator,
assignWeightedPartitions,
collectPartitionDurations,
coverageTestTimeoutOptions,
forwardedCoverageArgs,
parseCoveragePartitionCount,
parseCoverageTestTimeout,
parseListOutput,
readFileDurations,
writeFileDurations,
type CoverageCommand,
type CoverageCommandResult,
type CoveragePartitionCoordinatorOptions,
} from './coverage-partitions.ts'
import {
END_OF_LINE_COLUMN,
canonicalizeEndOfLineColumns,
} from './coverage-canonical-locations.ts'
const passed: CoverageCommandResult = { exitCode: 0, signalCode: null }
/** Every temporary root created by this file, removed after each test. */
const roots: string[] = []
afterEach(async () => {
vi.restoreAllMocks()
for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true })
})
async function writeBlob(command: CoverageCommand): Promise<void> {
if (command.blobPath === undefined) return
await mkdir(dirname(command.blobPath), { recursive: true })
await writeFile(command.blobPath, '{}')
}
async function temporaryRoot(): Promise<string> {
const root = await mkdtemp(join(tmpdir(), 'dsh-coverage-partitions-'))
roots.push(root)
return root
}
/** Write a Vitest results cache under a temporary root. */
async function writeVitestCache(root: string, entries: Array<[string, { duration: number }]>): Promise<void> {
const cacheDir = join(root, 'node_modules/.vite/vitest/cache-hash')
await mkdir(cacheDir, { recursive: true })
await writeFile(join(cacheDir, 'results.json'), JSON.stringify({ version: '4.1.8', results: entries }))
}
/** Run the coordinator and capture every partition config's source. */
async function runCoordinatorReadingConfigs(
root: string,
options: Omit<CoveragePartitionCoordinatorOptions, 'root' | 'pnpmEntrypoint' | 'runCommand'>,
): Promise<Map<string, string>> {
const configContents = new Map<string, string>()
const runCommand = vi.fn(async (command: CoverageCommand) => {
const configArgument = command.args.find(argument => argument.startsWith('--config='))
if (configArgument !== undefined) {
configContents.set(command.label, await readFile(join(root, configArgument.slice('--config='.length)), 'utf8'))
}
await writeBlob(command)
return passed
})
const coordinator = new CoveragePartitionCoordinator({
root,
pnpmEntrypoint: '/pnpm.cjs',
runCommand,
...options,
})
await expect(coordinator.run()).resolves.toBe(0)
return configContents
}
function successfulCommandRecorder(commands: CoverageCommand[]) {
return vi.fn(async (command: CoverageCommand) => {
commands.push(command)
await writeBlob(command)
return passed
})
}
describe('coverage partition count', () => {
it.each([
[undefined, undefined],
['', undefined],
['2', 2],
['3', 3],
])('parses %j as %j', (raw, expected) => {
expect(parseCoveragePartitionCount(raw)).toBe(expected)
})
it.each(['0', '1', '2.5', '02', 'many'])('rejects %j', (raw) => {
expect(() => parseCoveragePartitionCount(raw))
.toThrow(`${COVERAGE_PARTITIONS_ENV} must be an integer greater than 1`)
})
})
describe('lane test budget', () => {
it('applies one configured budget to tests, hooks, and expect.poll', () => {
expect(coverageTestTimeoutOptions('30000')).toEqual({
testTimeout: 30000,
hookTimeout: 30000,
expect: { poll: { timeout: 30000 } },
})
})
it.each([undefined, ''])('keeps Vitest defaults when the budget is %j', (raw) => {
expect(parseCoverageTestTimeout(raw)).toBeUndefined()
expect(coverageTestTimeoutOptions(raw)).toEqual({})
})
it.each(['0', '-1', '2.5', '090', '90000ms', 'many'])('rejects %j', (raw) => {
expect(() => parseCoverageTestTimeout(raw))
.toThrow(`${COVERAGE_TEST_TIMEOUT_ENV} must be a positive integer, got ${JSON.stringify(raw)}.`)
expect(() => coverageTestTimeoutOptions(raw))
.toThrow(`${COVERAGE_TEST_TIMEOUT_ENV} must be a positive integer`)
})
})
describe('coverage forwarded arguments', () => {
it('removes one package-script separator', () => {
expect(forwardedCoverageArgs(['--', 'scripts/example.spec.ts'])).toEqual(['scripts/example.spec.ts'])
})
it('preserves direct arguments and a subsequent Vitest separator', () => {
expect(forwardedCoverageArgs(['--testNamePattern=example'])).toEqual(['--testNamePattern=example'])
expect(forwardedCoverageArgs(['--', '--', 'example'])).toEqual(['--', 'example'])
})
})
describe('weighted partition assignment', () => {
it('seeds the heaviest files into different partitions', () => {
const weights = new Map<string, number>([
['packages/a/tests/heavy-1.spec.ts', 100],
['packages/a/tests/heavy-2.spec.ts', 90],
['packages/a/tests/heavy-3.spec.ts', 80],
['packages/a/tests/light.spec.ts', 1],
])
const buckets = assignWeightedPartitions([...weights.keys()], weights, 3)
expect(buckets).toHaveLength(3)
for (const bucket of buckets) {
expect(bucket).not.toHaveLength(0)
expect(bucket.filter(file => file.includes('heavy'))).toHaveLength(1)
}
})
it('balances total weight across partitions', () => {
const files = Array.from({ length: 20 }, (_, index) => `packages/a/tests/file-${index}.spec.ts`)
const weights = new Map(files.map((file, index) => [file, (index % 7) + 1]))
const buckets = assignWeightedPartitions(files, weights, 4)
const sums = buckets.map(bucket => bucket.reduce((sum, file) => sum + (weights.get(file) ?? 0), 0))
const spread = Math.max(...sums) - Math.min(...sums)
expect(spread).toBeLessThanOrEqual(7)
})
it('steers assignment by weight, not by file count', () => {
// Weight-aware LPT balances three buckets to 1500 each. A file-count-only
// rule pairs the heaviest file with the fourth (1700), so the assertion
// only passes when recorded weights steer the assignment.
const weights = new Map<string, number>([
['a.spec.ts', 1000],
['b.spec.ts', 900],
['c.spec.ts', 800],
['d.spec.ts', 700],
['e.spec.ts', 600],
['f.spec.ts', 500],
])
const buckets = assignWeightedPartitions([...weights.keys()], weights, 3)
const sums = buckets.map(bucket => bucket.reduce((sum, file) => sum + (weights.get(file) ?? 0), 0))
expect(Math.max(...sums)).toBeLessThanOrEqual(1550)
})
it('leaves trailing empty buckets when files are scarce', () => {
const files = ['a.spec.ts', 'b.spec.ts']
const buckets = assignWeightedPartitions(files, new Map(), 4)
expect(buckets.map(bucket => bucket.length).sort()).toEqual([0, 0, 1, 1])
})
it('returns one empty bucket per partition for an empty inventory', () => {
expect(assignWeightedPartitions([], new Map(), 3)).toEqual([[], [], []])
})
it('assigns unknown-weight files evenly', () => {
const files = ['a.spec.ts', 'b.spec.ts', 'c.spec.ts', 'd.spec.ts']
const buckets = assignWeightedPartitions(files, new Map(), 2)
expect(buckets.map(bucket => bucket.length).sort()).toEqual([2, 2])
})
})
describe('coverage file inventory', () => {
it('parses vitest list --filesOnly output, keeps project ownership, and drops exempt suites', async () => {
const root = await temporaryRoot()
const exemptDir = join(root, 'packages/experimental/webworker-runtime/tests')
await mkdir(exemptDir, { recursive: true })
await writeFile(join(exemptDir, 'transform-corpus.spec.ts'), '')
const output = [
'[thread-safe] packages/a/tests/a.spec.ts',
'[process-bound] packages/b/tests/b.spec.ts',
'[thread-safe] packages/experimental/webworker-runtime/tests/transform-corpus.spec.ts',
'not a test line',
].join('\n')
const inventory = parseListOutput(output, root)
expect(inventory.files).toEqual([
'packages/a/tests/a.spec.ts',
'packages/b/tests/b.spec.ts',
])
expect(inventory.projectOf.get('packages/a/tests/a.spec.ts')).toBe('thread-safe')
expect(inventory.projectOf.get('packages/b/tests/b.spec.ts')).toBe('process-bound')
})
it('removes every Typert package from instrumented files and project assignments', async () => {
const root = await temporaryRoot()
const typertFiles = [
'packages/typert/generator/tests/type-model.spec.ts',
'packages/typert/loader/tests/loader.spec.ts',
'packages/typert/protocol/tests/protocol.spec.ts',
'packages/typert/registry/tests/typert.spec.ts',
'packages/typert/future/tests/nested/client.spec.tsx',
]
for (const file of typertFiles) {
await mkdir(dirname(join(root, file)), { recursive: true })
await writeFile(join(root, file), '')
}
const retained = 'packages/api/gateway/tests/rpc.spec.ts'
const inventory = parseListOutput(
[...typertFiles, retained].map(file => `[thread-safe] ${file}`).join('\n'),
root,
)
expect(inventory.files).toEqual([retained])
expect([...inventory.projectOf]).toEqual([[retained, 'thread-safe']])
})
it('averages recorded durations per file from the results cache', async () => {
const root = await temporaryRoot()
await writeVitestCache(root, [
['thread-safe:packages/a/tests/x.spec.ts', { duration: 10 }],
['process-bound:packages/a/tests/x.spec.ts', { duration: 30 }],
['thread-safe:packages/a/tests/y.spec.ts', { duration: 5 }],
])
const durations = readFileDurations(root)
expect(durations.get('packages/a/tests/x.spec.ts')).toBe(20)
expect(durations.get('packages/a/tests/y.spec.ts')).toBe(5)
})
it('prefers the persisted duration file over the vitest cache', async () => {
const root = await temporaryRoot()
await writeVitestCache(root, [['thread-safe:packages/a/tests/x.spec.ts', { duration: 100 }]])
writeFileDurations(root, new Map([['packages/a/tests/x.spec.ts', 42]]))
expect(readFileDurations(root).get('packages/a/tests/x.spec.ts')).toBe(42)
})
it('merges new durations into the persisted file', async () => {
const root = await temporaryRoot()
writeFileDurations(root, new Map([['packages/a/tests/x.spec.ts', 42]]))
writeFileDurations(root, new Map([
['packages/a/tests/x.spec.ts', 55],
['packages/a/tests/y.spec.ts', 7],
]))
const durations = readFileDurations(root)
expect(durations.get('packages/a/tests/x.spec.ts')).toBe(55)
expect(durations.get('packages/a/tests/y.spec.ts')).toBe(7)
})
it('extracts per-file durations from compact partition timing reports', async () => {
const root = await temporaryRoot()
const report = join(root, 'partition-1.times.json')
await writeFile(report, JSON.stringify({
'packages/a/tests/x.spec.ts': 500,
'packages/a/tests/y.spec.ts': 0,
'packages/a/tests/invalid.spec.ts': -1,
'packages/a/tests/malformed.spec.ts': 'unknown',
}))
const durations = collectPartitionDurations([report])
expect(durations.get('packages/a/tests/x.spec.ts')).toBe(500)
expect(durations.get('packages/a/tests/y.spec.ts')).toBe(0)
expect(durations.size).toBe(2)
})
it.each(['null', '[]', '42', '"invalid"', '{'])('ignores corrupt timing metadata %s', async (contents) => {
const root = await temporaryRoot()
const report = join(root, 'partition.times.json')
await writeFile(report, contents)
expect(collectPartitionDurations([report])).toEqual(new Map())
})
})
/**
* One source statement as two Vite environments spell it: the ssr map ends the
* declaration at its identifier, the jsdom/client map at the nested call, and
* both stop at their own line's end.
*/
const CRASH_LOOP = { start: { line: 36, column: 2 }, end: { line: 54, column: Infinity } }
/** File path the canonicalization fixtures share. */
const CANONICALIZED_FILE = 'packages/util/home-paths/src/index.ts'
/** Istanbul statement location in the fixture's own terms. */
interface StatementLocation {
start: { line: number; column: number }
end: { line: number; column: number }
}
/** Statement hits and locations read back from a merged istanbul map. */
interface MergedStatements {
s: Record<string, number>
statementMap: Record<string, StatementLocation>
}
/** The istanbul entry points the merge assertions use. */
interface CoverageLibrary {
createCoverageMap: (data: unknown) => {
merge: (data: unknown) => void
fileCoverageFor: (file: string) => MergedStatements
}
}
/**
* istanbul-lib-coverage is the merge implementation the partition blobs feed.
* It arrives as a dependency of the declared istanbul-lib-report devDependency,
* so this spec resolves it through that owner instead of declaring its own copy.
*/
const coverageLibrary = createRequire(
createRequire(import.meta.url).resolve('istanbul-lib-report'),
)('istanbul-lib-coverage') as CoverageLibrary
/** One file's statement coverage, shaped as a partition blob carries it. */
function statementRecord(statements: StatementLocation[], hits: number[]): Record<string, unknown> {
return {
[CANONICALIZED_FILE]: {
path: CANONICALIZED_FILE,
statementMap: Object.fromEntries(statements.map((location, index) => [index, location])),
s: Object.fromEntries(hits.map((hit, index) => [index, hit])),
fnMap: {},
f: {},
branchMap: {},
b: {},
},
}
}
/**
* Merge partition records the way the coordinator's merge command does. The
* blob serializer's JSON hop turns a non-finite end column into `null`, which
* is what strips istanbul of the range containment it reconciles records with.
* Canonicalization runs on the `CoverageMap` the reporter hook receives, whose
* `data` holds the raw per-file records merged here.
*/
function mergePartitionRecords(
records: Array<Record<string, unknown>>,
canonicalize: boolean,
): MergedStatements {
const map = coverageLibrary.createCoverageMap({})
for (const record of records) {
if (canonicalize) canonicalizeEndOfLineColumns({ data: record })
const serializedRecord: unknown = JSON.parse(JSON.stringify(record))
map.merge(serializedRecord)
}
return map.fileCoverageFor(CANONICALIZED_FILE)
}
/** Start positions of the statements the merged map still reports as unhit. */
function uncoveredStarts(coverage: MergedStatements): Array<{ line: number; column: number }> {
return Object.entries(coverage.s)
.filter(([, hits]) => hits === 0)
.map(([index]) => coverage.statementMap[index])
.filter((location): location is StatementLocation => location !== undefined)
.map(location => location.start)
}
describe('coverage location canonicalization', () => {
// index.ts:48 as the ssr environment spells it (at `parent`) and as the
// client environment spells it (at `dirname(current)`); the loop holding
// both spellings ran in each, and only the client record never took the
// catch branch that owns the statement.
const ssrRecord = statementRecord(
[CRASH_LOOP, { start: { line: 48, column: 12 }, end: { line: 48, column: Infinity } }],
[3, 2],
)
const clientRecord = statementRecord(
[CRASH_LOOP, { start: { line: 48, column: 21 }, end: { line: 48, column: Infinity } }],
[5, 0],
)
it('drops the phantom statement a client-only spelling leaves after the blob merge', () => {
// 33 statements, 32 covered: the ssr spelling is hit while the client
// spelling of the same source statement stays unhit, so a file whose every
// statement ran fails the per-file 100% gate.
expect(uncoveredStarts(mergePartitionRecords([ssrRecord, clientRecord], false)))
.toEqual([{ line: 48, column: 21 }])
expect(uncoveredStarts(mergePartitionRecords([ssrRecord, clientRecord], true))).toEqual([])
})
it('canonicalizes line-end columns in statement, function, and branch locations', () => {
const lineEnd = (line: number): StatementLocation => ({
start: { line, column: 4 },
end: { line, column: Infinity },
})
const record = {
[CANONICALIZED_FILE]: {
path: CANONICALIZED_FILE,
statementMap: { 0: lineEnd(10), 1: { start: { line: 11, column: 4 }, end: { line: 11, column: 9 } } },
s: { 0: 1, 1: 1 },
fnMap: { 0: { name: 'probe', decl: lineEnd(10), loc: lineEnd(10) } },
f: { 0: 1 },
branchMap: { 0: { type: 'if', loc: lineEnd(12), locations: [lineEnd(12), lineEnd(13)] } },
b: { 0: [1, 1] },
},
}
canonicalizeEndOfLineColumns({ data: record })
const file = record[CANONICALIZED_FILE]
expect(file.statementMap[0]?.end.column).toBe(END_OF_LINE_COLUMN)
expect(file.statementMap[1]?.end.column).toBe(9)
expect(file.fnMap[0]?.decl.end.column).toBe(END_OF_LINE_COLUMN)
expect(file.fnMap[0]?.loc.end.column).toBe(END_OF_LINE_COLUMN)
expect(file.branchMap[0]?.loc.end.column).toBe(END_OF_LINE_COLUMN)
expect(file.branchMap[0]?.locations[0]?.end.column).toBe(END_OF_LINE_COLUMN)
expect(file.branchMap[0]?.locations[1]?.end.column).toBe(END_OF_LINE_COLUMN)
})
it('rejects a payload that carries no istanbul coverage data', () => {
// Vitest types the reporter hook as `unknown`, so a payload without the
// istanbul `data` record must fail the partition loudly rather than leave
// every location uncanonicalized.
expect(() => {
canonicalizeEndOfLineColumns(undefined)
}).toThrow(/not an istanbul CoverageMap/)
expect(() => {
canonicalizeEndOfLineColumns({})
}).toThrow(/not an istanbul CoverageMap/)
})
})
describe('coverage partition coordinator', () => {
const weightedFiles = ['a.spec.ts', 'b.spec.ts', 'c.spec.ts']
const weightedDurations = new Map([
['a.spec.ts', 100],
['b.spec.ts', 50],
['c.spec.ts', 10],
])
const weightedProjects = new Map([
['a.spec.ts', 'thread-safe'],
['b.spec.ts', 'process-bound'],
['c.spec.ts', 'process-bound'],
])
it('passes the canonicalizing reporter to every partition', async () => {
const root = await temporaryRoot()
const commands: CoverageCommand[] = []
const runCommand = successfulCommandRecorder(commands)
const coordinator = new CoveragePartitionCoordinator({
root,
partitions: 2,
pnpmEntrypoint: '/pnpm.cjs',
files: ['a.spec.ts', 'b.spec.ts'],
runCommand,
})
await expect(coordinator.run()).resolves.toBe(0)
// The coordinator passes a root-relative argument and runs every child with
// the repository root as its working directory, so the argument must name
// the reporter that sits beside this spec. Whether that reporter runs
// before the blob write is Vitest's reporter ordering, not a command the
// coordinator builds.
const specDirectory = dirname(fileURLToPath(import.meta.url))
const reporterPath = join(specDirectory, 'coverage-canonical-locations.ts')
for (const command of commands.slice(0, 2)) {
const argument = command.args.find(candidate => candidate.startsWith('--reporter=') && candidate.endsWith('coverage-canonical-locations.ts'))
if (argument === undefined) throw new Error(`${command.label} does not wire the coverage canonicalizer`)
expect(resolve(specDirectory, '..', argument.slice('--reporter='.length))).toBe(reporterPath)
}
})
it('runs every single-worker partition before one merged threshold check', async () => {
const root = await temporaryRoot()
const commands: CoverageCommand[] = []
const partitionConfigs: string[] = []
const runCommand = vi.fn(async (command: CoverageCommand) => {
commands.push(command)
const configArgument = command.args.find(argument => argument.startsWith('--config='))
if (configArgument !== undefined) {
partitionConfigs.push(await readFile(join(root, configArgument.slice('--config='.length)), 'utf8'))
}
await writeBlob(command)
return passed
})
const coordinator = new CoveragePartitionCoordinator({
root,
partitions: 3,
pnpmEntrypoint: '/pnpm.cjs',
vitestArgs: ['--testTimeout=30000'],
files: ['a.spec.ts', 'b.spec.ts', 'c.spec.ts'],
runCommand,
})
await expect(coordinator.run()).resolves.toBe(0)
expect(commands.map(command => command.label)).toEqual([
'partition 1/3',
'partition 2/3',
'partition 3/3',
'merged coverage report',
])
for (const command of commands.slice(0, 3)) {
expect(command.command).toBe(process.execPath)
expect(command.args[0]).toBe('/pnpm.cjs')
expect(command.args).toEqual(expect.arrayContaining([
'--coverage',
'--coverage.reportOnFailure',
'--maxWorkers=1',
'--reporter=default',
'--reporter=blob',
'--testTimeout=30000',
]))
expect(command.args).not.toContain('--reporter=json')
expect(command.args).toContain('--reporter=./scripts/coverage-file-times.ts')
expect(command.args).not.toContain('--shard=1/3')
expect(command.args.some(argument => argument.startsWith('--config='))).toBe(true)
expect(command.env).toEqual({
[COVERAGE_PARTITIONS_ENV]: undefined,
[COVERAGE_PARTITION_MODE_ENV]: '1',
})
}
// The partition file list travels in a temporary config, not on the
// command line (which exceeds the Windows CreateProcess limit).
expect(partitionConfigs).toHaveLength(3)
const allConfigs = partitionConfigs.join('\n')
expect(allConfigs).toContain('a.spec.ts')
expect(allConfigs).toContain('b.spec.ts')
expect(allConfigs).toContain('c.spec.ts')
for (const source of partitionConfigs) {
expect(source).toContain("from '../../vitest.config.ts'")
expect(source).toContain("from '../../scripts/coverage-fork-diagnostics.ts'")
expect(source).toContain('pool: coverageForkPool')
}
const mergeCommand = commands[3]
if (mergeCommand === undefined) throw new Error('coverage merge command was not observed')
expect(mergeCommand.args).toContain('--coverage')
expect(mergeCommand.args.some(argument => argument.startsWith('--merge-reports='))).toBe(true)
expect(mergeCommand.env).toEqual({
[COVERAGE_PARTITIONS_ENV]: undefined,
[COVERAGE_PARTITION_MODE_ENV]: undefined,
})
})
it('rejects an empty partition assignment before spawning any command', async () => {
const root = await temporaryRoot()
const runCommand = vi.fn()
const coordinator = new CoveragePartitionCoordinator({
root,
partitions: 3,
pnpmEntrypoint: '/pnpm.cjs',
// One file for three partitions leaves two buckets empty; an empty
// bucket would make Vitest run the whole suite.
files: ['a.spec.ts'],
runCommand,
})
await expect(coordinator.run()).rejects.toThrow('partition 2/3 has no files')
expect(runCommand).not.toHaveBeenCalled()
})
it('starts the heaviest partition first for fail-fast', async () => {
const root = await temporaryRoot()
const configContents = await runCoordinatorReadingConfigs(root, {
partitions: 2,
files: weightedFiles,
weights: weightedDurations,
})
// LPT: p1=[a] sum 100, p2=[b,c] sum 60; sorted heaviest-first makes
// partition 1/2 the heavy one, so its config names a.spec.ts.
expect(configContents.get('partition 1/2')).toContain('a.spec.ts')
expect(configContents.get('partition 1/2')).not.toContain('b.spec.ts')
})
it('gives each project only its own files in the partition config', async () => {
const root = await temporaryRoot()
const configContents = await runCoordinatorReadingConfigs(root, {
partitions: 2,
files: weightedFiles,
weights: weightedDurations,
projectOf: weightedProjects,
})
const allConfigs = [...configContents.values()].join('\n')
// The process-bound project must not receive the thread-safe file and
// vice versa, or plain files would run twice.
expect(allConfigs).toContain("include: project.test.name === 'process-bound' ?")
expect(allConfigs).not.toContain('"a.spec.ts","b.spec.ts","c.spec.ts"')
})
it('runs a native pnpm entrypoint directly', async () => {
const root = await temporaryRoot()
const commands: CoverageCommand[] = []
const runCommand = successfulCommandRecorder(commands)
const coordinator = new CoveragePartitionCoordinator({
root,
partitions: 2,
pnpmEntrypoint: '/tools/pnpm',
files: ['a.spec.ts', 'b.spec.ts'],
runCommand,
})
await expect(coordinator.run()).resolves.toBe(0)
expect(commands).toHaveLength(3)
for (const command of commands) {
expect(command.command).toBe('/tools/pnpm')
expect(command.args[0]).toBe('exec')
}
})
it('merges normal test failures and returns their failed status', async () => {
const root = await temporaryRoot()
const reported = vi.spyOn(console, 'error').mockImplementation(() => undefined)
const runCommand = vi.fn(async (command: CoverageCommand) => {
await writeBlob(command)
return command.label === 'partition 2/2'
? { exitCode: 1, signalCode: null, outputTail: 'specific Vitest failure' }
: passed
})
const coordinator = new CoveragePartitionCoordinator({
root,
partitions: 2,
pnpmEntrypoint: '/pnpm.cjs',
files: ['a.spec.ts', 'b.spec.ts'],
runCommand,
})
await expect(coordinator.run()).resolves.toBe(1)
expect(reported).toHaveBeenCalledWith('coverage-partitions: FAIL partition 2/2 (exit 1)')
expect(reported).toHaveBeenCalledWith(
'coverage-partitions: output tail for partition 2/2:\nspecific Vitest failure',
)
expect(runCommand).toHaveBeenCalledTimes(3)
})
it('rejects a missing partition blob before merge', async () => {
const root = await temporaryRoot()
const runCommand = vi.fn(async (command: CoverageCommand) => {
if (command.label !== 'partition 2/2') await writeBlob(command)
return passed
})
const coordinator = new CoveragePartitionCoordinator({
root,
partitions: 2,
pnpmEntrypoint: '/pnpm.cjs',
files: ['a.spec.ts', 'b.spec.ts'],
runCommand,
})
await expect(coordinator.run()).rejects.toThrow('coverage partitions produced')
expect(runCommand).toHaveBeenCalledTimes(2)
})
it('reports signal termination before missing-blob validation', async () => {
const root = await temporaryRoot()
const reported = vi.spyOn(console, 'error').mockImplementation(() => undefined)
const runCommand = vi.fn(async (command: CoverageCommand) => {
if (command.label === 'partition 1/2') await writeBlob(command)
return command.label === 'partition 2/2'
? { exitCode: null, signalCode: 'SIGTERM' as const }
: passed
})
const coordinator = new CoveragePartitionCoordinator({
root,
partitions: 2,
pnpmEntrypoint: '/pnpm.cjs',
files: ['a.spec.ts', 'b.spec.ts'],
runCommand,
})
await expect(coordinator.run()).rejects.toThrow('coverage partitions produced')
expect(reported).toHaveBeenCalledWith('coverage-partitions: FAIL partition 2/2 (signal SIGTERM)')
})
it('waits for every partition after one spawn failure', async () => {
const root = await temporaryRoot()
const reported = vi.spyOn(console, 'error').mockImplementation(() => undefined)
let secondFinished = false
const runCommand = vi.fn(async (command: CoverageCommand) => {
await writeBlob(command)
if (command.label === 'partition 1/2') {
return { exitCode: null, signalCode: null, error: 'spawn unavailable' }
}
if (command.label === 'partition 2/2') secondFinished = true
return passed
})
const coordinator = new CoveragePartitionCoordinator({
root,
partitions: 2,
pnpmEntrypoint: '/pnpm.cjs',
files: ['a.spec.ts', 'b.spec.ts'],
runCommand,
})
await expect(coordinator.run()).resolves.toBe(1)
expect(reported).toHaveBeenCalledWith('coverage-partitions: FAIL partition 1/2 (spawn unavailable)')
expect(secondFinished).toBe(true)
expect(runCommand).toHaveBeenCalledTimes(3)
})
it('unlinks a link-shaped coverage path without touching its target', async () => {
const root = await temporaryRoot()
const target = await temporaryRoot()
const marker = join(target, 'marker.txt')
await writeFile(marker, 'owned elsewhere')
await symlink(target, join(root, 'coverage'), process.platform === 'win32' ? 'junction' : 'dir')
const runCommand = vi.fn(async (command: CoverageCommand) => {
await writeBlob(command)
return passed
})
const coordinator = new CoveragePartitionCoordinator({
root,
partitions: 2,
pnpmEntrypoint: '/pnpm.cjs',
files: ['a.spec.ts', 'b.spec.ts'],
runCommand,
})
await expect(coordinator.run()).resolves.toBe(0)
await expect(access(marker)).resolves.toBeUndefined()
})
})