added unit testing, and started implementing unit tests...phew
This commit is contained in:
496
node_modules/mocha/lib/runnable.js
generated
vendored
Normal file
496
node_modules/mocha/lib/runnable.js
generated
vendored
Normal file
@ -0,0 +1,496 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Pending = require('./pending');
|
||||
var debug = require('debug')('mocha:runnable');
|
||||
var milliseconds = require('ms');
|
||||
var utils = require('./utils');
|
||||
var createInvalidExceptionError = require('./errors')
|
||||
.createInvalidExceptionError;
|
||||
|
||||
/**
|
||||
* Save timer references to avoid Sinon interfering (see GH-237).
|
||||
*/
|
||||
var Date = global.Date;
|
||||
var setTimeout = global.setTimeout;
|
||||
var clearTimeout = global.clearTimeout;
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
module.exports = Runnable;
|
||||
|
||||
/**
|
||||
* Initialize a new `Runnable` with the given `title` and callback `fn`.
|
||||
*
|
||||
* @class
|
||||
* @extends external:EventEmitter
|
||||
* @public
|
||||
* @param {String} title
|
||||
* @param {Function} fn
|
||||
*/
|
||||
function Runnable(title, fn) {
|
||||
this.title = title;
|
||||
this.fn = fn;
|
||||
this.body = (fn || '').toString();
|
||||
this.async = fn && fn.length;
|
||||
this.sync = !this.async;
|
||||
this._timeout = 2000;
|
||||
this._slow = 75;
|
||||
this._enableTimeouts = true;
|
||||
this.timedOut = false;
|
||||
this._retries = -1;
|
||||
this._currentRetry = 0;
|
||||
this.pending = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from `EventEmitter.prototype`.
|
||||
*/
|
||||
utils.inherits(Runnable, EventEmitter);
|
||||
|
||||
/**
|
||||
* Get current timeout value in msecs.
|
||||
*
|
||||
* @private
|
||||
* @returns {number} current timeout threshold value
|
||||
*/
|
||||
/**
|
||||
* @summary
|
||||
* Set timeout threshold value (msecs).
|
||||
*
|
||||
* @description
|
||||
* A string argument can use shorthand (e.g., "2s") and will be converted.
|
||||
* The value will be clamped to range [<code>0</code>, <code>2^<sup>31</sup>-1</code>].
|
||||
* If clamped value matches either range endpoint, timeouts will be disabled.
|
||||
*
|
||||
* @private
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Maximum_delay_value}
|
||||
* @param {number|string} ms - Timeout threshold value.
|
||||
* @returns {Runnable} this
|
||||
* @chainable
|
||||
*/
|
||||
Runnable.prototype.timeout = function(ms) {
|
||||
if (!arguments.length) {
|
||||
return this._timeout;
|
||||
}
|
||||
if (typeof ms === 'string') {
|
||||
ms = milliseconds(ms);
|
||||
}
|
||||
|
||||
// Clamp to range
|
||||
var INT_MAX = Math.pow(2, 31) - 1;
|
||||
var range = [0, INT_MAX];
|
||||
ms = utils.clamp(ms, range);
|
||||
|
||||
// see #1652 for reasoning
|
||||
if (ms === range[0] || ms === range[1]) {
|
||||
this._enableTimeouts = false;
|
||||
}
|
||||
debug('timeout %d', ms);
|
||||
this._timeout = ms;
|
||||
if (this.timer) {
|
||||
this.resetTimeout();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set or get slow `ms`.
|
||||
*
|
||||
* @private
|
||||
* @param {number|string} ms
|
||||
* @return {Runnable|number} ms or Runnable instance.
|
||||
*/
|
||||
Runnable.prototype.slow = function(ms) {
|
||||
if (!arguments.length || typeof ms === 'undefined') {
|
||||
return this._slow;
|
||||
}
|
||||
if (typeof ms === 'string') {
|
||||
ms = milliseconds(ms);
|
||||
}
|
||||
debug('slow %d', ms);
|
||||
this._slow = ms;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set and get whether timeout is `enabled`.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} enabled
|
||||
* @return {Runnable|boolean} enabled or Runnable instance.
|
||||
*/
|
||||
Runnable.prototype.enableTimeouts = function(enabled) {
|
||||
if (!arguments.length) {
|
||||
return this._enableTimeouts;
|
||||
}
|
||||
debug('enableTimeouts %s', enabled);
|
||||
this._enableTimeouts = enabled;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Halt and mark as pending.
|
||||
*
|
||||
* @memberof Mocha.Runnable
|
||||
* @public
|
||||
*/
|
||||
Runnable.prototype.skip = function() {
|
||||
throw new Pending('sync skip');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this runnable or its parent suite is marked as pending.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Runnable.prototype.isPending = function() {
|
||||
return this.pending || (this.parent && this.parent.isPending());
|
||||
};
|
||||
|
||||
/**
|
||||
* Return `true` if this Runnable has failed.
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
Runnable.prototype.isFailed = function() {
|
||||
return !this.isPending() && this.state === constants.STATE_FAILED;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return `true` if this Runnable has passed.
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
Runnable.prototype.isPassed = function() {
|
||||
return !this.isPending() && this.state === constants.STATE_PASSED;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set or get number of retries.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Runnable.prototype.retries = function(n) {
|
||||
if (!arguments.length) {
|
||||
return this._retries;
|
||||
}
|
||||
this._retries = n;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set or get current retry
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Runnable.prototype.currentRetry = function(n) {
|
||||
if (!arguments.length) {
|
||||
return this._currentRetry;
|
||||
}
|
||||
this._currentRetry = n;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the full title generated by recursively concatenating the parent's
|
||||
* full title.
|
||||
*
|
||||
* @memberof Mocha.Runnable
|
||||
* @public
|
||||
* @return {string}
|
||||
*/
|
||||
Runnable.prototype.fullTitle = function() {
|
||||
return this.titlePath().join(' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the title path generated by concatenating the parent's title path with the title.
|
||||
*
|
||||
* @memberof Mocha.Runnable
|
||||
* @public
|
||||
* @return {string}
|
||||
*/
|
||||
Runnable.prototype.titlePath = function() {
|
||||
return this.parent.titlePath().concat([this.title]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the timeout.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Runnable.prototype.clearTimeout = function() {
|
||||
clearTimeout(this.timer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Inspect the runnable void of private properties.
|
||||
*
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
Runnable.prototype.inspect = function() {
|
||||
return JSON.stringify(
|
||||
this,
|
||||
function(key, val) {
|
||||
if (key[0] === '_') {
|
||||
return;
|
||||
}
|
||||
if (key === 'parent') {
|
||||
return '#<Suite>';
|
||||
}
|
||||
if (key === 'ctx') {
|
||||
return '#<Context>';
|
||||
}
|
||||
return val;
|
||||
},
|
||||
2
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset the timeout.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Runnable.prototype.resetTimeout = function() {
|
||||
var self = this;
|
||||
var ms = this.timeout() || 1e9;
|
||||
|
||||
if (!this._enableTimeouts) {
|
||||
return;
|
||||
}
|
||||
this.clearTimeout();
|
||||
this.timer = setTimeout(function() {
|
||||
if (!self._enableTimeouts) {
|
||||
return;
|
||||
}
|
||||
self.callback(self._timeoutError(ms));
|
||||
self.timedOut = true;
|
||||
}, ms);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set or get a list of whitelisted globals for this test run.
|
||||
*
|
||||
* @private
|
||||
* @param {string[]} globals
|
||||
*/
|
||||
Runnable.prototype.globals = function(globals) {
|
||||
if (!arguments.length) {
|
||||
return this._allowedGlobals;
|
||||
}
|
||||
this._allowedGlobals = globals;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run the test and invoke `fn(err)`.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @private
|
||||
*/
|
||||
Runnable.prototype.run = function(fn) {
|
||||
var self = this;
|
||||
var start = new Date();
|
||||
var ctx = this.ctx;
|
||||
var finished;
|
||||
var emitted;
|
||||
|
||||
// Sometimes the ctx exists, but it is not runnable
|
||||
if (ctx && ctx.runnable) {
|
||||
ctx.runnable(this);
|
||||
}
|
||||
|
||||
// called multiple times
|
||||
function multiple(err) {
|
||||
if (emitted) {
|
||||
return;
|
||||
}
|
||||
emitted = true;
|
||||
var msg = 'done() called multiple times';
|
||||
if (err && err.message) {
|
||||
err.message += " (and Mocha's " + msg + ')';
|
||||
self.emit('error', err);
|
||||
} else {
|
||||
self.emit('error', new Error(msg));
|
||||
}
|
||||
}
|
||||
|
||||
// finished
|
||||
function done(err) {
|
||||
var ms = self.timeout();
|
||||
if (self.timedOut) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
return multiple(err);
|
||||
}
|
||||
|
||||
self.clearTimeout();
|
||||
self.duration = new Date() - start;
|
||||
finished = true;
|
||||
if (!err && self.duration > ms && self._enableTimeouts) {
|
||||
err = self._timeoutError(ms);
|
||||
}
|
||||
fn(err);
|
||||
}
|
||||
|
||||
// for .resetTimeout()
|
||||
this.callback = done;
|
||||
|
||||
// explicit async with `done` argument
|
||||
if (this.async) {
|
||||
this.resetTimeout();
|
||||
|
||||
// allows skip() to be used in an explicit async context
|
||||
this.skip = function asyncSkip() {
|
||||
done(new Pending('async skip call'));
|
||||
// halt execution. the Runnable will be marked pending
|
||||
// by the previous call, and the uncaught handler will ignore
|
||||
// the failure.
|
||||
throw new Pending('async skip; aborting execution');
|
||||
};
|
||||
|
||||
if (this.allowUncaught) {
|
||||
return callFnAsync(this.fn);
|
||||
}
|
||||
try {
|
||||
callFnAsync(this.fn);
|
||||
} catch (err) {
|
||||
emitted = true;
|
||||
done(Runnable.toValueOrError(err));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.allowUncaught) {
|
||||
if (this.isPending()) {
|
||||
done();
|
||||
} else {
|
||||
callFn(this.fn);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// sync or promise-returning
|
||||
try {
|
||||
if (this.isPending()) {
|
||||
done();
|
||||
} else {
|
||||
callFn(this.fn);
|
||||
}
|
||||
} catch (err) {
|
||||
emitted = true;
|
||||
done(Runnable.toValueOrError(err));
|
||||
}
|
||||
|
||||
function callFn(fn) {
|
||||
var result = fn.call(ctx);
|
||||
if (result && typeof result.then === 'function') {
|
||||
self.resetTimeout();
|
||||
result.then(
|
||||
function() {
|
||||
done();
|
||||
// Return null so libraries like bluebird do not warn about
|
||||
// subsequently constructed Promises.
|
||||
return null;
|
||||
},
|
||||
function(reason) {
|
||||
done(reason || new Error('Promise rejected with no or falsy reason'));
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if (self.asyncOnly) {
|
||||
return done(
|
||||
new Error(
|
||||
'--async-only option in use without declaring `done()` or returning a promise'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
function callFnAsync(fn) {
|
||||
var result = fn.call(ctx, function(err) {
|
||||
if (err instanceof Error || toString.call(err) === '[object Error]') {
|
||||
return done(err);
|
||||
}
|
||||
if (err) {
|
||||
if (Object.prototype.toString.call(err) === '[object Object]') {
|
||||
return done(
|
||||
new Error('done() invoked with non-Error: ' + JSON.stringify(err))
|
||||
);
|
||||
}
|
||||
return done(new Error('done() invoked with non-Error: ' + err));
|
||||
}
|
||||
if (result && utils.isPromise(result)) {
|
||||
return done(
|
||||
new Error(
|
||||
'Resolution method is overspecified. Specify a callback *or* return a Promise; not both.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiates a "timeout" error
|
||||
*
|
||||
* @param {number} ms - Timeout (in milliseconds)
|
||||
* @returns {Error} a "timeout" error
|
||||
* @private
|
||||
*/
|
||||
Runnable.prototype._timeoutError = function(ms) {
|
||||
var msg =
|
||||
'Timeout of ' +
|
||||
ms +
|
||||
'ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.';
|
||||
if (this.file) {
|
||||
msg += ' (' + this.file + ')';
|
||||
}
|
||||
return new Error(msg);
|
||||
};
|
||||
|
||||
var constants = utils.defineConstants(
|
||||
/**
|
||||
* {@link Runnable}-related constants.
|
||||
* @public
|
||||
* @memberof Runnable
|
||||
* @readonly
|
||||
* @static
|
||||
* @alias constants
|
||||
* @enum {string}
|
||||
*/
|
||||
{
|
||||
/**
|
||||
* Value of `state` prop when a `Runnable` has failed
|
||||
*/
|
||||
STATE_FAILED: 'failed',
|
||||
/**
|
||||
* Value of `state` prop when a `Runnable` has passed
|
||||
*/
|
||||
STATE_PASSED: 'passed'
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Given `value`, return identity if truthy, otherwise create an "invalid exception" error and return that.
|
||||
* @param {*} [value] - Value to return, if present
|
||||
* @returns {*|Error} `value`, otherwise an `Error`
|
||||
* @private
|
||||
*/
|
||||
Runnable.toValueOrError = function(value) {
|
||||
return (
|
||||
value ||
|
||||
createInvalidExceptionError(
|
||||
'Runnable failed with falsy or undefined exception. Please throw an Error instead.',
|
||||
value
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
Runnable.constants = constants;
|
Reference in New Issue
Block a user