Skip to content

Commit 8d3c588

Browse files
committed
Interrupt test workers when a test fails and --fail-fast is enabled
If a failure occurs in one worker, attempt to interrupt other workers. This only works as long as the other worker has not yet started running concurrent tests. Fixes #1158.
1 parent 45fae14 commit 8d3c588

File tree

6 files changed

+78
-13
lines changed

6 files changed

+78
-13
lines changed

api.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ class Api extends EventEmitter {
9999
if (test.error) {
100100
bailed = true;
101101
}
102+
103+
for (const fork of pendingForks) {
104+
fork.notifyOfPeerFailure();
105+
}
102106
});
103107
}
104108

lib/fork.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,14 @@ module.exports = (file, opts, execArgv) => {
142142
return promise;
143143
};
144144

145-
promise.send = (name, data) => {
146-
send(name, data);
147-
return promise;
148-
};
149-
150145
promise.exit = () => {
151146
send('init-exit');
152147
return promise;
153148
};
154149

150+
promise.notifyOfPeerFailure = () => {
151+
send('peer-failed');
152+
};
153+
155154
return promise;
156155
};

lib/runner.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Runner extends EventEmitter {
2424

2525
this.activeRunnables = new Set();
2626
this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this);
27+
this.interrupted = false;
2728
this.snapshots = null;
2829
this.stats = {
2930
failCount: 0,
@@ -402,8 +403,8 @@ class Runner extends EventEmitter {
402403
return serialTests.reduce((prev, task) => {
403404
return prev.then(prevOk => {
404405
// Prevent subsequent tests from running if `failFast` is enabled and
405-
// the previous test failed.
406-
if (!prevOk && this.failFast) {
406+
// the previous test failed, or an interrupt was received.
407+
if ((!prevOk && this.failFast) || this.interrupted) {
407408
return false;
408409
}
409410

@@ -415,8 +416,8 @@ class Runner extends EventEmitter {
415416
const beforeHooksOk = prevOkays[0];
416417
const serialOk = prevOkays[1];
417418
// Don't run tests if a `before` hook failed, or if `failFast` is enabled
418-
// and a previous serial test failed.
419-
if (!beforeHooksOk || (!serialOk && this.failFast)) {
419+
// and a previous serial test failed, or an interrupt was received.
420+
if (!beforeHooksOk || (!serialOk && this.failFast) || this.interrupted) {
420421
return false;
421422
}
422423

@@ -448,6 +449,10 @@ class Runner extends EventEmitter {
448449
todoTitles
449450
});
450451
}
452+
453+
interrupt() {
454+
this.interrupted = true;
455+
}
451456
}
452457

453458
module.exports = Runner;

lib/test-worker.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,12 @@ process.on('ava-init-exit', () => {
217217
exit();
218218
});
219219

220+
process.on('ava-peer-failed', () => {
221+
if (runner) {
222+
runner.interrupt();
223+
}
224+
});
225+
220226
// Store value in case to prevent required modules from modifying it.
221227
const testPath = opts.file;
222228

test/api.js

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ test('display filename prefixes for failed test stack traces in subdirs', t => {
202202
});
203203
});
204204

205-
test('fail-fast mode - single file', t => {
205+
test('fail-fast mode - single file & serial', t => {
206206
const api = apiCreator({
207207
failFast: true
208208
});
@@ -236,7 +236,7 @@ test('fail-fast mode - single file', t => {
236236
});
237237
});
238238

239-
test('fail-fast mode - multiple files', t => {
239+
test('fail-fast mode - multiple files & serial', t => {
240240
const api = apiCreator({
241241
failFast: true,
242242
serial: true
@@ -271,7 +271,48 @@ test('fail-fast mode - multiple files', t => {
271271
});
272272
});
273273

274-
test('fail-fast mode - crash', t => {
274+
test('fail-fast mode - multiple files & interrupt', t => {
275+
const api = apiCreator({
276+
failFast: true,
277+
concurrency: 2
278+
});
279+
280+
const tests = [];
281+
282+
api.on('test-run', runStatus => {
283+
runStatus.on('test', test => {
284+
tests.push({
285+
ok: !test.error,
286+
title: test.title
287+
});
288+
});
289+
});
290+
291+
return api.run([
292+
path.join(__dirname, 'fixture/fail-fast/multiple-files/fails.js'),
293+
path.join(__dirname, 'fixture/fail-fast/multiple-files/passes-slow.js')
294+
])
295+
.then(result => {
296+
t.ok(api.options.failFast);
297+
t.strictDeepEqual(tests, [{
298+
ok: true,
299+
title: `fails ${figures.pointerSmall} first pass`
300+
}, {
301+
ok: false,
302+
title: `fails ${figures.pointerSmall} second fail`
303+
}, {
304+
ok: true,
305+
title: `fails ${figures.pointerSmall} third pass`
306+
}, {
307+
ok: true,
308+
title: `passes-slow ${figures.pointerSmall} first pass`
309+
}]);
310+
t.is(result.passCount, 3);
311+
t.is(result.failCount, 1);
312+
});
313+
});
314+
315+
test('fail-fast mode - crash & serial', t => {
275316
const api = apiCreator({
276317
failFast: true,
277318
serial: true
@@ -307,7 +348,7 @@ test('fail-fast mode - crash', t => {
307348
});
308349
});
309350

310-
test('fail-fast mode - timeout', t => {
351+
test('fail-fast mode - timeout & serial', t => {
311352
const api = apiCreator({
312353
failFast: true,
313354
serial: true,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import test from '../../../../';
2+
3+
test.serial('first pass', async t => {
4+
t.pass();
5+
return new Promise(resolve => setTimeout(resolve, 3000));
6+
});
7+
8+
test('second pass', t => {
9+
t.pass();
10+
});

0 commit comments

Comments
 (0)