diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 1fc5198a02..d05434b88a 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -583,7 +583,7 @@ deno_core::extension!(deno_node, "node:constants" = "constants.ts", "node:crypto" = "crypto.ts", "node:dgram" = "dgram.ts", - "node:diagnostics_channel" = "diagnostics_channel.ts", + "node:diagnostics_channel" = "diagnostics_channel.js", "node:dns" = "dns.ts", "node:dns/promises" = "dns/promises.ts", "node:domain" = "domain.ts", diff --git a/ext/node/polyfills/diagnostics_channel.js b/ext/node/polyfills/diagnostics_channel.js new file mode 100644 index 0000000000..807c33e475 --- /dev/null +++ b/ext/node/polyfills/diagnostics_channel.js @@ -0,0 +1,430 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +// TODO(petamoriken): enable prefer-primordials for node polyfills +// deno-lint-ignore-file prefer-primordials ban-untagged-todo + +import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; +import { validateFunction } from "ext:deno_node/internal/validators.mjs"; +import { nextTick } from "node:process"; +import { primordials } from "ext:core/mod.js"; + +const { + ArrayPrototypeAt, + ArrayPrototypeIndexOf, + ArrayPrototypePush, + ArrayPrototypeSplice, + ObjectDefineProperty, + ObjectGetPrototypeOf, + ObjectSetPrototypeOf, + Promise, + PromisePrototypeThen, + PromiseReject, + PromiseResolve, + ReflectApply, + SafeFinalizationRegistry, + SafeMap, + SymbolHasInstance, +} = primordials; +import { WeakReference } from "ext:deno_node/internal/util.mjs"; + +// Can't delete when weakref count reaches 0 as it could increment again. +// Only GC can be used as a valid time to clean up the channels map. +class WeakRefMap extends SafeMap { + #finalizers = new SafeFinalizationRegistry((key) => { + this.delete(key); + }); + + set(key, value) { + this.#finalizers.register(value, key); + return super.set(key, new WeakReference(value)); + } + + get(key) { + return super.get(key)?.get(); + } + + incRef(key) { + return super.get(key)?.incRef(); + } + + decRef(key) { + return super.get(key)?.decRef(); + } +} + +function markActive(channel) { + ObjectSetPrototypeOf(channel, ActiveChannel.prototype); + channel._subscribers = []; + channel._stores = new SafeMap(); +} + +function maybeMarkInactive(channel) { + // When there are no more active subscribers or bound, restore to fast prototype. + if (!channel._subscribers.length && !channel._stores.size) { + ObjectSetPrototypeOf(channel, Channel.prototype); + channel._subscribers = undefined; + channel._stores = undefined; + } +} + +function defaultTransform(data) { + return data; +} + +function wrapStoreRun(store, data, next, transform = defaultTransform) { + return () => { + let context; + try { + context = transform(data); + } catch (err) { + nextTick(() => { + // TODO(bartlomieju): in Node.js this is using `triggerUncaughtException` API, need + // to clarify if we need that or if just throwing the error is enough here. + throw err; + // triggerUncaughtException(err, false); + }); + return next(); + } + + return store.run(context, next); + }; +} + +class ActiveChannel { + subscribe(subscription) { + validateFunction(subscription, "subscription"); + ArrayPrototypePush(this._subscribers, subscription); + channels.incRef(this.name); + } + + unsubscribe(subscription) { + const index = ArrayPrototypeIndexOf(this._subscribers, subscription); + if (index === -1) return false; + + ArrayPrototypeSplice(this._subscribers, index, 1); + + channels.decRef(this.name); + maybeMarkInactive(this); + + return true; + } + + bindStore(store, transform) { + const replacing = this._stores.has(store); + if (!replacing) channels.incRef(this.name); + this._stores.set(store, transform); + } + + unbindStore(store) { + if (!this._stores.has(store)) { + return false; + } + + this._stores.delete(store); + + channels.decRef(this.name); + maybeMarkInactive(this); + + return true; + } + + get hasSubscribers() { + return true; + } + + publish(data) { + for (let i = 0; i < (this._subscribers?.length || 0); i++) { + try { + const onMessage = this._subscribers[i]; + onMessage(data, this.name); + } catch (err) { + nextTick(() => { + // TODO(bartlomieju): in Node.js this is using `triggerUncaughtException` API, need + // to clarify if we need that or if just throwing the error is enough here. + throw err; + // triggerUncaughtException(err, false); + }); + } + } + } + + runStores(data, fn, thisArg, ...args) { + let run = () => { + this.publish(data); + return ReflectApply(fn, thisArg, args); + }; + + for (const entry of this._stores.entries()) { + const store = entry[0]; + const transform = entry[1]; + run = wrapStoreRun(store, data, run, transform); + } + + return run(); + } +} + +class Channel { + constructor(name) { + this._subscribers = undefined; + this._stores = undefined; + this.name = name; + + channels.set(name, this); + } + + static [SymbolHasInstance](instance) { + const prototype = ObjectGetPrototypeOf(instance); + return prototype === Channel.prototype || + prototype === ActiveChannel.prototype; + } + + subscribe(subscription) { + markActive(this); + this.subscribe(subscription); + } + + unsubscribe() { + return false; + } + + bindStore(store, transform) { + markActive(this); + this.bindStore(store, transform); + } + + unbindStore() { + return false; + } + + get hasSubscribers() { + return false; + } + + publish() {} + + runStores(_data, fn, thisArg, ...args) { + return ReflectApply(fn, thisArg, args); + } +} + +const channels = new WeakRefMap(); + +export function channel(name) { + const channel = channels.get(name); + if (channel) return channel; + + if (typeof name !== "string" && typeof name !== "symbol") { + throw new ERR_INVALID_ARG_TYPE("channel", ["string", "symbol"], name); + } + + return new Channel(name); +} + +export function subscribe(name, subscription) { + return channel(name).subscribe(subscription); +} + +export function unsubscribe(name, subscription) { + return channel(name).unsubscribe(subscription); +} + +export function hasSubscribers(name) { + const channel = channels.get(name); + if (!channel) return false; + + return channel.hasSubscribers; +} + +const traceEvents = [ + "start", + "end", + "asyncStart", + "asyncEnd", + "error", +]; + +function assertChannel(value, name) { + if (!(value instanceof Channel)) { + throw new ERR_INVALID_ARG_TYPE(name, ["Channel"], value); + } +} + +function tracingChannelFrom(nameOrChannels, name) { + if (typeof nameOrChannels === "string") { + return channel(`tracing:${nameOrChannels}:${name}`); + } + + if (typeof nameOrChannels === "object" && nameOrChannels !== null) { + const channel = nameOrChannels[name]; + assertChannel(channel, `nameOrChannels.${name}`); + return channel; + } + + throw new ERR_INVALID_ARG_TYPE("nameOrChannels", [ + "string", + "object", + "Channel", + ], nameOrChannels); +} + +class TracingChannel { + constructor(nameOrChannels) { + for (const eventName of traceEvents) { + ObjectDefineProperty(this, eventName, { + __proto__: null, + value: tracingChannelFrom(nameOrChannels, eventName), + }); + } + } + + get hasSubscribers() { + return this.start.hasSubscribers || + this.end.hasSubscribers || + this.asyncStart.hasSubscribers || + this.asyncEnd.hasSubscribers || + this.error.hasSubscribers; + } + + subscribe(handlers) { + for (const name of traceEvents) { + if (!handlers[name]) continue; + + this[name]?.subscribe(handlers[name]); + } + } + + unsubscribe(handlers) { + let done = true; + + for (const name of traceEvents) { + if (!handlers[name]) continue; + + if (!this[name]?.unsubscribe(handlers[name])) { + done = false; + } + } + + return done; + } + + traceSync(fn, context = {}, thisArg, ...args) { + if (!this.hasSubscribers) { + return ReflectApply(fn, thisArg, args); + } + + const { start, end, error } = this; + + return start.runStores(context, () => { + try { + const result = ReflectApply(fn, thisArg, args); + context.result = result; + return result; + } catch (err) { + context.error = err; + error.publish(context); + throw err; + } finally { + end.publish(context); + } + }); + } + + tracePromise(fn, context = {}, thisArg, ...args) { + if (!this.hasSubscribers) { + return ReflectApply(fn, thisArg, args); + } + + const { start, end, asyncStart, asyncEnd, error } = this; + + function reject(err) { + context.error = err; + error.publish(context); + asyncStart.publish(context); + // TODO: Is there a way to have asyncEnd _after_ the continuation? + asyncEnd.publish(context); + return PromiseReject(err); + } + + function resolve(result) { + context.result = result; + asyncStart.publish(context); + // TODO: Is there a way to have asyncEnd _after_ the continuation? + asyncEnd.publish(context); + return result; + } + + return start.runStores(context, () => { + try { + let promise = ReflectApply(fn, thisArg, args); + // Convert thenables to native promises + if (!(promise instanceof Promise)) { + promise = PromiseResolve(promise); + } + return PromisePrototypeThen(promise, resolve, reject); + } catch (err) { + context.error = err; + error.publish(context); + throw err; + } finally { + end.publish(context); + } + }); + } + + traceCallback(fn, position = -1, context = {}, thisArg, ...args) { + if (!this.hasSubscribers) { + return ReflectApply(fn, thisArg, args); + } + + const { start, end, asyncStart, asyncEnd, error } = this; + + function wrappedCallback(err, res) { + if (err) { + context.error = err; + error.publish(context); + } else { + context.result = res; + } + + // Using runStores here enables manual context failure recovery + asyncStart.runStores(context, () => { + try { + return ReflectApply(callback, this, arguments); + } finally { + asyncEnd.publish(context); + } + }); + } + + const callback = ArrayPrototypeAt(args, position); + validateFunction(callback, "callback"); + ArrayPrototypeSplice(args, position, 1, wrappedCallback); + + return start.runStores(context, () => { + try { + return ReflectApply(fn, thisArg, args); + } catch (err) { + context.error = err; + error.publish(context); + throw err; + } finally { + end.publish(context); + } + }); + } +} + +export function tracingChannel(nameOrChannels) { + return new TracingChannel(nameOrChannels); +} + +export default { + channel, + hasSubscribers, + subscribe, + tracingChannel, + unsubscribe, + Channel, +}; diff --git a/ext/node/polyfills/diagnostics_channel.ts b/ext/node/polyfills/diagnostics_channel.ts deleted file mode 100644 index 4f54c34313..0000000000 --- a/ext/node/polyfills/diagnostics_channel.ts +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -// TODO(petamoriken): enable prefer-primordials for node polyfills -// deno-lint-ignore-file prefer-primordials - -import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; -import { validateFunction } from "ext:deno_node/internal/validators.mjs"; -import { nextTick } from "node:process"; - -type Subscriber = (message: unknown, name?: string) => void; - -export class Channel { - _subscribers: Subscriber[]; - name: string; - constructor(name: string) { - this._subscribers = []; - this.name = name; - } - - publish(message: unknown) { - for (const subscriber of this._subscribers) { - try { - subscriber(message, this.name); - } catch (err) { - nextTick(() => { - throw err; - }); - } - } - } - - subscribe(subscription: Subscriber) { - validateFunction(subscription, "subscription"); - - this._subscribers.push(subscription); - } - - unsubscribe(subscription: Subscriber) { - if (!this._subscribers.includes(subscription)) { - return false; - } - - this._subscribers.splice(this._subscribers.indexOf(subscription), 1); - - return true; - } - - get hasSubscribers() { - return this._subscribers.length > 0; - } -} - -const channels: Record = {}; - -export function channel(name: string) { - if (typeof name !== "string" && typeof name !== "symbol") { - throw new ERR_INVALID_ARG_TYPE("channel", ["string", "symbol"], name); - } - - if (!Object.hasOwn(channels, name)) { - channels[name] = new Channel(name); - } - - return channels[name]; -} - -export function hasSubscribers(name: string) { - if (!Object.hasOwn(channels, name)) { - return false; - } - - return channels[name].hasSubscribers; -} - -export function subscribe(name: string, subscription: Subscriber) { - const c = channel(name); - - return c.subscribe(subscription); -} - -export function unsubscribe(name: string, subscription: Subscriber) { - const c = channel(name); - - return c.unsubscribe(subscription); -} - -export default { - channel, - hasSubscribers, - subscribe, - unsubscribe, - Channel, -}; diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts index a16656087e..cb4119411a 100644 --- a/ext/node/polyfills/internal/errors.ts +++ b/ext/node/polyfills/internal/errors.ts @@ -349,9 +349,8 @@ export class NodeErrorAbstraction extends Error { super(message); this.code = code; this.name = name; - //This number changes depending on the name of this class - //20 characters as of now - this.stack = this.stack && `${name} [${this.code}]${this.stack.slice(20)}`; + this.stack = this.stack && + `${name} [${this.code}]${this.stack.slice(this.name.length)}`; } override toString() { @@ -614,7 +613,6 @@ export class ERR_INVALID_ARG_TYPE_RANGE extends NodeRangeError { export class ERR_INVALID_ARG_TYPE extends NodeTypeError { constructor(name: string, expected: string | string[], actual: unknown) { const msg = createInvalidArgType(name, expected); - super("ERR_INVALID_ARG_TYPE", `${msg}.${invalidArgTypeHelper(actual)}`); } diff --git a/ext/node/polyfills/internal/util.mjs b/ext/node/polyfills/internal/util.mjs index 5965998597..e6b32d17d1 100644 --- a/ext/node/polyfills/internal/util.mjs +++ b/ext/node/polyfills/internal/util.mjs @@ -15,6 +15,10 @@ import { } from "ext:deno_node/internal/primordials.mjs"; import { ERR_UNKNOWN_SIGNAL } from "ext:deno_node/internal/errors.ts"; import { os } from "ext:deno_node/internal_binding/constants.ts"; +import { primordials } from "ext:core/mod.js"; +const { + SafeWeakRef, +} = primordials; export const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); export const kEnumerableProperty = Object.create(null); @@ -135,6 +139,38 @@ export function convertToValidSignal(signal) { throw new ERR_UNKNOWN_SIGNAL(signal); } +export class WeakReference { + #weak = null; + #strong = null; + #refCount = 0; + constructor(object) { + this.#weak = new SafeWeakRef(object); + } + + incRef() { + this.#refCount++; + if (this.#refCount === 1) { + const derefed = this.#weak.deref(); + if (derefed !== undefined) { + this.#strong = derefed; + } + } + return this.#refCount; + } + + decRef() { + this.#refCount--; + if (this.#refCount === 0) { + this.#strong = null; + } + return this.#refCount; + } + + get() { + return this.#weak.deref(); + } +} + promisify.custom = kCustomPromisifiedSymbol; export default { diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc index bc5580c1df..27cf6afb82 100644 --- a/tests/node_compat/config.jsonc +++ b/tests/node_compat/config.jsonc @@ -261,9 +261,16 @@ "test-dgram-close-during-bind.js", "test-dgram-close-signal.js", "test-diagnostics-channel-has-subscribers.js", + "test-diagnostics-channel-net.js", "test-diagnostics-channel-object-channel-pub-sub.js", "test-diagnostics-channel-pub-sub.js", "test-diagnostics-channel-symbol-named.js", + "test-diagnostics-channel-sync-unsubscribe.js", + "test-diagnostics-channel-tracing-channel-args-types.js", + "test-diagnostics-channel-tracing-channel-callback-run-stores.js", + "test-diagnostics-channel-tracing-channel-promise-run-stores.js", + "test-diagnostics-channel-tracing-channel-sync-error.js", + "test-diagnostics-channel-tracing-channel-sync.js", "test-diagnostics-channel-udp.js", "test-dns-lookup.js", "test-dns-memory-error.js", diff --git a/tests/node_compat/runner/TODO.md b/tests/node_compat/runner/TODO.md index 0d5bcc6261..e24c82b75c 100644 --- a/tests/node_compat/runner/TODO.md +++ b/tests/node_compat/runner/TODO.md @@ -628,18 +628,11 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co - [parallel/test-diagnostics-channel-http-server-start.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-http-server-start.js) - [parallel/test-diagnostics-channel-http.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-http.js) - [parallel/test-diagnostics-channel-memory-leak.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-memory-leak.js) -- [parallel/test-diagnostics-channel-net.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-net.js) - [parallel/test-diagnostics-channel-process.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-process.js) - [parallel/test-diagnostics-channel-safe-subscriber-errors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-safe-subscriber-errors.js) -- [parallel/test-diagnostics-channel-sync-unsubscribe.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-sync-unsubscribe.js) -- [parallel/test-diagnostics-channel-tracing-channel-args-types.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-args-types.js) - [parallel/test-diagnostics-channel-tracing-channel-async-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-async-error.js) - [parallel/test-diagnostics-channel-tracing-channel-async.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-async.js) -- [parallel/test-diagnostics-channel-tracing-channel-callback-run-stores.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-callback-run-stores.js) -- [parallel/test-diagnostics-channel-tracing-channel-promise-run-stores.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-promise-run-stores.js) - [parallel/test-diagnostics-channel-tracing-channel-run-stores.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-run-stores.js) -- [parallel/test-diagnostics-channel-tracing-channel-sync-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-sync-error.js) -- [parallel/test-diagnostics-channel-tracing-channel-sync.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-tracing-channel-sync.js) - [parallel/test-diagnostics-channel-worker-threads.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-diagnostics-channel-worker-threads.js) - [parallel/test-directory-import.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-directory-import.js) - [parallel/test-disable-proto-delete.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-disable-proto-delete.js) diff --git a/tests/node_compat/test/parallel/test-diagnostics-channel-net.js b/tests/node_compat/test/parallel/test-diagnostics-channel-net.js new file mode 100644 index 0000000000..504c3e5dc8 --- /dev/null +++ b/tests/node_compat/test/parallel/test-diagnostics-channel-net.js @@ -0,0 +1,32 @@ +// 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 `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); +const dc = require('diagnostics_channel'); + +const isNetSocket = (socket) => socket instanceof net.Socket; + +dc.subscribe('net.client.socket', common.mustCall(({ socket }) => { + assert.strictEqual(isNetSocket(socket), true); +})); + +dc.subscribe('net.server.socket', common.mustCall(({ socket }) => { + assert.strictEqual(isNetSocket(socket), true); +})); + +const server = net.createServer(common.mustCall((socket) => { + socket.destroy(); + server.close(); +})); + +server.listen(() => { + const { port } = server.address(); + net.connect(port); +}); diff --git a/tests/node_compat/test/parallel/test-diagnostics-channel-sync-unsubscribe.js b/tests/node_compat/test/parallel/test-diagnostics-channel-sync-unsubscribe.js new file mode 100644 index 0000000000..767382476b --- /dev/null +++ b/tests/node_compat/test/parallel/test-diagnostics-channel-sync-unsubscribe.js @@ -0,0 +1,21 @@ +// 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 `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +const common = require('../common'); +const dc = require('node:diagnostics_channel'); + +const channel_name = 'test:channel'; +const published_data = 'some message'; + +const onMessageHandler = common.mustCall(() => dc.unsubscribe(channel_name, onMessageHandler)); + +dc.subscribe(channel_name, onMessageHandler); + +// This must not throw. +dc.channel(channel_name).publish(published_data); diff --git a/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-args-types.js b/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-args-types.js new file mode 100644 index 0000000000..885c9d76b5 --- /dev/null +++ b/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-args-types.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +require('../common'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); + +let channel; + +// tracingChannel creating with name +channel = dc.tracingChannel('test'); +assert.strictEqual(channel.start.name, 'tracing:test:start'); + +// tracingChannel creating with channels +channel = dc.tracingChannel({ + start: dc.channel('tracing:test:start'), + end: dc.channel('tracing:test:end'), + asyncStart: dc.channel('tracing:test:asyncStart'), + asyncEnd: dc.channel('tracing:test:asyncEnd'), + error: dc.channel('tracing:test:error'), +}); + +// tracingChannel creating without nameOrChannels must throw TypeError +assert.throws(() => (channel = dc.tracingChannel(0)), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: + /The "nameOrChannels" argument must be of type string or an instance of Channel or Object/, +}); + +// tracingChannel creating without instance of Channel must throw error +assert.throws(() => (channel = dc.tracingChannel({ start: '' })), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "nameOrChannels\.start" property must be an instance of Channel/, +}); + +// tracingChannel creating with empty nameOrChannels must throw error +assert.throws(() => (channel = dc.tracingChannel({})), { + message: /Cannot convert undefined or null to object/, +}); diff --git a/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-callback-run-stores.js b/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-callback-run-stores.js new file mode 100644 index 0000000000..1160e6464a --- /dev/null +++ b/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-callback-run-stores.js @@ -0,0 +1,36 @@ +// 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 `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +const common = require('../common'); +const { AsyncLocalStorage } = require('async_hooks'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); + +const channel = dc.tracingChannel('test'); +const store = new AsyncLocalStorage(); + +const firstContext = { foo: 'bar' }; +const secondContext = { baz: 'buz' }; + +channel.start.bindStore(store, common.mustCall(() => { + return firstContext; +})); + +channel.asyncStart.bindStore(store, common.mustCall(() => { + return secondContext; +})); + +assert.strictEqual(store.getStore(), undefined); +channel.traceCallback(common.mustCall((cb) => { + assert.deepStrictEqual(store.getStore(), firstContext); + setImmediate(cb); +}), 0, {}, null, common.mustCall(() => { + assert.deepStrictEqual(store.getStore(), secondContext); +})); +assert.strictEqual(store.getStore(), undefined); diff --git a/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-promise-run-stores.js b/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-promise-run-stores.js new file mode 100644 index 0000000000..3f015e192b --- /dev/null +++ b/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-promise-run-stores.js @@ -0,0 +1,38 @@ +// 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 `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +const common = require('../common'); +const { setTimeout } = require('node:timers/promises'); +const { AsyncLocalStorage } = require('async_hooks'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); + +const channel = dc.tracingChannel('test'); +const store = new AsyncLocalStorage(); + +const firstContext = { foo: 'bar' }; +const secondContext = { baz: 'buz' }; + +channel.start.bindStore(store, common.mustCall(() => { + return firstContext; +})); + +channel.asyncStart.bindStore(store, common.mustNotCall(() => { + return secondContext; +})); + +assert.strictEqual(store.getStore(), undefined); +channel.tracePromise(common.mustCall(async () => { + assert.deepStrictEqual(store.getStore(), firstContext); + await setTimeout(1); + // Should _not_ switch to second context as promises don't have an "after" + // point at which to do a runStores. + assert.deepStrictEqual(store.getStore(), firstContext); +})); +assert.strictEqual(store.getStore(), undefined); diff --git a/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-sync-error.js b/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-sync-error.js new file mode 100644 index 0000000000..09fc103293 --- /dev/null +++ b/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-sync-error.js @@ -0,0 +1,46 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +const common = require('../common'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); + +const channel = dc.tracingChannel('test'); + +const expectedError = new Error('test'); +const input = { foo: 'bar' }; +const thisArg = { baz: 'buz' }; + +function check(found) { + assert.deepStrictEqual(found, input); +} + +const handlers = { + start: common.mustCall(check), + end: common.mustCall(check), + asyncStart: common.mustNotCall(), + asyncEnd: common.mustNotCall(), + error: common.mustCall((found) => { + check(found); + assert.deepStrictEqual(found.error, expectedError); + }) +}; + +channel.subscribe(handlers); +try { + channel.traceSync(function(err) { + assert.deepStrictEqual(this, thisArg); + assert.strictEqual(err, expectedError); + throw err; + }, input, thisArg, expectedError); + + throw new Error('It should not reach this error'); +} catch (error) { + assert.deepStrictEqual(error, expectedError); +} diff --git a/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-sync.js b/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-sync.js new file mode 100644 index 0000000000..036bcce3b8 --- /dev/null +++ b/tests/node_compat/test/parallel/test-diagnostics-channel-tracing-channel-sync.js @@ -0,0 +1,53 @@ +// 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 `tests/node_compat/runner/setup.ts`. Do not modify this file manually. + +'use strict'; + +const common = require('../common'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); + +const channel = dc.tracingChannel('test'); + +const expectedResult = { foo: 'bar' }; +const input = { foo: 'bar' }; +const thisArg = { baz: 'buz' }; +const arg = { baz: 'buz' }; + +function check(found) { + assert.strictEqual(found, input); +} + +const handlers = { + start: common.mustCall(check), + end: common.mustCall((found) => { + check(found); + assert.strictEqual(found.result, expectedResult); + }), + asyncStart: common.mustNotCall(), + asyncEnd: common.mustNotCall(), + error: common.mustNotCall() +}; + +assert.strictEqual(channel.start.hasSubscribers, false); +channel.subscribe(handlers); +assert.strictEqual(channel.start.hasSubscribers, true); +const result1 = channel.traceSync(function(arg1) { + assert.strictEqual(arg1, arg); + assert.strictEqual(this, thisArg); + return expectedResult; +}, input, thisArg, arg); +assert.strictEqual(result1, expectedResult); + +channel.unsubscribe(handlers); +assert.strictEqual(channel.start.hasSubscribers, false); +const result2 = channel.traceSync(function(arg1) { + assert.strictEqual(arg1, arg); + assert.strictEqual(this, thisArg); + return expectedResult; +}, input, thisArg, arg); +assert.strictEqual(result2, expectedResult); diff --git a/tools/core_import_map.json b/tools/core_import_map.json index 421769e523..698d232a5e 100644 --- a/tools/core_import_map.json +++ b/tools/core_import_map.json @@ -66,7 +66,7 @@ "node:constants": "../ext/node/polyfills/constants.ts", "node:crypto": "../ext/node/polyfills/crypto.ts", "node:dgram": "../ext/node/polyfills/dgram.ts", - "node:diagnostics_channel": "../ext/node/polyfills/diagnostics_channel.ts", + "node:diagnostics_channel": "../ext/node/polyfills/diagnostics_channel.js", "node:dns": "../ext/node/polyfills/dns.ts", "node:dns/promises": "../ext/node/polyfills/dns/promises.ts", "node:domain": "../ext/node/polyfills/domain.ts",