feat(test): shrink api to run only, rename pending to skipped (#3636)

This commit is contained in:
Pavel Feldman 2020-08-26 09:38:19 -07:00 committed by GitHub
parent 9b50a6d259
commit a87614a266
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 143 additions and 124 deletions

View file

@ -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);

View file

@ -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';
} }

View file

@ -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;
} }

View file

@ -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`));

View file

@ -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

View file

@ -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,

View file

@ -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');

View file

@ -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() {

View file

@ -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)

View file

@ -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();
} }

View file

@ -15,6 +15,7 @@
*/ */
export type RunnerConfig = { export type RunnerConfig = {
forbidOnly?: boolean;
jobs: number; jobs: number;
outputDir: string; outputDir: string;
snapshotDir: string; snapshotDir: string;

View file

@ -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();

View file

@ -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;
} }

View file

@ -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);

View file

@ -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],
}); });

View file

@ -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;