summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/tests/integration/mod.rs2
-rw-r--r--cli/tests/integration/node_unit_tests.rs21
-rw-r--r--cli/tests/unit_node/child_process_test.ts577
-rw-r--r--cli/tests/unit_node/process_test.ts713
-rw-r--r--cli/tests/unit_node/testdata/binary_stdio.js11
-rw-r--r--cli/tests/unit_node/testdata/child_process_unref.js9
-rw-r--r--cli/tests/unit_node/testdata/exec_file_text_error.js2
-rw-r--r--cli/tests/unit_node/testdata/exec_file_text_output.js1
-rw-r--r--cli/tests/unit_node/testdata/infinite_loop.js3
-rw-r--r--cli/tests/unit_node/testdata/node_modules/foo/index.js4
-rw-r--r--cli/tests/unit_node/testdata/node_modules/foo/package.json3
-rw-r--r--cli/tests/unit_node/testdata/process_exit.ts19
-rw-r--r--cli/tests/unit_node/testdata/process_exit2.ts4
-rw-r--r--cli/tests/unit_node/testdata/process_stdin.ts11
-rw-r--r--cli/tests/unit_node/testdata/process_stdin_dummy.txt2
-rw-r--r--core/runtime.rs9
-rw-r--r--ext/node/01_node.js2
-rw-r--r--ext/node/polyfills/_next_tick.ts13
-rw-r--r--ext/node/polyfills/_process/process.ts5
-rw-r--r--ext/node/polyfills/_process/streams.mjs76
-rw-r--r--ext/node/polyfills/_readline.mjs2
-rw-r--r--ext/node/polyfills/child_process.ts2
-rw-r--r--ext/node/polyfills/process.ts49
-rw-r--r--ext/node/polyfills/timers.ts5
-rw-r--r--runtime/js/99_main.js5
-rw-r--r--tools/copyright_checker.js1
26 files changed, 1471 insertions, 80 deletions
diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs
index 054a4e14a..7ed1d6e0d 100644
--- a/cli/tests/integration/mod.rs
+++ b/cli/tests/integration/mod.rs
@@ -70,6 +70,8 @@ mod js_unit_tests;
mod lint;
#[path = "lsp_tests.rs"]
mod lsp;
+#[path = "node_unit_tests.rs"]
+mod node_unit_tests;
#[path = "npm_tests.rs"]
mod npm;
#[path = "repl_tests.rs"]
diff --git a/cli/tests/integration/node_unit_tests.rs b/cli/tests/integration/node_unit_tests.rs
new file mode 100644
index 000000000..d29d988ba
--- /dev/null
+++ b/cli/tests/integration/node_unit_tests.rs
@@ -0,0 +1,21 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use test_util as util;
+
+#[test]
+fn node_unit_tests() {
+ let _g = util::http_server();
+
+ let mut deno = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("test")
+ .arg("--unstable")
+ .arg("-A")
+ .arg(util::tests_path().join("unit_node"))
+ .spawn()
+ .expect("failed to spawn script");
+
+ let status = deno.wait().expect("failed to wait for the child process");
+ assert_eq!(Some(0), status.code());
+ assert!(status.success());
+}
diff --git a/cli/tests/unit_node/child_process_test.ts b/cli/tests/unit_node/child_process_test.ts
new file mode 100644
index 000000000..c6e2e3ef2
--- /dev/null
+++ b/cli/tests/unit_node/child_process_test.ts
@@ -0,0 +1,577 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import CP from "node:child_process";
+import { Buffer } from "node:buffer";
+import {
+ assert,
+ assertEquals,
+ assertExists,
+ assertNotStrictEquals,
+ assertStrictEquals,
+ assertStringIncludes,
+} from "../../../test_util/std/testing/asserts.ts";
+import { Deferred, deferred } from "../../../test_util/std/async/deferred.ts";
+import * as path from "../../../test_util/std/path/mod.ts";
+
+const { spawn, execFile, execFileSync, ChildProcess } = CP;
+
+function withTimeout<T>(timeoutInMS: number): Deferred<T> {
+ const promise = deferred<T>();
+ const timer = setTimeout(() => {
+ promise.reject("Timeout");
+ }, timeoutInMS);
+ promise.then(() => {
+ clearTimeout(timer);
+ });
+ return promise;
+}
+
+// TODO(uki00a): Once Node.js's `parallel/test-child-process-spawn-error.js` works, this test case should be removed.
+Deno.test("[node/child_process spawn] The 'error' event is emitted when no binary is found", async () => {
+ const promise = withTimeout(1000);
+ const childProcess = spawn("no-such-cmd");
+ childProcess.on("error", (_err: Error) => {
+ // TODO(@bartlomieju) Assert an error message.
+ promise.resolve();
+ });
+ await promise;
+});
+
+Deno.test("[node/child_process spawn] The 'exit' event is emitted with an exit code after the child process ends", async () => {
+ const promise = withTimeout(3000);
+ const childProcess = spawn(Deno.execPath(), ["--help"], {
+ env: { NO_COLOR: "true" },
+ });
+ try {
+ let exitCode = null;
+ childProcess.on("exit", (code: number) => {
+ promise.resolve();
+ exitCode = code;
+ });
+ await promise;
+ assertStrictEquals(exitCode, 0);
+ assertStrictEquals(childProcess.exitCode, exitCode);
+ } finally {
+ childProcess.kill();
+ childProcess.stdout?.destroy();
+ childProcess.stderr?.destroy();
+ }
+});
+
+Deno.test("[node/child_process disconnect] the method exists", async () => {
+ const promise = withTimeout(1000);
+ const childProcess = spawn(Deno.execPath(), ["--help"], {
+ env: { NO_COLOR: "true" },
+ });
+ try {
+ childProcess.disconnect();
+ childProcess.on("exit", () => {
+ promise.resolve();
+ });
+ await promise;
+ } finally {
+ childProcess.kill();
+ childProcess.stdout?.destroy();
+ childProcess.stderr?.destroy();
+ }
+});
+
+Deno.test({
+ name: "[node/child_process spawn] Verify that stdin and stdout work",
+ fn: async () => {
+ const promise = withTimeout(3000);
+ const childProcess = spawn(Deno.execPath(), ["fmt", "-"], {
+ env: { NO_COLOR: "true" },
+ stdio: ["pipe", "pipe"],
+ });
+ try {
+ assert(childProcess.stdin, "stdin should be defined");
+ assert(childProcess.stdout, "stdout should be defined");
+ let data = "";
+ childProcess.stdout.on("data", (chunk) => {
+ data += chunk;
+ });
+ childProcess.stdin.write(" console.log('hello')", "utf-8");
+ childProcess.stdin.end();
+ childProcess.on("close", () => {
+ promise.resolve();
+ });
+ await promise;
+ assertStrictEquals(data, `console.log("hello");\n`);
+ } finally {
+ childProcess.kill();
+ }
+ },
+});
+
+Deno.test({
+ name: "[node/child_process spawn] stdin and stdout with binary data",
+ fn: async () => {
+ const promise = withTimeout(10000);
+ const p = path.join(
+ path.dirname(path.fromFileUrl(import.meta.url)),
+ "./testdata/binary_stdio.js",
+ );
+ const childProcess = spawn(Deno.execPath(), ["run", p], {
+ env: { NO_COLOR: "true" },
+ stdio: ["pipe", "pipe"],
+ });
+ try {
+ assert(childProcess.stdin, "stdin should be defined");
+ assert(childProcess.stdout, "stdout should be defined");
+ let data: Buffer;
+ childProcess.stdout.on("data", (chunk) => {
+ data = chunk;
+ });
+ const buffer = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ childProcess.stdin.write(buffer);
+ childProcess.stdin.end();
+ childProcess.on("close", () => {
+ promise.resolve();
+ });
+ await promise;
+ assertEquals(new Uint8Array(data!), buffer);
+ } finally {
+ childProcess.kill();
+ }
+ },
+});
+
+async function spawnAndGetEnvValue(
+ inputValue: string | number | boolean,
+): Promise<string> {
+ const promise = withTimeout<string>(3000);
+ const env = spawn(
+ `"${Deno.execPath()}" eval -p "Deno.env.toObject().BAZ"`,
+ {
+ env: { BAZ: String(inputValue), NO_COLOR: "true" },
+ shell: true,
+ },
+ );
+ try {
+ let envOutput = "";
+
+ assert(env.stdout);
+ env.on("error", (err: Error) => promise.reject(err));
+ env.stdout.on("data", (data) => {
+ envOutput += data;
+ });
+ env.on("close", () => {
+ promise.resolve(envOutput.trim());
+ });
+ return await promise;
+ } finally {
+ env.kill();
+ }
+}
+
+Deno.test({
+ ignore: Deno.build.os === "windows",
+ name:
+ "[node/child_process spawn] Verify that environment values can be numbers",
+ async fn() {
+ const envOutputValue = await spawnAndGetEnvValue(42);
+ assertStrictEquals(envOutputValue, "42");
+ },
+});
+
+Deno.test({
+ ignore: Deno.build.os === "windows",
+ name:
+ "[node/child_process spawn] Verify that environment values can be booleans",
+ async fn() {
+ const envOutputValue = await spawnAndGetEnvValue(false);
+ assertStrictEquals(envOutputValue, "false");
+ },
+});
+
+/* Start of ported part */ 3;
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Ported from Node 15.5.1
+
+// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-event.js` works.
+Deno.test("[child_process spawn] 'spawn' event", async () => {
+ const timeout = withTimeout(3000);
+ const subprocess = spawn(Deno.execPath(), ["eval", "console.log('ok')"]);
+
+ let didSpawn = false;
+ subprocess.on("spawn", function () {
+ didSpawn = true;
+ });
+
+ function mustNotBeCalled() {
+ timeout.reject(new Error("function should not have been called"));
+ }
+
+ const promises = [] as Promise<void>[];
+ function mustBeCalledAfterSpawn() {
+ const promise = deferred<void>();
+ promises.push(promise);
+ return () => {
+ if (didSpawn) {
+ promise.resolve();
+ } else {
+ promise.reject(
+ new Error("function should be called after the 'spawn' event"),
+ );
+ }
+ };
+ }
+
+ subprocess.on("error", mustNotBeCalled);
+ subprocess.stdout!.on("data", mustBeCalledAfterSpawn());
+ subprocess.stdout!.on("end", mustBeCalledAfterSpawn());
+ subprocess.stdout!.on("close", mustBeCalledAfterSpawn());
+ subprocess.stderr!.on("data", mustNotBeCalled);
+ subprocess.stderr!.on("end", mustBeCalledAfterSpawn());
+ subprocess.stderr!.on("close", mustBeCalledAfterSpawn());
+ subprocess.on("exit", mustBeCalledAfterSpawn());
+ subprocess.on("close", mustBeCalledAfterSpawn());
+
+ try {
+ await Promise.race([Promise.all(promises), timeout]);
+ timeout.resolve();
+ } finally {
+ subprocess.kill();
+ }
+});
+
+// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works.
+Deno.test("[child_process spawn] Verify that a shell is executed", async () => {
+ const promise = withTimeout(3000);
+ const doesNotExist = spawn("does-not-exist", { shell: true });
+ try {
+ assertNotStrictEquals(doesNotExist.spawnfile, "does-not-exist");
+ doesNotExist.on("error", () => {
+ promise.reject("The 'error' event must not be emitted.");
+ });
+ doesNotExist.on("exit", (code: number, signal: null) => {
+ assertStrictEquals(signal, null);
+
+ if (Deno.build.os === "windows") {
+ assertStrictEquals(code, 1); // Exit code of cmd.exe
+ } else {
+ assertStrictEquals(code, 127); // Exit code of /bin/sh });
+ }
+
+ promise.resolve();
+ });
+ await promise;
+ } finally {
+ doesNotExist.kill();
+ doesNotExist.stdout?.destroy();
+ doesNotExist.stderr?.destroy();
+ }
+});
+
+// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works.
+Deno.test({
+ ignore: Deno.build.os === "windows",
+ name: "[node/child_process spawn] Verify that passing arguments works",
+ async fn() {
+ const promise = withTimeout(3000);
+ const echo = spawn("echo", ["foo"], {
+ shell: true,
+ });
+ let echoOutput = "";
+
+ try {
+ assertStrictEquals(
+ echo.spawnargs[echo.spawnargs.length - 1].replace(/"/g, ""),
+ "echo foo",
+ );
+ assert(echo.stdout);
+ echo.stdout.on("data", (data) => {
+ echoOutput += data;
+ });
+ echo.on("close", () => {
+ assertStrictEquals(echoOutput.trim(), "foo");
+ promise.resolve();
+ });
+ await promise;
+ } finally {
+ echo.kill();
+ }
+ },
+});
+
+// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works.
+Deno.test({
+ ignore: Deno.build.os === "windows",
+ name: "[node/child_process spawn] Verity that shell features can be used",
+ async fn() {
+ const promise = withTimeout(3000);
+ const cmd = "echo bar | cat";
+ const command = spawn(cmd, {
+ shell: true,
+ });
+ try {
+ let commandOutput = "";
+
+ assert(command.stdout);
+ command.stdout.on("data", (data) => {
+ commandOutput += data;
+ });
+
+ command.on("close", () => {
+ assertStrictEquals(commandOutput.trim(), "bar");
+ promise.resolve();
+ });
+
+ await promise;
+ } finally {
+ command.kill();
+ }
+ },
+});
+
+// TODO(uki00a): Remove this case once Node's `parallel/test-child-process-spawn-shell.js` works.
+Deno.test({
+ ignore: Deno.build.os === "windows",
+ name:
+ "[node/child_process spawn] Verity that environment is properly inherited",
+ async fn() {
+ const promise = withTimeout(3000);
+ const env = spawn(
+ `"${Deno.execPath()}" eval -p "Deno.env.toObject().BAZ"`,
+ {
+ env: { BAZ: "buzz", NO_COLOR: "true" },
+ shell: true,
+ },
+ );
+ try {
+ let envOutput = "";
+
+ assert(env.stdout);
+ env.on("error", (err: Error) => promise.reject(err));
+ env.stdout.on("data", (data) => {
+ envOutput += data;
+ });
+ env.on("close", () => {
+ assertStrictEquals(envOutput.trim(), "buzz");
+ promise.resolve();
+ });
+ await promise;
+ } finally {
+ env.kill();
+ }
+ },
+});
+/* End of ported part */
+
+Deno.test({
+ name: "[node/child_process execFile] Get stdout as a string",
+ async fn() {
+ let child: unknown;
+ const script = path.join(
+ path.dirname(path.fromFileUrl(import.meta.url)),
+ "./testdata/exec_file_text_output.js",
+ );
+ const promise = new Promise<string | null>((resolve, reject) => {
+ child = execFile(Deno.execPath(), ["run", script], (err, stdout) => {
+ if (err) reject(err);
+ else if (stdout) resolve(stdout as string);
+ else resolve(null);
+ });
+ });
+ try {
+ const stdout = await promise;
+ assertEquals(stdout, "Hello World!\n");
+ } finally {
+ if (child instanceof ChildProcess) {
+ child.kill();
+ }
+ }
+ },
+});
+
+Deno.test({
+ name: "[node/child_process execFile] Get stdout as a buffer",
+ async fn() {
+ let child: unknown;
+ const script = path.join(
+ path.dirname(path.fromFileUrl(import.meta.url)),
+ "./testdata/exec_file_text_output.js",
+ );
+ const promise = new Promise<Buffer | null>((resolve, reject) => {
+ child = execFile(
+ Deno.execPath(),
+ ["run", script],
+ { encoding: "buffer" },
+ (err, stdout) => {
+ if (err) reject(err);
+ else if (stdout) resolve(stdout as Buffer);
+ else resolve(null);
+ },
+ );
+ });
+ try {
+ const stdout = await promise;
+ assert(Buffer.isBuffer(stdout));
+ assertEquals(stdout.toString("utf8"), "Hello World!\n");
+ } finally {
+ if (child instanceof ChildProcess) {
+ child.kill();
+ }
+ }
+ },
+});
+
+Deno.test({
+ name: "[node/child_process execFile] Get stderr",
+ async fn() {
+ let child: unknown;
+ const script = path.join(
+ path.dirname(path.fromFileUrl(import.meta.url)),
+ "./testdata/exec_file_text_error.js",
+ );
+ const promise = new Promise<
+ { err: Error | null; stderr?: string | Buffer }
+ >((resolve) => {
+ child = execFile(Deno.execPath(), ["run", script], (err, _, stderr) => {
+ resolve({ err, stderr });
+ });
+ });
+ try {
+ const { err, stderr } = await promise;
+ if (child instanceof ChildProcess) {
+ assertEquals(child.exitCode, 1);
+ assertEquals(stderr, "yikes!\n");
+ } else {
+ throw err;
+ }
+ } finally {
+ if (child instanceof ChildProcess) {
+ child.kill();
+ }
+ }
+ },
+});
+
+Deno.test({
+ name: "[node/child_process execFile] Exceed given maxBuffer limit",
+ async fn() {
+ let child: unknown;
+ const script = path.join(
+ path.dirname(path.fromFileUrl(import.meta.url)),
+ "./testdata/exec_file_text_error.js",
+ );
+ const promise = new Promise<
+ { err: Error | null; stderr?: string | Buffer }
+ >((resolve) => {
+ child = execFile(Deno.execPath(), ["run", script], {
+ encoding: "buffer",
+ maxBuffer: 3,
+ }, (err, _, stderr) => {
+ resolve({ err, stderr });
+ });
+ });
+ try {
+ const { err, stderr } = await promise;
+ if (child instanceof ChildProcess) {
+ assert(err);
+ assertEquals(
+ // deno-lint-ignore no-explicit-any
+ (err as any).code,
+ "ERR_CHILD_PROCESS_STDIO_MAXBUFFER",
+ );
+ assertEquals(err.message, "stderr maxBuffer length exceeded");
+ assertEquals((stderr as Buffer).toString("utf8"), "yik");
+ } else {
+ throw err;
+ }
+ } finally {
+ if (child instanceof ChildProcess) {
+ child.kill();
+ }
+ }
+ },
+});
+
+Deno.test({
+ name: "[node/child_process] ChildProcess.kill()",
+ async fn() {
+ const script = path.join(
+ path.dirname(path.fromFileUrl(import.meta.url)),
+ "./testdata/infinite_loop.js",
+ );
+ const childProcess = spawn(Deno.execPath(), ["run", script]);
+ const p = withTimeout(3000);
+ childProcess.on("exit", () => p.resolve());
+ childProcess.kill("SIGKILL");
+ await p;
+ assert(childProcess.killed);
+ assertEquals(childProcess.signalCode, "SIGKILL");
+ assertExists(childProcess.exitCode);
+ },
+});
+
+Deno.test({
+ name: "[node/child_process] ChildProcess.unref()",
+ async fn() {
+ const script = path.join(
+ path.dirname(path.fromFileUrl(import.meta.url)),
+ "testdata",
+ "child_process_unref.js",
+ );
+ const childProcess = spawn(Deno.execPath(), [
+ "run",
+ "-A",
+ "--unstable",
+ script,
+ ]);
+ const p = deferred();
+ childProcess.on("exit", () => p.resolve());
+ await p;
+ },
+});
+
+Deno.test({
+ name: "[node/child_process] child_process.fork",
+ async fn() {
+ const testdataDir = path.join(
+ path.dirname(path.fromFileUrl(import.meta.url)),
+ "testdata",
+ );
+ const script = path.join(
+ testdataDir,
+ "node_modules",
+ "foo",
+ "index.js",
+ );
+ const p = deferred();
+ const cp = CP.fork(script, [], { cwd: testdataDir, stdio: "pipe" });
+ let output = "";
+ cp.on("close", () => p.resolve());
+ cp.stdout?.on("data", (data) => {
+ output += data;
+ });
+ await p;
+ assertEquals(output, "foo\ntrue\ntrue\ntrue\n");
+ },
+});
+
+Deno.test("[node/child_process execFileSync] 'inherit' stdout and stderr", () => {
+ execFileSync(Deno.execPath(), ["--help"], { stdio: "inherit" });
+});
+
+Deno.test(
+ "[node/child_process spawn] supports windowsVerbatimArguments option",
+ { ignore: Deno.build.os !== "windows" },
+ async () => {
+ const cmdFinished = deferred();
+ let output = "";
+ const cp = spawn("cmd", ["/d", "/s", "/c", '"deno ^"--version^""'], {
+ stdio: "pipe",
+ windowsVerbatimArguments: true,
+ });
+ cp.on("close", () => cmdFinished.resolve());
+ cp.stdout?.on("data", (data) => {
+ output += data;
+ });
+ await cmdFinished;
+ assertStringIncludes(output, "deno");
+ assertStringIncludes(output, "v8");
+ assertStringIncludes(output, "typescript");
+ },
+);
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");
+ },
+});
diff --git a/cli/tests/unit_node/testdata/binary_stdio.js b/cli/tests/unit_node/testdata/binary_stdio.js
new file mode 100644
index 000000000..aa370a933
--- /dev/null
+++ b/cli/tests/unit_node/testdata/binary_stdio.js
@@ -0,0 +1,11 @@
+const buffer = new Uint8Array(10);
+const nread = await Deno.stdin.read(buffer);
+
+if (nread != 10) {
+ throw new Error("Too little data read");
+}
+
+const nwritten = await Deno.stdout.write(buffer);
+if (nwritten != 10) {
+ throw new Error("Too little data written");
+}
diff --git a/cli/tests/unit_node/testdata/child_process_unref.js b/cli/tests/unit_node/testdata/child_process_unref.js
new file mode 100644
index 000000000..cc7815d97
--- /dev/null
+++ b/cli/tests/unit_node/testdata/child_process_unref.js
@@ -0,0 +1,9 @@
+import cp from "node:child_process";
+import * as path from "node:path";
+
+const script = path.join(
+ path.dirname(path.fromFileUrl(import.meta.url)),
+ "infinite_loop.js",
+);
+const childProcess = cp.spawn(Deno.execPath(), ["run", script]);
+childProcess.unref();
diff --git a/cli/tests/unit_node/testdata/exec_file_text_error.js b/cli/tests/unit_node/testdata/exec_file_text_error.js
new file mode 100644
index 000000000..9697e6044
--- /dev/null
+++ b/cli/tests/unit_node/testdata/exec_file_text_error.js
@@ -0,0 +1,2 @@
+console.error("yikes!");
+Deno.exit(1);
diff --git a/cli/tests/unit_node/testdata/exec_file_text_output.js b/cli/tests/unit_node/testdata/exec_file_text_output.js
new file mode 100644
index 000000000..019c0f4bc
--- /dev/null
+++ b/cli/tests/unit_node/testdata/exec_file_text_output.js
@@ -0,0 +1 @@
+console.log("Hello World!");
diff --git a/cli/tests/unit_node/testdata/infinite_loop.js b/cli/tests/unit_node/testdata/infinite_loop.js
new file mode 100644
index 000000000..0e6540a7b
--- /dev/null
+++ b/cli/tests/unit_node/testdata/infinite_loop.js
@@ -0,0 +1,3 @@
+while (true) {
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+}
diff --git a/cli/tests/unit_node/testdata/node_modules/foo/index.js b/cli/tests/unit_node/testdata/node_modules/foo/index.js
new file mode 100644
index 000000000..24faba789
--- /dev/null
+++ b/cli/tests/unit_node/testdata/node_modules/foo/index.js
@@ -0,0 +1,4 @@
+console.log("foo");
+console.log(typeof require === "function");
+console.log(typeof module === "object");
+console.log(typeof exports === "object");
diff --git a/cli/tests/unit_node/testdata/node_modules/foo/package.json b/cli/tests/unit_node/testdata/node_modules/foo/package.json
new file mode 100644
index 000000000..bde99de92
--- /dev/null
+++ b/cli/tests/unit_node/testdata/node_modules/foo/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "foo"
+}
diff --git a/cli/tests/unit_node/testdata/process_exit.ts b/cli/tests/unit_node/testdata/process_exit.ts
new file mode 100644
index 000000000..57351c087
--- /dev/null
+++ b/cli/tests/unit_node/testdata/process_exit.ts
@@ -0,0 +1,19 @@
+import process from "node:process";
+
+//deno-lint-ignore no-undef
+process.on("exit", () => {
+ console.log(1);
+});
+
+function unexpected() {
+ console.log(null);
+}
+//deno-lint-ignore no-undef
+process.on("exit", unexpected);
+//deno-lint-ignore no-undef
+process.removeListener("exit", unexpected);
+
+//deno-lint-ignore no-undef
+process.on("exit", () => {
+ console.log(2);
+});
diff --git a/cli/tests/unit_node/testdata/process_exit2.ts b/cli/tests/unit_node/testdata/process_exit2.ts
new file mode 100644
index 000000000..3731f745a
--- /dev/null
+++ b/cli/tests/unit_node/testdata/process_exit2.ts
@@ -0,0 +1,4 @@
+import process from "node:process";
+
+process.on("exit", () => console.log("exit"));
+process.exit();
diff --git a/cli/tests/unit_node/testdata/process_stdin.ts b/cli/tests/unit_node/testdata/process_stdin.ts
new file mode 100644
index 000000000..23562b090
--- /dev/null
+++ b/cli/tests/unit_node/testdata/process_stdin.ts
@@ -0,0 +1,11 @@
+import process from "node:process";
+
+console.log(process.stdin.readableHighWaterMark);
+
+process.stdin.setEncoding("utf8");
+process.stdin.on("readable", () => {
+ console.log(process.stdin.read());
+});
+process.stdin.on("end", () => {
+ console.log("end");
+});
diff --git a/cli/tests/unit_node/testdata/process_stdin_dummy.txt b/cli/tests/unit_node/testdata/process_stdin_dummy.txt
new file mode 100644
index 000000000..a907ec3f4
--- /dev/null
+++ b/cli/tests/unit_node/testdata/process_stdin_dummy.txt
@@ -0,0 +1,2 @@
+foo
+bar \ No newline at end of file
diff --git a/core/runtime.rs b/core/runtime.rs
index a2b011362..25a09e85e 100644
--- a/core/runtime.rs
+++ b/core/runtime.rs
@@ -841,14 +841,7 @@ impl JsRuntime {
)
.await?;
let receiver = runtime.mod_evaluate(id);
- poll_fn(|cx| {
- let r = runtime.poll_event_loop(cx, false);
- // TODO(bartlomieju): some code in readable-stream polyfill in `ext/node`
- // is calling `nextTick()` during snapshotting, which causes infinite loop
- runtime.state.borrow_mut().has_tick_scheduled = false;
- r
- })
- .await?;
+ runtime.run_event_loop(false).await?;
receiver.await?
})
.with_context(|| format!("Couldn't execute '{}'", file_source.specifier))
diff --git a/ext/node/01_node.js b/ext/node/01_node.js
index 543a559f1..85346a44b 100644
--- a/ext/node/01_node.js
+++ b/ext/node/01_node.js
@@ -110,7 +110,7 @@ function initialize(nodeModules, nodeGlobalThisName) {
value: nodeGlobalThis,
});
// FIXME(bartlomieju): not nice to depend on `Deno` namespace here
- internals.__bootstrapNodeProcess(Deno.args);
+ internals.__bootstrapNodeProcess(Deno.args, Deno.version);
}
internals.node = {
diff --git a/ext/node/polyfills/_next_tick.ts b/ext/node/polyfills/_next_tick.ts
index d5aa88218..72a6fc120 100644
--- a/ext/node/polyfills/_next_tick.ts
+++ b/ext/node/polyfills/_next_tick.ts
@@ -11,6 +11,11 @@ interface Tock {
args: Array<unknown>;
}
+let nextTickEnabled = false;
+export function enableNextTick() {
+ nextTickEnabled = true;
+}
+
const queue = new FixedQueue();
export function processTicksAndRejections() {
@@ -71,8 +76,6 @@ export function runNextTicks() {
// return;
if (!core.hasTickScheduled()) {
core.runMicrotasks();
- }
- if (!core.hasTickScheduled()) {
return true;
}
@@ -93,6 +96,12 @@ export function nextTick<T extends Array<unknown>>(
callback: (...args: T) => void,
...args: T
) {
+ // If we're snapshotting we don't want to push nextTick to be run. We'll
+ // enable next ticks in "__bootstrapNodeProcess()";
+ if (!nextTickEnabled) {
+ return;
+ }
+
validateFunction(callback, "callback");
if (_exiting) {
diff --git a/ext/node/polyfills/_process/process.ts b/ext/node/polyfills/_process/process.ts
index 48b2e4620..24a4ae1a2 100644
--- a/ext/node/polyfills/_process/process.ts
+++ b/ext/node/polyfills/_process/process.ts
@@ -126,5 +126,8 @@ export const versions = {
unicode: "14.0",
ngtcp2: "0.8.1",
nghttp3: "0.7.0",
- ...Deno.version,
+ // Will be filled when calling "__bootstrapNodeProcess()",
+ deno: "",
+ v8: "",
+ typescript: "",
};
diff --git a/ext/node/polyfills/_process/streams.mjs b/ext/node/polyfills/_process/streams.mjs
index 46213a4ed..b27f75e2d 100644
--- a/ext/node/polyfills/_process/streams.mjs
+++ b/ext/node/polyfills/_process/streams.mjs
@@ -9,13 +9,12 @@ import {
moveCursor,
} from "internal:deno_node/polyfills/internal/readline/callbacks.mjs";
import { Duplex, Readable, Writable } from "internal:deno_node/polyfills/stream.ts";
-import { stdio } from "internal:deno_node/polyfills/_process/stdio.mjs";
import { isWindows } from "internal:deno_node/polyfills/_util/os.ts";
import { fs as fsConstants } from "internal:deno_node/polyfills/internal_binding/constants.ts";
import * as files from "internal:runtime/js/40_files.js";
// https://github.com/nodejs/node/blob/00738314828074243c9a52a228ab4c68b04259ef/lib/internal/bootstrap/switches/is_main_thread.js#L41
-function createWritableStdioStream(writer, name) {
+export function createWritableStdioStream(writer, name) {
const stream = new Writable({
write(buf, enc, cb) {
if (!writer) {
@@ -92,18 +91,6 @@ function createWritableStdioStream(writer, name) {
return stream;
}
-/** https://nodejs.org/api/process.html#process_process_stderr */
-export const stderr = stdio.stderr = createWritableStdioStream(
- files.stderr,
- "stderr",
-);
-
-/** https://nodejs.org/api/process.html#process_process_stdout */
-export const stdout = stdio.stdout = createWritableStdioStream(
- files.stdout,
- "stdout",
-);
-
// TODO(PolarETech): This function should be replaced by
// `guessHandleType()` in "../internal_binding/util.ts".
// https://github.com/nodejs/node/blob/v18.12.1/src/node_util.cc#L257
@@ -162,9 +149,10 @@ const _read = function (size) {
/** https://nodejs.org/api/process.html#process_process_stdin */
// https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L189
-export const stdin = stdio.stdin = (() => {
+/** Create process.stdin */
+export const initStdin = () => {
const fd = files.stdin?.rid;
- let _stdin;
+ let stdin;
const stdinType = _guessStdinType(fd);
switch (stdinType) {
@@ -173,7 +161,7 @@ export const stdin = stdio.stdin = (() => {
// use `Readable` instead.
// https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L200
// https://github.com/nodejs/node/blob/v18.12.1/lib/internal/fs/streams.js#L148
- _stdin = new Readable({
+ stdin = new Readable({
highWaterMark: 64 * 1024,
autoDestroy: false,
read: _read,
@@ -197,7 +185,7 @@ export const stdin = stdio.stdin = (() => {
// 2. Creating a net.Socket() from a fd is not currently supported.
// https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L206
// https://github.com/nodejs/node/blob/v18.12.1/lib/net.js#L329
- _stdin = new Duplex({
+ stdin = new Duplex({
readable: stdinType === "TTY" ? undefined : true,
writable: stdinType === "TTY" ? undefined : false,
readableHighWaterMark: stdinType === "TTY" ? 0 : undefined,
@@ -210,39 +198,41 @@ export const stdin = stdio.stdin = (() => {
if (stdinType !== "TTY") {
// Make sure the stdin can't be `.end()`-ed
- _stdin._writableState.ended = true;
+ stdin._writableState.ended = true;
}
break;
}
default: {
// Provide a dummy contentless input for e.g. non-console
// Windows applications.
- _stdin = new Readable({ read() {} });
- _stdin.push(null);
+ stdin = new Readable({ read() {} });
+ stdin.push(null);
}
}
- return _stdin;
-})();
-stdin.on("close", () => files.stdin?.close());
-stdin.fd = files.stdin?.rid ?? -1;
-Object.defineProperty(stdin, "isTTY", {
- enumerable: true,
- configurable: true,
- get() {
- return Deno.isatty?.(Deno.stdin.rid);
- },
-});
-stdin._isRawMode = false;
-stdin.setRawMode = (enable) => {
- files.stdin?.setRaw?.(enable);
- stdin._isRawMode = enable;
+ stdin.on("close", () => files.stdin?.close());
+ stdin.fd = files.stdin?.rid ?? -1;
+ Object.defineProperty(stdin, "isTTY", {
+ enumerable: true,
+ configurable: true,
+ get() {
+ return Deno.isatty?.(Deno.stdin.rid);
+ },
+ });
+ stdin._isRawMode = false;
+ stdin.setRawMode = (enable) => {
+ files.stdin?.setRaw?.(enable);
+ stdin._isRawMode = enable;
+ return stdin;
+ };
+ Object.defineProperty(stdin, "isRaw", {
+ enumerable: true,
+ configurable: true,
+ get() {
+ return stdin._isRawMode;
+ },
+ });
+
return stdin;
};
-Object.defineProperty(stdin, "isRaw", {
- enumerable: true,
- configurable: true,
- get() {
- return stdin._isRawMode;
- },
-});
+
diff --git a/ext/node/polyfills/_readline.mjs b/ext/node/polyfills/_readline.mjs
index 6e0968af0..0665dbcf3 100644
--- a/ext/node/polyfills/_readline.mjs
+++ b/ext/node/polyfills/_readline.mjs
@@ -33,7 +33,7 @@ import promises from "internal:deno_node/polyfills/readline/promises.ts";
import { validateAbortSignal } from "internal:deno_node/polyfills/internal/validators.mjs";
import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
import { AbortError } from "internal:deno_node/polyfills/internal/errors.ts";
-import { process } from "internal:deno_node/polyfills/process.ts";
+import process from "internal:deno_node/polyfills/process.ts";
import {
Interface as _Interface,
diff --git a/ext/node/polyfills/child_process.ts b/ext/node/polyfills/child_process.ts
index 06269e025..cc0e17ffb 100644
--- a/ext/node/polyfills/child_process.ts
+++ b/ext/node/polyfills/child_process.ts
@@ -40,7 +40,7 @@ import {
promisify,
} from "internal:deno_node/polyfills/util.ts";
import { createDeferredPromise } from "internal:deno_node/polyfills/internal/util.mjs";
-import { process } from "internal:deno_node/polyfills/process.ts";
+import process from "internal:deno_node/polyfills/process.ts";
import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
import {
convertToValidSignal,
diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts
index 828b4c660..42c55ccc5 100644
--- a/ext/node/polyfills/process.ts
+++ b/ext/node/polyfills/process.ts
@@ -29,15 +29,17 @@ import {
import { _exiting } from "internal:deno_node/polyfills/_process/exiting.ts";
export { _nextTick as nextTick, chdir, cwd, env, version, versions };
import {
- stderr as stderr_,
- stdin as stdin_,
- stdout as stdout_,
+ createWritableStdioStream,
+ initStdin,
} from "internal:deno_node/polyfills/_process/streams.mjs";
+import { stdio } from "internal:deno_node/polyfills/_process/stdio.mjs";
import {
+ enableNextTick,
processTicksAndRejections,
runNextTicks,
} from "internal:deno_node/polyfills/_next_tick.ts";
import { isWindows } from "internal:deno_node/polyfills/_util/os.ts";
+import * as files from "internal:runtime/js/40_files.js";
// TODO(kt3k): This should be set at start up time
export let arch = "";
@@ -50,11 +52,11 @@ export let pid = 0;
// TODO(kt3k): Give better types to stdio objects
// deno-lint-ignore no-explicit-any
-const stderr = stderr_ as any;
+let stderr = null as any;
// deno-lint-ignore no-explicit-any
-const stdin = stdin_ as any;
+let stdin = null as any;
// deno-lint-ignore no-explicit-any
-const stdout = stdout_ as any;
+let stdout = null as any;
export { stderr, stdin, stdout };
import { getBinding } from "internal:deno_node/polyfills/internal_binding/mod.ts";
@@ -663,13 +665,10 @@ class Process extends EventEmitter {
noDeprecation = false;
}
-// TODO(kt3k): Do the below at start up time.
-/*
-if (Deno.build.os === "windows") {
+if (isWindows) {
delete Process.prototype.getgid;
delete Process.prototype.getuid;
}
-*/
/** https://nodejs.org/api/process.html#process_process */
const process = new Process();
@@ -689,13 +688,21 @@ export const removeAllListeners = process.removeAllListeners;
// Should be called only once, in `runtime/js/99_main.js` when the runtime is
// bootstrapped.
-internals.__bootstrapNodeProcess = function (args: string[]) {
+internals.__bootstrapNodeProcess = function (
+ args: string[],
+ denoVersions: Record<string, string>,
+) {
for (let i = 0; i < args.length; i++) {
argv[i + 2] = args[i];
}
+ for (const [key, value] of Object.entries(denoVersions)) {
+ versions[key] = value;
+ }
+
core.setNextTickCallback(processTicksAndRejections);
core.setMacrotaskCallback(runNextTicks);
+ enableNextTick();
// TODO(bartlomieju): this is buggy, see https://github.com/denoland/deno/issues/16928
// We should use a specialized API in 99_main.js instead
@@ -740,12 +747,22 @@ internals.__bootstrapNodeProcess = function (args: string[]) {
}
});
+ // Initializes stdin
+ stdin = stdio.stdin = process.stdin = initStdin();
+
+ /** https://nodejs.org/api/process.html#process_process_stderr */
+ stderr = stdio.stderr = process.stderr = createWritableStdioStream(
+ files.stderr,
+ "stderr",
+ );
+
+ /** https://nodejs.org/api/process.html#process_process_stdout */
+ stdout = stdio.stdout = process.stdout = createWritableStdioStream(
+ files.stdout,
+ "stdout",
+ );
+
delete internals.__bootstrapNodeProcess;
};
export default process;
-
-//TODO(Soremwar)
-//Remove on 1.0
-//Kept for backwards compatibility with std
-export { process };
diff --git a/ext/node/polyfills/timers.ts b/ext/node/polyfills/timers.ts
index 57ff71a38..5a650c1cc 100644
--- a/ext/node/polyfills/timers.ts
+++ b/ext/node/polyfills/timers.ts
@@ -7,9 +7,10 @@ import {
import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs";
import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
export { setUnrefTimeout } from "internal:deno_node/polyfills/internal/timers.mjs";
+import * as timers from "internal:deno_web/02_timers.js";
-const clearTimeout_ = globalThis.clearTimeout;
-const clearInterval_ = globalThis.clearInterval;
+const clearTimeout_ = timers.clearTimeout;
+const clearInterval_ = timers.clearInterval;
export function setTimeout(
callback: (...args: unknown[]) => void,
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 399d12d38..fa9b0a20d 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -515,11 +515,6 @@ function bootstrapMainRuntime(runtimeOptions) {
ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs));
util.log("args", runtimeOptions.args);
-
- // FIXME(bartlomieju): this should be a helper function that is placed in
- // "internals" namespace
- // Initialize Node polyfills
- // internals.__bootstrapNodeProcess();
}
function bootstrapWorkerRuntime(
diff --git a/tools/copyright_checker.js b/tools/copyright_checker.js
index c8ddcdc91..8adab7a43 100644
--- a/tools/copyright_checker.js
+++ b/tools/copyright_checker.js
@@ -29,6 +29,7 @@ export async function checkCopyright() {
":!:cli/tsc/compiler.d.ts",
":!:test_util/wpt/**",
":!:cli/tools/init/templates/**",
+ ":!:cli/tests/unit_node/testdata/**",
// rust
"*.rs",