feat(test): shrink api to run only, rename pending to skipped (#3636)
This commit is contained in:
parent
9b50a6d259
commit
a87614a266
|
|
@ -17,7 +17,7 @@
|
||||||
import program from 'commander';
|
import program from 'commander';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { collectTests, runTests, RunnerConfig } from '.';
|
import { run, RunnerConfig } from '.';
|
||||||
import PytestReporter from './reporters/pytest';
|
import PytestReporter from './reporters/pytest';
|
||||||
import DotReporter from './reporters/dot';
|
import DotReporter from './reporters/dot';
|
||||||
import ListReporter from './reporters/list';
|
import ListReporter from './reporters/list';
|
||||||
|
|
@ -48,6 +48,7 @@ program
|
||||||
const testDir = path.resolve(process.cwd(), command.args[0]);
|
const testDir = path.resolve(process.cwd(), command.args[0]);
|
||||||
const config: RunnerConfig = {
|
const config: RunnerConfig = {
|
||||||
debug: command.debug,
|
debug: command.debug,
|
||||||
|
forbidOnly: command.forbidOnly,
|
||||||
quiet: command.quiet,
|
quiet: command.quiet,
|
||||||
grep: command.grep,
|
grep: command.grep,
|
||||||
jobs: command.jobs,
|
jobs: command.jobs,
|
||||||
|
|
@ -58,25 +59,6 @@ program
|
||||||
trialRun: command.trialRun,
|
trialRun: command.trialRun,
|
||||||
updateSnapshots: command.updateSnapshots
|
updateSnapshots: command.updateSnapshots
|
||||||
};
|
};
|
||||||
const files = collectFiles(testDir, '', command.args.slice(1));
|
|
||||||
const suite = collectTests(config, files);
|
|
||||||
if (command.forbidOnly) {
|
|
||||||
const hasOnly = suite.eachTest(t => t.only) || suite.eachSuite(s => s.only);
|
|
||||||
if (hasOnly) {
|
|
||||||
console.error('=====================================');
|
|
||||||
console.error(' --forbid-only found a focused test.');
|
|
||||||
console.error('=====================================');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const total = suite.total();
|
|
||||||
if (!total) {
|
|
||||||
console.error('=================');
|
|
||||||
console.error(' no tests found.');
|
|
||||||
console.error('=================');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const reporterList = command.reporter.split(',');
|
const reporterList = command.reporter.split(',');
|
||||||
const reporterObjects: Reporter[] = reporterList.map(c => {
|
const reporterObjects: Reporter[] = reporterList.map(c => {
|
||||||
|
|
@ -90,9 +72,24 @@ program
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await runTests(config, suite, new Multiplexer(reporterObjects));
|
|
||||||
const hasFailures = suite.eachTest(t => t.error);
|
const files = collectFiles(testDir, '', command.args.slice(1));
|
||||||
process.exit(hasFailures ? 1 : 0);
|
const result = await run(config, files, new Multiplexer(reporterObjects));
|
||||||
|
if (result === 'forbid-only') {
|
||||||
|
console.error('=====================================');
|
||||||
|
console.error(' --forbid-only found a focused test.');
|
||||||
|
console.error('=====================================');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result === 'no-tests') {
|
||||||
|
console.error('=================');
|
||||||
|
console.error(' no tests found.');
|
||||||
|
console.error('=================');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(result === 'failed' ? 1 : 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import { registerFixture as registerFixtureT, registerWorkerFixture as registerW
|
||||||
import { Reporter } from './reporter';
|
import { Reporter } from './reporter';
|
||||||
import { Runner } from './runner';
|
import { Runner } from './runner';
|
||||||
import { RunnerConfig } from './runnerConfig';
|
import { RunnerConfig } from './runnerConfig';
|
||||||
import { Suite, Test } from './test';
|
import { Test } from './test';
|
||||||
import { Matrix, TestCollector } from './testCollector';
|
import { Matrix, TestCollector } from './testCollector';
|
||||||
import { installTransform } from './transform';
|
import { installTransform } from './transform';
|
||||||
export { parameters, registerParameter } from './fixtures';
|
export { parameters, registerParameter } from './fixtures';
|
||||||
|
|
@ -58,7 +58,9 @@ export function registerWorkerFixture<T extends keyof (WorkerState & FixturePara
|
||||||
registerWorkerFixtureT<RunnerConfig>(name, fn);
|
registerWorkerFixtureT<RunnerConfig>(name, fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function collectTests(config: RunnerConfig, files: string[]): Suite {
|
type RunResult = 'passed' | 'failed' | 'forbid-only' | 'no-tests';
|
||||||
|
|
||||||
|
export async function run(config: RunnerConfig, files: string[], reporter: Reporter): Promise<RunResult> {
|
||||||
const revertBabelRequire = installTransform();
|
const revertBabelRequire = installTransform();
|
||||||
let hasSetup = false;
|
let hasSetup = false;
|
||||||
try {
|
try {
|
||||||
|
|
@ -74,10 +76,17 @@ export function collectTests(config: RunnerConfig, files: string[]): Suite {
|
||||||
revertBabelRequire();
|
revertBabelRequire();
|
||||||
|
|
||||||
const testCollector = new TestCollector(files, matrix, config);
|
const testCollector = new TestCollector(files, matrix, config);
|
||||||
return testCollector.suite;
|
const suite = testCollector.suite;
|
||||||
}
|
if (config.forbidOnly) {
|
||||||
|
const hasOnly = suite.findTest(t => t.only) || suite.eachSuite(s => s.only);
|
||||||
|
if (hasOnly)
|
||||||
|
return 'forbid-only';
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = suite.total();
|
||||||
|
if (!total)
|
||||||
|
return 'no-tests';
|
||||||
|
|
||||||
export async function runTests(config: RunnerConfig, suite: Suite, reporter: Reporter) {
|
|
||||||
// Trial run does not need many workers, use one.
|
// Trial run does not need many workers, use one.
|
||||||
const jobs = (config.trialRun || config.debug) ? 1 : config.jobs;
|
const jobs = (config.trialRun || config.debug) ? 1 : config.jobs;
|
||||||
const runner = new Runner(suite, { ...config, jobs }, reporter);
|
const runner = new Runner(suite, { ...config, jobs }, reporter);
|
||||||
|
|
@ -91,4 +100,5 @@ export async function runTests(config: RunnerConfig, suite: Suite, reporter: Rep
|
||||||
for (const f of afterFunctions)
|
for (const f of afterFunctions)
|
||||||
await f();
|
await f();
|
||||||
}
|
}
|
||||||
|
return suite.findTest(t => t.error) ? 'failed' : 'passed';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ import { Suite, Test } from './test';
|
||||||
export interface Reporter {
|
export interface Reporter {
|
||||||
onBegin(config: RunnerConfig, suite: Suite): void;
|
onBegin(config: RunnerConfig, suite: Suite): void;
|
||||||
onTest(test: Test): void;
|
onTest(test: Test): void;
|
||||||
onPending(test: Test): void;
|
onSkippedTest(test: Test): void;
|
||||||
onStdOut(test: Test, chunk: string | Buffer);
|
onTestStdOut(test: Test, chunk: string | Buffer);
|
||||||
onStdErr(test: Test, chunk: string | Buffer);
|
onTestStdErr(test: Test, chunk: string | Buffer);
|
||||||
onPass(test: Test): void;
|
onTestPassed(test: Test): void;
|
||||||
onFail(test: Test): void;
|
onTestFailed(test: Test): void;
|
||||||
onEnd(): void;
|
onEnd(): void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import { Suite, Test } from '../test';
|
||||||
const stackUtils = new StackUtils()
|
const stackUtils = new StackUtils()
|
||||||
|
|
||||||
export class BaseReporter implements Reporter {
|
export class BaseReporter implements Reporter {
|
||||||
pending: Test[] = [];
|
skipped: Test[] = [];
|
||||||
passes: Test[] = [];
|
passes: Test[] = [];
|
||||||
failures: Test[] = [];
|
failures: Test[] = [];
|
||||||
timeouts: Test[] = [];
|
timeouts: Test[] = [];
|
||||||
|
|
@ -54,25 +54,25 @@ export class BaseReporter implements Reporter {
|
||||||
onTest(test: Test) {
|
onTest(test: Test) {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPending(test: Test) {
|
onSkippedTest(test: Test) {
|
||||||
this.pending.push(test);
|
this.skipped.push(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdOut(test: Test, chunk: string | Buffer) {
|
onTestStdOut(test: Test, chunk: string | Buffer) {
|
||||||
if (!this.config.quiet)
|
if (!this.config.quiet)
|
||||||
process.stdout.write(chunk);
|
process.stdout.write(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdErr(test: Test, chunk: string | Buffer) {
|
onTestStdErr(test: Test, chunk: string | Buffer) {
|
||||||
if (!this.config.quiet)
|
if (!this.config.quiet)
|
||||||
process.stderr.write(chunk);
|
process.stderr.write(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPass(test: Test) {
|
onTestPassed(test: Test) {
|
||||||
this.passes.push(test);
|
this.passes.push(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFail(test: Test) {
|
onTestFailed(test: Test) {
|
||||||
if (test.duration >= test.timeout)
|
if (test.duration >= test.timeout)
|
||||||
this.timeouts.push(test);
|
this.timeouts.push(test);
|
||||||
else
|
else
|
||||||
|
|
@ -88,8 +88,8 @@ export class BaseReporter implements Reporter {
|
||||||
|
|
||||||
console.log(colors.green(` ${this.passes.length} passed`) + colors.dim(` (${milliseconds(this.duration)})`));
|
console.log(colors.green(` ${this.passes.length} passed`) + colors.dim(` (${milliseconds(this.duration)})`));
|
||||||
|
|
||||||
if (this.pending.length)
|
if (this.skipped.length)
|
||||||
console.log(colors.yellow(` ${this.pending.length} skipped`));
|
console.log(colors.yellow(` ${this.skipped.length} skipped`));
|
||||||
|
|
||||||
if (this.failures.length) {
|
if (this.failures.length) {
|
||||||
console.log(colors.red(` ${this.failures.length} failed`));
|
console.log(colors.red(` ${this.failures.length} failed`));
|
||||||
|
|
|
||||||
|
|
@ -19,18 +19,18 @@ import { BaseReporter } from './base';
|
||||||
import { Test } from '../test';
|
import { Test } from '../test';
|
||||||
|
|
||||||
class DotReporter extends BaseReporter {
|
class DotReporter extends BaseReporter {
|
||||||
onPending(test: Test) {
|
onSkippedTest(test: Test) {
|
||||||
super.onPending(test);
|
super.onSkippedTest(test);
|
||||||
process.stdout.write(colors.yellow('∘'))
|
process.stdout.write(colors.yellow('∘'))
|
||||||
}
|
}
|
||||||
|
|
||||||
onPass(test: Test) {
|
onTestPassed(test: Test) {
|
||||||
super.onPass(test);
|
super.onTestPassed(test);
|
||||||
process.stdout.write(colors.green('·'));
|
process.stdout.write(colors.green('·'));
|
||||||
}
|
}
|
||||||
|
|
||||||
onFail(test: Test) {
|
onTestFailed(test: Test) {
|
||||||
super.onFail(test);
|
super.onTestFailed(test);
|
||||||
if (test.duration >= test.timeout)
|
if (test.duration >= test.timeout)
|
||||||
process.stdout.write(colors.red('T'));
|
process.stdout.write(colors.red('T'));
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class JSONReporter extends BaseReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _serializeSuite(suite: Suite): any {
|
private _serializeSuite(suite: Suite): any {
|
||||||
if (!suite.eachTest(test => true))
|
if (!suite.findTest(test => true))
|
||||||
return null;
|
return null;
|
||||||
const suites = suite.suites.map(suite => this._serializeSuite(suite)).filter(s => s);
|
const suites = suite.suites.map(suite => this._serializeSuite(suite)).filter(s => s);
|
||||||
return {
|
return {
|
||||||
|
|
@ -50,7 +50,7 @@ class JSONReporter extends BaseReporter {
|
||||||
title: test.title,
|
title: test.title,
|
||||||
file: test.file,
|
file: test.file,
|
||||||
only: test.only,
|
only: test.only,
|
||||||
pending: test.pending,
|
skipped: test.skipped,
|
||||||
slow: test.slow,
|
slow: test.slow,
|
||||||
duration: test.duration,
|
duration: test.duration,
|
||||||
timeout: test.timeout,
|
timeout: test.timeout,
|
||||||
|
|
|
||||||
|
|
@ -32,21 +32,21 @@ class ListReporter extends BaseReporter {
|
||||||
process.stdout.write(' ' + colors.gray(test.fullTitle() + ': '));
|
process.stdout.write(' ' + colors.gray(test.fullTitle() + ': '));
|
||||||
}
|
}
|
||||||
|
|
||||||
onPending(test: Test) {
|
onSkippedTest(test: Test) {
|
||||||
super.onPending(test);
|
super.onSkippedTest(test);
|
||||||
process.stdout.write(colors.green(' - ') + colors.cyan(test.fullTitle()));
|
process.stdout.write(colors.green(' - ') + colors.cyan(test.fullTitle()));
|
||||||
process.stdout.write('\n');
|
process.stdout.write('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
onPass(test: Test) {
|
onTestPassed(test: Test) {
|
||||||
super.onPass(test);
|
super.onTestPassed(test);
|
||||||
process.stdout.write('\u001b[2K\u001b[0G');
|
process.stdout.write('\u001b[2K\u001b[0G');
|
||||||
process.stdout.write(colors.green(' ✓ ') + colors.gray(test.fullTitle()));
|
process.stdout.write(colors.green(' ✓ ') + colors.gray(test.fullTitle()));
|
||||||
process.stdout.write('\n');
|
process.stdout.write('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
onFail(test: Test) {
|
onTestFailed(test: Test) {
|
||||||
super.onFail(test);
|
super.onTestFailed(test);
|
||||||
process.stdout.write('\u001b[2K\u001b[0G');
|
process.stdout.write('\u001b[2K\u001b[0G');
|
||||||
process.stdout.write(colors.red(` ${++this._failure}) ` + test.fullTitle()));
|
process.stdout.write(colors.red(` ${++this._failure}) ` + test.fullTitle()));
|
||||||
process.stdout.write('\n');
|
process.stdout.write('\n');
|
||||||
|
|
|
||||||
|
|
@ -35,29 +35,29 @@ export class Multiplexer implements Reporter {
|
||||||
reporter.onTest(test);
|
reporter.onTest(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPending(test: Test) {
|
onSkippedTest(test: Test) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
reporter.onPending(test);
|
reporter.onSkippedTest(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdOut(test: Test, chunk: string | Buffer) {
|
onTestStdOut(test: Test, chunk: string | Buffer) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
reporter.onStdOut(test, chunk);
|
reporter.onTestStdOut(test, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdErr(test: Test, chunk: string | Buffer) {
|
onTestStdErr(test: Test, chunk: string | Buffer) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
reporter.onStdErr(test, chunk);
|
reporter.onTestStdErr(test, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPass(test: Test) {
|
onTestPassed(test: Test) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
reporter.onPass(test);
|
reporter.onTestPassed(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFail(test: Test) {
|
onTestFailed(test: Test) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
reporter.onFail(test);
|
reporter.onTestFailed(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnd() {
|
onEnd() {
|
||||||
|
|
|
||||||
|
|
@ -84,30 +84,30 @@ class PytestReporter extends BaseReporter {
|
||||||
row.startTime = Date.now();
|
row.startTime = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPending(test: Test) {
|
onSkippedTest(test: Test) {
|
||||||
super.onPending(test);
|
super.onSkippedTest(test);
|
||||||
this._append(test, colors.yellow('∘'));
|
this._append(test, colors.yellow('∘'));
|
||||||
this._progress.push('S');
|
this._progress.push('S');
|
||||||
this._throttler.schedule();
|
this._throttler.schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdOut(test: Test, chunk: string | Buffer) {
|
onTestStdOut(test: Test, chunk: string | Buffer) {
|
||||||
this._repaint(chunk);
|
this._repaint(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdErr(test: Test, chunk: string | Buffer) {
|
onTestStdErr(test: Test, chunk: string | Buffer) {
|
||||||
this._repaint(chunk);
|
this._repaint(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPass(test: Test) {
|
onTestPassed(test: Test) {
|
||||||
super.onPass(test);
|
super.onTestPassed(test);
|
||||||
this._append(test, colors.green('✓'));
|
this._append(test, colors.green('✓'));
|
||||||
this._progress.push('P');
|
this._progress.push('P');
|
||||||
this._throttler.schedule();
|
this._throttler.schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFail(test: Test) {
|
onTestFailed(test: Test) {
|
||||||
super.onFail(test);
|
super.onTestFailed(test);
|
||||||
const title = test.duration >= test.timeout ? colors.red('T') : colors.red('F');
|
const title = test.duration >= test.timeout ? colors.red('T') : colors.red('F');
|
||||||
const row = this._append(test, title);
|
const row = this._append(test, title);
|
||||||
row.failed = true;
|
row.failed = true;
|
||||||
|
|
@ -148,8 +148,8 @@ class PytestReporter extends BaseReporter {
|
||||||
const status = [];
|
const status = [];
|
||||||
if (this.passes.length)
|
if (this.passes.length)
|
||||||
status.push(colors.green(`${this.passes.length} passed`));
|
status.push(colors.green(`${this.passes.length} passed`));
|
||||||
if (this.pending.length)
|
if (this.skipped.length)
|
||||||
status.push(colors.yellow(`${this.pending.length} skipped`));
|
status.push(colors.yellow(`${this.skipped.length} skipped`));
|
||||||
if (this.failures.length)
|
if (this.failures.length)
|
||||||
status.push(colors.red(`${this.failures.length} failed`));
|
status.push(colors.red(`${this.failures.length} failed`));
|
||||||
if (this.timeouts.length)
|
if (this.timeouts.length)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export class Runner {
|
||||||
private _workers = new Set<Worker>();
|
private _workers = new Set<Worker>();
|
||||||
private _freeWorkers: Worker[] = [];
|
private _freeWorkers: Worker[] = [];
|
||||||
private _workerClaimers: (() => void)[] = [];
|
private _workerClaimers: (() => void)[] = [];
|
||||||
stats: { duration: number; failures: number; passes: number; pending: number; tests: number; };
|
stats: { duration: number; failures: number; passes: number; skipped: number; tests: number; };
|
||||||
|
|
||||||
private _testById = new Map<string, Test>();
|
private _testById = new Map<string, Test>();
|
||||||
private _queue: TestRunnerEntry[] = [];
|
private _queue: TestRunnerEntry[] = [];
|
||||||
|
|
@ -44,13 +44,13 @@ export class Runner {
|
||||||
duration: 0,
|
duration: 0,
|
||||||
failures: 0,
|
failures: 0,
|
||||||
passes: 0,
|
passes: 0,
|
||||||
pending: 0,
|
skipped: 0,
|
||||||
tests: 0,
|
tests: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
this._suite = suite;
|
this._suite = suite;
|
||||||
for (const suite of this._suite.suites) {
|
for (const suite of this._suite.suites) {
|
||||||
suite.eachTest(test => {
|
suite.findTest(test => {
|
||||||
this._testById.set(`${test._ordinal}@${suite.file}::[${suite._configurationString}]`, test);
|
this._testById.set(`${test._ordinal}@${suite.file}::[${suite._configurationString}]`, test);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +67,9 @@ export class Runner {
|
||||||
const result: TestRunnerEntry[] = [];
|
const result: TestRunnerEntry[] = [];
|
||||||
for (const suite of this._suite.suites) {
|
for (const suite of this._suite.suites) {
|
||||||
const ordinals: number[] = [];
|
const ordinals: number[] = [];
|
||||||
suite.eachTest(test => ordinals.push(test._ordinal) && false);
|
suite.findTest(test => ordinals.push(test._ordinal) && false);
|
||||||
|
if (!ordinals.length)
|
||||||
|
continue;
|
||||||
result.push({
|
result.push({
|
||||||
ordinals,
|
ordinals,
|
||||||
file: suite.file,
|
file: suite.file,
|
||||||
|
|
@ -109,15 +111,26 @@ export class Runner {
|
||||||
let doneCallback;
|
let doneCallback;
|
||||||
const result = new Promise(f => doneCallback = f);
|
const result = new Promise(f => doneCallback = f);
|
||||||
worker.once('done', params => {
|
worker.once('done', params => {
|
||||||
// When worker encounters error, we will restart it.
|
if (!params.failedTestId && !params.fatalError) {
|
||||||
if (params.error || params.fatalError) {
|
|
||||||
this._restartWorker(worker);
|
|
||||||
// If there are remaining tests, we will queue them.
|
|
||||||
if (params.remaining.length && !params.fatalError)
|
|
||||||
this._queue.unshift({ ...entry, ordinals: params.remaining });
|
|
||||||
} else {
|
|
||||||
this._workerAvailable(worker);
|
this._workerAvailable(worker);
|
||||||
|
doneCallback();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When worker encounters error, we will restart it.
|
||||||
|
this._restartWorker(worker);
|
||||||
|
|
||||||
|
// In case of fatal error, we are done with the entry.
|
||||||
|
if (params.fatalError) {
|
||||||
|
doneCallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remaining = params.remaining;
|
||||||
|
if (params.remaining.length)
|
||||||
|
this._queue.unshift({ ...entry, ordinals: remaining });
|
||||||
|
|
||||||
|
// This job is over, we just scheduled another one.
|
||||||
doneCallback();
|
doneCallback();
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -149,30 +162,30 @@ export class Runner {
|
||||||
++this.stats.tests;
|
++this.stats.tests;
|
||||||
this._reporter.onTest(this._updateTest(params.test));
|
this._reporter.onTest(this._updateTest(params.test));
|
||||||
});
|
});
|
||||||
worker.on('pending', params => {
|
worker.on('skipped', params => {
|
||||||
++this.stats.tests;
|
++this.stats.tests;
|
||||||
++this.stats.pending;
|
++this.stats.skipped;
|
||||||
this._reporter.onPending(this._updateTest(params.test));
|
this._reporter.onSkippedTest(this._updateTest(params.test));
|
||||||
});
|
});
|
||||||
worker.on('pass', params => {
|
worker.on('pass', params => {
|
||||||
++this.stats.passes;
|
++this.stats.passes;
|
||||||
this._reporter.onPass(this._updateTest(params.test));
|
this._reporter.onTestPassed(this._updateTest(params.test));
|
||||||
});
|
});
|
||||||
worker.on('fail', params => {
|
worker.on('fail', params => {
|
||||||
++this.stats.failures;
|
++this.stats.failures;
|
||||||
this._reporter.onFail(this._updateTest(params.test));
|
this._reporter.onTestFailed(this._updateTest(params.test));
|
||||||
});
|
});
|
||||||
worker.on('stdout', params => {
|
worker.on('stdout', params => {
|
||||||
const chunk = chunkFromParams(params);
|
const chunk = chunkFromParams(params);
|
||||||
const test = this._testById.get(params.testId);
|
const test = this._testById.get(params.testId);
|
||||||
test.stdout.push(chunk);
|
test.stdout.push(chunk);
|
||||||
this._reporter.onStdOut(test, chunk);
|
this._reporter.onTestStdOut(test, chunk);
|
||||||
});
|
});
|
||||||
worker.on('stderr', params => {
|
worker.on('stderr', params => {
|
||||||
const chunk = chunkFromParams(params);
|
const chunk = chunkFromParams(params);
|
||||||
const test = this._testById.get(params.testId);
|
const test = this._testById.get(params.testId);
|
||||||
test.stderr.push(chunk);
|
test.stderr.push(chunk);
|
||||||
this._reporter.onStdErr(test, chunk);
|
this._reporter.onTestStdErr(test, chunk);
|
||||||
});
|
});
|
||||||
worker.on('exit', () => {
|
worker.on('exit', () => {
|
||||||
this._workers.delete(worker);
|
this._workers.delete(worker);
|
||||||
|
|
@ -279,7 +292,7 @@ class InProcessWorker extends Worker {
|
||||||
delete require.cache[entry.file];
|
delete require.cache[entry.file];
|
||||||
const { TestRunner } = require('./testRunner');
|
const { TestRunner } = require('./testRunner');
|
||||||
const testRunner = new TestRunner(entry, this.runner._config, 0);
|
const testRunner = new TestRunner(entry, this.runner._config, 0);
|
||||||
for (const event of ['test', 'pending', 'pass', 'fail', 'done', 'stdout', 'stderr'])
|
for (const event of ['test', 'skipped', 'pass', 'fail', 'done', 'stdout', 'stderr'])
|
||||||
testRunner.on(event, this.emit.bind(this, event));
|
testRunner.on(event, this.emit.bind(this, event));
|
||||||
testRunner.run();
|
testRunner.run();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type RunnerConfig = {
|
export type RunnerConfig = {
|
||||||
|
forbidOnly?: boolean;
|
||||||
jobs: number;
|
jobs: number;
|
||||||
outputDir: string;
|
outputDir: string;
|
||||||
snapshotDir: string;
|
snapshotDir: string;
|
||||||
|
|
|
||||||
|
|
@ -64,9 +64,9 @@ export function spec(suite: Suite, file: string, timeout: number): () => void {
|
||||||
if (only)
|
if (only)
|
||||||
test.only = true;
|
test.only = true;
|
||||||
if (!only && specs.skip && specs.skip[0])
|
if (!only && specs.skip && specs.skip[0])
|
||||||
test.pending = true;
|
test.skipped = true;
|
||||||
if (!only && specs.fail && specs.fail[0])
|
if (!only && specs.fail && specs.fail[0])
|
||||||
test.pending = true;
|
test.skipped = true;
|
||||||
suite._addTest(test);
|
suite._addTest(test);
|
||||||
return test;
|
return test;
|
||||||
});
|
});
|
||||||
|
|
@ -79,9 +79,9 @@ export function spec(suite: Suite, file: string, timeout: number): () => void {
|
||||||
if (only)
|
if (only)
|
||||||
child.only = true;
|
child.only = true;
|
||||||
if (!only && specs.skip && specs.skip[0])
|
if (!only && specs.skip && specs.skip[0])
|
||||||
child.pending = true;
|
child.skipped = true;
|
||||||
if (!only && specs.fail && specs.fail[0])
|
if (!only && specs.fail && specs.fail[0])
|
||||||
child.pending = true;
|
child.skipped = true;
|
||||||
suites.unshift(child);
|
suites.unshift(child);
|
||||||
fn();
|
fn();
|
||||||
suites.shift();
|
suites.shift();
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export class Test {
|
||||||
title: string;
|
title: string;
|
||||||
file: string;
|
file: string;
|
||||||
only = false;
|
only = false;
|
||||||
pending = false;
|
skipped = false;
|
||||||
slow = false;
|
slow = false;
|
||||||
duration = 0;
|
duration = 0;
|
||||||
timeout = 0;
|
timeout = 0;
|
||||||
|
|
@ -53,7 +53,7 @@ export class Test {
|
||||||
test.suite = this.suite;
|
test.suite = this.suite;
|
||||||
test.only = this.only;
|
test.only = this.only;
|
||||||
test.file = this.file;
|
test.file = this.file;
|
||||||
test.pending = this.pending;
|
test.skipped = this.skipped;
|
||||||
test.timeout = this.timeout;
|
test.timeout = this.timeout;
|
||||||
test._overriddenFn = this._overriddenFn;
|
test._overriddenFn = this._overriddenFn;
|
||||||
return test;
|
return test;
|
||||||
|
|
@ -66,7 +66,7 @@ export class Suite {
|
||||||
suites: Suite[] = [];
|
suites: Suite[] = [];
|
||||||
tests: Test[] = [];
|
tests: Test[] = [];
|
||||||
only = false;
|
only = false;
|
||||||
pending = false;
|
skipped = false;
|
||||||
file: string;
|
file: string;
|
||||||
configuration: Configuration;
|
configuration: Configuration;
|
||||||
_configurationString: string;
|
_configurationString: string;
|
||||||
|
|
@ -87,14 +87,14 @@ export class Suite {
|
||||||
|
|
||||||
total(): number {
|
total(): number {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
this.eachTest(fn => {
|
this.findTest(fn => {
|
||||||
++count;
|
++count;
|
||||||
});
|
});
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
_isPending(): boolean {
|
_isSkipped(): boolean {
|
||||||
return this.pending || (this.parent && this.parent._isPending());
|
return this.skipped || (this.parent && this.parent._isSkipped());
|
||||||
}
|
}
|
||||||
|
|
||||||
_addTest(test: Test) {
|
_addTest(test: Test) {
|
||||||
|
|
@ -117,9 +117,9 @@ export class Suite {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
eachTest(fn: (test: Test) => boolean | void): boolean {
|
findTest(fn: (test: Test) => boolean | void): boolean {
|
||||||
for (const suite of this.suites) {
|
for (const suite of this.suites) {
|
||||||
if (suite.eachTest(fn))
|
if (suite.findTest(fn))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
for (const test of this.tests) {
|
for (const test of this.tests) {
|
||||||
|
|
@ -133,13 +133,13 @@ export class Suite {
|
||||||
const suite = new Suite(this.title);
|
const suite = new Suite(this.title);
|
||||||
suite.only = this.only;
|
suite.only = this.only;
|
||||||
suite.file = this.file;
|
suite.file = this.file;
|
||||||
suite.pending = this.pending;
|
suite.skipped = this.skipped;
|
||||||
return suite;
|
return suite;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renumber() {
|
_renumber() {
|
||||||
let ordinal = 0;
|
let ordinal = 0;
|
||||||
this.eachTest((test: Test) => {
|
this.findTest((test: Test) => {
|
||||||
// All tests are identified with their ordinals.
|
// All tests are identified with their ordinals.
|
||||||
test._ordinal = ordinal++;
|
test._ordinal = ordinal++;
|
||||||
});
|
});
|
||||||
|
|
@ -151,8 +151,8 @@ export class Suite {
|
||||||
|
|
||||||
_hasTestsToRun(): boolean {
|
_hasTestsToRun(): boolean {
|
||||||
let found = false;
|
let found = false;
|
||||||
this.eachTest(test => {
|
this.findTest(test => {
|
||||||
if (!test.pending) {
|
if (!test.skipped) {
|
||||||
found = true;
|
found = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export class TestCollector {
|
||||||
|
|
||||||
const workerGeneratorConfigurations = new Map();
|
const workerGeneratorConfigurations = new Map();
|
||||||
|
|
||||||
suite.eachTest((test: Test) => {
|
suite.findTest((test: Test) => {
|
||||||
// Get all the fixtures that the test needs.
|
// Get all the fixtures that the test needs.
|
||||||
const fixtures = fixturesForCallback(test.fn);
|
const fixtures = fixturesForCallback(test.fn);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,7 @@ export type SerializedTest = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class TestRunner extends EventEmitter {
|
export class TestRunner extends EventEmitter {
|
||||||
private _currentOrdinal = -1;
|
private _failedTestId: string | undefined;
|
||||||
private _failedWithError: any | undefined;
|
|
||||||
private _fatalError: any | undefined;
|
private _fatalError: any | undefined;
|
||||||
private _file: any;
|
private _file: any;
|
||||||
private _ordinals: Set<number>;
|
private _ordinals: Set<number>;
|
||||||
|
|
@ -136,15 +135,14 @@ export class TestRunner extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _runTest(test: Test) {
|
private async _runTest(test: Test) {
|
||||||
if (this._failedWithError)
|
if (this._failedTestId)
|
||||||
return false;
|
return false;
|
||||||
this._test = test;
|
this._test = test;
|
||||||
const ordinal = ++this._currentOrdinal;
|
if (this._ordinals.size && !this._ordinals.has(test._ordinal))
|
||||||
if (this._ordinals.size && !this._ordinals.has(ordinal))
|
|
||||||
return;
|
return;
|
||||||
this._remaining.delete(ordinal);
|
this._remaining.delete(test._ordinal);
|
||||||
if (test.pending || test.suite._isPending()) {
|
if (test.skipped || test.suite._isSkipped()) {
|
||||||
this.emit('pending', { test: this._serializeTest() });
|
this.emit('skipped', { test: this._serializeTest() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,11 +152,11 @@ export class TestRunner extends EventEmitter {
|
||||||
test._startTime = Date.now();
|
test._startTime = Date.now();
|
||||||
if (!this._trialRun)
|
if (!this._trialRun)
|
||||||
await this._testWrapper(test)();
|
await this._testWrapper(test)();
|
||||||
this.emit('pass', { test: this._serializeTest(true) });
|
|
||||||
await this._runHooks(test.suite, 'afterEach', 'after');
|
await this._runHooks(test.suite, 'afterEach', 'after');
|
||||||
|
this.emit('pass', { test: this._serializeTest(true) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
test.error = serializeError(error);
|
test.error = serializeError(error);
|
||||||
this._failedWithError = test.error;
|
this._failedTestId = this._testId();
|
||||||
this.emit('fail', { test: this._serializeTest(true) });
|
this.emit('fail', { test: this._serializeTest(true) });
|
||||||
}
|
}
|
||||||
this._test = null;
|
this._test = null;
|
||||||
|
|
@ -180,7 +178,7 @@ export class TestRunner extends EventEmitter {
|
||||||
|
|
||||||
private _reportDone() {
|
private _reportDone() {
|
||||||
this.emit('done', {
|
this.emit('done', {
|
||||||
error: this._failedWithError,
|
failedTestId: this._failedTestId,
|
||||||
fatalError: this._fatalError,
|
fatalError: this._fatalError,
|
||||||
remaining: [...this._remaining],
|
remaining: [...this._remaining],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ process.on('message', async message => {
|
||||||
}
|
}
|
||||||
if (message.method === 'run') {
|
if (message.method === 'run') {
|
||||||
testRunner = new TestRunner(message.params.entry, message.params.config, workerId);
|
testRunner = new TestRunner(message.params.entry, message.params.config, workerId);
|
||||||
for (const event of ['test', 'pending', 'pass', 'fail', 'done', 'stdout', 'stderr'])
|
for (const event of ['test', 'skipped', 'pass', 'fail', 'done', 'stdout', 'stderr'])
|
||||||
testRunner.on(event, sendMessageToParent.bind(null, event));
|
testRunner.on(event, sendMessageToParent.bind(null, event));
|
||||||
await testRunner.run();
|
await testRunner.run();
|
||||||
testRunner = null;
|
testRunner = null;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue