summaryrefslogtreecommitdiff
path: root/cli/tests/unit_node/child_process_test.ts
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2024-02-10 13:22:13 -0700
committerGitHub <noreply@github.com>2024-02-10 20:22:13 +0000
commitf5e46c9bf2f50d66a953fa133161fc829cecff06 (patch)
tree8faf2f5831c1c7b11d842cd9908d141082c869a5 /cli/tests/unit_node/child_process_test.ts
parentd2477f780630a812bfd65e3987b70c0d309385bb (diff)
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests -> tests, and updates of relative paths for files. This is the first step towards aggregate all of the integration test files under tests/, which will lead to a set of integration tests that can run without the CLI binary being built. While we could leave these tests under `cli`, it would require us to keep a more complex directory structure for the various test runners. In addition, we have a lot of complexity to ignore various test files in the `cli` project itself (cargo publish exclusion rules, autotests = false, etc). And finally, the `tests/` folder will eventually house the `test_ffi`, `test_napi` and other testing code, reducing the size of the root repo directory. For easier review, the extremely large and noisy "move" is in the first commit (with no changes -- just a move), while the remainder of the changes to actual files is in the second commit.
Diffstat (limited to 'cli/tests/unit_node/child_process_test.ts')
-rw-r--r--cli/tests/unit_node/child_process_test.ts773
1 files changed, 0 insertions, 773 deletions
diff --git a/cli/tests/unit_node/child_process_test.ts b/cli/tests/unit_node/child_process_test.ts
deleted file mode 100644
index 5314d66e7..000000000
--- a/cli/tests/unit_node/child_process_test.ts
+++ /dev/null
@@ -1,773 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-
-import CP from "node:child_process";
-import { Buffer } from "node:buffer";
-import {
- assert,
- assertEquals,
- assertExists,
- assertNotStrictEquals,
- assertStrictEquals,
- assertStringIncludes,
-} from "@test_util/std/assert/mod.ts";
-import * as path from "@test_util/std/path/mod.ts";
-
-const { spawn, spawnSync, execFile, execFileSync, ChildProcess } = CP;
-
-function withTimeout<T>(
- timeoutInMS = 10_000,
-): ReturnType<typeof Promise.withResolvers<T>> {
- const deferred = Promise.withResolvers<T>();
- const timer = setTimeout(() => {
- deferred.reject("Timeout");
- }, timeoutInMS);
- deferred.promise.then(() => {
- clearTimeout(timer);
- });
- return deferred;
-}
-
-// TODO(uki00a): Once Node.js's `parallel/test-child-process-spawn-error.js` works, this test case should be removed.
-Deno.test("[node/child_process spawn] The 'error' event is emitted when no binary is found", async () => {
- const deferred = withTimeout<void>();
- const childProcess = spawn("no-such-cmd");
- childProcess.on("error", (_err: Error) => {
- // TODO(@bartlomieju) Assert an error message.
- deferred.resolve();
- });
- await deferred.promise;
-});
-
-Deno.test("[node/child_process spawn] The 'exit' event is emitted with an exit code after the child process ends", async () => {
- const deferred = withTimeout<void>();
- const childProcess = spawn(Deno.execPath(), ["--help"], {
- env: { NO_COLOR: "true" },
- });
- try {
- let exitCode = null;
- childProcess.on("exit", (code: number) => {
- deferred.resolve();
- exitCode = code;
- });
- await deferred.promise;
- assertStrictEquals(exitCode, 0);
- assertStrictEquals(childProcess.exitCode, exitCode);
- } finally {
- childProcess.kill();
- childProcess.stdout?.destroy();
- childProcess.stderr?.destroy();
- }
-});
-
-Deno.test("[node/child_process disconnect] the method exists", async () => {
- const deferred = withTimeout<void>();
- const childProcess = spawn(Deno.execPath(), ["--help"], {
- env: { NO_COLOR: "true" },
- });
- try {
- childProcess.disconnect();
- childProcess.on("exit", () => {
- deferred.resolve();
- });
- await deferred.promise;
- } finally {
- childProcess.kill();
- childProcess.stdout?.destroy();
- childProcess.stderr?.destroy();
- }
-});
-
-Deno.test({
- name: "[node/child_process spawn] Verify that stdin and stdout work",
- fn: async () => {
- const deferred = withTimeout<void>();
- const childProcess = spawn(Deno.execPath(), ["fmt", "-"], {
- env: { NO_COLOR: "true" },
- stdio: ["pipe", "pipe"],
- });
- try {
- assert(childProcess.stdin, "stdin should be defined");
- assert(childProcess.stdout, "stdout should be defined");
- let data = "";
- childProcess.stdout.on("data", (chunk) => {
- data += chunk;
- });
- childProcess.stdin.write(" console.log('hello')", "utf-8");
- childProcess.stdin.end();
- childProcess.on("close", () => {
- deferred.resolve();
- });
- await deferred.promise;
- assertStrictEquals(data, `console.log("hello");\n`);
- } finally {
- childProcess.kill();
- }
- },
-});
-
-Deno.test({
- name: "[node/child_process spawn] stdin and stdout with binary data",
- fn: async () => {
- const deferred = withTimeout<void>();
- const p = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "./testdata/binary_stdio.js",
- );
- const childProcess = spawn(Deno.execPath(), ["run", p], {
- env: { NO_COLOR: "true" },
- stdio: ["pipe", "pipe"],
- });
- try {
- assert(childProcess.stdin, "stdin should be defined");
- assert(childProcess.stdout, "stdout should be defined");
- let data: Buffer;
- childProcess.stdout.on("data", (chunk) => {
- data = chunk;
- });
- const buffer = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
- childProcess.stdin.write(buffer);
- childProcess.stdin.end();
- childProcess.on("close", () => {
- deferred.resolve();
- });
- await deferred.promise;
- assertEquals(new Uint8Array(data!), buffer);
- } finally {
- childProcess.kill();
- }
- },
-});
-
-async function spawnAndGetEnvValue(
- inputValue: string | number | boolean,
-): Promise<string> {
- const deferred = withTimeout<string>();
- const env = spawn(
- `"${Deno.execPath()}" eval -p "Deno.env.toObject().BAZ"`,
- {
- env: { BAZ: String(inputValue), NO_COLOR: "true" },
- shell: true,
- },
- );
- try {
- let envOutput = "";
-
- assert(env.stdout);
- env.on("error", (err: Error) => deferred.reject(err));
- env.stdout.on("data", (data) => {
- envOutput += data;
- });
- env.on("close", () => {
- deferred.resolve(envOutput.trim());
- });
- return await deferred.promise;
- } finally {
- env.kill();
- }
-}
-
-Deno.test({
- ignore: Deno.build.os === "windows",
- name:
- "[node/child_process spawn] Verify that environment values can be numbers",
- async fn() {
- const envOutputValue = await spawnAndGetEnvValue(42);
- assertStrictEquals(envOutputValue, "42");
- },
-});
-
-Deno.test({
- ignore: Deno.build.os === "windows",
- name:
- "[node/child_process spawn] Verify that environment values can be booleans",
- async fn() {
- const envOutputValue = await spawnAndGetEnvValue(false);
- assertStrictEquals(envOutputValue, "false");
- },
-});
-
-/* Start of ported part */
-// Copyright Joyent and Node contributors. All rights reserved. MIT license.
-// Ported from Node 15.5.1
-
-// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-event.js` works.
-Deno.test("[child_process spawn] 'spawn' event", async () => {
- const timeout = withTimeout<void>();
- const subprocess = spawn(Deno.execPath(), ["eval", "console.log('ok')"]);
-
- let didSpawn = false;
- subprocess.on("spawn", function () {
- didSpawn = true;
- });
-
- function mustNotBeCalled() {
- timeout.reject(new Error("function should not have been called"));
- }
-
- const promises = [] as Promise<void>[];
- function mustBeCalledAfterSpawn() {
- const deferred = Promise.withResolvers<void>();
- promises.push(deferred.promise);
- return () => {
- if (didSpawn) {
- deferred.resolve();
- } else {
- deferred.reject(
- new Error("function should be called after the 'spawn' event"),
- );
- }
- };
- }
-
- subprocess.on("error", mustNotBeCalled);
- subprocess.stdout!.on("data", mustBeCalledAfterSpawn());
- subprocess.stdout!.on("end", mustBeCalledAfterSpawn());
- subprocess.stdout!.on("close", mustBeCalledAfterSpawn());
- subprocess.stderr!.on("data", mustNotBeCalled);
- subprocess.stderr!.on("end", mustBeCalledAfterSpawn());
- subprocess.stderr!.on("close", mustBeCalledAfterSpawn());
- subprocess.on("exit", mustBeCalledAfterSpawn());
- subprocess.on("close", mustBeCalledAfterSpawn());
-
- try {
- await Promise.race([Promise.all(promises), timeout.promise]);
- timeout.resolve();
- } finally {
- subprocess.kill();
- }
-});
-
-// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works.
-Deno.test("[child_process spawn] Verify that a shell is executed", async () => {
- const deferred = withTimeout<void>();
- const doesNotExist = spawn("does-not-exist", { shell: true });
- try {
- assertNotStrictEquals(doesNotExist.spawnfile, "does-not-exist");
- doesNotExist.on("error", () => {
- deferred.reject("The 'error' event must not be emitted.");
- });
- doesNotExist.on("exit", (code: number, signal: null) => {
- assertStrictEquals(signal, null);
-
- if (Deno.build.os === "windows") {
- assertStrictEquals(code, 1); // Exit code of cmd.exe
- } else {
- assertStrictEquals(code, 127); // Exit code of /bin/sh });
- }
-
- deferred.resolve();
- });
- await deferred.promise;
- } finally {
- doesNotExist.kill();
- doesNotExist.stdout?.destroy();
- doesNotExist.stderr?.destroy();
- }
-});
-
-// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works.
-Deno.test({
- ignore: Deno.build.os === "windows",
- name: "[node/child_process spawn] Verify that passing arguments works",
- async fn() {
- const deferred = withTimeout<void>();
- const echo = spawn("echo", ["foo"], {
- shell: true,
- });
- let echoOutput = "";
-
- try {
- assertStrictEquals(
- echo.spawnargs[echo.spawnargs.length - 1].replace(/"/g, ""),
- "echo foo",
- );
- assert(echo.stdout);
- echo.stdout.on("data", (data) => {
- echoOutput += data;
- });
- echo.on("close", () => {
- assertStrictEquals(echoOutput.trim(), "foo");
- deferred.resolve();
- });
- await deferred.promise;
- } finally {
- echo.kill();
- }
- },
-});
-
-// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works.
-Deno.test({
- ignore: Deno.build.os === "windows",
- name: "[node/child_process spawn] Verity that shell features can be used",
- async fn() {
- const deferred = withTimeout<void>();
- const cmd = "echo bar | cat";
- const command = spawn(cmd, {
- shell: true,
- });
- try {
- let commandOutput = "";
-
- assert(command.stdout);
- command.stdout.on("data", (data) => {
- commandOutput += data;
- });
-
- command.on("close", () => {
- assertStrictEquals(commandOutput.trim(), "bar");
- deferred.resolve();
- });
-
- await deferred.promise;
- } finally {
- command.kill();
- }
- },
-});
-
-// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works.
-Deno.test({
- ignore: Deno.build.os === "windows",
- name:
- "[node/child_process spawn] Verity that environment is properly inherited",
- async fn() {
- const deferred = withTimeout<void>();
- const env = spawn(
- `"${Deno.execPath()}" eval -p "Deno.env.toObject().BAZ"`,
- {
- env: { BAZ: "buzz", NO_COLOR: "true" },
- shell: true,
- },
- );
- try {
- let envOutput = "";
-
- assert(env.stdout);
- env.on("error", (err: Error) => deferred.reject(err));
- env.stdout.on("data", (data) => {
- envOutput += data;
- });
- env.on("close", () => {
- assertStrictEquals(envOutput.trim(), "buzz");
- deferred.resolve();
- });
- await deferred.promise;
- } finally {
- env.kill();
- }
- },
-});
-/* End of ported part */
-
-Deno.test({
- name: "[node/child_process execFile] Get stdout as a string",
- async fn() {
- let child: unknown;
- const script = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "./testdata/exec_file_text_output.js",
- );
- const promise = new Promise<string | null>((resolve, reject) => {
- child = execFile(Deno.execPath(), ["run", script], (err, stdout) => {
- if (err) reject(err);
- else if (stdout) resolve(stdout as string);
- else resolve(null);
- });
- });
- try {
- const stdout = await promise;
- assertEquals(stdout, "Hello World!\n");
- } finally {
- if (child instanceof ChildProcess) {
- child.kill();
- }
- }
- },
-});
-
-Deno.test({
- name: "[node/child_process execFile] Get stdout as a buffer",
- async fn() {
- let child: unknown;
- const script = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "./testdata/exec_file_text_output.js",
- );
- const promise = new Promise<Buffer | null>((resolve, reject) => {
- child = execFile(
- Deno.execPath(),
- ["run", script],
- { encoding: "buffer" },
- (err, stdout) => {
- if (err) reject(err);
- else if (stdout) resolve(stdout as Buffer);
- else resolve(null);
- },
- );
- });
- try {
- const stdout = await promise;
- assert(Buffer.isBuffer(stdout));
- assertEquals(stdout.toString("utf8"), "Hello World!\n");
- } finally {
- if (child instanceof ChildProcess) {
- child.kill();
- }
- }
- },
-});
-
-Deno.test({
- name: "[node/child_process execFile] Get stderr",
- async fn() {
- let child: unknown;
- const script = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "./testdata/exec_file_text_error.js",
- );
- const promise = new Promise<
- { err: Error | null; stderr?: string | Buffer }
- >((resolve) => {
- child = execFile(Deno.execPath(), ["run", script], (err, _, stderr) => {
- resolve({ err, stderr });
- });
- });
- try {
- const { err, stderr } = await promise;
- if (child instanceof ChildProcess) {
- assertEquals(child.exitCode, 1);
- assertEquals(stderr, "yikes!\n");
- } else {
- throw err;
- }
- } finally {
- if (child instanceof ChildProcess) {
- child.kill();
- }
- }
- },
-});
-
-Deno.test({
- name: "[node/child_process execFile] Exceed given maxBuffer limit",
- async fn() {
- let child: unknown;
- const script = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "./testdata/exec_file_text_error.js",
- );
- const promise = new Promise<
- { err: Error | null; stderr?: string | Buffer }
- >((resolve) => {
- child = execFile(Deno.execPath(), ["run", script], {
- encoding: "buffer",
- maxBuffer: 3,
- }, (err, _, stderr) => {
- resolve({ err, stderr });
- });
- });
- try {
- const { err, stderr } = await promise;
- if (child instanceof ChildProcess) {
- assert(err);
- assertEquals(
- // deno-lint-ignore no-explicit-any
- (err as any).code,
- "ERR_CHILD_PROCESS_STDIO_MAXBUFFER",
- );
- assertEquals(err.message, "stderr maxBuffer length exceeded");
- assertEquals((stderr as Buffer).toString("utf8"), "yik");
- } else {
- throw err;
- }
- } finally {
- if (child instanceof ChildProcess) {
- child.kill();
- }
- }
- },
-});
-
-Deno.test({
- name: "[node/child_process] ChildProcess.kill()",
- async fn() {
- const script = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "./testdata/infinite_loop.js",
- );
- const childProcess = spawn(Deno.execPath(), ["run", script]);
- const p = withTimeout<void>();
- const pStdout = withTimeout<void>();
- const pStderr = withTimeout<void>();
- childProcess.on("exit", () => p.resolve());
- childProcess.stdout.on("close", () => pStdout.resolve());
- childProcess.stderr.on("close", () => pStderr.resolve());
- childProcess.kill("SIGKILL");
- await p.promise;
- await pStdout.promise;
- await pStderr.promise;
- assert(childProcess.killed);
- assertEquals(childProcess.signalCode, "SIGKILL");
- assertExists(childProcess.exitCode);
- },
-});
-
-Deno.test({
- ignore: true,
- name: "[node/child_process] ChildProcess.unref()",
- async fn() {
- const script = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "testdata",
- "child_process_unref.js",
- );
- const childProcess = spawn(Deno.execPath(), [
- "run",
- "-A",
- "--unstable",
- script,
- ]);
- const deferred = Promise.withResolvers<void>();
- childProcess.on("exit", () => deferred.resolve());
- await deferred.promise;
- },
-});
-
-Deno.test({
- ignore: true,
- name: "[node/child_process] child_process.fork",
- async fn() {
- const testdataDir = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "testdata",
- );
- const script = path.join(
- testdataDir,
- "node_modules",
- "foo",
- "index.js",
- );
- const p = Promise.withResolvers<void>();
- const cp = CP.fork(script, [], { cwd: testdataDir, stdio: "pipe" });
- let output = "";
- cp.on("close", () => p.resolve());
- cp.stdout?.on("data", (data) => {
- output += data;
- });
- await p.promise;
- assertEquals(output, "foo\ntrue\ntrue\ntrue\n");
- },
-});
-
-Deno.test("[node/child_process execFileSync] 'inherit' stdout and stderr", () => {
- execFileSync(Deno.execPath(), ["--help"], { stdio: "inherit" });
-});
-
-Deno.test(
- "[node/child_process spawn] supports windowsVerbatimArguments option",
- { ignore: Deno.build.os !== "windows" },
- async () => {
- const cmdFinished = Promise.withResolvers<void>();
- let output = "";
- const cp = spawn("cmd", ["/d", "/s", "/c", '"deno ^"--version^""'], {
- stdio: "pipe",
- windowsVerbatimArguments: true,
- });
- cp.on("close", () => cmdFinished.resolve());
- cp.stdout?.on("data", (data) => {
- output += data;
- });
- await cmdFinished.promise;
- assertStringIncludes(output, "deno");
- assertStringIncludes(output, "v8");
- assertStringIncludes(output, "typescript");
- },
-);
-
-Deno.test(
- "[node/child_process spawn] supports stdio array option",
- async () => {
- const cmdFinished = Promise.withResolvers<void>();
- let output = "";
- const script = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "testdata",
- "child_process_stdio.js",
- );
- const cp = spawn(Deno.execPath(), ["run", "-A", script]);
- cp.stdout?.on("data", (data) => {
- output += data;
- });
- cp.on("close", () => cmdFinished.resolve());
- await cmdFinished.promise;
-
- assertStringIncludes(output, "foo");
- assertStringIncludes(output, "close");
- },
-);
-
-Deno.test(
- "[node/child_process spawn] supports stdio [0, 1, 2] option",
- async () => {
- const cmdFinished = Promise.withResolvers<void>();
- let output = "";
- const script = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "testdata",
- "child_process_stdio_012.js",
- );
- const cp = spawn(Deno.execPath(), ["run", "-A", script]);
- cp.stdout?.on("data", (data) => {
- output += data;
- });
- cp.on("close", () => cmdFinished.resolve());
- await cmdFinished.promise;
-
- assertStringIncludes(output, "foo");
- assertStringIncludes(output, "close");
- },
-);
-
-Deno.test({
- name: "[node/child_process spawn] supports SIGIOT signal",
- ignore: Deno.build.os === "windows",
- async fn() {
- // Note: attempting to kill Deno with SIGABRT causes the process to zombify on certain OSX builds
- // eg: 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:53:19 PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6020 arm64
- // M2 Pro running Ventura 13.4
-
- // Spawn an infinite cat
- const cp = spawn("cat", ["-"]);
- const p = withTimeout<void>();
- const pStdout = withTimeout<void>();
- const pStderr = withTimeout<void>();
- cp.on("exit", () => p.resolve());
- cp.stdout.on("close", () => pStdout.resolve());
- cp.stderr.on("close", () => pStderr.resolve());
- cp.kill("SIGIOT");
- await p.promise;
- await pStdout.promise;
- await pStderr.promise;
- assert(cp.killed);
- assertEquals(cp.signalCode, "SIGIOT");
- },
-});
-
-// Regression test for https://github.com/denoland/deno/issues/20373
-Deno.test(async function undefinedValueInEnvVar() {
- const deferred = withTimeout<string>();
- const env = spawn(
- `"${Deno.execPath()}" eval -p "Deno.env.toObject().BAZ"`,
- {
- env: {
- BAZ: "BAZ",
- NO_COLOR: "true",
- UNDEFINED_ENV: undefined,
- // deno-lint-ignore no-explicit-any
- NULL_ENV: null as any,
- },
- shell: true,
- },
- );
- try {
- let envOutput = "";
-
- assert(env.stdout);
- env.on("error", (err: Error) => deferred.reject(err));
- env.stdout.on("data", (data) => {
- envOutput += data;
- });
- env.on("close", () => {
- deferred.resolve(envOutput.trim());
- });
- await deferred.promise;
- } finally {
- env.kill();
- }
- const value = await deferred.promise;
- assertEquals(value, "BAZ");
-});
-
-// Regression test for https://github.com/denoland/deno/issues/20373
-Deno.test(function spawnSyncUndefinedValueInEnvVar() {
- const ret = spawnSync(
- `"${Deno.execPath()}" eval -p "Deno.env.toObject().BAZ"`,
- {
- env: {
- BAZ: "BAZ",
- NO_COLOR: "true",
- UNDEFINED_ENV: undefined,
- // deno-lint-ignore no-explicit-any
- NULL_ENV: null as any,
- },
- shell: true,
- },
- );
-
- assertEquals(ret.status, 0);
- assertEquals(ret.stdout.toString("utf-8").trim(), "BAZ");
-});
-
-Deno.test(function spawnSyncStdioUndefined() {
- const ret = spawnSync(
- `"${Deno.execPath()}" eval "console.log('hello');console.error('world')"`,
- {
- stdio: [undefined, undefined, undefined],
- shell: true,
- },
- );
-
- assertEquals(ret.status, 0);
- assertEquals(ret.stdout.toString("utf-8").trim(), "hello");
- assertEquals(ret.stderr.toString("utf-8").trim(), "world");
-});
-
-Deno.test(function spawnSyncExitNonZero() {
- const ret = spawnSync(
- `"${Deno.execPath()}" eval "Deno.exit(22)"`,
- { shell: true },
- );
-
- assertEquals(ret.status, 22);
-});
-
-// https://github.com/denoland/deno/issues/21630
-Deno.test(async function forkIpcKillDoesNotHang() {
- const testdataDir = path.join(
- path.dirname(path.fromFileUrl(import.meta.url)),
- "testdata",
- );
- const script = path.join(
- testdataDir,
- "node_modules",
- "foo",
- "index.js",
- );
- const p = Promise.withResolvers<void>();
- const cp = CP.fork(script, [], {
- cwd: testdataDir,
- stdio: ["inherit", "inherit", "inherit", "ipc"],
- });
- cp.on("close", () => p.resolve());
- cp.kill();
-
- await p.promise;
-});
-
-Deno.test(async function execFileWithUndefinedTimeout() {
- const { promise, resolve, reject } = Promise.withResolvers<void>();
- CP.execFile(
- "git",
- ["-v"],
- { timeout: undefined, encoding: "utf8" },
- (err) => {
- if (err) {
- reject(err);
- return;
- }
- resolve();
- },
- );
- await promise;
-});