Skip to content

Commit 4fa3784

Browse files
committed
test_runner: add cwd option to run
1 parent 3d954dc commit 4fa3784

File tree

5 files changed

+150
-4
lines changed

5 files changed

+150
-4
lines changed

doc/api/test.md

+6
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,9 @@ added:
12461246
- v18.9.0
12471247
- v16.19.0
12481248
changes:
1249+
- version: REPLACEME
1250+
pr-url: https://github.com/nodejs/node/pull/54225
1251+
description: Added the `cwd` option.
12491252
- version: REPLACEME
12501253
pr-url: https://github.com/nodejs/node/pull/53927
12511254
description: Added the `isolation` option.
@@ -1273,6 +1276,9 @@ changes:
12731276
parallel.
12741277
If `false`, it would only run one test file at a time.
12751278
**Default:** `false`.
1279+
* `cwd`: {string} Specifies the current working directory to be used by the test runner.
1280+
The cwd serves as the base path for resolving files according to the [test runner execution model][].
1281+
**Default:** `process.cwd()`.
12761282
* `files`: {Array} An array containing the list of files to run.
12771283
**Default** matching files from [test runner execution model][].
12781284
* `forceExit`: {boolean} Configures the test runner to exit the process once

lib/internal/test_runner/runner.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const {
5454
validateObject,
5555
validateOneOf,
5656
validateInteger,
57+
validateString,
5758
} = require('internal/validators');
5859
const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector');
5960
const { isRegExp } = require('internal/util/types');
@@ -536,6 +537,7 @@ function run(options = kEmptyObject) {
536537
setup,
537538
only,
538539
globPatterns,
540+
cwd = process.cwd(),
539541
} = options;
540542

541543
if (files != null) {
@@ -560,6 +562,8 @@ function run(options = kEmptyObject) {
560562
validateArray(globPatterns, 'options.globPatterns');
561563
}
562564

565+
validateString(cwd, 'options.cwd');
566+
563567
if (globPatterns?.length > 0 && files?.length > 0) {
564568
throw new ERR_INVALID_ARG_VALUE(
565569
'options.globPatterns', globPatterns, 'is not supported when specifying \'options.files\'',
@@ -625,9 +629,6 @@ function run(options = kEmptyObject) {
625629
};
626630
const root = createTestTree(rootTestOptions, globalOptions);
627631

628-
// This const should be replaced by a run option in the future.
629-
const cwd = process.cwd();
630-
631632
let testFiles = files ?? createTestFileList(globPatterns, cwd);
632633

633634
if (shard) {
@@ -698,7 +699,7 @@ function run(options = kEmptyObject) {
698699
root.harness.bootstrapPromise = promise;
699700

700701
for (let i = 0; i < testFiles.length; ++i) {
701-
const testFile = testFiles[i];
702+
const testFile = resolve(cwd, testFiles[i]);
702703
const fileURL = pathToFileURL(testFile);
703704
const parent = i === 0 ? undefined : parentURL;
704705
let threw = false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { allowGlobals, mustCall, mustNotCall } from '../common/index.mjs';
2+
import * as fixtures from '../common/fixtures.mjs';
3+
import { deepStrictEqual } from 'node:assert';
4+
import { run } from 'node:test';
5+
6+
try {
7+
const controller = new AbortController();
8+
const stream = run({
9+
cwd: fixtures.path('test-runner', 'no-isolation'),
10+
isolation: 'none',
11+
watch: true,
12+
signal: controller.signal,
13+
});
14+
15+
stream.on('test:fail', () => mustNotCall());
16+
stream.on('test:pass', mustCall(5));
17+
stream.on('data', function({ type }) {
18+
if (type === 'test:watch:drained') {
19+
controller.abort();
20+
}
21+
});
22+
// eslint-disable-next-line no-unused-vars
23+
for await (const _ of stream);
24+
allowGlobals(globalThis.GLOBAL_ORDER);
25+
deepStrictEqual(globalThis.GLOBAL_ORDER, [
26+
'before one: <root>',
27+
'suite one',
28+
'before two: <root>',
29+
'suite two',
30+
31+
'beforeEach one: suite one - test',
32+
'beforeEach two: suite one - test',
33+
'suite one - test',
34+
'afterEach one: suite one - test',
35+
'afterEach two: suite one - test',
36+
37+
'beforeEach one: test one',
38+
'beforeEach two: test one',
39+
'test one',
40+
'afterEach one: test one',
41+
'afterEach two: test one',
42+
43+
'before suite two: suite two',
44+
45+
'beforeEach one: suite two - test',
46+
'beforeEach two: suite two - test',
47+
'suite two - test',
48+
'afterEach one: suite two - test',
49+
'afterEach two: suite two - test',
50+
51+
'after one: <root>',
52+
'after two: <root>',
53+
]);
54+
} catch (err) {
55+
console.error(err);
56+
process.exit(1);
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { allowGlobals, mustCall } from '../common/index.mjs';
2+
import * as fixtures from '../common/fixtures.mjs';
3+
import { strictEqual } from 'node:assert';
4+
import { run } from 'node:test';
5+
6+
const stream = run({
7+
cwd: fixtures.path('test-runner', 'no-isolation'),
8+
isolation: 'none',
9+
});
10+
11+
let errors = 0;
12+
stream.on('test:fail', () => {
13+
errors++;
14+
});
15+
stream.on('test:pass', mustCall(5));
16+
// eslint-disable-next-line no-unused-vars
17+
for await (const _ of stream);
18+
strictEqual(errors, 0);
19+
allowGlobals(globalThis.GLOBAL_ORDER);
20+
strictEqual(globalThis.GLOBAL_ORDER, [
21+
'before one: <root>',
22+
'suite one',
23+
'before two: <root>',
24+
'suite two',
25+
26+
'beforeEach one: suite one - test',
27+
'beforeEach two: suite one - test',
28+
'suite one - test',
29+
'afterEach one: suite one - test',
30+
'afterEach two: suite one - test',
31+
32+
'beforeEach one: test one',
33+
'beforeEach two: test one',
34+
'test one',
35+
'afterEach one: test one',
36+
'afterEach two: test one',
37+
38+
'before suite two: suite two',
39+
40+
'beforeEach one: suite two - test',
41+
'beforeEach two: suite two - test',
42+
'suite two - test',
43+
'afterEach one: suite two - test',
44+
'afterEach two: suite two - test',
45+
46+
'after one: <root>',
47+
'after two: <root>',
48+
]);

test/parallel/test-runner-run.mjs

+34
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,13 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
481481
});
482482
});
483483

484+
it('should only allow a string in options.cwd', async () => {
485+
[Symbol(), {}, [], () => {}, 0, 1, 0n, 1n, true, false]
486+
.forEach((cwd) => assert.throws(() => run({ cwd }), {
487+
code: 'ERR_INVALID_ARG_TYPE'
488+
}));
489+
});
490+
484491
it('should only allow object as options', () => {
485492
[Symbol(), [], () => {}, 0, 1, 0n, 1n, '', '1', true, false]
486493
.forEach((options) => assert.throws(() => run(options), {
@@ -513,6 +520,33 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
513520
for await (const _ of stream);
514521
assert.match(stderr, /Warning: node:test run\(\) is being called recursively/);
515522
});
523+
524+
it('should run with different cwd', async () => {
525+
const stream = run({
526+
cwd: fixtures.path('test-runner', 'cwd')
527+
});
528+
stream.on('test:fail', common.mustNotCall());
529+
stream.on('test:pass', common.mustCall(1));
530+
531+
// eslint-disable-next-line no-unused-vars
532+
for await (const _ of stream);
533+
});
534+
535+
it('should run with different cwd while in watch mode', async () => {
536+
const controller = new AbortController();
537+
const stream = run({
538+
cwd: fixtures.path('test-runner', 'cwd'),
539+
watch: true,
540+
signal: controller.signal,
541+
}).on('data', function({ type }) {
542+
if (type === 'test:watch:drained') {
543+
controller.abort();
544+
}
545+
});
546+
547+
stream.on('test:fail', common.mustNotCall());
548+
stream.on('test:pass', common.mustCall(1));
549+
});
516550
});
517551

518552
describe('forceExit', () => {

0 commit comments

Comments
 (0)