summaryrefslogtreecommitdiff
path: root/tests/node_compat/test/common
diff options
context:
space:
mode:
Diffstat (limited to 'tests/node_compat/test/common')
-rw-r--r--tests/node_compat/test/common/child_process.js56
-rw-r--r--tests/node_compat/test/common/countdown.js35
-rw-r--r--tests/node_compat/test/common/dns.js327
-rw-r--r--tests/node_compat/test/common/duplexpair.js55
-rw-r--r--tests/node_compat/test/common/fixtures.js45
-rw-r--r--tests/node_compat/test/common/hijackstdio.js39
-rw-r--r--tests/node_compat/test/common/index.js496
-rw-r--r--tests/node_compat/test/common/index.mjs115
-rw-r--r--tests/node_compat/test/common/internet.js68
-rw-r--r--tests/node_compat/test/common/package.json1
-rw-r--r--tests/node_compat/test/common/tmpdir.js79
11 files changed, 1316 insertions, 0 deletions
diff --git a/tests/node_compat/test/common/child_process.js b/tests/node_compat/test/common/child_process.js
new file mode 100644
index 000000000..b860d7697
--- /dev/null
+++ b/tests/node_compat/test/common/child_process.js
@@ -0,0 +1,56 @@
+// 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 `tools/node_compat/setup.ts`. Do not modify this file manually.
+
+'use strict';
+
+const assert = require('assert');
+const common = require('./');
+
+// Workaround for Windows Server 2008R2
+// When CMD is used to launch a process and CMD is killed too quickly, the
+// process can stay behind running in suspended state, never completing.
+function cleanupStaleProcess(filename) {
+ if (!common.isWindows) {
+ return;
+ }
+ process.once('beforeExit', () => {
+ const basename = filename.replace(/.*[/\\]/g, '');
+ try {
+ require('child_process')
+ .execFileSync(`${process.env.SystemRoot}\\System32\\wbem\\WMIC.exe`, [
+ 'process',
+ 'where',
+ `commandline like '%${basename}%child'`,
+ 'delete',
+ '/nointeractive',
+ ]);
+ } catch {
+ // Ignore failures, there might not be any stale process to clean up.
+ }
+ });
+}
+
+// This should keep the child process running long enough to expire
+// the timeout.
+const kExpiringChildRunTime = common.platformTimeout(20 * 1000);
+const kExpiringParentTimer = 1;
+assert(kExpiringChildRunTime > kExpiringParentTimer);
+
+function logAfterTime(time) {
+ setTimeout(() => {
+ // The following console statements are part of the test.
+ console.log('child stdout');
+ console.error('child stderr');
+ }, time);
+}
+
+module.exports = {
+ cleanupStaleProcess,
+ logAfterTime,
+ kExpiringChildRunTime,
+ kExpiringParentTimer,
+};
diff --git a/tests/node_compat/test/common/countdown.js b/tests/node_compat/test/common/countdown.js
new file mode 100644
index 000000000..a7ae0d029
--- /dev/null
+++ b/tests/node_compat/test/common/countdown.js
@@ -0,0 +1,35 @@
+// 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 `tools/node_compat/setup.ts`. Do not modify this file manually.
+
+'use strict';
+
+const assert = require('assert');
+const kLimit = Symbol('limit');
+const kCallback = Symbol('callback');
+const common = require('./');
+
+class Countdown {
+ constructor(limit, cb) {
+ assert.strictEqual(typeof limit, 'number');
+ assert.strictEqual(typeof cb, 'function');
+ this[kLimit] = limit;
+ this[kCallback] = common.mustCall(cb);
+ }
+
+ dec() {
+ assert(this[kLimit] > 0, 'Countdown expired');
+ if (--this[kLimit] === 0)
+ this[kCallback]();
+ return this[kLimit];
+ }
+
+ get remaining() {
+ return this[kLimit];
+ }
+}
+
+module.exports = Countdown;
diff --git a/tests/node_compat/test/common/dns.js b/tests/node_compat/test/common/dns.js
new file mode 100644
index 000000000..54df6a55e
--- /dev/null
+++ b/tests/node_compat/test/common/dns.js
@@ -0,0 +1,327 @@
+// 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 `tools/node_compat/setup.ts`. Do not modify this file manually.
+
+'use strict';
+
+const assert = require('assert');
+const os = require('os');
+
+const types = {
+ A: 1,
+ AAAA: 28,
+ NS: 2,
+ CNAME: 5,
+ SOA: 6,
+ PTR: 12,
+ MX: 15,
+ TXT: 16,
+ ANY: 255,
+ CAA: 257,
+};
+
+const classes = {
+ IN: 1,
+};
+
+// Naïve DNS parser/serializer.
+
+function readDomainFromPacket(buffer, offset) {
+ assert.ok(offset < buffer.length);
+ const length = buffer[offset];
+ if (length === 0) {
+ return { nread: 1, domain: '' };
+ } else if ((length & 0xC0) === 0) {
+ offset += 1;
+ const chunk = buffer.toString('ascii', offset, offset + length);
+ // Read the rest of the domain.
+ const { nread, domain } = readDomainFromPacket(buffer, offset + length);
+ return {
+ nread: 1 + length + nread,
+ domain: domain ? `${chunk}.${domain}` : chunk,
+ };
+ }
+ // Pointer to another part of the packet.
+ assert.strictEqual(length & 0xC0, 0xC0);
+ // eslint-disable-next-line space-infix-ops, space-unary-ops
+ const pointeeOffset = buffer.readUInt16BE(offset) &~ 0xC000;
+ return {
+ nread: 2,
+ domain: readDomainFromPacket(buffer, pointeeOffset),
+ };
+}
+
+function parseDNSPacket(buffer) {
+ assert.ok(buffer.length > 12);
+
+ const parsed = {
+ id: buffer.readUInt16BE(0),
+ flags: buffer.readUInt16BE(2),
+ };
+
+ const counts = [
+ ['questions', buffer.readUInt16BE(4)],
+ ['answers', buffer.readUInt16BE(6)],
+ ['authorityAnswers', buffer.readUInt16BE(8)],
+ ['additionalRecords', buffer.readUInt16BE(10)],
+ ];
+
+ let offset = 12;
+ for (const [ sectionName, count ] of counts) {
+ parsed[sectionName] = [];
+ for (let i = 0; i < count; ++i) {
+ const { nread, domain } = readDomainFromPacket(buffer, offset);
+ offset += nread;
+
+ const type = buffer.readUInt16BE(offset);
+
+ const rr = {
+ domain,
+ cls: buffer.readUInt16BE(offset + 2),
+ };
+ offset += 4;
+
+ for (const name in types) {
+ if (types[name] === type)
+ rr.type = name;
+ }
+
+ if (sectionName !== 'questions') {
+ rr.ttl = buffer.readInt32BE(offset);
+ const dataLength = buffer.readUInt16BE(offset);
+ offset += 6;
+
+ switch (type) {
+ case types.A:
+ assert.strictEqual(dataLength, 4);
+ rr.address = `${buffer[offset + 0]}.${buffer[offset + 1]}.` +
+ `${buffer[offset + 2]}.${buffer[offset + 3]}`;
+ break;
+ case types.AAAA:
+ assert.strictEqual(dataLength, 16);
+ rr.address = buffer.toString('hex', offset, offset + 16)
+ .replace(/(.{4}(?!$))/g, '$1:');
+ break;
+ case types.TXT:
+ {
+ let position = offset;
+ rr.entries = [];
+ while (position < offset + dataLength) {
+ const txtLength = buffer[offset];
+ rr.entries.push(buffer.toString('utf8',
+ position + 1,
+ position + 1 + txtLength));
+ position += 1 + txtLength;
+ }
+ assert.strictEqual(position, offset + dataLength);
+ break;
+ }
+ case types.MX:
+ {
+ rr.priority = buffer.readInt16BE(buffer, offset);
+ offset += 2;
+ const { nread, domain } = readDomainFromPacket(buffer, offset);
+ rr.exchange = domain;
+ assert.strictEqual(nread, dataLength);
+ break;
+ }
+ case types.NS:
+ case types.CNAME:
+ case types.PTR:
+ {
+ const { nread, domain } = readDomainFromPacket(buffer, offset);
+ rr.value = domain;
+ assert.strictEqual(nread, dataLength);
+ break;
+ }
+ case types.SOA:
+ {
+ const mname = readDomainFromPacket(buffer, offset);
+ const rname = readDomainFromPacket(buffer, offset + mname.nread);
+ rr.nsname = mname.domain;
+ rr.hostmaster = rname.domain;
+ const trailerOffset = offset + mname.nread + rname.nread;
+ rr.serial = buffer.readUInt32BE(trailerOffset);
+ rr.refresh = buffer.readUInt32BE(trailerOffset + 4);
+ rr.retry = buffer.readUInt32BE(trailerOffset + 8);
+ rr.expire = buffer.readUInt32BE(trailerOffset + 12);
+ rr.minttl = buffer.readUInt32BE(trailerOffset + 16);
+
+ assert.strictEqual(trailerOffset + 20, dataLength);
+ break;
+ }
+ default:
+ throw new Error(`Unknown RR type ${rr.type}`);
+ }
+ offset += dataLength;
+ }
+
+ parsed[sectionName].push(rr);
+
+ assert.ok(offset <= buffer.length);
+ }
+ }
+
+ assert.strictEqual(offset, buffer.length);
+ return parsed;
+}
+
+function writeIPv6(ip) {
+ const parts = ip.replace(/^:|:$/g, '').split(':');
+ const buf = Buffer.alloc(16);
+
+ let offset = 0;
+ for (const part of parts) {
+ if (part === '') {
+ offset += 16 - 2 * (parts.length - 1);
+ } else {
+ buf.writeUInt16BE(parseInt(part, 16), offset);
+ offset += 2;
+ }
+ }
+
+ return buf;
+}
+
+function writeDomainName(domain) {
+ return Buffer.concat(domain.split('.').map((label) => {
+ assert(label.length < 64);
+ return Buffer.concat([
+ Buffer.from([label.length]),
+ Buffer.from(label, 'ascii'),
+ ]);
+ }).concat([Buffer.alloc(1)]));
+}
+
+function writeDNSPacket(parsed) {
+ const buffers = [];
+ const kStandardResponseFlags = 0x8180;
+
+ buffers.push(new Uint16Array([
+ parsed.id,
+ parsed.flags === undefined ? kStandardResponseFlags : parsed.flags,
+ parsed.questions && parsed.questions.length,
+ parsed.answers && parsed.answers.length,
+ parsed.authorityAnswers && parsed.authorityAnswers.length,
+ parsed.additionalRecords && parsed.additionalRecords.length,
+ ]));
+
+ for (const q of parsed.questions) {
+ assert(types[q.type]);
+ buffers.push(writeDomainName(q.domain));
+ buffers.push(new Uint16Array([
+ types[q.type],
+ q.cls === undefined ? classes.IN : q.cls,
+ ]));
+ }
+
+ for (const rr of [].concat(parsed.answers,
+ parsed.authorityAnswers,
+ parsed.additionalRecords)) {
+ if (!rr) continue;
+
+ assert(types[rr.type]);
+ buffers.push(writeDomainName(rr.domain));
+ buffers.push(new Uint16Array([
+ types[rr.type],
+ rr.cls === undefined ? classes.IN : rr.cls,
+ ]));
+ buffers.push(new Int32Array([rr.ttl]));
+
+ const rdLengthBuf = new Uint16Array(1);
+ buffers.push(rdLengthBuf);
+
+ switch (rr.type) {
+ case 'A':
+ rdLengthBuf[0] = 4;
+ buffers.push(new Uint8Array(rr.address.split('.')));
+ break;
+ case 'AAAA':
+ rdLengthBuf[0] = 16;
+ buffers.push(writeIPv6(rr.address));
+ break;
+ case 'TXT': {
+ const total = rr.entries.map((s) => s.length).reduce((a, b) => a + b);
+ // Total length of all strings + 1 byte each for their lengths.
+ rdLengthBuf[0] = rr.entries.length + total;
+ for (const txt of rr.entries) {
+ buffers.push(new Uint8Array([Buffer.byteLength(txt)]));
+ buffers.push(Buffer.from(txt));
+ }
+ break;
+ }
+ case 'MX':
+ rdLengthBuf[0] = 2;
+ buffers.push(new Uint16Array([rr.priority]));
+ // fall through
+ case 'NS':
+ case 'CNAME':
+ case 'PTR':
+ {
+ const domain = writeDomainName(rr.exchange || rr.value);
+ rdLengthBuf[0] += domain.length;
+ buffers.push(domain);
+ break;
+ }
+ case 'SOA':
+ {
+ const mname = writeDomainName(rr.nsname);
+ const rname = writeDomainName(rr.hostmaster);
+ rdLengthBuf[0] = mname.length + rname.length + 20;
+ buffers.push(mname, rname);
+ buffers.push(new Uint32Array([
+ rr.serial, rr.refresh, rr.retry, rr.expire, rr.minttl,
+ ]));
+ break;
+ }
+ case 'CAA':
+ {
+ rdLengthBuf[0] = 5 + rr.issue.length + 2;
+ buffers.push(Buffer.from([Number(rr.critical)]));
+ buffers.push(Buffer.from([Number(5)]));
+ buffers.push(Buffer.from('issue' + rr.issue));
+ break;
+ }
+ default:
+ throw new Error(`Unknown RR type ${rr.type}`);
+ }
+ }
+
+ return Buffer.concat(buffers.map((typedArray) => {
+ const buf = Buffer.from(typedArray.buffer,
+ typedArray.byteOffset,
+ typedArray.byteLength);
+ if (os.endianness() === 'LE') {
+ if (typedArray.BYTES_PER_ELEMENT === 2) buf.swap16();
+ if (typedArray.BYTES_PER_ELEMENT === 4) buf.swap32();
+ }
+ return buf;
+ }));
+}
+
+const mockedErrorCode = 'ENOTFOUND';
+const mockedSysCall = 'getaddrinfo';
+
+function errorLookupMock(code = mockedErrorCode, syscall = mockedSysCall) {
+ return function lookupWithError(hostname, dnsopts, cb) {
+ const err = new Error(`${syscall} ${code} ${hostname}`);
+ err.code = code;
+ err.errno = code;
+ err.syscall = syscall;
+ err.hostname = hostname;
+ cb(err);
+ };
+}
+
+module.exports = {
+ types,
+ classes,
+ writeDNSPacket,
+ parseDNSPacket,
+ errorLookupMock,
+ mockedErrorCode,
+ mockedSysCall,
+};
diff --git a/tests/node_compat/test/common/duplexpair.js b/tests/node_compat/test/common/duplexpair.js
new file mode 100644
index 000000000..6e5286cc8
--- /dev/null
+++ b/tests/node_compat/test/common/duplexpair.js
@@ -0,0 +1,55 @@
+// 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 `tools/node_compat/setup.ts`. Do not modify this file manually.
+
+'use strict';
+const { Duplex } = require('stream');
+const assert = require('assert');
+
+const kCallback = Symbol('Callback');
+const kOtherSide = Symbol('Other');
+
+class DuplexSocket extends Duplex {
+ constructor() {
+ super();
+ this[kCallback] = null;
+ this[kOtherSide] = null;
+ }
+
+ _read() {
+ const callback = this[kCallback];
+ if (callback) {
+ this[kCallback] = null;
+ callback();
+ }
+ }
+
+ _write(chunk, encoding, callback) {
+ assert.notStrictEqual(this[kOtherSide], null);
+ assert.strictEqual(this[kOtherSide][kCallback], null);
+ if (chunk.length === 0) {
+ process.nextTick(callback);
+ } else {
+ this[kOtherSide].push(chunk);
+ this[kOtherSide][kCallback] = callback;
+ }
+ }
+
+ _final(callback) {
+ this[kOtherSide].on('end', callback);
+ this[kOtherSide].push(null);
+ }
+}
+
+function makeDuplexPair() {
+ const clientSide = new DuplexSocket();
+ const serverSide = new DuplexSocket();
+ clientSide[kOtherSide] = serverSide;
+ serverSide[kOtherSide] = clientSide;
+ return { clientSide, serverSide };
+}
+
+module.exports = makeDuplexPair;
diff --git a/tests/node_compat/test/common/fixtures.js b/tests/node_compat/test/common/fixtures.js
new file mode 100644
index 000000000..64b888eb6
--- /dev/null
+++ b/tests/node_compat/test/common/fixtures.js
@@ -0,0 +1,45 @@
+// 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 `tools/node_compat/setup.ts`. Do not modify this file manually.
+
+'use strict';
+
+const path = require('path');
+const fs = require('fs');
+const { pathToFileURL } = require('url');
+
+const fixturesDir = path.join(__dirname, '..', 'fixtures');
+
+function fixturesPath(...args) {
+ return path.join(fixturesDir, ...args);
+}
+
+function fixturesFileURL(...args) {
+ return pathToFileURL(fixturesPath(...args));
+}
+
+function readFixtureSync(args, enc) {
+ if (Array.isArray(args))
+ return fs.readFileSync(fixturesPath(...args), enc);
+ return fs.readFileSync(fixturesPath(args), enc);
+}
+
+function readFixtureKey(name, enc) {
+ return fs.readFileSync(fixturesPath('keys', name), enc);
+}
+
+function readFixtureKeys(enc, ...names) {
+ return names.map((name) => readFixtureKey(name, enc));
+}
+
+module.exports = {
+ fixturesDir,
+ path: fixturesPath,
+ fileURL: fixturesFileURL,
+ readSync: readFixtureSync,
+ readKey: readFixtureKey,
+ readKeys: readFixtureKeys,
+};
diff --git a/tests/node_compat/test/common/hijackstdio.js b/tests/node_compat/test/common/hijackstdio.js
new file mode 100644
index 000000000..38582ece2
--- /dev/null
+++ b/tests/node_compat/test/common/hijackstdio.js
@@ -0,0 +1,39 @@
+// 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 `tools/node_compat/setup.ts`. Do not modify this file manually.
+
+'use strict';
+
+// Hijack stdout and stderr
+const stdWrite = {};
+function hijackStdWritable(name, listener) {
+ const stream = process[name];
+ const _write = stdWrite[name] = stream.write;
+
+ stream.writeTimes = 0;
+ stream.write = function(data, callback) {
+ try {
+ listener(data);
+ } catch (e) {
+ process.nextTick(() => { throw e; });
+ }
+
+ _write.call(stream, data, callback);
+ stream.writeTimes++;
+ };
+}
+
+function restoreWritable(name) {
+ process[name].write = stdWrite[name];
+ delete process[name].writeTimes;
+}
+
+module.exports = {
+ hijackStdout: hijackStdWritable.bind(null, 'stdout'),
+ hijackStderr: hijackStdWritable.bind(null, 'stderr'),
+ restoreStdout: restoreWritable.bind(null, 'stdout'),
+ restoreStderr: restoreWritable.bind(null, 'stderr'),
+};
diff --git a/tests/node_compat/test/common/index.js b/tests/node_compat/test/common/index.js
new file mode 100644
index 000000000..9f5b4814c
--- /dev/null
+++ b/tests/node_compat/test/common/index.js
@@ -0,0 +1,496 @@
+// 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,
+ createImageBitmap,
+ crypto,
+ Deno,
+ dispatchEvent,
+ EventSource,
+ fetch,
+ getParent,
+ global,
+ global.clearInterval,
+ global.clearTimeout,
+ global.setInterval,
+ global.setTimeout,
+ localStorage,
+ location,
+ name,
+ 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,
+ hasIntl: true,
+ hasMultiLocalhost() {
+ return false;
+ },
+ 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/tests/node_compat/test/common/index.mjs b/tests/node_compat/test/common/index.mjs
new file mode 100644
index 000000000..25fe5cbb0
--- /dev/null
+++ b/tests/node_compat/test/common/index.mjs
@@ -0,0 +1,115 @@
+// 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 `tools/node_compat/setup.ts`. Do not modify this file manually.
+
+import { createRequire } from 'module';
+
+const require = createRequire(import.meta.url);
+const common = require('./index.js');
+
+const {
+ isMainThread,
+ isWindows,
+ isAIX,
+ isIBMi,
+ isLinuxPPCBE,
+ isSunOS,
+ isDumbTerminal,
+ isFreeBSD,
+ isOpenBSD,
+ isLinux,
+ isOSX,
+ enoughTestMem,
+ buildType,
+ localIPv6Hosts,
+ opensslCli,
+ PIPE,
+ hasCrypto,
+ hasIPv6,
+ childShouldThrowAndAbort,
+ checkoutEOL,
+ createZeroFilledFile,
+ platformTimeout,
+ allowGlobals,
+ mustCall,
+ mustCallAtLeast,
+ mustSucceed,
+ hasMultiLocalhost,
+ skipIfDumbTerminal,
+ skipIfEslintMissing,
+ canCreateSymLink,
+ getCallSite,
+ mustNotCall,
+ mustNotMutateObjectDeep,
+ parseTestFlags,
+ printSkipMessage,
+ skip,
+ nodeProcessAborted,
+ isAlive,
+ expectWarning,
+ expectsError,
+ skipIfInspectorDisabled,
+ skipIf32Bits,
+ getArrayBufferViews,
+ getBufferSources,
+ getTTYfd,
+ runWithInvalidFD,
+ spawnPromisified,
+} = common;
+
+const getPort = () => common.PORT;
+
+export {
+ isMainThread,
+ isWindows,
+ isAIX,
+ isIBMi,
+ isLinuxPPCBE,
+ isSunOS,
+ isDumbTerminal,
+ isFreeBSD,
+ isOpenBSD,
+ isLinux,
+ isOSX,
+ enoughTestMem,
+ buildType,
+ localIPv6Hosts,
+ opensslCli,
+ PIPE,
+ hasCrypto,
+ hasIPv6,
+ childShouldThrowAndAbort,
+ checkoutEOL,
+ createZeroFilledFile,
+ platformTimeout,
+ allowGlobals,
+ mustCall,
+ mustCallAtLeast,
+ mustSucceed,
+ hasMultiLocalhost,
+ skipIfDumbTerminal,
+ skipIfEslintMissing,
+ canCreateSymLink,
+ getCallSite,
+ mustNotCall,
+ mustNotMutateObjectDeep,
+ parseTestFlags,
+ printSkipMessage,
+ skip,
+ nodeProcessAborted,
+ isAlive,
+ expectWarning,
+ expectsError,
+ skipIfInspectorDisabled,
+ skipIf32Bits,
+ getArrayBufferViews,
+ getBufferSources,
+ getTTYfd,
+ runWithInvalidFD,
+ createRequire,
+ spawnPromisified,
+ getPort,
+};
diff --git a/tests/node_compat/test/common/internet.js b/tests/node_compat/test/common/internet.js
new file mode 100644
index 000000000..b42fda66c
--- /dev/null
+++ b/tests/node_compat/test/common/internet.js
@@ -0,0 +1,68 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.0
+// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually
+
+'use strict';
+
+// Utilities for internet-related tests
+
+const addresses = {
+ // A generic host that has registered common DNS records,
+ // supports both IPv4 and IPv6, and provides basic HTTP/HTTPS services
+ INET_HOST: 'nodejs.org',
+ // A host that provides IPv4 services
+ INET4_HOST: 'nodejs.org',
+ // A host that provides IPv6 services
+ INET6_HOST: 'nodejs.org',
+ // An accessible IPv4 IP,
+ // defaults to the Google Public DNS IPv4 address
+ INET4_IP: '8.8.8.8',
+ // An accessible IPv6 IP,
+ // defaults to the Google Public DNS IPv6 address
+ INET6_IP: '2001:4860:4860::8888',
+ // An invalid host that cannot be resolved
+ // See https://tools.ietf.org/html/rfc2606#section-2
+ INVALID_HOST: 'something.invalid',
+ // A host with MX records registered
+ MX_HOST: 'nodejs.org',
+ // On some systems, .invalid returns a server failure/try again rather than
+ // record not found. Use this to guarantee record not found.
+ NOT_FOUND: 'come.on.fhqwhgads.test',
+ // A host with SRV records registered
+ // TODO(kt3k): Temporarily use _caldav._tcp.google.com instead of
+ // _jabber._tcp.google.com, which currently doesn't respond
+ // SRV_HOST: '_jabber._tcp.google.com',
+ SRV_HOST: '_caldav._tcp.google.com',
+ // A host with PTR records registered
+ PTR_HOST: '8.8.8.8.in-addr.arpa',
+ // A host with NAPTR records registered
+ NAPTR_HOST: 'sip2sip.info',
+ // A host with SOA records registered
+ SOA_HOST: 'nodejs.org',
+ // A host with CAA record registered
+ CAA_HOST: 'google.com',
+ // A host with CNAME records registered
+ CNAME_HOST: 'blog.nodejs.org',
+ // A host with NS records registered
+ NS_HOST: 'nodejs.org',
+ // A host with TXT records registered
+ TXT_HOST: 'nodejs.org',
+ // An accessible IPv4 DNS server
+ DNS4_SERVER: '8.8.8.8',
+ // An accessible IPv4 DNS server
+ DNS6_SERVER: '2001:4860:4860::8888'
+};
+
+for (const key of Object.keys(addresses)) {
+ const envName = `NODE_TEST_${key}`;
+ if (process.env[envName]) {
+ addresses[key] = process.env[envName];
+ }
+}
+
+module.exports = {
+ addresses
+};
diff --git a/tests/node_compat/test/common/package.json b/tests/node_compat/test/common/package.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/tests/node_compat/test/common/package.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/node_compat/test/common/tmpdir.js b/tests/node_compat/test/common/tmpdir.js
new file mode 100644
index 000000000..886c4a107
--- /dev/null
+++ b/tests/node_compat/test/common/tmpdir.js
@@ -0,0 +1,79 @@
+// 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 `tools/node_compat/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(tmpPath);
+ fs.mkdirSync(tmpPath);
+
+ 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
+ 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;
+ }
+}
+
+function resolve(...paths) {
+ return path.resolve(tmpPath, ...paths);
+}
+
+function hasEnoughSpace(size) {
+ const { bavail, bsize } = fs.statfsSync(tmpPath);
+ return bavail >= Math.ceil(size / bsize);
+}
+
+module.exports = {
+ path: tmpPath,
+ refresh,
+ hasEnoughSpace,
+ resolve,
+};