Skip to content

Commit a0750d0

Browse files
test_runner: scope file-level hooks per file in no-isolation mode
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com>
1 parent 4f08c64 commit a0750d0

File tree

7 files changed

+57
-70
lines changed

7 files changed

+57
-70
lines changed

doc/api/cli.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,9 +2744,12 @@ changes:
27442744

27452745
Configures the type of test isolation used in the test runner. When `mode` is
27462746
`'process'`, each test file is run in a separate child process. When `mode` is
2747-
`'none'`, all test files run in the same process as the test runner. The default
2748-
isolation mode is `'process'`. This flag is ignored if the `--test` flag is not
2749-
present. See the [test runner execution model][] section for more information.
2747+
`'none'`, all test files run in the same process as the test runner. Each test
2748+
file is wrapped in an implicit suite, so that lifecycle hooks such as
2749+
`beforeEach()` and `afterEach()` declared at the top level of a file only apply
2750+
to tests within that file. The default isolation mode is `'process'`. This flag
2751+
is ignored if the `--test` flag is not present. See the
2752+
[test runner execution model][] section for more information.
27502753

27512754
### `--test-name-pattern`
27522755

doc/api/test.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,8 +1631,10 @@ changes:
16311631
spawned. **Default:** `undefined`.
16321632
* `isolation` {string} Configures the type of test isolation. If set to
16331633
`'process'`, each test file is run in a separate child process. If set to
1634-
`'none'`, all test files run in the current process. **Default:**
1635-
`'process'`.
1634+
`'none'`, all test files run in the current process. Each test file is
1635+
wrapped in an implicit suite, so that lifecycle hooks such as
1636+
`beforeEach()` and `afterEach()` declared at the top level of a file only
1637+
apply to tests within that file. **Default:** `'process'`.
16361638
* `only` {boolean} If truthy, the test context will only run tests that
16371639
have the `only` option set
16381640
* `setup` {Function} A function that accepts the `TestsStream` instance

lib/internal/test_runner/runner.js

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const {
8080
kSubtestsFailed,
8181
kTestCodeFailure,
8282
kTestTimeoutFailure,
83+
Suite,
8384
Test,
8485
} = require('internal/test_runner/test');
8586
const { FastBuffer } = require('internal/buffer');
@@ -949,7 +950,6 @@ function run(options = kEmptyObject) {
949950
await root.runInAsyncScope(async () => {
950951
const parentURL = pathToFileURL(cwd + sep).href;
951952
const cascadedLoader = esmLoader.getOrInitializeCascadedLoader();
952-
let topLevelTestCount = 0;
953953

954954
root.harness.bootstrapPromise = root.harness.bootstrapPromise ?
955955
SafePromiseAllReturnVoid([root.harness.bootstrapPromise, promise]) :
@@ -974,37 +974,32 @@ function run(options = kEmptyObject) {
974974
const testFile = testFiles[i];
975975
const fileURL = pathToFileURL(resolve(cwd, testFile));
976976
const parent = i === 0 ? undefined : parentURL;
977-
let threw = false;
978-
let importError;
979977

980978
root.entryFile = resolve(testFile);
981979
debug('loading test file:', fileURL.href);
982-
try {
983-
await cascadedLoader.import(fileURL, parent, { __proto__: null });
984-
} catch (err) {
985-
threw = true;
986-
importError = err;
987-
}
980+
981+
// Wrap each file in an implicit suite so that top-level hooks
982+
// (beforeEach, afterEach, before, after) declared in one file
983+
// do not leak into tests from other files.
984+
const fileSuite = root.createSubtest(Suite, testFile, kEmptyObject, async () => {
985+
try {
986+
await cascadedLoader.import(fileURL, parent, { __proto__: null });
987+
} catch (err) {
988+
fileSuite.fail(err);
989+
}
990+
}, {
991+
__proto__: null,
992+
loc: [1, 1, resolve(testFile)],
993+
});
994+
await fileSuite.buildSuite;
988995

989996
debug(
990-
'loaded "%s": top level test count before = %d and after = %d',
997+
'loaded "%s": file suite subtest count = %d',
991998
testFile,
992-
topLevelTestCount,
993-
root.subtests.length,
999+
fileSuite.subtests.length,
9941000
);
995-
if (topLevelTestCount === root.subtests.length) {
996-
// This file had no tests in it. Add the placeholder test.
997-
const subtest = root.createSubtest(Test, testFile, kEmptyObject, undefined, {
998-
__proto__: null,
999-
loc: [1, 1, resolve(testFile)],
1000-
});
1001-
if (threw) {
1002-
subtest.fail(importError);
1003-
}
1004-
startSubtestAfterBootstrap(subtest);
1005-
}
10061001

1007-
topLevelTestCount = root.subtests.length;
1002+
startSubtestAfterBootstrap(fileSuite);
10081003
}
10091004
});
10101005

test/parallel/test-runner-no-isolation-different-cwd.mjs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,24 @@ const stream = run({
99
});
1010

1111

12-
stream.on('test:pass', mustCall(4));
12+
stream.on('test:pass', mustCall(6));
1313
// eslint-disable-next-line no-unused-vars
1414
for await (const _ of stream);
1515
allowGlobals(globalThis.GLOBAL_ORDER);
1616
assert.deepStrictEqual(globalThis.GLOBAL_ORDER, [
17-
'before one: <root>',
1817
'suite one',
19-
'before two: <root>',
2018
'suite two',
19+
20+
'before one: one.test.js',
2121
'beforeEach one: suite one - test',
22-
'beforeEach two: suite one - test',
2322
'suite one - test',
2423
'afterEach one: suite one - test',
25-
'afterEach two: suite one - test',
24+
'after one: one.test.js',
25+
26+
'before two: two.test.js',
2627
'before suite two: suite two',
27-
'beforeEach one: suite two - test',
2828
'beforeEach two: suite two - test',
2929
'suite two - test',
30-
'afterEach one: suite two - test',
3130
'afterEach two: suite two - test',
32-
'after one: <root>',
33-
'after two: <root>',
31+
'after two: two.test.js',
3432
]);

test/parallel/test-runner-no-isolation-filtering.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,9 @@ test('works with --test-only', () => {
2323
assert.strictEqual(child.status, 0);
2424
assert.strictEqual(child.signal, null);
2525
assert.match(stdout, /# tests 2/);
26-
assert.match(stdout, /# suites 2/);
26+
assert.match(stdout, /# suites 4/);
2727
assert.match(stdout, /# pass 2/);
28-
assert.match(stdout, /ok 1 - suite one/);
2928
assert.match(stdout, /ok 1 - suite one - test/);
30-
assert.match(stdout, /ok 2 - suite two/);
3129
assert.match(stdout, /ok 1 - suite two - test/);
3230
});
3331

@@ -45,11 +43,9 @@ test('works without --test-only', () => {
4543
assert.strictEqual(child.status, 0);
4644
assert.strictEqual(child.signal, null);
4745
assert.match(stdout, /# tests 2/);
48-
assert.match(stdout, /# suites 2/);
46+
assert.match(stdout, /# suites 4/);
4947
assert.match(stdout, /# pass 2/);
50-
assert.match(stdout, /ok 1 - suite one/);
5148
assert.match(stdout, /ok 1 - suite one - test/);
52-
assert.match(stdout, /ok 2 - suite two/);
5349
assert.match(stdout, /ok 1 - suite two - test/);
5450
});
5551

@@ -68,7 +64,7 @@ test('works with --test-name-pattern', () => {
6864
assert.strictEqual(child.status, 0);
6965
assert.strictEqual(child.signal, null);
7066
assert.match(stdout, /# tests 0/);
71-
assert.match(stdout, /# suites 0/);
67+
assert.match(stdout, /# suites 1/);
7268
});
7369

7470
test('works with --test-skip-pattern', () => {
@@ -86,7 +82,7 @@ test('works with --test-skip-pattern', () => {
8682
assert.strictEqual(child.status, 0);
8783
assert.strictEqual(child.signal, null);
8884
assert.match(stdout, /# tests 1/);
89-
assert.match(stdout, /# suites 1/);
85+
assert.match(stdout, /# suites 2/);
9086
assert.match(stdout, /# pass 1/);
9187
assert.match(stdout, /ok 1 - suite two - test/);
9288
});

test/parallel/test-runner-no-isolation-hooks.mjs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,30 @@ const testFiles = [
1515
const order = [
1616
'before(): global',
1717

18-
'before one: <root>',
1918
'suite one',
2019

21-
'before two: <root>',
2220
'suite two',
2321

22+
`before one: ${testFiles[0]}`,
2423
'beforeEach(): global',
2524
'beforeEach one: suite one - test',
26-
'beforeEach two: suite one - test',
2725

2826
'suite one - test',
29-
'afterEach(): global',
3027
'afterEach one: suite one - test',
31-
'afterEach two: suite one - test',
28+
'afterEach(): global',
29+
`after one: ${testFiles[0]}`,
3230

31+
`before two: ${testFiles[1]}`,
3332
'before suite two: suite two',
3433
'beforeEach(): global',
35-
'beforeEach one: suite two - test',
3634
'beforeEach two: suite two - test',
3735

3836
'suite two - test',
39-
'afterEach(): global',
40-
'afterEach one: suite two - test',
4137
'afterEach two: suite two - test',
38+
'afterEach(): global',
39+
`after two: ${testFiles[1]}`,
4240

4341
'after(): global',
44-
'after one: <root>',
45-
'after two: <root>',
4642
].join('\n');
4743

4844
test('use --import (CJS) to define global hooks', async (t) => {
@@ -52,7 +48,7 @@ test('use --import (CJS) to define global hooks', async (t) => {
5248
...testFiles,
5349
]);
5450

55-
const testHookOutput = stdout.split('\n▶')[0];
51+
const testHookOutput = stdout.split('\nafter(): global')[0] + '\nafter(): global';
5652

5753
t.assert.equal(testHookOutput, order);
5854
});
@@ -64,7 +60,7 @@ test('use --import (ESM) to define global hooks', async (t) => {
6460
...testFiles,
6561
]);
6662

67-
const testHookOutput = stdout.split('\n▶')[0];
63+
const testHookOutput = stdout.split('\nafter(): global')[0] + '\nafter(): global';
6864

6965
t.assert.equal(testHookOutput, order);
7066
});
@@ -76,7 +72,7 @@ test('use --require to define global hooks', async (t) => {
7672
...testFiles,
7773
]);
7874

79-
const testHookOutput = stdout.split('\n▶')[0];
75+
const testHookOutput = stdout.split('\nafter(): global')[0] + '\nafter(): global';
8076

8177
t.assert.equal(testHookOutput, order);
8278
});

test/parallel/test-runner-no-isolation.mjs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,28 @@ const stream = run({
1111
isolation: 'none',
1212
});
1313

14+
const file1 = fixtures.path('test-runner', 'no-isolation', 'one.test.js');
15+
const file2 = fixtures.path('test-runner', 'no-isolation', 'two.test.js');
16+
1417
stream.on('test:fail', mustNotCall());
15-
stream.on('test:pass', mustCall(4));
18+
stream.on('test:pass', mustCall(6));
1619
// eslint-disable-next-line no-unused-vars
1720
for await (const _ of stream);
1821
allowGlobals(globalThis.GLOBAL_ORDER);
1922
assert.deepStrictEqual(globalThis.GLOBAL_ORDER, [
20-
'before one: <root>',
2123
'suite one',
22-
'before two: <root>',
2324
'suite two',
2425

26+
`before one: ${file1}`,
2527
'beforeEach one: suite one - test',
26-
'beforeEach two: suite one - test',
2728
'suite one - test',
2829
'afterEach one: suite one - test',
29-
'afterEach two: suite one - test',
30+
`after one: ${file1}`,
3031

32+
`before two: ${file2}`,
3133
'before suite two: suite two',
32-
33-
'beforeEach one: suite two - test',
3434
'beforeEach two: suite two - test',
3535
'suite two - test',
36-
'afterEach one: suite two - test',
3736
'afterEach two: suite two - test',
38-
39-
'after one: <root>',
40-
'after two: <root>',
37+
`after two: ${file2}`,
4138
]);

0 commit comments

Comments
 (0)