@@ -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 (
@@ -959,30 +1025,46 @@ class Test extends AsyncResource {
959
1025
const runArgs = ArrayPrototypeSlice ( args ) ;
960
1026
ArrayPrototypeUnshift ( runArgs , this . fn , ctx ) ;
961
1027
1028
+ const promises = [ ] ;
962
1029
if ( this . fn . length === runArgs . length - 1 ) {
963
- // This test is using legacy Node.js error first callbacks.
1030
+ // This test is using legacy Node.js error- first callbacks.
964
1031
const { promise, cb } = createDeferredCallback ( ) ;
965
-
966
1032
ArrayPrototypePush ( runArgs , cb ) ;
1033
+
967
1034
const ret = ReflectApply ( this . runInAsyncScope , this , runArgs ) ;
968
1035
969
1036
if ( isPromise ( ret ) ) {
970
1037
this . fail ( new ERR_TEST_FAILURE (
971
1038
'passed a callback but also returned a Promise' ,
972
1039
kCallbackAndPromisePresent ,
973
1040
) ) ;
974
- await SafePromiseRace ( [ ret , stopPromise ] ) ;
1041
+ ArrayPrototypePush ( promises , ret ) ;
975
1042
} else {
976
- await SafePromiseRace ( [ PromiseResolve ( promise ) , stopPromise ] ) ;
1043
+ ArrayPrototypePush ( promises , PromiseResolve ( promise ) ) ;
977
1044
}
978
1045
} else {
979
1046
// This test is synchronous or using Promises.
980
1047
const promise = ReflectApply ( this . runInAsyncScope , this , runArgs ) ;
981
- await SafePromiseRace ( [ PromiseResolve ( promise ) , stopPromise ] ) ;
1048
+ ArrayPrototypePush ( promises , PromiseResolve ( promise ) ) ;
982
1049
}
983
1050
1051
+ ArrayPrototypePush ( promises , stopPromise ) ;
1052
+
1053
+ // Wait for the race to finish
1054
+ await SafePromiseRace ( promises ) ;
1055
+
984
1056
this [ kShouldAbort ] ( ) ;
985
1057
this . plan ?. check ( ) ;
1058
+
1059
+ if ( this . plan !== null ) {
1060
+ const planPromise = this . plan ?. check ( ) ;
1061
+ // If the plan returns a promise, it means that it is waiting for more assertions to be made before
1062
+ // continuing.
1063
+ if ( planPromise ) {
1064
+ await SafePromiseRace ( [ planPromise , stopPromise ] ) ;
1065
+ }
1066
+ }
1067
+
986
1068
this . pass ( ) ;
987
1069
await afterEach ( ) ;
988
1070
await after ( ) ;
0 commit comments