Skip to content

Commit a223ca4

Browse files
MoLowmarco-ippolito
authored andcommitted
test_runner: run before hook immediately if test started
PR-URL: #52003 Reviewed-By: Raz Luvaton <[email protected]> Reviewed-By: Chemi Atlow <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]>
1 parent f9d9567 commit a223ca4

File tree

8 files changed

+431
-38
lines changed

8 files changed

+431
-38
lines changed

lib/internal/test_runner/harness.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,11 @@ function setup(root) {
151151
const rejectionHandler =
152152
createProcessEventHandler('unhandledRejection', root);
153153
const coverage = configureCoverage(root, globalOptions);
154-
const exitHandler = () => {
154+
const exitHandler = async () => {
155+
if (root.subtests.length === 0 && (root.hooks.before.length > 0 || root.hooks.after.length > 0)) {
156+
// Run global before/after hooks in case there are no tests
157+
await root.run();
158+
}
155159
root.postRun(new ERR_TEST_FAILURE(
156160
'Promise resolution is still pending but the event loop has already resolved',
157161
kCancelledByParent));

lib/internal/test_runner/test.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,19 +176,23 @@ class TestContext {
176176
}
177177

178178
before(fn, options) {
179-
this.#test.createHook('before', fn, options);
179+
this.#test
180+
.createHook('before', fn, { __proto__: null, ...options, hookType: 'before', loc: getCallerLocation() });
180181
}
181182

182183
after(fn, options) {
183-
this.#test.createHook('after', fn, options);
184+
this.#test
185+
.createHook('after', fn, { __proto__: null, ...options, hookType: 'after', loc: getCallerLocation() });
184186
}
185187

186188
beforeEach(fn, options) {
187-
this.#test.createHook('beforeEach', fn, options);
189+
this.#test
190+
.createHook('beforeEach', fn, { __proto__: null, ...options, hookType: 'beforeEach', loc: getCallerLocation() });
188191
}
189192

190193
afterEach(fn, options) {
191-
this.#test.createHook('afterEach', fn, options);
194+
this.#test
195+
.createHook('afterEach', fn, { __proto__: null, ...options, hookType: 'afterEach', loc: getCallerLocation() });
192196
}
193197
}
194198

@@ -496,6 +500,14 @@ class Test extends AsyncResource {
496500
if (name === 'before' || name === 'after') {
497501
hook.run = runOnce(hook.run);
498502
}
503+
if (name === 'before' && this.startTime !== null) {
504+
// Test has already started, run the hook immediately
505+
PromisePrototypeThen(hook.run(this.getRunArgs()), () => {
506+
if (hook.error != null) {
507+
this.fail(hook.error);
508+
}
509+
});
510+
}
499511
ArrayPrototypePush(this.hooks[name], hook);
500512
return hook;
501513
}
@@ -593,26 +605,28 @@ class Test extends AsyncResource {
593605
return;
594606
}
595607

596-
const { args, ctx } = this.getRunArgs();
608+
const hookArgs = this.getRunArgs();
609+
const { args, ctx } = hookArgs;
597610
const after = async () => {
598611
if (this.hooks.after.length > 0) {
599-
await this.runHook('after', { __proto__: null, args, ctx });
612+
await this.runHook('after', hookArgs);
600613
}
601614
};
602615
const afterEach = runOnce(async () => {
603616
if (this.parent?.hooks.afterEach.length > 0) {
604-
await this.parent.runHook('afterEach', { __proto__: null, args, ctx });
617+
await this.parent.runHook('afterEach', hookArgs);
605618
}
606619
});
607620

608621
let stopPromise;
609622

610623
try {
611624
if (this.parent?.hooks.before.length > 0) {
625+
// This hook usually runs immediately, we need to wait for it to finish
612626
await this.parent.runHook('before', this.parent.getRunArgs());
613627
}
614628
if (this.parent?.hooks.beforeEach.length > 0) {
615-
await this.parent.runHook('beforeEach', { __proto__: null, args, ctx });
629+
await this.parent.runHook('beforeEach', hookArgs);
616630
}
617631
stopPromise = stopTest(this.timeout, this.signal);
618632
const runArgs = ArrayPrototypeSlice(args);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
const common = require('../../../common');
3+
const { before, after } = require('node:test');
4+
5+
before(common.mustCall(() => console.log('before')));
6+
after(common.mustCall(() => console.log('after')));
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
before
2+
TAP version 13
3+
after
4+
1..0
5+
# tests 0
6+
# suites 0
7+
# pass 0
8+
# fail 0
9+
# cancelled 0
10+
# skipped 0
11+
# todo 0
12+
# duration_ms *

test/fixtures/test-runner/output/hooks.js

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('describe hooks', () => {
1111
before(function() {
1212
testArr.push('before ' + this.name);
1313
});
14-
after(function() {
14+
after(common.mustCall(function() {
1515
testArr.push('after ' + this.name);
1616
assert.deepStrictEqual(testArr, [
1717
'before describe hooks',
@@ -23,7 +23,7 @@ describe('describe hooks', () => {
2323
'after nested',
2424
'after describe hooks',
2525
]);
26-
});
26+
}));
2727
beforeEach(function() {
2828
testArr.push('beforeEach ' + this.name);
2929
});
@@ -52,18 +52,43 @@ describe('describe hooks', () => {
5252
});
5353
});
5454

55+
describe('describe hooks - no subtests', () => {
56+
const testArr = [];
57+
before(function() {
58+
testArr.push('before ' + this.name);
59+
});
60+
after(common.mustCall(function() {
61+
testArr.push('after ' + this.name);
62+
assert.deepStrictEqual(testArr, [
63+
'before describe hooks - no subtests',
64+
'after describe hooks - no subtests',
65+
]);
66+
}));
67+
beforeEach(common.mustNotCall());
68+
afterEach(common.mustNotCall());
69+
});
70+
5571
describe('before throws', () => {
5672
before(() => { throw new Error('before'); });
5773
it('1', () => {});
5874
test('2', () => {});
5975
});
6076

77+
describe('before throws - no subtests', () => {
78+
before(() => { throw new Error('before'); });
79+
after(common.mustCall());
80+
});
81+
6182
describe('after throws', () => {
6283
after(() => { throw new Error('after'); });
6384
it('1', () => {});
6485
test('2', () => {});
6586
});
6687

88+
describe('after throws - no subtests', () => {
89+
after(() => { throw new Error('after'); });
90+
});
91+
6792
describe('beforeEach throws', () => {
6893
beforeEach(() => { throw new Error('beforeEach'); });
6994
it('1', () => {});
@@ -123,13 +148,48 @@ test('test hooks', async (t) => {
123148
}));
124149
});
125150

151+
152+
test('test hooks - no subtests', async (t) => {
153+
const testArr = [];
154+
155+
t.before((t) => testArr.push('before ' + t.name));
156+
t.after(common.mustCall((t) => testArr.push('after ' + t.name)));
157+
t.beforeEach(common.mustNotCall());
158+
t.afterEach(common.mustNotCall());
159+
160+
t.after(common.mustCall(() => {
161+
assert.deepStrictEqual(testArr, [
162+
'before test hooks - no subtests',
163+
'after test hooks - no subtests',
164+
]);
165+
}));
166+
});
167+
126168
test('t.before throws', async (t) => {
127169
t.after(common.mustCall());
128170
t.before(() => { throw new Error('before'); });
129171
await t.test('1', () => {});
130172
await t.test('2', () => {});
131173
});
132174

175+
test('t.before throws - no subtests', async (t) => {
176+
t.after(common.mustCall());
177+
t.before(() => { throw new Error('before'); });
178+
});
179+
180+
test('t.after throws', async (t) => {
181+
t.before(common.mustCall());
182+
t.after(() => { throw new Error('after'); });
183+
await t.test('1', () => {});
184+
await t.test('2', () => {});
185+
});
186+
187+
test('t.after throws - no subtests', async (t) => {
188+
t.before(common.mustCall());
189+
t.after(() => { throw new Error('after'); });
190+
});
191+
192+
133193
test('t.beforeEach throws', async (t) => {
134194
t.after(common.mustCall());
135195
t.beforeEach(() => { throw new Error('beforeEach'); });

0 commit comments

Comments
 (0)