@@ -176,22 +176,88 @@ function testMatchesPattern(test, patterns) {
176
176
}
177
177
178
178
class TestPlan {
179
- constructor ( count ) {
179
+ #waitIndefinitely = false ;
180
+ #planPromise = null ;
181
+ #timeoutId = null ;
182
+
183
+ constructor ( count , options = kEmptyObject ) {
180
184
validateUint32 ( count , 'count' ) ;
185
+ validateObject ( options , 'options' ) ;
181
186
this . expected = count ;
182
187
this . actual = 0 ;
188
+
189
+ const { wait } = options ;
190
+ if ( typeof wait === 'boolean' ) {
191
+ this . wait = wait ;
192
+ this . #waitIndefinitely = wait ;
193
+ } else if ( typeof wait === 'number' ) {
194
+ validateNumber ( wait , 'options.wait' , 0 , TIMEOUT_MAX ) ;
195
+ this . wait = wait ;
196
+ } else if ( wait !== undefined ) {
197
+ throw new ERR_INVALID_ARG_TYPE ( 'options.wait' , [ 'boolean' , 'number' ] , wait ) ;
198
+ }
199
+ }
200
+
201
+ #planMet( ) {
202
+ return this . actual === this . expected ;
203
+ }
204
+
205
+ #createTimeout( reject ) {
206
+ return setTimeout ( ( ) => {
207
+ const err = new ERR_TEST_FAILURE (
208
+ `plan timed out after ${ this . wait } ms with ${ this . actual } assertions when expecting ${ this . expected } ` ,
209
+ kTestTimeoutFailure ,
210
+ ) ;
211
+ reject ( err ) ;
212
+ } , this . wait ) ;
183
213
}
184
214
185
215
check ( ) {
186
- if ( this . actual !== this . expected ) {
216
+ if ( this . #planMet( ) ) {
217
+ if ( this . #timeoutId) {
218
+ clearTimeout ( this . #timeoutId) ;
219
+ this . #timeoutId = null ;
220
+ }
221
+ if ( this . #planPromise) {
222
+ const { resolve } = this . #planPromise;
223
+ resolve ( ) ;
224
+ this . #planPromise = null ;
225
+ }
226
+ return ;
227
+ }
228
+
229
+ if ( ! this . #shouldWait( ) ) {
187
230
throw new ERR_TEST_FAILURE (
188
231
`plan expected ${ this . expected } assertions but received ${ this . actual } ` ,
189
232
kTestCodeFailure ,
190
233
) ;
191
234
}
235
+
236
+ if ( ! this . #planPromise) {
237
+ const { promise, resolve, reject } = PromiseWithResolvers ( ) ;
238
+ this . #planPromise = { __proto__ : null , promise, resolve, reject } ;
239
+
240
+ if ( ! this . #waitIndefinitely) {
241
+ this . #timeoutId = this . #createTimeout( reject ) ;
242
+ }
243
+ }
244
+
245
+ return this . #planPromise. promise ;
246
+ }
247
+
248
+ count ( ) {
249
+ this . actual ++ ;
250
+ if ( this . #planPromise) {
251
+ this . check ( ) ;
252
+ }
253
+ }
254
+
255
+ #shouldWait( ) {
256
+ return this . wait !== undefined && this . wait !== false ;
192
257
}
193
258
}
194
259
260
+
195
261
class TestContext {
196
262
#assert;
197
263
#test;
@@ -228,15 +294,15 @@ class TestContext {
228
294
this . #test. diagnostic ( message ) ;
229
295
}
230
296
231
- plan ( count ) {
297
+ plan ( count , options = kEmptyObject ) {
232
298
if ( this . #test. plan !== null ) {
233
299
throw new ERR_TEST_FAILURE (
234
300
'cannot set plan more than once' ,
235
301
kTestCodeFailure ,
236
302
) ;
237
303
}
238
304
239
- this . #test. plan = new TestPlan ( count ) ;
305
+ this . #test. plan = new TestPlan ( count , options ) ;
240
306
}
241
307
242
308
get assert ( ) {
@@ -249,7 +315,7 @@ class TestContext {
249
315
map . forEach ( ( method , name ) => {
250
316
assert [ name ] = ( ...args ) => {
251
317
if ( plan !== null ) {
252
- plan . actual ++ ;
318
+ plan . count ( ) ;
253
319
}
254
320
return ReflectApply ( method , this , args ) ;
255
321
} ;
@@ -260,7 +326,7 @@ class TestContext {
260
326
// stacktrace from the correct starting point.
261
327
function ok ( ...args ) {
262
328
if ( plan !== null ) {
263
- plan . actual ++ ;
329
+ plan . count ( ) ;
264
330
}
265
331
innerOk ( ok , args . length , ...args ) ;
266
332
}
@@ -296,7 +362,7 @@ class TestContext {
296
362
297
363
const { plan } = this . #test;
298
364
if ( plan !== null ) {
299
- plan . actual ++ ;
365
+ plan . count ( ) ;
300
366
}
301
367
302
368
const subtest = this . #test. createSubtest (
@@ -968,35 +1034,49 @@ class Test extends AsyncResource {
968
1034
const runArgs = ArrayPrototypeSlice ( args ) ;
969
1035
ArrayPrototypeUnshift ( runArgs , this . fn , ctx ) ;
970
1036
1037
+ const promises = [ ] ;
971
1038
if ( this . fn . length === runArgs . length - 1 ) {
972
- // This test is using legacy Node.js error first callbacks.
1039
+ // This test is using legacy Node.js error- first callbacks.
973
1040
const { promise, cb } = createDeferredCallback ( ) ;
974
-
975
1041
ArrayPrototypePush ( runArgs , cb ) ;
1042
+
976
1043
const ret = ReflectApply ( this . runInAsyncScope , this , runArgs ) ;
977
1044
978
1045
if ( isPromise ( ret ) ) {
979
1046
this . fail ( new ERR_TEST_FAILURE (
980
1047
'passed a callback but also returned a Promise' ,
981
1048
kCallbackAndPromisePresent ,
982
1049
) ) ;
983
- await SafePromiseRace ( [ ret , stopPromise ] ) ;
1050
+ ArrayPrototypePush ( promises , ret ) ;
984
1051
} else {
985
- await SafePromiseRace ( [ PromiseResolve ( promise ) , stopPromise ] ) ;
1052
+ ArrayPrototypePush ( promises , PromiseResolve ( promise ) ) ;
986
1053
}
987
1054
} else {
988
1055
// This test is synchronous or using Promises.
989
1056
const promise = ReflectApply ( this . runInAsyncScope , this , runArgs ) ;
990
- await SafePromiseRace ( [ PromiseResolve ( promise ) , stopPromise ] ) ;
1057
+ ArrayPrototypePush ( promises , PromiseResolve ( promise ) ) ;
991
1058
}
992
1059
1060
+ ArrayPrototypePush ( promises , stopPromise ) ;
1061
+
1062
+ // Wait for the race to finish
1063
+ await SafePromiseRace ( promises ) ;
1064
+
993
1065
this [ kShouldAbort ] ( ) ;
994
1066
995
1067
if ( this . subtestsPromise !== null ) {
996
1068
await SafePromiseRace ( [ this . subtestsPromise . promise , stopPromise ] ) ;
997
1069
}
998
1070
999
- this . plan ?. check ( ) ;
1071
+ if ( this . plan !== null ) {
1072
+ const planPromise = this . plan ?. check ( ) ;
1073
+ // If the plan returns a promise, it means that it is waiting for more assertions to be made before
1074
+ // continuing.
1075
+ if ( planPromise ) {
1076
+ await SafePromiseRace ( [ planPromise , stopPromise ] ) ;
1077
+ }
1078
+ }
1079
+
1000
1080
this . pass ( ) ;
1001
1081
await afterEach ( ) ;
1002
1082
await after ( ) ;
0 commit comments