diff options
Diffstat (limited to 'cli/tests/unit_node/child_process_test.ts')
-rw-r--r-- | cli/tests/unit_node/child_process_test.ts | 773 |
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; -}); |