Skip to content

Commit c1b418f

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 963f5cf commit c1b418f

File tree

6 files changed

+88
-9
lines changed

6 files changed

+88
-9
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: 15 additions & 0 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,
@@ -401,6 +402,11 @@ class Runner extends EventEmitter {
401402

402403
return serialTests.reduce((prev, task) => {
403404
return prev.then(prevOk => {
405+
// Don't start tests after an interrupt.
406+
if (this.interrupted) {
407+
return prevOk;
408+
}
409+
404410
// Prevent subsequent tests from running if `failFast` is enabled and
405411
// the previous test failed.
406412
if (!prevOk && this.failFast) {
@@ -420,6 +426,11 @@ class Runner extends EventEmitter {
420426
return false;
421427
}
422428

429+
// Don't start tests after an interrupt.
430+
if (this.interrupted) {
431+
return true;
432+
}
433+
423434
// If a concurrent test fails, even if `failFast` is enabled it won't
424435
// stop other concurrent tests from running.
425436
return Promise.all(concurrentTests.map(task => {
@@ -448,6 +459,10 @@ class Runner extends EventEmitter {
448459
todoTitles
449460
});
450461
}
462+
463+
interrupt() {
464+
this.interrupted = true;
465+
}
451466
}
452467

453468
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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.serial('second pass', t => {
9+
t.pass();
10+
});
11+
12+
test('third pass', t => {
13+
t.pass();
14+
});

0 commit comments

Comments
 (0)