@@ -9,6 +9,8 @@ import type {
9
9
import { getSafeTimers } from '@vitest/utils'
10
10
import { PendingError } from './errors'
11
11
12
+ const now = Date . now
13
+
12
14
export const collectorContext : RuntimeContext = {
13
15
tasks : [ ] ,
14
16
currentSuite : null ,
@@ -41,19 +43,55 @@ export function withTimeout<T extends (...args: any[]) => any>(
41
43
42
44
// this function name is used to filter error in test/cli/test/fails.test.ts
43
45
return ( function runWithTimeout ( ...args : T extends ( ...args : infer A ) => any ? A : never ) {
44
- return Promise . race ( [
45
- new Promise ( ( resolve , reject ) => {
46
- const timer = setTimeout ( ( ) => {
47
- clearTimeout ( timer )
48
- reject ( new Error ( makeTimeoutMsg ( isHook , timeout ) ) )
49
- } , timeout )
50
- // `unref` might not exist in browser
51
- timer . unref ?.( )
52
- } ) ,
53
- Promise . resolve ( fn ( ...args ) ) . then ( ( result ) => {
54
- return new Promise ( resolve => setTimeout ( resolve , 0 , result ) )
55
- } ) ,
56
- ] ) as Awaitable < void >
46
+ const startTime = now ( )
47
+ return new Promise ( ( resolve_ , reject_ ) => {
48
+ const timer = setTimeout ( ( ) => {
49
+ clearTimeout ( timer )
50
+ reject ( new Error ( makeTimeoutMsg ( isHook , timeout ) ) )
51
+ } , timeout )
52
+ // `unref` might not exist in browser
53
+ timer . unref ?.( )
54
+
55
+ function resolve ( result : unknown ) {
56
+ clearTimeout ( timer )
57
+ resolve_ ( result )
58
+ }
59
+
60
+ function reject ( error : unknown ) {
61
+ clearTimeout ( timer )
62
+ reject_ ( error )
63
+ }
64
+
65
+ // sync test/hook will be caught by try/catch
66
+ try {
67
+ const result = fn ( ...args ) as PromiseLike < unknown >
68
+ // the result is a thenable, we don't wrap this in Promise.resolve
69
+ // to avoid creating new promises
70
+ if ( typeof result === 'object' && result != null && typeof result . then === 'function' ) {
71
+ result . then (
72
+ ( result ) => {
73
+ // if sync test/hook took too long, setTimeout won't be triggered,
74
+ // but we still need to fail the test, see
75
+ // https://github.com/vitest-dev/vitest/issues/2920
76
+ if ( now ( ) - startTime >= timeout ) {
77
+ reject ( new Error ( makeTimeoutMsg ( isHook , timeout ) ) )
78
+ }
79
+ else {
80
+ resolve ( result )
81
+ }
82
+ } ,
83
+ reject ,
84
+ )
85
+ }
86
+ else {
87
+ resolve ( result )
88
+ }
89
+ }
90
+ // user sync test/hook throws an error
91
+ catch ( error ) {
92
+ reject ( error )
93
+ }
94
+ } )
57
95
} ) as T
58
96
}
59
97
0 commit comments