aboutsummaryrefslogtreecommitdiff
path: root/test/js
diff options
context:
space:
mode:
authorGravatar dave caruso <me@paperdave.net> 2023-06-01 17:31:36 -0400
committerGravatar GitHub <noreply@github.com> 2023-06-01 14:31:36 -0700
commit2c1694f63bc4eb279aa708f216037d2e6204eaf1 (patch)
tree6784f39bc74a9d7b2d46e62e27ccec8ec1855d5d /test/js
parentae277a0dec2acddb2bebc2e46b26bc1543a55914 (diff)
downloadbun-2c1694f63bc4eb279aa708f216037d2e6204eaf1.tar.gz
bun-2c1694f63bc4eb279aa708f216037d2e6204eaf1.tar.zst
bun-2c1694f63bc4eb279aa708f216037d2e6204eaf1.zip
Fix streams breaking on reverted EventEmitter / Make Discord.js work (#2913)
* Revert "Revert "use a lazyily initialized stream for `node:crypto` `createHash` (#2652)"" This reverts commit 613bb4822ee8f4fbfd78aef391e2db8f07659a6f. * Revert "Revert "implement `node:events` in javascript (#2604)"" This reverts commit a4d0a1961abe0c6073e15cc6f7c0601b74f2e3f7. * oops * fix entrypoints stuff * fix hash copy * use native events for node streams and crypto * requested changes * oops * make discord.js work * fix webkit hash * headers tojson
Diffstat (limited to 'test/js')
-rw-r--r--test/js/node/crypto/crypto-lazyhash.test.ts13
-rw-r--r--test/js/node/crypto/crypto.test.ts1
-rw-r--r--test/js/node/events/event-emitter.test.ts529
-rw-r--r--test/js/node/events/node-builtins.test.js18
4 files changed, 464 insertions, 97 deletions
diff --git a/test/js/node/crypto/crypto-lazyhash.test.ts b/test/js/node/crypto/crypto-lazyhash.test.ts
new file mode 100644
index 000000000..78ac4daf7
--- /dev/null
+++ b/test/js/node/crypto/crypto-lazyhash.test.ts
@@ -0,0 +1,13 @@
+import { describe, expect, test } from "bun:test";
+import { Hash, createHash } from "crypto";
+import { Transform } from "stream";
+
+describe("LazyHash quirks", () => {
+ test("hash instanceof Transform", () => {
+ const hash = createHash("sha256");
+ expect(hash instanceof Transform).toBe(true);
+ });
+ test("Hash.prototype instanceof Transform", () => {
+ expect(Hash.prototype instanceof Transform).toBe(true);
+ });
+});
diff --git a/test/js/node/crypto/crypto.test.ts b/test/js/node/crypto/crypto.test.ts
index 6c34501cb..d03940b9f 100644
--- a/test/js/node/crypto/crypto.test.ts
+++ b/test/js/node/crypto/crypto.test.ts
@@ -1,6 +1,5 @@
import { sha, MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256, gc, CryptoHasher } from "bun";
import { it, expect, describe } from "bun:test";
-import { readFileSync } from "fs";
const HashClasses = [MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256];
diff --git a/test/js/node/events/event-emitter.test.ts b/test/js/node/events/event-emitter.test.ts
index 401ccf605..cef309d48 100644
--- a/test/js/node/events/event-emitter.test.ts
+++ b/test/js/node/events/event-emitter.test.ts
@@ -1,34 +1,106 @@
-import { test, describe, expect, it } from "bun:test";
-import { heapStats } from "bun:jsc";
-import { expectMaxObjectTypeCount, gc } from "harness";
+import { test, describe, expect } from "bun:test";
+import { sleep } from "bun";
+
// this is also testing that imports with default and named imports in the same statement work
// our transpiler transform changes this to a var with import.meta.require
import EventEmitter, { getEventListeners, captureRejectionSymbol } from "node:events";
-describe("EventEmitter", () => {
- it("captureRejectionSymbol", () => {
+describe("node:events", () => {
+ test("captureRejectionSymbol", () => {
expect(EventEmitter.captureRejectionSymbol).toBeDefined();
expect(captureRejectionSymbol).toBeDefined();
+ expect(captureRejectionSymbol).toBe(EventEmitter.captureRejectionSymbol);
+ });
+
+ test("once", done => {
+ const emitter = new EventEmitter();
+ EventEmitter.once(emitter, "hey").then(x => {
+ try {
+ expect(x).toEqual([1, 5]);
+ } catch (error) {
+ done(error);
+ }
+ done();
+ });
+ emitter.emit("hey", 1, 5);
});
+
+ test("once (abort)", done => {
+ const emitter = new EventEmitter();
+ const controller = new AbortController();
+ EventEmitter.once(emitter, "hey", { signal: controller.signal })
+ .then(() => done(new Error("Should not be called")))
+ .catch(() => done());
+ controller.abort();
+ });
+
+ test("once (two events in same tick)", done => {
+ const emitter = new EventEmitter();
+ EventEmitter.once(emitter, "hey").then(() => {
+ EventEmitter.once(emitter, "hey").then(data => {
+ try {
+ expect(data).toEqual([3]);
+ } catch (error) {
+ done(error);
+ }
+ done();
+ });
+ setTimeout(() => {
+ emitter.emit("hey", 3);
+ }, 10);
+ });
+ emitter.emit("hey", 1);
+ emitter.emit("hey", 2);
+ });
+
+ // TODO: extensive events.on tests
+ // test("on", () => {
+ // const emitter = new EventEmitter();
+ // const asyncIterator = EventEmitter.on(emitter, "hey");
+
+ // expect(asyncIterator.next).toBeDefined();
+ // expect(asyncIterator[Symbol.asyncIterator]).toBeDefined();
+
+ // const fn = async () => {
+ // const { value } = await asyncIterator.next();
+ // expect(value).toBe(1);
+ // };
+
+ // emitter.emit("hey", 1, 2, 3);
+ // });
+});
+
+describe("EventEmitter", () => {
test("getEventListeners", () => {
expect(getEventListeners(new EventEmitter(), "hey").length).toBe(0);
});
- test("EventEmitter constructor", () => {
+
+ test("constructor", () => {
var emitter = new EventEmitter();
emitter.setMaxListeners(100);
expect(emitter.getMaxListeners()).toBe(100);
});
- test("EventEmitter.removeAllListeners()", () => {
- var emitter = new EventEmitter();
+ test("removeAllListeners()", () => {
+ var emitter = new EventEmitter() as any;
var ran = false;
emitter.on("hey", () => {
ran = true;
});
+ emitter.on("hey", () => {
+ ran = true;
+ });
+ emitter.on("exit", () => {
+ ran = true;
+ });
+ const { _events } = emitter;
emitter.removeAllListeners();
expect(emitter.listenerCount("hey")).toBe(0);
+ expect(emitter.listenerCount("exit")).toBe(0);
emitter.emit("hey");
+ emitter.emit("exit");
expect(ran).toBe(false);
+ expect(_events).not.toBe(emitter._events); // This looks wrong but node.js replaces it too
emitter.on("hey", () => {
ran = true;
});
@@ -37,42 +109,366 @@ describe("EventEmitter", () => {
expect(emitter.listenerCount("hey")).toBe(1);
});
- // These are also tests for the done() function in the test runner.
- test("EventEmitter emit (different tick)", done => {
+ test("removeAllListeners(type)", () => {
var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- queueMicrotask(() => {
+ var ran = false;
+ emitter.on("hey", () => {
+ ran = true;
+ });
+ emitter.on("exit", () => {
+ ran = true;
+ });
+ expect(emitter.listenerCount("hey")).toBe(1);
+ emitter.removeAllListeners("hey");
+ expect(emitter.listenerCount("hey")).toBe(0);
+ expect(emitter.listenerCount("exit")).toBe(1);
+ emitter.emit("hey");
+ expect(ran).toBe(false);
+ emitter.emit("exit");
+ expect(ran).toBe(true);
+ });
+
+ // These are also tests for the done() function in the test runner.
+ describe("emit", () => {
+ test("different tick", done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ queueMicrotask(() => {
+ emitter.emit("wow");
+ });
+ });
+
+ // Unlike Jest, bun supports async and done
+ test("async microtask before", done => {
+ (async () => {
+ await 1;
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ emitter.emit("wow");
+ })();
+ });
+
+ test("async microtask after", done => {
+ (async () => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ await 1;
+ emitter.emit("wow");
+ })();
+ });
+
+ test("same tick", done => {
+ var emitter = new EventEmitter();
+
+ emitter.on("wow", () => done());
+
emitter.emit("wow");
});
+
+ test("setTimeout task", done => {
+ var emitter = new EventEmitter();
+ emitter.on("wow", () => done());
+ setTimeout(() => emitter.emit("wow"), 1);
+ });
});
- // Unlike Jest, bun supports async and done
- test("async EventEmitter emit (microtask)", async done => {
- await 1;
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- emitter.emit("wow");
+ test("addListener return type", () => {
+ var myEmitter = new EventEmitter();
+ expect(myEmitter.addListener("foo", () => {})).toBe(myEmitter);
});
- test("async EventEmitter emit (microtask) after", async done => {
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- await 1;
- emitter.emit("wow");
+ test("addListener validates function", () => {
+ var myEmitter = new EventEmitter();
+ expect(() => myEmitter.addListener("foo", {} as any)).toThrow();
});
- test("EventEmitter emit (same tick)", done => {
- var emitter = new EventEmitter();
+ test("removeListener return type", () => {
+ var myEmitter = new EventEmitter();
+ expect(myEmitter.removeListener("foo", () => {})).toBe(myEmitter);
+ });
+
+ test("once", () => {
+ var myEmitter = new EventEmitter();
+ var calls = 0;
+
+ const fn = () => {
+ calls++;
+ };
- emitter.on("wow", () => done());
+ myEmitter.once("foo", fn);
- emitter.emit("wow");
+ expect(myEmitter.listenerCount("foo")).toBe(1);
+ expect(myEmitter.listeners("foo")).toEqual([fn]);
+
+ myEmitter.emit("foo");
+ myEmitter.emit("foo");
+
+ expect(calls).toBe(1);
+ expect(myEmitter.listenerCount("foo")).toBe(0);
});
- test("EventEmitter emit (setTimeout task)", done => {
- var emitter = new EventEmitter();
- emitter.on("wow", () => done());
- setTimeout(() => emitter.emit("wow"), 1);
+ test("addListener/removeListener aliases", () => {
+ expect(EventEmitter.prototype.addListener).toBe(EventEmitter.prototype.on);
+ expect(EventEmitter.prototype.removeListener).toBe(EventEmitter.prototype.off);
+ });
+
+ test("prependListener", () => {
+ const myEmitter = new EventEmitter();
+ const order: number[] = [];
+
+ myEmitter.on("foo", () => {
+ order.push(1);
+ });
+
+ myEmitter.prependListener("foo", () => {
+ order.push(2);
+ });
+
+ myEmitter.prependListener("foo", () => {
+ order.push(3);
+ });
+
+ myEmitter.on("foo", () => {
+ order.push(4);
+ });
+
+ myEmitter.emit("foo");
+
+ expect(order).toEqual([3, 2, 1, 4]);
+ });
+
+ test("prependOnceListener", () => {
+ const myEmitter = new EventEmitter();
+ const order: number[] = [];
+
+ myEmitter.on("foo", () => {
+ order.push(1);
+ });
+
+ myEmitter.prependOnceListener("foo", () => {
+ order.push(2);
+ });
+ myEmitter.prependOnceListener("foo", () => {
+ order.push(3);
+ });
+
+ myEmitter.on("foo", () => {
+ order.push(4);
+ });
+
+ myEmitter.emit("foo");
+
+ expect(order).toEqual([3, 2, 1, 4]);
+
+ myEmitter.emit("foo");
+
+ expect(order).toEqual([3, 2, 1, 4, 1, 4]);
+ });
+
+ test("listeners", () => {
+ const myEmitter = new EventEmitter();
+ const fn = () => {};
+ myEmitter.on("foo", fn);
+ expect(myEmitter.listeners("foo")).toEqual([fn]);
+ const fn2 = () => {};
+ myEmitter.on("foo", fn2);
+ expect(myEmitter.listeners("foo")).toEqual([fn, fn2]);
+ myEmitter.off("foo", fn2);
+ expect(myEmitter.listeners("foo")).toEqual([fn]);
+ });
+
+ test("rawListeners", () => {
+ const myEmitter = new EventEmitter();
+ const fn = () => {};
+ myEmitter.on("foo", fn);
+ expect(myEmitter.listeners("foo")).toEqual([fn]);
+ const fn2 = () => {};
+ myEmitter.on("foo", fn2);
+ expect(myEmitter.listeners("foo")).toEqual([fn, fn2]);
+ myEmitter.off("foo", fn2);
+ expect(myEmitter.listeners("foo")).toEqual([fn]);
+ });
+
+ test("eventNames", () => {
+ const myEmitter = new EventEmitter();
+ expect(myEmitter.eventNames()).toEqual([]);
+ const fn = () => {};
+ myEmitter.on("foo", fn);
+ expect(myEmitter.eventNames()).toEqual(["foo"]);
+ myEmitter.on("bar", () => {});
+ expect(myEmitter.eventNames()).toEqual(["foo", "bar"]);
+ myEmitter.off("foo", fn);
+ expect(myEmitter.eventNames()).toEqual(["bar"]);
+ });
+
+ test("_eventsCount", () => {
+ const myEmitter = new EventEmitter() as EventEmitter & {
+ _eventsCount: number;
+ };
+ expect(myEmitter._eventsCount).toBe(0);
+ myEmitter.on("foo", () => {});
+ expect(myEmitter._eventsCount).toBe(1);
+ myEmitter.on("foo", () => {});
+ expect(myEmitter._eventsCount).toBe(1);
+ myEmitter.on("bar", () => {});
+ expect(myEmitter._eventsCount).toBe(2);
+ myEmitter.on("foo", () => {});
+ expect(myEmitter._eventsCount).toBe(2);
+ myEmitter.on("bar", () => {});
+ expect(myEmitter._eventsCount).toBe(2);
+ myEmitter.removeAllListeners("foo");
+ expect(myEmitter._eventsCount).toBe(1);
+ });
+
+ test("events.init", () => {
+ // init is a undocumented property that is identical to the constructor except it doesn't return the instance
+ // in node, EventEmitter just calls init()
+ let instance = Object.create(EventEmitter.prototype);
+ (EventEmitter as any).init.call(instance);
+ expect(instance._eventsCount).toBe(0);
+ expect(instance._maxListeners).toBeUndefined();
+ expect(instance._events).toEqual({});
+ expect(instance instanceof EventEmitter).toBe(true);
+ });
+});
+
+describe("EventEmitter error handling", () => {
+ test("unhandled error event throws on emit", () => {
+ const myEmitter = new EventEmitter();
+
+ expect(() => {
+ myEmitter.emit("error", "Hello!");
+ }).toThrow("Hello!");
+ });
+
+ test("unhandled error event throws on emit with no arguments", () => {
+ const myEmitter = new EventEmitter();
+
+ expect(() => {
+ myEmitter.emit("error");
+ }).toThrow("Unhandled error.");
+ });
+
+ test("handled error event", () => {
+ const myEmitter = new EventEmitter();
+
+ let handled = false;
+ myEmitter.on("error", (...args) => {
+ expect(args).toEqual(["Hello", "World"]);
+ handled = true;
+ });
+
+ myEmitter.emit("error", "Hello", "World");
+
+ expect(handled).toBe(true);
+ });
+
+ test("errorMonitor", () => {
+ const myEmitter = new EventEmitter();
+
+ let handled = false;
+ myEmitter.on(EventEmitter.errorMonitor, (...args) => {
+ expect(args).toEqual(["Hello", "World"]);
+ handled = true;
+ });
+
+ myEmitter.on("error", () => {});
+
+ myEmitter.emit("error", "Hello", "World");
+
+ expect(handled).toBe(true);
+ });
+
+ test("errorMonitor (unhandled)", () => {
+ const myEmitter = new EventEmitter();
+
+ let handled = false;
+ myEmitter.on(EventEmitter.errorMonitor, (...args) => {
+ expect(args).toEqual(["Hello", "World"]);
+ handled = true;
+ });
+
+ expect(() => {
+ myEmitter.emit("error", "Hello", "World");
+ }).toThrow("Hello");
+
+ expect(handled).toBe(true);
+ });
+});
+
+describe("EventEmitter captureRejections", () => {
+ // Can't catch the unhandled rejection because we do not have process.on("unhandledRejection")
+ // test("captureRejections off will not capture rejections", async () => {
+ // const myEmitter = new EventEmitter();
+
+ // let handled = false;
+ // myEmitter.on("error", (...args) => {
+ // handled = true;
+ // });
+
+ // myEmitter.on("action", async () => {
+ // throw new Error("Hello World");
+ // });
+
+ // myEmitter.emit("action");
+
+ // await sleep(1);
+
+ // expect(handled).toBe(false);
+ // });
+ test("it captures rejections", async () => {
+ const myEmitter = new EventEmitter({ captureRejections: true });
+
+ let handled: any = null;
+ myEmitter.on("error", (...args) => {
+ handled = args;
+ });
+
+ myEmitter.on("action", async () => {
+ throw 123;
+ });
+
+ myEmitter.emit("action");
+
+ await sleep(5);
+
+ expect(handled).toEqual([123]);
+ });
+ test("it does not capture successful promises", async () => {
+ const myEmitter = new EventEmitter({ captureRejections: true });
+
+ let handled: any = null;
+ myEmitter.on("error", () => {
+ handled = true;
+ });
+
+ myEmitter.on("action", async () => {
+ return 123;
+ });
+
+ myEmitter.emit("action");
+
+ await sleep(5);
+
+ expect(handled).toEqual(null);
+ });
+ test("it does not capture handled rejections", async () => {
+ const myEmitter = new EventEmitter({ captureRejections: true });
+
+ let handled: any = null;
+ myEmitter.on("error", () => {
+ handled = true;
+ });
+
+ myEmitter.on("action", async () => {
+ return Promise.reject(123).catch(() => 234);
+ });
+
+ myEmitter.emit("action");
+
+ await sleep(5);
+
+ expect(handled).toEqual(null);
});
});
@@ -112,53 +508,30 @@ const waysOfCreating = [
},
];
-for (let create of waysOfCreating) {
- it(`${create.toString().slice(10, 40).replaceAll("\n", "\\n").trim()} should work`, () => {
- var myEmitter = create();
- var called = false;
- (myEmitter as EventEmitter).once("event", function () {
- called = true;
- // @ts-ignore
- expect(this).toBe(myEmitter);
- });
- var firstEvents = myEmitter._events;
- expect(myEmitter.listenerCount("event")).toBe(1);
-
- expect(myEmitter.emit("event")).toBe(true);
- expect(myEmitter.listenerCount("event")).toBe(0);
+describe("EventEmitter constructors", () => {
+ for (let create of waysOfCreating) {
+ test(`${create
+ .toString()
+ .slice(6, 52)
+ .replaceAll("\n", "")
+ .trim()
+ .replaceAll(/ {2,}/g, " ")
+ .replace(/^\{ ?/, "")} should work`, () => {
+ var myEmitter = create();
+ var called = false;
+ (myEmitter as EventEmitter).once("event", function () {
+ called = true;
+ // @ts-ignore
+ expect(this).toBe(myEmitter);
+ });
+ var firstEvents = myEmitter._events;
+ expect(myEmitter.listenerCount("event")).toBe(1);
- expect(firstEvents).toBe(myEmitter._events);
- expect(called).toBe(true);
- });
-}
-
-test("EventEmitter.on", () => {
- var myEmitter = new EventEmitter();
- expect(myEmitter.on("foo", () => {})).toBe(myEmitter);
-});
+ expect(myEmitter.emit("event")).toBe(true);
+ expect(myEmitter.listenerCount("event")).toBe(0);
-test("EventEmitter.off", () => {
- var myEmitter = new EventEmitter();
- expect(myEmitter.off("foo", () => {})).toBe(myEmitter);
-});
-
-// Internally, EventEmitter has a JSC::Weak with the thisValue of the listener
-test("EventEmitter GCs", async () => {
- gc();
-
- const startCount = heapStats().objectTypeCounts["EventEmitter"] ?? 0;
- (function () {
- function EventEmitterSubclass(this: any) {
- EventEmitter.call(this);
- }
-
- Object.setPrototypeOf(EventEmitterSubclass.prototype, EventEmitter.prototype);
- Object.setPrototypeOf(EventEmitterSubclass, EventEmitter);
- // @ts-ignore
- var myEmitter = new EventEmitterSubclass();
- myEmitter.on("foo", () => {});
- myEmitter.emit("foo");
- })();
-
- await expectMaxObjectTypeCount(expect, "EventEmitter", startCount);
+ expect(firstEvents).toEqual({ event: firstEvents.event }); // it shouldn't mutate
+ expect(called).toBe(true);
+ });
+ }
});
diff --git a/test/js/node/events/node-builtins.test.js b/test/js/node/events/node-builtins.test.js
deleted file mode 100644
index 67050f31a..000000000
--- a/test/js/node/events/node-builtins.test.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { describe, it, expect } from "bun:test";
-
-import { EventEmitter } from "events";
-var emitters = [EventEmitter, require("events")];
-describe("EventEmitter", () => {
- it("should emit events", () => {
- for (let Emitter of emitters) {
- const emitter = new Emitter();
- var called = false;
- const listener = () => {
- called = true;
- };
- emitter.on("test", listener);
- emitter.emit("test");
- expect(called).toBe(true);
- }
- });
-});