1
1
'use strict' ;
2
2
const {
3
3
ArrayPrototypePush,
4
+ ArrayPrototypeReduce,
4
5
ArrayPrototypeShift,
6
+ ArrayPrototypeSlice,
5
7
ArrayPrototypeUnshift,
6
8
FunctionPrototype,
7
9
Number,
10
+ ObjectSeal,
8
11
PromisePrototypeThen,
9
12
PromiseResolve,
10
13
ReflectApply,
@@ -31,7 +34,7 @@ const {
31
34
kEmptyObject,
32
35
} = require ( 'internal/util' ) ;
33
36
const { isPromise } = require ( 'internal/util/types' ) ;
34
- const { isUint32, validateAbortSignal } = require ( 'internal/validators' ) ;
37
+ const { isUint32, validateAbortSignal, validateOneOf } = require ( 'internal/validators' ) ;
35
38
const { setTimeout } = require ( 'timers/promises' ) ;
36
39
const { cpus } = require ( 'os' ) ;
37
40
const { bigint : hrtime } = process . hrtime ;
@@ -50,6 +53,8 @@ const testOnlyFlag = !isTestRunner && getOptionValue('--test-only');
50
53
const rootConcurrency = isTestRunner ? cpus ( ) . length : 1 ;
51
54
52
55
const kShouldAbort = Symbol ( 'kShouldAbort' ) ;
56
+ const kRunHook = Symbol ( 'kRunHook' ) ;
57
+ const kHookNames = ObjectSeal ( [ 'before' , 'after' , 'beforeEach' , 'afterEach' ] ) ;
53
58
54
59
55
60
function stopTest ( timeout , signal ) {
@@ -75,6 +80,10 @@ class TestContext {
75
80
return this . #test. signal ;
76
81
}
77
82
83
+ get name ( ) {
84
+ return this . #test. name ;
85
+ }
86
+
78
87
diagnostic ( message ) {
79
88
this . #test. diagnostic ( message ) ;
80
89
}
@@ -97,6 +106,14 @@ class TestContext {
97
106
98
107
return subtest . start ( ) ;
99
108
}
109
+
110
+ beforeEach ( fn , options ) {
111
+ this . #test. createHook ( 'beforeEach' , fn , options ) ;
112
+ }
113
+
114
+ afterEach ( fn , options ) {
115
+ this . #test. createHook ( 'afterEach' , fn , options ) ;
116
+ }
100
117
}
101
118
102
119
class Test extends AsyncResource {
@@ -185,6 +202,12 @@ class Test extends AsyncResource {
185
202
this . pendingSubtests = [ ] ;
186
203
this . readySubtests = new SafeMap ( ) ;
187
204
this . subtests = [ ] ;
205
+ this . hooks = {
206
+ before : [ ] ,
207
+ after : [ ] ,
208
+ beforeEach : [ ] ,
209
+ afterEach : [ ] ,
210
+ } ;
188
211
this . waitingOn = 0 ;
189
212
this . finished = false ;
190
213
}
@@ -303,10 +326,19 @@ class Test extends AsyncResource {
303
326
kCancelledByParent
304
327
)
305
328
) ;
329
+ this . startTime = this . startTime || this . endTime ; // If a test was canceled before it was started, e.g inside a hook
306
330
this . cancelled = true ;
307
331
this . #abortController. abort ( ) ;
308
332
}
309
333
334
+ createHook ( name , fn , options ) {
335
+ validateOneOf ( name , 'hook name' , kHookNames ) ;
336
+ // eslint-disable-next-line no-use-before-define
337
+ const hook = new TestHook ( fn , options ) ;
338
+ ArrayPrototypePush ( this . hooks [ name ] , hook ) ;
339
+ return hook ;
340
+ }
341
+
310
342
fail ( err ) {
311
343
if ( this . error !== null ) {
312
344
return ;
@@ -370,26 +402,41 @@ class Test extends AsyncResource {
370
402
return { ctx, args : [ ctx ] } ;
371
403
}
372
404
405
+ async [ kRunHook ] ( hook , args ) {
406
+ validateOneOf ( hook , 'hook name' , kHookNames ) ;
407
+ await ArrayPrototypeReduce ( this . hooks [ hook ] , async ( prev , hook ) => {
408
+ await prev ;
409
+ await hook . run ( args ) ;
410
+ } , PromiseResolve ( ) ) ;
411
+ }
412
+
373
413
async run ( ) {
374
- this . parent . activeSubtests ++ ;
414
+ if ( this . parent !== null ) {
415
+ this . parent . activeSubtests ++ ;
416
+ }
375
417
this . startTime = hrtime ( ) ;
376
418
377
419
if ( this [ kShouldAbort ] ( ) ) {
378
420
this . postRun ( ) ;
379
421
return ;
380
422
}
381
423
424
+ const { args, ctx } = this . getRunArgs ( ) ;
425
+ if ( this . parent ?. hooks . beforeEach . length > 0 ) {
426
+ await this . parent [ kRunHook ] ( 'beforeEach' , { args, ctx } ) ;
427
+ }
428
+
382
429
try {
383
430
const stopPromise = stopTest ( this . timeout , this . signal ) ;
384
- const { args , ctx } = this . getRunArgs ( ) ;
385
- ArrayPrototypeUnshift ( args , this . fn , ctx ) ; // Note that if it's not OK to mutate args, we need to first clone it.
431
+ const runArgs = ArrayPrototypeSlice ( args ) ;
432
+ ArrayPrototypeUnshift ( runArgs , this . fn , ctx ) ;
386
433
387
- if ( this . fn . length === args . length - 1 ) {
434
+ if ( this . fn . length === runArgs . length - 1 ) {
388
435
// This test is using legacy Node.js error first callbacks.
389
436
const { promise, cb } = createDeferredCallback ( ) ;
390
437
391
- ArrayPrototypePush ( args , cb ) ;
392
- const ret = ReflectApply ( this . runInAsyncScope , this , args ) ;
438
+ ArrayPrototypePush ( runArgs , cb ) ;
439
+ const ret = ReflectApply ( this . runInAsyncScope , this , runArgs ) ;
393
440
394
441
if ( isPromise ( ret ) ) {
395
442
this . fail ( new ERR_TEST_FAILURE (
@@ -402,7 +449,7 @@ class Test extends AsyncResource {
402
449
}
403
450
} else {
404
451
// This test is synchronous or using Promises.
405
- const promise = ReflectApply ( this . runInAsyncScope , this , args ) ;
452
+ const promise = ReflectApply ( this . runInAsyncScope , this , runArgs ) ;
406
453
await SafePromiseRace ( [ PromiseResolve ( promise ) , stopPromise ] ) ;
407
454
}
408
455
@@ -420,6 +467,10 @@ class Test extends AsyncResource {
420
467
}
421
468
}
422
469
470
+ if ( this . parent ?. hooks . afterEach . length > 0 ) {
471
+ await this . parent [ kRunHook ] ( 'afterEach' , { args, ctx } ) ;
472
+ }
473
+
423
474
// Clean up the test. Then, try to report the results and execute any
424
475
// tests that were pending due to available concurrency.
425
476
this . postRun ( ) ;
@@ -523,26 +574,47 @@ class Test extends AsyncResource {
523
574
}
524
575
}
525
576
577
+ class TestHook extends Test {
578
+ #args;
579
+ constructor ( fn , options ) {
580
+ if ( options === null || typeof options !== 'object' ) {
581
+ options = kEmptyObject ;
582
+ }
583
+ super ( { __proto__ : null , fn, ...options } ) ;
584
+ }
585
+ run ( args ) {
586
+ this . #args = args ;
587
+ return super . run ( ) ;
588
+ }
589
+ getRunArgs ( ) {
590
+ return this . #args;
591
+ }
592
+ }
593
+
526
594
class ItTest extends Test {
527
595
constructor ( opt ) { super ( opt ) ; } // eslint-disable-line no-useless-constructor
528
596
getRunArgs ( ) {
529
- return { ctx : { signal : this . signal } , args : [ ] } ;
597
+ return { ctx : { signal : this . signal , name : this . name } , args : [ ] } ;
530
598
}
531
599
}
532
600
class Suite extends Test {
533
601
constructor ( options ) {
534
602
super ( options ) ;
535
603
536
604
try {
537
- const context = { signal : this . signal } ;
538
- this . buildSuite = this . runInAsyncScope ( this . fn , context , [ context ] ) ;
605
+ const { ctx , args } = this . getRunArgs ( ) ;
606
+ this . buildSuite = this . runInAsyncScope ( this . fn , ctx , args ) ;
539
607
} catch ( err ) {
540
608
this . fail ( new ERR_TEST_FAILURE ( err , kTestCodeFailure ) ) ;
541
609
}
542
610
this . fn = ( ) => { } ;
543
611
this . buildPhaseFinished = true ;
544
612
}
545
613
614
+ getRunArgs ( ) {
615
+ return { ctx : { signal : this . signal , name : this . name } , args : [ ] } ;
616
+ }
617
+
546
618
start ( ) {
547
619
return this . run ( ) ;
548
620
}
@@ -562,11 +634,16 @@ class Suite extends Test {
562
634
return ;
563
635
}
564
636
637
+
638
+ const hookArgs = this . getRunArgs ( ) ;
639
+ await this [ kRunHook ] ( 'before' , hookArgs ) ;
565
640
const stopPromise = stopTest ( this . timeout , this . signal ) ;
566
641
const subtests = this . skipped || this . error ? [ ] : this . subtests ;
567
642
const promise = SafePromiseAll ( subtests , ( subtests ) => subtests . start ( ) ) ;
568
643
569
644
await SafePromiseRace ( [ promise , stopPromise ] ) ;
645
+ await this [ kRunHook ] ( 'after' , hookArgs ) ;
646
+
570
647
this . pass ( ) ;
571
648
this . postRun ( ) ;
572
649
}
0 commit comments