Skip to content

Commit 0a7b6d0

Browse files
[Fix] Blocking tests exceeding the test timeout fail
Change the `timeoutAfter` handler to _also_ fail if a blocking test's runtime duration exceeds the timeout. Use a boolean flag `timedOut` to avoid double failures in the event of a race condition. Test the new functionality with both the `timeout` option and the `timeoutAfter` assertion.
1 parent 39470e7 commit 0a7b6d0

3 files changed

Lines changed: 107 additions & 9 deletions

File tree

lib/test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,25 @@ Test.prototype.plan = function plan(n) {
189189
Test.prototype.timeoutAfter = function timeoutAfter(ms) {
190190
if (!ms) { throw new Error('timeoutAfter requires a timespan'); }
191191
var self = this;
192+
var startTime = Date.now();
193+
var timedOut = false;
192194
var timeout = safeSetTimeout(function () {
195+
if (timedOut) { return; }
196+
timedOut = true;
193197
self.fail(self.name + ' timed out after ' + ms + 'ms');
194198
self.end();
195199
}, ms);
196200
this.once('end', function () {
197201
safeClearTimeout(timeout);
198202
});
203+
this.once('end', function () {
204+
if (timedOut) { return; }
205+
timedOut = true;
206+
var duration = Date.now() - startTime;
207+
if (duration > ms) {
208+
self.fail(self.name + ' timed out after ' + duration + 'ms');
209+
}
210+
});
199211
};
200212

201213
Test.prototype.end = function end(err) {

test/timeout.js

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,65 @@
11
'use strict';
22

3-
var test = require('../');
3+
var tape = require('../');
4+
var tap = require('tap');
5+
var concat = require('concat-stream');
6+
7+
var stripFullStack = require('./common').stripFullStack;
8+
49
var ran = 0;
510

6-
test('timeout', function (t) {
7-
t.pass('this should run');
8-
ran++;
9-
setTimeout(function () {
11+
tap.test('timeout with multiple tests', function (tt) {
12+
tt.plan(0);
13+
14+
var test = tape.createHarness();
15+
16+
test('timeout', function (t) {
17+
t.pass('this should run');
18+
ran++;
19+
setTimeout(function () {
20+
t.end();
21+
}, 100);
22+
});
23+
24+
test('should still run', { timeout: 50 }, function (t) {
25+
t.equal(ran, 1);
1026
t.end();
11-
}, 100);
27+
});
1228
});
1329

14-
test('should still run', { timeout: 50 }, function (t) {
15-
t.equal(ran, 1);
16-
t.end();
30+
tap.test('timeout with blocking', function (tt) {
31+
tt.plan(1);
32+
33+
var test = tape.createHarness();
34+
35+
test.createStream().pipe(concat({ encoding: 'string' }, function (rows) {
36+
tt.same(stripFullStack(rows), [
37+
'TAP version 13',
38+
'# timeout',
39+
'not ok 1 timeout timed out after 2ms',
40+
' ---',
41+
' operator: fail',
42+
' at: Test.<anonymous> ($TEST/timeout.js:$LINE:$COL)',
43+
' stack: |-',
44+
' Error: timeout timed out after 2ms',
45+
' [... stack stripped ...]',
46+
' at Test.<anonymous> ($TEST/timeout.js:$LINE:$COL)',
47+
' [... stack stripped ...]',
48+
' ...',
49+
'',
50+
'1..1',
51+
'# tests 1',
52+
'# pass 0',
53+
'# fail 1',
54+
''
55+
]);
56+
}));
57+
58+
test('timeout', { timeout: 1 }, function (t) {
59+
var start = Date.now();
60+
while (Date.now() < start + 2) {
61+
// Busy-wait to block the event loop
62+
}
63+
t.end();
64+
});
1765
});

test/timeoutAfter.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,41 @@ tap.test('timeoutAfter with Promises', { skip: typeof Promise === 'undefined' },
9797
});
9898
});
9999
});
100+
101+
tap.test('timeoutAfter with blocking', function (tt) {
102+
tt.plan(1);
103+
104+
var test = tape.createHarness();
105+
106+
test.createStream().pipe(concat({ encoding: 'string' }, function (rows) {
107+
tt.same(stripFullStack(rows), [
108+
'TAP version 13',
109+
'# timeoutAfter',
110+
'not ok 1 timeoutAfter timed out after 2ms',
111+
' ---',
112+
' operator: fail',
113+
' at: Test.<anonymous> ($TEST/timeoutAfter.js:$LINE:$COL)',
114+
' stack: |-',
115+
' Error: timeoutAfter timed out after 2ms',
116+
' [... stack stripped ...]',
117+
' at Test.<anonymous> ($TEST/timeoutAfter.js:$LINE:$COL)',
118+
' [... stack stripped ...]',
119+
' ...',
120+
'',
121+
'1..1',
122+
'# tests 1',
123+
'# pass 0',
124+
'# fail 1',
125+
''
126+
]);
127+
}));
128+
129+
test('timeoutAfter', function (t) {
130+
t.timeoutAfter(1);
131+
var start = Date.now();
132+
while (Date.now() < start + 2) {
133+
// Busy-wait to block the event loop
134+
}
135+
t.end();
136+
});
137+
});

0 commit comments

Comments
 (0)