1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-27 01:29:14 -05:00
denoland-deno/cli/tests/unit_node/process_test.ts
Bartek Iwańczuk 544f48c23c
fix(node): don't close stdio streams (#19256)
Closes https://github.com/denoland/deno/issues/19255

---------

Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
2023-06-09 01:13:20 +02:00

766 lines
21 KiB
TypeScript

// 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 { Readable } from "node:stream";
import { once } from "node:events";
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 process = new Deno.Command(Deno.execPath(), {
args: [
"eval",
`
import process from "node:process";
setInterval(() => {}, 1000);
process.on("SIGINT", () => {
console.log("foo");
});
`,
],
stdout: "piped",
stderr: "null",
}).spawn();
await delay(500);
for (const _ of Array(3)) {
process.kill("SIGINT");
await delay(20);
}
await delay(20);
process.kill("SIGTERM");
const output = await process.output();
assertEquals(new TextDecoder().decode(output.stdout), "foo\nfoo\nfoo\n");
},
});
Deno.test({
name: "process.off signal",
ignore: Deno.build.os == "windows",
async fn() {
const process = new Deno.Command(Deno.execPath(), {
args: [
"eval",
`
import process from "node:process";
setInterval(() => {}, 1000);
const listener = () => {
console.log("foo");
process.off("SIGINT")
};
process.on("SIGINT", listener);
`,
],
stdout: "piped",
stderr: "null",
}).spawn();
await delay(500);
for (const _ of Array(3)) {
try {
process.kill("SIGINT");
} catch { /* should die after the first one */ }
await delay(20);
}
await delay(20);
try {
process.kill("SIGTERM");
} catch { /* should be dead, avoid hanging just in case */ }
const output = await process.output();
assertEquals(new TextDecoder().decode(output.stdout), "foo\n");
},
});
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");
},
});
Deno.test({
name: "process.reallyExit",
async fn() {
const command = new Deno.Command(Deno.execPath(), {
args: [
"run",
"--quiet",
"--unstable",
"./testdata/process_really_exit.ts",
],
cwd: testDir,
});
const { stdout } = await command.output();
const decoder = new TextDecoder();
assertEquals(stripColor(decoder.decode(stdout).trim()), "really exited");
},
});
Deno.test({
name: "process.stdout isn't closed when source stream ended",
async fn() {
const source = Readable.from(["foo", "bar"]);
source.pipe(process.stdout);
await once(source, "end");
// Wait a bit to ensure that streaming is completely finished.
await delay(10);
// This checks if the rid 1 is still valid.
assert(typeof process.stdout.isTTY === "boolean");
},
});