import { EventEmitter } from "events" ; import Pending from "./pending" ; import * as utils from "./utils" ; import Runnable from "./runnable" ; import Suite from "./suite" ; import Test from "./test" ; import { createInvalidExceptionError , createUnsupportedError , createFatalError , isMochaError , constants as errorConstants , } from "./errors" ; const { HOOK_TYPE_BEFORE_EACH , HOOK_TYPE_AFTER_EACH , HOOK_TYPE_AFTER_ALL , HOOK_TYPE_BEFORE_ALL , EVENT_ROOT_SUITE_RUN , STATE_FAILED , STATE_PASSED , STATE_PENDING , } = Suite . constants ; const stackFilter = utils . stackTraceFilter ( ) ; const stringify = utils . stringify ; private readonly const globals = [ "setTimeout" , "clearTimeout" , "setInterval" , "clearInterval" , "XMLHttpRequest" , "Date" , "setImmediate" , "clearImmediate" , ] ; export const constants = utils . defineConstants ( { /**
     * Emitted when  Hook execution begins
      EVENT_HOOK_BEGIN : "hook" , /**
     * Emitted when  Hook execution ends
      EVENT_HOOK_END : "hook end" , /**
     * Emitted when Root  Suite execution begins (all files have been parsed and hooks/tests are ready for execution)
      EVENT_RUN_BEGIN : "start" , /**
     * Emitted when Root  Suite execution has been delayed via `delay` option
      EVENT_DELAY_BEGIN : "waiting" , /**
     * Emitted when delayed Root  Suite execution is triggered by user via `global.run()`
      EVENT_DELAY_END : "ready" , /**
     * Emitted when Root  Suite execution ends
      EVENT_RUN_END : "end" , /**
     * Emitted when  Suite execution begins
      EVENT_SUITE_BEGIN : "suite" , /**
     * Emitted when  Suite execution ends
      EVENT_SUITE_END : "suite end" , /**
     * Emitted when  Test execution begins
      EVENT_TEST_BEGIN : "test" , /**
     * Emitted when  Test execution ends
      EVENT_TEST_END : "test end" , /**
     * Emitted when  Test execution fails
      EVENT_TEST_FAIL : "fail" , /**
     * Emitted when  Test execution succeeds
      EVENT_TEST_PASS : "pass" , /**
     * Emitted when  Test becomes pending
      EVENT_TEST_PENDING : "pending" , /**
     * Emitted when  Test execution has failed, but will retry
      EVENT_TEST_RETRY : "retry" , /**
     * Initial state of Runner
     */ STATE_IDLE : "idle" , /**
     * State set to this value when the Runner has started running
     */ STATE_RUNNING : "running" , /**
     * State set to this value when the Runner has stopped
     */ STATE_STOPPED : "stopped" , } ) ; export interface RunnerOptions { /** Whether to delay execution of root suite until ready. */ delay ? : boolean ; /** Whether to report tests without running them. */ dryRun ? : boolean ; /** Whether to clean references to test fns and hooks when a suite is done. */ cleanReferencesAfterRun ? : boolean ; /** Whether to fail test run if zero tests encountered. */ failZero ? : boolean ; reporterOption ? : any ; } class Runner extends EventEmitter { static constants = constants ; private _globals ; private _abort ; private _delay ; private _defaultGrep ; private next ; private hookErr ; private prevGlobalsLength ; private nextSuite ; suite : Suite ; started : boolean ; total : number ; failures : number ; asyncOnly ? : boolean | undefined ; allowUncaught ? : boolean | undefined ; fullStackTrace ? : boolean | undefined ; forbidOnly ? : boolean | undefined ; forbidPending ? : boolean | undefined ; checkLeaks ? : boolean | undefined ; test ? : Test | undefined ; currentRunnable ? : Runnable | undefined ; stats ? : any | undefined ; _opts : any ; state : any ; _eventListeners : any ; /** Injected by JSONReporter */ testResults : any ; uncaught : any ; unhandled : any ; /**
     * Initialize a `Runner` at the Root  Suite , which represents a hierarchy of
     *  Suite and  Test .
      constructor ( suite , options = { } ) { super ( ) ; const self = this ; this . _delay = options . delay ; this . _globals = [ ] ; this . _abort = false ; this . suite = suite ; this . _opts = options ; this . state = constants . STATE_IDLE ; this . total = suite . total ( ) ; this . failures = 0 ; this . _eventListeners = new Map ( ) ; this . _defaultGrep = /.*/ ; this . on ( constants . EVENT_TEST_END , ( test ) => { if ( test . type === "test" && test . retriedTest ( ) && test . parent ) { const idx = test . parent . tests && test . parent . tests . indexOf ( test . retriedTest ( ) ) ; if ( idx > - 1 ) { test . parent . tests [ idx ] = test ; } } self . checkGlobals ( test ) ; } ) ; this . on ( constants . EVENT_HOOK_END , ( hook ) => { self . checkGlobals ( hook ) ; } ) ; this . grep ( this . _defaultGrep ) ; this . globals ( this . globalProps ( ) ) ; this . uncaught = this . _uncaught . bind ( this ) ; this . unhandled = ( reason , promise ) => { if ( isMochaError ( reason ) ) { this . uncaught ( reason ) ; } else { this . _removeEventListener ( process , "unhandledRejection" , this . unhandled ) ; try { process . emit ( "unhandledRejection" , reason , promise ) ; } finally { this . _addEventListener ( process , "unhandledRejection" , this . unhandled ) ; } } } ; } param { Function } fn private static immediately = global . setImmediate || process . nextTick ; param { EventEmitter } target param { string } eventName param { string } fn private _addEventListener = function ( target , eventName , listener ) { if ( this . _eventListeners . has ( target ) && this . _eventListeners . get ( target ) . has ( eventName ) && this . _eventListeners . get ( target ) . get ( eventName ) . has ( listener ) ) { return ; } target . on ( eventName , listener ) ; const targetListeners = this . _eventListeners . has ( target ) ? this . _eventListeners . get ( target ) : new Map ( ) ; const targetEventListeners = targetListeners . has ( eventName ) ? targetListeners . get ( eventName ) : new Set ( ) ; targetEventListeners . add ( listener ) ; targetListeners . set ( eventName , targetEventListeners ) ; this . _eventListeners . set ( target , targetListeners ) ; } ; param { EventEmitter } target param { string } eventName param { function } listener private _removeEventListener = function ( target , eventName , listener ) { target . removeListener ( eventName , listener ) ; if ( this . _eventListeners . has ( target ) ) { const targetListeners = this . _eventListeners . get ( target ) ; if ( targetListeners . has ( eventName ) ) { const targetEventListeners = targetListeners . get ( eventName ) ; targetEventListeners . delete ( listener ) ; if ( ! targetEventListeners . size ) { targetListeners . delete ( eventName ) ; } } if ( ! targetListeners . size ) { this . _eventListeners . delete ( target ) ; } } else { } } ; /**
     * Removes all event handlers set during a run on this instance.
     * Remark: this does _not_ clean/dispose the tests or suites themselves.
     */ dispose = function ( ) { this . removeAllListeners ( ) ; this . _eventListeners . forEach ( ( targetListeners , target ) => { targetListeners . forEach ( ( targetEventListeners , eventName ) => { targetEventListeners . forEach ( ( listener ) => { target . removeListener ( eventName , listener ) ; } ) ; } ) ; } ) ; this . _eventListeners . clear ( ) ; } ; public memberof param { RegExp } re param { boolean } invert return { Runner } grep = function ( re , invert ? ) { this . _grep = re ; this . _invert = invert ; this . total = this . grepTotal ( this . suite ) ; return this ; } ; memberof public param { Suite } suite return { number } grepTotal = function ( suite ) { var self = this ; var total = 0 ; suite . eachTest ( function ( test ) { var match = self . _grep . test ( test . fullTitle ( ) ) ; if ( self . _invert ) { match = ! match ; } if ( match ) { total ++ ; } } ) ; return total ; } ; return { Array } private globalProps = function ( ) { var props = Object . keys ( global ) ; for ( var i = 0 ; i < globals . length ; ++ i ) { if ( ~ props . indexOf ( globals [ i ] ) ) { continue ; } props . push ( globals [ i ] ) ; } return props ; } ; public memberof param { Array } arr return { Runner } globals = function ( arr ) { if ( ! arguments . length ) { return this . _globals ; } this . _globals = this . _globals . concat ( arr ) ; return this ; } ; private checkGlobals = function ( test ) { if ( ! this . checkLeaks ) { return ; } var ok = this . _globals ; var globals = this . globalProps ( ) ; var leaks ; if ( test ) { ok = ok . concat ( test . _allowedGlobals || [ ] ) ; } if ( this . prevGlobalsLength === globals . length ) { return ; } this . prevGlobalsLength = globals . length ; leaks = filterLeaks ( ok , globals ) ; this . _globals = this . _globals . concat ( leaks ) ; if ( leaks . length ) { var msg = `global leak(s) detected: ${ leaks . map ( ( e ) => `'${ e }'` ) . join ( ", " ) }` ; this . fail ( test , new Error ( msg ) ) ; } } ; private param { Runnable } test param { Error } err param { boolean } force fail = function ( test , err , force ) { force = force === true ; if ( test . isPending ( ) && ! force ) { return ; } if ( this . state === constants . STATE_STOPPED ) { if ( err . code === errorConstants . MULTIPLE_DONE ) { throw err ; } throw createFatalError ( "Test failed after root suite execution completed!" , err ) ; } ++ this . failures ; test . state = STATE_FAILED ; if ( ! isError ( err ) ) { err = thrown2Error ( err ) ; } try { err . stack = this . fullStackTrace || ! err . stack ? err . stack : stackFilter ( err . stack ) ; } catch ( ignore ) { } this . emit ( constants . EVENT_TEST_FAIL , test , err ) ; } ; private param { string } name param { Function } fn hook = function ( name , fn ) { if ( this . _opts . dryRun ) return fn ( ) ; var suite = this . suite ; var hooks = suite . getHooks ( name ) ; var self = this ; function next ( i ) { var hook = hooks [ i ] ; if ( ! hook ) { return fn ( ) ; } self . currentRunnable = hook ; if ( name === HOOK_TYPE_BEFORE_ALL ) { hook . ctx . currentTest = hook . parent . tests [ 0 ] ; } else if ( name === HOOK_TYPE_AFTER_ALL ) { hook . ctx . currentTest = hook . parent . tests [ hook . parent . tests . length - 1 ] ; } else { hook . ctx . currentTest = self . test ; } setHookTitle ( hook ) ; hook . allowUncaught = self . allowUncaught ; self . emit ( constants . EVENT_HOOK_BEGIN , hook ) ; if ( ! hook . listeners ( "error" ) . length ) { self . _addEventListener ( hook , "error" , function ( err ) { self . fail ( hook , err ) ; } ) ; } hook . run ( function cbHookRun ( err ) { var testError = hook . error ( ) ; if ( testError ) { self . fail ( self . test , testError ) ; } if ( hook . pending ) { if ( name === HOOK_TYPE_AFTER_EACH ) { if ( self . test ) { self . test . pending = true ; } } else if ( name === HOOK_TYPE_BEFORE_EACH ) { if ( self . test ) { self . test . pending = true ; } self . emit ( constants . EVENT_HOOK_END , hook ) ; hook . pending = false ; return fn ( new Error ( "abort hookDown" ) ) ; } else if ( name === HOOK_TYPE_BEFORE_ALL ) { suite . tests . forEach ( function ( test ) { test . pending = true ; } ) ; suite . suites . forEach ( function ( suite ) { suite . pending = true ; } ) ; hooks = [ ] ; } else { hook . pending = false ; var errForbid = createUnsupportedError ( "`this.skip` forbidden" ) ; self . fail ( hook , errForbid ) ; return fn ( errForbid ) ; } } else if ( err ) { self . fail ( hook , err ) ; return fn ( err ) ; } self . emit ( constants . EVENT_HOOK_END , hook ) ; delete hook . ctx . currentTest ; setHookTitle ( hook ) ; next ( ++ i ) ; } ) ; function setHookTitle ( hook ) { hook . originalTitle = hook . originalTitle || hook . title ; if ( hook . ctx && hook . ctx . currentTest ) { hook . title = `${ hook . originalTitle } for "${ hook . ctx . currentTest . title }"` ; } else { var parentTitle ; if ( hook . parent . title ) { parentTitle = hook . parent . title ; } else { parentTitle = hook . parent . root ? "{root}" : "" ; } hook . title = `${ hook . originalTitle } in "${ parentTitle }"` ; } } } Runner . immediately ( function ( ) { next ( 0 ) ; } ) ; } ; private param { string } name param { Array } suites param { Function } fn hooks = function ( name , suites , fn ) { var self = this ; var orig = this . suite ; function next ( suite ) { self . suite = suite ; if ( ! suite ) { self . suite = orig ; return fn ( ) ; } self . hook ( name , function ( err ) { if ( err ) { var errSuite = self . suite ; self . suite = orig ; return fn ( err , errSuite ) ; } next ( suites . pop ( ) ) ; } ) ; } next ( suites . pop ( ) ) ; } ; param { String } name param { Function } fn private hookUp = function ( name , fn ) { var suites = [ this . suite ] . concat ( this . parents ( ) ) . reverse ( ) ; this . hooks ( name , suites , fn ) ; } ; param { String } name param { Function } fn private hookDown = function ( name , fn ) { var suites = [ this . suite ] . concat ( this . parents ( ) ) ; this . hooks ( name , suites , fn ) ; } ; return { Array } private parents = function ( ) { var suite = this . suite ; var suites = [ ] ; while ( suite . parent ) { suite = suite . parent ; suites . push ( suite ) ; } return suites ; } ; param { Function } fn private runTest = function ( fn ) { if ( this . _opts . dryRun ) return fn ( ) ; var self = this ; var test = this . test ; if ( ! test ) { return ; } if ( this . asyncOnly ) { test . asyncOnly = true ; } this . _addEventListener ( test , "error" , function ( err ) { self . fail ( test , err ) ; } ) ; if ( this . allowUncaught ) { test . allowUncaught = true ; return test . run ( fn ) ; } try { test . run ( fn ) ; } catch ( err ) { fn ( err ) ; } } ; private param { Suite } suite param { Function } fn runTests = function ( suite , fn ) { var self = this ; var tests = suite . tests . slice ( ) ; var test ; function hookErr ( _ , errSuite , after ) { var orig = self . suite ; self . suite = after ? errSuite . parent : errSuite ; if ( self . suite ) { self . hookUp ( HOOK_TYPE_AFTER_EACH , function ( err2 , errSuite2 ) { self . suite = orig ; if ( err2 ) { return hookErr ( err2 , errSuite2 , true ) ; } fn ( errSuite ) ; } ) ; } else { self . suite = orig ; fn ( errSuite ) ; } } function next ( err ? , errSuite ? ) { if ( self . failures && suite . _bail ) { tests = [ ] ; } if ( self . _abort ) { return fn ( ) ; } if ( err ) { return hookErr ( err , errSuite , true ) ; } test = tests . shift ( ) ; if ( ! test ) { return fn ( ) ; } var match = self . _grep . test ( test . fullTitle ( ) ) ; if ( self . _invert ) { match = ! match ; } if ( ! match ) { if ( self . _grep !== self . _defaultGrep ) { Runner . immediately ( next ) ; } else { next ( ) ; } return ; } if ( test . isPending ( ) ) { if ( self . forbidPending ) { self . fail ( test , new Error ( "Pending test forbidden" ) , true ) ; } else { test . state = STATE_PENDING ; self . emit ( constants . EVENT_TEST_PENDING , test ) ; } self . emit ( constants . EVENT_TEST_END , test ) ; return next ( ) ; } self . emit ( constants . EVENT_TEST_BEGIN , ( self . test = test ) ) ; self . hookDown ( HOOK_TYPE_BEFORE_EACH , function ( err , errSuite ) { if ( test . isPending ( ) ) { if ( self . forbidPending ) { self . fail ( test , new Error ( "Pending test forbidden" ) , true ) ; } else { test . state = STATE_PENDING ; self . emit ( constants . EVENT_TEST_PENDING , test ) ; } self . emit ( constants . EVENT_TEST_END , test ) ; var origSuite = self . suite ; self . suite = errSuite || self . suite ; return self . hookUp ( HOOK_TYPE_AFTER_EACH , function ( e , eSuite ) { self . suite = origSuite ; next ( e , eSuite ) ; } ) ; } if ( err ) { return hookErr ( err , errSuite , false ) ; } self . currentRunnable = self . test ; self . runTest ( function ( err ) { test = self . test ; if ( test . pending ) { if ( self . forbidPending ) { self . fail ( test , new Error ( "Pending test forbidden" ) , true ) ; } else { test . state = STATE_PENDING ; self . emit ( constants . EVENT_TEST_PENDING , test ) ; } self . emit ( constants . EVENT_TEST_END , test ) ; return self . hookUp ( HOOK_TYPE_AFTER_EACH , next ) ; } else if ( err ) { var retry = test . currentRetry ( ) ; if ( retry < test . retries ( ) ) { var clonedTest = test . clone ( ) ; clonedTest . currentRetry ( retry + 1 ) ; tests . unshift ( clonedTest ) ; self . emit ( constants . EVENT_TEST_RETRY , test , err ) ; return self . hookUp ( HOOK_TYPE_AFTER_EACH , next ) ; } else { self . fail ( test , err ) ; } self . emit ( constants . EVENT_TEST_END , test ) ; return self . hookUp ( HOOK_TYPE_AFTER_EACH , next ) ; } test . state = STATE_PASSED ; self . emit ( constants . EVENT_TEST_PASS , test ) ; self . emit ( constants . EVENT_TEST_END , test ) ; self . hookUp ( HOOK_TYPE_AFTER_EACH , next ) ; } ) ; } ) ; } this . next = next ; this . hookErr = hookErr ; next ( ) ; } ; private param { Suite } suite param { Function } fn runSuite = function ( suite , fn ) { var i = 0 ; var self = this ; var total = this . grepTotal ( suite ) ; if ( ! total || ( self . failures && suite . _bail ) ) { return fn ( ) ; } this . emit ( constants . EVENT_SUITE_BEGIN , ( this . suite = suite ) ) ; function next ( errSuite ) { if ( errSuite ) { if ( errSuite === suite ) { return done ( ) ; } return done ( errSuite ) ; } if ( self . _abort ) { return done ( ) ; } var curr = suite . suites [ i ++ ] ; if ( ! curr ) { return done ( ) ; } if ( self . _grep !== self . _defaultGrep ) { Runner . immediately ( function ( ) { self . runSuite ( curr , next ) ; } ) ; } else { self . runSuite ( curr , next ) ; } } function done ( errSuite ? ) { self . suite = suite ; self . nextSuite = next ; delete self . test ; self . hook ( HOOK_TYPE_AFTER_ALL , function ( ) { self . emit ( constants . EVENT_SUITE_END , suite ) ; fn ( errSuite ) ; } ) ; } this . nextSuite = next ; this . hook ( HOOK_TYPE_BEFORE_ALL , function ( err ) { if ( err ) { return done ( ) ; } self . runTests ( suite , next ) ; } ) ; } ; /**
     * Handle uncaught exceptions within runner.
     *
     * This function is bound to the instance as `Runner#uncaught` at instantiation
     * time. It's intended to be listening on the `Process.uncaughtException` event.
     * In order to not leak EE listeners, we need to ensure no more than a single
     * `uncaughtException` listener exists per `Runner`.  The only way to do
     * this--because this function needs the context (and we don't have lambdas)--is
     * to use `Function.prototype.bind`. We need strict equality to unregister and
     * _only_ unregister the _one_ listener we set from the
     * `Process.uncaughtException` event; would be poor form to just remove
     * everything. See  Runner for where the event listener is registered
     * and unregistered.
     *  param { Error } err private _uncaught = function ( err ) { if ( ! ( this instanceof Runner ) ) { throw createFatalError ( "Runner#uncaught() called with invalid context" , this ) ; } if ( err instanceof Pending ) { return ; } if ( this . allowUncaught && ! utils . isBrowser ( ) ) { throw err ; } if ( this . state === constants . STATE_STOPPED ) { throw err ; } if ( err ) { } else { err = createInvalidExceptionError ( "Caught falsy/undefined exception which would otherwise be uncaught. No stack trace found; try a debugger" , err ) ; } if ( ! isError ( err ) ) { err = thrown2Error ( err ) ; } err . uncaught = true ; var runnable = this . currentRunnable ; if ( ! runnable ) { runnable = new Runnable ( "Uncaught error outside test suite" ) ; runnable . parent = this . suite ; if ( this . state === constants . STATE_RUNNING ) { this . fail ( runnable , err ) ; } else { this . emit ( constants . EVENT_RUN_BEGIN ) ; this . fail ( runnable , err ) ; this . emit ( constants . EVENT_RUN_END ) ; } return ; } runnable . clearTimeout ( ) ; if ( runnable . isFailed ( ) ) { return ; } else if ( runnable . isPending ( ) ) { this . fail ( runnable , err , true ) ; return ; } if ( runnable . isPassed ( ) ) { this . fail ( runnable , err ) ; this . abort ( ) ; } else { return runnable . callback ( err ) ; } } ; public memberof param { Function } fn param { { files : string [ ] , options : Options } } opts returns { Runner } run = function ( fn , opts = { } ) { var rootSuite = this . suite ; var options = ( opts as any ) . options || { } ; fn = fn || function ( ) { } ; const end = ( ) => { if ( ! this . total && this . _opts . failZero ) this . failures = 1 ; this . emit ( constants . EVENT_RUN_END ) ; } ; const begin = ( ) => { this . emit ( constants . EVENT_RUN_BEGIN ) ; this . runSuite ( rootSuite , end ) ; } ; const prepare = ( ) => { if ( rootSuite . hasOnly ( ) ) { rootSuite . filterOnly ( ) ; } this . state = constants . STATE_RUNNING ; if ( this . _delay ) { this . emit ( constants . EVENT_DELAY_END ) ; } return begin ( ) ; } ; if ( this . _opts . cleanReferencesAfterRun ) { this . on ( constants . EVENT_SUITE_END , ( suite ) => { suite . cleanReferences ( ) ; } ) ; } this . on ( constants . EVENT_RUN_END , function ( ) { this . state = constants . STATE_STOPPED ; fn ( this . failures ) ; } ) ; this . _removeEventListener ( process , "uncaughtException" , this . uncaught ) ; this . _removeEventListener ( process , "unhandledRejection" , this . unhandled ) ; this . _addEventListener ( process , "uncaughtException" , this . uncaught ) ; this . _addEventListener ( process , "unhandledRejection" , this . unhandled ) ; if ( this . _delay ) { this . emit ( constants . EVENT_DELAY_BEGIN , rootSuite ) ; rootSuite . once ( EVENT_ROOT_SUITE_RUN , prepare ) ; } else { Runner . immediately ( prepare ) ; } return this ; } ; abstract param { boolean } value returns { Runner } chainable public example linkPartialObjects = function ( value ) { return this ; } ; runAsync = async function runAsync ( opts = { } ) { return new Promise ( ( resolve ) => { this . run ( resolve , opts ) ; } ) ; } ; memberof public return { Runner } abort = function ( ) { this . _abort = true ; return this ; } ; public returns { false } isParallelMode = function isParallelMode ( ) { return false ; } ; public param { string } path returns { Runner } throws chainable abstract workerReporter = function ( ) { throw createUnsupportedError ( "workerReporter() not supported in serial mode" ) ; } ; } private param { Array } ok param { Array } globals return { Array } function filterLeaks ( ok , globals ) { return globals . filter ( function ( key ) { if ( /^\d+/ . test ( key ) ) { return false ; } if ( global . navigator && /^getInterface/ . test ( key ) ) { return false ; } if ( global . navigator && /^\d+/ . test ( key ) ) { return false ; } if ( /^mocha-/ . test ( key ) ) { return false ; } var matched = ok . filter ( function ( ok ) { if ( ~ ok . indexOf ( "*" ) ) { return key . indexOf ( ok . split ( "*" ) [ 0 ] ) === 0 ; } return key === ok ; } ) ; return ! matched . length && ( ! global . navigator || key !== "onerror" ) ; } ) ; } private param err param { string } err . message returns { boolean } function isError ( err ) { return err instanceof Error || ( err && typeof err . message === "string" ) ; } private param { * } thrown return { Error } function thrown2Error ( err ) { return new Error ( `the ${ utils . canonicalType ( err ) } ${ stringify ( err ) } was thrown, throw an Error :)` ) ; } export default Runner ;