aboutsummaryrefslogtreecommitdiff
path: root/test/js/node
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-07-11 19:14:34 -0700
committerGravatar GitHub <noreply@github.com> 2023-07-11 19:14:34 -0700
commitcbb88672f217a90db1aa1eb29cd92d5d9035b22b (patch)
tree43a00501f3cde495967e116f0b660777051551f8 /test/js/node
parent1f900cff453700b19bca2acadfe26da4468c1282 (diff)
parent34b0e7a2bbd8bf8097341cdb0075d0908283e834 (diff)
downloadbun-jarred/esm-conditions.tar.gz
bun-jarred/esm-conditions.tar.zst
bun-jarred/esm-conditions.zip
Merge branch 'main' into jarred/esm-conditionsjarred/esm-conditions
Diffstat (limited to '')
-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
42 files changed, 1974 insertions, 147 deletions
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");
+ });
+});