2019-11-18 18:18:28 -08:00
|
|
|
/**
|
|
|
|
* Copyright 2017 Google Inc. All rights reserved.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
const EventEmitter = require('events');
|
2020-01-08 16:16:54 +00:00
|
|
|
const {SourceMapSupport} = require('./SourceMapSupport');
|
2020-02-14 15:21:08 -08:00
|
|
|
const debug = require('debug');
|
2020-02-20 22:55:39 -08:00
|
|
|
const {getCallerLocation} = require('./utils');
|
2019-12-19 15:47:35 -08:00
|
|
|
|
2020-03-25 22:42:09 -07:00
|
|
|
const INFINITE_TIMEOUT = 100000000;
|
2019-11-18 18:18:28 -08:00
|
|
|
const TimeoutError = new Error('Timeout');
|
|
|
|
const TerminatedError = new Error('Terminated');
|
|
|
|
|
2020-03-26 14:43:28 -07:00
|
|
|
function runUserCallback(callback, timeout, args) {
|
|
|
|
let terminateCallback;
|
|
|
|
let timeoutId;
|
|
|
|
const promise = Promise.race([
|
|
|
|
Promise.resolve().then(callback.bind(null, ...args)).then(() => null).catch(e => e),
|
|
|
|
new Promise(resolve => {
|
|
|
|
timeoutId = setTimeout(resolve.bind(null, TimeoutError), timeout);
|
|
|
|
}),
|
|
|
|
new Promise(resolve => terminateCallback = resolve),
|
|
|
|
]).catch(e => e).finally(() => clearTimeout(timeoutId));
|
|
|
|
const terminate = () => terminateCallback(TerminatedError);
|
|
|
|
return { promise, terminate };
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
2020-03-15 23:10:49 -07:00
|
|
|
const TestExpectation = {
|
|
|
|
Ok: 'ok',
|
|
|
|
Fail: 'fail',
|
2019-11-18 18:18:28 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
const TestResult = {
|
|
|
|
Ok: 'ok',
|
2020-03-02 14:57:09 -08:00
|
|
|
MarkedAsFailing: 'markedAsFailing', // User marked as failed
|
|
|
|
Skipped: 'skipped', // User marked as skipped
|
2019-11-18 18:18:28 -08:00
|
|
|
Failed: 'failed', // Exception happened during running
|
|
|
|
TimedOut: 'timedout', // Timeout Exceeded while running
|
|
|
|
Terminated: 'terminated', // Execution terminated
|
|
|
|
Crashed: 'crashed', // If testrunner crashed due to this test
|
|
|
|
};
|
|
|
|
|
2020-03-27 22:57:22 -07:00
|
|
|
function createHook(callback, name) {
|
|
|
|
const location = getCallerLocation(__filename);
|
|
|
|
return { name, body: callback, location };
|
|
|
|
}
|
|
|
|
|
2019-11-18 18:18:28 -08:00
|
|
|
class Test {
|
2020-03-26 22:47:13 -07:00
|
|
|
constructor(suite, name, callback, location) {
|
|
|
|
this._suite = suite;
|
|
|
|
this._name = name;
|
|
|
|
this._fullName = (suite.fullName() + ' ' + name).trim();
|
2020-03-28 14:25:57 -07:00
|
|
|
this._skipped = false;
|
|
|
|
this._focused = false;
|
2020-03-26 22:47:13 -07:00
|
|
|
this._expectation = TestExpectation.Ok;
|
|
|
|
this._body = callback;
|
|
|
|
this._location = location;
|
|
|
|
this._timeout = INFINITE_TIMEOUT;
|
|
|
|
this._repeat = 1;
|
2020-03-27 22:57:22 -07:00
|
|
|
this._hooks = [];
|
2020-03-26 22:47:13 -07:00
|
|
|
|
|
|
|
this.Expectations = { ...TestExpectation };
|
|
|
|
}
|
|
|
|
|
|
|
|
suite() {
|
|
|
|
return this._suite;
|
|
|
|
}
|
|
|
|
|
|
|
|
name() {
|
|
|
|
return this._name;
|
|
|
|
}
|
|
|
|
|
|
|
|
fullName() {
|
|
|
|
return this._fullName;
|
|
|
|
}
|
|
|
|
|
|
|
|
location() {
|
|
|
|
return this._location;
|
|
|
|
}
|
|
|
|
|
|
|
|
body() {
|
|
|
|
return this._body;
|
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
skipped() {
|
|
|
|
return this._skipped;
|
2020-03-26 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
setSkipped(skipped) {
|
|
|
|
this._skipped = skipped;
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-28 14:25:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
focused() {
|
|
|
|
return this._focused;
|
|
|
|
}
|
|
|
|
|
|
|
|
setFocused(focused) {
|
|
|
|
this._focused = focused;
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-26 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
timeout() {
|
|
|
|
return this._timeout;
|
|
|
|
}
|
|
|
|
|
|
|
|
setTimeout(timeout) {
|
|
|
|
this._timeout = timeout;
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-26 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
expectation() {
|
|
|
|
return this._expectation;
|
|
|
|
}
|
|
|
|
|
|
|
|
setExpectation(expectation) {
|
|
|
|
this._expectation = expectation;
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-26 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
repeat() {
|
|
|
|
return this._repeat;
|
|
|
|
}
|
|
|
|
|
|
|
|
setRepeat(repeat) {
|
|
|
|
this._repeat = repeat;
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-26 22:47:13 -07:00
|
|
|
}
|
2020-03-27 22:57:22 -07:00
|
|
|
|
|
|
|
before(callback) {
|
|
|
|
this._hooks.push(createHook(callback, 'before'));
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-27 22:57:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
after(callback) {
|
|
|
|
this._hooks.push(createHook(callback, 'after'));
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-27 22:57:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
hooks(name) {
|
|
|
|
return this._hooks.filter(hook => !name || hook.name === name);
|
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
class Suite {
|
2020-03-26 22:47:13 -07:00
|
|
|
constructor(parentSuite, name, location) {
|
|
|
|
this._parentSuite = parentSuite;
|
|
|
|
this._name = name;
|
|
|
|
this._fullName = (parentSuite ? parentSuite.fullName() + ' ' + name : name).trim();
|
2020-03-28 14:25:57 -07:00
|
|
|
this._skipped = false;
|
|
|
|
this._focused = false;
|
2020-03-26 22:47:13 -07:00
|
|
|
this._expectation = TestExpectation.Ok;
|
|
|
|
this._location = location;
|
|
|
|
this._repeat = 1;
|
2020-03-27 22:57:22 -07:00
|
|
|
this._hooks = [];
|
2020-03-26 22:47:13 -07:00
|
|
|
|
|
|
|
this.Expectations = { ...TestExpectation };
|
|
|
|
}
|
|
|
|
|
|
|
|
parentSuite() {
|
|
|
|
return this._parentSuite;
|
|
|
|
}
|
|
|
|
|
|
|
|
name() {
|
|
|
|
return this._name;
|
|
|
|
}
|
|
|
|
|
|
|
|
fullName() {
|
|
|
|
return this._fullName;
|
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
skipped() {
|
|
|
|
return this._skipped;
|
2020-03-26 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
setSkipped(skipped) {
|
|
|
|
this._skipped = skipped;
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-28 14:25:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
focused() {
|
|
|
|
return this._focused;
|
|
|
|
}
|
|
|
|
|
|
|
|
setFocused(focused) {
|
|
|
|
this._focused = focused;
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-26 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
location() {
|
|
|
|
return this._location;
|
|
|
|
}
|
|
|
|
|
|
|
|
expectation() {
|
|
|
|
return this._expectation;
|
|
|
|
}
|
|
|
|
|
|
|
|
setExpectation(expectation) {
|
|
|
|
this._expectation = expectation;
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-26 22:47:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
repeat() {
|
|
|
|
return this._repeat;
|
|
|
|
}
|
|
|
|
|
|
|
|
setRepeat(repeat) {
|
|
|
|
this._repeat = repeat;
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
2020-03-27 22:57:22 -07:00
|
|
|
|
|
|
|
beforeEach(callback) {
|
|
|
|
this._hooks.push(createHook(callback, 'beforeEach'));
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-27 22:57:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
afterEach(callback) {
|
|
|
|
this._hooks.push(createHook(callback, 'afterEach'));
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-27 22:57:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
beforeAll(callback) {
|
|
|
|
this._hooks.push(createHook(callback, 'beforeAll'));
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-27 22:57:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
afterAll(callback) {
|
|
|
|
this._hooks.push(createHook(callback, 'afterAll'));
|
2020-03-29 11:37:45 -07:00
|
|
|
return this;
|
2020-03-27 22:57:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
hooks(name) {
|
|
|
|
return this._hooks.filter(hook => !name || hook.name === name);
|
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
class TestRun {
|
|
|
|
constructor(test) {
|
|
|
|
this._test = test;
|
|
|
|
this._result = null;
|
|
|
|
this._error = null;
|
|
|
|
this._startTimestamp = 0;
|
|
|
|
this._endTimestamp = 0;
|
|
|
|
this._workerId = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
finished() {
|
|
|
|
return this._result !== null && this._result !== 'running';
|
|
|
|
}
|
|
|
|
|
|
|
|
isFailure() {
|
|
|
|
return this._result === TestResult.Failed || this._result === TestResult.TimedOut || this._result === TestResult.Crashed;
|
|
|
|
}
|
|
|
|
|
|
|
|
ok() {
|
|
|
|
return this._result === TestResult.Ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
result() {
|
|
|
|
return this._result;
|
|
|
|
}
|
|
|
|
|
|
|
|
error() {
|
|
|
|
return this._error;
|
|
|
|
}
|
|
|
|
|
|
|
|
duration() {
|
|
|
|
return this._endTimestamp - this._startTimestamp;
|
|
|
|
}
|
|
|
|
|
|
|
|
test() {
|
|
|
|
return this._test;
|
|
|
|
}
|
|
|
|
|
|
|
|
workerId() {
|
|
|
|
return this._workerId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-12 17:32:53 -07:00
|
|
|
class Result {
|
|
|
|
constructor() {
|
|
|
|
this.result = TestResult.Ok;
|
|
|
|
this.exitCode = 0;
|
|
|
|
this.message = '';
|
|
|
|
this.errors = [];
|
2020-03-28 14:25:57 -07:00
|
|
|
this.runs = [];
|
2020-03-12 17:32:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
setResult(result, message) {
|
|
|
|
if (!this.ok())
|
|
|
|
return;
|
|
|
|
this.result = result;
|
|
|
|
this.message = message || '';
|
|
|
|
if (result === TestResult.Ok)
|
|
|
|
this.exitCode = 0;
|
|
|
|
else if (result === TestResult.Terminated)
|
|
|
|
this.exitCode = 130;
|
|
|
|
else if (result === TestResult.Crashed)
|
|
|
|
this.exitCode = 2;
|
|
|
|
else
|
|
|
|
this.exitCode = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
addError(message, error, worker) {
|
2020-03-28 14:25:57 -07:00
|
|
|
const data = { message, error, runs: [] };
|
|
|
|
if (worker)
|
|
|
|
data.runs = worker._runs.slice();
|
2020-03-12 17:32:53 -07:00
|
|
|
this.errors.push(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
ok() {
|
|
|
|
return this.result === TestResult.Ok;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-10 11:30:02 -07:00
|
|
|
class TestWorker {
|
|
|
|
constructor(testPass, workerId, parallelIndex) {
|
|
|
|
this._testPass = testPass;
|
|
|
|
this._state = { parallelIndex };
|
|
|
|
this._suiteStack = [];
|
2020-03-12 17:32:53 -07:00
|
|
|
this._terminating = false;
|
2020-03-10 11:30:02 -07:00
|
|
|
this._workerId = workerId;
|
2020-03-26 14:43:28 -07:00
|
|
|
this._runningTestTerminate = null;
|
|
|
|
this._runningHookTerminate = null;
|
2020-03-28 14:25:57 -07:00
|
|
|
this._runs = [];
|
2020-03-10 11:30:02 -07:00
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
|
2020-03-12 17:32:53 -07:00
|
|
|
terminate(terminateHooks) {
|
|
|
|
this._terminating = true;
|
2020-03-26 14:43:28 -07:00
|
|
|
if (this._runningTestTerminate)
|
|
|
|
this._runningTestTerminate();
|
|
|
|
if (terminateHooks && this._runningHookTerminate)
|
|
|
|
this._runningHookTerminate();
|
2020-03-10 11:30:02 -07:00
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
_markTerminated(testRun) {
|
2020-03-12 17:32:53 -07:00
|
|
|
if (!this._terminating)
|
2020-03-10 11:30:02 -07:00
|
|
|
return false;
|
2020-03-28 14:25:57 -07:00
|
|
|
testRun._result = TestResult.Terminated;
|
2020-03-10 11:30:02 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
async run(testRun) {
|
|
|
|
this._runs.push(testRun);
|
2020-03-12 17:32:53 -07:00
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
const test = testRun.test();
|
|
|
|
let skipped = test.skipped() && !test.focused();
|
2020-03-27 15:35:14 -07:00
|
|
|
for (let suite = test.suite(); suite; suite = suite.parentSuite())
|
2020-03-28 14:25:57 -07:00
|
|
|
skipped = skipped || (suite.skipped() && !suite.focused());
|
2020-03-27 15:35:14 -07:00
|
|
|
if (skipped) {
|
2020-03-28 14:25:57 -07:00
|
|
|
await this._willStartTestRun(testRun);
|
|
|
|
testRun._result = TestResult.Skipped;
|
|
|
|
await this._didFinishTestRun(testRun);
|
2020-03-10 11:30:02 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-03-27 15:35:14 -07:00
|
|
|
let expectedToFail = test.expectation() === TestExpectation.Fail;
|
|
|
|
for (let suite = test.suite(); suite; suite = suite.parentSuite())
|
|
|
|
expectedToFail = expectedToFail || (suite.expectation() === TestExpectation.Fail);
|
2020-03-28 14:25:57 -07:00
|
|
|
if (expectedToFail && !test.focused()) {
|
|
|
|
await this._willStartTestRun(testRun);
|
|
|
|
testRun._result = TestResult.MarkedAsFailing;
|
|
|
|
await this._didFinishTestRun(testRun);
|
2020-03-10 11:30:02 -07:00
|
|
|
return;
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
2020-03-10 11:30:02 -07:00
|
|
|
const suiteStack = [];
|
2020-03-26 22:47:13 -07:00
|
|
|
for (let suite = test.suite(); suite; suite = suite.parentSuite())
|
2020-03-10 11:30:02 -07:00
|
|
|
suiteStack.push(suite);
|
|
|
|
suiteStack.reverse();
|
|
|
|
|
|
|
|
let common = 0;
|
|
|
|
while (common < suiteStack.length && this._suiteStack[common] === suiteStack[common])
|
|
|
|
common++;
|
|
|
|
|
|
|
|
while (this._suiteStack.length > common) {
|
2020-03-28 14:25:57 -07:00
|
|
|
if (this._markTerminated(testRun))
|
2020-03-10 11:30:02 -07:00
|
|
|
return;
|
|
|
|
const suite = this._suiteStack.pop();
|
2020-03-27 22:57:22 -07:00
|
|
|
for (const hook of suite.hooks('afterAll')) {
|
2020-03-28 14:25:57 -07:00
|
|
|
if (!await this._runHook(testRun, hook, suite.fullName()))
|
2020-03-27 22:57:22 -07:00
|
|
|
return;
|
|
|
|
}
|
2020-03-10 11:30:02 -07:00
|
|
|
}
|
|
|
|
while (this._suiteStack.length < suiteStack.length) {
|
2020-03-28 14:25:57 -07:00
|
|
|
if (this._markTerminated(testRun))
|
2020-03-10 11:30:02 -07:00
|
|
|
return;
|
|
|
|
const suite = suiteStack[this._suiteStack.length];
|
|
|
|
this._suiteStack.push(suite);
|
2020-03-27 22:57:22 -07:00
|
|
|
for (const hook of suite.hooks('beforeAll')) {
|
2020-03-28 14:25:57 -07:00
|
|
|
if (!await this._runHook(testRun, hook, suite.fullName()))
|
2020-03-27 22:57:22 -07:00
|
|
|
return;
|
|
|
|
}
|
2020-03-10 11:30:02 -07:00
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
if (this._markTerminated(testRun))
|
2020-03-10 11:30:02 -07:00
|
|
|
return;
|
|
|
|
|
|
|
|
// From this point till the end, we have to run all hooks
|
|
|
|
// no matter what happens.
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
await this._willStartTestRun(testRun);
|
2020-03-27 22:57:22 -07:00
|
|
|
for (const suite of this._suiteStack) {
|
|
|
|
for (const hook of suite.hooks('beforeEach'))
|
2020-03-28 14:25:57 -07:00
|
|
|
await this._runHook(testRun, hook, suite.fullName(), true);
|
2020-03-27 22:57:22 -07:00
|
|
|
}
|
|
|
|
for (const hook of test.hooks('before'))
|
2020-03-28 14:25:57 -07:00
|
|
|
await this._runHook(testRun, hook, test.fullName(), true);
|
2020-03-10 11:30:02 -07:00
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
if (!testRun._error && !this._markTerminated(testRun)) {
|
|
|
|
await this._willStartTestBody(testRun);
|
2020-03-26 22:47:13 -07:00
|
|
|
const { promise, terminate } = runUserCallback(test.body(), test.timeout(), [this._state, test]);
|
2020-03-26 14:43:28 -07:00
|
|
|
this._runningTestTerminate = terminate;
|
2020-03-28 14:25:57 -07:00
|
|
|
testRun._error = await promise;
|
2020-03-26 14:43:28 -07:00
|
|
|
this._runningTestTerminate = null;
|
2020-03-28 14:25:57 -07:00
|
|
|
if (testRun._error && testRun._error.stack)
|
|
|
|
await this._testPass._runner._sourceMapSupport.rewriteStackTraceWithSourceMaps(testRun._error);
|
|
|
|
if (!testRun._error)
|
|
|
|
testRun._result = TestResult.Ok;
|
|
|
|
else if (testRun._error === TimeoutError)
|
|
|
|
testRun._result = TestResult.TimedOut;
|
|
|
|
else if (testRun._error === TerminatedError)
|
|
|
|
testRun._result = TestResult.Terminated;
|
2020-03-10 11:30:02 -07:00
|
|
|
else
|
2020-03-28 14:25:57 -07:00
|
|
|
testRun._result = TestResult.Failed;
|
|
|
|
await this._didFinishTestBody(testRun);
|
2020-03-10 11:30:02 -07:00
|
|
|
}
|
|
|
|
|
2020-03-27 22:57:22 -07:00
|
|
|
for (const hook of test.hooks('after'))
|
2020-03-28 14:25:57 -07:00
|
|
|
await this._runHook(testRun, hook, test.fullName(), true);
|
2020-03-27 22:57:22 -07:00
|
|
|
for (const suite of this._suiteStack.slice().reverse()) {
|
|
|
|
for (const hook of suite.hooks('afterEach'))
|
2020-03-28 14:25:57 -07:00
|
|
|
await this._runHook(testRun, hook, suite.fullName(), true);
|
2020-03-27 22:57:22 -07:00
|
|
|
}
|
2020-03-28 14:25:57 -07:00
|
|
|
await this._didFinishTestRun(testRun);
|
2020-03-10 11:30:02 -07:00
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
async _runHook(testRun, hook, fullName, passTest = false) {
|
|
|
|
await this._willStartHook(hook, fullName);
|
2020-03-26 14:43:28 -07:00
|
|
|
const timeout = this._testPass._runner._timeout;
|
2020-03-28 14:25:57 -07:00
|
|
|
const { promise, terminate } = runUserCallback(hook.body, timeout, passTest ? [this._state, testRun.test()] : [this._state]);
|
2020-03-26 14:43:28 -07:00
|
|
|
this._runningHookTerminate = terminate;
|
|
|
|
let error = await promise;
|
|
|
|
this._runningHookTerminate = null;
|
2020-03-10 11:30:02 -07:00
|
|
|
|
|
|
|
if (error) {
|
2020-03-26 14:43:28 -07:00
|
|
|
const locationString = `${hook.location.fileName}:${hook.location.lineNumber}:${hook.location.columnNumber}`;
|
2020-03-28 14:25:57 -07:00
|
|
|
if (testRun && testRun._result !== TestResult.Terminated) {
|
2020-03-10 11:30:02 -07:00
|
|
|
// Prefer terminated result over any hook failures.
|
2020-03-28 14:25:57 -07:00
|
|
|
testRun._result = error === TerminatedError ? TestResult.Terminated : TestResult.Crashed;
|
2020-03-10 11:30:02 -07:00
|
|
|
}
|
2020-03-12 17:32:53 -07:00
|
|
|
let message;
|
2020-03-10 11:30:02 -07:00
|
|
|
if (error === TimeoutError) {
|
2020-03-27 22:57:22 -07:00
|
|
|
message = `${locationString} - Timeout Exceeded ${timeout}ms while running "${hook.name}" in "${fullName}"`;
|
2020-03-12 17:32:53 -07:00
|
|
|
error = null;
|
2020-03-10 11:30:02 -07:00
|
|
|
} else if (error === TerminatedError) {
|
2020-03-24 14:40:59 -07:00
|
|
|
// Do not report termination details - it's just noise.
|
|
|
|
message = '';
|
2020-03-12 17:32:53 -07:00
|
|
|
error = null;
|
2020-03-10 11:30:02 -07:00
|
|
|
} else {
|
|
|
|
if (error.stack)
|
|
|
|
await this._testPass._runner._sourceMapSupport.rewriteStackTraceWithSourceMaps(error);
|
2020-03-27 22:57:22 -07:00
|
|
|
message = `${locationString} - FAILED while running "${hook.name}" in suite "${fullName}": `;
|
2020-03-10 11:30:02 -07:00
|
|
|
}
|
2020-03-28 14:25:57 -07:00
|
|
|
await this._didFailHook(hook, fullName, message, error);
|
|
|
|
if (testRun)
|
|
|
|
testRun._error = error;
|
2020-03-10 11:30:02 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
await this._didCompleteHook(hook, fullName);
|
2020-03-10 11:30:02 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
async _willStartTestRun(testRun) {
|
|
|
|
testRun._startTimestamp = Date.now();
|
|
|
|
testRun._workerId = this._workerId;
|
|
|
|
this._testPass._runner.emit(TestRunner.Events.TestStarted, testRun);
|
|
|
|
}
|
|
|
|
|
|
|
|
async _didFinishTestRun(testRun) {
|
|
|
|
testRun._endTimestamp = Date.now();
|
|
|
|
testRun._workerId = this._workerId;
|
|
|
|
this._testPass._runner.emit(TestRunner.Events.TestFinished, testRun);
|
|
|
|
}
|
|
|
|
|
|
|
|
async _willStartTestBody(testRun) {
|
|
|
|
debug('testrunner:test')(`[${this._workerId}] starting "${testRun.test().fullName()}" (${testRun.test().location().fileName + ':' + testRun.test().location().lineNumber})`);
|
|
|
|
}
|
|
|
|
|
|
|
|
async _didFinishTestBody(testRun) {
|
|
|
|
debug('testrunner:test')(`[${this._workerId}] ${testRun._result.toUpperCase()} "${testRun.test().fullName()}" (${testRun.test().location().fileName + ':' + testRun.test().location().lineNumber})`);
|
|
|
|
}
|
|
|
|
|
|
|
|
async _willStartHook(hook, fullName) {
|
|
|
|
debug('testrunner:hook')(`[${this._workerId}] "${hook.name}" started for "${fullName}" (${hook.location.fileName + ':' + hook.location.lineNumber})`);
|
|
|
|
}
|
|
|
|
|
|
|
|
async _didFailHook(hook, fullName, message, error) {
|
|
|
|
debug('testrunner:hook')(`[${this._workerId}] "${hook.name}" FAILED for "${fullName}" (${hook.location.fileName + ':' + hook.location.lineNumber})`);
|
|
|
|
if (message)
|
|
|
|
this._testPass._result.addError(message, error, this);
|
|
|
|
this._testPass._result.setResult(TestResult.Crashed, message);
|
|
|
|
}
|
|
|
|
|
|
|
|
async _didCompleteHook(hook, fullName) {
|
|
|
|
debug('testrunner:hook')(`[${this._workerId}] "${hook.name}" OK for "${fullName}" (${hook.location.fileName + ':' + hook.location.lineNumber})`);
|
|
|
|
}
|
|
|
|
|
2020-03-10 11:30:02 -07:00
|
|
|
async shutdown() {
|
|
|
|
while (this._suiteStack.length > 0) {
|
|
|
|
const suite = this._suiteStack.pop();
|
2020-03-27 22:57:22 -07:00
|
|
|
for (const hook of suite.hooks('afterAll'))
|
2020-03-28 14:25:57 -07:00
|
|
|
await this._runHook(null, hook, suite.fullName());
|
2020-03-10 11:30:02 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class TestPass {
|
|
|
|
constructor(runner, parallel, breakOnFailure) {
|
|
|
|
this._runner = runner;
|
|
|
|
this._workers = [];
|
2020-03-12 17:32:53 -07:00
|
|
|
this._nextWorkerId = 1;
|
2020-03-10 11:30:02 -07:00
|
|
|
this._parallel = parallel;
|
|
|
|
this._breakOnFailure = breakOnFailure;
|
2020-03-12 17:32:53 -07:00
|
|
|
this._errors = [];
|
|
|
|
this._result = new Result();
|
|
|
|
this._terminating = false;
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
async run(testRuns) {
|
2019-11-18 18:18:28 -08:00
|
|
|
const terminations = [
|
|
|
|
createTermination.call(this, 'SIGINT', TestResult.Terminated, 'SIGINT received'),
|
|
|
|
createTermination.call(this, 'SIGHUP', TestResult.Terminated, 'SIGHUP received'),
|
|
|
|
createTermination.call(this, 'SIGTERM', TestResult.Terminated, 'SIGTERM received'),
|
|
|
|
createTermination.call(this, 'unhandledRejection', TestResult.Crashed, 'UNHANDLED PROMISE REJECTION'),
|
2020-03-10 11:16:54 -07:00
|
|
|
createTermination.call(this, 'uncaughtException', TestResult.Crashed, 'UNHANDLED ERROR'),
|
2019-11-18 18:18:28 -08:00
|
|
|
];
|
|
|
|
for (const termination of terminations)
|
|
|
|
process.on(termination.event, termination.handler);
|
|
|
|
|
2020-03-12 17:32:53 -07:00
|
|
|
this._result = new Result();
|
2020-03-28 14:25:57 -07:00
|
|
|
this._result.runs = testRuns;
|
2020-03-10 11:30:02 -07:00
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
const parallel = Math.min(this._parallel, testRuns.length);
|
2019-11-18 18:18:28 -08:00
|
|
|
const workerPromises = [];
|
2020-03-10 11:30:02 -07:00
|
|
|
for (let i = 0; i < parallel; ++i) {
|
2020-03-28 14:25:57 -07:00
|
|
|
const initialTestRunIndex = i * Math.floor(testRuns.length / parallel);
|
|
|
|
workerPromises.push(this._runWorker(initialTestRunIndex, testRuns, i));
|
2020-03-10 11:30:02 -07:00
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
await Promise.all(workerPromises);
|
|
|
|
|
|
|
|
for (const termination of terminations)
|
|
|
|
process.removeListener(termination.event, termination.handler);
|
2020-03-12 17:32:53 -07:00
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
if (testRuns.some(run => run.isFailure()))
|
2020-03-12 17:32:53 -07:00
|
|
|
this._result.setResult(TestResult.Failed, '');
|
|
|
|
return this._result;
|
2019-11-18 18:18:28 -08:00
|
|
|
|
|
|
|
function createTermination(event, result, message) {
|
|
|
|
return {
|
|
|
|
event,
|
|
|
|
message,
|
2020-03-12 17:32:53 -07:00
|
|
|
handler: error => this._terminate(result, message, event === 'SIGTERM', event.startsWith('SIG') ? null : error)
|
2019-11-18 18:18:28 -08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
async _runWorker(testRunIndex, testRuns, parallelIndex) {
|
2020-03-10 11:30:02 -07:00
|
|
|
let worker = new TestWorker(this, this._nextWorkerId++, parallelIndex);
|
|
|
|
this._workers[parallelIndex] = worker;
|
2020-03-12 17:32:53 -07:00
|
|
|
while (!worker._terminating) {
|
2020-03-10 11:30:02 -07:00
|
|
|
let skipped = 0;
|
2020-03-28 14:25:57 -07:00
|
|
|
while (skipped < testRuns.length && testRuns[testRunIndex]._result !== null) {
|
|
|
|
testRunIndex = (testRunIndex + 1) % testRuns.length;
|
2020-03-10 11:30:02 -07:00
|
|
|
skipped++;
|
|
|
|
}
|
2020-03-28 14:25:57 -07:00
|
|
|
const testRun = testRuns[testRunIndex];
|
|
|
|
if (testRun._result !== null) {
|
2020-03-10 11:30:02 -07:00
|
|
|
// All tests have been run.
|
2019-11-18 18:18:28 -08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-03-10 11:30:02 -07:00
|
|
|
// Mark as running so that other workers do not run it again.
|
2020-03-28 14:25:57 -07:00
|
|
|
testRun._result = 'running';
|
|
|
|
await worker.run(testRun);
|
|
|
|
if (testRun.isFailure()) {
|
2020-03-10 11:30:02 -07:00
|
|
|
// Something went wrong during test run, let's use a fresh worker.
|
|
|
|
await worker.shutdown();
|
|
|
|
if (this._breakOnFailure) {
|
2020-03-12 17:32:53 -07:00
|
|
|
const message = `Terminating because a test has failed and |testRunner.breakOnFailure| is enabled`;
|
|
|
|
await this._terminate(TestResult.Terminated, message, false /* force */, null /* error */);
|
2020-03-10 11:30:02 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
worker = new TestWorker(this, this._nextWorkerId++, parallelIndex);
|
|
|
|
this._workers[parallelIndex] = worker;
|
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
2020-03-10 11:30:02 -07:00
|
|
|
await worker.shutdown();
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
2020-03-12 17:32:53 -07:00
|
|
|
async _terminate(result, message, force, error) {
|
|
|
|
debug('testrunner')(`TERMINATED result = ${result}, message = ${message}`);
|
2020-03-10 11:30:02 -07:00
|
|
|
for (const worker of this._workers)
|
2020-03-12 17:32:53 -07:00
|
|
|
worker.terminate(force /* terminateHooks */);
|
|
|
|
this._result.setResult(result, message);
|
2020-03-24 14:40:59 -07:00
|
|
|
if (this._result.message === 'SIGINT received' && message === 'SIGTERM received')
|
|
|
|
this._result.message = message;
|
2020-03-12 17:32:53 -07:00
|
|
|
if (error) {
|
|
|
|
if (error.stack)
|
|
|
|
await this._runner._sourceMapSupport.rewriteStackTraceWithSourceMaps(error);
|
2020-03-23 14:45:21 -07:00
|
|
|
this._result.addError(message, error, this._workers.length === 1 ? this._workers[0] : null);
|
2020-03-12 17:32:53 -07:00
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class TestRunner extends EventEmitter {
|
|
|
|
constructor(options = {}) {
|
|
|
|
super();
|
|
|
|
const {
|
|
|
|
timeout = 10 * 1000, // Default timeout is 10 seconds.
|
|
|
|
parallel = 1,
|
|
|
|
breakOnFailure = false,
|
2020-03-11 18:30:43 -07:00
|
|
|
crashIfTestsAreFocusedOnCI = true,
|
2020-03-28 08:49:00 -07:00
|
|
|
installCommonHelpers = true,
|
2019-11-18 18:18:28 -08:00
|
|
|
} = options;
|
2020-03-11 18:30:43 -07:00
|
|
|
this._crashIfTestsAreFocusedOnCI = crashIfTestsAreFocusedOnCI;
|
2020-01-08 16:16:54 +00:00
|
|
|
this._sourceMapSupport = new SourceMapSupport();
|
2020-03-26 22:47:13 -07:00
|
|
|
const dummyLocation = { fileName: '', filePath: '', lineNumber: 0, columnNumber: 0 };
|
|
|
|
this._rootSuite = new Suite(null, '', dummyLocation);
|
2019-11-18 18:18:28 -08:00
|
|
|
this._currentSuite = this._rootSuite;
|
|
|
|
this._tests = [];
|
2020-03-11 18:30:43 -07:00
|
|
|
this._suites = [];
|
2019-12-19 15:47:35 -08:00
|
|
|
this._timeout = timeout === 0 ? INFINITE_TIMEOUT : timeout;
|
2019-11-18 18:18:28 -08:00
|
|
|
this._parallel = parallel;
|
|
|
|
this._breakOnFailure = breakOnFailure;
|
2020-03-26 22:47:13 -07:00
|
|
|
this._suiteModifiers = new Map();
|
|
|
|
this._suiteAttributes = new Map();
|
|
|
|
this._testModifiers = new Map();
|
|
|
|
this._testAttributes = new Map();
|
2019-11-18 18:18:28 -08:00
|
|
|
|
2020-03-27 22:57:22 -07:00
|
|
|
this.beforeAll = (callback) => this._currentSuite.beforeAll(callback);
|
|
|
|
this.beforeEach = (callback) => this._currentSuite.beforeEach(callback);
|
|
|
|
this.afterAll = (callback) => this._currentSuite.afterAll(callback);
|
|
|
|
this.afterEach = (callback) => this._currentSuite.afterEach(callback);
|
2020-03-25 14:40:57 -07:00
|
|
|
|
2020-03-26 22:47:13 -07:00
|
|
|
this.describe = this._suiteBuilder([]);
|
|
|
|
this.it = this._testBuilder([]);
|
2020-03-29 11:37:45 -07:00
|
|
|
this.Expectations = { ...TestExpectation };
|
2020-03-25 22:42:09 -07:00
|
|
|
|
2020-03-28 08:49:00 -07:00
|
|
|
if (installCommonHelpers) {
|
2020-03-29 11:37:45 -07:00
|
|
|
this.fdescribe = this._suiteBuilder([{ callback: s => s.setFocused(true), args: [] }]);
|
|
|
|
this.xdescribe = this._suiteBuilder([{ callback: s => s.setSkipped(true), args: [] }]);
|
|
|
|
this.fit = this._testBuilder([{ callback: t => t.setFocused(true), args: [] }]);
|
|
|
|
this.xit = this._testBuilder([{ callback: t => t.setSkipped(true), args: [] }]);
|
2020-03-28 08:49:00 -07:00
|
|
|
}
|
2020-03-25 22:42:09 -07:00
|
|
|
}
|
|
|
|
|
2020-03-26 22:47:13 -07:00
|
|
|
_suiteBuilder(callbacks) {
|
|
|
|
return new Proxy((name, callback, ...suiteArgs) => {
|
|
|
|
const location = getCallerLocation(__filename);
|
|
|
|
const suite = new Suite(this._currentSuite, name, location);
|
|
|
|
for (const { callback, args } of callbacks)
|
|
|
|
callback(suite, ...args);
|
2020-03-28 14:25:57 -07:00
|
|
|
this._currentSuite = suite;
|
|
|
|
callback(...suiteArgs);
|
|
|
|
this._suites.push(suite);
|
|
|
|
this._currentSuite = suite.parentSuite();
|
2020-03-29 11:37:45 -07:00
|
|
|
return suite;
|
2020-03-26 22:47:13 -07:00
|
|
|
}, {
|
|
|
|
get: (obj, prop) => {
|
|
|
|
if (this._suiteModifiers.has(prop))
|
|
|
|
return (...args) => this._suiteBuilder([...callbacks, { callback: this._suiteModifiers.get(prop), args }]);
|
|
|
|
if (this._suiteAttributes.has(prop))
|
|
|
|
return this._suiteBuilder([...callbacks, { callback: this._suiteAttributes.get(prop), args: [] }]);
|
|
|
|
return obj[prop];
|
2020-03-25 22:42:09 -07:00
|
|
|
},
|
2020-03-26 22:47:13 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_testBuilder(callbacks) {
|
|
|
|
return new Proxy((name, callback) => {
|
|
|
|
const location = getCallerLocation(__filename);
|
|
|
|
const test = new Test(this._currentSuite, name, callback, location);
|
|
|
|
test.setTimeout(this._timeout);
|
|
|
|
for (const { callback, args } of callbacks)
|
|
|
|
callback(test, ...args);
|
2020-03-28 14:25:57 -07:00
|
|
|
this._tests.push(test);
|
2020-03-29 11:37:45 -07:00
|
|
|
return test;
|
2020-03-26 22:47:13 -07:00
|
|
|
}, {
|
2020-03-25 22:42:09 -07:00
|
|
|
get: (obj, prop) => {
|
2020-03-26 22:47:13 -07:00
|
|
|
if (this._testModifiers.has(prop))
|
|
|
|
return (...args) => this._testBuilder([...callbacks, { callback: this._testModifiers.get(prop), args }]);
|
|
|
|
if (this._testAttributes.has(prop))
|
|
|
|
return this._testBuilder([...callbacks, { callback: this._testAttributes.get(prop), args: [] }]);
|
2020-03-25 22:42:09 -07:00
|
|
|
return obj[prop];
|
|
|
|
},
|
|
|
|
});
|
2020-03-25 14:40:57 -07:00
|
|
|
}
|
|
|
|
|
2020-03-26 22:47:13 -07:00
|
|
|
testModifier(name, callback) {
|
|
|
|
this._testModifiers.set(name, callback);
|
2020-03-25 14:40:57 -07:00
|
|
|
}
|
|
|
|
|
2020-03-26 22:47:13 -07:00
|
|
|
testAttribute(name, callback) {
|
|
|
|
this._testAttributes.set(name, callback);
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
2020-03-26 22:47:13 -07:00
|
|
|
suiteModifier(name, callback) {
|
|
|
|
this._suiteModifiers.set(name, callback);
|
2020-03-25 22:42:09 -07:00
|
|
|
}
|
|
|
|
|
2020-03-26 22:47:13 -07:00
|
|
|
suiteAttribute(name, callback) {
|
|
|
|
this._suiteAttributes.set(name, callback);
|
2020-03-25 22:42:09 -07:00
|
|
|
}
|
|
|
|
|
2020-03-12 17:32:53 -07:00
|
|
|
async run(options = {}) {
|
|
|
|
const { totalTimeout = 0 } = options;
|
2020-03-28 14:25:57 -07:00
|
|
|
const testRuns = [];
|
|
|
|
for (const test of this._testsToRun()) {
|
|
|
|
let repeat = test.repeat();
|
|
|
|
for (let suite = test.suite(); suite; suite = suite.parentSuite())
|
|
|
|
repeat *= suite.repeat();
|
|
|
|
for (let i = 0; i < repeat; i++)
|
|
|
|
testRuns.push(new TestRun(test));
|
|
|
|
}
|
|
|
|
this.emit(TestRunner.Events.Started, testRuns);
|
2020-03-11 18:30:43 -07:00
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
let result;
|
2020-03-11 18:30:43 -07:00
|
|
|
if (this._crashIfTestsAreFocusedOnCI && process.env.CI && this.hasFocusedTestsOrSuites()) {
|
2020-03-28 14:25:57 -07:00
|
|
|
result = new Result();
|
2020-03-12 17:32:53 -07:00
|
|
|
result.setResult(TestResult.Crashed, '"focused" tests or suites are probitted on CI');
|
2019-11-18 18:18:28 -08:00
|
|
|
} else {
|
2020-03-26 22:47:13 -07:00
|
|
|
this._runningPass = new TestPass(this, this._parallel, this._breakOnFailure);
|
2020-03-12 17:32:53 -07:00
|
|
|
let timeoutId;
|
2020-03-26 22:47:13 -07:00
|
|
|
if (totalTimeout) {
|
|
|
|
timeoutId = setTimeout(() => {
|
|
|
|
this._runningPass._terminate(TestResult.Terminated, `Total timeout of ${totalTimeout}ms reached.`, true /* force */, null /* error */);
|
|
|
|
}, totalTimeout);
|
|
|
|
}
|
2020-03-12 17:32:53 -07:00
|
|
|
try {
|
2020-03-28 14:25:57 -07:00
|
|
|
result = await this._runningPass.run(testRuns).catch(e => { console.error(e); throw e; });
|
2020-03-12 17:32:53 -07:00
|
|
|
} finally {
|
2020-03-26 22:47:13 -07:00
|
|
|
this._runningPass = null;
|
2020-03-12 17:32:53 -07:00
|
|
|
clearTimeout(timeoutId);
|
2020-01-13 15:30:16 -08:00
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
this.emit(TestRunner.Events.Finished, result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
_testsToRun() {
|
2020-03-27 15:35:14 -07:00
|
|
|
if (!this.hasFocusedTestsOrSuites())
|
2020-03-28 14:25:57 -07:00
|
|
|
return this._tests;
|
2020-03-27 15:35:14 -07:00
|
|
|
const notFocusedSuites = new Set();
|
|
|
|
// Mark parent suites of focused tests as not focused.
|
|
|
|
for (const test of this._tests) {
|
2020-03-28 14:25:57 -07:00
|
|
|
if (test.focused()) {
|
2020-03-27 15:35:14 -07:00
|
|
|
for (let suite = test.suite(); suite; suite = suite.parentSuite())
|
|
|
|
notFocusedSuites.add(suite);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Pick all tests that are focused or belong to focused suites.
|
|
|
|
const tests = [];
|
|
|
|
for (const test of this._tests) {
|
2020-03-28 14:25:57 -07:00
|
|
|
let focused = test.focused();
|
2020-03-27 15:35:14 -07:00
|
|
|
for (let suite = test.suite(); suite; suite = suite.parentSuite())
|
2020-03-28 14:25:57 -07:00
|
|
|
focused = focused || (suite.focused() && !notFocusedSuites.has(suite));
|
2020-03-27 15:35:14 -07:00
|
|
|
if (focused)
|
|
|
|
tests.push(test);
|
|
|
|
}
|
|
|
|
return tests;
|
|
|
|
}
|
|
|
|
|
2020-01-09 16:37:19 +00:00
|
|
|
async terminate() {
|
2019-11-18 18:18:28 -08:00
|
|
|
if (!this._runningPass)
|
|
|
|
return;
|
2020-03-12 17:32:53 -07:00
|
|
|
await this._runningPass._terminate(TestResult.Terminated, 'Terminated with |TestRunner.terminate()| call', true /* force */, null /* error */);
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
timeout() {
|
|
|
|
return this._timeout;
|
|
|
|
}
|
|
|
|
|
|
|
|
hasFocusedTestsOrSuites() {
|
2020-03-28 14:25:57 -07:00
|
|
|
return this._tests.some(test => test.focused()) || this._suites.some(suite => suite.focused());
|
2020-03-11 18:30:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
focusMatchingTests(fullNameRegex) {
|
|
|
|
for (const test of this._tests) {
|
2020-03-26 22:47:13 -07:00
|
|
|
if (fullNameRegex.test(test.fullName()))
|
2020-03-28 14:25:57 -07:00
|
|
|
test.setFocused(true);
|
2020-03-11 18:30:43 -07:00
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
tests() {
|
|
|
|
return this._tests.slice();
|
|
|
|
}
|
|
|
|
|
2020-03-28 14:25:57 -07:00
|
|
|
suites() {
|
|
|
|
return this._suites.slice();
|
2020-03-02 14:57:09 -08:00
|
|
|
}
|
|
|
|
|
2019-11-18 18:18:28 -08:00
|
|
|
parallel() {
|
|
|
|
return this._parallel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TestRunner.Events = {
|
|
|
|
Started: 'started',
|
|
|
|
Finished: 'finished',
|
|
|
|
TestStarted: 'teststarted',
|
|
|
|
TestFinished: 'testfinished',
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = TestRunner;
|