Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test_runner: add t.after() hook #45792

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 27 additions & 0 deletions doc/api/test.md
Expand Up @@ -1125,6 +1125,33 @@ test('top level test', async (t) => {
});
```

### `context.after([fn][, options])`

<!-- YAML
added: REPLACEME
-->

* `fn` {Function|AsyncFunction} The hook function. The first argument
to this function is a [`TestContext`][] object. If the hook uses callbacks,
the callback function is passed as the second argument. **Default:** A no-op
function.
* `options` {Object} Configuration options for the hook. The following
properties are supported:
* `signal` {AbortSignal} Allows aborting an in-progress hook.
* `timeout` {number} A number of milliseconds the hook will fail after.
If unspecified, subtests inherit this value from their parent.
**Default:** `Infinity`.

This function is used to create a hook that runs after the current test
finishes.

```js
test('top level test', async (t) => {
t.after((t) => t.diagnostic(`finished running ${t.name}`));
assert.ok('some relevant assertion here');
});
```

### `context.afterEach([fn][, options])`

<!-- YAML
Expand Down
22 changes: 13 additions & 9 deletions lib/internal/test_runner/test.js
Expand Up @@ -75,7 +75,6 @@ const testNamePatterns = testNamePatternFlag?.length > 0 ?
(re) => convertStringToRegExp(re, '--test-name-pattern')
) : null;
const kShouldAbort = Symbol('kShouldAbort');
const kRunHook = Symbol('kRunHook');
const kHookNames = ObjectSeal(['before', 'after', 'beforeEach', 'afterEach']);
const kUnwrapErrors = new SafeSet()
.add(kTestCodeFailure).add(kHookFailure)
Expand Down Expand Up @@ -137,6 +136,10 @@ class TestContext {
return subtest.start();
}

after(fn, options) {
this.#test.createHook('after', fn, options);
}

beforeEach(fn, options) {
this.#test.createHook('beforeEach', fn, options);
}
Expand Down Expand Up @@ -476,7 +479,7 @@ class Test extends AsyncResource {
return { ctx, args: [ctx] };
}

async [kRunHook](hook, args) {
async runHook(hook, args) {
cjihrig marked this conversation as resolved.
Show resolved Hide resolved
validateOneOf(hook, 'hook name', kHookNames);
try {
await ArrayPrototypeReduce(this.hooks[hook], async (prev, hook) => {
Expand Down Expand Up @@ -507,13 +510,13 @@ class Test extends AsyncResource {
const { args, ctx } = this.getRunArgs();
const afterEach = runOnce(async () => {
if (this.parent?.hooks.afterEach.length > 0) {
await this.parent[kRunHook]('afterEach', { args, ctx });
await this.parent.runHook('afterEach', { args, ctx });
}
});

try {
if (this.parent?.hooks.beforeEach.length > 0) {
await this.parent[kRunHook]('beforeEach', { args, ctx });
await this.parent.runHook('beforeEach', { args, ctx });
}
const stopPromise = stopTest(this.timeout, this.signal);
const runArgs = ArrayPrototypeSlice(args);
Expand Down Expand Up @@ -546,6 +549,7 @@ class Test extends AsyncResource {
return;
}

await this.runHook('after', { args, ctx });
await afterEach();
this.pass();
} catch (err) {
Expand Down Expand Up @@ -761,9 +765,10 @@ class Suite extends Test {
const hookArgs = this.getRunArgs();
const afterEach = runOnce(async () => {
if (this.parent?.hooks.afterEach.length > 0) {
await this.parent[kRunHook]('afterEach', hookArgs);
await this.parent.runHook('afterEach', hookArgs);
}
});

try {
this.parent.activeSubtests++;
await this.buildSuite;
Expand All @@ -775,19 +780,18 @@ class Suite extends Test {
return;
}


if (this.parent?.hooks.beforeEach.length > 0) {
await this.parent[kRunHook]('beforeEach', hookArgs);
await this.parent.runHook('beforeEach', hookArgs);
}

await this[kRunHook]('before', hookArgs);
await this.runHook('before', hookArgs);

const stopPromise = stopTest(this.timeout, this.signal);
const subtests = this.skipped || this.error ? [] : this.subtests;
const promise = SafePromiseAll(subtests, (subtests) => subtests.start());

await SafePromiseRace([promise, stopPromise]);
await this[kRunHook]('after', hookArgs);
await this.runHook('after', hookArgs);
await afterEach();

this.pass();
Expand Down
8 changes: 7 additions & 1 deletion test/message/test_runner_hooks.js
Expand Up @@ -90,6 +90,8 @@ describe('afterEach throws and test fails', () => {

test('test hooks', async (t) => {
const testArr = [];

t.after(common.mustCall((t) => testArr.push('after ' + t.name)));
t.beforeEach((t) => testArr.push('beforeEach ' + t.name));
t.afterEach((t) => testArr.push('afterEach ' + t.name));
await t.test('1', () => testArr.push('1'));
Expand All @@ -113,26 +115,30 @@ test('test hooks', async (t) => {
});

test('t.beforeEach throws', async (t) => {
t.after(common.mustCall());
t.beforeEach(() => { throw new Error('beforeEach'); });
await t.test('1', () => {});
await t.test('2', () => {});
});

test('t.afterEach throws', async (t) => {
t.after(common.mustCall());
t.afterEach(() => { throw new Error('afterEach'); });
await t.test('1', () => {});
await t.test('2', () => {});
});


test('afterEach when test fails', async (t) => {
t.after(common.mustCall());
t.afterEach(common.mustCall(2));
await t.test('1', () => { throw new Error('test'); });
await t.test('2', () => {});
});

test('afterEach throws and test fails', async (t) => {
afterEach(() => { throw new Error('afterEach'); });
t.after(common.mustCall());
t.afterEach(() => { throw new Error('afterEach'); });
await t.test('1', () => { throw new Error('test'); });
await t.test('2', () => {});
});