summaryrefslogtreecommitdiff
path: root/tests/unit_node/child_process_test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unit_node/child_process_test.ts')
-rw-r--r--tests/unit_node/child_process_test.ts773
1 files changed, 773 insertions, 0 deletions
diff --git a/tests/unit_node/child_process_test.ts b/tests/unit_node/child_process_test.ts
new file mode 100644
index 000000000..5314d66e7
--- /dev/null
+++ b/tests/unit_node/child_process_test.ts
@@ -0,0 +1,773 @@
+// 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;
+});