diff options
author | Matt Mastracci <matthew@mastracci.com> | 2024-02-10 13:22:13 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-10 20:22:13 +0000 |
commit | f5e46c9bf2f50d66a953fa133161fc829cecff06 (patch) | |
tree | 8faf2f5831c1c7b11d842cd9908d141082c869a5 /tests/unit/command_test.ts | |
parent | d2477f780630a812bfd65e3987b70c0d309385bb (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 'tests/unit/command_test.ts')
-rw-r--r-- | tests/unit/command_test.ts | 967 |
1 files changed, 967 insertions, 0 deletions
diff --git a/tests/unit/command_test.ts b/tests/unit/command_test.ts new file mode 100644 index 000000000..cbb1c4921 --- /dev/null +++ b/tests/unit/command_test.ts @@ -0,0 +1,967 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { + assert, + assertEquals, + assertRejects, + assertStringIncludes, + assertThrows, +} from "./test_util.ts"; + +Deno.test( + { permissions: { write: true, run: true, read: true } }, + async function commandWithCwdIsAsync() { + const enc = new TextEncoder(); + const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); + + const exitCodeFile = "deno_was_here"; + const programFile = "poll_exit.ts"; + const program = ` +async function tryExit() { + try { + const code = parseInt(await Deno.readTextFile("${exitCodeFile}")); + Deno.exit(code); + } catch { + // Retry if we got here before deno wrote the file. + setTimeout(tryExit, 0.01); + } +} + +tryExit(); +`; + + Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); + + const command = new Deno.Command(Deno.execPath(), { + cwd, + args: ["run", "--allow-read", programFile], + stdout: "inherit", + stderr: "inherit", + }); + const child = command.spawn(); + + // Write the expected exit code *after* starting deno. + // This is how we verify that `Child` is actually asynchronous. + const code = 84; + Deno.writeFileSync(`${cwd}/${exitCodeFile}`, enc.encode(`${code}`)); + + const status = await child.status; + await Deno.remove(cwd, { recursive: true }); + assertEquals(status.success, false); + assertEquals(status.code, code); + assertEquals(status.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandStdinPiped() { + const command = new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "if (new TextDecoder().decode(await Deno.readAll(Deno.stdin)) !== 'hello') throw new Error('Expected \\'hello\\'')", + ], + stdin: "piped", + stdout: "null", + stderr: "null", + }); + const child = command.spawn(); + + assertThrows(() => child.stdout, TypeError, "stdout is not piped"); + assertThrows(() => child.stderr, TypeError, "stderr is not piped"); + + const msg = new TextEncoder().encode("hello"); + const writer = child.stdin.getWriter(); + await writer.write(msg); + writer.releaseLock(); + + await child.stdin.close(); + const status = await child.status; + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandStdinPiped() { + const command = new Deno.Command(Deno.execPath(), { + args: ["info"], + stdout: "null", + stderr: "null", + }); + const child = command.spawn(); + + assertThrows(() => child.stdin, TypeError, "stdin is not piped"); + assertThrows(() => child.stdout, TypeError, "stdout is not piped"); + assertThrows(() => child.stderr, TypeError, "stderr is not piped"); + + await child.status; + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandStdoutPiped() { + const command = new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "await Deno.stdout.write(new TextEncoder().encode('hello'))", + ], + stderr: "null", + stdout: "piped", + }); + const child = command.spawn(); + + assertThrows(() => child.stdin, TypeError, "stdin is not piped"); + assertThrows(() => child.stderr, TypeError, "stderr is not piped"); + + const readable = child.stdout.pipeThrough(new TextDecoderStream()); + const reader = readable.getReader(); + const res = await reader.read(); + assert(!res.done); + assertEquals(res.value, "hello"); + + const resEnd = await reader.read(); + assert(resEnd.done); + assertEquals(resEnd.value, undefined); + reader.releaseLock(); + + const status = await child.status; + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandStderrPiped() { + const command = new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "await Deno.stderr.write(new TextEncoder().encode('hello'))", + ], + stdout: "null", + stderr: "piped", + }); + const child = command.spawn(); + + assertThrows(() => child.stdin, TypeError, "stdin is not piped"); + assertThrows(() => child.stdout, TypeError, "stdout is not piped"); + + const readable = child.stderr.pipeThrough(new TextDecoderStream()); + const reader = readable.getReader(); + const res = await reader.read(); + assert(!res.done); + assertEquals(res.value, "hello"); + + const resEnd = await reader.read(); + assert(resEnd.done); + assertEquals(resEnd.value, undefined); + reader.releaseLock(); + + const status = await child.status; + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, write: true, read: true } }, + async function commandRedirectStdoutStderr() { + const tempDir = await Deno.makeTempDir(); + const fileName = tempDir + "/redirected_stdio.txt"; + const file = await Deno.open(fileName, { + create: true, + write: true, + }); + + const command = new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "Deno.stderr.write(new TextEncoder().encode('error\\n')); Deno.stdout.write(new TextEncoder().encode('output\\n'));", + ], + stdout: "piped", + stderr: "piped", + }); + const child = command.spawn(); + await child.stdout.pipeTo(file.writable, { + preventClose: true, + }); + await child.stderr.pipeTo(file.writable); + await child.status; + + const fileContents = await Deno.readFile(fileName); + const decoder = new TextDecoder(); + const text = decoder.decode(fileContents); + + assertStringIncludes(text, "error"); + assertStringIncludes(text, "output"); + }, +); + +Deno.test( + { permissions: { run: true, write: true, read: true } }, + async function commandRedirectStdin() { + const tempDir = await Deno.makeTempDir(); + const fileName = tempDir + "/redirected_stdio.txt"; + await Deno.writeTextFile(fileName, "hello"); + const file = await Deno.open(fileName); + + const command = new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "if (new TextDecoder().decode(await Deno.readAll(Deno.stdin)) !== 'hello') throw new Error('Expected \\'hello\\'')", + ], + stdin: "piped", + stdout: "null", + stderr: "null", + }); + const child = command.spawn(); + await file.readable.pipeTo(child.stdin, { + preventClose: true, + }); + + await child.stdin.close(); + const status = await child.status; + assertEquals(status.code, 0); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandKillSuccess() { + const command = new Deno.Command(Deno.execPath(), { + args: ["eval", "setTimeout(() => {}, 10000)"], + stdout: "null", + stderr: "null", + }); + const child = command.spawn(); + + child.kill("SIGKILL"); + const status = await child.status; + + assertEquals(status.success, false); + if (Deno.build.os === "windows") { + assertEquals(status.code, 1); + assertEquals(status.signal, null); + } else { + assertEquals(status.code, 137); + assertEquals(status.signal, "SIGKILL"); + } + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + // deno lint bug, see https://github.com/denoland/deno_lint/issues/1206 + // deno-lint-ignore require-await + async function childProcessExplicitResourceManagement() { + let dead = undefined; + { + const command = new Deno.Command(Deno.execPath(), { + args: ["eval", "setTimeout(() => {}, 10000)"], + stdout: "null", + stderr: "null", + }); + await using child = command.spawn(); + child.status.then(({ signal }) => { + dead = signal; + }); + } + + if (Deno.build.os == "windows") { + assertEquals(dead, null); + } else { + assertEquals(dead, "SIGTERM"); + } + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function childProcessExplicitResourceManagementManualClose() { + const command = new Deno.Command(Deno.execPath(), { + args: ["eval", "setTimeout(() => {}, 10000)"], + stdout: "null", + stderr: "null", + }); + await using child = command.spawn(); + child.kill("SIGTERM"); + await child.status; + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandKillFailed() { + const command = new Deno.Command(Deno.execPath(), { + args: ["eval", "setTimeout(() => {}, 5000)"], + stdout: "null", + stderr: "null", + }); + const child = command.spawn(); + + assertThrows(() => { + // @ts-expect-error testing runtime error of bad signal + child.kill("foobar"); + }, TypeError); + + await child.status; + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandKillOptional() { + const command = new Deno.Command(Deno.execPath(), { + args: ["eval", "setTimeout(() => {}, 10000)"], + stdout: "null", + stderr: "null", + }); + const child = command.spawn(); + + child.kill(); + const status = await child.status; + + assertEquals(status.success, false); + if (Deno.build.os === "windows") { + assertEquals(status.code, 1); + assertEquals(status.signal, null); + } else { + assertEquals(status.code, 143); + assertEquals(status.signal, "SIGTERM"); + } + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandAbort() { + const ac = new AbortController(); + const command = new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "setTimeout(console.log, 1e8)", + ], + signal: ac.signal, + stdout: "null", + stderr: "null", + }); + const child = command.spawn(); + queueMicrotask(() => ac.abort()); + const status = await child.status; + assertEquals(status.success, false); + if (Deno.build.os === "windows") { + assertEquals(status.code, 1); + assertEquals(status.signal, null); + } else { + assertEquals(status.success, false); + assertEquals(status.code, 143); + } + }, +); + +Deno.test( + { permissions: { read: true, run: false } }, + async function commandPermissions() { + await assertRejects(async () => { + await new Deno.Command(Deno.execPath(), { + args: ["eval", "console.log('hello world')"], + }).output(); + }, Deno.errors.PermissionDenied); + }, +); + +Deno.test( + { permissions: { read: true, run: false } }, + function commandSyncPermissions() { + assertThrows(() => { + new Deno.Command(Deno.execPath(), { + args: ["eval", "console.log('hello world')"], + }).outputSync(); + }, Deno.errors.PermissionDenied); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandSuccess() { + const output = await new Deno.Command(Deno.execPath(), { + args: ["eval", "console.log('hello world')"], + }).output(); + + assertEquals(output.success, true); + assertEquals(output.code, 0); + assertEquals(output.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function commandSyncSuccess() { + const output = new Deno.Command(Deno.execPath(), { + args: ["eval", "console.log('hello world')"], + }).outputSync(); + + assertEquals(output.success, true); + assertEquals(output.code, 0); + assertEquals(output.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandUrl() { + const output = await new Deno.Command( + new URL(`file:///${Deno.execPath()}`), + { + args: ["eval", "console.log('hello world')"], + }, + ).output(); + + assertEquals(new TextDecoder().decode(output.stdout), "hello world\n"); + + assertEquals(output.success, true); + assertEquals(output.code, 0); + assertEquals(output.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function commandSyncUrl() { + const output = new Deno.Command( + new URL(`file:///${Deno.execPath()}`), + { + args: ["eval", "console.log('hello world')"], + }, + ).outputSync(); + + assertEquals(new TextDecoder().decode(output.stdout), "hello world\n"); + + assertEquals(output.success, true); + assertEquals(output.code, 0); + assertEquals(output.signal, null); + }, +); + +Deno.test({ permissions: { run: true } }, function commandNotFound() { + assertThrows( + () => new Deno.Command("this file hopefully doesn't exist").output(), + Deno.errors.NotFound, + ); +}); + +Deno.test({ permissions: { run: true } }, function commandSyncNotFound() { + assertThrows( + () => new Deno.Command("this file hopefully doesn't exist").outputSync(), + Deno.errors.NotFound, + ); +}); + +Deno.test({ permissions: { run: true, read: true } }, function cwdNotFound() { + assertThrows( + () => + new Deno.Command(Deno.execPath(), { + cwd: Deno.cwd() + "/non-existent-directory", + }).output(), + Deno.errors.NotFound, + "No such cwd", + ); +}); + +Deno.test( + { permissions: { run: true, read: true } }, + function cwdNotDirectory() { + assertThrows( + () => + new Deno.Command(Deno.execPath(), { + cwd: Deno.execPath(), + }).output(), + Deno.errors.NotFound, + "cwd is not a directory", + ); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandFailedWithCode() { + const output = await new Deno.Command(Deno.execPath(), { + args: ["eval", "Deno.exit(41 + 1)"], + }).output(); + assertEquals(output.success, false); + assertEquals(output.code, 42); + assertEquals(output.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function commandSyncFailedWithCode() { + const output = new Deno.Command(Deno.execPath(), { + args: ["eval", "Deno.exit(41 + 1)"], + }).outputSync(); + assertEquals(output.success, false); + assertEquals(output.code, 42); + assertEquals(output.signal, null); + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + }, + async function commandFailedWithSignal() { + const output = await new Deno.Command(Deno.execPath(), { + args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"], + }).output(); + assertEquals(output.success, false); + if (Deno.build.os === "windows") { + assertEquals(output.code, 1); + assertEquals(output.signal, null); + } else { + assertEquals(output.code, 128 + 9); + assertEquals(output.signal, "SIGKILL"); + } + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + }, + function commandSyncFailedWithSignal() { + const output = new Deno.Command(Deno.execPath(), { + args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"], + }).outputSync(); + assertEquals(output.success, false); + if (Deno.build.os === "windows") { + assertEquals(output.code, 1); + assertEquals(output.signal, null); + } else { + assertEquals(output.code, 128 + 9); + assertEquals(output.signal, "SIGKILL"); + } + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandOutput() { + const { stdout } = await new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "await Deno.stdout.write(new TextEncoder().encode('hello'))", + ], + }).output(); + + const s = new TextDecoder().decode(stdout); + assertEquals(s, "hello"); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function commandSyncOutput() { + const { stdout } = new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "await Deno.stdout.write(new TextEncoder().encode('hello'))", + ], + }).outputSync(); + + const s = new TextDecoder().decode(stdout); + assertEquals(s, "hello"); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandStderrOutput() { + const { stderr } = await new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "await Deno.stderr.write(new TextEncoder().encode('error'))", + ], + }).output(); + + const s = new TextDecoder().decode(stderr); + assertEquals(s, "error"); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function commandSyncStderrOutput() { + const { stderr } = new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "await Deno.stderr.write(new TextEncoder().encode('error'))", + ], + }).outputSync(); + + const s = new TextDecoder().decode(stderr); + assertEquals(s, "error"); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandEnv() { + const { stdout } = await new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", + ], + env: { + FOO: "0123", + BAR: "4567", + }, + }).output(); + const s = new TextDecoder().decode(stdout); + assertEquals(s, "01234567"); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function commandSyncEnv() { + const { stdout } = new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", + ], + env: { + FOO: "0123", + BAR: "4567", + }, + }).outputSync(); + const s = new TextDecoder().decode(stdout); + assertEquals(s, "01234567"); + }, +); + +Deno.test( + { permissions: { run: true, read: true, env: true } }, + async function commandClearEnv() { + const { stdout } = await new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "-p", + "JSON.stringify(Deno.env.toObject())", + ], + clearEnv: true, + env: { + FOO: "23147", + }, + }).output(); + + const obj = JSON.parse(new TextDecoder().decode(stdout)); + + // can't check for object equality because the OS may set additional env + // vars for processes, so we check if PATH isn't present as that is a common + // env var across OS's and isn't set for processes. + assertEquals(obj.FOO, "23147"); + assert(!("PATH" in obj)); + }, +); + +Deno.test( + { permissions: { run: true, read: true, env: true } }, + function commandSyncClearEnv() { + const { stdout } = new Deno.Command(Deno.execPath(), { + args: [ + "eval", + "-p", + "JSON.stringify(Deno.env.toObject())", + ], + clearEnv: true, + env: { + FOO: "23147", + }, + }).outputSync(); + + const obj = JSON.parse(new TextDecoder().decode(stdout)); + + // can't check for object equality because the OS may set additional env + // vars for processes, so we check if PATH isn't present as that is a common + // env var across OS's and isn't set for processes. + assertEquals(obj.FOO, "23147"); + assert(!("PATH" in obj)); + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + ignore: Deno.build.os === "windows", + }, + async function commandUid() { + const { stdout } = await new Deno.Command("id", { + args: ["-u"], + }).output(); + + const currentUid = new TextDecoder().decode(stdout); + + if (currentUid !== "0") { + await assertRejects(async () => { + await new Deno.Command("echo", { + args: ["fhqwhgads"], + uid: 0, + }).output(); + }, Deno.errors.PermissionDenied); + } + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + ignore: Deno.build.os === "windows", + }, + function commandSyncUid() { + const { stdout } = new Deno.Command("id", { + args: ["-u"], + }).outputSync(); + + const currentUid = new TextDecoder().decode(stdout); + + if (currentUid !== "0") { + assertThrows(() => { + new Deno.Command("echo", { + args: ["fhqwhgads"], + uid: 0, + }).outputSync(); + }, Deno.errors.PermissionDenied); + } + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + ignore: Deno.build.os === "windows", + }, + async function commandGid() { + const { stdout } = await new Deno.Command("id", { + args: ["-g"], + }).output(); + + const currentGid = new TextDecoder().decode(stdout); + + if (currentGid !== "0") { + await assertRejects(async () => { + await new Deno.Command("echo", { + args: ["fhqwhgads"], + gid: 0, + }).output(); + }, Deno.errors.PermissionDenied); + } + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + ignore: Deno.build.os === "windows", + }, + function commandSyncGid() { + const { stdout } = new Deno.Command("id", { + args: ["-g"], + }).outputSync(); + + const currentGid = new TextDecoder().decode(stdout); + + if (currentGid !== "0") { + assertThrows(() => { + new Deno.Command("echo", { + args: ["fhqwhgads"], + gid: 0, + }).outputSync(); + }, Deno.errors.PermissionDenied); + } + }, +); + +Deno.test(function commandStdinPipedFails() { + assertThrows( + () => + new Deno.Command("id", { + stdin: "piped", + }).output(), + TypeError, + "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", + ); +}); + +Deno.test(function spawnSyncStdinPipedFails() { + assertThrows( + () => + new Deno.Command("id", { + stdin: "piped", + }).outputSync(), + TypeError, + "Piped stdin is not supported for this function, use 'Deno.Command.spawn()' instead", + ); +}); + +Deno.test( + // FIXME(bartlomieju): this test is very flaky on CI, fix it + { + ignore: true, + permissions: { write: true, run: true, read: true }, + }, + async function commandChildUnref() { + const enc = new TextEncoder(); + const cwd = await Deno.makeTempDir({ prefix: "deno_command_test" }); + + const programFile = "unref.ts"; + const program = ` +const command = await new Deno.Command(Deno.execPath(), { + cwd: Deno.args[0], + stdout: "piped", + args: ["run", "-A", "--unstable", Deno.args[1]], +}); +const child = command.spawn(); +const readable = child.stdout.pipeThrough(new TextDecoderStream()); +const reader = readable.getReader(); +// set up an interval that will end after reading a few messages from stdout, +// to verify that stdio streams are properly unrefed +let count = 0; +let interval; +interval = setInterval(async () => { + count += 1; + if (count > 10) { + clearInterval(interval); + console.log("cleared interval"); + } + const res = await reader.read(); + if (res.done) { + throw new Error("stream shouldn't be done"); + } + if (res.value.trim() != "hello from interval") { + throw new Error("invalid message received"); + } +}, 120); +console.log("spawned pid", child.pid); +child.unref(); +`; + + const childProgramFile = "unref_child.ts"; + const childProgram = ` +setInterval(() => { + console.log("hello from interval"); +}, 100); +`; + Deno.writeFileSync(`${cwd}/${programFile}`, enc.encode(program)); + Deno.writeFileSync(`${cwd}/${childProgramFile}`, enc.encode(childProgram)); + // In this subprocess we are spawning another subprocess which has + // an infinite interval set. Following call would never resolve unless + // child process gets unrefed. + const { success, stdout, stderr } = await new Deno.Command( + Deno.execPath(), + { + cwd, + args: ["run", "-A", "--unstable", programFile, cwd, childProgramFile], + }, + ).output(); + + assert(success); + const stdoutText = new TextDecoder().decode(stdout); + const stderrText = new TextDecoder().decode(stderr); + assert(stderrText.length == 0); + const [line1, line2] = stdoutText.split("\n"); + const pidStr = line1.split(" ").at(-1); + assert(pidStr); + assertEquals(line2, "cleared interval"); + const pid = Number.parseInt(pidStr, 10); + await Deno.remove(cwd, { recursive: true }); + // Child process should have been killed when parent process exits. + assertThrows(() => { + Deno.kill(pid, "SIGTERM"); + }, Deno.errors.NotFound); + }, +); + +Deno.test( + { ignore: Deno.build.os !== "windows" }, + async function commandWindowsRawArguments() { + let { success, stdout } = await new Deno.Command("cmd", { + args: ["/d", "/s", "/c", '"deno ^"--version^""'], + windowsRawArguments: true, + }).output(); + assert(success); + let stdoutText = new TextDecoder().decode(stdout); + assertStringIncludes(stdoutText, "deno"); + assertStringIncludes(stdoutText, "v8"); + assertStringIncludes(stdoutText, "typescript"); + + ({ success, stdout } = new Deno.Command("cmd", { + args: ["/d", "/s", "/c", '"deno ^"--version^""'], + windowsRawArguments: true, + }).outputSync()); + assert(success); + stdoutText = new TextDecoder().decode(stdout); + assertStringIncludes(stdoutText, "deno"); + assertStringIncludes(stdoutText, "v8"); + assertStringIncludes(stdoutText, "typescript"); + }, +); + +Deno.test( + { permissions: { read: true, run: true } }, + async function commandWithPrototypePollution() { + const originalThen = Promise.prototype.then; + const originalSymbolIterator = Array.prototype[Symbol.iterator]; + try { + Promise.prototype.then = Array.prototype[Symbol.iterator] = () => { + throw new Error(); + }; + await new Deno.Command(Deno.execPath(), { + args: ["eval", "console.log('hello world')"], + }).output(); + } finally { + Promise.prototype.then = originalThen; + Array.prototype[Symbol.iterator] = originalSymbolIterator; + } + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function commandKillAfterStatus() { + const command = new Deno.Command(Deno.execPath(), { + args: ["help"], + stdout: "null", + stderr: "null", + }); + const child = command.spawn(); + await child.status; + assertThrows( + () => child.kill(), + TypeError, + "Child process has already terminated.", + ); + }, +); + +Deno.test( + "process that fails to spawn, prints its name in error", + async () => { + assertThrows( + () => new Deno.Command("doesntexist").outputSync(), + Error, + "Failed to spawn 'doesntexist'", + ); + await assertRejects( + async () => await new Deno.Command("doesntexist").output(), + Error, + "Failed to spawn 'doesntexist'", + ); + }, +); |