Skip to content

print pending tests on interrupt #2022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions api.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ class Api extends Emittery {
this._allExtensions = this.options.extensions.all;
this._regexpFullExtensions = new RegExp(`\\.(${this.options.extensions.full.map(ext => escapeStringRegexp(ext)).join('|')})$`);
this._precompiler = null;
this._interruptHandler = () => {};

if (options.ranFromCli) {
process.on('SIGINT', () => this._interruptHandler());
}
}

run(files, runtimeOptions = {}) {
Expand All @@ -69,17 +74,36 @@ class Api extends Emittery {
bailed = true;
}

runStatus.emitStateChange({type: 'timeout', period: timeout});

for (const worker of pendingWorkers) {
timedOutWorkerFiles.add(worker.file);
worker.exit();
}

runStatus.emitStateChange({type: 'timeout', period: timeout, timedOutWorkerFiles});
}, timeout);
} else {
restartTimer = Object.assign(() => {}, {cancel() {}});
}

this._interruptHandler = () => {
if (bailed) {
// Exiting already
return;
}

// Prevent new test files from running
bailed = true;

// Make sure we don't run the timeout handler
restartTimer.cancel();

runStatus.emitStateChange({type: 'interrupt'});

for (const worker of pendingWorkers) {
worker.exit();
}
};

// Find all test files.
return new AvaFiles({cwd: apiOptions.resolveTestsFrom, files, extensions: this._allExtensions}).findTestFiles()
.then(files => {
Expand Down
14 changes: 12 additions & 2 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ exports.run = () => { // eslint-disable-line complexity
snapshotDir: conf.snapshotDir ? path.resolve(projectDir, conf.snapshotDir) : null,
color: conf.color,
workerArgv: cli.flags['--'],
parallelRuns
parallelRuns,
ranFromCli: true
});

let reporter;
Expand All @@ -230,7 +231,16 @@ exports.run = () => { // eslint-disable-line complexity
});
}

api.on('run', plan => reporter.startRun(plan));
api.on('run', plan => {
reporter.startRun(plan);

plan.status.on('stateChange', evt => {
if (evt.type === 'interrupt') {
reporter.endRun();
process.exit(1); // eslint-disable-line unicorn/no-process-exit
}
});
});

const files = cli.input.length > 0 ? cli.input : arrify(conf.files);

Expand Down
24 changes: 23 additions & 1 deletion lib/reporters/mini.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,14 @@ class MiniReporter {
this.writeTestSummary(evt);
break;
case 'timeout':
this.writeWithCounts(colors.error(`${figures.cross} Exited because no new tests completed within the last ${evt.period}ms of inactivity`));
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Timed out while running tests`));
this.lineWriter.writeLine('');
this.writePendingTests(evt);
break;
case 'interrupt':
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Exiting due to SIGINT`));
this.lineWriter.writeLine('');
this.writePendingTests(evt);
break;
case 'uncaught-exception':
this.uncaughtExceptions.push(evt);
Expand Down Expand Up @@ -347,6 +354,21 @@ class MiniReporter {
this.writeErr(evt);
}

writePendingTests(evt) {
for (const [file, testsInFile] of evt.pendingTests) {
if (testsInFile.size === 0) {
continue;
}

this.lineWriter.writeLine(`${testsInFile.size} tests were pending in ${file}\n`);
for (const title of testsInFile) {
this.lineWriter.writeLine(`${figures.circleDotted} ${this.prefixTitle(file, title)}`);
}

this.lineWriter.writeLine('');
}
}

endRun() { // eslint-disable-line complexity
this.spinner.stop();
cliCursor.show(this.reportStream);
Expand Down
58 changes: 18 additions & 40 deletions lib/reporters/verbose.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ class VerboseReporter {
const fileStats = this.stats && evt.testFile ? this.stats.byFile.get(evt.testFile) : null;

switch (evt.type) {
case 'declared-test':
this.addTestRunning(evt.testFile, evt.title);
break;
case 'hook-failed':
this.failures.push(evt);
this.writeTestSummary(evt);
Expand Down Expand Up @@ -145,20 +142,25 @@ class VerboseReporter {
this.stats = evt.stats;
break;
case 'test-failed':
this.removeTestRunning(evt.testFile, evt.title);
this.failures.push(evt);
this.writeTestSummary(evt);
break;
case 'test-passed':
this.removeTestRunning(evt.testFile, evt.title);
if (evt.knownFailing) {
this.knownFailures.push(evt);
}

this.writeTestSummary(evt);
break;
case 'timeout':
this.writeTimeoutSummary(evt);
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Timed out while running tests`));
this.lineWriter.writeLine('');
this.writePendingTests(evt);
break;
case 'interrupt':
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Exiting due to SIGINT`));
this.lineWriter.writeLine('');
this.writePendingTests(evt);
break;
case 'uncaught-exception':
this.lineWriter.ensureEmptyLine();
Expand Down Expand Up @@ -261,42 +263,18 @@ class VerboseReporter {
}
}

addTestRunning(file, title) {
if (!this.runningTestFiles.has(file)) {
this.runningTestFiles.set(file, new Set());
}

this.runningTestFiles.get(file).add(title);
}

removeTestRunning(file, title) {
const byFile = this.runningTestFiles.get(file);
if (byFile) {
byFile.delete(title);
}
}

writeTimeoutSummary(evt) {
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Exited because no new tests completed within the last ${evt.period}ms of inactivity`));
let wroteTrailingSeparator = false;

for (const timedOutFile of evt.timedOutWorkerFiles) {
const byFile = this.runningTestFiles.get(timedOutFile);
if (byFile) {
this.runningTestFiles.delete(timedOutFile);

if (!wroteTrailingSeparator) {
this.lineWriter.writeLine('');
}

this.lineWriter.writeLine(`${byFile.size} tests still running in ${timedOutFile}:\n`);
for (const title of byFile) {
this.lineWriter.writeLine(`${figures.circleDotted} ${this.prefixTitle(timedOutFile, title)}`);
}
writePendingTests(evt) {
for (const [file, testsInFile] of evt.pendingTests) {
if (testsInFile.size === 0) {
continue;
}

this.lineWriter.writeLine('');
wroteTrailingSeparator = true;
this.lineWriter.writeLine(`${testsInFile.size} tests were pending in ${file}\n`);
for (const title of testsInFile) {
this.lineWriter.writeLine(`${figures.circleDotted} ${this.prefixTitle(file, title)}`);
}

this.lineWriter.writeLine('');
}
}

Expand Down
26 changes: 26 additions & 0 deletions lib/run-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class RunStatus extends Emittery {
constructor(files, parallelRuns) {
super();

this.pendingTests = new Map();

this.stats = {
byFile: new Map(),
declaredTests: 0,
Expand Down Expand Up @@ -43,6 +45,8 @@ class RunStatus extends Emittery {
uncaughtExceptions: 0,
unhandledRejections: 0
});

this.pendingTests.set(testFile, new Set());
worker.onStateChange(data => this.emitStateChange(data));
}

Expand All @@ -55,6 +59,7 @@ class RunStatus extends Emittery {
case 'declared-test':
stats.declaredTests++;
fileStats.declaredTests++;
this.addPendingTest(event);
break;
case 'hook-failed':
stats.failedHooks++;
Expand Down Expand Up @@ -87,6 +92,7 @@ class RunStatus extends Emittery {
fileStats.failedTests++;
stats.remainingTests--;
fileStats.remainingTests--;
this.removePendingTest(event);
break;
case 'test-passed':
if (event.knownFailing) {
Expand All @@ -99,10 +105,17 @@ class RunStatus extends Emittery {

stats.remainingTests--;
fileStats.remainingTests--;
this.removePendingTest(event);
break;
case 'timeout':
event.pendingTests = this.pendingTests;
this.pendingTests = new Map();
stats.timeouts++;
break;
case 'interrupt':
event.pendingTests = this.pendingTests;
this.pendingTests = new Map();
break;
case 'uncaught-exception':
stats.uncaughtExceptions++;
fileStats.uncaughtExceptions++;
Expand Down Expand Up @@ -149,5 +162,18 @@ class RunStatus extends Emittery {

return 0;
}

addPendingTest(event) {
if (this.pendingTests.has(event.testFile)) {
this.pendingTests.get(event.testFile).add(event.title);
}
}

removePendingTest(event) {
if (this.pendingTests.has(event.testFile)) {
this.pendingTests.get(event.testFile).delete(event.title);
}
}
}

module.exports = RunStatus;
14 changes: 13 additions & 1 deletion test/integration/assorted.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,23 @@ const {execCli} = require('../helper/cli');
test('timeout', t => {
execCli(['long-running.js', '-T', '1s'], (err, stdout) => {
t.ok(err);
t.match(stdout, /Exited because no new tests completed within the last 1000ms of inactivity/);
t.match(stdout, /Timed out/);
t.end();
});
});

// FIXME: This test fails in CI, but not locally. Re-enable at some point…
// test('interrupt', t => {
// const proc = execCli(['long-running.js'], (_, stdout) => {
// t.match(stdout, /SIGINT/);
// t.end();
// });
//
// setTimeout(() => {
// proc.kill('SIGINT');
// }, 2000);
// });

test('include anonymous functions in error reports', t => {
execCli('error-in-anonymous-function.js', (err, stdout) => {
t.ok(err);
Expand Down
8 changes: 4 additions & 4 deletions test/reporters/verbose.timeoutinmultiplefiles.log
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
✔ a › a passes two
---tty-stream-chunk-separator

✖ Exited because no new tests completed within the last 10000ms of inactivity
✖ Timed out while running tests

2 tests still running in ~/test/fixture/report/timeoutinmultiplefiles/a.js:
2 tests were pending in ~/test/fixture/report/timeoutinmultiplefiles/a.js

◌ a › a slow
◌ a › a slow two
Expand All @@ -18,9 +18,9 @@
✔ b › b passes two
---tty-stream-chunk-separator

✖ Exited because no new tests completed within the last 10000ms of inactivity
✖ Timed out while running tests

3 tests still running in ~/test/fixture/report/timeoutinmultiplefiles/b.js:
3 tests were pending in ~/test/fixture/report/timeoutinmultiplefiles/b.js

◌ b › b slow
◌ b › b slow two
Expand Down
4 changes: 2 additions & 2 deletions test/reporters/verbose.timeoutinsinglefile.log
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
✔ passes two
---tty-stream-chunk-separator

✖ Exited because no new tests completed within the last 10000ms of inactivity
✖ Timed out while running tests

2 tests still running in ~/test/fixture/report/timeoutinsinglefile/a.js:
2 tests were pending in ~/test/fixture/report/timeoutinsinglefile/a.js

◌ slow
◌ slow two
Expand Down