Skip to content

Commit 2e3bf75

Browse files
committed
test: add escapePathsFromShellCommand util
1 parent c42dc60 commit 2e3bf75

File tree

3 files changed

+44
-22
lines changed

3 files changed

+44
-22
lines changed

test/common/index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,13 +944,45 @@ function expectRequiredModule(mod, expectation, checkESModule = true) {
944944
assert.deepStrictEqual(clone, { ...expectation });
945945
}
946946

947+
948+
/**
949+
* Escape quoted values in a string template literal. On Windows, this function
950+
* does not escape anything (which is fine for paths, as `"` is not a valid char
951+
* in a path on Windows), so you should use it only to escape paths – or other
952+
* values on tests which are skipped on Windows.
953+
* @returns {[string, object | undefined]} An array that can be passed as
954+
* arguments to `exec` or `execSync`.
955+
*/
956+
function escapePathsFromShellCommand(cmdParts, ...args) {
957+
if (common.isWindows) {
958+
// On Windows, paths cannot contain `"`, so we can return the string unchanged.
959+
return [String.raw({ raw: cmdParts }, ...args)];
960+
}
961+
// On POSIX shells, we can pass values via the env, as there's a standard way for referencing a variable.
962+
const env = {...process.env}
963+
let cmd = cmdParts[0];
964+
for (let i = 0; i < args.length; i++) {
965+
if (cmdParts[i].at(-1) === '"' && cmdParts[i + 1][0] === '"') {
966+
const envVarName = `ESCAPED_${i}`
967+
cmd += '$' + envVarName;
968+
env[envVarName] = args[i];
969+
} else {
970+
cmd += args[i];
971+
}
972+
cmd += cmdParts[i + 1]
973+
}
974+
975+
return [cmd, { env }]
976+
}
977+
947978
const common = {
948979
allowGlobals,
949980
buildType,
950981
canCreateSymLink,
951982
childShouldThrowAndAbort,
952983
createZeroFilledFile,
953984
defaultAutoSelectFamilyAttemptTimeout,
985+
escapePathsFromShellCommand,
954986
expectsError,
955987
expectRequiredModule,
956988
expectWarning,

test/sequential/test-cli-syntax-bad.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
require('../common');
3+
const common = require('../common');
44
const { exec } = require('child_process');
55
const { test } = require('node:test');
66
const fixtures = require('../common/fixtures');
@@ -9,8 +9,8 @@ const node = process.execPath;
99

1010
// Test both sets of arguments that check syntax
1111
const syntaxArgs = [
12-
['-c'],
13-
['--check'],
12+
'-c',
13+
'--check',
1414
];
1515

1616
// Match on the name of the `Error` but not the message as it is different
@@ -27,13 +27,10 @@ const syntaxErrorRE = /^SyntaxError: \b/m;
2727
const path = fixtures.path(file);
2828

2929
// Loop each possible option, `-c` or `--check`
30-
syntaxArgs.forEach((args) => {
31-
test(`Checking syntax for ${file} with ${args.join(' ')}`, async (t) => {
32-
const _args = args.concat(path);
33-
const cmd = [node, ..._args].join(' ');
34-
30+
syntaxArgs.forEach((flag) => {
31+
test(`Checking syntax for ${file} with ${flag}`, async (t) => {
3532
try {
36-
const { stdout, stderr } = await execPromise(cmd);
33+
const { stdout, stderr } = await execNode(flag, path);
3734

3835
// No stdout should be produced
3936
t.assert.strictEqual(stdout, '');
@@ -51,9 +48,11 @@ const syntaxErrorRE = /^SyntaxError: \b/m;
5148
});
5249

5350
// Helper function to promisify exec
54-
function execPromise(cmd) {
51+
function execNode(flag, path) {
5552
const { promise, resolve, reject } = Promise.withResolvers();
56-
exec(cmd, (err, stdout, stderr) => {
53+
exec(`"${common.isWindows ? node : '$NODE'}" ${flag} "${common.isWindows ? path : '$PATH'}"`, {
54+
env: common.isWindows ? process.env : { ...process.env, NODE: node, PATH: path },
55+
}, (err, stdout, stderr) => {
5756
if (err) return reject({ ...err, stdout, stderr });
5857
resolve({ stdout, stderr });
5958
});

test/sequential/test-cli-syntax-good.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
'use strict';
22

3-
const common = require('../common');
3+
const { escapePathsFromShellCommand } = require('../common');
44
const assert = require('assert');
55
const { exec } = require('child_process');
66
const fixtures = require('../common/fixtures');
77

8-
// The execPath might contain chars that should be escaped in a shell context.
9-
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
10-
// Windows, so we can simply pass the path.
11-
const execNode = (flag, file, callback) => exec(
12-
`"${common.isWindows ? process.execPath : '$NODE'}" ${flag} "${common.isWindows ? file : '$FILE'}"`,
13-
common.isWindows ? undefined : { env: { ...process.env, NODE: process.execPath, FILE: file } },
14-
callback,
15-
);
16-
178
// Test both sets of arguments that check syntax
189
const syntaxArgs = [
1910
'-c',
@@ -33,7 +24,7 @@ const syntaxArgs = [
3324

3425
// Loop each possible option, `-c` or `--check`
3526
syntaxArgs.forEach(function(flag) {
36-
execNode(flag, file, common.mustCall((err, stdout, stderr) => {
27+
exec(...escapePathsFromShellCommand`"${process.execPath}" ${flag} "${file}"`, common.mustCall((err, stdout, stderr) => {
3728
if (err) {
3829
console.log('-- stdout --');
3930
console.log(stdout);

0 commit comments

Comments
 (0)