Skip to content

Commit 728ba61

Browse files
authored
fix: close vitest if it failed to start (#9573)
1 parent c83395f commit 728ba61

File tree

10 files changed

+76
-26
lines changed

10 files changed

+76
-26
lines changed

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export async function startVitest(
106106
else {
107107
await ctx.start(cliFilters)
108108
}
109+
return ctx
109110
}
110111
catch (e) {
111112
if (e instanceof FilesNotFoundError) {
@@ -131,14 +132,12 @@ export async function startVitest(
131132
ctx.logger.error('\n\n')
132133
return ctx
133134
}
134-
135-
if (ctx.shouldKeepServer()) {
136-
return ctx
135+
finally {
136+
if (!ctx?.shouldKeepServer()) {
137+
stdinCleanup?.()
138+
await ctx.close()
139+
}
137140
}
138-
139-
stdinCleanup?.()
140-
await ctx.close()
141-
return ctx
142141
}
143142

144143
export async function prepareVitest(

packages/vitest/src/node/core.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,9 +1392,12 @@ export class Vitest {
13921392
if (this.coreWorkspaceProject && !teardownProjects.includes(this.coreWorkspaceProject)) {
13931393
teardownProjects.push(this.coreWorkspaceProject)
13941394
}
1395+
const teardownErrors: unknown[] = []
13951396
// do teardown before closing the server
13961397
for (const project of teardownProjects.reverse()) {
1397-
await project._teardownGlobalSetup()
1398+
await project._teardownGlobalSetup().catch((error) => {
1399+
teardownErrors.push(error)
1400+
})
13981401
}
13991402

14001403
const closePromises: unknown[] = this.projects.map(w => w.close())
@@ -1415,7 +1418,7 @@ export class Vitest {
14151418
closePromises.push(...this._onClose.map(fn => fn()))
14161419

14171420
await Promise.allSettled(closePromises).then((results) => {
1418-
results.forEach((r) => {
1421+
[...results, ...teardownErrors.map(r => ({ status: 'rejected', reason: r }))].forEach((r) => {
14191422
if (r.status === 'rejected') {
14201423
this.logger.error('error during close', r.reason)
14211424
}

packages/vitest/src/node/create.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,21 @@ export async function createVitest(
4343
plugins: await VitestPlugin(restOptions, ctx),
4444
}
4545

46-
const server = await createViteServer(
47-
mergeConfig(config, mergeConfig(viteOverrides, { root: options.root })),
48-
)
46+
try {
47+
const server = await createViteServer(
48+
mergeConfig(config, mergeConfig(viteOverrides, { root: options.root })),
49+
)
4950

50-
if (ctx.config.api?.port) {
51-
await server.listen()
52-
}
51+
if (ctx.config.api?.port) {
52+
await server.listen()
53+
}
5354

54-
return ctx
55+
return ctx
56+
}
57+
// Vitest can fail at any point inside "setServer" or inside a custom plugin
58+
// Then we need to make sure everything was properly closed (like the logger)
59+
catch (error) {
60+
await ctx.close()
61+
throw error
62+
}
5563
}

test/cli/test/bail-race.test.ts

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

@@ -21,6 +21,7 @@ test('cancels previous run before starting new one', async () => {
2121
},
2222
}],
2323
})
24+
onTestFinished(() => vitest.close())
2425

2526
for (let i = 0; i <= 4; i++) {
2627
await vitest.start()

test/cli/test/config-loader.test.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,39 @@ import { runVitest } from '../../test-utils'
44
const isTypeStrippingSupported = !!process.features.typescript
55

66
test.runIf(isTypeStrippingSupported)('configLoader native', async () => {
7-
const { stderr, exitCode } = await runVitest({
7+
const { stderr, exitCode, ctx } = await runVitest({
88
root: 'fixtures/config-loader',
9+
standalone: true,
10+
watch: true,
911
$cliOptions: {
1012
configLoader: 'native',
1113
},
1214
})
15+
expect(ctx?.projects.map(p => p.name)).toMatchInlineSnapshot(`
16+
[
17+
"node",
18+
"browser (chromium)",
19+
]
20+
`)
1321
expect(stderr).toBe('')
1422
expect(exitCode).toBe(0)
1523
})
1624

1725
test('configLoader runner', async () => {
18-
const { vitest, exitCode } = await runVitest({
26+
const { vitest, exitCode, ctx } = await runVitest({
1927
root: 'fixtures/config-loader',
28+
standalone: true,
29+
watch: true,
2030
$cliOptions: {
2131
configLoader: 'runner',
2232
},
2333
})
34+
expect(ctx?.projects.map(p => p.name)).toMatchInlineSnapshot(`
35+
[
36+
"node",
37+
"browser (chromium)",
38+
]
39+
`)
2440
expect(vitest.stderr).toBe('')
25-
expect(vitest.stdout).toContain('✓ |node|')
26-
expect(vitest.stdout).toContain('✓ |browser (chromium)|')
2741
expect(exitCode).toBe(0)
2842
})

test/cli/test/create-vitest.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { TestModule } from 'vitest/node'
2-
import { expect, it, vi } from 'vitest'
2+
import { expect, it, onTestFinished, vi } from 'vitest'
33
import { createVitest } from 'vitest/node'
44

55
it(createVitest, async () => {
@@ -13,6 +13,7 @@ it(createVitest, async () => {
1313
},
1414
],
1515
})
16+
onTestFinished(() => ctx.close())
1617
const testFiles = await ctx.globTestSpecifications()
1718
await ctx.runTestSpecifications(testFiles, false)
1819

test/cli/test/network-imports.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ it.runIf(Number(major) <= 20).each([
1616
'forks',
1717
'vmThreads',
1818
])('importing from network in %s', async (pool) => {
19-
const { ctx, exitCode } = await runVitest({
19+
const { ctx, stderr, exitCode } = await runVitest({
2020
...config,
2121
root: './fixtures/network-imports',
2222
pool,
23-
})
23+
}, [], { printExitCode: true })
24+
expect([...ctx!.state.errorsSet]).toStrictEqual([])
25+
expect(stderr.replace(/\(node:\d+\)/, '(node:\d+)')).toBe(`(node:d+) ExperimentalWarning: Network Imports is an experimental feature and might change at any time
26+
(Use \`node --trace-warnings ...\` to show where the warning was created)
27+
`)
2428
expect(ctx!.state.getTestModules()).toHaveLength(1)
2529
expect(ctx!.state.getTestModules()[0].state()).toBe('passed')
2630
expect(exitCode).toBe(0)

test/cli/test/public-api.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@ it.each([
9797

9898
it('can modify the global test name pattern', async () => {
9999
const { ctx } = await runVitest({
100+
standalone: true,
101+
watch: true,
100102
testNamePattern: 'custom',
101-
include: ['non-existing'],
102103
})
103104

104105
expect(ctx?.getGlobalTestNamePattern()).toEqual(/custom/)

test/cli/test/static-collect.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { CliOptions, TestCase, TestModule, TestSuite } from 'vitest/node'
22
import { runVitest } from '#test-utils'
33
import { resolve } from 'pathe'
4-
import { expect, test } from 'vitest'
4+
import { expect, onTestFinished, test } from 'vitest'
55
import { createVitest, rolldownVersion } from 'vitest/node'
66

77
test('correctly collects a simple test', async () => {
@@ -1078,6 +1078,7 @@ async function collectTestModule(code: string, options?: CliOptions) {
10781078
],
10791079
},
10801080
)
1081+
onTestFinished(() => vitest.close())
10811082
return vitest.experimental_parseSpecification(
10821083
vitest.getRootProject().createSpecification('simple.test.ts'),
10831084
)

test/test-utils/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ globalThis.__VITEST_GENERATE_UI_TOKEN__ = true
3131
export interface VitestRunnerCLIOptions {
3232
std?: 'inherit'
3333
fails?: boolean
34+
printExitCode?: boolean
3435
preserveAnsi?: boolean
3536
tty?: boolean
3637
mode?: 'test' | 'benchmark'
@@ -41,6 +42,8 @@ export interface RunVitestConfig extends TestUserConfig {
4142
$cliOptions?: TestCliOptions
4243
}
4344

45+
const process_ = process
46+
4447
/**
4548
* The config is assumed to be the config on the fille system, not CLI options
4649
* (Note that CLI only options like "standalone" are passed as CLI options, not config options)
@@ -60,6 +63,18 @@ export async function runVitest(
6063
process.exitCode = 0
6164
let exitCode = process.exitCode
6265

66+
if (runnerOptions.printExitCode) {
67+
globalThis.process = new Proxy(process_, {
68+
set(target, p, newValue, receiver) {
69+
if (p === 'exitCode') {
70+
// eslint-disable-next-line no-console
71+
console.trace('exitCode was set to', newValue)
72+
}
73+
return Reflect.set(target, p, newValue, receiver)
74+
},
75+
})
76+
}
77+
6378
// Prevent possible process.exit() calls, e.g. from --browser
6479
const exit = process.exit
6580
process.exit = (() => { }) as never
@@ -194,6 +209,9 @@ export async function runVitest(
194209
cli.stderr += inspect(e)
195210
}
196211
finally {
212+
if (runnerOptions.printExitCode) {
213+
globalThis.process = process_
214+
}
197215
exitCode = process.exitCode
198216
process.exitCode = 0
199217

0 commit comments

Comments
 (0)