Skip to content

Commit

Permalink
wip; simplify randomization API; fix some bad merging
Browse files Browse the repository at this point in the history
- also linting
  • Loading branch information
boneskull committed Sep 19, 2016
1 parent 2fe3fe4 commit ff75d4b
Show file tree
Hide file tree
Showing 13 changed files with 1,691 additions and 2,781 deletions.
3 changes: 2 additions & 1 deletion bin/_mocha
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ program
.option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options')
.option('-R, --reporter <name>', 'specify the reporter to use', 'spec')
.option('-S, --sort', 'sort test files')
.option('-a, --randomize <mode>[:seed]', "randomize tests")
.option('-b, --bail', 'bail after first test failure')
.option('-d, --debug', "enable node's debugger, synonym for node --debug")
.option('-g, --grep <pattern>', 'only run tests matching <pattern>')
Expand Down Expand Up @@ -107,6 +106,8 @@ program
.option('--use_strict', 'enforce strict mode')
.option('--watch-extensions <ext>,...', 'additional extensions to monitor with --watch', list, [])
.option('--delay', 'wait for async suite definition');
.option('--generate-seed', 'generate a random seed and exit')
.option('--random [seed]', 'randomize order of tests (with optional random seed)', false);

program._name = 'mocha';

Expand Down
27 changes: 12 additions & 15 deletions lib/interfaces/bdd.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,36 +59,33 @@ module.exports = function(suite) {
};

/**
* Do not randomize.
* Run tests "in order"; do not randomize.
*/
context.describe.noRandom = function func(title, fn) {
context.describe.inOrder = function func(title, fn) {
var suite = context.describe(title, fn);
suite.enableRandomize(false);
suite.randomized(false);
return suite;
};
// these three are disabled anyway, so don't actually implement noRandom
context.describe.noRandom.skip =
context.describe.skip.noRandom =
context.xdescribe.noRandom =
context.describe.skip;
// these three are disabled anyway, so don't actually implement inOrder
context.describe.inOrder.skip = context.describe.skip.inOrder
= context.xdescribe.inOrder = context.describe.skip;

/**
* Exclusive suite.
*/

context.describe.only = function(title, fn) {
return common.suite.only({
title: title,
file: file,
fn: fn
});
};
context.describe.only.noRandom =
context.describe.noRandom.only = function(title, fn){
var suite = context.describe.noRandom(title, fn);
mocha.grep(suite.fullTitle());
return suite;
};
context.describe.only.inOrder = context.describe.inOrder.only
= function(title, fn) {
var suite = context.describe.inOrder(title, fn);
mocha.grep(suite.fullTitle());
return suite;
};

/**
* Describe a specification or test-case
Expand Down
11 changes: 8 additions & 3 deletions lib/interfaces/qunit.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ module.exports = function(suite) {
/**
* Do not randomize.
*/
context.suite.noRandom = function(title) {
context.suite.inOrder = function(title) {
var suite = common.suite.create({
title: title,
file: file,
Expand All @@ -66,8 +66,8 @@ module.exports = function(suite) {
return suite;
};

context.suite.only.noRandom =
context.suite.noRandom.only = function(title) {
context.suite.only.inOrder =
context.suite.inOrder.only = function(title) {
var suite = common.suite.only({
title: title,
file: file,
Expand All @@ -89,6 +89,11 @@ module.exports = function(suite) {
file: file
});
};
context.suite.only.inOrder = context.suite.inOrder.only
= function(title, fn) {
var suite = context.suite.inOrder(title, fn);
mocha.grep(suite.fullTitle());
};

/**
* Describe a specification or test-case
Expand Down
16 changes: 8 additions & 8 deletions lib/interfaces/tdd.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ module.exports = function(suite) {
/**
* Do not randomize.
*/
context.suite.noRandom = function(title, fn) {
context.suite.inOrder = function(title, fn) {
var suite = context.suite(title, fn);
suite.enableRandomize(false);
suite.randomized(false);
return suite;
};
context.suite.noRandom.skip = context.suite.skip;
context.suite.inOrder.skip = context.suite.skip;

/**
* Exclusive test-case.
Expand All @@ -84,11 +84,11 @@ module.exports = function(suite) {
fn: fn
});
};
context.suite.only.noRandom =
context.suite.noRandom.only = function(title, fn){
var suite = context.suite.noRandom(title, fn);
mocha.grep(suite.fullTitle());
};
context.suite.only.inOrder = context.suite.inOrder.only
= function(title, fn) {
var suite = context.suite.inOrder(title, fn);
mocha.grep(suite.fullTitle());
};

/**
* Describe a specification or test-case with the given `title` and
Expand Down
75 changes: 54 additions & 21 deletions lib/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ var escapeRe = require('escape-string-regexp');
var path = require('path');
var reporters = require('./reporters');
var utils = require('./utils');
var Random = require('random-js');
var MAX_SAFE_INTEGER = 0x1fffffffffffff;

/**
* Expose `Mocha`.
Expand Down Expand Up @@ -68,8 +70,7 @@ function image(name) {
* - `ignoreLeaks` ignore global leaks
* - `fullTrace` display the full stack-trace on failing
* - `grep` string or regexp to filter tests with
* - `randomMode` mode for randomization of tests
* - `randomSeed` seed for randomization of tests
* - `random` for randomization of tests w/ optional seed
*
* @param {Object} options
* @api public
Expand Down Expand Up @@ -101,6 +102,9 @@ function Mocha(options) {
if (options.slow) {
this.slow(options.slow);
}
if (options.random) {
this.randomize(options.random);
}
}

/**
Expand Down Expand Up @@ -290,42 +294,71 @@ Mocha.prototype.invert = function() {
};

/**
* Apply randomization to the tests
* Ignore global leaks.
*
* @param {String} mode
* @param seed
* @param {Boolean} ignore
* @return {Mocha}
* @api public
* @param {boolean} ignore
* @return {Mocha}
*/
Mocha.prototype.ignoreLeaks = function(ignore) {
if (mode !== 'tests' /* && mode !== 'suites' && mode !== 'both' */) {
throw new Error('unrecognized randomization mode "' + mode + '"');
}
this.options.randomizeMode = mode;
this.options.ignoreLeaks = Boolean(ignore);
return this;
};

/**
* Apply randomization to the tests
* Enable or disable randomization of test execution order within suites.
*
* @param {String} mode
* @param seed
* @param {(boolean|number|string)} [seed] Optional random seed. Seed must be
* a 32-bit unsigned integer, or a string which can convert to one. If
* `true`, a seed will be created. If `false`, randomization will be
* globally disabled (if it was enabled previously).
* @return {Mocha}
* @api public
*/
Mocha.prototype.randomize = function randomize(seed) {
if (seed !== false) {
if (!arguments.length || seed === true) {
// generate a number for seeding
seed = Mocha.seed();
} else if (!(typeof seed === 'number' && seed > 0
&& seed <= MAX_SAFE_INTEGER && seed % 1 === 0)) {
// this would be a hexadecimal number represented as a string
if (/^(0x|0X)?[a-fA-F0-9]{1,14}$'/.test(seed)) {
seed = parseInt(seed, 16);
} else {
// a decimal number as string, presumably
seed = parseInt(seed, 10);
if (isNaN(seed)) {
throw new Error('Invalid random seed. Expected unsigned 32-bit '
+ 'integer; got: "' + seed + '"');
}
}
}

Mocha.prototype.randomize = function(mode, seed){
if (mode !== 'tests' /* && mode !== 'suites' && mode !== 'both' */) {
throw new Error('unrecognized randomization mode "' + mode + '"');
var engine = Random.engines.mt19937().seed(seed);
this.options.randomConfig = {
shuffleTests: function shuffleTests(tests) {
Random.shuffle(tests, engine);
return tests;
},
seed: seed,
hex: '0x' + Number(seed).toString(16) // for display
};
} else {
delete this.options.randomConfig;
}
this.options.randomizeMode = mode;
this.options.randomizeSeed = seed != null ? seed : Math.random();
return this;
};

/**
* Generate a random seed.
* @returns {number} Unsigned 32-bit integer
*/
Mocha.seed = function seed() {
return Random.integer(0, MAX_SAFE_INTEGER)(Random.engines.mt19937()
.autoSeed());
};

/**
* Enable global leak checking.
*
Expand Down Expand Up @@ -531,8 +564,8 @@ Mocha.prototype.run = function(fn) {
if (options.useColors !== undefined) {
exports.reporters.Base.useColors = options.useColors;
}
if (options.randomizeMode !== undefined) {
runner.randomize(options.randomizeMode, options.randomizeSeed);
if (options.randomConfig) {
runner.randomConfig = options.randomConfig;
}

exports.reporters.Base.inlineDiffs = options.useInlineDiffs;
Expand Down
26 changes: 10 additions & 16 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ function Runner(suite, delay) {
this._defaultGrep = /.*/;
this.grep(this._defaultGrep);
this.globals(this.globalProps().concat(extraGlobals()));
this.random = false;
}

/**
Expand Down Expand Up @@ -213,22 +214,16 @@ Runner.prototype.checkGlobals = function(test) {
};

/**
* Randomize tests in a suite.
*
* @param seed
* @return {Runner} for chaining
* @api public
* Get an array of a Suite's Tests, potentially in random order.
* @returns {Array.<Test>} Array of tests (a copy)
* @api private
*/

Runner.prototype.randomize = function(mode, seed){
debug('randomize mode: "' + mode + '"; seed: ' + seed);
if (mode !== 'tests') return this;
this._randomize = function(suite){
debug('randomize: ', suite.title, suite._enableRandomize);
if (!suite._enableRandomize) return suite.tests;
return randomize(suite.tests, seed);
Runner.prototype.suiteTests = function suiteTests(suite) {
var tests = suite.tests.slice();
if (this.randomConfig && suite.randomized()) {
return this.randomConfig.shuffleTests(tests);
}
return this;
return tests;
};

/**
Expand Down Expand Up @@ -466,8 +461,7 @@ Runner.prototype.runTest = function(fn) {
*/
Runner.prototype.runTests = function(suite, fn) {
var self = this;
var tests = suite.tests.slice();
if (self._randomize) self._randomize(self.suite);
var tests = self.suiteTests(suite);
var test;

function hookErr(_, errSuite, after) {
Expand Down
33 changes: 17 additions & 16 deletions lib/suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function Suite(title, parentContext) {
this._slow = 75;
this._bail = false;
this._retries = -1;
this._enableRandomize = false;
this._deterministic = false;
this._onlyTests = [];
this._onlySuites = [];
this.delayed = false;
Expand All @@ -87,7 +87,7 @@ Suite.prototype.clone = function() {
suite.enableTimeouts(this.enableTimeouts());
suite.slow(this.slow());
suite.bail(this.bail());
suite.enableRandomize(this.enableRandomize());
suite.randomized(this.randomized());
return suite;
};

Expand Down Expand Up @@ -190,20 +190,21 @@ Suite.prototype.isPending = function() {
};

/**
* Set randomization `enabled`.
*
* Do NOT use `this.enableRandomize()` in your test; use `describe.noRandom`
* instead.
*
* @param {Boolean} enabled
* @return {Suite|Boolean} self or enabled
* @api private
*/

Suite.prototype.enableRandomize = function(enabled){
if (arguments.length === 0) return this._enableRandomize;
debug('enableRandomize %s', enabled);
this._enableRandomize = enabled;
* If randomization is enabled, calling this with a falsy value
* will cause the Suite's tests to NOT be randomized.
*
* This cannot be used in the context of a test, since it will already be
* randomized (or not) by then.
*
* @param {boolean} enabled
* @return {Suite|Boolean} self or enabled
* @api private
*/
Suite.prototype.randomized = function randomized(enabled) {
if (!arguments.length) {
return this._randomized;
}
this._randomized = Boolean(enabled);
return this;
};

Expand Down
Loading

0 comments on commit ff75d4b

Please sign in to comment.