From 1087809633cf2e9f9278f03e981007338b6c8dce Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 13 Feb 2026 19:45:11 +0900 Subject: [PATCH] fix: fix teardown timeout of `aroundEach/All` when inner `aroundEach/All` failed --- packages/runner/src/run.ts | 16 +++- test/cli/test/around-each.test.ts | 130 ++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/packages/runner/src/run.ts b/packages/runner/src/run.ts index 398008044aec..59c1a6251d08 100644 --- a/packages/runner/src/run.ts +++ b/packages/runner/src/run.ts @@ -342,13 +342,23 @@ async function callAroundHooks( setupTimeout.clear() // Run inner hooks - don't time this against our teardown timeout - await runNextHook(index + 1) + let nextError: { value: unknown } | undefined + try { + await runNextHook(index + 1) + } + catch (value) { + nextError = { value } + } // Start teardown timer after inner hooks complete - only times this hook's teardown code teardownTimeout = createTimeoutPromise(timeout, 'teardown', stackTraceError) // Signal that use() is returning (teardown phase starting) resolveUseReturned() + + if (nextError) { + throw nextError.value + } } // Start setup timeout @@ -394,11 +404,11 @@ async function callAroundHooks( try { await Promise.race([ hookCompletePromise, - teardownTimeout!.promise, + teardownTimeout?.promise, ]) } finally { - teardownTimeout!.clear() + teardownTimeout?.clear() } } diff --git a/test/cli/test/around-each.test.ts b/test/cli/test/around-each.test.ts index 0bfdea07c076..ad6c695ec40d 100644 --- a/test/cli/test/around-each.test.ts +++ b/test/cli/test/around-each.test.ts @@ -1892,3 +1892,133 @@ test('tests are skipped when aroundAll setup fails', async () => { } `) }) + +test('aroundEach teardown timeout works when runTest error is caught', async () => { + const { stderr, errorTree } = await runInlineTests({ + 'caught-inner-error-timeout.test.ts': ` + import { aroundEach, afterAll, describe, expect, test } from 'vitest' + + let errorCaught = false + + afterAll(() => { + expect(errorCaught).toBe(true) + }) + + describe('suite', () => { + aroundEach(async (runTest) => { + try { + await runTest() + } + catch { + errorCaught = true + } + // this should timeout + await new Promise(resolve => setTimeout(resolve, 200)) + }, 50) + + aroundEach(async (runTest) => { + await runTest() + throw new Error('inner aroundEach teardown failure') + }) + + test('test', () => { + expect(1).toBe(1) + }) + }) + `, + }) + + expect(stderr).toMatchInlineSnapshot(` + " + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL caught-inner-error-timeout.test.ts > suite > test + AroundHookTeardownError: The teardown phase of "aroundEach" hook timed out after 50ms. + ❯ caught-inner-error-timeout.test.ts:11:9 + 9| + 10| describe('suite', () => { + 11| aroundEach(async (runTest) => { + | ^ + 12| try { + 13| await runTest() + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ + + " + `) + expect(errorTree()).toMatchInlineSnapshot(` + { + "caught-inner-error-timeout.test.ts": { + "suite": { + "test": [ + "The teardown phase of "aroundEach" hook timed out after 50ms.", + ], + }, + }, + } + `) +}) + +test('aroundAll teardown timeout works when runTest error is caught', async () => { + const { stderr, errorTree } = await runInlineTests({ + 'caught-inner-error-timeout.test.ts': ` + import { aroundAll, afterAll, describe, expect, test } from 'vitest' + + let errorCaught = false + + afterAll(() => { + expect(errorCaught).toBe(true) + }) + + describe('suite', () => { + aroundAll(async (runTest) => { + try { + await runTest() + } + catch { + errorCaught = true + } + // this should timeout + await new Promise(resolve => setTimeout(resolve, 200)) + }, 50) + + aroundAll(async (runTest) => { + await runTest() + throw new Error('inner aroundAll teardown failure') + }) + + test('test', () => { + expect(1).toBe(1) + }) + }) + `, + }) + + expect(stderr).toMatchInlineSnapshot(` + " + ⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL caught-inner-error-timeout.test.ts > suite + AroundHookTeardownError: The teardown phase of "aroundAll" hook timed out after 50ms. + ❯ caught-inner-error-timeout.test.ts:11:9 + 9| + 10| describe('suite', () => { + 11| aroundAll(async (runTest) => { + | ^ + 12| try { + 13| await runTest() + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ + + " + `) + expect(errorTree()).toMatchInlineSnapshot(` + { + "caught-inner-error-timeout.test.ts": { + "suite": { + "test": "passed", + }, + }, + } + `) +})