diff --git a/src/cares_sys/c_ares.rs b/src/cares_sys/c_ares.rs index 2be42bc9b582..5321d0e49f3e 100644 --- a/src/cares_sys/c_ares.rs +++ b/src/cares_sys/c_ares.rs @@ -751,11 +751,18 @@ impl Channel { sock_state_cb_data: std::ptr::from_ref::(this).cast_mut().cast::(), timeout: options.timeout.unwrap_or(-1), tries: options.tries.unwrap_or(4), + // Node turns c-ares' answer cache off (its default max TTL is one + // hour), so a resolver in Bun must not serve stale answers either. + // https://github.com/nodejs/node/blob/v26.3.0/src/cares_wrap.cc#L897 + qcache_max_ttl: 0, ..Default::default() }; - let optmask: c_int = - ARES_OPT_FLAGS | ARES_OPT_TIMEOUTMS | ARES_OPT_SOCK_STATE_CB | ARES_OPT_TRIES; + let optmask: c_int = ARES_OPT_FLAGS + | ARES_OPT_TIMEOUTMS + | ARES_OPT_SOCK_STATE_CB + | ARES_OPT_TRIES + | ARES_OPT_QUERY_CACHE; // SAFETY: idempotent Winsock init (uv_once); c-ares creates its sockets with // ws2_32 directly and libuv otherwise initializes Winsock lazily. @@ -1817,6 +1824,7 @@ pub(crate) const ARES_OPT_FLAGS: c_int = 1 << 0; pub(crate) const ARES_OPT_TRIES: c_int = 1 << 2; pub(crate) const ARES_OPT_SOCK_STATE_CB: c_int = 1 << 9; pub(crate) const ARES_OPT_TIMEOUTMS: c_int = 1 << 13; +pub(crate) const ARES_OPT_QUERY_CACHE: c_int = 1 << 21; pub(crate) const ARES_NI_NAMEREQD: c_int = 1 << 2; pub(crate) const ARES_NI_LOOKUPHOST: c_int = 1 << 8; pub(crate) const ARES_NI_LOOKUPSERVICE: c_int = 1 << 9; diff --git a/src/js/internal-for-testing.ts b/src/js/internal-for-testing.ts index 215190f16398..8e350b683fd3 100644 --- a/src/js/internal-for-testing.ts +++ b/src/js/internal-for-testing.ts @@ -1,11 +1,7 @@ // Hardcoded module "bun:internal-for-testing" -// If you want to test an internal API, add a binding into this file. -// -// Then at test time: import ... from "bun:internal-for-testing" -// -// In a debug build, the import is always allowed. -// It is disallowed in release builds unless run in Bun's CI. +// Add bindings for internal APIs here and `import ... from "bun:internal-for-testing"`. +// Always allowed in debug builds; in release builds only under Bun's CI. const fmtBinding = $bindgenFn("fmt_jsc.bind.ts", "fmtString"); @@ -169,10 +165,9 @@ export const setSyntheticAllocationLimitForTesting: (limit: number) => number = 1, ); -// Shrink the markdown parser's block-metadata cap (in bytes) so its -// `TooManyBlocks` error is reachable without 4 GiB of input. The cap can only -// be lowered, never raised past the real limit. Returns the previous value so -// a test can restore it. +// Lower the markdown parser's block-metadata cap (bytes) so `TooManyBlocks` is +// reachable without 4 GiB of input. Can only lower, never raise past the real +// limit; returns the previous value so a test can restore it. export const setMaxMarkdownBlockBytesForTesting: (limit: number) => number = $newRustFunction( "MarkdownObject.rs", "setMaxMarkdownBlockBytesForTesting", @@ -323,6 +318,19 @@ function assignFunctionName(name, fn, descriptor = nodeKEmptyObject) { }); } +// No-op emitter (Bun handles --pending-deprecation natively) but preserves `length`. +// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/util.js#L204 +function nodePendingDeprecate(fn) { + function deprecated(...args) { + return fn.$apply(this, args); + } + Object.defineProperty(deprecated, "length", { + __proto__: null, + ...Object.getOwnPropertyDescriptor(fn, "length"), + }); + return deprecated; +} + function nodeIsError(e) { return require("node:util/types").isNativeError(e) || e instanceof Error; } @@ -448,11 +456,9 @@ function nodeGetValidStdio(stdio, sync?) { let cachedInternalChildProcess; -// Userland access to node-internal modules for vendored node tests that -// declare `// Flags: --expose-internals` (served via the require interceptor -// in test/js/node/test/common/index.js). Static requires only — the builtin -// bundler cannot rewrite variable-path requires. Extend the map as more -// vendored tests need more internals. +// Node-internal modules for vendored tests with `// Flags: --expose-internals` +// (served via test/js/node/test/common/index.js). Static requires only — the +// builtin bundler cannot rewrite variable-path requires. export const exposedInternals = { "internal/streams/add-abort-signal": require("internal/streams/add-abort-signal"), "internal/util/debuglog": require("internal/util/debuglog"), @@ -460,6 +466,7 @@ export const exposedInternals = { "internal/async_hooks": require("internal/async_hooks"), "internal/webstreams/adapters": require("internal/webstreams_adapters"), "internal/dgram": require("internal/dgram"), + "internal/timers": require("internal/timers"), // Bun's real implementations, under the names node's tests import them by. "internal/validators": require("internal/validators"), "internal/util/inspect": require("internal/util/inspect"), @@ -479,6 +486,7 @@ export const exposedInternals = { assertCrypto() {}, getCIDR, isError: nodeIsError, + pendingDeprecate: nodePendingDeprecate, assignFunctionName, kEnumerableProperty: Object.freeze({ __proto__: null, enumerable: true }), kEmptyObject: nodeKEmptyObject, @@ -502,6 +510,10 @@ export const exposedInternals = { getValidStdio: nodeGetValidStdio, }); }, + // translatePeerCertificate lives in node:_tls_common in Bun; upstream keeps + // the implementation in internal/tls/common and re-exports it from + // _tls_common, which is where node's tests import it from. + "internal/tls/common": require("internal/tls/common"), "internal/fs/utils": { // Both are the REAL parsers the fs entry points use (FileSystemFlags::from_js // and args::Rm::from_js), not JS reimplementations -- vendored tests assert @@ -513,12 +525,9 @@ export const exposedInternals = { // module (src/js/internal/test/binding.ts), not from here. }; -// State of a web ReadableStream/WritableStream for vendored node tests that -// read Node's `stream[kState].state` / `.storedError` (served through the -// internal/webstreams/util shim in test/js/node/test/common/index.js). -// The stream's closed promise is settled from every terminal transition, so its -// status is the state. A WritableStream mid-`erroring` still reports "writable": -// erroring is not terminal, and nothing observable distinguishes the two here. +// Web stream state for vendored node tests that read `stream[kState].state`. +// The closed promise's status is the state; a mid-`erroring` WritableStream +// reports "writable" (erroring is non-terminal, nothing observable differs). export function getWebStreamState(stream: ReadableStream | WritableStream): { state: string; storedError: unknown; diff --git a/src/js/internal/timers.ts b/src/js/internal/timers.ts index ef48040fc6b8..f4ee968aff14 100644 --- a/src/js/internal/timers.ts +++ b/src/js/internal/timers.ts @@ -1,4 +1,4 @@ -const { validateNumber } = require("internal/validators"); +const { validateFunction, validateNumber } = require("internal/validators"); const NumberIsFinite = Number.isFinite; @@ -22,11 +22,35 @@ function getTimerDuration(msecs, name) { return msecs; } +// node's internal helper for timers that must not hold the event loop open. +// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/timers.js#L370 +function setUnrefTimeout(callback, after, arg1, arg2, arg3) { + validateFunction(callback, "callback"); + + let timer; + switch (arguments.length) { + case 1: + case 2: + timer = setTimeout(callback, after); + break; + case 3: + timer = setTimeout(callback, after, arg1); + break; + case 4: + timer = setTimeout(callback, after, arg1, arg2); + break; + default: + timer = setTimeout(callback, after, arg1, arg2, arg3); + break; + } + return timer.unref(); +} + export default { - // For hiding Timeouts on other internals. A registered symbol so the node - // test harness's --expose-internals shim ("internal/timers" virtual module - // in test/js/node/test/common/index.js) can hand the same symbol to ported - // tests that inspect socket[kTimeout]. + TIMEOUT_MAX, + // Registered symbol so the --expose-internals shim (test/js/node/test/common/index.js) + // hands the same symbol to ported tests that inspect socket[kTimeout]. kTimeout: Symbol.for("::buntimeout::"), getTimerDuration, + setUnrefTimeout, }; diff --git a/src/js/internal/tls/common.ts b/src/js/internal/tls/common.ts new file mode 100644 index 000000000000..8cbf986b1def --- /dev/null +++ b/src/js/internal/tls/common.ts @@ -0,0 +1,30 @@ +// Translate some fields from the handle's C-friendly format into more idiomatic +// javascript object representations before passing them back to the user. Can +// be used on any cert object, but changing the name would be semver-major. +function translatePeerCertificate(c) { + if (!c) return null; + + if (c.issuerCertificate != null && c.issuerCertificate !== c) { + c.issuerCertificate = translatePeerCertificate(c.issuerCertificate); + } + if (c.infoAccess != null) { + const info = c.infoAccess; + const parsed = (c.infoAccess = Object.create(null)); + + // XXX: More key validation? + info.replace(/([^\n:]*):([^\n]*)(?:\n|$)/g, (all, key, val) => { + if (val.charCodeAt(0) === 0x22) { + // Only used on internally created legacy cert objects; quoted values + // are always valid JSON string literals, so this never throws. + val = JSON.parse(val); + } + if (key in parsed) parsed[key].push(val); + else parsed[key] = [val]; + }); + } + return c; +} + +export default { + translatePeerCertificate, +}; diff --git a/src/js/internal/util/deprecate.ts b/src/js/internal/util/deprecate.ts index b361030ebc01..14692470ad96 100644 --- a/src/js/internal/util/deprecate.ts +++ b/src/js/internal/util/deprecate.ts @@ -19,7 +19,10 @@ function getDeprecationWarningEmitter(code, msg, deprecated, shouldEmitWarning = }; } -function deprecate(fn, msg, code) { +// `modifyPrototype` mirrors node's internal deprecate() parameter of the same +// name, surfaced publicly as util.deprecate(fn, msg, code, { modifyPrototype }). +// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/util.js#L162 +function deprecate(fn, msg, code, modifyPrototype = true) { // Lazy-load to avoid a circular dependency. if (code !== undefined) validateString(code, "code"); @@ -35,23 +38,42 @@ function deprecate(fn, msg, code) { return fn.$apply(this, args); } - // The wrapper will keep the same prototype as fn to maintain prototype chain - Object.setPrototypeOf(deprecated, fn); - const fnPrototype = fn.prototype; - if (fnPrototype) { - // Sharing fn.prototype makes instanceof work across the wrapper. Use defineProperty: - // builtin-compiled wrappers lack an own "prototype", so a plain assignment would hit - // fn's non-writable "prototype" through the prototype chain. - Object.defineProperty(deprecated, "prototype", { + // Bun compiles builtin modules such that function declarations get no own + // "prototype" property; node's wrapper is a plain function declaration, so + // give it the same default shape before applying node's prototype handling. + Object.defineProperty(deprecated, "prototype", { + __proto__: null, + value: Object.defineProperty({}, "constructor", { __proto__: null, - value: fnPrototype, + value: deprecated, writable: true, + enumerable: false, + configurable: true, + }), + writable: true, + enumerable: false, + configurable: false, + }); + + if (modifyPrototype) { + // The wrapper will keep the same prototype as fn to maintain prototype chain + Object.setPrototypeOf(deprecated, fn); + const fnPrototype = fn.prototype; + if (fnPrototype) { + // Sharing fn.prototype makes instanceof work across the wrapper. Use defineProperty: + // builtin-compiled wrappers lack an own "prototype", so a plain assignment would hit + // fn's non-writable "prototype" through the prototype chain. + Object.defineProperty(deprecated, "prototype", { + __proto__: null, + value: fnPrototype, + writable: true, + }); + } + Object.defineProperty(deprecated, "length", { + __proto__: null, + ...Object.getOwnPropertyDescriptor(fn, "length"), }); } - Object.defineProperty(deprecated, "length", { - __proto__: null, - ...Object.getOwnPropertyDescriptor(fn, "length"), - }); return deprecated; } diff --git a/src/js/node/_tls_common.ts b/src/js/node/_tls_common.ts index ff212f76036e..13b7dd34b4b2 100644 --- a/src/js/node/_tls_common.ts +++ b/src/js/node/_tls_common.ts @@ -1,40 +1,11 @@ // Hardcoded module "node:_tls_common" -// Deprecated shim mirroring node's lib/_tls_common.js: re-exports the real -// implementations and warns on load. +// Deprecated shim: re-exports the real impls and warns on load. // https://github.com/nodejs/node/blob/v26.3.0/lib/_tls_common.js const { SecureContext, createSecureContext } = require("node:tls"); process.emitWarning("The _tls_common module is deprecated. Use `node:tls` instead.", "DeprecationWarning", "DEP0192"); -// Translate some fields from the handle's C-friendly format into more idiomatic -// javascript object representations before passing them back to the user. Can -// be used on any cert object, but changing the name would be semver-major. -function translatePeerCertificate(c) { - if (!c) return null; - - if (c.issuerCertificate != null && c.issuerCertificate !== c) { - c.issuerCertificate = translatePeerCertificate(c.issuerCertificate); - } - if (c.infoAccess != null) { - const info = c.infoAccess; - const parsed = (c.infoAccess = Object.create(null)); - - // XXX: More key validation? - info.replace(/([^\n:]*):([^\n]*)(?:\n|$)/g, (all, key, val) => { - if (val.charCodeAt(0) === 0x22) { - // The translatePeerCertificate function is only - // used on internally created legacy certificate - // objects, and any value that contains a quote - // will always be a valid JSON string literal, - // so this should never throw. - val = JSON.parse(val); - } - if (key in parsed) parsed[key].push(val); - else parsed[key] = [val]; - }); - } - return c; -} +const { translatePeerCertificate } = require("internal/tls/common"); export default { SecureContext, diff --git a/src/js/node/dns.ts b/src/js/node/dns.ts index 203bd87a9b5d..0e3302c5e247 100644 --- a/src/js/node/dns.ts +++ b/src/js/node/dns.ts @@ -10,6 +10,7 @@ const { validateBoolean, validateNumber, validateInt32, + validateOneOf, validatePort, } = require("internal/validators"); @@ -164,10 +165,10 @@ function validateFlagsOption(options) { } } +const validFamilies = [0, 4, 6]; + function validateFamily(family) { - if (family !== 6 && family !== 4 && family !== 0) { - throw $ERR_INVALID_ARG_VALUE("family", family, "must be one of 0, 4 or 6"); - } + validateOneOf(family, "family", validFamilies); } function validateFamilyOption(options) { @@ -180,7 +181,7 @@ function validateFamilyOption(options) { options.family = 6; break; default: - validateFamily(options.family); + validateOneOf(options.family, "options.family", validFamilies); break; } } @@ -213,11 +214,24 @@ function validateOrderOption(options) { } } +// rrtypes in node's resolveMap; anything else is ERR_INVALID_ARG_VALUE, checked first. +// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/dns/callback_resolver.js#L95 +const rrtypes = new Set(["A", "AAAA", "ANY", "CAA", "CNAME", "MX", "NAPTR", "NS", "PTR", "SOA", "SRV", "TXT"]); + +// Bun has always accepted rrtypes in any case; node only accepts the canonical +// spelling. Uppercasing here keeps that leniency rather than newly rejecting +// input that used to work. +function validateRrtype(rrtype) { + if (!rrtypes.has(rrtype.toUpperCase())) { + throw $ERR_INVALID_ARG_VALUE("rrtype", rrtype); + } +} + // Validates and returns the callback wrapped by guardCallback. // Callers must use the return value, not the argument. -function validateResolve(hostname, callback) { - if (typeof hostname !== "string") { - throw $ERR_INVALID_ARG_TYPE("hostname", "string", hostname); +function validateResolve(name, callback) { + if (typeof name !== "string") { + throw $ERR_INVALID_ARG_TYPE("name", "string", name); } if (typeof callback !== "function") { @@ -234,19 +248,6 @@ function validateLocalAddresses(first, second) { } } -function invalidHostname(hostname) { - if (invalidHostname.warned) { - return; - } - - invalidHostname.warned = true; - process.emitWarning( - `The provided hostname "${String(hostname)}" is not a valid hostname, and is supported in the dns module solely for compatibility.`, - "DeprecationWarning", - "DEP0118", - ); -} - function translateLookupOptions(options) { if (!options || typeof options !== "object") { options = { family: options }; @@ -302,13 +303,7 @@ function lookup(hostname, options, callback) { validateLookupOptions(options); if (!hostname) { - invalidHostname(hostname); - if (options.all) { - callback(null, []); - } else { - callback(null, null, 4); - } - return; + throw $ERR_INVALID_ARG_VALUE("hostname", hostname, "must be a non-empty string"); } const family = isIP(hostname); @@ -363,6 +358,9 @@ function lookupService(address, port, callback) { } validateString(address); + if (isIP(address) === 0) { + throw $ERR_INVALID_ARG_VALUE("address", address); + } validatePort(port, "port"); callback = guardCallback(callback); @@ -409,6 +407,7 @@ var InternalResolver = class Resolver { } else if (typeof rrtype !== "string") { throw $ERR_INVALID_ARG_TYPE("rrtype", "string", rrtype); } + validateRrtype(rrtype); callback = validateResolve(hostname, callback); @@ -602,10 +601,7 @@ var InternalResolver = class Resolver { if (arguments.length > 2) { callback = arguments[2]; } - if (typeof callback !== "function") { - throw $ERR_INVALID_ARG_TYPE("callback", "function", callback); - } - callback = guardCallback(callback); + callback = validateResolve(hostname, callback); Resolver.#getResolver(this) .resolveCaa(hostname) @@ -623,10 +619,7 @@ var InternalResolver = class Resolver { if (arguments.length > 2) { callback = arguments[2]; } - if (typeof callback !== "function") { - throw $ERR_INVALID_ARG_TYPE("callback", "function", callback); - } - callback = guardCallback(callback); + callback = validateResolve(hostname, callback); Resolver.#getResolver(this) .resolveTxt(hostname) @@ -643,10 +636,7 @@ var InternalResolver = class Resolver { if (arguments.length > 2) { callback = arguments[2]; } - if (typeof callback !== "function") { - throw $ERR_INVALID_ARG_TYPE("callback", "function", callback); - } - callback = guardCallback(callback); + callback = validateResolve(hostname, callback); Resolver.#getResolver(this) .resolveSoa(hostname) @@ -783,16 +773,11 @@ const promises = { options = translateLookupOptions(options); validateLookupOptions(options); + // Unlike the callback form, the promise form reports an empty hostname by + // rejecting: node only reaches this check inside createLookupPromise(). + // https://github.com/nodejs/node/blob/v26.3.0/lib/internal/dns/promises.js#L124 if (!hostname) { - invalidHostname(hostname); - return Promise.$resolve( - options.all - ? [] - : { - address: null, - family: 4, - }, - ); + return Promise.$reject($ERR_INVALID_ARG_VALUE("hostname", hostname, "must be a non-empty string")); } const family = isIP(hostname); @@ -813,6 +798,9 @@ const promises = { } validateString(address); + if (isIP(address) === 0) { + throw $ERR_INVALID_ARG_VALUE("address", address); + } validatePort(port, "port"); try { @@ -830,7 +818,7 @@ const promises = { resolve(hostname, rrtype) { if (typeof hostname !== "string") { - throw $ERR_INVALID_ARG_TYPE("hostname", "string", hostname); + throw $ERR_INVALID_ARG_TYPE("name", "string", hostname); } if (typeof rrtype === "undefined") { @@ -838,6 +826,7 @@ const promises = { } else if (typeof rrtype !== "string") { throw $ERR_INVALID_ARG_TYPE("rrtype", "string", rrtype); } + validateRrtype(rrtype); switch (rrtype?.toLowerCase()) { case "a": diff --git a/src/js/node/http2.ts b/src/js/node/http2.ts index cfd4991c0359..5153a19075ff 100644 --- a/src/js/node/http2.ts +++ b/src/js/node/http2.ts @@ -29,7 +29,7 @@ const { isTypedArray } = require("node:util/types"); const { hideFromStack, hasObserver, enqueueNodeEntry, PerformanceNodeEntry } = require("internal/shared"); const { STATUS_CODES } = require("internal/http"); -const { kTimeout, getTimerDuration } = require("internal/timers"); +const { kTimeout, getTimerDuration, setUnrefTimeout } = require("internal/timers"); const tls = require("node:tls"); const net = require("node:net"); const fs = require("node:fs"); @@ -4829,7 +4829,7 @@ function setSessionTimeout(this: Http2Session, msecs, callback) { // getter's drain-driven mirror). this[kTimeoutWrittenSnapshot] = this[bunHTTP2Socket]?._handle?.bytesWritten ?? this[bunHTTP2Socket]?.bytesWritten ?? 0; - this[kTimeout] = setTimeout(sessionTimerExpired, msecs, this).unref(); + this[kTimeout] = setUnrefTimeout(sessionTimerExpired, msecs, this); if (callback !== undefined) { validateFunction(callback, "callback"); this.once("timeout", callback); diff --git a/src/js/node/net.ts b/src/js/node/net.ts index 1e9d83b4dda9..645d3982435a 100644 --- a/src/js/node/net.ts +++ b/src/js/node/net.ts @@ -39,7 +39,7 @@ const { import type { Socket, SocketHandler, SocketListener } from "bun"; import type { Server as NetServer, Socket as NetSocket, ServerOpts } from "node:net"; import type { TLSSocket } from "node:tls"; -const { kTimeout, getTimerDuration } = require("internal/timers"); +const { kTimeout, getTimerDuration, setUnrefTimeout } = require("internal/timers"); const { validateFunction, validateNumber, validateAbortSignal, validatePort, validateBoolean, validateInt32, validateString } = require("internal/validators"); // prettier-ignore const { isIPv4, isIPv6, isIP } = require("internal/net/isIP"); const { kArmHandshakeTimeout, kSecureConnectDone, kVerifyError } = require("internal/net/symbols"); @@ -2687,7 +2687,7 @@ Socket.prototype.setTimeout = { this.removeListener("timeout", callback); } } else { - this[kTimeout] = setTimeout(this._onTimeout.bind(this), msecs).unref(); + this[kTimeout] = setUnrefTimeout(this._onTimeout.bind(this), msecs); if (callback !== undefined) { validateFunction(callback, "callback"); @@ -3314,7 +3314,7 @@ function internalConnectMultiple(context, canceled?) { $debug("connect/multiple: setting the attempt timeout to %d ms", context.timeout); // If the attempt has not returned an error, start the connection timer - context[kTimeout] = setTimeout(internalConnectMultipleTimeout, context.timeout, context, req, self._handle).unref(); + context[kTimeout] = setUnrefTimeout(internalConnectMultipleTimeout, context.timeout, context, req, self._handle); } } diff --git a/src/js/node/util.ts b/src/js/node/util.ts index df41487dcff3..a3aef3ca4532 100644 --- a/src/js/node/util.ts +++ b/src/js/node/util.ts @@ -12,7 +12,14 @@ const { } = require("internal/validators"); const { resistStopPropagation, ErrnoException } = require("internal/shared"); const { MIMEType, MIMEParams } = require("internal/util/mime"); -const { deprecate } = require("internal/util/deprecate"); +const { deprecate: internalDeprecate } = require("internal/util/deprecate"); + +// Public util.deprecate API. Node takes modifyPrototype as an option bag here +// and as a positional argument internally. +// https://github.com/nodejs/node/blob/v26.3.0/lib/util.js#L586 +function deprecate(fn, msg, code, { modifyPrototype } = { __proto__: null }) { + return internalDeprecate(fn, msg, code, modifyPrototype); +} const internalErrorName = $newRustFunction("node_util_binding.rs", "internalErrorName", 1); const internalErrorEntries = $newRustFunction("node_util_binding.rs", "internalErrorEntries", 0); @@ -33,9 +40,9 @@ function isFunction(value) { return typeof value === "function"; } -// Node semantics (includes the [[Prototype]] identity check Bun.deepEquals omits) plus the -// skipPrototype third argument, which is public API in node v26.3.0 (fn.length === 3). -// https://github.com/nodejs/node/blob/main/lib/internal/util/comparisons.js +// Node semantics: includes the [[Prototype]] identity check Bun.deepEquals omits, +// plus the public skipPrototype third arg (fn.length === 3 in node v26.3.0, +// asserted by upstream test-util-isDeepStrictEqual.js). const { isDeepStrictEqual } = require("internal/util/comparisons"); const parseArgs = $newRustFunction("parse_args.rs", "parseArgs", 1); diff --git a/src/jsc/bindings/bindings.cpp b/src/jsc/bindings/bindings.cpp index df2bd53c73c0..a4d40752ed6e 100644 --- a/src/jsc/bindings/bindings.cpp +++ b/src/jsc/bindings/bindings.cpp @@ -5632,6 +5632,12 @@ static void JSC__JSValue__forEachPropertyImpl(JSC::EncodedJSValue JSValue0, JSC: if (builtinNames.bunNativePtrPrivateName() == prop) return true; + // Don't echo back the util.inspect.custom hook the inspector just + // consulted. It is installed non-enumerable, which is also why node + // never prints it. + if ((entry.attributes() & PropertyAttribute::DontEnum) != 0 && builtinNames.inspectCustomPublicName() == prop) + return true; + if (visitedProperties.contains(Identifier::fromUid(vm, prop))) { return true; } @@ -5734,7 +5740,11 @@ static void JSC__JSValue__forEachPropertyImpl(JSC::EncodedJSValue JSValue0, JSC: if ((slot.attributes() & PropertyAttribute::DontEnum) != 0) { if (property == propertyNames->underscoreProto - || property == propertyNames->toStringTagSymbol || property == propertyNames->__esModule) + || property == propertyNames->toStringTagSymbol || property == propertyNames->__esModule + // Don't echo back the util.inspect.custom hook the + // inspector just consulted; node hides it for the same + // reason, because it is non-enumerable. + || property == builtinNames.inspectCustomPublicName()) continue; } diff --git a/src/jsc/bindings/webcore/JSCustomEvent.cpp b/src/jsc/bindings/webcore/JSCustomEvent.cpp index 16cc119536a7..f2b9cdadcc24 100644 --- a/src/jsc/bindings/webcore/JSCustomEvent.cpp +++ b/src/jsc/bindings/webcore/JSCustomEvent.cpp @@ -58,8 +58,7 @@ template<> CustomEvent::Init convertDictionary(JSGlobalObject bool isNullOrUndefined = value.isUndefinedOrNull(); auto* object = isNullOrUndefined ? nullptr : value.getObject(); if (!isNullOrUndefined && !object) [[unlikely]] { - Bun::throwError(&lexicalGlobalObject, throwScope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE, - "The \"options\" argument must be of type object."_s); + Bun::ERR::INVALID_ARG_TYPE(throwScope, &lexicalGlobalObject, "options"_s, "object"_s, value); return {}; } CustomEvent::Init result; diff --git a/src/jsc/bindings/webcore/JSEvent.cpp b/src/jsc/bindings/webcore/JSEvent.cpp index 35652840af84..7cee72c071a3 100644 --- a/src/jsc/bindings/webcore/JSEvent.cpp +++ b/src/jsc/bindings/webcore/JSEvent.cpp @@ -63,6 +63,7 @@ using namespace JSC; // Functions static JSC_DECLARE_HOST_FUNCTION(jsEventPrototypeFunction_composedPath); +static JSC_DECLARE_HOST_FUNCTION(jsEventPrototype_inspectCustom); static JSC_DECLARE_HOST_FUNCTION(jsEventPrototypeFunction_stopPropagation); static JSC_DECLARE_HOST_FUNCTION(jsEventPrototypeFunction_stopImmediatePropagation); static JSC_DECLARE_HOST_FUNCTION(jsEventPrototypeFunction_preventDefault); @@ -93,7 +94,7 @@ class JSEventPrototype final : public JSC::JSNonFinalObject { static JSEventPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure) { JSEventPrototype* ptr = new (NotNull, Bun::allocatePlainObjectCell(vm, sizeof(JSEventPrototype))) JSEventPrototype(vm, globalObject, structure); - ptr->finishCreation(vm); + ptr->finishCreation(vm, globalObject); return ptr; } @@ -115,7 +116,7 @@ class JSEventPrototype final : public JSC::JSNonFinalObject { { } - void finishCreation(JSC::VM&); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); }; STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSEventPrototype, JSEventPrototype::Base); @@ -223,13 +224,52 @@ static const HashTableValue JSEventPrototypeTableValues[] = { { "BUBBLING_PHASE"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { HashTableValue::ConstantType, 3 } }, }; +// depth < 0 collapses to the constructor name; otherwise return `this` so Bun's own formatting runs. +// https://github.com/nodejs/node/blob/v26.3.0/lib/internal/event_target.js#L200 +JSC_DEFINE_HOST_FUNCTION(jsEventPrototype_inspectCustom, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + JSValue thisValue = callFrame->thisValue(); + // Bun's inspector calls this on Event.prototype itself (node bails when + // value.constructor.prototype === value); hand it back instead of ERR_INVALID_THIS. + if (!JSEvent::toWrapped(vm, thisValue)) { + return JSValue::encode(thisValue); + } + + double depth = callFrame->argument(0).toNumber(lexicalGlobalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + if (!(depth < 0)) { + return JSValue::encode(thisValue); + } + + // Subclasses print their own name, so this reads `this.constructor.name` + // rather than the wrapper's class name. + String name = "Event"_s; + JSValue constructorValue = thisValue.getObject()->get(lexicalGlobalObject, vm.propertyNames->constructor); + RETURN_IF_EXCEPTION(throwScope, {}); + if (constructorValue.isObject()) { + JSValue nameValue = constructorValue.getObject()->get(lexicalGlobalObject, vm.propertyNames->name); + RETURN_IF_EXCEPTION(throwScope, {}); + if (nameValue.isString()) { + name = nameValue.toWTFString(lexicalGlobalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + } + } + return JSValue::encode(jsString(vm, name)); +} + const ClassInfo JSEventPrototype::s_info = { "Event"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSEventPrototype) }; -void JSEventPrototype::finishCreation(VM& vm) +void JSEventPrototype::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject) { Base::finishCreation(vm); Bun::reifyStaticPropertyTable(vm, JSEvent::info(), JSEventPrototypeTableValues, *this); Bun::putToStringTagWithoutTransition(vm, this, info()); + putDirectNativeFunction(vm, globalObject, WebCore::builtinNames(vm).inspectCustomPublicName(), 2, + jsEventPrototype_inspectCustom, ImplementationVisibility::Public, NoIntrinsic, + JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum | 0); } const ClassInfo JSEvent::s_info = { "Event"_s, &Base::s_info, &JSEventTable, nullptr, diff --git a/test/js/node/dns/node-dns.test.js b/test/js/node/dns/node-dns.test.js index 0e0853c216c6..83e2b3a3d0e0 100644 --- a/test/js/node/dns/node-dns.test.js +++ b/test/js/node/dns/node-dns.test.js @@ -612,10 +612,10 @@ describe("test invalid arguments", () => { it("dns.lookupService", async () => { expect(() => { dns.lookupService("", 443, (err, hostname, service) => {}); - }).toThrow("Expected address to be a non-empty string for 'lookupService'."); + }).toThrow("The argument 'address' is invalid. Received ''"); expect(() => { dns.lookupService("google.com", 443, (err, hostname, service) => {}); - }).toThrow(`The "address" argument is invalid. Received type string ('google.com')`); + }).toThrow("The argument 'address' is invalid. Received 'google.com'"); }); }); @@ -733,14 +733,17 @@ describe("dns.lookupService", () => { }); }); -// 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); - }); +// node 26 removed DEP0118: an empty hostname is now rejected outright. +// https://github.com/nodejs/node/blob/v26.3.0/lib/dns.js#L180 +describe("lookup with an empty hostname", () => { + it.each([undefined, false, null, NaN, ""])("dns.lookup(%p) throws", domain => { + expect(() => dns.lookup(domain, () => {})).toThrow(expect.objectContaining({ code: "ERR_INVALID_ARG_VALUE" })); + }); + + it.each([undefined, false, null, NaN, ""])("dns.promises.lookup(%p) rejects", async domain => { + await expect(dns.promises.lookup(domain)).rejects.toThrow( + expect.objectContaining({ code: "ERR_INVALID_ARG_VALUE" }), + ); }); }); @@ -768,9 +771,14 @@ describe("uses `dns.promises` implementations for `util.promisify` factory", () }); it("util.promisify(dns.lookup) acts like dns.promises.lookup", async () => { - // Use a name that resolves locally: a public name with several A records - // behind round-robin DNS can return a different first address per call. - expect(await util.promisify(dns.lookup)("localhost")).toEqual(await dns.promises.lookup("localhost")); + // An IP literal short-circuits the resolver, so this stays deterministic. A + // real hostname can round-robin between answers and make the two calls + // disagree for reasons that have nothing to do with promisify. + const promisified = await util.promisify(dns.lookup)("127.0.0.1"); + // The callback form yields (err, address, family); only the promises + // implementation resolves to an object. + expect(promisified).toEqual({ address: "127.0.0.1", family: 4 }); + expect(promisified).toEqual(await dns.promises.lookup("127.0.0.1")); }); }); diff --git a/test/js/node/test/common/index.js b/test/js/node/test/common/index.js index a4ab5ba15f73..f91e129a535e 100644 --- a/test/js/node/test/common/index.js +++ b/test/js/node/test/common/index.js @@ -1457,12 +1457,6 @@ function installBunExposeInternalsShim() { loader: "object", exports: { ...(http2Internals.core ?? {}) }, })); - build.module("internal/timers", () => ({ - loader: "object", - // TIMEOUT_MAX mirrors Node's internal/timers (2 ** 31 - 1) so vendored - // tests exercising the > TIMEOUT_MAX clamp use the real threshold. - exports: { kTimeout: Symbol.for("::buntimeout::"), TIMEOUT_MAX: 2 ** 31 - 1 }, - })); build.module("internal/webstreams/util", () => ({ loader: "object", exports: { diff --git a/test/js/node/test/parallel/test-c-ares.js b/test/js/node/test/parallel/test-c-ares.js index 0d32d871dc60..08d77ed65731 100644 --- a/test/js/node/test/parallel/test-c-ares.js +++ b/test/js/node/test/parallel/test-c-ares.js @@ -29,9 +29,9 @@ const dnsPromises = dns.promises; (async function() { let res; - res = await dnsPromises.lookup(null); - assert.strictEqual(res.address, null); - assert.strictEqual(res.family, 4); + await assert.rejects(dnsPromises.lookup(null), { + code: 'ERR_INVALID_ARG_VALUE', + }); res = await dnsPromises.lookup('127.0.0.1'); assert.strictEqual(res.address, '127.0.0.1'); @@ -43,10 +43,9 @@ const dnsPromises = dns.promises; })().then(common.mustCall()); // Try resolution without hostname. -dns.lookup(null, common.mustSucceed((result, addressType) => { - assert.strictEqual(result, null); - assert.strictEqual(addressType, 4); -})); +assert.throws(() => dns.lookup(null, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_VALUE', +}); dns.lookup('127.0.0.1', common.mustSucceed((result, addressType) => { assert.strictEqual(result, '127.0.0.1'); @@ -67,11 +66,11 @@ dns.lookup('::1', common.mustSucceed((result, addressType) => { const err = { code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError', - // message: `The argument 'rrtype' is invalid. Received '${val}'`, + message: `The argument 'rrtype' is invalid. Received '${val}'`, }; assert.throws( - () => dns.resolve('www.google.com', val, common.mustNotCall()), + () => dns.resolve('www.google.com', val), err ); diff --git a/test/js/node/test/parallel/test-dns-lookup.js b/test/js/node/test/parallel/test-dns-lookup.js index bef563df6087..b5187aa59302 100644 --- a/test/js/node/test/parallel/test-dns-lookup.js +++ b/test/js/node/test/parallel/test-dns-lookup.js @@ -5,13 +5,9 @@ const assert = require('assert'); // Stub `getaddrinfo` to *always* error. This has to be done before we load the // `dns` module to guarantee that the `dns` module uses the stub. -if (typeof Bun === "undefined") { - const { internalBinding } = require('internal/test/binding'); - const cares = internalBinding('cares_wrap'); - cares.getaddrinfo = () => internalBinding('uv').UV_ENOMEM; -} else { - Bun.dns.lookup = (hostname) => Promise.reject(Object.assign(new Error('Out of memory'), { code: 'ENOMEM', hostname })); -} +// Bun has no internalBinding('cares_wrap'); force the same ENOMEM failure +// through the resolver dns.lookup() actually calls. +Bun.dns.lookup = (hostname) => Promise.reject(Object.assign(new Error('Out of memory'), { code: 'ENOMEM', hostname })); const dns = require('dns'); const dnsPromises = dns.promises; @@ -20,28 +16,13 @@ const dnsPromises = dns.promises; const err = { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: /^The "hostname" argument must be of type string\. Received( type number|: "number")/ + message: /^The "hostname" argument must be of type string\. Received type number/ }; assert.throws(() => dns.lookup(1, {}), err); assert.throws(() => dnsPromises.lookup(1, {}), err); } -// This also verifies different expectWarning notations. -common.expectWarning({ - // For 'internal/test/binding' module. - ...(typeof Bun === "undefined"? { - 'internal/test/binding': [ - 'These APIs are for internal testing only. Do not use them.', - ] - } : {}), - // For calling `dns.lookup` with falsy `hostname`. - 'DeprecationWarning': { - DEP0118: 'The provided hostname "false" is not a valid ' + - 'hostname, and is supported in the dns module solely for compatibility.' - } -}); - assert.throws(() => { dns.lookup(false, 'cb'); }, { @@ -60,7 +41,7 @@ assert.throws(() => { const err = { code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError', - message: /The argument 'hints' is invalid\. Received:? 100/ + message: "The argument 'hints' is invalid. Received 100" }; const options = { hints: 100, @@ -79,7 +60,7 @@ assert.throws(() => { const err = { code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError', - message: /^The (property 'options.family' must be one of: 0, 4, 6|argument 'family' must be one of 0, 4 or 6)\. Received:? 20$/ + message: `The property 'options.family' must be one of: 0, 4, 6. Received ${family}` }; const options = { hints: 0, @@ -151,12 +132,13 @@ assert.throws(() => dnsPromises.lookup(false, () => {}), (async function() { let res; - res = await dnsPromises.lookup(false, { + await assert.rejects(dnsPromises.lookup(false, { hints: 0, family: 0, all: true + }), { + code: 'ERR_INVALID_ARG_VALUE', }); - assert.deepStrictEqual(res, []); res = await dnsPromises.lookup('127.0.0.1', { hints: 0, @@ -173,14 +155,13 @@ assert.throws(() => dnsPromises.lookup(false, () => {}), assert.deepStrictEqual(res, { address: '127.0.0.1', family: 4 }); })().then(common.mustCall()); -dns.lookup(false, { +assert.throws(() => dns.lookup(false, { hints: 0, family: 0, all: true -}, common.mustSucceed((result, addressType) => { - assert.deepStrictEqual(result, []); - assert.strictEqual(addressType, undefined); -})); +}, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_VALUE', +}); dns.lookup('127.0.0.1', { hints: 0, @@ -220,4 +201,4 @@ tickValue = 1; // Should fail due to stub. assert.rejects(dnsPromises.lookup('example.com'), - { code: 'ENOMEM', hostname: 'example.com' }).then(common.mustCall()); + { code: 'ENOMEM', hostname: 'example.com' }).then(common.mustCall()); diff --git a/test/js/node/test/parallel/test-dns.js b/test/js/node/test/parallel/test-dns.js index 8c2b0f8e480e..d7c2efcbd16e 100644 --- a/test/js/node/test/parallel/test-dns.js +++ b/test/js/node/test/parallel/test-dns.js @@ -143,7 +143,8 @@ assert.deepStrictEqual(dns.getServers(), []); const errObj = { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: /The "rrtype" argument must be of type string\. Received( an instance of Array|: \[\]|: "object")$/ + message: 'The "rrtype" argument must be of type string. ' + + 'Received an instance of Array' }; assert.throws(() => { dns.resolve('example.com', [], common.mustNotCall()); @@ -156,7 +157,8 @@ assert.deepStrictEqual(dns.getServers(), []); const errObj = { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: /The "(host)?name" argument must be of type string\. Received:? undefined$/ + message: 'The "name" argument must be of type string. ' + + 'Received undefined' }; assert.throws(() => { dnsPromises.resolve(); @@ -168,7 +170,7 @@ assert.deepStrictEqual(dns.getServers(), []); const errorReg = { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: /^The "hostname" argument must be of type string\. Received:? .*|^Expected hostname to be a string/ + message: /^The "hostname" argument must be of type string\. Received .*/ }; assert.throws(() => dns.lookup({}, common.mustNotCall()), errorReg); @@ -191,16 +193,13 @@ assert.deepStrictEqual(dns.getServers(), []); // dns.lookup should accept falsey values { - const checkCallback = (err, address, family) => { - assert.ifError(err); - assert.strictEqual(address, null); - assert.strictEqual(family, 4); - }; - ['', null, undefined, 0, NaN].forEach(async (value) => { - const res = await dnsPromises.lookup(value); - assert.deepStrictEqual(res, { address: null, family: 4 }); - dns.lookup(value, common.mustCall(checkCallback)); + await assert.rejects(dnsPromises.lookup(value), { + code: 'ERR_INVALID_ARG_VALUE', + }); + assert.throws(() => dns.lookup(value, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_VALUE', + }); }); } @@ -216,7 +215,7 @@ assert.deepStrictEqual(dns.getServers(), []); const err = { code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError', - message: /The (argument 'hints'|"hints" option) is invalid\. Received:? \d+/ + message: /The argument 'hints' is invalid\. Received \d+/ }; assert.throws(() => { @@ -245,52 +244,104 @@ assert.throws(() => dns.lookup('', { name: 'TypeError' }); -dns.lookup('', { family: 4, hints: 0 }, common.mustCall()); +assert.throws(() => { + dns.lookup('', { family: 4, hints: 0 }, common.mustNotCall()); +}, { + code: 'ERR_INVALID_ARG_VALUE', +}); -dns.lookup('', { - family: 6, - hints: dns.ADDRCONFIG -}, common.mustCall()); +assert.throws(() => { + dns.lookup('', { + family: 6, + hints: dns.ADDRCONFIG + }, common.mustNotCall()); +}, { + code: 'ERR_INVALID_ARG_VALUE', +}); -dns.lookup('', { hints: dns.V4MAPPED }, common.mustCall()); +assert.throws(() => { + dns.lookup('', { hints: dns.V4MAPPED }, common.mustNotCall()); +}, { + code: 'ERR_INVALID_ARG_VALUE', +}); -dns.lookup('', { - hints: dns.ADDRCONFIG | dns.V4MAPPED -}, common.mustCall()); +assert.throws(() => { + dns.lookup('', { + hints: dns.ADDRCONFIG | dns.V4MAPPED + }, common.mustNotCall()); +}, { + code: 'ERR_INVALID_ARG_VALUE', +}); -dns.lookup('', { - hints: dns.ALL -}, common.mustCall()); +assert.throws(() => { + dns.lookup('', { + hints: dns.ALL + }, common.mustNotCall()); +}, { + code: 'ERR_INVALID_ARG_VALUE', +}); -dns.lookup('', { - hints: dns.V4MAPPED | dns.ALL -}, common.mustCall()); +assert.throws(() => { + dns.lookup('', { + hints: dns.V4MAPPED | dns.ALL + }, common.mustNotCall()); +}, { + code: 'ERR_INVALID_ARG_VALUE', +}); -dns.lookup('', { - hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL -}, common.mustCall()); +assert.throws(() => { + dns.lookup('', { + hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL + }, common.mustNotCall()); +}, { + code: 'ERR_INVALID_ARG_VALUE', +}); -dns.lookup('', { - hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL, - family: 'IPv4' -}, common.mustCall()); +assert.throws(() => { + dns.lookup('', { + hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL, + family: 'IPv4' + }, common.mustNotCall()); +}, { + code: 'ERR_INVALID_ARG_VALUE', +}); -dns.lookup('', { - hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL, - family: 'IPv6' -}, common.mustCall()); +assert.throws(() => { + dns.lookup('', { + hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL, + family: 'IPv6' + }, common.mustNotCall()); +}, { + code: 'ERR_INVALID_ARG_VALUE', +}); (async function() { - await dnsPromises.lookup('', { family: 4, hints: 0 }); - await dnsPromises.lookup('', { family: 6, hints: dns.ADDRCONFIG }); - await dnsPromises.lookup('', { hints: dns.V4MAPPED }); - await dnsPromises.lookup('', { hints: dns.ADDRCONFIG | dns.V4MAPPED }); - await dnsPromises.lookup('', { hints: dns.ALL }); - await dnsPromises.lookup('', { hints: dns.V4MAPPED | dns.ALL }); - await dnsPromises.lookup('', { + await assert.rejects(dnsPromises.lookup('', { family: 4, hints: 0 }), { + code: 'ERR_INVALID_ARG_VALUE', + }); + await assert.rejects(dnsPromises.lookup('', { family: 6, hints: dns.ADDRCONFIG }), { + code: 'ERR_INVALID_ARG_VALUE', + }); + await assert.rejects(dnsPromises.lookup('', { hints: dns.V4MAPPED }), { + code: 'ERR_INVALID_ARG_VALUE', + }); + await assert.rejects(dnsPromises.lookup('', { hints: dns.ADDRCONFIG | dns.V4MAPPED }), { + code: 'ERR_INVALID_ARG_VALUE', + }); + await assert.rejects(dnsPromises.lookup('', { hints: dns.ALL }), { + code: 'ERR_INVALID_ARG_VALUE', + }); + await assert.rejects(dnsPromises.lookup('', { hints: dns.V4MAPPED | dns.ALL }), { + code: 'ERR_INVALID_ARG_VALUE', + }); + await assert.rejects(dnsPromises.lookup('', { hints: dns.ADDRCONFIG | dns.V4MAPPED | dns.ALL + }), { + code: 'ERR_INVALID_ARG_VALUE', + }); + await assert.rejects(dnsPromises.lookup('', { order: 'verbatim' }), { + code: 'ERR_INVALID_ARG_VALUE', }); - await dnsPromises.lookup('', { order: 'verbatim' }); })().then(common.mustCall()); { @@ -311,7 +362,7 @@ dns.lookup('', { const err = { code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError', - message: /The (argument 'address'|"address" argument) is invalid\. Received/ + message: `The argument 'address' is invalid. Received '${invalidAddress}'` }; assert.throws(() => { @@ -323,7 +374,7 @@ dns.lookup('', { }, err); } -const portErr = (port) => { +[null, undefined, 65538, 'test', NaN, Infinity, Symbol(), 0n, true, false, '', () => {}, {}].forEach((port) => { const err = { code: 'ERR_SOCKET_BAD_PORT', name: 'RangeError' @@ -336,8 +387,7 @@ const portErr = (port) => { assert.throws(() => { dns.lookupService('0.0.0.0', port, common.mustNotCall()); }, err); -}; -[null, undefined, 65538, 'test', NaN, Infinity, Symbol(), 0n, true, false, '', () => {}, {}].forEach(portErr); +}); assert.throws(() => { dns.lookupService('0.0.0.0', 80, null); @@ -347,34 +397,34 @@ assert.throws(() => { }); { - dns.resolveMx('foo.onion', function(err) { + dns.resolveMx('foo.onion', common.mustCall((err) => { assert.strictEqual(err.code, 'ENOTFOUND'); assert.strictEqual(err.syscall, 'queryMx'); assert.strictEqual(err.hostname, 'foo.onion'); assert.strictEqual(err.message, 'queryMx ENOTFOUND foo.onion'); - }); + })); } { const cases = [ - // { method: 'resolveAny', - // answers: [ - // { type: 'A', address: '1.2.3.4', ttl: 0 }, - // { type: 'AAAA', address: '::42', ttl: 0 }, - // { type: 'MX', priority: 42, exchange: 'foobar.com', ttl: 0 }, - // { type: 'NS', value: 'foobar.org', ttl: 0 }, - // { type: 'PTR', value: 'baz.org', ttl: 0 }, - // { - // type: 'SOA', - // nsname: 'ns1.example.com', - // hostmaster: 'admin.example.com', - // serial: 3210987654, - // refresh: 900, - // retry: 900, - // expire: 1800, - // minttl: 3333333333 - // }, - // ] }, + { method: 'resolveAny', + answers: [ + { type: 'A', address: '1.2.3.4', ttl: 0 }, + { type: 'AAAA', address: '::42', ttl: 0 }, + { type: 'MX', priority: 42, exchange: 'foobar.com', ttl: 0 }, + { type: 'NS', value: 'foobar.org', ttl: 0 }, + { type: 'PTR', value: 'baz.org', ttl: 0 }, + { + type: 'SOA', + nsname: 'ns1.example.com', + hostmaster: 'admin.example.com', + serial: 3210987654, + refresh: 900, + retry: 900, + expire: 1800, + minttl: 3333333333 + }, + ] }, { method: 'resolve4', options: { ttl: true }, @@ -401,7 +451,7 @@ assert.throws(() => { const server = dgram.createSocket('udp4'); - server.on('message', common.mustCallAtLeast((msg, { address, port }) => { + server.on('message', common.mustCall((msg, { address, port }) => { const parsed = dnstools.parseDNSPacket(msg); const domain = parsed.questions[0].domain; assert.strictEqual(domain, 'example.org'); @@ -413,7 +463,7 @@ assert.throws(() => { (answer) => Object.assign({ domain }, answer) ), }), port, address); - }, cases.length * 2 - 1)); + }, cases.length * 2)); server.bind(0, common.mustCall(() => { const address = server.address(); @@ -450,7 +500,7 @@ assert.throws(() => { validateResults(await dnsPromises[method]('example.org', options)); - dns[method]('example.org', ...(options? [options] : []), common.mustSucceed((res) => { + dns[method]('example.org', options, common.mustSucceed((res) => { validateResults(res); cases.shift(); nextCase(); diff --git a/test/js/node/test/parallel/test-events-customevent.js b/test/js/node/test/parallel/test-events-customevent.js index 0cf36aa91cc5..ef4a061ee21f 100644 --- a/test/js/node/test/parallel/test-events-customevent.js +++ b/test/js/node/test/parallel/test-events-customevent.js @@ -3,43 +3,44 @@ 'use strict'; const common = require('../common'); -const { ok, strictEqual, deepStrictEqual, throws } = require('node:assert'); +const assert = require('node:assert'); const { inspect } = require('node:util'); +const { Event, EventTarget, CustomEvent } = require('internal/event_target'); { - ok(CustomEvent); + assert.ok(CustomEvent); // Default string const tag = Object.prototype.toString.call(new CustomEvent('$')); - strictEqual(tag, '[object CustomEvent]'); + assert.strictEqual(tag, '[object CustomEvent]'); } { // No argument behavior - throw TypeError - throws(() => { + assert.throws(() => { new CustomEvent(); }, TypeError); - throws(() => new CustomEvent(Symbol()), TypeError); + assert.throws(() => new CustomEvent(Symbol()), TypeError); // Too many arguments passed behavior - ignore additional arguments const ev = new CustomEvent('foo', {}, {}); - strictEqual(ev.type, 'foo'); + assert.strictEqual(ev.type, 'foo'); } { const ev = new CustomEvent('$'); - strictEqual(ev.type, '$'); - strictEqual(ev.bubbles, false); - strictEqual(ev.cancelable, false); - strictEqual(ev.detail, null); + assert.strictEqual(ev.type, '$'); + assert.strictEqual(ev.bubbles, false); + assert.strictEqual(ev.cancelable, false); + assert.strictEqual(ev.detail, null); } { // Coercion to string works - strictEqual(new CustomEvent(1).type, '1'); - strictEqual(new CustomEvent(false).type, 'false'); - strictEqual(new CustomEvent({}).type, String({})); + assert.strictEqual(new CustomEvent(1).type, '1'); + assert.strictEqual(new CustomEvent(false).type, 'false'); + assert.strictEqual(new CustomEvent({}).type, String({})); } { @@ -48,18 +49,18 @@ const { inspect } = require('node:util'); sweet: 'x', cancelable: true, }); - strictEqual(ev.type, '$'); - strictEqual(ev.bubbles, false); - strictEqual(ev.cancelable, true); - strictEqual(ev.sweet, undefined); - strictEqual(ev.detail, 56); + assert.strictEqual(ev.type, '$'); + assert.strictEqual(ev.bubbles, false); + assert.strictEqual(ev.cancelable, true); + assert.strictEqual(ev.sweet, undefined); + assert.strictEqual(ev.detail, 56); } { // Any types of value for `detail` are acceptable. ['foo', 1, false, [], {}].forEach((i) => { const ev = new CustomEvent('$', { detail: i }); - strictEqual(ev.detail, i); + assert.strictEqual(ev.detail, i); }); } @@ -68,21 +69,21 @@ const { inspect } = require('node:util'); const ev = new CustomEvent('$', { detail: 56, }); - strictEqual(ev.detail, 56); + assert.strictEqual(ev.detail, 56); try { ev.detail = 96; // eslint-disable-next-line no-unused-vars } catch (error) { common.mustCall()(); } - strictEqual(ev.detail, 56); + assert.strictEqual(ev.detail, 56); } { const ev = new Event('$', { detail: 96, }); - strictEqual(ev.detail, undefined); + assert.strictEqual(ev.detail, undefined); } // The following tests verify whether CustomEvent works the same as Event @@ -90,101 +91,103 @@ const { inspect } = require('node:util'); { const ev = new CustomEvent('$'); - strictEqual(ev.type, '$'); - strictEqual(ev.bubbles, false); - strictEqual(ev.cancelable, false); - strictEqual(ev.detail, null); + assert.strictEqual(ev.type, '$'); + assert.strictEqual(ev.bubbles, false); + assert.strictEqual(ev.cancelable, false); + assert.strictEqual(ev.detail, null); - strictEqual(ev.defaultPrevented, false); - strictEqual(typeof ev.timeStamp, 'number'); + assert.strictEqual(ev.defaultPrevented, false); + assert.strictEqual(typeof ev.timeStamp, 'number'); // Compatibility properties with the DOM - deepStrictEqual(ev.composedPath(), []); - strictEqual(ev.returnValue, true); - strictEqual(ev.composed, false); - strictEqual(ev.isTrusted, false); - strictEqual(ev.eventPhase, 0); - strictEqual(ev.cancelBubble, false); + assert.deepStrictEqual(ev.composedPath(), []); + assert.strictEqual(ev.returnValue, true); + assert.strictEqual(ev.composed, false); + assert.strictEqual(ev.isTrusted, false); + assert.strictEqual(ev.eventPhase, 0); + assert.strictEqual(ev.cancelBubble, false); // Not cancelable ev.preventDefault(); - strictEqual(ev.defaultPrevented, false); + assert.strictEqual(ev.defaultPrevented, false); } { // Invalid options ['foo', 1, false].forEach((i) => - throws(() => new CustomEvent('foo', i), { + assert.throws(() => new CustomEvent('foo', i), { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "options" argument must be of type object.' + message: + 'The "options" argument must be of type object.' + + common.invalidArgTypeHelper(i), }), ); } { const ev = new CustomEvent('$'); - strictEqual(ev.constructor.name, 'CustomEvent'); + assert.strictEqual(ev.constructor.name, 'CustomEvent'); // CustomEvent Statics - strictEqual(CustomEvent.NONE, 0); - strictEqual(CustomEvent.CAPTURING_PHASE, 1); - strictEqual(CustomEvent.AT_TARGET, 2); - strictEqual(CustomEvent.BUBBLING_PHASE, 3); - strictEqual(new CustomEvent('foo').eventPhase, CustomEvent.NONE); + assert.strictEqual(CustomEvent.NONE, 0); + assert.strictEqual(CustomEvent.CAPTURING_PHASE, 1); + assert.strictEqual(CustomEvent.AT_TARGET, 2); + assert.strictEqual(CustomEvent.BUBBLING_PHASE, 3); + assert.strictEqual(new CustomEvent('foo').eventPhase, CustomEvent.NONE); // CustomEvent is a function - strictEqual(CustomEvent.length, 1); + assert.strictEqual(CustomEvent.length, 1); } { const ev = new CustomEvent('foo'); - strictEqual(ev.cancelBubble, false); + assert.strictEqual(ev.cancelBubble, false); ev.cancelBubble = true; - strictEqual(ev.cancelBubble, true); + assert.strictEqual(ev.cancelBubble, true); } { const ev = new CustomEvent('foo'); - strictEqual(ev.cancelBubble, false); + assert.strictEqual(ev.cancelBubble, false); ev.stopPropagation(); - strictEqual(ev.cancelBubble, true); + assert.strictEqual(ev.cancelBubble, true); } { const ev = new CustomEvent('foo'); - strictEqual(ev.cancelBubble, false); + assert.strictEqual(ev.cancelBubble, false); ev.cancelBubble = 'some-truthy-value'; - strictEqual(ev.cancelBubble, true); + assert.strictEqual(ev.cancelBubble, true); } { const ev = new CustomEvent('foo'); - strictEqual(ev.cancelBubble, false); + assert.strictEqual(ev.cancelBubble, false); ev.cancelBubble = true; - strictEqual(ev.cancelBubble, true); + assert.strictEqual(ev.cancelBubble, true); } { const ev = new CustomEvent('foo'); - strictEqual(ev.cancelBubble, false); + assert.strictEqual(ev.cancelBubble, false); ev.stopPropagation(); - strictEqual(ev.cancelBubble, true); + assert.strictEqual(ev.cancelBubble, true); } { const ev = new CustomEvent('foo'); - strictEqual(ev.cancelBubble, false); + assert.strictEqual(ev.cancelBubble, false); ev.cancelBubble = 'some-truthy-value'; - strictEqual(ev.cancelBubble, true); + assert.strictEqual(ev.cancelBubble, true); } { const ev = new CustomEvent('foo', { cancelable: true }); - strictEqual(ev.type, 'foo'); - strictEqual(ev.cancelable, true); - strictEqual(ev.defaultPrevented, false); + assert.strictEqual(ev.type, 'foo'); + assert.strictEqual(ev.cancelable, true); + assert.strictEqual(ev.defaultPrevented, false); ev.preventDefault(); - strictEqual(ev.defaultPrevented, true); + assert.strictEqual(ev.defaultPrevented, true); } { const ev = new CustomEvent('foo'); - strictEqual(ev.isTrusted, false); + assert.strictEqual(ev.isTrusted, false); } // Works with EventTarget @@ -194,8 +197,8 @@ const { inspect } = require('node:util'); const et = new EventTarget(); const ev = new CustomEvent('$', { detail: obj }); const fn = common.mustCall((event) => { - strictEqual(event, ev); - deepStrictEqual(event.detail, obj); + assert.strictEqual(event, ev); + assert.deepStrictEqual(event.detail, obj); }); et.addEventListener('$', fn); et.dispatchEvent(ev); @@ -205,7 +208,7 @@ const { inspect } = require('node:util'); const eventTarget = new EventTarget(); const event = new CustomEvent('$'); eventTarget.dispatchEvent(event); - strictEqual(event.target, eventTarget); + assert.strictEqual(event.target, eventTarget); } { @@ -213,23 +216,23 @@ const { inspect } = require('node:util'); const eventTarget = new EventTarget(); const ev1 = common.mustCall(function(event) { - strictEqual(event.type, 'foo'); - strictEqual(event.detail, obj); - strictEqual(this, eventTarget); - strictEqual(event.eventPhase, 2); + assert.strictEqual(event.type, 'foo'); + assert.strictEqual(event.detail, obj); + assert.strictEqual(this, eventTarget); + assert.strictEqual(event.eventPhase, 2); }, 2); const ev2 = { handleEvent: common.mustCall(function(event) { - strictEqual(event.type, 'foo'); - strictEqual(event.detail, obj); - strictEqual(this, ev2); + assert.strictEqual(event.type, 'foo'); + assert.strictEqual(event.detail, obj); + assert.strictEqual(this, ev2); }), }; eventTarget.addEventListener('foo', ev1); eventTarget.addEventListener('foo', ev2, { once: true }); - ok(eventTarget.dispatchEvent(new CustomEvent('foo', { detail: obj }))); + assert.ok(eventTarget.dispatchEvent(new CustomEvent('foo', { detail: obj }))); eventTarget.dispatchEvent(new CustomEvent('foo', { detail: obj })); eventTarget.removeEventListener('foo', ev1); @@ -246,32 +249,32 @@ const { inspect } = require('node:util'); eventTarget1.addEventListener( 'foo', common.mustCall((event) => { - strictEqual(event.eventPhase, CustomEvent.AT_TARGET); - strictEqual(event.target, eventTarget1); - strictEqual(event.detail, obj); - deepStrictEqual(event.composedPath(), [eventTarget1]); + assert.strictEqual(event.eventPhase, CustomEvent.AT_TARGET); + assert.strictEqual(event.target, eventTarget1); + assert.strictEqual(event.detail, obj); + assert.deepStrictEqual(event.composedPath(), [eventTarget1]); }), ); eventTarget2.addEventListener( 'foo', common.mustCall((event) => { - strictEqual(event.eventPhase, CustomEvent.AT_TARGET); - strictEqual(event.target, eventTarget2); - strictEqual(event.detail, obj); - deepStrictEqual(event.composedPath(), [eventTarget2]); + assert.strictEqual(event.eventPhase, CustomEvent.AT_TARGET); + assert.strictEqual(event.target, eventTarget2); + assert.strictEqual(event.detail, obj); + assert.deepStrictEqual(event.composedPath(), [eventTarget2]); }), ); eventTarget1.dispatchEvent(event); - strictEqual(event.eventPhase, CustomEvent.NONE); - strictEqual(event.target, eventTarget1); - deepStrictEqual(event.composedPath(), []); + assert.strictEqual(event.eventPhase, CustomEvent.NONE); + assert.strictEqual(event.target, eventTarget1); + assert.deepStrictEqual(event.composedPath(), []); eventTarget2.dispatchEvent(event); - strictEqual(event.eventPhase, CustomEvent.NONE); - strictEqual(event.target, eventTarget2); - deepStrictEqual(event.composedPath(), []); + assert.strictEqual(event.eventPhase, CustomEvent.NONE); + assert.strictEqual(event.target, eventTarget2); + assert.deepStrictEqual(event.composedPath(), []); } { @@ -279,15 +282,15 @@ const { inspect } = require('node:util'); const target = new EventTarget(); const event = new CustomEvent('foo', { detail: obj }); - strictEqual(event.target, null); + assert.strictEqual(event.target, null); target.addEventListener( 'foo', common.mustCall((event) => { - strictEqual(event.target, target); - strictEqual(event.currentTarget, target); - strictEqual(event.srcElement, target); - strictEqual(event.detail, obj); + assert.strictEqual(event.target, target); + assert.strictEqual(event.currentTarget, target); + assert.strictEqual(event.srcElement, target); + assert.strictEqual(event.detail, obj); }), ); target.dispatchEvent(event); @@ -299,8 +302,8 @@ const { inspect } = require('node:util'); const ev = new SubEvent('foo', { detail: 56 }); const eventTarget = new EventTarget(); const fn = common.mustCall((event) => { - strictEqual(event, ev); - strictEqual(event.detail, 56); + assert.strictEqual(event, ev); + assert.strictEqual(event.detail, 56); }); eventTarget.addEventListener('foo', fn, { once: true }); eventTarget.dispatchEvent(ev); @@ -310,14 +313,13 @@ const { inspect } = require('node:util'); { const ev = new CustomEvent('test'); - // TODO: unskip - // const evConstructorName = inspect(ev, { - // depth: -1, - // }); - // strictEqual(evConstructorName, 'CustomEvent'); + const evConstructorName = inspect(ev, { + depth: -1, + }); + assert.strictEqual(evConstructorName, 'CustomEvent'); const inspectResult = inspect(ev, { depth: 1, }); - ok(inspectResult.includes('CustomEvent')); + assert.ok(inspectResult.includes('CustomEvent')); } diff --git a/test/js/node/test/parallel/test-eventtarget.js b/test/js/node/test/parallel/test-eventtarget.js index 6dac98e29ce5..dadd2b846321 100644 --- a/test/js/node/test/parallel/test-eventtarget.js +++ b/test/js/node/test/parallel/test-eventtarget.js @@ -649,11 +649,7 @@ if (typeof Bun === "undefined") { // Node internal const evConstructorName = inspect(ev, { depth: -1, }); - if (typeof Bun === "undefined") { - strictEqual(evConstructorName, 'Event'); - } else { - strictEqual(evConstructorName, '[Event]'); - } + strictEqual(evConstructorName, 'Event'); const inspectResult = inspect(ev, { depth: 1, diff --git a/test/js/node/test/parallel/test-timers-refresh.js b/test/js/node/test/parallel/test-timers-refresh.js index f7fdb723f11b..174039538e6b 100644 --- a/test/js/node/test/parallel/test-timers-refresh.js +++ b/test/js/node/test/parallel/test-timers-refresh.js @@ -1,8 +1,11 @@ +// Flags: --expose-internals + 'use strict'; const common = require('../common'); -const { strictEqual, throws } = require('assert'); +const assert = require('assert'); +const { setUnrefTimeout } = require('internal/timers'); // Schedule the unrefed cases first so that the later case keeps the event loop // active. @@ -21,10 +24,36 @@ const { strictEqual, throws } = require('assert'); // This relies on implicit timers handle sorting within libuv. setTimeout(common.mustCall(() => { - strictEqual(called, false, 'unref()\'d timer returned before check'); + assert.strictEqual(called, false); + }), 1); + + assert.strictEqual(timer.refresh(), timer); +} + +// Should throw with non-functions +{ + [null, true, false, 0, 1, NaN, '', 'foo', {}, Symbol()].forEach((cb) => { + assert.throws( + () => setUnrefTimeout(cb), + { + code: 'ERR_INVALID_ARG_TYPE', + } + ); + }); +} + +// unref pooled timer +{ + let called = false; + const timer = setUnrefTimeout(common.mustCall(() => { + called = true; + }), 1); + + setUnrefTimeout(common.mustCall(() => { + assert.strictEqual(called, false); }), 1); - strictEqual(timer.refresh(), timer); + assert.strictEqual(timer.refresh(), timer); } // regular timer @@ -35,10 +64,10 @@ const { strictEqual, throws } = require('assert'); }), 1); setTimeout(common.mustCall(() => { - strictEqual(called, false, 'pooled timer returned before check'); + assert.strictEqual(called, false); }), 1); - strictEqual(timer.refresh(), timer); + assert.strictEqual(timer.refresh(), timer); } // regular timer @@ -49,7 +78,7 @@ const { strictEqual, throws } = require('assert'); called = true; process.nextTick(common.mustCall(() => { timer.refresh(); - strictEqual(timer.hasRef(), true); + assert.strictEqual(timer.hasRef(), true); })); } }, 2), 1); @@ -66,8 +95,8 @@ const { strictEqual, throws } = require('assert'); }, 2), 1); setTimeout(common.mustCall(() => { - strictEqual(called, 0, 'pooled timer returned before check'); + assert.strictEqual(called, 0); }), 1); - strictEqual(timer.refresh(), timer); + assert.strictEqual(timer.refresh(), timer); } diff --git a/test/js/node/test/parallel/test-tls-translate-peer-certificate.js b/test/js/node/test/parallel/test-tls-translate-peer-certificate.js index de11567b138b..055e6094d076 100644 --- a/test/js/node/test/parallel/test-tls-translate-peer-certificate.js +++ b/test/js/node/test/parallel/test-tls-translate-peer-certificate.js @@ -1,3 +1,4 @@ +// Flags: --expose-internals --no-warnings /* eslint-disable no-proto */ 'use strict'; const common = require('../common'); @@ -5,57 +6,57 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); -const { strictEqual, deepStrictEqual } = require('assert'); -const { translatePeerCertificate } = require('_tls_common'); +const assert = require('assert'); +const { translatePeerCertificate } = require('internal/tls/common'); const certString = '__proto__=42\nA=1\nB=2\nC=3'; -strictEqual(translatePeerCertificate(null), null); -strictEqual(translatePeerCertificate(undefined), null); +assert.strictEqual(translatePeerCertificate(null), null); +assert.strictEqual(translatePeerCertificate(undefined), null); -strictEqual(translatePeerCertificate(0), null); -strictEqual(translatePeerCertificate(1), 1); +assert.strictEqual(translatePeerCertificate(0), null); +assert.strictEqual(translatePeerCertificate(1), 1); -deepStrictEqual(translatePeerCertificate({}), {}); +assert.deepStrictEqual(translatePeerCertificate({}), {}); // Earlier versions of Node.js parsed the issuer property but did so // incorrectly. This behavior has now reached end-of-life and user-supplied // strings will not be parsed at all. -deepStrictEqual(translatePeerCertificate({ issuer: '' }), - { issuer: '' }); -deepStrictEqual(translatePeerCertificate({ issuer: null }), - { issuer: null }); -deepStrictEqual(translatePeerCertificate({ issuer: certString }), - { issuer: certString }); +assert.deepStrictEqual(translatePeerCertificate({ issuer: '' }), + { issuer: '' }); +assert.deepStrictEqual(translatePeerCertificate({ issuer: null }), + { issuer: null }); +assert.deepStrictEqual(translatePeerCertificate({ issuer: certString }), + { issuer: certString }); // Earlier versions of Node.js parsed the issuer property but did so // incorrectly. This behavior has now reached end-of-life and user-supplied // strings will not be parsed at all. -deepStrictEqual(translatePeerCertificate({ subject: '' }), - { subject: '' }); -deepStrictEqual(translatePeerCertificate({ subject: null }), - { subject: null }); -deepStrictEqual(translatePeerCertificate({ subject: certString }), - { subject: certString }); +assert.deepStrictEqual(translatePeerCertificate({ subject: '' }), + { subject: '' }); +assert.deepStrictEqual(translatePeerCertificate({ subject: null }), + { subject: null }); +assert.deepStrictEqual(translatePeerCertificate({ subject: certString }), + { subject: certString }); -deepStrictEqual(translatePeerCertificate({ issuerCertificate: '' }), - { issuerCertificate: null }); -deepStrictEqual(translatePeerCertificate({ issuerCertificate: null }), - { issuerCertificate: null }); -deepStrictEqual( +assert.deepStrictEqual(translatePeerCertificate({ issuerCertificate: '' }), + { issuerCertificate: null }); +assert.deepStrictEqual(translatePeerCertificate({ issuerCertificate: null }), + { issuerCertificate: null }); +assert.deepStrictEqual( translatePeerCertificate({ issuerCertificate: { subject: certString } }), { issuerCertificate: { subject: certString } }); { const cert = {}; cert.issuerCertificate = cert; - deepStrictEqual(translatePeerCertificate(cert), { issuerCertificate: cert }); + assert.deepStrictEqual(translatePeerCertificate(cert), { issuerCertificate: cert }); } -deepStrictEqual(translatePeerCertificate({ infoAccess: '' }), - { infoAccess: { __proto__: null } }); -deepStrictEqual(translatePeerCertificate({ infoAccess: null }), - { infoAccess: null }); +assert.deepStrictEqual(translatePeerCertificate({ infoAccess: '' }), + { infoAccess: { __proto__: null } }); +assert.deepStrictEqual(translatePeerCertificate({ infoAccess: null }), + { infoAccess: null }); { const input = '__proto__:mostly harmless\n' + @@ -65,6 +66,6 @@ deepStrictEqual(translatePeerCertificate({ infoAccess: null }), expected.__proto__ = ['mostly harmless']; expected.hasOwnProperty = ['not a function']; expected['OCSP - URI'] = ['file:///etc/passwd']; - deepStrictEqual(translatePeerCertificate({ infoAccess: input }), - { infoAccess: expected }); + assert.deepStrictEqual(translatePeerCertificate({ infoAccess: input }), + { infoAccess: expected }); } diff --git a/test/js/node/test/parallel/test-util-deprecate.js b/test/js/node/test/parallel/test-util-deprecate.js index 1b4a5e766237..d4a3d69bd8f8 100644 --- a/test/js/node/test/parallel/test-util-deprecate.js +++ b/test/js/node/test/parallel/test-util-deprecate.js @@ -1,14 +1,33 @@ +// Flags: --expose-internals 'use strict'; -require('../common'); +const common = require('../common'); // Tests basic functionality of util.deprecate(). const assert = require('assert'); const util = require('util'); +const internalUtil = require('internal/util'); const expectedWarnings = new Map(); +// Deprecated function length is preserved +for (const fn of [ + function() {}, + function(a) {}, + function(a, b, c) {}, + function(...args) {}, + function(a, b, c, ...args) {}, + () => {}, + (a) => {}, + (a, b, c) => {}, + (...args) => {}, + (a, b, c, ...args) => {}, +]) { + assert.strictEqual(util.deprecate(fn).length, fn.length); + assert.strictEqual(internalUtil.pendingDeprecate(fn).length, fn.length); +} + // Emits deprecation only once if same function is called. { const msg = 'fhqwhgads'; @@ -42,7 +61,29 @@ const expectedWarnings = new Map(); fn2(); } -process.on('warning', (warning) => { + +// Test modifyPrototype option +{ + const msg = 'prototype-test'; + const code = 'proto-code'; + + function OriginalFn() {} + OriginalFn.prototype.testMethod = function() { return 'test'; }; + + const deprecatedWithoutProto = util.deprecate(OriginalFn, msg, code, { modifyPrototype: false }); + + assert.notStrictEqual(deprecatedWithoutProto.prototype, OriginalFn.prototype); + assert.notStrictEqual(Object.getPrototypeOf(deprecatedWithoutProto), OriginalFn); + assert.strictEqual(deprecatedWithoutProto.prototype.testMethod, undefined); + + const deprecatedWithProto = util.deprecate(OriginalFn, msg, code); + + assert.strictEqual(deprecatedWithProto.prototype, OriginalFn.prototype); + assert.strictEqual(Object.getPrototypeOf(deprecatedWithProto), OriginalFn); + assert.strictEqual(typeof deprecatedWithProto.prototype.testMethod, 'function'); +} + +process.on('warning', common.mustCallAtLeast((warning) => { assert.strictEqual(warning.name, 'DeprecationWarning'); assert.ok(expectedWarnings.has(warning.message)); const expected = expectedWarnings.get(warning.message); @@ -50,7 +91,7 @@ process.on('warning', (warning) => { expected.count = expected.count - 1; if (expected.count === 0) expectedWarnings.delete(warning.message); -}); +})); process.on('exit', () => { assert.deepStrictEqual(expectedWarnings, new Map());