Skip to content

Commit 17b7ce1

Browse files
committed
test_runner: add cwd option and update documentation
1 parent 6e05b5e commit 17b7ce1

File tree

5 files changed

+72
-14
lines changed

5 files changed

+72
-14
lines changed

doc/api/test.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,9 @@ added:
12391239
- v18.9.0
12401240
- v16.19.0
12411241
changes:
1242+
- version: REPLACEME
1243+
pr-url: https://github.com/nodejs/node/pull/54225
1244+
description: Added the `cwd` option.
12421245
- version: v22.6.0
12431246
pr-url: https://github.com/nodejs/node/pull/53866
12441247
description: Added the `globPatterns` option.
@@ -1263,6 +1266,9 @@ changes:
12631266
parallel.
12641267
If `false`, it would only run one test file at a time.
12651268
**Default:** `false`.
1269+
* `cwd`: {string} Specifies the current working directory (cwd) to be used by the test runner.
1270+
The cwd serves as the base path for resolving files according to the [test runner execution model][].
1271+
**Default:** `process.cwd()`.
12661272
* `files`: {Array} An array containing the list of files to run.
12671273
**Default** matching files from [test runner execution model][].
12681274
* `forceExit`: {boolean} Configures the test runner to exit the process once

lib/internal/test_runner/runner.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const {
5050
validateFunction,
5151
validateObject,
5252
validateInteger,
53+
validateString,
5354
} = require('internal/validators');
5455
const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector');
5556
const { isRegExp } = require('internal/util/types');
@@ -87,8 +88,7 @@ const kCanceledTests = new SafeSet()
8788

8889
let kResistStopPropagation;
8990

90-
function createTestFileList(patterns) {
91-
const cwd = process.cwd();
91+
function createTestFileList(patterns, cwd) {
9292
const hasUserSuppliedPattern = patterns != null;
9393
if (!patterns || patterns.length === 0) {
9494
patterns = [kDefaultPattern];
@@ -110,7 +110,7 @@ function createTestFileList(patterns) {
110110

111111
function filterExecArgv(arg, i, arr) {
112112
return !ArrayPrototypeIncludes(kFilterArgs, arg) &&
113-
!ArrayPrototypeSome(kFilterArgValues, (p) => arg === p || (i > 0 && arr[i - 1] === p) || StringPrototypeStartsWith(arg, `${p}=`));
113+
!ArrayPrototypeSome(kFilterArgValues, (p) => arg === p || (i > 0 && arr[i - 1] === p) || StringPrototypeStartsWith(arg, `${p}=`));
114114
}
115115

116116
function getRunArgs(path, { forceExit, inspectPort, testNamePatterns, testSkipPatterns, only }) {
@@ -167,7 +167,7 @@ class FileTest extends Test {
167167
if (firstSpaceIndex === -1) return false;
168168
const secondSpaceIndex = StringPrototypeIndexOf(comment, ' ', firstSpaceIndex + 1);
169169
return secondSpaceIndex === -1 &&
170-
ArrayPrototypeIncludes(kDiagnosticsFilterArgs, StringPrototypeSlice(comment, 0, firstSpaceIndex));
170+
ArrayPrototypeIncludes(kDiagnosticsFilterArgs, StringPrototypeSlice(comment, 0, firstSpaceIndex));
171171
}
172172
#handleReportItem(item) {
173173
const isTopLevel = item.data.nesting === 0;
@@ -417,16 +417,19 @@ function watchFiles(testFiles, opts) {
417417
const watcherMode = opts.hasFiles ? 'filter' : 'all';
418418
const watcher = new FilesWatcher({ __proto__: null, debounce: 200, mode: watcherMode, signal: opts.signal });
419419
if (!opts.hasFiles) {
420-
watcher.watchPath(process.cwd()); // TODO: https://github.com/nodejs/node/issues/53867 before closing this MR
420+
watcher.watchPath(opts.watchedDir);
421421
}
422422
const filesWatcher = { __proto__: null, watcher, runningProcesses, runningSubtests };
423423
opts.root.harness.watching = true;
424424
// Watch for changes in current filtered files
425425
watcher.on('changed', ({ owners, eventType }) => {
426426
if (!opts.hasFiles && (eventType === 'rename' || eventType === 'change')) {
427-
const updatedTestFiles = createTestFileList(opts.globPatterns);
427+
const updatedTestFiles = createTestFileList(opts.globPatterns, opts.watchedDir);
428428

429429
const newFileName = ArrayPrototypeFind(updatedTestFiles, (x) => !ArrayPrototypeIncludes(testFiles, x));
430+
const previousFileName = ArrayPrototypeFind(testFiles, (x) => !ArrayPrototypeIncludes(updatedTestFiles, x));
431+
432+
testFiles = updatedTestFiles;
430433

431434
// When file renamed (created / deleted) we need to update the watcher
432435
if (newFileName) {
@@ -437,8 +440,6 @@ function watchFiles(testFiles, opts) {
437440
if (!newFileName && previousFileName) {
438441
return; // Avoid rerunning files when file deleted
439442
}
440-
441-
testFiles = updatedTestFiles;
442443
}
443444

444445
watcher.unfilterFilesOwnedBy(owners);
@@ -491,6 +492,7 @@ function run(options = kEmptyObject) {
491492
setup,
492493
only,
493494
globPatterns,
495+
cwd = process.cwd(),
494496
} = options;
495497

496498
if (files != null) {
@@ -515,6 +517,10 @@ function run(options = kEmptyObject) {
515517
validateArray(globPatterns, 'options.globPatterns');
516518
}
517519

520+
if (cwd != null) {
521+
validateString(cwd, 'options.cwd');
522+
}
523+
518524
if (globPatterns?.length > 0 && files?.length > 0) {
519525
throw new ERR_INVALID_ARG_VALUE(
520526
'options.globPatterns', globPatterns, 'is not supported when specifying \'options.files\'',
@@ -584,7 +590,7 @@ function run(options = kEmptyObject) {
584590
root.postRun();
585591
return root.reporter;
586592
}
587-
let testFiles = files ?? createTestFileList(globPatterns);
593+
let testFiles = files ?? createTestFileList(globPatterns, cwd);
588594

589595
if (shard) {
590596
testFiles = ArrayPrototypeFilter(testFiles, (_, index) => index % shard.total === shard.index - 1);
@@ -604,6 +610,7 @@ function run(options = kEmptyObject) {
604610
globPatterns,
605611
only,
606612
forceExit,
613+
watchedDir: cwd,
607614
};
608615
if (watch) {
609616
filesWatcher = watchFiles(testFiles, opts);

test/parallel/test-runner-run-watch.mjs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { describe, it, beforeEach } from 'node:test';
44
import assert from 'node:assert';
55
import { spawn } from 'node:child_process';
66
import { once } from 'node:events';
7-
import { writeFileSync, renameSync, unlinkSync, existsSync } from 'node:fs';
7+
import { writeFileSync, renameSync, unlinkSync, existsSync, mkdtempSync } from 'node:fs';
88
import util from 'internal/util';
99
import tmpdir from '../common/tmpdir.js';
10-
import { join } from 'node:path';
10+
import { join, basename } from 'node:path';
1111

1212
if (common.isIBMi)
1313
common.skip('IBMi does not support `fs.watch()`');
@@ -222,4 +222,22 @@ describe('test runner watch mode', () => {
222222
it('should run new tests when a new file is created in the watched directory', async () => {
223223
await testWatch({ action: 'create', fileToCreate: 'new-test-file.test.js' });
224224
});
225+
226+
it('should run new tests when a new file is created in a different cwd', async () => {
227+
const newTestWithoutDep = `
228+
const test = require('node:test');
229+
test('test without dep has ran');
230+
`;
231+
const differentCwd = mkdtempSync(`${tmpdir.path}/different-cwd`);
232+
const testFileName = 'test-without-dep.js';
233+
const newTestFilePath = join(differentCwd, testFileName);
234+
writeFileSync(newTestFilePath, newTestWithoutDep);
235+
const differentCwdTmpPath = basename(differentCwd);
236+
237+
await testWatch({
238+
action: 'create',
239+
fileToCreate: `${differentCwdTmpPath}/new-test-file.test.js`,
240+
cwd: differentCwd
241+
});
242+
});
225243
});

test/parallel/test-runner-run.mjs

Lines changed: 7 additions & 0 deletions
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), {

test/parallel/test-runner-watch-mode.mjs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { describe, it, beforeEach } from 'node:test';
44
import { once } from 'node:events';
55
import assert from 'node:assert';
66
import { spawn } from 'node:child_process';
7-
import { writeFileSync, renameSync, unlinkSync, existsSync } from 'node:fs';
7+
import { writeFileSync, renameSync, unlinkSync, existsSync, mkdtempSync } from 'node:fs';
88
import util from 'internal/util';
99
import tmpdir from '../common/tmpdir.js';
10+
import { join, basename } from 'node:path';
1011

1112
if (common.isIBMi)
1213
common.skip('IBMi does not support `fs.watch()`');
@@ -41,13 +42,14 @@ async function testWatch({
4142
fileToUpdate,
4243
file,
4344
action = 'update',
44-
fileToCreate
45+
fileToCreate,
46+
cwd = tmpdir.path
4547
}) {
4648
const ran1 = util.createDeferredPromise();
4749
const ran2 = util.createDeferredPromise();
4850
const child = spawn(process.execPath,
4951
['--watch', '--test', file ? fixturePaths[file] : undefined].filter(Boolean),
50-
{ encoding: 'utf8', stdio: 'pipe', cwd: tmpdir.path }
52+
{ encoding: 'utf8', stdio: 'pipe', cwd }
5153
);
5254
let stdout = '';
5355
let currentRun = '';
@@ -191,4 +193,22 @@ describe('test runner watch mode', () => {
191193
it('should run new tests when a new file is created in the watched directory', async () => {
192194
await testWatch({ action: 'create', fileToCreate: 'new-test-file.test.js' });
193195
});
196+
197+
it('should run new tests when a new file is created in a different cwd', async () => {
198+
const newTestWithoutDep = `
199+
const test = require('node:test');
200+
test('test without dep has ran');
201+
`;
202+
const differentCwd = mkdtempSync(`${tmpdir.path}/different-cwd`);
203+
const testFileName = 'test-without-dep.js';
204+
const newTestFilePath = join(differentCwd, testFileName);
205+
writeFileSync(newTestFilePath, newTestWithoutDep);
206+
const differentCwdTmpPath = basename(differentCwd);
207+
208+
await testWatch({
209+
action: 'create',
210+
fileToCreate: `${differentCwdTmpPath}/new-test-file.test.js`,
211+
cwd: differentCwd
212+
});
213+
});
194214
});

0 commit comments

Comments
 (0)