feat(test-runner): allow to focus a test in a location (#7208)
This commit is contained in:
parent
5732307280
commit
bd86e70465
|
|
@ -22,6 +22,7 @@ import * as path from 'path';
|
|||
import type { Config } from './types';
|
||||
import { Runner } from './runner';
|
||||
import { stopProfiling, startProfiling } from './profiler';
|
||||
import type { FilePatternFilter } from './util';
|
||||
|
||||
const defaultTimeout = 30000;
|
||||
const defaultReporter = process.env.CI ? 'dot' : 'list';
|
||||
|
|
@ -131,7 +132,14 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
|
|||
runner.loadEmptyConfig(process.cwd());
|
||||
}
|
||||
|
||||
const result = await runner.run(!!opts.list, args.map(forceRegExp), opts.project || undefined);
|
||||
const filePatternFilters: FilePatternFilter[] = args.map(arg => {
|
||||
const match = /^(.*):(\d+)$/.exec(arg);
|
||||
return {
|
||||
re: forceRegExp(match ? match[1] : arg),
|
||||
line: match ? parseInt(match[2], 10) : null,
|
||||
};
|
||||
});
|
||||
const result = await runner.run(!!opts.list, filePatternFilters, opts.project || undefined);
|
||||
await stopProfiling(undefined);
|
||||
|
||||
if (result === 'sigint')
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import * as fs from 'fs';
|
|||
import * as path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import { Dispatcher } from './dispatcher';
|
||||
import { createMatcher, monotonicTime, raceAgainstDeadline } from './util';
|
||||
import { Suite } from './test';
|
||||
import { createMatcher, FilePatternFilter, monotonicTime, raceAgainstDeadline } from './util';
|
||||
import { Spec, Suite } from './test';
|
||||
import { Loader } from './loader';
|
||||
import { Reporter } from './reporter';
|
||||
import { Multiplexer } from './reporters/multiplexer';
|
||||
|
|
@ -81,11 +81,11 @@ export class Runner {
|
|||
this._loader.loadEmptyConfig(rootDir);
|
||||
}
|
||||
|
||||
async run(list: boolean, testFileReFilters: RegExp[], projectName?: string): Promise<RunResult> {
|
||||
async run(list: boolean, filePatternFilters: FilePatternFilter[], projectName?: string): Promise<RunResult> {
|
||||
this._reporter = this._createReporter();
|
||||
const config = this._loader.fullConfig();
|
||||
const globalDeadline = config.globalTimeout ? config.globalTimeout + monotonicTime() : undefined;
|
||||
const { result, timedOut } = await raceAgainstDeadline(this._run(list, testFileReFilters, projectName), globalDeadline);
|
||||
const { result, timedOut } = await raceAgainstDeadline(this._run(list, filePatternFilters, projectName), globalDeadline);
|
||||
if (timedOut) {
|
||||
if (!this._didBegin)
|
||||
this._reporter.onBegin(config, new Suite(''));
|
||||
|
|
@ -119,8 +119,8 @@ export class Runner {
|
|||
await new Promise(f => process.stderr.on('drain', f));
|
||||
}
|
||||
|
||||
async _run(list: boolean, testFileReFilters: RegExp[], projectName?: string): Promise<RunResult> {
|
||||
const testFileFilter = testFileReFilters.length ? createMatcher(testFileReFilters) : () => true;
|
||||
async _run(list: boolean, testFileReFilters: FilePatternFilter[], projectName?: string): Promise<RunResult> {
|
||||
const testFileFilter = testFileReFilters.length ? createMatcher(testFileReFilters.map(e => e.re)) : () => true;
|
||||
const config = this._loader.fullConfig();
|
||||
|
||||
const projects = this._loader.projects().filter(project => {
|
||||
|
|
@ -163,6 +163,7 @@ export class Runner {
|
|||
if (config.forbidOnly && rootSuite._hasOnly())
|
||||
return 'forbid-only';
|
||||
filterOnly(rootSuite);
|
||||
filterByFocusedLine(rootSuite, testFileReFilters);
|
||||
|
||||
const fileSuites = new Map<string, Suite>();
|
||||
for (const fileSuite of rootSuite.suites)
|
||||
|
|
@ -243,8 +244,24 @@ export class Runner {
|
|||
}
|
||||
|
||||
function filterOnly(suite: Suite) {
|
||||
const onlySuites = suite.suites.filter(child => filterOnly(child) || child._only);
|
||||
const onlyTests = suite.specs.filter(spec => spec._only);
|
||||
const suiteFilter = (suite: Suite) => suite._only;
|
||||
const specFilter = (spec: Spec) => spec._only;
|
||||
return filterSuite(suite, suiteFilter, specFilter);
|
||||
}
|
||||
|
||||
function filterByFocusedLine(suite: Suite, focusedTestFileLines: FilePatternFilter[]) {
|
||||
const testFileLineMatches = (specFileName: string, specLine: number) => focusedTestFileLines.some(({re, line}) => {
|
||||
re.lastIndex = 0;
|
||||
return re.test(specFileName) && (line === specLine || line === null);
|
||||
});
|
||||
const suiteFilter = (suite: Suite) => testFileLineMatches(suite.file, suite.line);
|
||||
const specFilter = (spec: Spec) => testFileLineMatches(spec.file, spec.line);
|
||||
return filterSuite(suite, suiteFilter, specFilter);
|
||||
}
|
||||
|
||||
function filterSuite(suite: Suite, suiteFilter: (suites: Suite) => boolean, specFilter: (spec: Spec) => boolean) {
|
||||
const onlySuites = suite.suites.filter(child => filterSuite(child, suiteFilter, specFilter) || suiteFilter(child));
|
||||
const onlyTests = suite.specs.filter(specFilter);
|
||||
const onlyEntries = new Set([...onlySuites, ...onlyTests]);
|
||||
if (onlyEntries.size) {
|
||||
suite.suites = onlySuites;
|
||||
|
|
|
|||
|
|
@ -91,6 +91,11 @@ export function isRegExp(e: any): e is RegExp {
|
|||
|
||||
export type Matcher = (value: string) => boolean;
|
||||
|
||||
export type FilePatternFilter = {
|
||||
re: RegExp;
|
||||
line: number | null;
|
||||
};
|
||||
|
||||
export function createMatcher(patterns: string | RegExp | (string | RegExp)[]): Matcher {
|
||||
const reList: RegExp[] = [];
|
||||
const filePatterns: string[] = [];
|
||||
|
|
|
|||
|
|
@ -211,6 +211,23 @@ test('should match regex string argument', async ({ runInlineTest }) => {
|
|||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should match regex string with a colon argument', async ({ runInlineTest }) => {
|
||||
test.skip(process.platform === 'win32', 'Windows does not support colons in the file name');
|
||||
const result = await runInlineTest({
|
||||
'fileb.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('pass', ({}) => {});
|
||||
`,
|
||||
'weird:file.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('pass', ({}) => {});
|
||||
`
|
||||
}, { args: ['/weird:file\.test\.ts/'] });
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.report.suites.map(s => s.file).sort()).toEqual(['weird:file.test.ts']);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should match case insensitive', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'capital/A.test.ts': `
|
||||
|
|
@ -231,6 +248,78 @@ test('should match case insensitive', async ({ runInlineTest }) => {
|
|||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test('should focus a single test spec', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'foo.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('pass1', ({}) => {});
|
||||
test('pass2', ({}) => {});
|
||||
test('pass3', ({}) => {});
|
||||
`,
|
||||
'bar.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('no-pass1', ({}) => {});
|
||||
`,
|
||||
}, { args: ['foo.test.ts:7'] });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.skipped).toBe(0);
|
||||
expect(result.report.suites[0].specs[0].title).toEqual('pass2');
|
||||
});
|
||||
|
||||
test('should focus a single nested test spec', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'foo.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('pass1', ({}) => {});
|
||||
test.describe('suite-1', () => {
|
||||
test.describe('suite-2', () => {
|
||||
test('pass2', ({}) => {});
|
||||
});
|
||||
});
|
||||
test('pass3', ({}) => {});
|
||||
`,
|
||||
'bar.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('pass3', ({}) => {});
|
||||
`,
|
||||
'noooo.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('no-pass1', ({}) => {});
|
||||
`,
|
||||
}, { args: ['foo.test.ts:9', 'bar.test.ts'] });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(2);
|
||||
expect(result.skipped).toBe(0);
|
||||
expect(result.report.suites[0].specs[0].title).toEqual('pass3');
|
||||
expect(result.report.suites[1].suites[0].suites[0].specs[0].title).toEqual('pass2');
|
||||
});
|
||||
|
||||
test('should focus a single test suite', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'foo.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('pass1', ({}) => {});
|
||||
test.describe('suite-1', () => {
|
||||
test.describe('suite-2', () => {
|
||||
test('pass2', ({}) => {});
|
||||
test('pass3', ({}) => {});
|
||||
});
|
||||
});
|
||||
test('pass4', ({}) => {});
|
||||
`,
|
||||
'bar.test.ts': `
|
||||
const { test } = pwt;
|
||||
test('no-pass1', ({}) => {});
|
||||
`,
|
||||
}, { args: ['foo.test.ts:8'] });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(2);
|
||||
expect(result.skipped).toBe(0);
|
||||
expect(result.report.suites[0].suites[0].suites[0].specs[0].title).toEqual('pass2');
|
||||
expect(result.report.suites[0].suites[0].suites[0].specs[1].title).toEqual('pass3');
|
||||
});
|
||||
|
||||
test('should match by directory', async ({ runInlineTest }) => {
|
||||
const result = await runInlineTest({
|
||||
'dir-a/file.test.ts': `
|
||||
|
|
|
|||
Loading…
Reference in a new issue