Skip to content

Commit 65fbe94

Browse files
aduh95targos
authored andcommitted
test: add escapePOSIXShell util
PR-URL: #55125 Reviewed-By: Jacob Smith <jacob@frende.me> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: LiviaMedeiros <livia@cirno.name> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent c308862 commit 65fbe94

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+275
-346
lines changed

test/abort/test-abort-fatal-error.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,17 @@ if (common.isWindows)
2727
const assert = require('assert');
2828
const exec = require('child_process').exec;
2929

30-
let cmdline = `ulimit -c 0; ${process.execPath}`;
31-
cmdline += ' --max-old-space-size=16 --max-semi-space-size=4';
32-
cmdline += ' -e "a = []; for (i = 0; i < 1e9; i++) { a.push({}) }"';
30+
const cmdline =
31+
common.escapePOSIXShell`ulimit -c 0; "${
32+
process.execPath
33+
}" --max-old-space-size=16 --max-semi-space-size=4 -e "a = []; for (i = 0; i < 1e9; i++) { a.push({}) }"`;
3334

34-
exec(cmdline, function(err, stdout, stderr) {
35+
exec(...cmdline, common.mustCall((err, stdout, stderr) => {
3536
if (!err) {
3637
console.log(stdout);
3738
console.log(stderr);
3839
assert(false, 'this test should fail');
3940
}
4041

4142
assert(common.nodeProcessAborted(err.code, err.signal));
42-
});
43+
}));

test/async-hooks/test-callback-error.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,14 @@ assert.ok(!arg);
6262
let program = process.execPath;
6363
let args = [
6464
'--abort-on-uncaught-exception', __filename, 'test_callback_abort' ];
65-
const options = { encoding: 'utf8' };
65+
let options = {};
6666
if (!common.isWindows) {
67-
program = `ulimit -c 0 && exec ${program} ${args.join(' ')}`;
67+
[program, options] = common.escapePOSIXShell`ulimit -c 0 && exec "${program}" ${args[0]} "${args[1]}" ${args[2]}`;
6868
args = [];
6969
options.shell = true;
7070
}
71+
72+
options.encoding = 'utf8';
7173
const child = spawnSync(program, args, options);
7274
if (common.isWindows) {
7375
assert.strictEqual(child.status, 134);

test/common/README.md

+27
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,33 @@ Creates a 10 MiB file of all null characters.
112112

113113
Indicates if there is more than 1gb of total memory.
114114

115+
### ``escapePOSIXShell`shell command` ``
116+
117+
Escapes values in a string template literal to pass them as env variable. On Windows, this function
118+
does not escape anything (which is fine for most paths, as `"` is not a valid
119+
char in a path on Windows), so for tests that must pass on Windows, you should
120+
use it only to escape paths, inside double quotes.
121+
This function is meant to be used for tagged template strings.
122+
123+
```js
124+
const { escapePOSIXShell } = require('../common');
125+
const fixtures = require('../common/fixtures');
126+
const { execSync } = require('node:child_process');
127+
const origin = fixtures.path('origin');
128+
const destination = fixtures.path('destination');
129+
130+
execSync(...escapePOSIXShell`cp "${origin}" "${destination}"`);
131+
132+
// When you need to specify specific options, and/or additional env variables:
133+
const [cmd, opts] = escapePOSIXShell`cp "${origin}" "${destination}"`;
134+
console.log(typeof cmd === 'string'); // true
135+
console.log(opts === undefined || typeof opts.env === 'object'); // true
136+
execSync(cmd, { ...opts, stdio: 'ignore' });
137+
execSync(cmd, { stdio: 'ignore', env: { ...opts?.env, KEY: 'value' } });
138+
```
139+
140+
When possible, avoid using a shell; that way, there's no need to escape values.
141+
115142
### `expectsError(validator[, exact])`
116143

117144
* `validator` [\<Object>][<Object>] | [\<RegExp>][<RegExp>] |

test/common/index.js

+30-5
Original file line numberDiff line numberDiff line change
@@ -249,15 +249,13 @@ const PIPE = (() => {
249249
// `$node --abort-on-uncaught-exception $file child`
250250
// the process aborts.
251251
function childShouldThrowAndAbort() {
252-
let testCmd = '';
252+
const escapedArgs = escapePOSIXShell`"${process.argv[0]}" --abort-on-uncaught-exception "${process.argv[1]}" child`;
253253
if (!isWindows) {
254254
// Do not create core files, as it can take a lot of disk space on
255255
// continuous testing and developers' machines
256-
testCmd += 'ulimit -c 0 && ';
256+
escapedArgs[0] = 'ulimit -c 0 && ' + escapedArgs[0];
257257
}
258-
testCmd += `"${process.argv[0]}" --abort-on-uncaught-exception `;
259-
testCmd += `"${process.argv[1]}" child`;
260-
const child = exec(testCmd);
258+
const child = exec(...escapedArgs);
261259
child.on('exit', function onExit(exitCode, signal) {
262260
const errMsg = 'Test should have aborted ' +
263261
`but instead exited with exit code ${exitCode}` +
@@ -888,6 +886,32 @@ function spawnPromisified(...args) {
888886
});
889887
}
890888

889+
/**
890+
* Escape values in a string template literal. On Windows, this function
891+
* does not escape anything (which is fine for paths, as `"` is not a valid char
892+
* in a path on Windows), so you should use it only to escape paths – or other
893+
* values on tests which are skipped on Windows.
894+
* This function is meant to be used for tagged template strings.
895+
* @returns {[string, object | undefined]} An array that can be passed as
896+
* arguments to `exec` or `execSync`.
897+
*/
898+
function escapePOSIXShell(cmdParts, ...args) {
899+
if (common.isWindows) {
900+
// On Windows, paths cannot contain `"`, so we can return the string unchanged.
901+
return [String.raw({ raw: cmdParts }, ...args)];
902+
}
903+
// On POSIX shells, we can pass values via the env, as there's a standard way for referencing a variable.
904+
const env = { ...process.env };
905+
let cmd = cmdParts[0];
906+
for (let i = 0; i < args.length; i++) {
907+
const envVarName = `ESCAPED_${i}`;
908+
env[envVarName] = args[i];
909+
cmd += '${' + envVarName + '}' + cmdParts[i + 1];
910+
}
911+
912+
return [cmd, { env }];
913+
};
914+
891915
function getPrintedStackTrace(stderr) {
892916
const lines = stderr.split('\n');
893917

@@ -951,6 +975,7 @@ const common = {
951975
childShouldThrowAndAbort,
952976
createZeroFilledFile,
953977
defaultAutoSelectFamilyAttemptTimeout,
978+
escapePOSIXShell,
954979
expectsError,
955980
expectRequiredModule,
956981
expectWarning,

test/common/index.mjs

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
childShouldThrowAndAbort,
1212
createZeroFilledFile,
1313
enoughTestMem,
14+
escapePOSIXShell,
1415
expectsError,
1516
expectWarning,
1617
getArrayBufferViews,
@@ -64,6 +65,7 @@ export {
6465
createRequire,
6566
createZeroFilledFile,
6667
enoughTestMem,
68+
escapePOSIXShell,
6769
expectsError,
6870
expectWarning,
6971
getArrayBufferViews,

test/parallel/test-child-process-bad-stdio.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ ChildProcess.prototype.spawn = function() {
2727
};
2828

2929
function createChild(options, callback) {
30-
const cmd = `"${process.execPath}" "${__filename}" child`;
30+
const [cmd, opts] = common.escapePOSIXShell`"${process.execPath}" "${__filename}" child`;
31+
options = { ...options, env: { ...opts?.env, ...options.env } };
3132

3233
return cp.exec(cmd, options, common.mustCall(callback));
3334
}

test/parallel/test-child-process-exec-encoding.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ if (process.argv[2] === 'child') {
1313
const expectedStdout = `${stdoutData}\n`;
1414
const expectedStderr = `${stderrData}\n`;
1515
function run(options, callback) {
16-
const cmd = `"${process.execPath}" "${__filename}" child`;
16+
const [cmd, opts] = common.escapePOSIXShell`"${process.execPath}" "${__filename}" child`;
17+
options = { ...options, env: { ...opts?.env, ...options.env } };
1718

1819
cp.exec(cmd, options, common.mustSucceed((stdout, stderr) => {
1920
callback(stdout, stderr);

test/parallel/test-child-process-exec-maxbuf.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ function runChecks(err, stdio, streamName, expected) {
1414
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
1515
// Windows, so we can simply pass the path.
1616
const execNode = (args, optionsOrCallback, callback) => {
17+
const [cmd, opts] = common.escapePOSIXShell`"${process.execPath}" `;
1718
let options = optionsOrCallback;
1819
if (typeof optionsOrCallback === 'function') {
1920
options = undefined;
2021
callback = optionsOrCallback;
2122
}
2223
return cp.exec(
23-
`"${common.isWindows ? process.execPath : '$NODE'}" ${args}`,
24-
common.isWindows ? options : { ...options, env: { ...process.env, NODE: process.execPath } },
24+
cmd + args,
25+
{ ...opts, ...options },
2526
callback,
2627
);
2728
};

test/parallel/test-child-process-exec-std-encoding.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ if (process.argv[2] === 'child') {
1212
console.log(stdoutData);
1313
console.error(stderrData);
1414
} else {
15-
const cmd = `"${process.execPath}" "${__filename}" child`;
16-
const child = cp.exec(cmd, common.mustSucceed((stdout, stderr) => {
15+
const child = cp.exec(...common.escapePOSIXShell`"${process.execPath}" "${__filename}" child`, common.mustSucceed((stdout, stderr) => {
1716
assert.strictEqual(stdout, expectedStdout);
1817
assert.strictEqual(stderr, expectedStderr);
1918
}));

test/parallel/test-child-process-exec-timeout-expire.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ if (process.argv[2] === 'child') {
1818
return;
1919
}
2020

21-
const cmd = `"${process.execPath}" "${__filename}" child`;
21+
const [cmd, opts] = common.escapePOSIXShell`"${process.execPath}" "${__filename}" child`;
2222

2323
cp.exec(cmd, {
24+
...opts,
2425
timeout: kExpiringParentTimer,
2526
}, common.mustCall((err, stdout, stderr) => {
2627
console.log('[stdout]', stdout.trim());

test/parallel/test-child-process-exec-timeout-kill.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ if (process.argv[2] === 'child') {
1818
return;
1919
}
2020

21-
const cmd = `"${process.execPath}" "${__filename}" child`;
21+
const [cmd, opts] = common.escapePOSIXShell`"${process.execPath}" "${__filename}" child`;
2222

2323
// Test with a different kill signal.
2424
cp.exec(cmd, {
25+
...opts,
2526
timeout: kExpiringParentTimer,
2627
killSignal: 'SIGKILL'
2728
}, common.mustCall((err, stdout, stderr) => {

test/parallel/test-child-process-exec-timeout-not-expired.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ if (process.argv[2] === 'child') {
2222
return;
2323
}
2424

25-
const cmd = `"${process.execPath}" "${__filename}" child`;
25+
const [cmd, opts] = common.escapePOSIXShell`"${process.execPath}" "${__filename}" child`;
2626

2727
cp.exec(cmd, {
28-
timeout: kTimeoutNotSupposedToExpire
28+
...opts,
29+
timeout: kTimeoutNotSupposedToExpire,
2930
}, common.mustSucceed((stdout, stderr) => {
3031
assert.strictEqual(stdout.trim(), 'child stdout');
3132
assert.strictEqual(stderr.trim(), 'child stderr');

test/parallel/test-child-process-execsync-maxbuf.js

+7-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use strict';
2-
require('../common');
2+
const { escapePOSIXShell } = require('../common');
33

44
// This test checks that the maxBuffer option for child_process.spawnSync()
55
// works as expected.
@@ -10,15 +10,12 @@ const { execSync } = require('child_process');
1010
const msgOut = 'this is stdout';
1111
const msgOutBuf = Buffer.from(`${msgOut}\n`);
1212

13-
const args = [
14-
'-e',
15-
`"console.log('${msgOut}')";`,
16-
];
13+
const [cmd, opts] = escapePOSIXShell`"${process.execPath}" -e "${`console.log('${msgOut}')`}"`;
1714

1815
// Verify that an error is returned if maxBuffer is surpassed.
1916
{
2017
assert.throws(() => {
21-
execSync(`"${process.execPath}" ${args.join(' ')}`, { maxBuffer: 1 });
18+
execSync(cmd, { ...opts, maxBuffer: 1 });
2219
}, (e) => {
2320
assert.ok(e, 'maxBuffer should error');
2421
assert.strictEqual(e.code, 'ENOBUFS');
@@ -33,8 +30,8 @@ const args = [
3330
// Verify that a maxBuffer size of Infinity works.
3431
{
3532
const ret = execSync(
36-
`"${process.execPath}" ${args.join(' ')}`,
37-
{ maxBuffer: Infinity }
33+
cmd,
34+
{ ...opts, maxBuffer: Infinity },
3835
);
3936

4037
assert.deepStrictEqual(ret, msgOutBuf);
@@ -43,9 +40,7 @@ const args = [
4340
// Default maxBuffer size is 1024 * 1024.
4441
{
4542
assert.throws(() => {
46-
execSync(
47-
`"${process.execPath}" -e "console.log('a'.repeat(1024 * 1024))"`
48-
);
43+
execSync(...escapePOSIXShell`"${process.execPath}" -e "console.log('a'.repeat(1024 * 1024))"`);
4944
}, (e) => {
5045
assert.ok(e, 'maxBuffer should error');
5146
assert.strictEqual(e.code, 'ENOBUFS');
@@ -56,9 +51,7 @@ const args = [
5651

5752
// Default maxBuffer size is 1024 * 1024.
5853
{
59-
const ret = execSync(
60-
`"${process.execPath}" -e "console.log('a'.repeat(1024 * 1024 - 1))"`
61-
);
54+
const ret = execSync(...escapePOSIXShell`"${process.execPath}" -e "console.log('a'.repeat(1024 * 1024 - 1))"`);
6255

6356
assert.deepStrictEqual(
6457
ret.toString().trim(),

test/parallel/test-child-process-promisified.js

+2-10
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,8 @@ const { promisify } = require('util');
77
const exec = promisify(child_process.exec);
88
const execFile = promisify(child_process.execFile);
99

10-
// The execPath might contain chars that should be escaped in a shell context.
11-
// On non-Windows, we can pass the path via the env; `"` is not a valid char on
12-
// Windows, so we can simply pass the path.
13-
const execNode = (args) => exec(
14-
`"${common.isWindows ? process.execPath : '$NODE'}" ${args}`,
15-
common.isWindows ? undefined : { env: { ...process.env, NODE: process.execPath } },
16-
);
17-
1810
{
19-
const promise = execNode('-p 42');
11+
const promise = exec(...common.escapePOSIXShell`"${process.execPath}" -p 42`);
2012

2113
assert(promise.child instanceof child_process.ChildProcess);
2214
promise.then(common.mustCall((obj) => {
@@ -53,7 +45,7 @@ const execNode = (args) => exec(
5345
const failingCodeWithStdoutErr =
5446
'console.log(42);console.error(43);process.exit(1)';
5547
{
56-
execNode(`-e "${failingCodeWithStdoutErr}"`)
48+
exec(...common.escapePOSIXShell`"${process.execPath}" -e "${failingCodeWithStdoutErr}"`)
5749
.catch(common.mustCall((err) => {
5850
assert.strictEqual(err.code, 1);
5951
assert.strictEqual(err.stdout, '42\n');

0 commit comments

Comments
 (0)