11'use strict' ;
22const {
33 ArrayPrototypePush,
4+ ArrayPrototypeReduce,
45 ArrayPrototypeShift,
6+ ArrayPrototypeSlice,
57 ArrayPrototypeUnshift,
68 FunctionPrototype,
79 Number,
10+ ObjectSeal,
811 PromisePrototypeThen,
912 PromiseResolve,
1013 ReflectApply,
@@ -20,18 +23,17 @@ const {
2023 codes : {
2124 ERR_TEST_FAILURE ,
2225 } ,
23- kIsNodeError,
2426 AbortError,
2527} = require ( 'internal/errors' ) ;
2628const { getOptionValue } = require ( 'internal/options' ) ;
2729const { TapStream } = require ( 'internal/test_runner/tap_stream' ) ;
28- const { createDeferredCallback } = require ( 'internal/test_runner/utils' ) ;
30+ const { createDeferredCallback, isTetFailureError } = require ( 'internal/test_runner/utils' ) ;
2931const {
3032 createDeferredPromise,
3133 kEmptyObject,
3234} = require ( 'internal/util' ) ;
3335const { isPromise } = require ( 'internal/util/types' ) ;
34- const { isUint32, validateAbortSignal } = require ( 'internal/validators' ) ;
36+ const { isUint32, validateAbortSignal, validateOneOf } = require ( 'internal/validators' ) ;
3537const { setTimeout } = require ( 'timers/promises' ) ;
3638const { cpus } = require ( 'os' ) ;
3739const { bigint : hrtime } = process . hrtime ;
@@ -41,6 +43,7 @@ const kParentAlreadyFinished = 'parentAlreadyFinished';
4143const kSubtestsFailed = 'subtestsFailed' ;
4244const kTestCodeFailure = 'testCodeFailure' ;
4345const kTestTimeoutFailure = 'testTimeoutFailure' ;
46+ const kHookFailure = 'hookFailed' ;
4447const kDefaultIndent = ' ' ;
4548const kDefaultTimeout = null ;
4649const noop = FunctionPrototype ;
@@ -50,6 +53,8 @@ const testOnlyFlag = !isTestRunner && getOptionValue('--test-only');
5053const rootConcurrency = isTestRunner ? cpus ( ) . length : 1 ;
5154
5255const kShouldAbort = Symbol ( 'kShouldAbort' ) ;
56+ const kRunHook = Symbol ( 'kRunHook' ) ;
57+ const kHookNames = ObjectSeal ( [ 'before' , 'after' , 'beforeEach' , 'afterEach' ] ) ;
5358
5459
5560function stopTest ( timeout , signal ) {
@@ -75,6 +80,10 @@ class TestContext {
7580 return this . #test. signal ;
7681 }
7782
83+ get name ( ) {
84+ return this . #test. name ;
85+ }
86+
7887 diagnostic ( message ) {
7988 this . #test. diagnostic ( message ) ;
8089 }
@@ -97,6 +106,14 @@ class TestContext {
97106
98107 return subtest . start ( ) ;
99108 }
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+ }
100117}
101118
102119class Test extends AsyncResource {
@@ -185,6 +202,12 @@ class Test extends AsyncResource {
185202 this . pendingSubtests = [ ] ;
186203 this . readySubtests = new SafeMap ( ) ;
187204 this . subtests = [ ] ;
205+ this . hooks = {
206+ before : [ ] ,
207+ after : [ ] ,
208+ beforeEach : [ ] ,
209+ afterEach : [ ] ,
210+ } ;
188211 this . waitingOn = 0 ;
189212 this . finished = false ;
190213 }
@@ -303,10 +326,19 @@ class Test extends AsyncResource {
303326 kCancelledByParent
304327 )
305328 ) ;
329+ this . startTime = this . startTime || this . endTime ; // If a test was canceled before it was started, e.g inside a hook
306330 this . cancelled = true ;
307331 this . #abortController. abort ( ) ;
308332 }
309333
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+
310342 fail ( err ) {
311343 if ( this . error !== null ) {
312344 return ;
@@ -370,8 +402,27 @@ class Test extends AsyncResource {
370402 return { ctx, args : [ ctx ] } ;
371403 }
372404
405+ async [ kRunHook ] ( hook , args ) {
406+ validateOneOf ( hook , 'hook name' , kHookNames ) ;
407+ try {
408+ await ArrayPrototypeReduce ( this . hooks [ hook ] , async ( prev , hook ) => {
409+ await prev ;
410+ await hook . run ( args ) ;
411+ if ( hook . error ) {
412+ throw hook . error ;
413+ }
414+ } , PromiseResolve ( ) ) ;
415+ } catch ( err ) {
416+ const error = new ERR_TEST_FAILURE ( `failed running ${ hook } hook` , kHookFailure ) ;
417+ error . cause = isTetFailureError ( err ) ? err . cause : err ;
418+ throw error ;
419+ }
420+ }
421+
373422 async run ( ) {
374- this . parent . activeSubtests ++ ;
423+ if ( this . parent !== null ) {
424+ this . parent . activeSubtests ++ ;
425+ }
375426 this . startTime = hrtime ( ) ;
376427
377428 if ( this [ kShouldAbort ] ( ) ) {
@@ -380,16 +431,20 @@ class Test extends AsyncResource {
380431 }
381432
382433 try {
383- const stopPromise = stopTest ( this . timeout , this . signal ) ;
384434 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.
435+ if ( this . parent ?. hooks . beforeEach . length > 0 ) {
436+ await this . parent [ kRunHook ] ( 'beforeEach' , { args, ctx } ) ;
437+ }
438+ const stopPromise = stopTest ( this . timeout , this . signal ) ;
439+ const runArgs = ArrayPrototypeSlice ( args ) ;
440+ ArrayPrototypeUnshift ( runArgs , this . fn , ctx ) ;
386441
387- if ( this . fn . length === args . length - 1 ) {
442+ if ( this . fn . length === runArgs . length - 1 ) {
388443 // This test is using legacy Node.js error first callbacks.
389444 const { promise, cb } = createDeferredCallback ( ) ;
390445
391- ArrayPrototypePush ( args , cb ) ;
392- const ret = ReflectApply ( this . runInAsyncScope , this , args ) ;
446+ ArrayPrototypePush ( runArgs , cb ) ;
447+ const ret = ReflectApply ( this . runInAsyncScope , this , runArgs ) ;
393448
394449 if ( isPromise ( ret ) ) {
395450 this . fail ( new ERR_TEST_FAILURE (
@@ -402,7 +457,7 @@ class Test extends AsyncResource {
402457 }
403458 } else {
404459 // This test is synchronous or using Promises.
405- const promise = ReflectApply ( this . runInAsyncScope , this , args ) ;
460+ const promise = ReflectApply ( this . runInAsyncScope , this , runArgs ) ;
406461 await SafePromiseRace ( [ PromiseResolve ( promise ) , stopPromise ] ) ;
407462 }
408463
@@ -411,9 +466,13 @@ class Test extends AsyncResource {
411466 return ;
412467 }
413468
469+ if ( this . parent ?. hooks . afterEach . length > 0 ) {
470+ await this . parent [ kRunHook ] ( 'afterEach' , { args, ctx } ) ;
471+ }
472+
414473 this . pass ( ) ;
415474 } catch ( err ) {
416- if ( err ?. code === 'ERR_TEST_FAILURE' && kIsNodeError in err ) {
475+ if ( isTetFailureError ( err ) ) {
417476 this . fail ( err ) ;
418477 } else {
419478 this . fail ( new ERR_TEST_FAILURE ( err , kTestCodeFailure ) ) ;
@@ -523,51 +582,81 @@ class Test extends AsyncResource {
523582 }
524583}
525584
585+ class TestHook extends Test {
586+ #args;
587+ constructor ( fn , options ) {
588+ if ( options === null || typeof options !== 'object' ) {
589+ options = kEmptyObject ;
590+ }
591+ super ( { __proto__ : null , fn, ...options } ) ;
592+ }
593+ run ( args ) {
594+ this . #args = args ;
595+ return super . run ( ) ;
596+ }
597+ getRunArgs ( ) {
598+ return this . #args;
599+ }
600+ }
601+
526602class ItTest extends Test {
527603 constructor ( opt ) { super ( opt ) ; } // eslint-disable-line no-useless-constructor
528604 getRunArgs ( ) {
529- return { ctx : { signal : this . signal } , args : [ ] } ;
605+ return { ctx : { signal : this . signal , name : this . name } , args : [ ] } ;
530606 }
531607}
532608class Suite extends Test {
533609 constructor ( options ) {
534610 super ( options ) ;
535611
536612 try {
537- const context = { signal : this . signal } ;
538- this . buildSuite = this . runInAsyncScope ( this . fn , context , [ context ] ) ;
613+ const { ctx , args } = this . getRunArgs ( ) ;
614+ this . buildSuite = this . runInAsyncScope ( this . fn , ctx , args ) ;
539615 } catch ( err ) {
540616 this . fail ( new ERR_TEST_FAILURE ( err , kTestCodeFailure ) ) ;
541617 }
542618 this . fn = ( ) => { } ;
543619 this . buildPhaseFinished = true ;
544620 }
545621
622+ getRunArgs ( ) {
623+ return { ctx : { signal : this . signal , name : this . name } , args : [ ] } ;
624+ }
625+
546626 start ( ) {
547627 return this . run ( ) ;
548628 }
549629
550630 async run ( ) {
551631 try {
552632 await this . buildSuite ;
553- } catch ( err ) {
554- this . fail ( new ERR_TEST_FAILURE ( err , kTestCodeFailure ) ) ;
555- }
556- this . parent . activeSubtests ++ ;
557- this . startTime = hrtime ( ) ;
633+ this . parent . activeSubtests ++ ;
634+ this . startTime = hrtime ( ) ;
558635
559- if ( this [ kShouldAbort ] ( ) ) {
560- this . subtests = [ ] ;
561- this . postRun ( ) ;
562- return ;
563- }
636+ if ( this [ kShouldAbort ] ( ) ) {
637+ this . subtests = [ ] ;
638+ this . postRun ( ) ;
639+ return ;
640+ }
641+
642+
643+ const hookArgs = this . getRunArgs ( ) ;
644+ await this [ kRunHook ] ( 'before' , hookArgs ) ;
645+ const stopPromise = stopTest ( this . timeout , this . signal ) ;
646+ const subtests = this . skipped || this . error ? [ ] : this . subtests ;
647+ const promise = SafePromiseAll ( subtests , ( subtests ) => subtests . start ( ) ) ;
564648
565- const stopPromise = stopTest ( this . timeout , this . signal ) ;
566- const subtests = this . skipped || this . error ? [ ] : this . subtests ;
567- const promise = SafePromiseAll ( subtests , ( subtests ) => subtests . start ( ) ) ;
649+ await SafePromiseRace ( [ promise , stopPromise ] ) ;
650+ await this [ kRunHook ] ( 'after' , hookArgs ) ;
651+ this . pass ( ) ;
652+ } catch ( err ) {
653+ if ( isTetFailureError ( err ) ) {
654+ this . fail ( err ) ;
655+ } else {
656+ this . fail ( new ERR_TEST_FAILURE ( err , kTestCodeFailure ) ) ;
657+ }
658+ }
568659
569- await SafePromiseRace ( [ promise , stopPromise ] ) ;
570- this . pass ( ) ;
571660 this . postRun ( ) ;
572661 }
573662}
0 commit comments