aboutsummaryrefslogtreecommitdiff
path: root/test/bun.js/fs.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'test/bun.js/fs.test.ts')
-rw-r--r--test/bun.js/fs.test.ts1016
1 files changed, 1016 insertions, 0 deletions
diff --git a/test/bun.js/fs.test.ts b/test/bun.js/fs.test.ts
new file mode 100644
index 000000000..4c847d25a
--- /dev/null
+++ b/test/bun.js/fs.test.ts
@@ -0,0 +1,1016 @@
+import { beforeEach, describe, expect, it } from "bun:test";
+import { gc, gcTick } from "./gc";
+import fs, {
+ closeSync,
+ existsSync,
+ mkdirSync,
+ openSync,
+ readdirSync,
+ readFile,
+ readFileSync,
+ readSync,
+ writeFileSync,
+ writeSync,
+ statSync,
+ lstatSync,
+ copyFileSync,
+ rmSync,
+ rmdir,
+ rmdirSync,
+ createReadStream,
+ createWriteStream,
+ promises,
+ unlinkSync,
+ mkdtempSync,
+ constants,
+ Dirent,
+ Stats,
+} from "node:fs";
+
+import _promises from "node:fs/promises";
+
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+
+import { ReadStream as ReadStream_, WriteStream as WriteStream_ } from "../fixtures/export-lazy-fs-streams/export-from";
+import {
+ ReadStream as ReadStreamStar_,
+ WriteStream as WriteStreamStar_,
+} from "../fixtures/export-lazy-fs-streams/export-*-from";
+
+const Buffer = globalThis.Buffer || Uint8Array;
+
+if (!import.meta.dir) {
+ import.meta.dir = ".";
+}
+
+function mkdirForce(path) {
+ if (!existsSync(path)) mkdirSync(path, { recursive: true });
+}
+
+describe("copyFileSync", () => {
+ it("should work for files < 128 KB", () => {
+ const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`;
+ expect(existsSync(tempdir)).toBe(false);
+ expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
+
+ // that don't exist
+ copyFileSync(import.meta.path, tempdir + "/copyFileSync.js");
+ expect(existsSync(tempdir + "/copyFileSync.js")).toBe(true);
+ expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe(readFileSync(import.meta.path, "utf-8"));
+
+ // that do exist
+ copyFileSync(tempdir + "/copyFileSync.js", tempdir + "/copyFileSync.js1");
+ writeFileSync(tempdir + "/copyFileSync.js1", "hello");
+ copyFileSync(tempdir + "/copyFileSync.js1", tempdir + "/copyFileSync.js");
+
+ expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe("hello");
+ });
+
+ it("should work for files > 128 KB ", () => {
+ const tempdir = `/tmp/fs.test.js/${Date.now()}-1/1234/hi`;
+ expect(existsSync(tempdir)).toBe(false);
+ expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
+ var buffer = new Int32Array(128 * 1024);
+ for (let i = 0; i < buffer.length; i++) {
+ buffer[i] = i % 256;
+ }
+
+ const hash = Bun.hash(buffer.buffer);
+ writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer);
+
+ expect(existsSync(tempdir + "/copyFileSync.dest.blob")).toBe(false);
+ expect(existsSync(tempdir + "/copyFileSync.src.blob")).toBe(true);
+ copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob");
+
+ expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(hash);
+ buffer[0] = 255;
+ writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer);
+ copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob");
+ expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(Bun.hash(buffer.buffer));
+ });
+});
+
+describe("mkdirSync", () => {
+ it("should create a directory", () => {
+ const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`;
+ expect(existsSync(tempdir)).toBe(false);
+ expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
+ expect(existsSync(tempdir)).toBe(true);
+ });
+});
+
+it("readdirSync on import.meta.dir", () => {
+ const dirs = readdirSync(import.meta.dir);
+ expect(dirs.length > 0).toBe(true);
+ var match = false;
+ gc(true);
+ for (let i = 0; i < dirs.length; i++) {
+ if (dirs[i] === import.meta.file) {
+ match = true;
+ }
+ }
+ gc(true);
+ expect(match).toBe(true);
+});
+
+// https://github.com/oven-sh/bun/issues/1887
+it("mkdtempSync, readdirSync, rmdirSync and unlinkSync with non-ascii", () => {
+ const tempdir = mkdtempSync(`/tmp/emoji-fruit-🍇 🍈 🍉 🍊 🍋`);
+ expect(existsSync(tempdir)).toBe(true);
+ writeFileSync(tempdir + "/non-ascii-👍.txt", "hello");
+ const dirs = readdirSync(tempdir);
+ expect(dirs.length > 0).toBe(true);
+ var match = false;
+ gc(true);
+ for (let i = 0; i < dirs.length; i++) {
+ if (dirs[i].endsWith("non-ascii-👍.txt")) {
+ match = true;
+ break;
+ }
+ }
+ gc(true);
+ expect(match).toBe(true);
+ unlinkSync(tempdir + "/non-ascii-👍.txt");
+ expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false);
+ rmdirSync(tempdir);
+ expect(existsSync(tempdir)).toBe(false);
+});
+
+it("mkdtempSync() empty name", () => {
+ // @ts-ignore-next-line
+ const tempdir = mkdtempSync();
+ expect(existsSync(tempdir)).toBe(true);
+ writeFileSync(tempdir + "/non-ascii-👍.txt", "hello");
+ const dirs = readdirSync(tempdir);
+ expect(dirs.length > 0).toBe(true);
+ var match = false;
+ gc(true);
+ for (let i = 0; i < dirs.length; i++) {
+ if (dirs[i].endsWith("non-ascii-👍.txt")) {
+ match = true;
+ break;
+ }
+ }
+ gc(true);
+ expect(match).toBe(true);
+ unlinkSync(tempdir + "/non-ascii-👍.txt");
+ expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false);
+ rmdirSync(tempdir);
+ expect(existsSync(tempdir)).toBe(false);
+});
+
+it("readdirSync on import.meta.dir with trailing slash", () => {
+ const dirs = readdirSync(import.meta.dir + "/");
+ expect(dirs.length > 0).toBe(true);
+ // this file should exist in it
+ var match = false;
+ for (let i = 0; i < dirs.length; i++) {
+ if (dirs[i] === import.meta.file) {
+ match = true;
+ }
+ }
+ expect(match).toBe(true);
+});
+
+it("readdirSync works on empty directories", () => {
+ const path = `/tmp/fs-test-empty-dir-${(Math.random() * 100000 + 100).toString(32)}`;
+ mkdirSync(path, { recursive: true });
+ expect(readdirSync(path).length).toBe(0);
+});
+
+it("readdirSync works on directories with under 32 files", () => {
+ const path = `/tmp/fs-test-one-dir-${(Math.random() * 100000 + 100).toString(32)}`;
+ mkdirSync(path, { recursive: true });
+ writeFileSync(`${path}/a`, "a");
+ const results = readdirSync(path);
+ expect(results.length).toBe(1);
+ expect(results[0]).toBe("a");
+});
+
+it("readdirSync throws when given a file path", () => {
+ try {
+ readdirSync(import.meta.path);
+ throw new Error("should not get here");
+ } catch (exception: any) {
+ expect(exception.name).toBe("ENOTDIR");
+ }
+});
+
+it("readdirSync throws when given a path that doesn't exist", () => {
+ try {
+ readdirSync(import.meta.path + "/does-not-exist/really");
+ throw new Error("should not get here");
+ } catch (exception: any) {
+ expect(exception.name).toBe("ENOTDIR");
+ }
+});
+
+it("readdirSync throws when given a file path with trailing slash", () => {
+ try {
+ readdirSync(import.meta.path + "/");
+ throw new Error("should not get here");
+ } catch (exception: any) {
+ expect(exception.name).toBe("ENOTDIR");
+ }
+});
+
+describe("readSync", () => {
+ const firstFourBytes = new Uint32Array(new TextEncoder().encode("File").buffer)[0];
+ it("works with a position set to 0", () => {
+ const fd = openSync(import.meta.dir + "/readFileSync.txt", "r");
+ const four = new Uint8Array(4);
+
+ {
+ const count = readSync(fd, four, 0, 4, 0);
+ const u32 = new Uint32Array(four.buffer)[0];
+ expect(u32).toBe(firstFourBytes);
+ expect(count).toBe(4);
+ }
+ closeSync(fd);
+ });
+ it("works without position set", () => {
+ const fd = openSync(import.meta.dir + "/readFileSync.txt", "r");
+ const four = new Uint8Array(4);
+ {
+ const count = readSync(fd, four);
+ const u32 = new Uint32Array(four.buffer)[0];
+ expect(u32).toBe(firstFourBytes);
+ expect(count).toBe(4);
+ }
+ closeSync(fd);
+ });
+});
+
+describe("writeSync", () => {
+ it("works with a position set to 0", () => {
+ const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+");
+ const four = new Uint8Array(4);
+
+ {
+ const count = writeSync(fd, new TextEncoder().encode("File"), 0, 4, 0);
+ expect(count).toBe(4);
+ }
+ closeSync(fd);
+ });
+ it("works without position set", () => {
+ const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+");
+ const four = new Uint8Array(4);
+ {
+ const count = writeSync(fd, new TextEncoder().encode("File"));
+ expect(count).toBe(4);
+ }
+ closeSync(fd);
+ });
+});
+
+describe("readFileSync", () => {
+ it("works", () => {
+ gc();
+ const text = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8");
+ gc();
+ expect(text).toBe("File read successfully");
+ gc();
+ });
+
+ it("works with a file url", () => {
+ gc();
+ const text = readFileSync(new URL("file://" + import.meta.dir + "/readFileSync.txt"), "utf8");
+ gc();
+ expect(text).toBe("File read successfully");
+ });
+
+ it("works with special files in the filesystem", () => {
+ {
+ const text = readFileSync("/dev/null", "utf8");
+ gc();
+ expect(text).toBe("");
+ }
+
+ if (process.platform === "linux") {
+ const text = readFileSync("/proc/filesystems");
+ gc();
+ expect(text.length > 0).toBe(true);
+ }
+ });
+
+ it("returning Buffer works", () => {
+ const text = readFileSync(import.meta.dir + "/readFileSync.txt");
+ const encoded = [
+ 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121,
+ ];
+ for (let i = 0; i < encoded.length; i++) {
+ expect(text[i]).toBe(encoded[i]);
+ }
+ });
+});
+
+describe("readFile", () => {
+ it("works", async () => {
+ gc();
+ await new Promise((resolve, reject) => {
+ readFile(import.meta.dir + "/readFileSync.txt", "utf8", (err, text) => {
+ gc();
+ expect(text).toBe("File read successfully");
+ resolve(true);
+ });
+ });
+ });
+
+ it("returning Buffer works", async () => {
+ gc();
+ await new Promise((resolve, reject) => {
+ gc();
+ readFile(import.meta.dir + "/readFileSync.txt", (err, text) => {
+ const encoded = [
+ 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121,
+ ];
+ gc();
+ for (let i = 0; i < encoded.length; i++) {
+ expect(text[i]).toBe(encoded[i]);
+ }
+ resolve(true);
+ });
+ });
+ });
+});
+
+describe("writeFileSync", () => {
+ it("works", () => {
+ const path = `/tmp/${Date.now()}.writeFileSync.txt`;
+ writeFileSync(path, "File written successfully", "utf8");
+
+ expect(readFileSync(path, "utf8")).toBe("File written successfully");
+ });
+
+ it("returning Buffer works", () => {
+ const buffer = new Buffer([
+ 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108,
+ 121,
+ ]);
+ const path = `/tmp/${Date.now()}.blob.writeFileSync.txt`;
+ writeFileSync(path, buffer);
+ const out = readFileSync(path);
+
+ for (let i = 0; i < buffer.length; i++) {
+ expect(buffer[i]).toBe(out[i]);
+ }
+ });
+ it("returning ArrayBuffer works", () => {
+ const buffer = new Buffer([
+ 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108,
+ 121,
+ ]);
+ const path = `/tmp/${Date.now()}.blob2.writeFileSync.txt`;
+ writeFileSync(path, buffer);
+ const out = readFileSync(path);
+
+ for (let i = 0; i < buffer.length; i++) {
+ expect(buffer[i]).toBe(out[i]);
+ }
+ });
+});
+
+function triggerDOMJIT(target, fn, result) {
+ for (let i = 0; i < 9999; i++) {
+ if (fn.apply(target) !== result) {
+ throw new Error("DOMJIT failed");
+ }
+ }
+}
+
+describe("lstat", () => {
+ it("file metadata is correct", () => {
+ const fileStats = lstatSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1));
+ expect(fileStats.isSymbolicLink()).toBe(false);
+ expect(fileStats.isFile()).toBe(true);
+ expect(fileStats.isDirectory()).toBe(false);
+
+ triggerDOMJIT(fileStats, fileStats.isFile, true);
+ triggerDOMJIT(fileStats, fileStats.isDirectory, false);
+ triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false);
+ });
+
+ it("folder metadata is correct", () => {
+ const fileStats = lstatSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1));
+ expect(fileStats.isSymbolicLink()).toBe(false);
+ expect(fileStats.isFile()).toBe(false);
+ expect(fileStats.isDirectory()).toBe(true);
+
+ triggerDOMJIT(fileStats, fileStats.isFile, false);
+ triggerDOMJIT(fileStats, fileStats.isDirectory, true);
+ triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false);
+ });
+
+ it("symlink metadata is correct", () => {
+ const linkStats = lstatSync(new URL("./fs-stream.link.js", import.meta.url).toString().slice("file://".length - 1));
+ expect(linkStats.isSymbolicLink()).toBe(true);
+ expect(linkStats.isFile()).toBe(false);
+ expect(linkStats.isDirectory()).toBe(false);
+
+ triggerDOMJIT(linkStats, linkStats.isFile, false);
+ triggerDOMJIT(linkStats, linkStats.isDirectory, false);
+ triggerDOMJIT(linkStats, linkStats.isSymbolicLink, true);
+ });
+});
+
+describe("stat", () => {
+ it("file metadata is correct", () => {
+ const fileStats = statSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1));
+ expect(fileStats.isSymbolicLink()).toBe(false);
+ expect(fileStats.isFile()).toBe(true);
+ expect(fileStats.isDirectory()).toBe(false);
+
+ triggerDOMJIT(fileStats, fileStats.isFile, true);
+ triggerDOMJIT(fileStats, fileStats.isDirectory, false);
+ triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false);
+ });
+
+ it("folder metadata is correct", () => {
+ const fileStats = statSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1));
+ expect(fileStats.isSymbolicLink()).toBe(false);
+ expect(fileStats.isFile()).toBe(false);
+ expect(fileStats.isDirectory()).toBe(true);
+ expect(typeof fileStats.dev).toBe("number");
+ expect(typeof fileStats.ino).toBe("number");
+ expect(typeof fileStats.mode).toBe("number");
+ expect(typeof fileStats.nlink).toBe("number");
+ expect(typeof fileStats.uid).toBe("number");
+ expect(typeof fileStats.gid).toBe("number");
+ expect(typeof fileStats.rdev).toBe("number");
+ expect(typeof fileStats.size).toBe("number");
+ expect(typeof fileStats.blksize).toBe("number");
+ expect(typeof fileStats.blocks).toBe("number");
+ expect(typeof fileStats.atimeMs).toBe("number");
+ expect(typeof fileStats.mtimeMs).toBe("number");
+ expect(typeof fileStats.ctimeMs).toBe("number");
+ expect(typeof fileStats.birthtimeMs).toBe("number");
+ expect(typeof fileStats.atime).toBe("object");
+ expect(typeof fileStats.mtime).toBe("object");
+ expect(typeof fileStats.ctime).toBe("object");
+ expect(typeof fileStats.birthtime).toBe("object");
+
+ triggerDOMJIT(fileStats, fileStats.isFile, false);
+ triggerDOMJIT(fileStats, fileStats.isDirectory, true);
+ triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false);
+ });
+
+ it("stat returns ENOENT", () => {
+ try {
+ statSync("/tmp/doesntexist");
+ throw "statSync should throw";
+ } catch (e: any) {
+ expect(e.code).toBe("ENOENT");
+ }
+ });
+});
+
+describe("rm", () => {
+ it("removes a file", () => {
+ const path = `/tmp/${Date.now()}.rm.txt`;
+ writeFileSync(path, "File written successfully", "utf8");
+ expect(existsSync(path)).toBe(true);
+ rmSync(path);
+ expect(existsSync(path)).toBe(false);
+ });
+
+ it("removes a dir", () => {
+ const path = `/tmp/${Date.now()}.rm.dir`;
+ try {
+ mkdirSync(path);
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmSync(path);
+ expect(existsSync(path)).toBe(false);
+ });
+
+ it("removes a dir recursively", () => {
+ const path = `/tmp/${Date.now()}.rm.dir/foo/bar`;
+ try {
+ mkdirSync(path, { recursive: true });
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmSync(join(path, "../../"), { recursive: true });
+ expect(existsSync(path)).toBe(false);
+ });
+});
+
+describe("rmdir", () => {
+ it("removes a file", done => {
+ const path = `/tmp/${Date.now()}.rm.txt`;
+ writeFileSync(path, "File written successfully", "utf8");
+ expect(existsSync(path)).toBe(true);
+ rmdir(path, err => {
+ try {
+ expect(err).toBeDefined();
+ expect(err!.code).toBe("EPERM");
+ expect(err!.message).toBe("Operation not permitted");
+ expect(existsSync(path)).toBe(true);
+ } catch (e) {
+ return done(e);
+ } finally {
+ done();
+ }
+ });
+ });
+
+ it("removes a dir", done => {
+ const path = `/tmp/${Date.now()}.rm.dir`;
+ try {
+ mkdirSync(path);
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmdir(path, err => {
+ if (err) return done(err);
+ expect(existsSync(path)).toBe(false);
+ done();
+ });
+ });
+ // TODO support `recursive: true`
+ it("removes a dir recursively", done => {
+ const path = `/tmp/${Date.now()}.rm.dir/foo/bar`;
+ try {
+ mkdirSync(path, { recursive: true });
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmdir(join(path, "../../"), { recursive: true }, err => {
+ try {
+ expect(existsSync(path)).toBe(false);
+ done(err);
+ } catch (e) {
+ return done(e);
+ } finally {
+ done();
+ }
+ });
+ });
+});
+
+describe("rmdirSync", () => {
+ it("removes a file", () => {
+ const path = `/tmp/${Date.now()}.rm.txt`;
+ writeFileSync(path, "File written successfully", "utf8");
+ expect(existsSync(path)).toBe(true);
+ expect(() => {
+ rmdirSync(path);
+ }).toThrow("Operation not permitted");
+ expect(existsSync(path)).toBe(true);
+ });
+ it("removes a dir", () => {
+ const path = `/tmp/${Date.now()}.rm.dir`;
+ try {
+ mkdirSync(path);
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmdirSync(path);
+ expect(existsSync(path)).toBe(false);
+ });
+ // TODO support `recursive: true`
+ it("removes a dir recursively", () => {
+ const path = `/tmp/${Date.now()}.rm.dir/foo/bar`;
+ try {
+ mkdirSync(path, { recursive: true });
+ } catch (e) {}
+ expect(existsSync(path)).toBe(true);
+ rmdirSync(join(path, "../../"), { recursive: true });
+ expect(existsSync(path)).toBe(false);
+ });
+});
+
+describe("createReadStream", () => {
+ it("works (1 chunk)", async () => {
+ return await new Promise((resolve, reject) => {
+ var stream = createReadStream(import.meta.dir + "/readFileSync.txt", {});
+
+ stream.on("error", e => {
+ reject(e);
+ });
+
+ stream.on("data", chunk => {
+ expect(chunk instanceof Buffer).toBe(true);
+ expect(chunk.length).toBe("File read successfully".length);
+ expect(chunk.toString()).toBe("File read successfully");
+ });
+
+ stream.on("close", () => {
+ resolve(true);
+ });
+ });
+ });
+
+ it("works (22 chunk)", async () => {
+ var stream = createReadStream(import.meta.dir + "/readFileSync.txt", {
+ highWaterMark: 1,
+ });
+
+ var data = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8");
+ var i = 0;
+ return await new Promise(resolve => {
+ stream.on("data", chunk => {
+ expect(chunk instanceof Buffer).toBe(true);
+ expect(chunk.length).toBe(1);
+ expect(chunk.toString()).toBe(data[i++]);
+ });
+
+ stream.on("end", () => {
+ resolve(true);
+ });
+ });
+ });
+});
+
+describe("fs.WriteStream", () => {
+ it("should be exported", () => {
+ expect(fs.WriteStream).toBeDefined();
+ });
+
+ it("should be constructable", () => {
+ // @ts-ignore-next-line
+ const stream = new fs.WriteStream("test.txt");
+ expect(stream instanceof fs.WriteStream).toBe(true);
+ });
+
+ it("should be able to write to a file", done => {
+ const pathToDir = `${tmpdir()}/${Date.now()}`;
+ mkdirForce(pathToDir);
+ const path = join(pathToDir, `fs-writestream-test.txt`);
+
+ // @ts-ignore-next-line
+ const stream = new fs.WriteStream(path, { flags: "w+" });
+ stream.write("Test file written successfully");
+ stream.end();
+
+ stream.on("error", e => {
+ done(e instanceof Error ? e : new Error(e));
+ });
+
+ stream.on("finish", () => {
+ expect(readFileSync(path, "utf8")).toBe("Test file written successfully");
+ done();
+ });
+ });
+
+ it("should work if re-exported by name", () => {
+ // @ts-ignore-next-line
+ const stream = new WriteStream_("test.txt");
+ expect(stream instanceof WriteStream_).toBe(true);
+ expect(stream instanceof WriteStreamStar_).toBe(true);
+ expect(stream instanceof fs.WriteStream).toBe(true);
+ });
+
+ it("should work if re-exported by name, called without new", () => {
+ // @ts-ignore-next-line
+ const stream = WriteStream_("test.txt");
+ expect(stream instanceof WriteStream_).toBe(true);
+ expect(stream instanceof WriteStreamStar_).toBe(true);
+ expect(stream instanceof fs.WriteStream).toBe(true);
+ });
+
+ it("should work if re-exported, as export * from ...", () => {
+ // @ts-ignore-next-line
+ const stream = new WriteStreamStar_("test.txt");
+ expect(stream instanceof WriteStream_).toBe(true);
+ expect(stream instanceof WriteStreamStar_).toBe(true);
+ expect(stream instanceof fs.WriteStream).toBe(true);
+ });
+
+ it("should work if re-exported, as export * from..., called without new", () => {
+ // @ts-ignore-next-line
+ const stream = WriteStreamStar_("test.txt");
+ expect(stream instanceof WriteStream_).toBe(true);
+ expect(stream instanceof WriteStreamStar_).toBe(true);
+ expect(stream instanceof fs.WriteStream).toBe(true);
+ });
+
+ it("should be able to write to a file with re-exported WriteStream", done => {
+ const pathToDir = `${tmpdir()}/${Date.now()}`;
+ mkdirForce(pathToDir);
+ const path = join(pathToDir, `fs-writestream-re-exported-test.txt`);
+ // @ts-ignore-next-line
+ const stream = new WriteStream_(path, { flags: "w+" });
+ stream.write("Test file written successfully");
+ stream.end();
+
+ stream.on("error", e => {
+ done(e instanceof Error ? e : new Error(e));
+ });
+
+ stream.on("finish", () => {
+ expect(readFileSync(path, "utf8")).toBe("Test file written successfully");
+ done();
+ });
+ });
+});
+
+describe("fs.ReadStream", () => {
+ it("should be exported", () => {
+ expect(fs.ReadStream).toBeDefined();
+ });
+
+ it("should be constructable", () => {
+ // @ts-ignore-next-line
+ const stream = new fs.ReadStream("test.txt");
+ expect(stream instanceof fs.ReadStream).toBe(true);
+ });
+
+ it("should be able to read from a file", done => {
+ const pathToDir = `${tmpdir()}/${Date.now()}`;
+ mkdirForce(pathToDir);
+ const path = join(pathToDir, `fs-readstream-test.txt`);
+
+ writeFileSync(path, "Test file written successfully", {
+ encoding: "utf8",
+ flag: "w+",
+ });
+ // @ts-ignore-next-line
+ const stream = new fs.ReadStream(path);
+ stream.setEncoding("utf8");
+ stream.on("error", e => {
+ done(e instanceof Error ? e : new Error(e));
+ });
+
+ let data = "";
+
+ stream.on("data", chunk => {
+ data += chunk;
+ });
+
+ stream.on("end", () => {
+ expect(data).toBe("Test file written successfully");
+ done();
+ });
+ });
+
+ it("should work if re-exported by name", () => {
+ // @ts-ignore-next-line
+ const stream = new ReadStream_("test.txt");
+ expect(stream instanceof ReadStream_).toBe(true);
+ expect(stream instanceof ReadStreamStar_).toBe(true);
+ expect(stream instanceof fs.ReadStream).toBe(true);
+ });
+
+ it("should work if re-exported by name, called without new", () => {
+ // @ts-ignore-next-line
+ const stream = ReadStream_("test.txt");
+ expect(stream instanceof ReadStream_).toBe(true);
+ expect(stream instanceof ReadStreamStar_).toBe(true);
+ expect(stream instanceof fs.ReadStream).toBe(true);
+ });
+
+ it("should work if re-exported as export * from ...", () => {
+ // @ts-ignore-next-line
+ const stream = new ReadStreamStar_("test.txt");
+ expect(stream instanceof ReadStreamStar_).toBe(true);
+ expect(stream instanceof ReadStream_).toBe(true);
+ expect(stream instanceof fs.ReadStream).toBe(true);
+ });
+
+ it("should work if re-exported as export * from ..., called without new", () => {
+ // @ts-ignore-next-line
+ const stream = ReadStreamStar_("test.txt");
+ expect(stream instanceof ReadStreamStar_).toBe(true);
+ expect(stream instanceof ReadStream_).toBe(true);
+ expect(stream instanceof fs.ReadStream).toBe(true);
+ });
+
+ it("should be able to read from a file, with re-exported ReadStream", done => {
+ const pathToDir = `${tmpdir()}/${Date.now()}`;
+ mkdirForce(pathToDir);
+ const path = join(pathToDir, `fs-readstream-re-exported-test.txt`);
+
+ writeFileSync(path, "Test file written successfully", {
+ encoding: "utf8",
+ flag: "w+",
+ });
+
+ // @ts-ignore-next-line
+ const stream = new ReadStream_(path);
+ stream.setEncoding("utf8");
+ stream.on("error", e => {
+ done(e instanceof Error ? e : new Error(e));
+ });
+
+ let data = "";
+
+ stream.on("data", chunk => {
+ data += chunk;
+ });
+
+ stream.on("end", () => {
+ expect(data).toBe("Test file written successfully");
+ done();
+ });
+ });
+});
+
+describe("createWriteStream", () => {
+ it("simple write stream finishes", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.createWriteStream.txt`;
+ const stream = createWriteStream(path);
+ stream.write("Test file written successfully");
+ stream.end();
+
+ return await new Promise((resolve, reject) => {
+ stream.on("error", e => {
+ reject(e);
+ });
+
+ stream.on("finish", () => {
+ expect(readFileSync(path, "utf8")).toBe("Test file written successfully");
+ resolve(true);
+ });
+ });
+ });
+
+ it("writing null throws ERR_STREAM_NULL_VALUES", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`;
+ const stream = createWriteStream(path);
+ try {
+ stream.write(null);
+ expect(() => {}).toThrow(Error);
+ } catch (exception: any) {
+ expect(exception.code).toBe("ERR_STREAM_NULL_VALUES");
+ }
+ });
+
+ it("writing null throws ERR_STREAM_NULL_VALUES (objectMode: true)", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`;
+ const stream = createWriteStream(path, {
+ // @ts-ignore-next-line
+ objectMode: true,
+ });
+ try {
+ stream.write(null);
+ expect(() => {}).toThrow(Error);
+ } catch (exception: any) {
+ expect(exception.code).toBe("ERR_STREAM_NULL_VALUES");
+ }
+ });
+
+ it("writing false throws ERR_INVALID_ARG_TYPE", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`;
+ const stream = createWriteStream(path);
+ try {
+ stream.write(false);
+ expect(() => {}).toThrow(Error);
+ } catch (exception: any) {
+ expect(exception.code).toBe("ERR_INVALID_ARG_TYPE");
+ }
+ });
+
+ it("writing false throws ERR_INVALID_ARG_TYPE (objectMode: true)", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`;
+ const stream = createWriteStream(path, {
+ // @ts-ignore-next-line
+ objectMode: true,
+ });
+ try {
+ stream.write(false);
+ expect(() => {}).toThrow(Error);
+ } catch (exception: any) {
+ expect(exception.code).toBe("ERR_INVALID_ARG_TYPE");
+ }
+ });
+});
+
+describe("fs/promises", () => {
+ const { exists, mkdir, readFile, rmdir, stat, writeFile } = promises;
+
+ it("should not segfault on exception", async () => {
+ try {
+ await stat("foo/bar");
+ } catch (e) {}
+ });
+
+ it("readFile", async () => {
+ const data = await readFile(import.meta.dir + "/readFileSync.txt", "utf8");
+ expect(data).toBe("File read successfully");
+ });
+
+ it("writeFile", async () => {
+ const path = `/tmp/fs.test.js/${Date.now()}.writeFile.txt`;
+ await writeFile(path, "File written successfully");
+ expect(readFileSync(path, "utf8")).toBe("File written successfully");
+ });
+
+ it("readdir()", async () => {
+ const files = await promises.readdir(import.meta.dir);
+ expect(files.length).toBeGreaterThan(0);
+ });
+
+ it("readdir() no args doesnt segfault", async () => {
+ const fizz = [
+ [],
+ [Symbol("ok")],
+ [Symbol("ok"), Symbol("ok")],
+ [Symbol("ok"), Symbol("ok"), Symbol("ok")],
+ [Infinity, -NaN, -Infinity],
+ "\0\0\0\0",
+ "\r\n",
+ ];
+ for (const args of fizz) {
+ try {
+ // check it doens't segfault when called with invalid arguments
+ await promises.readdir(...(args as [any, ...any[]]));
+ } catch (e) {
+ // check that producing the error doesn't cause any crashes
+ Bun.inspect(e);
+ }
+ }
+ });
+
+ describe("rmdir", () => {
+ it("removes a file", async () => {
+ const path = `/tmp/${Date.now()}.rm.txt`;
+ await writeFile(path, "File written successfully", "utf8");
+ expect(await exists(path)).toBe(true);
+ try {
+ await rmdir(path);
+ expect(() => {}).toThrow();
+ } catch (err: any) {
+ expect(err.code).toBe("ENOTDIR");
+ // expect(err.message).toBe("Operation not permitted");
+ expect(await exists(path)).toBe(true);
+ }
+ });
+
+ it("removes a dir", async () => {
+ const path = `/tmp/${Date.now()}.rm.dir`;
+ try {
+ await mkdir(path);
+ } catch (e) {}
+ expect(await exists(path)).toBe(true);
+ await rmdir(path);
+ expect(await exists(path)).toBe(false);
+ });
+ // TODO support `recursive: true`
+ // it("removes a dir recursively", async () => {
+ // const path = `/tmp/${Date.now()}.rm.dir/foo/bar`;
+ // try {
+ // await mkdir(path, { recursive: true });
+ // } catch (e) {}
+ // expect(await exists(path)).toBe(true);
+ // await rmdir(join(path, "../../"), { recursive: true });
+ // expect(await exists(path)).toBe(false);
+ // });
+ });
+});
+
+it("fs.constants", () => {
+ expect(constants).toBeDefined();
+ expect(constants.F_OK).toBeDefined();
+ expect(constants.R_OK).toBeDefined();
+ expect(constants.W_OK).toBeDefined();
+ expect(constants.X_OK).toBeDefined();
+ expect(constants.O_RDONLY).toBeDefined();
+ expect(constants.O_WRONLY).toBeDefined();
+ expect(constants.O_RDWR).toBeDefined();
+ expect(constants.O_CREAT).toBeDefined();
+ expect(constants.O_EXCL).toBeDefined();
+ expect(constants.O_NOCTTY).toBeDefined();
+ expect(constants.O_TRUNC).toBeDefined();
+ expect(constants.O_APPEND).toBeDefined();
+ expect(constants.O_DIRECTORY).toBeDefined();
+ expect(constants.O_NOATIME).toBeDefined();
+ expect(constants.O_NOFOLLOW).toBeDefined();
+ expect(constants.O_SYNC).toBeDefined();
+ expect(constants.O_DSYNC).toBeDefined();
+ expect(constants.O_SYMLINK).toBeDefined();
+ expect(constants.O_DIRECT).toBeDefined();
+ expect(constants.O_NONBLOCK).toBeDefined();
+ expect(constants.S_IFMT).toBeDefined();
+ expect(constants.S_IFREG).toBeDefined();
+ expect(constants.S_IFDIR).toBeDefined();
+ expect(constants.S_IFCHR).toBeDefined();
+ expect(constants.S_IFBLK).toBeDefined();
+ expect(constants.S_IFIFO).toBeDefined();
+ expect(constants.S_IFLNK).toBeDefined();
+ expect(constants.S_IFSOCK).toBeDefined();
+ expect(constants.S_IRWXU).toBeDefined();
+ expect(constants.S_IRUSR).toBeDefined();
+ expect(constants.S_IWUSR).toBeDefined();
+ expect(constants.S_IXUSR).toBeDefined();
+ expect(constants.S_IRWXG).toBeDefined();
+ expect(constants.S_IRGRP).toBeDefined();
+ expect(constants.S_IWGRP).toBeDefined();
+ expect(constants.S_IXGRP).toBeDefined();
+ expect(constants.S_IRWXO).toBeDefined();
+ expect(constants.S_IROTH).toBeDefined();
+ expect(constants.S_IWOTH).toBeDefined();
+});
+
+it("fs.Dirent", () => {
+ expect(Dirent).toBeDefined();
+});
+
+it("fs.Stats", () => {
+ expect(Stats).toBeDefined();
+});
+
+it("repro 1516: can use undefined/null to specify default flag", () => {
+ const path = "/tmp/repro_1516.txt";
+ writeFileSync(path, "b", { flag: undefined });
+ // @ts-ignore-next-line
+ expect(readFileSync(path, { encoding: "utf8", flag: null })).toBe("b");
+ rmSync(path);
+});