summaryrefslogtreecommitdiff
path: root/cli/tests
diff options
context:
space:
mode:
authorYoshiya Hinosawa <stibium121@gmail.com>2023-02-17 23:58:52 +0900
committerGitHub <noreply@github.com>2023-02-17 23:58:52 +0900
commita01af067d79e78ea8e2c21cf0ef92d86d425f8eb (patch)
treeb6345e86f04b61effd2be9b5dfd8c48507de7d65 /cli/tests
parent1a7666a6ca63b3dc6872d8f4631a2799af534159 (diff)
test: add node compat tests (#17805)
Diffstat (limited to 'cli/tests')
-rw-r--r--cli/tests/integration/mod.rs2
-rw-r--r--cli/tests/integration/node_compat_tests.rs25
-rw-r--r--cli/tests/node_compat/common.ts54
-rw-r--r--cli/tests/node_compat/config.json13
-rw-r--r--cli/tests/node_compat/import_map.json5
-rw-r--r--cli/tests/node_compat/runner.ts7
-rw-r--r--cli/tests/node_compat/test.ts115
-rw-r--r--cli/tests/node_compat/test/common/index.js489
-rw-r--r--cli/tests/node_compat/test/common/package.json1
-rw-r--r--cli/tests/node_compat/test/common/tmpdir.js69
-rw-r--r--cli/tests/node_compat/test/parallel/package.json1
-rw-r--r--cli/tests/node_compat/test/parallel/test-assert-fail.js51
-rw-r--r--cli/tests/node_compat/test/parallel/test-assert-strict-exists.js13
-rw-r--r--cli/tests/node_compat/test/parallel/test-btoa-atob.js46
14 files changed, 891 insertions, 0 deletions
diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs
index 7ed1d6e0d..d102b486d 100644
--- a/cli/tests/integration/mod.rs
+++ b/cli/tests/integration/mod.rs
@@ -70,6 +70,8 @@ mod js_unit_tests;
mod lint;
#[path = "lsp_tests.rs"]
mod lsp;
+#[path = "node_compat_tests.rs"]
+mod node_compat_tests;
#[path = "node_unit_tests.rs"]
mod node_unit_tests;
#[path = "npm_tests.rs"]
diff --git a/cli/tests/integration/node_compat_tests.rs b/cli/tests/integration/node_compat_tests.rs
new file mode 100644
index 000000000..7617b7037
--- /dev/null
+++ b/cli/tests/integration/node_compat_tests.rs
@@ -0,0 +1,25 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use test_util as util;
+
+#[test]
+fn node_compat_tests() {
+ let mut deno = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("test")
+ .arg("--unstable")
+ .arg("--import-map")
+ .arg(
+ util::tests_path()
+ .join("node_compat")
+ .join("import_map.json"),
+ )
+ .arg("-A")
+ .arg(util::tests_path().join("node_compat"))
+ .spawn()
+ .expect("failed to spawn script");
+
+ let status = deno.wait().expect("failed to wait for the child process");
+ assert_eq!(Some(0), status.code());
+ assert!(status.success());
+}
diff --git a/cli/tests/node_compat/common.ts b/cli/tests/node_compat/common.ts
new file mode 100644
index 000000000..72f44e510
--- /dev/null
+++ b/cli/tests/node_compat/common.ts
@@ -0,0 +1,54 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { join } from "std/path/mod.ts";
+
+/**
+ * The test suite matches the folders inside the `test` folder inside the
+ * node repo
+ *
+ * Each test suite contains a list of files (which can be paths
+ * or a regex to match) that will be pulled from the node repo
+ */
+type TestSuites = Record<string, string[]>;
+
+interface Config {
+ nodeVersion: string;
+ /** Ignored files won't regenerated by the update script */
+ ignore: TestSuites;
+ /**
+ * The files that will be run by the test suite
+ *
+ * The files to be generated with the update script must be listed here as well,
+ * but they won't be regenerated if they are listed in the `ignore` configuration
+ */
+ tests: TestSuites;
+ windowsIgnore: TestSuites;
+ darwinIgnore: TestSuites;
+}
+
+export const config: Config = JSON.parse(
+ await Deno.readTextFile(new URL("./config.json", import.meta.url)),
+);
+
+export const ignoreList = Object.entries(config.ignore).reduce(
+ (total: RegExp[], [suite, paths]) => {
+ paths.forEach((path) => total.push(new RegExp(join(suite, path))));
+ return total;
+ },
+ [],
+);
+
+export function getPathsFromTestSuites(suites: TestSuites): string[] {
+ const testPaths: string[] = [];
+ for (const [dir, paths] of Object.entries(suites)) {
+ if (
+ ["parallel", "internet", "pummel", "sequential", "pseudo-tty"].includes(
+ dir,
+ )
+ ) {
+ for (const path of paths) {
+ testPaths.push(join(dir, path));
+ }
+ }
+ }
+ return testPaths;
+}
diff --git a/cli/tests/node_compat/config.json b/cli/tests/node_compat/config.json
new file mode 100644
index 000000000..e032c699d
--- /dev/null
+++ b/cli/tests/node_compat/config.json
@@ -0,0 +1,13 @@
+{
+ "nodeVersion": "18.12.1",
+ "ignore": {},
+ "tests": {
+ "parallel": [
+ "test-assert-fail.js",
+ "test-assert-strict-exists.js",
+ "test-btoa-atob.js"
+ ]
+ },
+ "windowsIgnore": {},
+ "darwinIgnore": {}
+}
diff --git a/cli/tests/node_compat/import_map.json b/cli/tests/node_compat/import_map.json
new file mode 100644
index 000000000..ccdef1e00
--- /dev/null
+++ b/cli/tests/node_compat/import_map.json
@@ -0,0 +1,5 @@
+{
+ "imports": {
+ "std/": "../../../test_util/std/"
+ }
+}
diff --git a/cli/tests/node_compat/runner.ts b/cli/tests/node_compat/runner.ts
new file mode 100644
index 000000000..f12cc69b0
--- /dev/null
+++ b/cli/tests/node_compat/runner.ts
@@ -0,0 +1,7 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { createRequire } from "node:module";
+const file = Deno.args[0];
+if (!file) {
+ throw new Error("No file provided");
+}
+createRequire(import.meta.url)(file);
diff --git a/cli/tests/node_compat/test.ts b/cli/tests/node_compat/test.ts
new file mode 100644
index 000000000..3af1fb693
--- /dev/null
+++ b/cli/tests/node_compat/test.ts
@@ -0,0 +1,115 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { magenta } from "std/fmt/colors.ts";
+import { dirname, fromFileUrl, join } from "std/path/mod.ts";
+import { fail } from "std/testing/asserts.ts";
+import { config, getPathsFromTestSuites } from "./common.ts";
+
+// If the test case is invoked like
+// deno test -A cli/tests/node_compat/test.ts -- <test-names>
+// Use the test-names as filters
+const filters = Deno.args;
+
+/**
+ * This script will run the test files specified in the configuration file
+ *
+ * Each test file will be run independently and wait until completion, if an abnormal
+ * code for the test is reported, the test suite will fail immediately
+ */
+
+const toolsPath = dirname(fromFileUrl(import.meta.url));
+const stdRootUrl = new URL("../../", import.meta.url).href;
+const testPaths = getPathsFromTestSuites(config.tests);
+const cwd = new URL(".", import.meta.url);
+const importMap = "import_map.json";
+const windowsIgnorePaths = new Set(
+ getPathsFromTestSuites(config.windowsIgnore),
+);
+const darwinIgnorePaths = new Set(
+ getPathsFromTestSuites(config.darwinIgnore),
+);
+
+const decoder = new TextDecoder();
+
+for await (const path of testPaths) {
+ // If filter patterns are given and any pattern doesn't match
+ // to the file path, then skip the case
+ if (
+ filters.length > 0 &&
+ filters.every((pattern) => !path.includes(pattern))
+ ) {
+ continue;
+ }
+ const ignore =
+ (Deno.build.os === "windows" && windowsIgnorePaths.has(path)) ||
+ (Deno.build.os === "darwin" && darwinIgnorePaths.has(path));
+ Deno.test({
+ name: `Node.js compatibility "${path}"`,
+ ignore,
+ fn: async () => {
+ const testCase = join(toolsPath, "test", path);
+
+ const v8Flags = ["--stack-size=4000"];
+ const testSource = await Deno.readTextFile(testCase);
+ // TODO(kt3k): Parse `Flags` directive correctly
+ if (testSource.includes("Flags: --expose_externalize_string")) {
+ v8Flags.push("--expose-externalize-string");
+ }
+
+ const args = [
+ "run",
+ "-A",
+ "--quiet",
+ "--unstable",
+ "--unsafely-ignore-certificate-errors",
+ "--v8-flags=" + v8Flags.join(),
+ testCase.endsWith(".mjs") ? "--import-map=" + importMap : "runner.ts",
+ testCase,
+ ];
+
+ // Pipe stdout in order to output each test result as Deno.test output
+ // That way the tests will respect the `--quiet` option when provided
+ const command = new Deno.Command(Deno.execPath(), {
+ args,
+ env: {
+ DENO_NODE_COMPAT_URL: stdRootUrl,
+ },
+ cwd,
+ });
+ const { code, stdout, stderr } = await command.output();
+
+ if (stdout.length) console.log(decoder.decode(stdout));
+
+ if (code !== 0) {
+ console.log(`Error: "${path}" failed`);
+ console.log(
+ "You can repeat only this test with the command:",
+ magenta(
+ `./target/debug/deno test -A --import-map cli/tests/node_compat/import_map.json cli/tests/node_compat/test.ts -- ${path}`,
+ ),
+ );
+ fail(decoder.decode(stderr));
+ }
+ },
+ });
+}
+
+function checkConfigTestFilesOrder(testFileLists: Array<string[]>) {
+ for (const testFileList of testFileLists) {
+ const sortedTestList = JSON.parse(JSON.stringify(testFileList));
+ sortedTestList.sort();
+ if (JSON.stringify(testFileList) !== JSON.stringify(sortedTestList)) {
+ throw new Error(
+ `File names in \`config.json\` are not correct order.`,
+ );
+ }
+ }
+}
+
+if (filters.length === 0) {
+ Deno.test("checkConfigTestFilesOrder", function () {
+ checkConfigTestFilesOrder([
+ ...Object.keys(config.ignore).map((suite) => config.ignore[suite]),
+ ...Object.keys(config.tests).map((suite) => config.tests[suite]),
+ ]);
+ });
+}
diff --git a/cli/tests/node_compat/test/common/index.js b/cli/tests/node_compat/test/common/index.js
new file mode 100644
index 000000000..f93f7b7a6
--- /dev/null
+++ b/cli/tests/node_compat/test/common/index.js
@@ -0,0 +1,489 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+
+/**
+ * This file is meant as a replacement for the original common/index.js
+ *
+ * That file has a lot of node functionality not currently supported, so this is a lite
+ * version of that file, which most tests should be able to use
+ */
+'use strict';
+const assert = require("assert");
+const path = require("path");
+const util = require("util");
+const tmpdir = require("./tmpdir");
+
+function platformTimeout(ms) {
+ return ms;
+}
+
+let localhostIPv4 = null;
+
+let knownGlobals = [
+ AbortSignal,
+ addEventListener,
+ alert,
+ atob,
+ btoa,
+ Buffer,
+ caches,
+ clearImmediate,
+ close,
+ closed,
+ confirm,
+ console,
+ crypto,
+ Deno,
+ dispatchEvent,
+ fetch,
+ getParent,
+ global,
+ global.clearInterval,
+ global.clearTimeout,
+ global.setInterval,
+ global.setTimeout,
+ localStorage,
+ location,
+ navigator,
+ onload,
+ onunload,
+ process,
+ prompt,
+ queueMicrotask,
+ removeEventListener,
+ reportError,
+ self,
+ sessionStorage,
+ setImmediate,
+ window,
+];
+
+if (global.AbortController)
+ knownGlobals.push(global.AbortController);
+
+if (global.gc) {
+ knownGlobals.push(global.gc);
+}
+
+if (global.performance) {
+ knownGlobals.push(global.performance);
+}
+if (global.PerformanceMark) {
+ knownGlobals.push(global.PerformanceMark);
+}
+if (global.PerformanceMeasure) {
+ knownGlobals.push(global.PerformanceMeasure);
+}
+
+if (global.structuredClone) {
+ knownGlobals.push(global.structuredClone);
+}
+
+function allowGlobals(...allowlist) {
+ knownGlobals = knownGlobals.concat(allowlist);
+}
+
+if (process.env.NODE_TEST_KNOWN_GLOBALS !== '0') {
+ if (process.env.NODE_TEST_KNOWN_GLOBALS) {
+ const knownFromEnv = process.env.NODE_TEST_KNOWN_GLOBALS.split(',');
+ allowGlobals(...knownFromEnv);
+ }
+
+ function leakedGlobals() {
+ const leaked = [];
+
+ for (const val in global) {
+ if (!knownGlobals.includes(global[val])) {
+ leaked.push(val);
+ }
+ }
+
+ return leaked;
+ }
+
+ process.on('exit', function() {
+ const leaked = leakedGlobals();
+ if (leaked.length > 0) {
+ assert.fail(`Unexpected global(s) found: ${leaked.join(', ')}`);
+ }
+ });
+}
+
+function _expectWarning(name, expected, code) {
+ if (typeof expected === 'string') {
+ expected = [[expected, code]];
+ } else if (!Array.isArray(expected)) {
+ expected = Object.entries(expected).map(([a, b]) => [b, a]);
+ } else if (!(Array.isArray(expected[0]))) {
+ expected = [[expected[0], expected[1]]];
+ }
+ // Deprecation codes are mandatory, everything else is not.
+ if (name === 'DeprecationWarning') {
+ expected.forEach(([_, code]) => assert(code, expected));
+ }
+ return mustCall((warning) => {
+ const [ message, code ] = expected.shift();
+ assert.strictEqual(warning.name, name);
+ if (typeof message === 'string') {
+ assert.strictEqual(warning.message, message);
+ } else {
+ assert.match(warning.message, message);
+ }
+ assert.strictEqual(warning.code, code);
+ }, expected.length);
+}
+
+let catchWarning;
+
+// Accepts a warning name and description or array of descriptions or a map of
+// warning names to description(s) ensures a warning is generated for each
+// name/description pair.
+// The expected messages have to be unique per `expectWarning()` call.
+function expectWarning(nameOrMap, expected, code) {
+ if (catchWarning === undefined) {
+ catchWarning = {};
+ process.on('warning', (warning) => {
+ if (!catchWarning[warning.name]) {
+ throw new TypeError(
+ `"${warning.name}" was triggered without being expected.\n` +
+ util.inspect(warning)
+ );
+ }
+ catchWarning[warning.name](warning);
+ });
+ }
+ if (typeof nameOrMap === 'string') {
+ catchWarning[nameOrMap] = _expectWarning(nameOrMap, expected, code);
+ } else {
+ Object.keys(nameOrMap).forEach((name) => {
+ catchWarning[name] = _expectWarning(name, nameOrMap[name]);
+ });
+ }
+}
+
+/**
+ * Useful for testing expected internal/error objects
+ *
+ * @param {Error} error
+ */
+function expectsError(validator, exact) {
+ /**
+ * @param {Error} error
+ */
+ return mustCall((...args) => {
+ if (args.length !== 1) {
+ // Do not use `assert.strictEqual()` to prevent `inspect` from
+ // always being called.
+ assert.fail(`Expected one argument, got ${util.inspect(args)}`);
+ }
+ const error = args.pop();
+ const descriptor = Object.getOwnPropertyDescriptor(error, 'message');
+ // The error message should be non-enumerable
+ assert.strictEqual(descriptor.enumerable, false);
+
+ assert.throws(() => { throw error; }, validator);
+ return true;
+ }, exact);
+}
+
+const noop = () => {};
+
+/**
+ * @param {Function} fn
+ * @param {number} exact
+ */
+function mustCall(fn, exact) {
+ return _mustCallInner(fn, exact, "exact");
+}
+
+function mustCallAtLeast(fn, minimum) {
+ return _mustCallInner(fn, minimum, 'minimum');
+}
+
+function mustSucceed(fn, exact) {
+ return mustCall(function(err, ...args) {
+ assert.ifError(err);
+ if (typeof fn === 'function')
+ return fn.apply(this, args);
+ }, exact);
+}
+
+const mustCallChecks = [];
+/**
+ * @param {number} exitCode
+ */
+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);
+}
+
+/**
+ * @param {Function} fn
+ * @param {"exact" | "minimum"} field
+ */
+function _mustCallInner(fn, criteria = 1, field) {
+ // @ts-ignore
+ if (process._exiting) {
+ throw new Error("Cannot use common.mustCall*() in process exit handler");
+ }
+ if (typeof fn === "number") {
+ criteria = fn;
+ fn = noop;
+ } else if (fn === undefined) {
+ fn = noop;
+ }
+
+ if (typeof criteria !== "number") {
+ throw new TypeError(`Invalid ${field} value: ${criteria}`);
+ }
+
+ let context;
+ if (field === "exact") {
+ context = {
+ exact: criteria,
+ actual: 0,
+ stack: util.inspect(new Error()),
+ name: fn.name || "<anonymous>",
+ };
+ } else {
+ context = {
+ minimum: 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);
+
+ return function () {
+ context.actual++;
+ return fn.apply(this, arguments);
+ };
+}
+
+/**
+ * @param {string=} msg
+ */
+function mustNotCall(msg) {
+ /**
+ * @param {any[]} args
+ */
+ return function mustNotCall(...args) {
+ const argsInfo = args.length > 0
+ ? `\ncalled with arguments: ${args.map(util.inspect).join(", ")}`
+ : "";
+ assert.fail(
+ `${msg || "function should not have been called"} at unknown` +
+ argsInfo,
+ );
+ };
+}
+
+const _mustNotMutateObjectDeepProxies = new WeakMap();
+
+function mustNotMutateObjectDeep(original) {
+ // Return primitives and functions directly. Primitives are immutable, and
+ // proxied functions are impossible to compare against originals, e.g. with
+ // `assert.deepEqual()`.
+ if (original === null || typeof original !== 'object') {
+ return original;
+ }
+
+ const cachedProxy = _mustNotMutateObjectDeepProxies.get(original);
+ if (cachedProxy) {
+ return cachedProxy;
+ }
+
+ const _mustNotMutateObjectDeepHandler = {
+ __proto__: null,
+ defineProperty(target, property, descriptor) {
+ assert.fail(`Expected no side effects, got ${inspect(property)} ` +
+ 'defined');
+ },
+ deleteProperty(target, property) {
+ assert.fail(`Expected no side effects, got ${inspect(property)} ` +
+ 'deleted');
+ },
+ get(target, prop, receiver) {
+ return mustNotMutateObjectDeep(Reflect.get(target, prop, receiver));
+ },
+ preventExtensions(target) {
+ assert.fail('Expected no side effects, got extensions prevented on ' +
+ inspect(target));
+ },
+ set(target, property, value, receiver) {
+ assert.fail(`Expected no side effects, got ${inspect(value)} ` +
+ `assigned to ${inspect(property)}`);
+ },
+ setPrototypeOf(target, prototype) {
+ assert.fail(`Expected no side effects, got set prototype to ${prototype}`);
+ }
+ };
+
+ const proxy = new Proxy(original, _mustNotMutateObjectDeepHandler);
+ _mustNotMutateObjectDeepProxies.set(original, proxy);
+ return proxy;
+}
+
+// A helper function to simplify checking for ERR_INVALID_ARG_TYPE output.
+function invalidArgTypeHelper(input) {
+ if (input == null) {
+ return ` Received ${input}`;
+ }
+ if (typeof input === "function" && input.name) {
+ return ` Received function ${input.name}`;
+ }
+ if (typeof input === "object") {
+ if (input.constructor && input.constructor.name) {
+ return ` Received an instance of ${input.constructor.name}`;
+ }
+ return ` Received ${util.inspect(input, { depth: -1 })}`;
+ }
+ let inspected = util.inspect(input, { colors: false });
+ if (inspected.length > 25) {
+ inspected = `${inspected.slice(0, 25)}...`;
+ }
+ return ` Received type ${typeof input} (${inspected})`;
+}
+
+const isWindows = process.platform === 'win32';
+const isAIX = process.platform === 'aix';
+const isSunOS = process.platform === 'sunos';
+const isFreeBSD = process.platform === 'freebsd';
+const isOpenBSD = process.platform === 'openbsd';
+const isLinux = process.platform === 'linux';
+const isOSX = process.platform === 'darwin';
+
+const isDumbTerminal = process.env.TERM === 'dumb';
+
+function skipIfDumbTerminal() {
+ if (isDumbTerminal) {
+ skip('skipping - dumb terminal');
+ }
+}
+
+function printSkipMessage(msg) {
+ console.log(`1..0 # Skipped: ${msg}`);
+}
+
+function skip(msg) {
+ printSkipMessage(msg);
+ process.exit(0);
+}
+
+const PIPE = (() => {
+ const localRelative = path.relative(process.cwd(), `${tmpdir.path}/`);
+ const pipePrefix = isWindows ? "\\\\.\\pipe\\" : localRelative;
+ const pipeName = `node-test.${process.pid}.sock`;
+ return path.join(pipePrefix, pipeName);
+})();
+
+function getArrayBufferViews(buf) {
+ const { buffer, byteOffset, byteLength } = buf;
+
+ const out = [];
+
+ const arrayBufferViews = [
+ Int8Array,
+ Uint8Array,
+ Uint8ClampedArray,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array,
+ DataView,
+ ];
+
+ for (const type of arrayBufferViews) {
+ const { BYTES_PER_ELEMENT = 1 } = type;
+ if (byteLength % BYTES_PER_ELEMENT === 0) {
+ out.push(new type(buffer, byteOffset, byteLength / BYTES_PER_ELEMENT));
+ }
+ }
+ return out;
+}
+
+function getBufferSources(buf) {
+ return [...getArrayBufferViews(buf), new Uint8Array(buf).buffer];
+}
+
+const pwdCommand = isWindows ?
+ ['cmd.exe', ['/d', '/c', 'cd']] :
+ ['pwd', []];
+
+module.exports = {
+ allowGlobals,
+ expectsError,
+ expectWarning,
+ getArrayBufferViews,
+ getBufferSources,
+ hasCrypto: true,
+ invalidArgTypeHelper,
+ mustCall,
+ mustCallAtLeast,
+ mustNotCall,
+ mustNotMutateObjectDeep,
+ mustSucceed,
+ PIPE,
+ platformTimeout,
+ printSkipMessage,
+ pwdCommand,
+ skipIfDumbTerminal,
+ isDumbTerminal,
+ isWindows,
+ isAIX,
+ isSunOS,
+ isFreeBSD,
+ isOpenBSD,
+ isLinux,
+ isOSX,
+ isMainThread: true, // TODO(f3n67u): replace with `worker_thread.isMainThread` when `worker_thread` implemented
+ skip,
+ get hasIPv6() {
+ const iFaces = require('os').networkInterfaces();
+ const re = isWindows ? /Loopback Pseudo-Interface/ : /lo/;
+ return Object.keys(iFaces).some((name) => {
+ return re.test(name) &&
+ iFaces[name].some(({ family }) => family === 'IPv6');
+ });
+ },
+
+ get localhostIPv4() {
+ if (localhostIPv4 !== null) return localhostIPv4;
+ if (localhostIPv4 === null) localhostIPv4 = '127.0.0.1';
+
+ return localhostIPv4;
+ },
+
+ get PORT() {
+ return 12346;
+ },
+};
diff --git a/cli/tests/node_compat/test/common/package.json b/cli/tests/node_compat/test/common/package.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/cli/tests/node_compat/test/common/package.json
@@ -0,0 +1 @@
+{}
diff --git a/cli/tests/node_compat/test/common/tmpdir.js b/cli/tests/node_compat/test/common/tmpdir.js
new file mode 100644
index 000000000..d3ce98e45
--- /dev/null
+++ b/cli/tests/node_compat/test/common/tmpdir.js
@@ -0,0 +1,69 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 16.13.0
+// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+// const { isMainThread } = require('worker_threads');
+
+function rmSync(pathname) {
+ fs.rmSync(pathname, { maxRetries: 3, recursive: true, force: true });
+}
+
+const testRoot = process.env.NODE_TEST_DIR ?
+ fs.realpathSync(process.env.NODE_TEST_DIR) : path.resolve(__dirname, '..');
+
+// Using a `.` prefixed name, which is the convention for "hidden" on POSIX,
+// gets tools to ignore it by default or by simple rules, especially eslint.
+const tmpdirName = '.tmp.' +
+ (process.env.TEST_SERIAL_ID || process.env.TEST_THREAD_ID || '0');
+const tmpPath = path.join(testRoot, tmpdirName);
+
+let firstRefresh = true;
+function refresh() {
+ rmSync(this.path);
+ fs.mkdirSync(this.path);
+
+ if (firstRefresh) {
+ firstRefresh = false;
+ // Clean only when a test uses refresh. This allows for child processes to
+ // use the tmpdir and only the parent will clean on exit.
+ process.on('exit', onexit);
+ }
+}
+
+function onexit() {
+ // Change directory to avoid possible EBUSY
+ // TODO(f3n67u): uncomment when `worker_thread.isMainThread` implemented
+ // if (isMainThread)
+ // process.chdir(testRoot);
+
+ try {
+ rmSync(tmpPath);
+ } catch (e) {
+ console.error('Can\'t clean tmpdir:', tmpPath);
+
+ const files = fs.readdirSync(tmpPath);
+ console.error('Files blocking:', files);
+
+ if (files.some((f) => f.startsWith('.nfs'))) {
+ // Warn about NFS "silly rename"
+ console.error('Note: ".nfs*" might be files that were open and ' +
+ 'unlinked but not closed.');
+ console.error('See http://nfs.sourceforge.net/#faq_d2 for details.');
+ }
+
+ console.error();
+ throw e;
+ }
+}
+
+module.exports = {
+ path: tmpPath,
+ refresh
+};
diff --git a/cli/tests/node_compat/test/parallel/package.json b/cli/tests/node_compat/test/parallel/package.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/cli/tests/node_compat/test/parallel/package.json
@@ -0,0 +1 @@
+{}
diff --git a/cli/tests/node_compat/test/parallel/test-assert-fail.js b/cli/tests/node_compat/test/parallel/test-assert-fail.js
new file mode 100644
index 000000000..2aad9766d
--- /dev/null
+++ b/cli/tests/node_compat/test/parallel/test-assert-fail.js
@@ -0,0 +1,51 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.1
+// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually
+
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+
+// No args
+assert.throws(
+ () => { assert.fail(); },
+ {
+ code: 'ERR_ASSERTION',
+ name: 'AssertionError',
+ message: 'Failed',
+ operator: 'fail',
+ actual: undefined,
+ expected: undefined,
+ generatedMessage: true,
+ stack: /Failed/
+ }
+);
+
+// One arg = message
+assert.throws(() => {
+ assert.fail('custom message');
+}, {
+ code: 'ERR_ASSERTION',
+ name: 'AssertionError',
+ message: 'custom message',
+ operator: 'fail',
+ actual: undefined,
+ expected: undefined,
+ generatedMessage: false
+});
+
+// One arg = Error
+assert.throws(() => {
+ assert.fail(new TypeError('custom message'));
+}, {
+ name: 'TypeError',
+ message: 'custom message'
+});
+
+Object.prototype.get = common.mustNotCall();
+assert.throws(() => assert.fail(''), { code: 'ERR_ASSERTION' });
+delete Object.prototype.get;
diff --git a/cli/tests/node_compat/test/parallel/test-assert-strict-exists.js b/cli/tests/node_compat/test/parallel/test-assert-strict-exists.js
new file mode 100644
index 000000000..79139f1e5
--- /dev/null
+++ b/cli/tests/node_compat/test/parallel/test-assert-strict-exists.js
@@ -0,0 +1,13 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.1
+// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually
+
+'use strict';
+
+require('../common');
+const assert = require('assert');
+
+assert.strictEqual(require('assert/strict'), assert.strict);
diff --git a/cli/tests/node_compat/test/parallel/test-btoa-atob.js b/cli/tests/node_compat/test/parallel/test-btoa-atob.js
new file mode 100644
index 000000000..b17f4d2a6
--- /dev/null
+++ b/cli/tests/node_compat/test/parallel/test-btoa-atob.js
@@ -0,0 +1,46 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.1
+// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually
+
+'use strict';
+
+require('../common');
+
+const { strictEqual, throws } = require('assert');
+const buffer = require('buffer');
+
+// Exported on the global object
+strictEqual(globalThis.atob, buffer.atob);
+strictEqual(globalThis.btoa, buffer.btoa);
+
+// Throws type error on no argument passed
+throws(() => buffer.atob(), /TypeError/);
+throws(() => buffer.btoa(), /TypeError/);
+
+strictEqual(atob(' '), '');
+strictEqual(atob(' Y\fW\tJ\njZ A=\r= '), 'abcd');
+
+strictEqual(atob(null), '\x9Eée');
+strictEqual(atob(NaN), '5£');
+strictEqual(atob(Infinity), '"wâ\x9E+r');
+strictEqual(atob(true), '¶»\x9E');
+strictEqual(atob(1234), '×mø');
+strictEqual(atob([]), '');
+strictEqual(atob({ toString: () => '' }), '');
+strictEqual(atob({ [Symbol.toPrimitive]: () => '' }), '');
+
+throws(() => atob(Symbol()), /TypeError/);
+[
+ undefined, false, () => {}, {}, [1],
+ 0, 1, 0n, 1n, -Infinity,
+ 'a', 'a\n\n\n', '\ra\r\r', ' a ', '\t\t\ta', 'a\f\f\f', '\ta\r \n\f',
+].forEach((value) =>
+ // See #2 - https://html.spec.whatwg.org/multipage/webappapis.html#dom-atob
+ throws(() => atob(value), {
+ constructor: DOMException,
+ name: 'InvalidCharacterError',
+ code: 5,
+ }));