aboutsummaryrefslogtreecommitdiff
path: root/test/js
diff options
context:
space:
mode:
Diffstat (limited to 'test/js')
-rw-r--r--test/js/bun/eventsource/eventsource.test.ts284
-rw-r--r--test/js/bun/http/error-response.js8
-rw-r--r--test/js/bun/http/serve.test.ts15
-rw-r--r--test/js/bun/net/socket.test.ts23
-rw-r--r--test/js/bun/resolve/esModule-annotation.test.js68
-rw-r--r--test/js/bun/resolve/import-meta.test.js23
-rw-r--r--test/js/bun/resolve/with-type-module/export-esModule-annotation-empty.cjs1
-rw-r--r--test/js/bun/resolve/with-type-module/export-esModule-annotation-no-default.cjs1
-rw-r--r--test/js/bun/resolve/with-type-module/export-esModule-annotation.cjs2
-rw-r--r--test/js/bun/resolve/with-type-module/export-esModule-no-annotation.cjs1
-rw-r--r--test/js/bun/resolve/with-type-module/package.json4
-rw-r--r--test/js/bun/resolve/without-type-module/export-esModule-annotation-empty.cjs1
-rw-r--r--test/js/bun/resolve/without-type-module/export-esModule-annotation-no-default.cjs1
-rw-r--r--test/js/bun/resolve/without-type-module/export-esModule-annotation.cjs2
-rw-r--r--test/js/bun/resolve/without-type-module/export-esModule-no-annotation.cjs1
-rw-r--r--test/js/bun/resolve/without-type-module/package.json4
-rw-r--r--test/js/bun/spawn/spawn-streaming-stdin.test.ts12
-rw-r--r--test/js/bun/sqlite/sqlite.test.js18
-rw-r--r--test/js/bun/test/expect.test.js265
-rw-r--r--test/js/bun/test/jest-hooks.test.ts31
-rw-r--r--test/js/bun/test/mock-fn.test.js26
-rw-r--r--test/js/bun/test/test-interop.js19
-rw-r--r--test/js/bun/test/test-test.test.ts6
-rw-r--r--test/js/bun/test/test-timers.test.ts28
-rw-r--r--test/js/bun/util/bun-file-exists.test.js20
-rw-r--r--test/js/bun/util/error-gc-test.test.js81
-rw-r--r--test/js/bun/websocket/websocket-server.test.ts300
-rw-r--r--test/js/node/assert/assert.test.cjs9
-rw-r--r--test/js/node/assert/assert.test.ts (renamed from test/js/node/assert/assert-test.test.ts)0
-rw-r--r--test/js/node/buffer.test.js148
-rw-r--r--test/js/node/crypto/crypto.test.ts9
-rw-r--r--test/js/node/crypto/node-crypto.test.js65
-rw-r--r--test/js/node/disabled-module.test.cjs6
-rw-r--r--test/js/node/disabled-module.test.js19
-rw-r--r--test/js/node/dns/dns.node.mjs0
-rw-r--r--test/js/node/dns/node-dns.test.js33
-rw-r--r--test/js/node/events/event-emitter.test.ts7
-rw-r--r--test/js/node/events/events-cjs.test.js4
-rw-r--r--test/js/node/fs/fs.test.ts190
-rw-r--r--test/js/node/fs/node-fetch.cjs.test.js13
-rw-r--r--test/js/node/fs/node-fetch.test.js14
-rw-r--r--test/js/node/http/node-http.test.ts56
-rw-r--r--test/js/node/module/node-module-module.test.js24
-rw-r--r--test/js/node/net/node-net-server.test.ts55
-rw-r--r--test/js/node/os/os.test.js5
-rw-r--r--test/js/node/path/path.test.js2
-rw-r--r--test/js/node/process/call-raise.js15
-rw-r--r--test/js/node/process/print-process-args.js8
-rw-r--r--test/js/node/process/process-exit-fixture.js16
-rw-r--r--test/js/node/process/process-exitCode-fixture.js7
-rw-r--r--test/js/node/process/process-exitCode-with-exit.js8
-rw-r--r--test/js/node/process/process-onBeforeExit-fixture.js7
-rw-r--r--test/js/node/process/process-onBeforeExit-keepAlive.js18
-rw-r--r--test/js/node/process/process-signal-handler.fixture.js63
-rw-r--r--test/js/node/process/process.test.js260
-rw-r--r--test/js/node/string_decoder/string-decoder.test.js9
-rw-r--r--test/js/node/stubs.test.js3
-rw-r--r--test/js/node/timers/node-timers.test.ts13
-rw-r--r--test/js/node/tls/node-tls-connect.test.ts32
-rw-r--r--test/js/node/tls/node-tls-server.test.ts55
-rw-r--r--test/js/node/util/test-util-types.test.js32
-rw-r--r--test/js/node/util/util-callbackify.test.js323
-rw-r--r--test/js/node/util/util.test.js17
-rw-r--r--test/js/node/v8/capture-stack-trace.test.js12
-rw-r--r--test/js/node/watch/fixtures/close.js7
-rw-r--r--test/js/node/watch/fixtures/persistent.js5
-rw-r--r--test/js/node/watch/fixtures/relative.js23
-rw-r--r--test/js/node/watch/fixtures/unref.js7
-rw-r--r--test/js/node/watch/fs.watch.test.ts522
-rwxr-xr-xtest/js/third_party/esbuild/bun.lockbbin8425 -> 9601 bytes
-rw-r--r--test/js/third_party/esbuild/package.json4
-rw-r--r--test/js/third_party/nodemailer/nodemailer.test.ts19
-rw-r--r--test/js/third_party/nodemailer/package.json6
-rw-r--r--test/js/third_party/nodemailer/process-nodemailer-fixture.js20
-rw-r--r--test/js/third_party/postgres/package.json8
-rw-r--r--test/js/third_party/postgres/postgres.test.ts56
-rw-r--r--test/js/third_party/prisma/package.json4
-rw-r--r--test/js/third_party/prisma/prisma.test.ts2
-rw-r--r--test/js/third_party/socket.io/package.json7
-rw-r--r--test/js/third_party/socket.io/support/util.ts1
-rw-r--r--test/js/third_party/webpack/package.json7
-rw-r--r--test/js/third_party/webpack/test.js11
-rw-r--r--test/js/third_party/webpack/webpack.test.ts27
-rw-r--r--test/js/third_party/webpack/world.js3
-rw-r--r--test/js/web/console/console-log.expected.txt8
-rw-r--r--test/js/web/console/console-log.js6
-rw-r--r--test/js/web/fetch/fetch-leak-test-fixture.js2
-rw-r--r--test/js/web/fetch/fetch.test.ts23
-rw-r--r--test/js/web/html/FormData.test.ts19
-rw-r--r--test/js/web/html/URLSearchParams.test.ts5
-rw-r--r--test/js/web/timers/process-setImmediate-fixture.js9
-rw-r--r--test/js/web/timers/setImmediate.test.js27
-rw-r--r--test/js/web/timers/setTimeout-unref-fixture-2.js9
-rw-r--r--test/js/web/timers/setTimeout-unref-fixture-3.js7
-rw-r--r--test/js/web/timers/setTimeout-unref-fixture-4.js5
-rw-r--r--test/js/web/timers/setTimeout-unref-fixture-5.js5
-rw-r--r--test/js/web/timers/setTimeout-unref-fixture.js12
-rw-r--r--test/js/web/timers/setTimeout.test.js51
-rw-r--r--test/js/web/util/atob.test.js9
-rw-r--r--test/js/web/web-globals.test.js19
-rw-r--r--test/js/web/websocket/websocket.test.js34
-rw-r--r--test/js/workerd/html-rewriter.test.js87
102 files changed, 3372 insertions, 506 deletions
diff --git a/test/js/bun/eventsource/eventsource.test.ts b/test/js/bun/eventsource/eventsource.test.ts
index 2f5b7d755..d4da99aa3 100644
--- a/test/js/bun/eventsource/eventsource.test.ts
+++ b/test/js/bun/eventsource/eventsource.test.ts
@@ -1,153 +1,153 @@
-function sse(req: Request) {
- const signal = req.signal;
- return new Response(
- new ReadableStream({
- type: "direct",
- async pull(controller) {
- while (!signal.aborted) {
- await controller.write(`data:Hello, World!\n\n`);
- await controller.write(`event: bun\ndata: Hello, World!\n\n`);
- await controller.write(`event: lines\ndata: Line 1!\ndata: Line 2!\n\n`);
- await controller.write(`event: id_test\nid:1\n\n`);
- await controller.flush();
- await Bun.sleep(100);
- }
- controller.close();
- },
- }),
- { status: 200, headers: { "Content-Type": "text/event-stream" } },
- );
-}
+// function sse(req: Request) {
+// const signal = req.signal;
+// return new Response(
+// new ReadableStream({
+// type: "direct",
+// async pull(controller) {
+// while (!signal.aborted) {
+// await controller.write(`data:Hello, World!\n\n`);
+// await controller.write(`event: bun\ndata: Hello, World!\n\n`);
+// await controller.write(`event: lines\ndata: Line 1!\ndata: Line 2!\n\n`);
+// await controller.write(`event: id_test\nid:1\n\n`);
+// await controller.flush();
+// await Bun.sleep(100);
+// }
+// controller.close();
+// },
+// }),
+// { status: 200, headers: { "Content-Type": "text/event-stream" } },
+// );
+// }
-function sse_unstable(req: Request) {
- const signal = req.signal;
- let id = parseInt(req.headers.get("last-event-id") || "0", 10);
+// function sse_unstable(req: Request) {
+// const signal = req.signal;
+// let id = parseInt(req.headers.get("last-event-id") || "0", 10);
- return new Response(
- new ReadableStream({
- type: "direct",
- async pull(controller) {
- if (!signal.aborted) {
- await controller.write(`id:${++id}\ndata: Hello, World!\nretry:100\n\n`);
- await controller.flush();
- }
- controller.close();
- },
- }),
- { status: 200, headers: { "Content-Type": "text/event-stream" } },
- );
-}
+// return new Response(
+// new ReadableStream({
+// type: "direct",
+// async pull(controller) {
+// if (!signal.aborted) {
+// await controller.write(`id:${++id}\ndata: Hello, World!\nretry:100\n\n`);
+// await controller.flush();
+// }
+// controller.close();
+// },
+// }),
+// { status: 200, headers: { "Content-Type": "text/event-stream" } },
+// );
+// }
-function sseServer(
- done: (err?: unknown) => void,
- pathname: string,
- callback: (evtSource: EventSource, done: (err?: unknown) => void) => void,
-) {
- const server = Bun.serve({
- port: 0,
- fetch(req) {
- if (new URL(req.url).pathname === "/stream") {
- return sse(req);
- }
- if (new URL(req.url).pathname === "/unstable") {
- return sse_unstable(req);
- }
- return new Response("Hello, World!");
- },
- });
- let evtSource: EventSource | undefined;
- try {
- evtSource = new EventSource(`http://localhost:${server.port}${pathname}`);
- callback(evtSource, err => {
- try {
- done(err);
- evtSource?.close();
- } catch (err) {
- done(err);
- } finally {
- server.stop(true);
- }
- });
- } catch (err) {
- evtSource?.close();
- server.stop(true);
- done(err);
- }
-}
+// function sseServer(
+// done: (err?: unknown) => void,
+// pathname: string,
+// callback: (evtSource: EventSource, done: (err?: unknown) => void) => void,
+// ) {
+// const server = Bun.serve({
+// port: 0,
+// fetch(req) {
+// if (new URL(req.url).pathname === "/stream") {
+// return sse(req);
+// }
+// if (new URL(req.url).pathname === "/unstable") {
+// return sse_unstable(req);
+// }
+// return new Response("Hello, World!");
+// },
+// });
+// let evtSource: EventSource | undefined;
+// try {
+// evtSource = new EventSource(`http://localhost:${server.port}${pathname}`);
+// callback(evtSource, err => {
+// try {
+// done(err);
+// evtSource?.close();
+// } catch (err) {
+// done(err);
+// } finally {
+// server.stop(true);
+// }
+// });
+// } catch (err) {
+// evtSource?.close();
+// server.stop(true);
+// done(err);
+// }
+// }
-import { describe, expect, it } from "bun:test";
+// import { describe, expect, it } from "bun:test";
-describe("events", () => {
- it("should call open", done => {
- sseServer(done, "/stream", (evtSource, done) => {
- evtSource.onopen = () => {
- done();
- };
- evtSource.onerror = err => {
- done(err);
- };
- });
- });
+// describe("events", () => {
+// it("should call open", done => {
+// sseServer(done, "/stream", (evtSource, done) => {
+// evtSource.onopen = () => {
+// done();
+// };
+// evtSource.onerror = err => {
+// done(err);
+// };
+// });
+// });
- it("should call message", done => {
- sseServer(done, "/stream", (evtSource, done) => {
- evtSource.onmessage = e => {
- expect(e.data).toBe("Hello, World!");
- done();
- };
- });
- });
+// it("should call message", done => {
+// sseServer(done, "/stream", (evtSource, done) => {
+// evtSource.onmessage = e => {
+// expect(e.data).toBe("Hello, World!");
+// done();
+// };
+// });
+// });
- it("should call custom event", done => {
- sseServer(done, "/stream", (evtSource, done) => {
- evtSource.addEventListener("bun", e => {
- expect(e.data).toBe("Hello, World!");
- done();
- });
- });
- });
+// it("should call custom event", done => {
+// sseServer(done, "/stream", (evtSource, done) => {
+// evtSource.addEventListener("bun", e => {
+// expect(e.data).toBe("Hello, World!");
+// done();
+// });
+// });
+// });
- it("should call event with multiple lines", done => {
- sseServer(done, "/stream", (evtSource, done) => {
- evtSource.addEventListener("lines", e => {
- expect(e.data).toBe("Line 1!\nLine 2!");
- done();
- });
- });
- });
+// it("should call event with multiple lines", done => {
+// sseServer(done, "/stream", (evtSource, done) => {
+// evtSource.addEventListener("lines", e => {
+// expect(e.data).toBe("Line 1!\nLine 2!");
+// done();
+// });
+// });
+// });
- it("should receive id", done => {
- sseServer(done, "/stream", (evtSource, done) => {
- evtSource.addEventListener("id_test", e => {
- expect(e.lastEventId).toBe("1");
- done();
- });
- });
- });
+// it("should receive id", done => {
+// sseServer(done, "/stream", (evtSource, done) => {
+// evtSource.addEventListener("id_test", e => {
+// expect(e.lastEventId).toBe("1");
+// done();
+// });
+// });
+// });
- it("should reconnect with id", done => {
- sseServer(done, "/unstable", (evtSource, done) => {
- const ids: string[] = [];
- evtSource.onmessage = e => {
- ids.push(e.lastEventId);
- if (ids.length === 2) {
- for (let i = 0; i < 2; i++) {
- expect(ids[i]).toBe((i + 1).toString());
- }
- done();
- }
- };
- });
- });
+// it("should reconnect with id", done => {
+// sseServer(done, "/unstable", (evtSource, done) => {
+// const ids: string[] = [];
+// evtSource.onmessage = e => {
+// ids.push(e.lastEventId);
+// if (ids.length === 2) {
+// for (let i = 0; i < 2; i++) {
+// expect(ids[i]).toBe((i + 1).toString());
+// }
+// done();
+// }
+// };
+// });
+// });
- it("should call error", done => {
- sseServer(done, "/", (evtSource, done) => {
- evtSource.onerror = e => {
- expect(e.error.message).toBe(
- `EventSource's response has a MIME type that is not "text/event-stream". Aborting the connection.`,
- );
- done();
- };
- });
- });
-});
+// it("should call error", done => {
+// sseServer(done, "/", (evtSource, done) => {
+// evtSource.onerror = e => {
+// expect(e.error.message).toBe(
+// `EventSource's response has a MIME type that is not "text/event-stream". Aborting the connection.`,
+// );
+// done();
+// };
+// });
+// });
+// });
diff --git a/test/js/bun/http/error-response.js b/test/js/bun/http/error-response.js
new file mode 100644
index 000000000..3284c146b
--- /dev/null
+++ b/test/js/bun/http/error-response.js
@@ -0,0 +1,8 @@
+const s = Bun.serve({
+ fetch(req, res) {
+ s.stop(true);
+ throw new Error("1");
+ },
+ port: 0,
+});
+fetch(`http://${s.hostname}:${s.port}`).then(res => console.log(res.status));
diff --git a/test/js/bun/http/serve.test.ts b/test/js/bun/http/serve.test.ts
index 7182ba68d..bba35c085 100644
--- a/test/js/bun/http/serve.test.ts
+++ b/test/js/bun/http/serve.test.ts
@@ -2,8 +2,10 @@ import { file, gc, Serve, serve, Server } from "bun";
import { afterEach, describe, it, expect, afterAll } from "bun:test";
import { readFileSync, writeFileSync } from "fs";
import { resolve } from "path";
+import { bunExe, bunEnv } from "harness";
import { renderToReadableStream } from "react-dom/server";
import app_jsx from "./app.jsx";
+import { spawn } from "child_process";
type Handler = (req: Request) => Response;
afterEach(() => gc(true));
@@ -980,6 +982,19 @@ describe("should support Content-Range with Bun.file()", () => {
}
});
+it("formats error responses correctly", async () => {
+ const c = spawn(bunExe(), ["./error-response.js"], { cwd: import.meta.dir, env: bunEnv });
+
+ var output = "";
+ c.stderr.on("data", chunk => {
+ output += chunk.toString();
+ });
+ c.stderr.on("end", () => {
+ expect(output).toContain('throw new Error("1");');
+ c.kill();
+ });
+});
+
it("request body and signal life cycle", async () => {
{
const headers = {
diff --git a/test/js/bun/net/socket.test.ts b/test/js/bun/net/socket.test.ts
index 1da000834..5126067e6 100644
--- a/test/js/bun/net/socket.test.ts
+++ b/test/js/bun/net/socket.test.ts
@@ -105,7 +105,28 @@ it("should reject on connection error, calling both connectError() and rejecting
});
it("should not leak memory when connect() fails", async () => {
- await expectMaxObjectTypeCount(expect, "TCPSocket", 1, 100);
+ await (async () => {
+ var promises = new Array(100);
+ for (let i = 0; i < 100; i++) {
+ promises[i] = connect({
+ hostname: "localhost",
+ port: 55555,
+ socket: {
+ connectError(socket, error) {},
+ data() {},
+ drain() {},
+ close() {},
+ end() {},
+ error() {},
+ open() {},
+ },
+ });
+ }
+ await Promise.allSettled(promises);
+ promises.length = 0;
+ })();
+
+ await expectMaxObjectTypeCount(expect, "TCPSocket", 50, 100);
});
// this also tests we mark the promise as handled if connectError() is called
diff --git a/test/js/bun/resolve/esModule-annotation.test.js b/test/js/bun/resolve/esModule-annotation.test.js
new file mode 100644
index 000000000..33c84be5d
--- /dev/null
+++ b/test/js/bun/resolve/esModule-annotation.test.js
@@ -0,0 +1,68 @@
+import { test, expect, describe } from "bun:test";
+import * as WithTypeModuleExportEsModuleAnnotationMissingDefault from "./with-type-module/export-esModule-annotation-empty.cjs";
+import * as WithTypeModuleExportEsModuleAnnotationNoDefault from "./with-type-module/export-esModule-annotation-no-default.cjs";
+import * as WithTypeModuleExportEsModuleAnnotation from "./with-type-module/export-esModule-annotation.cjs";
+import * as WithTypeModuleExportEsModuleNoAnnotation from "./with-type-module/export-esModule-no-annotation.cjs";
+import * as WithoutTypeModuleExportEsModuleAnnotationMissingDefault from "./without-type-module/export-esModule-annotation-empty.cjs";
+import * as WithoutTypeModuleExportEsModuleAnnotationNoDefault from "./without-type-module/export-esModule-annotation-no-default.cjs";
+import * as WithoutTypeModuleExportEsModuleAnnotation from "./without-type-module/export-esModule-annotation.cjs";
+import * as WithoutTypeModuleExportEsModuleNoAnnotation from "./without-type-module/export-esModule-no-annotation.cjs";
+
+describe('without type: "module"', () => {
+ test("module.exports = {}", () => {
+ expect(WithoutTypeModuleExportEsModuleAnnotationMissingDefault.default).toEqual({});
+ expect(WithoutTypeModuleExportEsModuleAnnotationMissingDefault.__esModule).toBeUndefined();
+ });
+
+ test("exports.__esModule = true", () => {
+ expect(WithoutTypeModuleExportEsModuleAnnotationNoDefault.default).toEqual({
+ __esModule: true,
+ });
+
+ // The module namespace object will not have the __esModule property.
+ expect(WithoutTypeModuleExportEsModuleAnnotationNoDefault).not.toHaveProperty("__esModule");
+ });
+
+ test("exports.default = true; exports.__esModule = true;", () => {
+ expect(WithoutTypeModuleExportEsModuleAnnotation.default).toBeTrue();
+ expect(WithoutTypeModuleExportEsModuleAnnotation.__esModule).toBeUndefined();
+ });
+
+ test("exports.default = true;", () => {
+ expect(WithoutTypeModuleExportEsModuleNoAnnotation.default).toEqual({
+ default: true,
+ });
+ expect(WithoutTypeModuleExportEsModuleAnnotation.__esModule).toBeUndefined();
+ });
+});
+
+describe('with type: "module"', () => {
+ test("module.exports = {}", () => {
+ expect(WithTypeModuleExportEsModuleAnnotationMissingDefault.default).toEqual({});
+ expect(WithTypeModuleExportEsModuleAnnotationMissingDefault.__esModule).toBeUndefined();
+ });
+
+ test("exports.__esModule = true", () => {
+ expect(WithTypeModuleExportEsModuleAnnotationNoDefault.default).toEqual({
+ __esModule: true,
+ });
+
+ // The module namespace object WILL have the __esModule property.
+ expect(WithTypeModuleExportEsModuleAnnotationNoDefault).toHaveProperty("__esModule");
+ });
+
+ test("exports.default = true; exports.__esModule = true;", () => {
+ expect(WithTypeModuleExportEsModuleAnnotation.default).toEqual({
+ default: true,
+ __esModule: true,
+ });
+ expect(WithTypeModuleExportEsModuleAnnotation.__esModule).toBeTrue();
+ });
+
+ test("exports.default = true;", () => {
+ expect(WithTypeModuleExportEsModuleNoAnnotation.default).toEqual({
+ default: true,
+ });
+ expect(WithTypeModuleExportEsModuleAnnotation.__esModule).toBeTrue();
+ });
+});
diff --git a/test/js/bun/resolve/import-meta.test.js b/test/js/bun/resolve/import-meta.test.js
index 5771aeb30..e23b44446 100644
--- a/test/js/bun/resolve/import-meta.test.js
+++ b/test/js/bun/resolve/import-meta.test.js
@@ -9,7 +9,7 @@ import sync from "./require-json.json";
const { path, dir } = import.meta;
it("primordials are not here!", () => {
- expect(import.meta.primordials === undefined).toBe(true);
+ expect(globalThis[Symbol.for("Bun.lazy")]("primordials") === undefined).toBe(true);
});
it("import.meta.main", () => {
@@ -25,9 +25,14 @@ it("import.meta.main", () => {
it("import.meta.resolveSync", () => {
expect(import.meta.resolveSync("./" + import.meta.file, import.meta.path)).toBe(path);
+});
+
+it("Module.createRequire", () => {
const require = Module.createRequire(import.meta.path);
expect(require.resolve(import.meta.path)).toBe(path);
expect(require.resolve("./" + import.meta.file)).toBe(path);
+ const { resolve } = require;
+ expect(resolve("./" + import.meta.file)).toBe(path);
// check it works with URL objects
expect(Module.createRequire(new URL(import.meta.url)).resolve(import.meta.path)).toBe(import.meta.path);
@@ -68,8 +73,11 @@ it("import.meta.require (json)", () => {
});
it("const f = require;require(json)", () => {
+ function capture(f) {
+ return f.length;
+ }
const f = require;
- console.log(f);
+ capture(f);
expect(f("./require-json.json").hello).toBe(sync.hello);
});
@@ -82,12 +90,6 @@ it("Module.createRequire().resolve", () => {
expect(result).toBe(expected);
});
-// this is stubbed out
-it("Module._nodeModulePaths()", () => {
- const expected = Module._nodeModulePaths();
- expect(!!expected).toBe(true);
-});
-
// this isn't used in bun but exists anyway
// we just want it to not be undefined
it("Module._cache", () => {
@@ -95,9 +97,9 @@ it("Module._cache", () => {
expect(!!expected).toBe(true);
});
-it("Module._resolveFileName()", () => {
+it("Module._resolveFilename()", () => {
const expected = Bun.resolveSync(import.meta.path, "/");
- const result = Module._resolveFileName(import.meta.path, "/", true);
+ const result = Module._resolveFilename(import.meta.path, "/", true);
expect(result).toBe(expected);
});
@@ -107,7 +109,6 @@ it("Module.createRequire(file://url).resolve(file://url)", () => {
const createdRequire = Module.createRequire(import.meta.url);
const result1 = createdRequire.resolve("./require-json.json");
const result2 = createdRequire.resolve("file://./require-json.json");
-
expect(result1).toBe(expected);
expect(result2).toBe(expected);
});
diff --git a/test/js/bun/resolve/with-type-module/export-esModule-annotation-empty.cjs b/test/js/bun/resolve/with-type-module/export-esModule-annotation-empty.cjs
new file mode 100644
index 000000000..f053ebf79
--- /dev/null
+++ b/test/js/bun/resolve/with-type-module/export-esModule-annotation-empty.cjs
@@ -0,0 +1 @@
+module.exports = {};
diff --git a/test/js/bun/resolve/with-type-module/export-esModule-annotation-no-default.cjs b/test/js/bun/resolve/with-type-module/export-esModule-annotation-no-default.cjs
new file mode 100644
index 000000000..32b83d4a5
--- /dev/null
+++ b/test/js/bun/resolve/with-type-module/export-esModule-annotation-no-default.cjs
@@ -0,0 +1 @@
+exports.__esModule = true;
diff --git a/test/js/bun/resolve/with-type-module/export-esModule-annotation.cjs b/test/js/bun/resolve/with-type-module/export-esModule-annotation.cjs
new file mode 100644
index 000000000..bc0625a0c
--- /dev/null
+++ b/test/js/bun/resolve/with-type-module/export-esModule-annotation.cjs
@@ -0,0 +1,2 @@
+exports.default = true;
+exports.__esModule = true;
diff --git a/test/js/bun/resolve/with-type-module/export-esModule-no-annotation.cjs b/test/js/bun/resolve/with-type-module/export-esModule-no-annotation.cjs
new file mode 100644
index 000000000..a4b65815f
--- /dev/null
+++ b/test/js/bun/resolve/with-type-module/export-esModule-no-annotation.cjs
@@ -0,0 +1 @@
+exports.default = true;
diff --git a/test/js/bun/resolve/with-type-module/package.json b/test/js/bun/resolve/with-type-module/package.json
new file mode 100644
index 000000000..f1863a426
--- /dev/null
+++ b/test/js/bun/resolve/with-type-module/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "with-type-module",
+ "type": "module"
+}
diff --git a/test/js/bun/resolve/without-type-module/export-esModule-annotation-empty.cjs b/test/js/bun/resolve/without-type-module/export-esModule-annotation-empty.cjs
new file mode 100644
index 000000000..f053ebf79
--- /dev/null
+++ b/test/js/bun/resolve/without-type-module/export-esModule-annotation-empty.cjs
@@ -0,0 +1 @@
+module.exports = {};
diff --git a/test/js/bun/resolve/without-type-module/export-esModule-annotation-no-default.cjs b/test/js/bun/resolve/without-type-module/export-esModule-annotation-no-default.cjs
new file mode 100644
index 000000000..32b83d4a5
--- /dev/null
+++ b/test/js/bun/resolve/without-type-module/export-esModule-annotation-no-default.cjs
@@ -0,0 +1 @@
+exports.__esModule = true;
diff --git a/test/js/bun/resolve/without-type-module/export-esModule-annotation.cjs b/test/js/bun/resolve/without-type-module/export-esModule-annotation.cjs
new file mode 100644
index 000000000..bc0625a0c
--- /dev/null
+++ b/test/js/bun/resolve/without-type-module/export-esModule-annotation.cjs
@@ -0,0 +1,2 @@
+exports.default = true;
+exports.__esModule = true;
diff --git a/test/js/bun/resolve/without-type-module/export-esModule-no-annotation.cjs b/test/js/bun/resolve/without-type-module/export-esModule-no-annotation.cjs
new file mode 100644
index 000000000..a4b65815f
--- /dev/null
+++ b/test/js/bun/resolve/without-type-module/export-esModule-no-annotation.cjs
@@ -0,0 +1 @@
+exports.default = true;
diff --git a/test/js/bun/resolve/without-type-module/package.json b/test/js/bun/resolve/without-type-module/package.json
new file mode 100644
index 000000000..5b290db1c
--- /dev/null
+++ b/test/js/bun/resolve/without-type-module/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "without-type-module",
+ "type": "commonjs"
+}
diff --git a/test/js/bun/spawn/spawn-streaming-stdin.test.ts b/test/js/bun/spawn/spawn-streaming-stdin.test.ts
index 27efa14ec..0c430b680 100644
--- a/test/js/bun/spawn/spawn-streaming-stdin.test.ts
+++ b/test/js/bun/spawn/spawn-streaming-stdin.test.ts
@@ -2,18 +2,22 @@ import { it, test, expect } from "bun:test";
import { spawn } from "bun";
import { bunExe, bunEnv, gcTick } from "harness";
import { closeSync, openSync } from "fs";
+import { tmpdir } from "node:os";
+import { join } from "path";
+import { unlinkSync } from "node:fs";
const N = 100;
test("spawn can write to stdin multiple chunks", async () => {
const maxFD = openSync("/dev/null", "w");
for (let i = 0; i < N; i++) {
+ const tmperr = join(tmpdir(), "stdin-repro-error.log." + i);
var exited;
await (async function () {
const proc = spawn({
cmd: [bunExe(), import.meta.dir + "/stdin-repro.js"],
stdout: "pipe",
stdin: "pipe",
- stderr: Bun.file("/tmp/out.log"),
+ stderr: Bun.file(tmperr),
env: bunEnv,
});
exited = proc.exited;
@@ -45,6 +49,10 @@ test("spawn can write to stdin multiple chunks", async () => {
await Promise.all([prom, prom2]);
expect(Buffer.concat(chunks).toString().trim()).toBe("Wrote to stdin!\n".repeat(4).trim());
await proc.exited;
+
+ try {
+ unlinkSync(tmperr);
+ } catch (e) {}
})();
}
@@ -54,4 +62,4 @@ test("spawn can write to stdin multiple chunks", async () => {
// assert we didn't leak any file descriptors
expect(newMaxFD).toBe(maxFD);
-});
+}, 10_000);
diff --git a/test/js/bun/sqlite/sqlite.test.js b/test/js/bun/sqlite/sqlite.test.js
index faa7d5015..e4725cac2 100644
--- a/test/js/bun/sqlite/sqlite.test.js
+++ b/test/js/bun/sqlite/sqlite.test.js
@@ -513,6 +513,24 @@ it("latin1 supplement chars", () => {
expect(db.query("SELECT * FROM foo WHERE id > 9999").values()).toEqual([]);
});
+it("supports FTS5", () => {
+ const db = new Database();
+ db.run("CREATE VIRTUAL TABLE movies USING fts5(title, tokenize='trigram')");
+ const insert = db.prepare("INSERT INTO movies VALUES ($title)");
+ const insertMovies = db.transaction(movies => {
+ for (const movie of movies) insert.run(movie);
+ });
+ insertMovies([
+ { $title: "The Shawshank Redemption" },
+ { $title: "WarGames" },
+ { $title: "Interstellar" },
+ { $title: "Se7en" },
+ { $title: "City of God" },
+ { $title: "Spirited Away" },
+ ]);
+ expect(db.query("SELECT * FROM movies('game')").all()).toEqual([{ title: "WarGames" }]);
+});
+
describe("Database.run", () => {
it("should not throw error `not an error` when provided query containing only whitespace", () => {
const db = Database.open(":memory:");
diff --git a/test/js/bun/test/expect.test.js b/test/js/bun/test/expect.test.js
index f09f7d196..ed94b7e9a 100644
--- a/test/js/bun/test/expect.test.js
+++ b/test/js/bun/test/expect.test.js
@@ -6,6 +6,28 @@
var { isBun, test, describe, expect, jest, vi, mock, bunTest, spyOn } = require("./test-interop.js")();
describe("expect()", () => {
+ test("rejects", async () => {
+ await expect(Promise.reject(1)).rejects.toBe(1);
+
+ // Different task
+ await expect(
+ new Promise((_, reject) => {
+ setTimeout(() => reject(1), 0);
+ }),
+ ).rejects.toBe(1);
+ });
+
+ test("resolves", async () => {
+ await expect(Promise.resolve(1)).resolves.toBe(1);
+
+ // Different task
+ await expect(
+ new Promise(resolve => {
+ setTimeout(() => resolve(1), 0);
+ }),
+ ).resolves.toBe(1);
+ });
+
test("can call without an argument", () => {
expect().toBe(undefined);
});
@@ -1313,6 +1335,62 @@ describe("expect()", () => {
expect([1, 2, 3, 4]).not.toEqual([1, 2, 3]);
});
+ test("toEqual() - private class fields", () => {
+ class A {
+ #three = 3;
+ set three(value) {
+ this.#three = value;
+ }
+
+ get three() {
+ return this.#three;
+ }
+ }
+
+ class B {
+ #three = 3;
+ set three(value) {
+ this.#three = value;
+ }
+
+ get three() {
+ return this.#three;
+ }
+ }
+
+ let a1 = new A();
+ let a2 = new A();
+ a1.three = 4;
+ expect(a1).toEqual(a2);
+ expect(a2).toEqual(a1);
+
+ let a3 = new A();
+ let a4 = new A();
+ a3.three = 4;
+ // use indexed properties for slow path
+ a3[1] = 2;
+ a4[1] = 2;
+ expect(a3).toEqual(a4);
+ expect(a4).toEqual(a3);
+
+ let b1 = new B();
+ let a5 = new A();
+ expect(b1).toEqual(a5);
+ expect(a5).toEqual(b1);
+
+ b1.three = 4;
+ expect(b1).toEqual(a5);
+ expect(a5).toEqual(b1);
+
+ b1[1] = 2;
+ expect(b1).not.toEqual(a5);
+ expect(a5).not.toEqual(b1);
+
+ a5[1] = 2;
+ expect(b1).toEqual(a5);
+ expect(a5).toEqual(b1);
+ });
+
test("properties with different circularity are not equal", () => {
const a = {};
a.x = { y: a };
@@ -2654,28 +2732,74 @@ describe("expect()", () => {
describe("toBeEmpty()", () => {
const values = [
- "",
- [],
- {},
- new Set(),
- new Map(),
- new String(),
- new Array(),
- new Uint8Array(),
- new Object(),
- Buffer.from(""),
- ...(isBun ? [Bun.file("/tmp/empty.txt")] : []),
- new Headers(),
- new URLSearchParams(),
- new FormData(),
- (function* () {})(),
+ {
+ label: `""`,
+ value: "",
+ },
+ {
+ label: `[]`,
+ value: [],
+ },
+ {
+ label: `{}`,
+ value: {},
+ },
+ {
+ label: `new Set()`,
+ value: new Set(),
+ },
+ {
+ label: `new Map()`,
+ value: new Map(),
+ },
+ {
+ label: `new String()`,
+ value: new String(),
+ },
+ {
+ label: `new Array()`,
+ value: new Array(),
+ },
+ {
+ label: `new Uint8Array()`,
+ value: new Uint8Array(),
+ },
+ {
+ label: `new Object()`,
+ value: new Object(),
+ },
+ {
+ label: `Buffer.from("")`,
+ value: Buffer.from(""),
+ },
+ {
+ label: `new Headers()`,
+ value: new Headers(),
+ },
+ {
+ label: `new URLSearchParams()`,
+ value: new URLSearchParams(),
+ },
+ {
+ label: `new FormData()`,
+ value: new FormData(),
+ },
+ {
+ label: `(function* () {})()`,
+ value: (function* () {})(),
+ },
];
- for (const value of values) {
- test(label(value), () => {
- if (value && typeof value === "object" && value instanceof Blob) {
+ if (isBun) {
+ values.push({
+ label: `Bun.file()`,
+ value: Bun.file("/tmp/empty.txt"),
+ });
+ }
+ for (const { label, value } of values) {
+ test(label, () => {
+ if (value instanceof Blob) {
require("fs").writeFileSync("/tmp/empty.txt", "");
}
-
expect(value).toBeEmpty();
});
}
@@ -2683,34 +2807,81 @@ describe("expect()", () => {
describe("not.toBeEmpty()", () => {
const values = [
- " ",
- [""],
- [undefined],
- { "": "" },
- new Set([""]),
- new Map([["", ""]]),
- new String(" "),
- new Array(1),
- new Uint8Array(1),
- Buffer.from(" "),
- ...(isBun ? [Bun.file(__filename)] : []),
- new Headers({
- a: "b",
- c: "d",
- }),
- new URL("https://example.com?d=e&f=g").searchParams,
- (() => {
- var a = new FormData();
- a.append("a", "b");
- a.append("c", "d");
- return a;
- })(),
- (function* () {
- yield "123";
- })(),
+ {
+ label: `" "`,
+ value: " ",
+ },
+ {
+ label: `[""]`,
+ value: [""],
+ },
+ {
+ label: `[undefined]`,
+ value: [undefined],
+ },
+ {
+ label: `{ "": "" }`,
+ value: { "": "" },
+ },
+ {
+ label: `new Set([""])`,
+ value: new Set([""]),
+ },
+ {
+ label: `new Map([["", ""]])`,
+ value: new Map([["", ""]]),
+ },
+ {
+ label: `new String(" ")`,
+ value: new String(" "),
+ },
+ {
+ label: `new Array(1)`,
+ value: new Array(1),
+ },
+ {
+ label: `new Uint8Array(1)`,
+ value: new Uint8Array(1),
+ },
+ {
+ label: `Buffer.from(" ")`,
+ value: Buffer.from(" "),
+ },
+ {
+ label: `new Headers({...})`,
+ value: new Headers({
+ a: "b",
+ c: "d",
+ }),
+ },
+ {
+ label: `URL.searchParams`,
+ value: new URL("https://example.com?d=e&f=g").searchParams,
+ },
+ {
+ label: `FormData`,
+ value: (() => {
+ var a = new FormData();
+ a.append("a", "b");
+ a.append("c", "d");
+ return a;
+ })(),
+ },
+ {
+ label: `generator function`,
+ value: (function* () {
+ yield "123";
+ })(),
+ },
];
- for (const value of values) {
- test(label(value), () => {
+ if (isBun) {
+ values.push({
+ label: `Bun.file()`,
+ value: Bun.file(__filename),
+ });
+ }
+ for (const { label, value } of values) {
+ test(label, () => {
expect(value).not.toBeEmpty();
});
}
@@ -2743,7 +2914,7 @@ describe("expect()", () => {
expect([]).toBeArrayOfSize(0);
expect(new Array()).toBeArrayOfSize(0);
expect([1, 2, 3, "🫓"]).toBeArrayOfSize(4);
- expect((new Array() < string) | (number > (1, 2, 3, "🫓"))).toBeArrayOfSize(4);
+ expect(new Array(1, 2, 3, "🫓")).toBeArrayOfSize(4);
expect({}).not.toBeArrayOfSize(1);
expect("").not.toBeArrayOfSize(1);
expect(0).not.toBeArrayOfSize(1);
diff --git a/test/js/bun/test/jest-hooks.test.ts b/test/js/bun/test/jest-hooks.test.ts
index c99dc7759..618cdc4c6 100644
--- a/test/js/bun/test/jest-hooks.test.ts
+++ b/test/js/bun/test/jest-hooks.test.ts
@@ -1,5 +1,36 @@
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test";
+let hooks_run: string[] = [];
+
+beforeAll(() => hooks_run.push("global beforeAll"));
+beforeEach(() => hooks_run.push("global beforeEach"));
+afterAll(() => hooks_run.push("global afterAll"));
+afterEach(() => hooks_run.push("global afterEach"));
+
+describe("describe scope", () => {
+ beforeAll(() => hooks_run.push("describe beforeAll"));
+ beforeEach(() => hooks_run.push("describe beforeEach"));
+ afterAll(() => hooks_run.push("describe afterAll"));
+ afterEach(() => hooks_run.push("describe afterEach"));
+
+ it("should run after beforeAll/beforeEach in the correct order", () => {
+ expect(hooks_run).toEqual(["global beforeAll", "describe beforeAll", "global beforeEach", "describe beforeEach"]);
+ });
+
+ it("should run after afterEach/afterAll in the correct order", () => {
+ expect(hooks_run).toEqual([
+ "global beforeAll",
+ "describe beforeAll",
+ "global beforeEach",
+ "describe beforeEach",
+ "describe afterEach",
+ "global afterEach",
+ "global beforeEach",
+ "describe beforeEach",
+ ]);
+ });
+});
+
describe("test jest hooks in bun-test", () => {
describe("test beforeAll hook", () => {
let animal = "tiger";
diff --git a/test/js/bun/test/mock-fn.test.js b/test/js/bun/test/mock-fn.test.js
index eac981fd1..ef3c4b7d3 100644
--- a/test/js/bun/test/mock-fn.test.js
+++ b/test/js/bun/test/mock-fn.test.js
@@ -2,7 +2,16 @@
* This file is meant to be runnable in both Jest and Bun.
* `bunx jest mock-fn.test.js`
*/
-var { isBun, test, describe, expect, jest, vi, mock, bunTest, spyOn } = require("./test-interop.js")();
+var { isBun, expect, jest, vi, mock, spyOn } = require("./test-interop.js")();
+
+// if you want to test vitest, comment the above and uncomment the below
+
+// import { expect, describe, test, vi } from "vitest";
+// const isBun = false;
+// const jest = { fn: vi.fn, restoreAllMocks: vi.restoreAllMocks };
+// const spyOn = vi.spyOn;
+// import * as extended from "jest-extended";
+// expect.extend(extended);
async function expectResolves(promise) {
expect(promise).toBeInstanceOf(Promise);
@@ -434,7 +443,6 @@ describe("mock()", () => {
return "3";
},
);
- expect(result).toBe(undefined);
expect(fn()).toBe("1");
});
test("withImplementation (async)", async () => {
@@ -595,5 +603,19 @@ describe("spyOn", () => {
});
}
+ test("spyOn twice works", () => {
+ var obj = {
+ original() {
+ return 42;
+ },
+ };
+ const _original = obj.original;
+ const fn = spyOn(obj, "original");
+ const fn2 = spyOn(obj, "original");
+ expect(fn).toBe(obj.original);
+ expect(fn2).toBe(fn);
+ expect(fn).not.toBe(_original);
+ });
+
// spyOn does not work with getters/setters yet.
});
diff --git a/test/js/bun/test/test-interop.js b/test/js/bun/test/test-interop.js
index 5c41082d6..4b2199ae9 100644
--- a/test/js/bun/test/test-interop.js
+++ b/test/js/bun/test/test-interop.js
@@ -20,6 +20,25 @@ module.exports = () => {
vi: bunTest.vi,
spyOn: bunTest.spyOn,
};
+ } else if (process.env.VITEST) {
+ const vi = require("vitest");
+
+ return {
+ isBun: false,
+ bunTest: null,
+ test: vi.test,
+ describe: vi.describe,
+ it: vi.it,
+ expect: vi.expect,
+ beforeEach: vi.beforeEach,
+ afterEach: vi.afterEach,
+ beforeAll: vi.beforeAll,
+ afterAll: vi.afterAll,
+ jest: { fn: vi.fn },
+ mock: null,
+ vi,
+ spyOn: vi.spyOn,
+ };
} else {
const globals = require("@jest/globals");
const extended = require("jest-extended");
diff --git a/test/js/bun/test/test-test.test.ts b/test/js/bun/test/test-test.test.ts
index 7ecfdef11..5f732bb82 100644
--- a/test/js/bun/test/test-test.test.ts
+++ b/test/js/bun/test/test-test.test.ts
@@ -540,18 +540,18 @@ beforeEach: #2
beforeEach: TEST-FILE
beforeEach: one describe scope
-- inside one describe scope --
+afterEach: one describe scope
+afterEach: TEST-FILE
afterEach: #1
afterEach: #2
-afterEach: TEST-FILE
-afterEach: one describe scope
afterAll: one describe scope
beforeEach: #1
beforeEach: #2
beforeEach: TEST-FILE
-- the top-level test --
+afterEach: TEST-FILE
afterEach: #1
afterEach: #2
-afterEach: TEST-FILE
afterAll: TEST-FILE
afterAll: #1
afterAll: #2
diff --git a/test/js/bun/test/test-timers.test.ts b/test/js/bun/test/test-timers.test.ts
new file mode 100644
index 000000000..963467dee
--- /dev/null
+++ b/test/js/bun/test/test-timers.test.ts
@@ -0,0 +1,28 @@
+test("we can go back in time", () => {
+ const DateBeforeMocked = Date;
+ const orig = new Date();
+ orig.setHours(0, 0, 0, 0);
+ jest.useFakeTimers();
+ jest.setSystemTime(new Date("1995-12-19T00:00:00.000Z"));
+
+ expect(new Date().toISOString()).toBe("1995-12-19T00:00:00.000Z");
+ expect(Date.now()).toBe(819331200000);
+
+ if (typeof Bun !== "undefined") {
+ // In bun, the Date object remains the same despite being mocked.
+ // This prevents a whole bunch of subtle bugs in tests.
+ expect(DateBeforeMocked).toBe(Date);
+ expect(DateBeforeMocked.now).toBe(Date.now);
+
+ // Jest doesn't property mock new Intl.DateTimeFormat().format()
+ expect(new Intl.DateTimeFormat().format()).toBe("12/19/1995");
+ } else {
+ expect(DateBeforeMocked).not.toBe(Date);
+ expect(DateBeforeMocked.now).not.toBe(Date.now);
+ }
+
+ jest.useRealTimers();
+ const now = new Date();
+ now.setHours(0, 0, 0, 0);
+ expect(now.toISOString()).toBe(orig.toISOString());
+});
diff --git a/test/js/bun/util/bun-file-exists.test.js b/test/js/bun/util/bun-file-exists.test.js
new file mode 100644
index 000000000..cca28e359
--- /dev/null
+++ b/test/js/bun/util/bun-file-exists.test.js
@@ -0,0 +1,20 @@
+import { test, expect } from "bun:test";
+import { join } from "path";
+import { tmpdir } from "os";
+import { write } from "bun";
+import { unlinkSync } from "fs";
+test("bun-file-exists", async () => {
+ expect(await Bun.file(import.meta.path).exists()).toBeTrue();
+ expect(await Bun.file(import.meta.path + "boop").exists()).toBeFalse();
+ expect(await Bun.file(import.meta.dir).exists()).toBeFalse();
+ expect(await Bun.file(import.meta.dir + "/").exists()).toBeFalse();
+ const temp = join(tmpdir(), "bun-file-exists.test.js");
+ try {
+ unlinkSync(temp);
+ } catch (e) {}
+ expect(await Bun.file(temp).exists()).toBeFalse();
+ await write(temp, "boop");
+ expect(await Bun.file(temp).exists()).toBeTrue();
+ unlinkSync(temp);
+ expect(await Bun.file(temp).exists()).toBeFalse();
+});
diff --git a/test/js/bun/util/error-gc-test.test.js b/test/js/bun/util/error-gc-test.test.js
new file mode 100644
index 000000000..4a45346b6
--- /dev/null
+++ b/test/js/bun/util/error-gc-test.test.js
@@ -0,0 +1,81 @@
+import { test, expect } from "bun:test";
+import { readFileSync } from "fs";
+// This test checks that printing stack traces increments and decrements
+// reference-counted strings
+test("error gc test", () => {
+ for (let i = 0; i < 100; i++) {
+ var fn = function yo() {
+ var err = (function innerOne() {
+ var err = new Error();
+ for (let i = 0; i < 1000; i++) {
+ Bun.inspect(err);
+ }
+ Bun.gc(true);
+ return err;
+ })();
+ err.stack += "";
+ };
+
+ Object.defineProperty(fn, "name", {
+ value:
+ "yoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyoyo" +
+ i,
+ });
+
+ fn();
+ Bun.gc(true);
+ }
+});
+
+test("error gc test #2", () => {
+ for (let i = 0; i < 1000; i++) {
+ new Error().stack;
+ Bun.gc();
+ }
+});
+
+test("error gc test #3", () => {
+ for (let i = 0; i < 1000; i++) {
+ var err = new Error();
+ Error.captureStackTrace(err);
+ Bun.inspect(err);
+ Bun.gc();
+ }
+});
+
+// This test fails if:
+// - it crashes
+// - The test failure message gets a non-sensical error
+test("error gc test #4", () => {
+ for (let i = 0; i < 1000; i++) {
+ let path =
+ // Use a long-enough string for it to be obvious if we leak memory
+ "/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/ii/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/ii/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i/don/t/exist/tmp/i";
+ try {
+ readFileSync(path);
+ throw new Error("unreachable");
+ } catch (e) {
+ if (e.message === "unreachable") {
+ throw e;
+ }
+
+ const inspected = Bun.inspect(e);
+ Bun.gc(true);
+
+ // Deliberately avoid using .toContain() directly to avoid
+ // BunString shenanigins.
+ //
+ // Only JSC builtin functions to operate on the string after inspecting it.
+ //
+ if (!inspected.includes(path)) {
+ expect(inspected).toContain(path);
+ }
+
+ if (!inspected.includes("ENOENT")) {
+ expect(inspected).toContain("ENOENT");
+ }
+ } finally {
+ Bun.gc(true);
+ }
+ }
+});
diff --git a/test/js/bun/websocket/websocket-server.test.ts b/test/js/bun/websocket/websocket-server.test.ts
index 7d43c7e65..7a79f9f5f 100644
--- a/test/js/bun/websocket/websocket-server.test.ts
+++ b/test/js/bun/websocket/websocket-server.test.ts
@@ -3,6 +3,77 @@ import { gcTick } from "harness";
import { serve, ServerWebSocket } from "bun";
describe("websocket server", () => {
+ it("send & receive empty messages", done => {
+ const serverReceived: any[] = [];
+ const clientReceived: any[] = [];
+ var clientDone = false;
+ var serverDone = false;
+
+ let server = Bun.serve({
+ websocket: {
+ open(ws) {
+ ws.send("");
+ ws.send(new ArrayBuffer(0));
+ },
+ message(ws, data) {
+ serverReceived.push(data);
+
+ if (serverReceived.length === 2) {
+ if (serverReceived.find(d => d === "") === undefined) {
+ done(new Error("expected empty string"));
+ }
+
+ if (!serverReceived.find(d => d.byteLength === 0)) {
+ done(new Error("expected empty Buffer"));
+ }
+
+ serverDone = true;
+
+ if (clientDone && serverDone) {
+ z.close();
+ server.stop(true);
+ done();
+ }
+ }
+ },
+ close() {},
+ },
+ fetch(req, server) {
+ if (!server.upgrade(req)) {
+ return new Response(null, { status: 404 });
+ }
+ },
+ port: 0,
+ });
+
+ let z = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ z.onmessage = e => {
+ clientReceived.push(e.data);
+
+ if (clientReceived.length === 2) {
+ if (clientReceived.find(d => d === "") === undefined) {
+ done(new Error("expected empty string"));
+ }
+
+ if (!clientReceived.find(d => d.byteLength === 0)) {
+ done(new Error("expected empty Buffer"));
+ }
+
+ clientDone = true;
+ if (clientDone && serverDone) {
+ server.stop(true);
+ z.close();
+
+ done();
+ }
+ }
+ };
+ z.addEventListener("open", () => {
+ z.send("");
+ z.send(new Buffer(0));
+ });
+ });
+
it("remoteAddress works", done => {
let server = Bun.serve({
websocket: {
@@ -849,7 +920,7 @@ describe("websocket server", () => {
it("send rope strings", async () => {
var ropey = "hello world".repeat(10);
var sendQueue: any[] = [];
- for (var i = 0; i < 100; i++) {
+ for (var i = 0; i < 20; i++) {
sendQueue.push(ropey + " " + i);
}
@@ -859,16 +930,13 @@ describe("websocket server", () => {
const server = serve({
port: 0,
websocket: {
- open(ws) {
- server.stop();
- },
+ open(ws) {},
message(ws, msg) {
ws.send(sendQueue[serverCounter++] + " ");
- gcTick();
+ serverCounter % 10 === 0 && gcTick();
},
},
fetch(req, server) {
- server.stop();
if (
server.upgrade(req, {
data: { count: 0 },
@@ -879,54 +947,56 @@ describe("websocket server", () => {
return new Response("noooooo hello world");
},
});
+ try {
+ await new Promise<void>((resolve, reject) => {
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onerror = e => {
+ reject(e);
+ };
- await new Promise<void>((resolve, reject) => {
- const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
- websocket.onerror = e => {
- reject(e);
- };
+ websocket.onopen = () => {
+ server.stop();
+ websocket.send("first");
+ };
- var counter = 0;
- websocket.onopen = () => websocket.send("first");
- websocket.onmessage = e => {
- try {
- const expected = sendQueue[clientCounter++] + " ";
- expect(e.data).toBe(expected);
- websocket.send("next");
- if (clientCounter === sendQueue.length) {
+ websocket.onmessage = e => {
+ try {
+ const expected = sendQueue[clientCounter++] + " ";
+ expect(e.data).toBe(expected);
+ websocket.send("next");
+ if (clientCounter === sendQueue.length) {
+ websocket.close();
+ resolve();
+ }
+ } catch (r) {
+ reject(r);
+ console.error(r);
websocket.close();
- resolve();
}
- } catch (r) {
- reject(r);
- console.error(r);
- websocket.close();
- }
- };
- });
- server.stop(true);
+ };
+ });
+ } catch (e) {
+ throw e;
+ } finally {
+ server.stop(true);
+ }
});
- // this test sends 100 messages to 10 connected clients via pubsub
+ // this test sends 50 messages to 10 connected clients via pubsub
it("pub/sub", async () => {
var ropey = "hello world".repeat(10);
var sendQueue: any[] = [];
- for (var i = 0; i < 100; i++) {
+ for (var i = 0; i < 50; i++) {
sendQueue.push(ropey + " " + i);
- gcTick();
}
+
var serverCounter = 0;
var clientCount = 0;
const server = serve({
port: 0,
websocket: {
- // FIXME: update this test to not rely on publishToSelf: true,
- publishToSelf: true,
-
open(ws) {
- server.stop();
ws.subscribe("test");
- gcTick();
if (!ws.isSubscribed("test")) {
throw new Error("not subscribed");
}
@@ -936,15 +1006,15 @@ describe("websocket server", () => {
}
ws.subscribe("test");
clientCount++;
- if (clientCount === 10) setTimeout(() => ws.publish("test", "hello world"), 1);
+ if (clientCount === 10) {
+ setTimeout(() => server.publish("test", "hello world"), 1);
+ }
},
message(ws, msg) {
- if (serverCounter < sendQueue.length) ws.publish("test", sendQueue[serverCounter++] + " ");
+ if (serverCounter < sendQueue.length) server.publish("test", sendQueue[serverCounter++] + " ");
},
},
fetch(req) {
- gcTick();
- server.stop();
if (
server.upgrade(req, {
data: { count: 0 },
@@ -954,90 +1024,90 @@ describe("websocket server", () => {
return new Response("noooooo hello world");
},
});
+ try {
+ const connections = new Array(10);
+ const websockets = new Array(connections.length);
+ var doneCounter = 0;
+ await new Promise<void>(done => {
+ for (var i = 0; i < connections.length; i++) {
+ var j = i;
+ var resolve: (_?: unknown) => void,
+ reject: (_?: unknown) => void,
+ resolveConnection: (_?: unknown) => void,
+ rejectConnection: (_?: unknown) => void;
+ connections[j] = new Promise((res, rej) => {
+ resolveConnection = res;
+ rejectConnection = rej;
+ });
+ websockets[j] = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej;
+ });
+ const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
+ websocket.onerror = e => {
+ reject(e);
+ };
+ websocket.onclose = () => {
+ doneCounter++;
+ if (doneCounter === connections.length) {
+ done();
+ }
+ };
+ var hasOpened = false;
+ websocket.onopen = () => {
+ if (!hasOpened) {
+ hasOpened = true;
+ resolve(websocket);
+ }
+ };
- const connections = new Array(10);
- const websockets = new Array(connections.length);
- var doneCounter = 0;
- await new Promise<void>(done => {
- for (var i = 0; i < connections.length; i++) {
- var j = i;
- var resolve: (_?: unknown) => void,
- reject: (_?: unknown) => void,
- resolveConnection: (_?: unknown) => void,
- rejectConnection: (_?: unknown) => void;
- connections[j] = new Promise((res, rej) => {
- resolveConnection = res;
- rejectConnection = rej;
- });
- websockets[j] = new Promise((res, rej) => {
- resolve = res;
- reject = rej;
- });
- gcTick();
- const websocket = new WebSocket(`ws://${server.hostname}:${server.port}`);
- websocket.onerror = e => {
- reject(e);
- };
- websocket.onclose = () => {
- doneCounter++;
- if (doneCounter === connections.length) {
- done();
- }
- };
- var hasOpened = false;
- websocket.onopen = () => {
- if (!hasOpened) {
- hasOpened = true;
- resolve(websocket);
- }
- };
-
- let clientCounter = -1;
- var hasSentThisTick = false;
-
- websocket.onmessage = e => {
- gcTick();
-
- if (!hasOpened) {
- hasOpened = true;
- resolve(websocket);
- }
+ let clientCounter = -1;
+ var hasSentThisTick = false;
- if (e.data === "hello world") {
- clientCounter = 0;
- websocket.send("first");
- return;
- }
+ websocket.onmessage = e => {
+ if (!hasOpened) {
+ hasOpened = true;
+ resolve(websocket);
+ }
- try {
- expect(!!sendQueue.find(a => a + " " === e.data)).toBe(true);
-
- if (!hasSentThisTick) {
- websocket.send("second");
- hasSentThisTick = true;
- queueMicrotask(() => {
- hasSentThisTick = false;
- });
+ if (e.data === "hello world") {
+ clientCounter = 0;
+ websocket.send("first");
+ return;
}
- gcTick();
+ try {
+ expect(!!sendQueue.find(a => a + " " === e.data)).toBe(true);
+
+ if (!hasSentThisTick) {
+ websocket.send("second");
+ hasSentThisTick = true;
+ queueMicrotask(() => {
+ hasSentThisTick = false;
+ });
+ }
- if (clientCounter++ === sendQueue.length - 1) {
+ if (clientCounter++ === sendQueue.length - 1) {
+ websocket.close();
+ resolveConnection();
+ }
+ } catch (r) {
+ console.error(r);
websocket.close();
- resolveConnection();
+ rejectConnection(r);
}
- } catch (r) {
- console.error(r);
- websocket.close();
- rejectConnection(r);
- gcTick();
- }
- };
- }
- });
+ };
+ }
+ });
+ } catch (e) {
+ throw e;
+ } finally {
+ server.stop(true);
+ gcTick();
+ }
+
expect(serverCounter).toBe(sendQueue.length);
- server.stop(true);
- }, 10_000);
+ }, 30_000);
it("can close with reason and code #2631", done => {
let timeout: any;
let server = Bun.serve({
diff --git a/test/js/node/assert/assert.test.cjs b/test/js/node/assert/assert.test.cjs
new file mode 100644
index 000000000..e9d472412
--- /dev/null
+++ b/test/js/node/assert/assert.test.cjs
@@ -0,0 +1,9 @@
+const assert = require("assert");
+
+test("assert from require as a function does not throw", () => assert(true));
+test("assert from require as a function does throw", () => {
+ try {
+ assert(false);
+ expect(false).toBe(true);
+ } catch (e) {}
+});
diff --git a/test/js/node/assert/assert-test.test.ts b/test/js/node/assert/assert.test.ts
index 1723b7d47..1723b7d47 100644
--- a/test/js/node/assert/assert-test.test.ts
+++ b/test/js/node/assert/assert.test.ts
diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js
index 697774e0a..cfd114423 100644
--- a/test/js/node/buffer.test.js
+++ b/test/js/node/buffer.test.js
@@ -1,4 +1,4 @@
-import { Buffer, SlowBuffer } from "buffer";
+import { Buffer, SlowBuffer, isAscii, isUtf8 } from "buffer";
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
import { gc } from "harness";
@@ -7,6 +7,28 @@ const BufferModule = await import("buffer");
beforeEach(() => gc());
afterEach(() => gc());
+it("isAscii", () => {
+ expect(isAscii(new Buffer("abc"))).toBeTrue();
+ expect(isAscii(new Buffer(""))).toBeTrue();
+ expect(isAscii(new Buffer([32, 32, 128]))).toBeFalse();
+ expect(isAscii(new Buffer("What did the 🦊 say?"))).toBeFalse();
+
+ expect(isAscii(new Buffer("").buffer)).toBeTrue();
+ expect(isAscii(new Buffer([32, 32, 128]).buffer)).toBeFalse();
+});
+
+it("isUtf8", () => {
+ expect(isUtf8(new Buffer("abc"))).toBeTrue();
+ expect(isAscii(new Buffer(""))).toBeTrue();
+ expect(isUtf8(new Buffer("What did the 🦊 say?"))).toBeTrue();
+ expect(isUtf8(new Buffer([129, 129, 129]))).toBeFalse();
+
+ expect(isUtf8(new Buffer("abc").buffer)).toBeTrue();
+ expect(isAscii(new Buffer("").buffer)).toBeTrue();
+ expect(isUtf8(new Buffer("What did the 🦊 say?").buffer)).toBeTrue();
+ expect(isUtf8(new Buffer([129, 129, 129]).buffer)).toBeFalse();
+});
+
// https://github.com/oven-sh/bun/issues/2052
it("Buffer global is settable", () => {
var prevBuffer = globalThis.Buffer;
@@ -2353,6 +2375,85 @@ it("Buffer.byteLength()", () => {
}
});
+it("Buffer.toString(encoding, start, end)", () => {
+ const buf = Buffer.from("0123456789", "utf8");
+
+ expect(buf.toString()).toStrictEqual("0123456789");
+ expect(buf.toString("utf8")).toStrictEqual("0123456789");
+ expect(buf.toString("utf8", 3)).toStrictEqual("3456789");
+ expect(buf.toString("utf8", 3, 4)).toStrictEqual("3");
+
+ expect(buf.toString("utf8", 3, 100)).toStrictEqual("3456789");
+ expect(buf.toString("utf8", 3, 1)).toStrictEqual("");
+ expect(buf.toString("utf8", 100, 200)).toStrictEqual("");
+ expect(buf.toString("utf8", 100, 1)).toStrictEqual("");
+});
+
+it("Buffer.toString(offset, length, encoding)", () => {
+ const buf = Buffer.from("0123456789", "utf8");
+
+ expect(buf.toString(3, 6, "utf8")).toStrictEqual("345678");
+ expect(buf.toString(3, 100, "utf8")).toStrictEqual("3456789");
+ expect(buf.toString(100, 200, "utf8")).toStrictEqual("");
+ expect(buf.toString(100, 50, "utf8")).toStrictEqual("");
+});
+
+it("Buffer.asciiSlice())", () => {
+ const buf = Buffer.from("0123456789", "ascii");
+
+ expect(buf.asciiSlice()).toStrictEqual("0123456789");
+ expect(buf.asciiSlice(3)).toStrictEqual("3456789");
+ expect(buf.asciiSlice(3, 4)).toStrictEqual("3");
+});
+
+it("Buffer.latin1Slice()", () => {
+ const buf = Buffer.from("âéö", "latin1");
+
+ expect(buf.latin1Slice()).toStrictEqual("âéö");
+ expect(buf.latin1Slice(1)).toStrictEqual("éö");
+ expect(buf.latin1Slice(1, 2)).toStrictEqual("é");
+});
+
+it("Buffer.utf8Slice()", () => {
+ const buf = Buffer.from("あいうえお", "utf8");
+
+ expect(buf.utf8Slice()).toStrictEqual("あいうえお");
+ expect(buf.utf8Slice(3)).toStrictEqual("いうえお");
+ expect(buf.utf8Slice(3, 6)).toStrictEqual("い");
+});
+
+it("Buffer.hexSlice()", () => {
+ const buf = Buffer.from("0123456789", "utf8");
+
+ expect(buf.hexSlice()).toStrictEqual("30313233343536373839");
+ expect(buf.hexSlice(3)).toStrictEqual("33343536373839");
+ expect(buf.hexSlice(3, 4)).toStrictEqual("33");
+});
+
+it("Buffer.ucs2Slice()", () => {
+ const buf = Buffer.from("あいうえお", "ucs2");
+
+ expect(buf.ucs2Slice()).toStrictEqual("あいうえお");
+ expect(buf.ucs2Slice(2)).toStrictEqual("いうえお");
+ expect(buf.ucs2Slice(2, 6)).toStrictEqual("いう");
+});
+
+it("Buffer.base64Slice()", () => {
+ const buf = Buffer.from("0123456789", "utf8");
+
+ expect(buf.base64Slice()).toStrictEqual("MDEyMzQ1Njc4OQ==");
+ expect(buf.base64Slice(3)).toStrictEqual("MzQ1Njc4OQ==");
+ expect(buf.base64Slice(3, 4)).toStrictEqual("Mw==");
+});
+
+it("Buffer.base64urlSlice()", () => {
+ const buf = Buffer.from("0123456789", "utf8");
+
+ expect(buf.base64urlSlice()).toStrictEqual("MDEyMzQ1Njc4OQ");
+ expect(buf.base64urlSlice(3)).toStrictEqual("MzQ1Njc4OQ");
+ expect(buf.base64urlSlice(3, 4)).toStrictEqual("Mw");
+});
+
it("should not crash on invalid UTF-8 byte sequence", () => {
const buf = Buffer.from([0xc0, 0xfd]);
expect(buf.length).toBe(2);
@@ -2392,3 +2493,48 @@ it("inspect() should exist", () => {
expect(Buffer.prototype.inspect).toBeInstanceOf(Function);
expect(new Buffer("123").inspect()).toBe(Bun.inspect(new Buffer("123")));
});
+
+it("read alias", () => {
+ var buf = new Buffer(1024);
+ var data = new DataView(buf.buffer);
+
+ data.setUint8(0, 200, false);
+
+ expect(buf.readUint8(0)).toBe(buf.readUInt8(0));
+ expect(buf.readUintBE(0, 4)).toBe(buf.readUIntBE(0, 4));
+ expect(buf.readUintLE(0, 4)).toBe(buf.readUIntLE(0, 4));
+ expect(buf.readUint16BE(0)).toBe(buf.readUInt16BE(0));
+ expect(buf.readUint16LE(0)).toBe(buf.readUInt16LE(0));
+ expect(buf.readUint32BE(0)).toBe(buf.readUInt32BE(0));
+ expect(buf.readUint32LE(0)).toBe(buf.readUInt32LE(0));
+ expect(buf.readBigUint64BE(0)).toBe(buf.readBigUInt64BE(0));
+ expect(buf.readBigUint64LE(0)).toBe(buf.readBigUInt64LE(0));
+});
+
+it("write alias", () => {
+ var buf = new Buffer(1024);
+ var buf2 = new Buffer(1024);
+
+ function reset() {
+ new Uint8Array(buf.buffer).fill(0);
+ new Uint8Array(buf2.buffer).fill(0);
+ }
+
+ function shouldBeSame(name, name2, ...args) {
+ buf[name].call(buf, ...args);
+ buf2[name2].call(buf2, ...args);
+
+ expect(buf).toStrictEqual(buf2);
+ reset();
+ }
+
+ shouldBeSame("writeUint8", "writeUInt8", 10);
+ shouldBeSame("writeUintBE", "writeUIntBE", 10, 0, 4);
+ shouldBeSame("writeUintLE", "writeUIntLE", 10, 0, 4);
+ shouldBeSame("writeUint16BE", "writeUInt16BE", 1000);
+ shouldBeSame("writeUint16LE", "writeUInt16LE", 1000);
+ shouldBeSame("writeUint32BE", "writeUInt32BE", 1000);
+ shouldBeSame("writeUint32LE", "writeUInt32LE", 1000);
+ shouldBeSame("writeBigUint64BE", "writeBigUInt64BE", BigInt(1000));
+ shouldBeSame("writeBigUint64LE", "writeBigUInt64LE", BigInt(1000));
+});
diff --git a/test/js/node/crypto/crypto.test.ts b/test/js/node/crypto/crypto.test.ts
index d8bfe5353..b1b8646f3 100644
--- a/test/js/node/crypto/crypto.test.ts
+++ b/test/js/node/crypto/crypto.test.ts
@@ -1,6 +1,6 @@
import { sha, MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256, gc, CryptoHasher } from "bun";
import { it, expect, describe } from "bun:test";
-
+import crypto from "crypto";
const HashClasses = [MD5, MD4, SHA1, SHA224, SHA256, SHA384, SHA512, SHA512_256];
describe("CryptoHasher", () => {
@@ -109,6 +109,13 @@ describe("CryptoHasher", () => {
}
});
+describe("crypto.getCurves", () => {
+ it("should return an array of strings", () => {
+ expect(Array.isArray(crypto.getCurves())).toBe(true);
+ expect(typeof crypto.getCurves()[0]).toBe("string");
+ });
+});
+
describe("crypto", () => {
for (let Hash of HashClasses) {
for (let [input, label] of [
diff --git a/test/js/node/crypto/node-crypto.test.js b/test/js/node/crypto/node-crypto.test.js
index 9e0e7f396..2489f96c7 100644
--- a/test/js/node/crypto/node-crypto.test.js
+++ b/test/js/node/crypto/node-crypto.test.js
@@ -8,6 +8,27 @@ it("crypto.randomBytes should return a Buffer", () => {
expect(Buffer.isBuffer(crypto.randomBytes(1))).toBe(true);
});
+it("crypto.randomInt should return a number", () => {
+ const result = crypto.randomInt(0, 10);
+ expect(typeof result).toBe("number");
+ expect(result).toBeGreaterThanOrEqual(0);
+ expect(result).toBeLessThanOrEqual(10);
+});
+
+it("crypto.randomInt with no arguments", () => {
+ const result = crypto.randomInt();
+ expect(typeof result).toBe("number");
+ expect(result).toBeGreaterThanOrEqual(0);
+ expect(result).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
+});
+
+it("crypto.randomInt with one argument", () => {
+ const result = crypto.randomInt(100);
+ expect(typeof result).toBe("number");
+ expect(result).toBeGreaterThanOrEqual(0);
+ expect(result).toBeLessThanOrEqual(100);
+});
+
// https://github.com/oven-sh/bun/issues/1839
describe("createHash", () => {
it("update & digest", () => {
@@ -22,6 +43,50 @@ describe("createHash", () => {
expect(Buffer.isBuffer(hash.digest())).toBeTrue();
});
+ const otherEncodings = {
+ ucs2: [
+ 11626, 2466, 37699, 38942, 64564, 53010, 48101, 47943, 44761, 18499, 12442, 26994, 46434, 62582, 39395, 20542,
+ ],
+ latin1: [
+ 106, 45, 162, 9, 67, 147, 30, 152, 52, 252, 18, 207, 229, 187, 71, 187, 217, 174, 67, 72, 154, 48, 114, 105, 98,
+ 181, 118, 244, 227, 153, 62, 80,
+ ],
+ binary: [
+ 106, 45, 162, 9, 67, 147, 30, 152, 52, 252, 18, 207, 229, 187, 71, 187, 217, 174, 67, 72, 154, 48, 114, 105, 98,
+ 181, 118, 244, 227, 153, 62, 80,
+ ],
+ base64: [
+ 97, 105, 50, 105, 67, 85, 79, 84, 72, 112, 103, 48, 47, 66, 76, 80, 53, 98, 116, 72, 117, 57, 109, 117, 81, 48,
+ 105, 97, 77, 72, 74, 112, 89, 114, 86, 50, 57, 79, 79, 90, 80, 108, 65, 61,
+ ],
+ hex: [
+ 54, 97, 50, 100, 97, 50, 48, 57, 52, 51, 57, 51, 49, 101, 57, 56, 51, 52, 102, 99, 49, 50, 99, 102, 101, 53, 98,
+ 98, 52, 55, 98, 98, 100, 57, 97, 101, 52, 51, 52, 56, 57, 97, 51, 48, 55, 50, 54, 57, 54, 50, 98, 53, 55, 54, 102,
+ 52, 101, 51, 57, 57, 51, 101, 53, 48,
+ ],
+ ascii: [
+ 106, 45, 34, 9, 67, 19, 30, 24, 52, 124, 18, 79, 101, 59, 71, 59, 89, 46, 67, 72, 26, 48, 114, 105, 98, 53, 118,
+ 116, 99, 25, 62, 80,
+ ],
+ utf8: [
+ 106, 45, 65533, 9, 67, 65533, 30, 65533, 52, 65533, 18, 65533, 65533, 71, 65533, 1646, 67, 72, 65533, 48, 114,
+ 105, 98, 65533, 118, 65533, 65533, 62, 80,
+ ],
+ };
+
+ for (let encoding in otherEncodings) {
+ it("digest " + encoding, () => {
+ const hash = crypto.createHash("sha256");
+ hash.update("some data to hash");
+ expect(
+ hash
+ .digest(encoding)
+ .split("")
+ .map(a => a.charCodeAt(0)),
+ ).toEqual(otherEncodings[encoding]);
+ });
+ }
+
it("stream (sync)", () => {
const hash = crypto.createHash("sha256");
hash.write("some data to hash");
diff --git a/test/js/node/disabled-module.test.cjs b/test/js/node/disabled-module.test.cjs
new file mode 100644
index 000000000..bc4817b8d
--- /dev/null
+++ b/test/js/node/disabled-module.test.cjs
@@ -0,0 +1,6 @@
+test("not implemented yet module masquerades as undefined in cjs and throws an error", () => {
+ const worker_threads = require("worker_threads");
+
+ expect(typeof worker_threads).toBe("undefined");
+ expect(typeof worker_threads.getEnvironmentData).toBe("undefined");
+});
diff --git a/test/js/node/disabled-module.test.js b/test/js/node/disabled-module.test.js
index d02a6b6df..bb707a122 100644
--- a/test/js/node/disabled-module.test.js
+++ b/test/js/node/disabled-module.test.js
@@ -1,15 +1,16 @@
import { expect, test } from "bun:test";
+import { AsyncResource, AsyncLocalStorage } from "async_hooks";
+import * as worker_threads from "worker_threads";
+import worker_threads_default from "worker_threads";
test("not implemented yet module masquerades as undefined and throws an error", () => {
- const worker_threads = import.meta.require("worker_threads");
-
- expect(typeof worker_threads).toBe("undefined");
+ expect(typeof worker_threads.default).toBe("undefined");
+ expect(typeof worker_threads_default).toBe("undefined");
expect(typeof worker_threads.getEnvironmentData).toBe("undefined");
+ expect(typeof worker_threads_default.getEnvironmentData).toBe("undefined");
});
test("AsyncLocalStorage polyfill", () => {
- const { AsyncLocalStorage } = import.meta.require("async_hooks");
-
const store = new AsyncLocalStorage();
var called = false;
expect(store.getStore()).toBe(null);
@@ -22,8 +23,6 @@ test("AsyncLocalStorage polyfill", () => {
});
test("AsyncResource polyfill", () => {
- const { AsyncResource } = import.meta.require("async_hooks");
-
const resource = new AsyncResource("prisma-client-request");
var called = false;
resource.runInAsyncScope(
@@ -36,3 +35,9 @@ test("AsyncResource polyfill", () => {
);
expect(called).toBe(true);
});
+
+test("esbuild functions with worker_threads stub", async () => {
+ const esbuild = await import("esbuild");
+ const result = await esbuild.transform('console . log( "hello world" )', { minify: true });
+ expect(result.code).toBe('console.log("hello world");\n');
+});
diff --git a/test/js/node/dns/dns.node.mjs b/test/js/node/dns/dns.node.mjs
deleted file mode 100644
index e69de29bb..000000000
--- a/test/js/node/dns/dns.node.mjs
+++ /dev/null
diff --git a/test/js/node/dns/node-dns.test.js b/test/js/node/dns/node-dns.test.js
index 5fb8e0739..5de840146 100644
--- a/test/js/node/dns/node-dns.test.js
+++ b/test/js/node/dns/node-dns.test.js
@@ -1,5 +1,6 @@
import { expect, test } from "bun:test";
import * as dns from "node:dns";
+import * as dns_promises from "node:dns/promises";
// TODO:
test("it exists", () => {
@@ -18,6 +19,38 @@ test("it exists", () => {
expect(dns.resolveNs).toBeDefined();
expect(dns.resolvePtr).toBeDefined();
expect(dns.resolveCname).toBeDefined();
+
+ expect(dns.promises).toBeDefined();
+ expect(dns.promises.lookup).toBeDefined();
+ expect(dns.promises.lookupService).toBeDefined();
+ expect(dns.promises.resolve).toBeDefined();
+ expect(dns.promises.resolve4).toBeDefined();
+ expect(dns.promises.resolve6).toBeDefined();
+ expect(dns.promises.resolveSrv).toBeDefined();
+ expect(dns.promises.resolveTxt).toBeDefined();
+ expect(dns.promises.resolveSoa).toBeDefined();
+ expect(dns.promises.resolveNaptr).toBeDefined();
+ expect(dns.promises.resolveMx).toBeDefined();
+ expect(dns.promises.resolveCaa).toBeDefined();
+ expect(dns.promises.resolveNs).toBeDefined();
+ expect(dns.promises.resolvePtr).toBeDefined();
+ expect(dns.promises.resolveCname).toBeDefined();
+
+ expect(dns_promises).toBeDefined();
+ expect(dns_promises.lookup).toBeDefined();
+ expect(dns_promises.lookupService).toBeDefined();
+ expect(dns_promises.resolve).toBeDefined();
+ expect(dns_promises.resolve4).toBeDefined();
+ expect(dns_promises.resolve6).toBeDefined();
+ expect(dns_promises.resolveSrv).toBeDefined();
+ expect(dns_promises.resolveTxt).toBeDefined();
+ expect(dns_promises.resolveSoa).toBeDefined();
+ expect(dns_promises.resolveNaptr).toBeDefined();
+ expect(dns_promises.resolveMx).toBeDefined();
+ expect(dns_promises.resolveCaa).toBeDefined();
+ expect(dns_promises.resolveNs).toBeDefined();
+ expect(dns_promises.resolvePtr).toBeDefined();
+ expect(dns_promises.resolveCname).toBeDefined();
});
// //TODO: use a bun.sh SRV for testing
diff --git a/test/js/node/events/event-emitter.test.ts b/test/js/node/events/event-emitter.test.ts
index cef309d48..5a1385383 100644
--- a/test/js/node/events/event-emitter.test.ts
+++ b/test/js/node/events/event-emitter.test.ts
@@ -1,5 +1,6 @@
import { test, describe, expect } from "bun:test";
import { sleep } from "bun";
+import { createRequire } from "module";
// 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
@@ -534,4 +535,10 @@ describe("EventEmitter constructors", () => {
expect(called).toBe(true);
});
}
+
+ test("with createRequire, events is callable", () => {
+ const req = createRequire(import.meta.path);
+ const events = req("events");
+ new events();
+ });
});
diff --git a/test/js/node/events/events-cjs.test.js b/test/js/node/events/events-cjs.test.js
new file mode 100644
index 000000000..5bee9979f
--- /dev/null
+++ b/test/js/node/events/events-cjs.test.js
@@ -0,0 +1,4 @@
+test("in cjs, events is callable", () => {
+ const events = require("events");
+ new events();
+});
diff --git a/test/js/node/fs/fs.test.ts b/test/js/node/fs/fs.test.ts
index 37c3253a4..48aa9d3b9 100644
--- a/test/js/node/fs/fs.test.ts
+++ b/test/js/node/fs/fs.test.ts
@@ -29,6 +29,8 @@ import fs, {
realpathSync,
readlinkSync,
symlinkSync,
+ writevSync,
+ readvSync,
} from "node:fs";
import _promises from "node:fs/promises";
@@ -157,6 +159,18 @@ it("readdirSync on import.meta.dir", () => {
expect(match).toBe(true);
});
+it("statSync throwIfNoEntry", () => {
+ expect(statSync("/tmp/404/not-found/ok", { throwIfNoEntry: false })).toBeUndefined();
+ expect(lstatSync("/tmp/404/not-found/ok", { throwIfNoEntry: false })).toBeUndefined();
+});
+
+it("statSync throwIfNoEntry: true", () => {
+ expect(() => statSync("/tmp/404/not-found/ok", { throwIfNoEntry: true })).toThrow("No such file or directory");
+ expect(() => statSync("/tmp/404/not-found/ok")).toThrow("No such file or directory");
+ expect(() => lstatSync("/tmp/404/not-found/ok", { throwIfNoEntry: true })).toThrow("No such file or directory");
+ expect(() => lstatSync("/tmp/404/not-found/ok")).toThrow("No such file or directory");
+});
+
// https://github.com/oven-sh/bun/issues/1887
it("mkdtempSync, readdirSync, rmdirSync and unlinkSync with non-ascii", () => {
const tempdir = mkdtempSync(`${tmpdir()}/emoji-fruit-🍇 🍈 🍉 🍊 🍋`);
@@ -276,6 +290,41 @@ it("readdirSync throws when given a file path with trailing slash", () => {
describe("readSync", () => {
const firstFourBytes = new Uint32Array(new TextEncoder().encode("File").buffer)[0];
+
+ it("works on large files", () => {
+ const dest = join(tmpdir(), "readSync-large-file.txt");
+ rmSync(dest, { force: true });
+
+ const writefd = openSync(dest, "w");
+ writeSync(writefd, Buffer.from([0x10]), 0, 1, 4_900_000_000);
+ closeSync(writefd);
+
+ const fd = openSync(dest, "r");
+ const out = Buffer.alloc(1);
+ const bytes = readSync(fd, out, 0, 1, 4_900_000_000);
+ expect(bytes).toBe(1);
+ expect(out[0]).toBe(0x10);
+ closeSync(fd);
+ rmSync(dest, { force: true });
+ });
+
+ it("works with bigint on read", () => {
+ const dest = join(tmpdir(), "readSync-large-file-bigint.txt");
+ rmSync(dest, { force: true });
+
+ const writefd = openSync(dest, "w");
+ writeSync(writefd, Buffer.from([0x10]), 0, 1, 400);
+ closeSync(writefd);
+
+ const fd = openSync(dest, "r");
+ const out = Buffer.alloc(1);
+ const bytes = readSync(fd, out, 0, 1, 400n as any);
+ expect(bytes).toBe(1);
+ expect(out[0]).toBe(0x10);
+ closeSync(fd);
+ rmSync(dest, { force: true });
+ });
+
it("works with a position set to 0", () => {
const fd = openSync(import.meta.dir + "/readFileSync.txt", "r");
const four = new Uint8Array(4);
@@ -301,7 +350,87 @@ describe("readSync", () => {
});
});
+it("writevSync", () => {
+ var fd = openSync(`${tmpdir()}/writevSync.txt`, "w");
+ fs.ftruncateSync(fd, 0);
+ const buffers = [new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6]), new Uint8Array([7, 8, 9])];
+ const result = writevSync(fd, buffers);
+ expect(result).toBe(9);
+ closeSync(fd);
+
+ fd = openSync(`${tmpdir()}/writevSync.txt`, "r");
+ const buf = new Uint8Array(9);
+ readSync(fd, buf, 0, 9, 0);
+ expect(buf).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]));
+});
+
+it("pwritevSync", () => {
+ var fd = openSync(`${tmpdir()}/pwritevSync.txt`, "w");
+ fs.ftruncateSync(fd, 0);
+ writeSync(fd, "lalalala", 0);
+ const buffers = [new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6]), new Uint8Array([7, 8, 9])];
+ const result = writevSync(fd, buffers, "lalalala".length);
+ expect(result).toBe(9);
+ closeSync(fd);
+
+ const out = readFileSync(`${tmpdir()}/pwritevSync.txt`);
+ expect(out.slice(0, "lalalala".length).toString()).toBe("lalalala");
+ expect(out.slice("lalalala".length)).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]));
+});
+
+it("readvSync", () => {
+ var fd = openSync(`${tmpdir()}/readv.txt`, "w");
+ fs.ftruncateSync(fd, 0);
+
+ const buf = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ writeSync(fd, buf, 0, 9, 0);
+ closeSync(fd);
+
+ var fd = openSync(`${tmpdir()}/readv.txt`, "r");
+ const buffers = [new Uint8Array(3), new Uint8Array(3), new Uint8Array(3)];
+ const result = readvSync(fd, buffers);
+ expect(result).toBe(9);
+ expect(buffers[0]).toEqual(new Uint8Array([1, 2, 3]));
+ expect(buffers[1]).toEqual(new Uint8Array([4, 5, 6]));
+ expect(buffers[2]).toEqual(new Uint8Array([7, 8, 9]));
+ closeSync(fd);
+});
+
+it("preadv", () => {
+ var fd = openSync(`${tmpdir()}/preadv.txt`, "w");
+ fs.ftruncateSync(fd, 0);
+
+ const buf = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
+ writeSync(fd, buf, 0, buf.byteLength, 0);
+ closeSync(fd);
+
+ var fd = openSync(`${tmpdir()}/preadv.txt`, "r");
+ const buffers = [new Uint8Array(3), new Uint8Array(3), new Uint8Array(3)];
+ const result = readvSync(fd, buffers, 3);
+ expect(result).toBe(9);
+ expect(buffers[0]).toEqual(new Uint8Array([4, 5, 6]));
+ expect(buffers[1]).toEqual(new Uint8Array([7, 8, 9]));
+ expect(buffers[2]).toEqual(new Uint8Array([10, 11, 12]));
+});
+
describe("writeSync", () => {
+ it("works with bigint", () => {
+ const dest = join(tmpdir(), "writeSync-large-file-bigint.txt");
+ rmSync(dest, { force: true });
+
+ const writefd = openSync(dest, "w");
+ writeSync(writefd, Buffer.from([0x10]), 0, 1, 400n as any);
+ closeSync(writefd);
+
+ const fd = openSync(dest, "r");
+ const out = Buffer.alloc(1);
+ const bytes = readSync(fd, out, 0, 1, 400 as any);
+ expect(bytes).toBe(1);
+ expect(out[0]).toBe(0x10);
+ closeSync(fd);
+ rmSync(dest, { force: true });
+ });
+
it("works with a position set to 0", () => {
const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+");
const four = new Uint8Array(4);
@@ -1068,6 +1197,44 @@ describe("createWriteStream", () => {
expect(exception.code).toBe("ERR_INVALID_ARG_TYPE");
}
});
+
+ it("writing in append mode should not truncate the file", async () => {
+ const path = `${tmpdir()}/fs.test.js/${Date.now()}.createWriteStreamAppend.txt`;
+ const stream = createWriteStream(path, {
+ // @ts-ignore-next-line
+ flags: "a",
+ });
+ stream.write("first line\n");
+ stream.end();
+
+ await new Promise((resolve, reject) => {
+ stream.on("error", e => {
+ reject(e);
+ });
+
+ stream.on("finish", () => {
+ resolve(true);
+ });
+ });
+
+ const stream2 = createWriteStream(path, {
+ // @ts-ignore-next-line
+ flags: "a",
+ });
+ stream2.write("second line\n");
+ stream2.end();
+
+ return await new Promise((resolve, reject) => {
+ stream2.on("error", e => {
+ reject(e);
+ });
+
+ stream2.on("finish", () => {
+ expect(readFileSync(path, "utf8")).toBe("first line\nsecond line\n");
+ resolve(true);
+ });
+ });
+ });
});
describe("fs/promises", () => {
@@ -1293,3 +1460,26 @@ describe("utimesSync", () => {
expect(finalStats.atime).toEqual(prevAccessTime);
});
});
+
+it("createReadStream on a large file emits readable event correctly", () => {
+ return new Promise<void>((resolve, reject) => {
+ const tmp = mkdtempSync(`${tmpdir()}/readable`);
+ // write a 10mb file
+ writeFileSync(`${tmp}/large.txt`, "a".repeat(10 * 1024 * 1024));
+ var stream = createReadStream(`${tmp}/large.txt`);
+ var ended = false;
+ var timer: Timer;
+ stream.on("readable", () => {
+ const v = stream.read();
+ if (ended) {
+ clearTimeout(timer);
+ reject(new Error("readable emitted after end"));
+ } else if (v == null) {
+ ended = true;
+ timer = setTimeout(() => {
+ resolve();
+ }, 20);
+ }
+ });
+ });
+});
diff --git a/test/js/node/fs/node-fetch.cjs.test.js b/test/js/node/fs/node-fetch.cjs.test.js
new file mode 100644
index 000000000..9a6a4b407
--- /dev/null
+++ b/test/js/node/fs/node-fetch.cjs.test.js
@@ -0,0 +1,13 @@
+const fetch = require("node-fetch");
+
+test("require('node-fetch') fetches", async () => {
+ const server = Bun.serve({
+ port: 0,
+ fetch(req, server) {
+ server.stop();
+ return new Response();
+ },
+ });
+ expect(await fetch("http://" + server.hostname + ":" + server.port)).toBeInstanceOf(Response);
+ server.stop(true);
+});
diff --git a/test/js/node/fs/node-fetch.test.js b/test/js/node/fs/node-fetch.test.js
index 11c5e0ed3..33af3252d 100644
--- a/test/js/node/fs/node-fetch.test.js
+++ b/test/js/node/fs/node-fetch.test.js
@@ -1,4 +1,4 @@
-import { fetch, Response, Request, Headers } from "node-fetch";
+import fetch2, { fetch, Response, Request, Headers } from "node-fetch";
import { test, expect } from "bun:test";
@@ -19,3 +19,15 @@ test("node-fetch fetches", async () => {
expect(await fetch("http://" + server.hostname + ":" + server.port)).toBeInstanceOf(Response);
server.stop(true);
});
+
+test("node-fetch.default fetches", async () => {
+ const server = Bun.serve({
+ port: 0,
+ fetch(req, server) {
+ server.stop();
+ return new Response();
+ },
+ });
+ expect(await fetch2("http://" + server.hostname + ":" + server.port)).toBeInstanceOf(Response);
+ server.stop(true);
+});
diff --git a/test/js/node/http/node-http.test.ts b/test/js/node/http/node-http.test.ts
index b1910a1f7..0e7b3ca13 100644
--- a/test/js/node/http/node-http.test.ts
+++ b/test/js/node/http/node-http.test.ts
@@ -1,5 +1,14 @@
// @ts-nocheck
-import { createServer, request, get, Agent, globalAgent, Server } from "node:http";
+import {
+ createServer,
+ request,
+ get,
+ Agent,
+ globalAgent,
+ Server,
+ validateHeaderName,
+ validateHeaderValue,
+} from "node:http";
import { createTest } from "node-harness";
const { describe, expect, it, beforeAll, afterAll, createDoneDotAll } = createTest(import.meta.path);
@@ -137,6 +146,29 @@ describe("node:http", () => {
res.end("Path correct!\n");
return;
}
+ if (reqUrl.pathname === "/customWriteHead") {
+ function createWriteHead(prevWriteHead, listener) {
+ let fired = false;
+ return function writeHead() {
+ if (!fired) {
+ fired = true;
+ listener.call(this);
+ }
+ return prevWriteHead.apply(this, arguments);
+ };
+ }
+
+ function addPoweredBy() {
+ if (!this.getHeader("X-Powered-By")) {
+ this.setHeader("X-Powered-By", "Bun");
+ }
+ }
+
+ res.writeHead = createWriteHead(res.writeHead, addPoweredBy);
+ res.setHeader("Content-Type", "text/plain");
+ res.end("Hello World");
+ return;
+ }
}
res.writeHead(200, { "Content-Type": "text/plain" });
@@ -498,6 +530,16 @@ describe("node:http", () => {
req.end();
});
});
+ it("reassign writeHead method, issue#3585", done => {
+ runTest(done, (server, serverPort, done) => {
+ const req = request(`http://localhost:${serverPort}/customWriteHead`, res => {
+ expect(res.headers["content-type"]).toBe("text/plain");
+ expect(res.headers["x-powered-by"]).toBe("Bun");
+ done();
+ });
+ req.end();
+ });
+ });
});
describe("signal", () => {
@@ -624,4 +666,16 @@ describe("node:http", () => {
});
});
});
+
+ test("validateHeaderName", () => {
+ validateHeaderName("Foo");
+ expect(() => validateHeaderName("foo:")).toThrow();
+ expect(() => validateHeaderName("foo:bar")).toThrow();
+ });
+
+ test("validateHeaderValue", () => {
+ validateHeaderValue("Foo", "Bar");
+ expect(() => validateHeaderValue("Foo", undefined as any)).toThrow();
+ expect(() => validateHeaderValue("Foo", "Bar\r")).toThrow();
+ });
});
diff --git a/test/js/node/module/node-module-module.test.js b/test/js/node/module/node-module-module.test.js
index 549b5e085..434bac829 100644
--- a/test/js/node/module/node-module-module.test.js
+++ b/test/js/node/module/node-module-module.test.js
@@ -1,5 +1,29 @@
import { expect, test } from "bun:test";
+import { _nodeModulePaths } from "module";
+import Module from "module";
test("module.globalPaths exists", () => {
expect(Array.isArray(require("module").globalPaths)).toBe(true);
});
+
+test("Module exists", () => {
+ expect(Module).toBeDefined();
+});
+
+test("_nodeModulePaths() works", () => {
+ expect(() => {
+ _nodeModulePaths();
+ }).toThrow();
+ expect(_nodeModulePaths(".").length).toBeGreaterThan(0);
+ expect(_nodeModulePaths(".").pop()).toBe("/node_modules");
+ expect(_nodeModulePaths("")).toEqual(_nodeModulePaths("."));
+ expect(_nodeModulePaths("/")).toEqual(["/node_modules"]);
+ expect(_nodeModulePaths("/a/b/c/d")).toEqual([
+ "/a/b/c/d/node_modules",
+ "/a/b/c/node_modules",
+ "/a/b/node_modules",
+ "/a/node_modules",
+ "/node_modules",
+ ]);
+ expect(_nodeModulePaths("/a/b/../d")).toEqual(["/a/d/node_modules", "/a/node_modules", "/node_modules"]);
+});
diff --git a/test/js/node/net/node-net-server.test.ts b/test/js/node/net/node-net-server.test.ts
index 398959bd6..3cdaa17e1 100644
--- a/test/js/node/net/node-net-server.test.ts
+++ b/test/js/node/net/node-net-server.test.ts
@@ -181,61 +181,6 @@ describe("net.createServer listen", () => {
);
});
- it("should listen on the correct port", done => {
- const { mustCall, mustNotCall } = createCallCheckCtx(done);
-
- const server: Server = createServer();
-
- let timeout: Timer;
- const closeAndFail = () => {
- clearTimeout(timeout);
- server.close();
- mustNotCall()();
- };
- server.on("error", closeAndFail);
- timeout = setTimeout(closeAndFail, 100);
-
- server.listen(
- 49027,
- mustCall(() => {
- const address = server.address() as AddressInfo;
- expect(address.address).toStrictEqual("::");
- expect(address.port).toStrictEqual(49027);
- expect(address.family).toStrictEqual("IPv6");
- server.close();
- done();
- }),
- );
- });
-
- it("should listen on the correct port with IPV4", done => {
- const { mustCall, mustNotCall } = createCallCheckCtx(done);
-
- const server: Server = createServer();
-
- let timeout: Timer;
- const closeAndFail = () => {
- clearTimeout(timeout);
- server.close();
- mustNotCall()();
- };
- server.on("error", closeAndFail);
- timeout = setTimeout(closeAndFail, 100);
-
- server.listen(
- 49026,
- "0.0.0.0",
- mustCall(() => {
- const address = server.address() as AddressInfo;
- expect(address.address).toStrictEqual("0.0.0.0");
- expect(address.port).toStrictEqual(49026);
- expect(address.family).toStrictEqual("IPv4");
- server.close();
- done();
- }),
- );
- });
-
it("should listen on unix domain socket", done => {
const { mustCall, mustNotCall } = createCallCheckCtx(done);
diff --git a/test/js/node/os/os.test.js b/test/js/node/os/os.test.js
index d7229b56d..8b4d54bb7 100644
--- a/test/js/node/os/os.test.js
+++ b/test/js/node/os/os.test.js
@@ -43,11 +43,16 @@ it("tmpdir", () => {
expect(os.tmpdir()).toBe(process.env.TEMP || process.env.TMP);
expect(os.tmpdir()).toBe(`${process.env.SystemRoot || process.env.windir}\\temp`);
} else {
+ const originalEnv = process.env.TMPDIR;
let dir = process.env.TMPDIR || process.env.TMP || process.env.TEMP || "/tmp";
if (dir.length > 1 && dir.endsWith("/")) {
dir = dir.substring(0, dir.length - 1);
}
expect(realpathSync(os.tmpdir())).toBe(realpathSync(dir));
+
+ process.env.TMPDIR = "/boop";
+ expect(os.tmpdir()).toBe("/boop");
+ process.env.TMPDIR = originalEnv;
}
});
diff --git a/test/js/node/path/path.test.js b/test/js/node/path/path.test.js
index 94b0568f6..8f5a76da7 100644
--- a/test/js/node/path/path.test.js
+++ b/test/js/node/path/path.test.js
@@ -1,7 +1,7 @@
const { file } = import.meta;
import { describe, it, expect } from "bun:test";
-import * as path from "node:path";
+import path from "node:path";
import assert from "assert";
import { hideFromStackTrace } from "harness";
diff --git a/test/js/node/process/call-raise.js b/test/js/node/process/call-raise.js
new file mode 100644
index 000000000..898906759
--- /dev/null
+++ b/test/js/node/process/call-raise.js
@@ -0,0 +1,15 @@
+import { dlopen } from "bun:ffi";
+
+var lazyRaise;
+export function raise(signal) {
+ if (!lazyRaise) {
+ const suffix = process.platform === "darwin" ? "dylib" : "so.6";
+ lazyRaise = dlopen(`libc.${suffix}`, {
+ raise: {
+ args: ["int"],
+ returns: "int",
+ },
+ }).symbols.raise;
+ }
+ lazyRaise(signal);
+}
diff --git a/test/js/node/process/print-process-args.js b/test/js/node/process/print-process-args.js
index 0ab238122..e9d2295c8 100644
--- a/test/js/node/process/print-process-args.js
+++ b/test/js/node/process/print-process-args.js
@@ -1,3 +1,11 @@
+import assert from "assert";
+
+// ensure process.argv and Bun.argv are the same
+assert.deepStrictEqual(process.argv, Bun.argv, "process.argv does not equal Bun.argv");
+assert(process.argv === process.argv, "process.argv isn't cached");
+// assert(Bun.argv === Bun.argv, 'Bun.argv isn\'t cached');
+// assert(Bun.argv === process.argv, 'Bun.argv doesnt share same ref as process.argv');
+
var writer = Bun.stdout.writer();
writer.write(JSON.stringify(process.argv));
await writer.flush(true);
diff --git a/test/js/node/process/process-exit-fixture.js b/test/js/node/process/process-exit-fixture.js
new file mode 100644
index 000000000..c5a492285
--- /dev/null
+++ b/test/js/node/process/process-exit-fixture.js
@@ -0,0 +1,16 @@
+process.on("beforeExit", () => {
+ throw new Error("process.on('beforeExit') called");
+});
+
+if (process._exiting) {
+ throw new Error("process._exiting should be undefined");
+}
+
+process.on("exit", () => {
+ if (!process._exiting) {
+ throw new Error("process.on('exit') called with process._exiting false");
+ }
+ console.log("PASS");
+});
+
+process.exit(0);
diff --git a/test/js/node/process/process-exitCode-fixture.js b/test/js/node/process/process-exitCode-fixture.js
new file mode 100644
index 000000000..2d5182d93
--- /dev/null
+++ b/test/js/node/process/process-exitCode-fixture.js
@@ -0,0 +1,7 @@
+process.exitCode = Number(process.argv.at(-1));
+process.on("exit", code => {
+ if (code !== process.exitCode) {
+ throw new Error("process.exitCode should be " + process.exitCode);
+ }
+ console.log("PASS");
+});
diff --git a/test/js/node/process/process-exitCode-with-exit.js b/test/js/node/process/process-exitCode-with-exit.js
new file mode 100644
index 000000000..610975bc2
--- /dev/null
+++ b/test/js/node/process/process-exitCode-with-exit.js
@@ -0,0 +1,8 @@
+process.exitCode = Number(process.argv.at(-1));
+process.on("exit", code => {
+ if (code !== process.exitCode) {
+ throw new Error("process.exitCode should be " + process.exitCode);
+ }
+ console.log("PASS");
+});
+process.exit();
diff --git a/test/js/node/process/process-onBeforeExit-fixture.js b/test/js/node/process/process-onBeforeExit-fixture.js
new file mode 100644
index 000000000..8cbdcebf0
--- /dev/null
+++ b/test/js/node/process/process-onBeforeExit-fixture.js
@@ -0,0 +1,7 @@
+process.on("beforeExit", () => {
+ console.log("beforeExit");
+});
+
+process.on("exit", () => {
+ console.log("exit");
+});
diff --git a/test/js/node/process/process-onBeforeExit-keepAlive.js b/test/js/node/process/process-onBeforeExit-keepAlive.js
new file mode 100644
index 000000000..45b20b763
--- /dev/null
+++ b/test/js/node/process/process-onBeforeExit-keepAlive.js
@@ -0,0 +1,18 @@
+let counter = 0;
+process.on("beforeExit", () => {
+ if (process._exiting) {
+ throw new Error("process._exiting should be undefined");
+ }
+
+ console.log("beforeExit:", counter);
+ if (!counter++) {
+ setTimeout(() => {}, 1);
+ }
+});
+
+process.on("exit", () => {
+ if (!process._exiting) {
+ throw new Error("process.on('exit') called with process._exiting false");
+ }
+ console.log("exit:", counter);
+});
diff --git a/test/js/node/process/process-signal-handler.fixture.js b/test/js/node/process/process-signal-handler.fixture.js
new file mode 100644
index 000000000..de5a78bda
--- /dev/null
+++ b/test/js/node/process/process-signal-handler.fixture.js
@@ -0,0 +1,63 @@
+import os from "os";
+import { raise } from "./call-raise";
+
+var counter = 0;
+function done() {
+ counter++;
+ if (counter === 2) {
+ setTimeout(() => {
+ if (counter !== 2) {
+ console.log(counter);
+ console.log("FAIL");
+ process.exit(1);
+ }
+
+ console.log("PASS");
+ process.exit(0);
+ }, 1);
+ }
+}
+
+var counter2 = 0;
+function done2() {
+ counter2++;
+ if (counter2 === 2) {
+ setTimeout(() => {
+ if (counter2 !== 2) {
+ console.log(counter2);
+ console.log("FAIL");
+ process.exit(1);
+ }
+
+ console.log("PASS");
+ process.exit(0);
+ }, 1);
+ }
+}
+
+const SIGUSR1 = os.constants.signals.SIGUSR1;
+const SIGUSR2 = os.constants.signals.SIGUSR2;
+
+switch (process.argv.at(-1)) {
+ case "SIGUSR1": {
+ process.on("SIGUSR1", () => {
+ done();
+ });
+ process.on("SIGUSR1", () => {
+ done();
+ });
+ raise(SIGUSR1);
+ break;
+ }
+ case "SIGUSR2": {
+ process.on("SIGUSR2", () => {
+ done2();
+ });
+ process.emit("SIGUSR2");
+ raise(SIGUSR2);
+ break;
+ }
+ default: {
+ throw new Error("Unknown argument: " + process.argv.at(-1));
+ }
+}
diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js
index ee181e70c..890f5032e 100644
--- a/test/js/node/process/process.test.js
+++ b/test/js/node/process/process.test.js
@@ -1,8 +1,8 @@
-import { resolveSync, which } from "bun";
+import { spawnSync, which } from "bun";
import { describe, expect, it } from "bun:test";
-import { existsSync, readFileSync, realpathSync } from "fs";
-import { bunExe } from "harness";
-import { basename, resolve } from "path";
+import { existsSync, readFileSync } from "fs";
+import { bunEnv, bunExe } from "harness";
+import { basename, join, resolve } from "path";
it("process", () => {
// this property isn't implemented yet but it should at least return a string
@@ -162,7 +162,8 @@ it("process.umask()", () => {
expect(process.umask()).toBe(orig);
});
-const versions = existsSync(import.meta.dir + "/../../src/generated_versions_list.zig");
+const generated_versions_list = join(import.meta.dir, "../../../../src/generated_versions_list.zig");
+const versions = existsSync(generated_versions_list);
(versions ? it : it.skip)("process.versions", () => {
// Generate a list of all the versions in the versions object
// example:
@@ -178,7 +179,7 @@ const versions = existsSync(import.meta.dir + "/../../src/generated_versions_lis
// pub const c_ares = "0e7a5dee0fbb04080750cf6eabbe89d8bae87faa";
// pub const usockets = "fafc241e8664243fc0c51d69684d5d02b9805134";
const versions = Object.fromEntries(
- readFileSync(import.meta.dir + "/../../src/generated_versions_list.zig", "utf8")
+ readFileSync(generated_versions_list, "utf8")
.split("\n")
.filter(line => line.startsWith("pub const") && !line.includes("zig") && line.includes(' = "'))
.map(line => line.split(" = "))
@@ -226,11 +227,254 @@ it("process.binding", () => {
expect(() => process.binding("buffer")).toThrow();
});
-it("process.argv", () => {
+it("process.argv in testing", () => {
expect(process.argv).toBeInstanceOf(Array);
expect(process.argv[0]).toBe(bunExe());
- expect(process.argv).toEqual(Bun.argv);
// assert we aren't creating a new process.argv each call
expect(process.argv).toBe(process.argv);
});
+
+describe("process.exitCode", () => {
+ it("validates int", () => {
+ expect(() => (process.exitCode = "potato")).toThrow("exitCode must be a number");
+ expect(() => (process.exitCode = 1.2)).toThrow('The "code" argument must be an integer');
+ expect(() => (process.exitCode = NaN)).toThrow('The "code" argument must be an integer');
+ expect(() => (process.exitCode = Infinity)).toThrow('The "code" argument must be an integer');
+ expect(() => (process.exitCode = -Infinity)).toThrow('The "code" argument must be an integer');
+ expect(() => (process.exitCode = -1)).toThrow("exitCode must be between 0 and 127");
+ });
+
+ it("works with implicit process.exit", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), join(import.meta.dir, "process-exitCode-with-exit.js"), "42"],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(42);
+ expect(stdout.toString().trim()).toBe("PASS");
+ });
+
+ it("works with explicit process.exit", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), join(import.meta.dir, "process-exitCode-fixture.js"), "42"],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(42);
+ expect(stdout.toString().trim()).toBe("PASS");
+ });
+});
+
+it("process.exit", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), join(import.meta.dir, "process-exit-fixture.js")],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(0);
+ expect(stdout.toString().trim()).toBe("PASS");
+});
+
+describe("process.onBeforeExit", () => {
+ it("emitted", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), join(import.meta.dir, "process-onBeforeExit-fixture.js")],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(0);
+ expect(stdout.toString().trim()).toBe("beforeExit\nexit");
+ });
+
+ it("works with explicit process.exit", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), join(import.meta.dir, "process-onBeforeExit-keepAlive.js")],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(0);
+ expect(stdout.toString().trim()).toBe("beforeExit: 0\nbeforeExit: 1\nexit: 2");
+ });
+});
+
+it("process.memoryUsage", () => {
+ expect(process.memoryUsage()).toEqual({
+ rss: expect.any(Number),
+ heapTotal: expect.any(Number),
+ heapUsed: expect.any(Number),
+ external: expect.any(Number),
+ arrayBuffers: expect.any(Number),
+ });
+});
+
+it("process.memoryUsage.rss", () => {
+ expect(process.memoryUsage.rss()).toEqual(expect.any(Number));
+});
+
+describe("process.cpuUsage", () => {
+ it("works", () => {
+ expect(process.cpuUsage()).toEqual({
+ user: expect.any(Number),
+ system: expect.any(Number),
+ });
+ });
+
+ it("works with diff", () => {
+ const init = process.cpuUsage();
+ for (let i = 0; i < 1000; i++) {}
+ const delta = process.cpuUsage(init);
+ expect(delta.user).toBeGreaterThan(0);
+ expect(delta.system).toBeGreaterThan(0);
+ });
+
+ it("works with diff of different structure", () => {
+ const init = {
+ user: 0,
+ system: 0,
+ };
+ for (let i = 0; i < 1000; i++) {}
+ const delta = process.cpuUsage(init);
+ expect(delta.user).toBeGreaterThan(0);
+ expect(delta.system).toBeGreaterThan(0);
+ });
+
+ it("throws on invalid property", () => {
+ const fixtures = [
+ {},
+ { user: null },
+ { user: {} },
+ { user: "potato" },
+
+ { user: 123 },
+ { user: 123, system: null },
+ { user: 123, system: "potato" },
+ ];
+ for (const fixture of fixtures) {
+ expect(() => process.cpuUsage(fixture)).toThrow();
+ }
+ });
+
+ // Skipped on Linux because it seems to not change as often as on macOS
+ it.skipIf(process.platform === "linux")("increases monotonically", () => {
+ const init = process.cpuUsage();
+ for (let i = 0; i < 10000; i++) {}
+ const another = process.cpuUsage();
+ expect(another.user).toBeGreaterThan(init.user);
+ expect(another.system).toBeGreaterThan(init.system);
+ });
+});
+
+it("process.getegid", () => {
+ expect(typeof process.getegid()).toBe("number");
+});
+it("process.geteuid", () => {
+ expect(typeof process.geteuid()).toBe("number");
+});
+it("process.getgid", () => {
+ expect(typeof process.getgid()).toBe("number");
+});
+it("process.getgroups", () => {
+ expect(process.getgroups()).toBeInstanceOf(Array);
+ expect(process.getgroups().length).toBeGreaterThan(0);
+});
+it("process.getuid", () => {
+ expect(typeof process.getuid()).toBe("number");
+});
+
+it("process.getuid", () => {
+ expect(typeof process.getuid()).toBe("number");
+});
+
+describe("signal", () => {
+ const fixture = join(import.meta.dir, "./process-signal-handler.fixture.js");
+ it("simple case works", async () => {
+ const child = Bun.spawn({
+ cmd: [bunExe(), fixture, "SIGUSR1"],
+ env: bunEnv,
+ });
+
+ expect(await child.exited).toBe(0);
+ expect(await new Response(child.stdout).text()).toBe("PASS\n");
+ });
+ it("process.emit will call signal events", async () => {
+ const child = Bun.spawn({
+ cmd: [bunExe(), fixture, "SIGUSR2"],
+ env: bunEnv,
+ });
+
+ expect(await child.exited).toBe(0);
+ expect(await new Response(child.stdout).text()).toBe("PASS\n");
+ });
+
+ it("process.kill(2) works", async () => {
+ const child = Bun.spawn({
+ cmd: ["bash", "-c", "sleep 1000000"],
+ stdout: "pipe",
+ });
+ const prom = child.exited;
+ process.kill(child.pid, "SIGTERM");
+ await prom;
+ expect(child.signalCode).toBe("SIGTERM");
+ });
+
+ it("process._kill(2) works", async () => {
+ const child = Bun.spawn({
+ cmd: ["bash", "-c", "sleep 1000000"],
+ stdout: "pipe",
+ });
+ const prom = child.exited;
+ process.kill(child.pid, 9);
+ await prom;
+ expect(child.signalCode).toBe("SIGKILL");
+ });
+
+ it("process.kill(2) throws on invalid input", async () => {
+ expect(() => process.kill(0, "SIGPOOP")).toThrow();
+ expect(() => process.kill(0, 456)).toThrow();
+ });
+});
+
+const undefinedStubs = [
+ "_debugEnd",
+ "_debugProcess",
+ "_fatalException",
+ "_linkedBinding",
+ "_rawDebug",
+ "_startProfilerIdleNotifier",
+ "_stopProfilerIdleNotifier",
+ "_tickCallback",
+];
+
+for (const stub of undefinedStubs) {
+ it(`process.${stub}`, () => {
+ expect(process[stub]()).toBeUndefined();
+ });
+}
+
+const arrayStubs = ["getActiveResourcesInfo", "_getActiveRequests", "_getActiveHandles"];
+
+for (const stub of arrayStubs) {
+ it(`process.${stub}`, () => {
+ expect(process[stub]()).toBeInstanceOf(Array);
+ });
+}
+
+const emptyObjectStubs = ["_preload_modules"];
+const emptySetStubs = ["allowedNodeEnvironmentFlags"];
+const emptyArrayStubs = ["moduleLoadList"];
+
+for (const stub of emptyObjectStubs) {
+ it(`process.${stub}`, () => {
+ expect(process[stub]).toEqual({});
+ });
+}
+
+for (const stub of emptySetStubs) {
+ it(`process.${stub}`, () => {
+ expect(process[stub]).toBeInstanceOf(Set);
+ expect(process[stub].size).toBe(0);
+ });
+}
+
+for (const stub of emptyArrayStubs) {
+ it(`process.${stub}`, () => {
+ expect(process[stub]).toBeInstanceOf(Array);
+ expect(process[stub]).toHaveLength(0);
+ });
+}
diff --git a/test/js/node/string_decoder/string-decoder.test.js b/test/js/node/string_decoder/string-decoder.test.js
index f37326678..aba73401a 100644
--- a/test/js/node/string_decoder/string-decoder.test.js
+++ b/test/js/node/string_decoder/string-decoder.test.js
@@ -241,3 +241,12 @@ for (const StringDecoder of [FakeStringDecoderCall, RealStringDecoder]) {
});
});
}
+
+it("invalid utf-8 input, pr #3562", () => {
+ const decoder = new RealStringDecoder("utf-8");
+ let output = "";
+ output += decoder.write(Buffer.from("B9", "hex"));
+ output += decoder.write(Buffer.from("A9", "hex"));
+ output += decoder.end();
+ expect(output).toStrictEqual("\uFFFD\uFFFD");
+});
diff --git a/test/js/node/stubs.test.js b/test/js/node/stubs.test.js
index e6bce8aee..1025907ab 100644
--- a/test/js/node/stubs.test.js
+++ b/test/js/node/stubs.test.js
@@ -99,6 +99,9 @@ for (let specifier of specifiers) {
}
}
}
+ } else {
+ // TODO: uncomment this after node:module can be default imported
+ // throw new Error(`Module ${specifier} has no default export`);
}
});
}
diff --git a/test/js/node/timers/node-timers.test.ts b/test/js/node/timers/node-timers.test.ts
index e6fa48010..412eabc22 100644
--- a/test/js/node/timers/node-timers.test.ts
+++ b/test/js/node/timers/node-timers.test.ts
@@ -1,17 +1,18 @@
import { describe, test } from "bun:test";
-import { setTimeout, clearTimeout, setInterval, setImmediate } from "node:timers";
+import { setTimeout, clearTimeout, setInterval, clearInterval, setImmediate } from "node:timers";
-for (const fn of [setTimeout, setInterval, setImmediate]) {
+for (const fn of [setTimeout, setInterval]) {
describe(fn.name, () => {
test("unref is possible", done => {
const timer = fn(() => {
done(new Error("should not be called"));
- }, 1);
- fn(() => {
+ }, 1).unref();
+ const other = fn(() => {
+ clearInterval(other);
done();
}, 2);
- timer.unref();
- if (fn !== setImmediate) clearTimeout(timer);
+ if (fn === setTimeout) clearTimeout(timer);
+ if (fn === setInterval) clearInterval(timer);
});
});
}
diff --git a/test/js/node/tls/node-tls-connect.test.ts b/test/js/node/tls/node-tls-connect.test.ts
new file mode 100644
index 000000000..791dba88a
--- /dev/null
+++ b/test/js/node/tls/node-tls-connect.test.ts
@@ -0,0 +1,32 @@
+import { TLSSocket, connect } from "tls";
+
+it("should work with alpnProtocols", done => {
+ try {
+ let socket: TLSSocket | null = connect({
+ ALPNProtocols: ["http/1.1"],
+ host: "bun.sh",
+ servername: "bun.sh",
+ port: 443,
+ rejectUnauthorized: false,
+ });
+
+ const timeout = setTimeout(() => {
+ socket?.end();
+ done("timeout");
+ }, 3000);
+
+ socket.on("error", err => {
+ clearTimeout(timeout);
+ done(err);
+ });
+
+ socket.on("secureConnect", () => {
+ clearTimeout(timeout);
+ done(socket?.alpnProtocol === "http/1.1" ? undefined : "alpnProtocol is not http/1.1");
+ socket?.end();
+ socket = null;
+ });
+ } catch (err) {
+ done(err);
+ }
+});
diff --git a/test/js/node/tls/node-tls-server.test.ts b/test/js/node/tls/node-tls-server.test.ts
index 6879d0927..2a6101b9f 100644
--- a/test/js/node/tls/node-tls-server.test.ts
+++ b/test/js/node/tls/node-tls-server.test.ts
@@ -195,61 +195,6 @@ describe("tls.createServer listen", () => {
);
});
- it("should listen on the correct port", done => {
- const { mustCall, mustNotCall } = createCallCheckCtx(done);
-
- const server: Server = createServer(COMMON_CERT);
-
- let timeout: Timer;
- const closeAndFail = () => {
- clearTimeout(timeout);
- server.close();
- mustNotCall()();
- };
- server.on("error", closeAndFail);
- timeout = setTimeout(closeAndFail, 100);
-
- server.listen(
- 49027,
- mustCall(() => {
- const address = server.address() as AddressInfo;
- expect(address.address).toStrictEqual("::");
- expect(address.port).toStrictEqual(49027);
- expect(address.family).toStrictEqual("IPv6");
- server.close();
- done();
- }),
- );
- });
-
- it("should listen on the correct port with IPV4", done => {
- const { mustCall, mustNotCall } = createCallCheckCtx(done);
-
- const server: Server = createServer(COMMON_CERT);
-
- let timeout: Timer;
- const closeAndFail = () => {
- clearTimeout(timeout);
- server.close();
- mustNotCall()();
- };
- server.on("error", closeAndFail);
- timeout = setTimeout(closeAndFail, 100);
-
- server.listen(
- 49026,
- "0.0.0.0",
- mustCall(() => {
- const address = server.address() as AddressInfo;
- expect(address.address).toStrictEqual("0.0.0.0");
- expect(address.port).toStrictEqual(49026);
- expect(address.family).toStrictEqual("IPv4");
- server.close();
- done();
- }),
- );
- });
-
it("should listen on unix domain socket", done => {
const { mustCall, mustNotCall } = createCallCheckCtx(done);
diff --git a/test/js/node/util/test-util-types.test.js b/test/js/node/util/test-util-types.test.js
index f33ab4b1a..a75b9eac0 100644
--- a/test/js/node/util/test-util-types.test.js
+++ b/test/js/node/util/test-util-types.test.js
@@ -1,6 +1,9 @@
-const assert = require("assert");
-import { test, expect } from "bun:test";
-const types = require("util/types");
+import assert from "assert";
+import { describe, test, expect } from "bun:test";
+import def from "util/types";
+import * as ns from "util/types";
+const req = require("util/types");
+const types = def;
function inspect(val) {
return Bun.inspect(val);
@@ -52,15 +55,21 @@ for (const [value, _method] of [
assert(method in types, `Missing ${method} for ${inspect(value)}`);
assert(types[method](value), `Want ${inspect(value)} to match ${method}`);
- for (const key of Object.keys(types)) {
- if (
- ((types.isArrayBufferView(value) || types.isAnyArrayBuffer(value)) && key.includes("Array")) ||
- key === "isBoxedPrimitive"
- ) {
- continue;
- }
+ for (const [types, label] of [
+ [def, "default import"],
+ [ns, "ns import"],
+ [req, "require esm"],
+ ]) {
+ for (const key of Object.keys(types).filter(x => x !== "default")) {
+ if (
+ ((types.isArrayBufferView(value) || types.isAnyArrayBuffer(value)) && key.includes("Array")) ||
+ key === "isBoxedPrimitive"
+ ) {
+ continue;
+ }
- expect(types[key](value)).toBe(key === method);
+ expect(types[key](value)).toBe(key === method);
+ }
}
});
}
@@ -238,3 +247,4 @@ test("isBoxedPrimitive", () => {
});
}
}
+// */
diff --git a/test/js/node/util/util-callbackify.test.js b/test/js/node/util/util-callbackify.test.js
new file mode 100644
index 000000000..38c20f5c0
--- /dev/null
+++ b/test/js/node/util/util-callbackify.test.js
@@ -0,0 +1,323 @@
+import { callbackify } from "util";
+import { createTest } from "node-harness";
+
+const { describe, expect, it, createCallCheckCtx } = createTest(import.meta.path);
+
+const values = [
+ "hello world",
+ null,
+ undefined,
+ false,
+ 0,
+ {},
+ { key: "value" },
+ Symbol("I am a symbol"),
+ function ok() {},
+ ["array", "with", 4, "values"],
+ new Error("boo"),
+];
+
+describe("util.callbackify", () => {
+ describe("rejection reason", () => {
+ for (const value of values) {
+ it(`callback is async function, value is ${String(value)}`, done => {
+ const { mustCall } = createCallCheckCtx(done);
+ async function asyncFn() {
+ return Promise.reject(value);
+ }
+
+ const cbAsyncFn = callbackify(asyncFn);
+ cbAsyncFn(
+ mustCall((err, ret) => {
+ try {
+ expect(ret).toBeUndefined();
+ if (err instanceof Error) {
+ if ("reason" in err) {
+ expect(!value).toBeTrue();
+ expect(err.code).toStrictEqual("ERR_FALSY_VALUE_REJECTION");
+ expect(err.reason).toStrictEqual(value);
+ } else {
+ expect(String(value)).toEndWith(err.message);
+ }
+ } else {
+ expect(err).toStrictEqual(value);
+ }
+
+ done();
+ } catch (error) {
+ done(error);
+ }
+ }),
+ );
+ });
+
+ it(`callback is promise, value is ${String(value)}`, done => {
+ const { mustCall } = createCallCheckCtx(done);
+ function promiseFn() {
+ return Promise.reject(value);
+ }
+ const obj = {};
+ Object.defineProperty(promiseFn, "name", {
+ value: obj,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+ });
+
+ const cbPromiseFn = callbackify(promiseFn);
+ try {
+ expect(promiseFn.name).toStrictEqual(obj);
+ } catch (error) {
+ done(error);
+ }
+
+ cbPromiseFn(
+ mustCall((err, ret) => {
+ try {
+ expect(ret).toBeUndefined();
+ if (err instanceof Error) {
+ if ("reason" in err) {
+ expect(!value).toBeTrue();
+ expect(err.code).toStrictEqual("ERR_FALSY_VALUE_REJECTION");
+ expect(err.reason).toStrictEqual(value);
+ } else {
+ expect(String(value)).toEndWith(err.message);
+ }
+ } else {
+ expect(err).toStrictEqual(value);
+ }
+
+ done();
+ } catch (error) {
+ done(error);
+ }
+ }),
+ );
+ });
+
+ it(`callback is thenable, value is ${String(value)}`, done => {
+ const { mustCall } = createCallCheckCtx(done);
+ function thenableFn() {
+ return {
+ then(onRes, onRej) {
+ onRej(value);
+ },
+ };
+ }
+
+ const cbThenableFn = callbackify(thenableFn);
+ cbThenableFn(
+ mustCall((err, ret) => {
+ try {
+ expect(ret).toBeUndefined();
+ if (err instanceof Error) {
+ if ("reason" in err) {
+ expect(!value).toBeTrue();
+ expect(err.code).toStrictEqual("ERR_FALSY_VALUE_REJECTION");
+ expect(err.reason).toStrictEqual(value);
+ } else {
+ expect(String(value)).toEndWith(err.message);
+ }
+ } else {
+ expect(err).toStrictEqual(value);
+ }
+
+ done();
+ } catch (error) {
+ done(error);
+ }
+ }),
+ );
+ });
+ }
+ });
+
+ describe("return value", () => {
+ for (const value of values) {
+ it(`callback is async function, value is ${String(value)}`, done => {
+ const { mustSucceed } = createCallCheckCtx(done);
+ async function asyncFn() {
+ return value;
+ }
+
+ const cbAsyncFn = callbackify(asyncFn);
+ cbAsyncFn(
+ mustSucceed(ret => {
+ try {
+ expect(ret).toStrictEqual(value);
+ expect(ret).toStrictEqual(value);
+
+ done();
+ } catch (error) {
+ done(error);
+ }
+ }),
+ );
+ });
+
+ it(`callback is promise, value is ${String(value)}`, done => {
+ const { mustSucceed } = createCallCheckCtx(done);
+ function promiseFn() {
+ return Promise.resolve(value);
+ }
+
+ const cbPromiseFn = callbackify(promiseFn);
+ cbPromiseFn(
+ mustSucceed(ret => {
+ try {
+ expect(ret).toStrictEqual(value);
+ done();
+ } catch (error) {
+ done(error);
+ }
+ }),
+ );
+ });
+
+ it(`callback is thenable, value is ${String(value)}`, done => {
+ const { mustSucceed } = createCallCheckCtx(done);
+ function thenableFn() {
+ return {
+ then(onRes, onRej) {
+ onRes(value);
+ },
+ };
+ }
+
+ const cbThenableFn = callbackify(thenableFn);
+ cbThenableFn(
+ mustSucceed(ret => {
+ try {
+ expect(ret).toStrictEqual(value);
+ done();
+ } catch (error) {
+ done(error);
+ }
+ }),
+ );
+ });
+ }
+ });
+
+ describe("arguments", () => {
+ for (const value of values) {
+ it(`callback is async function, value is ${String(value)}`, done => {
+ const { mustSucceed } = createCallCheckCtx(done);
+ async function asyncFn(arg) {
+ try {
+ expect(arg).toStrictEqual(value);
+ } catch (error) {
+ done(error);
+ }
+ return arg;
+ }
+
+ const cbAsyncFn = callbackify(asyncFn);
+ cbAsyncFn(
+ value,
+ mustSucceed(ret => {
+ try {
+ expect(ret).toStrictEqual(value);
+ done();
+ } catch (error) {
+ done(error);
+ }
+ }),
+ );
+ });
+
+ it(`callback is promise, value is ${String(value)}`, done => {
+ const { mustSucceed } = createCallCheckCtx(done);
+ function promiseFn(arg) {
+ try {
+ expect(arg).toStrictEqual(value);
+ } catch (error) {
+ done(error);
+ }
+
+ return Promise.resolve(arg);
+ }
+ const obj = {};
+ Object.defineProperty(promiseFn, "length", {
+ value: obj,
+ writable: false,
+ enumerable: false,
+ configurable: true,
+ });
+ const cbPromiseFn = callbackify(promiseFn);
+ try {
+ expect(promiseFn.length).toStrictEqual(obj);
+ } catch (error) {
+ done(error);
+ }
+
+ cbPromiseFn(
+ value,
+ mustSucceed(ret => {
+ try {
+ expect(ret).toStrictEqual(value);
+ done();
+ } catch (error) {
+ done(error);
+ }
+ }),
+ );
+ });
+ }
+ });
+
+ describe("this binding", () => {
+ const value = "hello world";
+ it("callback is sync function", done => {
+ // TODO:
+ // const { mustSucceed } = createCallCheckCtx(done);
+ const iAmThis = {
+ fn(arg) {
+ try {
+ expect(this).toStrictEqual(iAmThis);
+ } catch (error) {
+ done(error);
+ }
+ return Promise.resolve(arg);
+ },
+ };
+
+ iAmThis.cbFn = callbackify(iAmThis.fn);
+ iAmThis.cbFn(value, function (rej, ret) {
+ try {
+ expect(ret).toStrictEqual(value);
+ expect(this).toStrictEqual(iAmThis);
+
+ done();
+ } catch (error) {
+ done(error);
+ }
+ });
+ });
+
+ it("callback is async function", done => {
+ const iAmThis = {
+ async fn(arg) {
+ try {
+ expect(this).toStrictEqual(iAmThis);
+ } catch (error) {
+ done(error);
+ }
+ return Promise.resolve(arg);
+ },
+ };
+
+ iAmThis.cbFn = callbackify(iAmThis.fn);
+ iAmThis.cbFn(value, function (rej, ret) {
+ try {
+ expect(ret).toStrictEqual(value);
+ expect(this).toStrictEqual(iAmThis);
+
+ done();
+ } catch (error) {
+ done(error);
+ }
+ });
+ });
+ });
+});
diff --git a/test/js/node/util/util.test.js b/test/js/node/util/util.test.js
index 45ecffda8..741b27d19 100644
--- a/test/js/node/util/util.test.js
+++ b/test/js/node/util/util.test.js
@@ -36,6 +36,23 @@ const deepStrictEqual = (...args) => {
// Tests adapted from https://github.com/nodejs/node/blob/main/test/parallel/test-util.js
describe("util", () => {
+ it("toUSVString", () => {
+ const strings = [
+ // Lone high surrogate
+ "ab\uD800",
+ "ab\uD800c",
+ // Lone low surrogate
+ "\uDFFFab",
+ "c\uDFFFab",
+ // Well-formed
+ "abc",
+ "ab\uD83D\uDE04c",
+ ];
+ const outputs = ["ab�", "ab�c", "�ab", "c�ab", "abc", "ab😄c"];
+ for (let i = 0; i < strings.length; i++) {
+ expect(util.toUSVString(strings[i])).toBe(outputs[i]);
+ }
+ });
describe("isArray", () => {
it("all cases", () => {
strictEqual(util.isArray([]), true);
diff --git a/test/js/node/v8/capture-stack-trace.test.js b/test/js/node/v8/capture-stack-trace.test.js
index d96f91483..cb2624681 100644
--- a/test/js/node/v8/capture-stack-trace.test.js
+++ b/test/js/node/v8/capture-stack-trace.test.js
@@ -5,6 +5,18 @@ afterEach(() => {
Error.prepareStackTrace = origPrepareStackTrace;
});
+test("Regular .stack", () => {
+ var err;
+ class Foo {
+ constructor() {
+ err = new Error("wat");
+ }
+ }
+
+ new Foo();
+ expect(err.stack).toMatch(/at new Foo/);
+});
+
test("capture stack trace", () => {
function f1() {
f2();
diff --git a/test/js/node/watch/fixtures/close.js b/test/js/node/watch/fixtures/close.js
new file mode 100644
index 000000000..8eeeb79a3
--- /dev/null
+++ b/test/js/node/watch/fixtures/close.js
@@ -0,0 +1,7 @@
+import fs from "fs";
+fs.watch(import.meta.path, { signal: AbortSignal.timeout(4000) })
+ .on("error", err => {
+ console.error(err.message);
+ process.exit(1);
+ })
+ .close();
diff --git a/test/js/node/watch/fixtures/persistent.js b/test/js/node/watch/fixtures/persistent.js
new file mode 100644
index 000000000..72a2b6564
--- /dev/null
+++ b/test/js/node/watch/fixtures/persistent.js
@@ -0,0 +1,5 @@
+import fs from "fs";
+fs.watch(import.meta.path, { persistent: false, signal: AbortSignal.timeout(4000) }).on("error", err => {
+ console.error(err.message);
+ process.exit(1);
+});
diff --git a/test/js/node/watch/fixtures/relative.js b/test/js/node/watch/fixtures/relative.js
new file mode 100644
index 000000000..26e09da1a
--- /dev/null
+++ b/test/js/node/watch/fixtures/relative.js
@@ -0,0 +1,23 @@
+import fs from "fs";
+const watcher = fs.watch("relative.txt", { signal: AbortSignal.timeout(2000) });
+
+watcher.on("change", function (event, filename) {
+ if (filename !== "relative.txt" && event !== "change") {
+ console.error("fail");
+ clearInterval(interval);
+ watcher.close();
+ process.exit(1);
+ } else {
+ clearInterval(interval);
+ watcher.close();
+ }
+});
+watcher.on("error", err => {
+ clearInterval(interval);
+ console.error(err.message);
+ process.exit(1);
+});
+
+const interval = setInterval(() => {
+ fs.writeFileSync("relative.txt", "world");
+}, 10);
diff --git a/test/js/node/watch/fixtures/unref.js b/test/js/node/watch/fixtures/unref.js
new file mode 100644
index 000000000..a0c506a04
--- /dev/null
+++ b/test/js/node/watch/fixtures/unref.js
@@ -0,0 +1,7 @@
+import fs from "fs";
+fs.watch(import.meta.path, { signal: AbortSignal.timeout(4000) })
+ .on("error", err => {
+ console.error(err.message);
+ process.exit(1);
+ })
+ .unref();
diff --git a/test/js/node/watch/fs.watch.test.ts b/test/js/node/watch/fs.watch.test.ts
new file mode 100644
index 000000000..aa7959bed
--- /dev/null
+++ b/test/js/node/watch/fs.watch.test.ts
@@ -0,0 +1,522 @@
+import fs, { FSWatcher } from "node:fs";
+import path from "path";
+import { tempDirWithFiles, bunRun, bunRunAsScript } from "harness";
+import { pathToFileURL } from "bun";
+
+import { describe, expect, test } from "bun:test";
+// Because macOS (and possibly other operating systems) can return a watcher
+// before it is actually watching, we need to repeat the operation to avoid
+// a race condition.
+function repeat(fn: any) {
+ const interval = setInterval(fn, 20);
+ return interval;
+}
+const encodingFileName = `新建文夹件.txt`;
+const testDir = tempDirWithFiles("watch", {
+ "watch.txt": "hello",
+ "relative.txt": "hello",
+ "abort.txt": "hello",
+ "url.txt": "hello",
+ "close.txt": "hello",
+ "close-close.txt": "hello",
+ "sym-sync.txt": "hello",
+ "sym.txt": "hello",
+ [encodingFileName]: "hello",
+});
+
+describe("fs.watch", () => {
+ test("non-persistent watcher should not block the event loop", done => {
+ try {
+ // https://github.com/joyent/node/issues/2293 - non-persistent watcher should not block the event loop
+ bunRun(path.join(import.meta.dir, "fixtures", "persistent.js"));
+ done();
+ } catch (e: any) {
+ done(e);
+ }
+ });
+
+ test("watcher should close and not block the event loop", done => {
+ try {
+ bunRun(path.join(import.meta.dir, "fixtures", "close.js"));
+ done();
+ } catch (e: any) {
+ done(e);
+ }
+ });
+
+ test("unref watcher should not block the event loop", done => {
+ try {
+ bunRun(path.join(import.meta.dir, "fixtures", "unref.js"));
+ done();
+ } catch (e: any) {
+ done(e);
+ }
+ });
+
+ test("should work with relative files", done => {
+ try {
+ bunRunAsScript(testDir, path.join(import.meta.dir, "fixtures", "relative.js"));
+ done();
+ } catch (e: any) {
+ done(e);
+ }
+ });
+
+ test("add file/folder to folder", done => {
+ let count = 0;
+ const root = path.join(testDir, "add-directory");
+ try {
+ fs.mkdirSync(root);
+ } catch {}
+ let err: Error | undefined = undefined;
+ const watcher = fs.watch(root, { signal: AbortSignal.timeout(3000) });
+ watcher.on("change", (event, filename) => {
+ count++;
+ try {
+ expect(event).toBe("rename");
+ expect(["new-file.txt", "new-folder.txt"]).toContain(filename);
+ if (count >= 2) {
+ watcher.close();
+ }
+ } catch (e: any) {
+ err = e;
+ watcher.close();
+ }
+ });
+
+ watcher.on("error", e => (err = e));
+ watcher.on("close", () => {
+ clearInterval(interval);
+ done(err);
+ });
+
+ const interval = repeat(() => {
+ fs.writeFileSync(path.join(root, "new-file.txt"), "hello");
+ fs.mkdirSync(path.join(root, "new-folder.txt"));
+ fs.rmdirSync(path.join(root, "new-folder.txt"));
+ });
+ });
+
+ test("add file/folder to subfolder", done => {
+ let count = 0;
+ const root = path.join(testDir, "add-subdirectory");
+ try {
+ fs.mkdirSync(root);
+ } catch {}
+ const subfolder = path.join(root, "subfolder");
+ fs.mkdirSync(subfolder);
+ const watcher = fs.watch(root, { recursive: true, signal: AbortSignal.timeout(3000) });
+ let err: Error | undefined = undefined;
+ watcher.on("change", (event, filename) => {
+ const basename = path.basename(filename as string);
+
+ if (basename === "subfolder") return;
+ count++;
+ try {
+ expect(event).toBe("rename");
+ expect(["new-file.txt", "new-folder.txt"]).toContain(basename);
+ if (count >= 2) {
+ watcher.close();
+ }
+ } catch (e: any) {
+ err = e;
+ watcher.close();
+ }
+ });
+ watcher.on("error", e => (err = e));
+ watcher.on("close", () => {
+ clearInterval(interval);
+ done(err);
+ });
+
+ const interval = repeat(() => {
+ fs.writeFileSync(path.join(subfolder, "new-file.txt"), "hello");
+ fs.mkdirSync(path.join(subfolder, "new-folder.txt"));
+ fs.rmdirSync(path.join(subfolder, "new-folder.txt"));
+ });
+ });
+
+ test("should emit event when file is deleted", done => {
+ const testsubdir = tempDirWithFiles("subdir", {
+ "deleted.txt": "hello",
+ });
+ const filepath = path.join(testsubdir, "deleted.txt");
+ let err: Error | undefined = undefined;
+ const watcher = fs.watch(testsubdir, function (event, filename) {
+ try {
+ expect(event).toBe("rename");
+ expect(filename).toBe("deleted.txt");
+ } catch (e: any) {
+ err = e;
+ } finally {
+ clearInterval(interval);
+ watcher.close();
+ }
+ });
+
+ watcher.once("close", () => {
+ done(err);
+ });
+
+ const interval = repeat(() => {
+ fs.rmSync(filepath, { force: true });
+ const fd = fs.openSync(filepath, "w");
+ fs.closeSync(fd);
+ });
+ });
+
+ test("should emit 'change' event when file is modified", done => {
+ const filepath = path.join(testDir, "watch.txt");
+
+ const watcher = fs.watch(filepath);
+ let err: Error | undefined = undefined;
+ watcher.on("change", function (event, filename) {
+ try {
+ expect(event).toBe("change");
+ expect(filename).toBe("watch.txt");
+ } catch (e: any) {
+ err = e;
+ } finally {
+ clearInterval(interval);
+ watcher.close();
+ }
+ });
+
+ watcher.once("close", () => {
+ done(err);
+ });
+
+ const interval = repeat(() => {
+ fs.writeFileSync(filepath, "world");
+ });
+ });
+
+ test("should error on invalid path", done => {
+ try {
+ fs.watch(path.join(testDir, "404.txt"));
+ done(new Error("should not reach here"));
+ } catch (err: any) {
+ expect(err).toBeInstanceOf(Error);
+ expect(err.code).toBe("ENOENT");
+ expect(err.syscall).toBe("watch");
+ done();
+ }
+ });
+
+ const encodings = ["utf8", "buffer", "hex", "ascii", "base64", "utf16le", "ucs2", "latin1", "binary"] as const;
+
+ test(`should work with encodings ${encodings.join(", ")}`, async () => {
+ const watchers: FSWatcher[] = [];
+ const filepath = path.join(testDir, encodingFileName);
+
+ const promises: Promise<any>[] = [];
+ encodings.forEach(name => {
+ const encoded_filename =
+ name !== "buffer" ? Buffer.from(encodingFileName, "utf8").toString(name) : Buffer.from(encodingFileName);
+
+ promises.push(
+ new Promise((resolve, reject) => {
+ watchers.push(
+ fs.watch(filepath, { encoding: name }, (event, filename) => {
+ try {
+ expect(event).toBe("change");
+
+ if (name !== "buffer") {
+ expect(filename).toBe(encoded_filename);
+ } else {
+ expect(filename).toBeInstanceOf(Buffer);
+ expect((filename as any as Buffer)!.toString("utf8")).toBe(encodingFileName);
+ }
+
+ resolve(undefined);
+ } catch (e: any) {
+ reject(e);
+ }
+ }),
+ );
+ }),
+ );
+ });
+
+ const interval = repeat(() => {
+ fs.writeFileSync(filepath, "world");
+ });
+
+ try {
+ await Promise.all(promises);
+ } finally {
+ clearInterval(interval);
+ watchers.forEach(watcher => watcher.close());
+ }
+ });
+
+ test("should work with url", done => {
+ const filepath = path.join(testDir, "url.txt");
+ try {
+ const watcher = fs.watch(pathToFileURL(filepath));
+ let err: Error | undefined = undefined;
+ watcher.on("change", function (event, filename) {
+ try {
+ expect(event).toBe("change");
+ expect(filename).toBe("url.txt");
+ } catch (e: any) {
+ err = e;
+ } finally {
+ clearInterval(interval);
+ watcher.close();
+ }
+ });
+
+ watcher.once("close", () => {
+ done(err);
+ });
+
+ const interval = repeat(() => {
+ fs.writeFileSync(filepath, "world");
+ });
+ } catch (e: any) {
+ done(e);
+ }
+ });
+
+ test("calling close from error event should not throw", done => {
+ const filepath = path.join(testDir, "close.txt");
+ try {
+ const ac = new AbortController();
+ const watcher = fs.watch(pathToFileURL(filepath), { signal: ac.signal });
+ watcher.once("error", () => {
+ try {
+ watcher.close();
+ done();
+ } catch (e: any) {
+ done("Should not error when calling close from error event");
+ }
+ });
+ ac.abort();
+ } catch (e: any) {
+ done(e);
+ }
+ });
+
+ test("calling close from close event should not throw", done => {
+ const filepath = path.join(testDir, "close-close.txt");
+ try {
+ const ac = new AbortController();
+ const watcher = fs.watch(pathToFileURL(filepath), { signal: ac.signal });
+
+ watcher.once("close", () => {
+ try {
+ watcher.close();
+ done();
+ } catch (e: any) {
+ done("Should not error when calling close from close event");
+ }
+ });
+
+ ac.abort();
+ } catch (e: any) {
+ done(e);
+ }
+ });
+
+ test("Signal aborted after creating the watcher", async () => {
+ const filepath = path.join(testDir, "abort.txt");
+
+ const ac = new AbortController();
+ const promise = new Promise((resolve, reject) => {
+ const watcher = fs.watch(filepath, { signal: ac.signal });
+ watcher.once("error", err => (err.message === "The operation was aborted." ? resolve(undefined) : reject(err)));
+ watcher.once("close", () => reject());
+ });
+ await Bun.sleep(10);
+ ac.abort();
+ await promise;
+ });
+
+ test("Signal aborted before creating the watcher", async () => {
+ const filepath = path.join(testDir, "abort.txt");
+
+ const signal = AbortSignal.abort();
+ await new Promise((resolve, reject) => {
+ const watcher = fs.watch(filepath, { signal });
+ watcher.once("error", err => (err.message === "The operation was aborted." ? resolve(undefined) : reject(err)));
+ watcher.once("close", () => reject());
+ });
+ });
+
+ test("should work with symlink", async () => {
+ const filepath = path.join(testDir, "sym-symlink2.txt");
+ await fs.promises.symlink(path.join(testDir, "sym-sync.txt"), filepath);
+
+ const interval = repeat(() => {
+ fs.writeFileSync(filepath, "hello");
+ });
+
+ const promise = new Promise((resolve, reject) => {
+ let timeout: any = null;
+ const watcher = fs.watch(filepath, event => {
+ clearTimeout(timeout);
+ clearInterval(interval);
+ try {
+ resolve(event);
+ } catch (e: any) {
+ reject(e);
+ } finally {
+ watcher.close();
+ }
+ });
+ setTimeout(() => {
+ clearInterval(interval);
+ watcher?.close();
+ reject("timeout");
+ }, 3000);
+ });
+ expect(promise).resolves.toBe("change");
+ });
+});
+
+describe("fs.promises.watch", () => {
+ test("add file/folder to folder", async () => {
+ let count = 0;
+ const root = path.join(testDir, "add-promise-directory");
+ try {
+ fs.mkdirSync(root);
+ } catch {}
+ let success = false;
+ let err: Error | undefined = undefined;
+ try {
+ const ac = new AbortController();
+ const watcher = fs.promises.watch(root, { signal: ac.signal });
+
+ const interval = repeat(() => {
+ fs.writeFileSync(path.join(root, "new-file.txt"), "hello");
+ fs.mkdirSync(path.join(root, "new-folder.txt"));
+ fs.rmdirSync(path.join(root, "new-folder.txt"));
+ });
+
+ for await (const event of watcher) {
+ count++;
+ try {
+ expect(event.eventType).toBe("rename");
+ expect(["new-file.txt", "new-folder.txt"]).toContain(event.filename);
+
+ if (count >= 2) {
+ success = true;
+ clearInterval(interval);
+ ac.abort();
+ }
+ } catch (e: any) {
+ err = e;
+ clearInterval(interval);
+ ac.abort();
+ }
+ }
+ } catch (e: any) {
+ if (!success) {
+ throw err || e;
+ }
+ }
+ });
+
+ test("add file/folder to subfolder", async () => {
+ let count = 0;
+ const root = path.join(testDir, "add-promise-subdirectory");
+ try {
+ fs.mkdirSync(root);
+ } catch {}
+ const subfolder = path.join(root, "subfolder");
+ fs.mkdirSync(subfolder);
+ let success = false;
+ let err: Error | undefined = undefined;
+
+ try {
+ const ac = new AbortController();
+ const watcher = fs.promises.watch(root, { recursive: true, signal: ac.signal });
+
+ const interval = repeat(() => {
+ fs.writeFileSync(path.join(subfolder, "new-file.txt"), "hello");
+ fs.mkdirSync(path.join(subfolder, "new-folder.txt"));
+ fs.rmdirSync(path.join(subfolder, "new-folder.txt"));
+ });
+ for await (const event of watcher) {
+ const basename = path.basename(event.filename!);
+ if (basename === "subfolder") continue;
+
+ count++;
+ try {
+ expect(event.eventType).toBe("rename");
+ expect(["new-file.txt", "new-folder.txt"]).toContain(basename);
+
+ if (count >= 2) {
+ success = true;
+ clearInterval(interval);
+ ac.abort();
+ }
+ } catch (e: any) {
+ err = e;
+ clearInterval(interval);
+ ac.abort();
+ }
+ }
+ } catch (e: any) {
+ if (!success) {
+ throw err || e;
+ }
+ }
+ });
+
+ test("Signal aborted after creating the watcher", async () => {
+ const filepath = path.join(testDir, "abort.txt");
+
+ const ac = new AbortController();
+ const watcher = fs.promises.watch(filepath, { signal: ac.signal });
+
+ const promise = (async () => {
+ try {
+ for await (const _ of watcher);
+ } catch (e: any) {
+ expect(e.message).toBe("The operation was aborted.");
+ }
+ })();
+ await Bun.sleep(10);
+ ac.abort();
+ await promise;
+ });
+
+ test("Signal aborted before creating the watcher", async () => {
+ const filepath = path.join(testDir, "abort.txt");
+
+ const signal = AbortSignal.abort();
+ const watcher = fs.promises.watch(filepath, { signal });
+ await (async () => {
+ try {
+ for await (const _ of watcher);
+ } catch (e: any) {
+ expect(e.message).toBe("The operation was aborted.");
+ }
+ })();
+ });
+
+ test("should work with symlink", async () => {
+ const filepath = path.join(testDir, "sym-symlink.txt");
+ await fs.promises.symlink(path.join(testDir, "sym.txt"), filepath);
+
+ const watcher = fs.promises.watch(filepath);
+ const interval = repeat(() => {
+ fs.writeFileSync(filepath, "hello");
+ });
+
+ const promise = (async () => {
+ try {
+ for await (const event of watcher) {
+ return event.eventType;
+ }
+ } catch (e: any) {
+ expect("unreacheable").toBe(false);
+ } finally {
+ clearInterval(interval);
+ }
+ })();
+ expect(promise).resolves.toBe("change");
+ });
+});
diff --git a/test/js/third_party/esbuild/bun.lockb b/test/js/third_party/esbuild/bun.lockb
index f46c6aa5d..1fcce3837 100755
--- a/test/js/third_party/esbuild/bun.lockb
+++ b/test/js/third_party/esbuild/bun.lockb
Binary files differ
diff --git a/test/js/third_party/esbuild/package.json b/test/js/third_party/esbuild/package.json
index ad6bc55f6..46535bc29 100644
--- a/test/js/third_party/esbuild/package.json
+++ b/test/js/third_party/esbuild/package.json
@@ -1,6 +1,6 @@
{
"type": "module",
"dependencies": {
- "esbuild": "^0.17.11"
+ "esbuild": "0.17.11"
}
-} \ No newline at end of file
+}
diff --git a/test/js/third_party/nodemailer/nodemailer.test.ts b/test/js/third_party/nodemailer/nodemailer.test.ts
new file mode 100644
index 000000000..36a229524
--- /dev/null
+++ b/test/js/third_party/nodemailer/nodemailer.test.ts
@@ -0,0 +1,19 @@
+import { test, expect, describe } from "bun:test";
+import { bunRun } from "harness";
+import path from "path";
+
+const it = process.env.SMTP_SENDGRID_KEY && process.env.SMTP_SENDGRID_SENDER ? test : test.skip;
+describe("nodemailer", () => {
+ it("basic smtp", async () => {
+ try {
+ const info = bunRun(path.join(import.meta.dir, "process-nodemailer-fixture.js"), {
+ SMTP_SENDGRID_SENDER: process.env.SMTP_SENDGRID_SENDER as string,
+ SMTP_SENDGRID_KEY: process.env.SMTP_SENDGRID_KEY as string,
+ });
+ expect(info.stdout).toBe("true");
+ expect(info.stderr || "").toBe("");
+ } catch (err: any) {
+ expect(err?.message || err).toBe("");
+ }
+ }, 10000);
+});
diff --git a/test/js/third_party/nodemailer/package.json b/test/js/third_party/nodemailer/package.json
new file mode 100644
index 000000000..08e98074f
--- /dev/null
+++ b/test/js/third_party/nodemailer/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "nodemailer",
+ "dependencies": {
+ "nodemailer": "6.9.3"
+ }
+}
diff --git a/test/js/third_party/nodemailer/process-nodemailer-fixture.js b/test/js/third_party/nodemailer/process-nodemailer-fixture.js
new file mode 100644
index 000000000..49ab6a516
--- /dev/null
+++ b/test/js/third_party/nodemailer/process-nodemailer-fixture.js
@@ -0,0 +1,20 @@
+const nodemailer = require("nodemailer");
+const transporter = nodemailer.createTransport({
+ host: "smtp.sendgrid.net",
+ port: 587,
+ secure: false,
+ auth: {
+ user: "apikey", // generated ethereal user
+ pass: process.env.SMTP_SENDGRID_KEY, // generated ethereal password
+ },
+});
+
+// send mail with defined transport object
+let info = await transporter.sendMail({
+ from: process.env.SMTP_SENDGRID_SENDER, // sender address
+ to: process.env.SMTP_SENDGRID_SENDER, // list of receivers
+ subject: "Hello ✔", // Subject line
+ text: "Hello world?", // plain text body
+ html: "<b>Hello world?</b>", // html body
+});
+console.log(typeof info?.messageId === "string");
diff --git a/test/js/third_party/postgres/package.json b/test/js/third_party/postgres/package.json
new file mode 100644
index 000000000..90809f48f
--- /dev/null
+++ b/test/js/third_party/postgres/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "postgres",
+ "dependencies": {
+ "pg": "8.11.1",
+ "postgres": "3.3.5",
+ "pg-connection-string": "2.6.1"
+ }
+}
diff --git a/test/js/third_party/postgres/postgres.test.ts b/test/js/third_party/postgres/postgres.test.ts
new file mode 100644
index 000000000..490192ae7
--- /dev/null
+++ b/test/js/third_party/postgres/postgres.test.ts
@@ -0,0 +1,56 @@
+import { test, expect, describe } from "bun:test";
+import { Pool } from "pg";
+import { parse } from "pg-connection-string";
+import postgres from "postgres";
+
+const CONNECTION_STRING = process.env.TLS_POSTGRES_DATABASE_URL;
+
+const it = CONNECTION_STRING ? test : test.skip;
+
+describe("pg", () => {
+ it("should connect using TLS", async () => {
+ const pool = new Pool(parse(CONNECTION_STRING as string));
+ try {
+ const { rows } = await pool.query("SELECT version()", []);
+ const [{ version }] = rows;
+
+ expect(version).toMatch(/PostgreSQL/);
+ } finally {
+ pool.end();
+ }
+ });
+});
+
+describe("postgres", () => {
+ it("should connect using TLS", async () => {
+ const sql = postgres(CONNECTION_STRING as string);
+ try {
+ const [{ version }] = await sql`SELECT version()`;
+ expect(version).toMatch(/PostgreSQL/);
+ } finally {
+ sql.end();
+ }
+ });
+
+ it("should insert, select and delete", async () => {
+ const sql = postgres(CONNECTION_STRING as string);
+ try {
+ await sql`CREATE TABLE IF NOT EXISTS users (
+ user_id serial PRIMARY KEY,
+ username VARCHAR ( 50 ) NOT NULL
+ );`;
+
+ const [{ user_id, username }] = await sql`insert into users (username) values ('bun') returning *`;
+ expect(username).toBe("bun");
+
+ const [{ user_id: user_id2, username: username2 }] = await sql`select * from users where user_id = ${user_id}`;
+ expect(username2).toBe("bun");
+ expect(user_id2).toBe(user_id);
+
+ const [{ username: username3 }] = await sql`delete from users where user_id = ${user_id} returning *`;
+ expect(username3).toBe("bun");
+ } finally {
+ sql.end();
+ }
+ });
+});
diff --git a/test/js/third_party/prisma/package.json b/test/js/third_party/prisma/package.json
index 369bd42ba..ac8694e36 100644
--- a/test/js/third_party/prisma/package.json
+++ b/test/js/third_party/prisma/package.json
@@ -3,11 +3,11 @@
"module": "index.ts",
"type": "module",
"devDependencies": {
- "bun-types": "^0.6.0",
+ "bun-types": "0.6.12",
"prisma": "4.15.0"
},
"peerDependencies": {
- "typescript": "^5.0.0"
+ "typescript": "5.0.0"
},
"dependencies": {
"@prisma/client": "4.15.0"
diff --git a/test/js/third_party/prisma/prisma.test.ts b/test/js/third_party/prisma/prisma.test.ts
index abe6f0635..2c87a9fcb 100644
--- a/test/js/third_party/prisma/prisma.test.ts
+++ b/test/js/third_party/prisma/prisma.test.ts
@@ -10,7 +10,7 @@ function* TestIDGenerator() {
}
const test_id = TestIDGenerator();
-["sqlite", "postgres", "mongodb"].forEach(async type => {
+["sqlite" /*"postgres", "mongodb"*/].forEach(async type => {
let Client: typeof PrismaClient;
try {
diff --git a/test/js/third_party/socket.io/package.json b/test/js/third_party/socket.io/package.json
index edeb84845..e54aa3de9 100644
--- a/test/js/third_party/socket.io/package.json
+++ b/test/js/third_party/socket.io/package.json
@@ -2,9 +2,8 @@
"name": "socket.io",
"version": "1.0.0",
"dependencies": {
- "socket.io": "^4.6.1",
- "socket.io-client": "^4.6.1",
- "supertest": "^6.1.6",
- "uWebSockets.js": "uNetworking/uWebSockets.js#v20.24.0"
+ "socket.io": "4.6.1",
+ "socket.io-client": "4.6.1",
+ "supertest": "6.3.3"
}
}
diff --git a/test/js/third_party/socket.io/support/util.ts b/test/js/third_party/socket.io/support/util.ts
index 597b40d65..b5f515568 100644
--- a/test/js/third_party/socket.io/support/util.ts
+++ b/test/js/third_party/socket.io/support/util.ts
@@ -1,3 +1,4 @@
+// @ts-nocheck
import type { Server } from "socket.io";
import request from "supertest";
diff --git a/test/js/third_party/webpack/package.json b/test/js/third_party/webpack/package.json
new file mode 100644
index 000000000..fb08bfdc5
--- /dev/null
+++ b/test/js/third_party/webpack/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "webpack-test",
+ "version": "2.0.0",
+ "dependencies": {
+ "webpack": "5.88.0"
+ }
+} \ No newline at end of file
diff --git a/test/js/third_party/webpack/test.js b/test/js/third_party/webpack/test.js
new file mode 100644
index 000000000..bfd3e80a1
--- /dev/null
+++ b/test/js/third_party/webpack/test.js
@@ -0,0 +1,11 @@
+import { world } from "./world.js";
+
+function component() {
+ const element = document.createElement("div");
+
+ element.innerHTML = "hello " + world();
+
+ return element;
+}
+
+document.body.appendChild(component());
diff --git a/test/js/third_party/webpack/webpack.test.ts b/test/js/third_party/webpack/webpack.test.ts
new file mode 100644
index 000000000..ffc8195c6
--- /dev/null
+++ b/test/js/third_party/webpack/webpack.test.ts
@@ -0,0 +1,27 @@
+import { bunExe, bunEnv } from "harness";
+import { existsSync, rmdirSync } from "fs";
+import { join } from "path";
+
+afterEach(() => {
+ rmdirSync(join(import.meta.dir, "dist"), { recursive: true });
+});
+
+test("webpack works", () => {
+ Bun.spawnSync({
+ cmd: [bunExe(), "-b", "webpack", "--entry", "./test.js", "-o", "./dist/test1/main.js"],
+ cwd: import.meta.dir,
+ env: bunEnv,
+ });
+
+ expect(existsSync(join(import.meta.dir, "dist", "test1/main.js"))).toBe(true);
+});
+
+test("webpack --watch works", async () => {
+ Bun.spawnSync({
+ cmd: ["timeout", "3", bunExe(), "-b", "webpack", "--entry", "./test.js", "-o", "./dist/test2/main.js", "--watch"],
+ cwd: import.meta.dir,
+ env: bunEnv,
+ });
+
+ expect(existsSync(join(import.meta.dir, "dist", "test2/main.js"))).toBe(true);
+});
diff --git a/test/js/third_party/webpack/world.js b/test/js/third_party/webpack/world.js
new file mode 100644
index 000000000..3fa21bf67
--- /dev/null
+++ b/test/js/third_party/webpack/world.js
@@ -0,0 +1,3 @@
+export function world() {
+ return "world";
+}
diff --git a/test/js/web/console/console-log.expected.txt b/test/js/web/console/console-log.expected.txt
index 97191c8be..332322665 100644
--- a/test/js/web/console/console-log.expected.txt
+++ b/test/js/web/console/console-log.expected.txt
@@ -1,4 +1,6 @@
Hello World!
+0
+-0
123
-123
123.567
@@ -7,6 +9,8 @@ true
false
null
undefined
+Infinity
+-Infinity
Symbol(Symbol Description)
2000-06-27T02:24:34.304Z
[ 123, 456, 789 ]
@@ -29,7 +33,9 @@ Symbol(Symbol Description)
}
Promise { <pending> }
[Function]
-[Function: Foo]
+[Function]
+[class Foo]
+[class]
{}
[Function: foooo]
/FooRegex/
diff --git a/test/js/web/console/console-log.js b/test/js/web/console/console-log.js
index e23a3e9cb..4db40aaac 100644
--- a/test/js/web/console/console-log.js
+++ b/test/js/web/console/console-log.js
@@ -1,4 +1,6 @@
console.log("Hello World!");
+console.log(0);
+console.log(-0);
console.log(123);
console.log(-123);
console.log(123.567);
@@ -7,6 +9,8 @@ console.log(true);
console.log(false);
console.log(null);
console.log(undefined);
+console.log(Infinity);
+console.log(-Infinity);
console.log(Symbol("Symbol Description"));
console.log(new Date(Math.pow(2, 34) * 56));
console.log([123, 456, 789]);
@@ -27,7 +31,9 @@ console.log(new Promise(() => {}));
class Foo {}
console.log(() => {});
+console.log(function () {});
console.log(Foo);
+console.log(class {});
console.log(new Foo());
console.log(function foooo() {});
diff --git a/test/js/web/fetch/fetch-leak-test-fixture.js b/test/js/web/fetch/fetch-leak-test-fixture.js
index 07275a425..c83bbea83 100644
--- a/test/js/web/fetch/fetch-leak-test-fixture.js
+++ b/test/js/web/fetch/fetch-leak-test-fixture.js
@@ -29,6 +29,6 @@ await (async function runAll() {
await Bun.sleep(10);
Bun.gc(true);
-if ((heapStats().objectTypeCounts.Response ?? 0) > 10) {
+if ((heapStats().objectTypeCounts.Response ?? 0) > 1 + ((COUNT / 2) | 0)) {
throw new Error("Too many Response objects: " + heapStats().objectTypeCounts.Response);
}
diff --git a/test/js/web/fetch/fetch.test.ts b/test/js/web/fetch/fetch.test.ts
index 4d529b231..768818420 100644
--- a/test/js/web/fetch/fetch.test.ts
+++ b/test/js/web/fetch/fetch.test.ts
@@ -387,6 +387,25 @@ describe("fetch", () => {
}).toThrow("fetch() request with GET/HEAD/OPTIONS method cannot have body.");
}),
);
+
+ it("content length is inferred", async () => {
+ startServer({
+ fetch(req) {
+ return new Response(req.headers.get("content-length"));
+ },
+ hostname: "localhost",
+ });
+
+ // POST with body
+ const url = `http://${server.hostname}:${server.port}`;
+ const response = await fetch(url, { method: "POST", body: "buntastic" });
+ expect(response.status).toBe(200);
+ expect(await response.text()).toBe("9");
+
+ const response2 = await fetch(url, { method: "POST", body: "" });
+ expect(response2.status).toBe(200);
+ expect(await response2.text()).toBe("0");
+ });
});
it("simultaneous HTTPS fetch", async () => {
@@ -1156,6 +1175,10 @@ it("#2794", () => {
expect(typeof Bun.fetch.bind).toBe("function");
});
+it("#3545", () => {
+ expect(() => fetch("http://example.com?a=b")).not.toThrow();
+});
+
it("invalid header doesnt crash", () => {
expect(() =>
fetch("http://example.com", {
diff --git a/test/js/web/html/FormData.test.ts b/test/js/web/html/FormData.test.ts
index cbaf5aaa7..45b4f2f5a 100644
--- a/test/js/web/html/FormData.test.ts
+++ b/test/js/web/html/FormData.test.ts
@@ -301,17 +301,18 @@ describe("FormData", () => {
expect(await (body.get("foo") as Blob).text()).toBe("baz");
server.stop(true);
});
-
+ type FetchReqArgs = [request: Request, init?: RequestInit];
+ type FetchURLArgs = [url: string | URL | Request, init?: FetchRequestInit];
for (let useRequestConstructor of [true, false]) {
describe(useRequestConstructor ? "Request constructor" : "fetch()", () => {
- function send(args: Parameters<typeof fetch>) {
+ function send(args: FetchReqArgs | FetchURLArgs) {
if (useRequestConstructor) {
- return fetch(new Request(...args));
+ return fetch(new Request(...(args as FetchReqArgs)));
} else {
- return fetch(...args);
+ return fetch(...(args as FetchURLArgs));
}
}
- for (let headers of [{}, undefined, { headers: { X: "Y" } }]) {
+ for (let headers of [{} as {}, undefined, { headers: { X: "Y" } }]) {
describe("headers: " + Bun.inspect(headers).replaceAll(/([\n ])/gim, ""), () => {
it("send on HTTP server with FormData & Blob (roundtrip)", async () => {
let contentType = "";
@@ -330,11 +331,10 @@ describe("FormData", () => {
form.append("bar", "baz");
// @ts-ignore
- const reqBody = [
+ const reqBody: FetchURLArgs = [
`http://${server.hostname}:${server.port}`,
{
body: form,
-
headers,
method: "POST",
},
@@ -364,7 +364,6 @@ describe("FormData", () => {
form.append("foo", file);
form.append("bar", "baz");
- // @ts-ignore
const reqBody = [
`http://${server.hostname}:${server.port}`,
{
@@ -374,7 +373,7 @@ describe("FormData", () => {
method: "POST",
},
];
- const res = await send(reqBody);
+ const res = await send(reqBody as FetchURLArgs);
const body = await res.formData();
expect(await (body.get("foo") as Blob).text()).toBe(text);
expect(contentType).toContain("multipart/form-data");
@@ -410,7 +409,7 @@ describe("FormData", () => {
method: "POST",
},
];
- const res = await send(reqBody);
+ const res = await send(reqBody as FetchURLArgs);
const body = await res.formData();
expect(contentType).toContain("multipart/form-data");
expect(body.get("foo")).toBe("boop");
diff --git a/test/js/web/html/URLSearchParams.test.ts b/test/js/web/html/URLSearchParams.test.ts
index 120bb2321..41c42c25d 100644
--- a/test/js/web/html/URLSearchParams.test.ts
+++ b/test/js/web/html/URLSearchParams.test.ts
@@ -7,15 +7,20 @@ describe("URLSearchParams", () => {
params.append("foo", "bar");
params.append("foo", "boop");
params.append("bar", "baz");
+ // @ts-ignore
expect(params.length).toBe(3);
params.delete("foo");
+ // @ts-ignore
expect(params.length).toBe(1);
params.append("foo", "bar");
+ // @ts-ignore
expect(params.length).toBe(2);
params.delete("foo");
params.delete("foo");
+ // @ts-ignore
expect(params.length).toBe(1);
params.delete("bar");
+ // @ts-ignore
expect(params.length).toBe(0);
});
diff --git a/test/js/web/timers/process-setImmediate-fixture.js b/test/js/web/timers/process-setImmediate-fixture.js
new file mode 100644
index 000000000..6ffd91c8d
--- /dev/null
+++ b/test/js/web/timers/process-setImmediate-fixture.js
@@ -0,0 +1,9 @@
+setImmediate(() => {
+ console.log("setImmediate");
+ return {
+ a: 1,
+ b: 2,
+ c: 3,
+ d: 4,
+ };
+});
diff --git a/test/js/web/timers/setImmediate.test.js b/test/js/web/timers/setImmediate.test.js
index 9cd6fa1c9..d00224e0f 100644
--- a/test/js/web/timers/setImmediate.test.js
+++ b/test/js/web/timers/setImmediate.test.js
@@ -1,4 +1,6 @@
import { it, expect } from "bun:test";
+import { bunExe, bunEnv } from "harness";
+import path from "path";
it("setImmediate", async () => {
var lastID = -1;
@@ -45,3 +47,28 @@ it("clearImmediate", async () => {
});
expect(called).toBe(false);
});
+
+it("setImmediate should not keep the process alive forever", async () => {
+ let process = null;
+ const success = async () => {
+ process = Bun.spawn({
+ cmd: [bunExe(), "run", path.join(import.meta.dir, "process-setImmediate-fixture.js")],
+ stdout: "ignore",
+ env: {
+ ...bunEnv,
+ NODE_ENV: undefined,
+ },
+ });
+ await process.exited;
+ process = null;
+ return true;
+ };
+
+ const fail = async () => {
+ await Bun.sleep(500);
+ process?.kill();
+ return false;
+ };
+
+ expect(await Promise.race([success(), fail()])).toBe(true);
+});
diff --git a/test/js/web/timers/setTimeout-unref-fixture-2.js b/test/js/web/timers/setTimeout-unref-fixture-2.js
new file mode 100644
index 000000000..6a78f13cd
--- /dev/null
+++ b/test/js/web/timers/setTimeout-unref-fixture-2.js
@@ -0,0 +1,9 @@
+setTimeout(() => {
+ console.log("TEST FAILED!");
+}, 100)
+ .ref()
+ .unref();
+
+setTimeout(() => {
+ // this one should always run
+}, 1);
diff --git a/test/js/web/timers/setTimeout-unref-fixture-3.js b/test/js/web/timers/setTimeout-unref-fixture-3.js
new file mode 100644
index 000000000..41808f5fc
--- /dev/null
+++ b/test/js/web/timers/setTimeout-unref-fixture-3.js
@@ -0,0 +1,7 @@
+setTimeout(() => {
+ setTimeout(() => {}, 999_999);
+}, 100).unref();
+
+setTimeout(() => {
+ // this one should always run
+}, 1);
diff --git a/test/js/web/timers/setTimeout-unref-fixture-4.js b/test/js/web/timers/setTimeout-unref-fixture-4.js
new file mode 100644
index 000000000..9968f3b36
--- /dev/null
+++ b/test/js/web/timers/setTimeout-unref-fixture-4.js
@@ -0,0 +1,5 @@
+setTimeout(() => {
+ console.log("TEST PASSED!");
+}, 1)
+ .unref()
+ .ref();
diff --git a/test/js/web/timers/setTimeout-unref-fixture-5.js b/test/js/web/timers/setTimeout-unref-fixture-5.js
new file mode 100644
index 000000000..e5caa1be4
--- /dev/null
+++ b/test/js/web/timers/setTimeout-unref-fixture-5.js
@@ -0,0 +1,5 @@
+setTimeout(() => {
+ console.log("TEST FAILED!");
+}, 100)
+ .ref()
+ .unref();
diff --git a/test/js/web/timers/setTimeout-unref-fixture.js b/test/js/web/timers/setTimeout-unref-fixture.js
new file mode 100644
index 000000000..97a0f78a2
--- /dev/null
+++ b/test/js/web/timers/setTimeout-unref-fixture.js
@@ -0,0 +1,12 @@
+const timer = setTimeout(() => {}, 999_999_999);
+if (timer.unref() !== timer) throw new Error("Expected timer.unref() === timer");
+
+var ranCount = 0;
+const going2Refresh = setTimeout(() => {
+ if (ranCount < 1) going2Refresh.refresh();
+ ranCount++;
+
+ if (ranCount === 2) {
+ console.log("SUCCESS");
+ }
+}, 1);
diff --git a/test/js/web/timers/setTimeout.test.js b/test/js/web/timers/setTimeout.test.js
index dbe89dea8..eef6bbae0 100644
--- a/test/js/web/timers/setTimeout.test.js
+++ b/test/js/web/timers/setTimeout.test.js
@@ -1,5 +1,7 @@
+import { spawnSync } from "bun";
import { it, expect } from "bun:test";
-
+import { bunEnv, bunExe } from "harness";
+import path from "node:path";
it("setTimeout", async () => {
var lastID = -1;
const result = await new Promise((resolve, reject) => {
@@ -172,11 +174,56 @@ it.skip("order of setTimeouts", done => {
Promise.resolve().then(maybeDone(() => nums.push(1)));
});
+it("setTimeout -> refresh", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), path.join(import.meta.dir, "setTimeout-unref-fixture.js")],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(0);
+ expect(stdout.toString()).toBe("SUCCESS\n");
+});
+
+it("setTimeout -> unref -> ref works", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), path.join(import.meta.dir, "setTimeout-unref-fixture-4.js")],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(0);
+ expect(stdout.toString()).toBe("TEST PASSED!\n");
+});
+
+it("setTimeout -> ref -> unref works, even if there is another timer", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), path.join(import.meta.dir, "setTimeout-unref-fixture-2.js")],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(0);
+ expect(stdout.toString()).toBe("");
+});
+
+it("setTimeout -> ref -> unref works", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), path.join(import.meta.dir, "setTimeout-unref-fixture-5.js")],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(0);
+ expect(stdout.toString()).toBe("");
+});
+
+it("setTimeout -> unref doesn't keep event loop alive forever", () => {
+ const { exitCode, stdout } = spawnSync({
+ cmd: [bunExe(), path.join(import.meta.dir, "setTimeout-unref-fixture-3.js")],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(0);
+ expect(stdout.toString()).toBe("");
+});
+
it("setTimeout should refresh N times", done => {
let count = 0;
let timer = setTimeout(() => {
count++;
- timer.refresh();
+ expect(timer.refresh()).toBe(timer);
}, 50);
setTimeout(() => {
diff --git a/test/js/web/util/atob.test.js b/test/js/web/util/atob.test.js
index 4945829e1..20c029f14 100644
--- a/test/js/web/util/atob.test.js
+++ b/test/js/web/util/atob.test.js
@@ -60,18 +60,15 @@ it("btoa", () => {
expect(btoa("abcde")).toBe("YWJjZGU=");
expect(btoa("abcdef")).toBe("YWJjZGVm");
expect(typeof btoa).toBe("function");
- try {
- btoa();
- throw new Error("Expected error");
- } catch (error) {
- expect(error.name).toBe("TypeError");
- }
+ expect(() => btoa()).toThrow("btoa requires 1 argument (a string)");
var window = "[object Window]";
expect(btoa("")).toBe("");
expect(btoa(null)).toBe("bnVsbA==");
expect(btoa(undefined)).toBe("dW5kZWZpbmVk");
expect(btoa(window)).toBe("W29iamVjdCBXaW5kb3dd");
expect(btoa("éé")).toBe("6ek=");
+ // check for utf16
+ expect(btoa("🧐éé".substring("🧐".length))).toBe("6ek=");
expect(btoa("\u0080\u0081")).toBe("gIE=");
expect(btoa(Bun)).toBe(btoa("[object Bun]"));
});
diff --git a/test/js/web/web-globals.test.js b/test/js/web/web-globals.test.js
index b7a243190..d687a1290 100644
--- a/test/js/web/web-globals.test.js
+++ b/test/js/web/web-globals.test.js
@@ -138,6 +138,25 @@ it("crypto.randomUUID", () => {
});
});
+it("crypto.randomUUID version, issues#3575", () => {
+ var uuid = crypto.randomUUID();
+
+ function validate(uuid) {
+ const regex =
+ /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
+ return typeof uuid === "string" && regex.test(uuid);
+ }
+ function version(uuid) {
+ if (!validate(uuid)) {
+ throw TypeError("Invalid UUID");
+ }
+
+ return parseInt(uuid.slice(14, 15), 16);
+ }
+
+ expect(version(uuid)).toBe(4);
+});
+
it("URL.prototype.origin", () => {
const url = new URL("https://html.spec.whatwg.org/");
const { origin, host, hostname } = url;
diff --git a/test/js/web/websocket/websocket.test.js b/test/js/web/websocket/websocket.test.js
index 867b86123..76ff16ecb 100644
--- a/test/js/web/websocket/websocket.test.js
+++ b/test/js/web/websocket/websocket.test.js
@@ -6,16 +6,33 @@ const TEST_WEBSOCKET_HOST = process.env.TEST_WEBSOCKET_HOST || "wss://ws.postman
describe("WebSocket", () => {
it("should connect", async () => {
- const ws = new WebSocket(TEST_WEBSOCKET_HOST);
- await new Promise((resolve, reject) => {
+ const server = Bun.serve({
+ port: 0,
+ fetch(req, server) {
+ if (server.upgrade(req)) {
+ server.stop();
+ return;
+ }
+
+ return new Response();
+ },
+ websocket: {
+ open(ws) {},
+ message(ws) {
+ ws.close();
+ },
+ },
+ });
+ const ws = new WebSocket(`ws://${server.hostname}:${server.port}`, {});
+ await new Promise(resolve => {
ws.onopen = resolve;
- ws.onerror = reject;
});
- var closed = new Promise((resolve, reject) => {
+ var closed = new Promise(resolve => {
ws.onclose = resolve;
});
ws.close();
await closed;
+ server.stop(true);
});
it("should connect over https", async () => {
@@ -59,17 +76,18 @@ describe("WebSocket", () => {
const server = Bun.serve({
port: 0,
fetch(req, server) {
- server.stop();
done();
+ server.stop();
return new Response();
},
websocket: {
- open(ws) {
+ open(ws) {},
+ message(ws) {
ws.close();
},
},
});
- const ws = new WebSocket(`http://${server.hostname}:${server.port}`, {});
+ new WebSocket(`http://${server.hostname}:${server.port}`, {});
});
describe("nodebuffer", () => {
it("should support 'nodebuffer' binaryType", done => {
@@ -93,6 +111,7 @@ describe("WebSocket", () => {
expect(ws.binaryType).toBe("nodebuffer");
Bun.gc(true);
ws.onmessage = ({ data }) => {
+ ws.close();
expect(Buffer.isBuffer(data)).toBe(true);
expect(data).toEqual(new Uint8Array([1, 2, 3]));
server.stop(true);
@@ -117,6 +136,7 @@ describe("WebSocket", () => {
ws.sendBinary(new Uint8Array([1, 2, 3]));
setTimeout(() => {
client.onmessage = ({ data }) => {
+ client.close();
expect(Buffer.isBuffer(data)).toBe(true);
expect(data).toEqual(new Uint8Array([1, 2, 3]));
server.stop(true);
diff --git a/test/js/workerd/html-rewriter.test.js b/test/js/workerd/html-rewriter.test.js
index 1ca92a567..44961df3b 100644
--- a/test/js/workerd/html-rewriter.test.js
+++ b/test/js/workerd/html-rewriter.test.js
@@ -300,4 +300,91 @@ describe("HTMLRewriter", () => {
.text(),
).toEqual("<div></div>");
});
+
+ it("it supports lastInTextNode", async () => {
+ let lastInTextNode;
+
+ await new HTMLRewriter()
+ .on("p", {
+ text(text) {
+ lastInTextNode ??= text.lastInTextNode;
+ },
+ })
+ .transform(new Response("<p>Lorem ipsum!</p>"))
+ .text();
+
+ expect(lastInTextNode).toBeBoolean();
+ });
+});
+
+// By not segfaulting, this test passes
+it("#3334 regression", async () => {
+ for (let i = 0; i < 10; i++) {
+ const headers = new Headers({
+ "content-type": "text/html",
+ });
+ const response = new Response("<div>content</div>", { headers });
+
+ const result = await new HTMLRewriter()
+ .on("div", {
+ element(elem) {
+ elem.setInnerContent("new");
+ },
+ })
+ .transform(response)
+ .text();
+ expect(result).toEqual("<div>new</div>");
+ }
+ Bun.gc(true);
+});
+
+it("#3489", async () => {
+ var el;
+ await new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ el = element.getAttribute("id");
+ },
+ })
+ .transform(new Response('<p id="Šžõäöü"></p>'))
+ .text();
+ expect(el).toEqual("Šžõäöü");
+});
+
+it("get attribute - ascii", async () => {
+ for (let i = 0; i < 10; i++) {
+ var el;
+ await new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ el = element.getAttribute("id");
+ },
+ })
+ .transform(new Response(`<p id="asciii"></p>`))
+ .text();
+ expect(el).toEqual("asciii");
+ }
+});
+
+it("#3520", async () => {
+ const pairs = [];
+
+ await new HTMLRewriter()
+ .on("p", {
+ element(element) {
+ for (const pair of element.attributes) {
+ pairs.push(pair);
+ }
+ },
+ })
+ .transform(new Response('<p šž="Õäöü" ab="Õäöü" šž="Õäöü" šž="dc" šž="🕵🏻"></p>'))
+ .text();
+
+ expect(pairs).toEqual([
+ ["šž", "Õäöü"],
+ ["ab", "Õäöü"],
+ ["šž", "Õäöü"],
+ ["šž", "dc"],
+ ["šž", "🕵🏻"],
+ ]);
});