Skip to content

Commit b65c6d7

Browse files
dflupunovemberborn
authored andcommitted
Add t.timeout()
Fixes #1565
1 parent ed7807e commit b65c6d7

File tree

6 files changed

+123
-2
lines changed

6 files changed

+123
-2
lines changed

docs/02-execution-context.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,7 @@ End the test. Only works with `test.cb()`.
3333
## `t.log(...values)`
3434

3535
Log values contextually alongside the test result instead of immediately printing them to `stdout`. Behaves somewhat like `console.log`, but without support for placeholder tokens.
36+
37+
## `t.timeout(ms)`
38+
39+
Set a timeout for the test, in milliseconds. The test will fail if this timeout is exceeded. The timeout is reset each time an assertion is made.

docs/07-test-timeouts.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,12 @@ npx ava --timeout=10s # 10 seconds
1313
npx ava --timeout=2m # 2 minutes
1414
npx ava --timeout=100 # 100 milliseconds
1515
```
16+
17+
Timeouts can also be set individually for each test. These timeouts are reset each time an assertion is made.
18+
19+
```js
20+
test('foo', t => {
21+
t.timeout(100); // 100 milliseconds
22+
// Write your assertions here
23+
});
24+
```

index.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ export interface ExecutionContext<Context = {}> extends Assertions {
348348

349349
log: LogFn;
350350
plan: PlanFn;
351+
timeout: TimeoutFn;
351352
}
352353

353354
export interface LogFn {
@@ -369,6 +370,14 @@ export interface PlanFn {
369370
skip(count: number): void;
370371
}
371372

373+
export interface TimeoutFn {
374+
/**
375+
* Set a timeout for the test, in milliseconds. The test will fail if the timeout is exceeded.
376+
* The timeout is reset each time an assertion is made.
377+
*/
378+
(ms: number): void;
379+
}
380+
372381
/** The `t` value passed to implementations for tests & hooks declared with the `.cb` modifier. */
373382
export interface CbExecutionContext<Context = {}> extends ExecutionContext<Context> {
374383
/**

index.js.flow

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ export interface ExecutionContext<Context = {}> extends Assertions {
361361

362362
log: LogFn;
363363
plan: PlanFn;
364+
timeout: TimeoutFn;
364365
}
365366

366367
export interface LogFn {
@@ -382,6 +383,14 @@ export interface PlanFn {
382383
skip(count: number): void;
383384
}
384385

386+
export interface TimeoutFn {
387+
/**
388+
* Set a timeout for the test, in milliseconds. The test will fail if the timeout is exceeded.
389+
* The timeout is reset each time an assertion is made.
390+
*/
391+
(ms: number): void;
392+
}
393+
385394
/** The `t` value passed to implementations for tests & hooks declared with the `.cb` modifier. */
386395
export interface CbExecutionContext<Context = {}> extends ExecutionContext<Context> {
387396
/**

lib/test.js

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ function plan(count) {
5353
this.plan(count, captureStack(this.plan));
5454
}
5555

56+
function timeout(ms) {
57+
this.timeout(ms);
58+
}
59+
5660
const testMap = new WeakMap();
5761
class ExecutionContext {
5862
constructor(test) {
@@ -71,7 +75,8 @@ class ExecutionContext {
7175
return props;
7276
}, {
7377
log: {value: log.bind(test)},
74-
plan: {value: boundPlan}
78+
plan: {value: boundPlan},
79+
timeout: {value: timeout.bind(test)}
7580
}));
7681

7782
this.snapshot.skip = () => {
@@ -140,11 +145,14 @@ class Test {
140145
this.endCallbackFinisher = null;
141146
this.finishDueToAttributedError = null;
142147
this.finishDueToInactivity = null;
148+
this.finishDueToTimeout = null;
143149
this.finishing = false;
144150
this.pendingAssertionCount = 0;
145151
this.pendingThrowsAssertion = null;
146152
this.planCount = null;
147153
this.startedAt = 0;
154+
this.timeoutTimer = null;
155+
this.timeoutMs = 0;
148156
}
149157

150158
bindEndCallback() {
@@ -189,6 +197,7 @@ class Test {
189197
}
190198

191199
this.assertCount++;
200+
this.refreshTimeout();
192201
}
193202

194203
addLog(text) {
@@ -202,9 +211,14 @@ class Test {
202211

203212
this.assertCount++;
204213
this.pendingAssertionCount++;
214+
this.refreshTimeout();
215+
205216
promise
206217
.catch(error => this.saveFirstError(error))
207-
.then(() => this.pendingAssertionCount--);
218+
.then(() => {
219+
this.pendingAssertionCount--;
220+
this.refreshTimeout();
221+
});
208222
}
209223

210224
addFailedAssertion(error) {
@@ -213,6 +227,7 @@ class Test {
213227
}
214228

215229
this.assertCount++;
230+
this.refreshTimeout();
216231
this.saveFirstError(error);
217232
}
218233

@@ -234,6 +249,39 @@ class Test {
234249
this.planStack = planStack;
235250
}
236251

252+
timeout(ms) {
253+
if (this.finishing) {
254+
return;
255+
}
256+
257+
this.clearTimeout();
258+
this.timeoutMs = ms;
259+
this.timeoutTimer = nowAndTimers.setTimeout(() => {
260+
this.saveFirstError(new Error('Test timeout exceeded'));
261+
262+
if (this.finishDueToTimeout) {
263+
this.finishDueToTimeout();
264+
}
265+
}, ms);
266+
}
267+
268+
refreshTimeout() {
269+
if (!this.timeoutTimer) {
270+
return;
271+
}
272+
273+
if (this.timeoutTimer.refresh) {
274+
this.timeoutTimer.refresh();
275+
} else {
276+
this.timeout(this.timeoutMs);
277+
}
278+
}
279+
280+
clearTimeout() {
281+
clearTimeout(this.timeoutTimer);
282+
this.timeoutTimer = null;
283+
}
284+
237285
verifyPlan() {
238286
if (!this.assertError && this.planCount !== null && this.planCount !== this.assertCount) {
239287
this.saveFirstError(new assert.AssertionError({
@@ -370,6 +418,10 @@ class Test {
370418
resolve(this.finishPromised());
371419
};
372420

421+
this.finishDueToTimeout = () => {
422+
resolve(this.finishPromised());
423+
};
424+
373425
this.finishDueToInactivity = () => {
374426
this.saveFirstError(new Error('`t.end()` was never called'));
375427
resolve(this.finishPromised());
@@ -383,6 +435,10 @@ class Test {
383435
resolve(this.finishPromised());
384436
};
385437

438+
this.finishDueToTimeout = () => {
439+
resolve(this.finishPromised());
440+
};
441+
386442
this.finishDueToInactivity = () => {
387443
const error = returnedObservable ?
388444
new Error('Observable returned by test never completed') :
@@ -415,6 +471,7 @@ class Test {
415471
return this.waitForPendingThrowsAssertion();
416472
}
417473

474+
this.clearTimeout();
418475
this.verifyPlan();
419476
this.verifyAssertions();
420477

test/test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,3 +764,36 @@ test('implementation runs with null scope', t => {
764764
t.is(this, null);
765765
}).run();
766766
});
767+
768+
test('timeout with promise', t => {
769+
return ava(a => {
770+
a.timeout(10);
771+
return delay(200);
772+
}).run().then(result => {
773+
t.is(result.passed, false);
774+
t.match(result.error.message, /timeout/);
775+
});
776+
});
777+
778+
test('timeout with cb', t => {
779+
return ava.cb(a => {
780+
a.timeout(10);
781+
setTimeout(() => a.end(), 200);
782+
}).run().then(result => {
783+
t.is(result.passed, false);
784+
t.match(result.error.message, /timeout/);
785+
});
786+
});
787+
788+
test('timeout is refreshed on assert', t => {
789+
return ava.cb(a => {
790+
a.timeout(10);
791+
a.plan(3);
792+
setTimeout(() => a.pass(), 5);
793+
setTimeout(() => a.pass(), 10);
794+
setTimeout(() => a.pass(), 15);
795+
setTimeout(() => a.end(), 20);
796+
}).run().then(result => {
797+
t.is(result.passed, true);
798+
});
799+
});

0 commit comments

Comments
 (0)