[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix: close vitest if it failed to start (#9573)

authored by

Vladimir and committed by
GitHub
(Feb 3, 2026, 3:11 PM +0100) 728ba617 c83395f2

+76 -26
+18
test/test-utils/index.ts
··· 31 31 export interface VitestRunnerCLIOptions { 32 32 std?: 'inherit' 33 33 fails?: boolean 34 + printExitCode?: boolean 34 35 preserveAnsi?: boolean 35 36 tty?: boolean 36 37 mode?: 'test' | 'benchmark' ··· 40 41 $viteConfig?: Omit<ViteUserConfig, 'test'> 41 42 $cliOptions?: TestCliOptions 42 43 } 44 + 45 + const process_ = process 43 46 44 47 /** 45 48 * The config is assumed to be the config on the fille system, not CLI options ··· 59 62 // Reset possible previous runs 60 63 process.exitCode = 0 61 64 let exitCode = process.exitCode 65 + 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 + } 62 77 63 78 // Prevent possible process.exit() calls, e.g. from --browser 64 79 const exit = process.exit ··· 194 209 cli.stderr += inspect(e) 195 210 } 196 211 finally { 212 + if (runnerOptions.printExitCode) { 213 + globalThis.process = process_ 214 + } 197 215 exitCode = process.exitCode 198 216 process.exitCode = 0 199 217
+2 -1
test/cli/test/bail-race.test.ts
··· 1 1 import { resolve } from 'pathe' 2 - import { expect, test } from 'vitest' 2 + import { expect, onTestFinished, test } from 'vitest' 3 3 import { createVitest } from 'vitest/node' 4 4 import { StableTestFileOrderSorter } from '../../test-utils' 5 5 ··· 21 21 }, 22 22 }], 23 23 }) 24 + onTestFinished(() => vitest.close()) 24 25 25 26 for (let i = 0; i <= 4; i++) { 26 27 await vitest.start()
+18 -4
test/cli/test/config-loader.test.ts
··· 4 4 const isTypeStrippingSupported = !!process.features.typescript 5 5 6 6 test.runIf(isTypeStrippingSupported)('configLoader native', async () => { 7 - const { stderr, exitCode } = await runVitest({ 7 + const { stderr, exitCode, ctx } = await runVitest({ 8 8 root: 'fixtures/config-loader', 9 + standalone: true, 10 + watch: true, 9 11 $cliOptions: { 10 12 configLoader: 'native', 11 13 }, 12 14 }) 15 + expect(ctx?.projects.map(p => p.name)).toMatchInlineSnapshot(` 16 + [ 17 + "node", 18 + "browser (chromium)", 19 + ] 20 + `) 13 21 expect(stderr).toBe('') 14 22 expect(exitCode).toBe(0) 15 23 }) 16 24 17 25 test('configLoader runner', async () => { 18 - const { vitest, exitCode } = await runVitest({ 26 + const { vitest, exitCode, ctx } = await runVitest({ 19 27 root: 'fixtures/config-loader', 28 + standalone: true, 29 + watch: true, 20 30 $cliOptions: { 21 31 configLoader: 'runner', 22 32 }, 23 33 }) 34 + expect(ctx?.projects.map(p => p.name)).toMatchInlineSnapshot(` 35 + [ 36 + "node", 37 + "browser (chromium)", 38 + ] 39 + `) 24 40 expect(vitest.stderr).toBe('') 25 - expect(vitest.stdout).toContain('✓ |node|') 26 - expect(vitest.stdout).toContain('✓ |browser (chromium)|') 27 41 expect(exitCode).toBe(0) 28 42 })
+2 -1
test/cli/test/create-vitest.test.ts
··· 1 1 import type { TestModule } from 'vitest/node' 2 - import { expect, it, vi } from 'vitest' 2 + import { expect, it, onTestFinished, vi } from 'vitest' 3 3 import { createVitest } from 'vitest/node' 4 4 5 5 it(createVitest, async () => { ··· 13 13 }, 14 14 ], 15 15 }) 16 + onTestFinished(() => ctx.close()) 16 17 const testFiles = await ctx.globTestSpecifications() 17 18 await ctx.runTestSpecifications(testFiles, false) 18 19
+6 -2
test/cli/test/network-imports.test.ts
··· 16 16 'forks', 17 17 'vmThreads', 18 18 ])('importing from network in %s', async (pool) => { 19 - const { ctx, exitCode } = await runVitest({ 19 + const { ctx, stderr, exitCode } = await runVitest({ 20 20 ...config, 21 21 root: './fixtures/network-imports', 22 22 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 + `) 24 28 expect(ctx!.state.getTestModules()).toHaveLength(1) 25 29 expect(ctx!.state.getTestModules()[0].state()).toBe('passed') 26 30 expect(exitCode).toBe(0)
+2 -1
test/cli/test/public-api.test.ts
··· 97 97 98 98 it('can modify the global test name pattern', async () => { 99 99 const { ctx } = await runVitest({ 100 + standalone: true, 101 + watch: true, 100 102 testNamePattern: 'custom', 101 - include: ['non-existing'], 102 103 }) 103 104 104 105 expect(ctx?.getGlobalTestNamePattern()).toEqual(/custom/)
+2 -1
test/cli/test/static-collect.test.ts
··· 1 1 import type { CliOptions, TestCase, TestModule, TestSuite } from 'vitest/node' 2 2 import { runVitest } from '#test-utils' 3 3 import { resolve } from 'pathe' 4 - import { expect, test } from 'vitest' 4 + import { expect, onTestFinished, test } from 'vitest' 5 5 import { createVitest, rolldownVersion } from 'vitest/node' 6 6 7 7 test('correctly collects a simple test', async () => { ··· 1078 1078 ], 1079 1079 }, 1080 1080 ) 1081 + onTestFinished(() => vitest.close()) 1081 1082 return vitest.experimental_parseSpecification( 1082 1083 vitest.getRootProject().createSpecification('simple.test.ts'), 1083 1084 )
+5 -2
packages/vitest/src/node/core.ts
··· 1392 1392 if (this.coreWorkspaceProject && !teardownProjects.includes(this.coreWorkspaceProject)) { 1393 1393 teardownProjects.push(this.coreWorkspaceProject) 1394 1394 } 1395 + const teardownErrors: unknown[] = [] 1395 1396 // do teardown before closing the server 1396 1397 for (const project of teardownProjects.reverse()) { 1397 - await project._teardownGlobalSetup() 1398 + await project._teardownGlobalSetup().catch((error) => { 1399 + teardownErrors.push(error) 1400 + }) 1398 1401 } 1399 1402 1400 1403 const closePromises: unknown[] = this.projects.map(w => w.close()) ··· 1415 1418 closePromises.push(...this._onClose.map(fn => fn())) 1416 1419 1417 1420 await Promise.allSettled(closePromises).then((results) => { 1418 - results.forEach((r) => { 1421 + [...results, ...teardownErrors.map(r => ({ status: 'rejected', reason: r }))].forEach((r) => { 1419 1422 if (r.status === 'rejected') { 1420 1423 this.logger.error('error during close', r.reason) 1421 1424 }
+15 -7
packages/vitest/src/node/create.ts
··· 43 43 plugins: await VitestPlugin(restOptions, ctx), 44 44 } 45 45 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 + ) 49 50 50 - if (ctx.config.api?.port) { 51 - await server.listen() 51 + if (ctx.config.api?.port) { 52 + await server.listen() 53 + } 54 + 55 + return ctx 52 56 } 53 - 54 - return ctx 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 + } 55 63 }
+6 -7
packages/vitest/src/node/cli/cli-api.ts
··· 106 106 else { 107 107 await ctx.start(cliFilters) 108 108 } 109 + return ctx 109 110 } 110 111 catch (e) { 111 112 if (e instanceof FilesNotFoundError) { ··· 131 132 ctx.logger.error('\n\n') 132 133 return ctx 133 134 } 134 - 135 - if (ctx.shouldKeepServer()) { 136 - return ctx 135 + finally { 136 + if (!ctx?.shouldKeepServer()) { 137 + stdinCleanup?.() 138 + await ctx.close() 139 + } 137 140 } 138 - 139 - stdinCleanup?.() 140 - await ctx.close() 141 - return ctx 142 141 } 143 142 144 143 export async function prepareVitest(