Skip to content

Commit 5e69aba

Browse files
authored
fix: own the post-restart rerun, keep process.exit disabled in workers (#10963)
1 parent cf9176b commit 5e69aba

8 files changed

Lines changed: 69 additions & 48 deletions

File tree

packages/vitest/src/node/cache/files.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ export class FilesStatsCache {
2121
}
2222

2323
public async updateStats(fsPath: string, key: string): Promise<void> {
24-
if (!fs.existsSync(fsPath)) {
25-
return
24+
try {
25+
const stats = await fs.promises.stat(fsPath)
26+
this.cache.set(key, { size: stats.size })
27+
}
28+
catch {
29+
// the file can be deleted while the stat is in flight; a file
30+
// without stats only loses sorting heuristics
2631
}
27-
const stats = await fs.promises.stat(fsPath)
28-
this.cache.set(key, { size: stats.size })
2932
}
3033

3134
public removeStats(fsPath: string): void {

packages/vitest/src/node/cli/cli-api.ts

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,20 @@ export async function startVitest(
129129
stdinCleanup = registerConsoleShortcuts(ctx, stdin, stdout)
130130
}
131131

132-
ctx.onAfterSetServer(() => {
133-
if (ctx.config.standalone) {
134-
ctx.standalone()
132+
ctx.onAfterSetServer(async () => {
133+
if (ctx.closingPromise) {
134+
return
135135
}
136-
else {
137-
ctx.start(cliFilters)
136+
try {
137+
if (ctx.config.standalone) {
138+
await ctx.standalone()
139+
}
140+
else {
141+
await ctx.start(cliFilters)
142+
}
143+
}
144+
catch (error) {
145+
reportStartError(ctx, error)
138146
}
139147
})
140148

@@ -157,27 +165,7 @@ export async function startVitest(
157165
return ctx
158166
}
159167
catch (e) {
160-
if (e instanceof FilesNotFoundError) {
161-
return ctx
162-
}
163-
164-
if (e instanceof GitNotFoundError) {
165-
ctx.logger.error(e.message)
166-
return ctx
167-
}
168-
169-
if (
170-
e instanceof IncludeTaskLocationDisabledError
171-
|| e instanceof RangeLocationFilterProvidedError
172-
|| e instanceof LocationFilterFileNotFoundError
173-
) {
174-
ctx.logger.printError(e, { verbose: false })
175-
return ctx
176-
}
177-
178-
process.exitCode = 1
179-
ctx.logger.printError(e, { fullStack: true, type: 'Unhandled Error' })
180-
ctx.logger.error('\n\n')
168+
reportStartError(ctx, e)
181169
return ctx
182170
}
183171
finally {
@@ -188,6 +176,30 @@ export async function startVitest(
188176
}
189177
}
190178

179+
function reportStartError(ctx: Vitest, error: unknown): void {
180+
if (error instanceof FilesNotFoundError) {
181+
return
182+
}
183+
184+
if (error instanceof GitNotFoundError) {
185+
ctx.logger.error(error.message)
186+
return
187+
}
188+
189+
if (
190+
error instanceof IncludeTaskLocationDisabledError
191+
|| error instanceof RangeLocationFilterProvidedError
192+
|| error instanceof LocationFilterFileNotFoundError
193+
) {
194+
ctx.logger.printError(error, { verbose: false })
195+
return
196+
}
197+
198+
process.exitCode = 1
199+
ctx.logger.printError(error, { fullStack: true, type: 'Unhandled Error' })
200+
ctx.logger.error('\n\n')
201+
}
202+
191203
export async function prepareVitest(
192204
options?: CliOptions,
193205
viteOverrides?: ViteUserConfig,

packages/vitest/src/node/core.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,10 @@ export class Vitest {
347347
|| this.projects.some(p => p.vite.config.configFile === file)
348348
|| this.config._containerConfigFiles?.includes(file)
349349
if (isConfig) {
350-
await this._restart('config')
350+
// a floating rejection in an event handler would crash the process
351+
await this._restart('config').catch((error) => {
352+
this.logger.printError(error, { fullStack: true, type: 'Restart Error' })
353+
})
351354
}
352355
})
353356

packages/vitest/src/runtime/workers/base.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ async function startModuleRunner(options: ContextModuleRunnerOptions): Promise<T
2727
return _moduleRunner
2828
}
2929

30+
const state = () => getSafeWorkerState() || options.state
31+
3032
process.exit = (code = process.exitCode || 0): never => {
31-
throw new Error(`process.exit unexpectedly called with "${code}"`)
33+
const filepath = state().filepath
34+
throw new Error(`process.exit unexpectedly called with "${code}"${filepath ? ` (test file: ${filepath})` : ''}`)
3235
}
33-
const state = () => getSafeWorkerState() || options.state
3436

3537
listenForErrors(state)
3638

packages/vitest/src/runtime/workers/init-forks.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,15 @@ export default function workerInit(options: {
4141
teardown: () => {
4242
processRemoveAllListeners('message')
4343
processOff('error', onError)
44+
// the guard installed by the test runner stays active between test
45+
// files: with `isolate: false` a late process.exit would kill the
46+
// other files sharing this process
47+
process.exit = processExit
4448
},
45-
runTests: (state, traces) => executeTests('run', state, traces),
46-
collectTests: (state, traces) => executeTests('collect', state, traces),
49+
runTests: (state, traces) => runTests('run', state, traces),
50+
collectTests: (state, traces) => runTests('collect', state, traces),
4751
setup: options.setup,
4852
})
49-
50-
async function executeTests(method: 'run' | 'collect', state: WorkerGlobalState, traces: Traces) {
51-
try {
52-
await runTests(method, state, traces)
53-
}
54-
finally {
55-
process.exit = processExit
56-
}
57-
}
5853
}
5954

6055
// Prevent leaving worker in loops where it tries to send message to closed main

packages/vitest/src/runtime/workers/vm.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ export async function runVmTests(method: 'run' | 'collect', state: WorkerGlobalS
119119
})
120120

121121
process.exit = (code = process.exitCode || 0): never => {
122-
throw new Error(`process.exit unexpectedly called with "${code}"`)
122+
const filepath = state.filepath
123+
throw new Error(`process.exit unexpectedly called with "${code}"${filepath ? ` (test file: ${filepath})` : ''}`)
123124
}
124125

125126
listenForErrors(() => state)

test/e2e/test/cli-config.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { resolve } from 'pathe'
2-
import { expect, it, test } from 'vitest'
2+
import { expect, it, onTestFinished, test } from 'vitest'
33
import { createVitest } from 'vitest/node'
44
import { runVitest, useFS } from '../../test-utils'
55

66
test('can pass down the config as a module', async () => {
77
const vitest = await createVitest('test', {
88
config: '@test/test-dep-config',
99
})
10+
onTestFinished(() => vitest.close())
1011

1112
expect(vitest.vite.config.configFile).toBe(
1213
resolve(import.meta.dirname, '../deps/test-dep-config/index.js'),

test/e2e/test/config/browser-configs.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,12 @@ const vitest = vi.defineHelper(async (options: TestUserConfig & { $viteConfig?:
4242
vitestOptions,
4343
)
4444
onTestFinished(async () => {
45-
await vitest.vite.waitForRequestsIdle()
46-
await vitest.close()
45+
try {
46+
await vitest.vite.waitForRequestsIdle()
47+
}
48+
finally {
49+
await vitest.close()
50+
}
4751
})
4852
return vitest
4953
})

0 commit comments

Comments
 (0)