Skip to content

Commit 1c7795e

Browse files
authoredOct 3, 2024··
test_runner: add cwd option to run
PR-URL: #54705 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent fe45be2 commit 1c7795e

9 files changed

+283
-50
lines changed
 

‎doc/api/test.md

+6
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,9 @@ added:
12561256
- v18.9.0
12571257
- v16.19.0
12581258
changes:
1259+
- version: REPLACEME
1260+
pr-url: https://github.com/nodejs/node/pull/54705
1261+
description: Added the `cwd` option.
12591262
- version: REPLACEME
12601263
pr-url: https://github.com/nodejs/node/pull/53937
12611264
description: Added coverage options.
@@ -1286,6 +1289,9 @@ changes:
12861289
parallel.
12871290
If `false`, it would only run one test file at a time.
12881291
**Default:** `false`.
1292+
* `cwd`: {string} Specifies the current working directory to be used by the test runner.
1293+
Serves as the base path for resolving files according to the [test runner execution model][].
1294+
**Default:** `process.cwd()`.
12891295
* `files`: {Array} An array containing the list of files to run.
12901296
**Default:** matching files from [test runner execution model][].
12911297
* `forceExit`: {boolean} Configures the test runner to exit the process once

‎lib/internal/test_runner/coverage.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -487,15 +487,14 @@ function sortCoverageFiles(a, b) {
487487

488488
function setupCoverage(options) {
489489
let originalCoverageDirectory = process.env.NODE_V8_COVERAGE;
490-
const cwd = process.cwd();
491490

492491
if (originalCoverageDirectory) {
493492
// NODE_V8_COVERAGE was already specified. Convert it to an absolute path
494493
// and store it for later. The test runner will use a temporary directory
495494
// so that no preexisting coverage files interfere with the results of the
496495
// coverage report. Then, once the coverage is computed, move the coverage
497496
// files back to the original NODE_V8_COVERAGE directory.
498-
originalCoverageDirectory = resolve(cwd, originalCoverageDirectory);
497+
originalCoverageDirectory = resolve(options.cwd, originalCoverageDirectory);
499498
}
500499

501500
const coverageDirectory = mkdtempSync(join(tmpdir(), 'node-coverage-'));
@@ -512,7 +511,7 @@ function setupCoverage(options) {
512511
return new TestCoverage(
513512
coverageDirectory,
514513
originalCoverageDirectory,
515-
cwd,
514+
options.cwd,
516515
options.coverageExcludeGlobs,
517516
options.coverageIncludeGlobs,
518517
{

‎lib/internal/test_runner/harness.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ function createProcessEventHandler(eventName, rootTest) {
116116
const name = test.hookType ? `Test hook "${test.hookType}"` : `Test "${test.name}"`;
117117
let locInfo = '';
118118
if (test.loc) {
119-
const relPath = relative(process.cwd(), test.loc.file);
119+
const relPath = relative(rootTest.config.cwd, test.loc.file);
120120
locInfo = ` at ${relPath}:${test.loc.line}:${test.loc.column}`;
121121
}
122122

@@ -260,6 +260,7 @@ function lazyBootstrapRoot() {
260260
loc: entryFile ? [1, 1, entryFile] : undefined,
261261
};
262262
const globalOptions = parseCommandLine();
263+
globalOptions.cwd = process.cwd();
263264
createTestTree(rootTestOptions, globalOptions);
264265
globalRoot.reporter.on('test:summary', (data) => {
265266
if (!data.success) {

‎lib/internal/test_runner/runner.js

+14-11
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const {
3333

3434
const { spawn } = require('child_process');
3535
const { finished } = require('internal/streams/end-of-stream');
36-
const { resolve } = require('path');
36+
const { resolve, sep, isAbsolute } = require('path');
3737
const { DefaultDeserializer, DefaultSerializer } = require('v8');
3838
const { getOptionValue } = require('internal/options');
3939
const { Interface } = require('internal/readline/interface');
@@ -56,14 +56,14 @@ const {
5656
validateObject,
5757
validateOneOf,
5858
validateInteger,
59+
validateString,
5960
validateStringArray,
6061
} = require('internal/validators');
6162
const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector');
6263
const { isRegExp } = require('internal/util/types');
6364
const { pathToFileURL } = require('internal/url');
6465
const {
6566
createDeferredPromise,
66-
getCWDURL,
6767
kEmptyObject,
6868
} = require('internal/util');
6969
const { kEmitMessage } = require('internal/test_runner/tests_stream');
@@ -137,7 +137,8 @@ function getRunArgs(path, { forceExit,
137137
testSkipPatterns,
138138
only,
139139
argv: suppliedArgs,
140-
execArgv }) {
140+
execArgv,
141+
cwd }) {
141142
const argv = ArrayPrototypeFilter(process.execArgv, filterExecArgv);
142143
if (forceExit === true) {
143144
ArrayPrototypePush(argv, '--test-force-exit');
@@ -494,7 +495,8 @@ function watchFiles(testFiles, opts) {
494495
// When file renamed (created / deleted) we need to update the watcher
495496
if (newFileName) {
496497
owners = new SafeSet().add(newFileName);
497-
watcher.filterFile(resolve(newFileName), owners);
498+
const resolveFileName = isAbsolute(newFileName) ? newFileName : resolve(opts.cwd, newFileName);
499+
watcher.filterFile(resolveFileName, owners);
498500
}
499501

500502
if (!newFileName && previousFileName) {
@@ -562,6 +564,7 @@ function run(options = kEmptyObject) {
562564
functionCoverage = 0,
563565
execArgv = [],
564566
argv = [],
567+
cwd = process.cwd(),
565568
} = options;
566569

567570
if (files != null) {
@@ -586,6 +589,8 @@ function run(options = kEmptyObject) {
586589
validateArray(globPatterns, 'options.globPatterns');
587590
}
588591

592+
validateString(cwd, 'options.cwd');
593+
589594
if (globPatterns?.length > 0 && files?.length > 0) {
590595
throw new ERR_INVALID_ARG_VALUE(
591596
'options.globPatterns', globPatterns, 'is not supported when specifying \'options.files\'',
@@ -673,12 +678,9 @@ function run(options = kEmptyObject) {
673678
lineCoverage: lineCoverage,
674679
branchCoverage: branchCoverage,
675680
functionCoverage: functionCoverage,
681+
cwd,
676682
};
677683
const root = createTestTree(rootTestOptions, globalOptions);
678-
679-
// This const should be replaced by a run option in the future.
680-
const cwd = process.cwd();
681-
682684
let testFiles = files ?? createTestFileList(globPatterns, cwd);
683685

684686
if (shard) {
@@ -731,7 +733,8 @@ function run(options = kEmptyObject) {
731733
};
732734
} else if (isolation === 'none') {
733735
if (watch) {
734-
filesWatcher = watchFiles(testFiles, opts);
736+
const absoluteTestFiles = ArrayPrototypeMap(testFiles, (file) => (isAbsolute(file) ? file : resolve(cwd, file)));
737+
filesWatcher = watchFiles(absoluteTestFiles, opts);
735738
runFiles = async () => {
736739
root.harness.bootstrapPromise = null;
737740
root.harness.buildPromise = null;
@@ -744,7 +747,7 @@ function run(options = kEmptyObject) {
744747
const { promise, resolve: finishBootstrap } = createDeferredPromise();
745748

746749
await root.runInAsyncScope(async () => {
747-
const parentURL = getCWDURL().href;
750+
const parentURL = pathToFileURL(cwd + sep).href;
748751
const cascadedLoader = esmLoader.getOrInitializeCascadedLoader();
749752
let topLevelTestCount = 0;
750753

@@ -757,7 +760,7 @@ function run(options = kEmptyObject) {
757760

758761
for (let i = 0; i < testFiles.length; ++i) {
759762
const testFile = testFiles[i];
760-
const fileURL = pathToFileURL(testFile);
763+
const fileURL = pathToFileURL(resolve(cwd, testFile));
761764
const parent = i === 0 ? undefined : parentURL;
762765
let threw = false;
763766
let importError;

‎test/fixtures/test-runner-watch.mjs

+19-1
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,37 @@ const options = {
66
file: {
77
type: 'string',
88
},
9+
cwd: {
10+
type: 'string',
11+
},
12+
isolation: {
13+
type: 'string',
14+
},
915
};
1016
const {
1117
values,
1218
positionals,
1319
} = parseArgs({ args: process.argv.slice(2), options });
1420

1521
let files;
22+
let cwd;
23+
let isolation;
1624

1725
if (values.file) {
1826
files = [values.file];
1927
}
2028

29+
if (values.cwd) {
30+
cwd = values.cwd;
31+
}
32+
33+
if (values.isolation) {
34+
isolation = values.isolation;
35+
}
36+
2137
run({
2238
files,
23-
watch: true
39+
watch: true,
40+
cwd,
41+
isolation,
2442
}).compose(tap).pipe(process.stdout);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { allowGlobals, mustCall } 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+
const stream = run({
7+
cwd: fixtures.path('test-runner', 'no-isolation'),
8+
isolation: 'none',
9+
});
10+
11+
12+
stream.on('test:pass', mustCall(4));
13+
// eslint-disable-next-line no-unused-vars
14+
for await (const _ of stream);
15+
allowGlobals(globalThis.GLOBAL_ORDER);
16+
deepStrictEqual(globalThis.GLOBAL_ORDER, [
17+
'before one: <root>',
18+
'suite one',
19+
'before two: <root>',
20+
'suite two',
21+
'beforeEach one: suite one - test',
22+
'beforeEach two: suite one - test',
23+
'suite one - test',
24+
'afterEach one: suite one - test',
25+
'afterEach two: suite one - test',
26+
'before suite two: suite two',
27+
'beforeEach one: suite two - test',
28+
'beforeEach two: suite two - test',
29+
'suite two - test',
30+
'afterEach one: suite two - test',
31+
'afterEach two: suite two - test',
32+
'after one: <root>',
33+
'after two: <root>',
34+
]);

0 commit comments

Comments
 (0)