Skip to content

Commit

Permalink
Support assertion message in t.timeout()
Browse files Browse the repository at this point in the history
Fixes #2443.

Co-authored-by: Mark Wubben <mark@novemberborn.net>
  • Loading branch information
jonathansamines and novemberborn committed Jul 4, 2020
1 parent f72fab4 commit ca8ea45
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 19 deletions.
13 changes: 12 additions & 1 deletion docs/07-test-timeouts.md
Expand Up @@ -14,11 +14,22 @@ npx ava --timeout=2m # 2 minutes
npx ava --timeout=100 # 100 milliseconds
```

Timeouts can also be set individually for each test. These timeouts are reset each time an assertion is made.
### `t.timeout(ms, message?)`

Timeouts can also be set individually for each test These timeouts are reset each time an assertion is made. The test fails if it takes more than `ms` for an assertion to be made or the test to complete.

```js
test('foo', t => {
t.timeout(100); // 100 milliseconds
// Write your assertions here
});
```

An optional message string can be provided. This can be useful if your test depends on some other setup that may not have been completed:

```js
test('foo', t => {
t.timeout(100, 'make sure database has started'); // 100 milliseconds
// Write your assertions here
});
```
2 changes: 1 addition & 1 deletion index.d.ts
Expand Up @@ -353,7 +353,7 @@ export interface TimeoutFn {
* Set a timeout for the test, in milliseconds. The test will fail if the timeout is exceeded.
* The timeout is reset each time an assertion is made.
*/
(ms: number): void;
(ms: number, message?: string): void;
}

export interface TeardownFn {
Expand Down
31 changes: 20 additions & 11 deletions lib/assert.js
Expand Up @@ -64,6 +64,21 @@ class AssertionError extends Error {
}
exports.AssertionError = AssertionError;

function checkAssertionMessage(assertion, message) {
if (typeof message === 'undefined' || typeof message === 'string') {
return true;
}

return new AssertionError({
assertion,
improperUsage: true,
message: 'The assertion message must be a string',
values: [formatWithLabel('Called with:', message)]
});
}

exports.checkAssertionMessage = checkAssertionMessage;

function getErrorWithLongStackTrace() {
const limitBefore = Error.stackTraceLimit;
Error.stackTraceLimit = Infinity;
Expand Down Expand Up @@ -268,22 +283,16 @@ class Assertions {
});

const checkMessage = (assertion, message, powerAssert = false) => {
if (typeof message === 'undefined' || typeof message === 'string') {
return true;
const result = checkAssertionMessage(assertion, message);
if (result === true) {
return this.true;
}

const error = new AssertionError({
assertion,
improperUsage: true,
message: 'The assertion message must be a string',
values: [formatWithLabel('Called with:', message)]
});

if (powerAssert) {
throw error;
throw result;
}

fail(error);
fail(result);
return false;
};

Expand Down
15 changes: 11 additions & 4 deletions lib/test.js
Expand Up @@ -65,8 +65,8 @@ class ExecutionContext extends assert.Assertions {

this.plan.skip = () => {};

this.timeout = ms => {
test.timeout(ms);
this.timeout = (ms, message) => {
test.timeout(ms, message);
};

this.teardown = callback => {
Expand Down Expand Up @@ -431,15 +431,22 @@ class Test {
this.planError = planError;
}

timeout(ms) {
timeout(ms, message) {
const result = assert.checkAssertionMessage('timeout', message);
if (result !== true) {
this.saveFirstError(result);
// Allow the timeout to be set even when the message is invalid.
message = '';
}

if (this.finishing) {
return;
}

this.clearTimeout();
this.timeoutMs = ms;
this.timeoutTimer = nowAndTimers.setTimeout(() => {
this.saveFirstError(new Error('Test timeout exceeded'));
this.saveFirstError(new Error(message || 'Test timeout exceeded'));

if (this.finishDueToTimeout) {
this.finishDueToTimeout();
Expand Down
10 changes: 8 additions & 2 deletions test/helpers/exec.js
Expand Up @@ -19,11 +19,15 @@ exports.fixture = async (...args) => {
serialization
});

const errors = new WeakMap();
const stats = {
failed: [],
skipped: [],
unsavedSnapshots: [],
passed: []
passed: [],
getError(statObject) {
return errors.get(statObject);
}
};

running.on('message', message => {
Expand Down Expand Up @@ -55,7 +59,9 @@ exports.fixture = async (...args) => {

case 'test-failed': {
const {title, testFile} = message;
stats.failed.push({title, file: normalizePath(cwd, testFile)});
const statObject = {title, file: normalizePath(cwd, testFile)};
errors.set(statObject, message.err);
stats.failed.push(statObject);
break;
}

Expand Down
6 changes: 6 additions & 0 deletions test/test-timeouts/fixtures/custom-message.js
@@ -0,0 +1,6 @@
const test = require('ava');

test('timeout with custom message', async t => {
t.timeout(10, 'time budget exceeded'); // eslint-disable-line ava/assertion-arguments
await new Promise(() => {});
});
6 changes: 6 additions & 0 deletions test/test-timeouts/fixtures/invalid-message.js
@@ -0,0 +1,6 @@
const test = require('ava');

test('timeout with invalid message', t => {
t.timeout(10, 20); // eslint-disable-line ava/assertion-arguments
});

10 changes: 10 additions & 0 deletions test/test-timeouts/fixtures/package.json
@@ -0,0 +1,10 @@
{
"ava": {
"files": [
"*.js"
]
},
"dependencies": {
"ava": "file:../../.."
}
}
20 changes: 20 additions & 0 deletions test/test-timeouts/snapshots/test.js.md
@@ -0,0 +1,20 @@
# Snapshot report for `test/test-timeouts/test.js`

The actual snapshot is saved in `test.js.snap`.

Generated by [AVA](https://avajs.dev).

## timeout messages must be strings

> error message
'The assertion message must be a string'

> formatted values
[
{
formatted: '20',
label: 'Called with:',
},
]
Binary file added test/test-timeouts/snapshots/test.js.snap
Binary file not shown.
15 changes: 15 additions & 0 deletions test/test-timeouts/test.js
@@ -0,0 +1,15 @@
const test = require('@ava/test');
const exec = require('../helpers/exec');

test('timeout message can be specified', async t => {
const result = await t.throwsAsync(exec.fixture('custom-message.js'));
const error = result.stats.getError(result.stats.failed[0]);
t.is(error.message, 'time budget exceeded');
});

test('timeout messages must be strings', async t => {
const result = await t.throwsAsync(exec.fixture('invalid-message.js'));
const error = result.stats.getError(result.stats.failed[0]);
t.snapshot(error.message, 'error message');
t.snapshot(error.values, 'formatted values');
});

0 comments on commit ca8ea45

Please sign in to comment.