mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
897159dc6e
rewrite vm implementation to increase compat. vm.Module+importModuleDynamically callbacks should be added in a followup.
526 lines
13 KiB
JavaScript
526 lines
13 KiB
JavaScript
// 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 { spawn } = require('child_process');
|
|
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,
|
|
sessionStorage,
|
|
setImmediate,
|
|
];
|
|
|
|
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(',').map((name) => global[name]);
|
|
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', []];
|
|
|
|
function spawnPromisified(...args) {
|
|
let stderr = '';
|
|
let stdout = '';
|
|
|
|
const child = spawn(...args);
|
|
child.stderr.setEncoding('utf8');
|
|
child.stderr.on('data', (data) => { stderr += data; });
|
|
child.stdout.setEncoding('utf8');
|
|
child.stdout.on('data', (data) => { stdout += data; });
|
|
|
|
return new Promise((resolve, reject) => {
|
|
child.on('close', (code, signal) => {
|
|
resolve({
|
|
code,
|
|
signal,
|
|
stderr,
|
|
stdout,
|
|
});
|
|
});
|
|
child.on('error', (code, signal) => {
|
|
reject({
|
|
code,
|
|
signal,
|
|
stderr,
|
|
stdout,
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
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,
|
|
spawnPromisified,
|
|
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;
|
|
},
|
|
};
|