aboutsummaryrefslogtreecommitdiff
path: root/test/js/node
diff options
context:
space:
mode:
Diffstat (limited to 'test/js/node')
-rw-r--r--test/js/node/async_hooks/async_hooks.node.test.ts28
-rw-r--r--test/js/node/buffer.test.js11
-rw-r--r--test/js/node/child_process/child_process.test.ts22
-rw-r--r--test/js/node/child_process/fixtures/child-process-exit-event.js13
-rw-r--r--test/js/node/console/console.test.ts90
-rw-r--r--test/js/node/crypto/crypto.hmac.test.ts426
-rw-r--r--test/js/node/crypto/crypto.key-objects.test.ts1643
-rw-r--r--test/js/node/crypto/fixtures/ec_p256_private.pem5
-rw-r--r--test/js/node/crypto/fixtures/ec_p256_public.pem4
-rw-r--r--test/js/node/crypto/fixtures/ec_p384_private.pem6
-rw-r--r--test/js/node/crypto/fixtures/ec_p384_public.pem5
-rw-r--r--test/js/node/crypto/fixtures/ec_p521_private.pem8
-rw-r--r--test/js/node/crypto/fixtures/ec_p521_public.pem6
-rw-r--r--test/js/node/crypto/fixtures/ec_secp256k1_private.pem5
-rw-r--r--test/js/node/crypto/fixtures/ec_secp256k1_public.pem4
-rw-r--r--test/js/node/crypto/fixtures/ed25519_private.pem3
-rw-r--r--test/js/node/crypto/fixtures/ed25519_public.pem3
-rw-r--r--test/js/node/crypto/fixtures/ed448_private.pem4
-rw-r--r--test/js/node/crypto/fixtures/ed448_public.pem4
-rw-r--r--test/js/node/crypto/fixtures/rsa_private.pem27
-rw-r--r--test/js/node/crypto/fixtures/rsa_private_2048.pem27
-rw-r--r--test/js/node/crypto/fixtures/rsa_private_4096.pem51
-rw-r--r--test/js/node/crypto/fixtures/rsa_private_encrypted.pem30
-rw-r--r--test/js/node/crypto/fixtures/rsa_pss_private_2048.pem28
-rw-r--r--test/js/node/crypto/fixtures/rsa_pss_public_2048.pem9
-rw-r--r--test/js/node/crypto/fixtures/rsa_public.pem9
-rw-r--r--test/js/node/crypto/fixtures/rsa_public_2048.pem9
-rw-r--r--test/js/node/crypto/fixtures/rsa_public_4096.pem14
-rw-r--r--test/js/node/crypto/fixtures/x25519_private.pem3
-rw-r--r--test/js/node/crypto/fixtures/x25519_public.pem3
-rw-r--r--test/js/node/crypto/fixtures/x448_private.pem4
-rw-r--r--test/js/node/crypto/fixtures/x448_public.pem4
-rw-r--r--test/js/node/dns/node-dns.test.js141
-rw-r--r--test/js/node/events/event-emitter.test.ts83
-rw-r--r--test/js/node/fs/fs.test.ts121
-rw-r--r--test/js/node/http/fixtures/cert.key28
-rw-r--r--test/js/node/http/fixtures/cert.pem23
-rw-r--r--test/js/node/http/node-http.test.ts197
-rw-r--r--test/js/node/module/modulePrototypeOverwrite-fixture.cjs1
-rw-r--r--test/js/node/module/modulePrototypeOverwrite.cjs17
-rw-r--r--test/js/node/module/node-module-module.test.js48
-rw-r--r--test/js/node/module/resolveFilenameOverwrite-fixture.cjs1
-rw-r--r--test/js/node/module/resolveFilenameOverwrite.cjs14
-rw-r--r--test/js/node/net/node-net.test.ts37
-rw-r--r--test/js/node/os/os.test.js10
-rw-r--r--test/js/node/path/path.test.js13
-rw-r--r--test/js/node/process-binding.test.ts26
-rw-r--r--test/js/node/process/print-process-args.js4
-rw-r--r--test/js/node/process/process-stdio.test.ts2
-rw-r--r--test/js/node/process/process.test.js27
-rw-r--r--test/js/node/stream/node-stream.test.js12
-rw-r--r--test/js/node/tty.test.ts25
-rw-r--r--test/js/node/util/node-inspect-tests/import.test.mjs9
-rw-r--r--test/js/node/util/node-inspect-tests/internal-inspect.test.js57
-rw-r--r--test/js/node/util/node-inspect-tests/parallel/util-format.test.js489
-rw-r--r--test/js/node/util/node-inspect-tests/parallel/util-inspect-getters-accessing-this.test.js60
-rw-r--r--test/js/node/util/node-inspect-tests/parallel/util-inspect-long-running.test.mjs27
-rw-r--r--test/js/node/util/node-inspect-tests/parallel/util-inspect-proxy.test.js177
-rw-r--r--test/js/node/util/node-inspect-tests/parallel/util-inspect.test.js3294
-rw-r--r--test/js/node/v8/capture-stack-trace.test.js81
-rw-r--r--test/js/node/v8/v8-date-parser.test.js462
-rw-r--r--test/js/node/vm/vm.test.ts5
-rw-r--r--test/js/node/watch/fs.watch.test.ts30
-rw-r--r--test/js/node/worker_threads/worker_threads.test.ts63
64 files changed, 8043 insertions, 49 deletions
diff --git a/test/js/node/async_hooks/async_hooks.node.test.ts b/test/js/node/async_hooks/async_hooks.node.test.ts
index 3d6183948..5fc56a39b 100644
--- a/test/js/node/async_hooks/async_hooks.node.test.ts
+++ b/test/js/node/async_hooks/async_hooks.node.test.ts
@@ -1,13 +1,13 @@
-import { AsyncLocalStorage } from "async_hooks";
+import { AsyncLocalStorage, AsyncResource } from "async_hooks";
import assert from "assert";
test("node async_hooks.AsyncLocalStorage enable disable", async done => {
- const asyncLocalStorage = new AsyncLocalStorage();
+ const asyncLocalStorage = new AsyncLocalStorage<Map<string, any>>();
asyncLocalStorage.run(new Map(), () => {
- asyncLocalStorage.getStore().set("foo", "bar");
+ asyncLocalStorage.getStore()!.set("foo", "bar");
process.nextTick(() => {
- assert.strictEqual(asyncLocalStorage.getStore().get("foo"), "bar");
+ assert.strictEqual(asyncLocalStorage.getStore()!.get("foo"), "bar");
process.nextTick(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
});
@@ -24,7 +24,7 @@ test("node async_hooks.AsyncLocalStorage enable disable", async done => {
process.nextTick(() => {
assert.strictEqual(asyncLocalStorage.getStore(), undefined);
asyncLocalStorage.run(new Map().set("bar", "foo"), () => {
- assert.strictEqual(asyncLocalStorage.getStore().get("bar"), "foo");
+ assert.strictEqual(asyncLocalStorage.getStore()!.get("bar"), "foo");
done();
});
@@ -32,3 +32,21 @@ test("node async_hooks.AsyncLocalStorage enable disable", async done => {
});
});
});
+
+test("AsyncResource.prototype.bind", () => {
+ const localStorage = new AsyncLocalStorage<true>();
+ let ar!: AsyncResource;
+ localStorage.run(true, () => {
+ ar = new AsyncResource("test");
+ });
+ expect(ar.bind(() => localStorage.getStore())()).toBe(true);
+});
+
+test("AsyncResource.bind", () => {
+ const localStorage = new AsyncLocalStorage<true>();
+ let fn!: () => true | undefined;
+ localStorage.run(true, () => {
+ fn = AsyncResource.bind(() => localStorage.getStore());
+ });
+ expect(fn()).toBe(true);
+});
diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js
index afc9cdee8..0256934ce 100644
--- a/test/js/node/buffer.test.js
+++ b/test/js/node/buffer.test.js
@@ -2585,3 +2585,14 @@ it("construct buffer from hex, issue #4919", () => {
expect(buf1).toStrictEqual(Buffer.from([]));
expect(buf2).toStrictEqual(Buffer.from([0x63, 0xe9, 0xf6, 0xc4, 0xb0, 0x4f, 0xa8, 0xc8, 0x0f, 0x3f, 0xb0, 0xee]));
});
+
+it("new Buffer.alloc()", () => {
+ const buf = new Buffer.alloc(10);
+ expect(buf.length).toBe(10);
+ expect(buf[0]).toBe(0);
+});
+
+it("new Buffer.from()", () => {
+ const buf = new Buffer.from("🥶");
+ expect(buf.length).toBe(4);
+});
diff --git a/test/js/node/child_process/child_process.test.ts b/test/js/node/child_process/child_process.test.ts
index baf422bc9..8c24ef55e 100644
--- a/test/js/node/child_process/child_process.test.ts
+++ b/test/js/node/child_process/child_process.test.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from "bun:test";
import { ChildProcess, spawn, execFile, exec, fork, spawnSync, execFileSync, execSync } from "node:child_process";
import { tmpdir } from "node:os";
import { promisify } from "node:util";
+import { bunExe, bunEnv } from "harness";
+import path from "path";
const debug = process.env.DEBUG ? console.log : () => {};
@@ -308,3 +310,23 @@ describe("Bun.spawn()", () => {
// expect(child.pid).toBe(undefined);
// });
});
+
+it("should call close and exit before process exits", async () => {
+ const proc = Bun.spawn({
+ cmd: [bunExe(), path.join("fixtures", "child-process-exit-event.js")],
+ cwd: import.meta.dir,
+ env: bunEnv,
+ stdout: "pipe",
+ });
+ await proc.exited;
+ expect(proc.exitCode).toBe(0);
+ let data = "";
+ const reader = proc.stdout.getReader();
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ data += new TextDecoder().decode(value);
+ }
+ expect(data).toContain("closeHandler called");
+ expect(data).toContain("exithHandler called");
+});
diff --git a/test/js/node/child_process/fixtures/child-process-exit-event.js b/test/js/node/child_process/fixtures/child-process-exit-event.js
new file mode 100644
index 000000000..4400ace1b
--- /dev/null
+++ b/test/js/node/child_process/fixtures/child-process-exit-event.js
@@ -0,0 +1,13 @@
+const { spawn } = require("node:child_process");
+
+function exitHandler() {
+ console.log("exithHandler called");
+}
+function closeHandler() {
+ console.log("closeHandler called");
+}
+
+const p = spawn("bun", ["--version"]);
+
+p.on("exit", exitHandler);
+p.on("close", closeHandler);
diff --git a/test/js/node/console/console.test.ts b/test/js/node/console/console.test.ts
new file mode 100644
index 000000000..4585f7cfb
--- /dev/null
+++ b/test/js/node/console/console.test.ts
@@ -0,0 +1,90 @@
+import { test, describe, expect } from "bun:test";
+import { Console } from "node:console";
+
+import { Writable } from "node:stream";
+
+function writable() {
+ let intoString = "";
+ const { promise, resolve } = Promise.withResolvers();
+ const stream = new Writable({
+ write(chunk) {
+ intoString += chunk.toString();
+ },
+ destroy() {
+ resolve(intoString);
+ },
+ autoDestroy: true,
+ });
+
+ (stream as any).write = (chunk: any) => {
+ intoString += Buffer.from(chunk).toString("utf-8");
+ };
+
+ return [stream, () => promise] as const;
+}
+
+describe("console.Console", () => {
+ test("global instanceof Console", () => {
+ expect(global.console).toBeInstanceOf(Console);
+ });
+
+ test("new Console instanceof Console", () => {
+ const c = new Console({ stdout: process.stdout, stderr: process.stderr });
+ expect(c).toBeInstanceOf(Console);
+ });
+
+ test("it can write to a stream", async () => {
+ console.log();
+ const [stream, value] = writable();
+ const c = new Console({ stdout: stream, stderr: stream, colorMode: false });
+ c.log("hello");
+ c.log({ foo: "bar" });
+ stream.end();
+ expect(await value()).toBe("hello\n{ foo: 'bar' }\n");
+ });
+
+ test("can enable colors", async () => {
+ const [stream, value] = writable();
+ const c = new Console({ stdout: stream, stderr: stream, colorMode: true });
+ c.log("hello");
+ c.log({ foo: "bar" });
+ stream.end();
+ expect(await value()).toBe("hello\n{ foo: \u001B[32m'bar'\u001B[39m }\n");
+ });
+
+ test("stderr and stdout are separate", async () => {
+ const [out, outValue] = writable();
+ const [err, errValue] = writable();
+ const c = new Console({ stdout: out, stderr: err });
+ c.log("hello world!");
+ c.error("uh oh!");
+ out.end();
+ err.end();
+ expect(await outValue()).toBe("hello world!\n");
+ expect(await errValue()).toBe("uh oh!\n");
+ });
+});
+
+test("console._stdout", () => {
+ // @ts-ignore
+ expect(console._stdout).toBe(process.stdout);
+
+ expect(Object.getOwnPropertyDescriptor(console, "_stdout")).toEqual({
+ value: process.stdout,
+ writable: true,
+ enumerable: false,
+ configurable: true,
+ });
+});
+
+test("console._stderr", () => {
+ // @ts-ignore
+ expect(console._stderr).toBe(process.stderr);
+
+ expect(Object.getOwnPropertyDescriptor(console, "_stderr")).toEqual({
+ value: process.stderr,
+ writable: true,
+ enumerable: false,
+ configurable: true,
+ });
+});
diff --git a/test/js/node/crypto/crypto.hmac.test.ts b/test/js/node/crypto/crypto.hmac.test.ts
new file mode 100644
index 000000000..6a54d1a82
--- /dev/null
+++ b/test/js/node/crypto/crypto.hmac.test.ts
@@ -0,0 +1,426 @@
+import { Hmac, createHmac, createSecretKey } from "crypto";
+import { test, expect, describe } from "bun:test";
+
+function testHmac(algo, key, data, expected) {
+ if (!Array.isArray(data)) data = [data];
+ // If the key is a Buffer, test Hmac with a key object as well.
+ const keyWrappers = [key => key, ...(typeof key === "string" ? [] : [createSecretKey])];
+ const wrapperName = ["default", "KeyObject"];
+
+ for (const i in keyWrappers) {
+ const keyWrapper = keyWrappers[i];
+ test(`Hmac ${algo} with ${wrapperName[i]} key`, async () => {
+ const hmac = createHmac(algo, keyWrapper(key));
+ for (const chunk of data) hmac.update(chunk);
+ const actual = hmac.digest("hex");
+ expect(actual).toEqual(expected);
+ });
+ }
+}
+
+describe("crypto.Hmac", () => {
+ test.todo("Hmac is expected to return a new instance", async () => {
+ const instance = Hmac("sha256", "Node");
+ expect(instance instanceof Hmac).toBe(true);
+ });
+
+ test("createHmac should throw when using invalid options", async () => {
+ expect(() => createHmac(null)).toThrow("null is not an object");
+ expect(() => createHmac("sha1", null)).toThrow("null is not an object");
+ });
+
+ describe("test HMAC with multiple updates.", async () => {
+ testHmac("sha1", "Node", ["some data", "to hmac"], "19fd6e1ba73d9ed2224dd5094a71babe85d9a892");
+ });
+
+ describe("test HMAC with Wikipidia test cases", async () => {
+ const wikipedia = [
+ {
+ key: "key",
+ data: "The quick brown fox jumps over the lazy dog",
+ hmac: {
+ // HMACs lifted from Wikipedia.
+ md5: "80070713463e7749b90c2dc24911e275",
+ sha1: "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9",
+ sha256: "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc" + "2d1a3cd8",
+ },
+ },
+ {
+ key: "key",
+ data: "",
+ hmac: {
+ // Intermediate test to help debugging.
+ md5: "63530468a04e386459855da0063b6596",
+ sha1: "f42bb0eeb018ebbd4597ae7213711ec60760843f",
+ sha256: "5d5d139563c95b5967b9bd9a8c9b233a9dedb45072794cd232dc1b74" + "832607d0",
+ },
+ },
+ {
+ key: "",
+ data: "The quick brown fox jumps over the lazy dog",
+ hmac: {
+ // Intermediate test to help debugging.
+ md5: "ad262969c53bc16032f160081c4a07a0",
+ sha1: "2ba7f707ad5f187c412de3106583c3111d668de8",
+ sha256: "fb011e6154a19b9a4c767373c305275a5a69e8b68b0b4c9200c383dc" + "ed19a416",
+ },
+ },
+ {
+ key: "",
+ data: "",
+ hmac: {
+ // HMACs lifted from Wikipedia.
+ md5: "74e6f7298a9c2d168935f58c001bad88",
+ sha1: "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d",
+ sha256: "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c71214" + "4292c5ad",
+ },
+ },
+ ];
+
+ for (const { key, data, hmac } of wikipedia) {
+ for (const hash in hmac) testHmac(hash, key, data, hmac[hash]);
+ }
+ });
+
+ describe("HMAC-SHA-* (rfc 4231 Test Cases)", async () => {
+ const rfc4231 = [
+ {
+ key: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"),
+ data: Buffer.from("4869205468657265", "hex"), // 'Hi There'
+ hmac: {
+ sha224: "896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22",
+ sha256: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c" + "2e32cff7",
+ sha384:
+ "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c" + "7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6",
+ sha512:
+ "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b305" +
+ "45e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f170" +
+ "2e696c203a126854",
+ },
+ },
+ {
+ key: Buffer.from("4a656665", "hex"), // 'Jefe'
+ data: Buffer.from("7768617420646f2079612077616e7420666f72206e6f74686" + "96e673f", "hex"), // 'what do ya want for nothing?'
+ hmac: {
+ sha224: "a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44",
+ sha256: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b9" + "64ec3843",
+ sha384:
+ "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373" + "6322445e8e2240ca5e69e2c78b3239ecfab21649",
+ sha512:
+ "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7" +
+ "ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b" +
+ "636e070a38bce737",
+ },
+ },
+ {
+ key: Buffer.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "hex"),
+ data: Buffer.from(
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd" + "ddddddddddddddddddddddddddddddddddddddddddddddddddd",
+ "hex",
+ ),
+ hmac: {
+ sha224: "7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea",
+ sha256: "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514" + "ced565fe",
+ sha384:
+ "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5" + "5966144b2a5ab39dc13814b94e3ab6e101a34f27",
+ sha512:
+ "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33" +
+ "b2279d39bf3e848279a722c806b485a47e67c807b946a337bee89426" +
+ "74278859e13292fb",
+ },
+ },
+ {
+ key: Buffer.from("0102030405060708090a0b0c0d0e0f10111213141516171819", "hex"),
+ data: Buffer.from(
+ "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc" + "dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
+ "hex",
+ ),
+ hmac: {
+ sha224: "6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a",
+ sha256: "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff4" + "6729665b",
+ sha384:
+ "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e" + "1f573b4e6801dd23c4a7d679ccf8a386c674cffb",
+ sha512:
+ "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050" +
+ "361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2d" +
+ "e2adebeb10a298dd",
+ },
+ },
+
+ {
+ key: Buffer.from("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "hex"),
+ // 'Test With Truncation'
+ data: Buffer.from("546573742057697468205472756e636174696f6e", "hex"),
+ hmac: {
+ sha224: "0e2aea68a90c8d37c988bcdb9fca6fa8",
+ sha256: "a3b6167473100ee06e0c796c2955552b",
+ sha384: "3abf34c3503b2a23a46efc619baef897",
+ sha512: "415fad6271580a531d4179bc891d87a6",
+ },
+ truncate: true,
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaa",
+ "hex",
+ ),
+ // 'Test Using Larger Than Block-Size Key - Hash Key First'
+ data: Buffer.from(
+ "54657374205573696e67204c6172676572205468616e20426" +
+ "c6f636b2d53697a65204b6579202d2048617368204b657920" +
+ "4669727374",
+ "hex",
+ ),
+ hmac: {
+ sha224: "95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e",
+ sha256: "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f" + "0ee37f54",
+ sha384:
+ "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05" + "033ac4c60c2ef6ab4030fe8296248df163f44952",
+ sha512:
+ "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b0137" +
+ "83f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec" +
+ "8b915a985d786598",
+ },
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaa",
+ "hex",
+ ),
+ // 'This is a test using a larger than block-size key and a larger ' +
+ // 'than block-size data. The key needs to be hashed before being ' +
+ // 'used by the HMAC algorithm.'
+ data: Buffer.from(
+ "5468697320697320612074657374207573696e672061206c6" +
+ "172676572207468616e20626c6f636b2d73697a65206b6579" +
+ "20616e642061206c6172676572207468616e20626c6f636b2" +
+ "d73697a6520646174612e20546865206b6579206e65656473" +
+ "20746f20626520686173686564206265666f7265206265696" +
+ "e6720757365642062792074686520484d414320616c676f72" +
+ "6974686d2e",
+ "hex",
+ ),
+ hmac: {
+ sha224: "3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1",
+ sha256: "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f5153" + "5c3a35e2",
+ sha384:
+ "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82" + "461e99c5a678cc31e799176d3860e6110c46523e",
+ sha512:
+ "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d" +
+ "20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de04460" +
+ "65c97440fa8c6a58",
+ },
+ },
+ ];
+
+ for (let i = 0, l = rfc4231.length; i < l; i++) {
+ for (const hash in rfc4231[i].hmac) {
+ test(`Test HMAC-${hash} rfc 4231 case ${i + 1}`, async () => {
+ const str = createHmac(hash, rfc4231[i].key);
+ str.end(rfc4231[i].data);
+ let strRes = str.read().toString("hex");
+ let actual = createHmac(hash, rfc4231[i].key).update(rfc4231[i].data).digest("hex");
+ if (rfc4231[i].truncate) {
+ actual = actual.substr(0, 32); // first 128 bits == 32 hex chars
+ strRes = strRes.substr(0, 32);
+ }
+ const expected = rfc4231[i].hmac[hash];
+ // `Test HMAC-${hash} rfc 4231 case ${i + 1}: ${actual} must be ${expected}`);
+ expect(actual).toEqual(expected);
+ expect(actual).toEqual(strRes);
+ });
+ }
+ }
+ });
+
+ describe("HMAC-MD5/SHA1 (rfc 2202 Test Cases)", async () => {
+ const rfc2202_md5 = [
+ {
+ key: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"),
+ data: "Hi There",
+ hmac: "9294727a3638bb1c13f48ef8158bfc9d",
+ },
+ {
+ key: "Jefe",
+ data: "what do ya want for nothing?",
+ hmac: "750c783e6ab0b503eaa86e310a5db738",
+ },
+ {
+ key: Buffer.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "hex"),
+ data: Buffer.from(
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd" + "ddddddddddddddddddddddddddddddddddddddddddddddddddd",
+ "hex",
+ ),
+ hmac: "56be34521d144c88dbb8c733f0e8b3f6",
+ },
+ {
+ key: Buffer.from("0102030405060708090a0b0c0d0e0f10111213141516171819", "hex"),
+ data: Buffer.from(
+ "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc" +
+ "dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
+ "cdcdcdcdcd",
+ "hex",
+ ),
+ hmac: "697eaf0aca3a3aea3a75164746ffaa79",
+ },
+ {
+ key: Buffer.from("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "hex"),
+ data: "Test With Truncation",
+ hmac: "56461ef2342edc00f9bab995690efd4c",
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaa",
+ "hex",
+ ),
+ data: "Test Using Larger Than Block-Size Key - Hash Key First",
+ hmac: "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd",
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaa",
+ "hex",
+ ),
+ data: "Test Using Larger Than Block-Size Key and Larger Than One " + "Block-Size Data",
+ hmac: "6f630fad67cda0ee1fb1f562db3aa53e",
+ },
+ ];
+
+ for (const { key, data, hmac } of rfc2202_md5) {
+ describe(`rfc 2202 md5 case ${hmac}`, async () => {
+ testHmac("md5", key, data, hmac);
+ });
+ }
+
+ const rfc2202_sha1 = [
+ {
+ key: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"),
+ data: "Hi There",
+ hmac: "b617318655057264e28bc0b6fb378c8ef146be00",
+ },
+ {
+ key: "Jefe",
+ data: "what do ya want for nothing?",
+ hmac: "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79",
+ },
+ {
+ key: Buffer.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "hex"),
+ data: Buffer.from(
+ "ddddddddddddddddddddddddddddddddddddddddddddd" +
+ "ddddddddddddddddddddddddddddddddddddddddddddd" +
+ "dddddddddd",
+ "hex",
+ ),
+ hmac: "125d7342b9ac11cd91a39af48aa17b4f63f175d3",
+ },
+ {
+ key: Buffer.from("0102030405060708090a0b0c0d0e0f10111213141516171819", "hex"),
+ data: Buffer.from(
+ "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc" +
+ "dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
+ "cdcdcdcdcd",
+ "hex",
+ ),
+ hmac: "4c9007f4026250c6bc8414f9bf50c86c2d7235da",
+ },
+ {
+ key: Buffer.from("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "hex"),
+ data: "Test With Truncation",
+ hmac: "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04",
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaa",
+ "hex",
+ ),
+ data: "Test Using Larger Than Block-Size Key - Hash Key First",
+ hmac: "aa4ae5e15272d00e95705637ce8a3b55ed402112",
+ },
+ {
+ key: Buffer.from(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
+ "aaaaaaaaaaaaaaaaaaaaaa",
+ "hex",
+ ),
+ data: "Test Using Larger Than Block-Size Key and Larger Than One " + "Block-Size Data",
+ hmac: "e8e99d0f45237d786d6bbaa7965c7808bbff1a91",
+ },
+ ];
+
+ for (const { key, data, hmac } of rfc2202_sha1) {
+ describe(`rfc 2202 sha1 case ${hmac}`, async () => {
+ testHmac("sha1", key, data, hmac);
+ });
+ }
+ });
+
+ test("sha256 w00t ucs2", async () => {
+ expect(createHmac("sha256", "w00t").digest("ucs2")).toEqual(createHmac("sha256", "w00t").digest().toString("ucs2"));
+ });
+
+ test("Check initialized -> uninitialized state transition after calling digest().", async () => {
+ {
+ const expected =
+ "\u0010\u0041\u0052\u00c5\u00bf\u00dc\u00a0\u007b\u00c6\u0033" +
+ "\u00ee\u00bd\u0046\u0019\u009f\u0002\u0055\u00c9\u00f4\u009d";
+
+ {
+ const h = createHmac("sha1", "key").update("data");
+ expect(h.digest("latin1")).toBe(expected);
+ expect(h.digest("latin1")).toBe("");
+ }
+ {
+ const h = createHmac("sha1", "key").update("data");
+ expect(h.digest("buffer")).toEqual(Buffer.from(expected, "latin1"));
+ expect(h.digest("buffer")).toEqual(Buffer.from(""));
+ }
+ }
+ {
+ const expected =
+ "\u00f4\u002b\u00b0\u00ee\u00b0\u0018\u00eb\u00bd\u0045\u0097" +
+ "\u00ae\u0072\u0013\u0071\u001e\u00c6\u0007\u0060\u0084\u003f";
+ {
+ const h = createHmac("sha1", "key");
+ expect(h.digest("latin1")).toBe(expected);
+ expect(h.digest("latin1")).toBe("");
+ }
+ {
+ const h = createHmac("sha1", "key");
+ expect(h.digest("buffer")).toEqual(Buffer.from(expected, "latin1"));
+ expect(h.digest("buffer")).toEqual(Buffer.from(""));
+ }
+ }
+ });
+ test("Invalid digest", async () => {
+ expect(() => createHmac("sha7", "key")).toThrow(/sha7 is not supported/);
+ });
+
+ test("secret digest", async () => {
+ const buf = Buffer.alloc(0);
+ const keyObject = createSecretKey(Buffer.alloc(0));
+ expect(createHmac("sha256", buf).update("foo").digest()).toEqual(
+ createHmac("sha256", keyObject).update("foo").digest(),
+ );
+ });
+});
diff --git a/test/js/node/crypto/crypto.key-objects.test.ts b/test/js/node/crypto/crypto.key-objects.test.ts
new file mode 100644
index 000000000..b124ca479
--- /dev/null
+++ b/test/js/node/crypto/crypto.key-objects.test.ts
@@ -0,0 +1,1643 @@
+"use strict";
+
+import {
+ createCipheriv,
+ createDecipheriv,
+ createSign,
+ createVerify,
+ createSecretKey,
+ createPublicKey,
+ createPrivateKey,
+ KeyObject,
+ randomBytes,
+ publicDecrypt,
+ publicEncrypt,
+ privateDecrypt,
+ privateEncrypt,
+ generateKeyPairSync,
+ generateKeySync,
+ generateKeyPair,
+ sign,
+ verify,
+ generateKey,
+} from "crypto";
+import { test, it, expect, describe } from "bun:test";
+import { createContext, Script } from "node:vm";
+import fs from "fs";
+import path from "path";
+
+const publicPem = fs.readFileSync(path.join(import.meta.dir, "fixtures", "rsa_public.pem"), "ascii");
+const privatePem = fs.readFileSync(path.join(import.meta.dir, "fixtures", "rsa_private.pem"), "ascii");
+const privateEncryptedPem = fs.readFileSync(
+ path.join(import.meta.dir, "fixtures", "rsa_private_encrypted.pem"),
+ "ascii",
+);
+
+// Constructs a regular expression for a PEM-encoded key with the given label.
+function getRegExpForPEM(label: string, cipher?: string) {
+ const head = `\\-\\-\\-\\-\\-BEGIN ${label}\\-\\-\\-\\-\\-`;
+ const rfc1421Header = cipher == null ? "" : `\nProc-Type: 4,ENCRYPTED\nDEK-Info: ${cipher},[^\n]+\n`;
+ const body = "([a-zA-Z0-9\\+/=]{64}\n)*[a-zA-Z0-9\\+/=]{1,64}";
+ const end = `\\-\\-\\-\\-\\-END ${label}\\-\\-\\-\\-\\-`;
+ return new RegExp(`^${head}${rfc1421Header}\n${body}\n${end}\n$`);
+}
+const pkcs1PubExp = getRegExpForPEM("RSA PUBLIC KEY");
+const pkcs1PrivExp = getRegExpForPEM("RSA PRIVATE KEY");
+const pkcs1EncExp = (cipher: string) => getRegExpForPEM("RSA PRIVATE KEY", cipher);
+const spkiExp = getRegExpForPEM("PUBLIC KEY");
+const pkcs8Exp = getRegExpForPEM("PRIVATE KEY");
+const pkcs8EncExp = getRegExpForPEM("ENCRYPTED PRIVATE KEY");
+const sec1Exp = getRegExpForPEM("EC PRIVATE KEY");
+const sec1EncExp = (cipher: string) => getRegExpForPEM("EC PRIVATE KEY", cipher);
+
+// Asserts that the size of the given key (in chars or bytes) is within 10% of
+// the expected size.
+function assertApproximateSize(key: any, expectedSize: number) {
+ const min = Math.floor(0.9 * expectedSize);
+ const max = Math.ceil(1.1 * expectedSize);
+ expect(key.length).toBeGreaterThanOrEqual(min);
+ expect(key.length).toBeLessThanOrEqual(max);
+}
+// Tests that a key pair can be used for encryption / decryption.
+function testEncryptDecrypt(publicKey: any, privateKey: any) {
+ const message = "Hello Node.js world!";
+ const plaintext = Buffer.from(message, "utf8");
+ for (const key of [publicKey, privateKey]) {
+ const ciphertext = publicEncrypt(key, plaintext);
+ const received = privateDecrypt(privateKey, ciphertext);
+ expect(received.toString("utf8")).toEqual(message);
+ }
+}
+
+// Tests that a key pair can be used for signing / verification.
+function testSignVerify(publicKey: any, privateKey: any) {
+ const message = Buffer.from("Hello Node.js world!");
+
+ function oldSign(algo: string, data: string | Buffer, key: any) {
+ return createSign(algo).update(data).sign(key);
+ }
+
+ function oldVerify(algo: string, data: string | Buffer, key: any, signature: any) {
+ return createVerify(algo).update(data).verify(key, signature);
+ }
+
+ for (const signFn of [sign, oldSign]) {
+ const signature = signFn("SHA256", message, privateKey);
+ for (const verifyFn of [verify, oldVerify]) {
+ for (const key of [publicKey, privateKey]) {
+ const okay = verifyFn("SHA256", message, key, signature);
+ expect(okay).toBeTrue();
+ }
+ }
+ }
+}
+
+describe("crypto.KeyObjects", () => {
+ test("Attempting to create a key using other than CryptoKey should throw", async () => {
+ expect(() => new KeyObject("secret", "")).toThrow();
+ expect(() => new KeyObject("secret")).toThrow();
+ expect(() => KeyObject.from("invalid_key")).toThrow();
+ });
+ test("basics of createSecretKey should work", async () => {
+ const keybuf = randomBytes(32);
+ const key = createSecretKey(keybuf);
+ expect(key.type).toBe("secret");
+ expect(key.toString()).toBe("[object KeyObject]");
+ expect(key.symmetricKeySize).toBe(32);
+ expect(key.asymmetricKeyType).toBe(undefined);
+ expect(key.asymmetricKeyDetails).toBe(undefined);
+
+ const exportedKey = key.export();
+ expect(keybuf).toEqual(exportedKey);
+
+ const plaintext = Buffer.from("Hello world", "utf8");
+
+ const cipher = createCipheriv("aes-256-ecb", key, null);
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
+
+ const decipher = createDecipheriv("aes-256-ecb", key, null);
+ const deciphered = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
+
+ expect(plaintext).toEqual(deciphered);
+ });
+
+ test("Passing an existing public key object to createPublicKey should throw", async () => {
+ // Passing an existing public key object to createPublicKey should throw.
+ const publicKey = createPublicKey(publicPem);
+ expect(() => createPublicKey(publicKey)).toThrow();
+
+ // Constructing a private key from a public key should be impossible, even
+ // if the public key was derived from a private key.
+ expect(() => createPrivateKey(createPublicKey(privatePem))).toThrow();
+
+ // Similarly, passing an existing private key object to createPrivateKey
+ // should throw.
+ const privateKey = createPrivateKey(privatePem);
+ expect(() => createPrivateKey(privateKey)).toThrow();
+ });
+
+ test("basics should work", async () => {
+ const jwk = {
+ e: "AQAB",
+ n:
+ "t9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe" +
+ "1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ" +
+ "MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE" +
+ "u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF" +
+ "di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q",
+ d:
+ "ktnq2LvIMqBj4txP82IEOorIRQGVsw1khbm8A-cEpuEkgM71Yi_0WzupKktucUeevQ5i0" +
+ "Yh8w9e1SJiTLDRAlJz66kdky9uejiWWl6zR4dyNZVMFYRM43ijLC-P8rPne9Fz16IqHFW" +
+ "5VbJqA1xCBhKmuPMsD71RNxZ4Hrsa7Kt_xglQTYsLbdGIwDmcZihId9VGXRzvmCPsDRf2" +
+ "fCkAj7HDeRxpUdEiEDpajADc-PWikra3r3b40tVHKWm8wxJLivOIN7GiYXKQIW6RhZgH-" +
+ "Rk45JIRNKxNagxdeXUqqyhnwhbTo1Hite0iBDexN9tgoZk0XmdYWBn6ElXHRZ7VCDQ",
+ p:
+ "8UovlB4nrBm7xH-u7XXBMbqxADQm5vaEZxw9eluc-tP7cIAI4sglMIvL_FMpbd2pEeP_B" +
+ "kR76NTDzzDuPAZvUGRavgEjy0O9j2NAs_WPK4tZF-vFdunhnSh4EHAF4Ij9kbsUi90NOp" +
+ "bGfVqPdOaHqzgHKoR23Cuusk9wFQ2XTV8",
+ q:
+ "wxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrWOb-76rSfuL8wGR4OBNmQdhLuU9zTIh22pog-X" +
+ "PnLPAecC-4yu_wtJ2SPCKiKDbJBre0CKPyRfGqzvA3njXwMxXazU4kGs-2Fg-xu_iKbaI" +
+ "jxXrclBLhkxhBtySrwAFhxxOk6fFcPLSs",
+ dp:
+ "qS_Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc_Gh4cnO-b7BNJ_-5L8WZog0vr" +
+ "6PgiLhrqBaCYm2wjpyoG2o2wDHm-NAlzN_wp3G2EFhrSxdOux-S1c0kpRcyoiAO2n29rN" +
+ "Da-jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8",
+ dq:
+ "WAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCDdCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP_" +
+ "Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNmbDXFPdG0G3hzQovx_9fajiRV4DWghLHeT9wzJ" +
+ "fZabRRiI0VQR472300AVEeX4vgbrDBn600",
+ qi:
+ "k7czBCT9rHn_PNwCa17hlTy88C4vXkwbz83Oa-aX5L4e5gw5lhcR2ZuZHLb2r6oMt9rl" +
+ "D7EIDItSs-u21LOXWPTAlazdnpYUyw_CzogM_PN-qNwMRXn5uXFFhmlP2mVg2EdELTahX" +
+ "ch8kWqHaCSX53yvqCtRKu_j76V31TfQZGM",
+ kty: "RSA",
+ };
+ const publicJwk = { kty: jwk.kty, e: jwk.e, n: jwk.n };
+
+ const publicKey = createPublicKey(publicPem);
+ expect(publicKey.type).toBe("public");
+ expect(publicKey.toString()).toBe("[object KeyObject]");
+ expect(publicKey.asymmetricKeyType).toBe("rsa");
+ expect(publicKey.symmetricKeySize).toBe(undefined);
+
+ const privateKey = createPrivateKey(privatePem);
+ expect(privateKey.type).toBe("private");
+ expect(privateKey.toString()).toBe("[object KeyObject]");
+ expect(privateKey.asymmetricKeyType).toBe("rsa");
+ expect(privateKey.symmetricKeySize).toBe(undefined);
+
+ // It should be possible to derive a public key from a private key.
+ const derivedPublicKey = createPublicKey(privateKey);
+ expect(derivedPublicKey.type).toBe("public");
+ expect(derivedPublicKey.toString()).toBe("[object KeyObject]");
+ expect(derivedPublicKey.asymmetricKeyType).toBe("rsa");
+ expect(derivedPublicKey.symmetricKeySize).toBe(undefined);
+
+ const publicKeyFromJwk = createPublicKey({ key: publicJwk, format: "jwk" });
+ expect(publicKey.type).toBe("public");
+ expect(publicKey.toString()).toBe("[object KeyObject]");
+ expect(publicKey.asymmetricKeyType).toBe("rsa");
+ expect(publicKey.symmetricKeySize).toBe(undefined);
+
+ const privateKeyFromJwk = createPrivateKey({ key: jwk, format: "jwk" });
+ expect(privateKey.type).toBe("private");
+ expect(privateKey.toString()).toBe("[object KeyObject]");
+ expect(privateKey.asymmetricKeyType).toBe("rsa");
+ expect(privateKey.symmetricKeySize).toBe(undefined);
+
+ // It should also be possible to import an encrypted private key as a public
+ // key.
+ const decryptedKey = createPublicKey({
+ key: privateKey.export({
+ type: "pkcs8",
+ format: "pem",
+ passphrase: Buffer.from("123"),
+ cipher: "aes-128-cbc",
+ }),
+ format: "pem",
+ passphrase: "123", // this is not documented, but it works
+ });
+ expect(decryptedKey.type).toBe("public");
+ expect(decryptedKey.asymmetricKeyType).toBe("rsa");
+
+ // Exporting the key using JWK should not work since this format does not
+ // support key encryption
+ expect(() => {
+ privateKey.export({ format: "jwk", passphrase: "secret" });
+ }).toThrow();
+
+ // Test exporting with an invalid options object, this should throw.
+ for (const opt of [undefined, null, "foo", 0, NaN]) {
+ expect(() => publicKey.export(opt)).toThrow();
+ }
+
+ for (const keyObject of [publicKey, derivedPublicKey, publicKeyFromJwk]) {
+ const exported = keyObject.export({ format: "jwk" });
+ expect(exported).toBeDefined();
+ const { kty, n, e } = exported as { kty: string; n: string; e: string };
+ expect({ kty, n, e }).toEqual({ kty: "RSA", n: jwk.n, e: jwk.e });
+ }
+
+ for (const keyObject of [privateKey, privateKeyFromJwk]) {
+ const exported = keyObject.export({ format: "jwk" });
+ expect(exported).toEqual(jwk);
+ }
+
+ const publicDER = publicKey.export({
+ format: "der",
+ type: "pkcs1",
+ });
+
+ const privateDER = privateKey.export({
+ format: "der",
+ type: "pkcs1",
+ });
+
+ expect(Buffer.isBuffer(publicDER)).toBe(true);
+ expect(Buffer.isBuffer(privateDER)).toBe(true);
+ const plaintext = Buffer.from("Hello world", "utf8");
+
+ const testDecryption = (fn, ciphertexts, decryptionKeys) => {
+ for (const ciphertext of ciphertexts) {
+ for (const key of decryptionKeys) {
+ const deciphered = fn(key, ciphertext);
+ expect(deciphered).toEqual(plaintext);
+ }
+ }
+ };
+
+ testDecryption(
+ privateDecrypt,
+ [
+ // Encrypt using the public key.
+ publicEncrypt(publicKey, plaintext),
+ publicEncrypt({ key: publicKey }, plaintext),
+ publicEncrypt({ key: publicJwk, format: "jwk" }, plaintext),
+
+ // Encrypt using the private key.
+ publicEncrypt(privateKey, plaintext),
+ publicEncrypt({ key: privateKey }, plaintext),
+ publicEncrypt({ key: jwk, format: "jwk" }, plaintext),
+
+ // Encrypt using a public key derived from the private key.
+ publicEncrypt(derivedPublicKey, plaintext),
+ publicEncrypt({ key: derivedPublicKey }, plaintext),
+
+ // Test distinguishing PKCS#1 public and private keys based on the
+ // DER-encoded data only.
+ publicEncrypt({ format: "der", type: "pkcs1", key: publicDER }, plaintext),
+ publicEncrypt({ format: "der", type: "pkcs1", key: privateDER }, plaintext),
+ ],
+ [
+ privateKey,
+ { format: "pem", key: privatePem },
+ { format: "der", type: "pkcs1", key: privateDER },
+ { key: jwk, format: "jwk" },
+ ],
+ );
+
+ testDecryption(
+ publicDecrypt,
+ [privateEncrypt(privateKey, plaintext)],
+ [
+ // Decrypt using the public key.
+ publicKey,
+ { format: "pem", key: publicPem },
+ { format: "der", type: "pkcs1", key: publicDER },
+ { key: publicJwk, format: "jwk" },
+
+ // Decrypt using the private key.
+ privateKey,
+ { format: "pem", key: privatePem },
+ { format: "der", type: "pkcs1", key: privateDER },
+ { key: jwk, format: "jwk" },
+ ],
+ );
+ });
+
+ test("This should not cause a crash: https://github.com/nodejs/node/issues/25247", async () => {
+ expect(() => createPrivateKey({ key: "" })).toThrow();
+ });
+ test("This should not abort either: https://github.com/nodejs/node/issues/29904", async () => {
+ expect(() => createPrivateKey({ key: Buffer.alloc(0), format: "der", type: "spki" })).toThrow();
+ });
+
+ test("BoringSSL will not parse PKCS#1", async () => {
+ // Unlike SPKI, PKCS#1 is a valid encoding for private keys (and public keys),
+ // so it should be accepted by createPrivateKey, but OpenSSL won't parse it.
+ expect(() => {
+ const key = createPublicKey(publicPem).export({
+ format: "der",
+ type: "pkcs1",
+ });
+ createPrivateKey({ key, format: "der", type: "pkcs1" });
+ }).toThrow("Invalid use of PKCS#1 as private key");
+ });
+
+ [
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ed25519_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ed25519_public.pem"), "ascii"),
+ keyType: "ed25519",
+ jwk: {
+ crv: "Ed25519",
+ x: "K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768",
+ d: "wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA",
+ kty: "OKP",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ed448_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ed448_public.pem"), "ascii"),
+ keyType: "ed448",
+ jwk: {
+ crv: "Ed448",
+ x: "oX_ee5-jlcU53-BbGRsGIzly0V-SZtJ_oGXY0udf84q2hTW2RdstLktvwpkVJOoNb7o" + "Dgc2V5ZUA",
+ d: "060Ke71sN0GpIc01nnGgMDkp0sFNQ09woVo4AM1ffax1-mjnakK0-p-S7-Xf859QewX" + "jcR9mxppY",
+ kty: "OKP",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "x25519_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "x25519_public.pem"), "ascii"),
+ keyType: "x25519",
+ jwk: {
+ crv: "X25519",
+ x: "aSb8Q-RndwfNnPeOYGYPDUN3uhAPnMLzXyfi-mqfhig",
+ d: "mL_IWm55RrALUGRfJYzw40gEYWMvtRkesP9mj8o8Omc",
+ kty: "OKP",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "x448_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "x448_public.pem"), "ascii"),
+ keyType: "x448",
+ jwk: {
+ crv: "X448",
+ x: "ioHSHVpTs6hMvghosEJDIR7ceFiE3-Xccxati64oOVJ7NWjfozE7ae31PXIUFq6cVYg" + "vSKsDFPA",
+ d: "tMNtrO_q8dlY6Y4NDeSTxNQ5CACkHiPvmukidPnNIuX_EkcryLEXt_7i6j6YZMKsrWy" + "S0jlSYJk",
+ kty: "OKP",
+ },
+ },
+ ].forEach(info => {
+ const keyType = info.keyType;
+ // X25519 implementation is incomplete, Ed448 and X448 are not supported yet
+ const test = keyType === "ed25519" ? it : it.skip;
+ let privateKey: KeyObject;
+ test(`${keyType} from Buffer should work`, async () => {
+ const key = createPrivateKey(info.private);
+ privateKey = key;
+ expect(key.type).toBe("private");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.symmetricKeySize).toBe(undefined);
+ expect(key.export({ type: "pkcs8", format: "pem" })).toEqual(info.private);
+ const jwt = key.export({ format: "jwk" });
+ expect(jwt).toEqual(info.jwk);
+ });
+
+ test(`${keyType} createPrivateKey from jwk should work`, async () => {
+ const key = createPrivateKey({ key: info.jwk, format: "jwk" });
+ expect(key.type).toBe("private");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.symmetricKeySize).toBe(undefined);
+ expect(key.export({ type: "pkcs8", format: "pem" })).toEqual(info.private);
+ const jwt = key.export({ format: "jwk" });
+ expect(jwt).toEqual(info.jwk);
+ });
+
+ [
+ ["public", info.public],
+ ["private", info.private],
+ ["jwk", { key: info.jwk, format: "jwk" }],
+ ].forEach(([name, input]) => {
+ test(`${keyType} createPublicKey using ${name} key should work`, async () => {
+ const key = createPublicKey(input);
+ expect(key.type).toBe("public");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.symmetricKeySize).toBe(undefined);
+ if (name == "public") {
+ expect(key.export({ type: "spki", format: "pem" })).toEqual(info.public);
+ }
+ if (name == "jwk") {
+ const jwt = { ...info.jwk };
+ delete jwt.d;
+ const jwk_exported = key.export({ format: "jwk" });
+ expect(jwk_exported).toEqual(jwt);
+ }
+ });
+ });
+ });
+
+ [
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p256_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p256_public.pem"), "ascii"),
+ keyType: "ec",
+ namedCurve: "prime256v1",
+ jwk: {
+ crv: "P-256",
+ d: "DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo",
+ kty: "EC",
+ x: "X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs",
+ y: "UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_secp256k1_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_secp256k1_public.pem"), "ascii"),
+ keyType: "ec",
+ namedCurve: "secp256k1",
+ jwk: {
+ crv: "secp256k1",
+ d: "c34ocwTwpFa9NZZh3l88qXyrkoYSxvC0FEsU5v1v4IM",
+ kty: "EC",
+ x: "cOzhFSpWxhalCbWNdP2H_yUkdC81C9T2deDpfxK7owA",
+ y: "-A3DAZTk9IPppN-f03JydgHaFvL1fAHaoXf4SX4NXyo",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p384_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p384_public.pem"), "ascii"),
+ keyType: "ec",
+ namedCurve: "secp384r1",
+ jwk: {
+ crv: "P-384",
+ d: "dwfuHuAtTlMRn7ZBCBm_0grpc1D_4hPeNAgevgelljuC0--k_LDFosDgBlLLmZsi",
+ kty: "EC",
+ x: "hON3nzGJgv-08fdHpQxgRJFZzlK-GZDGa5f3KnvM31cvvjJmsj4UeOgIdy3rDAjV",
+ y: "fidHhtecNCGCfLqmrLjDena1NSzWzWH1u_oUdMKGo5XSabxzD7-8JZxjpc8sR9cl",
+ },
+ },
+ {
+ private: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p521_private.pem"), "ascii"),
+ public: fs.readFileSync(path.join(import.meta.dir, "fixtures", "ec_p521_public.pem"), "ascii"),
+ keyType: "ec",
+ namedCurve: "secp521r1",
+ jwk: {
+ crv: "P-521",
+ d: "Eghuafcab9jXW4gOQLeDaKOlHEiskQFjiL8klijk6i6DNOXcFfaJ9GW48kxpodw16ttAf9Z1WQstfzpKGUetHIk",
+ kty: "EC",
+ x: "AaLFgjwZtznM3N7qsfb86awVXe6c6djUYOob1FN-kllekv0KEXV0bwcDjPGQz5f6MxL" + "CbhMeHRavUS6P10rsTtBn",
+ y: "Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkOSEASxPB" + "cvA2iFJRUyQ3whC00j0Np",
+ },
+ },
+ ].forEach(info => {
+ const { keyType, namedCurve } = info;
+ const test = namedCurve === "secp256k1" ? it.skip : it;
+ let privateKey: KeyObject;
+ test(`${keyType} ${namedCurve} createPrivateKey from Buffer should work`, async () => {
+ const key = createPrivateKey(info.private);
+ privateKey = key;
+ expect(key.type).toBe("private");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.asymmetricKeyDetails?.namedCurve).toBe(namedCurve);
+ expect(key.symmetricKeySize).toBe(undefined);
+ expect(key.export({ type: "pkcs8", format: "pem" })).toEqual(info.private);
+ const jwt = key.export({ format: "jwk" });
+ expect(jwt).toEqual(info.jwk);
+ });
+
+ test(`${keyType} ${namedCurve} createPrivateKey from jwk should work`, async () => {
+ const key = createPrivateKey({ key: info.jwk, format: "jwk" });
+ expect(key.type).toBe("private");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.asymmetricKeyDetails?.namedCurve).toBe(namedCurve);
+ expect(key.symmetricKeySize).toBe(undefined);
+ expect(key.export({ type: "pkcs8", format: "pem" })).toEqual(info.private);
+ const jwt = key.export({ format: "jwk" });
+ expect(jwt).toEqual(info.jwk);
+ });
+
+ [
+ ["public", info.public],
+ ["private", info.private],
+ ["jwk", { key: info.jwk, format: "jwk" }],
+ ].forEach(([name, input]) => {
+ test(`${keyType} ${namedCurve} createPublicKey using ${name} should work`, async () => {
+ const key = createPublicKey(input);
+ expect(key.type).toBe("public");
+ expect(key.asymmetricKeyType).toBe(keyType);
+ expect(key.asymmetricKeyDetails?.namedCurve).toBe(namedCurve);
+ expect(key.symmetricKeySize).toBe(undefined);
+ if (name == "public") {
+ expect(key.export({ type: "spki", format: "pem" })).toEqual(info.public);
+ }
+ if (name == "jwk") {
+ const jwt = { ...info.jwk };
+ delete jwt.d;
+ const jwk_exported = key.export({ format: "jwk" });
+ expect(jwk_exported).toEqual(jwt);
+ }
+
+ const pkey = privateKey || info.private;
+ const signature = createSign("sha256").update("foo").sign({ key: pkey });
+ const okay = createVerify("sha256").update("foo").verify({ key: key }, signature);
+ expect(okay).toBeTrue();
+ });
+ });
+ });
+
+ test("private encrypted should work", async () => {
+ // Reading an encrypted key without a passphrase should fail.
+ expect(() => createPrivateKey(privateEncryptedPem)).toThrow();
+ // Reading an encrypted key with a passphrase that exceeds OpenSSL's buffer
+ // size limit should fail with an appropriate error code.
+ expect(() =>
+ createPrivateKey({
+ key: privateEncryptedPem,
+ format: "pem",
+ passphrase: Buffer.alloc(1025, "a"),
+ }),
+ ).toThrow();
+ // The buffer has a size of 1024 bytes, so this passphrase should be permitted
+ // (but will fail decryption).
+ expect(() =>
+ createPrivateKey({
+ key: privateEncryptedPem,
+ format: "pem",
+ passphrase: Buffer.alloc(1024, "a"),
+ }),
+ ).toThrow();
+ const publicKey = createPublicKey({
+ key: privateEncryptedPem,
+ format: "pem",
+ passphrase: "password", // this is not documented but should work
+ });
+ expect(publicKey.type).toBe("public");
+ expect(publicKey.asymmetricKeyType).toBe("rsa");
+ expect(publicKey.symmetricKeySize).toBe(undefined);
+
+ const privateKey = createPrivateKey({
+ key: privateEncryptedPem,
+ format: "pem",
+ passphrase: "password",
+ });
+ expect(privateKey.type).toBe("private");
+ expect(privateKey.asymmetricKeyType).toBe("rsa");
+ expect(privateKey.symmetricKeySize).toBe(undefined);
+ });
+
+ [2048, 4096].forEach(suffix => {
+ test(`RSA-${suffix} should work`, async () => {
+ {
+ const publicPem = fs.readFileSync(path.join(import.meta.dir, "fixtures", `rsa_public_${suffix}.pem`), "ascii");
+ const privatePem = fs.readFileSync(
+ path.join(import.meta.dir, "fixtures", `rsa_private_${suffix}.pem`),
+ "ascii",
+ );
+ const publicKey = createPublicKey(publicPem);
+ const expectedKeyDetails = {
+ modulusLength: suffix,
+ publicExponent: 65537n,
+ };
+ expect(publicKey.type).toBe("public");
+ expect(publicKey.asymmetricKeyType).toBe("rsa");
+ expect(publicKey.asymmetricKeyDetails).toEqual(expectedKeyDetails);
+
+ const privateKey = createPrivateKey(privatePem);
+ expect(privateKey.type).toBe("private");
+ expect(privateKey.asymmetricKeyType).toBe("rsa");
+ expect(privateKey.asymmetricKeyDetails).toEqual(expectedKeyDetails);
+
+ for (const key of [privatePem, privateKey]) {
+ // Any algorithm should work.
+ for (const algo of ["sha1", "sha256"]) {
+ // Any salt length should work.
+ for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) {
+ const signature = createSign(algo).update("foo").sign({ key, saltLength });
+ for (const pkey of [key, publicKey, publicPem]) {
+ const okay = createVerify(algo).update("foo").verify({ key: pkey, saltLength }, signature);
+ expect(okay).toBeTrue();
+ }
+ }
+ }
+ }
+ }
+ });
+ });
+
+ test("Exporting an encrypted private key requires a cipher", async () => {
+ // Exporting an encrypted private key requires a cipher
+ const privateKey = createPrivateKey(privatePem);
+ expect(() => {
+ privateKey.export({
+ format: "pem",
+ type: "pkcs8",
+ passphrase: "super-secret",
+ });
+ }).toThrow(/cipher is required when passphrase is specified/);
+ });
+
+ test("secret export buffer format (default)", async () => {
+ const buffer = Buffer.from("Hello World");
+ const keyObject = createSecretKey(buffer);
+ expect(keyObject.export()).toEqual(buffer);
+ expect(keyObject.export({})).toEqual(buffer);
+ expect(keyObject.export({ format: "buffer" })).toEqual(buffer);
+ expect(keyObject.export({ format: undefined })).toEqual(buffer);
+ });
+
+ test('exporting an "oct" JWK from a secret', async () => {
+ const buffer = Buffer.from("Hello World");
+ const keyObject = createSecretKey(buffer);
+ const jwk = keyObject.export({ format: "jwk" });
+ expect(jwk).toEqual({ kty: "oct", k: "SGVsbG8gV29ybGQ" });
+ });
+
+ test("secret equals", async () => {
+ {
+ const first = Buffer.from("Hello");
+ const second = Buffer.from("World");
+ const keyObject = createSecretKey(first);
+ expect(createSecretKey(first).equals(createSecretKey(first))).toBeTrue();
+ expect(createSecretKey(first).equals(createSecretKey(second))).toBeFalse();
+
+ expect(() => keyObject.equals(0)).toThrow(/otherKey must be a KeyObject/);
+
+ expect(keyObject.equals(keyObject)).toBeTrue();
+ expect(keyObject.equals(createPublicKey(publicPem))).toBeFalse();
+ expect(keyObject.equals(createPrivateKey(privatePem))).toBeFalse();
+ }
+
+ {
+ const first = createSecretKey(Buffer.alloc(0));
+ const second = createSecretKey(new ArrayBuffer(0));
+ const third = createSecretKey(Buffer.alloc(1));
+ expect(first.equals(first)).toBeTrue();
+ expect(first.equals(second)).toBeTrue();
+ expect(first.equals(third)).toBeFalse();
+ expect(third.equals(first)).toBeFalse();
+ }
+ });
+
+ ["ed25519", "x25519"].forEach(keyType => {
+ const test = keyType === "ed25519" ? it : it.skip;
+ test(`${keyType} equals should work`, async () => {
+ const first = generateKeyPairSync(keyType);
+ const second = generateKeyPairSync(keyType);
+
+ const secret = generateKeySync("aes", { length: 128 });
+
+ expect(first.publicKey.equals(first.publicKey)).toBeTrue();
+
+ expect(first.publicKey.equals(createPublicKey(first.publicKey.export({ format: "pem", type: "spki" }))));
+
+ expect(first.publicKey.equals(second.publicKey)).toBeFalse();
+ expect(first.publicKey.equals(second.privateKey)).toBeFalse();
+ expect(first.publicKey.equals(secret)).toBeFalse();
+
+ expect(first.privateKey.equals(first.privateKey)).toBeTrue();
+ expect(
+ first.privateKey.equals(createPrivateKey(first.privateKey.export({ format: "pem", type: "pkcs8" }))),
+ ).toBeTrue();
+ expect(first.privateKey.equals(second.privateKey)).toBeFalse();
+ expect(first.privateKey.equals(second.publicKey)).toBeFalse();
+ expect(first.privateKey.equals(secret)).toBeFalse();
+ });
+ });
+
+ test("This should not cause a crash: https://github.com/nodejs/node/issues/44471", async () => {
+ for (const key of ["", "foo", null, undefined, true, Boolean]) {
+ expect(() => {
+ createPublicKey({ key, format: "jwk" });
+ }).toThrow();
+ expect(() => {
+ createPrivateKey({ key, format: "jwk" });
+ }).toThrow();
+ }
+ });
+
+ ["hmac", "aes"].forEach(type => {
+ [128, 256].forEach(length => {
+ test(`generateKey ${type} ${length}`, async () => {
+ {
+ const key = generateKeySync(type, { length });
+ expect(key).toBeDefined();
+ const keybuf = key.export();
+ expect(keybuf.byteLength).toBe(length / 8);
+ }
+
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKey(type, { length }, (err, key) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(key);
+ }
+ });
+
+ {
+ const key = await promise;
+ expect(key).toBeDefined();
+ const keybuf = key.export();
+ expect(keybuf.byteLength).toBe(length / 8);
+ }
+ });
+ });
+ });
+ describe("Test async elliptic curve key generation with 'jwk' encoding and named curve", () => {
+ ["P-384", "P-256", "P-521", "secp256k1"].forEach(curve => {
+ const test = curve === "secp256k1" ? it.skip : it;
+ test(`should work with ${curve}`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "ec",
+ {
+ namedCurve: curve,
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: any; privateKey: any }>);
+ expect(typeof publicKey).toBe("object");
+ expect(typeof privateKey).toBe("object");
+ expect(publicKey.x).toBe(privateKey.x);
+ expect(publicKey.y).toBe(publicKey.y);
+ expect(publicKey.d).toBeUndefined();
+ expect(privateKey.d).toBeDefined();
+ expect(publicKey.kty).toEqual("EC");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ expect(publicKey.crv).toEqual(curve);
+ expect(publicKey.crv).toEqual(privateKey.crv);
+ });
+ });
+ });
+
+ describe("Test async elliptic curve key generation with 'jwk' encoding and RSA.", () => {
+ [256, 1024, 2048].forEach(modulusLength => {
+ test(`should work with ${modulusLength}`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "rsa",
+ {
+ modulusLength,
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: any; privateKey: any }>);
+ expect(typeof publicKey).toEqual("object");
+ expect(typeof privateKey).toEqual("object");
+ expect(publicKey.kty).toEqual("RSA");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ expect(typeof publicKey.n).toEqual("string");
+ expect(publicKey.n).toEqual(privateKey.n);
+ expect(typeof publicKey.e).toEqual("string");
+ expect(publicKey.e).toEqual(privateKey.e);
+ expect(typeof privateKey.d).toEqual("string");
+ expect(typeof privateKey.p).toEqual("string");
+ expect(typeof privateKey.q).toEqual("string");
+ expect(typeof privateKey.dp).toEqual("string");
+ expect(typeof privateKey.dq).toEqual("string");
+ expect(typeof privateKey.qi).toEqual("string");
+ });
+ });
+ });
+
+ describe("Test async elliptic curve key generation with 'jwk' encoding", () => {
+ ["ed25519", "ed448", "x25519", "x448"].forEach(type => {
+ const test = type === "ed25519" ? it : it.skip;
+ test(`should work with ${type}`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ type,
+ {
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: any; privateKey: any }>);
+ expect(typeof publicKey).toEqual("object");
+ expect(typeof privateKey).toEqual("object");
+ expect(publicKey.x).toEqual(privateKey.x);
+ expect(publicKey.d).toBeUndefined();
+ expect(privateKey.d).toBeDefined();
+ expect(publicKey.kty).toEqual("OKP");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ const expectedCrv = `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
+ expect(publicKey.crv).toEqual(expectedCrv);
+ expect(publicKey.crv).toEqual(privateKey.crv);
+ });
+ });
+ });
+
+ test(`Test async RSA key generation with an encrypted private key, but encoded as DER`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "rsa",
+ {
+ publicExponent: 0x10001,
+ modulusLength: 512,
+ publicKeyEncoding: {
+ type: "pkcs1",
+ format: "der",
+ },
+ privateKeyEncoding: {
+ type: "pkcs1",
+ format: "pem",
+ cipher: "aes-256-cbc",
+ passphrase: "secret",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey: publicKeyDER, privateKey } = await (promise as Promise<{
+ publicKey: Buffer;
+ privateKey: string;
+ }>);
+ expect(Buffer.isBuffer(publicKeyDER)).toBeTrue();
+ assertApproximateSize(publicKeyDER, 74);
+
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs1EncExp("AES-256-CBC"));
+
+ const publicKey = {
+ key: publicKeyDER,
+ type: "pkcs1",
+ format: "der",
+ };
+ expect(() => {
+ testEncryptDecrypt(publicKey, privateKey);
+ }).toThrow();
+
+ const key = { key: privateKey, passphrase: "secret" };
+ testEncryptDecrypt(publicKey, key);
+ testSignVerify(publicKey, key);
+ });
+
+ test(`Test async RSA key generation with an encrypted private key`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "rsa",
+ {
+ publicExponent: 0x10001,
+ modulusLength: 512,
+ publicKeyEncoding: {
+ type: "pkcs1",
+ format: "der",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "der",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey: publicKeyDER, privateKey: privateKeyDER } = await (promise as Promise<{
+ publicKey: Buffer;
+ privateKey: Buffer;
+ }>);
+ expect(Buffer.isBuffer(publicKeyDER)).toBeTrue();
+ assertApproximateSize(publicKeyDER, 74);
+
+ expect(Buffer.isBuffer(privateKeyDER)).toBeTrue();
+
+ const publicKey = {
+ key: publicKeyDER,
+ type: "pkcs1",
+ format: "der",
+ };
+ const privateKey = {
+ key: privateKeyDER,
+ format: "der",
+ type: "pkcs8",
+ passphrase: "secret",
+ };
+ testEncryptDecrypt(publicKey, privateKey);
+ testSignVerify(publicKey, privateKey);
+ });
+
+ test(`Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted private key`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "ec",
+ {
+ namedCurve: "P-256",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "top secret",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: string; privateKey: string }>);
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs8EncExp);
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "top secret",
+ });
+ });
+
+ test(`Test async explicit elliptic curve key generation with an encrypted private key`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "ec",
+ {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "sec1",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "secret",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: string; privateKey: string }>);
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(sec1EncExp("AES-128-CBC"));
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "secret",
+ });
+ });
+
+ test(`Test async explicit elliptic curve key generation, e.g. for ECDSA, with a SEC1 private key`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "ec",
+ {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "sec1",
+ format: "pem",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: string; privateKey: string }>);
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(sec1Exp);
+ testSignVerify(publicKey, privateKey);
+ });
+
+ test(`Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted private key`, async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "ec",
+ {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "top secret",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: string; privateKey: string }>);
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs8EncExp);
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "top secret",
+ });
+ });
+
+ describe("Test sync elliptic curve key generation with 'jwk' encoding and named curve", () => {
+ ["P-384", "P-256", "P-521", "secp256k1"].forEach(curve => {
+ const test = curve === "secp256k1" ? it.skip : it;
+ test(`should work with ${curve}`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync("ec", {
+ namedCurve: curve,
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ });
+ expect(typeof publicKey).toBe("object");
+ expect(typeof privateKey).toBe("object");
+ expect(publicKey.x).toBe(privateKey.x);
+ expect(publicKey.y).toBe(publicKey.y);
+ expect(publicKey.d).toBeUndefined();
+ expect(privateKey.d).toBeDefined();
+ expect(publicKey.kty).toEqual("EC");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ expect(publicKey.crv).toEqual(curve);
+ expect(publicKey.crv).toEqual(privateKey.crv);
+ });
+ });
+ });
+
+ describe("Test sync elliptic curve key generation with 'jwk' encoding and RSA.", () => {
+ [256, 1024, 2048].forEach(modulusLength => {
+ test(`should work with ${modulusLength}`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync("rsa", {
+ modulusLength,
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ });
+ expect(typeof publicKey).toEqual("object");
+ expect(typeof privateKey).toEqual("object");
+ expect(publicKey.kty).toEqual("RSA");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ expect(typeof publicKey.n).toEqual("string");
+ expect(publicKey.n).toEqual(privateKey.n);
+ expect(typeof publicKey.e).toEqual("string");
+ expect(publicKey.e).toEqual(privateKey.e);
+ expect(typeof privateKey.d).toEqual("string");
+ expect(typeof privateKey.p).toEqual("string");
+ expect(typeof privateKey.q).toEqual("string");
+ expect(typeof privateKey.dp).toEqual("string");
+ expect(typeof privateKey.dq).toEqual("string");
+ expect(typeof privateKey.qi).toEqual("string");
+ });
+ });
+ });
+
+ describe("Test sync elliptic curve key generation with 'jwk' encoding", () => {
+ ["ed25519", "ed448", "x25519", "x448"].forEach(type => {
+ const test = type === "ed25519" ? it : it.skip;
+ test(`should work with ${type}`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync(type, {
+ publicKeyEncoding: {
+ format: "jwk",
+ },
+ privateKeyEncoding: {
+ format: "jwk",
+ },
+ });
+
+ expect(typeof publicKey).toEqual("object");
+ expect(typeof privateKey).toEqual("object");
+ expect(publicKey.x).toEqual(privateKey.x);
+ expect(publicKey.d).toBeUndefined();
+ expect(privateKey.d).toBeDefined();
+ expect(publicKey.kty).toEqual("OKP");
+ expect(publicKey.kty).toEqual(privateKey.kty);
+ const expectedCrv = `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
+ expect(publicKey.crv).toEqual(expectedCrv);
+ expect(publicKey.crv).toEqual(privateKey.crv);
+ });
+ });
+ });
+
+ test(`Test sync RSA key generation with an encrypted private key, but encoded as DER`, async () => {
+ const { publicKey: publicKeyDER, privateKey } = generateKeyPairSync("rsa", {
+ publicExponent: 0x10001,
+ modulusLength: 512,
+ publicKeyEncoding: {
+ type: "pkcs1",
+ format: "der",
+ },
+ privateKeyEncoding: {
+ type: "pkcs1",
+ format: "pem",
+ cipher: "aes-256-cbc",
+ passphrase: "secret",
+ },
+ });
+
+ expect(Buffer.isBuffer(publicKeyDER)).toBeTrue();
+ assertApproximateSize(publicKeyDER, 74);
+
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs1EncExp("AES-256-CBC"));
+
+ const publicKey = {
+ key: publicKeyDER,
+ type: "pkcs1",
+ format: "der",
+ };
+ expect(() => {
+ testEncryptDecrypt(publicKey, privateKey);
+ }).toThrow();
+
+ const key = { key: privateKey, passphrase: "secret" };
+ testEncryptDecrypt(publicKey, key);
+ testSignVerify(publicKey, key);
+ });
+
+ test(`Test sync RSA key generation with an encrypted private key`, async () => {
+ const { publicKey: publicKeyDER, privateKey: privateKeyDER } = generateKeyPairSync("rsa", {
+ publicExponent: 0x10001,
+ modulusLength: 512,
+ publicKeyEncoding: {
+ type: "pkcs1",
+ format: "der",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "der",
+ },
+ });
+
+ expect(Buffer.isBuffer(publicKeyDER)).toBeTrue();
+ assertApproximateSize(publicKeyDER, 74);
+
+ expect(Buffer.isBuffer(privateKeyDER)).toBeTrue();
+
+ const publicKey = {
+ key: publicKeyDER,
+ type: "pkcs1",
+ format: "der",
+ };
+ const privateKey = {
+ key: privateKeyDER,
+ format: "der",
+ type: "pkcs8",
+ passphrase: "secret",
+ };
+ testEncryptDecrypt(publicKey, privateKey);
+ testSignVerify(publicKey, privateKey);
+ });
+
+ test(`Test sync elliptic curve key generation, e.g. for ECDSA, with an encrypted private key`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync("ec", {
+ namedCurve: "P-256",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "top secret",
+ },
+ });
+
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs8EncExp);
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "top secret",
+ });
+ });
+
+ test(`Test sync explicit elliptic curve key generation with an encrypted private key`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync(
+ "ec",
+ {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "sec1",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "secret",
+ },
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(sec1EncExp("AES-128-CBC"));
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "secret",
+ });
+ });
+
+ test(`Test sync explicit elliptic curve key generation, e.g. for ECDSA, with a SEC1 private key`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync("ec", {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "sec1",
+ format: "pem",
+ },
+ });
+
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(sec1Exp);
+ testSignVerify(publicKey, privateKey);
+ });
+
+ test(`Test sync elliptic curve key generation, e.g. for ECDSA, with an encrypted private key`, async () => {
+ const { publicKey, privateKey } = generateKeyPairSync("ec", {
+ namedCurve: "prime256v1",
+ publicKeyEncoding: {
+ type: "spki",
+ format: "pem",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "pem",
+ cipher: "aes-128-cbc",
+ passphrase: "top secret",
+ },
+ });
+
+ expect(typeof publicKey).toBe("string");
+ expect(publicKey).toMatch(spkiExp);
+ expect(typeof privateKey).toBe("string");
+ expect(privateKey).toMatch(pkcs8EncExp);
+
+ expect(() => {
+ testSignVerify(publicKey, privateKey);
+ }).toThrow();
+
+ testSignVerify(publicKey, {
+ key: privateKey,
+ passphrase: "top secret",
+ });
+ });
+ // SKIPED because we round the key size to the nearest multiple of 8 like documented
+ test.skip(`this tests check that generateKeyPair returns correct bit length in KeyObject's asymmetricKeyDetails.`, async () => {
+ // This tests check that generateKeyPair returns correct bit length in
+ // https://github.com/nodejs/node/issues/46102#issuecomment-1372153541
+ const { promise, resolve, reject } = Promise.withResolvers();
+ generateKeyPair(
+ "rsa",
+ {
+ modulusLength: 513,
+ },
+ (err, publicKey, privateKey) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ publicKey, privateKey });
+ },
+ );
+
+ const { publicKey, privateKey } = await (promise as Promise<{ publicKey: KeyObject; privateKey: KeyObject }>);
+ expect(publicKey.asymmetricKeyDetails?.modulusLength).toBe(513);
+ expect(privateKey.asymmetricKeyDetails?.modulusLength).toBe(513);
+ });
+
+ function testRunInContext(fn: any) {
+ test("can generate key", () => {
+ const context = createContext({ generateKeySync });
+ const result = fn(`generateKeySync("aes", { length: 128 })`, context);
+ expect(result).toBeDefined();
+ const keybuf = result.export();
+ expect(keybuf.byteLength).toBe(128 / 8);
+ });
+ test("can be used on another context", () => {
+ const context = createContext({ generateKeyPairSync, assertApproximateSize, testEncryptDecrypt, testSignVerify });
+ const result = fn(
+ `
+ const { publicKey: publicKeyDER, privateKey: privateKeyDER } = generateKeyPairSync(
+ "rsa",
+ {
+ publicExponent: 0x10001,
+ modulusLength: 512,
+ publicKeyEncoding: {
+ type: "pkcs1",
+ format: "der",
+ },
+ privateKeyEncoding: {
+ type: "pkcs8",
+ format: "der",
+ },
+ }
+ );
+
+
+ assertApproximateSize(publicKeyDER, 74);
+
+ const publicKey = {
+ key: publicKeyDER,
+ type: "pkcs1",
+ format: "der",
+ };
+ const privateKey = {
+ key: privateKeyDER,
+ format: "der",
+ type: "pkcs8",
+ passphrase: "secret",
+ };
+ testEncryptDecrypt(publicKey, privateKey);
+ testSignVerify(publicKey, privateKey);
+ `,
+ context,
+ );
+ });
+ }
+ describe("Script", () => {
+ describe("runInContext()", () => {
+ testRunInContext((code, context, options) => {
+ // @ts-expect-error
+ const script = new Script(code, options);
+ return script.runInContext(context);
+ });
+ });
+ describe("runInNewContext()", () => {
+ testRunInContext((code, context, options) => {
+ // @ts-expect-error
+ const script = new Script(code, options);
+ return script.runInNewContext(context);
+ });
+ });
+ describe("runInThisContext()", () => {
+ testRunInContext((code, context, options) => {
+ // @ts-expect-error
+ const script = new Script(code, options);
+ return script.runInThisContext(context);
+ });
+ });
+ });
+});
+
+test.todo("RSA-PSS should work", async () => {
+ // Test RSA-PSS.
+ {
+ // This key pair does not restrict the message digest algorithm or salt
+ // length.
+ // const publicPem = fs.readFileSync(path.join(import.meta.dir, "fixtures", "rsa_pss_public_2048.pem"), "ascii");
+ // const privatePem = fs.readFileSync(path.join(import.meta.dir, "fixtures", "rsa_pss_private_2048.pem"), "ascii");
+ // const publicKey = createPublicKey(publicPem);
+ // const privateKey = createPrivateKey(privatePem);
+ // // Because no RSASSA-PSS-params appears in the PEM, no defaults should be
+ // // added for the PSS parameters. This is different from an empty
+ // // RSASSA-PSS-params sequence (see test below).
+ // const expectedKeyDetails = {
+ // modulusLength: 2048,
+ // publicExponent: 65537n,
+ // };
+ // expect(publicKey.type).toBe("public");
+ // expect(publicKey.asymmetricKeyType).toBe("rsa-pss");
+ // expect(publicKey.asymmetricKeyDetails).toBe(expectedKeyDetails);
+ // expect(privateKey.type).toBe("private");
+ // expect(privateKey.asymmetricKeyType).toBe("rsa-pss");
+ // expect(privateKey.asymmetricKeyDetails).toBe(expectedKeyDetails);
+ // assert.throws(
+ // () => publicKey.export({ format: 'jwk' }),
+ // { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
+ // assert.throws(
+ // () => privateKey.export({ format: 'jwk' }),
+ // { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
+ // for (const key of [privatePem, privateKey]) {
+ // // Any algorithm should work.
+ // for (const algo of ['sha1', 'sha256']) {
+ // // Any salt length should work.
+ // for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) {
+ // const signature = createSign(algo)
+ // .update('foo')
+ // .sign({ key, saltLength });
+ // for (const pkey of [key, publicKey, publicPem]) {
+ // const okay = createVerify(algo)
+ // .update('foo')
+ // .verify({ key: pkey, saltLength }, signature);
+ // assert.ok(okay);
+ // }
+ // }
+ // }
+ // }
+ // // Exporting the key using PKCS#1 should not work since this would discard
+ // // any algorithm restrictions.
+ // assert.throws(() => {
+ // publicKey.export({ format: 'pem', type: 'pkcs1' });
+ // }, {
+ // code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS'
+ // });
+ // {
+ // // This key pair enforces sha1 as the message digest and the MGF1
+ // // message digest and a salt length of 20 bytes.
+ // const publicPem = fixtures.readKey('rsa_pss_public_2048_sha1_sha1_20.pem');
+ // const privatePem =
+ // fixtures.readKey('rsa_pss_private_2048_sha1_sha1_20.pem');
+ // const publicKey = createPublicKey(publicPem);
+ // const privateKey = createPrivateKey(privatePem);
+ // // Unlike the previous key pair, this key pair contains an RSASSA-PSS-params
+ // // sequence. However, because all values in the RSASSA-PSS-params are set to
+ // // their defaults (see RFC 3447), the ASN.1 structure contains an empty
+ // // sequence. Node.js should add the default values to the key details.
+ // const expectedKeyDetails = {
+ // modulusLength: 2048,
+ // publicExponent: 65537n,
+ // hashAlgorithm: 'sha1',
+ // mgf1HashAlgorithm: 'sha1',
+ // saltLength: 20
+ // };
+ // assert.strictEqual(publicKey.type, 'public');
+ // assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
+ // assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
+ // assert.strictEqual(privateKey.type, 'private');
+ // assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
+ // assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
+ // }
+ // {
+ // // This key pair enforces sha256 as the message digest and the MGF1
+ // // message digest and a salt length of at least 16 bytes.
+ // const publicPem =
+ // fixtures.readKey('rsa_pss_public_2048_sha256_sha256_16.pem');
+ // const privatePem =
+ // fixtures.readKey('rsa_pss_private_2048_sha256_sha256_16.pem');
+ // const publicKey = createPublicKey(publicPem);
+ // const privateKey = createPrivateKey(privatePem);
+ // assert.strictEqual(publicKey.type, 'public');
+ // assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
+ // assert.strictEqual(privateKey.type, 'private');
+ // assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
+ // for (const key of [privatePem, privateKey]) {
+ // // Signing with anything other than sha256 should fail.
+ // assert.throws(() => {
+ // createSign('sha1').sign(key);
+ // }, /digest not allowed/);
+ // // Signing with salt lengths less than 16 bytes should fail.
+ // for (const saltLength of [8, 10, 12]) {
+ // assert.throws(() => {
+ // createSign('sha1').sign({ key, saltLength });
+ // }, /pss saltlen too small/);
+ // }
+ // // Signing with sha256 and appropriate salt lengths should work.
+ // for (const saltLength of [undefined, 16, 18, 20]) {
+ // const signature = createSign('sha256')
+ // .update('foo')
+ // .sign({ key, saltLength });
+ // for (const pkey of [key, publicKey, publicPem]) {
+ // const okay = createVerify('sha256')
+ // .update('foo')
+ // .verify({ key: pkey, saltLength }, signature);
+ // assert.ok(okay);
+ // }
+ // }
+ // }
+ // }
+ // {
+ // // This key enforces sha512 as the message digest and sha256 as the MGF1
+ // // message digest.
+ // const publicPem =
+ // fixtures.readKey('rsa_pss_public_2048_sha512_sha256_20.pem');
+ // const privatePem =
+ // fixtures.readKey('rsa_pss_private_2048_sha512_sha256_20.pem');
+ // const publicKey = createPublicKey(publicPem);
+ // const privateKey = createPrivateKey(privatePem);
+ // const expectedKeyDetails = {
+ // modulusLength: 2048,
+ // publicExponent: 65537n,
+ // hashAlgorithm: 'sha512',
+ // mgf1HashAlgorithm: 'sha256',
+ // saltLength: 20
+ // };
+ // assert.strictEqual(publicKey.type, 'public');
+ // assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
+ // assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
+ // assert.strictEqual(privateKey.type, 'private');
+ // assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
+ // assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
+ // // Node.js usually uses the same hash function for the message and for MGF1.
+ // // However, when a different MGF1 message digest algorithm has been
+ // // specified as part of the key, it should automatically switch to that.
+ // // This behavior is required by sections 3.1 and 3.3 of RFC4055.
+ // for (const key of [privatePem, privateKey]) {
+ // // sha256 matches the MGF1 hash function and should be used internally,
+ // // but it should not be permitted as the main message digest algorithm.
+ // for (const algo of ['sha1', 'sha256']) {
+ // assert.throws(() => {
+ // createSign(algo).sign(key);
+ // }, /digest not allowed/);
+ // }
+ // // sha512 should produce a valid signature.
+ // const signature = createSign('sha512')
+ // .update('foo')
+ // .sign(key);
+ // for (const pkey of [key, publicKey, publicPem]) {
+ // const okay = createVerify('sha512')
+ // .update('foo')
+ // .verify(pkey, signature);
+ // assert.ok(okay);
+ // }
+ // }
+ // }
+ // }
+ }
+});
diff --git a/test/js/node/crypto/fixtures/ec_p256_private.pem b/test/js/node/crypto/fixtures/ec_p256_private.pem
new file mode 100644
index 000000000..6bb0bb9cf
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p256_private.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDxBsPQPIgMuMyQbx
+zbb9toew6Ev6e9O6ZhpxLNgmAEqhRANCAARfSYxhH+6V5lIg+M3O0iQBLf+53kuE
+2luIgWnp81/Ya1Gybj8tl4tJVu1GEwcTyt8hoA7vRACmCHnI5B1+bNpS
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_p256_public.pem b/test/js/node/crypto/fixtures/ec_p256_public.pem
new file mode 100644
index 000000000..08f7bd26d
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p256_public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEX0mMYR/uleZSIPjNztIkAS3/ud5L
+hNpbiIFp6fNf2GtRsm4/LZeLSVbtRhMHE8rfIaAO70QApgh5yOQdfmzaUg==
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_p384_private.pem b/test/js/node/crypto/fixtures/ec_p384_private.pem
new file mode 100644
index 000000000..06393e263
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p384_private.pem
@@ -0,0 +1,6 @@
+-----BEGIN PRIVATE KEY-----
+MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB3B+4e4C1OUxGftkEI
+Gb/SCulzUP/iE940CB6+B6WWO4LT76T8sMWiwOAGUsuZmyKhZANiAASE43efMYmC
+/7Tx90elDGBEkVnOUr4ZkMZrl/cqe8zfVy++MmayPhR46Ah3LesMCNV+J0eG15w0
+IYJ8uqasuMN6drU1LNbNYfW7+hR0woajldJpvHMPv7wlnGOlzyxH1yU=
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_p384_public.pem b/test/js/node/crypto/fixtures/ec_p384_public.pem
new file mode 100644
index 000000000..2b50f3bbc
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p384_public.pem
@@ -0,0 +1,5 @@
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhON3nzGJgv+08fdHpQxgRJFZzlK+GZDG
+a5f3KnvM31cvvjJmsj4UeOgIdy3rDAjVfidHhtecNCGCfLqmrLjDena1NSzWzWH1
+u/oUdMKGo5XSabxzD7+8JZxjpc8sR9cl
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_p521_private.pem b/test/js/node/crypto/fixtures/ec_p521_private.pem
new file mode 100644
index 000000000..e4a8a655c
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p521_private.pem
@@ -0,0 +1,8 @@
+-----BEGIN PRIVATE KEY-----
+MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAEghuafcab9jXW4gO
+QLeDaKOlHEiskQFjiL8klijk6i6DNOXcFfaJ9GW48kxpodw16ttAf9Z1WQstfzpK
+GUetHImhgYkDgYYABAGixYI8Gbc5zNze6rH2/OmsFV3unOnY1GDqG9RTfpJZXpL9
+ChF1dG8HA4zxkM+X+jMSwm4THh0Wr1Euj9dK7E7QZwHd35XsQXgH13Hjc0QR9dvJ
+BWzlg+luNTY8CkaqiBdur5oFv/AjpXRimYxZDkhAEsTwXLwNohSUVMkN8IQtNI9D
+aQ==
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_p521_public.pem b/test/js/node/crypto/fixtures/ec_p521_public.pem
new file mode 100644
index 000000000..c0ed00f64
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_p521_public.pem
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBosWCPBm3Oczc3uqx9vzprBVd7pzp
+2NRg6hvUU36SWV6S/QoRdXRvBwOM8ZDPl/ozEsJuEx4dFq9RLo/XSuxO0GcB3d+V
+7EF4B9dx43NEEfXbyQVs5YPpbjU2PApGqogXbq+aBb/wI6V0YpmMWQ5IQBLE8Fy8
+DaIUlFTJDfCELTSPQ2k=
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_secp256k1_private.pem b/test/js/node/crypto/fixtures/ec_secp256k1_private.pem
new file mode 100644
index 000000000..f753c751b
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_secp256k1_private.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgc34ocwTwpFa9NZZh3l88
+qXyrkoYSxvC0FEsU5v1v4IOhRANCAARw7OEVKlbGFqUJtY10/Yf/JSR0LzUL1PZ1
+4Ol/ErujAPgNwwGU5PSD6aTfn9NycnYB2hby9XwB2qF3+El+DV8q
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ec_secp256k1_public.pem b/test/js/node/crypto/fixtures/ec_secp256k1_public.pem
new file mode 100644
index 000000000..e95322efb
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ec_secp256k1_public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEcOzhFSpWxhalCbWNdP2H/yUkdC81C9T2
+deDpfxK7owD4DcMBlOT0g+mk35/TcnJ2AdoW8vV8Adqhd/hJfg1fKg==
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/ed25519_private.pem b/test/js/node/crypto/fixtures/ed25519_private.pem
new file mode 100644
index 000000000..f837457cb
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ed25519_private.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIMFSujN0jIUIdzSvuxka0lfgVVkMdRTuaVvIYUHrvzXQ
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ed25519_public.pem b/test/js/node/crypto/fixtures/ed25519_public.pem
new file mode 100644
index 000000000..4127a471b
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ed25519_public.pem
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEAK1wIouqnuiA04b3WrMa+xKIKIpfHetNZRv3h9fBf768=
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/ed448_private.pem b/test/js/node/crypto/fixtures/ed448_private.pem
new file mode 100644
index 000000000..9643665d6
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ed448_private.pem
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MEcCAQAwBQYDK2VxBDsEOdOtCnu9bDdBqSHNNZ5xoDA5KdLBTUNPcKFaOADNX32s
+dfpo52pCtPqfku/l3/OfUHsF43EfZsaaWA==
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/ed448_public.pem b/test/js/node/crypto/fixtures/ed448_public.pem
new file mode 100644
index 000000000..b767109b1
--- /dev/null
+++ b/test/js/node/crypto/fixtures/ed448_public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEMwBQYDK2VxAzoAoX/ee5+jlcU53+BbGRsGIzly0V+SZtJ/oGXY0udf84q2hTW2
+RdstLktvwpkVJOoNb7oDgc2V5ZUA
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_private.pem b/test/js/node/crypto/fixtures/rsa_private.pem
new file mode 100644
index 000000000..215e5cc51
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_private.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAt9xYiIonscC3vz/A2ceR7KhZZlDu/5bye53nCVTcKnWd2seY
+6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq+/8i
+BkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2y/Hy4DjQKBq1ThZ0UBnK+9IhX37J
+u/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/kN2VnnbRUtkYTF97ggcv5h+hDpUQ
+jQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsFdi6hHcpZgbopPL630296iByyigQC
+PJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx9QIDAQABAoIBAQCS2erYu8gyoGPi
+3E/zYgQ6ishFAZWzDWSFubwD5wSm4SSAzvViL/RbO6kqS25xR569DmLRiHzD17VI
+mJMsNECUnPrqR2TL256OJZaXrNHh3I1lUwVhEzjeKMsL4/ys+d70XPXoiocVblVs
+moDXEIGEqa48ywPvVE3Fngeuxrsq3/GCVBNiwtt0YjAOZxmKEh31UZdHO+YI+wNF
+/Z8KQCPscN5HGlR0SIQOlqMANz49aKStrevdvjS1UcpabzDEkuK84g3saJhcpAhb
+pGFmAf5GTjkkhE0rE1qDF15dSqrKGfCFtOjUeK17SIEN7E322ChmTReZ1hYGfoSV
+cdFntUINAoGBAPFKL5QeJ6wZu8R/ru11wTG6sQA0Jub2hGccPXpbnPrT+3CACOLI
+JTCLy/xTKW3dqRHj/wZEe+jUw88w7jwGb1BkWr4BI8tDvY9jQLP1jyuLWRfrxXbp
+4Z0oeBBwBeCI/ZG7FIvdDTqWxn1aj3Tmh6s4ByqEdtwrrrJPcBUNl01fAoGBAMMR
+3RGE/ca6X6xz6kgUD6TtHVhiiRJK1jm/u+q0n7i/MBkeDgTZkHYS7lPc0yIdtqaI
+Plz5yzwHnAvuMrv8LSdkjwioig2yQa3tAij8kXxqs7wN5418DMV2s1OJBrPthYPs
+bv4im2iI8V63JQS4ZMYQbckq8ABYccTpOnxXDy0rAoGBAKkvzHa+QjERhjB9GyoT
+1FhLQIsVBmYSWrp1+cGO9V6HPxoeHJzvm+wTSf/uS/FmaINL6+j4Ii4a6gWgmJts
+I6cqBtqNsAx5vjQJczf8KdxthBYa0sXTrsfktXNJKUXMqIgDtp9vazQ2vozs8AQX
+FPAAhD3SzgkJdCBBRSTt97ZfAoGAWAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCD
+dCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP/Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNm
+bDXFPdG0G3hzQovx/9fajiRV4DWghLHeT9wzJfZabRRiI0VQR472300AVEeX4vgb
+rDBn600CgYEAk7czBCT9rHn/PNwCa17hlTy88C4vXkwbz83Oa+aX5L4e5gw5lhcR
+2ZuZHLb2r6oMt9rlD7EIDItSs+u21LOXWPTAlazdnpYUyw/CzogM/PN+qNwMRXn5
+uXFFhmlP2mVg2EdELTahXch8kWqHaCSX53yvqCtRKu/j76V31TfQZGM=
+-----END RSA PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_private_2048.pem b/test/js/node/crypto/fixtures/rsa_private_2048.pem
new file mode 100644
index 000000000..0e5bde2e0
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_private_2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEArk4OqxBqU5/k0FoUDU7CpZpjz6YJEXUpyqeJmFRVZPMUv/Rc
+7U4seLY+Qp6k26T/wlQ2WJWuyY+VJcbQNWLvjJWks5HWknwDuVs6sjuTM8CfHWn1
+960JkK5Ec2TjRhCQ1KJy+uc3GJLtWb4rWVgTbbaaC5fiR1/GeuJ8JH1Q50lB3mDs
+NGIk1U5jhNaYY82hYvlbErf6Ft5njHK0BOM5OTvQ6BBv7c363WNG7tYlNw1J40du
+p9OQPo5JmXN/h+sRbdgG8iUxrkRibuGv7loh52QQgq2snznuRMdKidRfUZjCDGgw
+bgK23Q7n8VZ9Y10j8PIvPTLJ83PX4lOEA37JlwIDAQABAoIBACoL2Ev5lLyBaI+9
++vJO2nNaL9OKSMu2SJODIJTnWwYUASBg0P3Jir6/r3sgi8IUJkH5UHbD/LrQcPkA
+4X7PU9vEyUsr1efWFIvk7t7JsjOctoVA5z2Mty74arivUIe5PUadvUC6/7Zk0u6A
+CjLuJRmlH7nGNKZk+xrvgWTH+fkgc5ddbFxoGH129RcVC+ePbsi1EF60C9KbJvp1
+xjUJ5cDtNYnZ/g+ULo6ZJjRG5kUCVSI8H/Nc/DmStKsjN0isKpNGofU5ArEwywGC
+Cqxz/tr4hT2haAkVEto04ooYpqDUSqGEfPpLWL+CjFNPfCsWJ1tX5LQRvpu6eukd
+FO72oVECgYEA4+Ot7RQtGOtPeaxl3P7sbEzBZk0W/ZhCk+mzE2iYh0QXU44VtjtO
+/9CENajTklckiwlxjqBZ5NO1SiMQKBcmqkoA03x/DEujo2rMVuNPoc6ZYp1Gc4qA
+4ImkMQNsM7Swum42rKE960WoiWW7dsdEAq6vqgeApZlMU8lcKRAlOZkCgYEAw85H
+3bjF7gMatVibsWzj0zp2L4616m2v5Z3YkgohGRAvm1912DI5ku5Nmy9am57Z1EP2
+UtDOxahd/Vf6mK9lR4YEbNW1TenykViQJ6lmljOFUeZEZYYO3O+fthkyN/42l5yn
+MyUANTTb2rvt8amdRr0ARdRqWJmt5NfJzYBV+q8CgYB1ZjuZoQVCiybcRcYMPX/K
+oxgW/avUZPYXgRNx8jZxqNBjiRUCVjdybhdOFXU5NI9s2SaZFV56Fd6VHM8b+CFB
+JPKcAMzqpqTccQ5nzJ6fevFl7iP3LekKw53EakD5uiI5SMH92OsvIymZ7sDOhgUx
+ZJC2hTrvFLRPjbJerSSgMQKBgAv5iZuduT0dI30DtkHbjvNUF/ZAnA+CNcetJ5mG
+1Q9bVg4CgIqAR9UcjdJ3yurJhDjfDylxa7Pa4CSmRMUhtOfy4kJlr3jcXeFVsTs7
+uPJmpDimBHjRAgew/+t7Dv8tpNkQ04jlMmYOnYN7CspEvUGePW4H15kjjOb563WN
+67QxAoGAdhJPaHVtDBUrobn50ORVkVNJVZPBhEDbR6tNtHsgEevH7iYjxAdxEeUa
+c9S2iV9lir3hkrQceiYWAADcphWfO1HyP5Uld4sJzYiEGbSM7a0zRQjYlQgXFbZo
+SAc6Gok78kwECPwpmeH4lpGVeKNmzEteSBVYxGb9b6C/SSsu7l0=
+-----END RSA PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_private_4096.pem b/test/js/node/crypto/fixtures/rsa_private_4096.pem
new file mode 100644
index 000000000..4177b9ef9
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_private_4096.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAxeStwofbjtZuol4lwKn1w08AzcSNLHfCqNFHa+W7er8is7LQ
+sPljtPT4yn4lsao83ngHFvSC3tbMiRNDpUHYqH2wBuUkuOmCtYkZLi0307H0CwcV
+V6W5P3tNEt80IJ+PqlRxtTknezUtbOasbIi/aornVWG+psgqDGrFZ4oTsWtiE0Sv
+i7sDqN5E2dijmH/YYnlnwqszgzHdtAnARp1bG34E64sqWCmLoGCfPdHtym/CSdxO
+LOsDV15jrwODZQ/TJZ5thkwKZRxu7g9fwlhA1PiI5WDP4reXNaqa2bSgrzpAljQE
+xYs4N0L7okSVOJQX9BEaoWtq8NLU8MpMdGoHNDU0Xr60Lfr58Z5qn8RGEvlTxoCb
+PJzPV2zgzD/lmEqft6NnfTclveA3sd8xSrOBUn4o3S8hS0b9Su7PBukHjM96/e0R
+eoIshSwXlQTLr2Ft8KwupyPm1ltNcTDtjqHcIWU6Bg+kPy9mxSVtGGZYAPtqGzNB
+A/m+oOja/OSPxAblPdln691DaDuZs5nuZCGwGcLaJWgiyoqvXAcyXDZFyH4OZZh8
+rsBLKbnFXHZ/ziG0cAozEygZEPJappw8Lx/ady7WL/SJjxooiKapc7Bnfy8eSLV3
++XAKxhLW/MQ6ChJ+e/8ExAY02ca4MpCvqwIk9TfV6FM8pWGqHzQFj0v3NL0CAwEA
+AQKCAgBTb8eTbZS09NRQwUFJql9kqbq9B1I+nYAFjbd/Vq1lY5FOEubKt1vCwEbl
+mapq7kwbwJ+8nftP2WEDqouq8chXwialwZdqH4ps4BEt1wLizvUGcUYeXlFs4p/s
+hQ+FccExH8mRjzeGSzWL5PZuDHoogchnx36K83pHIf15Wk5TT+NaHGunjoJMgOqm
+ryDK+5xQaL/G5Egj2LKRZksbet0fClMovNRtt5aXWCXL+uc3o0dXvPt5FN2jyLhe
+4ixUQAfWpKWpKgZ3+zUKSpElb/Bl2yRdEiSUgrPOfNAtWmsldnok2mnooHpjUmqm
+UCRaZpZy4YNI6/F6+Gmv3Ju/ubSvHzoxQLlvgUqWAnVshivF1TJImHSIiLIvBKPp
+29SD6maWIT1DC9sKC4E1gq7VO4762l1//zEOAY7XK0Z7LrbZO4WXHnsgFOpGthQ3
+g9Qi/SeM6mb5xEJTBUBTmkhGs1x8jolzca30mqv8T63W4PXkXHmZdK7vyH5useiI
+s0eGUeaYK892WgfxCBo24JCNQiAcH/wTwV4l4yROqeH2V4ShbIYmCzla++7vsPYW
+hAwQR9eH0+4ogTkaMQrm16plZk0ezVX9BKK8KTnd4G9/T18VstQbiowF2/cKnGKC
+OqrmoR2vHOksQdUJVmnwCRqU1symBxhY0GSIps98v+lUYExKQQKCAQEA/uVYE2/H
+eNcV/uWAI9LspANXHJE33TFMZ8SuyOYtp3MYJizmQ1uT7Om2LEanDnNiz+fAQhrE
+vo1sDIF9xOAde2qjIH+iDzcLvFPgC3gkQspFjU31M9OO5xAjzBxfL3KDiG2MtmTR
+hNuKJX56eCOqkEp6WKaWOA35ccaKYHxNzMS49weCv95ZPpR9q0J1sgzD7HtVh4yu
+XI01/BC8F0RmYjtsuUo+PmB6sO2K94uqqo0GPUos7Mhgrbff3L36EkOPgmRiA1AV
+Zy1sKKxUKspGQ3m1fg+CA/+GZGckvYkVot1lFrwmrS2dok8EhT1HcVJde+++jx7z
+JsRLgFRvKHXklwKCAQEAxsAfxIQjjjKmuyJCzIvxG7lnuzovdy4OEdSuJL4yK5m3
+4BHJHn+yHeRIcrDnJKUTUYffcH/OjOnJS94BA6wH1tEuvGQz6LV6UpwApZ1M/2md
+nP0eC2L2JtSRL8mdxfyqXDloWMpD7rncBZ6ChLEZ6sWYa6WBQTARmPVePyUpNNG2
+qymxN3/vRBGGBunD3j6zX0M0szWK5iU+qsYDy3KzCKG8FU7XxwzRbP7iARRD5Hpt
+Zmy2W52EJg1uhmlVXJMm32SEBfrD2oDmlnjAqaZdqi5Mq2e4uB3dhM9RwJppSALG
+BY6k9DeanAFbOlawMJri2pk7B0phCn+DN2pg0+W3ywKCAQBeTwzfZCQxmaMRxGg8
+2PWlWXcJotFAjdTvL95bho6tve/ZcBNiKKf6qB43E40L07VjpyODUdQpjLnFhsO5
+7BH8b+AbTh3v8zXsYDwtAi6oZ56EQavPmR7ubxJPms+9BmmUOLQvZ+39ch0S8lDt
+0oRxDp1l330FEGaSqhrYyCUg9khZXfYKd4IdnWNB0j0pu39iJ9/lXy/EHpsywB5X
+nX8kKUh45fdRrPC4NauNG6fxomwEkUU99oWOwNGbIs87orOeUvXQs/i3TB8QjXI2
+wtBsdsOn+KTqRci7rU3ysp3GvJOCbesBeDcyrnnFsn6Udx0Plgyzd4gPd+FXgeX+
+2l/RAoIBAH81FKAY2xD2RlTb1tlIcGeIQWZKFXs4VPUApP0LZt0VI+UcPRdyL7SG
+GgCeTTLdHQI/7rj4dGEoeRg/3XJWNyY8+KbHk5nMHaCmDJvzlAaduK10LDipfFba
+Epr9dif0Ua15aNn7i4NOHg7Sp0L6f1YOZkHvykzI0VqPIWVVCYyu9TWUF8Mn9SIh
+/SCLmjuy8ed1AlP5Xw9yoyt2VZNvtDtAGTuiHOVfxOL4N/rs149y9HZr+kOlC6G3
+Uxhgbqwz2tt8YCvblmNRwURpwRZUTvrPa28Bke713oRUlUSrD9txOwDvjZBpzmEv
+VQ5/0YEqgSvcizVdW8L2XiunwJWfIAUCggEBALr4RF9TYa37CImZOs+vJ8FGRKMz
+h1EUwO2PvuITvkTtu/7E4IjyxAo5dkAokkWQCGABciiDJJEYUWqcUX45qQChOgtm
+NU2od6f9tgyDFxN5KS8cE32NXV3rJXs3bBZmIKLSPETf3uIPuEpFPjpdR5v5jlV+
+TDjH4RrItE3hDCvypTXhXXMmWp3VfYbgEfIP03uR2iIhL+/g3BUqbrywPEsTViSN
+NM/uBDQyamXLXB1bQ2I/Ob41I82PD1iNCqGi7ZvZ3eVYGgUTQyw6Q4O8glTPP9cC
+SFVXwE9gHbLe8TqfTZCWrM6crGX6Bb6hV2tqNsA+7J69U9NGuw5GNqXjafU=
+-----END RSA PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_private_encrypted.pem b/test/js/node/crypto/fixtures/rsa_private_encrypted.pem
new file mode 100644
index 000000000..f1914289e
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_private_encrypted.pem
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,DB3D20E60E8FDC3356BD79712FF8EF7E
+
+K+vu0U3IFTJBBi6zW5Zng80O1jXq/ZmlOFs/j/SQpPwfW1Do9i/Dwa7ntBlTwrCm
+sd3IIPgu2ikfLwxvbxsZN540oCaCqaZ/bmmyzH3MyVDA9MllUu+X8+Q3ATzcYa9R
+U5XfF5DAXsSRnstCbmKagWVQpO0oX8k3ratfny6Ixq86Y82tK8+o5YiBFq1kqa+9
+4yat7IWQbqV5ifUtUPCHZwEqBt+WKazX05BqERjkckHdpfaDrBvSSPXTwoLm6uRR
+ktkUVpO4tHMZ4VlcTfFtpz8gdYYod0nM6vz26hvbESHSwztSgMhmKdsE5eqmYfgu
+F4WkEN4bqAiPjKK3jnUKPt/vg2oKYFQlVYFl9QnBjiRqcQTi3e9lwn1hI7uoMb6g
+HuaCc57JJHPN/ZLP3ts4ZxFbwUjTGioh5Zh6WozG3L3+Ujwq/sDrAskRyzdcuP7I
+Rs3oLbHY03OHyg8IbxR5Iu89l6FLqnR45yvbxXtZ7ImGOPM5Z9pB1CzDhGDx2F6g
+J/Kf/7ZF2DmYUVbVKDfESEDhRfuMAVzhasDPTRqipSA5QvJVQY+J/6QDPrNNmHVB
+4e4ouHIDWERUf0t1Be7THvP3X8OJozj2HApzqa5ZCaJDo8eaL8TCD5uH75ID5URJ
+VscGHaUXT8/sxfHi1x8BibW5W5J/akFsnrnJU/1BZgGznIxjf5tKfHGppSIVdlKP
+3ghYNmEIFPNJ6cxuUA0D2IOV4uO3FTCU6seIzvJhYkmXnticcZYGtmGxXKrodtzS
+J1YuaNkkO/YRZah285lQ6QCIhCFo4Oa4ILjgoTQISuw7nQj5ESyncauzLUBXKX0c
+XDUej64KNTvVF9UXdG48fYvNmSZWCnTye4UmPu17FmwpVra38U+EdoLyWyMIAI5t
+rP6Hhgc9BxOo41Im9QpTcAPfKAknP8Rbm3ACJG5T9FKq/c29d1E//eFR6SL51e/a
+yWdCgJN/FJOAX60+erPwoVoRFEttAeDPkklgFGdc8F4LIYAig9gEZ92ykFFz3fWz
+jIcUVLrL+IokFbPVUBoMihqVyMQsWH+5Qq9wjxf6EDIf0BVtm9U4BJoOkPStFIfF
+Kof7OVv7izyL8R/GIil9VQs9ftwkIUPeXx2Hw0bE3HJ3C8K4+mbLg3tKhGnBDU5Z
+Xm5mLHoCRBa3ZRFWZtigX7POszdLAzftYo8o65Be4OtPS+tQAORk9gHsXATv7dDB
+OGw61x5KA55LHVHhWaRvu3J8E7nhxw0q/HskyZhDC+Y+Xs6vmQSb4nO4ET4NYX1P
+m3PMdgGoqRDJ2jZw4eoQdRKCM0EHSepSAYpO1tcAXhPZS4ITogoRgPpVgOebEQUL
+nKNeNu/BxMSH/IH15jjDLF3TiEoguF9xdTaCxIBzE1SFpVO0u9m9vXpWdPThVgsb
+VcEI487p7v9iImP3BYPT8ZYvytC26EH0hyOrwhahTvTb4vXghkLIyvPUg1lZHc6e
+aPHb2AzYAHLnp/ehDQGKWrCOJ1JE2vBv8ZkLa+XZo7YASXBRZitPOMlvykEyzxmR
+QAmNhKGvFmeM2mmHAp0aC03rgF3lxNsXQ1CyfEdq3UV9ReSnttq8gtrJfCwxV+wY
+-----END RSA PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_pss_private_2048.pem b/test/js/node/crypto/fixtures/rsa_pss_private_2048.pem
new file mode 100644
index 000000000..ffca137a7
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_pss_private_2048.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADALBgkqhkiG9w0BAQoEggSoMIIEpAIBAAKCAQEAy4OMdS84PlgI5CRL
+bdbud9Ru7vprFr2YNNUmdT7D3YgApiv8CjzKXLiVDnbMET+lwmtag/EcZsxVCKov
+su30pYASBriHOiMVYui9+ZaJoQ9yI6lOjG1RbuUBJXNSjHBJxqBqmcgZOb1mdRr/
+eXzpAMWJ3hfuLojU2+zUSJ3/rvepepcLFG2q9nA0+PJskJ7Pnh3L0ydnv3U3hduM
+n5OVfm/Jx1FPyZpD184tJff+N+MY3s3hIcfuOnL9Pl4RPGeaTC4T1o460NaG6bG7
+c2Whg6NOaVgaFIaiNbrTTNCpVjeTyalsTXYlQQ3hiKjst0Q7pfFEkJDo8qiqLad1
+Msl59wIDAQABAoIBAQC6G8aqs0/f02nuGDLSc6cH9kCsUlz0ItW6GuJcfdVoFSNi
+0v5d7lGwkSveWk0ryOSw8rOHzUqHx3xLvDZ6jpkXcBMMClu/kq3QEb8JK90YaKOc
+cQvf52h83Pc7ZEatH1KYTcKudwp6fvXfSZ0vYEdD6WG2tHOgIollxSIsdjCHs1qi
+7baNHdK9T4DveuEZNcZ+LraZ1haHmFeqIPcy+KvpGuTaLCg5FPhH2jsIkw9apr7i
+iFLi+IJ7S5Bn/8XShmJWk4hPyx0jtIkC5r2iJnHf4x+XYWZfdo7oew3Dg6Pa7T6r
+I164Nnaw0u0LvO4gQdvYaJ/j9A602nHTp7Tsq8chAoGBAOtVHgIqpmdzwR5KjotW
+LuGXDdO9X6Xfge9ca2MlWH1jOj+zqEV7JtrjnZAzzOgP2kgqzpIR71Njs8wkaxTJ
+Tle0Ke6R/ghU9YOQgRByKjqJfQXHZnYFPsMg0diNYLroJ4SG8LO4+2SygTYZ4eKL
+qU0bda3QvQ7FL+rTNQBy01b9AoGBAN1jEQI80JxCT7AMvXE6nObIhbkASHte4yOE
+1CBwcOuBEGcuvMOvQVMzKITgea6+kgsu4ids4dM5PTPapQgpKqIIQ2/eSesaf56g
+73clGGSTPHJP0v+EfKg4+GYJf8o2swT0xDHkgWLgjjdsncB9hATc2j6DvHeau18d
+pgCLz9kDAoGAXl/SGvhTp2UqayVnKMW1I07agrGNLA4II5+iiS4u4InskCNSNhr/
+KATj6TJ82AuTdCGGmdmLapuvPQzVzI42VsGvlzcA8wJvOwW2XIwMF1GPy8N9eZL8
+6m+89+Uqh4oWXvVmjgx+9JEJdFLI3Xs4t+1tMfll+AhoAPoWZUmnK1kCgYAvEBxR
+iXQfg8lE97BeHcO1G/OxfGnsMCPBLT+bFcwrhGhkRv9B6kPM2BdJCB9WEpUhY3oY
+P4FSUdy85UIoFfhGMdOEOJEmNZ/jrPq7LVueJd63vlhwkU2exV2o82QDLNWpvA7p
+PFZ1Gp+hEKoIfaZPElQi7gZmtrIWaksb2pz42QKBgQCct9NP2qJfqeS4206RDnfv
+M238/O2lNhLWdSwY0g+tcN+I1sGs3+4vvrm95cxwAmXZyIM11wjdMcAPNxibodY7
+vufsebPHDBA0j0yuTjGkXefUKd1GdO88i5fppzxB7prDX9//DsWWrFhIMMRNYe0Q
+aeHd/NPuHcjZKcnaVBgukQ==
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_pss_public_2048.pem b/test/js/node/crypto/fixtures/rsa_pss_public_2048.pem
new file mode 100644
index 000000000..ade38f20a
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_pss_public_2048.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIDALBgkqhkiG9w0BAQoDggEPADCCAQoCggEBAMuDjHUvOD5YCOQkS23W7nfU
+bu76axa9mDTVJnU+w92IAKYr/Ao8yly4lQ52zBE/pcJrWoPxHGbMVQiqL7Lt9KWA
+Ega4hzojFWLovfmWiaEPciOpToxtUW7lASVzUoxwScagapnIGTm9ZnUa/3l86QDF
+id4X7i6I1Nvs1Eid/673qXqXCxRtqvZwNPjybJCez54dy9MnZ791N4XbjJ+TlX5v
+ycdRT8maQ9fOLSX3/jfjGN7N4SHH7jpy/T5eETxnmkwuE9aOOtDWhumxu3NloYOj
+TmlYGhSGojW600zQqVY3k8mpbE12JUEN4Yio7LdEO6XxRJCQ6PKoqi2ndTLJefcC
+AwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_public.pem b/test/js/node/crypto/fixtures/rsa_public.pem
new file mode 100644
index 000000000..8c30cfa52
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_public.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9xYiIonscC3vz/A2ceR
+7KhZZlDu/5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAG
+YbFswlNmeD44edEGM939B6Lq+/8iBkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2
+y/Hy4DjQKBq1ThZ0UBnK+9IhX37Ju/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/
+kN2VnnbRUtkYTF97ggcv5h+hDpUQjQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsF
+di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx
+9QIDAQAB
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_public_2048.pem b/test/js/node/crypto/fixtures/rsa_public_2048.pem
new file mode 100644
index 000000000..0c80ceb4d
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_public_2048.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArk4OqxBqU5/k0FoUDU7C
+pZpjz6YJEXUpyqeJmFRVZPMUv/Rc7U4seLY+Qp6k26T/wlQ2WJWuyY+VJcbQNWLv
+jJWks5HWknwDuVs6sjuTM8CfHWn1960JkK5Ec2TjRhCQ1KJy+uc3GJLtWb4rWVgT
+bbaaC5fiR1/GeuJ8JH1Q50lB3mDsNGIk1U5jhNaYY82hYvlbErf6Ft5njHK0BOM5
+OTvQ6BBv7c363WNG7tYlNw1J40dup9OQPo5JmXN/h+sRbdgG8iUxrkRibuGv7loh
+52QQgq2snznuRMdKidRfUZjCDGgwbgK23Q7n8VZ9Y10j8PIvPTLJ83PX4lOEA37J
+lwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/rsa_public_4096.pem b/test/js/node/crypto/fixtures/rsa_public_4096.pem
new file mode 100644
index 000000000..4fd0bbc1c
--- /dev/null
+++ b/test/js/node/crypto/fixtures/rsa_public_4096.pem
@@ -0,0 +1,14 @@
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeStwofbjtZuol4lwKn1
+w08AzcSNLHfCqNFHa+W7er8is7LQsPljtPT4yn4lsao83ngHFvSC3tbMiRNDpUHY
+qH2wBuUkuOmCtYkZLi0307H0CwcVV6W5P3tNEt80IJ+PqlRxtTknezUtbOasbIi/
+aornVWG+psgqDGrFZ4oTsWtiE0Svi7sDqN5E2dijmH/YYnlnwqszgzHdtAnARp1b
+G34E64sqWCmLoGCfPdHtym/CSdxOLOsDV15jrwODZQ/TJZ5thkwKZRxu7g9fwlhA
+1PiI5WDP4reXNaqa2bSgrzpAljQExYs4N0L7okSVOJQX9BEaoWtq8NLU8MpMdGoH
+NDU0Xr60Lfr58Z5qn8RGEvlTxoCbPJzPV2zgzD/lmEqft6NnfTclveA3sd8xSrOB
+Un4o3S8hS0b9Su7PBukHjM96/e0ReoIshSwXlQTLr2Ft8KwupyPm1ltNcTDtjqHc
+IWU6Bg+kPy9mxSVtGGZYAPtqGzNBA/m+oOja/OSPxAblPdln691DaDuZs5nuZCGw
+GcLaJWgiyoqvXAcyXDZFyH4OZZh8rsBLKbnFXHZ/ziG0cAozEygZEPJappw8Lx/a
+dy7WL/SJjxooiKapc7Bnfy8eSLV3+XAKxhLW/MQ6ChJ+e/8ExAY02ca4MpCvqwIk
+9TfV6FM8pWGqHzQFj0v3NL0CAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/x25519_private.pem b/test/js/node/crypto/fixtures/x25519_private.pem
new file mode 100644
index 000000000..926c4e3a2
--- /dev/null
+++ b/test/js/node/crypto/fixtures/x25519_private.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VuBCIEIJi/yFpueUawC1BkXyWM8ONIBGFjL7UZHrD/Zo/KPDpn
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/x25519_public.pem b/test/js/node/crypto/fixtures/x25519_public.pem
new file mode 100644
index 000000000..e2d756bd1
--- /dev/null
+++ b/test/js/node/crypto/fixtures/x25519_public.pem
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VuAyEAaSb8Q+RndwfNnPeOYGYPDUN3uhAPnMLzXyfi+mqfhig=
+-----END PUBLIC KEY-----
diff --git a/test/js/node/crypto/fixtures/x448_private.pem b/test/js/node/crypto/fixtures/x448_private.pem
new file mode 100644
index 000000000..61cd52c39
--- /dev/null
+++ b/test/js/node/crypto/fixtures/x448_private.pem
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MEYCAQAwBQYDK2VvBDoEOLTDbazv6vHZWOmODQ3kk8TUOQgApB4j75rpInT5zSLl
+/xJHK8ixF7f+4uo+mGTCrK1sktI5UmCZ
+-----END PRIVATE KEY-----
diff --git a/test/js/node/crypto/fixtures/x448_public.pem b/test/js/node/crypto/fixtures/x448_public.pem
new file mode 100644
index 000000000..6475d0438
--- /dev/null
+++ b/test/js/node/crypto/fixtures/x448_public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEIwBQYDK2VvAzkAioHSHVpTs6hMvghosEJDIR7ceFiE3+Xccxati64oOVJ7NWjf
+ozE7ae31PXIUFq6cVYgvSKsDFPA=
+-----END PUBLIC KEY-----
diff --git a/test/js/node/dns/node-dns.test.js b/test/js/node/dns/node-dns.test.js
index 5c08f128f..1988e9155 100644
--- a/test/js/node/dns/node-dns.test.js
+++ b/test/js/node/dns/node-dns.test.js
@@ -1,4 +1,4 @@
-import { expect, test } from "bun:test";
+import { describe, expect, test, it } from "bun:test";
import * as dns from "node:dns";
import * as dns_promises from "node:dns/promises";
import * as fs from "node:fs";
@@ -102,6 +102,15 @@ test("dns.resolveSoa (bun.sh)", done => {
});
});
+test("dns.resolveSoa (empty string)", done => {
+ dns.resolveSoa("", (err, result) => {
+ expect(err).toBeNull();
+ // one of root server
+ expect(result).not.toBeUndefined();
+ done(err);
+ });
+});
+
test("dns.resolveNaptr (naptr.socketify.dev)", done => {
dns.resolveNaptr("naptr.socketify.dev", (err, results) => {
expect(err).toBeNull();
@@ -132,7 +141,7 @@ test("dns.resolveMx (bun.sh)", done => {
expect(results instanceof Array).toBe(true);
const priority = results[0].priority;
expect(priority >= 0 && priority < 65535).toBe(true);
- expect(results[0].exchange.includes(".registrar-servers.com")).toBe(true);
+ expect(results[0].exchange.includes("aspmx.l.google.com")).toBe(true);
done(err);
});
});
@@ -146,6 +155,32 @@ test("dns.resolveNs (bun.sh) ", done => {
});
});
+test("dns.resolveNs (empty string) ", done => {
+ dns.resolveNs("", (err, results) => {
+ expect(err).toBeNull();
+ expect(results instanceof Array).toBe(true);
+ // root servers
+ expect(results.sort()).toStrictEqual(
+ [
+ "e.root-servers.net",
+ "h.root-servers.net",
+ "l.root-servers.net",
+ "i.root-servers.net",
+ "a.root-servers.net",
+ "d.root-servers.net",
+ "c.root-servers.net",
+ "b.root-servers.net",
+ "j.root-servers.net",
+ "k.root-servers.net",
+ "g.root-servers.net",
+ "m.root-servers.net",
+ "f.root-servers.net",
+ ].sort(),
+ );
+ done(err);
+ });
+});
+
test("dns.resolvePtr (ptr.socketify.dev)", done => {
dns.resolvePtr("ptr.socketify.dev", (err, results) => {
expect(err).toBeNull();
@@ -268,3 +303,105 @@ test("dns.promises.reverse", async () => {
expect(hostnames).toContain("one.one.one.one");
}
});
+
+describe("test invalid arguments", () => {
+ it.each([
+ // TODO: dns.resolveAny is not implemented yet
+ ["dns.resolveCname", dns.resolveCname],
+ ["dns.resolveCaa", dns.resolveCaa],
+ ["dns.resolveMx", dns.resolveMx],
+ ["dns.resolveNaptr", dns.resolveNaptr],
+ ["dns.resolveNs", dns.resolveNs],
+ ["dns.resolvePtr", dns.resolvePtr],
+ ["dns.resolveSoa", dns.resolveSoa],
+ ["dns.resolveSrv", dns.resolveSrv],
+ ["dns.resolveTxt", dns.resolveTxt],
+ ])("%s", (_, fn, done) => {
+ fn("a".repeat(2000), (err, results) => {
+ try {
+ expect(err).not.toBeNull();
+ expect(results).toBeUndefined();
+ done();
+ } catch (e) {
+ done(e);
+ }
+ });
+ });
+
+ it("dns.lookupService", async () => {
+ expect(() => {
+ dns.lookupService("", 443, (err, hostname, service) => {});
+ }).toThrow("Expected address to be a non-empty string for 'lookupService'.");
+ expect(() => {
+ dns.lookupService("google.com", 443, (err, hostname, service) => {});
+ }).toThrow("Expected address to be a invalid address for 'lookupService'.");
+ });
+});
+
+describe("dns.lookupService", () => {
+ it.each([
+ ["1.1.1.1", 53, ["one.one.one.one", "domain"]],
+ ["2606:4700:4700::1111", 53, ["one.one.one.one", "domain"]],
+ ["2606:4700:4700::1001", 53, ["one.one.one.one", "domain"]],
+ ["1.1.1.1", 80, ["one.one.one.one", "http"]],
+ ["1.1.1.1", 443, ["one.one.one.one", "https"]],
+ ])("lookupService(%s, %d)", (address, port, expected, done) => {
+ dns.lookupService(address, port, (err, hostname, service) => {
+ try {
+ expect(err).toBeNull();
+ expect(hostname).toStrictEqual(expected[0]);
+ expect(service).toStrictEqual(expected[1]);
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ });
+
+ it("lookupService(255.255.255.255, 443)", done => {
+ dns.lookupService("255.255.255.255", 443, (err, hostname, service) => {
+ if (process.platform == "darwin") {
+ try {
+ expect(err).toBeNull();
+ expect(hostname).toStrictEqual("broadcasthost");
+ expect(service).toStrictEqual("https");
+ done();
+ } catch (err) {
+ done(err);
+ }
+ } else {
+ try {
+ expect(err).not.toBeNull();
+ expect(hostname).toBeUndefined();
+ expect(service).toBeUndefined();
+ done();
+ } catch (err) {
+ done(err);
+ }
+ }
+ });
+ });
+
+ it.each([
+ ["1.1.1.1", 53, ["one.one.one.one", "domain"]],
+ ["2606:4700:4700::1111", 53, ["one.one.one.one", "domain"]],
+ ["2606:4700:4700::1001", 53, ["one.one.one.one", "domain"]],
+ ["1.1.1.1", 80, ["one.one.one.one", "http"]],
+ ["1.1.1.1", 443, ["one.one.one.one", "https"]],
+ ])("promises.lookupService(%s, %d)", async (address, port, expected) => {
+ const [hostname, service] = await dns.promises.lookupService(address, port);
+ expect(hostname).toStrictEqual(expected[0]);
+ expect(service).toStrictEqual(expected[1]);
+ });
+});
+
+// Deprecated reference: https://nodejs.org/api/deprecations.html#DEP0118
+describe("lookup deprecated behavior", () => {
+ it.each([undefined, false, null, NaN, ""])("dns.lookup", domain => {
+ dns.lookup(domain, (error, address, family) => {
+ expect(error).toBeNull();
+ expect(address).toBeNull();
+ expect(family).toBe(4);
+ });
+ });
+});
diff --git a/test/js/node/events/event-emitter.test.ts b/test/js/node/events/event-emitter.test.ts
index 366bbb03a..687e90910 100644
--- a/test/js/node/events/event-emitter.test.ts
+++ b/test/js/node/events/event-emitter.test.ts
@@ -183,6 +183,21 @@ describe("EventEmitter", () => {
emitter.on("wow", () => done());
setTimeout(() => emitter.emit("wow"), 1);
});
+
+ test("emit multiple values", () => {
+ const emitter = new EventEmitter();
+
+ const receivedVals: number[] = [];
+ emitter.on("multiple-vals", (val1, val2, val3) => {
+ receivedVals[0] = val1;
+ receivedVals[1] = val2;
+ receivedVals[2] = val3;
+ });
+
+ emitter.emit("multiple-vals", 1, 2, 3);
+
+ expect(receivedVals).toEqual([1, 2, 3]);
+ });
});
test("addListener return type", () => {
@@ -278,6 +293,60 @@ describe("EventEmitter", () => {
expect(order).toEqual([3, 2, 1, 4, 1, 4]);
});
+ test("prependListener in callback", () => {
+ const myEmitter = new EventEmitter();
+ const order: number[] = [];
+
+ myEmitter.on("foo", () => {
+ order.push(1);
+ });
+
+ myEmitter.once("foo", () => {
+ myEmitter.prependListener("foo", () => {
+ order.push(2);
+ });
+ });
+
+ myEmitter.on("foo", () => {
+ order.push(3);
+ });
+
+ myEmitter.emit("foo");
+
+ expect(order).toEqual([1, 3]);
+
+ myEmitter.emit("foo");
+
+ expect(order).toEqual([1, 3, 2, 1, 3]);
+ });
+
+ test("addListener in callback", () => {
+ const myEmitter = new EventEmitter();
+ const order: number[] = [];
+
+ myEmitter.on("foo", () => {
+ order.push(1);
+ });
+
+ myEmitter.once("foo", () => {
+ myEmitter.addListener("foo", () => {
+ order.push(2);
+ });
+ });
+
+ myEmitter.on("foo", () => {
+ order.push(3);
+ });
+
+ myEmitter.emit("foo");
+
+ expect(order).toEqual([1, 3]);
+
+ myEmitter.emit("foo");
+
+ expect(order).toEqual([1, 3, 1, 3, 2]);
+ });
+
test("listeners", () => {
const myEmitter = new EventEmitter();
const fn = () => {};
@@ -288,18 +357,26 @@ describe("EventEmitter", () => {
expect(myEmitter.listeners("foo")).toEqual([fn, fn2]);
myEmitter.off("foo", fn2);
expect(myEmitter.listeners("foo")).toEqual([fn]);
+ const fn3 = () => {};
+ myEmitter.once("foo", fn3);
+ expect(myEmitter.listeners("foo")).toEqual([fn, fn3]);
});
test("rawListeners", () => {
const myEmitter = new EventEmitter();
const fn = () => {};
myEmitter.on("foo", fn);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
+ expect(myEmitter.rawListeners("foo")).toEqual([fn]);
const fn2 = () => {};
myEmitter.on("foo", fn2);
- expect(myEmitter.listeners("foo")).toEqual([fn, fn2]);
+ expect(myEmitter.rawListeners("foo")).toEqual([fn, fn2]);
myEmitter.off("foo", fn2);
- expect(myEmitter.listeners("foo")).toEqual([fn]);
+ expect(myEmitter.rawListeners("foo")).toEqual([fn]);
+ const fn3 = () => {};
+ myEmitter.once("foo", fn3);
+ const rawListeners: (Function & { listener?: Function })[] = myEmitter.rawListeners("foo");
+ // rawListeners() returns onceWrappers as well
+ expect([rawListeners[0], rawListeners[1].listener]).toEqual([fn, fn3]);
});
test("eventNames", () => {
diff --git a/test/js/node/fs/fs.test.ts b/test/js/node/fs/fs.test.ts
index 8ea9522e1..791386956 100644
--- a/test/js/node/fs/fs.test.ts
+++ b/test/js/node/fs/fs.test.ts
@@ -136,6 +136,35 @@ describe("copyFileSync", () => {
expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(Bun.hash(buffer.buffer));
});
+ it("constants are right", () => {
+ expect(fs.constants.COPYFILE_EXCL).toBe(1);
+ expect(fs.constants.COPYFILE_FICLONE).toBe(2);
+ expect(fs.constants.COPYFILE_FICLONE_FORCE).toBe(4);
+ });
+
+ it("FICLONE option does not error ever", () => {
+ const tempdir = `${tmpdir()}/fs.test.js/${Date.now()}.FICLONE/1234/hi`;
+ expect(existsSync(tempdir)).toBe(false);
+ expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
+
+ // that don't exist
+ copyFileSync(import.meta.path, tempdir + "/copyFileSync.js", fs.constants.COPYFILE_FICLONE);
+ copyFileSync(import.meta.path, tempdir + "/copyFileSync.js", fs.constants.COPYFILE_FICLONE);
+ copyFileSync(import.meta.path, tempdir + "/copyFileSync.js", fs.constants.COPYFILE_FICLONE);
+ });
+
+ it("COPYFILE_EXCL works", () => {
+ const tempdir = `${tmpdir()}/fs.test.js/${Date.now()}.COPYFILE_EXCL/1234/hi`;
+ expect(existsSync(tempdir)).toBe(false);
+ expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
+
+ // that don't exist
+ copyFileSync(import.meta.path, tempdir + "/copyFileSync.js", fs.constants.COPYFILE_EXCL);
+ expect(() => {
+ copyFileSync(import.meta.path, tempdir + "/copyFileSync.js", fs.constants.COPYFILE_EXCL);
+ }).toThrow();
+ });
+
if (process.platform === "linux") {
describe("should work when copyFileRange is not available", () => {
it("on large files", () => {
@@ -212,7 +241,7 @@ describe("copyFileSync", () => {
describe("mkdirSync", () => {
it("should create a directory", () => {
- const tempdir = `${tmpdir()}/fs.test.js/${Date.now()}/1234/hi`;
+ const tempdir = `${tmpdir()}/fs.test.js/${Date.now()}.mkdirSync/1234/hi`;
expect(existsSync(tempdir)).toBe(false);
expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
expect(existsSync(tempdir)).toBe(true);
@@ -953,6 +982,64 @@ describe("stat", () => {
} catch (e: any) {
expect(e.code).toBe("ENOENT");
}
+
+ try {
+ statSync("");
+ throw "statSync should throw";
+ } catch (e: any) {
+ expect(e.code).toBe("ENOENT");
+ }
+ });
+});
+
+describe("exist", () => {
+ it("should return false with invalid path", () => {
+ expect(existsSync("/pathNotExist")).toBe(false);
+ });
+
+ it("should return false with empty string", () => {
+ expect(existsSync("")).toBe(false);
+ });
+});
+
+describe("fs.exists", () => {
+ it("should throw TypeError with invalid argument", done => {
+ let err = undefined;
+ try {
+ // @ts-ignore
+ fs.exists(import.meta.path);
+ } catch (e) {
+ err = e;
+ }
+ try {
+ expect(err).not.toBeUndefined();
+ expect(err).toBeInstanceOf(TypeError);
+ // @ts-ignore
+ expect(err.code).toStrictEqual("ERR_INVALID_ARG_TYPE");
+ done();
+ } catch (e) {
+ done(e);
+ }
+ });
+ it("should return false with invalid path", done => {
+ fs.exists(`${tmpdir()}/test-fs-exists-${Date.now()}`, exists => {
+ try {
+ expect(exists).toBe(false);
+ done();
+ } catch (e) {
+ done(e);
+ }
+ });
+ });
+ it("should return true with existed path", done => {
+ fs.exists(import.meta.path, exists => {
+ try {
+ expect(exists).toBe(true);
+ done();
+ } catch (e) {
+ done(e);
+ }
+ });
});
});
@@ -1016,19 +1103,41 @@ describe("rmdir", () => {
done();
});
});
- it("does not remove a dir with a file in it", done => {
+
+ it("removes a dir x 512", async () => {
+ var queue = new Array(512);
+ var paths = new Array(512);
+ for (let i = 0; i < 512; i++) {
+ const path = `${tmpdir()}/${Date.now()}.rm.dir${i}`;
+ try {
+ mkdirSync(path);
+ } catch (e) {}
+ paths[i] = path;
+ queue[i] = promises.rmdir(path);
+ }
+
+ await Promise.all(queue);
+
+ for (let i = 0; i < 512; i++) {
+ expect(existsSync(paths[i])).toBe(false);
+ }
+ });
+ it("does not remove a dir with a file in it", async () => {
const path = `${tmpdir()}/${Date.now()}.rm.dir`;
try {
mkdirSync(path);
writeFileSync(`${path}/file.txt`, "File written successfully", "utf8");
} catch (e) {}
expect(existsSync(path + "/file.txt")).toBe(true);
- rmdir(path, err => {
+ try {
+ await promises.rmdir(path);
+ } catch (err) {
expect("ENOTEMPTY EPERM").toContain(err!.code);
- done();
- });
+ }
+
expect(existsSync(path + "/file.txt")).toBe(true);
- rmdir(path, { recursive: true }, () => {});
+
+ await promises.rmdir(path, { recursive: true });
expect(existsSync(path + "/file.txt")).toBe(false);
});
it("removes a dir recursively", done => {
diff --git a/test/js/node/http/fixtures/cert.key b/test/js/node/http/fixtures/cert.key
new file mode 100644
index 000000000..bf41b7883
--- /dev/null
+++ b/test/js/node/http/fixtures/cert.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCIzOJskt6VkEJY
+XKSJv/Gdil3XYkjk3NVc/+m+kzqnkTRbPtT9w+IGWgmJhuf9DJPLCwHFAEFarVwV
+x16Q0PbU4ajXaLRHEYGhrH10oTMjQnJ24xVm26mxRXPQa5vaLpWJqNyIdNLIQLe+
+UXUOzSGGsFTRMAjvYrkzjBe4ZUnaZV+aFY/ug0jfzeA1dJjzKZs6+yTJRbsuWUEb
+8MsDmT4v+kBZDKdaDn7AFDWRVqx/38BnqsRzkM0CxpnyT2kRzw5zQajIE13gdTJo
+1EHvYSUkkxrY5m30Rl9BuBBZBjhMzOHq0fYVVooHO+sf4XHPgvFTTxJum85u7J1J
+oEUjrLKtAgMBAAECggEACInVNhaiqu4infZGVMy0rXMV8VwSlapM7O2SLtFsr0nK
+XUmaLK6dvGzBPKK9dxdiYCFzPlMKQTkhzsAvYFWSmm3tRmikG+11TFyCRhXLpc8/
+ark4vD9Io6ZkmKUmyKLwtXNjNGcqQtJ7RXc7Ga3nAkueN6JKZHqieZusXVeBGQ70
+YH1LKyVNBeJggbj+g9rqaksPyNJQ8EWiNTJkTRQPazZ0o1VX/fzDFyr/a5npFtHl
+4BHfafv9o1Xyr70Kie8CYYRJNViOCN+ylFs7Gd3XRaAkSkgMT/7DzrHdEM2zrrHK
+yNg2gyDVX9UeEJG2X5UtU0o9BVW7WBshz/2hqIUHoQKBgQC8zsRFvC7u/rGr5vRR
+mhZZG+Wvg03/xBSuIgOrzm+Qie6mAzOdVmfSL/pNV9EFitXt1yd2ROo31AbS7Evy
+Bm/QVKr2mBlmLgov3B7O/e6ABteooOL7769qV/v+yo8VdEg0biHmsfGIIXDe3Lwl
+OT0XwF9r/SeZLbw1zfkSsUVG/QKBgQC5fANM3Dc9LEek+6PHv5+eC1cKkyioEjUl
+/y1VUD00aABI1TUcdLF3BtFN2t/S6HW0hrP3KwbcUfqC25k+GDLh1nM6ZK/gI3Yn
+IGtCHxtE3S6jKhE9QcK/H+PzGVKWge9SezeYRP0GHJYDrTVTA8Kt9HgoZPPeReJl
++Ss9c8ThcQKBgECX6HQHFnNzNSufXtSQB7dCoQizvjqTRZPxVRoxDOABIGExVTYt
+umUhPtu5AGyJ+/hblEeU+iBRbGg6qRzK8PPwE3E7xey8MYYAI5YjL7YjISKysBUL
+AhM6uJ6Jg/wOBSnSx8xZ8kzlS+0izUda1rjKeprCSArSp8IsjlrDxPStAoGAEcPr
++P+altRX5Fhpvmb/Hb8OTif8G+TqjEIdkG9H/W38oP0ywg/3M2RGxcMx7txu8aR5
+NjI7zPxZFxF7YvQkY3cLwEsGgVxEI8k6HLIoBXd90Qjlb82NnoqqZY1GWL4HMwo0
+L/Rjm6M/Rwje852Hluu0WoIYzXA6F/Q+jPs6nzECgYAxx4IbDiGXuenkwSF1SUyj
+NwJXhx4HDh7U6EO/FiPZE5BHE3BoTrFu3o1lzverNk7G3m+j+m1IguEAalHlukYl
+rip9iUISlKYqbYZdLBoLwHAfHhszdrjqn8/v6oqbB5yR3HXjPFUWJo0WJ2pqJp56
+ZshgmQQ/5Khoj6x0/dMPSg==
+-----END PRIVATE KEY-----
diff --git a/test/js/node/http/fixtures/cert.pem b/test/js/node/http/fixtures/cert.pem
new file mode 100644
index 000000000..8ae1c1ea4
--- /dev/null
+++ b/test/js/node/http/fixtures/cert.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID5jCCAs6gAwIBAgIUN7coIsdMcLo9amZfkwogu0YkeLEwDQYJKoZIhvcNAQEL
+BQAwfjELMAkGA1UEBhMCU0UxDjAMBgNVBAgMBVN0YXRlMREwDwYDVQQHDAhMb2Nh
+dGlvbjEaMBgGA1UECgwRT3JnYW5pemF0aW9uIE5hbWUxHDAaBgNVBAsME09yZ2Fu
+aXphdGlvbmFsIFVuaXQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzA5MjExNDE2
+MjNaFw0yNDA5MjAxNDE2MjNaMH4xCzAJBgNVBAYTAlNFMQ4wDAYDVQQIDAVTdGF0
+ZTERMA8GA1UEBwwITG9jYXRpb24xGjAYBgNVBAoMEU9yZ2FuaXphdGlvbiBOYW1l
+MRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRIwEAYDVQQDDAlsb2NhbGhv
+c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCIzOJskt6VkEJYXKSJ
+v/Gdil3XYkjk3NVc/+m+kzqnkTRbPtT9w+IGWgmJhuf9DJPLCwHFAEFarVwVx16Q
+0PbU4ajXaLRHEYGhrH10oTMjQnJ24xVm26mxRXPQa5vaLpWJqNyIdNLIQLe+UXUO
+zSGGsFTRMAjvYrkzjBe4ZUnaZV+aFY/ug0jfzeA1dJjzKZs6+yTJRbsuWUEb8MsD
+mT4v+kBZDKdaDn7AFDWRVqx/38BnqsRzkM0CxpnyT2kRzw5zQajIE13gdTJo1EHv
+YSUkkxrY5m30Rl9BuBBZBjhMzOHq0fYVVooHO+sf4XHPgvFTTxJum85u7J1JoEUj
+rLKtAgMBAAGjXDBaMA4GA1UdDwEB/wQEAwIDiDATBgNVHSUEDDAKBggrBgEFBQcD
+ATAUBgNVHREEDTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFNzx4Rfs9m8XR5ML0WsI
+sorKmB4PMA0GCSqGSIb3DQEBCwUAA4IBAQB87iQy8R0fiOky9WTcyzVeMaavS3MX
+iTe1BRn1OCyDq+UiwwoNz7zdzZJFEmRtFBwPNFOe4HzLu6E+7yLFR552eYRHlqIi
+/fiLb5JiZfPtokUHeqwELWBsoXtU8vKxViPiLZ09jkWOPZWo7b/xXd6QYykBfV91
+usUXLzyTD2orMagpqNksLDGS3p3ggHEJBZtRZA8R7kPEw98xZHznOQpr26iv8kYz
+ZWdLFoFdwgFBSfxePKax5rfo+FbwdrcTX0MhbORyiu2XsBAghf8s2vKDkHg2UQE8
+haonxFYMFaASfaZ/5vWKYDTCJkJ67m/BtkpRafFEO+ad1i1S61OjfxH4
+-----END CERTIFICATE-----
diff --git a/test/js/node/http/node-http.test.ts b/test/js/node/http/node-http.test.ts
index f80a6c631..269970cb7 100644
--- a/test/js/node/http/node-http.test.ts
+++ b/test/js/node/http/node-http.test.ts
@@ -8,20 +8,25 @@ import {
Server,
validateHeaderName,
validateHeaderValue,
+ ServerResponse,
} from "node:http";
+import { createServer as createHttpsServer } from "node:https";
import { createTest } from "node-harness";
import url from "node:url";
import { tmpdir } from "node:os";
import { spawnSync } from "node:child_process";
+import nodefs from "node:fs";
+import { join as joinPath } from "node:path";
+import { unlinkSync } from "node:fs";
const { describe, expect, it, beforeAll, afterAll, createDoneDotAll } = createTest(import.meta.path);
-function listen(server: Server): Promise<URL> {
+function listen(server: Server, protocol: string = "http"): Promise<URL> {
return new Promise((resolve, reject) => {
server.listen({ port: 0 }, (err, hostname, port) => {
if (err) {
reject(err);
} else {
- resolve(new URL(`http://${hostname}:${port}`));
+ resolve(new URL(`${protocol}://${hostname}:${port}`));
}
});
setTimeout(() => reject("Timed out"), 5000);
@@ -46,7 +51,22 @@ describe("node:http", () => {
server.close();
}
});
-
+ it("is not marked encrypted (#5867)", async () => {
+ try {
+ var server = createServer((req, res) => {
+ expect(req.connection.encrypted).toBe(undefined);
+ res.writeHead(200, { "Content-Type": "text/plain" });
+ res.end("Hello World");
+ });
+ const url = await listen(server);
+ const res = await fetch(new URL("", url));
+ expect(await res.text()).toBe("Hello World");
+ } catch (e) {
+ throw e;
+ } finally {
+ server.close();
+ }
+ });
it("request & response body streaming (large)", async () => {
try {
const bodyBlob = new Blob(["hello world", "hello world".repeat(9000)]);
@@ -113,6 +133,23 @@ describe("node:http", () => {
});
});
+ describe("response", () => {
+ test("set-cookie works with getHeader", () => {
+ const res = new ServerResponse({});
+ res.setHeader("Set-Cookie", ["swag=true", "yolo=true"]);
+ expect(res.getHeader("Set-Cookie")).toEqual(["swag=true", "yolo=true"]);
+ });
+ test("set-cookie works with getHeaders", () => {
+ const res = new ServerResponse({});
+ res.setHeader("Set-Cookie", ["swag=true", "yolo=true"]);
+ res.setHeader("test", "test");
+ expect(res.getHeaders()).toEqual({
+ "Set-Cookie": ["swag=true", "yolo=true"],
+ "test": "test",
+ });
+ });
+ });
+
describe("request", () => {
function runTest(done: Function, callback: (server: Server, port: number, done: (err?: Error) => void) => void) {
var timer;
@@ -241,6 +278,27 @@ describe("node:http", () => {
// });
// });
+ it("should not insert extraneous accept-encoding header", async done => {
+ try {
+ let headers;
+ var server = createServer((req, res) => {
+ headers = req.headers;
+ req.on("data", () => {});
+ req.on("end", () => {
+ res.end();
+ });
+ });
+ const url = await listen(server);
+ await fetch(url, { decompress: false });
+ expect(headers["accept-encoding"]).toBeFalsy();
+ done();
+ } catch (e) {
+ done(e);
+ } finally {
+ server.close();
+ }
+ });
+
it("should make a standard GET request when passed string as first arg", done => {
runTest(done, (server, port, done) => {
const req = request(`http://localhost:${port}`, res => {
@@ -887,4 +945,137 @@ describe("node:http", () => {
}
});
});
+
+ test("error event not fired, issue#4651", done => {
+ const server = createServer((req, res) => {
+ res.end();
+ });
+ server.listen({ port: 42069 }, () => {
+ const server2 = createServer((_, res) => {
+ res.end();
+ });
+ server2.on("error", err => {
+ expect(err.code).toBe("EADDRINUSE");
+ done();
+ });
+ server2.listen({ port: 42069 }, () => {});
+ });
+ });
+});
+describe("node https server", async () => {
+ const httpsOptions = {
+ key: nodefs.readFileSync(joinPath(import.meta.dir, "fixtures", "cert.key")),
+ cert: nodefs.readFileSync(joinPath(import.meta.dir, "fixtures", "cert.pem")),
+ };
+ const createServer = onRequest => {
+ return new Promise(resolve => {
+ const server = createHttpsServer(httpsOptions, (req, res) => {
+ onRequest(req, res);
+ });
+ listen(server, "https").then(url => {
+ resolve({
+ server,
+ done: () => server.close(),
+ url,
+ });
+ });
+ });
+ };
+ it("is marked encrypted (#5867)", async () => {
+ const { server, url, done } = await createServer(async (req, res) => {
+ expect(req.connection.encrypted).toBe(true);
+ res.end();
+ });
+ try {
+ await fetch(url);
+ } catch (e) {
+ throw e;
+ } finally {
+ done();
+ }
+ });
+});
+
+describe("server.address should be valid IP", () => {
+ it("should return null before listening", done => {
+ const server = createServer((req, res) => {});
+ try {
+ expect(server.address()).toBeNull();
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ it("should return null after close", done => {
+ const server = createServer((req, res) => {});
+ server.listen(0, async (_err, host, port) => {
+ try {
+ expect(server.address()).not.toBeNull();
+ server.close();
+ expect(server.address()).toBeNull();
+ done();
+ } catch (err) {
+ done(err);
+ }
+ });
+ });
+ it("test default hostname, issue#5850", done => {
+ const server = createServer((req, res) => {});
+ server.listen(0, async (_err, host, port) => {
+ try {
+ const { address, family, port } = server.address();
+ expect(port).toBeInteger();
+ expect(port).toBeGreaterThan(0);
+ expect(port).toBeLessThan(65536);
+ expect(["::", "0.0.0.0"]).toContain(address);
+ if (address === "0.0.0.0") {
+ expect(family).toStrictEqual("IPv4");
+ } else {
+ expect(family).toStrictEqual("IPv6");
+ }
+ done();
+ } catch (err) {
+ done(err);
+ } finally {
+ server.close();
+ }
+ });
+ });
+ it.each([["localhost"], ["127.0.0.1"]])("test %s", (hostname, done) => {
+ const server = createServer((req, res) => {});
+ server.listen(0, hostname, async (_err, host, port) => {
+ try {
+ const { address, family } = server.address();
+ expect(port).toBeInteger();
+ expect(port).toBeGreaterThan(0);
+ expect(port).toBeLessThan(65536);
+ expect(["IPv4", "IPv6"]).toContain(family);
+ if (family === "IPv4") {
+ expect(address).toStrictEqual("127.0.0.1");
+ } else {
+ expect(address).toStrictEqual("::1");
+ }
+ done();
+ } catch (err) {
+ done(err);
+ } finally {
+ server.close();
+ }
+ });
+ });
+ it("test unix socket, issue#6413", done => {
+ const socketPath = `${tmpdir()}/bun-server-${Math.random().toString(32)}.sock`;
+ const server = createServer((req, res) => {});
+ server.listen(socketPath, async (_err, host, port) => {
+ try {
+ expect(server.address()).toStrictEqual(socketPath);
+ done();
+ } catch (err) {
+ done(err);
+ } finally {
+ server.close();
+ unlinkSync(socketPath);
+ }
+ });
+ });
});
diff --git a/test/js/node/module/modulePrototypeOverwrite-fixture.cjs b/test/js/node/module/modulePrototypeOverwrite-fixture.cjs
new file mode 100644
index 000000000..eecab81c1
--- /dev/null
+++ b/test/js/node/module/modulePrototypeOverwrite-fixture.cjs
@@ -0,0 +1 @@
+module.exports = require("hook");
diff --git a/test/js/node/module/modulePrototypeOverwrite.cjs b/test/js/node/module/modulePrototypeOverwrite.cjs
new file mode 100644
index 000000000..4e84026a6
--- /dev/null
+++ b/test/js/node/module/modulePrototypeOverwrite.cjs
@@ -0,0 +1,17 @@
+// This behavior is required for Next.js to work
+const eql = require("assert").deepStrictEqual;
+const Module = require("module");
+
+const old = Module.prototype.require;
+Module.prototype.require = function (str) {
+ if (str === "hook") return "winner";
+ return {
+ wrap: old.call(this, str),
+ };
+};
+
+// this context has the new require
+const result = require("./modulePrototypeOverwrite-fixture.cjs");
+eql(result, { wrap: "winner" });
+
+console.log("--pass--");
diff --git a/test/js/node/module/node-module-module.test.js b/test/js/node/module/node-module-module.test.js
index 08955a7b7..5ac48d426 100644
--- a/test/js/node/module/node-module-module.test.js
+++ b/test/js/node/module/node-module-module.test.js
@@ -1,6 +1,8 @@
import { expect, test } from "bun:test";
+import { bunEnv, bunExe } from "harness";
import { _nodeModulePaths, builtinModules, isBuiltin, wrap } from "module";
import Module from "module";
+import path from "path";
test("builtinModules exists", () => {
expect(Array.isArray(builtinModules)).toBe(true);
@@ -57,3 +59,49 @@ test("Module.wrap", () => {
expect(mod.exports.foo).toBe(1);
expect(wrap()).toBe("(function (exports, require, module, __filename, __dirname) { undefined\n});");
});
+
+test("Overwriting _resolveFilename", () => {
+ const { stdout, exitCode } = Bun.spawnSync({
+ cmd: [bunExe(), "run", path.join(import.meta.dir, "resolveFilenameOverwrite.cjs")],
+ env: bunEnv,
+ stderr: "inherit",
+ });
+
+ expect(stdout.toString().trim().endsWith("--pass--")).toBe(true);
+ expect(exitCode).toBe(0);
+});
+
+test("Overwriting Module.prototype.require", () => {
+ const { stdout, exitCode } = Bun.spawnSync({
+ cmd: [bunExe(), "run", path.join(import.meta.dir, "modulePrototypeOverwrite.cjs")],
+ env: bunEnv,
+ stderr: "inherit",
+ });
+
+ expect(stdout.toString().trim().endsWith("--pass--")).toBe(true);
+ expect(exitCode).toBe(0);
+});
+
+test("Module.prototype._compile", () => {
+ const module = new Module("module id goes here");
+ const starting_exports = module.exports;
+ const r = module._compile(
+ "module.exports = { module, exports, require, __filename, __dirname }",
+ "/file/path/goes/here.js",
+ );
+ expect(r).toBe(undefined);
+ expect(module.exports).not.toBe(starting_exports);
+ const { module: m, exports: e, require: req, __filename: fn, __dirname: dn } = module.exports;
+ expect(m).toBe(module);
+ expect(e).toBe(starting_exports);
+ expect(req).toBe(module.require);
+ expect(fn).toBe("/file/path/goes/here.js");
+ expect(dn).toBe("/file/path/goes");
+});
+
+test("Module._extensions", () => {
+ expect(".js" in Module._extensions).toBeTrue();
+ expect(".json" in Module._extensions).toBeTrue();
+ expect(".node" in Module._extensions).toBeTrue();
+ expect(require.extensions).toBe(Module._extensions);
+});
diff --git a/test/js/node/module/resolveFilenameOverwrite-fixture.cjs b/test/js/node/module/resolveFilenameOverwrite-fixture.cjs
new file mode 100644
index 000000000..adae3dd6e
--- /dev/null
+++ b/test/js/node/module/resolveFilenameOverwrite-fixture.cjs
@@ -0,0 +1 @@
+module.exports = "winner";
diff --git a/test/js/node/module/resolveFilenameOverwrite.cjs b/test/js/node/module/resolveFilenameOverwrite.cjs
new file mode 100644
index 000000000..e2d1327a7
--- /dev/null
+++ b/test/js/node/module/resolveFilenameOverwrite.cjs
@@ -0,0 +1,14 @@
+// This behavior is required for Next.js to work
+const eql = require("assert").strictEqual;
+const path = require("path");
+const Module = require("module");
+
+const original = Module._resolveFilename;
+Module._resolveFilename = str => {
+ eql(str.endsWith("💔"), true);
+ return path.join(__dirname, "./resolveFilenameOverwrite-fixture.cjs");
+};
+eql(require("overwriting _resolveFilename broke 💔"), "winner");
+Module._resolveFilename = original;
+
+console.log("--pass--");
diff --git a/test/js/node/net/node-net.test.ts b/test/js/node/net/node-net.test.ts
index efdff4bc5..a16ac6db1 100644
--- a/test/js/node/net/node-net.test.ts
+++ b/test/js/node/net/node-net.test.ts
@@ -370,8 +370,8 @@ it("should handle connection error", done => {
}
errored = true;
expect(error).toBeDefined();
- expect(error.name).toBe("SystemError");
expect(error.message).toBe("Failed to connect");
+ expect((error as any).code).toBe("ECONNREFUSED");
});
socket.on("connect", () => {
@@ -383,3 +383,38 @@ it("should handle connection error", done => {
done();
});
});
+
+it("should handle connection error (unix)", done => {
+ let errored = false;
+
+ // @ts-ignore
+ const socket = connect("loser", () => {
+ done(new Error("Should not have connected"));
+ });
+
+ socket.on("error", error => {
+ if (errored) {
+ return done(new Error("Should not have errored twice"));
+ }
+ errored = true;
+ expect(error).toBeDefined();
+ expect(error.message).toBe("Failed to connect");
+ expect((error as any).code).toBe("ENOENT");
+ });
+
+ socket.on("connect", () => {
+ done(new Error("Should not have connected"));
+ });
+
+ socket.on("close", () => {
+ expect(errored).toBe(true);
+ done();
+ });
+});
+
+it("Socket has a prototype", () => {
+ function Connection() {}
+ function Connection2() {}
+ require("util").inherits(Connection, Socket);
+ require("util").inherits(Connection2, require("tls").TLSSocket);
+});
diff --git a/test/js/node/os/os.test.js b/test/js/node/os/os.test.js
index 8b4d54bb7..7d64719d4 100644
--- a/test/js/node/os/os.test.js
+++ b/test/js/node/os/os.test.js
@@ -19,8 +19,10 @@ it("totalmem", () => {
});
it("getPriority", () => {
- expect(os.getPriority()).toBe(0);
- expect(os.getPriority(0)).toBe(0);
+ var prio = os.getPriority();
+ expect(-20 <= prio && prio <= 20).toBe(true);
+ prio = os.getPriority(0);
+ expect(-20 <= prio && prio <= 20).toBe(true);
});
it("setPriority", () => {
@@ -154,3 +156,7 @@ it("devNull", () => {
if (process.platform === "win32") expect(os.devNull).toBe("\\\\.\\nul");
else expect(os.devNull).toBe("/dev/null");
});
+
+it("availableParallelism", () => {
+ expect(os.availableParallelism()).toBeGreaterThan(0);
+});
diff --git a/test/js/node/path/path.test.js b/test/js/node/path/path.test.js
index 26b6a3b6d..1d14e1d9e 100644
--- a/test/js/node/path/path.test.js
+++ b/test/js/node/path/path.test.js
@@ -271,6 +271,19 @@ it("path.basename", () => {
strictEqual(path.posix.basename(`/a/b/${controlCharFilename}`), controlCharFilename);
});
+describe("path.join #5769", () => {
+ for (let length of [4096, 4095, 4097, 65_432, 65_431, 65_433]) {
+ it("length " + length, () => {
+ const tooLengthyFolderName = Array.from({ length }).fill("b").join("");
+ expect(path.join(tooLengthyFolderName)).toEqual("b".repeat(length));
+ });
+ it("length " + length + "joined", () => {
+ const tooLengthyFolderName = Array.from({ length }).fill("b");
+ expect(path.join(...tooLengthyFolderName)).toEqual("b/".repeat(length).substring(0, 2 * length - 1));
+ });
+ }
+});
+
it("path.join", () => {
const failures = [];
const backslashRE = /\\/g;
diff --git a/test/js/node/process-binding.test.ts b/test/js/node/process-binding.test.ts
new file mode 100644
index 000000000..c60a38bae
--- /dev/null
+++ b/test/js/node/process-binding.test.ts
@@ -0,0 +1,26 @@
+describe("process.binding", () => {
+ test("process.binding('constants')", () => {
+ /* @ts-ignore */
+ const constants = process.binding("constants");
+ expect(constants).toBeDefined();
+ expect(constants).toHaveProperty("os");
+ expect(constants).toHaveProperty("crypto");
+ expect(constants).toHaveProperty("fs");
+ expect(constants).toHaveProperty("trace");
+ expect(constants).toHaveProperty("zlib");
+ });
+ test("process.binding('uv')", () => {
+ /* @ts-ignore */
+ const uv = process.binding("uv");
+ expect(uv).toBeDefined();
+
+ expect(uv).toHaveProperty("errname");
+ expect(uv).toHaveProperty("UV_EACCES");
+ expect(uv.errname(-4)).toBe("EINTR");
+ expect(uv.errname(5)).toBe("Unknown system error 5");
+
+ const map = uv.getErrorMap();
+ expect(map).toBeDefined();
+ expect(map.get(-56)).toEqual(["EISCONN", "socket is already connected"]);
+ });
+});
diff --git a/test/js/node/process/print-process-args.js b/test/js/node/process/print-process-args.js
index e9d2295c8..c34c1b231 100644
--- a/test/js/node/process/print-process-args.js
+++ b/test/js/node/process/print-process-args.js
@@ -3,8 +3,8 @@ import assert from "assert";
// ensure process.argv and Bun.argv are the same
assert.deepStrictEqual(process.argv, Bun.argv, "process.argv does not equal Bun.argv");
assert(process.argv === process.argv, "process.argv isn't cached");
-// assert(Bun.argv === Bun.argv, 'Bun.argv isn\'t cached');
-// assert(Bun.argv === process.argv, 'Bun.argv doesnt share same ref as process.argv');
+assert(Bun.argv === Bun.argv, "Bun.argv isn't cached");
+// assert(Bun.argv === process.argv, "Bun.argv doesnt share same ref as process.argv");
var writer = Bun.stdout.writer();
writer.write(JSON.stringify(process.argv));
diff --git a/test/js/node/process/process-stdio.test.ts b/test/js/node/process/process-stdio.test.ts
index 463ab5fda..5349587af 100644
--- a/test/js/node/process/process-stdio.test.ts
+++ b/test/js/node/process/process-stdio.test.ts
@@ -5,7 +5,7 @@ import { isatty } from "tty";
test("process.stdin", () => {
expect(process.stdin).toBeDefined();
- expect(process.stdout.isTTY).toBe(isatty(0));
+ expect(process.stdin.isTTY).toBe(isatty(0) ? true : undefined);
expect(process.stdin.on("close", function () {})).toBe(process.stdin);
expect(process.stdin.once("end", function () {})).toBe(process.stdin);
});
diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js
index bb74bb998..fd91fdef5 100644
--- a/test/js/node/process/process.test.js
+++ b/test/js/node/process/process.test.js
@@ -103,7 +103,7 @@ it("process.env.TZ", () => {
var origTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
// the default timezone is Etc/UTC
- if (!"TZ" in process.env) {
+ if (!("TZ" in process.env)) {
expect(origTimezone).toBe("Etc/UTC");
}
@@ -263,6 +263,15 @@ describe("process.exitCode", () => {
});
});
+it("process exitCode range (#6284)", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), join(import.meta.dir, "process-exitCode-fixture.js"), "255"],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(255);
+ expect(stdout.toString().trim()).toBe("PASS");
+});
+
it("process.exit", () => {
const { exitCode, stdout } = spawnSync({
cmd: [bunExe(), join(import.meta.dir, "process-exit-fixture.js")],
@@ -407,7 +416,8 @@ describe("signal", () => {
stdout: "pipe",
});
const prom = child.exited;
- process.kill(child.pid, "SIGTERM");
+ const ret = process.kill(child.pid, "SIGTERM");
+ expect(ret).toBe(true);
await prom;
expect(child.signalCode).toBe("SIGTERM");
});
@@ -418,7 +428,8 @@ describe("signal", () => {
stdout: "pipe",
});
const prom = child.exited;
- process.kill(child.pid, 9);
+ const ret = process.kill(child.pid, 9);
+ expect(ret).toBe(true);
await prom;
expect(child.signalCode).toBe("SIGKILL");
});
@@ -487,3 +498,13 @@ it("dlopen args parsing", () => {
expect(() => process.dlopen({ module: { exports: Symbol("123") } }, "/tmp/not-found.so")).toThrow();
expect(() => process.dlopen({ module: { exports: Symbol("123") } }, Symbol("badddd"))).toThrow();
});
+
+it("process.constrainedMemory()", () => {
+ if (process.platform === "linux") {
+ // On Linux, it returns 0 if the kernel doesn't support it
+ expect(process.constrainedMemory() >= 0).toBe(true);
+ } else {
+ // On unsupported platforms, it returns undefined
+ expect(process.constrainedMemory()).toBeUndefined();
+ }
+});
diff --git a/test/js/node/stream/node-stream.test.js b/test/js/node/stream/node-stream.test.js
index bc6a4fcfb..ddbd2bc7a 100644
--- a/test/js/node/stream/node-stream.test.js
+++ b/test/js/node/stream/node-stream.test.js
@@ -197,6 +197,7 @@ describe("PassThrough", () => {
const ttyStreamsTest = `
import tty from "tty";
+import fs from "fs";
import { dlopen } from "bun:ffi";
@@ -278,10 +279,11 @@ describe("TTY", () => {
close(child_fd);
});
it("process.stdio tty", () => {
- expect(process.stdin instanceof tty.ReadStream).toBe(true);
+ // this isnt run in a tty, so stdin will not appear to be a tty
+ expect(process.stdin instanceof fs.ReadStream).toBe(true);
expect(process.stdout instanceof tty.WriteStream).toBe(true);
expect(process.stderr instanceof tty.WriteStream).toBe(true);
- expect(process.stdin.isTTY).toBeDefined();
+ expect(process.stdin.isTTY).toBeUndefined();
expect(process.stdout.isTTY).toBeDefined();
expect(process.stderr.isTTY).toBeDefined();
});
@@ -311,7 +313,11 @@ it("TTY streams", () => {
});
expect(stdout.toString()).toBe("");
- expect(stderr.toString()).toContain("0 fail");
+ try {
+ expect(stderr.toString()).toContain("0 fail");
+ } catch (error) {
+ throw new Error(stderr.toString());
+ }
expect(exitCode).toBe(0);
});
diff --git a/test/js/node/tty.test.ts b/test/js/node/tty.test.ts
new file mode 100644
index 000000000..c1723ad48
--- /dev/null
+++ b/test/js/node/tty.test.ts
@@ -0,0 +1,25 @@
+import { describe, it, expect } from "bun:test";
+import { WriteStream } from "node:tty";
+
+describe("WriteStream.prototype.getColorDepth", () => {
+ it("iTerm ancient", () => {
+ expect(
+ WriteStream.prototype.getColorDepth.call(undefined, {
+ TERM_PROGRAM: "iTerm.app",
+ }),
+ ).toBe(8);
+ });
+
+ it("iTerm modern", () => {
+ expect(
+ WriteStream.prototype.getColorDepth.call(undefined, {
+ TERM_PROGRAM: "iTerm.app",
+ TERM_PROGRAM_VERSION: 3,
+ }),
+ ).toBe(24);
+ });
+
+ it("empty", () => {
+ expect(WriteStream.prototype.getColorDepth.call(undefined, {})).toBe(1);
+ });
+});
diff --git a/test/js/node/util/node-inspect-tests/import.test.mjs b/test/js/node/util/node-inspect-tests/import.test.mjs
new file mode 100644
index 000000000..a49902f53
--- /dev/null
+++ b/test/js/node/util/node-inspect-tests/import.test.mjs
@@ -0,0 +1,9 @@
+import assert from "assert";
+import util, { inspect } from "util";
+
+test("no assertion failures", () => {
+ assert.strictEqual(typeof util.inspect, "function");
+ assert.strictEqual(util.inspect, inspect);
+ assert.strictEqual(util.inspect(null), "null");
+ assert.strictEqual(util.inspect({ a: 1 }, { compact: false }), "{\n a: 1\n}");
+});
diff --git a/test/js/node/util/node-inspect-tests/internal-inspect.test.js b/test/js/node/util/node-inspect-tests/internal-inspect.test.js
new file mode 100644
index 000000000..6e7a76e0d
--- /dev/null
+++ b/test/js/node/util/node-inspect-tests/internal-inspect.test.js
@@ -0,0 +1,57 @@
+import assert from "assert";
+import util from "util";
+
+test("no assertion failures", () => {
+ // Errors in accessors are not triggered
+ const obj = new Proxy(
+ { x: 5 },
+ {
+ get() {
+ throw new Error("Error message");
+ },
+ },
+ );
+ assert.strictEqual(util.format(obj), "{ x: 5 }");
+
+ assert.strictEqual(util.formatWithOptions({ numericSeparator: true }, "%d", 4000), "4_000");
+
+ const a = {};
+ a.b = a;
+ assert.strictEqual(util.inspect(a, { compact: false }), "<ref *1> {\n b: [Circular *1]\n}");
+ assert.strictEqual(util.inspect(a, { compact: true }), "<ref *1> { b: [Circular *1] }");
+
+ const cause = new Error("cause");
+ const e2 = new Error("wrapper", { cause });
+ assert.match(util.inspect(e2), /\[cause\]: Error: cause\n/);
+});
+
+//! non-standard property, should this be kept?
+test.skip("util.stylizeWithHTML", () => {
+ assert.strictEqual(
+ util.inspect(
+ {
+ a: 1,
+ b: "<p>\xA0\u{1F4A9}</p>",
+ "&lt;": NaN,
+ [Symbol("<br>")]: false,
+ buf: new Uint8Array([1, 2, 3, 4]),
+ },
+ {
+ compact: false,
+ stylize: util.stylizeWithHTML,
+ },
+ ),
+ "{\n" +
+ ' a: <span style="color:yellow;">1</span>,\n' +
+ ' b: <span style="color:green;">&apos;&lt;p&gt;&nbsp;\u{1F4A9}&lt;&#47;p&gt;&apos;</span>,\n' +
+ ' <span style="color:green;">&apos;&amp;lt&#59;&apos;</span>: <span style="color:yellow;">NaN</span>,\n' +
+ " buf: Uint8Array(4) [\n" +
+ ' <span style="color:yellow;">1</span>,\n' +
+ ' <span style="color:yellow;">2</span>,\n' +
+ ' <span style="color:yellow;">3</span>,\n' +
+ ' <span style="color:yellow;">4</span>\n' +
+ " ],\n" +
+ ' [<span style="color:green;">Symbol&#40;&lt;br&gt;&#41;</span>]: <span style="color:yellow;">false</span>\n' +
+ "}",
+ );
+});
diff --git a/test/js/node/util/node-inspect-tests/parallel/util-format.test.js b/test/js/node/util/node-inspect-tests/parallel/util-format.test.js
new file mode 100644
index 000000000..045671cf7
--- /dev/null
+++ b/test/js/node/util/node-inspect-tests/parallel/util-format.test.js
@@ -0,0 +1,489 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import assert from "assert";
+import util from "util";
+const symbol = Symbol("foo");
+
+test("no assertion failures", () => {
+ assert.strictEqual(util.format(), "");
+ assert.strictEqual(util.format(""), "");
+ assert.strictEqual(util.format([]), "[]");
+ assert.strictEqual(util.format([0]), "[ 0 ]");
+ assert.strictEqual(util.format({}), "{}");
+ assert.strictEqual(util.format({ foo: 42 }), "{ foo: 42 }");
+ assert.strictEqual(util.format(null), "null");
+ assert.strictEqual(util.format(true), "true");
+ assert.strictEqual(util.format(false), "false");
+ assert.strictEqual(util.format("test"), "test");
+
+ // CHECKME this is for console.log() compatibility - but is it *right*?
+ assert.strictEqual(util.format("foo", "bar", "baz"), "foo bar baz");
+
+ // ES6 Symbol handling
+ assert.strictEqual(util.format(symbol), "Symbol(foo)");
+ assert.strictEqual(util.format("foo", symbol), "foo Symbol(foo)");
+ assert.strictEqual(util.format("%s", symbol), "Symbol(foo)");
+ assert.strictEqual(util.format("%j", symbol), "undefined");
+
+ // Number format specifier
+ assert.strictEqual(util.format("%d"), "%d");
+ assert.strictEqual(util.format("%d", 42.0), "42");
+ assert.strictEqual(util.format("%d", 42), "42");
+ assert.strictEqual(util.format("%d", "42"), "42");
+ assert.strictEqual(util.format("%d", "42.0"), "42");
+ assert.strictEqual(util.format("%d", 1.5), "1.5");
+ assert.strictEqual(util.format("%d", -0.5), "-0.5");
+ assert.strictEqual(util.format("%d", -0.0), "-0");
+ assert.strictEqual(util.format("%d", ""), "0");
+ assert.strictEqual(util.format("%d", " -0.000"), "-0");
+ assert.strictEqual(util.format("%d", Symbol()), "NaN");
+ assert.strictEqual(util.format("%d", Infinity), "Infinity");
+ assert.strictEqual(util.format("%d", -Infinity), "-Infinity");
+ assert.strictEqual(util.format("%d %d", 42, 43), "42 43");
+ assert.strictEqual(util.format("%d %d", 42), "42 %d");
+ assert.strictEqual(util.format("%d", 1180591620717411303424), "1.1805916207174113e+21");
+ assert.strictEqual(util.format("%d", 1180591620717411303424n), "1180591620717411303424n");
+ assert.strictEqual(
+ util.format("%d %d", 1180591620717411303424n, 12345678901234567890123n),
+ "1180591620717411303424n 12345678901234567890123n",
+ );
+
+ {
+ const { numericSeparator } = util.inspect.defaultOptions;
+ util.inspect.defaultOptions.numericSeparator = true;
+
+ assert.strictEqual(util.format("%d", 1180591620717411303424), "1.1805916207174113e+21");
+
+ assert.strictEqual(
+ util.format("%d %s %i", 118059162071741130342, 118059162071741130342, 123_123_123),
+ "118_059_162_071_741_140_000 118_059_162_071_741_140_000 123_123_123",
+ );
+
+ assert.strictEqual(
+ util.format("%d %s", 1_180_591_620_717_411_303_424n, 12_345_678_901_234_567_890_123n),
+ "1_180_591_620_717_411_303_424n 12_345_678_901_234_567_890_123n",
+ );
+
+ assert.strictEqual(util.format("%i", 1_180_591_620_717_411_303_424n), "1_180_591_620_717_411_303_424n");
+
+ util.inspect.defaultOptions.numericSeparator = numericSeparator;
+ }
+ // Integer format specifier
+ assert.strictEqual(util.format("%i"), "%i");
+ assert.strictEqual(util.format("%i", 42.0), "42");
+ assert.strictEqual(util.format("%i", 42), "42");
+ assert.strictEqual(util.format("%i", "42"), "42");
+ assert.strictEqual(util.format("%i", "42.0"), "42");
+ assert.strictEqual(util.format("%i", 1.5), "1");
+ assert.strictEqual(util.format("%i", -0.5), "-0");
+ assert.strictEqual(util.format("%i", ""), "NaN");
+ assert.strictEqual(util.format("%i", Infinity), "NaN");
+ assert.strictEqual(util.format("%i", -Infinity), "NaN");
+ assert.strictEqual(util.format("%i", Symbol()), "NaN");
+ assert.strictEqual(util.format("%i %i", 42, 43), "42 43");
+ assert.strictEqual(util.format("%i %i", 42), "42 %i");
+ assert.strictEqual(util.format("%i", 1180591620717411303424), "1");
+ assert.strictEqual(util.format("%i", 1180591620717411303424n), "1180591620717411303424n");
+ assert.strictEqual(
+ util.format("%i %i", 1180591620717411303424n, 12345678901234567890123n),
+ "1180591620717411303424n 12345678901234567890123n",
+ );
+
+ assert.strictEqual(
+ util.format("%d %i", 1180591620717411303424n, 12345678901234567890123n),
+ "1180591620717411303424n 12345678901234567890123n",
+ );
+
+ assert.strictEqual(
+ util.format("%i %d", 1180591620717411303424n, 12345678901234567890123n),
+ "1180591620717411303424n 12345678901234567890123n",
+ );
+
+ assert.strictEqual(
+ util.formatWithOptions({ numericSeparator: true }, "%i %d", 1180591620717411303424n, 12345678901234567890123n),
+ "1_180_591_620_717_411_303_424n 12_345_678_901_234_567_890_123n",
+ );
+
+ // Float format specifier
+ assert.strictEqual(util.format("%f"), "%f");
+ assert.strictEqual(util.format("%f", 42.0), "42");
+ assert.strictEqual(util.format("%f", 42), "42");
+ assert.strictEqual(util.format("%f", "42"), "42");
+ assert.strictEqual(util.format("%f", "-0.0"), "-0");
+ assert.strictEqual(util.format("%f", "42.0"), "42");
+ assert.strictEqual(util.format("%f", 1.5), "1.5");
+ assert.strictEqual(util.format("%f", -0.5), "-0.5");
+ assert.strictEqual(util.format("%f", Math.PI), "3.141592653589793");
+ assert.strictEqual(util.format("%f", ""), "NaN");
+ assert.strictEqual(util.format("%f", Symbol("foo")), "NaN");
+ assert.strictEqual(util.format("%f", 5n), "5");
+ assert.strictEqual(util.format("%f", Infinity), "Infinity");
+ assert.strictEqual(util.format("%f", -Infinity), "-Infinity");
+ assert.strictEqual(util.format("%f %f", 42, 43), "42 43");
+ assert.strictEqual(util.format("%f %f", 42), "42 %f");
+
+ // String format specifier
+ assert.strictEqual(util.format("%s"), "%s");
+ assert.strictEqual(util.format("%s", undefined), "undefined");
+ assert.strictEqual(util.format("%s", null), "null");
+ assert.strictEqual(util.format("%s", "foo"), "foo");
+ assert.strictEqual(util.format("%s", 42), "42");
+ assert.strictEqual(util.format("%s", "42"), "42");
+ assert.strictEqual(util.format("%s", -0), "-0");
+ assert.strictEqual(util.format("%s", "-0.0"), "-0.0");
+ assert.strictEqual(util.format("%s %s", 42, 43), "42 43");
+ assert.strictEqual(util.format("%s %s", 42), "42 %s");
+ assert.strictEqual(util.format("%s", 42n), "42n");
+ assert.strictEqual(util.format("%s", Symbol("foo")), "Symbol(foo)");
+ assert.strictEqual(util.format("%s", true), "true");
+ assert.strictEqual(util.format("%s", { a: [1, 2, 3] }), "{ a: [Array] }");
+ assert.strictEqual(
+ util.format("%s", {
+ toString() {
+ return "Foo";
+ },
+ }),
+ "Foo",
+ );
+ assert.strictEqual(util.format("%s", { toString: 5 }), "{ toString: 5 }");
+ assert.strictEqual(
+ util.format("%s", () => 5),
+ "() => 5",
+ );
+ assert.strictEqual(util.format("%s", Infinity), "Infinity");
+ assert.strictEqual(util.format("%s", -Infinity), "-Infinity");
+
+ // String format specifier including `toString` properties on the prototype.
+ {
+ class Foo {
+ toString() {
+ return "Bar";
+ }
+ }
+ assert.strictEqual(util.format("%s", new Foo()), "Bar");
+ // TODO: null prototypes
+ // assert.strictEqual(
+ // util.format('%s', Object.setPrototypeOf(new Foo(), null)),
+ // '[Foo: null prototype] {}'
+ // );
+ global.Foo = Foo;
+ assert.strictEqual(util.format("%s", new Foo()), "Bar");
+ delete global.Foo;
+ class Bar {
+ abc = true;
+ }
+ assert.strictEqual(util.format("%s", new Bar()), "Bar { abc: true }");
+ class Foobar extends Array {
+ aaa = true;
+ }
+ assert.strictEqual(util.format("%s", new Foobar(5)), "Foobar(5) [ <5 empty items>, aaa: true ]");
+
+ // Subclassing:
+ class B extends Foo {}
+
+ function C() {}
+ C.prototype.toString = function () {
+ return "Custom";
+ };
+
+ function D() {
+ C.call(this);
+ }
+ D.prototype = { __proto__: C.prototype };
+
+ assert.strictEqual(util.format("%s", new B()), "Bar");
+ assert.strictEqual(util.format("%s", new C()), "Custom");
+ assert.strictEqual(util.format("%s", new D()), "Custom");
+
+ D.prototype.constructor = D;
+ assert.strictEqual(util.format("%s", new D()), "Custom");
+
+ D.prototype.constructor = null;
+ assert.strictEqual(util.format("%s", new D()), "Custom");
+
+ D.prototype.constructor = { name: "Foobar" };
+ assert.strictEqual(util.format("%s", new D()), "Custom");
+
+ Object.defineProperty(D.prototype, "constructor", {
+ get() {
+ throw new Error();
+ },
+ configurable: true,
+ });
+ assert.strictEqual(util.format("%s", new D()), "Custom");
+
+ assert.strictEqual(util.format("%s", { __proto__: null }), "[Object: null prototype] {}");
+ }
+
+ // JSON format specifier
+ assert.strictEqual(util.format("%j"), "%j");
+ assert.strictEqual(util.format("%j", 42), "42");
+ assert.strictEqual(util.format("%j", "42"), '"42"');
+ assert.strictEqual(util.format("%j %j", 42, 43), "42 43");
+ assert.strictEqual(util.format("%j %j", 42), "42 %j");
+
+ // Object format specifier
+ const obj = {
+ foo: "bar",
+ foobar: 1,
+ func: function () {},
+ };
+ const nestedObj = {
+ foo: "bar",
+ foobar: {
+ foo: "bar",
+ func: function () {},
+ },
+ };
+ const nestedObj2 = {
+ foo: "bar",
+ foobar: 1,
+ func: [{ a: function () {} }],
+ };
+ assert.strictEqual(util.format("%o"), "%o");
+ assert.strictEqual(util.format("%o", 42), "42");
+ assert.strictEqual(util.format("%o", "foo"), "'foo'");
+ assert.strictEqual(
+ util.format("%o", obj),
+ "{\n" +
+ " foo: 'bar',\n" +
+ " foobar: 1,\n" +
+ " func: <ref *1> [Function: func] {\n" +
+ " [length]: 0,\n" +
+ " [name]: 'func',\n" +
+ " [prototype]: { [constructor]: [Circular *1] }\n" +
+ " }\n" +
+ "}",
+ );
+ assert.strictEqual(
+ util.format("%o", nestedObj2),
+ "{\n" +
+ " foo: 'bar',\n" +
+ " foobar: 1,\n" +
+ " func: [\n" +
+ " {\n" +
+ " a: <ref *1> [Function: a] {\n" +
+ " [length]: 0,\n" +
+ " [name]: 'a',\n" +
+ " [prototype]: { [constructor]: [Circular *1] }\n" +
+ " }\n" +
+ " },\n" +
+ " [length]: 1\n" +
+ " ]\n" +
+ "}",
+ );
+ assert.strictEqual(
+ util.format("%o", nestedObj),
+ "{\n" +
+ " foo: 'bar',\n" +
+ " foobar: {\n" +
+ " foo: 'bar',\n" +
+ " func: <ref *1> [Function: func] {\n" +
+ " [length]: 0,\n" +
+ " [name]: 'func',\n" +
+ " [prototype]: { [constructor]: [Circular *1] }\n" +
+ " }\n" +
+ " }\n" +
+ "}",
+ );
+
+ assert.strictEqual(
+ util.format("%o %o", obj, obj),
+ "{\n" +
+ " foo: 'bar',\n" +
+ " foobar: 1,\n" +
+ " func: <ref *1> [Function: func] {\n" +
+ " [prototype]: { [constructor]: [Circular *1] },\n" +
+ " [name]: 'func',\n" +
+ " [length]: 0\n" +
+ " }\n" +
+ "} {\n" +
+ " foo: 'bar',\n" +
+ " foobar: 1,\n" +
+ " func: <ref *1> [Function: func] {\n" +
+ " [prototype]: { [constructor]: [Circular *1] },\n" +
+ " [name]: 'func',\n" +
+ " [length]: 0\n" +
+ " }\n" +
+ "}",
+ );
+
+ assert.strictEqual(
+ util.format("%o %o", obj),
+ "{\n" +
+ " foo: 'bar',\n" +
+ " foobar: 1,\n" +
+ " func: <ref *1> [Function: func] {\n" +
+ " [prototype]: { [constructor]: [Circular *1] },\n" +
+ " [name]: 'func',\n" +
+ " [length]: 0\n" +
+ " }\n" +
+ "} %o",
+ );
+
+ assert.strictEqual(util.format("%O"), "%O");
+ assert.strictEqual(util.format("%O", 42), "42");
+ assert.strictEqual(util.format("%O", "foo"), "'foo'");
+ assert.strictEqual(util.format("%O", obj), "{ foo: 'bar', foobar: 1, func: [Function: func] }");
+ assert.strictEqual(util.format("%O", nestedObj), "{ foo: 'bar', foobar: { foo: 'bar', func: [Function: func] } }");
+ assert.strictEqual(
+ util.format("%O %O", obj, obj),
+ "{ foo: 'bar', foobar: 1, func: [Function: func] } " + "{ foo: 'bar', foobar: 1, func: [Function: func] }",
+ );
+ assert.strictEqual(util.format("%O %O", obj), "{ foo: 'bar', foobar: 1, func: [Function: func] } %O");
+
+ // Various format specifiers
+ assert.strictEqual(util.format("%%s%s", "foo"), "%sfoo");
+ assert.strictEqual(util.format("%s:%s"), "%s:%s");
+ assert.strictEqual(util.format("%s:%s", undefined), "undefined:%s");
+ assert.strictEqual(util.format("%s:%s", "foo"), "foo:%s");
+ assert.strictEqual(util.format("%s:%i", "foo"), "foo:%i");
+ assert.strictEqual(util.format("%s:%f", "foo"), "foo:%f");
+ assert.strictEqual(util.format("%s:%s", "foo", "bar"), "foo:bar");
+ assert.strictEqual(util.format("%s:%s", "foo", "bar", "baz"), "foo:bar baz");
+ assert.strictEqual(util.format("%%%s%%", "hi"), "%hi%");
+ assert.strictEqual(util.format("%%%s%%%%", "hi"), "%hi%%");
+ assert.strictEqual(util.format("%sbc%%def", "a"), "abc%def");
+ assert.strictEqual(util.format("%d:%d", 12, 30), "12:30");
+ assert.strictEqual(util.format("%d:%d", 12), "12:%d");
+ assert.strictEqual(util.format("%d:%d"), "%d:%d");
+ assert.strictEqual(util.format("%i:%i", 12, 30), "12:30");
+ assert.strictEqual(util.format("%i:%i", 12), "12:%i");
+ assert.strictEqual(util.format("%i:%i"), "%i:%i");
+ assert.strictEqual(util.format("%f:%f", 12, 30), "12:30");
+ assert.strictEqual(util.format("%f:%f", 12), "12:%f");
+ assert.strictEqual(util.format("%f:%f"), "%f:%f");
+ assert.strictEqual(util.format("o: %j, a: %j", {}, []), "o: {}, a: []");
+ assert.strictEqual(util.format("o: %j, a: %j", {}), "o: {}, a: %j");
+ assert.strictEqual(util.format("o: %j, a: %j"), "o: %j, a: %j");
+ assert.strictEqual(util.format("o: %o, a: %O", {}, []), "o: {}, a: []");
+ assert.strictEqual(util.format("o: %o, a: %o", {}), "o: {}, a: %o");
+ assert.strictEqual(util.format("o: %O, a: %O"), "o: %O, a: %O");
+
+ // Invalid format specifiers
+ assert.strictEqual(util.format("a% b", "x"), "a% b x");
+ assert.strictEqual(util.format("percent: %d%, fraction: %d", 10, 0.1), "percent: 10%, fraction: 0.1");
+ assert.strictEqual(util.format("abc%", 1), "abc% 1");
+
+ // Additional arguments after format specifiers
+ assert.strictEqual(util.format("%i", 1, "number"), "1 number");
+ assert.strictEqual(
+ util.format("%i", 1, () => {}),
+ "1 [Function (anonymous)]",
+ );
+
+ // %c from https://console.spec.whatwg.org/
+ assert.strictEqual(util.format("%c"), "%c");
+ assert.strictEqual(util.format("%cab"), "%cab");
+ assert.strictEqual(util.format("%cab", "color: blue"), "ab");
+ assert.strictEqual(util.format("%cab", "color: blue", "c"), "ab c");
+
+ {
+ const o = {};
+ o.o = o;
+ assert.strictEqual(util.format("%j", o), "[Circular]");
+ }
+
+ {
+ const o = {
+ toJSON() {
+ throw new Error("Not a circular object but still not serializable");
+ },
+ };
+ assert.throws(() => util.format("%j", o), /^Error: Not a circular object but still not serializable$/);
+ }
+
+ // Errors
+ const err = new Error("foo");
+ assert(util.format(err).startsWith(err.stack), `Expected "${util.format(err)}" to start with "${err.stack}"`);
+
+ class CustomError extends Error {
+ constructor(msg) {
+ super();
+ Object.defineProperty(this, "message", { value: msg, enumerable: false });
+ Object.defineProperty(this, "name", { value: "CustomError", enumerable: false });
+ Error.captureStackTrace(this, CustomError);
+ }
+ }
+ const customError = new CustomError("bar");
+ assert.strictEqual(util.format(customError), customError.stack.replace(/^Error/, "Custom$&")); //! temp bug workaround
+ // Doesn't capture stack trace
+ function BadCustomError(msg) {
+ Error.call(this);
+ Object.defineProperty(this, "message", { value: msg, enumerable: false });
+ Object.defineProperty(this, "name", { value: "BadCustomError", enumerable: false });
+ }
+ Object.setPrototypeOf(BadCustomError.prototype, Error.prototype);
+ Object.setPrototypeOf(BadCustomError, Error);
+ assert.strictEqual(util.format(new BadCustomError("foo")), "[BadCustomError: foo]");
+
+ // The format of arguments should not depend on type of the first argument
+ assert.strictEqual(util.format("1", "1"), "1 1");
+ assert.strictEqual(util.format(1, "1"), "1 1");
+ assert.strictEqual(util.format("1", 1), "1 1");
+ assert.strictEqual(util.format(1, -0), "1 -0");
+ assert.strictEqual(
+ util.format("1", () => {}),
+ "1 [Function (anonymous)]",
+ );
+ assert.strictEqual(
+ util.format(1, () => {}),
+ "1 [Function (anonymous)]",
+ );
+ assert.strictEqual(util.format("1", "'"), "1 '");
+ assert.strictEqual(util.format(1, "'"), "1 '");
+ assert.strictEqual(util.format("1", "number"), "1 number");
+ assert.strictEqual(util.format(1, "number"), "1 number");
+ assert.strictEqual(util.format(5n), "5n");
+ assert.strictEqual(util.format(5n, 5n), "5n 5n");
+
+ // Check `formatWithOptions`.
+ assert.strictEqual(
+ util.formatWithOptions({ colors: true }, true, undefined, Symbol(), 1, 5n, null, "foobar"),
+ "\u001b[33mtrue\u001b[39m " +
+ "\u001b[90mundefined\u001b[39m " +
+ "\u001b[32mSymbol()\u001b[39m " +
+ "\u001b[33m1\u001b[39m " +
+ "\u001b[33m5n\u001b[39m " +
+ "\u001b[1mnull\u001b[22m " +
+ "foobar",
+ );
+
+ assert.strictEqual(
+ util.format(new SharedArrayBuffer(4)),
+ "SharedArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }",
+ );
+
+ assert.strictEqual(util.formatWithOptions({ colors: true, compact: 3 }, "%s", [1, { a: true }]), "[ 1, [Object] ]");
+
+ [undefined, null, false, 5n, 5, "test", Symbol()].forEach(invalidOptions => {
+ assert.throws(
+ () => {
+ util.formatWithOptions(invalidOptions, { a: true });
+ },
+ {
+ code: "ERR_INVALID_ARG_TYPE",
+ message: /"inspectOptions".+object/,
+ },
+ );
+ });
+});
diff --git a/test/js/node/util/node-inspect-tests/parallel/util-inspect-getters-accessing-this.test.js b/test/js/node/util/node-inspect-tests/parallel/util-inspect-getters-accessing-this.test.js
new file mode 100644
index 000000000..ab3c17e59
--- /dev/null
+++ b/test/js/node/util/node-inspect-tests/parallel/util-inspect-getters-accessing-this.test.js
@@ -0,0 +1,60 @@
+// This test ensures that util.inspect logs getters which access this.
+
+import assert from "assert";
+import { inspect } from "util";
+
+test("no assertion failures", () => {
+ {
+ class X {
+ constructor() {
+ this._y = 123;
+ }
+
+ get y() {
+ return this._y;
+ }
+ }
+
+ const result = inspect(new X(), {
+ getters: true,
+ showHidden: true,
+ });
+
+ assert.strictEqual(result, "X { _y: 123, [y]: [Getter: 123] }");
+ }
+
+ // Regression test for https://github.com/nodejs/node/issues/37054
+ {
+ class A {
+ constructor(B) {
+ this.B = B;
+ }
+ get b() {
+ return this.B;
+ }
+ }
+
+ class B {
+ constructor() {
+ this.A = new A(this);
+ }
+ get a() {
+ return this.A;
+ }
+ }
+
+ const result = inspect(new B(), {
+ depth: 1,
+ getters: true,
+ showHidden: true,
+ });
+
+ assert.strictEqual(
+ result,
+ "<ref *1> B {\n" +
+ " A: A { B: [Circular *1], [b]: [Getter] [Circular *1] },\n" +
+ " [a]: [Getter] A { B: [Circular *1], [b]: [Getter] [Circular *1] }\n" +
+ "}",
+ );
+ }
+});
diff --git a/test/js/node/util/node-inspect-tests/parallel/util-inspect-long-running.test.mjs b/test/js/node/util/node-inspect-tests/parallel/util-inspect-long-running.test.mjs
new file mode 100644
index 000000000..2cf3b7d14
--- /dev/null
+++ b/test/js/node/util/node-inspect-tests/parallel/util-inspect-long-running.test.mjs
@@ -0,0 +1,27 @@
+import util from "util";
+// Test that huge objects don't crash due to exceeding the maximum heap size.
+
+// Create a difficult to stringify object. Without the artificial limitation
+// this would crash or throw an maximum string size error.
+
+//! This test currently relies on a non-standard extension to util.inspect
+// It optimizes the output of circular objects. If that extension ends up
+// being removed, this test will likely hang for a pretty long time.
+// We are missing some kind of optimization Node does to pass this test near instantly even without the extension.
+
+test("should not take longer than 2 seconds", () => {
+ let last = {};
+ const obj = last;
+
+ for (let i = 0; i < 500; i++) {
+ // original value: 1000 (reduced to 500 to let tests run faster)
+ last.next = { circular: obj, last, obj: { a: i, b: 2, c: true } };
+ last = last.next;
+ obj[i] = last;
+ }
+
+ const str = util.inspect(obj, { depth: Infinity, colors: false });
+ void str;
+ //console.log(str);
+ //console.log(str.length);
+});
diff --git a/test/js/node/util/node-inspect-tests/parallel/util-inspect-proxy.test.js b/test/js/node/util/node-inspect-tests/parallel/util-inspect-proxy.test.js
new file mode 100644
index 000000000..f87bd5a3c
--- /dev/null
+++ b/test/js/node/util/node-inspect-tests/parallel/util-inspect-proxy.test.js
@@ -0,0 +1,177 @@
+import assert from "assert";
+import util from "util";
+const opts = { showProxy: true };
+
+test("no assertion failures", () => {
+ let proxyObj;
+ let called = false;
+ const target = {
+ [util.inspect.custom](depth, { showProxy }) {
+ if (showProxy === false) {
+ called = true;
+ if (proxyObj !== this) {
+ throw new Error("Failed");
+ }
+ }
+ return [1, 2, 3];
+ },
+ };
+ const handler = {
+ getPrototypeOf() {
+ throw new Error("getPrototypeOf");
+ },
+ setPrototypeOf() {
+ throw new Error("setPrototypeOf");
+ },
+ isExtensible() {
+ throw new Error("isExtensible");
+ },
+ preventExtensions() {
+ throw new Error("preventExtensions");
+ },
+ getOwnPropertyDescriptor() {
+ throw new Error("getOwnPropertyDescriptor");
+ },
+ defineProperty() {
+ throw new Error("defineProperty");
+ },
+ has() {
+ throw new Error("has");
+ },
+ get(_, key) {
+ throw new Error("get: " + String(key));
+ },
+ set() {
+ throw new Error("set");
+ },
+ deleteProperty() {
+ throw new Error("deleteProperty");
+ },
+ ownKeys() {
+ throw new Error("ownKeys");
+ },
+ apply() {
+ throw new Error("apply");
+ },
+ construct() {
+ throw new Error("construct");
+ },
+ };
+ proxyObj = new Proxy(target, handler);
+
+ // Inspecting the proxy should not actually walk it's properties
+ util.inspect(proxyObj, opts);
+
+ // Make sure inspecting object does not trigger any proxy traps.
+ util.format("%s", proxyObj);
+
+ const r = Proxy.revocable({}, {});
+ r.revoke();
+
+ assert.strictEqual(util.inspect(r.proxy), "<Revoked Proxy>");
+ assert.strictEqual(
+ util.inspect(r, { showProxy: true }),
+ "{ proxy: <Revoked Proxy>, revoke: [Function (anonymous)] }",
+ );
+
+ assert.strictEqual(util.format("%s", r.proxy), "<Revoked Proxy>");
+
+ assert.strictEqual(
+ util.inspect(proxyObj, opts),
+ "Proxy [\n" +
+ " [ 1, 2, 3 ],\n" +
+ " {\n" +
+ " getPrototypeOf: [Function: getPrototypeOf],\n" +
+ " setPrototypeOf: [Function: setPrototypeOf],\n" +
+ " isExtensible: [Function: isExtensible],\n" +
+ " preventExtensions: [Function: preventExtensions],\n" +
+ " getOwnPropertyDescriptor: [Function: getOwnPropertyDescriptor],\n" +
+ " defineProperty: [Function: defineProperty],\n" +
+ " has: [Function: has],\n" +
+ " get: [Function: get],\n" +
+ " set: [Function: set],\n" +
+ " deleteProperty: [Function: deleteProperty],\n" +
+ " ownKeys: [Function: ownKeys],\n" +
+ " apply: [Function: apply],\n" +
+ " construct: [Function: construct]\n" +
+ " }\n" +
+ "]",
+ );
+
+ // Inspecting a proxy without the showProxy option set to true should not
+ // trigger any proxy handlers.
+ assert.strictEqual(util.inspect(proxyObj), "[ 1, 2, 3 ]");
+ assert(called);
+
+ // Yo dawg, I heard you liked Proxy so I put a Proxy
+ // inside your Proxy that proxies your Proxy's Proxy.
+ const proxy1 = new Proxy({}, {});
+ const proxy2 = new Proxy(proxy1, {});
+ const proxy3 = new Proxy(proxy2, proxy1);
+ const proxy4 = new Proxy(proxy1, proxy2);
+ const proxy5 = new Proxy(proxy3, proxy4);
+ const proxy6 = new Proxy(proxy5, proxy5);
+ const expected0 = "{}";
+ const expected1 = "Proxy [ {}, {} ]";
+ const expected2 = "Proxy [ Proxy [ {}, {} ], {} ]";
+ const expected3 = "Proxy [ Proxy [ Proxy [ {}, {} ], {} ], Proxy [ {}, {} ] ]";
+ const expected4 = "Proxy [ Proxy [ {}, {} ], Proxy [ Proxy [ {}, {} ], {} ] ]";
+ const expected5 =
+ "Proxy [\n " +
+ "Proxy [ Proxy [ Proxy [Array], {} ], Proxy [ {}, {} ] ],\n" +
+ " Proxy [ Proxy [ {}, {} ], Proxy [ Proxy [Array], {} ] ]" +
+ "\n]";
+ const expected6 =
+ "Proxy [\n" +
+ " Proxy [\n" +
+ " Proxy [ Proxy [Array], Proxy [Array] ],\n" +
+ " Proxy [ Proxy [Array], Proxy [Array] ]\n" +
+ " ],\n" +
+ " Proxy [\n" +
+ " Proxy [ Proxy [Array], Proxy [Array] ],\n" +
+ " Proxy [ Proxy [Array], Proxy [Array] ]\n" +
+ " ]\n" +
+ "]";
+ assert.strictEqual(util.inspect(proxy1, { showProxy: 1, depth: null }), expected1);
+ assert.strictEqual(util.inspect(proxy2, opts), expected2);
+ assert.strictEqual(util.inspect(proxy3, opts), expected3);
+ assert.strictEqual(util.inspect(proxy4, opts), expected4);
+ assert.strictEqual(util.inspect(proxy5, opts), expected5);
+ assert.strictEqual(util.inspect(proxy6, opts), expected6);
+ assert.strictEqual(util.inspect(proxy1), expected0);
+ assert.strictEqual(util.inspect(proxy2), expected0);
+ assert.strictEqual(util.inspect(proxy3), expected0);
+ assert.strictEqual(util.inspect(proxy4), expected0);
+ assert.strictEqual(util.inspect(proxy5), expected0);
+ assert.strictEqual(util.inspect(proxy6), expected0);
+
+ // Just for fun, let's create a Proxy using Arrays.
+ const proxy7 = new Proxy([], []);
+ const expected7 = "Proxy [ [], [] ]";
+ assert.strictEqual(util.inspect(proxy7, opts), expected7);
+ assert.strictEqual(util.inspect(proxy7), "[]");
+
+ // Now we're just getting silly, right?
+ const proxy8 = new Proxy(Date, []);
+ const proxy9 = new Proxy(Date, String);
+ const expected8 = "Proxy [ [Function: Date], [] ]";
+ const expected9 = "Proxy [ [Function: Date], [Function: String] ]";
+ assert.strictEqual(util.inspect(proxy8, opts), expected8);
+ assert.strictEqual(util.inspect(proxy9, opts), expected9);
+ assert.strictEqual(util.inspect(proxy8), "[Function: Date]");
+ assert.strictEqual(util.inspect(proxy9), "[Function: Date]");
+
+ const proxy10 = new Proxy(() => {}, {});
+ const proxy11 = new Proxy(() => {}, {
+ get() {
+ return proxy11;
+ },
+ apply() {
+ return proxy11;
+ },
+ });
+ const expected10 = "[Function (anonymous)]";
+ const expected11 = "[Function (anonymous)]";
+ assert.strictEqual(util.inspect(proxy10), expected10);
+ assert.strictEqual(util.inspect(proxy11), expected11);
+});
diff --git a/test/js/node/util/node-inspect-tests/parallel/util-inspect.test.js b/test/js/node/util/node-inspect-tests/parallel/util-inspect.test.js
new file mode 100644
index 000000000..19b0f1b39
--- /dev/null
+++ b/test/js/node/util/node-inspect-tests/parallel/util-inspect.test.js
@@ -0,0 +1,3294 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import assert from "assert";
+import util, { inspect } from "util";
+import vm from "vm";
+import { MessageChannel } from "worker_threads";
+import url from "url";
+const noop = () => {};
+const mustCallChecks = [];
+
+//? Bun does not have this function yet
+assert.doesNotMatch = (string, regexp, message) => {
+ try {
+ assert.match(string, regexp, message);
+ throw null;
+ } catch (e) {
+ if (e === null) {
+ const msg =
+ message || `The input was expected to not match the regular expression ${regexp}. Input:\n'${string}'`;
+ throw new assert.AssertionError({
+ message: msg,
+ actual: string,
+ expected: regexp,
+ operator: "doesNotMatch",
+ stackStartFn: assert.doesNotMatch,
+ });
+ }
+ // pass
+ }
+};
+
+test("no assertion failures", () => {
+ assert.strictEqual(util.inspect(1), "1");
+ assert.strictEqual(util.inspect(false), "false");
+ assert.strictEqual(util.inspect(""), "''");
+ assert.strictEqual(util.inspect("hello"), "'hello'");
+ assert.strictEqual(
+ util.inspect(function abc() {}),
+ "[Function: abc]",
+ );
+ assert.strictEqual(
+ util.inspect(() => {}),
+ "[Function (anonymous)]",
+ );
+ assert.strictEqual(
+ util.inspect(async function () {}),
+ "[AsyncFunction (anonymous)]",
+ );
+ assert.strictEqual(
+ util.inspect(async () => {}),
+ "[AsyncFunction (anonymous)]",
+ );
+
+ // Special function inspection.
+ {
+ const fn = (() => function* () {})();
+ assert.strictEqual(util.inspect(fn), "[GeneratorFunction (anonymous)]");
+ assert.strictEqual(
+ util.inspect(async function* abc() {}),
+ "[AsyncGeneratorFunction: abc]",
+ );
+ Object.setPrototypeOf(
+ fn,
+ Object.getPrototypeOf(async () => {}),
+ );
+ //! Note:
+ // The following tests are relying on the JS-based is(Async|Generator)Function polyfills,
+ // when switching them out back for the native implementation make sure they are fixed!
+ // Due to the bug with the native impl. being flaky make sure its tested multiple times.
+ assert.strictEqual(util.inspect(fn), "[GeneratorFunction (anonymous)] AsyncFunction");
+ Object.defineProperty(fn, "name", { value: 5, configurable: true });
+ assert.strictEqual(util.inspect(fn), "[GeneratorFunction: 5] AsyncFunction");
+ Object.defineProperty(fn, Symbol.toStringTag, {
+ value: "Foobar",
+ configurable: true,
+ });
+ assert.strictEqual(util.inspect({ ["5"]: fn }), "{ '5': [GeneratorFunction: 5] AsyncFunction [Foobar] }");
+ Object.defineProperty(fn, "name", { value: "5", configurable: true });
+ Object.setPrototypeOf(fn, null);
+ assert.strictEqual(util.inspect(fn), "[GeneratorFunction (null prototype): 5] [Foobar]");
+ assert.strictEqual(util.inspect({ ["5"]: fn }), "{ '5': [GeneratorFunction (null prototype): 5] [Foobar] }");
+ }
+
+ assert.strictEqual(util.inspect(undefined), "undefined");
+ assert.strictEqual(util.inspect(null), "null");
+ assert.strictEqual(util.inspect(/foo(bar\n)?/gi), "/foo(bar\\n)?/gi");
+ assert.strictEqual(
+ util.inspect(new Date("Sun, 14 Feb 2010 11:48:40 GMT")),
+ new Date("2010-02-14T12:48:40+01:00").toISOString(),
+ );
+ assert.strictEqual(util.inspect(new Date("")), new Date("").toString());
+ assert.strictEqual(util.inspect("\n\x01"), "'\\n\\x01'");
+ assert.strictEqual(
+ util.inspect(`${Array(75).fill(1)}'\n\x1d\n\x03\x85\x7f\x7e\x9f\xa0`),
+ `"${Array(75).fill(1)}'\\n" +\n '\\x1D\\n' +\n '\\x03\\x85\\x7F~\\x9F '`,
+ );
+ assert.strictEqual(util.inspect([]), "[]");
+ assert.strictEqual(util.inspect({ __proto__: [] }), "Array {}");
+ assert.strictEqual(util.inspect([1, 2]), "[ 1, 2 ]");
+ assert.strictEqual(util.inspect([1, [2, 3]]), "[ 1, [ 2, 3 ] ]");
+ assert.strictEqual(util.inspect({}), "{}");
+ assert.strictEqual(util.inspect({ a: 1 }), "{ a: 1 }");
+ assert.strictEqual(util.inspect({ a: function () {} }), "{ a: [Function: a] }");
+ assert.strictEqual(util.inspect({ a: () => {} }), "{ a: [Function: a] }");
+
+ assert.strictEqual(util.inspect({ a: async function abc() {} }), "{ a: [AsyncFunction: abc] }");
+ assert.strictEqual(util.inspect({ a: async () => {} }), "{ a: [AsyncFunction: a] }");
+ assert.strictEqual(util.inspect({ a: function* () {} }), "{ a: [GeneratorFunction: a] }");
+ assert.strictEqual(util.inspect({ a: 1, b: 2 }), "{ a: 1, b: 2 }");
+ assert.strictEqual(util.inspect({ "a": {} }), "{ a: {} }");
+ assert.strictEqual(util.inspect({ "a": { "b": 2 } }), "{ a: { b: 2 } }");
+ assert.strictEqual(util.inspect({ "a": { "b": { "c": { "d": 2 } } } }), "{ a: { b: { c: [Object] } } }");
+ assert.strictEqual(
+ util.inspect({ "a": { "b": { "c": { "d": 2 } } } }, false, null),
+ "{\n a: { b: { c: { d: 2 } } }\n}",
+ );
+ assert.strictEqual(util.inspect([1, 2, 3], true), "[ 1, 2, 3, [length]: 3 ]");
+ assert.strictEqual(util.inspect({ "a": { "b": { "c": 2 } } }, false, 0), "{ a: [Object] }");
+ assert.strictEqual(util.inspect({ "a": { "b": { "c": 2 } } }, false, 1), "{ a: { b: [Object] } }");
+ assert.strictEqual(util.inspect({ "a": { "b": ["c"] } }, false, 1), "{ a: { b: [Array] } }");
+ assert.strictEqual(util.inspect(new Uint8Array(0)), "Uint8Array(0) []");
+ assert(inspect(new Uint8Array(0), { showHidden: true }).includes("[buffer]"));
+ assert.strictEqual(
+ util.inspect(Object.create({}, { visible: { value: 1, enumerable: true }, hidden: { value: 2 } })),
+ "{ visible: 1 }",
+ );
+ assert.strictEqual(
+ util.inspect(Object.assign(new String("hello"), { [Symbol("foo")]: 123 }), { showHidden: true }),
+ "[String: 'hello'] { [length]: 5, [Symbol(foo)]: 123 }",
+ );
+
+ {
+ const regexp = /regexp/;
+ regexp.aprop = 42;
+ assert.strictEqual(util.inspect({ a: regexp }, false, 0), "{ a: /regexp/ }");
+ }
+
+ assert.match(util.inspect({ a: { a: { a: { a: {} } } } }, undefined, undefined, true), /Object/);
+ assert.doesNotMatch(util.inspect({ a: { a: { a: { a: {} } } } }, undefined, null, true), /Object/);
+
+ {
+ const showHidden = true;
+ const ab = new Uint8Array([1, 2, 3, 4]).buffer;
+ const dv = new DataView(ab, 1, 2);
+ assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }");
+ assert.strictEqual(
+ util.inspect(new DataView(ab, 1, 2), showHidden),
+ "DataView {\n" +
+ " byteLength: 2,\n" +
+ " byteOffset: 1,\n" +
+ " buffer: ArrayBuffer {" +
+ " [Uint8Contents]: <01 02 03 04>, byteLength: 4 }\n}",
+ );
+ assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }");
+ assert.strictEqual(
+ util.inspect(dv, showHidden),
+ "DataView {\n" +
+ " byteLength: 2,\n" +
+ " byteOffset: 1,\n" +
+ " buffer: ArrayBuffer { [Uint8Contents]: " +
+ "<01 02 03 04>, byteLength: 4 }\n}",
+ );
+ ab.x = 42;
+ dv.y = 1337;
+ assert.strictEqual(
+ util.inspect(ab, showHidden),
+ "ArrayBuffer { [Uint8Contents]: <01 02 03 04>, " + "byteLength: 4, x: 42 }",
+ );
+ assert.strictEqual(
+ util.inspect(dv, showHidden),
+ "DataView {\n" +
+ " byteLength: 2,\n" +
+ " byteOffset: 1,\n" +
+ " buffer: ArrayBuffer { [Uint8Contents]: <01 02 03 04>," +
+ " byteLength: 4, x: 42 },\n" +
+ " y: 1337\n}",
+ );
+ }
+
+ {
+ const ab = new ArrayBuffer(42);
+ assert.strictEqual(ab.byteLength, 42);
+ new MessageChannel().port1.postMessage(ab, [ab]);
+ assert.strictEqual(ab.byteLength, 0);
+ assert.strictEqual(util.inspect(ab), "ArrayBuffer { (detached), byteLength: 0 }");
+ }
+
+ // Truncate output for ArrayBuffers using plural or singular bytes
+ {
+ const ab = new ArrayBuffer(3);
+ assert.strictEqual(
+ util.inspect(ab, { showHidden: true, maxArrayLength: 2 }),
+ "ArrayBuffer { [Uint8Contents]" + ": <00 00 ... 1 more byte>, byteLength: 3 }",
+ );
+ assert.strictEqual(
+ util.inspect(ab, { showHidden: true, maxArrayLength: 1 }),
+ "ArrayBuffer { [Uint8Contents]" + ": <00 ... 2 more bytes>, byteLength: 3 }",
+ );
+ }
+});
+
+// Now do the same checks but from a different context.
+test("inspect from a different context", () => {
+ const showHidden = false;
+ const ab = vm.runInNewContext("new ArrayBuffer(4)");
+ const dv = vm.runInNewContext("new DataView(ab, 1, 2)", { ab });
+ assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }");
+ assert.strictEqual(
+ util.inspect(new DataView(ab, 1, 2), showHidden),
+ "DataView {\n" +
+ " byteLength: 2,\n" +
+ " byteOffset: 1,\n" +
+ " buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }\n}",
+ );
+ assert.strictEqual(util.inspect(ab, showHidden), "ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }");
+ //! segfaults
+ /*assert.strictEqual(
+ util.inspect(dv, showHidden),
+ 'DataView {\n' +
+ ' byteLength: 2,\n' +
+ ' byteOffset: 1,\n' +
+ ' buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }\n}'
+ );*/
+ ab.x = 42;
+ dv.y = 1337;
+ assert.strictEqual(
+ util.inspect(ab, showHidden),
+ "ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4, x: 42 }",
+ );
+ //! segfaults
+ /*assert.strictEqual(
+ util.inspect(dv, showHidden),
+ 'DataView {\n' +
+ ' byteLength: 2,\n' +
+ ' byteOffset: 1,\n' +
+ ' buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4, x: 42 },\n' +
+ ' y: 1337\n}'
+ );*/
+});
+
+test("no assertion failures 2", () => {
+ [
+ Float32Array,
+ Float64Array,
+ Int16Array,
+ Int32Array,
+ Int8Array,
+ Uint16Array,
+ Uint32Array,
+ Uint8Array,
+ Uint8ClampedArray,
+ ].forEach(constructor => {
+ const length = 2;
+ const byteLength = length * constructor.BYTES_PER_ELEMENT;
+ const array = new constructor(new ArrayBuffer(byteLength), 0, length);
+ array[0] = 65;
+ array[1] = 97;
+ assert.strictEqual(
+ util.inspect(array, { showHidden: true }),
+ `${constructor.name}(${length}) [\n` +
+ " 65,\n" +
+ " 97,\n" +
+ ` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` +
+ ` [length]: ${length},\n` +
+ ` [byteLength]: ${byteLength},\n` +
+ " [byteOffset]: 0,\n" +
+ ` [buffer]: ArrayBuffer { byteLength: ${byteLength} }\n]`,
+ );
+ assert.strictEqual(util.inspect(array, false), `${constructor.name}(${length}) [ 65, 97 ]`);
+ });
+
+ // Now check that declaring a TypedArray in a different context works the same.
+ [
+ Float32Array,
+ Float64Array,
+ Int16Array,
+ Int32Array,
+ Int8Array,
+ Uint16Array,
+ Uint32Array,
+ Uint8Array,
+ Uint8ClampedArray,
+ ].forEach(constructor => {
+ const length = 2;
+ const byteLength = length * constructor.BYTES_PER_ELEMENT;
+ const array = vm.runInNewContext("new constructor(new ArrayBuffer(byteLength), 0, length)", {
+ constructor,
+ byteLength,
+ length,
+ });
+ array[0] = 65;
+ array[1] = 97;
+ assert.strictEqual(
+ util.inspect(array, true),
+ `${constructor.name}(${length}) [\n` +
+ " 65,\n" +
+ " 97,\n" +
+ ` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` +
+ ` [length]: ${length},\n` +
+ ` [byteLength]: ${byteLength},\n` +
+ " [byteOffset]: 0,\n" +
+ ` [buffer]: ArrayBuffer { byteLength: ${byteLength} }\n]`,
+ );
+ assert.strictEqual(util.inspect(array, false), `${constructor.name}(${length}) [ 65, 97 ]`);
+ });
+
+ {
+ const brokenLength = new Float32Array(2);
+ Object.defineProperty(brokenLength, "length", { value: -1 });
+ assert.strictEqual(inspect(brokenLength), "Float32Array(2) [ 0n, 0n ]");
+ }
+
+ assert.strictEqual(
+ util.inspect(
+ Object.create(
+ {},
+ {
+ visible: { value: 1, enumerable: true },
+ hidden: { value: 2 },
+ },
+ ),
+ { showHidden: true },
+ ),
+ "{ visible: 1, [hidden]: 2 }",
+ );
+ // Objects without prototype.
+ assert.strictEqual(
+ util.inspect(
+ Object.create(null, {
+ name: { value: "Tim", enumerable: true },
+ hidden: { value: "secret" },
+ }),
+ { showHidden: true },
+ ),
+ "[Object: null prototype] { name: 'Tim', [hidden]: 'secret' }",
+ );
+
+ assert.strictEqual(
+ util.inspect(
+ Object.create(null, {
+ name: { value: "Tim", enumerable: true },
+ hidden: { value: "secret" },
+ }),
+ ),
+ "[Object: null prototype] { name: 'Tim' }",
+ );
+
+ // Dynamic properties.
+ {
+ assert.strictEqual(
+ util.inspect({
+ get readonly() {
+ return 1;
+ },
+ }),
+ "{ readonly: [Getter] }",
+ );
+
+ assert.strictEqual(
+ util.inspect({
+ get readwrite() {
+ return 1;
+ },
+ set readwrite(val) {},
+ }),
+ "{ readwrite: [Getter/Setter] }",
+ );
+
+ assert.strictEqual(util.inspect({ set writeonly(val) {} }), "{ writeonly: [Setter] }");
+
+ const value = {};
+ value.a = value;
+ assert.strictEqual(util.inspect(value), "<ref *1> { a: [Circular *1] }");
+ const getterFn = {
+ get one() {
+ return null;
+ },
+ };
+ assert.strictEqual(util.inspect(getterFn, { getters: true }), "{ one: [Getter: null] }");
+ }
+
+ // Array with dynamic properties.
+ {
+ const value = [1, 2, 3];
+ Object.defineProperty(value, "growingLength", {
+ enumerable: true,
+ get: function () {
+ this.push(true);
+ return this.length;
+ },
+ });
+ Object.defineProperty(value, "-1", {
+ enumerable: true,
+ value: -1,
+ });
+ assert.strictEqual(util.inspect(value), "[ 1, 2, 3, growingLength: [Getter], '-1': -1 ]");
+ }
+
+ // Array with inherited number properties.
+ {
+ class CustomArray extends Array {}
+ CustomArray.prototype[5] = "foo";
+ CustomArray.prototype[49] = "bar";
+ CustomArray.prototype.foo = true;
+ const arr = new CustomArray(50);
+ arr[49] = "I win";
+ assert.strictEqual(util.inspect(arr), "CustomArray(50) [ <49 empty items>, 'I win' ]");
+ assert.strictEqual(
+ util.inspect(arr, { showHidden: true }),
+ "CustomArray(50) [\n" +
+ " <49 empty items>,\n" +
+ " 'I win',\n" +
+ " [length]: 50,\n" +
+ " '5': 'foo',\n" +
+ " foo: true\n" +
+ "]",
+ );
+ }
+
+ // Array with extra properties.
+ {
+ const arr = [1, 2, 3, ,];
+ arr.foo = "bar";
+ assert.strictEqual(util.inspect(arr), "[ 1, 2, 3, <1 empty item>, foo: 'bar' ]");
+
+ const arr2 = [];
+ assert.strictEqual(util.inspect([], { showHidden: true }), "[ [length]: 0 ]");
+ arr2["00"] = 1;
+ assert.strictEqual(util.inspect(arr2), "[ '00': 1 ]");
+ assert.strictEqual(util.inspect(arr2, { showHidden: true }), "[ [length]: 0, '00': 1 ]");
+ arr2[1] = 0;
+ assert.strictEqual(util.inspect(arr2), "[ <1 empty item>, 0, '00': 1 ]");
+ assert.strictEqual(util.inspect(arr2, { showHidden: true }), "[ <1 empty item>, 0, [length]: 2, '00': 1 ]");
+ delete arr2[1];
+ assert.strictEqual(util.inspect(arr2), "[ <2 empty items>, '00': 1 ]");
+ assert.strictEqual(util.inspect(arr2, { showHidden: true }), "[ <2 empty items>, [length]: 2, '00': 1 ]");
+ arr2["01"] = 2;
+ assert.strictEqual(util.inspect(arr2), "[ <2 empty items>, '00': 1, '01': 2 ]");
+ assert.strictEqual(util.inspect(arr2, { showHidden: true }), "[ <2 empty items>, [length]: 2, '00': 1, '01': 2 ]");
+ delete arr2["00"];
+ arr2[0] = 0;
+ assert.strictEqual(util.inspect(arr2), "[ 0, <1 empty item>, '01': 2 ]");
+ assert.strictEqual(util.inspect(arr2, { showHidden: true }), "[ 0, <1 empty item>, [length]: 2, '01': 2 ]");
+ delete arr2["01"];
+ arr2[2 ** 32 - 2] = "max";
+ arr2[2 ** 32 - 1] = "too far";
+ assert.strictEqual(util.inspect(arr2), "[ 0, <4294967293 empty items>, 'max', '4294967295': 'too far' ]");
+
+ const arr3 = [];
+ arr3[-1] = -1;
+ assert.strictEqual(util.inspect(arr3), "[ '-1': -1 ]");
+ }
+
+ // Indices out of bounds.
+ {
+ const arr = [];
+ arr[2 ** 32] = true; // Not a valid array index.
+ assert.strictEqual(util.inspect(arr), "[ '4294967296': true ]");
+ arr[0] = true;
+ arr[10] = true;
+ assert.strictEqual(util.inspect(arr), "[ true, <9 empty items>, true, '4294967296': true ]");
+ arr[2 ** 32 - 2] = true;
+ arr[2 ** 32 - 1] = true;
+ arr[2 ** 32 + 1] = true;
+ delete arr[0];
+ delete arr[10];
+ assert.strictEqual(
+ util.inspect(arr),
+ [
+ "[",
+ "<4294967294 empty items>,",
+ "true,",
+ "'4294967296': true,",
+ "'4294967295': true,",
+ "'4294967297': true\n]",
+ ].join("\n "),
+ );
+ }
+
+ // Function with properties.
+ {
+ const value = () => {};
+ value.aprop = 42;
+ assert.strictEqual(util.inspect(value), "[Function: value] { aprop: 42 }");
+ }
+
+ // Anonymous function with properties.
+ {
+ const value = (() => function () {})();
+ value.aprop = 42;
+ assert.strictEqual(util.inspect(value), "[Function (anonymous)] { aprop: 42 }");
+ }
+
+ // Regular expressions with properties.
+ {
+ const value = /123/gi;
+ value.aprop = 42;
+ assert.strictEqual(util.inspect(value), "/123/gi { aprop: 42 }");
+ }
+
+ // Dates with properties.
+ {
+ const value = new Date("Sun, 14 Feb 2010 11:48:40 GMT");
+ value.aprop = 42;
+ assert.strictEqual(util.inspect(value), "2010-02-14T11:48:40.000Z { aprop: 42 }");
+ }
+
+ // Test the internal isDate implementation.
+ {
+ const Date2 = vm.runInNewContext("Date");
+ const d = new Date2();
+ const orig = util.inspect(d);
+ Date2.prototype.foo = "bar";
+ const after = util.inspect(d);
+ assert.strictEqual(orig, after);
+ }
+
+ // Test positive/negative zero.
+ assert.strictEqual(util.inspect(0), "0");
+ assert.strictEqual(util.inspect(-0), "-0");
+ // Edge case from check.
+ assert.strictEqual(util.inspect(-5e-324), "-5e-324");
+
+ // Test for sparse array.
+ {
+ const a = ["foo", "bar", "baz"];
+ assert.strictEqual(util.inspect(a), "[ 'foo', 'bar', 'baz' ]");
+ delete a[1];
+ assert.strictEqual(util.inspect(a), "[ 'foo', <1 empty item>, 'baz' ]");
+ assert.strictEqual(util.inspect(a, true), "[ 'foo', <1 empty item>, 'baz', [length]: 3 ]");
+ assert.strictEqual(util.inspect(new Array(5)), "[ <5 empty items> ]");
+ a[3] = "bar";
+ a[100] = "qux";
+ assert.strictEqual(
+ util.inspect(a, { breakLength: Infinity }),
+ "[ 'foo', <1 empty item>, 'baz', 'bar', <96 empty items>, 'qux' ]",
+ );
+ delete a[3];
+ assert.strictEqual(
+ util.inspect(a, { maxArrayLength: 4 }),
+ "[ 'foo', <1 empty item>, 'baz', <97 empty items>, ... 1 more item ]",
+ );
+ // test 4 special case
+ assert.strictEqual(
+ util.inspect(a, {
+ maxArrayLength: 2,
+ }),
+ "[ 'foo', <1 empty item>, ... 99 more items ]",
+ );
+ }
+
+ // Test for other constructors in different context.
+ {
+ let obj = vm.runInNewContext("(function(){return {}})()", {});
+ assert.strictEqual(util.inspect(obj), "{}");
+ obj = vm.runInNewContext("const m=new Map();m.set(1,2);m", {});
+ assert.strictEqual(util.inspect(obj), "Map(1) { 1 => 2 }");
+ obj = vm.runInNewContext("const s=new Set();s.add(1);s.add(2);s", {});
+ assert.strictEqual(util.inspect(obj), "Set(2) { 1, 2 }");
+ obj = vm.runInNewContext("fn=function(){};new Promise(fn,fn)", {});
+ assert.strictEqual(util.inspect(obj), "Promise { <pending> }");
+ }
+
+ // Test for property descriptors.
+ {
+ const getter = Object.create(null, {
+ a: {
+ get: function () {
+ return "aaa";
+ },
+ },
+ });
+ const setter = Object.create(null, {
+ b: {
+ set: function () {},
+ },
+ });
+ const getterAndSetter = Object.create(null, {
+ c: {
+ get: function () {
+ return "ccc";
+ },
+ set: function () {},
+ },
+ });
+ assert.strictEqual(util.inspect(getter, true), "[Object: null prototype] { [a]: [Getter] }");
+ assert.strictEqual(util.inspect(setter, true), "[Object: null prototype] { [b]: [Setter] }");
+ assert.strictEqual(util.inspect(getterAndSetter, true), "[Object: null prototype] { [c]: [Getter/Setter] }");
+ }
+
+ // Exceptions should print the error message, not '{}'.
+ {
+ [new Error(), new Error("FAIL"), new TypeError("FAIL"), new SyntaxError("FAIL")].forEach(err => {
+ assert(
+ //! temp bug workaround with replace()'s
+ util.inspect(err).startsWith(err.stack.replace(/^Error: /, err.message ? "$&" : "Error")),
+ `Expected "${util.inspect(err)}" to start with "${err.stack.replace(
+ /^Error: /,
+ err.message ? "$&" : "Error",
+ )}"`,
+ );
+ });
+
+ assert.throws(
+ () => undef(),
+ e => {
+ assert(util.inspect(e).startsWith(e.stack), `Expected "${util.inspect(e)}" to start with "${e.stack}"`);
+ return true;
+ },
+ );
+
+ const ex = util.inspect(new Error("FAIL"), true);
+ assert(ex.includes("Error: FAIL"));
+ assert(ex.includes("[stack]"));
+ assert(ex.includes("[message]"));
+ }
+
+ {
+ const falsyCause1 = new Error("", { cause: false });
+ delete falsyCause1.stack;
+ const falsyCause2 = new Error(undefined, { cause: null });
+ falsyCause2.stack = "";
+ const undefinedCause = new Error("", { cause: undefined });
+ undefinedCause.stack = "";
+
+ assert.match(util.inspect(falsyCause1), /^\[Error\] \{ .*\[cause\]: false.* \}$/);
+ assert.match(util.inspect(falsyCause2), /^\[Error\] \{ .*\[cause\]: null.* \}$/);
+ assert.match(util.inspect(undefinedCause), /^\[Error\] \{ .*\[cause\]: undefined.* \}$/);
+ }
+
+ {
+ const tmp = Error.stackTraceLimit;
+ Error.stackTraceLimit = 0;
+ const err = new Error("foo");
+ const err2 = new Error("foo\nbar");
+ assert.strictEqual(util.inspect(err, { compact: true }), "[Error: foo]");
+ //assert(err.stack); //! skipped test, broken in bun
+ delete err.stack;
+ assert(!err.stack);
+ assert.strictEqual(util.inspect(err, { compact: true }), "[Error: foo]");
+ assert.strictEqual(util.inspect(err2, { compact: true }), "[Error: foo\nbar]");
+
+ err.bar = true;
+ err2.bar = true;
+
+ assert.strictEqual(util.inspect(err, { compact: true }), "{ [Error: foo] bar: true }");
+ assert.strictEqual(util.inspect(err2, { compact: true }), "{ [Error: foo\nbar]\n bar: true }");
+ assert.strictEqual(util.inspect(err, { compact: true, breakLength: 5 }), "{ [Error: foo]\n bar: true }");
+ assert.strictEqual(util.inspect(err, { compact: true, breakLength: 1 }), "{ [Error: foo]\n bar:\n true }");
+ assert.strictEqual(util.inspect(err2, { compact: true, breakLength: 5 }), "{ [Error: foo\nbar]\n bar: true }");
+ assert.strictEqual(util.inspect(err, { compact: false }), "[Error: foo] {\n bar: true\n}");
+ assert.strictEqual(util.inspect(err2, { compact: false }), "[Error: foo\nbar] {\n bar: true\n}");
+
+ Error.stackTraceLimit = tmp;
+ }
+
+ // Prevent non-enumerable error properties from being printed.
+ {
+ // TODO(bun): Make originalLine and originalColumn non-enumerable
+ let err = new Error();
+ err.message = "foobar";
+ let out = util
+ .inspect(err)
+ .replace(/\{\s*originalLine: .+\s*originalColumn: .+\s*\}/, "")
+ .trim()
+ .split("\n");
+ assert.strictEqual(out[0], "Error: foobar");
+ assert(out.at(-1).startsWith(" at "), 'Expected "' + out.at(-1) + '" to start with " at "');
+ // Reset the error, the stack is otherwise not recreated.
+ err = new Error();
+ err.message = "foobar";
+ err.name = "Unique";
+ Object.defineProperty(err, "stack", { value: err.stack, enumerable: true });
+ out = util
+ .inspect(err)
+ .replace(/\{\s*originalLine: .+\s*originalColumn: .+\s*\}/, "")
+ .trim()
+ .split("\n");
+ assert.strictEqual(out[0], "Unique: foobar");
+ assert(out.at(-1).startsWith(" at "), 'Expected "' + out.at(-1) + '" to start with " at "');
+ err.name = "Baz";
+ out = util
+ .inspect(err)
+ .replace(/\n\s*originalLine: .+\s*originalColumn: .+/, "")
+ .trim()
+ .split("\n");
+ assert.strictEqual(out[0], "Unique: foobar");
+ assert.strictEqual(out.at(-2), " name: 'Baz',");
+ assert.strictEqual(out.at(-1), "}");
+ }
+
+ // Doesn't capture stack trace.
+ {
+ function BadCustomError(msg) {
+ Error.call(this);
+ Object.defineProperty(this, "message", { value: msg, enumerable: false });
+ Object.defineProperty(this, "name", { value: "BadCustomError", enumerable: false });
+ }
+ Object.setPrototypeOf(BadCustomError.prototype, Error.prototype);
+ Object.setPrototypeOf(BadCustomError, Error);
+ assert.strictEqual(util.inspect(new BadCustomError("foo")), "[BadCustomError: foo]");
+ }
+
+ // Tampered error stack or name property (different type than string).
+ // Note: Symbols are not supported by `Error#toString()` which is called by accessing the `stack` property.
+ // TODO: Node 20+ changed how it handles the these cases, we should update to match such behavior.
+ // It should now preserve the original name and display the user-defined one as an extra property.
+ [
+ [404, "404: foo", "[404]"],
+ [0, "0: foo", "[RangeError: foo]"],
+ [0n, "0: foo", "[RangeError: foo]"],
+ [null, "null: foo", "[RangeError: foo]"],
+ //[undefined, 'RangeError: foo', '[RangeError: foo]'],
+ [false, "false: foo", "[RangeError: foo]"],
+ ["", "foo", "[RangeError: foo]"],
+ //[[1, 2, 3], '1,2,3: foo', '[1,2,3]'],
+ ].forEach(([value, outputStart, stack]) => {
+ let err = new RangeError("foo");
+ err.name = value;
+ assert(
+ util.inspect(err).startsWith(outputStart),
+ util.format(
+ 'The name set to %o did not result in the expected output "%s", received "%s"',
+ value,
+ outputStart,
+ util.inspect(err).split("\n")[0],
+ ),
+ );
+
+ err = new RangeError("foo");
+ err.stack = value;
+ assert.strictEqual(util.inspect(err).split(" { ")[0], stack);
+ });
+
+ // https://github.com/nodejs/node-v0.x-archive/issues/1941
+ assert.strictEqual(util.inspect({ __proto__: Date.prototype }), "Date {}");
+
+ // https://github.com/nodejs/node-v0.x-archive/issues/1944
+ {
+ const d = new Date();
+ d.toUTCString = null;
+ util.inspect(d);
+ }
+
+ // Should not throw.
+ {
+ const d = new Date();
+ d.toISOString = null;
+ util.inspect(d);
+ }
+
+ // Should not throw.
+ {
+ const r = /regexp/;
+ r.toString = null;
+ util.inspect(r);
+ }
+
+ // Ref: https://github.com/nodejs/node-v0.x-archive/issues/2225
+ {
+ const x = { [util.inspect.custom]: util.inspect };
+ assert(
+ util.inspect(x).includes("[Symbol(nodejs.util.inspect.custom)]: [Function: inspect] {\n"),
+ `Expected '${util.inspect(x)}' to include '[Symbol(nodejs.util.inspect.custom)]: [Function: inspect] {\n'`,
+ );
+ }
+
+ // `util.inspect` should display the escaped value of a key.
+ {
+ const w = {
+ "\\": 1,
+ "\\\\": 2,
+ "\\\\\\": 3,
+ "\\\\\\\\": 4,
+ "\n": 5,
+ "\r": 6,
+ };
+
+ const y = ["a", "b", "c"];
+ y["\\\\"] = "d";
+ y["\n"] = "e";
+ y["\r"] = "f";
+
+ assert.strictEqual(
+ util.inspect(w),
+ "{ '\\\\': 1, '\\\\\\\\': 2, '\\\\\\\\\\\\': 3, " + "'\\\\\\\\\\\\\\\\': 4, '\\n': 5, '\\r': 6 }",
+ );
+ assert.strictEqual(util.inspect(y), "[ 'a', 'b', 'c', '\\\\\\\\': 'd', " + "'\\n': 'e', '\\r': 'f' ]");
+ }
+
+ // Escape unpaired surrogate pairs.
+ {
+ const edgeChar = String.fromCharCode(0xd799);
+
+ for (let charCode = 0xd800; charCode < 0xdfff; charCode++) {
+ const surrogate = String.fromCharCode(charCode);
+
+ assert.strictEqual(util.inspect(surrogate), `'\\u${charCode.toString(16)}'`);
+ assert.strictEqual(
+ util.inspect(`${"a".repeat(200)}${surrogate}`),
+ `'${"a".repeat(200)}\\u${charCode.toString(16)}'`,
+ );
+ assert.strictEqual(
+ util.inspect(`${surrogate}${"a".repeat(200)}`),
+ `'\\u${charCode.toString(16)}${"a".repeat(200)}'`,
+ );
+ if (charCode < 0xdc00) {
+ const highSurrogate = surrogate;
+ const lowSurrogate = String.fromCharCode(charCode + 1024);
+ assert(!util.inspect(`${edgeChar}${highSurrogate}${lowSurrogate}${edgeChar}`).includes("\\u"));
+ assert.strictEqual(
+ (util.inspect(`${highSurrogate}${highSurrogate}${lowSurrogate}`).match(/\\u/g) ?? []).length,
+ 1,
+ );
+ } else {
+ assert.strictEqual(
+ util.inspect(`${edgeChar}${surrogate}${edgeChar}`),
+ `'${edgeChar}\\u${charCode.toString(16)}${edgeChar}'`,
+ );
+ }
+ }
+ }
+
+ // Test util.inspect.styles and util.inspect.colors.
+ {
+ function testColorStyle(style, input) {
+ const colorName = util.inspect.styles[style];
+ let color = ["", ""];
+ if (util.inspect.colors[colorName]) color = util.inspect.colors[colorName];
+
+ const withoutColor = util.inspect(input, false, 0, false);
+ const withColor = util.inspect(input, false, 0, true);
+ const expect = `\u001b[${color[0]}m${withoutColor}\u001b[${color[1]}m`;
+ assert.strictEqual(withColor, expect, `util.inspect color for style ${style}`);
+ }
+
+ testColorStyle("special", function () {});
+ testColorStyle("number", 123.456);
+ testColorStyle("boolean", true);
+ testColorStyle("undefined", undefined);
+ testColorStyle("null", null);
+ testColorStyle("string", "test string");
+ testColorStyle("date", new Date());
+ testColorStyle("regexp", /regexp/);
+ }
+
+ // An object with "hasOwnProperty" overwritten should not throw.
+ util.inspect({ hasOwnProperty: null });
+
+ // New API, accepts an "options" object.
+ {
+ const subject = { foo: "bar", hello: 31, a: { b: { c: { d: 0 } } } };
+ Object.defineProperty(subject, "hidden", { enumerable: false, value: null });
+
+ assert.strictEqual(util.inspect(subject, { showHidden: false }).includes("hidden"), false);
+ assert.strictEqual(util.inspect(subject, { showHidden: true }).includes("hidden"), true);
+ assert.strictEqual(util.inspect(subject, { colors: false }).includes("\u001b[32m"), false);
+ assert.strictEqual(util.inspect(subject, { colors: true }).includes("\u001b[32m"), true);
+ assert.strictEqual(util.inspect(subject, { depth: 2 }).includes("c: [Object]"), true);
+ assert.strictEqual(util.inspect(subject, { depth: 0 }).includes("a: [Object]"), true);
+ assert.strictEqual(util.inspect(subject, { depth: null }).includes("{ d: 0 }"), true);
+ assert.strictEqual(util.inspect(subject, { depth: undefined }).includes("{ d: 0 }"), true);
+ }
+
+ {
+ // "customInspect" option can enable/disable calling [util.inspect.custom]().
+ const subject = { [util.inspect.custom]: () => 123 };
+
+ assert.strictEqual(util.inspect(subject, { customInspect: true }).includes("123"), true);
+ assert.strictEqual(util.inspect(subject, { customInspect: true }).includes("inspect"), false);
+ assert.strictEqual(util.inspect(subject, { customInspect: false }).includes("123"), false);
+ assert.strictEqual(util.inspect(subject, { customInspect: false }).includes("inspect"), true);
+
+ // A custom [util.inspect.custom]() should be able to return other Objects.
+ subject[util.inspect.custom] = () => ({ foo: "bar" });
+
+ assert.strictEqual(util.inspect(subject), "{ foo: 'bar' }");
+
+ subject[util.inspect.custom] = mustCall((depth, opts, inspect) => {
+ const clone = { ...opts };
+ // This might change at some point but for now we keep the stylize function.
+ // The function should either be documented or an alternative should be implemented.
+ assert.strictEqual(typeof opts.stylize, "function");
+ assert.strictEqual(opts.seen, undefined);
+ assert.strictEqual(opts.budget, undefined);
+ assert.strictEqual(opts.indentationLvl, undefined);
+ assert.strictEqual(opts.showHidden, false);
+ assert.strictEqual(inspect, util.inspect);
+ assert.deepStrictEqual(
+ new Set(Object.keys(inspect.defaultOptions).concat(["stylize"])),
+ new Set(Object.keys(opts)),
+ );
+ opts.showHidden = true;
+ return {
+ [inspect.custom]: mustCall((depth, opts2) => {
+ assert.deepStrictEqual(clone, opts2);
+ }),
+ };
+ });
+
+ util.inspect(subject);
+
+ // util.inspect.custom is a shared symbol which can be accessed as
+ // Symbol.for("nodejs.util.inspect.custom").
+ const inspect = Symbol.for("nodejs.util.inspect.custom");
+
+ subject[inspect] = () => ({ baz: "quux" });
+
+ assert.strictEqual(util.inspect(subject), "{ baz: 'quux' }");
+
+ subject[inspect] = (depth, opts) => {
+ assert.strictEqual(opts.customInspectOptions, true);
+ assert.strictEqual(opts.seen, null);
+ return {};
+ };
+
+ util.inspect(subject, { customInspectOptions: true, seen: null });
+ }
+
+ {
+ const subject = {
+ [util.inspect.custom]: mustCall((depth, opts) => {
+ assert.strictEqual(depth, null);
+ assert.strictEqual(opts.compact, true);
+ }),
+ };
+ util.inspect(subject, { depth: null, compact: true });
+ }
+
+ {
+ // Returning `this` from a custom inspection function works.
+ const subject = {
+ a: 123,
+ [util.inspect.custom]() {
+ return this;
+ },
+ };
+ const UIC = "nodejs.util.inspect.custom";
+ assert.strictEqual(util.inspect(subject), `{\n a: 123,\n [Symbol(${UIC})]: [Function: [${UIC}]]\n}`);
+ }
+
+ //! non-standard property
+ // Verify that it's possible to use the stylize function to manipulate input.
+ /*assert.strictEqual(
+ util.inspect([1, 2, 3], { stylize() { return 'x'; } }),
+ '[ x, x, x ]'
+ );*/
+
+ // Using `util.inspect` with "colors" option should produce as many lines as
+ // without it.
+ {
+ function testLines(input) {
+ const countLines = str => (str.match(/\n/g) || []).length;
+ const withoutColor = util.inspect(input);
+ const withColor = util.inspect(input, { colors: true });
+ assert.strictEqual(countLines(withoutColor), countLines(withColor));
+ }
+
+ const bigArray = new Array(100).fill().map((value, index) => index);
+
+ testLines([1, 2, 3, 4, 5, 6, 7]);
+ testLines(bigArray);
+ testLines({ foo: "bar", baz: 35, b: { a: 35 } });
+ testLines({ a: { a: 3, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1, h: 1 }, b: 1 });
+ testLines({
+ foo: "bar",
+ baz: 35,
+ b: { a: 35 },
+ veryLongKey: "very long value",
+ evenLongerKey: ["with even longer value in array"],
+ });
+ }
+
+ // Test boxed primitives output the correct values.
+ assert.strictEqual(util.inspect(new String("test")), "[String: 'test']");
+ assert.strictEqual(util.inspect(new String("test"), { colors: true }), "\u001b[32m[String: 'test']\u001b[39m");
+ assert.strictEqual(util.inspect(Object(Symbol("test"))), "[Symbol: Symbol(test)]");
+ assert.strictEqual(util.inspect(new Boolean(false)), "[Boolean: false]");
+ assert.strictEqual(util.inspect(Object.setPrototypeOf(new Boolean(true), null)), "[Boolean (null prototype): true]");
+ assert.strictEqual(util.inspect(new Number(0)), "[Number: 0]");
+
+ assert.strictEqual(
+ util.inspect(
+ Object.defineProperty(Object.setPrototypeOf(new Number(-0), Array.prototype), Symbol.toStringTag, {
+ value: "Foobar",
+ }),
+ ),
+ "[Number (Array): -0] [Foobar]",
+ );
+ assert.strictEqual(util.inspect(new Number(-1.1)), "[Number: -1.1]");
+ assert.strictEqual(util.inspect(new Number(13.37)), "[Number: 13.37]");
+
+ // Test boxed primitives with own properties.
+ {
+ const str = new String("baz");
+ str.foo = "bar";
+ assert.strictEqual(util.inspect(str), "[String: 'baz'] { foo: 'bar' }");
+
+ const bool = new Boolean(true);
+ bool.foo = "bar";
+ assert.strictEqual(util.inspect(bool), "[Boolean: true] { foo: 'bar' }");
+
+ const num = new Number(13.37);
+ num.foo = "bar";
+ assert.strictEqual(util.inspect(num), "[Number: 13.37] { foo: 'bar' }");
+
+ const sym = Object(Symbol("foo"));
+ sym.foo = "bar";
+ assert.strictEqual(util.inspect(sym), "[Symbol: Symbol(foo)] { foo: 'bar' }");
+
+ const big = Object(BigInt(55));
+ big.foo = "bar";
+ assert.strictEqual(util.inspect(big), "[BigInt: 55n] { foo: 'bar' }");
+ }
+
+ // Test es6 Symbol.
+ if (typeof Symbol !== "undefined") {
+ assert.strictEqual(util.inspect(Symbol()), "Symbol()");
+ assert.strictEqual(util.inspect(Symbol(123)), "Symbol(123)");
+ assert.strictEqual(util.inspect(Symbol("hi")), "Symbol(hi)");
+ assert.strictEqual(util.inspect([Symbol()]), "[ Symbol() ]");
+ assert.strictEqual(util.inspect({ foo: Symbol() }), "{ foo: Symbol() }");
+
+ const options = { showHidden: true };
+ let subject = {};
+
+ subject[Symbol("sym\nbol")] = 42;
+
+ assert.strictEqual(util.inspect(subject), "{ [Symbol(sym\\nbol)]: 42 }");
+ assert.strictEqual(util.inspect(subject, options), "{ [Symbol(sym\\nbol)]: 42 }");
+
+ Object.defineProperty(subject, Symbol(), { enumerable: false, value: "non-enum" });
+ assert.strictEqual(util.inspect(subject), "{ [Symbol(sym\\nbol)]: 42 }");
+ assert.strictEqual(util.inspect(subject, options), "{ [Symbol(sym\\nbol)]: 42, [Symbol()]: 'non-enum' }");
+
+ subject = [1, 2, 3];
+ subject[Symbol("symbol")] = 42;
+ assert.strictEqual(util.inspect(subject), "[ 1, 2, 3, [Symbol(symbol)]: 42 ]");
+ }
+
+ // Test Set.
+ {
+ assert.strictEqual(util.inspect(new Set()), "Set(0) {}");
+ assert.strictEqual(util.inspect(new Set([1, 2, 3])), "Set(3) { 1, 2, 3 }");
+ assert.strictEqual(util.inspect(new Set([1, 2, 3]), { maxArrayLength: 1 }), "Set(3) { 1, ... 2 more items }");
+ const set = new Set(["foo"]);
+ set.bar = 42;
+ assert.strictEqual(util.inspect(set, { showHidden: true }), "Set(1) { 'foo', bar: 42 }");
+ }
+
+ // Test circular Set.
+ {
+ const set = new Set();
+ set.add(set);
+ assert.strictEqual(util.inspect(set), "<ref *1> Set(1) { [Circular *1] }");
+ }
+
+ // Test Map.
+ {
+ assert.strictEqual(util.inspect(new Map()), "Map(0) {}");
+ assert.strictEqual(
+ util.inspect(
+ new Map([
+ [1, "a"],
+ [2, "b"],
+ [3, "c"],
+ ]),
+ ),
+ "Map(3) { 1 => 'a', 2 => 'b', 3 => 'c' }",
+ );
+ assert.strictEqual(
+ util.inspect(
+ new Map([
+ [1, "a"],
+ [2, "b"],
+ [3, "c"],
+ ]),
+ { maxArrayLength: 1 },
+ ),
+ "Map(3) { 1 => 'a', ... 2 more items }",
+ );
+ const map = new Map([["foo", null]]);
+ map.bar = 42;
+ assert.strictEqual(util.inspect(map, true), "Map(1) { 'foo' => null, bar: 42 }");
+ }
+
+ // Test circular Map.
+ {
+ const map = new Map();
+ map.set(map, "map");
+ assert.strictEqual(inspect(map), "<ref *1> Map(1) { [Circular *1] => 'map' }");
+ map.set(map, map);
+ assert.strictEqual(inspect(map), "<ref *1> Map(1) { [Circular *1] => [Circular *1] }");
+ map.delete(map);
+ map.set("map", map);
+ assert.strictEqual(inspect(map), "<ref *1> Map(1) { 'map' => [Circular *1] }");
+ }
+
+ // Test multiple circular references.
+ {
+ const obj = {};
+ obj.a = [obj];
+ obj.b = {};
+ obj.b.inner = obj.b;
+ obj.b.obj = obj;
+
+ assert.strictEqual(
+ inspect(obj),
+ "<ref *1> {\n" +
+ " a: [ [Circular *1] ],\n" +
+ " b: <ref *2> { inner: [Circular *2], obj: [Circular *1] }\n" +
+ "}",
+ );
+ }
+
+ // Test Promise.
+ {
+ const resolved = Promise.resolve(3);
+ assert.strictEqual(util.inspect(resolved), "Promise { 3 }");
+
+ const rejected = Promise.reject(3);
+ assert.strictEqual(util.inspect(rejected), "Promise { <rejected> 3 }");
+ // Squelch UnhandledPromiseRejection.
+ rejected.catch(() => {});
+
+ const pending = new Promise(() => {});
+ assert.strictEqual(util.inspect(pending), "Promise { <pending> }");
+
+ const promiseWithProperty = Promise.resolve("foo");
+ promiseWithProperty.bar = 42;
+ assert.strictEqual(util.inspect(promiseWithProperty), "Promise { 'foo', bar: 42 }");
+ }
+
+ // Make sure it doesn't choke on polyfills. Unlike Set/Map, there is no standard interface to
+ // synchronously inspect a Promise, so our techniques only work on a bonafide native Promise.
+ {
+ const oldPromise = Promise;
+ global.Promise = function () {
+ this.bar = 42;
+ };
+ assert.strictEqual(util.inspect(new Promise()), "{ bar: 42 }");
+ global.Promise = oldPromise;
+ }
+
+ // Test Map iterators.
+ {
+ const map = new Map([["foo", "bar"]]);
+ assert.strictEqual(util.inspect(map.keys()), "[Map Iterator] { 'foo' }");
+ const mapValues = map.values();
+ Object.defineProperty(mapValues, Symbol.toStringTag, { value: "Foo" });
+ assert.strictEqual(util.inspect(mapValues), "[Foo] [Map Iterator] { 'bar' }");
+ map.set("A", "B!");
+ assert.strictEqual(
+ util.inspect(map.entries(), { maxArrayLength: 1 }),
+ "[Map Entries] { [ 'foo', 'bar' ], ... 1 more item }",
+ );
+ // Make sure the iterator doesn't get consumed.
+ const keys = map.keys();
+ assert.strictEqual(util.inspect(keys), "[Map Iterator] { 'foo', 'A' }");
+ assert.strictEqual(util.inspect(keys), "[Map Iterator] { 'foo', 'A' }");
+ keys.extra = true;
+ assert.strictEqual(util.inspect(keys, { maxArrayLength: 0 }), "[Map Iterator] { ... 2 more items, extra: true }");
+ }
+
+ // Test Set iterators.
+ {
+ const aSet = new Set([1]);
+ assert.strictEqual(util.inspect(aSet.entries(), { compact: false }), "[Set Entries] {\n [\n 1,\n 1\n ]\n}");
+ aSet.add(3);
+ assert.strictEqual(util.inspect(aSet.keys()), "[Set Iterator] { 1, 3 }");
+ assert.strictEqual(util.inspect(aSet.values()), "[Set Iterator] { 1, 3 }");
+ const setEntries = aSet.entries();
+ Object.defineProperty(setEntries, Symbol.toStringTag, { value: "Foo" });
+ assert.strictEqual(util.inspect(setEntries), "[Foo] [Set Entries] { [ 1, 1 ], [ 3, 3 ] }");
+ // Make sure the iterator doesn't get consumed.
+ const keys = aSet.keys();
+ Object.defineProperty(keys, Symbol.toStringTag, { value: null });
+ assert.strictEqual(util.inspect(keys), "[Set Iterator] { 1, 3 }");
+ assert.strictEqual(util.inspect(keys), "[Set Iterator] { 1, 3 }");
+ keys.extra = true;
+ assert.strictEqual(util.inspect(keys, { maxArrayLength: 1 }), "[Set Iterator] { 1, ... 1 more item, extra: true }");
+ }
+
+ // Minimal inspection should still return as much information as possible about
+ // the constructor and Symbol.toStringTag.
+ {
+ class Foo {
+ get [Symbol.toStringTag]() {
+ return "ABC";
+ }
+ }
+ const a = new Foo();
+ assert.strictEqual(inspect(a, { depth: -1 }), "Foo [ABC] {}");
+ a.foo = true;
+ assert.strictEqual(inspect(a, { depth: -1 }), "[Foo [ABC]]");
+ Object.defineProperty(a, Symbol.toStringTag, {
+ value: "Foo",
+ configurable: true,
+ writable: true,
+ });
+ assert.strictEqual(inspect(a, { depth: -1 }), "[Foo]");
+ delete a[Symbol.toStringTag];
+ Object.setPrototypeOf(a, null);
+ // TODO: null prototypes
+ // assert.strictEqual(inspect(a, { depth: -1 }), '[Foo: null prototype]');
+ assert.strictEqual(inspect(a, { depth: -1 }), "[Object: null prototype]");
+ delete a.foo;
+ assert.strictEqual(
+ inspect(a, { depth: -1 }),
+ // TODO: '[Foo: null prototype] {}');
+ "[Object: null prototype] {}",
+ );
+ Object.defineProperty(a, Symbol.toStringTag, {
+ value: "ABC",
+ configurable: true,
+ });
+ assert.strictEqual(
+ inspect(a, { depth: -1 }),
+ // TODO: '[Foo: null prototype] [ABC] {}'
+ "[Object: null prototype] [ABC] {}",
+ );
+ Object.defineProperty(a, Symbol.toStringTag, {
+ value: "Foo",
+ configurable: true,
+ });
+ assert.strictEqual(inspect(a, { depth: -1 }), "[Object: null prototype] [Foo] {}");
+ }
+
+ // Test alignment of items in container.
+ // Assumes that the first numeric character is the start of an item.
+ {
+ function checkAlignment(container, start, lineX, end) {
+ const lines = util.inspect(container).split("\n");
+ lines.forEach((line, i) => {
+ if (i === 0) {
+ assert.strictEqual(line, start);
+ } else if (i === lines.length - 1) {
+ assert.strictEqual(line, end);
+ } else {
+ let expected = lineX.replace("X", i - 1);
+ if (i !== lines.length - 2) expected += ",";
+ assert.strictEqual(line, expected);
+ }
+ });
+ }
+
+ const bigArray = [];
+ for (let i = 0; i < 100; i++) {
+ bigArray.push(i);
+ }
+
+ const obj = {};
+ bigArray.forEach(prop => {
+ obj[prop] = null;
+ });
+
+ checkAlignment(obj, "{", " 'X': null", "}");
+ checkAlignment(new Set(bigArray), "Set(100) {", " X", "}");
+ checkAlignment(new Map(bigArray.map(number => [number, null])), "Map(100) {", " X => null", "}");
+ }
+
+ // Test display of constructors.
+ {
+ class ObjectSubclass {}
+ class ArraySubclass extends Array {}
+ class SetSubclass extends Set {}
+ class MapSubclass extends Map {}
+ class PromiseSubclass extends Promise {}
+ class SymbolNameClass {
+ static name = Symbol("name");
+ }
+
+ const x = new ObjectSubclass();
+ x.foo = 42;
+ assert.strictEqual(util.inspect(x), "ObjectSubclass { foo: 42 }");
+ assert.strictEqual(util.inspect(new ArraySubclass(1, 2, 3)), "ArraySubclass(3) [ 1, 2, 3 ]");
+ assert.strictEqual(util.inspect(new SetSubclass([1, 2, 3])), "SetSubclass(3) [Set] { 1, 2, 3 }");
+ assert.strictEqual(util.inspect(new MapSubclass([["foo", 42]])), "MapSubclass(1) [Map] { 'foo' => 42 }");
+ assert.strictEqual(util.inspect(new PromiseSubclass(() => {})), "PromiseSubclass [Promise] { <pending> }");
+ assert.strictEqual(util.inspect(new SymbolNameClass()), "Symbol(name) {}");
+ assert.strictEqual(
+ util.inspect({ a: { b: new ArraySubclass([1, [2], 3]) } }, { depth: 1 }),
+ "{ a: { b: [ArraySubclass] } }",
+ );
+ assert.strictEqual(
+ util.inspect(Object.setPrototypeOf(x, null)),
+ // TODO: '[ObjectSubclass: null prototype] { foo: 42 }'
+ "[Object: null prototype] { foo: 42 }",
+ );
+ }
+
+ // Empty and circular before depth.
+ {
+ const arr = [[[[]]]];
+ assert.strictEqual(util.inspect(arr), "[ [ [ [] ] ] ]");
+ arr[0][0][0][0] = [];
+ assert.strictEqual(util.inspect(arr), "[ [ [ [Array] ] ] ]");
+ arr[0][0][0] = {};
+ assert.strictEqual(util.inspect(arr), "[ [ [ {} ] ] ]");
+ arr[0][0][0] = { a: 2 };
+ assert.strictEqual(util.inspect(arr), "[ [ [ [Object] ] ] ]");
+ arr[0][0][0] = arr;
+ assert.strictEqual(util.inspect(arr), "<ref *1> [ [ [ [Circular *1] ] ] ]");
+ arr[0][0][0] = arr[0][0];
+ assert.strictEqual(util.inspect(arr), "[ [ <ref *1> [ [Circular *1] ] ] ]");
+ }
+
+ // Corner cases.
+ {
+ const x = { constructor: 42 };
+ assert.strictEqual(util.inspect(x), "{ constructor: 42 }");
+ }
+
+ {
+ const x = {};
+ Object.defineProperty(x, "constructor", {
+ get: function () {
+ throw new Error("should not access constructor");
+ },
+ enumerable: true,
+ });
+ assert.strictEqual(util.inspect(x), "{ constructor: [Getter] }");
+ }
+
+ {
+ const x = new (function () {})();
+ assert.strictEqual(util.inspect(x), "{}");
+ }
+
+ {
+ const x = { __proto__: null };
+ assert.strictEqual(util.inspect(x), "[Object: null prototype] {}");
+ }
+
+ {
+ const x = [];
+ x[""] = 1;
+ assert.strictEqual(util.inspect(x), "[ '': 1 ]");
+ }
+
+ // The following maxArrayLength tests were introduced after v6.0.0 was released.
+ // Do not backport to v5/v4 unless all of
+ // https://github.com/nodejs/node/pull/6334 is backported.
+ {
+ const x = new Array(101).fill();
+ assert(util.inspect(x).endsWith("1 more item\n]"));
+ assert(!util.inspect(x, { maxArrayLength: 101 }).endsWith("1 more item\n]"));
+ assert.strictEqual(util.inspect(x, { maxArrayLength: -1 }), "[ ... 101 more items ]");
+ assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }), "[ ... 101 more items ]");
+ }
+
+ {
+ const x = Array(101);
+ assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }), "[ ... 101 more items ]");
+ assert(!util.inspect(x, { maxArrayLength: null }).endsWith("1 more item\n]"));
+ assert(!util.inspect(x, { maxArrayLength: Infinity }).endsWith("1 more item ]"));
+ }
+
+ {
+ const x = new Uint8Array(101);
+ assert(util.inspect(x).endsWith("1 more item\n]"));
+ assert(!util.inspect(x, { maxArrayLength: 101 }).includes("1 more item"));
+ assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }), "Uint8Array(101) [ ... 101 more items ]");
+ assert(!util.inspect(x, { maxArrayLength: null }).includes("1 more item"));
+ assert(util.inspect(x, { maxArrayLength: Infinity }).endsWith(" 0, 0\n]"));
+ }
+
+ {
+ const obj = { foo: "abc", bar: "xyz" };
+ const oneLine = util.inspect(obj, { breakLength: Infinity });
+ // Subtract four for the object's two curly braces and two spaces of padding.
+ // Add one more to satisfy the strictly greater than condition in the code.
+ const breakpoint = oneLine.length - 5;
+ const twoLines = util.inspect(obj, { breakLength: breakpoint });
+
+ assert.strictEqual(oneLine, "{ foo: 'abc', bar: 'xyz' }");
+ assert.strictEqual(util.inspect(obj, { breakLength: breakpoint + 1 }), twoLines);
+ assert.strictEqual(twoLines, "{\n foo: 'abc',\n bar: 'xyz'\n}");
+ }
+
+ // util.inspect.defaultOptions tests.
+ {
+ const arr = new Array(101).fill();
+ const obj = { a: { a: { a: { a: 1 } } } };
+
+ const oldOptions = { ...util.inspect.defaultOptions };
+
+ // Set single option through property assignment.
+ util.inspect.defaultOptions.maxArrayLength = null;
+ assert.doesNotMatch(util.inspect(arr), /1 more item/);
+ util.inspect.defaultOptions.maxArrayLength = oldOptions.maxArrayLength;
+ assert.match(util.inspect(arr), /1 more item/);
+ util.inspect.defaultOptions.depth = null;
+ assert.doesNotMatch(util.inspect(obj), /Object/);
+ util.inspect.defaultOptions.depth = oldOptions.depth;
+ assert.match(util.inspect(obj), /Object/);
+ assert.strictEqual(JSON.stringify(util.inspect.defaultOptions), JSON.stringify(oldOptions));
+
+ // Set multiple options through object assignment.
+ util.inspect.defaultOptions = { maxArrayLength: null, depth: 2 };
+ assert.doesNotMatch(util.inspect(arr), /1 more item/);
+ assert.match(util.inspect(obj), /Object/);
+ util.inspect.defaultOptions = oldOptions;
+ assert.match(util.inspect(arr), /1 more item/);
+ assert.match(util.inspect(obj), /Object/);
+ assert.strictEqual(JSON.stringify(util.inspect.defaultOptions), JSON.stringify(oldOptions));
+
+ assert.throws(
+ () => {
+ util.inspect.defaultOptions = null;
+ },
+ {
+ code: "ERR_INVALID_ARG_TYPE",
+ name: "TypeError",
+ message: 'The "options" argument must be of type object. ' + "Received null",
+ },
+ );
+
+ assert.throws(
+ () => {
+ util.inspect.defaultOptions = "bad";
+ },
+ {
+ code: "ERR_INVALID_ARG_TYPE",
+ name: "TypeError",
+ message: 'The "options" argument must be of type object. ' + "Received type string ('bad')",
+ },
+ );
+ }
+
+ util.inspect(process);
+
+ // Setting custom inspect property to a non-function should do nothing.
+ {
+ const obj = { [util.inspect.custom]: "fhqwhgads" };
+ assert.strictEqual(util.inspect(obj), "{ [Symbol(nodejs.util.inspect.custom)]: 'fhqwhgads' }");
+ }
+
+ {
+ // @@toStringTag
+ const obj = { [Symbol.toStringTag]: "a" };
+ assert.strictEqual(util.inspect(obj), "{ [Symbol(Symbol.toStringTag)]: 'a' }");
+ Object.defineProperty(obj, Symbol.toStringTag, {
+ value: "a",
+ enumerable: false,
+ });
+ assert.strictEqual(util.inspect(obj), "Object [a] {}");
+ assert.strictEqual(util.inspect(obj, { showHidden: true }), "{ [Symbol(Symbol.toStringTag)]: 'a' }");
+
+ class Foo {
+ constructor() {
+ this.foo = "bar";
+ }
+
+ get [Symbol.toStringTag]() {
+ return this.foo;
+ }
+ }
+
+ assert.strictEqual(
+ util.inspect(Object.create(null, { [Symbol.toStringTag]: { value: "foo" } })),
+ "[Object: null prototype] [foo] {}",
+ );
+
+ assert.strictEqual(util.inspect(new Foo()), "Foo [bar] { foo: 'bar' }");
+
+ assert.strictEqual(util.inspect(new (class extends Foo {})()), "Foo [bar] { foo: 'bar' }");
+
+ assert.strictEqual(
+ util.inspect(
+ Object.create(
+ { __proto__: Foo.prototype },
+ {
+ foo: { value: "bar", enumerable: true },
+ },
+ ),
+ ),
+ "Foo [bar] { foo: 'bar' }",
+ );
+
+ class ThrowingClass {
+ get [Symbol.toStringTag]() {
+ throw new Error("toStringTag error");
+ }
+ }
+
+ assert.throws(() => util.inspect(new ThrowingClass()), /toStringTag error/);
+
+ class NotStringClass {
+ get [Symbol.toStringTag]() {
+ return null;
+ }
+ }
+
+ assert.strictEqual(util.inspect(new NotStringClass()), "NotStringClass {}");
+ }
+
+ {
+ const o = {
+ a: [
+ 1,
+ 2,
+ [
+ [
+ "Lorem ipsum dolor\nsit amet,\tconsectetur adipiscing elit, sed do " +
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+ "test",
+ "foo",
+ ],
+ ],
+ 4,
+ ],
+ b: new Map([
+ ["za", 1],
+ ["zb", "test"],
+ ]),
+ };
+
+ let out = util.inspect(o, { compact: true, depth: 5, breakLength: 80 });
+ let expect = [
+ "{ a:",
+ " [ 1,",
+ " 2,",
+ " [ [ 'Lorem ipsum dolor\\nsit amet,\\tconsectetur adipiscing elit, " +
+ "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',",
+ " 'test',",
+ " 'foo' ] ],",
+ " 4 ],",
+ " b: Map(2) { 'za' => 1, 'zb' => 'test' } }",
+ ].join("\n");
+ assert.strictEqual(out, expect);
+
+ out = util.inspect(o, { compact: false, depth: 5, breakLength: 60 });
+ expect = [
+ "{",
+ " a: [",
+ " 1,",
+ " 2,",
+ " [",
+ " [",
+ " 'Lorem ipsum dolor\\n' +",
+ " 'sit amet,\\tconsectetur adipiscing elit, sed do eiusmod " +
+ "tempor incididunt ut labore et dolore magna aliqua.',",
+ " 'test',",
+ " 'foo'",
+ " ]",
+ " ],",
+ " 4",
+ " ],",
+ " b: Map(2) {",
+ " 'za' => 1,",
+ " 'zb' => 'test'",
+ " }",
+ "}",
+ ].join("\n");
+ assert.strictEqual(out, expect);
+
+ out = util.inspect(o.a[2][0][0], { compact: false, breakLength: 30 });
+ expect = [
+ "'Lorem ipsum dolor\\n' +",
+ " 'sit amet,\\tconsectetur adipiscing elit, sed do eiusmod tempor " +
+ "incididunt ut labore et dolore magna aliqua.'",
+ ].join("\n");
+ assert.strictEqual(out, expect);
+
+ out = util.inspect("12345678901234567890123456789012345678901234567890", { compact: false, breakLength: 3 });
+ expect = "'12345678901234567890123456789012345678901234567890'";
+ assert.strictEqual(out, expect);
+
+ out = util.inspect("12 45 78 01 34 67 90 23 56 89 123456789012345678901234567890", {
+ compact: false,
+ breakLength: 3,
+ });
+ expect = ["'12 45 78 01 34 67 90 23 56 89 123456789012345678901234567890'"].join("\n");
+ assert.strictEqual(out, expect);
+
+ o.a = () => {};
+ o.b = new Number(3);
+ out = util.inspect(o, { compact: false, breakLength: 3 });
+ expect = ["{", " a: [Function (anonymous)],", " b: [Number: 3]", "}"].join("\n");
+ assert.strictEqual(out, expect);
+
+ out = util.inspect(o, { compact: false, breakLength: 3, showHidden: true });
+ expect = [
+ "{",
+ " a: [Function (anonymous)] {",
+ " [length]: 0,",
+ " [name]: ''",
+ " },",
+ " b: [Number: 3]",
+ "}",
+ ].join("\n");
+ assert.strictEqual(out, expect);
+
+ o[util.inspect.custom] = () => 42;
+ out = util.inspect(o, { compact: false, breakLength: 3 });
+ expect = "42";
+ assert.strictEqual(out, expect);
+
+ o[util.inspect.custom] = () => "12 45 78 01 34 67 90 23";
+ out = util.inspect(o, { compact: false, breakLength: 3 });
+ expect = "12 45 78 01 34 67 90 23";
+ assert.strictEqual(out, expect);
+
+ o[util.inspect.custom] = () => ({ a: "12 45 78 01 34 67 90 23" });
+ out = util.inspect(o, { compact: false, breakLength: 3 });
+ expect = "{\n a: '12 45 78 01 34 67 90 23'\n}";
+ assert.strictEqual(out, expect);
+ }
+
+ // Check compact indentation.
+ {
+ const typed = new Uint8Array();
+ typed.buffer.foo = true;
+ const set = new Set([[1, 2]]);
+ const promise = Promise.resolve([[1, set]]);
+ const map = new Map([[promise, typed]]);
+ map.set(set.values(), map.values());
+
+ let out = util.inspect(map, { compact: false, showHidden: true, depth: 9 });
+ let expected = [
+ "Map(2) {",
+ " Promise {",
+ " [",
+ " [",
+ " 1,",
+ " Set(1) {",
+ " [",
+ " 1,",
+ " 2,",
+ " [length]: 2",
+ " ]",
+ " },",
+ " [length]: 2",
+ " ],",
+ " [length]: 1",
+ " ]",
+ " } => Uint8Array(0) [",
+ " [BYTES_PER_ELEMENT]: 1,",
+ " [length]: 0,",
+ " [byteLength]: 0,",
+ " [byteOffset]: 0,",
+ " [buffer]: ArrayBuffer {",
+ " byteLength: 0,",
+ " foo: true",
+ " }",
+ " ],",
+ " [Set Iterator] {",
+ " [",
+ " 1,",
+ " 2,",
+ " [length]: 2",
+ " ],",
+ " [Symbol(Symbol.toStringTag)]: 'Set Iterator'",
+ " } => <ref *1> [Map Iterator] {",
+ " Uint8Array(0) [",
+ " [BYTES_PER_ELEMENT]: 1,",
+ " [length]: 0,",
+ " [byteLength]: 0,",
+ " [byteOffset]: 0,",
+ " [buffer]: ArrayBuffer {",
+ " byteLength: 0,",
+ " foo: true",
+ " }",
+ " ],",
+ " [Circular *1],",
+ " [Symbol(Symbol.toStringTag)]: 'Map Iterator'",
+ " }",
+ "}",
+ ].join("\n");
+
+ assert.strict.equal(out, expected);
+
+ out = util.inspect(map, { compact: 2, showHidden: true, depth: 9 });
+
+ expected = [
+ "Map(2) {",
+ " Promise {\n" + " [",
+ " [",
+ " 1,",
+ " Set(1) { [ 1, 2, [length]: 2 ] },",
+ " [length]: 2",
+ " ],",
+ " [length]: 1",
+ " ]",
+ " } => Uint8Array(0) [",
+ " [BYTES_PER_ELEMENT]: 1,",
+ " [length]: 0,",
+ " [byteLength]: 0,",
+ " [byteOffset]: 0,",
+ " [buffer]: ArrayBuffer { byteLength: 0, foo: true }",
+ " ],",
+ " [Set Iterator] {\n" + " [ 1, 2, [length]: 2 ],",
+ " [Symbol(Symbol.toStringTag)]: 'Set Iterator'\n" +
+ " } => <ref *1> [Map Iterator] {\n" +
+ " Uint8Array(0) [",
+ " [BYTES_PER_ELEMENT]: 1,",
+ " [length]: 0,",
+ " [byteLength]: 0,",
+ " [byteOffset]: 0,",
+ " [buffer]: ArrayBuffer { byteLength: 0, foo: true }",
+ " ],",
+ " [Circular *1],",
+ " [Symbol(Symbol.toStringTag)]: 'Map Iterator'\n" + " }",
+ "}",
+ ].join("\n");
+
+ assert.strict.equal(out, expected);
+
+ out = util.inspect(map, {
+ showHidden: true,
+ depth: 9,
+ breakLength: 4,
+ compact: true,
+ });
+
+ expected = [
+ "Map(2) {",
+ " Promise {",
+ " [ [ 1,",
+ " Set(1) {",
+ " [ 1,",
+ " 2,",
+ " [length]: 2 ] },",
+ " [length]: 2 ],",
+ " [length]: 1 ] } => Uint8Array(0) [",
+ " [BYTES_PER_ELEMENT]: 1,",
+ " [length]: 0,",
+ " [byteLength]: 0,",
+ " [byteOffset]: 0,",
+ " [buffer]: ArrayBuffer {",
+ " byteLength: 0,",
+ " foo: true } ],",
+ " [Set Iterator] {",
+ " [ 1,",
+ " 2,",
+ " [length]: 2 ],",
+ " [Symbol(Symbol.toStringTag)]:",
+ " 'Set Iterator' } => <ref *1> [Map Iterator] {",
+ " Uint8Array(0) [",
+ " [BYTES_PER_ELEMENT]: 1,",
+ " [length]: 0,",
+ " [byteLength]: 0,",
+ " [byteOffset]: 0,",
+ " [buffer]: ArrayBuffer {",
+ " byteLength: 0,",
+ " foo: true } ],",
+ " [Circular *1],",
+ " [Symbol(Symbol.toStringTag)]:",
+ " 'Map Iterator' } }",
+ ].join("\n");
+
+ assert.strict.equal(out, expected);
+ }
+
+ {
+ // Test WeakMap && WeakSet
+ const obj = {};
+ const arr = [];
+ const weakMap = new WeakMap([
+ [obj, arr],
+ [arr, obj],
+ ]);
+ let out = util.inspect(weakMap, { showHidden: true });
+ // TODO: WeakMap internals
+ // let expect = 'WeakMap { [ [length]: 0 ] => {}, {} => [ [length]: 0 ] }';
+ let expect = "WeakMap { }";
+ assert.strictEqual(out, expect);
+
+ out = util.inspect(weakMap);
+ expect = "WeakMap { <items unknown> }";
+ assert.strictEqual(out, expect);
+
+ out = util.inspect(weakMap, { maxArrayLength: 0, showHidden: true });
+ // expect = 'WeakMap { ... 2 more items }';
+ expect = "WeakMap { }";
+ assert.strictEqual(out, expect);
+
+ weakMap.extra = true;
+ out = util.inspect(weakMap, { maxArrayLength: 1, showHidden: true });
+ // It is not possible to determine the output reliable.
+ // expect = 'WeakMap { [ [length]: 0 ] => {}, ... 1 more item, extra: true }';
+ // let expectAlt = 'WeakMap { {} => [ [length]: 0 ], ... 1 more item, extra: true }';
+ assert(
+ out === "WeakMap { extra: true }", //out === expect || out === expectAlt,
+ `Found: "${out}"\nrather than: "WeakMap { extra: true }"`,
+ ); //"${expect}"\nor: "${expectAlt}"`);
+
+ // Test WeakSet
+ arr.push(1);
+ const weakSet = new WeakSet([obj, arr]);
+ out = util.inspect(weakSet, { showHidden: true });
+ // TODO: WeakSet internals
+ // expect = 'WeakSet { [ 1, [length]: 1 ], {} }';
+ expect = "WeakSet { }";
+ assert.strictEqual(out, expect);
+
+ out = util.inspect(weakSet);
+ expect = "WeakSet { <items unknown> }";
+ assert.strictEqual(out, expect);
+
+ out = util.inspect(weakSet, { maxArrayLength: -2, showHidden: true });
+ //expect = 'WeakSet { ... 2 more items }';
+ expect = "WeakSet { }";
+ assert.strictEqual(out, expect);
+
+ weakSet.extra = true;
+ out = util.inspect(weakSet, { maxArrayLength: 1, showHidden: true });
+ // It is not possible to determine the output reliable.
+ // expect = 'WeakSet { {}, ... 1 more item, extra: true }';
+ // expectAlt = 'WeakSet { [ 1, [length]: 1 ], ... 1 more item, extra: true }';
+ assert(
+ out === "WeakSet { extra: true }", //out === expect || out === expectAlt,
+ `Found: "${out}"\nrather than: "WeakSet { extra: true }"`,
+ ); //"${expect}"\nor: "${expectAlt}"`);
+ // Keep references to the WeakMap entries, otherwise they could be GCed too early.
+ assert(obj && arr);
+ }
+
+ {
+ // Test argument objects.
+ const args = (function () {
+ return arguments;
+ })("a");
+ assert.strictEqual(util.inspect(args), "[Arguments] { '0': 'a' }");
+ }
+});
+
+test("util.inspect stack overflow handling", () => {
+ // Test that a long linked list can be inspected without throwing an error.
+ const list = {};
+ let head = list;
+ // A linked list of length 100k should be inspectable in some way, even though
+ // the real cutoff value is much lower than 100k.
+ for (let i = 0; i < 10000; i++) head = head.next = {};
+ assert.strictEqual(util.inspect(list), "{ next: { next: { next: [Object] } } }");
+ const longList = util.inspect(list, { depth: Infinity });
+ const match = longList.match(/next/g);
+ //? The current limit is set to a fixed 1000, as higher values cause
+ //? the creation of unmanageably large strings which can crash or hang.
+ //? For comparison, despite their test checking for up to 10000,
+ //? Node will generally cutoff with a stack overflow at around 900.
+ assert(match.length > 500 && match.length < 10000);
+ assert(longList.includes("[Object: Inspection interrupted prematurely. Maximum call stack size exceeded.]"));
+});
+
+test("no assertion failures 3", () => {
+ // Do not escape single quotes if no double quote or backtick is present.
+ assert.strictEqual(util.inspect("'"), '"\'"');
+ assert.strictEqual(util.inspect("\"'"), "`\"'`");
+ assert.strictEqual(util.inspect("\"'${a}"), "'\"\\'${a}'");
+
+ // Errors should visualize as much information as possible.
+ // If the name is not included in the stack, visualize it as well.
+ [
+ [class Foo extends TypeError {}, "test"],
+ [class Foo extends TypeError {}, undefined],
+ [class BarError extends Error {}, "test"],
+ [
+ class BazError extends Error {
+ get name() {
+ return "BazError";
+ }
+ },
+ undefined,
+ ],
+ ].forEach(([Class, message], i) => {
+ const foo = new Class(message);
+ const extra = Class.name.includes("Error") ? "" : ` [${foo.name}]`;
+ assert(
+ util.inspect(foo).startsWith(`${Class.name}${extra}${message ? `: ${message}` : "\n"}`),
+ util.inspect(foo) + "\n...did not start with: " + `${Class.name}${extra}${message ? `: ${message}` : "\n"}`,
+ );
+ Object.defineProperty(foo, Symbol.toStringTag, {
+ value: "WOW",
+ writable: true,
+ configurable: true,
+ });
+ const stack = foo.stack;
+ foo.stack = "This is a stack";
+ assert(
+ util.inspect(foo).startsWith("[This is a stack]"),
+ `Expected to start with: "[This is a stack]"\nFound: "${util.inspect(foo)}"`,
+ );
+ foo.stack = stack;
+ assert(
+ util.inspect(foo).startsWith(`${Class.name} [WOW]${extra}${message ? `: ${message}` : "\n"}`),
+ util.inspect(foo),
+ );
+ Object.setPrototypeOf(foo, null);
+ assert(
+ util.inspect(foo).startsWith(
+ // TODO: null prototypes
+ // `[${name}: null prototype] [WOW]${message ? `: ${message}` : '\n'}`
+ "[Object: null prototype] [WOW] {",
+ ),
+ util.inspect(foo),
+ );
+ foo.bar = true;
+ delete foo[Symbol.toStringTag];
+ let tmp = util.inspect(foo);
+ assert(
+ tmp.startsWith(
+ // TODO: null prototypes
+ // `[${name}: null prototype]${message ? `: ${message}` : '\n'}`),
+ "[Error: null prototype] {",
+ ) && tmp.includes("bar: true"),
+ tmp,
+ );
+ foo.stack = "This is a stack";
+ tmp = util.inspect(foo);
+ assert(
+ tmp.startsWith(
+ // TODO: null prototypes
+ // '[[Error: null prototype]: This is a stack] { bar: true }'
+ "[Error: null prototype] {",
+ ) && tmp.includes("bar: true"),
+ tmp,
+ );
+ foo.stack = stack.split("\n")[0];
+ tmp = util.inspect(foo);
+ assert(
+ tmp.startsWith(
+ // TODO: null prototypes
+ // `[[${name}: null prototype]${message ? `:\n ${message}` : ''}] { bar: true }`
+ "[Error: null prototype] {",
+ ) && tmp.includes("bar: true"),
+ tmp,
+ );
+ });
+
+ // Verify that classes are properly inspected.
+ [
+ // The whitespace is intentional.
+ [class {}, "[class (anonymous)]"],
+ [
+ class extends Error {
+ log() {}
+ },
+ "[class (anonymous) extends Error]",
+ ],
+ [
+ class A {
+ constructor(a) {
+ this.a = a;
+ }
+ log() {
+ return this.a;
+ }
+ },
+ "[class A]",
+ ],
+ [
+ class // Random { // comments /* */ are part of the toString() result
+ äß /**/
+ extends /*{*/ TypeError {},
+ "[class äß extends TypeError]",
+ ],
+ /* The whitespace and new line is intended! */
+ // Foobar !!!
+ [
+ class X extends /****/ Error {
+ // More comments
+ },
+ "[class X extends Error]",
+ ],
+ ].forEach(([clazz, string]) => {
+ const inspected = util.inspect(clazz);
+ assert.strictEqual(inspected, string);
+ Object.defineProperty(clazz, Symbol.toStringTag, {
+ value: "Woohoo",
+ });
+ const parts = inspected.slice(0, -1).split(" ");
+ const [, name, ...rest] = parts;
+ rest.unshift("[Woohoo]");
+ if (rest.length) {
+ rest[rest.length - 1] += "]";
+ }
+ assert.strictEqual(util.inspect(clazz), ["[class", name, ...rest].join(" "));
+ if (rest.length) {
+ rest[rest.length - 1] = rest[rest.length - 1].slice(0, -1);
+ rest.length = 1;
+ }
+ Object.setPrototypeOf(clazz, Map.prototype);
+ assert.strictEqual(util.inspect(clazz), ["[class", name, "[Map]", ...rest].join(" ") + "]");
+ Object.setPrototypeOf(clazz, null);
+ assert.strictEqual(util.inspect(clazz), ["[class", name, ...rest, "extends [null prototype]]"].join(" "));
+ Object.defineProperty(clazz, "name", { value: "Foo" });
+ const res = ["[class", "Foo", ...rest, "extends [null prototype]]"].join(" ");
+ assert.strictEqual(util.inspect(clazz), res);
+ clazz.foo = true;
+ assert.strictEqual(util.inspect(clazz), `${res} { foo: true }`);
+ });
+
+ // "class" properties should not be detected as "class".
+ {
+ let obj = { class() {} };
+ assert.strictEqual(util.inspect(obj), "{ class: [Function: class] }");
+ obj = { class: () => {} };
+ assert.strictEqual(util.inspect(obj), "{ class: [Function: class] }");
+ obj = { ["class Foo {}"]() {} };
+ assert.strictEqual(util.inspect(obj), "{ 'class Foo {}': [Function: class Foo {}] }");
+ function Foo() {}
+ Object.defineProperty(Foo, "toString", { value: () => "class Foo {}" });
+ assert.strictEqual(util.inspect(Foo), "[Function: Foo]");
+ function fn() {}
+ Object.defineProperty(fn, "name", { value: "class Foo {}" });
+ assert.strictEqual(util.inspect(fn), "[Function: class Foo {}]");
+ }
+
+ // Verify that throwing in valueOf and toString still produces nice results.
+ [
+ [new String(55), "[String: '55']"],
+ [new Boolean(true), "[Boolean: true]"],
+ [new Number(55), "[Number: 55]"],
+ [Object(BigInt(55)), "[BigInt: 55n]"],
+ [Object(Symbol("foo")), "[Symbol: Symbol(foo)]"],
+ [function () {}, "[Function (anonymous)]"],
+ [() => {}, "[Function (anonymous)]"],
+ [[1, 2], "[ 1, 2 ]"],
+ [[, , 5, , , ,], "[ <2 empty items>, 5, <3 empty items> ]"],
+ [{ a: 5 }, "{ a: 5 }"],
+ [new Set([1, 2]), "Set(2) { 1, 2 }"],
+ [new Map([[1, 2]]), "Map(1) { 1 => 2 }"],
+ [new Set([1, 2]).entries(), "[Set Entries] { [ 1, 1 ], [ 2, 2 ] }"],
+ [new Map([[1, 2]]).keys(), "[Map Iterator] { 1 }"],
+ [new Date(2000), "1970-01-01T00:00:02.000Z"],
+ [new Uint8Array(2), "Uint8Array(2) [ 0, 0 ]"],
+ [new Promise(resolve => setTimeout(resolve, 10)), "Promise { <pending> }"],
+ [new WeakSet(), "WeakSet { <items unknown> }"],
+ [new WeakMap(), "WeakMap { <items unknown> }"],
+ [/foobar/g, "/foobar/g"],
+ ].forEach(([value, expected]) => {
+ Object.defineProperty(value, "valueOf", {
+ get() {
+ throw new Error("valueOf");
+ },
+ });
+ Object.defineProperty(value, "toString", {
+ get() {
+ throw new Error("toString");
+ },
+ });
+ assert.strictEqual(util.inspect(value), expected);
+ value.foo = "bar";
+ assert.notStrictEqual(util.inspect(value), expected);
+ delete value.foo;
+ value[Symbol("foo")] = "yeah";
+ assert.notStrictEqual(util.inspect(value), expected);
+ });
+
+ // Verify that having no prototype still produces nice results.
+ [
+ [[1, 3, 4], "[Array(3): null prototype] [ 1, 3, 4 ]"],
+ [/foobar/, "[RegExp: null prototype] /foobar/"],
+ [new Set([1, 2]), "[Set(2): null prototype] { 1, 2 }"],
+ [new Map([[1, 2]]), "[Map(1): null prototype] { 1 => 2 }"],
+ [new Promise(resolve => setTimeout(resolve, 10)), "[Promise: null prototype] { <pending> }"],
+ [new WeakSet(), "[WeakSet: null prototype] { <items unknown> }"],
+ [new WeakMap(), "[WeakMap: null prototype] { <items unknown> }"],
+ [new Uint8Array(2), "[Uint8Array(2): null prototype] [ 0, 0 ]"],
+ [new Uint16Array(2), "[Uint16Array(2): null prototype] [ 0, 0 ]"],
+ [new Uint32Array(2), "[Uint32Array(2): null prototype] [ 0, 0 ]"],
+ [new Int8Array(2), "[Int8Array(2): null prototype] [ 0, 0 ]"],
+ [new Int16Array(2), "[Int16Array(2): null prototype] [ 0, 0 ]"],
+ [new Int32Array(2), "[Int32Array(2): null prototype] [ 0, 0 ]"],
+ [new Float32Array(2), "[Float32Array(2): null prototype] [ 0, 0 ]"],
+ [new Float64Array(2), "[Float64Array(2): null prototype] [ 0, 0 ]"],
+ [new BigInt64Array(2), "[BigInt64Array(2): null prototype] [ 0n, 0n ]"],
+ [new BigUint64Array(2), "[BigUint64Array(2): null prototype] [ 0n, 0n ]"],
+ [
+ new ArrayBuffer(4),
+ "[ArrayBuffer: null prototype] {\n [Uint8Contents]: <00 00 00 00>,\n byteLength: undefined\n}",
+ ],
+ [
+ new DataView(new ArrayBuffer(4)),
+ "[DataView: null prototype] {\n byteLength: undefined,\n byteOffset: undefined,\n buffer: undefined\n}",
+ ],
+ [
+ new SharedArrayBuffer(2),
+ "[SharedArrayBuffer: null prototype] {\n [Uint8Contents]: <00 00>,\n byteLength: undefined\n}",
+ ],
+ [new Date("Sun, 14 Feb 2010 11:48:40 GMT"), "[Date: null prototype] 2010-02-14T11:48:40.000Z"],
+ ].forEach(([value, expected]) => {
+ assert.strictEqual(util.inspect(Object.setPrototypeOf(value, null)), expected);
+ value.foo = "bar";
+ assert.notStrictEqual(util.inspect(value), expected);
+ delete value.foo;
+ value[Symbol("foo")] = "yeah";
+ assert.notStrictEqual(util.inspect(value), expected);
+ });
+
+ // Verify that subclasses with and without prototype produce nice results.
+ [
+ [RegExp, ["foobar", "g"], "/foobar/g"],
+ [WeakSet, [[{}]], "{ <items unknown> }"],
+ [WeakMap, [[[{}, {}]]], "{ <items unknown> }"],
+ [BigInt64Array, [10], "[\n 0n, 0n, 0n, 0n, 0n,\n 0n, 0n, 0n, 0n, 0n\n]"],
+ [Date, ["Sun, 14 Feb 2010 11:48:40 GMT"], "2010-02-14T11:48:40.000Z"],
+ [Date, ["invalid_date"], "Invalid Date"],
+ ].forEach(([base, input, rawExpected]) => {
+ class Foo extends base {}
+ const value = new Foo(...input);
+ const symbol = value[Symbol.toStringTag];
+ const size = base.name.includes("Array") ? `(${input[0]})` : "";
+ const expected = `Foo${size} ${symbol ? `[${symbol}] ` : ""}${rawExpected}`;
+ const expectedWithoutProto = `[${base.name}${size}: null prototype] ${rawExpected}`;
+ assert.strictEqual(util.inspect(value), expected);
+ value.foo = "bar";
+ assert.notStrictEqual(util.inspect(value), expected);
+ delete value.foo;
+ assert.strictEqual(util.inspect(Object.setPrototypeOf(value, null)), expectedWithoutProto);
+ value.foo = "bar";
+ let res = util.inspect(value);
+ assert.notStrictEqual(res, expectedWithoutProto);
+ assert.match(res, /foo: 'bar'/);
+ delete value.foo;
+ value[Symbol("foo")] = "yeah";
+ res = util.inspect(value);
+ assert.notStrictEqual(res, expectedWithoutProto);
+ assert.match(res, /\[Symbol\(foo\)]: 'yeah'/);
+ });
+
+ assert.strictEqual(inspect(1n), "1n");
+ assert.strictEqual(inspect(Object(-1n)), "[BigInt: -1n]");
+ assert.strictEqual(inspect(Object(13n)), "[BigInt: 13n]");
+ assert.strictEqual(inspect(new BigInt64Array([0n])), "BigInt64Array(1) [ 0n ]");
+ assert.strictEqual(inspect(new BigUint64Array([0n])), "BigUint64Array(1) [ 0n ]");
+
+ // Verify non-enumerable keys get escaped.
+ {
+ const obj = {};
+ Object.defineProperty(obj, "Non\nenumerable\tkey", { value: true });
+ assert.strictEqual(util.inspect(obj, { showHidden: true }), "{ [Non\\nenumerable\\tkey]: true }");
+ }
+
+ // Check for special colors.
+ {
+ const special = inspect.colors[inspect.styles.special];
+ const string = inspect.colors[inspect.styles.string];
+
+ assert.strictEqual(
+ inspect(new WeakSet(), { colors: true }),
+ `WeakSet { \u001b[${special[0]}m<items unknown>\u001b[${special[1]}m }`,
+ );
+ assert.strictEqual(
+ inspect(new WeakMap(), { colors: true }),
+ `WeakMap { \u001b[${special[0]}m<items unknown>\u001b[${special[1]}m }`,
+ );
+ assert.strictEqual(
+ inspect(new Promise(() => {}), { colors: true }),
+ `Promise { \u001b[${special[0]}m<pending>\u001b[${special[1]}m }`,
+ );
+
+ const rejection = Promise.reject("Oh no!");
+ assert.strictEqual(
+ inspect(rejection, { colors: true }),
+ `Promise { \u001b[${special[0]}m<rejected>\u001b[${special[1]}m ` +
+ `\u001b[${string[0]}m'Oh no!'\u001b[${string[1]}m }`,
+ );
+ rejection.catch(() => {});
+
+ // Verify that aliases do not show up as key while checking `inspect.colors`.
+ const colors = Object.keys(inspect.colors);
+ const aliases = Object.getOwnPropertyNames(inspect.colors).filter(c => !colors.includes(c));
+ assert(!colors.includes("grey"), colors);
+ assert(colors.includes("gray"), colors);
+ // Verify that all aliases are correctly mapped.
+ for (const alias of aliases) {
+ assert(Array.isArray(inspect.colors[alias]));
+ }
+ // Check consistent naming.
+ ["black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"].forEach((color, i) => {
+ assert.deepStrictEqual(inspect.colors[color], [30 + i, 39]);
+ assert.deepStrictEqual(inspect.colors[`${color}Bright`], [90 + i, 39]);
+ const bgColor = `bg${color[0].toUpperCase()}${color.slice(1)}`;
+ assert.deepStrictEqual(inspect.colors[bgColor], [40 + i, 49]);
+ assert.deepStrictEqual(inspect.colors[`${bgColor}Bright`], [100 + i, 49]);
+ });
+
+ // Unknown colors are handled gracefully:
+ const stringStyle = inspect.styles.string;
+ inspect.styles.string = "UNKNOWN";
+ assert.strictEqual(inspect("foobar", { colors: true }), "'foobar'");
+ inspect.styles.string = stringStyle;
+ }
+
+ assert.strictEqual(inspect([1, 3, 2], { sorted: true }), inspect([1, 3, 2]));
+ assert.strictEqual(inspect({ c: 3, a: 1, b: 2 }, { sorted: true }), "{ a: 1, b: 2, c: 3 }");
+ assert.strictEqual(
+ inspect(
+ { a200: 4, a100: 1, a102: 3, a101: 2 },
+ {
+ sorted(a, b) {
+ return b.localeCompare(a);
+ },
+ },
+ ),
+ "{ a200: 4, a102: 3, a101: 2, a100: 1 }",
+ );
+
+ // Non-indices array properties are sorted as well.
+ {
+ const arr = [3, 2, 1];
+ arr.b = 2;
+ arr.c = 3;
+ arr.a = 1;
+ arr[Symbol("b")] = true;
+ arr[Symbol("a")] = false;
+ assert.strictEqual(
+ inspect(arr, { sorted: true }),
+ "[ 3, 2, 1, [Symbol(a)]: false, [Symbol(b)]: true, a: 1, b: 2, c: 3 ]",
+ );
+ }
+
+ // Manipulate the prototype in weird ways.
+ {
+ let obj = { a: true };
+ let value = (function () {
+ return function () {};
+ })();
+ Object.setPrototypeOf(value, null);
+ Object.setPrototypeOf(obj, value);
+ assert.strictEqual(util.inspect(obj), "Object <[Function (null prototype) (anonymous)]> { a: true }");
+ assert.strictEqual(
+ util.inspect(obj, { colors: true }),
+ "Object <\u001b[36m[Function (null prototype) (anonymous)]\u001b[39m> " + "{ a: \u001b[33mtrue\u001b[39m }",
+ );
+
+ obj = { a: true };
+ value = [];
+ Object.setPrototypeOf(value, null);
+ Object.setPrototypeOf(obj, value);
+ assert.strictEqual(util.inspect(obj), "Object <[Array(0): null prototype] []> { a: true }");
+
+ function StorageObject() {}
+ StorageObject.prototype = { __proto__: null };
+ assert.strictEqual(
+ util.inspect(new StorageObject()),
+ // TODO: null prototypes
+ // 'StorageObject <[Object: null prototype] {}> {}'
+ "Object <[Object: null prototype] {}> {}",
+ );
+
+ obj = [1, 2, 3];
+ Object.setPrototypeOf(obj, Number.prototype);
+ assert.strictEqual(inspect(obj), "Number { '0': 1, '1': 2, '2': 3 }");
+
+ Object.setPrototypeOf(obj, { __proto__: null });
+ assert.strictEqual(inspect(obj), "Array <[Object: null prototype] {}> { '0': 1, '1': 2, '2': 3 }");
+
+ StorageObject.prototype = { __proto__: null };
+ Object.setPrototypeOf(StorageObject.prototype, { __proto__: null });
+ Object.setPrototypeOf(Object.getPrototypeOf(StorageObject.prototype), { __proto__: null });
+ assert.strictEqual(
+ util.inspect(new StorageObject()),
+ // TODO: null prototypes
+ // 'StorageObject <Object <Object <[Object: null prototype] {}>>> {}'
+ "Object <Object <Object <[Object: null prototype] {}>>> {}",
+ );
+ assert.strictEqual(
+ util.inspect(new StorageObject(), { depth: 1 }),
+ // TODO: null prototypes
+ // 'StorageObject <Object <Object <Complex prototype>>> {}'
+ "Object <Object <Object <Complex prototype>>> {}",
+ );
+ }
+
+ // Check that the fallback always works.
+ {
+ const obj = new Set([1, 2]);
+ const iterator = obj[Symbol.iterator];
+ Object.setPrototypeOf(obj, null);
+ Object.defineProperty(obj, Symbol.iterator, {
+ value: iterator,
+ configurable: true,
+ });
+ assert.strictEqual(util.inspect(obj), "[Set(2): null prototype] { 1, 2 }");
+ Object.defineProperty(obj, Symbol.iterator, {
+ value: true,
+ configurable: true,
+ });
+ Object.defineProperty(obj, "size", {
+ value: NaN,
+ configurable: true,
+ enumerable: true,
+ });
+ assert.strictEqual(util.inspect(obj), "[Set(2): null prototype] { 1, 2, size: NaN }");
+ }
+
+ // Check the getter option.
+ {
+ let foo = 1;
+ const get = {
+ get foo() {
+ return foo;
+ },
+ };
+ const getset = {
+ get foo() {
+ return foo;
+ },
+ set foo(val) {
+ foo = val;
+ },
+ get inc() {
+ return ++foo;
+ },
+ };
+ const thrower = {
+ get foo() {
+ throw new Error("Oops");
+ },
+ };
+ assert.strictEqual(
+ inspect(get, { getters: true, colors: true }),
+ "{ foo: \u001b[36m[Getter:\u001b[39m " + "\u001b[33m1\u001b[39m\u001b[36m]\u001b[39m }",
+ );
+ assert.strictEqual(inspect(thrower, { getters: true }), "{ foo: [Getter: <Inspection threw (Oops)>] }");
+ assert.strictEqual(inspect(getset, { getters: true }), "{ foo: [Getter/Setter: 1], inc: [Getter: 2] }");
+ assert.strictEqual(inspect(getset, { getters: "get" }), "{ foo: [Getter/Setter], inc: [Getter: 3] }");
+ assert.strictEqual(inspect(getset, { getters: "set" }), "{ foo: [Getter/Setter: 3], inc: [Getter] }");
+ getset.foo = new Set([[{ a: true }, 2, {}], "foobar", { x: 1 }]);
+ assert.strictEqual(
+ inspect(getset, { getters: true }),
+ "{\n foo: [Getter/Setter] Set(3) { [ [Object], 2, {} ], " + "'foobar', { x: 1 } },\n inc: [Getter: NaN]\n}",
+ );
+ }
+
+ // Check compact number mode.
+ {
+ let obj = {
+ a: {
+ b: {
+ x: 5,
+ c: {
+ x: "10000000000000000 00000000000000000 ".repeat(1e1),
+ d: 2,
+ e: 3,
+ },
+ },
+ },
+ b: [1, 2, [1, 2, { a: 1, b: 2, c: 3 }]],
+ c: ["foo", 4, 444444],
+ d: Array.from({ length: 101 }).map((e, i) => {
+ return i % 2 === 0 ? i * i : i;
+ }),
+ e: Array(6).fill("foobar"),
+ f: Array(9).fill("foobar"),
+ g: Array(21).fill("foobar baz"),
+ h: [100].concat(Array.from({ length: 9 }).map((e, n) => n)),
+ long: Array(9).fill("This text is too long for grouping!"),
+ };
+
+ let out = util.inspect(obj, { compact: 3, depth: 10, breakLength: 60 });
+ let expected = [
+ "{",
+ " a: {",
+ " b: {",
+ " x: 5,",
+ " c: {",
+ " x: '10000000000000000 00000000000000000 10000000000000000 " +
+ "00000000000000000 10000000000000000 00000000000000000 " +
+ "10000000000000000 00000000000000000 10000000000000000 " +
+ "00000000000000000 10000000000000000 00000000000000000 " +
+ "10000000000000000 00000000000000000 10000000000000000 " +
+ "00000000000000000 10000000000000000 00000000000000000 " +
+ "10000000000000000 00000000000000000 ',",
+ " d: 2,",
+ " e: 3",
+ " }",
+ " }",
+ " },",
+ " b: [ 1, 2, [ 1, 2, { a: 1, b: 2, c: 3 } ] ],",
+ " c: [ 'foo', 4, 444444 ],",
+ " d: [",
+ " 0, 1, 4, 3, 16, 5, 36, 7, 64,",
+ " 9, 100, 11, 144, 13, 196, 15, 256, 17,",
+ " 324, 19, 400, 21, 484, 23, 576, 25, 676,",
+ " 27, 784, 29, 900, 31, 1024, 33, 1156, 35,",
+ " 1296, 37, 1444, 39, 1600, 41, 1764, 43, 1936,",
+ " 45, 2116, 47, 2304, 49, 2500, 51, 2704, 53,",
+ " 2916, 55, 3136, 57, 3364, 59, 3600, 61, 3844,",
+ " 63, 4096, 65, 4356, 67, 4624, 69, 4900, 71,",
+ " 5184, 73, 5476, 75, 5776, 77, 6084, 79, 6400,",
+ " 81, 6724, 83, 7056, 85, 7396, 87, 7744, 89,",
+ " 8100, 91, 8464, 93, 8836, 95, 9216, 97, 9604,",
+ " 99,",
+ " ... 1 more item",
+ " ],",
+ " e: [",
+ " 'foobar',",
+ " 'foobar',",
+ " 'foobar',",
+ " 'foobar',",
+ " 'foobar',",
+ " 'foobar'",
+ " ],",
+ " f: [",
+ " 'foobar', 'foobar',",
+ " 'foobar', 'foobar',",
+ " 'foobar', 'foobar',",
+ " 'foobar', 'foobar',",
+ " 'foobar'",
+ " ],",
+ " g: [",
+ " 'foobar baz', 'foobar baz',",
+ " 'foobar baz', 'foobar baz',",
+ " 'foobar baz', 'foobar baz',",
+ " 'foobar baz', 'foobar baz',",
+ " 'foobar baz', 'foobar baz',",
+ " 'foobar baz', 'foobar baz',",
+ " 'foobar baz', 'foobar baz',",
+ " 'foobar baz', 'foobar baz',",
+ " 'foobar baz', 'foobar baz',",
+ " 'foobar baz', 'foobar baz',",
+ " 'foobar baz'",
+ " ],",
+ " h: [",
+ " 100, 0, 1, 2, 3,",
+ " 4, 5, 6, 7, 8",
+ " ],",
+ " long: [",
+ " 'This text is too long for grouping!',",
+ " 'This text is too long for grouping!',",
+ " 'This text is too long for grouping!',",
+ " 'This text is too long for grouping!',",
+ " 'This text is too long for grouping!',",
+ " 'This text is too long for grouping!',",
+ " 'This text is too long for grouping!',",
+ " 'This text is too long for grouping!',",
+ " 'This text is too long for grouping!'",
+ " ]",
+ "}",
+ ].join("\n");
+
+ assert.strictEqual(out, expected);
+
+ obj = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 123456789];
+
+ out = util.inspect(obj, { compact: 3 });
+
+ expected = [
+ "[",
+ " 1, 1, 1, 1,",
+ " 1, 1, 1, 1,",
+ " 1, 1, 1, 1,",
+ " 1, 1, 1, 1,",
+ " 1, 1, 1, 1,",
+ " 1, 1, 1, 1,",
+ " 1, 1, 123456789",
+ "]",
+ ].join("\n");
+
+ assert.strictEqual(out, expected);
+
+ // Unicode support. あ has a length of one and a width of two.
+ obj = ["123", "123", "123", "123", "あああ", "123", "123", "123", "123", "あああ"];
+
+ out = util.inspect(obj, { compact: 3 });
+
+ expected = [
+ "[",
+ " '123', '123',",
+ " '123', '123',",
+ " 'あああ', '123',",
+ " '123', '123',",
+ " '123', 'あああ'",
+ "]",
+ ].join("\n");
+
+ assert.strictEqual(out, expected);
+
+ // Array grouping should prevent lining up outer elements on a single line.
+ obj = [[[1, 2, 3, 4, 5, 6, 7, 8, 9]]];
+
+ out = util.inspect(obj, { compact: 3 });
+
+ expected = ["[", " [", " [", " 1, 2, 3, 4, 5,", " 6, 7, 8, 9", " ]", " ]", "]"].join("\n");
+
+ assert.strictEqual(out, expected);
+
+ // Verify that array grouping and line consolidation does not happen together.
+ obj = {
+ a: {
+ b: {
+ x: 5,
+ c: {
+ d: 2,
+ e: 3,
+ },
+ },
+ },
+ b: Array.from({ length: 9 }).map((e, n) => {
+ return n % 2 === 0 ? "foobar" : "baz";
+ }),
+ };
+
+ out = util.inspect(obj, { compact: 1, breakLength: Infinity, colors: true });
+
+ expected = [
+ "{",
+ " a: {",
+ " b: { x: \u001b[33m5\u001b[39m, c: \u001b[36m[Object]\u001b[39m }",
+ " },",
+ " b: [",
+ " \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,",
+ " \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,",
+ " \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,",
+ " \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,",
+ " \u001b[32m'foobar'\u001b[39m",
+ " ]",
+ "}",
+ ].join("\n");
+
+ assert.strictEqual(out, expected);
+
+ obj = Array.from({ length: 60 }).map((e, i) => i);
+ out = util.inspect(obj, { compact: 1, breakLength: Infinity, colors: true });
+
+ expected = [
+ "[",
+ " \u001b[33m0\u001b[39m, \u001b[33m1\u001b[39m, \u001b[33m2\u001b[39m, \u001b[33m3\u001b[39m,",
+ " \u001b[33m4\u001b[39m, \u001b[33m5\u001b[39m, \u001b[33m6\u001b[39m, \u001b[33m7\u001b[39m,",
+ " \u001b[33m8\u001b[39m, \u001b[33m9\u001b[39m, \u001b[33m10\u001b[39m, \u001b[33m11\u001b[39m,",
+ " \u001b[33m12\u001b[39m, \u001b[33m13\u001b[39m, \u001b[33m14\u001b[39m, \u001b[33m15\u001b[39m,",
+ " \u001b[33m16\u001b[39m, \u001b[33m17\u001b[39m, \u001b[33m18\u001b[39m, \u001b[33m19\u001b[39m,",
+ " \u001b[33m20\u001b[39m, \u001b[33m21\u001b[39m, \u001b[33m22\u001b[39m, \u001b[33m23\u001b[39m,",
+ " \u001b[33m24\u001b[39m, \u001b[33m25\u001b[39m, \u001b[33m26\u001b[39m, \u001b[33m27\u001b[39m,",
+ " \u001b[33m28\u001b[39m, \u001b[33m29\u001b[39m, \u001b[33m30\u001b[39m, \u001b[33m31\u001b[39m,",
+ " \u001b[33m32\u001b[39m, \u001b[33m33\u001b[39m, \u001b[33m34\u001b[39m, \u001b[33m35\u001b[39m,",
+ " \u001b[33m36\u001b[39m, \u001b[33m37\u001b[39m, \u001b[33m38\u001b[39m, \u001b[33m39\u001b[39m,",
+ " \u001b[33m40\u001b[39m, \u001b[33m41\u001b[39m, \u001b[33m42\u001b[39m, \u001b[33m43\u001b[39m,",
+ " \u001b[33m44\u001b[39m, \u001b[33m45\u001b[39m, \u001b[33m46\u001b[39m, \u001b[33m47\u001b[39m,",
+ " \u001b[33m48\u001b[39m, \u001b[33m49\u001b[39m, \u001b[33m50\u001b[39m, \u001b[33m51\u001b[39m,",
+ " \u001b[33m52\u001b[39m, \u001b[33m53\u001b[39m, \u001b[33m54\u001b[39m, \u001b[33m55\u001b[39m,",
+ " \u001b[33m56\u001b[39m, \u001b[33m57\u001b[39m, \u001b[33m58\u001b[39m, \u001b[33m59\u001b[39m",
+ "]",
+ ].join("\n");
+
+ assert.strictEqual(out, expected);
+
+ out = util.inspect([1, 2, 3, 4], { compact: 1, colors: true });
+ expected = "[ \u001b[33m1\u001b[39m, \u001b[33m2\u001b[39m, " + "\u001b[33m3\u001b[39m, \u001b[33m4\u001b[39m ]";
+
+ assert.strictEqual(out, expected);
+
+ obj = [
+ "Object",
+ "Function",
+ "Array",
+ "Number",
+ "parseFloat",
+ "parseInt",
+ "Infinity",
+ "NaN",
+ "undefined",
+ "Boolean",
+ "String",
+ "Symbol",
+ "Date",
+ "Promise",
+ "RegExp",
+ "Error",
+ "EvalError",
+ "RangeError",
+ "ReferenceError",
+ "SyntaxError",
+ "TypeError",
+ "URIError",
+ "JSON",
+ "Math",
+ "console",
+ "Intl",
+ "ArrayBuffer",
+ "Uint8Array",
+ "Int8Array",
+ "Uint16Array",
+ "Int16Array",
+ "Uint32Array",
+ "Int32Array",
+ "Float32Array",
+ "Float64Array",
+ "Uint8ClampedArray",
+ "BigUint64Array",
+ "BigInt64Array",
+ "DataView",
+ "Map",
+ "BigInt",
+ "Set",
+ "WeakMap",
+ "WeakSet",
+ "Proxy",
+ "Reflect",
+ "decodeURI",
+ "decodeURIComponent",
+ "encodeURI",
+ "encodeURIComponent",
+ "escape",
+ "unescape",
+ "eval",
+ "isFinite",
+ "isNaN",
+ "SharedArrayBuffer",
+ "Atomics",
+ "globalThis",
+ "WebAssembly",
+ "global",
+ "process",
+ "Buffer",
+ "URL",
+ "URLSearchParams",
+ "TextEncoder",
+ "TextDecoder",
+ "clearInterval",
+ "clearTimeout",
+ "setInterval",
+ "setTimeout",
+ "queueMicrotask",
+ "clearImmediate",
+ "setImmediate",
+ "module",
+ "require",
+ "assert",
+ "async_hooks",
+ "buffer",
+ "child_process",
+ "cluster",
+ "crypto",
+ "dgram",
+ "dns",
+ "domain",
+ "events",
+ "fs",
+ "http",
+ "http2",
+ "https",
+ "inspector",
+ "net",
+ "os",
+ "path",
+ "perf_hooks",
+ "punycode",
+ "querystring",
+ "readline",
+ "repl",
+ "stream",
+ "string_decoder",
+ "tls",
+ "trace_events",
+ "tty",
+ "url",
+ "v8",
+ "vm",
+ "worker_threads",
+ "zlib",
+ "_",
+ "_error",
+ "util",
+ ];
+
+ out = util.inspect(obj, { compact: 3, breakLength: 80, maxArrayLength: 250 });
+ expected = [
+ "[",
+ " 'Object', 'Function', 'Array',",
+ " 'Number', 'parseFloat', 'parseInt',",
+ " 'Infinity', 'NaN', 'undefined',",
+ " 'Boolean', 'String', 'Symbol',",
+ " 'Date', 'Promise', 'RegExp',",
+ " 'Error', 'EvalError', 'RangeError',",
+ " 'ReferenceError', 'SyntaxError', 'TypeError',",
+ " 'URIError', 'JSON', 'Math',",
+ " 'console', 'Intl', 'ArrayBuffer',",
+ " 'Uint8Array', 'Int8Array', 'Uint16Array',",
+ " 'Int16Array', 'Uint32Array', 'Int32Array',",
+ " 'Float32Array', 'Float64Array', 'Uint8ClampedArray',",
+ " 'BigUint64Array', 'BigInt64Array', 'DataView',",
+ " 'Map', 'BigInt', 'Set',",
+ " 'WeakMap', 'WeakSet', 'Proxy',",
+ " 'Reflect', 'decodeURI', 'decodeURIComponent',",
+ " 'encodeURI', 'encodeURIComponent', 'escape',",
+ " 'unescape', 'eval', 'isFinite',",
+ " 'isNaN', 'SharedArrayBuffer', 'Atomics',",
+ " 'globalThis', 'WebAssembly', 'global',",
+ " 'process', 'Buffer', 'URL',",
+ " 'URLSearchParams', 'TextEncoder', 'TextDecoder',",
+ " 'clearInterval', 'clearTimeout', 'setInterval',",
+ " 'setTimeout', 'queueMicrotask', 'clearImmediate',",
+ " 'setImmediate', 'module', 'require',",
+ " 'assert', 'async_hooks', 'buffer',",
+ " 'child_process', 'cluster', 'crypto',",
+ " 'dgram', 'dns', 'domain',",
+ " 'events', 'fs', 'http',",
+ " 'http2', 'https', 'inspector',",
+ " 'net', 'os', 'path',",
+ " 'perf_hooks', 'punycode', 'querystring',",
+ " 'readline', 'repl', 'stream',",
+ " 'string_decoder', 'tls', 'trace_events',",
+ " 'tty', 'url', 'v8',",
+ " 'vm', 'worker_threads', 'zlib',",
+ " '_', '_error', 'util'",
+ "]",
+ ].join("\n");
+
+ assert.strictEqual(out, expected);
+ }
+
+ {
+ // TODO: don't care if invalid node internals get highlighted wrong
+ // See: loaders.js if you want to fix
+
+ const originalCWD = process.cwd();
+
+ process.cwd = () =>
+ process.platform === "win32"
+ ? "C:\\workspace\\node-test-binary-windows js-suites-%percent-encoded\\node"
+ : "/home/user directory/repository%encoded/node";
+
+ // Use a fake stack to verify the expected colored outcome.
+ const stack = [
+ "Error: CWD is grayed out, even cwd that are percent encoded!",
+ " at A.<anonymous> (/test/node_modules/foo/node_modules/bar/baz.js:2:7)",
+ " at Module._compile (node:internal/modules/cjs/loader:827:30)",
+ " at Fancy (node:vm:697:32)",
+ // This file is not an actual Node.js core file.
+ // ' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)',
+ " at Function.Module._load (node:internal/modules/cjs/loader:621:3)",
+ // This file is not an actual Node.js core file.
+ // ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
+ " at require (node:internal/modules/helpers:14:16)",
+ " at Array.forEach (<anonymous>)",
+ ` at ${process.cwd()}/test/parallel/test-util-inspect.js:2760:12`,
+ ` at Object.<anonymous> (${process.cwd()}/node_modules/hyper_module/folder/file.js:2753:10)`,
+ " at /test/test-util-inspect.js:2239:9",
+ " at getActual (node:assert:592:5)",
+ ];
+ const err = new Error("CWD is grayed out, even cwd that are percent encoded!");
+ err.stack = stack.join("\n");
+ if (process.platform === "win32") {
+ err.stack = stack.map(frame => (frame.includes("node:") ? frame : frame.replace(/\//g, "\\"))).join("\n");
+ }
+ const escapedCWD = util.inspect(process.cwd()).slice(1, -1);
+ util
+ .inspect(err, { colors: true })
+ .split("\n")
+ .forEach((line, i) => {
+ line = line.replace(/ \{$/, "");
+ if (i >= stack.length) return; //! workaround to ignore the extra error props at the end
+ let expected = stack[i]
+ .replace(/node_modules\/([^/]+)/gi, (_, m) => {
+ return `node_modules/\u001b[4m${m}\u001b[24m`;
+ })
+ .replace(new RegExp(`(\\(?${escapedCWD}(\\\\|/))`, "gi"), (_, m) => {
+ return `\x1B[90m${m}\x1B[39m`;
+ });
+ if (expected.includes(process.cwd()) && expected.endsWith(")")) {
+ expected = `${expected.slice(0, -1)}\x1B[90m)\x1B[39m`;
+ }
+ if (line.includes("node:")) {
+ if (!line.includes("foo") && !line.includes("aaa")) {
+ expected = `\u001b[90m${expected}\u001b[39m`;
+ }
+ } else if (process.platform === "win32") {
+ expected = expected.replace(/\//g, "\\");
+ }
+ assert.strictEqual(line, expected);
+ });
+
+ // Check ESM
+ //const encodedCwd = url.pathToFileURL(process.cwd());
+ const sl = process.platform === "win32" ? "\\" : "/";
+
+ // Use a fake stack to verify the expected colored outcome.
+ //? Something goes wrong with these file URLs but Bun doesn't use those in errors anyway so it's fine (for now at least)
+ err.stack =
+ "Error: ESM and CJS mixed are both grayed out!\n" +
+ //?` at ${encodedCwd}/test/parallel/test-esm.mjs:2760:12\n` +
+ //?` at Object.<anonymous> (${encodedCwd}/node_modules/esm_module/folder/file.js:2753:10)\n` +
+ ` at ${process.cwd()}${sl}test${sl}parallel${sl}test-cjs.js:2760:12\n` +
+ ` at Object.<anonymous> (${process.cwd()}${sl}node_modules${sl}cjs_module${sl}folder${sl}file.js:2753:10)`;
+
+ let actual = util.inspect(err, { colors: true });
+ let expected =
+ "Error: ESM and CJS mixed are both grayed out!\n" +
+ //?` at \x1B[90m${encodedCwd}/\x1B[39mtest/parallel/test-esm.mjs:2760:12\n` +
+ //?` at Object.<anonymous> \x1B[90m(${encodedCwd}/\x1B[39mnode_modules/\x1B[4mesm_module\x1B[24m/folder/file.js:2753:10\x1B[90m)\x1B[39m\n` +
+ ` at \x1B[90m${process.cwd()}${sl}\x1B[39mtest${sl}parallel${sl}test-cjs.js:2760:12\n` +
+ ` at Object.<anonymous> \x1B[90m(${process.cwd()}${sl}\x1B[39mnode_modules${sl}\x1B[4mcjs_module\x1B[24m${sl}folder${sl}file.js:2753:10\x1B[90m)\x1B[39m`;
+
+ assert.strictEqual(actual.split(" {\n")[0], expected);
+
+ // ESM without need for encoding
+ process.cwd = () =>
+ process.platform === "win32"
+ ? "C:\\workspace\\node-test-binary-windows-js-suites\\node"
+ : "/home/user/repository/node";
+ let expectedCwd = process.cwd();
+ if (process.platform === "win32") {
+ expectedCwd = `/${expectedCwd.replace(/\\/g, "/")}`;
+ }
+ // Use a fake stack to verify the expected colored outcome.
+ err.stack = "Error: ESM without need for encoding!\n" + ` at file://${expectedCwd}/file.js:15:15`;
+
+ actual = util.inspect(err, { colors: true });
+ expected = "Error: ESM without need for encoding!\n" + ` at \x1B[90mfile://${expectedCwd}/\x1B[39mfile.js:15:15`;
+ assert.strictEqual(actual.split(" {\n")[0], expected);
+
+ process.cwd = originalCWD;
+ }
+
+ // This starts to work in node 15
+ {
+ // Cross platform checks.
+ const err = new Error("foo");
+ util
+ .inspect(err, { colors: true })
+ .split("\n")
+ .forEach((line, i) => {
+ assert(i < 5 || line.startsWith("\u001b[90m"), i + " " + line);
+ });
+ }
+
+ {
+ // Tracing class respects inspect depth.
+ try {
+ const trace = require("trace_events").createTracing({ categories: ["fo"] });
+ const actualDepth0 = util.inspect({ trace }, { depth: 0 });
+ assert.strictEqual(actualDepth0, "{ trace: [Tracing] }");
+ //! bun's tracing_events implementation is buggy/incomplete (?)
+ //const actualDepth1 = util.inspect({ trace }, { depth: 1 });
+ //assert.strictEqual(
+ // actualDepth1,
+ // "{ trace: Tracing { enabled: false, categories: 'fo' } }"
+ //);
+ } catch (err) {
+ if (err.code !== "ERR_TRACE_EVENTS_UNAVAILABLE") throw err;
+ }
+ }
+
+ // Inspect prototype properties.
+ {
+ function _defineProperty(obj, key, value) {
+ if (key in obj) {
+ Object.defineProperty(obj, key, {
+ value: value,
+ enumerable: true,
+ configurable: true,
+ writable: true,
+ });
+ } else {
+ obj[key] = value;
+ }
+ return obj;
+ }
+
+ class Foo extends Map {
+ constructor(...args) {
+ super(...args);
+ _defineProperty(this, "prop", false);
+ _defineProperty(this, "prop2", true);
+ }
+ get abc() {
+ return true;
+ }
+ get def() {
+ return false;
+ }
+ set def(v) {}
+ get xyz() {
+ return "Should be ignored";
+ }
+ func(a) {}
+ [util.inspect.custom]() {
+ return this;
+ }
+ }
+
+ class Bar extends Foo {
+ constructor(...args) {
+ super(...args);
+ _defineProperty(this, "prop", true);
+ _defineProperty(this, "abc", true);
+ }
+
+ get xyz() {
+ return "YES!";
+ }
+ [util.inspect.custom]() {
+ return this;
+ }
+ }
+
+ const bar = new Bar();
+
+ assert.strictEqual(inspect(bar), "Bar(0) [Map] { prop: true, prop2: true, abc: true }");
+ assert.strictEqual(
+ inspect(bar, { showHidden: true, getters: true, colors: false }),
+ "Bar(0) [Map] {\n" +
+ " prop: true,\n" +
+ " prop2: true,\n" +
+ " abc: true,\n" +
+ " [xyz]: [Getter: 'YES!'],\n" +
+ " [def]: [Getter/Setter: false]\n" +
+ "}",
+ );
+ assert.strictEqual(
+ inspect(bar, { showHidden: true, getters: false, colors: true }),
+ "Bar(0) [Map] {\n" +
+ " prop: \x1B[33mtrue\x1B[39m,\n" +
+ " prop2: \x1B[33mtrue\x1B[39m,\n" +
+ " abc: \x1B[33mtrue\x1B[39m,\n" +
+ " \x1B[2m[xyz]: \x1B[36m[Getter]\x1B[39m\x1B[22m,\n" +
+ " \x1B[2m[def]: \x1B[36m[Getter/Setter]\x1B[39m\x1B[22m\n" +
+ "}",
+ );
+
+ const obj = { __proto__: { abc: true, def: 5, toString() {} } };
+ assert.strictEqual(
+ inspect(obj, { showHidden: true, colors: true }),
+ "{ \x1B[2mabc: \x1B[33mtrue\x1B[39m\x1B[22m, " + "\x1B[2mdef: \x1B[33m5\x1B[39m\x1B[22m }",
+ );
+
+ assert.strictEqual(
+ inspect(Object.getPrototypeOf(bar), { showHidden: true, getters: true }),
+ "<ref *1> Foo [Map] {\n" +
+ " [constructor]: [class Bar extends Foo] {\n" +
+ ` [prototype]: [Circular *1],\n [name]: 'Bar',\n` +
+ " [length]: 0,\n" +
+ " [Symbol(Symbol.species)]: [Getter: <Inspection threw " +
+ "(Symbol.prototype.toString requires that |this| be a symbol or a symbol object)>]\n" +
+ " },\n" +
+ " [xyz]: [Getter: 'YES!'],\n" +
+ " [Symbol(nodejs.util.inspect.custom)]: " +
+ "[Function: [nodejs.util.inspect.custom]] {\n" +
+ " [length]: 0,\n" +
+ " [name]: '[nodejs.util.inspect.custom]'\n" +
+ " },\n" +
+ " [abc]: [Getter: true],\n" +
+ " [def]: [Getter/Setter: false]\n" +
+ " }",
+ );
+
+ assert.strictEqual(inspect(Object.getPrototypeOf(bar)), "Foo [Map] {}");
+
+ assert.strictEqual(inspect(Object.getPrototypeOf(new Foo())), "Map {}");
+ }
+
+ // Check that prototypes with a null prototype are inspectable.
+ // Regression test for https://github.com/nodejs/node/issues/35730
+ {
+ function Func() {}
+ Func.prototype = null;
+ const object = {};
+ object.constructor = Func;
+
+ assert.strictEqual(util.inspect(object), "{ constructor: [Function: Func] }");
+ }
+
+ // Test changing util.inspect.colors colors and aliases.
+ {
+ const colors = util.inspect.colors;
+
+ const originalValue = colors.gray;
+
+ // "grey" is reference-equal alias of "gray".
+ assert.strictEqual(colors.grey, colors.gray);
+
+ // Assigninging one should assign the other. This tests that the alias setter
+ // function keeps things reference-equal.
+ colors.gray = [0, 0];
+ assert.deepStrictEqual(colors.gray, [0, 0]);
+ assert.strictEqual(colors.grey, colors.gray);
+
+ colors.grey = [1, 1];
+ assert.deepStrictEqual(colors.grey, [1, 1]);
+ assert.strictEqual(colors.grey, colors.gray);
+
+ // Restore original value to avoid side effects in other tests.
+ colors.gray = originalValue;
+ assert.deepStrictEqual(colors.gray, originalValue);
+ assert.strictEqual(colors.grey, colors.gray);
+ }
+
+ // https://github.com/nodejs/node/issues/31889
+ //{ //! how to get the undetectable object in JSC?
+ // const undetectable = vm.runInThisContext('%GetUndetectable()');
+ // assert.strictEqual(inspect(undetectable), '{}');
+ //}
+
+ // Truncate output for Primitives with 1 character left
+ {
+ assert.strictEqual(util.inspect("bl", { maxStringLength: 1 }), "'b'... 1 more character");
+ }
+
+ {
+ const x = "a".repeat(1e6);
+ assert(util.inspect(x).endsWith("... 990000 more characters"));
+ assert.strictEqual(util.inspect(x, { maxStringLength: 4 }), "'aaaa'... 999996 more characters");
+ assert.match(util.inspect(x, { maxStringLength: null }), /a'$/);
+ }
+
+ {
+ // Verify that util.inspect() invokes custom inspect functions on objects
+ // from other vm.Contexts but does not pass data from its own Context to that
+ // function.
+ const target = vm.runInNewContext(
+ /*js*/ `
+ ({
+ [Symbol.for('nodejs.util.inspect.custom')](depth, ctx) {
+ this.depth = depth;
+ this.ctx = ctx;
+ try {
+ this.stylized = ctx.stylize('🐈');
+ } catch (e) {
+ this.stylizeException = e;
+ }
+ return this.stylized;
+ }
+ })
+ `,
+ { __proto__: null },
+ );
+ assert.strictEqual(target.ctx, undefined);
+
+ {
+ // Subtest 1: Just try to inspect the object with default options.
+ assert.strictEqual(util.inspect(target), "🐈");
+ assert.strictEqual(typeof target.ctx, "object");
+ const objectGraph = fullObjectGraph(target);
+ assert(!objectGraph.has(Object));
+ assert(!objectGraph.has(Function));
+ }
+
+ if (false) {
+ //! SKIP TEST: non-standard api
+ // Subtest 2: Use a stylize function that returns a non-primitive.
+ const output = util.inspect(target, {
+ stylize: mustCall(str => {
+ return {};
+ }),
+ });
+ assert.strictEqual(output, "[object Object]");
+ assert.strictEqual(typeof target.ctx, "object");
+ const objectGraph = fullObjectGraph(target);
+ assert(!objectGraph.has(Object));
+ assert(!objectGraph.has(Function));
+ }
+
+ if (false) {
+ //! SKIP TEST: non-standard api
+ // Subtest 3: Use a stylize function that throws an exception.
+ const output = util.inspect(target, {
+ stylize: mustCall(str => {
+ throw new Error("oops");
+ }),
+ });
+ assert.strictEqual(output, "🐈");
+ assert.strictEqual(typeof target.ctx, "object");
+ const objectGraph = fullObjectGraph(target);
+ assert(!objectGraph.has(Object));
+ assert(!objectGraph.has(Function));
+ }
+
+ function fullObjectGraph(value) {
+ const graph = new Set([value]);
+
+ for (const entry of graph) {
+ if ((typeof entry !== "object" && typeof entry !== "function") || entry === null) {
+ continue;
+ }
+
+ graph.add(Object.getPrototypeOf(entry));
+ const descriptors = Object.values(Object.getOwnPropertyDescriptors(entry));
+ for (const descriptor of descriptors) {
+ graph.add(descriptor.value);
+ graph.add(descriptor.set);
+ graph.add(descriptor.get);
+ }
+ }
+
+ return graph;
+ }
+
+ // Consistency check.
+ assert(fullObjectGraph(global).has(Function.prototype));
+ }
+
+ {
+ // Confirm that own constructor value displays correctly.
+
+ function Fhqwhgads() {}
+
+ const sterrance = new Fhqwhgads();
+ sterrance.constructor = Fhqwhgads;
+
+ assert.strictEqual(
+ util.inspect(sterrance, { showHidden: true }),
+ "Fhqwhgads {\n" +
+ " constructor: <ref *1> [Function: Fhqwhgads] {\n" +
+ " [length]: 0,\n" +
+ " [prototype]: { [constructor]: [Circular *1] },\n" +
+ " [name]: 'Fhqwhgads'\n" +
+ " }\n" +
+ "}",
+ );
+ }
+
+ {
+ // Confirm null prototype of generator prototype displays as expected.
+
+ function getProtoOfProto() {
+ return Object.getPrototypeOf(Object.getPrototypeOf(function* () {}));
+ }
+
+ function* generator() {}
+
+ const generatorPrototype = Object.getPrototypeOf(generator);
+ const originalProtoOfProto = Object.getPrototypeOf(generatorPrototype);
+ assert.strictEqual(getProtoOfProto(), originalProtoOfProto);
+ Object.setPrototypeOf(generatorPrototype, null);
+ assert.notStrictEqual(getProtoOfProto, originalProtoOfProto);
+
+ // This is the actual test. The other assertions in this block are about
+ // making sure the test is set up correctly and isn't polluting other tests.
+ assert.strictEqual(
+ util.inspect(generator, { showHidden: true }),
+ "[GeneratorFunction: generator] {\n" +
+ " [length]: 0,\n" +
+ " [name]: 'generator',\n" +
+ " [prototype]: Object [Generator] { [Symbol(Symbol.toStringTag)]: 'Generator' },\n" +
+ " [Symbol(Symbol.toStringTag)]: 'GeneratorFunction'\n" +
+ "}",
+ );
+
+ // Reset so we don't pollute other tests
+ Object.setPrototypeOf(generatorPrototype, originalProtoOfProto);
+ assert.strictEqual(getProtoOfProto(), originalProtoOfProto);
+ }
+
+ {
+ // Test for when breakLength results in a single column.
+ const obj = Array(9).fill("fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf");
+ assert.strictEqual(
+ util.inspect(obj, { breakLength: 256 }),
+ "[\n" +
+ " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
+ " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
+ " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
+ " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
+ " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
+ " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
+ " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
+ " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
+ " 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf'\n" +
+ "]",
+ );
+ }
+
+ {
+ assert.strictEqual(util.inspect({ ["__proto__"]: { a: 1 } }), "{ ['__proto__']: { a: 1 } }");
+ }
+
+ {
+ const { numericSeparator } = util.inspect.defaultOptions;
+ util.inspect.defaultOptions.numericSeparator = true;
+
+ assert.strictEqual(util.inspect(1234567891234567891234), "1.234567891234568e+21");
+ assert.strictEqual(util.inspect(123456789.12345678), "123_456_789.123_456_78");
+
+ assert.strictEqual(util.inspect(10_000_000), "10_000_000");
+ assert.strictEqual(util.inspect(1_000_000), "1_000_000");
+ assert.strictEqual(util.inspect(100_000), "100_000");
+ assert.strictEqual(util.inspect(99_999.9), "99_999.9");
+ assert.strictEqual(util.inspect(9_999), "9_999");
+ assert.strictEqual(util.inspect(999), "999");
+ assert.strictEqual(util.inspect(NaN), "NaN");
+ assert.strictEqual(util.inspect(Infinity), "Infinity");
+ assert.strictEqual(util.inspect(-Infinity), "-Infinity");
+
+ assert.strictEqual(util.inspect(new Float64Array([100_000_000])), "Float64Array(1) [ 100_000_000 ]");
+ assert.strictEqual(util.inspect(new BigInt64Array([9_100_000_100n])), "BigInt64Array(1) [ 9_100_000_100n ]");
+
+ assert.strictEqual(util.inspect(123456789), "123_456_789");
+ assert.strictEqual(util.inspect(123456789n), "123_456_789n");
+
+ util.inspect.defaultOptions.numericSeparator = numericSeparator;
+
+ assert.strictEqual(util.inspect(123456789.12345678, { numericSeparator: true }), "123_456_789.123_456_78");
+
+ assert.strictEqual(util.inspect(-123456789.12345678, { numericSeparator: true }), "-123_456_789.123_456_78");
+ }
+
+ // Regression test for https://github.com/nodejs/node/issues/41244
+ {
+ assert.strictEqual(
+ util.inspect({
+ get [Symbol.iterator]() {
+ throw new Error();
+ },
+ }),
+ "{ [Symbol(Symbol.iterator)]: [Getter] }",
+ );
+ }
+});
+
+// Utility functions
+function runCallChecks(exitCode) {
+ if (exitCode !== 0) return;
+
+ const failed = mustCallChecks.filter(function (context) {
+ if ("minimum" in context) {
+ context.messageSegment = `at least ${context.minimum}`;
+ return context.actual < context.minimum;
+ }
+ context.messageSegment = `exactly ${context.exact}`;
+ return context.actual !== context.exact;
+ });
+
+ failed.forEach(function (context) {
+ console.log(
+ "Mismatched %s function calls. Expected %s, actual %d.",
+ context.name,
+ context.messageSegment,
+ context.actual,
+ );
+ console.log(context.stack.split("\n").slice(2).join("\n"));
+ });
+
+ if (failed.length) process.exit(1);
+}
+
+function mustCall(fn, criteria = 1) {
+ if (process._exiting) throw new Error("Cannot use mustCall() in process exit handler");
+ if (typeof fn === "number") {
+ criteria = fn;
+ fn = noop;
+ } else if (fn === undefined) fn = noop;
+
+ const field = "exact";
+ if (typeof criteria !== "number") throw new TypeError(`Invalid ${field} value: ${criteria}`);
+
+ const context = {
+ [field]: criteria,
+ actual: 0,
+ stack: util.inspect(new Error()),
+ name: fn.name || "<anonymous>",
+ };
+
+ // Add the exit listener only once to avoid listener leak warnings
+ if (mustCallChecks.length === 0) process.on("exit", runCallChecks);
+ mustCallChecks.push(context);
+
+ const _return = function () {
+ context.actual++;
+ return fn.apply(this, arguments);
+ };
+ // Function instances have own properties that may be relevant.
+ // Let's replicate those properties to the returned function.
+ // Refs: https://tc39.es/ecma262/#sec-function-instances
+ Object.defineProperties(_return, {
+ name: {
+ value: fn.name,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+ },
+ length: {
+ value: fn.length,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+ },
+ });
+ return _return;
+}
diff --git a/test/js/node/v8/capture-stack-trace.test.js b/test/js/node/v8/capture-stack-trace.test.js
index cb2624681..5e9492955 100644
--- a/test/js/node/v8/capture-stack-trace.test.js
+++ b/test/js/node/v8/capture-stack-trace.test.js
@@ -17,6 +17,15 @@ test("Regular .stack", () => {
expect(err.stack).toMatch(/at new Foo/);
});
+test("throw inside Error.prepareStackTrace doesnt crash", () => {
+ Error.prepareStackTrace = function (err, stack) {
+ Error.prepareStackTrace = null;
+ throw new Error("wat");
+ };
+
+ expect(() => new Error().stack).toThrow("wat");
+});
+
test("capture stack trace", () => {
function f1() {
f2();
@@ -460,14 +469,66 @@ test("CallFrame.p.toString", () => {
expect(e.stack[0].toString().includes("<anonymous>")).toBe(true);
});
-test.todo("err.stack should invoke prepareStackTrace", () => {
- // This is V8's behavior.
- let prevPrepareStackTrace = Error.prepareStackTrace;
- let wasCalled = false;
- Error.prepareStackTrace = (e, s) => {
- wasCalled = true;
- };
- const e = new Error();
- e.stack;
- expect(wasCalled).toBe(true);
+test("err.stack should invoke prepareStackTrace", () => {
+ var lineNumber = -1;
+ var functionName = "";
+ var parentLineNumber = -1;
+ function functionWithAName() {
+ // This is V8's behavior.
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+
+ Error.prepareStackTrace = (e, s) => {
+ lineNumber = s[0].getLineNumber();
+ functionName = s[0].getFunctionName();
+ parentLineNumber = s[1].getLineNumber();
+ expect(s[0].getFileName().includes("capture-stack-trace.test.js")).toBe(true);
+ expect(s[1].getFileName().includes("capture-stack-trace.test.js")).toBe(true);
+ };
+ const e = new Error();
+ e.stack;
+ Error.prepareStackTrace = prevPrepareStackTrace;
+ }
+
+ functionWithAName();
+
+ expect(functionName).toBe("functionWithAName");
+ expect(lineNumber).toBe(488);
+ // TODO: this is wrong
+ expect(parentLineNumber).toBe(497);
+});
+
+test("Error.prepareStackTrace inside a node:vm works", () => {
+ const { runInNewContext } = require("node:vm");
+ Error.prepareStackTrace = null;
+ const result = runInNewContext(
+ `
+ Error.prepareStackTrace = (err, stack) => {
+ if (typeof err.stack !== "string") {
+ throw new Error("err.stack is not a string");
+ }
+
+ return "custom stack trace";
+ };
+
+ const err = new Error();
+ err.stack;
+ `,
+ );
+ expect(result).toBe("custom stack trace");
+ expect(Error.prepareStackTrace).toBeNull();
+});
+
+test("Error.captureStackTrace inside error constructor works", () => {
+ class ExtendedError extends Error {
+ constructor() {
+ super();
+ Error.captureStackTrace(this, ExtendedError);
+ }
+ }
+
+ class AnotherError extends ExtendedError {}
+
+ expect(() => {
+ throw new AnotherError();
+ }).toThrow();
});
diff --git a/test/js/node/v8/v8-date-parser.test.js b/test/js/node/v8/v8-date-parser.test.js
new file mode 100644
index 000000000..42e5aa4fc
--- /dev/null
+++ b/test/js/node/v8/v8-date-parser.test.js
@@ -0,0 +1,462 @@
+// Copyright 2013 the V8 project authors. All rights reserved.
+// Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+describe("v8 date parser", () => {
+ // https://github.com/v8/v8/blob/c45b7804109ece574f71fd45417b4ad498a99e6f/test/webkit/date-parse-comments-test.js#L27
+ test("test/webkit/date-parse-comments-test.js", () => {
+ var timeZoneOffset = Date.parse(" Dec 25 1995 1:30 ") - Date.parse(" Dec 25 1995 1:30 GMT ");
+ function testDateParse(date, numericResult) {
+ if (numericResult === "NaN") {
+ expect(Date.parse(date)).toBeNaN();
+ expect(Date.parse(date.toUpperCase())).toBeNaN();
+ expect(Date.parse(date.toLowerCase())).toBeNaN();
+ expect(new Date(date).getMilliseconds()).toBeNaN();
+ } else {
+ expect(Date.parse(date)).toBe(numericResult);
+ expect(Date.parse(date.toUpperCase())).toBe(numericResult);
+ expect(Date.parse(date.toLowerCase())).toBe(numericResult);
+ expect(new Date(date).toString()).toBe(new Date(numericResult).toString());
+ }
+ }
+
+ testDateParse("Dec ((27) 26 (24)) 25 1995 1:30 PM UTC", 819898200000);
+ testDateParse("Dec 25 1995 1:30 PM UTC (", 819898200000);
+ testDateParse("Dec 25 1995 1:30 (PM)) UTC", "NaN");
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) GMT (EST)", 819849600000);
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996)", 819849600000 + timeZoneOffset);
+
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) 1:30 (1:40) GMT (EST)", 819855000000);
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) 1:30 (1:40)", 819855000000 + timeZoneOffset);
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) 1:30 ", 819855000000 + timeZoneOffset);
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) 1:30 AM (1:40 PM) GMT (EST)", 819855000000);
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) 1:30 AM (1:40 PM)", 819855000000 + timeZoneOffset);
+ testDateParse("Dec 25 1995 1:30( )AM (PM)", "NaN");
+ testDateParse("Dec 25 1995 1:30 AM (PM)", 819855000000 + timeZoneOffset);
+
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) 13:30 (13:40) GMT (PST)", 819898200000);
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) 13:30 (13:40)", 819898200000 + timeZoneOffset);
+ testDateParse("(Nov) Dec (24) 25 (26) 13:30 (13:40) 1995 (1996)", 819898200000 + timeZoneOffset);
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) 13:30 (13:40) ", 819898200000 + timeZoneOffset);
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) 1:30 (1:40) PM (AM) GMT (PST)", 819898200000);
+ testDateParse("(Nov) Dec (24) 25 (26) 1995 (1996) 1:30 (1:40) PM (AM)", 819898200000 + timeZoneOffset);
+ testDateParse("Dec 25 1995 1:30(AM)PM", "NaN");
+ testDateParse("Dec 25 1995 1:30 (AM)PM ", 819898200000 + timeZoneOffset);
+
+ testDateParse("Dec 25 1995 (PDT)UTC(PST)", 819849600000);
+ testDateParse("Dec 25 1995 (PDT)UT(PST)", 819849600000);
+ testDateParse("Dec 25 1995 (UTC)PST(GMT)", 819878400000);
+ testDateParse("Dec 25 1995 (UTC)PDT(GMT)", 819874800000);
+
+ testDateParse("Dec 25 1995 1:30 (PDT)UTC(PST)", 819855000000);
+ testDateParse("Dec 25 1995 1:30 (PDT)UT(PST)", 819855000000);
+ testDateParse("Dec 25 1995 1:30 (UTC)PST(GMT)", 819883800000);
+ testDateParse("Dec 25 1995 1:30 (UTC)PDT(GMT)", 819880200000);
+
+ testDateParse("Dec 25 1995 1:30 (AM) PM (PST) UTC", 819898200000);
+ testDateParse("Dec 25 1995 1:30 PM (AM) (PST) UT", 819898200000);
+ testDateParse("Dec 25 1995 1:30 PM (AM) (UTC) PST", 819927000000);
+ testDateParse("Dec 25 1995 1:30 (AM) PM PDT (UTC)", 819923400000);
+
+ testDateParse("Dec 25 1995 XXX (GMT)", "NaN");
+ testDateParse("Dec 25 1995 1:30 XXX (GMT)", "NaN");
+
+ testDateParse("Dec 25 1995 1:30 U(TC)", "NaN");
+ testDateParse("Dec 25 1995 1:30 V(UTC)", "NaN");
+ testDateParse("Dec 25 1995 1:30 (UTC)W", "NaN");
+ testDateParse("Dec 25 1995 1:30 (GMT)X", "NaN");
+
+ testDateParse("Dec 25 1995 0:30 (PM) GMT", 819851400000);
+ testDateParse("Dec 25 1995 (1)0:30 AM GMT", 819851400000);
+ testDateParse("Dec 25 1995 (1)0:30 PM GMT", 819894600000);
+
+ testDateParse("Anf(Dec) 25 1995 GMT", "NaN");
+
+ testDateParse("(Sat) Wed (Nov) Dec (Nov) 25 1995 1:30 GMT", 819855000000);
+ testDateParse("Wed (comment 1) (comment 2) Dec 25 1995 1:30 GMT", 819855000000);
+ testDateParse("Wed(comment 1) (comment 2) Dec 25 1995 1:30 GMT", 819855000000);
+ testDateParse("We(comment 1) (comment 2) Dec 25 1995 1:30 GMT", 819855000000);
+ });
+
+ // https://github.com/v8/v8/blob/c45b7804109ece574f71fd45417b4ad498a99e6f/test/mjsunit/regress/regress-4640.js#L6
+ test("test/mjsunit/regress-4640.js", () => {
+ expect(new Date("275760-10-14").getMilliseconds()).toBeNaN();
+ expect(new Date("275760-09-23").getMilliseconds()).toBeNaN();
+ expect(new Date("+275760-09-24").getMilliseconds()).toBeNaN();
+ expect(new Date("+275760-10-13").getMilliseconds()).toBeNaN();
+
+ // The following cases used to throw "illegal access"
+ expect(new Date("275760-09-24").getMilliseconds()).toBeNaN();
+ expect(new Date("275760-10-13").getMilliseconds()).toBeNaN();
+ expect(new Date("+275760-10-13 ").getMilliseconds()).toBeNaN();
+
+ // However, dates within the range or valid
+ expect(new Date("100000-10-13").getMilliseconds()).not.toBeNaN();
+ expect(new Date("+100000-10-13").getMilliseconds()).not.toBeNaN();
+ expect(new Date("+100000-10-13 ").getMilliseconds()).not.toBeNaN();
+ });
+
+ // https://github.com/v8/v8/blob/c45b7804109ece574f71fd45417b4ad498a99e6f/test/mjsunit/date-parse.js#L34
+ test("test/mjsunit/date-parse.js", () => {
+ // Test that we can parse dates in all the different formats that we
+ // have to support.
+ //
+ // These formats are all supported by KJS but a lot of them are not
+ // supported by Spidermonkey.
+
+ function testDateParse(string) {
+ var d = Date.parse(string);
+ expect(d).toBe(946713600000);
+ }
+
+ // For local time we just test that parsing returns non-NaN positive
+ // number of milliseconds to make it timezone independent.
+ function testDateParseLocalTime(string) {
+ var d = Date.parse("parse-local-time:" + string);
+ expect(d).not.toBeNaN();
+ expect(d).toBeGreaterThan(0);
+ }
+
+ function testDateParseMisc(array) {
+ expect(array.length).toBe(2);
+ var string = array[0];
+ var expected = array[1];
+ var d = Date.parse(string);
+ expect(expected).toBe(d);
+ }
+
+ //
+ // Test all the formats in UT timezone.
+ //
+ var testCasesUT = [
+ "Sat, 01-Jan-2000 08:00:00 UT",
+ "Sat, 01 Jan 2000 08:00:00 UT",
+ "Jan 01 2000 08:00:00 UT",
+ "Jan 01 08:00:00 UT 2000",
+ "Saturday, 01-Jan-00 08:00:00 UT",
+ "01 Jan 00 08:00 +0000",
+ // Ignore weekdays.
+ "Mon, 01 Jan 2000 08:00:00 UT",
+ "Tue, 01 Jan 2000 08:00:00 UT",
+ // Ignore prefix that is not part of a date.
+ "[Saturday] Jan 01 08:00:00 UT 2000",
+ "Ignore all of this stuff because it is annoying 01 Jan 2000 08:00:00 UT",
+ "[Saturday] Jan 01 2000 08:00:00 UT",
+ "All of this stuff is really annnoying, so it will be ignored Jan 01 2000 08:00:00 UT",
+ // If the three first letters of the month is a
+ // month name we are happy - ignore the rest.
+ "Sat, 01-Janisamonth-2000 08:00:00 UT",
+ "Sat, 01 Janisamonth 2000 08:00:00 UT",
+ "Janisamonth 01 2000 08:00:00 UT",
+ "Janisamonth 01 08:00:00 UT 2000",
+ "Saturday, 01-Janisamonth-00 08:00:00 UT",
+ "01 Janisamonth 00 08:00 +0000",
+ // Allow missing space between month and day.
+ "Janisamonthandtherestisignored01 2000 08:00:00 UT",
+ "Jan01 2000 08:00:00 UT",
+ // Allow year/month/day format.
+ "Sat, 2000/01/01 08:00:00 UT",
+ // Allow month/day/year format.
+ "Sat, 01/01/2000 08:00:00 UT",
+ // Allow month/day year format.
+ "Sat, 01/01 2000 08:00:00 UT",
+ // Allow comma instead of space after day, month and year.
+ "Sat, 01,Jan,2000,08:00:00 UT",
+ // Seconds are optional.
+ "Sat, 01-Jan-2000 08:00 UT",
+ "Sat, 01 Jan 2000 08:00 UT",
+ "Jan 01 2000 08:00 UT",
+ "Jan 01 08:00 UT 2000",
+ "Saturday, 01-Jan-00 08:00 UT",
+ "01 Jan 00 08:00 +0000",
+ // Allow AM/PM after the time.
+ "Sat, 01-Jan-2000 08:00 AM UT",
+ "Sat, 01 Jan 2000 08:00 AM UT",
+ "Jan 01 2000 08:00 AM UT",
+ "Jan 01 08:00 AM UT 2000",
+ "Saturday, 01-Jan-00 08:00 AM UT",
+ "01 Jan 00 08:00 AM +0000",
+ // White space and stuff in parenthesis is
+ // apparently allowed in most places where white
+ // space is allowed.
+ " Sat, 01-Jan-2000 08:00:00 UT ",
+ " Sat, 01 Jan 2000 08:00:00 UT ",
+ " Saturday, 01-Jan-00 08:00:00 UT ",
+ " 01 Jan 00 08:00 +0000 ",
+ " ()(Sat, 01-Jan-2000) Sat, 01-Jan-2000 08:00:00 UT ",
+ " Sat()(Sat, 01-Jan-2000)01 Jan 2000 08:00:00 UT ",
+ " Sat,(02)01 Jan 2000 08:00:00 UT ",
+ " Sat, 01(02)Jan 2000 08:00:00 UT ",
+ " Sat, 01 Jan 2000 (2001)08:00:00 UT ",
+ " Sat, 01 Jan 2000 (01)08:00:00 UT ",
+ " Sat, 01 Jan 2000 (01:00:00)08:00:00 UT ",
+ " Sat, 01 Jan 2000 08:00:00 (CDT)UT ",
+ " Sat, 01 Jan 2000 08:00:00 UT((((CDT))))",
+ " Saturday, 01-Jan-00 ()(((asfd)))(Sat, 01-Jan-2000)08:00:00 UT ",
+ " 01 Jan 00 08:00 ()(((asdf)))(Sat, 01-Jan-2000)+0000 ",
+ " 01 Jan 00 08:00 +0000()((asfd)(Sat, 01-Jan-2000)) ",
+ ];
+
+ //
+ // Test that we do the right correction for different time zones.
+ // I'll assume that we can handle the same formats as for UT and only
+ // test a few formats for each of the timezones.
+ //
+
+ // GMT = UT
+ var testCasesGMT = [
+ "Sat, 01-Jan-2000 08:00:00 GMT",
+ "Sat, 01-Jan-2000 08:00:00 GMT+0",
+ "Sat, 01-Jan-2000 08:00:00 GMT+00",
+ "Sat, 01-Jan-2000 08:00:00 GMT+000",
+ "Sat, 01-Jan-2000 08:00:00 GMT+0000",
+ "Sat, 01-Jan-2000 08:00:00 GMT+00:00", // Interestingly, KJS cannot handle this.
+ "Sat, 01 Jan 2000 08:00:00 GMT",
+ "Saturday, 01-Jan-00 08:00:00 GMT",
+ "01 Jan 00 08:00 -0000",
+ "01 Jan 00 08:00 +0000",
+ ];
+
+ // EST = UT minus 5 hours.
+ var testCasesEST = [
+ "Sat, 01-Jan-2000 03:00:00 UTC-0500",
+ "Sat, 01-Jan-2000 03:00:00 UTC-05:00", // Interestingly, KJS cannot handle this.
+ "Sat, 01-Jan-2000 03:00:00 EST",
+ "Sat, 01 Jan 2000 03:00:00 EST",
+ "Saturday, 01-Jan-00 03:00:00 EST",
+ "01 Jan 00 03:00 -0500",
+ ];
+
+ // EDT = UT minus 4 hours.
+ var testCasesEDT = [
+ "Sat, 01-Jan-2000 04:00:00 EDT",
+ "Sat, 01 Jan 2000 04:00:00 EDT",
+ "Saturday, 01-Jan-00 04:00:00 EDT",
+ "01 Jan 00 04:00 -0400",
+ ];
+
+ // CST = UT minus 6 hours.
+ var testCasesCST = [
+ "Sat, 01-Jan-2000 02:00:00 CST",
+ "Sat, 01 Jan 2000 02:00:00 CST",
+ "Saturday, 01-Jan-00 02:00:00 CST",
+ "01 Jan 00 02:00 -0600",
+ ];
+
+ // CDT = UT minus 5 hours.
+ var testCasesCDT = [
+ "Sat, 01-Jan-2000 03:00:00 CDT",
+ "Sat, 01 Jan 2000 03:00:00 CDT",
+ "Saturday, 01-Jan-00 03:00:00 CDT",
+ "01 Jan 00 03:00 -0500",
+ ];
+
+ // MST = UT minus 7 hours.
+ var testCasesMST = [
+ "Sat, 01-Jan-2000 01:00:00 MST",
+ "Sat, 01 Jan 2000 01:00:00 MST",
+ "Saturday, 01-Jan-00 01:00:00 MST",
+ "01 Jan 00 01:00 -0700",
+ ];
+
+ // MDT = UT minus 6 hours.
+ var testCasesMDT = [
+ "Sat, 01-Jan-2000 02:00:00 MDT",
+ "Sat, 01 Jan 2000 02:00:00 MDT",
+ "Saturday, 01-Jan-00 02:00:00 MDT",
+ "01 Jan 00 02:00 -0600",
+ ];
+
+ // PST = UT minus 8 hours.
+ var testCasesPST = [
+ "Sat, 01-Jan-2000 00:00:00 PST",
+ "Sat, 01 Jan 2000 00:00:00 PST",
+ "Saturday, 01-Jan-00 00:00:00 PST",
+ "01 Jan 00 00:00 -0800",
+ // Allow missing time.
+ "Sat, 01-Jan-2000 PST",
+ ];
+
+ // PDT = UT minus 7 hours.
+ var testCasesPDT = [
+ "Sat, 01-Jan-2000 01:00:00 PDT",
+ "Sat, 01 Jan 2000 01:00:00 PDT",
+ "Saturday, 01-Jan-00 01:00:00 PDT",
+ "01 Jan 00 01:00 -0700",
+ ];
+
+ // Local time cases.
+ var testCasesLocalTime = [
+ // Allow timezone omission.
+ "Sat, 01-Jan-2000 08:00:00",
+ "Sat, 01 Jan 2000 08:00:00",
+ "Jan 01 2000 08:00:00",
+ "Jan 01 08:00:00 2000",
+ "Saturday, 01-Jan-00 08:00:00",
+ "01 Jan 00 08:00",
+ ];
+
+ // Misc. test cases that result in a different time value.
+ var testCasesMisc = [
+ // Special handling for years in the [0, 100) range.
+ ["Sat, 01 Jan 0 08:00:00 UT", 946713600000], // year 2000
+ ["Sat, 01 Jan 49 08:00:00 UT", 2493100800000], // year 2049
+ ["Sat, 01 Jan 50 08:00:00 UT", -631123200000], // year 1950
+ ["Sat, 01 Jan 99 08:00:00 UT", 915177600000], // year 1999
+ ["Sat, 01 Jan 100 08:00:00 UT", -59011430400000], // year 100
+ // Test PM after time.
+ ["Sat, 01-Jan-2000 08:00 PM UT", 946756800000],
+ ["Sat, 01 Jan 2000 08:00 PM UT", 946756800000],
+ ["Jan 01 2000 08:00 PM UT", 946756800000],
+ ["Jan 01 08:00 PM UT 2000", 946756800000],
+ ["Saturday, 01-Jan-00 08:00 PM UT", 946756800000],
+ ["01 Jan 00 08:00 PM +0000", 946756800000],
+ ];
+
+ // Test different version of the ES5 date time string format.
+ var testCasesES5Misc = [
+ ["2000-01-01T08:00:00.000Z", 946713600000],
+ ["2000-01-01T08:00:00Z", 946713600000],
+ ["2000-01-01T08:00Z", 946713600000],
+ ["2000-01T08:00:00.000Z", 946713600000],
+ ["2000T08:00:00.000Z", 946713600000],
+ ["2000T08:00Z", 946713600000],
+ ["2000-01T00:00:00.000-08:00", 946713600000],
+ ["2000-01T08:00:00.001Z", 946713600001],
+ ["2000-01T08:00:00.099Z", 946713600099],
+ ["2000-01T08:00:00.999Z", 946713600999],
+ ["2000-01T00:00:00.001-08:00", 946713600001],
+ ["2000-01-01T24:00Z", 946771200000],
+ ["2000-01-01T24:00:00Z", 946771200000],
+ ["2000-01-01T24:00:00.000Z", 946771200000],
+ ["2000-01-01T24:00:00.000Z", 946771200000],
+ ];
+
+ var testCasesES5MiscNegative = [
+ "2000-01-01TZ",
+ "2000-01-01T60Z",
+ "2000-01-01T60:60Z",
+ "2000-01-0108:00Z",
+ "2000-01-01T08Z",
+ "2000-01-01T24:01",
+ "2000-01-01T24:00:01",
+ "2000-01-01T24:00:00.001",
+ "2000-01-01T24:00:00.999Z",
+ ];
+
+ // TODO(littledan): This is an hack that could break in historically
+ // changing timezones that happened on this day, but allows us to
+ // check the date value for local times.
+ var localOffset = new Date("2000-01-01").getTimezoneOffset() * 1000 * 60;
+
+ // Sanity check which is even more of a hack: in the timezones where
+ // these tests are likely to be run, the offset is nonzero because
+ // dates which don't include Z are in the local timezone.
+ if (
+ this.Intl &&
+ ["America/Los_Angeles", "Europe/Berlin", "Europe/Madrid"].indexOf(
+ Intl.DateTimeFormat().resolvedOptions().timeZone,
+ ) != -1
+ ) {
+ expect(localOffset).not.toBe(0);
+ }
+
+ var testCasesES2016TZ = [
+ // If the timezone is absent and time is present, use local time
+ ["2000-01-02T00:00", 946771200000 + localOffset],
+ ["2000-01-02T00:00:00", 946771200000 + localOffset],
+ ["2000-01-02T00:00:00.000", 946771200000 + localOffset],
+ // If timezone is absent and time is absent, use UTC
+ ["2000-01-02", 946771200000],
+ ["2000-01-02", 946771200000],
+ ["2000-01-02", 946771200000],
+ ];
+
+ // Run all the tests.
+ testCasesUT.forEach(testDateParse);
+ testCasesGMT.forEach(testDateParse);
+ testCasesEST.forEach(testDateParse);
+ testCasesEDT.forEach(testDateParse);
+ testCasesCST.forEach(testDateParse);
+ testCasesCDT.forEach(testDateParse);
+ testCasesMST.forEach(testDateParse);
+ testCasesMDT.forEach(testDateParse);
+ testCasesPST.forEach(testDateParse);
+ testCasesPDT.forEach(testDateParse);
+ testCasesLocalTime.forEach(testDateParseLocalTime);
+ testCasesMisc.forEach(testDateParseMisc);
+
+ // ES5 date time string format compliance.
+ testCasesES5Misc.forEach(testDateParseMisc);
+ testCasesES5MiscNegative.forEach(function (s) {
+ expect(new Date(s).toString()).toBe("Invalid Date");
+ });
+
+ testCasesES2016TZ.forEach(testDateParseMisc);
+
+ // Test that we can parse our own date format.
+ // (Dates from 1970 to ~2070 with 150h steps.)
+ for (var i = 0; i < 24 * 365 * 100; i += 150) {
+ var ms = i * (3600 * 1000);
+ var s = new Date(ms).toString();
+ expect(Date.parse(s)).toBe(ms);
+ }
+
+ // Negative tests.
+ var testCasesNegative = [
+ "May 25 2008 1:30 (PM)) UTC", // Bad unmatched ')' after number.
+ "May 25 2008 1:30( )AM (PM)", //
+ "a1", // Issue 126448, 53209.
+ "nasfdjklsfjoaifg1",
+ "x_2",
+ "May 25 2008 AAA (GMT)",
+ ]; // Unknown word after number.
+
+ testCasesNegative.forEach(function (s) {
+ expect(new Date(s).getMilliseconds()).toBeNaN();
+ });
+ });
+
+ // https://github.com/v8/v8/blob/c45b7804109ece574f71fd45417b4ad498a99e6f/test/intl/regress-1451943.js#L5
+ test("test/intl/regress-1451943.js", () => {
+ let beforeOct1582GregorianTransition = new Date("1582-01-01T00:00Z");
+ let afterOct1582GregorianTransition = new Date("1583-01-01T00:00Z");
+
+ expect(beforeOct1582GregorianTransition.toLocaleDateString("en-US", { timeZone: "UTC", calendar: "gregory" })).toBe(
+ "1/1/1582",
+ );
+ expect(beforeOct1582GregorianTransition.toLocaleDateString("en-US", { timeZone: "UTC", calendar: "iso8601" })).toBe(
+ "1/1/1582",
+ );
+ expect(afterOct1582GregorianTransition.toLocaleDateString("en-US", { timeZone: "UTC", calendar: "iso8601" })).toBe(
+ "1/1/1583",
+ );
+ });
+
+ test("random invalid dates in JSC", () => {
+ var input = "Sep 09 2022 03:53:45Z";
+ expect(Date.parse(input)).toBe(1662695625000);
+
+ input = "2020-09-21 15:19:06 +00:00";
+ expect(Date.parse(input)).toBe(1600701546000);
+ });
+});
diff --git a/test/js/node/vm/vm.test.ts b/test/js/node/vm/vm.test.ts
index 510448c5e..4e291ac9f 100644
--- a/test/js/node/vm/vm.test.ts
+++ b/test/js/node/vm/vm.test.ts
@@ -18,7 +18,6 @@ describe("Script", () => {
describe("runInContext()", () => {
testRunInContext(
(code, context, options) => {
- // @ts-expect-error
const script = new Script(code, options);
return script.runInContext(context);
},
@@ -28,7 +27,6 @@ describe("Script", () => {
describe("runInNewContext()", () => {
testRunInContext(
(code, context, options) => {
- // @ts-expect-error
const script = new Script(code, options);
return script.runInNewContext(context);
},
@@ -37,7 +35,6 @@ describe("Script", () => {
});
describe("runInThisContext()", () => {
testRunInContext((code, context, options) => {
- // @ts-expect-error
const script = new Script(code, options);
return script.runInThisContext(context);
});
@@ -101,7 +98,7 @@ function testRunInContext(
expect(typeof result).toBe("function");
expect(result()).toBe("bar");
});
- test.skip("can throw a syntax error", () => {
+ test("can throw a syntax error", () => {
const context = createContext({});
const result = () => fn("!?", context);
expect(result).toThrow({
diff --git a/test/js/node/watch/fs.watch.test.ts b/test/js/node/watch/fs.watch.test.ts
index 5086ae1d8..787ce413c 100644
--- a/test/js/node/watch/fs.watch.test.ts
+++ b/test/js/node/watch/fs.watch.test.ts
@@ -165,6 +165,36 @@ describe("fs.watch", () => {
});
});
+ // https://github.com/oven-sh/bun/issues/5442
+ test("should work with paths with trailing slashes", done => {
+ const testsubdir = tempDirWithFiles("subdir", {
+ "trailing.txt": "hello",
+ });
+ const filepath = path.join(testsubdir, "trailing.txt");
+ let err: Error | undefined = undefined;
+ const watcher = fs.watch(testsubdir + "/", function (event, filename) {
+ try {
+ expect(event).toBe("rename");
+ expect(filename).toBe("trailing.txt");
+ } catch (e: any) {
+ err = e;
+ } finally {
+ clearInterval(interval);
+ watcher.close();
+ }
+ });
+
+ watcher.once("close", () => {
+ done(err);
+ });
+
+ const interval = repeat(() => {
+ fs.rmSync(filepath, { force: true });
+ const fd = fs.openSync(filepath, "w");
+ fs.closeSync(fd);
+ });
+ });
+
test("should emit 'change' event when file is modified", done => {
const filepath = path.join(testDir, "watch.txt");
diff --git a/test/js/node/worker_threads/worker_threads.test.ts b/test/js/node/worker_threads/worker_threads.test.ts
index ae88add17..86cbaa208 100644
--- a/test/js/node/worker_threads/worker_threads.test.ts
+++ b/test/js/node/worker_threads/worker_threads.test.ts
@@ -16,7 +16,7 @@ import {
MessagePort,
Worker,
} from "worker_threads";
-test("all properties are present", () => {
+test("all worker_threads module properties are present", () => {
expect(wt).toHaveProperty("getEnvironmentData");
expect(wt).toHaveProperty("isMainThread");
expect(wt).toHaveProperty("markAsUntransferable");
@@ -42,7 +42,7 @@ test("all properties are present", () => {
expect(resourceLimits).toBeDefined();
expect(SHARE_ENV).toBeDefined();
expect(setEnvironmentData).toBeDefined();
- expect(threadId).toBeDefined();
+ expect(threadId).toBeNumber();
expect(workerData).toBeUndefined();
expect(BroadcastChannel).toBeDefined();
expect(MessageChannel).toBeDefined();
@@ -60,9 +60,64 @@ test("all properties are present", () => {
}).toThrow("not yet implemented");
});
+test("all worker_threads worker instance properties are present", () => {
+ const worker = new Worker(new URL("./worker.js", import.meta.url).href);
+ expect(worker).toHaveProperty("threadId");
+ expect(worker).toHaveProperty("ref");
+ expect(worker).toHaveProperty("unref");
+ expect(worker).toHaveProperty("stdin");
+ expect(worker).toHaveProperty("stdout");
+ expect(worker).toHaveProperty("stderr");
+ expect(worker).toHaveProperty("performance");
+ expect(worker).toHaveProperty("terminate");
+ expect(worker).toHaveProperty("postMessage");
+ expect(worker).toHaveProperty("getHeapSnapshot");
+ expect(worker).toHaveProperty("setMaxListeners");
+ expect(worker).toHaveProperty("getMaxListeners");
+ expect(worker).toHaveProperty("emit");
+ expect(worker).toHaveProperty("addListener");
+ expect(worker).toHaveProperty("on");
+ expect(worker).toHaveProperty("prependListener");
+ expect(worker).toHaveProperty("once");
+ expect(worker).toHaveProperty("prependOnceListener");
+ expect(worker).toHaveProperty("removeListener");
+ expect(worker).toHaveProperty("off");
+ expect(worker).toHaveProperty("removeAllListeners");
+ expect(worker).toHaveProperty("listeners");
+ expect(worker).toHaveProperty("rawListeners");
+ expect(worker).toHaveProperty("listenerCount");
+ expect(worker).toHaveProperty("eventNames");
+
+ expect(worker.threadId).toBeNumber();
+ expect(worker.ref).toBeFunction();
+ expect(worker.unref).toBeFunction();
+ expect(worker.stdin).toBeNull();
+ expect(worker.stdout).toBeNull();
+ expect(worker.stderr).toBeNull();
+ expect(worker.performance).toBeDefined();
+ expect(worker.terminate).toBeFunction();
+ expect(worker.postMessage).toBeFunction();
+ expect(worker.getHeapSnapshot).toBeFunction();
+ expect(worker.setMaxListeners).toBeFunction();
+ expect(worker.getMaxListeners).toBeFunction();
+ expect(worker.emit).toBeFunction();
+ expect(worker.addListener).toBeFunction();
+ expect(worker.on).toBeFunction();
+ expect(worker.prependListener).toBeFunction();
+ expect(worker.once).toBeFunction();
+ expect(worker.prependOnceListener).toBeFunction();
+ expect(worker.removeListener).toBeFunction();
+ expect(worker.off).toBeFunction();
+ expect(worker.removeAllListeners).toBeFunction();
+ expect(worker.listeners).toBeFunction();
+ expect(worker.rawListeners).toBeFunction();
+ expect(worker.listenerCount).toBeFunction();
+ expect(worker.eventNames).toBeFunction();
+});
+
test("receiveMessageOnPort works across threads", () => {
const { port1, port2 } = new MessageChannel();
- var worker = new wt.Worker(new URL("./worker.js", import.meta.url).href, {
+ const worker = new Worker(new URL("./worker.js", import.meta.url).href, {
workerData: port2,
transferList: [port2],
});
@@ -77,7 +132,7 @@ test("receiveMessageOnPort works across threads", () => {
});
test("receiveMessageOnPort works with FIFO", () => {
- const { port1, port2 } = new wt.MessageChannel();
+ const { port1, port2 } = new MessageChannel();
const message1 = { hello: "world" };
const message2 = { foo: "bar" };