Allow tests to run in random order
This commit is contained in:
@@ -20,6 +20,8 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
var currentlyExecutingSuites = [];
|
||||
var currentDeclarationSuite = null;
|
||||
var throwOnExpectationFailure = false;
|
||||
var random = false;
|
||||
var seed = null;
|
||||
|
||||
var currentSuite = function() {
|
||||
return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
|
||||
@@ -169,6 +171,21 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
return throwOnExpectationFailure;
|
||||
};
|
||||
|
||||
this.randomizeTests = function(value) {
|
||||
random = !!value;
|
||||
};
|
||||
|
||||
this.randomTests = function() {
|
||||
return random;
|
||||
};
|
||||
|
||||
this.seed = function(value) {
|
||||
if (value) {
|
||||
seed = value;
|
||||
}
|
||||
return seed;
|
||||
};
|
||||
|
||||
var queueRunnerFactory = function(options) {
|
||||
options.catchException = catchException;
|
||||
options.clearStack = options.clearStack || clearStack;
|
||||
@@ -200,6 +217,12 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
runnablesToRun = [topSuite.id];
|
||||
}
|
||||
}
|
||||
|
||||
var order = new j$.Order({
|
||||
random: random,
|
||||
seed: seed
|
||||
});
|
||||
|
||||
var processor = new j$.TreeProcessor({
|
||||
tree: topSuite,
|
||||
runnableIds: runnablesToRun,
|
||||
@@ -215,6 +238,9 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
}
|
||||
currentlyExecutingSuites.pop();
|
||||
reporter.suiteDone(result);
|
||||
},
|
||||
orderChildren: function(node) {
|
||||
return order.sort(node.children);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -226,7 +252,11 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
totalSpecsDefined: totalSpecsDefined
|
||||
});
|
||||
|
||||
processor.execute(reporter.jasmineDone);
|
||||
processor.execute(function() {
|
||||
reporter.jasmineDone({
|
||||
order: order
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.addReporter = function(reporterToAdd) {
|
||||
|
||||
46
src/core/Order.js
Normal file
46
src/core/Order.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/*jshint bitwise: false*/
|
||||
|
||||
getJasmineRequireObj().Order = function() {
|
||||
function Order(options) {
|
||||
this.random = 'random' in options ? options.random : true;
|
||||
var seed = this.seed = options.seed || generateSeed();
|
||||
this.sort = this.random ? randomOrder : naturalOrder;
|
||||
|
||||
function naturalOrder(items) {
|
||||
return items;
|
||||
}
|
||||
|
||||
function randomOrder(items) {
|
||||
var copy = items.slice();
|
||||
copy.sort(function(a, b) {
|
||||
return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id);
|
||||
});
|
||||
return copy;
|
||||
}
|
||||
|
||||
function generateSeed() {
|
||||
return String(Math.random()).slice(-5);
|
||||
}
|
||||
|
||||
// Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function
|
||||
// used to get a different output when the key changes slighly.
|
||||
// We use your return to sort the children randomly in a consistent way when
|
||||
// used in conjunction with a seed
|
||||
|
||||
function jenkinsHash(key) {
|
||||
var hash, i;
|
||||
for(hash = i = 0; i < key.length; ++i) {
|
||||
hash += key.charCodeAt(i);
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
}
|
||||
hash += (hash << 3);
|
||||
hash ^= (hash >> 11);
|
||||
hash += (hash << 15);
|
||||
return hash;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Order;
|
||||
};
|
||||
@@ -5,6 +5,7 @@ getJasmineRequireObj().TreeProcessor = function() {
|
||||
queueRunnerFactory = attrs.queueRunnerFactory,
|
||||
nodeStart = attrs.nodeStart || function() {},
|
||||
nodeComplete = attrs.nodeComplete || function() {},
|
||||
orderChildren = attrs.orderChildren || function(node) { return node.children; },
|
||||
stats = { valid: true },
|
||||
processed = false,
|
||||
defaultMin = Infinity,
|
||||
@@ -68,8 +69,10 @@ getJasmineRequireObj().TreeProcessor = function() {
|
||||
} else {
|
||||
var hasExecutableChild = false;
|
||||
|
||||
for (var i = 0; i < node.children.length; i++) {
|
||||
var child = node.children[i];
|
||||
var orderedChildren = orderChildren(node);
|
||||
|
||||
for (var i = 0; i < orderedChildren.length; i++) {
|
||||
var child = orderedChildren[i];
|
||||
|
||||
processNode(child, parentEnabled);
|
||||
|
||||
@@ -86,7 +89,7 @@ getJasmineRequireObj().TreeProcessor = function() {
|
||||
executable: hasExecutableChild
|
||||
};
|
||||
|
||||
segmentChildren(node, stats[node.id], executableIndex);
|
||||
segmentChildren(node, orderedChildren, stats[node.id], executableIndex);
|
||||
|
||||
if (!node.canBeReentered() && stats[node.id].segments.length > 1) {
|
||||
stats = { valid: false };
|
||||
@@ -102,11 +105,11 @@ getJasmineRequireObj().TreeProcessor = function() {
|
||||
return executableIndex === undefined ? defaultMax : executableIndex;
|
||||
}
|
||||
|
||||
function segmentChildren(node, nodeStats, executableIndex) {
|
||||
function segmentChildren(node, orderedChildren, nodeStats, executableIndex) {
|
||||
var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) },
|
||||
result = [currentSegment],
|
||||
lastMax = defaultMax,
|
||||
orderedChildSegments = orderChildSegments(node.children);
|
||||
orderedChildSegments = orderChildSegments(orderedChildren);
|
||||
|
||||
function isSegmentBoundary(minIndex) {
|
||||
return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1;
|
||||
|
||||
@@ -50,6 +50,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) {
|
||||
j$.Timer = jRequire.Timer();
|
||||
j$.TreeProcessor = jRequire.TreeProcessor();
|
||||
j$.version = jRequire.version();
|
||||
j$.Order = jRequire.Order();
|
||||
|
||||
j$.matchers = jRequire.requireMatchers(jRequire, j$);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
createTextNode = options.createTextNode,
|
||||
onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
|
||||
onThrowExpectationsClick = options.onThrowExpectationsClick || function() {},
|
||||
onRandomClick = options.onRandomClick || function() {},
|
||||
addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
|
||||
timer = options.timer || noopTimer,
|
||||
results = [],
|
||||
@@ -117,9 +118,10 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
this.jasmineDone = function() {
|
||||
this.jasmineDone = function(doneResult) {
|
||||
var banner = find('.jasmine-banner');
|
||||
var alert = find('.jasmine-alert');
|
||||
var order = doneResult && doneResult.order;
|
||||
alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
|
||||
|
||||
banner.appendChild(
|
||||
@@ -139,7 +141,14 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
id: 'jasmine-throw-failures',
|
||||
type: 'checkbox'
|
||||
}),
|
||||
createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure'))
|
||||
createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')),
|
||||
createDom('div', { className: 'jasmine-random-order' },
|
||||
createDom('input', {
|
||||
className: 'jasmine-random',
|
||||
id: 'jasmine-random-order',
|
||||
type: 'checkbox'
|
||||
}),
|
||||
createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order'))
|
||||
)
|
||||
));
|
||||
|
||||
@@ -152,6 +161,10 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
throwCheckbox.checked = env.throwingExpectationFailures();
|
||||
throwCheckbox.onclick = onThrowExpectationsClick;
|
||||
|
||||
var randomCheckbox = find('#jasmine-random-order');
|
||||
randomCheckbox.checked = env.randomTests();
|
||||
randomCheckbox.onclick = onRandomClick;
|
||||
|
||||
var optionsMenu = find('.jasmine-run-options'),
|
||||
optionsTrigger = optionsMenu.querySelector('.jasmine-trigger'),
|
||||
optionsPayload = optionsMenu.querySelector('.jasmine-payload'),
|
||||
@@ -185,7 +198,15 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
statusBarMessage += 'No specs found';
|
||||
}
|
||||
|
||||
alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage));
|
||||
var seedBar;
|
||||
if (order && order.random) {
|
||||
seedBar = createDom('span', {class: 'jasmine-seed-bar'},
|
||||
', randomized with seed ',
|
||||
createDom('a', {title: 'randomized with seed ' + order.seed, href: seedHref(order.seed)}, order.seed)
|
||||
);
|
||||
}
|
||||
|
||||
alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar));
|
||||
|
||||
for(i = 0; i < failedSuites.length; i++) {
|
||||
var failedSuite = failedSuites[i];
|
||||
@@ -316,6 +337,10 @@ jasmineRequire.HtmlReporter = function(j$) {
|
||||
return addToExistingQueryString('spec', result.fullName);
|
||||
}
|
||||
|
||||
function seedHref(seed) {
|
||||
return addToExistingQueryString('seed', seed);
|
||||
}
|
||||
|
||||
function defaultQueryString(key, value) {
|
||||
return '?' + key + '=' + value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user