Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions packages/runner/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,14 @@ async function callAroundHooks<THook extends Function>(
timeout: number,
phase: 'setup' | 'teardown',
stackTraceError: Error | undefined,
): { promise: Promise<never>; clear: () => void } => {
): { promise: Promise<never>; isTimedOut: () => boolean; clear: () => void } => {
let timer: ReturnType<typeof setTimeout> | undefined
let timedout = false

const promise = new Promise<never>((_, reject) => {
if (timeout > 0 && timeout !== Number.POSITIVE_INFINITY) {
timer = setTimeout(() => {
timedout = true
const error = makeAroundHookTimeoutError(hookName, phase, timeout, stackTraceError)
onTimeout?.(error)
reject(error)
Expand All @@ -292,7 +294,7 @@ async function callAroundHooks<THook extends Function>(
}
}

return { promise, clear }
return { promise, clear, isTimedOut: () => timedout }
}

const runNextHook = async (index: number): Promise<void> => {
Expand All @@ -305,8 +307,8 @@ async function callAroundHooks<THook extends Function>(
const stackTraceError = getAroundHookStackTrace(hook)

let useCalled = false
let setupTimeout: { promise: Promise<never>; clear: () => void }
let teardownTimeout: { promise: Promise<never>; clear: () => void } | undefined
let setupTimeout: ReturnType<typeof createTimeoutPromise>
let teardownTimeout: ReturnType<typeof createTimeoutPromise> | undefined

// Promise that resolves when use() is called (setup phase complete)
let resolveUseCalled!: () => void
Expand All @@ -329,6 +331,14 @@ async function callAroundHooks<THook extends Function>(
})

const use = async () => {
// shouldn't continue to next (runTest/Suite or inner aroundEach/All) when aroundEach/All setup timed out.
if (setupTimeout.isTimedOut()) {
// we can throw any error to bail out.
// this error is not seen by end users since `runNextHook` already rejected with timeout error
// and this error is caught by `rejectHookComplete`.
throw new Error('__VITEST_INTERNAL_AROUND_HOOK_ABORT__')
}

if (useCalled) {
throw new AroundHookMultipleCallsError(
`The \`${callbackName}\` callback was called multiple times in the \`${hookName}\` hook. `
Expand Down
14 changes: 7 additions & 7 deletions packages/runner/src/types/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,28 @@ import type {
export interface VitestRunnerConfig {
root: string
setupFiles: string[]
name?: string
name: string | undefined
passWithNoTests: boolean
testNamePattern?: RegExp
allowOnly?: boolean
testNamePattern: RegExp | undefined
allowOnly: boolean
sequence: {
shuffle?: boolean
concurrent?: boolean
seed: number
hooks: SequenceHooks
setupFiles: SequenceSetupFiles
}
chaiConfig?: {
chaiConfig: {
truncateThreshold?: number
}
} | undefined
maxConcurrency: number
testTimeout: number
hookTimeout: number
retry: SerializableRetry
includeTaskLocation?: boolean
includeTaskLocation: boolean | undefined
diffOptions?: DiffOptions
tags: TestTagDefinition[]
tagsFilter?: string[]
tagsFilter: string[] | undefined
strictTags: boolean
}

Expand Down
5 changes: 4 additions & 1 deletion packages/utils/src/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ function baseFormat(args: unknown[], options: FormatOptions = {}): string {
if (typeof value === 'bigint') {
return `${value.toString()}n`
}
if (typeof value === 'symbol') {
return 'NaN'
}
return Number(value).toString()
}
case '%i': {
Expand Down Expand Up @@ -221,7 +224,7 @@ function baseFormat(args: unknown[], options: FormatOptions = {}): string {

for (let x = args[i]; i < len; x = args[++i]) {
if (x === null || typeof x !== 'object') {
str += ` ${x}`
str += ` ${typeof x === 'symbol' ? x.toString() : x}`
}
else {
str += ` ${formatArg(x)}`
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions test/cli/dts/exact-optional-property/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "@vitest/test-integration-dts-exact-optional-property",
"type": "module",
"private": true,
"scripts": {
"test": "tsc -b"
},
"devDependencies": {
"@vitest/runner": "workspace:*",
"vitest": "workspace:*"
}
}
8 changes: 8 additions & 0 deletions test/cli/dts/exact-optional-property/src/reporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { File } from '@vitest/runner'
import { DefaultReporter } from 'vitest/reporters'

export class MyReporter extends DefaultReporter {
override reportTestSummary(files: File[], errors: unknown[], leakCount: number): void {
super.reportTestSummary(files, errors, leakCount)
}
}
4 changes: 4 additions & 0 deletions test/cli/dts/exact-optional-property/src/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { VitestRunner } from '@vitest/runner'
import { VitestTestRunner } from 'vitest/runners'

export class MyRunner extends VitestTestRunner implements VitestRunner {}
16 changes: 16 additions & 0 deletions test/cli/dts/exact-optional-property/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": ["../_shared/tsconfig.patch.json"],
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"exactOptionalPropertyTypes": true,
"declaration": true,
"declarationMap": true,
"outDir": "dist",
"sourceMap": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true
}
}
138 changes: 138 additions & 0 deletions test/cli/test/around-each.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2022,3 +2022,141 @@ test('aroundAll teardown timeout works when runTest error is caught', async () =
}
`)
})

test('aroundEach aborts late runTest after setup timeout', async () => {
const { stdout, stderr, errorTree } = await runInlineTests({
'late-run-test-after-timeout.test.ts': `
import { afterAll, aroundEach, test } from 'vitest'

afterAll(async () => {
console.log('>> afterAll 0ms')
await new Promise(r => setTimeout(r, 200))
console.log('>> afterAll 200ms')
})

aroundEach(async (runTest) => {
console.log(">> outer aroundEach setup 0ms")
await new Promise(r => setTimeout(r, 100))
// this is still executed after timeout error
console.log(">> outer aroundEach setup 100ms")
// but this shouldn't continue to inner aroundEach or test
await runTest()
console.log(">> outer aroundEach teardown")
}, 10)

aroundEach(async (runTest) => {
console.log('>> inner aroundEach setup')
await runTest()
console.log('>> inner aroundEach teardown')
})

test('basic', () => {
console.log('>> test')
})
`,
})

expect(stderr).toMatchInlineSnapshot(`
"
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯

FAIL late-run-test-after-timeout.test.ts > basic
AroundHookSetupError: The setup phase of "aroundEach" hook timed out after 10ms.
❯ late-run-test-after-timeout.test.ts:10:7
8| })
9|
10| aroundEach(async (runTest) => {
| ^
11| console.log(">> outer aroundEach setup 0ms")
12| await new Promise(r => setTimeout(r, 100))

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

"
`)
expect(extractLogs(stdout)).toMatchInlineSnapshot(`
">> outer aroundEach setup 0ms
>> afterAll 0ms
>> outer aroundEach setup 100ms
>> afterAll 200ms"
`)
expect(errorTree()).toMatchInlineSnapshot(`
{
"late-run-test-after-timeout.test.ts": {
"basic": [
"The setup phase of "aroundEach" hook timed out after 10ms.",
],
},
}
`)
})

test('aroundAll aborts late runSuite after setup timeout', async () => {
const { stdout, stderr, errorTree } = await runInlineTests({
'late-run-suite-after-timeout.test.ts': `
import { afterAll, aroundAll, describe, test } from 'vitest'

afterAll(async () => {
console.log('>> afterAll 0ms')
await new Promise(r => setTimeout(r, 200))
console.log('>> afterAll 200ms')
})

describe('timed out suite', () => {
aroundAll(async (runSuite) => {
console.log('>> outer aroundAll setup 0ms')
await new Promise(r => setTimeout(r, 100))
// this is still executed after timeout error
console.log('>> outer aroundAll setup 100ms')
// but this should not continue to inner aroundAll or tests
await runSuite()
console.log('>> outer aroundAll teardown')
}, 10)

aroundAll(async (runSuite) => {
console.log('>> inner aroundAll setup')
await runSuite()
console.log('>> inner aroundAll teardown')
})

test('basic', () => {
console.log('>> test')
})
})
`,
})

expect(stderr).toMatchInlineSnapshot(`
"
⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯

FAIL late-run-suite-after-timeout.test.ts > timed out suite
AroundHookSetupError: The setup phase of "aroundAll" hook timed out after 10ms.
❯ late-run-suite-after-timeout.test.ts:11:9
9|
10| describe('timed out suite', () => {
11| aroundAll(async (runSuite) => {
| ^
12| console.log('>> outer aroundAll setup 0ms')
13| await new Promise(r => setTimeout(r, 100))

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

"
`)
expect(extractLogs(stdout)).toMatchInlineSnapshot(`
">> outer aroundAll setup 0ms
>> afterAll 0ms
>> outer aroundAll setup 100ms
>> afterAll 200ms"
`)
expect(errorTree()).toMatchInlineSnapshot(`
{
"late-run-suite-after-timeout.test.ts": {
"timed out suite": {
"basic": "skipped",
},
},
}
`)
})
8 changes: 8 additions & 0 deletions test/core/test/utils-display.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,39 @@ describe('format', () => {
}
})('string value'),
],
['%s', Symbol('test')],
['%d', 100],
['%d', 100n],
['%d', null],
['%d', {}],
['%d', {}, 'next'],
['%d', Symbol('test')],
['%i', 100],
['%i', 100n],
['%i', null],
['%i', {}],
['%i', {}, 'next'],
['%i', Symbol('test')],
['%f', 100],
['%f', 100n],
['%f', null],
['%f', {}],
['%f', {}, 'next'],
['%f', Symbol('test')],
['%o', 'string'],
['%o', 100],
['%o', 100n],
['%o', null],
['%o', {}],
['%o', {}, 'next'],
['%o', Symbol('test')],
['%O', 'string'],
['%O', 100],
['%O', 100n],
['%O', null],
['%O', {}],
['%O', {}, 'next'],
['%O', Symbol('test')],
['%c', 'css value'],
['%c', 'css value', 'some other value'],
['%c %f', 'css value', '100.00'],
Expand All @@ -64,7 +70,9 @@ describe('format', () => {
['%j', {}, 'next'],
['%j', { obj }],
['%j', { fn: () => {} }],
['%j', Symbol('test')],
['%%', 'string'],
['prefix', Symbol('test')],
])('format(%s)', (formatString, ...args) => {
expect(format(formatString, ...args), `failed ${formatString}`).toBe(util.format(formatString, ...args))
})
Expand Down
Loading