Skip to content

Commit

Permalink
test_runner: add --experimental-test-cwd option to test runner
Browse files Browse the repository at this point in the history
  • Loading branch information
pmarchini committed Sep 11, 2024
1 parent c4d59f8 commit d8aaaf6
Show file tree
Hide file tree
Showing 8 changed files with 35 additions and 21 deletions.
12 changes: 12 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,18 @@ generated as part of the test runner output. If no tests are run, a coverage
report is not generated. See the documentation on
[collecting code coverage from tests][] for more details.

### `--experimental-test-cwd=directory`

<!-- YAML
added: CHANGEME
-->

> Stability: 1.0 - Early development
Set the current working directory for the test runner.
If not specified, the current working directory is used.
This flag is particularly useful when running tests from a different directory.

### `--experimental-test-isolation=mode`

<!-- YAML
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/test_runner/coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ function sortCoverageFiles(a, b) {

function setupCoverage(options) {
let originalCoverageDirectory = process.env.NODE_V8_COVERAGE;
const cwd = process.cwd();
const cwd = options.testCwd;

if (originalCoverageDirectory) {
// NODE_V8_COVERAGE was already specified. Convert it to an absolute path
Expand Down
8 changes: 4 additions & 4 deletions lib/internal/test_runner/harness.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ function createTestTree(rootTestOptions, globalOptions) {
return globalRoot;
}

function createProcessEventHandler(eventName, rootTest) {
function createProcessEventHandler(eventName, rootTest, cwd) {
return (err) => {
if (rootTest.harness.bootstrapPromise) {
// Something went wrong during the asynchronous portion of bootstrapping
Expand All @@ -109,7 +109,7 @@ function createProcessEventHandler(eventName, rootTest) {
const name = test.hookType ? `Test hook "${test.hookType}"` : `Test "${test.name}"`;
let locInfo = '';
if (test.loc) {
const relPath = relative(process.cwd(), test.loc.file);
const relPath = relative(cwd, test.loc.file);
locInfo = ` at ${relPath}:${test.loc.line}:${test.loc.column}`;
}

Expand Down Expand Up @@ -197,9 +197,9 @@ function setupProcessState(root, globalOptions) {
hook.enable();

const exceptionHandler =
createProcessEventHandler('uncaughtException', root);
createProcessEventHandler('uncaughtException', root, globalOptions.testCwd);
const rejectionHandler =
createProcessEventHandler('unhandledRejection', root);
createProcessEventHandler('unhandledRejection', root, globalOptions.testCwd);
const coverage = configureCoverage(root, globalOptions);
const exitHandler = async () => {
if (root.subtests.length === 0 && (root.hooks.before.length > 0 || root.hooks.after.length > 0)) {
Expand Down
18 changes: 9 additions & 9 deletions lib/internal/test_runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const {

const { spawn } = require('child_process');
const { finished } = require('internal/streams/end-of-stream');
const { resolve } = require('path');
const { resolve, sep, isAbsolute } = require('path');
const { DefaultDeserializer, DefaultSerializer } = require('v8');
const { getOptionValue } = require('internal/options');
const { Interface } = require('internal/readline/interface');
Expand Down Expand Up @@ -62,7 +62,6 @@ const { isRegExp } = require('internal/util/types');
const { pathToFileURL } = require('internal/url');
const {
createDeferredPromise,
getCWDURL,
kEmptyObject,
} = require('internal/util');
const { kEmitMessage } = require('internal/test_runner/tests_stream');
Expand Down Expand Up @@ -96,7 +95,7 @@ let debug = require('internal/util/debuglog').debuglog('test_runner', (fn) => {
});

const kIsolatedProcessName = Symbol('kIsolatedProcessName');
const kFilterArgs = ['--test', '--experimental-test-coverage', '--watch'];
const kFilterArgs = ['--test', '--experimental-test-coverage', '--watch', '--experimental-test-cwd'];
const kFilterArgValues = ['--test-reporter', '--test-reporter-destination'];
const kDiagnosticsFilterArgs = ['tests', 'suites', 'pass', 'fail', 'cancelled', 'skipped', 'todo', 'duration_ms'];

Expand Down Expand Up @@ -130,7 +129,7 @@ function filterExecArgv(arg, i, arr) {
!ArrayPrototypeSome(kFilterArgValues, (p) => arg === p || (i > 0 && arr[i - 1] === p) || StringPrototypeStartsWith(arg, `${p}=`));
}

function getRunArgs(path, { forceExit, inspectPort, testNamePatterns, testSkipPatterns, only }) {
function getRunArgs(path, { forceExit, inspectPort, testNamePatterns, testSkipPatterns, only, cwd }) {
const argv = ArrayPrototypeFilter(process.execArgv, filterExecArgv);
if (forceExit === true) {
ArrayPrototypePush(argv, '--test-force-exit');
Expand Down Expand Up @@ -629,7 +628,6 @@ function run(options = kEmptyObject) {
setup, // This line can be removed when parseCommandLine() is removed here.
};
const root = createTestTree(rootTestOptions, globalOptions);

let testFiles = files ?? createTestFileList(globPatterns, cwd);

if (shard) {
Expand Down Expand Up @@ -680,7 +678,9 @@ function run(options = kEmptyObject) {
};
} else if (isolation === 'none') {
if (watch) {
filesWatcher = watchFiles(testFiles, opts);
// TODO
const absoluteTestFiles = ArrayPrototypeMap(testFiles, (file) => isAbsolute(file) ? file : resolve(cwd, file));
filesWatcher = watchFiles(absoluteTestFiles, opts);
runFiles = async () => {
root.harness.bootstrapPromise = null;
root.harness.buildPromise = null;
Expand All @@ -693,7 +693,7 @@ function run(options = kEmptyObject) {
const { promise, resolve: finishBootstrap } = createDeferredPromise();

await root.runInAsyncScope(async () => {
const parentURL = getCWDURL().href;
const parentURL = pathToFileURL(cwd + sep).href;
const cascadedLoader = esmLoader.getOrInitializeCascadedLoader();
let topLevelTestCount = 0;

Expand All @@ -705,8 +705,8 @@ function run(options = kEmptyObject) {
}

for (let i = 0; i < testFiles.length; ++i) {
const testFile = resolve(cwd, testFiles[i]);
const fileURL = pathToFileURL(testFile);
const testFile = testFiles[i];
const fileURL = pathToFileURL(resolve(cwd, testFile));
const parent = i === 0 ? undefined : parentURL;
let threw = false;
let importError;
Expand Down
2 changes: 2 additions & 0 deletions lib/internal/test_runner/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ function parseCommandLine() {
let functionCoverage;
let destinations;
let isolation;
let testCwd = getOptionValue('--experimental-test-cwd') || process.cwd()
let only = getOptionValue('--test-only');
let reporters;
let shard;
Expand Down Expand Up @@ -311,6 +312,7 @@ function parseCommandLine() {
destinations,
forceExit,
isolation,
testCwd,
branchCoverage,
functionCoverage,
lineCoverage,
Expand Down
3 changes: 3 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
&EnvironmentOptions::test_coverage_lines,
kAllowedInEnvvar);

AddOption("--experimental-test-cwd",
"specify the working directory for the test runner",
&EnvironmentOptions::test_runner_cwd);
AddOption("--experimental-test-isolation",
"configures the type of test isolation used in the test runner",
&EnvironmentOptions::test_isolation);
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ class EnvironmentOptions : public Options {
bool test_only = false;
bool test_udp_no_try_send = false;
std::string test_isolation = "process";
std::string test_runner_cwd;
std::string test_shard;
std::vector<std::string> test_skip_pattern;
std::vector<std::string> coverage_include_pattern;
Expand Down
10 changes: 3 additions & 7 deletions test/parallel/test-runner-no-isolation-different-cwd.mjs
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import { allowGlobals, mustCall } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { strictEqual } from 'node:assert';
import { deepStrictEqual } from 'node:assert';
import { run } from 'node:test';

const stream = run({
cwd: fixtures.path('test-runner', 'no-isolation'),
isolation: 'none',
});

let errors = 0;
stream.on('test:fail', () => {
errors++;
});

stream.on('test:pass', mustCall(5));
// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
strictEqual(errors, 0);
allowGlobals(globalThis.GLOBAL_ORDER);
strictEqual(globalThis.GLOBAL_ORDER, [
deepStrictEqual(globalThis.GLOBAL_ORDER, [
'before one: <root>',
'suite one',
'before two: <root>',
Expand Down

0 comments on commit d8aaaf6

Please sign in to comment.