<commit_before>( function _Ext_test_s_()
{

'use strict';

if( typeof module !== 'undefined' )
{
  const _ = require( '../../../../node_modules/Tools' );

  _.include( 'wTesting' );
  // _.include( 'wLogger' );
  // _.include( 'wProcess' );

  require( '../../l9/consequence/Namespace.s' );
}

const _ = _global_.wTools;
const __ = _globals_.testing.wTools;
const fileProvider = __.fileProvider;
const path = fileProvider.path;

// --
// context
// --

function onSuiteBegin()
{
  let self = this;
  self.suiteTempPath = path.tempOpen( path.join( __dirname, '../..' ), 'ConsequenceExternal' );
  self.assetsOriginalPath = path.join( __dirname, '_asset' );
}

//

function onSuiteEnd()
{
  let self = this;
  _.assert( _.strHas( self.suiteTempPath, '/ConsequenceExternal-' ) )
  path.tempClose( self.suiteTempPath );
}

//

function assetFor( test, ... args )
{
  let context = this;
  let a = test.assetFor( ... args );

  _.assert( _.routineIs( a.program.head ) );
  _.assert( _.routineIs( a.program.body ) );

  let oprogram = a.program;
  program_body.defaults = a.program.defaults;
  a.program = _.routine.uniteCloning_replaceByUnite( a.program.head, program_body );

  return a;

  /* */

  function program_body( o )
  {
    let locals =
    {
      context : { t0 : context.t0, t1 : context.t1, t2 : context.t2, t3 : context.t3 },
      toolsPath : _.module.resolve( 'wTools' ),
    };
    o.locals = o.locals || locals;
    _.props.supplement( o.locals, locals );
    _.props.supplement( o.locals.context, locals.context );
    if( !o.locals.consequencePath )
    o.locals.consequencePath = a.path.nativize( a.path.join( __dirname, '../../l9/consequence/Namespace.s' ) );
    let r = oprogram.body.call( a, o );
    r.filePath/*programPath*/ = a.path.nativize( r.filePath/*programPath*/ );
    return r;
  }

}

// --
// tests
// --

function retry( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* - */

  a.ready.then( () =>
  {
    test.case = 'only callback, return value';
    return _.retry({ routine : () => 1 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, 1 );
    return null;
  });

  a.ready.then( () =>
  {
    test.case = 'only callback that throws errors';
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made ${ _.retry.defaults.attemptLimit } attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine : () => { throw _.err( 'Wrong attempt' ) } }), onErrorCallback );
  });

  /* */

  if( Config.debug )
  {
    a.ready.then( () =>
    {
      test.case = 'without arguments';
      test.shouldThrowErrorSync( () => _.retry() );

      test.case = 'extra arguments';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : 3 };
      test.shouldThrowErrorSync( () => _.retry( o, o ) );

      test.case = 'wrong type of options map';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : 3 };
      test.shouldThrowErrorSync( () => _.retry([ o ]) );

      test.case = 'unknown option in options map';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : 3, unknown : 1 };
      test.shouldThrowErrorSync( () => _.retry( o ) );

      test.case = 'wrong type of o.routine';
      var o = { routine : 'wrong', onError : ( err ) => _.errAttend( err ), attemptLimit : 3 };
      test.shouldThrowErrorSync( () => _.retry( o ) );

      test.case = 'wrong type of o.attemptLimit';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : 'wrong' };
      test.shouldThrowErrorSync( () => _.retry( o ) );

      test.case = 'wrong value of o.attemptLimit';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : 0 };
      test.shouldThrowErrorSync( () => _.retry( o ) );

      test.case = 'wrong value of o.attemptDelay';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : -100 };
      test.shouldThrowErrorSync( () => _.retry( o ) );

      return null;
    });
  }

  /* - */

  return a.ready;
}

//

function retryCheckOptionAttemptLimit( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* - */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit < wrong attempts, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made 2 attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      test.identical( attempts, 2 );
      return null;
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine : () => routine(), onError, attemptLimit : 2 }), onErrorCallback );
  });

  a.ready.then( () =>
  {
    test.case = 'attemptLimit === wrong attempts, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made 3 attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      test.identical( attempts, 3 );
      return null;
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine : () => routine(), onError, attemptLimit : 3 }), onErrorCallback );
  });

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, should return result, no args';
    attempts = 0;
    return _.retry({ routine : () => routine(), onError, attemptLimit : 4 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, true );
    return null;
  });

  /* */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, should return result, with args';
    attempts = 0;
    return _.retry({ routine : () => routine([ 1, 2 ]), onError, attemptLimit : 4 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, [ 1, 2 ] );
    return null;
  });

  /* - */

  return a.ready;
}

//

function retryCheckOptionAttemptDelay( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* - */

  let start;
  a.ready.then( () =>
  {
    test.case = 'check option attemptDelay';
    attempts = 0;
    start = _.time.now();
    return _.retry({ routine : () => routine(), onError, attemptLimit : 4, attemptDelay : 1000 });
  });
  a.ready.then( ( op ) =>
  {
    var spent = _.time.now() - start;
    test.ge( spent, 3000 );
    test.identical( op, true );
    test.identical( attempts, 3 );
    return null;
  });

  /* - */

  return a.ready;
}

retryCheckOptionAttemptDelay.timeOut = 10000;

//

function retryCheckOptionAttemptDelayMultiplier( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* - */

  let start;
  a.ready.then( () =>
  {
    test.case = 'check option attemptDelay';
    attempts = 0;
    start = _.time.now();
    return _.retry({ routine : () => routine(), onError, attemptLimit : 4, attemptDelay : 250, attemptDelayMultiplier : 3 });
  });
  a.ready.then( ( op ) =>
  {
    var spent = _.time.now() - start;
    test.ge( spent, 3250 );
    test.identical( op, true );
    test.identical( attempts, 3 );
    return null;
  });

  /* - */

  return a.ready;
}

retryCheckOptionAttemptDelayMultiplier.timeOut = 10000;

//

function retryCheckOptionOnError( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* - */

  a.ready.then( () =>
  {
    test.case = 'callback returns result, no onError, should pass';
    attempts = 0;
    return _.retry({ routine : () => 1 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, 1 );
    return null;
  });

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, no onError to handle error, should throw error';
    attempts = 0;
    return _.retry({ routine : () => routine(), attemptLimit : 4 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, true );
    test.identical( attempts, 3 );
    return null;
  });

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, onError handle error, returns false, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    var o = { routine : () => routine(), onError : ( err ) => { _.errAttend( err ); return false }, attemptLimit : 4 };
    return test.shouldThrowErrorAsync( () => _.retry( o ), onErrorCallback );
  });

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, onError handle error, returns not false, should pass';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    return _.retry
    ({
      routine : () => routine(),
      onError : ( err ) => { _.errAttend( err ); return undefined },
      attemptLimit : 4
    });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, true );
    return null;
  });

  /* */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, onError throws error, should not retry';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nThe error thown in callback {-onError-}`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      test.identical( attempts, 1 );
      return null;
    };
    return test.shouldThrowErrorAsync
    (
      () =>
      {
        return _.retry
        ({
          routine : () => routine(),
          onError : ( err ) => { throw _.err( 'from onError' ) },
          attemptLimit : 4
        });
      },
      onErrorCallback
    );
  });

  /* - */

  return a.ready;
}

//

function retryCheckOptionOnSucces( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };
  const onSuccess = ( arg ) =>
  {
    if( attempts < 4 )
    {
      ++attempts;
      return false;
    }
    return true;
  };

  /* - */

  a.ready.then( () =>
  {
    test.case = 'check option onSuccess, attemptLimit === wrong attempts, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made 4 attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    return test.shouldThrowErrorAsync
    (
      () => _.retry({ routine : () => routine(), onError, onSuccess, attemptLimit : 4 }), onErrorCallback
    );
  });

  a.ready.then( () =>
  {
    test.case = 'check option onSuccess, attemptLimit > wrong attempts, should return result';
    attempts = 0;
    return _.retry({ routine : () => routine( 'arg' ), onError, onSuccess, attemptLimit : 5 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, 'arg' );
    return null;
  });

  /* - */

  return a.ready;
}

//

function retryCheckNotBlocking( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  let ready = _.take( null );
  let shell = __.process.starter({ ready, mode : 'shell' });
  const routine = () => shell( 'wrong command' );

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, onError handle error, returns false, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      test.identical( _.strCount( err.originalMessage, 'Launched as "wrong command"' ), 1 );
      var exp = `Attempts is exhausted, made ${ _.retry.defaults.attemptLimit } attempts`;
      test.identical( _.strCount( err.originalMessage, exp ), 1 );
      return null;
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine }), onErrorCallback );
  });

  /* - */

  return a.ready;
}

//

function retryHandleExternalConsequenceError( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = () => new _.Consequence().error( 'wrong command' );

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, onError handle error, returns false, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `wrong command\nAttempts is exhausted, made ${ _.retry.defaults.attemptLimit } attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine }), onErrorCallback );
  });

  /* - */

  return a.ready;
}

//

function retryCheckOptionDefaults( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* - */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit from option';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made 2 attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    var defaults =
    {
      attemptLimit : 5,
      attemptDelay : 500,
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine, attemptLimit : 2, defaults }), onErrorCallback );
  });

  /* */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit from map `defaults`';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made 2 attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    var defaults =
    {
      attemptLimit : 2,
      attemptDelay : 500,
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine, defaults }), onErrorCallback );
  });

  /* */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit from routine defaults';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made ${ _.retry.defaults.attemptLimit } attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    var defaults =
    {
      attemptDelay : 500,
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine, defaults }), onErrorCallback );
  });

  /* */

  if( Config.debug )
  {
    a.ready.then( () =>
    {
      test.case = 'defaults has wrong value';
      var defaults = { attemptLimit : null };
      test.shouldThrowErrorSync( () => _.retry({ routine, defaults }) );

      test.case = 'defaults has unknown option';
      var defaults = { attemptLimit : 3, unknown : 1 };
      test.shouldThrowErrorSync( () => _.retry({ routine, defaults }) );
      return null;
    });
  }

  /* - */

  return a.ready;
}

//

function retryCheckOptionLogger( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  let filePath;

  /* - */

  a.ready.then( () =>
  {
    test.case = 'verbosity < 4';
    filePath = a.program({ entry : testApp, locals : { logger : 2 } }).filePath;
    return null;
  });
  a.shell( `node ${ filePath }` );
  a.ready.then( ( op ) =>
  {
    test.identical( op.exitCode, 0 );
    test.identical( op.output, '' );
    return null;
  });

  /* */

  a.ready.then( () =>
  {
    test.case = 'verbosity === 4';
    filePath = a.program({ entry : testApp, locals : { logger : 4 } }).filePath;
    return null;
  });
  a.shell( `node ${ filePath }` );
  a.ready.then( ( op ) =>
  {
    test.identical( op.exitCode, 0 );
    test.identical( _.strCount( op.output, /Attempt #. runned at : / ), 4 );
    var lines = op.output.split( '\n' );
    var time1 = _.number.from( lines[ 0 ].match( /(\d+):(\d+):(\d+)/ )[ 3 ] );
    var time2 = _.number.from( lines[ 1 ].match( /(\d+):(\d+):(\d+)/ )[ 3 ] );
    var time3 = _.number.from( lines[ 2 ].match( /(\d+):(\d+):(\d+)/ )[ 3 ] );
    var time4 = _.number.from( lines[ 3 ].match( /(\d+):(\d+):(\d+)/ )[ 3 ] );
    var timeDelta1 = time2 - time1 < 0 ? 60 - time1 + time2 : time2 - time1;
    test.ge( timeDelta1, 2 );
    var timeDelta2 = time3 - time2 < 0 ? 60 - time2 + time3 : time3 - time2;
    test.ge( timeDelta2, 2 );
    var timeDelta3 = time4 - time3 < 0 ? 60 - time3 + time4 : time4 - time3;
    test.ge( time4 - time3, 2 );
    return null;
  });
<commit_msg>Fix test routine `retryCheckOptionLogger`, use variable for calculating time delta<commit_after>( function _Ext_test_s_()
{

'use strict';

if( typeof module !== 'undefined' )
{
  const _ = require( '../../../../node_modules/Tools' );

  _.include( 'wTesting' );
  // _.include( 'wLogger' );
  // _.include( 'wProcess' );

  require( '../../l9/consequence/Namespace.s' );
}

const _ = _global_.wTools;
const __ = _globals_.testing.wTools;
const fileProvider = __.fileProvider;
const path = fileProvider.path;

// --
// context
// --

function onSuiteBegin()
{
  let self = this;
  self.suiteTempPath = path.tempOpen( path.join( __dirname, '../..' ), 'ConsequenceExternal' );
  self.assetsOriginalPath = path.join( __dirname, '_asset' );
}

//

function onSuiteEnd()
{
  let self = this;
  _.assert( _.strHas( self.suiteTempPath, '/ConsequenceExternal-' ) )
  path.tempClose( self.suiteTempPath );
}

//

function assetFor( test, ... args )
{
  let context = this;
  let a = test.assetFor( ... args );

  _.assert( _.routineIs( a.program.head ) );
  _.assert( _.routineIs( a.program.body ) );

  let oprogram = a.program;
  program_body.defaults = a.program.defaults;
  a.program = _.routine.uniteCloning_replaceByUnite( a.program.head, program_body );

  return a;

  /* */

  function program_body( o )
  {
    let locals =
    {
      context : { t0 : context.t0, t1 : context.t1, t2 : context.t2, t3 : context.t3 },
      toolsPath : _.module.resolve( 'wTools' ),
    };
    o.locals = o.locals || locals;
    _.props.supplement( o.locals, locals );
    _.props.supplement( o.locals.context, locals.context );
    if( !o.locals.consequencePath )
    o.locals.consequencePath = a.path.nativize( a.path.join( __dirname, '../../l9/consequence/Namespace.s' ) );
    let r = oprogram.body.call( a, o );
    r.filePath/*programPath*/ = a.path.nativize( r.filePath/*programPath*/ );
    return r;
  }

}

// --
// tests
// --

function retry( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* - */

  a.ready.then( () =>
  {
    test.case = 'only callback, return value';
    return _.retry({ routine : () => 1 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, 1 );
    return null;
  });

  a.ready.then( () =>
  {
    test.case = 'only callback that throws errors';
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made ${ _.retry.defaults.attemptLimit } attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine : () => { throw _.err( 'Wrong attempt' ) } }), onErrorCallback );
  });

  /* */

  if( Config.debug )
  {
    a.ready.then( () =>
    {
      test.case = 'without arguments';
      test.shouldThrowErrorSync( () => _.retry() );

      test.case = 'extra arguments';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : 3 };
      test.shouldThrowErrorSync( () => _.retry( o, o ) );

      test.case = 'wrong type of options map';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : 3 };
      test.shouldThrowErrorSync( () => _.retry([ o ]) );

      test.case = 'unknown option in options map';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : 3, unknown : 1 };
      test.shouldThrowErrorSync( () => _.retry( o ) );

      test.case = 'wrong type of o.routine';
      var o = { routine : 'wrong', onError : ( err ) => _.errAttend( err ), attemptLimit : 3 };
      test.shouldThrowErrorSync( () => _.retry( o ) );

      test.case = 'wrong type of o.attemptLimit';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : 'wrong' };
      test.shouldThrowErrorSync( () => _.retry( o ) );

      test.case = 'wrong value of o.attemptLimit';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : 0 };
      test.shouldThrowErrorSync( () => _.retry( o ) );

      test.case = 'wrong value of o.attemptDelay';
      var o = { routine : () => 1, onError : ( err ) => _.errAttend( err ), attemptLimit : -100 };
      test.shouldThrowErrorSync( () => _.retry( o ) );

      return null;
    });
  }

  /* - */

  return a.ready;
}

//

function retryCheckOptionAttemptLimit( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* - */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit < wrong attempts, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made 2 attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      test.identical( attempts, 2 );
      return null;
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine : () => routine(), onError, attemptLimit : 2 }), onErrorCallback );
  });

  a.ready.then( () =>
  {
    test.case = 'attemptLimit === wrong attempts, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made 3 attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      test.identical( attempts, 3 );
      return null;
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine : () => routine(), onError, attemptLimit : 3 }), onErrorCallback );
  });

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, should return result, no args';
    attempts = 0;
    return _.retry({ routine : () => routine(), onError, attemptLimit : 4 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, true );
    return null;
  });

  /* */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, should return result, with args';
    attempts = 0;
    return _.retry({ routine : () => routine([ 1, 2 ]), onError, attemptLimit : 4 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, [ 1, 2 ] );
    return null;
  });

  /* - */

  return a.ready;
}

//

function retryCheckOptionAttemptDelay( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* - */

  let start;
  a.ready.then( () =>
  {
    test.case = 'check option attemptDelay';
    attempts = 0;
    start = _.time.now();
    return _.retry({ routine : () => routine(), onError, attemptLimit : 4, attemptDelay : 1000 });
  });
  a.ready.then( ( op ) =>
  {
    var spent = _.time.now() - start;
    test.ge( spent, 3000 );
    test.identical( op, true );
    test.identical( attempts, 3 );
    return null;
  });

  /* - */

  return a.ready;
}

retryCheckOptionAttemptDelay.timeOut = 10000;

//

function retryCheckOptionAttemptDelayMultiplier( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* - */

  let start;
  a.ready.then( () =>
  {
    test.case = 'check option attemptDelay';
    attempts = 0;
    start = _.time.now();
    return _.retry({ routine : () => routine(), onError, attemptLimit : 4, attemptDelay : 250, attemptDelayMultiplier : 3 });
  });
  a.ready.then( ( op ) =>
  {
    var spent = _.time.now() - start;
    test.ge( spent, 3250 );
    test.identical( op, true );
    test.identical( attempts, 3 );
    return null;
  });

  /* - */

  return a.ready;
}

retryCheckOptionAttemptDelayMultiplier.timeOut = 10000;

//

function retryCheckOptionOnError( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* - */

  a.ready.then( () =>
  {
    test.case = 'callback returns result, no onError, should pass';
    attempts = 0;
    return _.retry({ routine : () => 1 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, 1 );
    return null;
  });

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, no onError to handle error, should throw error';
    attempts = 0;
    return _.retry({ routine : () => routine(), attemptLimit : 4 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, true );
    test.identical( attempts, 3 );
    return null;
  });

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, onError handle error, returns false, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    var o = { routine : () => routine(), onError : ( err ) => { _.errAttend( err ); return false }, attemptLimit : 4 };
    return test.shouldThrowErrorAsync( () => _.retry( o ), onErrorCallback );
  });

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, onError handle error, returns not false, should pass';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    return _.retry
    ({
      routine : () => routine(),
      onError : ( err ) => { _.errAttend( err ); return undefined },
      attemptLimit : 4
    });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, true );
    return null;
  });

  /* */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, onError throws error, should not retry';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nThe error thown in callback {-onError-}`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      test.identical( attempts, 1 );
      return null;
    };
    return test.shouldThrowErrorAsync
    (
      () =>
      {
        return _.retry
        ({
          routine : () => routine(),
          onError : ( err ) => { throw _.err( 'from onError' ) },
          attemptLimit : 4
        });
      },
      onErrorCallback
    );
  });

  /* - */

  return a.ready;
}

//

function retryCheckOptionOnSucces( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };
  const onSuccess = ( arg ) =>
  {
    if( attempts < 4 )
    {
      ++attempts;
      return false;
    }
    return true;
  };

  /* - */

  a.ready.then( () =>
  {
    test.case = 'check option onSuccess, attemptLimit === wrong attempts, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made 4 attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    return test.shouldThrowErrorAsync
    (
      () => _.retry({ routine : () => routine(), onError, onSuccess, attemptLimit : 4 }), onErrorCallback
    );
  });

  a.ready.then( () =>
  {
    test.case = 'check option onSuccess, attemptLimit > wrong attempts, should return result';
    attempts = 0;
    return _.retry({ routine : () => routine( 'arg' ), onError, onSuccess, attemptLimit : 5 });
  });
  a.ready.then( ( op ) =>
  {
    test.identical( op, 'arg' );
    return null;
  });

  /* - */

  return a.ready;
}

//

function retryCheckNotBlocking( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  let ready = _.take( null );
  let shell = __.process.starter({ ready, mode : 'shell' });
  const routine = () => shell( 'wrong command' );

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, onError handle error, returns false, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      test.identical( _.strCount( err.originalMessage, 'Launched as "wrong command"' ), 1 );
      var exp = `Attempts is exhausted, made ${ _.retry.defaults.attemptLimit } attempts`;
      test.identical( _.strCount( err.originalMessage, exp ), 1 );
      return null;
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine }), onErrorCallback );
  });

  /* - */

  return a.ready;
}

//

function retryHandleExternalConsequenceError( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* */

  let attempts = 0;
  const routine = () => new _.Consequence().error( 'wrong command' );

  a.ready.then( () =>
  {
    test.case = 'attemptLimit > wrong attempts, onError handle error, returns false, should throw error';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `wrong command\nAttempts is exhausted, made ${ _.retry.defaults.attemptLimit } attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine }), onErrorCallback );
  });

  /* - */

  return a.ready;
}

//

function retryCheckOptionDefaults( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  a.fileProvider.dirMake( a.abs( '.' ) );

  /* - */

  let attempts = 0;
  const routine = ( arg ) =>
  {
    if( attempts < 3 )
    {
      ++attempts;
      throw _.err( 'Wrong attempt' );
    }
    return arg || true;
  }
  const onError = ( err ) => { _.errAttend( err ); return true };

  /* */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit from option';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made 2 attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    var defaults =
    {
      attemptLimit : 5,
      attemptDelay : 500,
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine, attemptLimit : 2, defaults }), onErrorCallback );
  });

  /* */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit from map `defaults`';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made 2 attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    var defaults =
    {
      attemptLimit : 2,
      attemptDelay : 500,
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine, defaults }), onErrorCallback );
  });

  /* */

  a.ready.then( () =>
  {
    test.case = 'attemptLimit from routine defaults';
    attempts = 0;
    var onErrorCallback = ( err, arg ) =>
    {
      test.true( _.error.is( err ) );
      test.identical( arg, undefined );
      var exp = `Wrong attempt\nAttempts is exhausted, made ${ _.retry.defaults.attemptLimit } attempts`;
      test.true( _.strBegins( err.originalMessage, exp ) );
      return null;
    };
    var defaults =
    {
      attemptDelay : 500,
    };
    return test.shouldThrowErrorAsync( () => _.retry({ routine, defaults }), onErrorCallback );
  });

  /* */

  if( Config.debug )
  {
    a.ready.then( () =>
    {
      test.case = 'defaults has wrong value';
      var defaults = { attemptLimit : null };
      test.shouldThrowErrorSync( () => _.retry({ routine, defaults }) );

      test.case = 'defaults has unknown option';
      var defaults = { attemptLimit : 3, unknown : 1 };
      test.shouldThrowErrorSync( () => _.retry({ routine, defaults }) );
      return null;
    });
  }

  /* - */

  return a.ready;
}

//

function retryCheckOptionLogger( test )
{
  let context = this;
  let a = context.assetFor( test, false );
  let filePath;

  /* - */

  a.ready.then( () =>
  {
    test.case = 'verbosity < 4';
    filePath = a.program({ entry : testApp, locals : { logger : 2 } }).filePath;
    return null;
  });
  a.shell( `node ${ filePath }` );
  a.ready.then( ( op ) =>
  {
    test.identical( op.exitCode, 0 );
    test.identical( op.output, '' );
    return null;
  });

  /* */

  a.ready.then( () =>
  {
    test.case = 'verbosity === 4';
    filePath = a.program({ entry : testApp, locals : { logger : 4 } }).filePath;
    return null;
  });
  a.shell( `node ${ filePath }` );
  a.ready.then( ( op ) =>
  {
    test.identical( op.exitCode, 0 );
    test.identical( _.strCount( op.output, /Attempt #. runned at : / ), 4 );
    var lines = op.output.split( '\n' );
    var time1 = _.number.from( lines[ 0 ].match( /(\d+):(\d+):(\d+)/ )[ 3 ] );
    var time2 = _.number.from( lines[ 1 ].match( /(\d+):(\d+):(\d+)/ )[ 3 ] );
    var time3 = _.number.from( lines[ 2 ].match( /(\d+):(\d+):(\d+)/ )[ 3 ] );
    var time4 = _.number.from( lines[ 3 ].match( /(\d+):(\d+):(\d+)/ )[ 3 ] );
    var timeDelta1 = time2 - time1 < 0 ? 60 - time1 + time2 : time2 - time1;
    test.ge( timeDelta1, 2 );
    var timeDelta2 = time3 - time2 < 0 ? 60 - time2 + time3 : time3 - time2;
    test.ge( timeDelta2, 2 );
    var timeDelta3 = time4 - time3 < 0 ? 60 - time3 + time4 : time4 - time3;
    test.ge( timeDelta3, 2 );
    return null;
  });

{'fraction_non_alphanumeric': 0.17105058365758755, 'fraction_numerical': 0.009779507133592737, 'mean_word_length': 2.343248634116729, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 253, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2644955', 'n_tokens_mistral': 13318, 'n_tokens_neox': 12865, 'n_words': 3204}