// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { assert, assertEquals, assertStrContains, unitTest } from "./test_util.ts"; const { kill, run, readFile, open, makeTempDir, writeFile } = Deno; unitTest(function runPermissions(): void { let caughtError = false; try { Deno.run({ args: ["python", "-c", "print('hello world')"] }); } catch (e) { caughtError = true; assert(e instanceof Deno.errors.PermissionDenied); } assert(caughtError); }); unitTest({ perms: { run: true } }, async function runSuccess(): Promise { const p = run({ args: ["python", "-c", "print('hello world')"], stdout: "piped", stderr: "null" }); const status = await p.status(); assertEquals(status.success, true); assertEquals(status.code, 0); assertEquals(status.signal, undefined); p.stdout!.close(); p.close(); }); unitTest( { perms: { run: true } }, async function runCommandFailedWithCode(): Promise { const p = run({ args: ["python", "-c", "import sys;sys.exit(41 + 1)"] }); const status = await p.status(); assertEquals(status.success, false); assertEquals(status.code, 42); assertEquals(status.signal, undefined); p.close(); } ); unitTest( { // No signals on windows. skip: Deno.build.os === "win", perms: { run: true } }, async function runCommandFailedWithSignal(): Promise { const p = run({ args: ["python", "-c", "import os;os.kill(os.getpid(), 9)"] }); const status = await p.status(); assertEquals(status.success, false); assertEquals(status.code, undefined); assertEquals(status.signal, 9); p.close(); } ); unitTest({ perms: { run: true } }, function runNotFound(): void { let error; try { run({ args: ["this file hopefully doesn't exist"] }); } catch (e) { error = e; } assert(error !== undefined); assert(error instanceof Deno.errors.NotFound); }); unitTest( { perms: { write: true, run: true } }, async function runWithCwdIsAsync(): Promise { const enc = new TextEncoder(); const cwd = await makeTempDir({ prefix: "deno_command_test" }); const exitCodeFile = "deno_was_here"; const pyProgramFile = "poll_exit.py"; const pyProgram = ` from sys import exit from time import sleep while True: try: with open("${exitCodeFile}", "r") as f: line = f.readline() code = int(line) exit(code) except IOError: # Retry if we got here before deno wrote the file. sleep(0.01) pass `; Deno.writeFileSync(`${cwd}/${pyProgramFile}.py`, enc.encode(pyProgram)); const p = run({ cwd, args: ["python", `${pyProgramFile}.py`] }); // Write the expected exit code *after* starting python. // This is how we verify that `run()` is actually asynchronous. const code = 84; Deno.writeFileSync(`${cwd}/${exitCodeFile}`, enc.encode(`${code}`)); const status = await p.status(); assertEquals(status.success, false); assertEquals(status.code, code); assertEquals(status.signal, undefined); p.close(); } ); unitTest({ perms: { run: true } }, async function runStdinPiped(): Promise< void > { const p = run({ args: ["python", "-c", "import sys; assert 'hello' == sys.stdin.read();"], stdin: "piped" }); assert(p.stdin); assert(!p.stdout); assert(!p.stderr); const msg = new TextEncoder().encode("hello"); const n = await p.stdin.write(msg); assertEquals(n, msg.byteLength); p.stdin!.close(); const status = await p.status(); assertEquals(status.success, true); assertEquals(status.code, 0); assertEquals(status.signal, undefined); p.close(); }); unitTest({ perms: { run: true } }, async function runStdoutPiped(): Promise< void > { const p = run({ args: ["python", "-c", "import sys; sys.stdout.write('hello')"], stdout: "piped" }); assert(!p.stdin); assert(!p.stderr); const data = new Uint8Array(10); let r = await p.stdout!.read(data); if (r === Deno.EOF) { throw new Error("p.stdout.read(...) should not be EOF"); } assertEquals(r, 5); const s = new TextDecoder().decode(data.subarray(0, r)); assertEquals(s, "hello"); r = await p.stdout!.read(data); assertEquals(r, Deno.EOF); p.stdout!.close(); const status = await p.status(); assertEquals(status.success, true); assertEquals(status.code, 0); assertEquals(status.signal, undefined); p.close(); }); unitTest({ perms: { run: true } }, async function runStderrPiped(): Promise< void > { const p = run({ args: ["python", "-c", "import sys; sys.stderr.write('hello')"], stderr: "piped" }); assert(!p.stdin); assert(!p.stdout); const data = new Uint8Array(10); let r = await p.stderr!.read(data); if (r === Deno.EOF) { throw new Error("p.stderr.read should not return EOF here"); } assertEquals(r, 5); const s = new TextDecoder().decode(data.subarray(0, r)); assertEquals(s, "hello"); r = await p.stderr!.read(data); assertEquals(r, Deno.EOF); p.stderr!.close(); const status = await p.status(); assertEquals(status.success, true); assertEquals(status.code, 0); assertEquals(status.signal, undefined); p.close(); }); unitTest({ perms: { run: true } }, async function runOutput(): Promise { const p = run({ args: ["python", "-c", "import sys; sys.stdout.write('hello')"], stdout: "piped" }); const output = await p.output(); const s = new TextDecoder().decode(output); assertEquals(s, "hello"); p.close(); }); unitTest({ perms: { run: true } }, async function runStderrOutput(): Promise< void > { const p = run({ args: ["python", "-c", "import sys; sys.stderr.write('error')"], stderr: "piped" }); const error = await p.stderrOutput(); const s = new TextDecoder().decode(error); assertEquals(s, "error"); p.close(); }); unitTest( { perms: { run: true, write: true, read: true } }, async function runRedirectStdoutStderr(): Promise { const tempDir = await makeTempDir(); const fileName = tempDir + "/redirected_stdio.txt"; const file = await open(fileName, "w"); const p = run({ args: [ "python", "-c", "import sys; sys.stderr.write('error\\n'); sys.stdout.write('output\\n');" ], stdout: file.rid, stderr: file.rid }); await p.status(); p.close(); file.close(); const fileContents = await readFile(fileName); const decoder = new TextDecoder(); const text = decoder.decode(fileContents); assertStrContains(text, "error"); assertStrContains(text, "output"); } ); unitTest( { perms: { run: true, write: true, read: true } }, async function runRedirectStdin(): Promise { const tempDir = await makeTempDir(); const fileName = tempDir + "/redirected_stdio.txt"; const encoder = new TextEncoder(); await writeFile(fileName, encoder.encode("hello")); const file = await open(fileName, "r"); const p = run({ args: ["python", "-c", "import sys; assert 'hello' == sys.stdin.read();"], stdin: file.rid }); const status = await p.status(); assertEquals(status.code, 0); p.close(); file.close(); } ); unitTest({ perms: { run: true } }, async function runEnv(): Promise { const p = run({ args: [ "python", "-c", "import os, sys; sys.stdout.write(os.environ.get('FOO', '') + os.environ.get('BAR', ''))" ], env: { FOO: "0123", BAR: "4567" }, stdout: "piped" }); const output = await p.output(); const s = new TextDecoder().decode(output); assertEquals(s, "01234567"); p.close(); }); unitTest({ perms: { run: true } }, async function runClose(): Promise { const p = run({ args: [ "python", "-c", "from time import sleep; import sys; sleep(10000); sys.stderr.write('error')" ], stderr: "piped" }); assert(!p.stdin); assert(!p.stdout); p.close(); const data = new Uint8Array(10); const r = await p.stderr!.read(data); assertEquals(r, Deno.EOF); p.stderr!.close(); }); unitTest(function signalNumbers(): void { if (Deno.build.os === "mac") { assertEquals(Deno.Signal.SIGSTOP, 17); } else if (Deno.build.os === "linux") { assertEquals(Deno.Signal.SIGSTOP, 19); } }); // Ignore signal tests on windows for now... if (Deno.build.os !== "win") { unitTest(function killPermissions(): void { let caughtError = false; try { // Unlike the other test cases, we don't have permission to spawn a // subprocess we can safely kill. Instead we send SIGCONT to the current // process - assuming that Deno does not have a special handler set for it // and will just continue even if a signal is erroneously sent. Deno.kill(Deno.pid, Deno.Signal.SIGCONT); } catch (e) { caughtError = true; assert(e instanceof Deno.errors.PermissionDenied); } assert(caughtError); }); unitTest({ perms: { run: true } }, async function killSuccess(): Promise< void > { const p = run({ args: ["python", "-c", "from time import sleep; sleep(10000)"] }); assertEquals(Deno.Signal.SIGINT, 2); kill(p.pid, Deno.Signal.SIGINT); const status = await p.status(); assertEquals(status.success, false); // TODO(ry) On Linux, status.code is sometimes undefined and sometimes 1. // The following assert is causing this test to be flaky. Investigate and // re-enable when it can be made deterministic. // assertEquals(status.code, 1); // assertEquals(status.signal, Deno.Signal.SIGINT); p.close(); }); unitTest({ perms: { run: true } }, async function killFailed(): Promise< void > { const p = run({ args: ["python", "-c", "from time import sleep; sleep(10000)"] }); assert(!p.stdin); assert(!p.stdout); let err; try { kill(p.pid, 12345); } catch (e) { err = e; } assert(!!err); assert(err instanceof TypeError); p.close(); }); }