UNPKG

async-kit

Version:

A simple and powerful async abstraction lib for easily writing Node.js code.

1,491 lines (1,237 loc) 121 kB
/* Async Kit Copyright (c) 2014 - 2016 Cédric Ronvel The MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* jshint unused:false */ /* global describe, it, before, after */ /* TODO: Async.EventEmitter Async.Plan: .clone() .export() .while( condition , true ) .race() .waterfall() .iterator() & .usingIterator() -- should be mostly covered by foreach .aggregator() Exec: .execArgs() */ "use strict" ; var expect = require( 'expect.js' ) ; var async ; if ( process.browser ) { async = require( '../lib/browser.js' ) ; } else { async = require( '../lib/async.js' ) ; } /* Helper functions */ function createStats( n ) { var i ; var stats = { startCounter: [] , endCounter: [] , order: [] , plan: { then: 0 , 'else': 0 , 'catch': 0 , 'finally': 0 } , exec: { then: 0 , 'else': 0 , 'catch': 0 , 'finally': 0 } } ; for ( i = 0 ; i < n ; i ++ ) { stats.startCounter[ i ] = stats.endCounter[ i ] = 0 ; } return stats ; } function asyncJob( stats , id , delay , options , result , callback ) { var jobContext = this , realResult = result.slice() ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; if ( typeof options.failCount === 'number' && options.failCount >= stats.endCounter[ id ] && ! ( result[ 0 ] instanceof Error ) ) { realResult[ 0 ] = new Error( "Planned failure" ) ; } if ( options.abort ) { jobContext.abort.apply( jobContext , realResult ) ; } else { callback.apply( undefined , realResult ) ; } } , delay ) ; } function syncJob( stats , id , options , result , callback ) { var realResult = result.slice() ; stats.startCounter[ id ] ++ ; stats.endCounter[ id ] ++ ; stats.order.push( id ) ; if ( typeof options.failCount === 'number' && options.failCount >= stats.endCounter[ id ] && ! ( result[ 0 ] instanceof Error ) ) { realResult[ 0 ] = new Error( "Planned failure" ) ; } if ( options.abort ) { this.abort.apply( this , realResult ) ; } else { callback.apply( undefined , realResult ) ; } } /* Tests */ describe( "async.series()" , function() { it( "should run the series of job which do not have errors, in the good order, and trigger the callback with the correct result" , function( done ) { var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; it( "when a job has error, it should start running a series of job, be interrupted by that error and return it" , function( done ) { var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ new Error() , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ new Error() , 'wonderful' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.order ).to.eql( [ 0, 1 ] ) ; done() ; } ) ; } ) ; it( "when a function is given instead of an array of job, it should format the result using the returnLastResultOnly mode" , function( done ) { var stats = createStats( 1 ) ; async.series( function ( callback ) { asyncJob( stats , 0 , 50 , {} , [ undefined , 'my wonderful result' ] , callback ) ; } ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.equal( 'my wonderful result' ) ; expect( stats.endCounter ).to.eql( [ 1 ] ) ; expect( stats.order ).to.eql( [ 0 ] ) ; done() ; } ) ; } ) ; it( "when a function is given instead of an array of job that transmit error, it should be directly transmited as the global error" , function( done ) { var stats = createStats( 1 ) ; async.do( function ( callback ) { asyncJob( stats , 0 , 50 , {} , [ new Error() , 'my wonderful result' ] , callback ) ; } ) .exec( function( error , results ) { expect( error ).to.be.an( Error ) ; expect( results ).to.equal( 'my wonderful result' ) ; expect( stats.endCounter ).to.eql( [ 1 ] ) ; expect( stats.order ).to.eql( [ 0 ] ) ; done() ; } ) ; } ) ; } ) ; describe( "async.parallel()" , function() { it( "should run jobs which do not have errors in parallel, and trigger the callback with the correct result" , function( done ) { var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ; done() ; } ) ; } ) ; it( "when a job has error, it should start running jobs in parallel, be interrupted by that error and trigger callback with it before other pending jobs can complete" , function( done ) { var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ new Error() , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).to.be.an( Error ) ; expect( results ).to.eql( [ undefined , undefined , [ new Error() , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 0, 0, 1 ] ) ; expect( stats.order ).to.eql( [ 2 ] ) ; done() ; } ) ; } ) ; it( "when the slower job has error, it should start running jobs in parallel, all other job complete and it trigger callback with the error" , function( done ) { var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ new Error() , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ new Error() , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ; done() ; } ) ; } ) ; } ) ; describe( "Jobs" , function() { it( "can be an array of async function accepting a completion callback" , function( done ) { var stats = createStats( 3 ) ; async.series( [ function( callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'my' ) ; } , 0 ) ; } , function( callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'wonderful' ) ; } , 0 ) ; } , function( callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'result' ) ; } , 0 ) ; } ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; it( "can be an array of synchronous function, if it still accept and use the completion callback" , function( done ) { var stats = createStats( 3 ) ; async.series( [ function( callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'my' ) ; } , function( callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'wonderful' ) ; } , function( callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'result' ) ; } ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; it( "can be an array of array, each of them having a async function as the first element and then a list of argument to pass to this function, it should accept one more argument: the callback for completion being added by the async lib" , function( done ) { var stats = createStats( 3 ) ; async.do( [ [ asyncJob , stats , 0 , 0 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; it( "can be an array of array, each of them having a synchronous function as the first element and then a list of argument to pass to this function, if those functions still accept and use the completion callback" , function( done ) { var stats = createStats( 3 ) ; async.do( [ [ syncJob , stats , 0 , {} , [ undefined , 'my' ] ] , [ syncJob , stats , 1 , {} , [ undefined , 'wonderful' ] ] , [ syncJob , stats , 2 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; it( "can be an array of async.Plan, each of them will be used by calling their .exec() method" , function( done ) { var stats = createStats( 6 ) ; async.parallel( [ async.series( [ [ asyncJob , stats , 0 , 10 , {} , [ undefined , 'a' ] ] , [ asyncJob , stats , 1 , 10 , {} , [ undefined , 'nice' ] ] , [ asyncJob , stats , 2 , 10 , {} , [ undefined , 'output' ] ] ] ) , async.series( [ [ asyncJob , stats , 3 , 10 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 4 , 10 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 5 , 10 , {} , [ undefined , 'result' ] ] ] ) ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results[ 0 ][ 0 ] ).not.to.be.an( Error ) ; expect( results[ 1 ][ 0 ] ).not.to.be.an( Error ) ; expect( results[ 0 ][ 1 ] ).to.eql( [ [ undefined , 'a' ], [ undefined , 'nice' ], [ undefined , 'output' ] ] ) ; expect( results[ 1 ][ 1 ] ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1, 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 3, 1, 4, 2, 5 ] ) ; done() ; } ) ; } ) ; it( "can be an array that mix all those type of jobs" , function( done ) { var stats = createStats( 7 ) ; async.parallel( [ function( callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "I'm an async anonymous function" ) ; } , 0 ) ; } , function( callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "I'm a synchronous anonymous function" ) ; } , async.series( [ [ asyncJob , stats , 2 , 20 , {} , [ undefined , 'nested' ] ] , [ asyncJob , stats , 3 , 20 , {} , [ undefined , 'async.Plan' ] ] , [ asyncJob , stats , 4 , 20 , {} , [ undefined , 'results' ] ] ] ) , [ syncJob , stats , 5 , {} , [ undefined , "I'm a synchronous array of function and arguments" ] ] , [ asyncJob , stats , 6 , 10 , {} , [ undefined , "I'm an async array of function and arguments" ] ] , ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , "I'm an async anonymous function" ] , [ undefined , "I'm a synchronous anonymous function" ] , [ undefined , [ [ undefined , "nested" ] , [ undefined , "async.Plan" ] , [ undefined , "results" ] ] ] , [ undefined , "I'm a synchronous array of function and arguments" ] , [ undefined , "I'm an async array of function and arguments" ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1, 1, 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 1, 5, 0, 6, 2, 3, 4 ] ) ; done() ; } ) ; } ) ; it( "objects can be used instead of array as the top container, the results should be an objects with the same properties mapping, properties' order should be preserved (*IF* they do not start with a digit - because of V8 behaviours with objects)" , function( done ) { var stats = createStats( 3 ) ; async.parallel( { one: [ asyncJob , stats , 0 , 40 , {} , [ undefined , 'my' ] ] , two: [ asyncJob , stats , 1 , 20 , {} , [ undefined , 'wonderful' ] ] , three: [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] } ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( { one: [ undefined , 'my' ], two: [ undefined , 'wonderful' ], three: [ undefined , 'result' ] } ) ; expect( Object.keys( results ) ).to.eql( [ 'one' , 'two' , 'three' ] ) ; // Check the keys order expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; done() ; } ) ; } ) ; } ) ; describe( "Jobs & async.Plan.prototype.using()" , function() { describe( "passing a function to .using()" , function() { it( "should take each job as an array of arguments to pass to the .using()'s function" , function( done ) { var stats = createStats( 3 ) ; async.do( [ [ stats , 0 , 0 , {} , [ undefined , 'my' ] ] , [ stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] , [ stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .using( asyncJob ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; it( "when the job is not an array, it should take each job as the first argument to pass to the .using()'s function" , function( done ) { var id = 0 , stats = createStats( 3 ) ; async.do( [ 'my' , 'wonderful' , 'result' ] ) .using( function( data , callback ) { stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; id ++ ; callback( undefined , data ) ; } , 0 ) ; } ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; } ) ; describe( "passing an array to .using()" , function() { it( "when a job is a function, it should take the .using()'s array as argument" , function( done ) { var stats = createStats( 3 ) ; async.parallel( [ function( data , callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "DESCRIPTION: " + data.describe ) ; } , 20 ) ; } , function( data , callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "LENGTH: " + data.body.length ) ; } , 10 ) ; } , function( data , callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "BODY: " + data.body ) ; } , 0 ) ; } ] ) .using( [ { describe: 'some data' , body: 'blahblihblah' } ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'DESCRIPTION: some data' ] , [ undefined , 'LENGTH: 12' ] , [ undefined , 'BODY: blahblihblah' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; done() ; } ) ; } ) ; } ) ; } ) ; describe( "Jobs scheduling with async.prototype.nice()" , function() { it( "using .nice( -2 ), it should run the series of job with synchonous scheduling" , function( done ) { var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( -2 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; it( "using .nice( -1 ), it should run the series of job with an async scheduling (setImmediate)" , function( done ) { var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( -1 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; it( "using .nice( 10 ), it should run the series of job with an async scheduling (setTimeout 100ms)" , function( done ) { var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( 10 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; it( "using .nice( -2 ), it should run the jobs in parallel with synchonous scheduling" , function( done ) { var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( -2 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ; done() ; } ) ; } ) ; it( "using .nice( -1 ), it should run the jobs in parallel with an async scheduling (setImmediate)" , function( done ) { var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( -1 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ; done() ; } ) ; } ) ; it( "using .nice( 10 ), it should run the jobs in parallel with an async scheduling (setTimeout 100ms)" , function( done ) { var stats = createStats( 3 ) ; async.parallel( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .nice( 10 ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ; done() ; } ) ; } ) ; } ) ; describe( "Jobs & async.Plan.prototype.execMapping(), adding input arguments to .exec()" , function() { it( "using default exec()'s arguments mapping, called with no argument, it should not throw error" , function( done ) { var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] , function( callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "result" ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } , 0 ) ; } ] ) .exec() ; } ) ; it( "using default exec()'s arguments mapping, when a job is a function, it should take the input arguments passed to .exec()" , function( done ) { var stats = createStats( 3 ) ; async.parallel( [ function( describe , body , callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "DESCRIPTION: " + describe ) ; } , 20 ) ; } , function( describe , body , callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "LENGTH: " + body.length ) ; } , 10 ) ; } , function( describe , body , callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "BODY: " + body ) ; } , 0 ) ; } ] ) .exec( 'some data' , 'blahblihblah' , function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'DESCRIPTION: some data' ] , [ undefined , 'LENGTH: 12' ] , [ undefined , 'BODY: blahblihblah' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; done() ; } ) ; } ) ; it( "when a job is a function, it should take the input arguments passed to .exec()" , function( done ) { var stats = createStats( 3 ) ; async.parallel( [ function( describe , body , callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "DESCRIPTION: " + describe ) ; } , 20 ) ; } , function( describe , body , callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "LENGTH: " + body.length ) ; } , 10 ) ; } , function( describe , body , callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "BODY: " + body ) ; } , 0 ) ; } ] ) .execMapping( { callbacks: [ 'finally' ] , minInputs: 2 , maxInputs: 2 , inputsName: [ 'describe' , 'body' ] } ) .exec( 'some data' , 'blahblihblah' , function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'DESCRIPTION: some data' ] , [ undefined , 'LENGTH: 12' ] , [ undefined , 'BODY: blahblihblah' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; done() ; } ) ; } ) ; it( "when mixing arguments passed to .exec() and .using(), .exec()'s arguments overlapping .using()'s arguments should overwrite" , function( done ) { var stats ; var asyncPlan = async.parallel( [ function( describe , body , callback ) { var id = 0 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "DESCRIPTION: " + describe ) ; } , 20 ) ; } , function( describe , body , callback ) { var id = 1 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "LENGTH: " + body.length ) ; } , 10 ) ; } , function( describe , body , callback ) { var id = 2 ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , "BODY: " + body ) ; } , 0 ) ; } ] ) .using( [ "<insert .using()'s description here>" , "<insert .using()'s body here>" ] ) .execMapping( { callbacks: [ 'finally' ] , minInputs: 0 , maxInputs: 2 , inputsName: [ 'describe' , 'body' ] } ) ; stats = createStats( 3 ) ; asyncPlan.exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , "DESCRIPTION: <insert .using()'s description here>" ] , [ undefined , 'LENGTH: 29' ] , [ undefined , "BODY: <insert .using()'s body here>" ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; stats = createStats( 3 ) ; asyncPlan.exec( "<insert .exec()'s description here>" , function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , "DESCRIPTION: <insert .exec()'s description here>" ] , [ undefined , 'LENGTH: 29' ] , [ undefined , "BODY: <insert .using()'s body here>" ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; stats = createStats( 3 ) ; asyncPlan.exec( "<insert .exec()'s description here>" , "<insert .exec()'s body here>" , function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , "DESCRIPTION: <insert .exec()'s description here>" ] , [ undefined , 'LENGTH: 28' ] , [ undefined , "BODY: <insert .exec()'s body here>" ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 2, 1, 0 ] ) ; done() ; } ) ; } ) ; } ) ; } ) ; } ) ; describe( "*this*" , function() { it( "each job function should have *this* set to the current jobContext, jobCallback.jobContext should be an alternate way to access it" , function( done ) { var stats = createStats( 3 ) ; async.series( [ function( callback ) { var id = 0 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( this.execContext.results ).to.eql( [ undefined ] ) ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'my' ) ; } , 0 ) ; } , function( callback ) { var id = 1 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( this.execContext.results ).to.eql( [ [ undefined , 'my' ] , undefined ] ) ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'wonderful' ) ; } , 0 ) ; } , function( callback ) { var id = 2 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( this.execContext.results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], undefined ] ) ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'result' ) ; } , 0 ) ; } ] ) .exec( done ) ; } ) ; it( "using()'s function should have *this* set to the current jobContext" , function( done ) { var stats = createStats( 3 ) ; async.series( [ [ 0 , 'my' , [ undefined ] ] , [ 1 , 'wonderful' , [ [ undefined , 'my' ] , undefined ] ] , [ 2 , 'result' , [ [ undefined , 'my' ], [ undefined , 'wonderful' ], undefined ] ] ] ) .using( function( id , result , expectedThisResults , callback ) { expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( this.execContext.results ).to.eql( expectedThisResults ) ; stats.startCounter[ id ] ++ ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , result ) ; } , 0 ) ; } ) .exec( done ) ; } ) ; it( "every user provided callback should have *this* set to the current execContext" , function( done ) { var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 0 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .then( function( results ) { expect( this ).to.be.an( async.ExecContext ) ; expect( this.results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; } ) .finally( function( error , results ) { expect( this ).to.be.an( async.ExecContext ) ; expect( this.results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; } ) .execThenCatch( function( results ) { expect( this ).to.be.an( async.ExecContext ) ; expect( this.results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; } , function( error ) {} , function( error , results ) { expect( this ).to.be.an( async.ExecContext ) ; expect( this.results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; done() ; } ) ; } ) ; it( "a job can register to the 'timeout' event, that will be triggered when using .timeout() when the job exceed the time limit" , function( done ) { var stats = createStats( 3 ) ; var timeoutArray = [ false , false , false ] ; async.parallel( [ function( callback ) { var id = 0 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; stats.startCounter[ id ] ++ ; this.on( 'timeout' , function() { timeoutArray[ id ] = true ; } ) ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'my' ) ; } , 20 ) ; } , function( callback ) { var id = 1 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; stats.startCounter[ id ] ++ ; this.on( 'timeout' , function() { timeoutArray[ id ] = true ; } ) ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'wonderful' ) ; } , 60 ) ; } , function( callback ) { var id = 2 ; expect( this ).to.be.an( async.JobContext ) ; expect( callback.jobContext ).to.be( this ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; stats.startCounter[ id ] ++ ; this.on( 'timeout' , function() { timeoutArray[ id ] = true ; } ) ; setTimeout( function() { stats.endCounter[ id ] ++ ; stats.order.push( id ) ; callback( undefined , 'result' ) ; } , 0 ) ; } ] ) .timeout( 40 ) .exec( function( error , results ) { expect( error ).to.be.ok() ; expect( results ).to.eql( [ [ undefined, 'my' ] , [ new async.AsyncError( 'jobTimeout' ) ] , [ undefined, 'result' ] ] ) ; expect( timeoutArray ).to.be.eql( [ false , true , false ] ) ; done() ; } ) ; } ) ; } ) ; describe( "JobContext.abort()" , function() { it( "should start a series of sync job, one of them call this.abort(), so it should abort the whole job's queue" , function( done ) { var stats = createStats( 3 ) ; async.series( [ [ syncJob , stats , 0 , {} , [ undefined , 'my' ] ] , [ syncJob , stats , 1 , { abort: true } , [ undefined , 'wonderful' ] ] , [ syncJob , stats , 2 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ] ] ) ; expect( stats.startCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.order ).to.eql( [ 0, 1 ] ) ; done() ; } ) ; } ) ; it( "should start a series of async job, one of them call this.abort(), so it should abort the whole job's queue" , function( done ) { var stats = createStats( 3 ) ; async.series( [ [ asyncJob , stats , 0 , 20 , {} , [ undefined , 'my' ] ] , [ asyncJob , stats , 1 , 50 , { abort: true } , [ undefined , 'wonderful' ] ] , [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ] ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ] ] ) ; expect( stats.startCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.order ).to.eql( [ 0, 1 ] ) ; done() ; } ) ; } ) ; it( "a job within a while loop, calling this.abort(), cannot abort the whole loop" , function( done ) { var stats = createStats( 3 ) ; var count = -1 ; async.while( function( error , results , callback ) { count ++ ; callback( count < 3 ) ; } ) .do( [ function( callback ) { stats.startCounter[ count ] ++ ; stats.endCounter[ count ] ++ ; stats.order.push( count ) ; if ( count === 1 ) { this.abort( undefined , count ) ; } else { callback( undefined , count ) ; } } ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined, 2 ] ] ) ; expect( stats.startCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ; done() ; } ) ; } ) ; it( "should run a job within a while loop, one of them call this.abortLoop(), so it should abort the whole loop" , function( done ) { var stats = createStats( 3 ) ; var count = -1 ; async.while( function( error , results , callback ) { count ++ ; callback( count < 3 ) ; } ) .do( [ function( callback ) { stats.startCounter[ count ] ++ ; stats.endCounter[ count ] ++ ; stats.order.push( count ) ; if ( count === 1 ) { this.abortLoop( undefined , count ) ; } else { callback( undefined , count ) ; } } ] ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined, 1 ] ] ) ; expect( stats.startCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ; expect( stats.order ).to.eql( [ 0, 1 ] ) ; done() ; } ) ; } ) ; } ) ; describe( "ExecContext.getJobsStatus()" , function() { it( "should return the real-time status of all jobs at any time (here in series mode)" , function( done ) { async.series( [ function one( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( undefined , 'ok' ) ; } , 20 ) ; } , function two( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( new Error( 'somethingBadHappens' ) ) ; } , 20 ) ; } , function three( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'failed' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( undefined , 'should timeout!' ) ; } , 60 ) ; } , function four( callback ) { var self = this , jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'failed' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'timeout' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { self.abort() ; } , 0 ) ; } , function five( callback ) { expect().fail( "This code should never be reached" ) ; } ] ) .timeout( 40 ) .fatal( false ) .exec( function( error , results ) { var jobsStatus = this.getJobsStatus() ; expect( error ).not.to.be.ok() ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'failed' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'timeout' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'aborted' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; done() ; } ) ; } ) ; it( "should return the real-time status of all jobs at any time (here in parallel mode)" , function( done ) { async.parallel( [ function one( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( undefined , 'ok' ) ; } , 20 ) ; } , function two( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( new Error( 'somethingBadHappens' ) ) ; } , 20 ) ; } , function three( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'waiting' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { callback( undefined , 'should timeout!' ) ; } , 60 ) ; } , function four( callback ) { var self = this , jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'waiting' ) ; setTimeout( function() { self.abort() ; } , 30 ) ; } , function five( callback ) { var jobsStatus = this.execContext.getJobsStatus() ; expect( this ).to.be.an( async.JobContext ) ; expect( this.execContext ).to.be.an( async.ExecContext ) ; expect( jobsStatus[ 0 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 2 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 3 ].status ).to.be( 'pending' ) ; expect( jobsStatus[ 4 ].status ).to.be( 'pending' ) ; } ] ) .timeout( 40 ) .fatal( false ) .exec( function( error , results ) { var jobsStatus = this.getJobsStatus() ; expect( error ).not.to.be.ok() ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].status ).to.be( 'failed' ) ; // Sometime 'timeout' kick in before execThen(), but it should be considered as a normal behaviour //expect( jobsStatus[ 2 ].status ).to.be( 'pending' ) ; expect( [ 'pending' , 'timeout' ] ).to.contain( jobsStatus[ 2 ].status ) ; expect( jobsStatus[ 3 ].status ).to.be( 'aborted' ) ; // Same here //expect( jobsStatus[ 4 ].status ).to.be( 'pending' ) ; expect( [ 'pending' , 'timeout' ] ).to.contain( jobsStatus[ 4 ].status ) ; done() ; } ) ; } ) ; it( "should report the number of retry of a job, as well as the list of successives errors" , function( done ) { var jobOneTries = 1 , jobTwoTries = 1 ; async.parallel( [ function one( callback ) { setTimeout( function() { if ( jobOneTries < 6 ) { callback( new Error( 'somethingBadHappens' + jobOneTries ) ) ; } else { callback( undefined , 'ok' ) ; } jobOneTries ++ ; } , 20 ) ; } , function two( callback ) { setTimeout( function() { if ( jobTwoTries < 2 ) { callback( new Error( 'somethingBadHappens' + jobTwoTries ) ) ; } else { callback( undefined , 'ok' ) ; } jobTwoTries ++ ; } , 20 ) ; } ] ) .retry( 3 , 5 ) .exec( function( error , results ) { var jobsStatus = this.getJobsStatus() ; //console.log( jobsStatus ) ; expect( error ).to.be.ok() ; expect( jobsStatus[ 0 ].status ).to.be( 'failed' ) ; expect( jobsStatus[ 0 ].tried ).to.be( 4 ) ; expect( jobsStatus[ 0 ].errors.length ).to.be( 4 ) ; expect( jobsStatus[ 0 ].errors[ 0 ].message ).to.be( 'somethingBadHappens1' ) ; expect( jobsStatus[ 0 ].errors[ 1 ].message ).to.be( 'somethingBadHappens2' ) ; expect( jobsStatus[ 0 ].errors[ 2 ].message ).to.be( 'somethingBadHappens3' ) ; expect( jobsStatus[ 0 ].errors[ 3 ].message ).to.be( 'somethingBadHappens4' ) ; expect( jobsStatus[ 0 ].result ).to.eql( [ new Error( 'somethingBadHappens4' ) ] ) ; expect( jobsStatus[ 1 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].tried ).to.be( 2 ) ; expect( jobsStatus[ 1 ].errors.length ).to.be( 1 ) ; expect( jobsStatus[ 1 ].errors[ 0 ].message ).to.be( 'somethingBadHappens1' ) ; expect( jobsStatus[ 1 ].result ).to.eql( [ undefined, 'ok' ] ) ; done() ; } ) ; } ) ; it( "should give insight when a job call its callback twice or more" , function( done ) { async.parallel( [ function one( callback ) { setTimeout( function() { callback( undefined , 'ok' ) ; callback( undefined , 'callback used twice' ) ; callback( undefined , 'callback used three times' ) ; } , 20 ) ; } , function two( callback ) { setTimeout( function() { callback( undefined , 'ok' ) ; } , 20 ) ; } ] ) .exec( function( error , results ) { var jobsStatus = this.getJobsStatus() ; //console.log( jobsStatus ) ; expect( error ).not.to.be.ok() ; expect( jobsStatus[ 0 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 0 ].tried ).to.be( 1 ) ; expect( jobsStatus[ 0 ].errors.length ).to.be( 2 ) ; expect( jobsStatus[ 0 ].errors[ 0 ].message ).to.be( 'This job has called its completion callback 2 times' ) ; expect( jobsStatus[ 0 ].errors[ 1 ].message ).to.be( 'This job has called its completion callback 3 times' ) ; expect( jobsStatus[ 0 ].result ).to.eql( [ undefined , 'ok' ] ) ; expect( jobsStatus[ 1 ].status ).to.be( 'ok' ) ; expect( jobsStatus[ 1 ].tried ).to.be( 1 ) ; expect( jobsStatus[ 1 ].errors.length ).to.be( 0 ) ; expect( jobsStatus[ 1 ].result ).to.eql( [ undefined, 'ok' ] ) ; done() ; } ) ; } ) ; } ) ; describe( "async.foreach()" , function() { it( "should take each job as an element to pass to the iterator function" , function( done ) { var stats = createStats( 3 ) ; var myArray = [ { id: 0 , timeout: 10 , result: [ undefined , 'my' ] } , { id: 1 , timeout: 0 , result: [ undefined , 'wonderful' ] } , { id: 2 , timeout: 0 , result: [ undefined , 'result' ] } ] ; async.foreach( myArray , function( element , callback ) { stats.startCounter[ element.id ] ++ ; setTimeout( function() { stats.endCounter[ element.id ] ++ ; stats.order.push( element.id ) ; callback.apply( undefined , element.result ) ; } , element.delay ) ; } ) .exec( function( error , results ) { expect( error ).not.to.be.an( Error ) ; expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ; expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ; ex