playwright/utils/testrunner/TestCollector.js
Andrey Lushnikov a9f0c40af3
feat(testrunner): improvements to testrunner (#2070)
This patch:
- teaches test runner to understand custom argument spelling, e.g. `--file=evalu` and `-j10`
- fixes `--file` filter to actually focus file paths instead of focusing
all tests with given path
2020-05-01 10:09:45 -07:00

251 lines
7.8 KiB
JavaScript

/**
* 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._focusedTests = new Set();
this._focusedSuites = new Set();
this._focusedFilePaths = new Set();
}
focusTest(test) { this._focusedTests.add(test); }
focusSuite(suite) { this._focusedSuites.add(suite); }
focusFilePath(filePath) { this._focusedFilePaths.add(filePath); }
hasFocusedTestsOrSuitesOrFiles() {
return !!this._focusedTests.size || !!this._focusedSuites.size || !!this._focusedFilePaths.size;
}
focusedTests(tests) {
return tests.filter(test => this._focusedTests.has(test));
}
focusedSuites(suites) {
return suites.filter(suite => this._focusedSuites.has(suite));
}
focusedFilePaths(filePaths) {
return filePaths.filter(filePath => this._focusedFilePaths.has(filePath));
}
filter(tests) {
if (!this.hasFocusedTestsOrSuitesOrFiles())
return tests;
const ignoredSuites = new Set();
const ignoredFilePaths = new Set();
for (const test of tests) {
if (this._focusedTests.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);
ignoredFilePaths.add(test.location().filePath());
}
for (let suite = test.suite(); suite; suite = suite.parentSuite()) {
if (this._focusedSuites.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._focusedTests.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._focusedTests.has(test) || (this._focusedFilePaths.has(test.location().filePath()) && !ignoredFilePaths.has(test.location().filePath()));
for (let suite = test.suite(); suite; suite = suite.parentSuite())
focused = focused || (this._focusedSuites.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 suiteToChildren = new Map();
const rootSuites = new Set();
for (const test of tests) {
let children = suiteToChildren.get(test.suite());
if (!children) {
children = new Set();
suiteToChildren.set(test.suite(), children);
}
children.add(test);
for (let suite = test.suite(); suite; suite = suite.parentSuite()) {
let children = suiteToChildren.get(suite.parentSuite());
if (!children) {
children = new Set();
suiteToChildren.set(suite.parentSuite(), children);
}
children.add(suite);
// Add root suites.
if (!suite.parentSuite())
rootSuites.add(suite);
}
}
const collectTests = (testOrSuite) => {
const testOrder = [];
if (testOrSuite instanceof Test) {
testOrder.push(testOrSuite);
} else {
for (const child of suiteToChildren.get(testOrSuite))
testOrder.push(...collectTests(child));
}
const repeat = this._repeatCount.has(testOrSuite) ? this._repeatCount.get(testOrSuite) : 1;
const result = [];
for (let i = 0; i < repeat; ++i)
result.push(...testOrder);
return result;
}
const testOrder = [];
for (const rootSuite of rootSuites)
testOrder.push(...collectTests(rootSuite));
return testOrder.map(test => new TestRun(test));
}
}
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._rootSuite = this._currentSuite;
this._api.describe = specBuilder(this._suiteModifiers, this._suiteAttributes, (specs, name, suiteCallback, ...suiteArgs) => {
const location = Location.getCallerLocation();
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();
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);
}
useEnvironment(environment) {
return this._currentSuite.addEnvironment(environment);
}
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;
}
filePaths() {
const filePaths = new Set();
for (const test of this._tests)
filePaths.add(test.location().filePath());
for (const suite of this._suites)
filePaths.add(suite.location().filePath());
return [...filePaths];
}
rootSuite() {
return this._rootSuite;
}
}
module.exports = { TestCollector, specBuilder, FocusedFilter, Repeater };