Skip to content

Commit

Permalink
test_runner: add cwd option and update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
pmarchini committed Aug 16, 2024
1 parent 6e05b5e commit 17b7ce1
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 14 deletions.
6 changes: 6 additions & 0 deletions doc/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,9 @@ added:
- v18.9.0
- v16.19.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/54225
description: Added the `cwd` option.
- version: v22.6.0
pr-url: https://github.com/nodejs/node/pull/53866
description: Added the `globPatterns` option.
Expand All @@ -1263,6 +1266,9 @@ changes:
parallel.
If `false`, it would only run one test file at a time.
**Default:** `false`.
* `cwd`: {string} Specifies the current working directory (cwd) to be used by the test runner.
The cwd serves as the base path for resolving files according to the [test runner execution model][].
**Default:** `process.cwd()`.
* `files`: {Array} An array containing the list of files to run.
**Default** matching files from [test runner execution model][].
* `forceExit`: {boolean} Configures the test runner to exit the process once
Expand Down
25 changes: 16 additions & 9 deletions lib/internal/test_runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const {
validateFunction,
validateObject,
validateInteger,
validateString,
} = require('internal/validators');
const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector');
const { isRegExp } = require('internal/util/types');
Expand Down Expand Up @@ -87,8 +88,7 @@ const kCanceledTests = new SafeSet()

let kResistStopPropagation;

function createTestFileList(patterns) {
const cwd = process.cwd();
function createTestFileList(patterns, cwd) {
const hasUserSuppliedPattern = patterns != null;
if (!patterns || patterns.length === 0) {
patterns = [kDefaultPattern];
Expand All @@ -110,7 +110,7 @@ function createTestFileList(patterns) {

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

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

const newFileName = ArrayPrototypeFind(updatedTestFiles, (x) => !ArrayPrototypeIncludes(testFiles, x));
const previousFileName = ArrayPrototypeFind(testFiles, (x) => !ArrayPrototypeIncludes(updatedTestFiles, x));

testFiles = updatedTestFiles;

// When file renamed (created / deleted) we need to update the watcher
if (newFileName) {
Expand All @@ -437,8 +440,6 @@ function watchFiles(testFiles, opts) {
if (!newFileName && previousFileName) {
return; // Avoid rerunning files when file deleted
}

testFiles = updatedTestFiles;
}

watcher.unfilterFilesOwnedBy(owners);
Expand Down Expand Up @@ -491,6 +492,7 @@ function run(options = kEmptyObject) {
setup,
only,
globPatterns,
cwd = process.cwd(),
} = options;

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

if (cwd != null) {
validateString(cwd, 'options.cwd');
}

if (globPatterns?.length > 0 && files?.length > 0) {
throw new ERR_INVALID_ARG_VALUE(
'options.globPatterns', globPatterns, 'is not supported when specifying \'options.files\'',
Expand Down Expand Up @@ -584,7 +590,7 @@ function run(options = kEmptyObject) {
root.postRun();
return root.reporter;
}
let testFiles = files ?? createTestFileList(globPatterns);
let testFiles = files ?? createTestFileList(globPatterns, cwd);

if (shard) {
testFiles = ArrayPrototypeFilter(testFiles, (_, index) => index % shard.total === shard.index - 1);
Expand All @@ -604,6 +610,7 @@ function run(options = kEmptyObject) {
globPatterns,
only,
forceExit,
watchedDir: cwd,
};
if (watch) {
filesWatcher = watchFiles(testFiles, opts);
Expand Down
22 changes: 20 additions & 2 deletions test/parallel/test-runner-run-watch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert';
import { spawn } from 'node:child_process';
import { once } from 'node:events';
import { writeFileSync, renameSync, unlinkSync, existsSync } from 'node:fs';
import { writeFileSync, renameSync, unlinkSync, existsSync, mkdtempSync } from 'node:fs';
import util from 'internal/util';
import tmpdir from '../common/tmpdir.js';
import { join } from 'node:path';
import { join, basename } from 'node:path';

if (common.isIBMi)
common.skip('IBMi does not support `fs.watch()`');
Expand Down Expand Up @@ -222,4 +222,22 @@ describe('test runner watch mode', () => {
it('should run new tests when a new file is created in the watched directory', async () => {
await testWatch({ action: 'create', fileToCreate: 'new-test-file.test.js' });
});

it('should run new tests when a new file is created in a different cwd', async () => {
const newTestWithoutDep = `
const test = require('node:test');
test('test without dep has ran');
`;
const differentCwd = mkdtempSync(`${tmpdir.path}/different-cwd`);
const testFileName = 'test-without-dep.js';
const newTestFilePath = join(differentCwd, testFileName);
writeFileSync(newTestFilePath, newTestWithoutDep);
const differentCwdTmpPath = basename(differentCwd);

await testWatch({
action: 'create',
fileToCreate: `${differentCwdTmpPath}/new-test-file.test.js`,
cwd: differentCwd
});
});
});
7 changes: 7 additions & 0 deletions test/parallel/test-runner-run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,13 @@ describe('require(\'node:test\').run', { concurrency: true }, () => {
});
});

it('should only allow a string in options.cwd', async () => {
[Symbol(), {}, [], () => {}, 0, 1, 0n, 1n, true, false]
.forEach((cwd) => assert.throws(() => run({ cwd }), {
code: 'ERR_INVALID_ARG_TYPE'
}));
});

it('should only allow object as options', () => {
[Symbol(), [], () => {}, 0, 1, 0n, 1n, '', '1', true, false]
.forEach((options) => assert.throws(() => run(options), {
Expand Down
26 changes: 23 additions & 3 deletions test/parallel/test-runner-watch-mode.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { describe, it, beforeEach } from 'node:test';
import { once } from 'node:events';
import assert from 'node:assert';
import { spawn } from 'node:child_process';
import { writeFileSync, renameSync, unlinkSync, existsSync } from 'node:fs';
import { writeFileSync, renameSync, unlinkSync, existsSync, mkdtempSync } from 'node:fs';
import util from 'internal/util';
import tmpdir from '../common/tmpdir.js';
import { join, basename } from 'node:path';

if (common.isIBMi)
common.skip('IBMi does not support `fs.watch()`');
Expand Down Expand Up @@ -41,13 +42,14 @@ async function testWatch({
fileToUpdate,
file,
action = 'update',
fileToCreate
fileToCreate,
cwd = tmpdir.path
}) {
const ran1 = util.createDeferredPromise();
const ran2 = util.createDeferredPromise();
const child = spawn(process.execPath,
['--watch', '--test', file ? fixturePaths[file] : undefined].filter(Boolean),
{ encoding: 'utf8', stdio: 'pipe', cwd: tmpdir.path }
{ encoding: 'utf8', stdio: 'pipe', cwd }
);
let stdout = '';
let currentRun = '';
Expand Down Expand Up @@ -191,4 +193,22 @@ describe('test runner watch mode', () => {
it('should run new tests when a new file is created in the watched directory', async () => {
await testWatch({ action: 'create', fileToCreate: 'new-test-file.test.js' });
});

it('should run new tests when a new file is created in a different cwd', async () => {
const newTestWithoutDep = `
const test = require('node:test');
test('test without dep has ran');
`;
const differentCwd = mkdtempSync(`${tmpdir.path}/different-cwd`);
const testFileName = 'test-without-dep.js';
const newTestFilePath = join(differentCwd, testFileName);
writeFileSync(newTestFilePath, newTestWithoutDep);
const differentCwdTmpPath = basename(differentCwd);

await testWatch({
action: 'create',
fileToCreate: `${differentCwdTmpPath}/new-test-file.test.js`,
cwd: differentCwd
});
});
});

0 comments on commit 17b7ce1

Please sign in to comment.