# BDD Spec

The Mocha framework is used for BDD-style tests.

To help understand the following tests, here are the three helper functions used:

```js
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 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" ) ;
		}
		
		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" ) ;
	}
	
	callback.apply( undefined , realResult ) ;
}
```

The Full BDD spec generated by Mocha:

# TOC
   - [async.series()](#asyncseries)
   - [async.parallel()](#asyncparallel)
   - [Jobs](#jobs)
   - [Jobs & async.Plan.prototype.using()](#jobs--asyncplanprototypeusing)
     - [passing a function to .using()](#jobs--asyncplanprototypeusing-passing-a-function-to-using)
     - [passing an array to .using()](#jobs--asyncplanprototypeusing-passing-an-array-to-using)
   - [Jobs scheduling with async.prototype.nice()](#jobs-scheduling-with-asyncprototypenice)
   - [Jobs & async.Plan.prototype.execMapping(), adding input arguments to .exec()](#jobs--asyncplanprototypeexecmapping-adding-input-arguments-to-exec)
   - [*this*](#this)
   - [JobContext.abort()](#jobcontextabort)
   - [ExecContext.getJobsStatus()](#execcontextgetjobsstatus)
   - [async.foreach()](#asyncforeach)
   - [async.map()](#asyncmap)
   - [async.reduce()](#asyncreduce)
   - [async.waterfall()](#asyncwaterfall)
   - [async.race()](#asyncrace)
   - [async.while()](#asyncwhile)
   - [async.do().while()](#asyncdowhile)
   - [async.do().repeat()](#asyncdorepeat)
   - [Async conditional](#async-conditional)
     - [async.if.and()](#async-conditional-asyncifand)
     - [async.if.or()](#async-conditional-asyncifor)
     - [async.and()](#async-conditional-asyncand)
     - [async.or()](#async-conditional-asyncor)
     - [nested async.or() and async.and() in async.if()](#async-conditional-nested-asyncor-and-asyncand-in-asyncif)
     - [async.Plan.prototype.boolean()](#async-conditional-asyncplanprototypeboolean)
   - [async.Plan.prototype.then(), .else(), .catch(), .finally(), .execThenCatch(), .execThenElse() and .execThenElseCatch()](#asyncplanprototypethen-else-catch-finally-execthencatch-execthenelse-and-execthenelsecatch)
   - [async.Plan.prototype.timeout()](#asyncplanprototypetimeout)
   - [async.Plan.prototype.retry()](#asyncplanprototyperetry)
   - [Mixing async.Plan.prototype.retry() & async.Plan.prototype.timeout()](#mixing-asyncplanprototyperetry--asyncplanprototypetimeout)
   - [async.Plan.prototype.parallel()](#asyncplanprototypeparallel)
   - [async.Plan.prototype.fatal()](#asyncplanprototypefatal)
   - [async.Plan.prototype.lastJobOnly()](#asyncplanprototypelastjobonly)
   - [async.Plan.prototype.mapping1to1()](#asyncplanprototypemapping1to1)
   - [async.Plan.prototype.execKV()](#asyncplanprototypeexeckv)
   - [Events](#events)
   - [async.callTimeout()](#asynccalltimeout)
   - [async.wrapper.timeout()](#asyncwrappertimeout)
   - [Misc tests](#misc-tests)
   - [Async EventEmitter](#async-eventemitter)
   - ['Maximum call stack size exceeded' prevention](#maximum-call-stack-size-exceeded-prevention)
<a name=""></a>
 
<a name="asyncseries"></a>
# async.series()
should run the series of job which do not have errors, in the good order, and trigger the callback with the correct result.

```js
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() ;
} ) ;
```

when a job has error, it should start running a series of job, be interrupted by that error and return it.

```js
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() ;
} ) ;
```

when a function is given instead of an array of job, it should format the result using the returnLastResultOnly mode.

```js
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() ;
} ) ;
```

when a function is given instead of an array of job that transmit error, it should be directly transmited as the global error.

```js
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() ;
} ) ;
```

<a name="asyncparallel"></a>
# async.parallel()
should run jobs which do not have errors in parallel, and trigger the callback with the correct result.

```js
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() ;
} ) ;
```

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.

```js
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() ;
} ) ;
```

when the slower job has error, it should start running jobs in parallel, all other job complete and it trigger callback with the error.

```js
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() ;
} ) ;
```

<a name="jobs"></a>
# Jobs
can be an array of async function accepting a completion callback.

```js
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() ;
} ) ;
```

can be an array of synchronous function, if it still accept and use the completion callback.

```js
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() ;
} ) ;
```

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.

```js
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() ;
} ) ;
```

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.

```js
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() ;
} ) ;
```

can be an array of async.Plan, each of them will be used by calling their .exec() method.

```js
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() ;
} ) ;
```

can be an array that mix all those type of jobs.

```js
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() ;
} ) ;
```

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).

```js
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() ;
} ) ;
```

<a name="jobs--asyncplanprototypeusing"></a>
# Jobs & async.Plan.prototype.using()
<a name="jobs--asyncplanprototypeusing-passing-a-function-to-using"></a>
## passing a function to .using()
should take each job as an array of arguments to pass to the .using()'s function.

```js
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() ;
} ) ;
```

when the job is not an array, it should take each job as the first argument to pass to the .using()'s function.

```js
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() ;
} ) ;
```

<a name="jobs--asyncplanprototypeusing-passing-an-array-to-using"></a>
## passing an array to .using()
when a job is a function, it should take the .using()'s array as argument.

```js
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() ;
} ) ;
```

<a name="jobs-scheduling-with-asyncprototypenice"></a>
# Jobs scheduling with async.prototype.nice()
using .nice( -2 ), it should run the series of job with synchonous scheduling.

```js
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() ;
} ) ;
```

using .nice( -1 ), it should run the series of job with an async scheduling (setImmediate).

```js
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() ;
} ) ;
```

using .nice( 10 ), it should run the series of job with an async scheduling (setTimeout 100ms).

```js
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() ;
} ) ;
```

using .nice( -2 ), it should run the jobs in parallel with synchonous scheduling.

```js
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() ;
} ) ;
```

using .nice( -1 ), it should run the jobs in parallel with an async scheduling (setImmediate).

```js
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() ;
} ) ;
```

using .nice( 10 ), it should run the jobs in parallel with an async scheduling (setTimeout 100ms).

```js
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() ;
} ) ;
```

<a name="jobs--asyncplanprototypeexecmapping-adding-input-arguments-to-exec"></a>
# Jobs & async.Plan.prototype.execMapping(), adding input arguments to .exec()
using default exec()'s arguments mapping, called with no argument, it should not throw error.

```js
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() ;
```

using default exec()'s arguments mapping, when a job is a function, it should take the input arguments passed to .exec().

```js
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() ;
} ) ;
```

when a job is a function, it should take the input arguments passed to .exec().

```js
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() ;
} ) ;
```

when mixing arguments passed to .exec() and .using(), .exec()'s arguments overlapping .using()'s arguments should overwrite.

```js
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() ;
		} ) ;
	} ) ;
} ) ;
```

<a name="this"></a>
# *this*
each job function should have *this* set to the current jobContext, jobCallback.jobContext should be an alternate way to access it.

```js
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 ) ;
```

using()'s function should have *this* set to the current jobContext.

```js
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 ) ;
```

every user provided callback should have *this* set to the current execContext.

```js
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() ;
	}
) ;
```

a job can register to the 'timeout' event, that will be triggered when using .timeout() when the job exceed the time limit.

```js
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() ;
} ) ;
```

<a name="jobcontextabort"></a>
# JobContext.abort()
should start a series of sync job, one of them call this.abort(), so it should abort the whole job's queue.

```js
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() ;
} ) ;
```

should start a series of async job, one of them call this.abort(), so it should abort the whole job's queue.

```js
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() ;
} ) ;
```

a job within a while loop, calling this.abort(), cannot abort the whole loop.

```js
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() ;
} ) ;
```

should run a job within a while loop, one of them call this.abortLoop(), so it should abort the whole loop.

```js
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() ;
} ) ;
```

<a name="execcontextgetjobsstatus"></a>
# ExecContext.getJobsStatus()
should return the real-time status of all jobs at any time (here in series mode).

```js
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() ;
} ) ;
```

should return the real-time status of all jobs at any time (here in parallel mode).

```js
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() ;
} ) ;
```

should report the number of retry of a job, as well as the list of successives errors.

```js
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() ;
} ) ;
```

should give insight when a job call its callback twice or more.

```js
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() ;
	} ) ;
```

<a name="asyncforeach"></a>
# async.foreach()
should take each job as an element to pass to the iterator function.

```js
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 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

when the *iterator* accepts three arguments, the current key (array's index) is passed to it as the second argument.

```js
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 , key , callback ) {
	
	stats.startCounter[ element.id ] ++ ;
	expect( key ).to.equal( 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 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

if the container to iterate is an object, the current key (property name) is passed to it as the second argument.

```js
var stats = createStats( 3 ) ;

var myObject = {
	one: { id: 0 , name: 'one' , timeout: 10 , result: [ undefined , 'my' ] } ,
	two: { id: 1 , name: 'two' , timeout: 0 , result: [ undefined , 'wonderful' ] } ,
	three: { id: 2 , name: 'three' , timeout: 0 , result: [ undefined , 'result' ] }
} ;

async.foreach( myObject , function( element , key , callback ) {
	
	stats.startCounter[ element.id ] ++ ;
	expect( key ).to.equal( element.name ) ;
	
	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( { one: [ undefined , 'my' ], two: [ undefined , 'wonderful' ], three: [ undefined , 'result' ] } ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

when the *iterator* accepts (at least) four arguments, the whole job's array or object is passed to it as the third argument.

```js
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 , key , array , callback ) {
	
	stats.startCounter[ element.id ] ++ ;
	expect( key ).to.equal( element.id ) ;
	expect( array ).to.eql( myArray ) ;
	
	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 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

if a job fails, it should continue anyway processing others.

```js
var stats = createStats( 3 ) ;

var myArray = [
	{ id: 0 , timeout: 10 , result: [ undefined , 'my' ] } ,
	{ id: 1 , timeout: 0 , result: [ new Error() , '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.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( [ 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

<a name="asyncmap"></a>
# async.map()
should take each job as an element to pass to the iterator function, and create a new array with computed values and 1:1 mapping.

```js
var myArray = [ 'my' , 'wonderful' , 'result' ] ;

async.map( myArray , function( element , callback ) {
	
	setTimeout( function() {
		callback( undefined , element.length ) ;
	} , 0 ) ;
} )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ 2, 9, 6 ] ) ;
	done() ;
} ) ;
```

should take each job of an object as an element to pass to the iterator function, and create a new object with computed values and 1:1 mapping.

```js
var myObject = { one: 'my' , two: 'wonderful' , three: 'result' } ;

async.map( myObject , function( element , callback ) {
	
	setTimeout( function() {
		callback( undefined , element.length ) ;
	} , 0 ) ;
} )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( { one: 2, two: 9, three: 6 } ) ;
	done() ;
} ) ;
```

when the *iterator* accepts (at least) three arguments, the current key (array's index) is passed to it as the second argument.

```js
var myArray = [ 'my' , 'wonderful' , 'result' ] ;
var count = 0 ;

async.map( myArray , function( element , key , callback ) {
	
	expect( key ).to.equal( count ) ;
	count ++ ;
	
	setTimeout( function() {
		callback( undefined , element.length ) ;
	} , 0 ) ;
} )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ 2, 9, 6 ] ) ;
	done() ;
} ) ;
```

if the container to iterate is an object, the current key (property name) is passed to it as the second argument.

```js
var myObject = { my: 'my' , wonderful: 'wonderful' , result: 'result' } ;

async.map( myObject , function( element , key , callback ) {
	
	expect( key ).to.equal( element ) ;
	
	setTimeout( function() {
		callback( undefined , element.length ) ;
	} , 0 ) ;
} )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( { my: 2, wonderful: 9, result: 6 } ) ;
	done() ;
} ) ;
```

<a name="asyncreduce"></a>
# async.reduce()
should take each job as an element to pass to the iterator function, and trigger callback with an aggregated value.

```js
var myArray = [ 'my' , 'wonderful' , 'result' ] ;

async.reduce( myArray , 5 , function( aggregate , element , callback ) {
	
	setTimeout( function() {
		callback( undefined , aggregate + element.length ) ;
	} , 0 ) ;
} )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( 22 ) ;
	done() ;
} ) ;
```

if a default initial aggregate value is not supplied to async.reduce(), this initial value should be supplied as exec()'s first argument by default.

```js
var myArray = [ 'my' , 'wonderful' , 'result' ] ;

var plan = async.reduce( myArray , function( aggregate , element , callback ) {
	
	setTimeout( function() {
		callback( undefined , aggregate + element.length ) ;
	} , 0 ) ;
} )
.exec( 7 , function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( 24 ) ;
	done() ;
} ) ;
```

<a name="asyncwaterfall"></a>
# async.waterfall()
should run the series of job in waterfall mode: each job received the result of the previous, the final result is the result of the last job, the first job receive arguments from exec(), if any.

```js
var stats = createStats( 3 ) ;

async.waterfall( [
	function( str , callback ) {
		setTimeout( function() {
			callback( undefined , str + ' my' ) ;
		} , 10 ) ;
	} ,
	function( str , callback ) {
		setTimeout( function() {
			callback( undefined , str + ' wonderful' ) ;
		} , 20 ) ;
	} ,
	function( str , callback ) {
		setTimeout( function() {
			callback( undefined , str + ' result' ) ;
		} , 0 ) ;
	}
] )
.exec( 'oh' , function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.equal( 'oh my wonderful result' ) ;
	done() ;
} ) ;
```

using async.Plan.prototype.transmitError(), each job received the full list of arguments transmited by the previous job, including the error argument taht is truncated by default.

```js
var stats = createStats( 3 ) ;

async.waterfall( [
	function( str , callback ) {
		setTimeout( function() {
			callback( undefined , str + ' my' ) ;
		} , 10 ) ;
	} ,
	function( error , str , callback ) {
		setTimeout( function() {
			callback( undefined , str + ' wonderful' ) ;
		} , 20 ) ;
	} ,
	function( error , str , callback ) {
		setTimeout( function() {
			callback( undefined , str + ' result' ) ;
		} , 0 ) ;
	}
] )
.transmitError()
.exec( 'oh' , function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.equal( 'oh my wonderful result' ) ;
	done() ;
} ) ;
```

<a name="asyncrace"></a>
# async.race()
should run parallel racing jobs, and should trigger the callback after the fastest job complete, with the winning job's results only.

```js
var stats = createStats( 3 ) ;

async.race( [
	[ asyncJob , stats , 0 , 150 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 10 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 50 , {} , [ undefined , 'result' ] ]
] )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.equal( 'wonderful' ) ;
	expect( stats.endCounter ).to.eql( [ 0, 1, 0 ] ) ;
	done() ;
} ) ;
```

when some jobs have errors, it should return after the fastest successful job, other failed results are discarded.

```js
var stats = createStats( 3 ) ;

async.race( [
	[ asyncJob , stats , 0 , 150 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 10 , {} , [ new Error() , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 50 , {} , [ undefined , 'result' ] ]
] )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.equal( 'result' ) ;
	expect( stats.endCounter ).to.eql( [ 0, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 1, 2 ] ) ;
	done() ;
} ) ;
```

when all jobs have errors, it should return an error.

```js
var stats = createStats( 3 ) ;

async.race( [
	[ asyncJob , stats , 0 , 100 , {} , [ new Error() , 'my' ] ] ,
	[ asyncJob , stats , 1 , 10 , {} , [ new Error() , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 50 , {} , [ new Error() , 'result' ] ]
] )
.exec( function( error , results ) {
	expect( error ).to.be.an( Error ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 1, 2, 0 ] ) ;
	done() ;
} ) ;
```

when using a parallel limit, no new jobs should be processed after a job complete without error.

```js
var stats = createStats( 4 ) ;

async.race( [
	[ asyncJob , stats , 0 , 150 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 10 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 50 , {} , [ undefined , 'result' ] ] ,
	[ asyncJob , stats , 1 , 10 , {} , [ undefined , 'again' ] ]
] )
.parallel( 3 )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.equal( 'wonderful' ) ;
	expect( stats.startCounter ).to.eql( [ 1, 1, 1, 0 ] ) ;
	expect( stats.endCounter ).to.eql( [ 0, 1, 0, 0 ] ) ;
	done() ;
} ) ;
```

<a name="asyncwhile"></a>
# async.while()
while the while()'s callback's result is true, it should run jobs in series (by default), and do it again and again, the final result contains only the last iteration.

```js
var stats = createStats( 3 ) ;
var whileCount = 0 ;

async.while( function( error , results , callback ) {
	whileCount ++ ;
	callback( whileCount <= 3 ) ;
} )
.do( [
	[ asyncJob , stats , 0 , 30 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 15 , {} , [ undefined , 'result' ] ]
] )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ;
	expect( whileCount ).to.equal( 4 ) ;
	expect( stats.endCounter ).to.eql( [ 3, 3, 3 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2, 0, 1, 2, 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

when the while()'s callback has an error, no more iteration are performed, the last iteration results are transmitted, but the error in the while is transmitted as well.

```js
var stats = createStats( 3 ) ;
var whileCount = 0 ;

async.while( function( error , results , callback ) {
	whileCount ++ ;
	callback( whileCount <= 3 ? true : new Error() ) ;
} )
.do( [
	[ asyncJob , stats , 0 , 30 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 15 , {} , [ undefined , 'result' ] ]
] )
.exec( function( error , results ) {
	expect( error ).to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ;
	expect( whileCount ).to.equal( 4 ) ;
	expect( stats.endCounter ).to.eql( [ 3, 3, 3 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2, 0, 1, 2, 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

when using async.Plan.prototype.parallel(), it should run jobs in parallel, and start a new iteration only when all jobs in the current iteration have been completed, other behaviour are the same like in series.

```js
var stats = createStats( 3 ) ;
var whileCount = 0 ;

async.while( function( error , results , callback ) {
	whileCount ++ ;
	callback( whileCount <= 3 ) ;
} )
.do( [
	[ asyncJob , stats , 0 , 30 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 15 , {} , [ undefined , 'result' ] ]
] )
.parallel( Infinity )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ;
	expect( whileCount ).to.equal( 4 ) ;
	expect( stats.endCounter ).to.eql( [ 3, 3, 3 ] ) ;
	expect( stats.order ).to.eql( [ 1, 2, 0, 1, 2, 0, 1, 2, 0 ] ) ;
	done() ;
} ) ;
```

when the first call to while()'s callback's result is false, no jobs are even started, and the final result is empty.

```js
var stats = createStats( 3 ) ;
var whileCount = 0 ;

async.while( function( error , results , callback ) {
	whileCount ++ ;
	callback( false ) ;
} )
.do( [
	[ asyncJob , stats , 0 , 30 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 15 , {} , [ undefined , 'result' ] ]
] )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [] ) ;
	expect( whileCount ).to.equal( 1 ) ;
	expect( stats.endCounter ).to.eql( [ 0, 0, 0 ] ) ;
	expect( stats.order ).to.eql( [] ) ;
	done() ;
} ) ;
```

<a name="asyncdowhile"></a>
# async.do().while()
should work the same way as async.while() except that the while()'s callback's is evaluated at the end of the loop.

```js
var stats = createStats( 3 ) ;
var whileCount = 0 ;

async.do( [
	[ asyncJob , stats , 0 , 30 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 15 , {} , [ undefined , 'result' ] ]
] )
.while( function( error , results , callback ) {
	whileCount ++ ;
	callback( whileCount <= 3 ) ;
} )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ;
	expect( whileCount ).to.equal( 4 ) ;
	expect( stats.endCounter ).to.eql( [ 4, 4, 4 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

so even if the first call to while()'s callback's result is false, the first iteration is already done.

```js
var stats = createStats( 3 ) ;
var whileCount = 0 ;

async.do( [
	[ asyncJob , stats , 0 , 30 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 15 , {} , [ undefined , 'result' ] ]
] )
.while( function( error , results , callback ) {
	whileCount ++ ;
	callback( false ) ;
} )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ;
	expect( whileCount ).to.equal( 1 ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

<a name="asyncdorepeat"></a>
# async.do().repeat()
should repeat the action the given time.

```js
var stats = createStats( 3 ) ;

async.do( [
	[ asyncJob , stats , 0 , 20 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 10 , {} , [ undefined , 'result' ] ]
] )
.repeat( 4 )
.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( [ 4, 4, 4 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

<a name="async-conditional"></a>
# Async conditional
<a name="async-conditional-asyncifand"></a>
## async.if.and()
should evaluate async truthy && truthy && truthy to true, and run all jobs.

```js
var stats = createStats( 3 ) ;

async.if.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ true ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ 7 ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ 'wonderful' ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( true ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	done() ;
} ) ;
```

should evaluate async truthy && falsy && truthy to false, and run just the first and second jobs.

```js
var stats = createStats( 3 ) ;

async.if.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ true ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ null ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ 'wonderful' ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( false ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
	done() ;
} ) ;
```

should evaluate async falsy && falsy && falsy to false, and run just the first job.

```js
var stats = createStats( 3 ) ;

async.if.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ undefined ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ null ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ false ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( false ) ;
	expect( stats.endCounter ).to.eql( [ 1, 0, 0 ] ) ;
	done() ;
} ) ;
```

<a name="async-conditional-asyncifor"></a>
## async.if.or()
should evaluate async truthy || truthy || truthy to true, and run only the first jobs.

```js
var stats = createStats( 3 ) ;

async.if.or( [
	[ asyncJob , stats , 0 , 0 , {} , [ true ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ 7 ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ 'wonderful' ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( true ) ;
	expect( stats.endCounter ).to.eql( [ 1, 0, 0 ] ) ;
	done() ;
} ) ;
```

should evaluate async falsy || truthy || falsy to true, and run just the first and second jobs.

```js
var stats = createStats( 3 ) ;

async.if.or( [
	[ asyncJob , stats , 0 , 0 , {} , [ undefined ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ 7 ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ false ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( true ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
	done() ;
} ) ;
```

should evaluate async falsy || falsy || falsy to false, and run all jobs.

```js
var stats = createStats( 3 ) ;

async.if.or( [
	[ asyncJob , stats , 0 , 0 , {} , [ undefined ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ null ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ false ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( false ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	done() ;
} ) ;
```

<a name="async-conditional-asyncand"></a>
## async.and()
should evaluate async true && 7 && 'wonderful' to 'wonderful', and run all jobs.

```js
var stats = createStats( 3 ) ;

async.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ true ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ 7 ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ 'wonderful' ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( 'wonderful' ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	done() ;
} ) ;
```

should evaluate async true && 0 && 'wonderful' to 0, and run just the first and second jobs.

```js
var stats = createStats( 3 ) ;

async.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ true ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ 0 ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ 'wonderful' ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( 0 ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
	done() ;
} ) ;
```

should evaluate async undefined && null && false to undefined, and run just the first job.

```js
var stats = createStats( 3 ) ;

async.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ undefined ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ null ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ false ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( undefined ) ;
	expect( stats.endCounter ).to.eql( [ 1, 0, 0 ] ) ;
	done() ;
} ) ;
```

<a name="async-conditional-asyncor"></a>
## async.or()
should evaluate async 7 || true || 'wonderful' to 7, and run only the first jobs.

```js
var stats = createStats( 3 ) ;

async.or( [
	[ asyncJob , stats , 0 , 0 , {} , [ 7 ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ true ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ 'wonderful' ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( 7 ) ;
	expect( stats.endCounter ).to.eql( [ 1, 0, 0 ] ) ;
	done() ;
} ) ;
```

should evaluate async undefined || 7 || false to 7, and run just the first and second jobs.

```js
var stats = createStats( 3 ) ;

async.or( [
	[ asyncJob , stats , 0 , 0 , {} , [ undefined ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ 7 ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ false ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( 7 ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
	done() ;
} ) ;
```

should evaluate async undefined || null || '' to '', and run all jobs.

```js
var stats = createStats( 3 ) ;

async.or( [
	[ asyncJob , stats , 0 , 0 , {} , [ undefined ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ null ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ '' ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( '' ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	done() ;
} ) ;
```

<a name="async-conditional-nested-asyncor-and-asyncand-in-asyncif"></a>
## nested async.or() and async.and() in async.if()
should evaluate async ( truthy || falsy ) && truthy to true, and run first and third jobs.

```js
var stats = createStats( 3 ) ;

async.if.and( [
	async.or( [
		[ asyncJob , stats , 0 , 0 , {} , [ 'wonderful' ] ] ,
		[ asyncJob , stats , 1 , 0 , {} , [ false ] ]
	] ) ,
	[ asyncJob , stats , 2 , 0 , {} , [ true ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( true ) ;
	expect( stats.endCounter ).to.eql( [ 1, 0, 1 ] ) ;
	done() ;
} ) ;
```

should evaluate async ( falsy || truthy ) && falsy to false, and run all jobs.

```js
var stats = createStats( 3 ) ;

async.if.and( [
	async.or( [
		[ asyncJob , stats , 0 , 0 , {} , [ undefined ] ] ,
		[ asyncJob , stats , 1 , 0 , {} , [ 7 ] ]
	] ) ,
	[ asyncJob , stats , 2 , 0 , {} , [ 0 ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( false ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	done() ;
} ) ;
```

should evaluate async ( truthy && falsy ) || truthy to true, and run all jobs.

```js
var stats = createStats( 3 ) ;

async.if.or( [
	async.and( [
		[ asyncJob , stats , 0 , 0 , {} , [ 'wonderful' ] ] ,
		[ asyncJob , stats , 1 , 0 , {} , [ false ] ]
	] ) ,
	[ asyncJob , stats , 2 , 0 , {} , [ true ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( true ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	done() ;
} ) ;
```

should evaluate async ( falsy && truthy ) || falsy to false, and run the first and third jobs.

```js
var stats = createStats( 3 ) ;

async.if.or( [
	async.and( [
		[ asyncJob , stats , 0 , 0 , {} , [ undefined ] ] ,
		[ asyncJob , stats , 1 , 0 , {} , [ 7 ] ]
	] ) ,
	[ asyncJob , stats , 2 , 0 , {} , [ 0 ] ]
] )
.exec( function( result ) {
	expect( result ).to.equal( false ) ;
	expect( stats.endCounter ).to.eql( [ 1, 0, 1 ] ) ;
	done() ;
} ) ;
```

<a name="async-conditional-asyncplanprototypeboolean"></a>
## async.Plan.prototype.boolean()
should force async.and()'s result to be a boolean, so 'wonderful' && 7 should evaluate to true.

```js
var stats = createStats( 2 ) ;

async.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ 'wonderful' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ 7 ] ]
] )
.boolean()
.exec( function( result ) {
	expect( result ).to.equal( true ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1 ] ) ;
	done() ;
} ) ;
```

using .boolean( false ), it should force async.if.and()'s result to preserve the last evaluated value (the javascript way), so 'wonderful' && 7 should evaluate to 7.

```js
var stats = createStats( 2 ) ;

async.if.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ 'wonderful' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ 7 ] ]
] )
.boolean( false )
.exec( function( result ) {
	expect( result ).to.equal( 7 ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1 ] ) ;
	done() ;
} ) ;
```

<a name="asyncplanprototypethen-else-catch-finally-execthencatch-execthenelse-and-execthenelsecatch"></a>
# async.Plan.prototype.then(), .else(), .catch(), .finally(), .execThenCatch(), .execThenElse() and .execThenElseCatch()
should run a series of successful jobs and trigger in-plan and in-exec then() and finally().

```js
var stats = createStats( 3 ) ;

async.do( [
	[ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ]
] )
.then( function( results ) {
	stats.plan.then ++ ;
	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 ] ) ;
} )
.catch( function( error , results ) {
	stats.plan.catch ++ ;
	done( new Error( "Should not trigger catch()" ) ) ;
} )
.finally( function( error , results ) {
	stats.plan.finally ++ ;
	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 ] ) ;
} )
.execThenCatch(
	function( results ) {
		stats.exec.then ++ ;
		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 ] ) ;
	} ,
	function( error , results ) {
		stats.exec.catch ++ ;
		done( new Error( "Should not trigger catch()" ) ) ;
	} ,
	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 ] ) ;
		expect( stats.plan.then ).to.equal( 1 ) ;
		expect( stats.plan.catch ).to.equal( 0 ) ;
		expect( stats.plan.finally ).to.equal( 1 ) ;
		expect( stats.exec.then ).to.equal( 1 ) ;
		expect( stats.exec.catch ).to.equal( 0 ) ;
		done() ;
	}
) ;
```

should run a series of jobs, interrupted by an error, and trigger in-plan and in-exec catch() and finally().

```js
var stats = createStats( 3 ) ;

async.do( [
	[ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 100 , {} , [ new Error() , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ]
] )
.then( function( results ) {
	stats.plan.then ++ ;
	done( new Error( "Should not trigger then()" ) ) ;
} )
.catch( function( error , results ) {
	stats.plan.catch ++ ;
	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 ] ) ;
} )
.finally( function( error , results ) {
	stats.plan.finally ++ ;
	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 ] ) ;
} )
.execThenCatch(
	function( results ) {
		stats.exec.then ++ ;
		done( new Error( "Should not trigger then()" ) ) ;
	} ,
	function( error , results ) {
		stats.exec.catch ++ ;
		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 ] ) ;
	} ,
	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 ] ) ;
		expect( stats.plan.then ).to.equal( 0 ) ;
		expect( stats.plan.catch ).to.equal( 1 ) ;
		expect( stats.plan.finally ).to.equal( 1 ) ;
		expect( stats.exec.then ).to.equal( 0 ) ;
		expect( stats.exec.catch ).to.equal( 1 ) ;
		done() ;
	}
) ;
```

should evaluate async truthy && truthy && truthy to true, and trigger in-plan and in-exec then() and finally().

```js
var stats = createStats( 3 ) ;

async.if.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ true ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ 7 ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ 'wonderful' ] ]
] )
.then( function( result ) {
	stats.plan.then ++ ;
	expect( result ).to.equal( true ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
} )
.else( function( result ) {
	stats.plan.else ++ ;
	done( new Error( "Should not trigger else()" ) ) ;
} )
.catch( function( error ) {
	stats.plan.catch ++ ;
	done( new Error( "Should not trigger catch()" ) ) ;
} )
.finally( function( result ) {
	stats.plan.finally ++ ;
	expect( result ).to.equal( true ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
} )
.execThenElseCatch(
	function( result ) {
		stats.exec.then ++ ;
		expect( result ).to.equal( true ) ;
		expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	} ,
	function( result ) {
		stats.exec.else ++ ;
		done( new Error( "Should not trigger else()" ) ) ;
	} ,
	function( error ) {
		stats.exec.catch ++ ;
		done( new Error( "Should not trigger catch()" ) ) ;
	} ,
	function( result ) {
		expect( result ).to.equal( true ) ;
		expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
		expect( stats.plan.then ).to.equal( 1 ) ;
		expect( stats.plan.else ).to.equal( 0 ) ;
		expect( stats.plan.catch ).to.equal( 0 ) ;
		expect( stats.plan.finally ).to.equal( 1 ) ;
		expect( stats.exec.then ).to.equal( 1 ) ;
		expect( stats.exec.else ).to.equal( 0 ) ;
		expect( stats.exec.catch ).to.equal( 0 ) ;
		done() ;
	}
) ;
```

should evaluate async truthy && falsy && truthy to false, and trigger in-plan and in-exec else() and finally().

```js
var stats = createStats( 3 ) ;

async.if.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ true ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ 0 ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ 'wonderful' ] ]
] )
.then( function( result ) {
	stats.plan.then ++ ;
	done( new Error( "Should not trigger then()" ) ) ;
} )
.else( function( result ) {
	stats.plan.else ++ ;
	expect( result ).to.equal( false ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
} )
.catch( function( error ) {
	stats.plan.catch ++ ;
	done( new Error( "Should not trigger catch()" ) ) ;
} )
.finally( function( result ) {
	stats.plan.finally ++ ;
	expect( result ).to.equal( false ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
} )
.execThenElseCatch(
	function( result ) {
		stats.exec.then ++ ;
		done( new Error( "Should not trigger then()" ) ) ;
	} ,
	function( result ) {
		stats.exec.else ++ ;
		expect( result ).to.equal( false ) ;
		expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
	} ,
	function( error ) {
		stats.exec.catch ++ ;
		done( new Error( "Should not trigger catch()" ) ) ;
	} ,
	function( result ) {
		expect( result ).to.equal( false ) ;
		expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
		expect( stats.plan.then ).to.equal( 0 ) ;
		expect( stats.plan.else ).to.equal( 1 ) ;
		expect( stats.plan.catch ).to.equal( 0 ) ;
		expect( stats.plan.finally ).to.equal( 1 ) ;
		expect( stats.exec.then ).to.equal( 0 ) ;
		expect( stats.exec.else ).to.equal( 1 ) ;
		expect( stats.exec.catch ).to.equal( 0 ) ;
		done() ;
	}
) ;
```

should evaluate async truthy && Error && truthy to Error, and trigger in-plan and in-exec catch() and finally().

```js
var stats = createStats( 3 ) ;

async.if.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ true ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ new Error() ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ 'wonderful' ] ]
] )
.then( function( result ) {
	stats.plan.then ++ ;
	done( new Error( "Should not trigger then()" ) ) ;
} )
.else( function( result ) {
	stats.plan.else ++ ;
	done( new Error( "Should not trigger else()" ) ) ;
} )
.catch( function( error ) {
	stats.plan.catch ++ ;
	expect( error ).to.be.an( Error ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
} )
.finally( function( result ) {
	stats.plan.finally ++ ;
	expect( result ).to.be.an( Error ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
} )
.execThenElseCatch(
	function( result ) {
		stats.exec.then ++ ;
		done( new Error( "Should not trigger then()" ) ) ;
	} ,
	function( result ) {
		stats.exec.else ++ ;
		done( new Error( "Should not trigger else()" ) ) ;
	} ,
	function( error ) {
		stats.exec.catch ++ ;
		expect( error ).to.be.an( Error ) ;
		expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
	} ,
	function( result ) {
		expect( result ).to.be.an( Error ) ;
		expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
		expect( stats.plan.then ).to.equal( 0 ) ;
		expect( stats.plan.else ).to.equal( 0 ) ;
		expect( stats.plan.catch ).to.equal( 1 ) ;
		expect( stats.plan.finally ).to.equal( 1 ) ;
		expect( stats.exec.then ).to.equal( 0 ) ;
		expect( stats.exec.else ).to.equal( 0 ) ;
		expect( stats.exec.catch ).to.equal( 1 ) ;
		done() ;
	}
) ;
```

when there isn't any catch() and a job has an error, it should trigger in-plan and in-exec else() and finally().

```js
var stats = createStats( 3 ) ;

async.if.and( [
	[ asyncJob , stats , 0 , 0 , {} , [ true ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ new Error() ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ 'wonderful' ] ]
] )
.then( function( result ) {
	stats.plan.then ++ ;
	done( new Error( "Should not trigger then()" ) ) ;
} )
.else( function( result ) {
	stats.plan.else ++ ;
	expect( result ).to.be( false ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
} )
.finally( function( result ) {
	stats.plan.finally ++ ;
	expect( result ).to.be( false ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
} )
.execThenElse(
	function( result ) {
		stats.exec.then ++ ;
		done( new Error( "Should not trigger then()" ) ) ;
	} ,
	function( result ) {
		stats.exec.else ++ ;
		expect( result ).to.be( false ) ;
		expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
	} ,
	function( result ) {
		expect( result ).to.be( false ) ;
		expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
		expect( stats.plan.then ).to.equal( 0 ) ;
		expect( stats.plan.else ).to.equal( 1 ) ;
		expect( stats.plan.finally ).to.equal( 1 ) ;
		expect( stats.exec.then ).to.equal( 0 ) ;
		expect( stats.exec.else ).to.equal( 1 ) ;
		done() ;
	}
) ;
```

<a name="asyncplanprototypetimeout"></a>
# async.Plan.prototype.timeout()
should abort job in a series that take too much time to complete, its result should be an error.

```js
var stats = createStats( 3 ) ;

async.series( [
	[ asyncJob , stats , 0 , 0 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 50 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ]
] )
.timeout( 20 )
.exec( function( error , results ) {
	expect( error ).to.be.an( async.AsyncError ) ;
	expect( error ).to.be.an( Error ) ;	// ensure that async.AsyncError is an instance of Error
	expect( results ).to.eql( [ [ undefined , 'my' ] , [ new async.AsyncError( 'jobTimeout' ) ] ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 0, 0 ] ) ;
	expect( stats.order ).to.eql( [ 0 ] ) ;
	done() ;
} ) ;
```

should abort job in a parallel flow that take too much time to complete, its result should be an error.

```js
var stats = createStats( 3 ) ;

async.parallel( [
	[ asyncJob , stats , 0 , 0 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 50 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ]
] )
.timeout( 20 )
.exec( function( error , results ) {
	expect( error ).to.be.an( async.AsyncError ) ;
	expect( error ).to.be.an( Error ) ;	// ensure that async.AsyncError is an instance of Error
	expect( results ).to.eql( [ [ undefined , 'my' ] , [ new async.AsyncError( 'jobTimeout' ) ] , [ undefined , 'result' ] ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 0, 1 ] ) ;
	expect( stats.order ).to.eql( [ 0, 2 ] ) ;
	done() ;
} ) ;
```

<a name="asyncplanprototyperetry"></a>
# async.Plan.prototype.retry()
should retry a series of job with failure the good amount of time, in the good order, then succeed and return the good results.

```js
var stats = createStats( 3 ) ;

async.do( [
	[ asyncJob , stats , 0 , 20 , { failCount: 3 } , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 10 , { failCount: 5 } , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 5 , { failCount: 2 } , [ undefined , 'result' ] ]
] )
.retry( 10 , 5 )
.exec( function( error , results ) {
	//console.log( arguments ) ;
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ] , [ undefined , 'wonderful' ] , [ undefined , 'result' ] ] ) ;
	expect( stats.endCounter ).to.eql( [ 4, 6, 3 ] ) ;
	expect( stats.order ).to.eql( [ 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2 ] ) ;
	done() ;
} ) ;
```

should retry parallel jobs with failure the good amount of time, then succeed and return the good results.

```js
var stats = createStats( 3 ) ;

async.parallel( [
	[ asyncJob , stats , 0 , 20 , { failCount: 3 } , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 10 , { failCount: 5 } , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 5 , { failCount: 2 } , [ undefined , 'result' ] ]
] )
.retry( 10 , 5 )
.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( [ 4, 6, 3 ] ) ;
	// stats.order is not relevant here
	done() ;
} ) ;
```

should retry many times, and evaluate async falsy || falsy || truthy to true.

```js
var stats = createStats( 3 ) ;

async.or( [
	[ asyncJob , stats , 0 , 20 , { failCount: 3 } , [ undefined , false ] ] ,
	[ asyncJob , stats , 1 , 10 , { failCount: 5 } , [ undefined , 0 ] ] ,
	[ asyncJob , stats , 2 , 5 , { failCount: 2 } , [ undefined , 'wonderful' ] ]
] )
.retry( 10 )
.exec( function( result ) {
	expect( result ).to.equal( 'wonderful' ) ;
	expect( stats.endCounter ).to.eql( [ 4, 6, 3 ] ) ;
	done() ;
} ) ;
```

should retry many times, and evaluate async truthy && truthy && truthy to true.

```js
var stats = createStats( 3 ) ;

async.and( [
	[ asyncJob , stats , 0 , 20 , { failCount: 3 } , [ undefined , true ] ] ,
	[ asyncJob , stats , 1 , 10 , { failCount: 5 } , [ undefined , 7 ] ] ,
	[ asyncJob , stats , 2 , 5 , { failCount: 2 } , [ undefined , 'wonderful' ] ]
] )
.retry( 10 )
.exec( function( result ) {
	expect( result ).to.equal( 'wonderful' ) ;
	expect( stats.endCounter ).to.eql( [ 4, 6, 3 ] ) ;
	done() ;
} ) ;
```

<a name="mixing-asyncplanprototyperetry--asyncplanprototypetimeout"></a>
# Mixing async.Plan.prototype.retry() & async.Plan.prototype.timeout()
when a job timeout and is still pending, it should be retried, if the second try complete before, it transmit its result.

```js
var stats = createStats( 3 ) ;

async.do( [
	[ asyncJob , stats , 0 , 5 , {} , [ undefined , 'my' ] ] ,
	function( callback ) {
		var timeout , result ;
		
		stats.startCounter[ 1 ] ++ ;
		timeout = 0 ;
		
		switch ( stats.startCounter[ 1 ] )
		{
			case 1 :
				result = '1st' ;
				timeout = 100 ;
				break ;
			case 2 :
				result = '2nd' ;
				break ;
			case 3 :
				result = '3rd' ;
				break ;
			default :
				result = '' + stats.startCounter[ 1 ] + 'th' ;
				break ;
		}
		
		setTimeout( function() {
			stats.endCounter[ 1 ] ++ ;
			stats.order.push( 1 ) ;
			callback( undefined , result ) ;
		} , timeout ) ;
	} ,
	[ asyncJob , stats , 2 , 5 , {} , [ undefined , 'result' ] ]
] )
.timeout( 20 )
.retry( 5 )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ] , [ undefined , '2nd' ] , [ undefined , 'result' ] ] ) ;
	expect( stats.startCounter ).to.eql( [ 1, 2, 1 ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

be careful when mixing .timeout() and .retry(), if a job timeout and retry, the first try may finally complete before others tries, so it should return the result of the first try to complete without error.

```js
var stats = createStats( 3 ) ;

async.do( [
	[ asyncJob , stats , 0 , 5 , {} , [ undefined , 'my' ] ] ,
	function( callback ) {
		var timeout , result ;
		
		stats.startCounter[ 1 ] ++ ;
		timeout = 50 ;
		
		switch ( stats.startCounter[ 1 ] )
		{
			case 1 :
				result = '1st' ;
				break ;
			case 2 :
				result = '2nd' ;
				break ;
			case 3 :
				result = '3rd' ;
				break ;
			default :
				result = '' + stats.startCounter[ 1 ] + 'th' ;
				break ;
		}
		
		setTimeout( function() {
			stats.endCounter[ 1 ] ++ ;
			stats.order.push( 1 ) ;
			callback( undefined , result ) ;
		} , timeout ) ;
	} ,
	[ asyncJob , stats , 2 , 5 , {} , [ undefined , 'result' ] ]
] )
.timeout( 20 )
.retry( 5 )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ] , [ undefined , '1st' ] , [ undefined , 'result' ] ] ) ;
	expect( stats.startCounter ).to.eql( [ 1, 3, 1 ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

when a job's first try timeout, a second try kick in, and then the first try finish with an error before the second try complete, the second try result is used.

```js
var stats = createStats( 3 ) ;

async.do( [
	[ asyncJob , stats , 0 , 5 , {} , [ undefined , 'my' ] ] ,
	function( callback ) {
		var timeout , error , result ;
		
		stats.startCounter[ 1 ] ++ ;
		timeout = 50 ;
		error = undefined ;
		
		switch ( stats.startCounter[ 1 ] )
		{
			case 1 :
				result = '1st' ;
				error = new Error( "Failed!" ) ;
				break ;
			//case 1 : result = '1st' ; break ;
			case 2 :
				result = '2nd' ;
				break ;
			case 3 :
				result = '3rd' ;
				break ;
			default :
				result = '' + stats.startCounter[ 1 ] + 'th' ;
				break ;
		}
		
		setTimeout( function() {
			stats.endCounter[ 1 ] ++ ;
			stats.order.push( 1 ) ;
			callback( error , result ) ;
		} , timeout ) ;
	} ,
	[ asyncJob , stats , 2 , 5 , {} , [ undefined , 'result' ] ]
] )
.timeout( 40 )
.retry( 1 )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ] , [ undefined , '2nd' ] , [ undefined , 'result' ] ] ) ;
	expect( stats.startCounter ).to.eql( [ 1, 2, 1 ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 2, 1 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 1, 2 ] ) ;
	done() ;
} ) ;
```

<a name="asyncplanprototypeparallel"></a>
# async.Plan.prototype.parallel()
should run parallel jobs, with a limit of jobs running at a time.

```js
var stats = createStats( 6 ) ;

async.parallel( [
	[ asyncJob , stats , 0 , 60 , {} , [ undefined , 'one' ] ] ,	// @60
	[ asyncJob , stats , 1 , 20 , {} , [ undefined , 'two' ] ] ,	// @20
	[ asyncJob , stats , 2 , 40 , {} , [ undefined , 'three' ] ] ,	// @40
	[ asyncJob , stats , 3 , 0 , {} , [ undefined , 'four' ] ] ,	// @20+
	[ asyncJob , stats , 4 , 30 , {} , [ undefined , 'five' ] ] ,	// @50+
	[ asyncJob , stats , 5 , 0 , {} , [ undefined , 'six' ] ]	// @40+
] )
.parallel( 3 )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined, 'one' ], [ undefined, 'two' ], [ undefined, 'three' ], [ undefined, 'four' ], [ undefined, 'five' ], [ undefined, 'six' ] ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1, 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 1, 3, 2, 5, 4, 0 ] ) ;
	done() ;
} ) ;
```

<a name="asyncplanprototypefatal"></a>
# async.Plan.prototype.fatal()
should run the series of job and continue on error.

```js
var stats = createStats( 3 ) ;

async.do( [
	[ asyncJob , stats , 0 , 20 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ new Error() , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 10 , {} , [ undefined , 'result' ] ]
] )
.fatal( false )
.exec( function( error , results ) {
	expect( error ).not.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( [ 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

should run parallel jobs and continue on error.

```js
var stats = createStats( 3 ) ;

async.parallel( [
	[ asyncJob , stats , 0 , 20 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 0 , {} , [ new Error() , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 10 , {} , [ undefined , 'result' ] ]
] )
.fatal( false )
.exec( function( error , results ) {
	expect( error ).not.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( [ 1, 2, 0 ] ) ;
	done() ;
} ) ;
```

<a name="asyncplanprototypelastjobonly"></a>
# async.Plan.prototype.lastJobOnly()
should run the series of job and pass only the results of the last job.

```js
var stats = createStats( 3 ) ;

async.series( [
	[ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ]
] )
.lastJobOnly()
.exec( function() {
	var args = Array.prototype.slice.call( arguments ) ;
	expect( args ).to.eql( [ undefined , 'result' ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 0, 1, 2 ] ) ;
	done() ;
} ) ;
```

should run jobs in parallel and pass only the results of the last job - can produce random result with parallel mode!.

```js
var stats = createStats( 3 ) ;

async.parallel( [
	[ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ]
] )
.lastJobOnly()
.exec( function() {
	var args = Array.prototype.slice.call( arguments ) ;
	expect( args ).to.eql( [ undefined , 'wonderful' ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ;
	done() ;
} ) ;
```

<a name="asyncplanprototypemapping1to1"></a>
# async.Plan.prototype.mapping1to1()
the results should map one to one the job's list, any extra arguments passed to the job's callback should be ignored.

```js
var stats = createStats( 3 ) ;

async.parallel( [
	[ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' , 'extra argument that will be dropped' ] ]
] )
.mapping1to1()
.exec( function( error , results ) {
	expect( results ).to.eql( [ 'my' , 'wonderful' , 'result' ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ;
	done() ;
} ) ;
```

when using an object as the job's list, the result is an object mapping one to one the job's list.

```js
var stats = createStats( 3 ) ;

async.parallel( {
	one: [ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] ,
	two: [ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] ,
	three: [ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' , 'extra argument that will be dropped' ] ]
} )
.mapping1to1()
.exec( function( error , results ) {
	expect( results ).to.eql( { one: 'my' , two: 'wonderful' , three: 'result' } ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 2, 0, 1 ] ) ;
	done() ;
} ) ;
```

<a name="asyncplanprototypeexeckv"></a>
# async.Plan.prototype.execKV()
should pass an object with inputs arguments in 'inputs' property and 'then' & 'finally' callback in properties of the same name.

```js
var stats = createStats( 3 ) ;
var then ;

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 ) ;
	}
] )
.execKV( {
	inputs: [ 'some data' , 'blahblihblah' ],
	then: function( results ) {
		then = true ;
		expect( results ).to.eql( [
			[ undefined , 'DESCRIPTION: some data' ] ,
			[ undefined , 'LENGTH: 12' ] ,
			[ undefined , 'BODY: blahblihblah' ]
		] ) ;
	} ,
	'finally': function( error , results ) {
		expect( error ).not.to.be.an( Error ) ;
		expect( then ).to.equal( true ) ;
		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() ;
	}
} ) ;
```

should accept 'catch' callback in the 'catch' property.

```js
var stats = createStats( 3 ) ;
var then , catch_ ;

async.series( [
	[ asyncJob , stats , 0 , 50 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 100 , {} , [ new Error() , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 0 , {} , [ undefined , 'result' ] ]
] )
.execKV( {
	inputs: [ 'some data' , 'blahblihblah' ],
	then: function( results ) {
		then = true ;
	} ,
	'catch': function( results ) {
		catch_ = true ;
	} ,
	'finally': function( error , results ) {
		expect( error ).to.be.an( Error ) ;
		expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
		expect( stats.order ).to.eql( [ 0, 1 ] ) ;
		expect( then ).to.not.be.equal( true ) ;
		expect( catch_ ).to.equal( true ) ;
		expect( results ).to.eql( [ [ undefined , 'my' ], [ new Error() , 'wonderful' ] ] ) ;
		done() ;
	}
} ) ;
```

should accept the aggegate property as well.

```js
var myArray = [ 'my' , 'wonderful' , 'result' ] ;
var then ;

async.reduce( myArray , 5 , function( aggregate , element , callback ) {
	
	setTimeout( function() {
		callback( undefined , aggregate + element.length ) ;
	} , 0 ) ;
} )
.execKV( {
	aggregate: 11,
	'then': function( results ) {
		then = true ;
		expect( results ).to.eql( 28 ) ;
	} ,
	'finally': function( error , results ) {
		expect( error ).not.to.be.an( Error ) ;
		expect( then ).to.equal( true ) ;
		expect( results ).to.eql( 28 ) ;
		done() ;
	}
} ) ;
```

<a name="events"></a>
# Events
should trigger a 'progress' event after each jobs of a series complete, the 'resolved' event triggers callbacks, the 'finish' event should be triggered after all callbacks and 'progress' event.

```js
var stats = createStats( 3 ) ;
var finallyTriggered = false ;
var resolvedTriggered = false ;

var context = async.series( [
	[ asyncJob , stats , 0 , 10 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 10 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 10 , {} , [ undefined , 'result' ] ]
] )
.nice( 0 )
.exec( function( error , results ) {
	finallyTriggered = true ;
	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 ] ) ;
} ) ;

expect( context ).to.be.an( async.ExecContext ) ;

var progressCount = 0 ;

context.on( 'progress' , function( progressStatus , error , results ) {
	
	progressCount ++ ;
	expect( error ).not.to.be.an( Error ) ;
	
	switch ( progressCount )
	{
		case 1 :
			expect( progressStatus ).to.eql( { loop: 0, resolved: 1, ok: 1, failed: 0, pending: 0, waiting: 2 } ) ;
			expect( results ).to.eql( [ [ undefined , 'my' ], undefined ] ) ;
			expect( stats.endCounter ).to.eql( [ 1, 0, 0 ] ) ;
			expect( stats.order ).to.eql( [ 0 ] ) ;
			break ;
		case 2 :
			expect( progressStatus ).to.eql( { loop: 0, resolved: 2, ok: 2, failed: 0, pending: 0, waiting: 1 } ) ;
			expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], undefined ] ) ;
			expect( stats.endCounter ).to.eql( [ 1, 1, 0 ] ) ;
			expect( stats.order ).to.eql( [ 0, 1 ] ) ;
			break ;
		case 3 :
			expect( progressStatus ).to.eql( { loop: 0, resolved: 3, ok: 3, failed: 0, pending: 0, waiting: 0 } ) ;
			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 ] ) ;
			break ;
		default :
			throw new Error( 'progress event received too much time' ) ;
	}
} ) ;

context.on( 'resolved' , function( error , results ) {
	resolvedTriggered = true ;
	expect( progressCount ).to.be( 2 ) ; // resolved is triggered before the last 'progress' event
	expect( finallyTriggered ).to.be( true ) ;
	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 ] ) ;
} ) ;

context.on( 'finish' , function( error , results ) {
	expect( progressCount ).to.be( 3 ) ;
	expect( finallyTriggered ).to.be( true ) ;
	expect( resolvedTriggered ).to.be( true ) ;
	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() ;
} ) ;
```

should trigger a 'progress' event after each jobs of a parallel batch complete, the 'resolved' event triggers callbacks, the 'finish' event should be triggered after all callbacks and 'progress' event.

```js
var stats = createStats( 3 ) ;
var finallyTriggered = false ;
var resolvedTriggered = false ;

var context = async.parallel( [
	[ asyncJob , stats , 0 , 0 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 50 , {} , [ undefined , 'result' ] ]
] )
.nice( 0 )
.exec( function( error , results ) {
	finallyTriggered = true ;
	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, 2, 1 ] ) ;
} ) ;

expect( context ).to.be.an( async.ExecContext ) ;

var progressCount = 0 ;

context.on( 'progress' , function( progressStatus , error , results ) {
	
	progressCount ++ ;
	expect( error ).not.to.be.an( Error ) ;
	
	switch ( progressCount )
	{
		case 1 :
			expect( progressStatus ).to.eql( { loop: 0, resolved: 1, ok: 1, failed: 0, pending: 2, waiting: 0 } ) ;
			expect( results ).to.eql( [ [ undefined , 'my' ], undefined, undefined ] ) ;
			expect( stats.endCounter ).to.eql( [ 1, 0, 0 ] ) ;
			expect( stats.order ).to.eql( [ 0 ] ) ;
			break ;
		case 2 :
			expect( progressStatus ).to.eql( { loop: 0, resolved: 2, ok: 2, failed: 0, pending: 1, waiting: 0 } ) ;
			expect( results ).to.eql( [ [ undefined , 'my' ], undefined, [ undefined , 'result' ] ] ) ;
			expect( stats.endCounter ).to.eql( [ 1, 0, 1 ] ) ;
			expect( stats.order ).to.eql( [ 0, 2 ] ) ;
			break ;
		case 3 :
			expect( progressStatus ).to.eql( { loop: 0, resolved: 3, ok: 3, failed: 0, pending: 0, waiting: 0 } ) ;
			expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ undefined , 'result' ] ] ) ;
			expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
			expect( stats.order ).to.eql( [ 0, 2, 1 ] ) ;
			break ;
		default :
			throw new Error( 'progress event received too much time' ) ;
	}
} ) ;

context.on( 'resolved' , function( error , results ) {
	resolvedTriggered = true ;
	expect( progressCount ).to.be( 2 ) ; // resolved is triggered before the last 'progress' event
	expect( finallyTriggered ).to.be( true ) ;
	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, 2, 1 ] ) ;
} ) ;

context.on( 'finish' , function( error , results ) {
	expect( progressCount ).to.be( 3 ) ;
	expect( finallyTriggered ).to.be( true ) ;
	expect( resolvedTriggered ).to.be( true ) ;
	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, 2, 1 ] ) ;
	done() ;
} ) ;
```

in parallel mode, when an error occurs the 'resolved' event is triggered, however if another job is running, the 'finish' event is triggered only when it is done.

```js
var stats = createStats( 3 ) ;
var finallyTriggered = false ;
var resolvedTriggered = false ;

var context = async.parallel( [
	[ asyncJob , stats , 0 , 0 , {} , [ undefined , 'my' ] ] ,
	[ asyncJob , stats , 1 , 100 , {} , [ undefined , 'wonderful' ] ] ,
	[ asyncJob , stats , 2 , 50 , {} , [ new Error() ] ]
] )
.nice( 0 )
.exec( function( error , results ) {
	finallyTriggered = true ;
	expect( error ).to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ], undefined, [ new Error() ] ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 0, 1 ] ) ;
	expect( stats.order ).to.eql( [ 0, 2 ] ) ;
} ) ;

expect( context ).to.be.an( async.ExecContext ) ;

var progressCount = 0 ;

context.on( 'progress' , function( progressStatus , error , results ) {
	
	progressCount ++ ;
	
	switch ( progressCount )
	{
		case 1 :
			expect( error ).not.to.be.an( Error ) ;
			expect( progressStatus ).to.eql( { loop: 0, resolved: 1, ok: 1, failed: 0, pending: 2, waiting: 0 } ) ;
			expect( results ).to.eql( [ [ undefined , 'my' ], undefined, undefined ] ) ;
			expect( stats.endCounter ).to.eql( [ 1, 0, 0 ] ) ;
			expect( stats.order ).to.eql( [ 0 ] ) ;
			break ;
		case 2 :
			expect( error ).to.be.an( Error ) ;
			expect( progressStatus ).to.eql( { loop: 0, resolved: 2, ok: 1, failed: 1, pending: 1, waiting: 0 } ) ;
			expect( results ).to.eql( [ [ undefined , 'my' ], undefined, [ new Error() ] ] ) ;
			expect( stats.endCounter ).to.eql( [ 1, 0, 1 ] ) ;
			expect( stats.order ).to.eql( [ 0, 2 ] ) ;
			break ;
		case 3 :
			expect( error ).to.be.an( Error ) ;
			expect( progressStatus ).to.eql( { loop: 0, resolved: 3, ok: 2, failed: 1, pending: 0, waiting: 0 } ) ;
			expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ new Error() ] ] ) ;
			expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
			expect( stats.order ).to.eql( [ 0, 2, 1 ] ) ;
			break ;
		default :
			throw new Error( 'progress event received too much time' ) ;
	}
} ) ;

context.on( 'resolved' , function( error , results ) {
	resolvedTriggered = true ;
	expect( progressCount ).to.be( 1 ) ; // resolved is triggered before the last 'progress' event
	expect( finallyTriggered ).to.be( true ) ;
	expect( error ).to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ], undefined, [ new Error() ] ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 0, 1 ] ) ;
	expect( stats.order ).to.eql( [ 0, 2 ] ) ;
} ) ;

context.on( 'finish' , function( error , results ) {
	expect( progressCount ).to.be( 3 ) ;
	expect( finallyTriggered ).to.be( true ) ;
	expect( resolvedTriggered ).to.be( true ) ;
	expect( error ).to.be.an( Error ) ;
	expect( results ).to.eql( [ [ undefined , 'my' ], [ undefined , 'wonderful' ], [ new Error() ] ] ) ;
	expect( stats.endCounter ).to.eql( [ 1, 1, 1 ] ) ;
	expect( stats.order ).to.eql( [ 0, 2, 1 ] ) ;
	done() ;
} ) ;
```

<a name="asynccalltimeout"></a>
# async.callTimeout()
should perform a call with timeout which will succeed in time and another one which will timeout, argument and this context should be passed correctly.

```js
var completionCallback1 = function( error , result ) {
	expect( error ).not.to.be.ok() ;
	expect( result ).to.be( 'ok: hello world' ) ;
} ;

var completionCallback2 = function( error , result ) {
	expect( error ).to.be.ok() ;
	expect( error ).to.be.an( Error ) ;
	expect( error.message ).to.be( 'jobTimeout' ) ;
	done() ;
} ;

var object = {
	prop: "property" ,
	fn: function( arg1 , arg2 , t , callback ) {
		expect( this ).to.be.an( Object ) ;
		expect( this.prop ).to.be( "property" ) ;
		
		setTimeout( function() { callback( undefined , 'ok: ' + arg1 + ' ' + arg2 ) ; } , t ) ;
	}
} ;

async.callTimeout( 20 , completionCallback1 , object.fn , object , 'hello' , 'world' , 10 ) ;
async.callTimeout( 20 , completionCallback2 , object.fn , object , 'hello' , 'world' , 30 ) ;
```

<a name="asyncwrappertimeout"></a>
# async.wrapper.timeout()
should create a wrapper around an asynchronous function, that call automatically the callback with an error after a predefined time (and also ensure the callback is called only once).

```js
var object = {
	prop: "property" ,
	fn: function( arg1 , arg2 , t , callback ) {
		expect( this ).to.be.an( Object ) ;
		expect( this.prop ).to.be( "property" ) ;
		
		setTimeout( function() { callback( undefined , 'ok: ' + arg1 + ' ' + arg2 ) ; } , t ) ;
	}
} ;

object.fnWrapper = async.wrapper.timeout( object.fn , 20 ) ;

object.fnWrapper( 'hello' , 'world' , 10 , function( error , result ) {
	expect( error ).not.to.be.ok() ;
	expect( result ).to.be( 'ok: hello world' ) ;
} ) ;

object.fnWrapper( 'hello' , 'world' , 30 , function( error , result ) {
	expect( error ).to.be.ok() ;
	expect( error ).to.be.an( Error ) ;
	expect( error.message ).to.be( 'Timeout' ) ;
	done() ;
} ) ;
```

<a name="misc-tests"></a>
# Misc tests
should trigger the callback even if no job is provided (empty array).

```js
async.series( [] )
.exec( function( error , results ) {
	expect( error ).not.to.be.an( Error ) ;
	expect( results ).to.eql( [] ) ;
	done() ;
} ) ;
```

<a name="async-eventemitter"></a>
# Async EventEmitter
should emit synchronously, with a synchronous flow (nice=-2).

```js
asyncEventTest( -2 , function( order ) {
	expect( order ).to.eql( [ 'listener' , 'flow' , 'nextTick' , 'setImmediate' , 'setTimeout5' , 'setTimeout20' ] ) ;
	done() ;
} ) ;
```

should emit asynchronously, with an asynchronous flow, almost as fast as possible (nice=-1 -> setImmediate).

```js
asyncEventTest( -1 , function( order ) {
	expect( order ).to.eql( [ 'flow' , 'nextTick' , 'listener' , 'setImmediate' , 'setTimeout5' , 'setTimeout20' ] ) ;
	done() ;
} ) ;
```

should emit asynchronously, with an asynchronous flow, with minimal delay (nice=0 -> setTimeout 0ms).

```js
asyncEventTest( 0 , function( order ) {
	try {
		expect( order ).to.eql( [ 'flow' , 'nextTick' , 'setImmediate' , 'listener' , 'setTimeout5' , 'setTimeout20' ] ) ;
	}
	catch( error ) {
		// Sometime setImmediate() is unpredictable and is slower than setTimeout(fn,0)
		// It is a bug of V8, not a bug of the async lib
		expect( order ).to.eql( [ 'flow' , 'nextTick' , 'listener' , 'setImmediate' , 'setTimeout5' , 'setTimeout20' ] ) ;
	}
	done() ;
} ) ;
```

should emit asynchronously, with an asynchronous flow, with a 10ms delay (nice=1 -> setTimeout 10ms).

```js
asyncEventTest( 1 , function( order ) {
	expect( order ).to.eql( [ 'flow' , 'nextTick' , 'setImmediate' , 'setTimeout5' , 'listener' , 'setTimeout20' ] ) ;
	done() ;
} ) ;
```

should emit asynchronously, with an asynchronous flow, with a 30ms delay (nice=3 -> setTimeout 30ms).

```js
asyncEventTest( 3 , function( order ) {
	expect( order ).to.eql( [ 'flow' , 'nextTick' , 'setImmediate' , 'setTimeout5' , 'setTimeout20' , 'listener' ] ) ;
	done() ;
} ) ;
```

<a name="maximum-call-stack-size-exceeded-prevention"></a>
# 'Maximum call stack size exceeded' prevention
nice -20 (the new default) should call setImmediate() once every 190 recursive synchronous calls.

```js
//this.timeout( 5000 ) ;

var i , array = [] ;

for ( i = 0 ; i <= 10000 ; i ++ ) { array[ i ] = i ; }

async.foreach( array , function( element , k , foreachCallback ) {
	//if ( k % 100 === 0 ) { console.log( 'k:' , k ) ; }
	foreachCallback() ;
} )
.nice( -20 )
.exec( function() {
	done() ;
} ) ;
```

