From b67f874b3fb172168997be410b1d5e3c3109c763 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Wed, 11 May 2022 07:59:39 +0200 Subject: feat(runtime/spawn): add `AbortSignal` support (#14538) --- cli/tests/unit/command_test.ts | 669 --------------------------------------- cli/tests/unit/spawn_test.ts | 695 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 695 insertions(+), 669 deletions(-) delete mode 100644 cli/tests/unit/command_test.ts create mode 100644 cli/tests/unit/spawn_test.ts (limited to 'cli/tests/unit') diff --git a/cli/tests/unit/command_test.ts b/cli/tests/unit/command_test.ts deleted file mode 100644 index 4b3d5bd11..000000000 --- a/cli/tests/unit/command_test.ts +++ /dev/null @@ -1,669 +0,0 @@ -// Copyright 2018-2021 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 spawnWithCwdIsAsync() { - 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 child = Deno.spawnChild(Deno.execPath(), { - cwd, - args: ["run", "--allow-read", programFile], - stdout: "inherit", - stderr: "inherit", - }); - - // 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 spawnStdinPiped() { - const child = Deno.spawnChild(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", - }); - - assert(child.stdin !== null); - assert(child.stdout === null); - assert(child.stderr === null); - - 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 spawnStdoutPiped() { - const child = Deno.spawnChild(Deno.execPath(), { - args: [ - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - stderr: "null", - }); - - assert(child.stdin === null); - assert(child.stdout !== null); - assert(child.stderr === null); - - 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 spawnStderrPiped() { - const child = Deno.spawnChild(Deno.execPath(), { - args: [ - "eval", - "await Deno.stderr.write(new TextEncoder().encode('hello'))", - ], - stderr: "piped", - stdout: "null", - }); - - assert(child.stdin === null); - assert(child.stdout === null); - assert(child.stderr !== null); - - 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 spawnRedirectStdoutStderr() { - const tempDir = await Deno.makeTempDir(); - const fileName = tempDir + "/redirected_stdio.txt"; - const file = await Deno.open(fileName, { - create: true, - write: true, - }); - - const child = Deno.spawnChild(Deno.execPath(), { - args: [ - "eval", - "Deno.stderr.write(new TextEncoder().encode('error\\n')); Deno.stdout.write(new TextEncoder().encode('output\\n'));", - ], - }); - 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 spawnRedirectStdin() { - const tempDir = await Deno.makeTempDir(); - const fileName = tempDir + "/redirected_stdio.txt"; - const encoder = new TextEncoder(); - await Deno.writeFile(fileName, encoder.encode("hello")); - const file = await Deno.open(fileName); - - const child = Deno.spawnChild(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", - }); - 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 spawnKillSuccess() { - const child = Deno.spawnChild(Deno.execPath(), { - args: ["eval", "setTimeout(() => {}, 10000)"], - stdout: "null", - stderr: "null", - }); - - 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, 9); - } - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnKillFailed() { - const child = Deno.spawnChild(Deno.execPath(), { - args: ["eval", "setTimeout(() => {}, 5000)"], - stdout: "null", - stderr: "null", - }); - - assertThrows(() => { - // @ts-expect-error testing runtime error of bad signal - child.kill("foobar"); - }, TypeError); - - await child.status; - }, -); - -Deno.test( - { permissions: { read: true, run: false } }, - async function spawnPermissions() { - await assertRejects(async () => { - await Deno.spawn(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { read: true, run: false } }, - function spawnSyncPermissions() { - assertThrows(() => { - Deno.spawnSync(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }); - }, Deno.errors.PermissionDenied); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnSuccess() { - const { status } = await Deno.spawn(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }); - - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncSuccess() { - const { status } = Deno.spawnSync(Deno.execPath(), { - args: ["eval", "console.log('hello world')"], - }); - - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnUrl() { - const { status, stdout } = await Deno.spawn( - new URL(`file:///${Deno.execPath()}`), - { - args: ["eval", "console.log('hello world')"], - }, - ); - - assertEquals(new TextDecoder().decode(stdout), "hello world\n"); - - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncUrl() { - const { status, stdout } = Deno.spawnSync( - new URL(`file:///${Deno.execPath()}`), - { - args: ["eval", "console.log('hello world')"], - }, - ); - - assertEquals(new TextDecoder().decode(stdout), "hello world\n"); - - assertEquals(status.success, true); - assertEquals(status.code, 0); - assertEquals(status.signal, null); - }, -); - -Deno.test({ permissions: { run: true } }, async function spawnNotFound() { - await assertRejects( - () => Deno.spawn("this file hopefully doesn't exist"), - Deno.errors.NotFound, - ); -}); - -Deno.test({ permissions: { run: true } }, function spawnSyncNotFound() { - assertThrows( - () => Deno.spawnSync("this file hopefully doesn't exist"), - Deno.errors.NotFound, - ); -}); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnFailedWithCode() { - const { status } = await Deno.spawn(Deno.execPath(), { - args: ["eval", "Deno.exit(41 + 1)"], - }); - assertEquals(status.success, false); - assertEquals(status.code, 42); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncFailedWithCode() { - const { status } = Deno.spawnSync(Deno.execPath(), { - args: ["eval", "Deno.exit(41 + 1)"], - }); - assertEquals(status.success, false); - assertEquals(status.code, 42); - assertEquals(status.signal, null); - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - }, - async function spawnFailedWithSignal() { - const { status } = await Deno.spawn(Deno.execPath(), { - args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"], - }); - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, null); - } else { - assertEquals(status.code, 128 + 9); - assertEquals(status.signal, 9); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - }, - function spawnSyncFailedWithSignal() { - const { status } = Deno.spawnSync(Deno.execPath(), { - args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"], - }); - assertEquals(status.success, false); - if (Deno.build.os === "windows") { - assertEquals(status.code, 1); - assertEquals(status.signal, null); - } else { - assertEquals(status.code, 128 + 9); - assertEquals(status.signal, 9); - } - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnOutput() { - const { stdout } = await Deno.spawn(Deno.execPath(), { - args: [ - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - }); - - const s = new TextDecoder().decode(stdout); - assertEquals(s, "hello"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncOutput() { - const { stdout } = Deno.spawnSync(Deno.execPath(), { - args: [ - "eval", - "await Deno.stdout.write(new TextEncoder().encode('hello'))", - ], - }); - - const s = new TextDecoder().decode(stdout); - assertEquals(s, "hello"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnStderrOutput() { - const { stderr } = await Deno.spawn(Deno.execPath(), { - args: [ - "eval", - "await Deno.stderr.write(new TextEncoder().encode('error'))", - ], - }); - - const s = new TextDecoder().decode(stderr); - assertEquals(s, "error"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnSyncStderrOutput() { - const { stderr } = Deno.spawnSync(Deno.execPath(), { - args: [ - "eval", - "await Deno.stderr.write(new TextEncoder().encode('error'))", - ], - }); - - const s = new TextDecoder().decode(stderr); - assertEquals(s, "error"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - async function spawnEnv() { - const { stdout } = await Deno.spawn(Deno.execPath(), { - args: [ - "eval", - "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", - ], - env: { - FOO: "0123", - BAR: "4567", - }, - }); - const s = new TextDecoder().decode(stdout); - assertEquals(s, "01234567"); - }, -); - -Deno.test( - { permissions: { run: true, read: true } }, - function spawnEnv() { - const { stdout } = Deno.spawnSync(Deno.execPath(), { - args: [ - "eval", - "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", - ], - env: { - FOO: "0123", - BAR: "4567", - }, - }); - const s = new TextDecoder().decode(stdout); - assertEquals(s, "01234567"); - }, -); - -Deno.test( - { permissions: { run: true, read: true, env: true } }, - async function spawnClearEnv() { - const { stdout } = await Deno.spawn(Deno.execPath(), { - args: [ - "eval", - "-p", - "JSON.stringify(Deno.env.toObject())", - ], - clearEnv: true, - env: { - FOO: "23147", - }, - }); - - 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 spawnSyncClearEnv() { - const { stdout } = Deno.spawnSync(Deno.execPath(), { - args: [ - "eval", - "-p", - "JSON.stringify(Deno.env.toObject())", - ], - clearEnv: true, - env: { - FOO: "23147", - }, - }); - - 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 spawnUid() { - const { stdout } = await Deno.spawn("id", { - args: ["-u"], - }); - - const currentUid = new TextDecoder().decode(stdout); - - if (currentUid !== "0") { - await assertRejects(async () => { - await Deno.spawn("echo", { - args: ["fhqwhgads"], - uid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - function spawnSyncUid() { - const { stdout } = Deno.spawnSync("id", { - args: ["-u"], - }); - - const currentUid = new TextDecoder().decode(stdout); - - if (currentUid !== "0") { - assertThrows(() => { - Deno.spawnSync("echo", { - args: ["fhqwhgads"], - uid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - async function spawnGid() { - const { stdout } = await Deno.spawn("id", { - args: ["-g"], - }); - - const currentGid = new TextDecoder().decode(stdout); - - if (currentGid !== "0") { - await assertRejects(async () => { - await Deno.spawn("echo", { - args: ["fhqwhgads"], - gid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test( - { - permissions: { run: true, read: true }, - ignore: Deno.build.os === "windows", - }, - function spawnSyncGid() { - const { stdout } = Deno.spawnSync("id", { - args: ["-g"], - }); - - const currentGid = new TextDecoder().decode(stdout); - - if (currentGid !== "0") { - assertThrows(() => { - Deno.spawnSync("echo", { - args: ["fhqwhgads"], - gid: 0, - }); - }, Deno.errors.PermissionDenied); - } - }, -); - -Deno.test(async function spawnStdinPipedFails() { - await assertRejects( - () => - Deno.spawn("id", { - stdin: "piped", - }), - TypeError, - "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", - ); -}); - -Deno.test(function spawnSyncStdinPipedFails() { - assertThrows( - () => - Deno.spawnSync("id", { - stdin: "piped", - }), - TypeError, - "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", - ); -}); diff --git a/cli/tests/unit/spawn_test.ts b/cli/tests/unit/spawn_test.ts new file mode 100644 index 000000000..c2c7fdbac --- /dev/null +++ b/cli/tests/unit/spawn_test.ts @@ -0,0 +1,695 @@ +// Copyright 2018-2021 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 spawnWithCwdIsAsync() { + 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 child = Deno.spawnChild(Deno.execPath(), { + cwd, + args: ["run", "--allow-read", programFile], + stdout: "inherit", + stderr: "inherit", + }); + + // 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 spawnStdinPiped() { + const child = Deno.spawnChild(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", + }); + + assert(child.stdin !== null); + assert(child.stdout === null); + assert(child.stderr === null); + + 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 spawnStdoutPiped() { + const child = Deno.spawnChild(Deno.execPath(), { + args: [ + "eval", + "await Deno.stdout.write(new TextEncoder().encode('hello'))", + ], + stderr: "null", + }); + + assert(child.stdin === null); + assert(child.stdout !== null); + assert(child.stderr === null); + + 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 spawnStderrPiped() { + const child = Deno.spawnChild(Deno.execPath(), { + args: [ + "eval", + "await Deno.stderr.write(new TextEncoder().encode('hello'))", + ], + stderr: "piped", + stdout: "null", + }); + + assert(child.stdin === null); + assert(child.stdout === null); + assert(child.stderr !== null); + + 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 spawnRedirectStdoutStderr() { + const tempDir = await Deno.makeTempDir(); + const fileName = tempDir + "/redirected_stdio.txt"; + const file = await Deno.open(fileName, { + create: true, + write: true, + }); + + const child = Deno.spawnChild(Deno.execPath(), { + args: [ + "eval", + "Deno.stderr.write(new TextEncoder().encode('error\\n')); Deno.stdout.write(new TextEncoder().encode('output\\n'));", + ], + }); + 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 spawnRedirectStdin() { + const tempDir = await Deno.makeTempDir(); + const fileName = tempDir + "/redirected_stdio.txt"; + const encoder = new TextEncoder(); + await Deno.writeFile(fileName, encoder.encode("hello")); + const file = await Deno.open(fileName); + + const child = Deno.spawnChild(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", + }); + 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 spawnKillSuccess() { + const child = Deno.spawnChild(Deno.execPath(), { + args: ["eval", "setTimeout(() => {}, 10000)"], + stdout: "null", + stderr: "null", + }); + + 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, 9); + } + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function spawnKillFailed() { + const child = Deno.spawnChild(Deno.execPath(), { + args: ["eval", "setTimeout(() => {}, 5000)"], + stdout: "null", + stderr: "null", + }); + + 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 spawnAbort() { + const ac = new AbortController(); + const child = Deno.spawnChild(Deno.execPath(), { + args: [ + "eval", + "setTimeout(console.log, 1e8)", + ], + signal: ac.signal, + stdout: "null", + stderr: "null", + }); + 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 spawnPermissions() { + await assertRejects(async () => { + await Deno.spawn(Deno.execPath(), { + args: ["eval", "console.log('hello world')"], + }); + }, Deno.errors.PermissionDenied); + }, +); + +Deno.test( + { permissions: { read: true, run: false } }, + function spawnSyncPermissions() { + assertThrows(() => { + Deno.spawnSync(Deno.execPath(), { + args: ["eval", "console.log('hello world')"], + }); + }, Deno.errors.PermissionDenied); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function spawnSuccess() { + const { status } = await Deno.spawn(Deno.execPath(), { + args: ["eval", "console.log('hello world')"], + }); + + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function spawnSyncSuccess() { + const { status } = Deno.spawnSync(Deno.execPath(), { + args: ["eval", "console.log('hello world')"], + }); + + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function spawnUrl() { + const { status, stdout } = await Deno.spawn( + new URL(`file:///${Deno.execPath()}`), + { + args: ["eval", "console.log('hello world')"], + }, + ); + + assertEquals(new TextDecoder().decode(stdout), "hello world\n"); + + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function spawnSyncUrl() { + const { status, stdout } = Deno.spawnSync( + new URL(`file:///${Deno.execPath()}`), + { + args: ["eval", "console.log('hello world')"], + }, + ); + + assertEquals(new TextDecoder().decode(stdout), "hello world\n"); + + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, null); + }, +); + +Deno.test({ permissions: { run: true } }, async function spawnNotFound() { + await assertRejects( + () => Deno.spawn("this file hopefully doesn't exist"), + Deno.errors.NotFound, + ); +}); + +Deno.test({ permissions: { run: true } }, function spawnSyncNotFound() { + assertThrows( + () => Deno.spawnSync("this file hopefully doesn't exist"), + Deno.errors.NotFound, + ); +}); + +Deno.test( + { permissions: { run: true, read: true } }, + async function spawnFailedWithCode() { + const { status } = await Deno.spawn(Deno.execPath(), { + args: ["eval", "Deno.exit(41 + 1)"], + }); + assertEquals(status.success, false); + assertEquals(status.code, 42); + assertEquals(status.signal, null); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function spawnSyncFailedWithCode() { + const { status } = Deno.spawnSync(Deno.execPath(), { + args: ["eval", "Deno.exit(41 + 1)"], + }); + assertEquals(status.success, false); + assertEquals(status.code, 42); + assertEquals(status.signal, null); + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + }, + async function spawnFailedWithSignal() { + const { status } = await Deno.spawn(Deno.execPath(), { + args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"], + }); + assertEquals(status.success, false); + if (Deno.build.os === "windows") { + assertEquals(status.code, 1); + assertEquals(status.signal, null); + } else { + assertEquals(status.code, 128 + 9); + assertEquals(status.signal, 9); + } + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + }, + function spawnSyncFailedWithSignal() { + const { status } = Deno.spawnSync(Deno.execPath(), { + args: ["eval", "--unstable", "Deno.kill(Deno.pid, 'SIGKILL')"], + }); + assertEquals(status.success, false); + if (Deno.build.os === "windows") { + assertEquals(status.code, 1); + assertEquals(status.signal, null); + } else { + assertEquals(status.code, 128 + 9); + assertEquals(status.signal, 9); + } + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function spawnOutput() { + const { stdout } = await Deno.spawn(Deno.execPath(), { + args: [ + "eval", + "await Deno.stdout.write(new TextEncoder().encode('hello'))", + ], + }); + + const s = new TextDecoder().decode(stdout); + assertEquals(s, "hello"); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function spawnSyncOutput() { + const { stdout } = Deno.spawnSync(Deno.execPath(), { + args: [ + "eval", + "await Deno.stdout.write(new TextEncoder().encode('hello'))", + ], + }); + + const s = new TextDecoder().decode(stdout); + assertEquals(s, "hello"); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function spawnStderrOutput() { + const { stderr } = await Deno.spawn(Deno.execPath(), { + args: [ + "eval", + "await Deno.stderr.write(new TextEncoder().encode('error'))", + ], + }); + + const s = new TextDecoder().decode(stderr); + assertEquals(s, "error"); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function spawnSyncStderrOutput() { + const { stderr } = Deno.spawnSync(Deno.execPath(), { + args: [ + "eval", + "await Deno.stderr.write(new TextEncoder().encode('error'))", + ], + }); + + const s = new TextDecoder().decode(stderr); + assertEquals(s, "error"); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + async function spawnEnv() { + const { stdout } = await Deno.spawn(Deno.execPath(), { + args: [ + "eval", + "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", + ], + env: { + FOO: "0123", + BAR: "4567", + }, + }); + const s = new TextDecoder().decode(stdout); + assertEquals(s, "01234567"); + }, +); + +Deno.test( + { permissions: { run: true, read: true } }, + function spawnEnv() { + const { stdout } = Deno.spawnSync(Deno.execPath(), { + args: [ + "eval", + "Deno.stdout.write(new TextEncoder().encode(Deno.env.get('FOO') + Deno.env.get('BAR')))", + ], + env: { + FOO: "0123", + BAR: "4567", + }, + }); + const s = new TextDecoder().decode(stdout); + assertEquals(s, "01234567"); + }, +); + +Deno.test( + { permissions: { run: true, read: true, env: true } }, + async function spawnClearEnv() { + const { stdout } = await Deno.spawn(Deno.execPath(), { + args: [ + "eval", + "-p", + "JSON.stringify(Deno.env.toObject())", + ], + clearEnv: true, + env: { + FOO: "23147", + }, + }); + + 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 spawnSyncClearEnv() { + const { stdout } = Deno.spawnSync(Deno.execPath(), { + args: [ + "eval", + "-p", + "JSON.stringify(Deno.env.toObject())", + ], + clearEnv: true, + env: { + FOO: "23147", + }, + }); + + 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 spawnUid() { + const { stdout } = await Deno.spawn("id", { + args: ["-u"], + }); + + const currentUid = new TextDecoder().decode(stdout); + + if (currentUid !== "0") { + await assertRejects(async () => { + await Deno.spawn("echo", { + args: ["fhqwhgads"], + uid: 0, + }); + }, Deno.errors.PermissionDenied); + } + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + ignore: Deno.build.os === "windows", + }, + function spawnSyncUid() { + const { stdout } = Deno.spawnSync("id", { + args: ["-u"], + }); + + const currentUid = new TextDecoder().decode(stdout); + + if (currentUid !== "0") { + assertThrows(() => { + Deno.spawnSync("echo", { + args: ["fhqwhgads"], + uid: 0, + }); + }, Deno.errors.PermissionDenied); + } + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + ignore: Deno.build.os === "windows", + }, + async function spawnGid() { + const { stdout } = await Deno.spawn("id", { + args: ["-g"], + }); + + const currentGid = new TextDecoder().decode(stdout); + + if (currentGid !== "0") { + await assertRejects(async () => { + await Deno.spawn("echo", { + args: ["fhqwhgads"], + gid: 0, + }); + }, Deno.errors.PermissionDenied); + } + }, +); + +Deno.test( + { + permissions: { run: true, read: true }, + ignore: Deno.build.os === "windows", + }, + function spawnSyncGid() { + const { stdout } = Deno.spawnSync("id", { + args: ["-g"], + }); + + const currentGid = new TextDecoder().decode(stdout); + + if (currentGid !== "0") { + assertThrows(() => { + Deno.spawnSync("echo", { + args: ["fhqwhgads"], + gid: 0, + }); + }, Deno.errors.PermissionDenied); + } + }, +); + +Deno.test(async function spawnStdinPipedFails() { + await assertRejects( + () => + Deno.spawn("id", { + stdin: "piped", + }), + TypeError, + "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", + ); +}); + +Deno.test(function spawnSyncStdinPipedFails() { + assertThrows( + () => + Deno.spawnSync("id", { + stdin: "piped", + }), + TypeError, + "Piped stdin is not supported for this function, use 'Deno.spawnChild()' instead", + ); +}); -- cgit v1.2.3