Extracted most suite-running code out of Env

This commit is contained in:
Steve Gravrock
2022-06-12 13:30:43 -07:00
parent d8b65028a1
commit 93c5f654d9
4 changed files with 539 additions and 441 deletions

View File

@@ -91,6 +91,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
); );
j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(j$);
j$.RunableResources = jRequire.RunableResources(j$); j$.RunableResources = jRequire.RunableResources(j$);
j$.Runner = jRequire.Runner(j$);
j$.Spec = jRequire.Spec(j$); j$.Spec = jRequire.Spec(j$);
j$.Spy = jRequire.Spy(j$); j$.Spy = jRequire.Spy(j$);
j$.SpyFactory = jRequire.SpyFactory(j$); j$.SpyFactory = jRequire.SpyFactory(j$);
@@ -791,7 +792,12 @@ getJasmineRequireObj().Spec = function(j$) {
return this.asyncExpectationFactory(actual, this); return this.asyncExpectationFactory(actual, this);
}; };
Spec.prototype.execute = function(queueRunnerFactory, onComplete, excluded, failSpecWithNoExp) { Spec.prototype.execute = function(
queueRunnerFactory,
onComplete,
excluded,
failSpecWithNoExp
) {
const onStart = { const onStart = {
fn: done => { fn: done => {
this.timer.start(); this.timer.start();
@@ -1112,15 +1118,13 @@ getJasmineRequireObj().Env = function(j$) {
); );
const runableResources = new j$.RunableResources(function() { const runableResources = new j$.RunableResources(function() {
const r = currentRunable(); const r = runner.currentRunable();
return r ? r.id : null; return r ? r.id : null;
}); });
let currentSpec = null;
const currentlyExecutingSuites = [];
let hasFailures = false;
let reporter; let reporter;
let topSuite; let topSuite;
let runner;
/** /**
* This represents the available options to configure Jasmine. * This represents the available options to configure Jasmine.
@@ -1222,14 +1226,6 @@ getJasmineRequireObj().Env = function(j$) {
verboseDeprecations: false verboseDeprecations: false
}; };
function currentSuite() {
return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
}
function currentRunable() {
return currentSpec || currentSuite();
}
let globalErrors = null; let globalErrors = null;
function installGlobalErrors() { function installGlobalErrors() {
@@ -1401,7 +1397,7 @@ getJasmineRequireObj().Env = function(j$) {
function routeLateFailure(expectationResult) { function routeLateFailure(expectationResult) {
// Report the result on the nearest ancestor suite that hasn't already // Report the result on the nearest ancestor suite that hasn't already
// been reported done. // been reported done.
for (let r = currentRunable(); r; r = r.parentSuite) { for (let r = runner.currentRunable(); r; r = r.parentSuite) {
if (!r.reportedDone) { if (!r.reportedDone) {
if (r === topSuite) { if (r === topSuite) {
expectationResult.globalErrorType = 'lateError'; expectationResult.globalErrorType = 'lateError';
@@ -1427,7 +1423,7 @@ getJasmineRequireObj().Env = function(j$) {
}); });
function addExpectationResult(passed, result) { function addExpectationResult(passed, result) {
if (currentRunable() !== spec) { if (runner.currentRunable() !== spec) {
recordLateExpectation(spec, runableType, result); recordLateExpectation(spec, runableType, result);
} }
return spec.addExpectationResult(passed, result); return spec.addExpectationResult(passed, result);
@@ -1457,7 +1453,7 @@ getJasmineRequireObj().Env = function(j$) {
* @param {Object} [options] Optional extra options, as described above * @param {Object} [options] Optional extra options, as described above
*/ */
this.deprecated = function(deprecation, options) { this.deprecated = function(deprecation, options) {
const runable = currentRunable() || topSuite; const runable = runner.currentRunable() || topSuite;
deprecator.addDeprecationWarning(runable, deprecation, options); deprecator.addDeprecationWarning(runable, deprecation, options);
}; };
@@ -1487,7 +1483,7 @@ getJasmineRequireObj().Env = function(j$) {
options.onException = options.onException =
options.onException || options.onException ||
function(e) { function(e) {
(currentRunable() || topSuite).handleException(e); (runner.currentRunable() || topSuite).handleException(e);
}; };
options.deprecated = self.deprecated; options.deprecated = self.deprecated;
@@ -1594,6 +1590,17 @@ getJasmineRequireObj().Env = function(j$) {
recordLateError recordLateError
); );
runner = new j$.Runner({
topSuite,
totalSpecsDefined: () => suiteBuilder.totalSpecsDefined,
focusedRunables: () => suiteBuilder.focusedRunables,
runableResources,
reporter,
queueRunnerFactory,
getConfig: () => config,
reportSpecDone
});
/** /**
* Executes the specs. * Executes the specs.
* *
@@ -1626,195 +1633,15 @@ getJasmineRequireObj().Env = function(j$) {
* @return {Promise<JasmineDoneInfo>} * @return {Promise<JasmineDoneInfo>}
*/ */
this.execute = function(runablesToRun, onComplete) { this.execute = function(runablesToRun, onComplete) {
if (this._executedBefore) {
topSuite.reset();
}
this._executedBefore = true;
runableResources.initForRunable(topSuite.id);
installGlobalErrors(); installGlobalErrors();
if (!runablesToRun) { return runner.execute(runablesToRun).then(function(jasmineDoneInfo) {
if (suiteBuilder.focusedRunables.length) { if (onComplete) {
runablesToRun = suiteBuilder.focusedRunables; onComplete();
} else {
runablesToRun = [topSuite.id];
} }
}
const order = new j$.Order({ return jasmineDoneInfo;
random: config.random,
seed: config.seed
}); });
const processor = new j$.TreeProcessor({
tree: topSuite,
runnableIds: runablesToRun,
queueRunnerFactory: queueRunnerFactory,
failSpecWithNoExpectations: config.failSpecWithNoExpectations,
nodeStart: function(suite, next) {
currentlyExecutingSuites.push(suite);
runableResources.initForRunable(suite.id, suite.parentSuite.id);
reporter.suiteStarted(suite.result, next);
suite.startTimer();
},
nodeComplete: function(suite, result, next) {
if (suite !== currentSuite()) {
throw new Error('Tried to complete the wrong suite');
}
runableResources.clearForRunable(suite.id);
currentlyExecutingSuites.pop();
if (result.status === 'failed') {
hasFailures = true;
}
suite.endTimer();
if (suite.hadBeforeAllFailure) {
reportChildrenOfBeforeAllFailure(suite).then(function() {
reportSuiteDone(suite, result, next);
});
} else {
reportSuiteDone(suite, result, next);
}
},
orderChildren: function(node) {
return order.sort(node.children);
},
excludeNode: function(spec) {
return !config.specFilter(spec);
}
});
if (!processor.processTree().valid) {
throw new Error(
'Invalid order: would cause a beforeAll or afterAll to be run multiple times'
);
}
const jasmineTimer = new j$.Timer();
jasmineTimer.start();
return new Promise(function(resolve) {
runAll(function(jasmineDoneInfo) {
if (onComplete) {
onComplete();
}
resolve(jasmineDoneInfo);
});
});
function runAll(done) {
/**
* Information passed to the {@link Reporter#jasmineStarted} event.
* @typedef JasmineStartedInfo
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @since 2.0.0
*/
reporter.jasmineStarted(
{
totalSpecsDefined: suiteBuilder.totalSpecsDefined,
order: order
},
function() {
currentlyExecutingSuites.push(topSuite);
processor.execute(function() {
(async function() {
if (topSuite.hadBeforeAllFailure) {
await reportChildrenOfBeforeAllFailure(topSuite);
}
runableResources.clearForRunable(topSuite.id);
currentlyExecutingSuites.pop();
let overallStatus, incompleteReason;
if (
hasFailures ||
topSuite.result.failedExpectations.length > 0
) {
overallStatus = 'failed';
} else if (suiteBuilder.focusedRunables.length > 0) {
overallStatus = 'incomplete';
incompleteReason = 'fit() or fdescribe() was found';
} else if (suiteBuilder.totalSpecsDefined === 0) {
overallStatus = 'incomplete';
incompleteReason = 'No specs found';
} else {
overallStatus = 'passed';
}
/**
* Information passed to the {@link Reporter#jasmineDone} event.
* @typedef JasmineDoneInfo
* @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'.
* @property {Int} totalTime - The total time (in ms) that it took to execute the suite
* @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
* @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level.
* @since 2.4.0
*/
const jasmineDoneInfo = {
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
order: order,
failedExpectations: topSuite.result.failedExpectations,
deprecationWarnings: topSuite.result.deprecationWarnings
};
topSuite.reportedDone = true;
reporter.jasmineDone(jasmineDoneInfo, function() {
done(jasmineDoneInfo);
});
})();
});
}
);
}
async function reportChildrenOfBeforeAllFailure(suite) {
for (const child of suite.children) {
if (child instanceof j$.Suite) {
await new Promise(function(resolve) {
reporter.suiteStarted(child.result, resolve);
});
await reportChildrenOfBeforeAllFailure(child);
// Marking the suite passed is consistent with how suites that
// contain failed specs but no suite-level failures are reported.
child.result.status = 'passed';
await new Promise(function(resolve) {
reporter.suiteDone(child.result, resolve);
});
} else {
/* a spec */
await new Promise(function(resolve) {
reporter.specStarted(child.result, resolve);
});
child.addExpectationResult(
false,
{
passed: false,
message:
'Not run because a beforeAll function failed. The ' +
'beforeAll failure will be reported on the suite that ' +
'caused it.'
},
true
);
child.result.status = 'failed';
await new Promise(function(resolve) {
reportSpecDone(child, child.result, resolve);
});
}
}
}
}; };
/** /**
@@ -1898,7 +1725,7 @@ getJasmineRequireObj().Env = function(j$) {
}; };
function ensureIsNotNested(method) { function ensureIsNotNested(method) {
const runable = currentRunable(); const runable = runner.currentRunable();
if (runable !== null && runable !== undefined) { if (runable !== null && runable !== undefined) {
throw new Error( throw new Error(
"'" + method + "' should only be used in 'describe' function" "'" + method + "' should only be used in 'describe' function"
@@ -1923,17 +1750,17 @@ getJasmineRequireObj().Env = function(j$) {
function specResultCallback(spec, result, next) { function specResultCallback(spec, result, next) {
runableResources.clearForRunable(spec.id); runableResources.clearForRunable(spec.id);
currentSpec = null; runner.currentSpec = null;
if (result.status === 'failed') { if (result.status === 'failed') {
hasFailures = true; runner.hasFailures = true;
} }
reportSpecDone(spec, result, next); reportSpecDone(spec, result, next);
} }
function specStarted(spec, suite, next) { function specStarted(spec, suite, next) {
currentSpec = spec; runner.currentSpec = spec;
runableResources.initForRunable(spec.id, suite.id); runableResources.initForRunable(spec.id, suite.id);
reporter.specStarted(spec.result, next); reporter.specStarted(spec.result, next);
} }
@@ -1943,11 +1770,6 @@ getJasmineRequireObj().Env = function(j$) {
reporter.specDone(result, next); reporter.specDone(result, next);
} }
function reportSuiteDone(suite, result, next) {
suite.reportedDone = true;
reporter.suiteDone(result, next);
}
this.it = function(description, fn, timeout) { this.it = function(description, fn, timeout) {
ensureIsNotNested('it'); ensureIsNotNested('it');
return suiteBuilder.it(description, fn, timeout).metadata; return suiteBuilder.it(description, fn, timeout).metadata;
@@ -1972,12 +1794,15 @@ getJasmineRequireObj().Env = function(j$) {
* @param {*} value The value of the property * @param {*} value The value of the property
*/ */
this.setSpecProperty = function(key, value) { this.setSpecProperty = function(key, value) {
if (!currentRunable() || currentRunable() == currentSuite()) { if (
!runner.currentRunable() ||
runner.currentRunable() == runner.currentSuite()
) {
throw new Error( throw new Error(
"'setSpecProperty' was used when there was no current spec" "'setSpecProperty' was used when there was no current spec"
); );
} }
currentRunable().setSpecProperty(key, value); runner.currentRunable().setSpecProperty(key, value);
}; };
/** /**
@@ -1989,16 +1814,16 @@ getJasmineRequireObj().Env = function(j$) {
* @param {*} value The value of the property * @param {*} value The value of the property
*/ */
this.setSuiteProperty = function(key, value) { this.setSuiteProperty = function(key, value) {
if (!currentSuite()) { if (!runner.currentSuite()) {
throw new Error( throw new Error(
"'setSuiteProperty' was used when there was no current suite" "'setSuiteProperty' was used when there was no current suite"
); );
} }
currentSuite().setSuiteProperty(key, value); runner.currentSuite().setSuiteProperty(key, value);
}; };
this.debugLog = function(msg) { this.debugLog = function(msg) {
const maybeSpec = currentRunable(); const maybeSpec = runner.currentRunable();
if (!maybeSpec || !maybeSpec.debugLog) { if (!maybeSpec || !maybeSpec.debugLog) {
throw new Error("'debugLog' was called when there was no current spec"); throw new Error("'debugLog' was called when there was no current spec");
@@ -2008,23 +1833,23 @@ getJasmineRequireObj().Env = function(j$) {
}; };
this.expect = function(actual) { this.expect = function(actual) {
if (!currentRunable()) { if (!runner.currentRunable()) {
throw new Error( throw new Error(
"'expect' was used when there was no current spec, this could be because an asynchronous test timed out" "'expect' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
} }
return currentRunable().expect(actual); return runner.currentRunable().expect(actual);
}; };
this.expectAsync = function(actual) { this.expectAsync = function(actual) {
if (!currentRunable()) { if (!runner.currentRunable()) {
throw new Error( throw new Error(
"'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out" "'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
} }
return currentRunable().expectAsync(actual); return runner.currentRunable().expectAsync(actual);
}; };
this.beforeEach = function(beforeEachFunction, timeout) { this.beforeEach = function(beforeEachFunction, timeout) {
@@ -2056,7 +1881,7 @@ getJasmineRequireObj().Env = function(j$) {
}; };
this.fail = function(error) { this.fail = function(error) {
if (!currentRunable()) { if (!runner.currentRunable()) {
throw new Error( throw new Error(
"'fail' was used when there was no current spec, this could be because an asynchronous test timed out" "'fail' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
@@ -2076,7 +1901,7 @@ getJasmineRequireObj().Env = function(j$) {
} }
} }
currentRunable().addExpectationResult(false, { runner.currentRunable().addExpectationResult(false, {
matcherName: '', matcherName: '',
passed: false, passed: false,
expected: '', expected: '',
@@ -8422,6 +8247,233 @@ getJasmineRequireObj().RunableResources = function(j$) {
return RunableResources; return RunableResources;
}; };
getJasmineRequireObj().Runner = function(j$) {
class Runner {
constructor(options) {
this.topSuite_ = options.topSuite;
this.totalSpecsDefined_ = options.totalSpecsDefined;
this.focusedRunables_ = options.focusedRunables;
this.runableResources_ = options.runableResources;
this.queueRunnerFactory_ = options.queueRunnerFactory;
this.reporter_ = options.reporter;
this.getConfig_ = options.getConfig;
this.reportSpecDone_ = options.reportSpecDone;
this.hasFailures = false;
this.executedBefore_ = false;
this.currentlyExecutingSuites_ = [];
this.currentSpec = null;
}
currentRunable() {
return this.currentSpec || this.currentSuite();
}
currentSuite() {
return this.currentlyExecutingSuites_[
this.currentlyExecutingSuites_.length - 1
];
}
// Although execute returns a promise, it isn't async for backwards
// compatibility: The "Invalid order" exception needs to be propagated
// synchronously from Env#execute.
// TODO: make this and Env#execute async in the next major release
execute(runablesToRun) {
if (this.executedBefore_) {
this.topSuite_.reset();
}
this.executedBefore_ = true;
this.hasFailures = false;
const totalSpecsDefined = this.totalSpecsDefined_();
const focusedRunables = this.focusedRunables_();
const config = this.getConfig_();
if (!runablesToRun) {
if (focusedRunables.length) {
runablesToRun = focusedRunables;
} else {
runablesToRun = [this.topSuite_.id];
}
}
const order = new j$.Order({
random: config.random,
seed: config.seed
});
const processor = new j$.TreeProcessor({
tree: this.topSuite_,
runnableIds: runablesToRun,
queueRunnerFactory: this.queueRunnerFactory_,
failSpecWithNoExpectations: config.failSpecWithNoExpectations,
nodeStart: (suite, next) => {
this.currentlyExecutingSuites_.push(suite);
this.runableResources_.initForRunable(suite.id, suite.parentSuite.id);
this.reporter_.suiteStarted(suite.result, next);
suite.startTimer();
},
nodeComplete: (suite, result, next) => {
if (suite !== this.currentSuite()) {
throw new Error('Tried to complete the wrong suite');
}
this.runableResources_.clearForRunable(suite.id);
this.currentlyExecutingSuites_.pop();
if (result.status === 'failed') {
this.hasFailures = true;
}
suite.endTimer();
if (suite.hadBeforeAllFailure) {
this.reportChildrenOfBeforeAllFailure_(suite).then(() => {
this.reportSuiteDone_(suite, result, next);
});
} else {
this.reportSuiteDone_(suite, result, next);
}
},
orderChildren: function(node) {
return order.sort(node.children);
},
excludeNode: function(spec) {
return !config.specFilter(spec);
}
});
if (!processor.processTree().valid) {
throw new Error(
'Invalid order: would cause a beforeAll or afterAll to be run multiple times'
);
}
this.runableResources_.initForRunable(this.topSuite_.id);
const jasmineTimer = new j$.Timer();
jasmineTimer.start();
return new Promise(resolve => {
/**
* Information passed to the {@link Reporter#jasmineStarted} event.
* @typedef JasmineStartedInfo
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @since 2.0.0
*/
this.reporter_.jasmineStarted(
{
totalSpecsDefined,
order: order
},
() => {
this.currentlyExecutingSuites_.push(this.topSuite_);
processor.execute(() => {
(async () => {
if (this.topSuite_.hadBeforeAllFailure) {
await this.reportChildrenOfBeforeAllFailure_(this.topSuite_);
}
this.runableResources_.clearForRunable(this.topSuite_.id);
this.currentlyExecutingSuites_.pop();
let overallStatus, incompleteReason;
if (
this.hasFailures ||
this.topSuite_.result.failedExpectations.length > 0
) {
overallStatus = 'failed';
} else if (focusedRunables.length > 0) {
overallStatus = 'incomplete';
incompleteReason = 'fit() or fdescribe() was found';
} else if (totalSpecsDefined === 0) {
overallStatus = 'incomplete';
incompleteReason = 'No specs found';
} else {
overallStatus = 'passed';
}
/**
* Information passed to the {@link Reporter#jasmineDone} event.
* @typedef JasmineDoneInfo
* @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'.
* @property {Int} totalTime - The total time (in ms) that it took to execute the suite
* @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
* @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level.
* @since 2.4.0
*/
const jasmineDoneInfo = {
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
order: order,
failedExpectations: this.topSuite_.result.failedExpectations,
deprecationWarnings: this.topSuite_.result.deprecationWarnings
};
this.topSuite_.reportedDone = true;
this.reporter_.jasmineDone(jasmineDoneInfo, function() {
resolve(jasmineDoneInfo);
});
})();
});
}
);
});
}
reportSuiteDone_(suite, result, next) {
suite.reportedDone = true;
this.reporter_.suiteDone(result, next);
}
async reportChildrenOfBeforeAllFailure_(suite) {
for (const child of suite.children) {
if (child instanceof j$.Suite) {
await new Promise(resolve => {
this.reporter_.suiteStarted(child.result, resolve);
});
await this.reportChildrenOfBeforeAllFailure_(child);
// Marking the suite passed is consistent with how suites that
// contain failed specs but no suite-level failures are reported.
child.result.status = 'passed';
await new Promise(resolve => {
this.reporter_.suiteDone(child.result, resolve);
});
} else {
/* a spec */
await new Promise(resolve => {
this.reporter_.specStarted(child.result, resolve);
});
child.addExpectationResult(
false,
{
passed: false,
message:
'Not run because a beforeAll function failed. The ' +
'beforeAll failure will be reported on the suite that ' +
'caused it.'
},
true
);
child.result.status = 'failed';
await new Promise(resolve => {
this.reportSpecDone_(child, child.result, resolve);
});
}
}
}
}
return Runner;
};
getJasmineRequireObj().SkipAfterBeforeAllErrorPolicy = function(j$) { getJasmineRequireObj().SkipAfterBeforeAllErrorPolicy = function(j$) {
function SkipAfterBeforeAllErrorPolicy(queueableFns) { function SkipAfterBeforeAllErrorPolicy(queueableFns) {
this.queueableFns_ = queueableFns; this.queueableFns_ = queueableFns;

View File

@@ -25,15 +25,13 @@ getJasmineRequireObj().Env = function(j$) {
); );
const runableResources = new j$.RunableResources(function() { const runableResources = new j$.RunableResources(function() {
const r = currentRunable(); const r = runner.currentRunable();
return r ? r.id : null; return r ? r.id : null;
}); });
let currentSpec = null;
const currentlyExecutingSuites = [];
let hasFailures = false;
let reporter; let reporter;
let topSuite; let topSuite;
let runner;
/** /**
* This represents the available options to configure Jasmine. * This represents the available options to configure Jasmine.
@@ -135,14 +133,6 @@ getJasmineRequireObj().Env = function(j$) {
verboseDeprecations: false verboseDeprecations: false
}; };
function currentSuite() {
return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
}
function currentRunable() {
return currentSpec || currentSuite();
}
let globalErrors = null; let globalErrors = null;
function installGlobalErrors() { function installGlobalErrors() {
@@ -314,7 +304,7 @@ getJasmineRequireObj().Env = function(j$) {
function routeLateFailure(expectationResult) { function routeLateFailure(expectationResult) {
// Report the result on the nearest ancestor suite that hasn't already // Report the result on the nearest ancestor suite that hasn't already
// been reported done. // been reported done.
for (let r = currentRunable(); r; r = r.parentSuite) { for (let r = runner.currentRunable(); r; r = r.parentSuite) {
if (!r.reportedDone) { if (!r.reportedDone) {
if (r === topSuite) { if (r === topSuite) {
expectationResult.globalErrorType = 'lateError'; expectationResult.globalErrorType = 'lateError';
@@ -340,7 +330,7 @@ getJasmineRequireObj().Env = function(j$) {
}); });
function addExpectationResult(passed, result) { function addExpectationResult(passed, result) {
if (currentRunable() !== spec) { if (runner.currentRunable() !== spec) {
recordLateExpectation(spec, runableType, result); recordLateExpectation(spec, runableType, result);
} }
return spec.addExpectationResult(passed, result); return spec.addExpectationResult(passed, result);
@@ -370,7 +360,7 @@ getJasmineRequireObj().Env = function(j$) {
* @param {Object} [options] Optional extra options, as described above * @param {Object} [options] Optional extra options, as described above
*/ */
this.deprecated = function(deprecation, options) { this.deprecated = function(deprecation, options) {
const runable = currentRunable() || topSuite; const runable = runner.currentRunable() || topSuite;
deprecator.addDeprecationWarning(runable, deprecation, options); deprecator.addDeprecationWarning(runable, deprecation, options);
}; };
@@ -400,7 +390,7 @@ getJasmineRequireObj().Env = function(j$) {
options.onException = options.onException =
options.onException || options.onException ||
function(e) { function(e) {
(currentRunable() || topSuite).handleException(e); (runner.currentRunable() || topSuite).handleException(e);
}; };
options.deprecated = self.deprecated; options.deprecated = self.deprecated;
@@ -507,6 +497,17 @@ getJasmineRequireObj().Env = function(j$) {
recordLateError recordLateError
); );
runner = new j$.Runner({
topSuite,
totalSpecsDefined: () => suiteBuilder.totalSpecsDefined,
focusedRunables: () => suiteBuilder.focusedRunables,
runableResources,
reporter,
queueRunnerFactory,
getConfig: () => config,
reportSpecDone
});
/** /**
* Executes the specs. * Executes the specs.
* *
@@ -539,195 +540,15 @@ getJasmineRequireObj().Env = function(j$) {
* @return {Promise<JasmineDoneInfo>} * @return {Promise<JasmineDoneInfo>}
*/ */
this.execute = function(runablesToRun, onComplete) { this.execute = function(runablesToRun, onComplete) {
if (this._executedBefore) {
topSuite.reset();
}
this._executedBefore = true;
runableResources.initForRunable(topSuite.id);
installGlobalErrors(); installGlobalErrors();
if (!runablesToRun) { return runner.execute(runablesToRun).then(function(jasmineDoneInfo) {
if (suiteBuilder.focusedRunables.length) { if (onComplete) {
runablesToRun = suiteBuilder.focusedRunables; onComplete();
} else {
runablesToRun = [topSuite.id];
} }
}
const order = new j$.Order({ return jasmineDoneInfo;
random: config.random,
seed: config.seed
}); });
const processor = new j$.TreeProcessor({
tree: topSuite,
runnableIds: runablesToRun,
queueRunnerFactory: queueRunnerFactory,
failSpecWithNoExpectations: config.failSpecWithNoExpectations,
nodeStart: function(suite, next) {
currentlyExecutingSuites.push(suite);
runableResources.initForRunable(suite.id, suite.parentSuite.id);
reporter.suiteStarted(suite.result, next);
suite.startTimer();
},
nodeComplete: function(suite, result, next) {
if (suite !== currentSuite()) {
throw new Error('Tried to complete the wrong suite');
}
runableResources.clearForRunable(suite.id);
currentlyExecutingSuites.pop();
if (result.status === 'failed') {
hasFailures = true;
}
suite.endTimer();
if (suite.hadBeforeAllFailure) {
reportChildrenOfBeforeAllFailure(suite).then(function() {
reportSuiteDone(suite, result, next);
});
} else {
reportSuiteDone(suite, result, next);
}
},
orderChildren: function(node) {
return order.sort(node.children);
},
excludeNode: function(spec) {
return !config.specFilter(spec);
}
});
if (!processor.processTree().valid) {
throw new Error(
'Invalid order: would cause a beforeAll or afterAll to be run multiple times'
);
}
const jasmineTimer = new j$.Timer();
jasmineTimer.start();
return new Promise(function(resolve) {
runAll(function(jasmineDoneInfo) {
if (onComplete) {
onComplete();
}
resolve(jasmineDoneInfo);
});
});
function runAll(done) {
/**
* Information passed to the {@link Reporter#jasmineStarted} event.
* @typedef JasmineStartedInfo
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @since 2.0.0
*/
reporter.jasmineStarted(
{
totalSpecsDefined: suiteBuilder.totalSpecsDefined,
order: order
},
function() {
currentlyExecutingSuites.push(topSuite);
processor.execute(function() {
(async function() {
if (topSuite.hadBeforeAllFailure) {
await reportChildrenOfBeforeAllFailure(topSuite);
}
runableResources.clearForRunable(topSuite.id);
currentlyExecutingSuites.pop();
let overallStatus, incompleteReason;
if (
hasFailures ||
topSuite.result.failedExpectations.length > 0
) {
overallStatus = 'failed';
} else if (suiteBuilder.focusedRunables.length > 0) {
overallStatus = 'incomplete';
incompleteReason = 'fit() or fdescribe() was found';
} else if (suiteBuilder.totalSpecsDefined === 0) {
overallStatus = 'incomplete';
incompleteReason = 'No specs found';
} else {
overallStatus = 'passed';
}
/**
* Information passed to the {@link Reporter#jasmineDone} event.
* @typedef JasmineDoneInfo
* @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'.
* @property {Int} totalTime - The total time (in ms) that it took to execute the suite
* @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
* @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level.
* @since 2.4.0
*/
const jasmineDoneInfo = {
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
order: order,
failedExpectations: topSuite.result.failedExpectations,
deprecationWarnings: topSuite.result.deprecationWarnings
};
topSuite.reportedDone = true;
reporter.jasmineDone(jasmineDoneInfo, function() {
done(jasmineDoneInfo);
});
})();
});
}
);
}
async function reportChildrenOfBeforeAllFailure(suite) {
for (const child of suite.children) {
if (child instanceof j$.Suite) {
await new Promise(function(resolve) {
reporter.suiteStarted(child.result, resolve);
});
await reportChildrenOfBeforeAllFailure(child);
// Marking the suite passed is consistent with how suites that
// contain failed specs but no suite-level failures are reported.
child.result.status = 'passed';
await new Promise(function(resolve) {
reporter.suiteDone(child.result, resolve);
});
} else {
/* a spec */
await new Promise(function(resolve) {
reporter.specStarted(child.result, resolve);
});
child.addExpectationResult(
false,
{
passed: false,
message:
'Not run because a beforeAll function failed. The ' +
'beforeAll failure will be reported on the suite that ' +
'caused it.'
},
true
);
child.result.status = 'failed';
await new Promise(function(resolve) {
reportSpecDone(child, child.result, resolve);
});
}
}
}
}; };
/** /**
@@ -811,7 +632,7 @@ getJasmineRequireObj().Env = function(j$) {
}; };
function ensureIsNotNested(method) { function ensureIsNotNested(method) {
const runable = currentRunable(); const runable = runner.currentRunable();
if (runable !== null && runable !== undefined) { if (runable !== null && runable !== undefined) {
throw new Error( throw new Error(
"'" + method + "' should only be used in 'describe' function" "'" + method + "' should only be used in 'describe' function"
@@ -836,17 +657,17 @@ getJasmineRequireObj().Env = function(j$) {
function specResultCallback(spec, result, next) { function specResultCallback(spec, result, next) {
runableResources.clearForRunable(spec.id); runableResources.clearForRunable(spec.id);
currentSpec = null; runner.currentSpec = null;
if (result.status === 'failed') { if (result.status === 'failed') {
hasFailures = true; runner.hasFailures = true;
} }
reportSpecDone(spec, result, next); reportSpecDone(spec, result, next);
} }
function specStarted(spec, suite, next) { function specStarted(spec, suite, next) {
currentSpec = spec; runner.currentSpec = spec;
runableResources.initForRunable(spec.id, suite.id); runableResources.initForRunable(spec.id, suite.id);
reporter.specStarted(spec.result, next); reporter.specStarted(spec.result, next);
} }
@@ -856,11 +677,6 @@ getJasmineRequireObj().Env = function(j$) {
reporter.specDone(result, next); reporter.specDone(result, next);
} }
function reportSuiteDone(suite, result, next) {
suite.reportedDone = true;
reporter.suiteDone(result, next);
}
this.it = function(description, fn, timeout) { this.it = function(description, fn, timeout) {
ensureIsNotNested('it'); ensureIsNotNested('it');
return suiteBuilder.it(description, fn, timeout).metadata; return suiteBuilder.it(description, fn, timeout).metadata;
@@ -885,12 +701,15 @@ getJasmineRequireObj().Env = function(j$) {
* @param {*} value The value of the property * @param {*} value The value of the property
*/ */
this.setSpecProperty = function(key, value) { this.setSpecProperty = function(key, value) {
if (!currentRunable() || currentRunable() == currentSuite()) { if (
!runner.currentRunable() ||
runner.currentRunable() == runner.currentSuite()
) {
throw new Error( throw new Error(
"'setSpecProperty' was used when there was no current spec" "'setSpecProperty' was used when there was no current spec"
); );
} }
currentRunable().setSpecProperty(key, value); runner.currentRunable().setSpecProperty(key, value);
}; };
/** /**
@@ -902,16 +721,16 @@ getJasmineRequireObj().Env = function(j$) {
* @param {*} value The value of the property * @param {*} value The value of the property
*/ */
this.setSuiteProperty = function(key, value) { this.setSuiteProperty = function(key, value) {
if (!currentSuite()) { if (!runner.currentSuite()) {
throw new Error( throw new Error(
"'setSuiteProperty' was used when there was no current suite" "'setSuiteProperty' was used when there was no current suite"
); );
} }
currentSuite().setSuiteProperty(key, value); runner.currentSuite().setSuiteProperty(key, value);
}; };
this.debugLog = function(msg) { this.debugLog = function(msg) {
const maybeSpec = currentRunable(); const maybeSpec = runner.currentRunable();
if (!maybeSpec || !maybeSpec.debugLog) { if (!maybeSpec || !maybeSpec.debugLog) {
throw new Error("'debugLog' was called when there was no current spec"); throw new Error("'debugLog' was called when there was no current spec");
@@ -921,23 +740,23 @@ getJasmineRequireObj().Env = function(j$) {
}; };
this.expect = function(actual) { this.expect = function(actual) {
if (!currentRunable()) { if (!runner.currentRunable()) {
throw new Error( throw new Error(
"'expect' was used when there was no current spec, this could be because an asynchronous test timed out" "'expect' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
} }
return currentRunable().expect(actual); return runner.currentRunable().expect(actual);
}; };
this.expectAsync = function(actual) { this.expectAsync = function(actual) {
if (!currentRunable()) { if (!runner.currentRunable()) {
throw new Error( throw new Error(
"'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out" "'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
} }
return currentRunable().expectAsync(actual); return runner.currentRunable().expectAsync(actual);
}; };
this.beforeEach = function(beforeEachFunction, timeout) { this.beforeEach = function(beforeEachFunction, timeout) {
@@ -969,7 +788,7 @@ getJasmineRequireObj().Env = function(j$) {
}; };
this.fail = function(error) { this.fail = function(error) {
if (!currentRunable()) { if (!runner.currentRunable()) {
throw new Error( throw new Error(
"'fail' was used when there was no current spec, this could be because an asynchronous test timed out" "'fail' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
@@ -989,7 +808,7 @@ getJasmineRequireObj().Env = function(j$) {
} }
} }
currentRunable().addExpectationResult(false, { runner.currentRunable().addExpectationResult(false, {
matcherName: '', matcherName: '',
passed: false, passed: false,
expected: '', expected: '',

226
src/core/Runner.js Normal file
View File

@@ -0,0 +1,226 @@
getJasmineRequireObj().Runner = function(j$) {
class Runner {
constructor(options) {
this.topSuite_ = options.topSuite;
this.totalSpecsDefined_ = options.totalSpecsDefined;
this.focusedRunables_ = options.focusedRunables;
this.runableResources_ = options.runableResources;
this.queueRunnerFactory_ = options.queueRunnerFactory;
this.reporter_ = options.reporter;
this.getConfig_ = options.getConfig;
this.reportSpecDone_ = options.reportSpecDone;
this.hasFailures = false;
this.executedBefore_ = false;
this.currentlyExecutingSuites_ = [];
this.currentSpec = null;
}
currentRunable() {
return this.currentSpec || this.currentSuite();
}
currentSuite() {
return this.currentlyExecutingSuites_[
this.currentlyExecutingSuites_.length - 1
];
}
// Although execute returns a promise, it isn't async for backwards
// compatibility: The "Invalid order" exception needs to be propagated
// synchronously from Env#execute.
// TODO: make this and Env#execute async in the next major release
execute(runablesToRun) {
if (this.executedBefore_) {
this.topSuite_.reset();
}
this.executedBefore_ = true;
this.hasFailures = false;
const totalSpecsDefined = this.totalSpecsDefined_();
const focusedRunables = this.focusedRunables_();
const config = this.getConfig_();
if (!runablesToRun) {
if (focusedRunables.length) {
runablesToRun = focusedRunables;
} else {
runablesToRun = [this.topSuite_.id];
}
}
const order = new j$.Order({
random: config.random,
seed: config.seed
});
const processor = new j$.TreeProcessor({
tree: this.topSuite_,
runnableIds: runablesToRun,
queueRunnerFactory: this.queueRunnerFactory_,
failSpecWithNoExpectations: config.failSpecWithNoExpectations,
nodeStart: (suite, next) => {
this.currentlyExecutingSuites_.push(suite);
this.runableResources_.initForRunable(suite.id, suite.parentSuite.id);
this.reporter_.suiteStarted(suite.result, next);
suite.startTimer();
},
nodeComplete: (suite, result, next) => {
if (suite !== this.currentSuite()) {
throw new Error('Tried to complete the wrong suite');
}
this.runableResources_.clearForRunable(suite.id);
this.currentlyExecutingSuites_.pop();
if (result.status === 'failed') {
this.hasFailures = true;
}
suite.endTimer();
if (suite.hadBeforeAllFailure) {
this.reportChildrenOfBeforeAllFailure_(suite).then(() => {
this.reportSuiteDone_(suite, result, next);
});
} else {
this.reportSuiteDone_(suite, result, next);
}
},
orderChildren: function(node) {
return order.sort(node.children);
},
excludeNode: function(spec) {
return !config.specFilter(spec);
}
});
if (!processor.processTree().valid) {
throw new Error(
'Invalid order: would cause a beforeAll or afterAll to be run multiple times'
);
}
this.runableResources_.initForRunable(this.topSuite_.id);
const jasmineTimer = new j$.Timer();
jasmineTimer.start();
return new Promise(resolve => {
/**
* Information passed to the {@link Reporter#jasmineStarted} event.
* @typedef JasmineStartedInfo
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @since 2.0.0
*/
this.reporter_.jasmineStarted(
{
totalSpecsDefined,
order: order
},
() => {
this.currentlyExecutingSuites_.push(this.topSuite_);
processor.execute(() => {
(async () => {
if (this.topSuite_.hadBeforeAllFailure) {
await this.reportChildrenOfBeforeAllFailure_(this.topSuite_);
}
this.runableResources_.clearForRunable(this.topSuite_.id);
this.currentlyExecutingSuites_.pop();
let overallStatus, incompleteReason;
if (
this.hasFailures ||
this.topSuite_.result.failedExpectations.length > 0
) {
overallStatus = 'failed';
} else if (focusedRunables.length > 0) {
overallStatus = 'incomplete';
incompleteReason = 'fit() or fdescribe() was found';
} else if (totalSpecsDefined === 0) {
overallStatus = 'incomplete';
incompleteReason = 'No specs found';
} else {
overallStatus = 'passed';
}
/**
* Information passed to the {@link Reporter#jasmineDone} event.
* @typedef JasmineDoneInfo
* @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'.
* @property {Int} totalTime - The total time (in ms) that it took to execute the suite
* @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
* @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level.
* @since 2.4.0
*/
const jasmineDoneInfo = {
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
order: order,
failedExpectations: this.topSuite_.result.failedExpectations,
deprecationWarnings: this.topSuite_.result.deprecationWarnings
};
this.topSuite_.reportedDone = true;
this.reporter_.jasmineDone(jasmineDoneInfo, function() {
resolve(jasmineDoneInfo);
});
})();
});
}
);
});
}
reportSuiteDone_(suite, result, next) {
suite.reportedDone = true;
this.reporter_.suiteDone(result, next);
}
async reportChildrenOfBeforeAllFailure_(suite) {
for (const child of suite.children) {
if (child instanceof j$.Suite) {
await new Promise(resolve => {
this.reporter_.suiteStarted(child.result, resolve);
});
await this.reportChildrenOfBeforeAllFailure_(child);
// Marking the suite passed is consistent with how suites that
// contain failed specs but no suite-level failures are reported.
child.result.status = 'passed';
await new Promise(resolve => {
this.reporter_.suiteDone(child.result, resolve);
});
} else {
/* a spec */
await new Promise(resolve => {
this.reporter_.specStarted(child.result, resolve);
});
child.addExpectationResult(
false,
{
passed: false,
message:
'Not run because a beforeAll function failed. The ' +
'beforeAll failure will be reported on the suite that ' +
'caused it.'
},
true
);
child.result.status = 'failed';
await new Promise(resolve => {
this.reportSpecDone_(child, child.result, resolve);
});
}
}
}
}
return Runner;
};

View File

@@ -69,6 +69,7 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
); );
j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.ReportDispatcher = jRequire.ReportDispatcher(j$);
j$.RunableResources = jRequire.RunableResources(j$); j$.RunableResources = jRequire.RunableResources(j$);
j$.Runner = jRequire.Runner(j$);
j$.Spec = jRequire.Spec(j$); j$.Spec = jRequire.Spec(j$);
j$.Spy = jRequire.Spy(j$); j$.Spy = jRequire.Spy(j$);
j$.SpyFactory = jRequire.SpyFactory(j$); j$.SpyFactory = jRequire.SpyFactory(j$);