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
MathMax,
8
10
Number,
11
+ ObjectSeal,
9
12
PromisePrototypeThen,
10
13
PromiseResolve,
11
14
ReflectApply,
@@ -22,12 +25,11 @@ const {
22
25
ERR_INVALID_ARG_TYPE ,
23
26
ERR_TEST_FAILURE ,
24
27
} ,
25
- kIsNodeError,
26
28
AbortError,
27
29
} = require ( 'internal/errors' ) ;
28
30
const { getOptionValue } = require ( 'internal/options' ) ;
29
31
const { TapStream } = require ( 'internal/test_runner/tap_stream' ) ;
30
- const { createDeferredCallback } = require ( 'internal/test_runner/utils' ) ;
32
+ const { createDeferredCallback, isTestFailureError } = require ( 'internal/test_runner/utils' ) ;
31
33
const {
32
34
createDeferredPromise,
33
35
kEmptyObject,
@@ -36,6 +38,7 @@ const { isPromise } = require('internal/util/types');
36
38
const {
37
39
validateAbortSignal,
38
40
validateNumber,
41
+ validateOneOf,
39
42
validateUint32,
40
43
} = require ( 'internal/validators' ) ;
41
44
const { setTimeout } = require ( 'timers/promises' ) ;
@@ -48,6 +51,7 @@ const kParentAlreadyFinished = 'parentAlreadyFinished';
48
51
const kSubtestsFailed = 'subtestsFailed' ;
49
52
const kTestCodeFailure = 'testCodeFailure' ;
50
53
const kTestTimeoutFailure = 'testTimeoutFailure' ;
54
+ const kHookFailure = 'hookFailed' ;
51
55
const kDefaultIndent = ' ' ;
52
56
const kDefaultTimeout = null ;
53
57
const noop = FunctionPrototype ;
@@ -56,6 +60,8 @@ const testOnlyFlag = !isTestRunner && getOptionValue('--test-only');
56
60
// TODO(cjihrig): Use uv_available_parallelism() once it lands.
57
61
const rootConcurrency = isTestRunner ? MathMax ( cpus ( ) . length - 1 , 1 ) : 1 ;
58
62
const kShouldAbort = Symbol ( 'kShouldAbort' ) ;
63
+ const kRunHook = Symbol ( 'kRunHook' ) ;
64
+ const kHookNames = ObjectSeal ( [ 'before' , 'after' , 'beforeEach' , 'afterEach' ] ) ;
59
65
60
66
61
67
function stopTest ( timeout , signal ) {
@@ -81,6 +87,10 @@ class TestContext {
81
87
return this . #test. signal ;
82
88
}
83
89
90
+ get name ( ) {
91
+ return this . #test. name ;
92
+ }
93
+
84
94
diagnostic ( message ) {
85
95
this . #test. diagnostic ( message ) ;
86
96
}
@@ -103,6 +113,14 @@ class TestContext {
103
113
104
114
return subtest . start ( ) ;
105
115
}
116
+
117
+ beforeEach ( fn , options ) {
118
+ this . #test. createHook ( 'beforeEach' , fn , options ) ;
119
+ }
120
+
121
+ afterEach ( fn , options ) {
122
+ this . #test. createHook ( 'afterEach' , fn , options ) ;
123
+ }
106
124
}
107
125
108
126
class Test extends AsyncResource {
@@ -207,6 +225,12 @@ class Test extends AsyncResource {
207
225
this . pendingSubtests = [ ] ;
208
226
this . readySubtests = new SafeMap ( ) ;
209
227
this . subtests = [ ] ;
228
+ this . hooks = {
229
+ before : [ ] ,
230
+ after : [ ] ,
231
+ beforeEach : [ ] ,
232
+ afterEach : [ ] ,
233
+ } ;
210
234
this . waitingOn = 0 ;
211
235
this . finished = false ;
212
236
}
@@ -325,10 +349,19 @@ class Test extends AsyncResource {
325
349
kCancelledByParent
326
350
)
327
351
) ;
352
+ this . startTime = this . startTime || this . endTime ; // If a test was canceled before it was started, e.g inside a hook
328
353
this . cancelled = true ;
329
354
this . #abortController. abort ( ) ;
330
355
}
331
356
357
+ createHook ( name , fn , options ) {
358
+ validateOneOf ( name , 'hook name' , kHookNames ) ;
359
+ // eslint-disable-next-line no-use-before-define
360
+ const hook = new TestHook ( fn , options ) ;
361
+ ArrayPrototypePush ( this . hooks [ name ] , hook ) ;
362
+ return hook ;
363
+ }
364
+
332
365
fail ( err ) {
333
366
if ( this . error !== null ) {
334
367
return ;
@@ -392,8 +425,27 @@ class Test extends AsyncResource {
392
425
return { ctx, args : [ ctx ] } ;
393
426
}
394
427
428
+ async [ kRunHook ] ( hook , args ) {
429
+ validateOneOf ( hook , 'hook name' , kHookNames ) ;
430
+ try {
431
+ await ArrayPrototypeReduce ( this . hooks [ hook ] , async ( prev , hook ) => {
432
+ await prev ;
433
+ await hook . run ( args ) ;
434
+ if ( hook . error ) {
435
+ throw hook . error ;
436
+ }
437
+ } , PromiseResolve ( ) ) ;
438
+ } catch ( err ) {
439
+ const error = new ERR_TEST_FAILURE ( `failed running ${ hook } hook` , kHookFailure ) ;
440
+ error . cause = isTestFailureError ( err ) ? err . cause : err ;
441
+ throw error ;
442
+ }
443
+ }
444
+
395
445
async run ( ) {
396
- this . parent . activeSubtests ++ ;
446
+ if ( this . parent !== null ) {
447
+ this . parent . activeSubtests ++ ;
448
+ }
397
449
this . startTime = hrtime ( ) ;
398
450
399
451
if ( this [ kShouldAbort ] ( ) ) {
@@ -402,16 +454,20 @@ class Test extends AsyncResource {
402
454
}
403
455
404
456
try {
405
- const stopPromise = stopTest ( this . timeout , this . signal ) ;
406
457
const { args, ctx } = this . getRunArgs ( ) ;
407
- ArrayPrototypeUnshift ( args , this . fn , ctx ) ; // Note that if it's not OK to mutate args, we need to first clone it.
458
+ if ( this . parent ?. hooks . beforeEach . length > 0 ) {
459
+ await this . parent [ kRunHook ] ( 'beforeEach' , { args, ctx } ) ;
460
+ }
461
+ const stopPromise = stopTest ( this . timeout , this . signal ) ;
462
+ const runArgs = ArrayPrototypeSlice ( args ) ;
463
+ ArrayPrototypeUnshift ( runArgs , this . fn , ctx ) ;
408
464
409
- if ( this . fn . length === args . length - 1 ) {
465
+ if ( this . fn . length === runArgs . length - 1 ) {
410
466
// This test is using legacy Node.js error first callbacks.
411
467
const { promise, cb } = createDeferredCallback ( ) ;
412
468
413
- ArrayPrototypePush ( args , cb ) ;
414
- const ret = ReflectApply ( this . runInAsyncScope , this , args ) ;
469
+ ArrayPrototypePush ( runArgs , cb ) ;
470
+ const ret = ReflectApply ( this . runInAsyncScope , this , runArgs ) ;
415
471
416
472
if ( isPromise ( ret ) ) {
417
473
this . fail ( new ERR_TEST_FAILURE (
@@ -424,7 +480,7 @@ class Test extends AsyncResource {
424
480
}
425
481
} else {
426
482
// This test is synchronous or using Promises.
427
- const promise = ReflectApply ( this . runInAsyncScope , this , args ) ;
483
+ const promise = ReflectApply ( this . runInAsyncScope , this , runArgs ) ;
428
484
await SafePromiseRace ( [ PromiseResolve ( promise ) , stopPromise ] ) ;
429
485
}
430
486
@@ -433,9 +489,13 @@ class Test extends AsyncResource {
433
489
return ;
434
490
}
435
491
492
+ if ( this . parent ?. hooks . afterEach . length > 0 ) {
493
+ await this . parent [ kRunHook ] ( 'afterEach' , { args, ctx } ) ;
494
+ }
495
+
436
496
this . pass ( ) ;
437
497
} catch ( err ) {
438
- if ( err ?. code === 'ERR_TEST_FAILURE' && kIsNodeError in err ) {
498
+ if ( isTestFailureError ( err ) ) {
439
499
if ( err . failureType === kTestTimeoutFailure ) {
440
500
this . cancel ( err ) ;
441
501
} else {
@@ -549,20 +609,38 @@ class Test extends AsyncResource {
549
609
}
550
610
}
551
611
612
+ class TestHook extends Test {
613
+ #args;
614
+ constructor ( fn , options ) {
615
+ if ( options === null || typeof options !== 'object' ) {
616
+ options = kEmptyObject ;
617
+ }
618
+ const { timeout, signal } = options ;
619
+ super ( { __proto__ : null , fn, timeout, signal } ) ;
620
+ }
621
+ run ( args ) {
622
+ this . #args = args ;
623
+ return super . run ( ) ;
624
+ }
625
+ getRunArgs ( ) {
626
+ return this . #args;
627
+ }
628
+ }
629
+
552
630
class ItTest extends Test {
553
631
constructor ( opt ) { super ( opt ) ; } // eslint-disable-line no-useless-constructor
554
632
getRunArgs ( ) {
555
- return { ctx : { signal : this . signal } , args : [ ] } ;
633
+ return { ctx : { signal : this . signal , name : this . name } , args : [ ] } ;
556
634
}
557
635
}
558
636
class Suite extends Test {
559
637
constructor ( options ) {
560
638
super ( options ) ;
561
639
562
640
try {
563
- const context = { signal : this . signal } ;
641
+ const { ctx , args } = this . getRunArgs ( ) ;
564
642
this . buildSuite = PromisePrototypeThen (
565
- PromiseResolve ( this . runInAsyncScope ( this . fn , context , [ context ] ) ) ,
643
+ PromiseResolve ( this . runInAsyncScope ( this . fn , ctx , args ) ) ,
566
644
undefined ,
567
645
( err ) => {
568
646
this . fail ( new ERR_TEST_FAILURE ( err , kTestCodeFailure ) ) ;
@@ -574,23 +652,40 @@ class Suite extends Test {
574
652
this . buildPhaseFinished = true ;
575
653
}
576
654
655
+ getRunArgs ( ) {
656
+ return { ctx : { signal : this . signal , name : this . name } , args : [ ] } ;
657
+ }
658
+
577
659
async run ( ) {
578
- this . parent . activeSubtests ++ ;
579
- await this . buildSuite ;
580
- this . startTime = hrtime ( ) ;
660
+ try {
661
+ this . parent . activeSubtests ++ ;
662
+ await this . buildSuite ;
663
+ this . startTime = hrtime ( ) ;
581
664
582
- if ( this [ kShouldAbort ] ( ) ) {
583
- this . subtests = [ ] ;
584
- this . postRun ( ) ;
585
- return ;
586
- }
665
+ if ( this [ kShouldAbort ] ( ) ) {
666
+ this . subtests = [ ] ;
667
+ this . postRun ( ) ;
668
+ return ;
669
+ }
670
+
671
+
672
+ const hookArgs = this . getRunArgs ( ) ;
673
+ await this [ kRunHook ] ( 'before' , hookArgs ) ;
674
+ const stopPromise = stopTest ( this . timeout , this . signal ) ;
675
+ const subtests = this . skipped || this . error ? [ ] : this . subtests ;
676
+ const promise = SafePromiseAll ( subtests , ( subtests ) => subtests . start ( ) ) ;
587
677
588
- const stopPromise = stopTest ( this . timeout , this . signal ) ;
589
- const subtests = this . skipped || this . error ? [ ] : this . subtests ;
590
- const promise = SafePromiseAll ( subtests , ( subtests ) => subtests . start ( ) ) ;
678
+ await SafePromiseRace ( [ promise , stopPromise ] ) ;
679
+ await this [ kRunHook ] ( 'after' , hookArgs ) ;
680
+ this . pass ( ) ;
681
+ } catch ( err ) {
682
+ if ( isTestFailureError ( err ) ) {
683
+ this . fail ( err ) ;
684
+ } else {
685
+ this . fail ( new ERR_TEST_FAILURE ( err , kTestCodeFailure ) ) ;
686
+ }
687
+ }
591
688
592
- await SafePromiseRace ( [ promise , stopPromise ] ) ;
593
- this . pass ( ) ;
594
689
this . postRun ( ) ;
595
690
}
596
691
}
0 commit comments