chore(testrunner): split TestRunner into parts (#1679)

This commit is contained in:
Dmitry Gozman 2020-04-06 17:21:42 -07:00 committed by GitHub
parent aeeac55732
commit f2b13c0e93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 859 additions and 761 deletions

View file

@ -18,7 +18,7 @@ const fs = require('fs');
const path = require('path');
const rm = require('rimraf').sync;
const GoldenUtils = require('./golden-utils');
const {Matchers} = require('../utils/testrunner/');
const {Matchers} = require('../utils/testrunner/Matchers');
const readline = require('readline');
const {TestServer} = require('../utils/testserver/');
@ -174,14 +174,13 @@ module.exports.addPlaywrightTests = ({testRunner, platform, products, playwright
state._stderr.close();
});
beforeEach(async(state, test) => {
test.output = [];
const dumpout = data => test.output.push(`\x1b[33m[pw:stdio:out]\x1b[0m ${data}`);
const dumperr = data => test.output.push(`\x1b[31m[pw:stdio:err]\x1b[0m ${data}`);
beforeEach(async(state, testRun) => {
const dumpout = data => testRun.log(`\x1b[33m[pw:stdio:out]\x1b[0m ${data}`);
const dumperr = data => testRun.log(`\x1b[31m[pw:stdio:err]\x1b[0m ${data}`);
state._stdout.on('line', dumpout);
state._stderr.on('line', dumperr);
if (dumpProtocolOnFailure)
state.browser._debugProtocol.log = data => test.output.push(`\x1b[32m[pw:protocol]\x1b[0m ${data}`);
state.browser._debugProtocol.log = data => testRun.log(`\x1b[32m[pw:protocol]\x1b[0m ${data}`);
state.tearDown = async () => {
state._stdout.off('line', dumpout);
state._stderr.off('line', dumperr);

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
const {TestRunner, Reporter} = require('../utils/testrunner/');
const TestRunner = require('../utils/testrunner/');
const utils = require('./utils');
const os = require('os');
@ -38,9 +38,13 @@ if (MAJOR_NODEJS_VERSION >= 8 && require('inspector').url()) {
const testRunner = new TestRunner({
timeout,
totalTimeout: process.env.CI ? 15 * 60 * 1000 : 0,
parallel,
breakOnFailure: process.argv.indexOf('--break-on-failure') !== -1,
installCommonHelpers: false
verbose: process.argv.includes('--verbose'),
summary: !process.argv.includes('--verbose'),
showSlowTests: process.env.CI ? 5 : 0,
showMarkedAsFailingTests: 10,
});
utils.setupTestRunner(testRunner);
@ -68,7 +72,7 @@ require('./playwright.spec.js').addPlaywrightTests({
playwrightPath: utils.projectRoot(),
products,
platform: os.platform(),
testRunner,
testRunner: testRunner.api(),
headless: !!valueFromEnv('HEADLESS', true),
slowMo: valueFromEnv('SLOW_MO', 0),
dumpProtocolOnFailure: valueFromEnv('DEBUGP', false),
@ -81,15 +85,5 @@ if (filterArgIndex !== -1) {
testRunner.focusMatchingTests(new RegExp(filter, 'i'));
}
new Reporter(testRunner, {
verbose: process.argv.includes('--verbose'),
summary: !process.argv.includes('--verbose'),
showSlowTests: process.env.CI ? 5 : 0,
showMarkedAsFailingTests: 10,
});
// await utils.initializeFlakinessDashboardIfNeeded(testRunner);
testRunner.run({ totalTimeout: process.env.CI ? 15 * 60 * 1000 : 0 }).then(result => {
process.exit(result.exitCode);
});
testRunner.run();

View file

@ -85,9 +85,8 @@ const utils = module.exports = {
const ignoredMethods = new Set(ignoredMethodsArray);
for (const [className, classType] of Object.entries(api))
traceAPICoverage(coverage, events, className, classType);
const focus = testRunner.hasFocusedTestsOrSuites();
(focus ? testRunner.fdescribe : testRunner.describe)(COVERAGE_TESTSUITE_NAME, () => {
(focus ? testRunner.fit : testRunner.it)('should call all API methods', () => {
testRunner.describe(COVERAGE_TESTSUITE_NAME, () => {
testRunner.it('should call all API methods', () => {
const missingMethods = [];
const extraIgnoredMethods = [];
for (const method of coverage.keys()) {
@ -292,34 +291,31 @@ const utils = module.exports = {
},
setupTestRunner: function(testRunner) {
testRunner.testModifier('skip', (t, condition) => condition && t.setSkipped(true));
testRunner.suiteModifier('skip', (s, condition) => condition && s.setSkipped(true));
testRunner.testModifier('fail', (t, condition) => condition && t.setExpectation(t.Expectations.Fail));
testRunner.suiteModifier('fail', (s, condition) => condition && s.setExpectation(s.Expectations.Fail));
testRunner.testModifier('slow', (t, condition) => condition && t.setTimeout(t.timeout() * 3));
testRunner.testModifier('repeat', (t, count) => t.setRepeat(count));
testRunner.suiteModifier('repeat', (s, count) => s.setRepeat(count));
testRunner.testAttribute('focus', t => t.setFocused(true));
testRunner.suiteAttribute('focus', s => s.setFocused(true));
testRunner.testAttribute('debug', t => {
t.setFocused(true);
const collector = testRunner._collector;
collector.addTestModifier('skip', (t, condition) => condition && t.setSkipped(true));
collector.addSuiteModifier('skip', (s, condition) => condition && s.setSkipped(true));
collector.addTestModifier('fail', (t, condition) => condition && t.setExpectation(t.Expectations.Fail));
collector.addSuiteModifier('fail', (s, condition) => condition && s.setExpectation(s.Expectations.Fail));
collector.addTestModifier('slow', t => t.setTimeout(t.timeout() * 3));
collector.addTestAttribute('debug', t => {
t.setTimeout(100000000);
let session;
t.before(async () => {
t.environment().beforeEach(async () => {
const inspector = require('inspector');
const readFileAsync = util.promisify(fs.readFile.bind(fs));
session = new require('inspector').Session();
session = new inspector.Session();
session.connect();
const postAsync = util.promisify(session.post.bind(session));
await postAsync('Debugger.enable');
const setBreakpointCommands = [];
const N = t.body().toString().split('\n').length;
const location = t.location();
const lines = (await readFileAsync(location.filePath, 'utf8')).split('\n');
const lines = (await readFileAsync(location.filePath(), 'utf8')).split('\n');
for (let line = 0; line < N; ++line) {
const lineNumber = line + location.lineNumber;
const lineNumber = line + location.lineNumber();
setBreakpointCommands.push(postAsync('Debugger.setBreakpointByUrl', {
url: url.pathToFileURL(location.filePath),
url: url.pathToFileURL(location.filePath()),
lineNumber,
condition: `console.log('${String(lineNumber + 1).padStart(6, ' ')} | ' + ${JSON.stringify(lines[lineNumber])})`,
}).catch(e => {}));
@ -327,14 +323,14 @@ const utils = module.exports = {
await Promise.all(setBreakpointCommands);
});
t.after(async () => {
t.environment().afterEach(async () => {
session.disconnect();
});
});
testRunner.fdescribe = testRunner.describe.focus;
testRunner.xdescribe = testRunner.describe.skip(true);
testRunner.fit = testRunner.it.focus;
testRunner.xit = testRunner.it.skip(true);
testRunner.dit = testRunner.it.debug;
testRunner.api().fdescribe = testRunner.api().describe.only;
testRunner.api().xdescribe = testRunner.api().describe.skip(true);
testRunner.api().fit = testRunner.api().it.only;
testRunner.api().xit = testRunner.api().it.skip(true);
testRunner.api().dit = testRunner.api().it.only.debug;
},
};

View file

@ -22,15 +22,15 @@ const mdBuilder = require('../MDBuilder');
const jsBuilder = require('../JSBuilder');
const GoldenUtils = require('../../../../test/golden-utils');
const {TestRunner, Reporter, Matchers} = require('../../../testrunner/');
const {Matchers} = require('../../../testrunner/Matchers');
const TestRunner = require('../../../testrunner/');
const runner = new TestRunner();
const reporter = new Reporter(runner);
const {describe, xdescribe, fdescribe} = runner;
const {it, fit, xit} = runner;
const {beforeAll, beforeEach, afterAll, afterEach} = runner;
const {describe, xdescribe, fdescribe} = runner.api();
const {it, fit, xit} = runner.api();
const {beforeAll, beforeEach, afterAll, afterEach} = runner.api();
let browserContext;
let browser;
let page;
beforeAll(async function() {
@ -60,8 +60,8 @@ describe('checkPublicAPI', function() {
runner.run();
async function testLint(state, test) {
const dirPath = path.join(__dirname, test.name());
async function testLint(state, testRun) {
const dirPath = path.join(__dirname, testRun.test().name());
const {expect} = new Matchers({
toBeGolden: GoldenUtils.compare.bind(null, dirPath, dirPath)
});
@ -74,8 +74,8 @@ async function testLint(state, test) {
expect(errors.join('\n')).toBeGolden('result.txt');
}
async function testMDBuilder(state, test) {
const dirPath = path.join(__dirname, test.name());
async function testMDBuilder(state, testRun) {
const dirPath = path.join(__dirname, testRun.test().name());
const {expect} = new Matchers({
toBeGolden: GoldenUtils.compare.bind(null, dirPath, dirPath)
});
@ -84,8 +84,8 @@ async function testMDBuilder(state, test) {
expect(serialize(documentation)).toBeGolden('result.txt');
}
async function testJSBuilder(state, test) {
const dirPath = path.join(__dirname, test.name());
async function testJSBuilder(state, testRun) {
const dirPath = path.join(__dirname, testRun.test().name());
const {expect} = new Matchers({
toBeGolden: GoldenUtils.compare.bind(null, dirPath, dirPath)
});

View file

@ -16,14 +16,13 @@
const {runCommands, ensureReleasedAPILinks} = require('.');
const Source = require('../Source');
const {TestRunner, Reporter, Matchers} = require('../../testrunner/');
const TestRunner = require('../../testrunner/');
const runner = new TestRunner();
new Reporter(runner);
const {describe, xdescribe, fdescribe} = runner;
const {it, fit, xit} = runner;
const {beforeAll, beforeEach, afterAll, afterEach} = runner;
const {expect} = new Matchers();
const {describe, xdescribe, fdescribe} = runner.api();
const {it, fit, xit} = runner.api();
const {beforeAll, beforeEach, afterAll, afterEach} = runner.api();
const {expect} = runner.api();
describe('ensureReleasedAPILinks', function() {
it('should work with non-release version', function() {

View file

@ -49,6 +49,8 @@ class Location {
return this._fileName + ':' + this._lineNumber + ':' + this._columnNumber;
}
// TODO: static getCallerLocationIn(glob) vs getCallerLocationIgnoring(glob).
static getCallerLocation(filename) {
const error = new Error();
const stackFrames = error.stack.split('\n').slice(1);

View file

@ -19,7 +19,7 @@ const colors = require('colors/safe');
const {MatchError} = require('./Matchers.js');
class Reporter {
constructor(runner, options = {}) {
constructor(delegate, options = {}) {
const {
showSlowTests = 3,
showMarkedAsFailingTests = Infinity,
@ -27,27 +27,25 @@ class Reporter {
summary = true,
} = options;
this._filePathToLines = new Map();
this._runner = runner;
this._delegate = delegate;
this._showSlowTests = showSlowTests;
this._showMarkedAsFailingTests = showMarkedAsFailingTests;
this._verbose = verbose;
this._summary = summary;
this._testCounter = 0;
runner.setDelegate(this);
}
onStarted(testRuns) {
this._testCounter = 0;
this._timestamp = Date.now();
const allTests = this._runner.tests();
if (!this._runner.hasFocusedTestsOrSuites()) {
console.log(`Running all ${colors.yellow(testRuns.length)} tests on ${colors.yellow(this._runner.parallel())} worker${this._runner.parallel() > 1 ? 's' : ''}:\n`);
if (!this._delegate.hasFocusedTestsOrSuites()) {
console.log(`Running all ${colors.yellow(testRuns.length)} tests on ${colors.yellow(this._delegate.parallel())} worker${this._delegate.parallel() > 1 ? 's' : ''}:\n`);
} else {
console.log(`Running ${colors.yellow(testRuns.length)} focused tests out of total ${colors.yellow(allTests.length)} on ${colors.yellow(this._runner.parallel())} worker${this._runner.parallel() > 1 ? 's' : ''}`);
console.log(`Running ${colors.yellow(testRuns.length)} focused tests out of total ${colors.yellow(this._delegate.testCount())} on ${colors.yellow(this._delegate.parallel())} worker${this._delegate.parallel() > 1 ? 's' : ''}`);
console.log('');
const focusedEntities = [
...this._runner.suites().filter(suite => suite.focused()),
...this._runner.tests().filter(test => test.focused()),
...this._delegate.focusedSuites(),
...this._delegate.focusedTests(),
];
if (focusedEntities.length) {
console.log('Focused Suites and Tests:');
@ -174,7 +172,7 @@ class Reporter {
_printVerboseTestRunResult(resultIndex, testRun) {
const test = testRun.test();
let prefix = `${resultIndex})`;
if (this._runner.parallel() > 1)
if (this._delegate.parallel() > 1)
prefix += ' ' + colors.gray(`[worker = ${testRun.workerId()}]`);
if (testRun.result() === 'ok') {
console.log(`${prefix} ${colors.green('[OK]')} ${test.fullName()} (${formatLocation(test.location())})`);
@ -187,9 +185,10 @@ class Reporter {
console.log(`${prefix} ${colors.yellow('[MARKED AS FAILING]')} ${test.fullName()} (${formatLocation(test.location())})`);
} else if (testRun.result() === 'timedout') {
console.log(`${prefix} ${colors.red(`[TIMEOUT ${test.timeout()}ms]`)} ${test.fullName()} (${formatLocation(test.location())})`);
if (testRun.output) {
const output = testRun.output();
if (output.length) {
console.log(' Output:');
for (const line of testRun.output)
for (const line of output)
console.log(' ' + line);
}
} else if (testRun.result() === 'failed') {
@ -235,9 +234,10 @@ class Reporter {
console.log(padLines(stack, 4));
}
}
if (testRun.output) {
const output = testRun.output();
if (output.length) {
console.log(' Output:');
for (const line of testRun.output)
for (const line of output)
console.log(' ' + line);
}
}

219
utils/testrunner/Test.js Normal file
View file

@ -0,0 +1,219 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* 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 Location = require('./Location');
const TestExpectation = {
Ok: 'ok',
Fail: 'fail',
};
function createHook(callback, name) {
const location = Location.getCallerLocation(__filename);
return { name, body: callback, location };
}
class Environment {
constructor(name, parentEnvironment = null) {
this._parentEnvironment = parentEnvironment;
this._name = name;
this._hooks = [];
}
parentEnvironment() {
return this._parentEnvironment;
}
name() {
return this._name;
}
beforeEach(callback) {
this._hooks.push(createHook(callback, 'beforeEach'));
return this;
}
afterEach(callback) {
this._hooks.push(createHook(callback, 'afterEach'));
return this;
}
beforeAll(callback) {
this._hooks.push(createHook(callback, 'beforeAll'));
return this;
}
afterAll(callback) {
this._hooks.push(createHook(callback, 'afterAll'));
return this;
}
hooks(name) {
return this._hooks.filter(hook => !name || hook.name === name);
}
isEmpty() {
return !this._hooks.length;
}
}
class Test {
constructor(suite, name, callback, location) {
this._suite = suite;
this._name = name;
this._fullName = (suite.fullName() + ' ' + name).trim();
this._skipped = false;
this._expectation = TestExpectation.Ok;
this._body = callback;
this._location = location;
this._timeout = 100000000;
this._defaultEnvironment = new Environment(this._fullName);
this._environments = [this._defaultEnvironment];
this.Expectations = { ...TestExpectation };
}
suite() {
return this._suite;
}
name() {
return this._name;
}
fullName() {
return this._fullName;
}
location() {
return this._location;
}
body() {
return this._body;
}
skipped() {
return this._skipped;
}
setSkipped(skipped) {
this._skipped = skipped;
return this;
}
timeout() {
return this._timeout;
}
setTimeout(timeout) {
this._timeout = timeout;
return this;
}
expectation() {
return this._expectation;
}
setExpectation(expectation) {
this._expectation = expectation;
return this;
}
environment() {
return this._defaultEnvironment;
}
addEnvironment(environment) {
this._environments.push(environment);
return this;
}
removeEnvironment(environment) {
const index = this._environments.indexOf(environment);
if (index === -1)
throw new Error(`Environment "${environment.name()}" cannot be removed because it was not added to the suite "${this.fullName()}"`);
this._environments.splice(index, 1);
return this;
}
}
class Suite {
constructor(parentSuite, name, location) {
this._parentSuite = parentSuite;
this._name = name;
this._fullName = (parentSuite ? parentSuite.fullName() + ' ' + name : name).trim();
this._location = location;
this._skipped = false;
this._expectation = TestExpectation.Ok;
this._defaultEnvironment = new Environment(this._fullName);
this._environments = [this._defaultEnvironment];
this.Expectations = { ...TestExpectation };
}
parentSuite() {
return this._parentSuite;
}
name() {
return this._name;
}
fullName() {
return this._fullName;
}
skipped() {
return this._skipped;
}
setSkipped(skipped) {
this._skipped = skipped;
return this;
}
location() {
return this._location;
}
expectation() {
return this._expectation;
}
setExpectation(expectation) {
this._expectation = expectation;
return this;
}
environment() {
return this._defaultEnvironment;
}
addEnvironment(environment) {
this._environments.push(environment);
return this;
}
removeEnvironment(environment) {
const index = this._environments.indexOf(environment);
if (index === -1)
throw new Error(`Environment "${environment.name()}" cannot be removed because it was not added to the suite "${this.fullName()}"`);
this._environments.splice(index, 1);
return this;
}
}
module.exports = { TestExpectation, Environment, Test, Suite };

View file

@ -0,0 +1,191 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* 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 Location = require('./Location');
const { Test, Suite } = require('./Test');
const { TestRun } = require('./TestRunner');
class FocusedFilter {
constructor() {
this._focused = new Set();
}
markFocused(testOrSuite) {
this._focused.add(testOrSuite);
}
hasFocusedTestsOrSuites() {
return !!this._focused.size;
}
focusedTests(tests) {
return tests.filter(test => this._focused.has(test));
}
focusedSuites(suites) {
return suites.filter(suite => this._focused.has(suite));
}
filter(tests) {
if (!this.hasFocusedTestsOrSuites())
return tests;
const ignoredSuites = new Set();
for (const test of tests) {
if (this._focused.has(test)) {
// Focused tests should be run even if skipped.
test.setSkipped(false);
// TODO: remove next line once we run failing tests.
test.setExpectation(test.Expectations.Ok);
}
for (let suite = test.suite(); suite; suite = suite.parentSuite()) {
if (this._focused.has(suite)) {
// Focused suites should be run even if skipped.
suite.setSkipped(false);
// TODO: remove next line once we run failing tests.
suite.setExpectation(suite.Expectations.Ok);
}
// Mark parent suites of focused tests as ignored.
if (this._focused.has(test))
ignoredSuites.add(suite);
}
}
// Pick all tests that are focused or belong to focused suites.
const result = [];
for (const test of tests) {
let focused = this._focused.has(test);
for (let suite = test.suite(); suite; suite = suite.parentSuite())
focused = focused || (this._focused.has(suite) && !ignoredSuites.has(suite));
if (focused)
result.push(test);
}
return result;
}
}
class Repeater {
constructor() {
this._repeatCount = new Map();
}
repeat(testOrSuite, count) {
this._repeatCount.set(testOrSuite, count);
}
_get(testOrSuite) {
const repeat = this._repeatCount.get(testOrSuite);
return repeat === undefined ? 1 : repeat;
}
createTestRuns(tests) {
const testRuns = [];
for (const test of tests) {
let repeat = this._get(test);
for (let suite = test.suite(); suite; suite = suite.parentSuite())
repeat *= this._get(suite);
for (let i = 0; i < repeat; i++)
testRuns.push(new TestRun(test));
}
return testRuns;
}
}
function specBuilder(modifiers, attributes, specCallback) {
function builder(specs) {
return new Proxy((...args) => specCallback(specs, ...args), {
get: (obj, prop) => {
if (modifiers.has(prop))
return (...args) => builder([...specs, { callback: modifiers.get(prop), args }]);
if (attributes.has(prop))
return builder([...specs, { callback: attributes.get(prop), args: [] }]);
return obj[prop];
},
});
}
return builder([]);
}
class TestCollector {
constructor(options = {}) {
let { timeout = 10 * 1000 } = options;
if (timeout === 0)
timeout = 100000000; // Inifinite timeout.
this._tests = [];
this._suites = [];
this._suiteModifiers = new Map();
this._suiteAttributes = new Map();
this._testModifiers = new Map();
this._testAttributes = new Map();
this._api = {};
this._currentSuite = new Suite(null, '', new Location());
this._api.describe = specBuilder(this._suiteModifiers, this._suiteAttributes, (specs, name, suiteCallback, ...suiteArgs) => {
const location = Location.getCallerLocation(__filename);
const suite = new Suite(this._currentSuite, name, location);
for (const { callback, args } of specs)
callback(suite, ...args);
this._currentSuite = suite;
suiteCallback(...suiteArgs);
this._suites.push(suite);
this._currentSuite = suite.parentSuite();
});
this._api.it = specBuilder(this._testModifiers, this._testAttributes, (specs, name, testCallback) => {
const location = Location.getCallerLocation(__filename);
const test = new Test(this._currentSuite, name, testCallback, location);
test.setTimeout(timeout);
for (const { callback, args } of specs)
callback(test, ...args);
this._tests.push(test);
});
this._api.beforeAll = callback => this._currentSuite.environment().beforeAll(callback);
this._api.beforeEach = callback => this._currentSuite.environment().beforeEach(callback);
this._api.afterAll = callback => this._currentSuite.environment().afterAll(callback);
this._api.afterEach = callback => this._currentSuite.environment().afterEach(callback);
}
addTestModifier(name, callback) {
this._testModifiers.set(name, callback);
}
addTestAttribute(name, callback) {
this._testAttributes.set(name, callback);
}
addSuiteModifier(name, callback) {
this._suiteModifiers.set(name, callback);
}
addSuiteAttribute(name, callback) {
this._suiteAttributes.set(name, callback);
}
api() {
return this._api;
}
tests() {
return this._tests;
}
suites() {
return this._suites;
}
}
module.exports = { TestCollector, specBuilder, FocusedFilter, Repeater };

View file

@ -15,11 +15,10 @@
* limitations under the License.
*/
const {SourceMapSupport} = require('./SourceMapSupport');
const { SourceMapSupport } = require('./SourceMapSupport');
const debug = require('debug');
const Location = require('./Location');
const { TestExpectation } = require('./Test');
const INFINITE_TIMEOUT = 100000000;
const TimeoutError = new Error('Timeout');
const TerminatedError = new Error('Terminated');
@ -37,11 +36,6 @@ function runUserCallback(callback, timeout, args) {
return { promise, terminate };
}
const TestExpectation = {
Ok: 'ok',
Fail: 'fail',
};
const TestResult = {
Ok: 'ok',
MarkedAsFailing: 'markedAsFailing', // User marked as failed
@ -52,241 +46,6 @@ const TestResult = {
Crashed: 'crashed', // If testrunner crashed due to this test
};
function createHook(callback, name) {
const location = Location.getCallerLocation(__filename);
return { name, body: callback, location };
}
class Test {
constructor(suite, name, callback, location) {
this._suite = suite;
this._name = name;
this._fullName = (suite.fullName() + ' ' + name).trim();
this._skipped = false;
this._focused = false;
this._expectation = TestExpectation.Ok;
this._body = callback;
this._location = location;
this._timeout = INFINITE_TIMEOUT;
this._repeat = 1;
this._hooks = [];
this._environments = [];
this.Expectations = { ...TestExpectation };
}
suite() {
return this._suite;
}
name() {
return this._name;
}
fullName() {
return this._fullName;
}
location() {
return this._location;
}
body() {
return this._body;
}
skipped() {
return this._skipped;
}
setSkipped(skipped) {
this._skipped = skipped;
return this;
}
focused() {
return this._focused;
}
setFocused(focused) {
this._focused = focused;
return this;
}
timeout() {
return this._timeout;
}
setTimeout(timeout) {
this._timeout = timeout;
return this;
}
expectation() {
return this._expectation;
}
setExpectation(expectation) {
this._expectation = expectation;
return this;
}
repeat() {
return this._repeat;
}
setRepeat(repeat) {
this._repeat = repeat;
return this;
}
before(callback) {
this._hooks.push(createHook(callback, 'before'));
return this;
}
after(callback) {
this._hooks.push(createHook(callback, 'after'));
return this;
}
hooks(name) {
return this._hooks.filter(hook => !name || hook.name === name);
}
addEnvironment(environment) {
const parents = new Set();
for (let parent = environment; !(parent instanceof Suite); parent = parent.parentEnvironment())
parents.add(parent);
for (const env of this._environments) {
for (let parent = env; !(parent instanceof Suite); parent = parent.parentEnvironment()) {
if (parents.has(parent))
throw new Error(`Cannot use environments "${environment.name()}" and "${env.name()}" that share a parent environment "${parent.fullName()}" in test "${this.fullName()}"`);
}
}
const environmentParentSuite = environment.parentSuite();
for (let suite = this.suite(); suite; suite = suite.parentSuite()) {
if (suite === environmentParentSuite) {
this._environments.push(environment);
return this;
}
}
throw new Error(`Cannot use environment "${environment.name()}" from suite "${environment.parentSuite().fullName()}" in unrelated test "${this.fullName()}"`);
}
removeEnvironment(environment) {
const index = this._environments.indexOf(environment);
if (index === -1)
throw new Error(`Environment "${environment.name()}" cannot be removed because it was not added to the test "${test.fullName()}"`);
this._environments.splice(index, 1);
return this;
}
}
class Environment {
constructor(parentEnvironment, name, location) {
this._parentEnvironment = parentEnvironment;
this._parentSuite = parentEnvironment;
if (parentEnvironment && !(parentEnvironment instanceof Suite))
this._parentSuite = parentEnvironment.parentSuite();
this._name = name;
this._fullName = (parentEnvironment ? parentEnvironment.fullName() + ' ' + name : name).trim();
this._location = location;
this._hooks = [];
}
parentEnvironment() {
return this._parentEnvironment;
}
parentSuite() {
return this._parentSuite;
}
name() {
return this._name;
}
fullName() {
return this._fullName;
}
beforeEach(callback) {
this._hooks.push(createHook(callback, 'beforeEach'));
return this;
}
afterEach(callback) {
this._hooks.push(createHook(callback, 'afterEach'));
return this;
}
beforeAll(callback) {
this._hooks.push(createHook(callback, 'beforeAll'));
return this;
}
afterAll(callback) {
this._hooks.push(createHook(callback, 'afterAll'));
return this;
}
hooks(name) {
return this._hooks.filter(hook => !name || hook.name === name);
}
}
class Suite extends Environment {
constructor(parentSuite, name, location) {
super(parentSuite, name, location);
this._skipped = false;
this._focused = false;
this._expectation = TestExpectation.Ok;
this._repeat = 1;
this.Expectations = { ...TestExpectation };
}
skipped() {
return this._skipped;
}
setSkipped(skipped) {
this._skipped = skipped;
return this;
}
focused() {
return this._focused;
}
setFocused(focused) {
this._focused = focused;
return this;
}
location() {
return this._location;
}
expectation() {
return this._expectation;
}
setExpectation(expectation) {
this._expectation = expectation;
return this;
}
repeat() {
return this._repeat;
}
setRepeat(repeat) {
this._repeat = repeat;
return this;
}
}
class TestRun {
constructor(test) {
this._test = test;
@ -295,6 +54,7 @@ class TestRun {
this._startTimestamp = 0;
this._endTimestamp = 0;
this._workerId = null;
this._output = [];
}
finished() {
@ -328,6 +88,14 @@ class TestRun {
workerId() {
return this._workerId;
}
log(log) {
this._output.push(log);
}
output() {
return this._output;
}
}
class Result {
@ -397,9 +165,9 @@ class TestWorker {
this._runs.push(testRun);
const test = testRun.test();
let skipped = test.skipped() && !test.focused();
let skipped = test.skipped();
for (let suite = test.suite(); suite; suite = suite.parentSuite())
skipped = skipped || (suite.skipped() && !suite.focused());
skipped = skipped || suite.skipped();
if (skipped) {
await this._willStartTestRun(testRun);
testRun._result = TestResult.Skipped;
@ -410,7 +178,7 @@ class TestWorker {
let expectedToFail = test.expectation() === TestExpectation.Fail;
for (let suite = test.suite(); suite; suite = suite.parentSuite())
expectedToFail = expectedToFail || (suite.expectation() === TestExpectation.Fail);
if (expectedToFail && !test.focused()) {
if (expectedToFail) {
await this._willStartTestRun(testRun);
testRun._result = TestResult.MarkedAsFailing;
await this._didFinishTestRun(testRun);
@ -418,15 +186,20 @@ class TestWorker {
}
const environmentStack = [];
for (let suite = test.suite(); suite; suite = suite.parentSuite())
environmentStack.push(suite);
environmentStack.reverse();
for (const environment of test._environments) {
const insert = [];
for (let parent = environment; !(parent instanceof Suite); parent = parent.parentEnvironment())
insert.push(parent);
environmentStack.splice(environmentStack.indexOf(environment.parentSuite()) + 1, 0, ...insert.reverse());
function appendEnvironment(e) {
while (e) {
if (!e.isEmpty())
environmentStack.push(e);
e = e.parentEnvironment();
}
}
for (const environment of test._environments.slice().reverse())
appendEnvironment(environment);
for (let suite = test.suite(); suite; suite = suite.parentSuite()) {
for (const environment of suite._environments.slice().reverse())
appendEnvironment(environment);
}
environmentStack.reverse();
let common = 0;
while (common < environmentStack.length && this._environmentStack[common] === environmentStack[common])
@ -437,7 +210,7 @@ class TestWorker {
return;
const environment = this._environmentStack.pop();
for (const hook of environment.hooks('afterAll')) {
if (!await this._runHook(testRun, hook, environment.fullName()))
if (!await this._runHook(testRun, hook, environment.name()))
return;
}
}
@ -447,7 +220,7 @@ class TestWorker {
const environment = environmentStack[this._environmentStack.length];
this._environmentStack.push(environment);
for (const hook of environment.hooks('beforeAll')) {
if (!await this._runHook(testRun, hook, environment.fullName()))
if (!await this._runHook(testRun, hook, environment.name()))
return;
}
}
@ -461,14 +234,12 @@ class TestWorker {
await this._willStartTestRun(testRun);
for (const environment of this._environmentStack) {
for (const hook of environment.hooks('beforeEach'))
await this._runHook(testRun, hook, environment.fullName(), true);
await this._runHook(testRun, hook, environment.name(), true);
}
for (const hook of test.hooks('before'))
await this._runHook(testRun, hook, test.fullName(), true);
if (!testRun._error && !this._markTerminated(testRun)) {
await this._willStartTestBody(testRun);
const { promise, terminate } = runUserCallback(test.body(), test.timeout(), [this._state, test]);
const { promise, terminate } = runUserCallback(test.body(), test.timeout(), [this._state, testRun]);
this._runningTestTerminate = terminate;
testRun._error = await promise;
this._runningTestTerminate = null;
@ -485,19 +256,17 @@ class TestWorker {
await this._didFinishTestBody(testRun);
}
for (const hook of test.hooks('after'))
await this._runHook(testRun, hook, test.fullName(), true);
for (const environment of this._environmentStack.slice().reverse()) {
for (const hook of environment.hooks('afterEach'))
await this._runHook(testRun, hook, environment.fullName(), true);
await this._runHook(testRun, hook, environment.name(), true);
}
await this._didFinishTestRun(testRun);
}
async _runHook(testRun, hook, fullName, passTest = false) {
async _runHook(testRun, hook, fullName, passTestRun = false) {
await this._willStartHook(hook, fullName);
const timeout = this._testRunner._timeout;
const { promise, terminate } = runUserCallback(hook.body, timeout, passTest ? [this._state, testRun.test()] : [this._state]);
const timeout = this._testRunner._hookTimeout;
const { promise, terminate } = runUserCallback(hook.body, timeout, passTestRun ? [this._state, testRun] : [this._state]);
this._runningHookTerminate = terminate;
let error = await promise;
this._runningHookTerminate = null;
@ -569,198 +338,84 @@ class TestWorker {
while (this._environmentStack.length > 0) {
const environment = this._environmentStack.pop();
for (const hook of environment.hooks('afterAll'))
await this._runHook(null, hook, environment.fullName());
await this._runHook(null, hook, environment.name());
}
}
}
class TestRunner {
constructor(options = {}) {
const {
timeout = 10 * 1000, // Default timeout is 10 seconds.
parallel = 1,
breakOnFailure = false,
crashIfTestsAreFocusedOnCI = true,
installCommonHelpers = true,
} = options;
this._crashIfTestsAreFocusedOnCI = crashIfTestsAreFocusedOnCI;
constructor() {
this._sourceMapSupport = new SourceMapSupport();
this._rootSuite = new Suite(null, '', new Location());
this._currentEnvironment = this._rootSuite;
this._tests = [];
this._suites = [];
this._timeout = timeout === 0 ? INFINITE_TIMEOUT : timeout;
this._parallel = parallel;
this._breakOnFailure = breakOnFailure;
this._suiteModifiers = new Map();
this._suiteAttributes = new Map();
this._testModifiers = new Map();
this._testAttributes = new Map();
this._nextWorkerId = 1;
this._workers = [];
this._terminating = false;
this._result = null;
}
async run(testRuns, options = {}) {
const {
parallel = 1,
breakOnFailure = false,
hookTimeout = 10 * 1000,
totalTimeout = 0,
onStarted = async (testRuns) => {},
onFinished = async (result) => {},
onTestRunStarted = async(testRun) => {},
onTestRunFinished = async (testRun) => {},
} = options;
this._breakOnFailure = breakOnFailure;
this._hookTimeout = hookTimeout;
this._delegate = {
async onStarted(testRuns) {},
async onFinished(result) {},
async onTestRunStarted(testRun) {},
async onTestRunFinished(testRun) {},
onStarted,
onFinished,
onTestRunStarted,
onTestRunFinished
};
this.beforeAll = (callback) => this._currentEnvironment.beforeAll(callback);
this.beforeEach = (callback) => this._currentEnvironment.beforeEach(callback);
this.afterAll = (callback) => this._currentEnvironment.afterAll(callback);
this.afterEach = (callback) => this._currentEnvironment.afterEach(callback);
this.describe = this._suiteBuilder([]);
this.it = this._testBuilder([]);
this.environment = (name, callback) => {
const location = Location.getCallerLocation(__filename);
const environment = new Environment(this._currentEnvironment, name, location);
this._currentEnvironment = environment;
callback();
this._currentEnvironment = environment.parentEnvironment();
return environment;
};
this.Expectations = { ...TestExpectation };
if (installCommonHelpers) {
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: [] }]);
}
}
_suiteBuilder(callbacks) {
return new Proxy((name, callback, ...suiteArgs) => {
if (!(this._currentEnvironment instanceof Suite))
throw new Error(`Cannot define a suite inside an environment`);
const location = Location.getCallerLocation(__filename);
const suite = new Suite(this._currentEnvironment, name, location);
for (const { callback, args } of callbacks)
callback(suite, ...args);
this._currentEnvironment = suite;
callback(...suiteArgs);
this._suites.push(suite);
this._currentEnvironment = suite.parentSuite();
return suite;
}, {
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];
},
});
}
_testBuilder(callbacks) {
return new Proxy((name, callback) => {
if (!(this._currentEnvironment instanceof Suite))
throw new Error(`Cannot define a test inside an environment`);
const location = Location.getCallerLocation(__filename);
const test = new Test(this._currentEnvironment, name, callback, location);
test.setTimeout(this._timeout);
for (const { callback, args } of callbacks)
callback(test, ...args);
this._tests.push(test);
return test;
}, {
get: (obj, prop) => {
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: [] }]);
return obj[prop];
},
});
}
testModifier(name, callback) {
this._testModifiers.set(name, callback);
}
testAttribute(name, callback) {
this._testAttributes.set(name, callback);
}
suiteModifier(name, callback) {
this._suiteModifiers.set(name, callback);
}
suiteAttribute(name, callback) {
this._suiteAttributes.set(name, callback);
}
setDelegate(delegate) {
this._delegate = delegate;
}
async run(options = {}) {
const { totalTimeout = 0 } = options;
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._result = new Result();
this._result.runs = testRuns;
await this._delegate.onStarted(testRuns);
if (this._crashIfTestsAreFocusedOnCI && process.env.CI && this.hasFocusedTestsOrSuites()) {
await this._delegate.onStarted([]);
this._result.setResult(TestResult.Crashed, '"focused" tests or suites are probitted on CI');
await this._delegate.onFinished(this._result);
} else {
await this._delegate.onStarted(testRuns);
this._result.runs = testRuns;
let timeoutId;
if (totalTimeout) {
timeoutId = setTimeout(() => {
this._terminate(TestResult.Terminated, `Total timeout of ${totalTimeout}ms reached.`, true /* force */, null /* error */);
}, totalTimeout);
}
let timeoutId;
if (totalTimeout) {
timeoutId = setTimeout(() => {
this._terminate(TestResult.Terminated, `Total timeout of ${totalTimeout}ms reached.`, true /* force */, null /* error */);
}, totalTimeout);
}
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'),
createTermination.call(this, 'uncaughtException', TestResult.Crashed, 'UNHANDLED ERROR'),
];
for (const termination of terminations)
process.on(termination.event, termination.handler);
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'),
createTermination.call(this, 'uncaughtException', TestResult.Crashed, 'UNHANDLED ERROR'),
];
for (const termination of terminations)
process.on(termination.event, termination.handler);
const workerCount = Math.min(parallel, testRuns.length);
const workerPromises = [];
for (let i = 0; i < workerCount; ++i) {
const initialTestRunIndex = i * Math.floor(testRuns.length / workerCount);
workerPromises.push(this._runWorker(initialTestRunIndex, testRuns, i));
}
await Promise.all(workerPromises);
const parallel = Math.min(this._parallel, testRuns.length);
const workerPromises = [];
for (let i = 0; i < parallel; ++i) {
const initialTestRunIndex = i * Math.floor(testRuns.length / parallel);
workerPromises.push(this._runWorker(initialTestRunIndex, testRuns, i));
}
await Promise.all(workerPromises);
for (const termination of terminations)
process.removeListener(termination.event, termination.handler);
for (const termination of terminations)
process.removeListener(termination.event, termination.handler);
if (testRuns.some(run => run.isFailure()))
this._result.setResult(TestResult.Failed, '');
if (testRuns.some(run => run.isFailure()))
this._result.setResult(TestResult.Failed, '');
clearTimeout(timeoutId);
await this._delegate.onFinished(this._result);
clearTimeout(timeoutId);
await this._delegate.onFinished(this._result);
function createTermination(event, result, message) {
return {
event,
message,
handler: error => this._terminate(result, message, event === 'SIGTERM', event.startsWith('SIG') ? null : error),
};
}
function createTermination(event, result, message) {
return {
event,
message,
handler: error => this._terminate(result, message, event === 'SIGTERM', event.startsWith('SIG') ? null : error),
};
}
const result = this._result;
@ -818,61 +473,11 @@ class TestRunner {
}
}
_testsToRun() {
if (!this.hasFocusedTestsOrSuites())
return this._tests;
const notFocusedSuites = new Set();
// Mark parent suites of focused tests as not focused.
for (const test of this._tests) {
if (test.focused()) {
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) {
let focused = test.focused();
for (let suite = test.suite(); suite; suite = suite.parentSuite())
focused = focused || (suite.focused() && !notFocusedSuites.has(suite));
if (focused)
tests.push(test);
}
return tests;
}
async terminate() {
if (!this._result)
return;
await this._terminate(TestResult.Terminated, 'Terminated with |TestRunner.terminate()| call', true /* force */, null /* error */);
}
timeout() {
return this._timeout;
}
hasFocusedTestsOrSuites() {
return this._tests.some(test => test.focused()) || this._suites.some(suite => suite.focused());
}
focusMatchingTests(fullNameRegex) {
for (const test of this._tests) {
if (fullNameRegex.test(test.fullName()))
test.setFocused(true);
}
}
tests() {
return this._tests.slice();
}
suites() {
return this._suites.slice();
}
parallel() {
return this._parallel;
}
}
module.exports = TestRunner;
module.exports = { TestRunner, TestRun, TestResult, Result };

View file

@ -14,8 +14,125 @@
* limitations under the License.
*/
const TestRunner = require('./TestRunner');
const { TestRunner, Result, TestResult } = require('./TestRunner');
const { TestCollector, FocusedFilter, Repeater } = require('./TestCollector');
const Reporter = require('./Reporter');
const {Matchers} = require('./Matchers');
const { Matchers } = require('./Matchers');
module.exports = { TestRunner, Reporter, Matchers };
class DefaultTestRunner {
constructor(options = {}) {
const {
// Our options.
crashIfTestsAreFocusedOnCI = true,
exit = true,
reporter = true,
// Collector options.
timeout,
// Runner options.
parallel = 1,
breakOnFailure,
totalTimeout,
hookTimeout = timeout,
// Reporting options.
showSlowTests,
showMarkedAsFailingTests,
verbose,
summary,
} = options;
this._crashIfTestsAreFocusedOnCI = crashIfTestsAreFocusedOnCI;
this._exit = exit;
this._parallel = parallel;
this._breakOnFailure = breakOnFailure;
this._totalTimeout = totalTimeout;
this._hookTimeout = hookTimeout;
this._needReporter = reporter;
this._showSlowTests = showSlowTests;
this._showMarkedAsFailingTests = showMarkedAsFailingTests;
this._verbose = verbose;
this._summary = summary;
this._filter = new FocusedFilter();
this._repeater = new Repeater();
this._collector = new TestCollector({ timeout });
this._api = {
...this._collector.api(),
expect: new Matchers().expect,
};
this._collector.addSuiteAttribute('only', s => this._filter.markFocused(s));
this._collector.addSuiteAttribute('skip', s => s.setSkipped(true));
this._collector.addSuiteModifier('repeat', (s, count) => this._repeater.repeat(s, count));
this._collector.addTestAttribute('only', t => this._filter.markFocused(t));
this._collector.addTestAttribute('skip', t => t.setSkipped(true));
this._collector.addTestAttribute('todo', t => t.setSkipped(true));
this._collector.addTestAttribute('slow', t => t.setTimeout(t.timeout() * 3));
this._collector.addTestModifier('repeat', (t, count) => this._repeater.repeat(t, count));
this._api.fdescribe = this._api.describe.only;
this._api.xdescribe = this._api.describe.skip;
this._api.fit = this._api.it.only;
this._api.xit = this._api.it.skip;
}
api() {
return this._api;
}
focusMatchingTests(fullNameRegex) {
for (const test of this._collector.tests()) {
if (fullNameRegex.test(test.fullName()))
this._filter.markFocused(test);
}
}
async run() {
let reporter = null;
if (this._needReporter) {
const reporterDelegate = {
focusedSuites: () => this._filter.focusedTests(this._collector.suites()),
focusedTests: () => this._filter.focusedSuites(this._collector.tests()),
hasFocusedTestsOrSuites: () => this._filter.hasFocusedTestsOrSuites(),
parallel: () => this._parallel,
testCount: () => this._collector.tests().length,
};
const reporterOptions = {
showSlowTests: this._showSlowTests,
showMarkedAsFailingTests: this._showMarkedAsFailingTests,
verbose: this._verbose,
summary: this._summary,
};
reporter = new Reporter(reporterDelegate, reporterOptions);
}
if (this._crashIfTestsAreFocusedOnCI && process.env.CI && this._filter.hasFocusedTestsOrSuites()) {
if (reporter)
await reporter.onStarted([]);
const result = new Result();
result.setResult(TestResult.Crashed, '"focused" tests or suites are probitted on CI');
if (reporter)
await reporter.onFinished(result);
if (this._exit)
process.exit(result.exitCode);
return result;
}
const testRuns = this._repeater.createTestRuns(this._filter.filter(this._collector.tests()));
const testRunner = new TestRunner();
const result = await testRunner.run(testRuns, {
parallel: this._parallel,
breakOnFailure: this._breakOnFailure,
totalTimeout: this._totalTimeout,
hookTimeout: this._hookTimeout,
onStarted: (...args) => reporter && reporter.onStarted(...args),
onFinished: (...args) => reporter && reporter.onFinished(...args),
onTestRunStarted: (...args) => reporter && reporter.onTestRunStarted(...args),
onTestRunFinished: (...args) => reporter && reporter.onTestRunFinished(...args),
});
if (this._exit)
process.exit(result.exitCode);
return result;
}
}
module.exports = DefaultTestRunner;

View file

@ -1,14 +1,4 @@
const {TestRunner, Matchers, Reporter} = require('..');
const TestRunner = require('..');
const testRunner = new TestRunner();
const {expect} = new Matchers();
require('./testrunner.spec.js').addTests({testRunner, expect});
new Reporter(testRunner, {
verbose: process.argv.includes('--verbose'),
summary: true,
showSlowTests: 0,
});
require('./testrunner.spec.js').addTests(testRunner.api());
testRunner.run();

View file

@ -1,25 +1,70 @@
const {TestRunner} = require('..');
const { TestRunner } = require('../TestRunner');
const { TestCollector, FocusedFilter, Repeater } = require('../TestCollector');
const { TestExpectation, Environment } = require('../Test');
function newTestRunner(options) {
return new TestRunner({
crashIfTestsAreFocusedOnCI: false,
...options,
});
class Runner {
constructor(options = {}) {
this._options = options;
this._filter = new FocusedFilter();
this._repeater = new Repeater();
this._collector = new TestCollector(options);
this._collector.addSuiteAttribute('only', s => this._filter.markFocused(s));
this._collector.addTestAttribute('only', t => this._filter.markFocused(t));
this._collector.addSuiteAttribute('skip', s => s.setSkipped(true));
this._collector.addTestAttribute('skip', t => t.setSkipped(true));
this._collector.addTestAttribute('fail', t => t.setExpectation(t.Expectations.Fail));
this._collector.addSuiteModifier('repeat', (s, count) => this._repeater.repeat(s, count));
this._collector.addTestModifier('repeat', (t, count) => this._repeater.repeat(t, count));
const api = this._collector.api();
for (const [key, value] of Object.entries(api))
this[key] = value;
this.fdescribe = api.describe.only;
this.xdescribe = api.describe.skip;
this.fit = api.it.only;
this.xit = api.it.skip;
this.Expectations = { ...TestExpectation };
}
createTestRuns() {
return this._repeater.createTestRuns(this._filter.filter(this._collector.tests()));
}
run() {
this._testRunner = new TestRunner();
return this._testRunner.run(this.createTestRuns(), this._options);
}
tests() {
return this._collector.tests();
}
focusedTests() {
return this._filter.focusedTests(this._collector.tests());
}
suites() {
return this._collector.suites();
}
focusedSuites() {
return this._filter.focusedSuites(this._collector.suites());
}
terminate() {
this._testRunner.terminate();
}
}
module.exports.addTests = function({testRunner, expect}) {
const {describe, fdescribe, xdescribe} = testRunner;
const {it, xit, fit} = testRunner;
module.exports.addTests = function({describe, fdescribe, xdescribe, it, xit, fit, expect}) {
describe('TestRunner.it', () => {
it('should declare a test', async() => {
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => {});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.name()).toBe('uno');
expect(test.fullName()).toBe('uno');
expect(test.focused()).toBe(false);
expect(test.skipped()).toBe(false);
expect(test.location().filePath()).toEqual(__filename);
expect(test.location().fileName()).toEqual('testrunner.spec.js');
@ -27,7 +72,7 @@ module.exports.addTests = function({testRunner, expect}) {
expect(test.location().columnNumber()).toBeTruthy();
});
it('should run a test', async() => {
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => {});
const result = await t.run();
expect(result.runs.length).toBe(1);
@ -38,17 +83,16 @@ module.exports.addTests = function({testRunner, expect}) {
describe('TestRunner.xit', () => {
it('should declare a skipped test', async() => {
const t = newTestRunner();
const t = new Runner();
t.xit('uno', () => {});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.name()).toBe('uno');
expect(test.fullName()).toBe('uno');
expect(test.focused()).toBe(false);
expect(test.skipped()).toBe(true);
});
it('should not run a skipped test', async() => {
const t = newTestRunner();
const t = new Runner();
t.xit('uno', () => {});
const result = await t.run();
expect(result.runs.length).toBe(1);
@ -59,17 +103,17 @@ module.exports.addTests = function({testRunner, expect}) {
describe('TestRunner.fit', () => {
it('should declare a focused test', async() => {
const t = newTestRunner();
const t = new Runner();
t.fit('uno', () => {});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.name()).toBe('uno');
expect(test.fullName()).toBe('uno');
expect(test.focused()).toBe(true);
expect(test.skipped()).toBe(false);
expect(t.focusedTests()[0]).toBe(test);
});
it('should run a focused test', async() => {
const t = newTestRunner();
const t = new Runner();
t.fit('uno', () => {});
const result = await t.run();
expect(result.runs.length).toBe(1);
@ -77,12 +121,12 @@ module.exports.addTests = function({testRunner, expect}) {
expect(result.runs[0].result()).toBe('ok');
});
it('should run a failed focused test', async() => {
const t = newTestRunner();
const t = new Runner();
let run = false;
t.it('uno', () => {
t.it.only.fail('uno', () => {
run = true; throw new Error('failure');
}).setFocused(true).setExpectation(t.Expectations.Fail);
expect(t.tests()[0].focused()).toBe(true);
});
expect(t.focusedTests().length).toBe(1);
expect(t.tests()[0].expectation()).toBe(t.Expectations.Fail);
const result = await t.run();
expect(run).toBe(true);
@ -94,7 +138,7 @@ module.exports.addTests = function({testRunner, expect}) {
describe('TestRunner.describe', () => {
it('should declare a suite', async() => {
const t = newTestRunner();
const t = new Runner();
t.describe('suite', () => {
t.it('uno', () => {});
});
@ -102,30 +146,26 @@ module.exports.addTests = function({testRunner, expect}) {
const test = t.tests()[0];
expect(test.name()).toBe('uno');
expect(test.fullName()).toBe('suite uno');
expect(test.focused()).toBe(false);
expect(test.skipped()).toBe(false);
expect(test.suite().name()).toBe('suite');
expect(test.suite().fullName()).toBe('suite');
expect(test.suite().focused()).toBe(false);
expect(test.suite().skipped()).toBe(false);
});
});
describe('TestRunner.xdescribe', () => {
it('should declare a skipped suite', async() => {
const t = newTestRunner();
const t = new Runner();
t.xdescribe('suite', () => {
t.it('uno', () => {});
});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.focused()).toBe(false);
expect(test.skipped()).toBe(false);
expect(test.suite().focused()).toBe(false);
expect(test.suite().skipped()).toBe(true);
});
it('focused tests inside a skipped suite are not run', async() => {
const t = newTestRunner();
const t = new Runner();
let run = false;
t.xdescribe('suite', () => {
t.fit('uno', () => { run = true; });
@ -140,19 +180,18 @@ module.exports.addTests = function({testRunner, expect}) {
describe('TestRunner.fdescribe', () => {
it('should declare a focused suite', async() => {
const t = newTestRunner();
const t = new Runner();
t.fdescribe('suite', () => {
t.it('uno', () => {});
});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.focused()).toBe(false);
expect(test.skipped()).toBe(false);
expect(test.suite().focused()).toBe(true);
expect(t.focusedSuites()[0]).toBe(test.suite());
expect(test.suite().skipped()).toBe(false);
});
it('skipped tests inside a focused suite should not be run', async() => {
const t = newTestRunner();
const t = new Runner();
t.fdescribe('suite', () => {
t.xit('uno', () => {});
});
@ -163,7 +202,7 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should run all "run" tests inside a focused suite', async() => {
const log = [];
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => log.push(1));
t.fdescribe('suite1', () => {
t.it('dos', () => log.push(2));
@ -175,7 +214,7 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should run only "focus" tests inside a focused suite', async() => {
const log = [];
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => log.push(1));
t.fdescribe('suite1', () => {
t.fit('dos', () => log.push(2));
@ -187,7 +226,7 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should run both "run" tests in focused suite and non-descendant focus tests', async() => {
const log = [];
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => log.push(1));
t.fdescribe('suite1', () => {
t.it('dos', () => log.push(2));
@ -201,38 +240,32 @@ module.exports.addTests = function({testRunner, expect}) {
describe('TestRunner attributes', () => {
it('should work', async() => {
const t = newTestRunner({timeout: 123});
const t = new Runner({timeout: 123});
const log = [];
t.testModifier('foo', (t, ...args) => {
t._collector.addTestModifier('foo', (t, ...args) => {
log.push('foo');
expect(t.focused()).toBe(false);
expect(t.skipped()).toBe(false);
expect(t.Expectations.Ok).toBeTruthy();
expect(t.Expectations.Fail).toBeTruthy();
expect(t.expectation()).toBe(t.Expectations.Ok);
expect(t.timeout()).toBe(123);
expect(t.repeat()).toBe(1);
expect(args.length).toBe(2);
expect(args[0]).toBe('uno');
expect(args[1]).toBe('dos');
t.setFocused(true);
t.setExpectation(t.Expectations.Fail);
t.setTimeout(234);
t.setRepeat(42);
});
t.testAttribute('bar', t => {
t._collector.addTestAttribute('bar', t => {
log.push('bar');
t.setSkipped(true);
expect(t.focused()).toBe(true);
expect(t.skipped()).toBe(true);
expect(t.expectation()).toBe(t.Expectations.Fail);
expect(t.timeout()).toBe(234);
expect(t.repeat()).toBe(42);
});
t.it.foo('uno', 'dos').bar('test', () => { });
@ -243,18 +276,15 @@ module.exports.addTests = function({testRunner, expect}) {
describe('TestRunner hooks', () => {
it('should run all hooks in proper order', async() => {
const log = [];
const t = newTestRunner();
let e2;
t.environment('env', () => {
t.beforeAll(() => log.push('env:beforeAll'));
t.afterAll(() => log.push('env:afterAll'));
t.beforeEach(() => log.push('env:beforeEach'));
t.afterEach(() => log.push('env:afterEach'));
e2 = t.environment('env2', () => {
t.beforeAll(() => log.push('env2:beforeAll'));
t.afterAll(() => log.push('env2:afterAll'));
});
});
const t = new Runner();
const e = new Environment('env');
e.beforeAll(() => log.push('env:beforeAll'));
e.afterAll(() => log.push('env:afterAll'));
e.beforeEach(() => log.push('env:beforeEach'));
e.afterEach(() => log.push('env:afterEach'));
const e2 = new Environment('env2', e);
e2.beforeAll(() => log.push('env2:beforeAll'));
e2.afterAll(() => log.push('env2:afterAll'));
t.beforeAll(() => log.push('root:beforeAll'));
t.beforeEach(() => log.push('root:beforeEach1'));
t.beforeEach(() => log.push('root:beforeEach2'));
@ -262,29 +292,33 @@ module.exports.addTests = function({testRunner, expect}) {
t.describe('suite1', () => {
t.beforeAll(() => log.push('suite:beforeAll1'));
t.beforeAll(() => log.push('suite:beforeAll2'));
t.beforeEach((state, test) => {
log.push('suite:beforeEach');
test.before(() => log.push('test:before1'));
test.before(() => log.push('test:before2'));
test.after(() => log.push('test:after1'));
test.after(() => log.push('test:after2'));
});
t.beforeEach(() => log.push('suite:beforeEach'));
t.it('dos', () => log.push('test #2'));
t.tests()[t.tests().length - 1].environment().beforeEach(() => log.push('test:before1'));
t.tests()[t.tests().length - 1].environment().beforeEach(() => log.push('test:before2'));
t.tests()[t.tests().length - 1].environment().afterEach(() => log.push('test:after1'));
t.tests()[t.tests().length - 1].environment().afterEach(() => log.push('test:after2'));
t.it('tres', () => log.push('test #3'));
t.tests()[t.tests().length - 1].environment().beforeEach(() => log.push('test:before1'));
t.tests()[t.tests().length - 1].environment().beforeEach(() => log.push('test:before2'));
t.tests()[t.tests().length - 1].environment().afterEach(() => log.push('test:after1'));
t.tests()[t.tests().length - 1].environment().afterEach(() => log.push('test:after2'));
t.afterEach(() => log.push('suite:afterEach1'));
t.afterEach(() => log.push('suite:afterEach2'));
t.afterAll(() => log.push('suite:afterAll'));
});
t.it('cuatro', () => log.push('test #4')).addEnvironment(e2);
t.it('cuatro', () => log.push('test #4'));
t.tests()[t.tests().length - 1].addEnvironment(e2);
t.describe('no hooks suite', () => {
t.describe('suite2', () => {
t.beforeAll(() => log.push('suite2:beforeAll'));
t.afterAll(() => log.push('suite2:afterAll'));
t.describe('no hooks suite 2', () => {
t.it('cinco', () => log.push('test #5')).addEnvironment(e2);
t.it('cinco', () => log.push('test #5'));
});
});
});
t.suites()[t.suites().length - 1].addEnvironment(e2);
t.afterEach(() => log.push('root:afterEach'));
t.afterAll(() => log.push('root:afterAll1'));
t.afterAll(() => log.push('root:afterAll2'));
@ -353,20 +387,19 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should remove environment', async() => {
const log = [];
const t = newTestRunner();
const e = t.environment('env', () => {
t.beforeAll(() => log.push('env:beforeAll'));
t.afterAll(() => log.push('env:afterAll'));
t.beforeEach(() => log.push('env:beforeEach'));
t.afterEach(() => log.push('env:afterEach'));
});
const e2 = t.environment('env2', () => {
t.beforeAll(() => log.push('env2:beforeAll'));
t.afterAll(() => log.push('env2:afterAll'));
t.beforeEach(() => log.push('env2:beforeEach'));
t.afterEach(() => log.push('env2:afterEach'));
});
t.it('uno', () => log.push('test #1')).addEnvironment(e).addEnvironment(e2).removeEnvironment(e);
const t = new Runner();
const e = new Environment('env');
e.beforeAll(() => log.push('env:beforeAll'));
e.afterAll(() => log.push('env:afterAll'));
e.beforeEach(() => log.push('env:beforeEach'));
e.afterEach(() => log.push('env:afterEach'));
const e2 = new Environment('env2');
e2.beforeAll(() => log.push('env2:beforeAll'));
e2.afterAll(() => log.push('env2:afterAll'));
e2.beforeEach(() => log.push('env2:beforeEach'));
e2.afterEach(() => log.push('env2:afterEach'));
t.it('uno', () => log.push('test #1'));
t.tests()[0].addEnvironment(e).addEnvironment(e2).removeEnvironment(e);
await t.run();
expect(log).toEqual([
'env2:beforeAll',
@ -376,49 +409,9 @@ module.exports.addTests = function({testRunner, expect}) {
'env2:afterAll',
]);
});
it('environment restrictions', async () => {
const t = newTestRunner();
let env;
let env2;
t.describe('suite1', () => {
env = t.environment('env', () => {
env2 = t.environment('env2', () => {});
try {
t.it('test', () => {});
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot define a test inside an environment');
}
try {
t.describe('suite', () => {});
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot define a suite inside an environment');
}
});
try {
t.it('test', () => {}).addEnvironment(env).addEnvironment(env2);
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot use environments "env2" and "env" that share a parent environment "suite1 env" in test "suite1 test"');
}
try {
t.it('test', () => {}).addEnvironment(env2).addEnvironment(env);
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot use environments "env" and "env2" that share a parent environment "suite1 env" in test "suite1 test"');
}
});
try {
t.it('test', () => {}).addEnvironment(env);
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe('Cannot use environment "env" from suite "suite1" in unrelated test "test"');
}
});
it('should have the same state object in hooks and test', async() => {
const states = [];
const t = newTestRunner();
const t = new Runner();
t.beforeEach(state => states.push(state));
t.afterEach(state => states.push(state));
t.beforeAll(state => states.push(state));
@ -431,7 +424,7 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should unwind hooks properly when terminated', async() => {
const log = [];
const t = newTestRunner({timeout: 10000});
const t = new Runner({timeout: 10000});
t.beforeAll(() => log.push('beforeAll'));
t.beforeEach(() => log.push('beforeEach'));
t.afterEach(() => log.push('afterEach'));
@ -451,14 +444,14 @@ module.exports.addTests = function({testRunner, expect}) {
]);
});
it('should report as terminated even when hook crashes', async() => {
const t = newTestRunner({timeout: 10000});
const t = new Runner({timeout: 10000});
t.afterEach(() => { throw new Error('crash!'); });
t.it('uno', () => { t.terminate(); });
const result = await t.run();
expect(result.runs[0].result()).toBe('terminated');
});
it('should report as terminated when terminated during hook', async() => {
const t = newTestRunner({timeout: 10000});
const t = new Runner({timeout: 10000});
t.afterEach(() => { t.terminate(); });
t.it('uno', () => { });
const result = await t.run();
@ -466,7 +459,7 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should unwind hooks properly when crashed', async() => {
const log = [];
const t = newTestRunner({timeout: 10000});
const t = new Runner({timeout: 10000});
t.beforeAll(() => log.push('root beforeAll'));
t.beforeEach(() => log.push('root beforeEach'));
t.describe('suite', () => {
@ -499,16 +492,14 @@ module.exports.addTests = function({testRunner, expect}) {
describe('TestRunner.run', () => {
it('should run a test', async() => {
const t = newTestRunner();
const t = new Runner();
let ran = false;
t.it('uno', () => ran = true);
await t.run();
expect(ran).toBe(true);
});
it('should handle repeat', async() => {
const t = newTestRunner();
t.testModifier('repeat', (t, count) => t.setRepeat(count));
t.suiteModifier('repeat', (s, count) => s.setRepeat(count));
const t = new Runner();
let suite = 0;
let test = 0;
let beforeAll = 0;
@ -526,7 +517,7 @@ module.exports.addTests = function({testRunner, expect}) {
expect(test).toBe(6);
});
it('should run tests if some fail', async() => {
const t = newTestRunner();
const t = new Runner();
const log = [];
t.it('uno', () => log.push(1));
t.it('dos', () => { throw new Error('bad'); });
@ -535,7 +526,7 @@ module.exports.addTests = function({testRunner, expect}) {
expect(log.join()).toBe('1,3');
});
it('should run tests if some timeout', async() => {
const t = newTestRunner({timeout: 1});
const t = new Runner({timeout: 1});
const log = [];
t.it('uno', () => log.push(1));
t.it('dos', async() => new Promise(() => {}));
@ -545,7 +536,7 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should break on first failure if configured so', async() => {
const log = [];
const t = newTestRunner({breakOnFailure: true});
const t = new Runner({breakOnFailure: true});
t.it('test#1', () => log.push('test#1'));
t.it('test#2', () => log.push('test#2'));
t.it('test#3', () => { throw new Error('crash'); });
@ -558,17 +549,17 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should pass a state and a test as a test parameters', async() => {
const log = [];
const t = newTestRunner();
const t = new Runner();
t.beforeEach(state => state.FOO = 42);
t.it('uno', (state, test) => {
t.it('uno', (state, testRun) => {
log.push('state.FOO=' + state.FOO);
log.push('test=' + test.name());
log.push('test=' + testRun.test().name());
});
await t.run();
expect(log.join()).toBe('state.FOO=42,test=uno');
});
it('should run async test', async() => {
const t = newTestRunner();
const t = new Runner();
let ran = false;
t.it('uno', async() => {
await new Promise(x => setTimeout(x, 10));
@ -579,7 +570,7 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should run async tests in order of their declaration', async() => {
const log = [];
const t = newTestRunner();
const t = new Runner();
t.it('uno', async() => {
await new Promise(x => setTimeout(x, 30));
log.push(1);
@ -597,14 +588,14 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should run multiple tests', async() => {
const log = [];
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => log.push(1));
t.it('dos', () => log.push(2));
await t.run();
expect(log.join()).toBe('1,2');
});
it('should NOT run a skipped test', async() => {
const t = newTestRunner();
const t = new Runner();
let ran = false;
t.xit('uno', () => ran = true);
await t.run();
@ -612,7 +603,7 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should run ONLY non-skipped tests', async() => {
const log = [];
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => log.push(1));
t.xit('dos', () => log.push(2));
t.it('tres', () => log.push(3));
@ -621,7 +612,7 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should run ONLY focused tests', async() => {
const log = [];
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => log.push(1));
t.xit('dos', () => log.push(2));
t.fit('tres', () => log.push(3));
@ -630,7 +621,7 @@ module.exports.addTests = function({testRunner, expect}) {
});
it('should run tests in order of their declaration', async() => {
const log = [];
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => log.push(1));
t.describe('suite1', () => {
t.it('dos', () => log.push(2));
@ -641,9 +632,9 @@ module.exports.addTests = function({testRunner, expect}) {
expect(log.join()).toBe('1,2,3,4');
});
it('should respect total timeout', async() => {
const t = newTestRunner({timeout: 10000});
const t = new Runner({timeout: 10000, totalTimeout: 1});
t.it('uno', async () => { await new Promise(() => {}); });
const result = await t.run({totalTimeout: 1});
const result = await t.run();
expect(result.runs[0].result()).toBe('terminated');
expect(result.message).toContain('Total timeout');
});
@ -651,25 +642,25 @@ module.exports.addTests = function({testRunner, expect}) {
describe('TestRunner.run result', () => {
it('should return OK if all tests pass', async() => {
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => {});
const result = await t.run();
expect(result.result).toBe('ok');
});
it('should return FAIL if at least one test fails', async() => {
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => { throw new Error('woof'); });
const result = await t.run();
expect(result.result).toBe('failed');
});
it('should return FAIL if at least one test times out', async() => {
const t = newTestRunner({timeout: 1});
const t = new Runner({timeout: 1});
t.it('uno', async() => new Promise(() => {}));
const result = await t.run();
expect(result.result).toBe('failed');
});
it('should return TERMINATED if it was terminated', async() => {
const t = newTestRunner({timeout: 1});
const t = new Runner({timeout: 1000000});
t.it('uno', async() => new Promise(() => {}));
const [result] = await Promise.all([
t.run(),
@ -678,7 +669,7 @@ module.exports.addTests = function({testRunner, expect}) {
expect(result.result).toBe('terminated');
});
it('should return CRASHED if it crashed', async() => {
const t = newTestRunner({timeout: 1});
const t = new Runner({timeout: 1});
t.it('uno', async() => new Promise(() => {}));
t.afterAll(() => { throw new Error('woof');});
const result = await t.run();
@ -689,7 +680,7 @@ module.exports.addTests = function({testRunner, expect}) {
describe('TestRunner parallel', () => {
it('should run tests in parallel', async() => {
const log = [];
const t = newTestRunner({parallel: 2});
const t = new Runner({parallel: 2});
t.it('uno', async state => {
log.push(`Worker #${state.parallelIndex} Starting: UNO`);
await Promise.resolve();
@ -712,17 +703,17 @@ module.exports.addTests = function({testRunner, expect}) {
describe('TestRunner.hasFocusedTestsOrSuites', () => {
it('should work', () => {
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => {});
expect(t.hasFocusedTestsOrSuites()).toBe(false);
expect(t._filter.hasFocusedTestsOrSuites()).toBe(false);
});
it('should work #2', () => {
const t = newTestRunner();
const t = new Runner();
t.fit('uno', () => {});
expect(t.hasFocusedTestsOrSuites()).toBe(true);
expect(t._filter.hasFocusedTestsOrSuites()).toBe(true);
});
it('should work #3', () => {
const t = newTestRunner();
const t = new Runner();
t.describe('suite #1', () => {
t.fdescribe('suite #2', () => {
t.describe('suite #3', () => {
@ -730,13 +721,13 @@ module.exports.addTests = function({testRunner, expect}) {
});
});
});
expect(t.hasFocusedTestsOrSuites()).toBe(true);
expect(t._filter.hasFocusedTestsOrSuites()).toBe(true);
});
});
describe('TestRunner result', () => {
it('should work for both throwing and timeouting tests', async() => {
const t = newTestRunner({timeout: 1});
const t = new Runner({timeout: 1});
t.it('uno', () => { throw new Error('boo');});
t.it('dos', () => new Promise(() => {}));
const result = await t.run();
@ -744,50 +735,50 @@ module.exports.addTests = function({testRunner, expect}) {
expect(result.runs[1].result()).toBe('timedout');
});
it('should report crashed tests', async() => {
const t = newTestRunner();
const t = new Runner();
t.beforeEach(() => { throw new Error('woof');});
t.it('uno', () => {});
const result = await t.run();
expect(result.runs[0].result()).toBe('crashed');
});
it('skipped should work for both throwing and timeouting tests', async() => {
const t = newTestRunner({timeout: 1});
const t = new Runner({timeout: 1});
t.xit('uno', () => { throw new Error('boo');});
const result = await t.run();
expect(result.runs[0].result()).toBe('skipped');
});
it('should return OK', async() => {
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => {});
const result = await t.run();
expect(result.runs[0].result()).toBe('ok');
});
it('should return TIMEDOUT', async() => {
const t = newTestRunner({timeout: 1});
const t = new Runner({timeout: 1});
t.it('uno', async() => new Promise(() => {}));
const result = await t.run();
expect(result.runs[0].result()).toBe('timedout');
});
it('should return SKIPPED', async() => {
const t = newTestRunner();
const t = new Runner();
t.xit('uno', () => {});
const result = await t.run();
expect(result.runs[0].result()).toBe('skipped');
});
it('should return FAILED', async() => {
const t = newTestRunner();
const t = new Runner();
t.it('uno', async() => Promise.reject('woof'));
const result = await t.run();
expect(result.runs[0].result()).toBe('failed');
});
it('should return TERMINATED', async() => {
const t = newTestRunner();
const t = new Runner();
t.it('uno', async() => t.terminate());
const result = await t.run();
expect(result.runs[0].result()).toBe('terminated');
});
it('should return CRASHED', async() => {
const t = newTestRunner();
const t = new Runner();
t.it('uno', () => {});
t.afterEach(() => {throw new Error('foo');});
const result = await t.run();
@ -796,20 +787,19 @@ module.exports.addTests = function({testRunner, expect}) {
});
describe('TestRunner delegate', () => {
it('should call delegate methpds in proper order', async() => {
it('should call delegate methods in proper order', async() => {
const log = [];
const t = newTestRunner();
t.beforeAll(() => log.push('beforeAll'));
t.beforeEach(() => log.push('beforeEach'));
t.it('test#1', () => log.push('test#1'));
t.afterEach(() => log.push('afterEach'));
t.afterAll(() => log.push('afterAll'));
t.setDelegate({
const t = new Runner({
onStarted: () => log.push('E:started'),
onTestRunStarted: () => log.push('E:teststarted'),
onTestRunFinished: () => log.push('E:testfinished'),
onFinished: () => log.push('E:finished'),
});
t.beforeAll(() => log.push('beforeAll'));
t.beforeEach(() => log.push('beforeEach'));
t.it('test#1', () => log.push('test#1'));
t.afterEach(() => log.push('afterEach'));
t.afterAll(() => log.push('afterAll'));
await t.run();
expect(log).toEqual([
'E:started',
@ -824,15 +814,11 @@ module.exports.addTests = function({testRunner, expect}) {
]);
});
it('should call onFinished with result', async() => {
const t = newTestRunner();
let onFinished;
const finishedPromise = new Promise(f => onFinished = f);
const [result] = await Promise.all([
new Promise(x => t.setDelegate({
onStarted() {},
onFinished(result) { x(result); },
onTestRunStarted() {},
onTestRunFinished() {},
})),
t.run(),
finishedPromise,
new TestRunner().run([], { onFinished }),
]);
expect(result.result).toBe('ok');
});