Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(node): add child_process.exec() #2684

Merged
merged 23 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4e8d26f
add child_process.exec roughly
PolarETech Sep 20, 2022
3b389ca
add tests
PolarETech Sep 20, 2022
00ea320
add test-child-process-exec-stdout-stderr-data-string.js
PolarETech Sep 22, 2022
a901c0f
add encoding tests
PolarETech Sep 22, 2022
69b6702
modify encoding tests
PolarETech Sep 22, 2022
d83d307
fix handling maxBuffer option in execFile()
PolarETech Sep 22, 2022
5f23a6d
add test-child-process-exec-maxbuf.js
PolarETech Sep 22, 2022
7f0d998
modify test-child-process-exec-maxbuf.js
PolarETech Sep 22, 2022
455a033
add test-child-process-exec-kill-throws.js
PolarETech Sep 22, 2022
096bb80
fix killSignal option should be passed to child.kill() in execFile()
PolarETech Sep 22, 2022
da19b19
add internal/child_process to module_all.ts
PolarETech Sep 22, 2022
58dc846
modify test-child-process-exec-kill-throws.js
PolarETech Sep 22, 2022
d7bc152
Merge branch 'denoland:main' into feat-child-process-exec
PolarETech Sep 22, 2022
475ece6
add tests with escaping issue to windowsIgnore in config.json
PolarETech Sep 22, 2022
ca40c0a
fix handling cwd option in execFile() and ChildProcess class
PolarETech Sep 22, 2022
3e958d5
add test-child-process-exec-cwd.js
PolarETech Sep 22, 2022
1831bad
fix type of encoding should be string
PolarETech Sep 22, 2022
696b6e1
Merge branch 'denoland:main' into feat-child-process-exec
PolarETech Sep 22, 2022
c033e41
add AbortSignal validation in spawn()
PolarETech Sep 23, 2022
aacaf9a
add promisify.custom
PolarETech Sep 23, 2022
63ebe64
add test-child-process-exec-abortcontroller-promisified.js
PolarETech Sep 23, 2022
eeea704
modify test-child-process-exec-abortcontroller-promisified.js
PolarETech Sep 23, 2022
ff95750
Merge branch 'denoland:main' into feat-child-process-exec
PolarETech Sep 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions node/_tools/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
"test-buffer-from.js",
"test-buffer-includes.js",
"test-buffer-indexof.js",
"test-child-process-exec-abortcontroller-promisified.js",
"test-child-process-exec-encoding.js",
"test-child-process-exec-kill-throws.js",
"test-child-process-exec-maxbuf.js",
"test-child-process-exec-std-encoding.js",
"test-child-process-spawnsync-env.js",
"test-child-process-spawnsync-maxbuf.js",
"test-console-instance.js",
Expand Down Expand Up @@ -166,6 +171,15 @@
"test-buffer-zero-fill-cli.js",
"test-buffer-zero-fill-reset.js",
"test-buffer-zero-fill.js",
"test-child-process-exec-abortcontroller-promisified.js",
"test-child-process-exec-cwd.js",
"test-child-process-exec-encoding.js",
"test-child-process-exec-env.js",
"test-child-process-exec-error.js",
"test-child-process-exec-kill-throws.js",
"test-child-process-exec-maxbuf.js",
"test-child-process-exec-std-encoding.js",
"test-child-process-exec-stdout-stderr-data-string.js",
"test-child-process-kill.js",
"test-child-process-spawnsync-args.js",
"test-child-process-spawnsync-env.js",
Expand Down Expand Up @@ -684,6 +698,12 @@
},
"windowsIgnore": {
"parallel": [
"test-child-process-exec-cwd.js",
"test-child-process-exec-encoding.js",
"test-child-process-exec-env.js",
"test-child-process-exec-maxbuf.js",
"test-child-process-exec-std-encoding.js",
"test-child-process-exec-stdout-stderr-data-string.js",
"test-child-process-kill.js",
"test-child-process-spawnsync-args.js",
"test-console-log-throw-primitive.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 18.8.0
// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually

// TODO(#2674): The "eval" subcommand passed to execPromisifed() should be the "-e" option.

'use strict';
const common = require('../common');
const assert = require('assert');
const exec = require('child_process').exec;
const { promisify } = require('util');

const execPromisifed = promisify(exec);
const invalidArgTypeError = {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
};

const waitCommand = common.isLinux ?
'sleep 2m' :
`${process.execPath} eval "setInterval(()=>{}, 99)"`;

{
const ac = new AbortController();
const signal = ac.signal;
const promise = execPromisifed(waitCommand, { signal });
assert.rejects(promise, /AbortError/, 'post aborted sync signal failed')
.then(common.mustCall());
ac.abort();
}

{
assert.throws(() => {
execPromisifed(waitCommand, { signal: {} });
}, invalidArgTypeError);
}

{
function signal() {}
assert.throws(() => {
execPromisifed(waitCommand, { signal });
}, invalidArgTypeError);
}

{
const signal = AbortSignal.abort(); // Abort in advance
const promise = execPromisifed(waitCommand, { signal });

assert.rejects(promise, /AbortError/, 'pre aborted signal failed')
.then(common.mustCall());
}
46 changes: 46 additions & 0 deletions node/_tools/test/parallel/test-child-process-exec-cwd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 18.8.0
// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

'use strict';
const common = require('../common');
const assert = require('assert');
const exec = require('child_process').exec;

let pwdcommand, dir;

if (common.isWindows) {
pwdcommand = 'echo %cd%';
dir = 'c:\\windows';
} else {
pwdcommand = 'pwd';
dir = '/dev';
}

exec(pwdcommand, { cwd: dir }, common.mustSucceed((stdout, stderr) => {
assert(stdout.startsWith(dir));
}));
59 changes: 59 additions & 0 deletions node/_tools/test/parallel/test-child-process-exec-encoding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 18.8.0
// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually

// TODO(#2674): The process.argv[3] check should be argv[2], and the
// command passed to exec() should not need to include "run", "-A",
// "--unstable", and "require.ts".

'use strict';
const common = require('../common');
const stdoutData = 'foo';
const stderrData = 'bar';

if (process.argv[3] === 'child') {
// The following console calls are part of the test.
console.log(stdoutData);
console.error(stderrData);
} else {
const assert = require('assert');
const cp = require('child_process');
const expectedStdout = `${stdoutData}\n`;
const expectedStderr = `${stderrData}\n`;
function run(options, callback) {
const cmd = `"${process.execPath}" run -A --unstable require.ts "${__filename}" child`;

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

// Test default encoding, which should be utf8.
run({}, (stdout, stderr) => {
assert.strictEqual(typeof stdout, 'string');
assert.strictEqual(typeof stderr, 'string');
assert.strictEqual(stdout, expectedStdout);
assert.strictEqual(stderr, expectedStderr);
});

// Test explicit utf8 encoding.
run({ encoding: 'utf8' }, (stdout, stderr) => {
assert.strictEqual(typeof stdout, 'string');
assert.strictEqual(typeof stderr, 'string');
assert.strictEqual(stdout, expectedStdout);
assert.strictEqual(stderr, expectedStderr);
});

// Test cases that result in buffer encodings.
[undefined, null, 'buffer', 'invalid'].forEach((encoding) => {
run({ encoding }, (stdout, stderr) => {
assert(stdout instanceof Buffer);
assert(stdout instanceof Buffer);
assert.strictEqual(stdout.toString(), expectedStdout);
assert.strictEqual(stderr.toString(), expectedStderr);
});
});
}
71 changes: 71 additions & 0 deletions node/_tools/test/parallel/test-child-process-exec-env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 18.8.0
// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

'use strict';
const { isWindows } = require('../common');
const assert = require('assert');
const exec = require('child_process').exec;
const debug = require('util').debuglog('test');

let success_count = 0;
let error_count = 0;
let response = '';
let child;

function after(err, stdout, stderr) {
if (err) {
error_count++;
debug(`error!: ${err.code}`);
debug(`stdout: ${JSON.stringify(stdout)}`);
debug(`stderr: ${JSON.stringify(stderr)}`);
assert.strictEqual(err.killed, false);
} else {
success_count++;
assert.notStrictEqual(stdout, '');
}
}

if (!isWindows) {
child = exec('/usr/bin/env', { env: { 'HELLO': 'WORLD' } }, after);
} else {
child = exec('set',
{ env: { ...process.env, 'HELLO': 'WORLD' } },
after);
}

child.stdout.setEncoding('utf8');
child.stdout.on('data', function(chunk) {
response += chunk;
});

process.on('exit', function() {
debug('response: ', response);
assert.strictEqual(success_count, 1);
assert.strictEqual(error_count, 0);
assert.ok(response.includes('HELLO=WORLD'));
});
51 changes: 51 additions & 0 deletions node/_tools/test/parallel/test-child-process-exec-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 18.8.0
// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

'use strict';
const common = require('../common');
const assert = require('assert');
const child_process = require('child_process');

function test(fn, code, expectPidType = 'number') {
const child = fn('does-not-exist', common.mustCall(function(err) {
assert.strictEqual(err.code, code);
assert(err.cmd.includes('does-not-exist'));
}));

assert.strictEqual(typeof child.pid, expectPidType);
}

// With `shell: true`, expect pid (of the shell)
if (common.isWindows) {
test(child_process.exec, 1, 'number'); // Exit code of cmd.exe
} else {
test(child_process.exec, 127, 'number'); // Exit code of /bin/sh
}

// With `shell: false`, expect no pid
test(child_process.execFile, 'ENOENT', 'undefined');
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file

// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 18.8.0
// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually

// TODO(#2674): The process.argv[3] check should be argv[2], and the
// command passed to exec() should not need to include "run", "-A",
// "--unstable", and "require.ts".

'use strict';
// Flags: --expose-internals
const common = require('../common');
const assert = require('assert');
const cp = require('child_process');

if (process.argv[3] === 'child') {
// Since maxBuffer is 0, this should trigger an error.
console.log('foo');
} else {
const internalCp = require('internal/child_process');

// Monkey patch ChildProcess#kill() to kill the process and then throw.
const kill = internalCp.ChildProcess.prototype.kill;

internalCp.ChildProcess.prototype.kill = function() {
kill.apply(this, arguments);
throw new Error('mock error');
};

const cmd = `"${process.execPath}" run -A --unstable require.ts "${__filename}" child`;
const options = { maxBuffer: 0, killSignal: 'SIGKILL' };

const child = cp.exec(cmd, options, common.mustCall((err, stdout, stderr) => {
// Verify that if ChildProcess#kill() throws, the error is reported.
assert.strictEqual(err.message, 'mock error', err);
assert.strictEqual(stdout, '');
assert.strictEqual(stderr, '');
assert.strictEqual(child.killed, true);
}));
}
Loading