diff options
Diffstat (limited to 'cli/tests/unit_node/process_test.ts')
-rw-r--r-- | cli/tests/unit_node/process_test.ts | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/cli/tests/unit_node/process_test.ts b/cli/tests/unit_node/process_test.ts new file mode 100644 index 000000000..7310e4ad7 --- /dev/null +++ b/cli/tests/unit_node/process_test.ts @@ -0,0 +1,713 @@ +// deno-lint-ignore-file no-undef +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import process, { argv, env } from "node:process"; +import { + assert, + assertEquals, + assertFalse, + assertObjectMatch, + assertStrictEquals, + assertThrows, +} from "../../../test_util/std/testing/asserts.ts"; +import { stripColor } from "../../../test_util/std/fmt/colors.ts"; +import { deferred } from "../../../test_util/std/async/deferred.ts"; +import * as path from "../../../test_util/std/path/mod.ts"; +import { delay } from "../../../test_util/std/async/delay.ts"; + +const testDir = new URL(".", import.meta.url); + +Deno.test({ + name: "process.cwd and process.chdir success", + fn() { + assertEquals(process.cwd(), Deno.cwd()); + + const currentDir = Deno.cwd(); + + const tempDir = Deno.makeTempDirSync(); + process.chdir(tempDir); + assertEquals( + Deno.realPathSync(process.cwd()), + Deno.realPathSync(tempDir), + ); + + process.chdir(currentDir); + }, +}); + +Deno.test({ + name: "process.chdir failure", + fn() { + assertThrows( + () => { + process.chdir("non-existent-directory-name"); + }, + Deno.errors.NotFound, + "file", + // On every OS Deno returns: "No such file" except for Windows, where it's: + // "The system cannot find the file specified. (os error 2)" so "file" is + // the only common string here. + ); + }, +}); + +Deno.test({ + name: "process.version", + fn() { + assertEquals(typeof process, "object"); + assertEquals(typeof process.version, "string"); + assertEquals(typeof process.versions, "object"); + assertEquals(typeof process.versions.node, "string"); + assertEquals(typeof process.versions.v8, "string"); + assertEquals(typeof process.versions.uv, "string"); + assertEquals(typeof process.versions.zlib, "string"); + assertEquals(typeof process.versions.brotli, "string"); + assertEquals(typeof process.versions.ares, "string"); + assertEquals(typeof process.versions.modules, "string"); + assertEquals(typeof process.versions.nghttp2, "string"); + assertEquals(typeof process.versions.napi, "string"); + assertEquals(typeof process.versions.llhttp, "string"); + assertEquals(typeof process.versions.openssl, "string"); + assertEquals(typeof process.versions.cldr, "string"); + assertEquals(typeof process.versions.icu, "string"); + assertEquals(typeof process.versions.tz, "string"); + assertEquals(typeof process.versions.unicode, "string"); + // These two are not present in `process.versions` in Node, but we + // add them anyway + assertEquals(typeof process.versions.deno, "string"); + assertEquals(typeof process.versions.typescript, "string"); + }, +}); + +Deno.test({ + name: "process.platform", + fn() { + assertEquals(typeof process.platform, "string"); + }, +}); + +Deno.test({ + name: "process.mainModule", + fn() { + assertEquals(process.mainModule, undefined); + // Check that it is writable + // @ts-ignore these are deprecated now + process.mainModule = "foo"; + // @ts-ignore these are deprecated now + assertEquals(process.mainModule, "foo"); + }, +}); + +Deno.test({ + name: "process.arch", + fn() { + assertEquals(typeof process.arch, "string"); + if (Deno.build.arch == "x86_64") { + assertEquals(process.arch, "x64"); + } else if (Deno.build.arch == "aarch64") { + assertEquals(process.arch, "arm64"); + } else { + throw new Error("unreachable"); + } + }, +}); + +Deno.test({ + name: "process.pid", + fn() { + assertEquals(typeof process.pid, "number"); + assertEquals(process.pid, Deno.pid); + }, +}); + +Deno.test({ + name: "process.on", + async fn() { + assertEquals(typeof process.on, "function"); + + let triggered = false; + process.on("exit", () => { + triggered = true; + }); + // @ts-ignore fix the type here + process.emit("exit"); + assert(triggered); + + const cwd = path.dirname(path.fromFileUrl(import.meta.url)); + + const command = new Deno.Command(Deno.execPath(), { + args: [ + "run", + "--quiet", + "--unstable", + "./testdata/process_exit.ts", + ], + cwd, + }); + const { stdout } = await command.output(); + + const decoder = new TextDecoder(); + assertEquals(stripColor(decoder.decode(stdout).trim()), "1\n2"); + }, +}); + +Deno.test({ + name: "process.on signal", + ignore: Deno.build.os == "windows", + async fn() { + const promise = deferred(); + let c = 0; + const listener = () => { + c += 1; + }; + process.on("SIGINT", listener); + setTimeout(async () => { + // Sends SIGINT 3 times. + for (const _ of Array(3)) { + await delay(20); + Deno.kill(Deno.pid, "SIGINT"); + } + await delay(20); + Deno.removeSignalListener("SIGINT", listener); + promise.resolve(); + }); + await promise; + assertEquals(c, 3); + }, +}); + +Deno.test({ + name: "process.off signal", + ignore: Deno.build.os == "windows", + async fn() { + const promise = deferred(); + let c = 0; + const listener = () => { + c += 1; + process.off("SIGINT", listener); + }; + process.on("SIGINT", listener); + setTimeout(async () => { + // Sends SIGINT 3 times. + for (const _ of Array(3)) { + await delay(20); + Deno.kill(Deno.pid, "SIGINT"); + } + await delay(20); + promise.resolve(); + }); + await promise; + assertEquals(c, 1); + }, +}); + +Deno.test({ + name: "process.on SIGBREAK doesn't throw", + fn() { + const listener = () => {}; + process.on("SIGBREAK", listener); + process.off("SIGBREAK", listener); + }, +}); + +Deno.test({ + name: "process.on SIGTERM doesn't throw on windows", + ignore: Deno.build.os !== "windows", + fn() { + const listener = () => {}; + process.on("SIGTERM", listener); + process.off("SIGTERM", listener); + }, +}); + +Deno.test({ + name: "process.argv", + fn() { + assert(Array.isArray(argv)); + assert(Array.isArray(process.argv)); + assert( + process.argv[0].match(/[^/\\]*deno[^/\\]*$/), + "deno included in the file name of argv[0]", + ); + assertEquals( + process.argv[1], + path.fromFileUrl(Deno.mainModule), + ); + // argv supports array methods. + assert(Array.isArray(process.argv.slice(2))); + assertEquals(process.argv.indexOf(Deno.execPath()), 0); + assertEquals(process.argv.indexOf(path.fromFileUrl(Deno.mainModule)), 1); + }, +}); + +Deno.test({ + name: "process.execArgv", + fn() { + assert(Array.isArray(process.execArgv)); + assert(process.execArgv.length == 0); + // execArgv supports array methods. + assert(Array.isArray(process.argv.slice(0))); + assertEquals(process.argv.indexOf("foo"), -1); + }, +}); + +Deno.test({ + name: "process.env", + fn() { + Deno.env.set("HELLO", "WORLD"); + + assertObjectMatch(process.env, Deno.env.toObject()); + + assertEquals(typeof (process.env.HELLO), "string"); + assertEquals(process.env.HELLO, "WORLD"); + + assertEquals(typeof env.HELLO, "string"); + assertEquals(env.HELLO, "WORLD"); + + assert(Object.getOwnPropertyNames(process.env).includes("HELLO")); + assert(Object.keys(process.env).includes("HELLO")); + + assert(Object.prototype.hasOwnProperty.call(process.env, "HELLO")); + assert( + !Object.prototype.hasOwnProperty.call( + process.env, + "SURELY_NON_EXISTENT_VAR", + ), + ); + + // deno-lint-ignore no-prototype-builtins + assert(process.env.hasOwnProperty("HELLO")); + assert("HELLO" in process.env); + assert(Object.keys(process.env.valueOf()).includes("HELLO")); + + assertEquals(process.env.toString(), "[object Object]"); + assertEquals(process.env.toLocaleString(), "[object Object]"); + + // should not error when assigning false to an env var + process.env.HELLO = false as unknown as string; + assertEquals(process.env.HELLO, "false"); + process.env.HELLO = "WORLD"; + assertEquals(process.env.HELLO, "WORLD"); + }, +}); + +Deno.test({ + name: "process.env requires scoped env permission", + permissions: { env: ["FOO"] }, + fn() { + Deno.env.set("FOO", "1"); + assert("FOO" in process.env); + assertFalse("BAR" in process.env); + assert(Object.hasOwn(process.env, "FOO")); + assertFalse(Object.hasOwn(process.env, "BAR")); + }, +}); + +Deno.test({ + name: "process.env doesn't throw with invalid env var names", + fn() { + assertEquals(process.env[""], undefined); + assertEquals(process.env["\0"], undefined); + assertEquals(process.env["=c:"], undefined); + assertFalse(Object.hasOwn(process.env, "")); + assertFalse(Object.hasOwn(process.env, "\0")); + assertFalse(Object.hasOwn(process.env, "=c:")); + assertFalse("" in process.env); + assertFalse("\0" in process.env); + assertFalse("=c:" in process.env); + }, +}); + +Deno.test({ + name: "process.stdin", + fn() { + assertEquals(process.stdin.fd, Deno.stdin.rid); + assertEquals(process.stdin.isTTY, Deno.isatty(Deno.stdin.rid)); + }, +}); + +Deno.test({ + name: "process.stdin readable with a TTY", + // TODO(PolarETech): Run this test even in non tty environment + ignore: !Deno.isatty(Deno.stdin.rid), + async fn() { + const promise = deferred(); + const expected = ["foo", "bar", null, "end"]; + const data: (string | null)[] = []; + + process.stdin.setEncoding("utf8"); + process.stdin.on("readable", () => { + data.push(process.stdin.read()); + }); + process.stdin.on("end", () => { + data.push("end"); + }); + + process.stdin.push("foo"); + process.nextTick(() => { + process.stdin.push("bar"); + process.nextTick(() => { + process.stdin.push(null); + promise.resolve(); + }); + }); + + await promise; + assertEquals(process.stdin.readableHighWaterMark, 0); + assertEquals(data, expected); + }, +}); + +Deno.test({ + name: "process.stdin readable with piping a file", + async fn() { + const expected = ["65536", "foo", "bar", "null", "end"]; + const scriptPath = "./testdata/process_stdin.ts"; + const filePath = "./testdata/process_stdin_dummy.txt"; + + const shell = Deno.build.os === "windows" ? "cmd.exe" : "/bin/sh"; + const cmd = `"${Deno.execPath()}" run ${scriptPath} < ${filePath}`; + const args = Deno.build.os === "windows" ? ["/d", "/c", cmd] : ["-c", cmd]; + + const p = new Deno.Command(shell, { + args, + stdin: "null", + stdout: "piped", + stderr: "null", + windowsRawArguments: true, + cwd: testDir, + }); + + const { stdout } = await p.output(); + const data = new TextDecoder().decode(stdout).trim().split("\n"); + assertEquals(data, expected); + }, +}); + +Deno.test({ + name: "process.stdin readable with piping a stream", + async fn() { + const expected = ["16384", "foo", "bar", "null", "end"]; + const scriptPath = "./testdata/process_stdin.ts"; + + const command = new Deno.Command(Deno.execPath(), { + args: ["run", scriptPath], + stdin: "piped", + stdout: "piped", + stderr: "null", + cwd: testDir, + }); + const child = command.spawn(); + + const writer = await child.stdin.getWriter(); + writer.ready + .then(() => writer.write(new TextEncoder().encode("foo\nbar"))) + .then(() => writer.releaseLock()) + .then(() => child.stdin.close()); + + const { stdout } = await child.output(); + const data = new TextDecoder().decode(stdout).trim().split("\n"); + assertEquals(data, expected); + }, +}); + +Deno.test({ + name: "process.stdin readable with piping a socket", + ignore: Deno.build.os === "windows", + async fn() { + const expected = ["16384", "foo", "bar", "null", "end"]; + const scriptPath = "./testdata/process_stdin.ts"; + + const listener = Deno.listen({ hostname: "127.0.0.1", port: 9000 }); + listener.accept().then(async (conn) => { + await conn.write(new TextEncoder().encode("foo\nbar")); + conn.close(); + listener.close(); + }); + + const shell = "/bin/bash"; + const cmd = + `"${Deno.execPath()}" run ${scriptPath} < /dev/tcp/127.0.0.1/9000`; + const args = ["-c", cmd]; + + const p = new Deno.Command(shell, { + args, + stdin: "null", + stdout: "piped", + stderr: "null", + cwd: testDir, + }); + + const { stdout } = await p.output(); + const data = new TextDecoder().decode(stdout).trim().split("\n"); + assertEquals(data, expected); + }, +}); + +Deno.test({ + name: "process.stdin readable with null", + async fn() { + const expected = ["65536", "null", "end"]; + const scriptPath = "./testdata/process_stdin.ts"; + + const command = new Deno.Command(Deno.execPath(), { + args: ["run", scriptPath], + stdin: "null", + stdout: "piped", + stderr: "null", + cwd: testDir, + }); + + const { stdout } = await command.output(); + const data = new TextDecoder().decode(stdout).trim().split("\n"); + assertEquals(data, expected); + }, +}); + +// TODO(kt3k): Enable this test case. 'readable' event handler in +// `process_stdin.ts` doesn't work now +Deno.test({ + name: "process.stdin readable with unsuitable stdin", + ignore: true, + // // TODO(PolarETech): Prepare a similar test that can be run on Windows + // ignore: Deno.build.os === "windows", + async fn() { + const expected = ["16384", "null", "end"]; + const scriptPath = "./testdata/process_stdin.ts"; + const directoryPath = "./testdata/"; + + const shell = "/bin/bash"; + const cmd = `"${Deno.execPath()}" run ${scriptPath} < ${directoryPath}`; + const args = ["-c", cmd]; + + const p = new Deno.Command(shell, { + args, + stdin: "null", + stdout: "piped", + stderr: "null", + windowsRawArguments: true, + cwd: testDir, + }); + + const { stdout } = await p.output(); + const data = new TextDecoder().decode(stdout).trim().split("\n"); + assertEquals(data, expected); + }, +}); + +Deno.test({ + name: "process.stdout", + fn() { + assertEquals(process.stdout.fd, Deno.stdout.rid); + const isTTY = Deno.isatty(Deno.stdout.rid); + assertEquals(process.stdout.isTTY, isTTY); + const consoleSize = isTTY ? Deno.consoleSize() : undefined; + assertEquals(process.stdout.columns, consoleSize?.columns); + assertEquals(process.stdout.rows, consoleSize?.rows); + assertEquals( + `${process.stdout.getWindowSize()}`, + `${consoleSize && [consoleSize.columns, consoleSize.rows]}`, + ); + + if (isTTY) { + assertStrictEquals(process.stdout.cursorTo(1, 2, () => {}), true); + assertStrictEquals(process.stdout.moveCursor(3, 4, () => {}), true); + assertStrictEquals(process.stdout.clearLine(1, () => {}), true); + assertStrictEquals(process.stdout.clearScreenDown(() => {}), true); + } else { + assertStrictEquals(process.stdout.cursorTo, undefined); + assertStrictEquals(process.stdout.moveCursor, undefined); + assertStrictEquals(process.stdout.clearLine, undefined); + assertStrictEquals(process.stdout.clearScreenDown, undefined); + } + }, +}); + +Deno.test({ + name: "process.stderr", + fn() { + assertEquals(process.stderr.fd, Deno.stderr.rid); + const isTTY = Deno.isatty(Deno.stderr.rid); + assertEquals(process.stderr.isTTY, isTTY); + const consoleSize = isTTY ? Deno.consoleSize() : undefined; + assertEquals(process.stderr.columns, consoleSize?.columns); + assertEquals(process.stderr.rows, consoleSize?.rows); + assertEquals( + `${process.stderr.getWindowSize()}`, + `${consoleSize && [consoleSize.columns, consoleSize.rows]}`, + ); + + if (isTTY) { + assertStrictEquals(process.stderr.cursorTo(1, 2, () => {}), true); + assertStrictEquals(process.stderr.moveCursor(3, 4, () => {}), true); + assertStrictEquals(process.stderr.clearLine(1, () => {}), true); + assertStrictEquals(process.stderr.clearScreenDown(() => {}), true); + } else { + assertStrictEquals(process.stderr.cursorTo, undefined); + assertStrictEquals(process.stderr.moveCursor, undefined); + assertStrictEquals(process.stderr.clearLine, undefined); + assertStrictEquals(process.stderr.clearScreenDown, undefined); + } + }, +}); + +Deno.test({ + name: "process.nextTick", + async fn() { + let withoutArguments = false; + process.nextTick(() => { + withoutArguments = true; + }); + + const expected = 12; + let result; + process.nextTick((x: number) => { + result = x; + }, 12); + + await delay(10); + assert(withoutArguments); + assertEquals(result, expected); + }, +}); + +Deno.test({ + name: "process.hrtime", + // TODO(kt3k): Enable this test + ignore: true, + fn() { + const [sec0, nano0] = process.hrtime(); + // seconds and nano seconds are positive integers. + assert(sec0 > 0); + assert(Number.isInteger(sec0)); + assert(nano0 > 0); + assert(Number.isInteger(nano0)); + + const [sec1, nano1] = process.hrtime(); + // the later call returns bigger value + assert(sec1 >= sec0); + assert(nano1 > nano0); + + const [sec2, nano2] = process.hrtime([sec1, nano1]); + // the difference of the 2 calls is a small positive value. + assertEquals(sec2, 0); + assert(nano2 > 0); + }, +}); + +Deno.test({ + name: "process.hrtime.bigint", + fn() { + const time = process.hrtime.bigint(); + assertEquals(typeof time, "bigint"); + assert(time > 0n); + }, +}); + +Deno.test("process.on, process.off, process.removeListener doesn't throw on unimplemented events", () => { + const events = [ + "beforeExit", + "disconnect", + "message", + "multipleResolves", + "rejectionHandled", + "uncaughtException", + "uncaughtExceptionMonitor", + "unhandledRejection", + "worker", + ]; + const handler = () => {}; + events.forEach((ev) => { + process.on(ev, handler); + assertEquals(process.listenerCount(ev), 1); + process.off(ev, handler); + assertEquals(process.listenerCount(ev), 0); + process.on(ev, handler); + assertEquals(process.listenerCount(ev), 1); + process.removeListener(ev, handler); + assertEquals(process.listenerCount(ev), 0); + }); +}); + +Deno.test("process.memoryUsage()", () => { + const mem = process.memoryUsage(); + assert(typeof mem.rss === "number"); + assert(typeof mem.heapTotal === "number"); + assert(typeof mem.heapUsed === "number"); + assert(typeof mem.external === "number"); + assert(typeof mem.arrayBuffers === "number"); + assertEquals(mem.arrayBuffers, 0); +}); + +Deno.test("process.memoryUsage.rss()", () => { + const rss = process.memoryUsage.rss(); + assert(typeof rss === "number"); +}); + +Deno.test("process.exitCode", () => { + assert(process.exitCode === undefined); + process.exitCode = 127; + assert(process.exitCode === 127); +}); + +Deno.test("process.config", () => { + assert(process.config !== undefined); + assert(process.config.target_defaults !== undefined); + assert(process.config.variables !== undefined); +}); + +Deno.test("process._exiting", () => { + // @ts-ignore fix the type here + assert(process._exiting === false); +}); + +Deno.test("process.execPath", () => { + assertEquals(process.execPath, process.argv[0]); +}); + +Deno.test("process.execPath is writable", () => { + // pnpm writes to process.execPath + // https://github.com/pnpm/pnpm/blob/67d8b65d2e8da1df3725034b8c5b1fcf3af4ad81/packages/config/src/index.ts#L175 + const originalExecPath = process.execPath; + try { + process.execPath = "/path/to/node"; + assertEquals(process.execPath, "/path/to/node"); + } finally { + process.execPath = originalExecPath; + } +}); + +Deno.test("process.getgid", () => { + if (Deno.build.os === "windows") { + assertEquals(process.getgid, undefined); + } else { + assertEquals(process.getgid?.(), Deno.gid()); + } +}); + +Deno.test("process.getuid", () => { + if (Deno.build.os === "windows") { + assertEquals(process.getuid, undefined); + } else { + assertEquals(process.getuid?.(), Deno.uid()); + } +}); + +Deno.test({ + name: "process.exit", + async fn() { + const command = new Deno.Command(Deno.execPath(), { + args: [ + "run", + "--quiet", + "--unstable", + "./testdata/process_exit2.ts", + ], + cwd: testDir, + }); + const { stdout } = await command.output(); + + const decoder = new TextDecoder(); + assertEquals(stripColor(decoder.decode(stdout).trim()), "exit"); + }, +}); |