Simulator first commit
This commit is contained in:
86
node_modules/backoff/CHANGES.md
generated
vendored
Normal file
86
node_modules/backoff/CHANGES.md
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
# Changelog
|
||||
|
||||
## 2.5.0
|
||||
|
||||
Those changes are not released yet.
|
||||
|
||||
- In the functional API, invoke the wrapped function callback on abort and emit
|
||||
an `abort` event. This makes it possible to detect when abort is called.
|
||||
- Add a method on the function API, `call.retryIf(predicate)`, which specifies
|
||||
a predicate used to determine whether a given error is retriable or not. The
|
||||
default behavior is unaffected, errors remain retriable by default.
|
||||
|
||||
## 2.4.1
|
||||
|
||||
- Add support for specifying the factor to use in the `ExponentialStrategy`.
|
||||
|
||||
## 2.4.0
|
||||
|
||||
- Replace `FunctionCall.getResults` by `FunctionCall.getLastResult` to avoid
|
||||
storing intermediary results forever as this may lead to memory exhaustion
|
||||
when used in conjunction with an infinite number of backoffs.
|
||||
- Add `FunctionCall.getNumRetries` which returns the number of times the
|
||||
wrapped function was retried.
|
||||
|
||||
## 2.3.0
|
||||
|
||||
- Add four new methods to `FunctionCall` to query the state of the call.
|
||||
- isPending
|
||||
- isRunning
|
||||
- isCompleted
|
||||
- isAborted
|
||||
|
||||
## 2.2.0
|
||||
|
||||
- To match `Backoff` default behavior, `FunctionCall` no longer sets a
|
||||
default failAfter of 5, i.e. the maximum number of backoffs is now
|
||||
unbounded by default.
|
||||
|
||||
## 2.1.0
|
||||
|
||||
- `Backoff.backoff` now accepts an optional error argument that is re-emitted
|
||||
as the last argument of the `backoff` and `fail` events. This provides some
|
||||
context to the listeners as to why a given backoff operation was attempted.
|
||||
- The `backoff` event emitted by the `FunctionCall` class now contains, as its
|
||||
last argument, the error that caused the backoff operation to be attempted.
|
||||
This provides some context to the listeners as to why a given backoff
|
||||
operation was attempted.
|
||||
|
||||
## 2.0.0
|
||||
|
||||
- `FunctionCall.call` renamed into `FunctionCall.start`.
|
||||
- `backoff.call` no longer invokes the wrapped function on `nextTick`. That
|
||||
way, the first attempt is not delayed until the end of the current event
|
||||
loop.
|
||||
|
||||
## 1.2.1
|
||||
|
||||
- Make `FunctionCall.backoffFactory` a private member.
|
||||
|
||||
## 1.2.0
|
||||
|
||||
- Add `backoff.call` and the associated `FunctionCall` class.
|
||||
|
||||
## 1.1.0
|
||||
|
||||
- Add a `Backoff.failAfter`.
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- Rename `start` and `done` events `backoff` and `ready`.
|
||||
- Remove deprecated `backoff.fibonnaci`.
|
||||
|
||||
## 0.2.1
|
||||
|
||||
- Create `backoff.fibonacci`.
|
||||
- Deprecate `backoff.fibonnaci`.
|
||||
- Expose fibonacci and exponential strategies.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- Provide exponential and fibonacci backoffs.
|
||||
|
||||
## 0.1.0
|
||||
|
||||
- Change `initialTimeout` and `maxTimeout` to `initialDelay` and `maxDelay`.
|
||||
- Use fibonnaci backoff.
|
19
node_modules/backoff/LICENSE
generated
vendored
Normal file
19
node_modules/backoff/LICENSE
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (C) 2012 Mathieu Turcotte
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
382
node_modules/backoff/README.md
generated
vendored
Normal file
382
node_modules/backoff/README.md
generated
vendored
Normal file
@ -0,0 +1,382 @@
|
||||
# Backoff for Node.js
|
||||
[](http://travis-ci.org/MathieuTurcotte/node-backoff)
|
||||
[](http://badge.fury.io/js/backoff)
|
||||
|
||||
Fibonacci and exponential backoffs for Node.js.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install backoff
|
||||
```
|
||||
|
||||
## Unit tests
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Object Oriented
|
||||
|
||||
The usual way to instantiate a new `Backoff` object is to use one predefined
|
||||
factory method: `backoff.fibonacci([options])`, `backoff.exponential([options])`.
|
||||
|
||||
`Backoff` inherits from `EventEmitter`. When a backoff starts, a `backoff`
|
||||
event is emitted and, when a backoff ends, a `ready` event is emitted.
|
||||
Handlers for these two events are called with the current backoff number and
|
||||
delay.
|
||||
|
||||
``` js
|
||||
var backoff = require('backoff');
|
||||
|
||||
var fibonacciBackoff = backoff.fibonacci({
|
||||
randomisationFactor: 0,
|
||||
initialDelay: 10,
|
||||
maxDelay: 300
|
||||
});
|
||||
|
||||
fibonacciBackoff.failAfter(10);
|
||||
|
||||
fibonacciBackoff.on('backoff', function(number, delay) {
|
||||
// Do something when backoff starts, e.g. show to the
|
||||
// user the delay before next reconnection attempt.
|
||||
console.log(number + ' ' + delay + 'ms');
|
||||
});
|
||||
|
||||
fibonacciBackoff.on('ready', function(number, delay) {
|
||||
// Do something when backoff ends, e.g. retry a failed
|
||||
// operation (DNS lookup, API call, etc.). If it fails
|
||||
// again then backoff, otherwise reset the backoff
|
||||
// instance.
|
||||
fibonacciBackoff.backoff();
|
||||
});
|
||||
|
||||
fibonacciBackoff.on('fail', function() {
|
||||
// Do something when the maximum number of backoffs is
|
||||
// reached, e.g. ask the user to check its connection.
|
||||
console.log('fail');
|
||||
});
|
||||
|
||||
fibonacciBackoff.backoff();
|
||||
```
|
||||
|
||||
The previous example would print the following.
|
||||
|
||||
```
|
||||
0 10ms
|
||||
1 10ms
|
||||
2 20ms
|
||||
3 30ms
|
||||
4 50ms
|
||||
5 80ms
|
||||
6 130ms
|
||||
7 210ms
|
||||
8 300ms
|
||||
9 300ms
|
||||
fail
|
||||
```
|
||||
|
||||
Note that `Backoff` objects are meant to be instantiated once and reused
|
||||
several times by calling `reset` after a successful "retry".
|
||||
|
||||
### Functional
|
||||
|
||||
It's also possible to avoid some boilerplate code when invoking an asynchronous
|
||||
function in a backoff loop by using `backoff.call(fn, [args, ...], callback)`.
|
||||
|
||||
Typical usage looks like the following.
|
||||
|
||||
``` js
|
||||
var call = backoff.call(get, 'https://duplika.ca/', function(err, res) {
|
||||
console.log('Num retries: ' + call.getNumRetries());
|
||||
|
||||
if (err) {
|
||||
console.log('Error: ' + err.message);
|
||||
} else {
|
||||
console.log('Status: ' + res.statusCode);
|
||||
}
|
||||
});
|
||||
|
||||
call.retryIf(function(err) { return err.status == 503; });
|
||||
call.setStrategy(new backoff.ExponentialStrategy());
|
||||
call.failAfter(10);
|
||||
call.start();
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### backoff.fibonacci([options])
|
||||
|
||||
Constructs a Fibonacci backoff (10, 10, 20, 30, 50, etc.).
|
||||
|
||||
The options are the following.
|
||||
|
||||
- randomisationFactor: defaults to 0, must be between 0 and 1
|
||||
- initialDelay: defaults to 100 ms
|
||||
- maxDelay: defaults to 10000 ms
|
||||
|
||||
With these values, the backoff delay will increase from 100 ms to 10000 ms. The
|
||||
randomisation factor controls the range of randomness and must be between 0
|
||||
and 1. By default, no randomisation is applied on the backoff delay.
|
||||
|
||||
### backoff.exponential([options])
|
||||
|
||||
Constructs an exponential backoff (10, 20, 40, 80, etc.).
|
||||
|
||||
The options are the following.
|
||||
|
||||
- randomisationFactor: defaults to 0, must be between 0 and 1
|
||||
- initialDelay: defaults to 100 ms
|
||||
- maxDelay: defaults to 10000 ms
|
||||
- factor: defaults to 2, must be greater than 1
|
||||
|
||||
With these values, the backoff delay will increase from 100 ms to 10000 ms. The
|
||||
randomisation factor controls the range of randomness and must be between 0
|
||||
and 1. By default, no randomisation is applied on the backoff delay.
|
||||
|
||||
### backoff.call(fn, [args, ...], callback)
|
||||
|
||||
- fn: function to call in a backoff handler, i.e. the wrapped function
|
||||
- args: function's arguments
|
||||
- callback: function's callback accepting an error as its first argument
|
||||
|
||||
Constructs a `FunctionCall` instance for the given function. The wrapped
|
||||
function will get retried until it succeds or reaches the maximum number
|
||||
of backoffs. In both cases, the callback function will be invoked with the
|
||||
last result returned by the wrapped function.
|
||||
|
||||
It is the caller's responsability to initiate the call by invoking the
|
||||
`start` method on the returned `FunctionCall` instance.
|
||||
|
||||
### Class Backoff
|
||||
|
||||
#### new Backoff(strategy)
|
||||
|
||||
- strategy: the backoff strategy to use
|
||||
|
||||
Constructs a new backoff object from a specific backoff strategy. The backoff
|
||||
strategy must implement the `BackoffStrategy`interface defined bellow.
|
||||
|
||||
#### backoff.failAfter(numberOfBackoffs)
|
||||
|
||||
- numberOfBackoffs: maximum number of backoffs before the fail event gets
|
||||
emitted, must be greater than 0
|
||||
|
||||
Sets a limit on the maximum number of backoffs that can be performed before
|
||||
a fail event gets emitted and the backoff instance is reset. By default, there
|
||||
is no limit on the number of backoffs that can be performed.
|
||||
|
||||
#### backoff.backoff([err])
|
||||
|
||||
Starts a backoff operation. If provided, the error parameter will be emitted
|
||||
as the last argument of the `backoff` and `fail` events to let the listeners
|
||||
know why the backoff operation was attempted.
|
||||
|
||||
An error will be thrown if a backoff operation is already in progress.
|
||||
|
||||
In practice, this method should be called after a failed attempt to perform a
|
||||
sensitive operation (connecting to a database, downloading a resource over the
|
||||
network, etc.).
|
||||
|
||||
#### backoff.reset()
|
||||
|
||||
Resets the backoff delay to the initial backoff delay and stop any backoff
|
||||
operation in progress. After reset, a backoff instance can and should be
|
||||
reused.
|
||||
|
||||
In practice, this method should be called after having successfully completed
|
||||
the sensitive operation guarded by the backoff instance or if the client code
|
||||
request to stop any reconnection attempt.
|
||||
|
||||
#### Event: 'backoff'
|
||||
|
||||
- number: number of backoffs since last reset, starting at 0
|
||||
- delay: backoff delay in milliseconds
|
||||
- err: optional error parameter passed to `backoff.backoff([err])`
|
||||
|
||||
Emitted when a backoff operation is started. Signals to the client how long
|
||||
the next backoff delay will be.
|
||||
|
||||
#### Event: 'ready'
|
||||
|
||||
- number: number of backoffs since last reset, starting at 0
|
||||
- delay: backoff delay in milliseconds
|
||||
|
||||
Emitted when a backoff operation is done. Signals that the failing operation
|
||||
should be retried.
|
||||
|
||||
#### Event: 'fail'
|
||||
|
||||
- err: optional error parameter passed to `backoff.backoff([err])`
|
||||
|
||||
Emitted when the maximum number of backoffs is reached. This event will only
|
||||
be emitted if the client has set a limit on the number of backoffs by calling
|
||||
`backoff.failAfter(numberOfBackoffs)`. The backoff instance is automatically
|
||||
reset after this event is emitted.
|
||||
|
||||
### Interface BackoffStrategy
|
||||
|
||||
A backoff strategy must provide the following methods.
|
||||
|
||||
#### strategy.next()
|
||||
|
||||
Computes and returns the next backoff delay.
|
||||
|
||||
#### strategy.reset()
|
||||
|
||||
Resets the backoff delay to its initial value.
|
||||
|
||||
### Class ExponentialStrategy
|
||||
|
||||
Exponential (10, 20, 40, 80, etc.) backoff strategy implementation.
|
||||
|
||||
#### new ExponentialStrategy([options])
|
||||
|
||||
The options are the following.
|
||||
|
||||
- randomisationFactor: defaults to 0, must be between 0 and 1
|
||||
- initialDelay: defaults to 100 ms
|
||||
- maxDelay: defaults to 10000 ms
|
||||
- factor: defaults to 2, must be greater than 1
|
||||
|
||||
### Class FibonacciStrategy
|
||||
|
||||
Fibonnaci (10, 10, 20, 30, 50, etc.) backoff strategy implementation.
|
||||
|
||||
#### new FibonacciStrategy([options])
|
||||
|
||||
The options are the following.
|
||||
|
||||
- randomisationFactor: defaults to 0, must be between 0 and 1
|
||||
- initialDelay: defaults to 100 ms
|
||||
- maxDelay: defaults to 10000 ms
|
||||
|
||||
### Class FunctionCall
|
||||
|
||||
This class manages the calling of an asynchronous function within a backoff
|
||||
loop.
|
||||
|
||||
This class should rarely be instantiated directly since the factory method
|
||||
`backoff.call(fn, [args, ...], callback)` offers a more convenient and safer
|
||||
way to create `FunctionCall` instances.
|
||||
|
||||
#### new FunctionCall(fn, args, callback)
|
||||
|
||||
- fn: asynchronous function to call
|
||||
- args: an array containing fn's args
|
||||
- callback: fn's callback
|
||||
|
||||
Constructs a function handler for the given asynchronous function.
|
||||
|
||||
#### call.isPending()
|
||||
|
||||
Returns whether the call is pending, i.e. hasn't been started.
|
||||
|
||||
#### call.isRunning()
|
||||
|
||||
Returns whether the call is in progress.
|
||||
|
||||
#### call.isCompleted()
|
||||
|
||||
Returns whether the call is completed.
|
||||
|
||||
#### call.isAborted()
|
||||
|
||||
Returns whether the call is aborted.
|
||||
|
||||
#### call.setStrategy(strategy)
|
||||
|
||||
- strategy: strategy instance to use, defaults to `FibonacciStrategy`.
|
||||
|
||||
Sets the backoff strategy to use. This method should be called before
|
||||
`call.start()` otherwise an exception will be thrown.
|
||||
|
||||
#### call.failAfter(maxNumberOfBackoffs)
|
||||
|
||||
- maxNumberOfBackoffs: maximum number of backoffs before the call is aborted
|
||||
|
||||
Sets the maximum number of backoffs before the call is aborted. By default,
|
||||
there is no limit on the number of backoffs that can be performed.
|
||||
|
||||
This method should be called before `call.start()` otherwise an exception will
|
||||
be thrown..
|
||||
|
||||
#### call.retryIf(predicate)
|
||||
|
||||
- predicate: a function which takes in as its argument the error returned
|
||||
by the wrapped function and determines whether it is retriable.
|
||||
|
||||
Sets the predicate which will be invoked to determine whether a given error
|
||||
should be retried or not, e.g. a network error would be retriable while a type
|
||||
error would stop the function call. By default, all errors are considered to be
|
||||
retriable.
|
||||
|
||||
This method should be called before `call.start()` otherwise an exception will
|
||||
be thrown.
|
||||
|
||||
#### call.getLastResult()
|
||||
|
||||
Returns an array containing the last arguments passed to the completion callback
|
||||
of the wrapped function. For example, to get the error code returned by the last
|
||||
call, one would do the following.
|
||||
|
||||
``` js
|
||||
var results = call.getLastResult();
|
||||
// The error code is the first parameter of the callback.
|
||||
var error = results[0];
|
||||
```
|
||||
|
||||
Note that if the call was aborted, it will contain the abort error and not the
|
||||
last error returned by the wrapped function.
|
||||
|
||||
#### call.getNumRetries()
|
||||
|
||||
Returns the number of times the wrapped function call was retried. For a
|
||||
wrapped function that succeeded immediately, this would return 0. This
|
||||
method can be called at any point in time during the call life cycle, i.e.
|
||||
before, during and after the wrapped function invocation.
|
||||
|
||||
#### call.start()
|
||||
|
||||
Initiates the call the wrapped function. This method should only be called
|
||||
once otherwise an exception will be thrown.
|
||||
|
||||
#### call.abort()
|
||||
|
||||
Aborts the call and causes the completion callback to be invoked with an abort
|
||||
error if the call was pending or running; does nothing otherwise. This method
|
||||
can safely be called mutliple times.
|
||||
|
||||
#### Event: 'call'
|
||||
|
||||
- args: wrapped function's arguments
|
||||
|
||||
Emitted each time the wrapped function is called.
|
||||
|
||||
#### Event: 'callback'
|
||||
|
||||
- results: wrapped function's return values
|
||||
|
||||
Emitted each time the wrapped function invokes its callback.
|
||||
|
||||
#### Event: 'backoff'
|
||||
|
||||
- number: backoff number, starts at 0
|
||||
- delay: backoff delay in milliseconds
|
||||
- err: the error that triggered the backoff operation
|
||||
|
||||
Emitted each time a backoff operation is started.
|
||||
|
||||
#### Event: 'abort'
|
||||
|
||||
Emitted when a call is aborted.
|
||||
|
||||
## Annotated source code
|
||||
|
||||
The annotated source code can be found at [mathieuturcotte.github.io/node-backoff/docs](http://mathieuturcotte.github.io/node-backoff/docs/).
|
||||
|
||||
## License
|
||||
|
||||
This code is free to use under the terms of the [MIT license](http://mturcotte.mit-license.org/).
|
31
node_modules/backoff/index.js
generated
vendored
Normal file
31
node_modules/backoff/index.js
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2012 Mathieu Turcotte
|
||||
// Licensed under the MIT license.
|
||||
|
||||
var Backoff = require('./lib/backoff');
|
||||
var ExponentialBackoffStrategy = require('./lib/strategy/exponential');
|
||||
var FibonacciBackoffStrategy = require('./lib/strategy/fibonacci');
|
||||
var FunctionCall = require('./lib/function_call.js');
|
||||
|
||||
module.exports.Backoff = Backoff;
|
||||
module.exports.FunctionCall = FunctionCall;
|
||||
module.exports.FibonacciStrategy = FibonacciBackoffStrategy;
|
||||
module.exports.ExponentialStrategy = ExponentialBackoffStrategy;
|
||||
|
||||
// Constructs a Fibonacci backoff.
|
||||
module.exports.fibonacci = function(options) {
|
||||
return new Backoff(new FibonacciBackoffStrategy(options));
|
||||
};
|
||||
|
||||
// Constructs an exponential backoff.
|
||||
module.exports.exponential = function(options) {
|
||||
return new Backoff(new ExponentialBackoffStrategy(options));
|
||||
};
|
||||
|
||||
// Constructs a FunctionCall for the given function and arguments.
|
||||
module.exports.call = function(fn, vargs, callback) {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
fn = args[0];
|
||||
vargs = args.slice(1, args.length - 1);
|
||||
callback = args[args.length - 1];
|
||||
return new FunctionCall(fn, vargs, callback);
|
||||
};
|
65
node_modules/backoff/lib/backoff.js
generated
vendored
Normal file
65
node_modules/backoff/lib/backoff.js
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2012 Mathieu Turcotte
|
||||
// Licensed under the MIT license.
|
||||
|
||||
var events = require('events');
|
||||
var precond = require('precond');
|
||||
var util = require('util');
|
||||
|
||||
// A class to hold the state of a backoff operation. Accepts a backoff strategy
|
||||
// to generate the backoff delays.
|
||||
function Backoff(backoffStrategy) {
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
this.backoffStrategy_ = backoffStrategy;
|
||||
this.maxNumberOfRetry_ = -1;
|
||||
this.backoffNumber_ = 0;
|
||||
this.backoffDelay_ = 0;
|
||||
this.timeoutID_ = -1;
|
||||
|
||||
this.handlers = {
|
||||
backoff: this.onBackoff_.bind(this)
|
||||
};
|
||||
}
|
||||
util.inherits(Backoff, events.EventEmitter);
|
||||
|
||||
// Sets a limit, greater than 0, on the maximum number of backoffs. A 'fail'
|
||||
// event will be emitted when the limit is reached.
|
||||
Backoff.prototype.failAfter = function(maxNumberOfRetry) {
|
||||
precond.checkArgument(maxNumberOfRetry > 0,
|
||||
'Expected a maximum number of retry greater than 0 but got %s.',
|
||||
maxNumberOfRetry);
|
||||
|
||||
this.maxNumberOfRetry_ = maxNumberOfRetry;
|
||||
};
|
||||
|
||||
// Starts a backoff operation. Accepts an optional parameter to let the
|
||||
// listeners know why the backoff operation was started.
|
||||
Backoff.prototype.backoff = function(err) {
|
||||
precond.checkState(this.timeoutID_ === -1, 'Backoff in progress.');
|
||||
|
||||
if (this.backoffNumber_ === this.maxNumberOfRetry_) {
|
||||
this.emit('fail', err);
|
||||
this.reset();
|
||||
} else {
|
||||
this.backoffDelay_ = this.backoffStrategy_.next();
|
||||
this.timeoutID_ = setTimeout(this.handlers.backoff, this.backoffDelay_);
|
||||
this.emit('backoff', this.backoffNumber_, this.backoffDelay_, err);
|
||||
}
|
||||
};
|
||||
|
||||
// Handles the backoff timeout completion.
|
||||
Backoff.prototype.onBackoff_ = function() {
|
||||
this.timeoutID_ = -1;
|
||||
this.emit('ready', this.backoffNumber_, this.backoffDelay_);
|
||||
this.backoffNumber_++;
|
||||
};
|
||||
|
||||
// Stops any backoff operation and resets the backoff delay to its inital value.
|
||||
Backoff.prototype.reset = function() {
|
||||
this.backoffNumber_ = 0;
|
||||
this.backoffStrategy_.reset();
|
||||
clearTimeout(this.timeoutID_);
|
||||
this.timeoutID_ = -1;
|
||||
};
|
||||
|
||||
module.exports = Backoff;
|
190
node_modules/backoff/lib/function_call.js
generated
vendored
Normal file
190
node_modules/backoff/lib/function_call.js
generated
vendored
Normal file
@ -0,0 +1,190 @@
|
||||
// Copyright (c) 2012 Mathieu Turcotte
|
||||
// Licensed under the MIT license.
|
||||
|
||||
var events = require('events');
|
||||
var precond = require('precond');
|
||||
var util = require('util');
|
||||
|
||||
var Backoff = require('./backoff');
|
||||
var FibonacciBackoffStrategy = require('./strategy/fibonacci');
|
||||
|
||||
// Wraps a function to be called in a backoff loop.
|
||||
function FunctionCall(fn, args, callback) {
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
precond.checkIsFunction(fn, 'Expected fn to be a function.');
|
||||
precond.checkIsArray(args, 'Expected args to be an array.');
|
||||
precond.checkIsFunction(callback, 'Expected callback to be a function.');
|
||||
|
||||
this.function_ = fn;
|
||||
this.arguments_ = args;
|
||||
this.callback_ = callback;
|
||||
this.lastResult_ = [];
|
||||
this.numRetries_ = 0;
|
||||
|
||||
this.backoff_ = null;
|
||||
this.strategy_ = null;
|
||||
this.failAfter_ = -1;
|
||||
this.retryPredicate_ = FunctionCall.DEFAULT_RETRY_PREDICATE_;
|
||||
|
||||
this.state_ = FunctionCall.State_.PENDING;
|
||||
}
|
||||
util.inherits(FunctionCall, events.EventEmitter);
|
||||
|
||||
// States in which the call can be.
|
||||
FunctionCall.State_ = {
|
||||
// Call isn't started yet.
|
||||
PENDING: 0,
|
||||
// Call is in progress.
|
||||
RUNNING: 1,
|
||||
// Call completed successfully which means that either the wrapped function
|
||||
// returned successfully or the maximal number of backoffs was reached.
|
||||
COMPLETED: 2,
|
||||
// The call was aborted.
|
||||
ABORTED: 3
|
||||
};
|
||||
|
||||
// The default retry predicate which considers any error as retriable.
|
||||
FunctionCall.DEFAULT_RETRY_PREDICATE_ = function(err) {
|
||||
return true;
|
||||
};
|
||||
|
||||
// Checks whether the call is pending.
|
||||
FunctionCall.prototype.isPending = function() {
|
||||
return this.state_ == FunctionCall.State_.PENDING;
|
||||
};
|
||||
|
||||
// Checks whether the call is in progress.
|
||||
FunctionCall.prototype.isRunning = function() {
|
||||
return this.state_ == FunctionCall.State_.RUNNING;
|
||||
};
|
||||
|
||||
// Checks whether the call is completed.
|
||||
FunctionCall.prototype.isCompleted = function() {
|
||||
return this.state_ == FunctionCall.State_.COMPLETED;
|
||||
};
|
||||
|
||||
// Checks whether the call is aborted.
|
||||
FunctionCall.prototype.isAborted = function() {
|
||||
return this.state_ == FunctionCall.State_.ABORTED;
|
||||
};
|
||||
|
||||
// Sets the backoff strategy to use. Can only be called before the call is
|
||||
// started otherwise an exception will be thrown.
|
||||
FunctionCall.prototype.setStrategy = function(strategy) {
|
||||
precond.checkState(this.isPending(), 'FunctionCall in progress.');
|
||||
this.strategy_ = strategy;
|
||||
return this; // Return this for chaining.
|
||||
};
|
||||
|
||||
// Sets the predicate which will be used to determine whether the errors
|
||||
// returned from the wrapped function should be retried or not, e.g. a
|
||||
// network error would be retriable while a type error would stop the
|
||||
// function call.
|
||||
FunctionCall.prototype.retryIf = function(retryPredicate) {
|
||||
precond.checkState(this.isPending(), 'FunctionCall in progress.');
|
||||
this.retryPredicate_ = retryPredicate;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Returns all intermediary results returned by the wrapped function since
|
||||
// the initial call.
|
||||
FunctionCall.prototype.getLastResult = function() {
|
||||
return this.lastResult_.concat();
|
||||
};
|
||||
|
||||
// Returns the number of times the wrapped function call was retried.
|
||||
FunctionCall.prototype.getNumRetries = function() {
|
||||
return this.numRetries_;
|
||||
};
|
||||
|
||||
// Sets the backoff limit.
|
||||
FunctionCall.prototype.failAfter = function(maxNumberOfRetry) {
|
||||
precond.checkState(this.isPending(), 'FunctionCall in progress.');
|
||||
this.failAfter_ = maxNumberOfRetry;
|
||||
return this; // Return this for chaining.
|
||||
};
|
||||
|
||||
// Aborts the call.
|
||||
FunctionCall.prototype.abort = function() {
|
||||
if (this.isCompleted() || this.isAborted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isRunning()) {
|
||||
this.backoff_.reset();
|
||||
}
|
||||
|
||||
this.state_ = FunctionCall.State_.ABORTED;
|
||||
this.lastResult_ = [new Error('Backoff aborted.')];
|
||||
this.emit('abort');
|
||||
this.doCallback_();
|
||||
};
|
||||
|
||||
// Initiates the call to the wrapped function. Accepts an optional factory
|
||||
// function used to create the backoff instance; used when testing.
|
||||
FunctionCall.prototype.start = function(backoffFactory) {
|
||||
precond.checkState(!this.isAborted(), 'FunctionCall is aborted.');
|
||||
precond.checkState(this.isPending(), 'FunctionCall already started.');
|
||||
|
||||
var strategy = this.strategy_ || new FibonacciBackoffStrategy();
|
||||
|
||||
this.backoff_ = backoffFactory ?
|
||||
backoffFactory(strategy) :
|
||||
new Backoff(strategy);
|
||||
|
||||
this.backoff_.on('ready', this.doCall_.bind(this, true /* isRetry */));
|
||||
this.backoff_.on('fail', this.doCallback_.bind(this));
|
||||
this.backoff_.on('backoff', this.handleBackoff_.bind(this));
|
||||
|
||||
if (this.failAfter_ > 0) {
|
||||
this.backoff_.failAfter(this.failAfter_);
|
||||
}
|
||||
|
||||
this.state_ = FunctionCall.State_.RUNNING;
|
||||
this.doCall_(false /* isRetry */);
|
||||
};
|
||||
|
||||
// Calls the wrapped function.
|
||||
FunctionCall.prototype.doCall_ = function(isRetry) {
|
||||
if (isRetry) {
|
||||
this.numRetries_++;
|
||||
}
|
||||
var eventArgs = ['call'].concat(this.arguments_);
|
||||
events.EventEmitter.prototype.emit.apply(this, eventArgs);
|
||||
var callback = this.handleFunctionCallback_.bind(this);
|
||||
this.function_.apply(null, this.arguments_.concat(callback));
|
||||
};
|
||||
|
||||
// Calls the wrapped function's callback with the last result returned by the
|
||||
// wrapped function.
|
||||
FunctionCall.prototype.doCallback_ = function() {
|
||||
this.callback_.apply(null, this.lastResult_);
|
||||
};
|
||||
|
||||
// Handles wrapped function's completion. This method acts as a replacement
|
||||
// for the original callback function.
|
||||
FunctionCall.prototype.handleFunctionCallback_ = function() {
|
||||
if (this.isAborted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
this.lastResult_ = args; // Save last callback arguments.
|
||||
events.EventEmitter.prototype.emit.apply(this, ['callback'].concat(args));
|
||||
|
||||
var err = args[0];
|
||||
if (err && this.retryPredicate_(err)) {
|
||||
this.backoff_.backoff(err);
|
||||
} else {
|
||||
this.state_ = FunctionCall.State_.COMPLETED;
|
||||
this.doCallback_();
|
||||
}
|
||||
};
|
||||
|
||||
// Handles the backoff event by reemitting it.
|
||||
FunctionCall.prototype.handleBackoff_ = function(number, delay, err) {
|
||||
this.emit('backoff', number, delay, err);
|
||||
};
|
||||
|
||||
module.exports = FunctionCall;
|
41
node_modules/backoff/lib/strategy/exponential.js
generated
vendored
Normal file
41
node_modules/backoff/lib/strategy/exponential.js
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2012 Mathieu Turcotte
|
||||
// Licensed under the MIT license.
|
||||
|
||||
var util = require('util');
|
||||
var precond = require('precond');
|
||||
|
||||
var BackoffStrategy = require('./strategy');
|
||||
|
||||
// Exponential backoff strategy.
|
||||
function ExponentialBackoffStrategy(options) {
|
||||
BackoffStrategy.call(this, options);
|
||||
this.backoffDelay_ = 0;
|
||||
this.nextBackoffDelay_ = this.getInitialDelay();
|
||||
this.factor_ = ExponentialBackoffStrategy.DEFAULT_FACTOR;
|
||||
|
||||
if (options && options.factor !== undefined) {
|
||||
precond.checkArgument(options.factor > 1,
|
||||
'Exponential factor should be greater than 1 but got %s.',
|
||||
options.factor);
|
||||
this.factor_ = options.factor;
|
||||
}
|
||||
}
|
||||
util.inherits(ExponentialBackoffStrategy, BackoffStrategy);
|
||||
|
||||
// Default multiplication factor used to compute the next backoff delay from
|
||||
// the current one. The value can be overridden by passing a custom factor as
|
||||
// part of the options.
|
||||
ExponentialBackoffStrategy.DEFAULT_FACTOR = 2;
|
||||
|
||||
ExponentialBackoffStrategy.prototype.next_ = function() {
|
||||
this.backoffDelay_ = Math.min(this.nextBackoffDelay_, this.getMaxDelay());
|
||||
this.nextBackoffDelay_ = this.backoffDelay_ * this.factor_;
|
||||
return this.backoffDelay_;
|
||||
};
|
||||
|
||||
ExponentialBackoffStrategy.prototype.reset_ = function() {
|
||||
this.backoffDelay_ = 0;
|
||||
this.nextBackoffDelay_ = this.getInitialDelay();
|
||||
};
|
||||
|
||||
module.exports = ExponentialBackoffStrategy;
|
28
node_modules/backoff/lib/strategy/fibonacci.js
generated
vendored
Normal file
28
node_modules/backoff/lib/strategy/fibonacci.js
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2012 Mathieu Turcotte
|
||||
// Licensed under the MIT license.
|
||||
|
||||
var util = require('util');
|
||||
|
||||
var BackoffStrategy = require('./strategy');
|
||||
|
||||
// Fibonacci backoff strategy.
|
||||
function FibonacciBackoffStrategy(options) {
|
||||
BackoffStrategy.call(this, options);
|
||||
this.backoffDelay_ = 0;
|
||||
this.nextBackoffDelay_ = this.getInitialDelay();
|
||||
}
|
||||
util.inherits(FibonacciBackoffStrategy, BackoffStrategy);
|
||||
|
||||
FibonacciBackoffStrategy.prototype.next_ = function() {
|
||||
var backoffDelay = Math.min(this.nextBackoffDelay_, this.getMaxDelay());
|
||||
this.nextBackoffDelay_ += this.backoffDelay_;
|
||||
this.backoffDelay_ = backoffDelay;
|
||||
return backoffDelay;
|
||||
};
|
||||
|
||||
FibonacciBackoffStrategy.prototype.reset_ = function() {
|
||||
this.nextBackoffDelay_ = this.getInitialDelay();
|
||||
this.backoffDelay_ = 0;
|
||||
};
|
||||
|
||||
module.exports = FibonacciBackoffStrategy;
|
80
node_modules/backoff/lib/strategy/strategy.js
generated
vendored
Normal file
80
node_modules/backoff/lib/strategy/strategy.js
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2012 Mathieu Turcotte
|
||||
// Licensed under the MIT license.
|
||||
|
||||
var events = require('events');
|
||||
var util = require('util');
|
||||
|
||||
function isDef(value) {
|
||||
return value !== undefined && value !== null;
|
||||
}
|
||||
|
||||
// Abstract class defining the skeleton for the backoff strategies. Accepts an
|
||||
// object holding the options for the backoff strategy:
|
||||
//
|
||||
// * `randomisationFactor`: The randomisation factor which must be between 0
|
||||
// and 1 where 1 equates to a randomization factor of 100% and 0 to no
|
||||
// randomization.
|
||||
// * `initialDelay`: The backoff initial delay in milliseconds.
|
||||
// * `maxDelay`: The backoff maximal delay in milliseconds.
|
||||
function BackoffStrategy(options) {
|
||||
options = options || {};
|
||||
|
||||
if (isDef(options.initialDelay) && options.initialDelay < 1) {
|
||||
throw new Error('The initial timeout must be greater than 0.');
|
||||
} else if (isDef(options.maxDelay) && options.maxDelay < 1) {
|
||||
throw new Error('The maximal timeout must be greater than 0.');
|
||||
}
|
||||
|
||||
this.initialDelay_ = options.initialDelay || 100;
|
||||
this.maxDelay_ = options.maxDelay || 10000;
|
||||
|
||||
if (this.maxDelay_ <= this.initialDelay_) {
|
||||
throw new Error('The maximal backoff delay must be ' +
|
||||
'greater than the initial backoff delay.');
|
||||
}
|
||||
|
||||
if (isDef(options.randomisationFactor) &&
|
||||
(options.randomisationFactor < 0 || options.randomisationFactor > 1)) {
|
||||
throw new Error('The randomisation factor must be between 0 and 1.');
|
||||
}
|
||||
|
||||
this.randomisationFactor_ = options.randomisationFactor || 0;
|
||||
}
|
||||
|
||||
// Gets the maximal backoff delay.
|
||||
BackoffStrategy.prototype.getMaxDelay = function() {
|
||||
return this.maxDelay_;
|
||||
};
|
||||
|
||||
// Gets the initial backoff delay.
|
||||
BackoffStrategy.prototype.getInitialDelay = function() {
|
||||
return this.initialDelay_;
|
||||
};
|
||||
|
||||
// Template method that computes and returns the next backoff delay in
|
||||
// milliseconds.
|
||||
BackoffStrategy.prototype.next = function() {
|
||||
var backoffDelay = this.next_();
|
||||
var randomisationMultiple = 1 + Math.random() * this.randomisationFactor_;
|
||||
var randomizedDelay = Math.round(backoffDelay * randomisationMultiple);
|
||||
return randomizedDelay;
|
||||
};
|
||||
|
||||
// Computes and returns the next backoff delay. Intended to be overridden by
|
||||
// subclasses.
|
||||
BackoffStrategy.prototype.next_ = function() {
|
||||
throw new Error('BackoffStrategy.next_() unimplemented.');
|
||||
};
|
||||
|
||||
// Template method that resets the backoff delay to its initial value.
|
||||
BackoffStrategy.prototype.reset = function() {
|
||||
this.reset_();
|
||||
};
|
||||
|
||||
// Resets the backoff delay to its initial value. Intended to be overridden by
|
||||
// subclasses.
|
||||
BackoffStrategy.prototype.reset_ = function() {
|
||||
throw new Error('BackoffStrategy.reset_() unimplemented.');
|
||||
};
|
||||
|
||||
module.exports = BackoffStrategy;
|
69
node_modules/backoff/package.json
generated
vendored
Normal file
69
node_modules/backoff/package.json
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
{
|
||||
"_from": "backoff@~2.5.0",
|
||||
"_id": "backoff@2.5.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=",
|
||||
"_location": "/backoff",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "backoff@~2.5.0",
|
||||
"name": "backoff",
|
||||
"escapedName": "backoff",
|
||||
"rawSpec": "~2.5.0",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "~2.5.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/reconnect-core"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
|
||||
"_shasum": "f616eda9d3e4b66b8ca7fca79f695722c5f8e26f",
|
||||
"_spec": "backoff@~2.5.0",
|
||||
"_where": "/home/sergiu/linx-audio-simulator/node_modules/reconnect-core",
|
||||
"author": {
|
||||
"name": "Mathieu Turcotte",
|
||||
"email": "turcotte.mat@gmail.com"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/MathieuTurcotte/node-backoff/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"precond": "0.2"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Fibonacci and exponential backoffs.",
|
||||
"devDependencies": {
|
||||
"nodeunit": "0.9",
|
||||
"sinon": "1.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"lib",
|
||||
"tests"
|
||||
],
|
||||
"homepage": "https://github.com/MathieuTurcotte/node-backoff#readme",
|
||||
"keywords": [
|
||||
"backoff",
|
||||
"retry",
|
||||
"fibonacci",
|
||||
"exponential"
|
||||
],
|
||||
"license": "MIT",
|
||||
"name": "backoff",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/MathieuTurcotte/node-backoff.git"
|
||||
},
|
||||
"scripts": {
|
||||
"docco": "docco lib/*.js lib/strategy/* index.js",
|
||||
"pretest": "jshint lib/ tests/ examples/ index.js",
|
||||
"test": "node_modules/nodeunit/bin/nodeunit tests/"
|
||||
},
|
||||
"version": "2.5.0"
|
||||
}
|
68
node_modules/backoff/tests/api.js
generated
vendored
Normal file
68
node_modules/backoff/tests/api.js
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Mathieu Turcotte
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
var sinon = require('sinon');
|
||||
|
||||
var backoff = require('../index');
|
||||
|
||||
exports["API"] = {
|
||||
"backoff.fibonnaci should be a function that returns a backoff instance": function(test) {
|
||||
test.ok(backoff.fibonacci, 'backoff.fibonacci should be defined.');
|
||||
test.equal(typeof backoff.fibonacci, 'function',
|
||||
'backoff.fibonacci should be a function.');
|
||||
test.equal(backoff.fibonacci().constructor.name, 'Backoff');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff.exponential should be a function that returns a backoff instance": function(test) {
|
||||
test.ok(backoff.exponential, 'backoff.exponential should be defined.');
|
||||
test.equal(typeof backoff.exponential, 'function',
|
||||
'backoff.exponential should be a function.');
|
||||
test.equal(backoff.exponential().constructor.name, 'Backoff');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff.call should be a function that returns a FunctionCall instance": function(test) {
|
||||
var fn = function() {};
|
||||
var callback = function() {};
|
||||
test.ok(backoff.Backoff, 'backoff.call should be defined.');
|
||||
test.equal(typeof backoff.call, 'function',
|
||||
'backoff.call should be a function.');
|
||||
test.equal(backoff.call(fn, 1, 2, 3, callback).constructor.name,
|
||||
'FunctionCall');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff.Backoff should be defined and a function": function(test) {
|
||||
test.ok(backoff.Backoff, 'backoff.Backoff should be defined.');
|
||||
test.equal(typeof backoff.Backoff, 'function',
|
||||
'backoff.Backoff should be a function.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff.FunctionCall should be defined and a function": function(test) {
|
||||
test.ok(backoff.FunctionCall,
|
||||
'backoff.FunctionCall should be defined.');
|
||||
test.equal(typeof backoff.FunctionCall, 'function',
|
||||
'backoff.FunctionCall should be a function.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff.FibonacciStrategy should be defined and a function": function(test) {
|
||||
test.ok(backoff.FibonacciStrategy,
|
||||
'backoff.FibonacciStrategy should be defined.');
|
||||
test.equal(typeof backoff.FibonacciStrategy, 'function',
|
||||
'backoff.FibonacciStrategy should be a function.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff.ExponentialStrategy should be defined and a function": function(test) {
|
||||
test.ok(backoff.ExponentialStrategy,
|
||||
'backoff.ExponentialStrategy should be defined.');
|
||||
test.equal(typeof backoff.ExponentialStrategy, 'function',
|
||||
'backoff.ExponentialStrategy should be a function.');
|
||||
test.done();
|
||||
}
|
||||
};
|
166
node_modules/backoff/tests/backoff.js
generated
vendored
Normal file
166
node_modules/backoff/tests/backoff.js
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Mathieu Turcotte
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
var sinon = require('sinon');
|
||||
|
||||
var Backoff = require('../lib/backoff');
|
||||
var BackoffStrategy = require('../lib/strategy/strategy');
|
||||
|
||||
exports["Backoff"] = {
|
||||
setUp: function(callback) {
|
||||
this.backoffStrategy = sinon.stub(new BackoffStrategy());
|
||||
this.backoff = new Backoff(this.backoffStrategy);
|
||||
this.clock = sinon.useFakeTimers();
|
||||
this.spy = new sinon.spy();
|
||||
callback();
|
||||
},
|
||||
|
||||
tearDown: function(callback) {
|
||||
this.clock.restore();
|
||||
callback();
|
||||
},
|
||||
|
||||
"the backoff event should be emitted when backoff starts": function(test) {
|
||||
this.backoffStrategy.next.returns(10);
|
||||
this.backoff.on('backoff', this.spy);
|
||||
|
||||
this.backoff.backoff();
|
||||
|
||||
test.ok(this.spy.calledOnce,
|
||||
'Backoff event should be emitted when backoff starts.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"the ready event should be emitted on backoff completion": function(test) {
|
||||
this.backoffStrategy.next.returns(10);
|
||||
this.backoff.on('ready', this.spy);
|
||||
|
||||
this.backoff.backoff();
|
||||
this.clock.tick(10);
|
||||
|
||||
test.ok(this.spy.calledOnce,
|
||||
'Ready event should be emitted when backoff ends.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"the backoff event should be passed the backoff delay": function(test) {
|
||||
this.backoffStrategy.next.returns(989);
|
||||
this.backoff.on('backoff', this.spy);
|
||||
|
||||
this.backoff.backoff();
|
||||
|
||||
test.equal(this.spy.getCall(0).args[1], 989, 'Backoff event should ' +
|
||||
'carry the backoff delay as its second argument.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"the ready event should be passed the backoff delay": function(test) {
|
||||
this.backoffStrategy.next.returns(989);
|
||||
this.backoff.on('ready', this.spy);
|
||||
|
||||
this.backoff.backoff();
|
||||
this.clock.tick(989);
|
||||
|
||||
test.equal(this.spy.getCall(0).args[1], 989, 'Ready event should ' +
|
||||
'carry the backoff delay as its second argument.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"the fail event should be emitted when backoff limit is reached": function(test) {
|
||||
var err = new Error('Fail');
|
||||
|
||||
this.backoffStrategy.next.returns(10);
|
||||
this.backoff.on('fail', this.spy);
|
||||
|
||||
this.backoff.failAfter(2);
|
||||
|
||||
// Consume first 2 backoffs.
|
||||
for (var i = 0; i < 2; i++) {
|
||||
this.backoff.backoff();
|
||||
this.clock.tick(10);
|
||||
}
|
||||
|
||||
// Failure should occur on the third call, and not before.
|
||||
test.ok(!this.spy.calledOnce, 'Fail event shouldn\'t have been emitted.');
|
||||
this.backoff.backoff(err);
|
||||
test.ok(this.spy.calledOnce, 'Fail event should have been emitted.');
|
||||
test.equal(this.spy.getCall(0).args[0], err, 'Error should be passed');
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"calling backoff while a backoff is in progress should throw an error": function(test) {
|
||||
this.backoffStrategy.next.returns(10);
|
||||
var backoff = this.backoff;
|
||||
|
||||
backoff.backoff();
|
||||
|
||||
test.throws(function() {
|
||||
backoff.backoff();
|
||||
}, /in progress/);
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff limit should be greater than 0": function(test) {
|
||||
var backoff = this.backoff;
|
||||
test.throws(function() {
|
||||
backoff.failAfter(0);
|
||||
}, /greater than 0 but got 0/);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"reset should cancel any backoff in progress": function(test) {
|
||||
this.backoffStrategy.next.returns(10);
|
||||
this.backoff.on('ready', this.spy);
|
||||
|
||||
this.backoff.backoff();
|
||||
|
||||
this.backoff.reset();
|
||||
this.clock.tick(100); // 'ready' should not be emitted.
|
||||
|
||||
test.equals(this.spy.callCount, 0, 'Reset should have aborted the backoff.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"reset should reset the backoff strategy": function(test) {
|
||||
this.backoff.reset();
|
||||
test.ok(this.backoffStrategy.reset.calledOnce,
|
||||
'The backoff strategy should have been resetted.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff should be reset after fail": function(test) {
|
||||
this.backoffStrategy.next.returns(10);
|
||||
|
||||
this.backoff.failAfter(1);
|
||||
|
||||
this.backoff.backoff();
|
||||
this.clock.tick(10);
|
||||
this.backoff.backoff();
|
||||
|
||||
test.ok(this.backoffStrategy.reset.calledOnce,
|
||||
'Backoff should have been resetted after failure.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"the backoff number should increase from 0 to N - 1": function(test) {
|
||||
this.backoffStrategy.next.returns(10);
|
||||
this.backoff.on('backoff', this.spy);
|
||||
|
||||
var expectedNumbers = [0, 1, 2, 3, 4];
|
||||
var actualNumbers = [];
|
||||
|
||||
for (var i = 0; i < expectedNumbers.length; i++) {
|
||||
this.backoff.backoff();
|
||||
this.clock.tick(10);
|
||||
actualNumbers.push(this.spy.getCall(i).args[0]);
|
||||
}
|
||||
|
||||
test.deepEqual(expectedNumbers, actualNumbers,
|
||||
'Backoff number should increase from 0 to N - 1.');
|
||||
test.done();
|
||||
}
|
||||
};
|
123
node_modules/backoff/tests/backoff_strategy.js
generated
vendored
Normal file
123
node_modules/backoff/tests/backoff_strategy.js
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Mathieu Turcotte
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
var sinon = require('sinon');
|
||||
var util = require('util');
|
||||
|
||||
var BackoffStrategy = require('../lib/strategy/strategy');
|
||||
|
||||
function SampleBackoffStrategy(options) {
|
||||
BackoffStrategy.call(this, options);
|
||||
}
|
||||
util.inherits(SampleBackoffStrategy, BackoffStrategy);
|
||||
|
||||
SampleBackoffStrategy.prototype.next_ = function() {
|
||||
return this.getInitialDelay();
|
||||
};
|
||||
|
||||
SampleBackoffStrategy.prototype.reset_ = function() {};
|
||||
|
||||
exports["BackoffStrategy"] = {
|
||||
setUp: function(callback) {
|
||||
this.random = sinon.stub(Math, 'random');
|
||||
callback();
|
||||
},
|
||||
|
||||
tearDown: function(callback) {
|
||||
this.random.restore();
|
||||
callback();
|
||||
},
|
||||
|
||||
"the randomisation factor should be between 0 and 1": function(test) {
|
||||
test.throws(function() {
|
||||
new BackoffStrategy({
|
||||
randomisationFactor: -0.1
|
||||
});
|
||||
});
|
||||
|
||||
test.throws(function() {
|
||||
new BackoffStrategy({
|
||||
randomisationFactor: 1.1
|
||||
});
|
||||
});
|
||||
|
||||
test.doesNotThrow(function() {
|
||||
new BackoffStrategy({
|
||||
randomisationFactor: 0.5
|
||||
});
|
||||
});
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"the raw delay should be randomized based on the randomisation factor": function(test) {
|
||||
var strategy = new SampleBackoffStrategy({
|
||||
randomisationFactor: 0.5,
|
||||
initialDelay: 1000
|
||||
});
|
||||
this.random.returns(0.5);
|
||||
|
||||
var backoffDelay = strategy.next();
|
||||
|
||||
test.equals(backoffDelay, 1000 + (1000 * 0.5 * 0.5));
|
||||
test.done();
|
||||
},
|
||||
|
||||
"the initial backoff delay should be greater than 0": function(test) {
|
||||
test.throws(function() {
|
||||
new BackoffStrategy({
|
||||
initialDelay: -1
|
||||
});
|
||||
});
|
||||
|
||||
test.throws(function() {
|
||||
new BackoffStrategy({
|
||||
initialDelay: 0
|
||||
});
|
||||
});
|
||||
|
||||
test.doesNotThrow(function() {
|
||||
new BackoffStrategy({
|
||||
initialDelay: 1
|
||||
});
|
||||
});
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"the maximal backoff delay should be greater than 0": function(test) {
|
||||
test.throws(function() {
|
||||
new BackoffStrategy({
|
||||
maxDelay: -1
|
||||
});
|
||||
});
|
||||
|
||||
test.throws(function() {
|
||||
new BackoffStrategy({
|
||||
maxDelay: 0
|
||||
});
|
||||
});
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"the maximal backoff delay should be greater than the initial backoff delay": function(test) {
|
||||
test.throws(function() {
|
||||
new BackoffStrategy({
|
||||
initialDelay: 10,
|
||||
maxDelay: 10
|
||||
});
|
||||
});
|
||||
|
||||
test.doesNotThrow(function() {
|
||||
new BackoffStrategy({
|
||||
initialDelay: 10,
|
||||
maxDelay: 11
|
||||
});
|
||||
});
|
||||
|
||||
test.done();
|
||||
}
|
||||
};
|
61
node_modules/backoff/tests/exponential_backoff_strategy.js
generated
vendored
Normal file
61
node_modules/backoff/tests/exponential_backoff_strategy.js
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Mathieu Turcotte
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
var sinon = require('sinon');
|
||||
|
||||
var ExponentialBackoffStrategy = require('../lib/strategy/exponential');
|
||||
|
||||
exports["ExponentialBackoffStrategy"] = {
|
||||
|
||||
"backoff delays should follow an exponential sequence": function(test) {
|
||||
var strategy = new ExponentialBackoffStrategy({
|
||||
initialDelay: 10,
|
||||
maxDelay: 1000
|
||||
});
|
||||
|
||||
// Exponential sequence: x[i] = x[i-1] * 2.
|
||||
var expectedDelays = [10, 20, 40, 80, 160, 320, 640, 1000, 1000];
|
||||
var actualDelays = expectedDelays.map(function () {
|
||||
return strategy.next();
|
||||
});
|
||||
|
||||
test.deepEqual(expectedDelays, actualDelays,
|
||||
'Generated delays should follow an exponential sequence.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff delay factor should be configurable": function (test) {
|
||||
var strategy = new ExponentialBackoffStrategy({
|
||||
initialDelay: 10,
|
||||
maxDelay: 270,
|
||||
factor: 3
|
||||
});
|
||||
|
||||
// Exponential sequence: x[i] = x[i-1] * 3.
|
||||
var expectedDelays = [10, 30, 90, 270, 270];
|
||||
var actualDelays = expectedDelays.map(function () {
|
||||
return strategy.next();
|
||||
});
|
||||
|
||||
test.deepEqual(expectedDelays, actualDelays,
|
||||
'Generated delays should follow a configurable exponential sequence.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff delays should restart from the initial delay after reset": function(test) {
|
||||
var strategy = new ExponentialBackoffStrategy({
|
||||
initialDelay: 10,
|
||||
maxDelay: 1000
|
||||
});
|
||||
|
||||
strategy.next();
|
||||
strategy.reset();
|
||||
|
||||
var backoffDelay = strategy.next();
|
||||
test.equals(backoffDelay, 10,
|
||||
'Strategy should return the initial delay after reset.');
|
||||
test.done();
|
||||
}
|
||||
};
|
47
node_modules/backoff/tests/fibonacci_backoff_strategy.js
generated
vendored
Normal file
47
node_modules/backoff/tests/fibonacci_backoff_strategy.js
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Mathieu Turcotte
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
var sinon = require('sinon');
|
||||
|
||||
var FibonacciBackoffStrategy = require('../lib/strategy/fibonacci');
|
||||
|
||||
exports["FibonacciBackoffStrategy"] = {
|
||||
setUp: function(callback) {
|
||||
this.strategy = new FibonacciBackoffStrategy({
|
||||
initialDelay: 10,
|
||||
maxDelay: 1000
|
||||
});
|
||||
callback();
|
||||
},
|
||||
|
||||
"backoff delays should follow a Fibonacci sequence": function(test) {
|
||||
// Fibonacci sequence: x[i] = x[i-1] + x[i-2].
|
||||
var expectedDelays = [10, 10, 20, 30, 50, 80, 130, 210, 340, 550, 890, 1000];
|
||||
var actualDelays = [];
|
||||
|
||||
for (var i = 0; i < expectedDelays.length; i++) {
|
||||
actualDelays.push(this.strategy.next());
|
||||
}
|
||||
|
||||
test.deepEqual(expectedDelays, actualDelays,
|
||||
'Generated delays should follow a Fibonacci sequence.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff delays should restart from the initial delay after reset": function(test) {
|
||||
var strategy = new FibonacciBackoffStrategy({
|
||||
initialDelay: 10,
|
||||
maxDelay: 1000
|
||||
});
|
||||
|
||||
strategy.next();
|
||||
strategy.reset();
|
||||
|
||||
var backoffDelay = strategy.next();
|
||||
test.equals(backoffDelay, 10,
|
||||
'Strategy should return the initial delay after reset.');
|
||||
test.done();
|
||||
}
|
||||
};
|
406
node_modules/backoff/tests/function_call.js
generated
vendored
Normal file
406
node_modules/backoff/tests/function_call.js
generated
vendored
Normal file
@ -0,0 +1,406 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Mathieu Turcotte
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
var events = require('events');
|
||||
var sinon = require('sinon');
|
||||
var util = require('util');
|
||||
|
||||
var FunctionCall = require('../lib/function_call');
|
||||
|
||||
function MockBackoff() {
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
this.reset = sinon.spy();
|
||||
this.backoff = sinon.spy();
|
||||
this.failAfter = sinon.spy();
|
||||
}
|
||||
util.inherits(MockBackoff, events.EventEmitter);
|
||||
|
||||
exports["FunctionCall"] = {
|
||||
setUp: function(callback) {
|
||||
this.wrappedFn = sinon.stub();
|
||||
this.callback = sinon.stub();
|
||||
this.backoff = new MockBackoff();
|
||||
this.backoffFactory = sinon.stub();
|
||||
this.backoffFactory.returns(this.backoff);
|
||||
callback();
|
||||
},
|
||||
|
||||
tearDown: function(callback) {
|
||||
callback();
|
||||
},
|
||||
|
||||
"constructor's first argument should be a function": function(test) {
|
||||
test.throws(function() {
|
||||
new FunctionCall(1, [], function() {});
|
||||
}, /Expected fn to be a function./);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"constructor's last argument should be a function": function(test) {
|
||||
test.throws(function() {
|
||||
new FunctionCall(function() {}, [], 3);
|
||||
}, /Expected callback to be a function./);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"isPending should return false once the call is started": function(test) {
|
||||
this.wrappedFn.
|
||||
onFirstCall().yields(new Error()).
|
||||
onSecondCall().yields(null, 'Success!');
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
|
||||
test.ok(call.isPending());
|
||||
|
||||
call.start(this.backoffFactory);
|
||||
test.ok(!call.isPending());
|
||||
|
||||
this.backoff.emit('ready');
|
||||
test.ok(!call.isPending());
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"isRunning should return true when call is in progress": function(test) {
|
||||
this.wrappedFn.
|
||||
onFirstCall().yields(new Error()).
|
||||
onSecondCall().yields(null, 'Success!');
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
|
||||
test.ok(!call.isRunning());
|
||||
|
||||
call.start(this.backoffFactory);
|
||||
test.ok(call.isRunning());
|
||||
|
||||
this.backoff.emit('ready');
|
||||
test.ok(!call.isRunning());
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"isCompleted should return true once the call completes": function(test) {
|
||||
this.wrappedFn.
|
||||
onFirstCall().yields(new Error()).
|
||||
onSecondCall().yields(null, 'Success!');
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
|
||||
test.ok(!call.isCompleted());
|
||||
|
||||
call.start(this.backoffFactory);
|
||||
test.ok(!call.isCompleted());
|
||||
|
||||
this.backoff.emit('ready');
|
||||
test.ok(call.isCompleted());
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"isAborted should return true once the call is aborted": function(test) {
|
||||
this.wrappedFn.
|
||||
onFirstCall().yields(new Error()).
|
||||
onSecondCall().yields(null, 'Success!');
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
|
||||
test.ok(!call.isAborted());
|
||||
call.abort();
|
||||
test.ok(call.isAborted());
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"setStrategy should overwrite the default strategy": function(test) {
|
||||
var replacementStrategy = {};
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
call.setStrategy(replacementStrategy);
|
||||
call.start(this.backoffFactory);
|
||||
test.ok(this.backoffFactory.calledWith(replacementStrategy),
|
||||
'User defined strategy should be used to instantiate ' +
|
||||
'the backoff instance.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"setStrategy should throw if the call is in progress": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
call.start(this.backoffFactory);
|
||||
test.throws(function() {
|
||||
call.setStrategy({});
|
||||
}, /in progress/);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"failAfter should not be set by default": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
call.start(this.backoffFactory);
|
||||
test.equal(0, this.backoff.failAfter.callCount);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"failAfter should be used as the maximum number of backoffs": function(test) {
|
||||
var failAfterValue = 99;
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
call.failAfter(failAfterValue);
|
||||
call.start(this.backoffFactory);
|
||||
test.ok(this.backoff.failAfter.calledWith(failAfterValue),
|
||||
'User defined maximum number of backoffs shoud be ' +
|
||||
'used to configure the backoff instance.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"failAfter should throw if the call is in progress": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
call.start(this.backoffFactory);
|
||||
test.throws(function() {
|
||||
call.failAfter(1234);
|
||||
}, /in progress/);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"start shouldn't allow overlapping invocation": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
var backoffFactory = this.backoffFactory;
|
||||
|
||||
call.start(backoffFactory);
|
||||
test.throws(function() {
|
||||
call.start(backoffFactory);
|
||||
}, /already started/);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"start shouldn't allow invocation of aborted call": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
var backoffFactory = this.backoffFactory;
|
||||
|
||||
call.abort();
|
||||
test.throws(function() {
|
||||
call.start(backoffFactory);
|
||||
}, /aborted/);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"call should forward its arguments to the wrapped function": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
|
||||
call.start(this.backoffFactory);
|
||||
test.ok(this.wrappedFn.calledWith(1, 2, 3));
|
||||
test.done();
|
||||
},
|
||||
|
||||
"call should complete when the wrapped function succeeds": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
|
||||
this.wrappedFn.
|
||||
onCall(0).yields(new Error()).
|
||||
onCall(1).yields(new Error()).
|
||||
onCall(2).yields(new Error()).
|
||||
onCall(3).yields(null, 'Success!');
|
||||
|
||||
call.start(this.backoffFactory);
|
||||
|
||||
for (var i = 0; i < 2; i++) {
|
||||
this.backoff.emit('ready');
|
||||
}
|
||||
|
||||
test.equals(this.callback.callCount, 0);
|
||||
this.backoff.emit('ready');
|
||||
|
||||
test.ok(this.callback.calledWith(null, 'Success!'));
|
||||
test.ok(this.wrappedFn.alwaysCalledWith(1, 2, 3));
|
||||
test.done();
|
||||
},
|
||||
|
||||
"call should fail when the backoff limit is reached": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
|
||||
var error = new Error();
|
||||
this.wrappedFn.yields(error);
|
||||
call.start(this.backoffFactory);
|
||||
|
||||
for (var i = 0; i < 3; i++) {
|
||||
this.backoff.emit('ready');
|
||||
}
|
||||
|
||||
test.equals(this.callback.callCount, 0);
|
||||
|
||||
this.backoff.emit('fail');
|
||||
|
||||
test.ok(this.callback.calledWith(error));
|
||||
test.ok(this.wrappedFn.alwaysCalledWith(1, 2, 3));
|
||||
test.done();
|
||||
},
|
||||
|
||||
"call should fail when the retry predicate returns false": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
|
||||
call.retryIf(function(err) { return err.retriable; });
|
||||
|
||||
var retriableError = new Error();
|
||||
retriableError.retriable = true;
|
||||
|
||||
var fatalError = new Error();
|
||||
fatalError.retriable = false;
|
||||
|
||||
this.wrappedFn.
|
||||
onCall(0).yields(retriableError).
|
||||
onCall(1).yields(retriableError).
|
||||
onCall(2).yields(fatalError);
|
||||
|
||||
call.start(this.backoffFactory);
|
||||
|
||||
for (var i = 0; i < 2; i++) {
|
||||
this.backoff.emit('ready');
|
||||
}
|
||||
|
||||
test.equals(this.callback.callCount, 1);
|
||||
test.ok(this.callback.calledWith(fatalError));
|
||||
test.ok(this.wrappedFn.alwaysCalledWith(1, 2, 3));
|
||||
test.done();
|
||||
},
|
||||
|
||||
"wrapped function's callback shouldn't be called after abort": function(test) {
|
||||
var call = new FunctionCall(function(callback) {
|
||||
call.abort(); // Abort in middle of wrapped function's execution.
|
||||
callback(null, 'ok');
|
||||
}, [], this.callback);
|
||||
|
||||
call.start(this.backoffFactory);
|
||||
|
||||
test.equals(this.callback.callCount, 1,
|
||||
'Wrapped function\'s callback shouldn\'t be called after abort.');
|
||||
test.ok(this.callback.calledWithMatch(sinon.match(function (err) {
|
||||
return !!err.message.match(/Backoff aborted/);
|
||||
}, "abort error")));
|
||||
test.done();
|
||||
},
|
||||
|
||||
"abort event is emitted once when abort is called": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
this.wrappedFn.yields(new Error());
|
||||
var callEventSpy = sinon.spy();
|
||||
|
||||
call.on('abort', callEventSpy);
|
||||
call.start(this.backoffFactory);
|
||||
|
||||
call.abort();
|
||||
call.abort();
|
||||
call.abort();
|
||||
|
||||
test.equals(callEventSpy.callCount, 1);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"getLastResult should return the last intermediary result": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
this.wrappedFn.yields(1);
|
||||
call.start(this.backoffFactory);
|
||||
|
||||
for (var i = 2; i < 5; i++) {
|
||||
this.wrappedFn.yields(i);
|
||||
this.backoff.emit('ready');
|
||||
test.deepEqual([i], call.getLastResult());
|
||||
}
|
||||
|
||||
this.wrappedFn.yields(null);
|
||||
this.backoff.emit('ready');
|
||||
test.deepEqual([null], call.getLastResult());
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"getNumRetries should return the number of retries": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [], this.callback);
|
||||
|
||||
this.wrappedFn.yields(1);
|
||||
call.start(this.backoffFactory);
|
||||
// The inital call doesn't count as a retry.
|
||||
test.equals(0, call.getNumRetries());
|
||||
|
||||
for (var i = 2; i < 5; i++) {
|
||||
this.wrappedFn.yields(i);
|
||||
this.backoff.emit('ready');
|
||||
test.equals(i - 1, call.getNumRetries());
|
||||
}
|
||||
|
||||
this.wrappedFn.yields(null);
|
||||
this.backoff.emit('ready');
|
||||
test.equals(4, call.getNumRetries());
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
"wrapped function's errors should be propagated": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
|
||||
this.wrappedFn.throws(new Error());
|
||||
test.throws(function() {
|
||||
call.start(this.backoffFactory);
|
||||
}, Error);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"wrapped callback's errors should be propagated": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [1, 2, 3], this.callback);
|
||||
this.wrappedFn.yields(null, 'Success!');
|
||||
this.callback.throws(new Error());
|
||||
test.throws(function() {
|
||||
call.start(this.backoffFactory);
|
||||
}, Error);
|
||||
test.done();
|
||||
},
|
||||
|
||||
"call event should be emitted when wrapped function gets called": function(test) {
|
||||
this.wrappedFn.yields(1);
|
||||
var callEventSpy = sinon.spy();
|
||||
|
||||
var call = new FunctionCall(this.wrappedFn, [1, 'two'], this.callback);
|
||||
call.on('call', callEventSpy);
|
||||
call.start(this.backoffFactory);
|
||||
|
||||
for (var i = 1; i < 5; i++) {
|
||||
this.backoff.emit('ready');
|
||||
}
|
||||
|
||||
test.equal(5, callEventSpy.callCount,
|
||||
'The call event should have been emitted 5 times.');
|
||||
test.deepEqual([1, 'two'], callEventSpy.getCall(0).args,
|
||||
'The call event should carry function\'s args.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"callback event should be emitted when callback is called": function(test) {
|
||||
var call = new FunctionCall(this.wrappedFn, [1, 'two'], this.callback);
|
||||
var callbackSpy = sinon.spy();
|
||||
call.on('callback', callbackSpy);
|
||||
|
||||
this.wrappedFn.yields('error');
|
||||
call.start(this.backoffFactory);
|
||||
|
||||
this.wrappedFn.yields(null, 'done');
|
||||
this.backoff.emit('ready');
|
||||
|
||||
test.equal(2, callbackSpy.callCount,
|
||||
'Callback event should have been emitted 2 times.');
|
||||
test.deepEqual(['error'], callbackSpy.firstCall.args,
|
||||
'First callback event should carry first call\'s results.');
|
||||
test.deepEqual([null, 'done'], callbackSpy.secondCall.args,
|
||||
'Second callback event should carry second call\'s results.');
|
||||
test.done();
|
||||
},
|
||||
|
||||
"backoff event should be emitted on backoff start": function(test) {
|
||||
var err = new Error('backoff event error');
|
||||
var call = new FunctionCall(this.wrappedFn, [1, 'two'], this.callback);
|
||||
var backoffSpy = sinon.spy();
|
||||
|
||||
call.on('backoff', backoffSpy);
|
||||
|
||||
this.wrappedFn.yields(err);
|
||||
call.start(this.backoffFactory);
|
||||
this.backoff.emit('backoff', 3, 1234, err);
|
||||
|
||||
test.ok(this.backoff.backoff.calledWith(err),
|
||||
'The backoff instance should have been called with the error.');
|
||||
test.equal(1, backoffSpy.callCount,
|
||||
'Backoff event should have been emitted 1 time.');
|
||||
test.deepEqual([3, 1234, err], backoffSpy.firstCall.args,
|
||||
'Backoff event should carry the backoff number, delay and error.');
|
||||
test.done();
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user