From 0307d883602314576f3b8cf2ccaaa1ec7aec3137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Wed, 3 Apr 2024 20:37:10 +0100 Subject: [PATCH] fix(ext/node): polyfill node:domain module (#23088) Closes https://github.com/denoland/deno/issues/16852 --------- Co-authored-by: Nathan Whitaker --- ext/node/polyfills/domain.ts | 81 ++++++++++++++++++- tests/integration/node_unit_tests.rs | 1 + tests/unit_node/domain_test.ts | 112 +++++++++++++++++++++++++++ 3 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 tests/unit_node/domain_test.ts diff --git a/ext/node/polyfills/domain.ts b/ext/node/polyfills/domain.ts index 16fec26a03..f9c99f7254 100644 --- a/ext/node/polyfills/domain.ts +++ b/ext/node/polyfills/domain.ts @@ -1,14 +1,87 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright Joyent and Node contributors. All rights reserved. MIT license. +// This code has been inspired by https://github.com/bevry/domain-browser/commit/8bce7f4a093966ca850da75b024239ad5d0b33c6 -import { notImplemented } from "ext:deno_node/_utils.ts"; +import { primordials } from "ext:core/mod.js"; +const { + ArrayPrototypeSlice, + FunctionPrototypeBind, + FunctionPrototypeCall, + FunctionPrototypeApply, +} = primordials; +import { EventEmitter } from "node:events"; + +function emitError(e) { + this.emit("error", e); +} export function create() { - notImplemented("domain.create"); + return new Domain(); } -export class Domain { +export class Domain extends EventEmitter { + #handler; + constructor() { - notImplemented("domain.Domain.prototype.constructor"); + super(); + this.#handler = FunctionPrototypeBind(emitError, this); + } + + add(emitter) { + emitter.on("error", this.#handler); + } + + remove(emitter) { + emitter.off("error", this.#handler); + } + + bind(fn) { + // deno-lint-ignore no-this-alias + const self = this; + return function () { + try { + FunctionPrototypeApply(fn, null, ArrayPrototypeSlice(arguments)); + } catch (e) { + FunctionPrototypeCall(emitError, self, e); + } + }; + } + + intercept(fn) { + // deno-lint-ignore no-this-alias + const self = this; + return function (e) { + if (e) { + FunctionPrototypeCall(emitError, self, e); + } else { + try { + FunctionPrototypeApply(fn, null, ArrayPrototypeSlice(arguments, 1)); + } catch (e) { + FunctionPrototypeCall(emitError, self, e); + } + } + }; + } + + run(fn) { + try { + fn(); + } catch (e) { + FunctionPrototypeCall(emitError, this, e); + } + return this; + } + + dispose() { + this.removeAllListeners(); + return this; + } + + enter() { + return this; + } + + exit() { + return this; } } export default { diff --git a/tests/integration/node_unit_tests.rs b/tests/integration/node_unit_tests.rs index fc636e807f..a034897ef2 100644 --- a/tests/integration/node_unit_tests.rs +++ b/tests/integration/node_unit_tests.rs @@ -64,6 +64,7 @@ util::unit_test_factory!( crypto_sign_test = crypto / crypto_sign_test, events_test, dgram_test, + domain_test, fs_test, http_test, http2_test, diff --git a/tests/unit_node/domain_test.ts b/tests/unit_node/domain_test.ts new file mode 100644 index 0000000000..88c2b4b47c --- /dev/null +++ b/tests/unit_node/domain_test.ts @@ -0,0 +1,112 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright © Benjamin Lupton +// This code has been forked by https://github.com/bevry/domain-browser/commit/8bce7f4a093966ca850da75b024239ad5d0b33c6 + +import domain from "node:domain"; +import { EventEmitter } from "node:events"; +import { assertEquals } from "@std/assert/mod.ts"; + +Deno.test("should work on throws", async function () { + const deferred = Promise.withResolvers(); + const d = domain.create(); + + d.on("error", function (err) { + // @ts-ignore node:domain types are out of date + assertEquals(err && err.message, "a thrown error", "error message"); + deferred.resolve(); + }); + d.run(function () { + throw new Error("a thrown error"); + }); + await deferred.promise; +}); + +Deno.test("should be able to add emitters", async function () { + const deferred = Promise.withResolvers(); + const d = domain.create(); + const emitter = new EventEmitter(); + + d.add(emitter); + d.on("error", function (err) { + assertEquals(err && err.message, "an emitted error", "error message"); + deferred.resolve(); + }); + + emitter.emit("error", new Error("an emitted error")); + await deferred.promise; +}); + +Deno.test("should be able to remove emitters", async function () { + const deferred = Promise.withResolvers(); + const emitter = new EventEmitter(); + const d = domain.create(); + let domainGotError = false; + + d.add(emitter); + d.on("error", function (_err) { + domainGotError = true; + }); + + emitter.on("error", function (err) { + assertEquals( + err && err.message, + "This error should not go to the domain", + "error message", + ); + + // Make sure nothing race condition-y is happening + setTimeout(function () { + assertEquals(domainGotError, false, "no domain error"); + deferred.resolve(); + }, 0); + }); + + d.remove(emitter); + emitter.emit("error", new Error("This error should not go to the domain")); + await deferred.promise; +}); + +Deno.test("bind should work", async function () { + const deferred = Promise.withResolvers(); + const d = domain.create(); + + d.on("error", function (err) { + assertEquals(err && err.message, "a thrown error", "error message"); + deferred.resolve(); + }); + d.bind(function (err: Error, a: number, b: number) { + assertEquals(err && err.message, "a passed error", "error message"); + assertEquals(a, 2, "value of a"); + assertEquals(b, 3, "value of b"); + throw new Error("a thrown error"); + })(new Error("a passed error"), 2, 3); + await deferred.promise; +}); + +Deno.test("intercept should work", async function () { + const deferred = Promise.withResolvers(); + const d = domain.create(); + let count = 0; + d.on("error", function (err) { + if (count === 0) { + assertEquals(err && err.message, "a thrown error", "error message"); + } else if (count === 1) { + assertEquals(err && err.message, "a passed error", "error message"); + deferred.resolve(); + } + count++; + }); + + d.intercept(function (a: number, b: number) { + assertEquals(a, 2, "value of a"); + assertEquals(b, 3, "value of b"); + throw new Error("a thrown error"); + // @ts-ignore node:domain types are out of date + })(null, 2, 3); + + d.intercept(function (_a: number, _b: number) { + throw new Error("should never reach here"); + // @ts-ignore node:domain types are out of date + })(new Error("a passed error"), 2, 3); + await deferred.promise; +});