import { describe, expect, it, spyOn } from "bun:test"; import { bunEnv, bunExe, gc, getMaxFD, isBroken, isIntelMacOS, isPosix, isWindows, tempDirWithFiles, tmpdirSync, } from "harness"; import { isAscii } from "node:buffer"; import fs, { closeSync, constants, copyFileSync, createReadStream, createWriteStream, Dir, Dirent, existsSync, fdatasync, fdatasyncSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, mkdtemp, mkdtempSync, openAsBlob, openSync, promises, readdirSync, readFile, readFileSync, readlinkSync, readSync, readvSync, realpathSync, renameSync, rmdir, rmdirSync, rmSync, statfsSync, Stats, statSync, symlinkSync, unlinkSync, writeFileSync, writeSync, writevSync, } from "node:fs"; import * as os from "node:os"; import path, { dirname, relative, resolve } from "node:path"; import { promisify } from "node:util"; import _promises, { type FileHandle } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { spawnSync } from "bun"; import { mkfifo } from "mkfifo"; import { ReadStream as ReadStream_, WriteStream as WriteStream_ } from "./export-from.js"; import { ReadStream as ReadStreamStar_, WriteStream as WriteStreamStar_ } from "./export-star-from.js"; const Buffer = globalThis.Buffer || Uint8Array; if (!import.meta.dir) { //@ts-expect-error import.meta.dir = "."; } function mkdirForce(path: string) { if (!existsSync(path)) mkdirSync(path, { recursive: true }); } function tmpdirTestMkdir(): string { const now = Date.now().toString() + Math.random().toString(16).slice(2, 10); const tempdir = `${tmpdir()}/fs.test.ts/${now}/1234/hi`; expect(existsSync(tempdir), `tempdir ${tempdir} should not exist`).toBe(false); const res = mkdirSync(tempdir, { recursive: true }); if (!res?.includes(now)) { expect(res).toInclude("fs.test.ts"); } expect(res).not.toInclude("1234"); expect(existsSync(tempdir)).toBe(true); return tempdir; } it("fs.writeFile(1, data) should work when its inherited", async () => { expect([join(import.meta.dir, "fs-writeFile-1-fixture.js"), "1"]).toRun(); }); it("fs.writeFile(2, data) should work when its inherited", async () => { expect([join(import.meta.dir, "fs-writeFile-1-fixture.js"), "2"]).toRun(); }); it("fs.writeFile(/dev/null, data) should work", async () => { expect([join(import.meta.dir, "fs-writeFile-1-fixture.js"), require("os").devNull]).toRun(); }); it("fs.openAsBlob", async () => { expect((await openAsBlob(import.meta.path)).size).toBe(statSync(import.meta.path).size); }); it("writing to 1, 2 are possible", () => { expect(fs.writeSync(1, Buffer.from("\nhello-stdout-test\n"))).toBe(19); expect(fs.writeSync(2, Buffer.from("\nhello-stderr-test\n"))).toBe(19); }); describe("test-fs-assert-encoding-error", () => { const testPath = join(tmpdirSync(), "assert-encoding-error"); const options = "test"; const expectedError = expect.objectContaining({ code: "ERR_INVALID_ARG_VALUE", name: "TypeError", }); it("readFile throws on invalid encoding", () => { expect(() => { fs.readFile(testPath, options, () => {}); }).toThrow(expectedError); }); it("readFileSync throws on invalid encoding", () => { expect(() => { fs.readFileSync(testPath, options); }).toThrow(expectedError); }); it("readdir throws on invalid encoding", () => { expect(() => { fs.readdir(testPath, options, () => {}); }).toThrow(expectedError); }); it("readdirSync throws on invalid encoding", () => { expect(() => { fs.readdirSync(testPath, options); }).toThrow(expectedError); }); it("readlink throws on invalid encoding", () => { expect(() => { fs.readlink(testPath, options, () => {}); }).toThrow(expectedError); }); it("readlinkSync throws on invalid encoding", () => { expect(() => { fs.readlinkSync(testPath, options); }).toThrow(expectedError); }); it("writeFile throws on invalid encoding", () => { expect(() => { fs.writeFile(testPath, "data", options, () => {}); }).toThrow(expectedError); }); it("writeFileSync throws on invalid encoding", () => { expect(() => { fs.writeFileSync(testPath, "data", options); }).toThrow(expectedError); }); it("appendFile throws on invalid encoding", () => { expect(() => { fs.appendFile(testPath, "data", options, () => {}); }).toThrow(expectedError); }); it("appendFileSync throws on invalid encoding", () => { expect(() => { fs.appendFileSync(testPath, "data", options); }).toThrow(expectedError); }); it("watch throws on invalid encoding", () => { expect(() => { fs.watch(testPath, options, () => {}); }).toThrow(expectedError); }); it("realpath throws on invalid encoding", () => { expect(() => { fs.realpath(testPath, options, () => {}); }).toThrow(expectedError); }); it("realpathSync throws on invalid encoding", () => { expect(() => { fs.realpathSync(testPath, options); }).toThrow(expectedError); }); it("mkdtemp throws on invalid encoding", () => { expect(() => { fs.mkdtemp(testPath, options, () => {}); }).toThrow(expectedError); }); it("mkdtempSync throws on invalid encoding", () => { expect(() => { fs.mkdtempSync(testPath, options); }).toThrow(expectedError); }); it("ReadStream throws on invalid encoding", () => { expect(() => { fs.ReadStream(testPath, options); }).toThrow(expectedError); }); it("WriteStream throws on invalid encoding", () => { expect(() => { fs.WriteStream(testPath, options); }).toThrow(expectedError); }); }); it("fs.readv returns object", async done => { const fd = await promisify(fs.open)(import.meta.path, "r"); const buffers = [Buffer.alloc(10), Buffer.alloc(10)]; fs.readv(fd, buffers, 0, (err, bytesRead, output) => { promisify(fs.close)(fd); if (err) { done(err); return; } expect(bytesRead).toBe(20); expect(output).toEqual(buffers); done(); }); }); it("fs.writev returns object", async done => { const outpath = tempDirWithFiles("fswritevtest", { "a.txt": "b" }); const fd = await promisify(fs.open)(join(outpath, "b.txt"), "w"); const buffers = [Buffer.alloc(10), Buffer.alloc(10)]; fs.writev(fd, buffers, 0, (err, bytesWritten, output) => { promisify(fs.close)(fd); if (err) { done(err); return; } expect(bytesWritten).toBe(20); expect(output).toEqual(buffers); done(); }); }); describe("FileHandle", () => { it("FileHandle#read returns object", async () => { await using fd = await fs.promises.open(__filename); const buf = Buffer.alloc(10); expect(await fd.read(buf, 0, 10, 0)).toEqual({ bytesRead: 10, buffer: buf }); }); it("FileHandle#readv returns object", async () => { await using fd = await fs.promises.open(__filename); const buffers = [Buffer.alloc(10), Buffer.alloc(10)]; expect(await fd.readv(buffers, 0)).toEqual({ bytesRead: 20, buffers }); }); it("FileHandle#write throws EBADF when closed", async () => { let handle: FileHandle; let spy; { await using fd = await fs.promises.open(__filename); handle = fd; spy = spyOn(handle, "close"); const buffers = [Buffer.alloc(10), Buffer.alloc(10)]; expect(await fd.readv(buffers, 0)).toEqual({ bytesRead: 20, buffers }); } expect(handle.close).toHaveBeenCalled(); expect(async () => await handle.read(Buffer.alloc(10))).toThrow("Bad file descriptor"); }); it("FileHandle#write returns object", async () => { await using fd = await fs.promises.open(`${tmpdir()}/${Date.now()}.writeFile.txt`, "w"); const buf = Buffer.from("test"); expect(await fd.write(buf, 0, 4, 0)).toEqual({ bytesWritten: 4, buffer: buf }); }); it("FileHandle#writev returns object", async () => { await using fd = await fs.promises.open(`${tmpdir()}/${Date.now()}.writeFile.txt`, "w"); const buffers = [Buffer.from("test"), Buffer.from("test")]; expect(await fd.writev(buffers, 0)).toEqual({ bytesWritten: 8, buffers }); }); it("FileHandle#readFile returns buffer", async () => { await using fd = await fs.promises.open(__filename); const buf = await fd.readFile(); expect(buf instanceof Buffer).toBe(true); }); it("FileHandle#readableWebStream", async () => { await using fd = await fs.promises.open(__filename); const stream = fd.readableWebStream(); const reader = stream.getReader(); const chunk = await reader.read(); expect(chunk.value instanceof Uint8Array).toBe(true); reader.releaseLock(); }); it("FileHandle#createReadStream", async () => { await using fd = await fs.promises.open(__filename); const readable = fd.createReadStream(); const data = await new Promise(resolve => { let data = ""; readable.on("data", chunk => { data += chunk; }); readable.on("end", () => { resolve(data); }); }); expect(data).toBe(readFileSync(__filename, "utf8")); }); it("FileHandle#writeFile", async () => { const path = `${tmpdir()}/${Date.now()}.writeFile.txt`; await using fd = await fs.promises.open(path, "w"); await fd.writeFile("File written successfully"); expect(readFileSync(path, "utf8")).toBe("File written successfully"); }); it("FileHandle#createWriteStream", async () => { const path = `${tmpdir()}/${Date.now()}.createWriteStream.txt`; { await using fd = await fs.promises.open(path, "w"); const stream = fd.createWriteStream(); await new Promise((resolve, reject) => { stream.on("error", e => { reject(e); }); stream.on("finish", () => { resolve(true); }); stream.write("Test file written successfully"); stream.end(); }); } expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); }); it("FileHandle#createWriteStream fixture 2", async () => { const path = `${tmpdir()}/${Date.now()}.createWriteStream.txt`; { await using fd = await fs.promises.open(path, "w"); const stream = fd.createWriteStream(); await new Promise((resolve, reject) => { stream.on("error", e => { reject(e); }); stream.on("close", () => { resolve(true); }); stream.write("Test file written successfully"); stream.end(); }); } expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); }); }); it("fdatasyncSync", () => { const temp = tmpdir(); const fd = openSync(join(temp, "test.blob"), "w", 0o664); fdatasyncSync(fd); closeSync(fd); }); it("fdatasync", done => { const temp = tmpdir(); const fd = openSync(join(temp, "test.blob"), "w", 0o664); fdatasync(fd, function () { done(...arguments); closeSync(fd); }); }); it("Dirent.name setter", () => { const dirent = Object.create(Dirent.prototype); expect(dirent.name).toBeUndefined(); dirent.name = "hello"; expect(dirent.name).toBe("hello"); }); it("writeFileSync should correctly resolve ../..", () => { const base = tmpdirSync(); const path = join(base, "foo", "bar"); mkdirSync(path, { recursive: true }); const cwd = process.cwd(); process.chdir(path); writeFileSync("../../test.txt", "hello"); expect(readFileSync(join(base, "test.txt"), "utf8")).toBe("hello"); process.chdir(cwd); }); it("writeFileSync in append should not truncate the file", () => { const path = join(tmpdirSync(), "should-not-append.txt"); var str = ""; writeFileSync(path, "---BEGIN---"); str += "---BEGIN---"; for (let i = 0; i < 10; i++) { const line = "Line #" + i; str += line; writeFileSync(path, line, { flag: "a" }); } expect(readFileSync(path, "utf8")).toBe(str); }); it("await readdir #3931", async () => { const { exitCode } = spawnSync({ cmd: [bunExe(), join(import.meta.dir, "./repro-3931.js")], env: bunEnv, cwd: import.meta.dir, }); expect(exitCode).toBe(0); }); it("writeFileSync NOT in append SHOULD truncate the file", () => { const path = join(tmpdirSync(), "should-not-append.txt"); for (let options of [{ flag: "w" }, { flag: undefined }, {}, undefined]) { writeFileSync(path, "---BEGIN---", options); var str = "---BEGIN---"; expect(readFileSync(path, "utf8")).toBe(str); for (let i = 0; i < 10; i++) { const line = "Line #" + i; str = line; writeFileSync(path, line, options); expect(readFileSync(path, "utf8")).toBe(str); } } }); describe("copyFileSync", () => { it("should work for files < 128 KB", () => { const tempdir = tmpdirTestMkdir(); // 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 = tmpdirTestMkdir(); 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)); }); it("constants are right", () => { expect(fs.constants.COPYFILE_EXCL).toBe(1); expect(fs.constants.COPYFILE_FICLONE).toBe(2); expect(fs.constants.COPYFILE_FICLONE_FORCE).toBe(4); }); it("FICLONE option does not error ever", () => { const tempdir = tmpdirTestMkdir(); // that don't exist copyFileSync(import.meta.path, tempdir + "/copyFileSync.js", fs.constants.COPYFILE_FICLONE); copyFileSync(import.meta.path, tempdir + "/copyFileSync.js", fs.constants.COPYFILE_FICLONE); copyFileSync(import.meta.path, tempdir + "/copyFileSync.js", fs.constants.COPYFILE_FICLONE); }); it("COPYFILE_EXCL works", () => { const tempdir = tmpdirTestMkdir(); // that don't exist copyFileSync(import.meta.path, tempdir + "/copyFileSync.js", fs.constants.COPYFILE_EXCL); expect(() => { copyFileSync(import.meta.path, tempdir + "/copyFileSync.js", fs.constants.COPYFILE_EXCL); }).toThrow(); }); if (process.platform === "linux") { describe("should work when copyFileRange is not available", () => { it("on large files", () => { const tempdir = tmpdirTestMkdir(); var buffer = new Int32Array(128 * 1024); for (let i = 0; i < buffer.length; i++) { buffer[i] = i % 256; } const hash = Bun.hash(buffer.buffer); const src = tempdir + "/copyFileSync.src.blob"; const dest = tempdir + "/copyFileSync.dest.blob"; writeFileSync(src, buffer.buffer); try { expect(existsSync(dest)).toBe(false); const { exitCode } = spawnSync({ stdio: ["inherit", "inherit", "inherit"], cmd: [bunExe(), join(import.meta.dir, "./fs-fixture-copyFile-no-copy_file_range.js"), src, dest], env: { ...bunEnv, BUN_CONFIG_DISABLE_COPY_FILE_RANGE: "1", }, }); expect(exitCode).toBe(0); expect(Bun.hash(readFileSync(dest))).toBe(hash); } finally { rmSync(src, { force: true }); rmSync(dest, { force: true }); } }); it("on small files", () => { const tempdir = tmpdirTestMkdir(); var buffer = new Int32Array(1 * 1024); for (let i = 0; i < buffer.length; i++) { buffer[i] = i % 256; } const hash = Bun.hash(buffer.buffer); const src = tempdir + "/copyFileSync.src.blob"; const dest = tempdir + "/copyFileSync.dest.blob"; try { writeFileSync(src, buffer.buffer); expect(existsSync(dest)).toBe(false); const { exitCode } = spawnSync({ stdio: ["inherit", "inherit", "inherit"], cmd: [bunExe(), join(import.meta.dir, "./fs-fixture-copyFile-no-copy_file_range.js"), src, dest], env: { ...bunEnv, BUN_CONFIG_DISABLE_COPY_FILE_RANGE: "1", }, }); expect(exitCode).toBe(0); expect(Bun.hash(readFileSync(dest))).toBe(hash); } finally { rmSync(src, { force: true }); rmSync(dest, { force: true }); } }); }); } }); describe("mkdirSync", () => { it("should create a directory", () => { const now = Date.now().toString(); const base = join(now, ".mkdirSync", "1234", "hi"); const tempdir = `${tmpdir()}/${base}`; expect(existsSync(tempdir)).toBe(false); const res = mkdirSync(tempdir, { recursive: true }); expect(res).toInclude(now); expect(res).not.toInclude(".mkdirSync"); expect(existsSync(tempdir)).toBe(true); }); it("should throw ENOENT for empty string", () => { expect(() => mkdirSync("", { recursive: true })).toThrow("no such file or directory"); expect(() => mkdirSync("")).toThrow("no such file or directory"); }); it("throws for invalid options", () => { const path = `${tmpdir()}/${Date.now()}.rm.dir2/foo/bar`; expect(() => mkdirSync( path, // @ts-expect-error { recursive: "lalala" }, ), ).toThrow('The "recursive" property must be of type boolean, got string'); }); }); 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); }); it("Dirent has the expected fields", () => { const dir = tmpdirSync(); writeFileSync(join(dir, "file.txt"), ""); const dirs = readdirSync(dir, { withFileTypes: true }); expect(dirs.length).toBe(1); expect(dirs[0].name).toBe("file.txt"); expect(dirs[0].path).toBe(dir); expect(dirs[0].parentPath).toBe(dir); }); it("promises.readdir on a large folder", async () => { const huge = tmpdirSync(); for (let i = 0; i < 128; i++) { writeFileSync(join(huge, "file-" + i), ""); } for (let j = 0; j < 4; j++) { const promises = await Promise.all([ fs.promises.readdir(huge), fs.promises.readdir(huge), fs.promises.readdir(huge), fs.promises.readdir(huge), ]); for (let chunk of promises) { expect(chunk).toHaveLength(128); chunk.sort(); let count = 0; for (let i = 0; i < 128; i++) { const current = chunk[i]; if (!current.startsWith("file-")) { throw new Error("invalid file name"); } const num = parseInt(current.slice(5)); // @ts-expect-error count += !!(num >= 0 && num < 128); } expect(count).toBe(128); } } rmSync(huge, { force: true, recursive: true }); }); it("promises.readFile", async () => { expect(await fs.promises.readFile(import.meta.path, "utf-8")).toEqual(readFileSync(import.meta.path, "utf-8")); expect(await fs.promises.readFile(import.meta.path, { encoding: "latin1" })).toEqual( readFileSync(import.meta.path, { encoding: "latin1" }), ); // We do this 20 times to check for any GC issues. for (let i = 0; i < 20; i++) { try { await fs.promises.readFile("/i-dont-exist", "utf-8"); expect.unreachable(); } catch (e: any) { expect(e).toBeInstanceOf(Error); expect(e.message).toBe("ENOENT: no such file or directory, open '/i-dont-exist'"); expect(e.code).toBe("ENOENT"); expect(e.errno).toBe(-2); expect(e.path).toBe("/i-dont-exist"); } } }); describe("promises.readFile", async () => { const nodeOutput = [ { "encoding": "utf8", "text": "ascii", "correct": { "type": "Buffer", "data": [97, 115, 99, 105, 105], }, "out": "ascii", }, { "encoding": "utf8", "text": "utf16 🍇 🍈 🍉 🍊 🍋", "correct": { "type": "Buffer", "data": [ 117, 116, 102, 49, 54, 32, 240, 159, 141, 135, 32, 240, 159, 141, 136, 32, 240, 159, 141, 137, 32, 240, 159, 141, 138, 32, 240, 159, 141, 139, ], }, "out": "utf16 🍇 🍈 🍉 🍊 🍋", }, { "encoding": "utf8", "text": "👍", "correct": { "type": "Buffer", "data": [240, 159, 145, 141], }, "out": "👍", }, { "encoding": "utf-8", "text": "ascii", "correct": { "type": "Buffer", "data": [97, 115, 99, 105, 105], }, "out": "ascii", }, { "encoding": "utf-8", "text": "utf16 🍇 🍈 🍉 🍊 🍋", "correct": { "type": "Buffer", "data": [ 117, 116, 102, 49, 54, 32, 240, 159, 141, 135, 32, 240, 159, 141, 136, 32, 240, 159, 141, 137, 32, 240, 159, 141, 138, 32, 240, 159, 141, 139, ], }, "out": "utf16 🍇 🍈 🍉 🍊 🍋", }, { "encoding": "utf-8", "text": "👍", "correct": { "type": "Buffer", "data": [240, 159, 145, 141], }, "out": "👍", }, { "encoding": "utf16le", "text": "ascii", "correct": { "type": "Buffer", "data": [97, 0, 115, 0, 99, 0, 105, 0, 105, 0], }, "out": "ascii", }, { "encoding": "utf16le", "text": "utf16 🍇 🍈 🍉 🍊 🍋", "correct": { "type": "Buffer", "data": [ 117, 0, 116, 0, 102, 0, 49, 0, 54, 0, 32, 0, 60, 216, 71, 223, 32, 0, 60, 216, 72, 223, 32, 0, 60, 216, 73, 223, 32, 0, 60, 216, 74, 223, 32, 0, 60, 216, 75, 223, ], }, "out": "utf16 🍇 🍈 🍉 🍊 🍋", }, { "encoding": "utf16le", "text": "👍", "correct": { "type": "Buffer", "data": [61, 216, 77, 220], }, "out": "👍", }, { "encoding": "latin1", "text": "ascii", "correct": { "type": "Buffer", "data": [97, 115, 99, 105, 105], }, "out": "ascii", }, { "encoding": "latin1", "text": "utf16 🍇 🍈 🍉 🍊 🍋", "correct": { "type": "Buffer", "data": [117, 116, 102, 49, 54, 32, 60, 71, 32, 60, 72, 32, 60, 73, 32, 60, 74, 32, 60, 75], }, "out": "utf16 { const results = []; for (let encoding of [ "utf8", "utf-8", "utf16le", "latin1", "binary", "base64", /* TODO: "base64url", */ "hex", ] as const) { for (let text of ["ascii", "utf16 🍇 🍈 🍉 🍊 🍋", "👍"]) { if (encoding === "base64" && !isAscii(Buffer.from(text))) // TODO: output does not match Node.js, and it's not a problem with readFile specifically. continue; const correct = Buffer.from(text, encoding); const outfile = join( tmpdir(), "promises.readFile-" + Date.now() + "-" + Math.random().toString(32) + "-" + encoding + ".txt", ); writeFileSync(outfile, correct); const out = await fs.promises.readFile(outfile, encoding); { const { promise, resolve, reject } = Promise.withResolvers(); fs.readFile(outfile, encoding, (err, data) => { if (err) reject(err); else resolve(data); }); expect(await promise).toEqual(out); } expect(fs.readFileSync(outfile, encoding)).toEqual(out); await promises.rm(outfile, { force: true }); expect(await promises.writeFile(outfile, text, encoding)).toBeUndefined(); expect(await promises.readFile(outfile, encoding)).toEqual(out); promises.rm(outfile, { force: true }); results.push({ encoding, text, correct, out, }); } } expect(JSON.parse(JSON.stringify(results, null, 2))).toEqual(nodeOutput); }); }); it("promises.readFile - UTF16 file path", async () => { const filename = `superduperduperdupduperdupersuperduperduperduperduperduperdupersuperduperduperduperduperduperdupersuperduperduperdupe-Bun-👍-${Date.now()}-${ (Math.random() * 1024000) | 0 }.txt`; const dest = join(tmpdir(), filename); await fs.promises.copyFile(import.meta.path, dest); const expected = readFileSync(import.meta.path, "utf-8"); Bun.gc(true); for (let i = 0; i < 100; i++) { expect(await fs.promises.readFile(dest, "utf-8")).toEqual(expected); } Bun.gc(true); }); it("promises.readFile - atomized file path", async () => { const filename = `superduperduperdupduperdupersuperduperduperduperduperduperdupersuperduperduperduperduperduperdupersuperduperduperdupe-Bun-👍-${Date.now()}-${ (Math.random() * 1024000) | 0 }.txt`; const destInput = join(tmpdir(), filename); // Force it to become an atomized string by making it a property access const dest: string = ( { [destInput]: destInput, boop: 123, } as const )[destInput] as string; await fs.promises.copyFile(import.meta.path, dest); const expected = readFileSync(import.meta.path, "utf-8"); Bun.gc(true); for (let i = 0; i < 100; i++) { expect(await fs.promises.readFile(dest, "utf-8")).toEqual(expected); } Bun.gc(true); }); it("promises.readFile with buffer as file path", async () => { for (let i = 0; i < 10; i++) expect(await fs.promises.readFile(Buffer.from(import.meta.path), "utf-8")).toEqual( readFileSync(import.meta.path, "utf-8"), ); }); it("promises.readdir on a large folder withFileTypes", async () => { const huge = tmpdirSync(); let withFileTypes = { withFileTypes: true } as const; for (let i = 0; i < 128; i++) { writeFileSync(join(huge, "file-" + i), ""); } for (let j = 0; j < 4; j++) { const promises = await Promise.all([ fs.promises.readdir(huge, withFileTypes), fs.promises.readdir(huge, withFileTypes), fs.promises.readdir(huge, withFileTypes), fs.promises.readdir(huge, withFileTypes), ]); for (let chunk of promises) { expect(chunk).toHaveLength(128); chunk.sort(); let count = 0; for (let i = 0; i < 128; i++) { const current = chunk[i].name; if (!current.startsWith("file-")) { throw new Error("invalid file name"); } const num = parseInt(current.slice(5)); // @ts-expect-error count += !!(num >= 0 && num < 128); } expect(count).toBe(128); } } rmSync(huge, { force: true, recursive: true }); }); it("statSync throwIfNoEntry", () => { const path = join(tmpdirSync(), "does", "not", "exist"); expect(statSync(path, { throwIfNoEntry: false })).toBeUndefined(); expect(lstatSync(path, { throwIfNoEntry: false })).toBeUndefined(); }); it("statSync throwIfNoEntry: true", () => { const path = join(tmpdirSync(), "does", "not", "exist"); expect(() => statSync(path, { throwIfNoEntry: true })).toThrow("no such file or directory"); expect(() => statSync(path)).toThrow("no such file or directory"); expect(() => lstatSync(path, { throwIfNoEntry: true })).toThrow("no such file or directory"); expect(() => lstatSync(path)).toThrow("no such file or directory"); }); it("stat == statSync", async () => { const sync = statSync(import.meta.path); const async = await promises.stat(import.meta.path); expect(Object.entries(sync)).toEqual(Object.entries(async)); }); // https://github.com/oven-sh/bun/issues/1887 it("mkdtempSync, readdirSync, rmdirSync and unlinkSync with non-ascii", () => { const tempdir = mkdtempSync(`${tmpdir()}/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", () => { const tempdir = mkdtempSync(os.tmpdir()); 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() non-exist dir #2568", () => { const path = join(tmpdirSync(), "does", "not", "exist"); try { expect(mkdtempSync(path)).toBeFalsy(); } catch (err: any) { expect(err?.errno).toBe(-2); } }); it("mkdtemp() non-exist dir #2568", done => { const path = join(tmpdirSync(), "does", "not", "exist"); mkdtemp(path, (err, folder) => { try { expect(err?.errno).toBe(-2); expect(folder).toBeUndefined(); done(); } catch (e) { done(e); } }); }); 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 = tmpdirSync(); expect(readdirSync(path).length).toBe(0); }); it("readdirSync works on directories with under 32 files", () => { const path = tmpdirSync(); 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("Error"); expect(exception.code).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) { // the correct error to return in this case is actually ENOENT (which we do on windows), // but on posix we return ENOTDIR expect(exception.name).toBe("Error"); expect(exception.code).toMatch(/ENOTDIR|ENOENT/); } }); 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("Error"); expect(exception.code).toBe("ENOTDIR"); } }); 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); { 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 with offset + length passed but not position", () => { const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); const four = new Uint8Array(4); { const count = readSync(fd, four, 0, 4); 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); }); it("works with invalid fd but zero length", () => { expect(readSync(2147483640, Buffer.alloc(0))).toBe(0); expect(readSync(2147483640, Buffer.alloc(10), 0, 0, 0)).toBe(0); }); }); 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(join(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 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 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("./readFileSync.txt", import.meta.url), "utf8"); gc(); expect(text).toBe("File read successfully"); }); it("works with a file path which contains spaces", async () => { gc(); const outpath = join(tmpdir(), "read file sync with space characters " + Math.random().toString(32) + " .txt"); await Bun.write(outpath, Bun.file(Bun.fileURLToPath(new URL("./readFileSync.txt", import.meta.url)))); const text = readFileSync(outpath, "utf8"); gc(); expect(text).toBe("File read successfully"); }); it("works with a file URL which contains spaces", async () => { gc(); const outpath = join(tmpdir(), "read file sync with space characters " + Math.random().toString(32) + " .txt"); await Bun.write(outpath, Bun.file(Bun.fileURLToPath(new URL("./readFileSync.txt", import.meta.url)))); // on windows constructing a file url from an absolute path containing a drive letter will not add the "file:///" prefix // node.js has the same behavior, not sure what makes the most sense here const url = isWindows ? new URL("file:///" + outpath) : new URL(outpath, import.meta.url); const text = readFileSync(url, "utf8"); gc(); expect(text).toBe("File read successfully"); }); it.skipIf(isWindows)("works with special posix 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); }); }); }); it("works with flags", async () => { const mydir = tempDirWithFiles("fs-read", {}); console.log(mydir); for (const [flag, code] of [ ["a", "EBADF"], ["ax", "EBADF"], ["a+", undefined], ["as", "EBADF"], ["as+", undefined], ["r", "ENOENT"], ["rs", "ENOENT"], ["r+", "ENOENT"], ["rs+", "ENOENT"], ["w", "EBADF"], ["wx", "EBADF"], ["w+", undefined], ["wx+", undefined], ]) { const name = flag!.replace("+", "_plus") + ".txt"; if (code == null) { expect(readFileSync(mydir + "/" + name, { encoding: "utf8", flag })).toBe(""); expect(readFileSync(mydir + "/" + name, { encoding: "utf8" })).toBe(""); } else { expect.toThrowWithCode(() => readFileSync(mydir + "/" + name, { encoding: "utf8", flag }), code); expect.toThrowWithCode(() => readFileSync(mydir + "/" + name, { encoding: "utf8" }), "ENOENT"); } } }); }); describe("writeFileSync", () => { it("works", () => { const path = `${tmpdirSync()}/writeFileSync.txt`; writeFileSync(path, "File written successfully", "utf8"); expect(readFileSync(path, "utf8")).toBe("File written successfully"); }); it("write file with mode, issue #3740", () => { const path = `${tmpdirSync()}/writeFileSyncWithMode.txt`; writeFileSync(path, "bun", { mode: 33188 }); const stat = fs.statSync(path); expect(stat.mode).toBe(isWindows ? 33206 : 33188); }); 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 = `${tmpdirSync()}/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 = `${tmpdirSync()}/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: fs.Stats, fn: (..._: any[]) => any, result: any) { 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(join(import.meta.dir, "fs-stream.js")); 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 path = join(import.meta.dir, "../../../../test"); const fileStats = lstatSync(path); 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 link = join(tmpdirSync(), `fs-stream.link.js`); symlinkSync(join(import.meta.dir, "fs-stream.js"), link); const linkStats = lstatSync(link); 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); }); }); it("symlink", () => { const actual = join(tmpdirSync(), "fs-symlink.txt"); try { unlinkSync(actual); } catch (e) {} symlinkSync(import.meta.path, actual); expect(realpathSync(actual)).toBe(realpathSync(import.meta.path)); }); it.if(isPosix)("realpathSync doesn't block on FIFO", () => { const path = join(tmpdirSync(), "test-fs-fifo-block.fifo"); mkfifo(path, 0o666); realpathSync(path); unlinkSync(path); }); it("readlink", () => { const actual = join(tmpdirSync(), "fs-readlink.txt"); try { unlinkSync(actual); } catch (e) {} symlinkSync(import.meta.path, actual); expect(readlinkSync(actual)).toBe(realpathSync(import.meta.path)); }); it.if(isWindows)("symlink on windows with forward slashes", async () => { const r = tmpdirSync(); await fs.promises.rm(join(r, "files/2024"), { recursive: true, force: true }); await fs.promises.mkdir(join(r, "files/2024"), { recursive: true }); await fs.promises.writeFile(join(r, "files/2024/123.txt"), "text"); await fs.promises.symlink("files/2024/123.txt", join(r, "file-sym.txt")); expect(await fs.promises.readlink(join(r, "file-sym.txt"))).toBe("files\\2024\\123.txt"); }); it("realpath async", async () => { const actual = join(tmpdirSync(), "fs-realpath.txt"); try { unlinkSync(actual); } catch (e) {} symlinkSync(import.meta.path, actual); expect(await promises.realpath(actual)).toBe(realpathSync(import.meta.path)); const tasks = new Array(500); for (let i = 0; i < 500; i++) { const current = actual + i; tasks[i] = promises.realpath(current).then( () => { throw new Error("should not get here"); }, e => { expect(e?.path).toBe(current); }, ); } await Promise.all(tasks); const { promise, resolve, reject } = Promise.withResolvers(); fs.realpath(actual, (err, path) => { err ? reject(err) : resolve(path); }); expect(await promise).toBe(realpathSync(import.meta.path)); }, 30_000); describe("stat", () => { it("file metadata is correct", () => { const fileStats = statSync(join(import.meta.dir, "fs-stream.js")); 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 path = join(import.meta.dir, "../../../../test"); const fileStats = statSync(path); 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(`${tmpdir()}/doesntexist`); throw "statSync should throw"; } catch (e: any) { expect(e.code).toBe("ENOENT"); } try { statSync(""); throw "statSync should throw"; } catch (e: any) { expect(e.code).toBe("ENOENT"); } }); }); describe("exist", () => { it("should return false with invalid path", () => { expect(existsSync("/pathNotExist")).toBe(false); }); it("should return false with empty string", () => { expect(existsSync("")).toBe(false); }); }); describe("fs.exists", () => { it("should throw TypeError with invalid argument", done => { let err = undefined; try { // @ts-ignore fs.exists(import.meta.path); } catch (e) { err = e; } try { expect(err).not.toBeUndefined(); expect(err).toBeInstanceOf(TypeError); // @ts-ignore expect(err.code).toStrictEqual("ERR_INVALID_ARG_TYPE"); done(); } catch (e) { done(e); } }); it("should return false with invalid path", done => { fs.exists(`${tmpdir()}/test-fs-exists-${Date.now()}`, exists => { try { expect(exists).toBe(false); done(); } catch (e) { done(e); } }); }); it("should return true with existed path", done => { fs.exists(import.meta.path, exists => { try { expect(exists).toBe(true); done(); } catch (e) { done(e); } }); }); it("should work with util.promisify when path exists", async () => { const fsexists = promisify(fs.exists); expect(await fsexists(import.meta.path)).toBe(true); }); it("should work with util.promisify when path doesn't exist", async () => { const fsexists = promisify(fs.exists); expect(await fsexists(`${tmpdir()}/test-fs-exists-${Date.now()}`)).toBe(false); }); }); describe("rm", () => { it("removes a file", () => { const path = `${tmpdir()}/${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 = `${tmpdir()}/${Date.now()}.rm.dir`; try { mkdirSync(path); } catch (e) {} expect(existsSync(path)).toBe(true); rmSync(path, { recursive: true }); expect(existsSync(path)).toBe(false); }); it("removes a dir recursively", () => { const path = `${tmpdir()}/${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("does not remove a file", done => { const path = `${tmpdir()}/${Date.now()}.rm.txt`; writeFileSync(path, "File written successfully", "utf8"); expect(existsSync(path)).toBe(true); rmdir(path, err => { try { expect(err).toBeDefined(); expect("ENOENT ENOTDIR EPERM").toContain(err!.code); expect(existsSync(path)).toBe(true); } catch (e) { return done(e); } finally { done(); } }); }); it("removes a dir", done => { const path = `${tmpdir()}/${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(); }); }); it("removes a dir x 512", async () => { var queue = new Array(512); var paths = new Array(512); for (let i = 0; i < 512; i++) { const path = `${tmpdir()}/${Date.now()}.rm.dir${i}`; try { mkdirSync(path); } catch (e) {} paths[i] = path; queue[i] = promises.rmdir(path); } await Promise.all(queue); for (let i = 0; i < 512; i++) { expect(existsSync(paths[i])).toBe(false); } }); it("does not remove a dir with a file in it", async () => { const path = `${tmpdir()}/${Date.now()}.rm.dir`; try { mkdirSync(path); writeFileSync(`${path}/file.txt`, "File written successfully", "utf8"); } catch (e) {} expect(existsSync(path + "/file.txt")).toBe(true); try { await promises.rmdir(path); } catch (err) { expect("ENOTEMPTY EPERM").toContain(err!.code); } expect(existsSync(path + "/file.txt")).toBe(true); await promises.rmdir(path, { recursive: true }); expect(existsSync(path + "/file.txt")).toBe(false); }); it("removes a dir recursively", done => { const path = `${tmpdir()}/${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("does not remove a file", () => { const path = `${tmpdir()}/${Date.now()}.rm.txt`; writeFileSync(path, "File written successfully", "utf8"); expect(existsSync(path)).toBe(true); expect(() => { rmdirSync(path); }).toThrow(); expect(existsSync(path)).toBe(true); }); it("removes a dir", () => { const path = `${tmpdir()}/${Date.now()}.rm.dir`; try { mkdirSync(path); } catch (e) {} expect(existsSync(path)).toBe(true); rmdirSync(path); expect(existsSync(path)).toBe(false); }); it("removes a dir recursively", () => { const path = `${tmpdir()}/${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 (highWaterMark 1)", 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.slice(i, i + 1)); i++; }); stream.on("end", () => { expect(i).toBe(data.length); resolve(true); }); }); }); it("works (highWaterMark 512)", async () => { var stream = createReadStream(import.meta.dir + "/readLargeFileSync.txt", { highWaterMark: 512, }); var data = readFileSync(import.meta.dir + "/readLargeFileSync.txt", "utf8"); var i = 0; return await new Promise(resolve => { stream.on("data", chunk => { expect(chunk instanceof Buffer).toBe(true); expect(chunk.length).toBeLessThanOrEqual(512); expect(chunk.toString()).toBe(data.slice(i, i + 512)); i += 512; }); stream.on("end", () => { resolve(true); }); }); }); it.skip("works (512 chunk)", async () => { var stream = createReadStream(import.meta.dir + "/readLargeFileSync.txt", { highWaterMark: 512, }); var data = readFileSync(import.meta.dir + "/readLargeFileSync.txt", "utf8"); var i = 0; return await new Promise(resolve => { stream.on("data", chunk => { expect(chunk instanceof Buffer).toBe(true); expect(chunk.length).toBe(512); expect(chunk.toString()).toBe(data.slice(i, i + 512)); i += 512; }); stream.on("end", () => { resolve(true); }); }); }); it.skip("works with larger highWaterMark (1024 chunk)", async () => { var stream = createReadStream(import.meta.dir + "/readLargeFileSync.txt", { highWaterMark: 1024, }); var data = readFileSync(import.meta.dir + "/readLargeFileSync.txt", "utf8"); var i = 0; return await new Promise(resolve => { stream.on("data", chunk => { expect(chunk instanceof Buffer).toBe(true); expect(chunk.length).toBe(1024); expect(chunk.toString()).toBe(data.slice(i, i + 1024)); i += 1024; }); stream.on("end", () => { resolve(true); }); }); }); it("works with very large file", async () => { const tempFile = tmpdir() + "/" + "large-file" + Date.now() + ".txt"; await Bun.write(Bun.file(tempFile), "big data big data big data".repeat(10000)); var stream = createReadStream(tempFile, { highWaterMark: 512, }); var data = readFileSync(tempFile, "utf8"); var i = 0; return await new Promise(resolve => { stream.on("data", chunk => { expect(chunk instanceof Buffer).toBe(true); expect(chunk.toString()).toBe(data.slice(i, i + chunk.length)); i += chunk.length; }); stream.on("end", () => { expect(i).toBe("big data big data big data".repeat(10000).length); rmSync(tempFile); resolve(true); }); }); }); it("should emit open", done => { const ws = createReadStream(join(import.meta.dir, "readFileSync.txt")); ws.on("open", data => { expect(data).toBeDefined(); done(); }); }); it("should call close callback", done => { const ws = createReadStream(join(import.meta.dir, "readFileSync.txt")); ws.close(err => { expect(err).toBeDefined(); expect(err?.message).toContain("Premature close"); done(); }); }); it( "correctly handles file descriptors with an offset", done => { const path = `${tmpdir()}/bun-fs-createReadStream-${Date.now()}.txt`; const fd = fs.openSync(path, "w+"); const stream = fs.createReadStream("", { fd: fd, start: 2 }); stream.on("data", chunk => { expect(chunk.toString()).toBe("llo, world!"); done(); }); stream.on("error", done); fs.writeSync(fd, "Hello, world!"); }, { timeout: 100 }, ); }); 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", () => { Bun.sleep(1000).then(() => { 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(); }); }); it("should use fd if provided", () => { const path = join(tmpdir(), `not-used-${Date.now()}.txt`); expect(existsSync(path)).toBe(false); const ws = new WriteStream_(path, { fd: 2 }); // @ts-ignore-next-line expect(ws.fd).toBe(2); expect(existsSync(path)).toBe(false); }); }); 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(); }); }); it("should use fd if provided", () => { const path = join(tmpdir(), `not-used-${Date.now()}.txt`); expect(existsSync(path)).toBe(false); // @ts-ignore-next-line const ws = new ReadStream_(path, { fd: 0, }); // @ts-ignore-next-line expect(ws.fd).toBe(0); expect(existsSync(path)).toBe(false); }); }); describe("createWriteStream", () => { it.todoIf(isBroken && isWindows)("simple write stream finishes", async () => { const streamPath = join(tmpdirSync(), "create-write-stream.txt"); const { promise: done, resolve, reject } = Promise.withResolvers(); const stream = createWriteStream(streamPath); stream.on("error", reject); stream.on("finish", resolve); stream.write("Test file written successfully"); stream.end(); await done; expect(readFileSync(streamPath, "utf8")).toBe("Test file written successfully"); }); it("writing null throws ERR_STREAM_NULL_VALUES", async () => { const streamPath = join(tmpdirSync(), "create-write-stream-nulls.txt"); const stream = createWriteStream(streamPath); expect.toThrowWithCode(() => stream.write(null), "ERR_STREAM_NULL_VALUES"); }); it("writing null throws ERR_STREAM_NULL_VALUES (objectMode: true)", async () => { const streamPath = join(tmpdirSync(), "create-write-stream-nulls-object-mode.txt"); const stream = createWriteStream(streamPath, { // @ts-ignore-next-line objectMode: true, }); expect.toThrowWithCode(() => stream.write(null), "ERR_STREAM_NULL_VALUES"); }); it("writing false throws ERR_INVALID_ARG_TYPE", async () => { const streamPath = join(tmpdirSync(), "create-write-stream-false.txt"); const stream = createWriteStream(streamPath); expect.toThrowWithCode(() => stream.write(false), "ERR_INVALID_ARG_TYPE"); }); it("writing false throws ERR_INVALID_ARG_TYPE (objectMode: true)", async () => { const streamPath = join(tmpdirSync(), "create-write-stream-false-object-mode.txt"); const stream = createWriteStream(streamPath, { // @ts-ignore-next-line objectMode: true, }); expect.toThrowWithCode(() => stream.write(false), "ERR_INVALID_ARG_TYPE"); }); it("writing in append mode should not truncate the file", async () => { const streamPath = join(tmpdirSync(), "create-write-stream-append.txt"); const stream = createWriteStream(streamPath, { // @ts-ignore-next-line flags: "a", }); const { promise: done1, resolve: resolve1, reject: reject1 } = Promise.withResolvers(); stream.on("error", reject1); stream.on("finish", resolve1); stream.write("first line\n"); stream.end(); await done1; const { promise: done2, resolve: resolve2, reject: reject2 } = Promise.withResolvers(); const stream2 = createWriteStream(streamPath, { flags: "a" }); stream2.on("error", reject2); stream2.on("finish", resolve2); stream2.write("second line\n"); stream2.end(); await done2; expect(readFileSync(streamPath, "utf8")).toBe("first line\nsecond line\n"); }); it("should emit open and call close callback", done => { const ws = createWriteStream(join(tmpdir(), "fs")); ws.on("open", data => { expect(data).toBeDefined(); done(); }); }); it("should call close callback", done => { const ws = createWriteStream(join(tmpdir(), "fs")); ws.close(err => { expect(err).toBeUndefined(); done(); }); }); it("should call callbacks in the correct order", done => { const ws = createWriteStream(join(tmpdir(), "fs")); let counter1 = 0; ws.on("open", () => { expect(counter1++).toBe(0); }); ws.close(() => { expect(counter1++).toBe(1); if (counter2 === 2) { done(); } }); let counter2 = 0; const rs = createReadStream(join(import.meta.dir, "readFileSync.txt")); rs.on("open", () => { expect(counter2++).toBe(0); }); rs.close(() => { expect(counter2++).toBe(1); if (counter1 === 2) { done(); } }); }); }); 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 = `${tmpdir()}/fs.test.ts/${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(path, {recursive: true}) produces the same result as Node.js", async () => { const full = resolve(import.meta.dir, "../"); const [bun, subprocess] = await Promise.all([ (async function () { const files = await promises.readdir(full, { recursive: true }); files.sort(); return files; })(), (async function () { const subprocess = Bun.spawn({ cmd: [ "node", "-e", `process.stdout.write(JSON.stringify(require("fs").readdirSync(${JSON.stringify( full, )}, { recursive: true }).sort()), null, 2)`, ], cwd: process.cwd(), stdout: "pipe", stderr: "inherit", stdin: "inherit", }); await subprocess.exited; return subprocess; })(), ]); expect(subprocess.exitCode).toBe(0); const text = await subprocess.stdout.text(); const node = JSON.parse(text); expect(bun).toEqual(node as string[]); }, 100000); it("readdir(path, {withFileTypes: true}) produces the same result as Node.js", async () => { const full = resolve(import.meta.dir, "../"); const [bun, subprocess] = await Promise.all([ (async function () { const files = await promises.readdir(full, { withFileTypes: true }); files.sort(); return files; })(), (async function () { const subprocess = Bun.spawn({ cmd: [ "node", "-e", `process.stdout.write(JSON.stringify(require("fs").readdirSync(${JSON.stringify( full, )}, { withFileTypes: true }).map(v => ({ path: v.parentPath ?? v.path, name: v.name })).sort()), null, 2)`, ], cwd: process.cwd(), stdout: "pipe", stderr: "inherit", stdin: "inherit", }); await subprocess.exited; return subprocess; })(), ]); expect(subprocess.exitCode).toBe(0); const text = await subprocess.stdout.text(); const node = JSON.parse(text); expect(bun.length).toEqual(node.length); expect([...new Set(node.map(v => v.parentPath ?? v.path))]).toEqual([full]); expect([...new Set(bun.map(v => v.parentPath ?? v.path))]).toEqual([full]); expect(bun.map(v => join(v.parentPath ?? v.path, v.name)).sort()).toEqual( node.map(v => join(v.path, v.name)).sort(), ); }, 100000); it("readdir(path, {withFileTypes: true, recursive: true}) produces the same result as Node.js", async () => { const full = resolve(import.meta.dir, "../"); const [bun, subprocess] = await Promise.all([ (async function () { const files = await promises.readdir(full, { withFileTypes: true, recursive: true }); files.sort((a, b) => a.path.localeCompare(b.path)); return files; })(), (async function () { const subprocess = Bun.spawn({ cmd: [ "node", "-e", `process.stdout.write(JSON.stringify(require("fs").readdirSync(${JSON.stringify( full, )}, { withFileTypes: true, recursive: true }).map(v => ({ path: v.parentPath ?? v.path, name: v.name })).sort((a, b) => a.path.localeCompare(b.path))), null, 2)`, ], cwd: process.cwd(), stdout: "pipe", stderr: "inherit", stdin: "inherit", }); await subprocess.exited; return subprocess; })(), ]); expect(subprocess.exitCode).toBe(0); const text = await subprocess.stdout.text(); const node = JSON.parse(text); expect(bun.length).toEqual(node.length); expect(new Set(bun.map(v => v.parentPath ?? v.path))).toEqual(new Set(node.map(v => v.path))); expect(bun.map(v => join(v.parentPath ?? v.path, v.name)).sort()).toEqual( node.map(v => join(v.path, v.name)).sort(), ); }, 100000); it("readdirSync(path, {withFileTypes: true, recursive: true}) produces the same result as Node.js", async () => { const full = resolve(import.meta.dir, "../"); const [bun, subprocess] = await Promise.all([ (async function () { const files = readdirSync(full, { withFileTypes: true, recursive: true }); files.sort((a, b) => a.path.localeCompare(b.path)); return files; })(), (async function () { const subprocess = Bun.spawn({ cmd: [ "node", "-e", `process.stdout.write(JSON.stringify(require("fs").readdirSync(${JSON.stringify( full, )}, { withFileTypes: true, recursive: true }).map(v => ({ path: v.parentPath ?? v.path, name: v.name })).sort((a, b) => a.path.localeCompare(b.path))), null, 2)`, ], cwd: process.cwd(), stdout: "pipe", stderr: "inherit", stdin: "inherit", }); await subprocess.exited; return subprocess; })(), ]); expect(subprocess.exitCode).toBe(0); const text = await subprocess.stdout.text(); const node = JSON.parse(text); expect(bun.length).toEqual(node.length); expect(new Set(bun.map(v => v.parentPath ?? v.path))).toEqual(new Set(node.map(v => v.path))); expect(bun.map(v => join(v.parentPath ?? v.path, v.name)).sort()).toEqual( node.map(v => join(v.path, v.name)).sort(), ); }, 100000); for (let withFileTypes of [false, true] as const) { const iterCount = 200; const full = resolve(import.meta.dir, "../"); const doIt = async () => { const maxFD = getMaxFD(); await Promise.all( Array.from({ length: iterCount }, () => promises.readdir(full, { withFileTypes, recursive: true })), ); const pending = new Array(iterCount); for (let i = 0; i < iterCount; i++) { pending[i] = promises.readdir(full, { recursive: true, withFileTypes }); } const results = await Promise.all(pending); // Sort the results for determinism. if (withFileTypes) { for (let i = 0; i < iterCount; i++) { results[i].sort((a, b) => a.path.localeCompare(b.path)); } } else { for (let i = 0; i < iterCount; i++) { results[i].sort(); } } expect(results[0].length).toBeGreaterThan(0); for (let i = 1; i < iterCount; i++) { expect(results[i]).toEqual(results[0]); } if (!withFileTypes) { expect(results[0]).toContain(relative(full, import.meta.path)); } else { expect(results[0][0].path).toEqual(full); } const newMaxFD = getMaxFD(); // assert we do not leak file descriptors // but we might start some threads or create kqueue // so we should allow *some* increase expect(newMaxFD - maxFD).toBeLessThan(5); }; const fail = async () => { const notfound = isWindows ? "C:\\notfound\\for\\sure" : "/notfound/for/sure"; const maxFD = getMaxFD(); const pending = new Array(iterCount); for (let i = 0; i < iterCount; i++) { pending[i] = promises.readdir(join(notfound, `${i}`), { recursive: true, withFileTypes }); } const results = await Promise.allSettled(pending); for (let i = 0; i < iterCount; i++) { expect(results[i].status).toBe("rejected"); expect(results[i].reason!.code).toBe("ENOENT"); expect(results[i].reason!.path).toBe(join(notfound, `${i}`)); } const newMaxFD = getMaxFD(); expect(maxFD).toBe(newMaxFD); // assert we do not leak file descriptors }; if (withFileTypes) { describe("withFileTypes", () => { it("readdir(path, {recursive: true} should work x 100", doIt, 10_000); it("readdir(path, {recursive: true} should fail x 100", fail, 10_000); }); } else { it("readdir(path, {recursive: true} should work x 100", doIt, 10_000); it("readdir(path, {recursive: true} should fail x 100", fail, 10_000); } } for (let withFileTypes of [false, true] as const) { const warmup = 1; const iterCount = 200; const full = resolve(import.meta.dir, "../"); const doIt = async () => { for (let i = 0; i < warmup; i++) { readdirSync(full, { withFileTypes }); } const maxFD = getMaxFD(); const results = new Array(iterCount); for (let i = 0; i < iterCount; i++) { results[i] = readdirSync(full, { recursive: true, withFileTypes }); } for (let i = 0; i < iterCount; i++) { results[i].sort(); } expect(results[0].length).toBeGreaterThan(0); for (let i = 1; i < iterCount; i++) { expect(results[i]).toEqual(results[0]); } if (!withFileTypes) { expect(results[0]).toContain(relative(full, import.meta.path)); } else { expect(results[0][0].path).toEqual(full); } const newMaxFD = getMaxFD(); expect(maxFD).toBe(newMaxFD); // assert we do not leak file descriptors }; if (withFileTypes) { it("readdirSync(path, {recursive: true, withFileTypes: true} should work x 100", doIt, 10_000); } else { it("readdirSync(path, {recursive: true} should work x 100", doIt, 10_000); } } 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 = `${tmpdir()}/${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("ENOTDIR EPERM ENOENT").toContain(err.code); expect(await exists(path)).toBe(true); } }); it("removes a dir", async () => { const path = `${tmpdir()}/${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); }); it("removes a dir recursively", async () => { const path = `${tmpdir()}/${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("opendir should have a path property, issue#4995", async () => { expect((await fs.promises.opendir(".")).path).toBe("."); const { promise, resolve } = Promise.withResolvers(); fs.opendir(".", (err, dir) => { resolve(dir); }); expect((await promise).path).toBe("."); }); }); it("fstatSync(decimal)", () => { expect(() => fstatSync(eval("1.0"))).not.toThrow(); expect(() => fstatSync(eval("0.0"))).not.toThrow(); expect(() => fstatSync(eval("2.0"))).not.toThrow(); expect(() => fstatSync(eval("-1.0"))).toThrow(); expect(() => fstatSync(eval("Infinity"))).toThrow(); expect(() => fstatSync(eval("-Infinity"))).toThrow(); expect(() => fstatSync(2147483647 + 1)).toThrow(expect.objectContaining({ code: "ERR_OUT_OF_RANGE" })); // > max int32 is not valid in most C APIs still. expect(() => fstatSync(2147483647)).toThrow(expect.objectContaining({ code: "EBADF" })); // max int32 is a valid fd }); it("fstat on a large file", () => { var dest: string = "", fd; try { dest = `${tmpdir()}/fs.test.ts/${Math.trunc(Math.random() * 10000000000).toString(32)}.stat.txt`; mkdirSync(dirname(dest), { recursive: true }); fd = openSync(dest, "w"); // Instead of writing the actual bytes, we can use ftruncate to make a // hole-y file and extend it to the desired size This should generally avoid // the ENOSPC issue and avoid timeouts. ftruncateSync(fd, 5 * 1024 * 1024 * 1024); fdatasyncSync(fd); const stats = fstatSync(fd); expect(stats.size).toEqual(5 * 1024 * 1024 * 1024); } catch (error) { // TODO: Once `fs.statfsSync` is implemented, make sure that the buffer size // is small enough not to cause: ENOSPC: No space left on device. if (error.code === "ENOSPC") { console.warn("Skipping test 'fstat on a large file' because not enough disk space"); return; } throw error; } finally { if (fd) closeSync(fd); unlinkSync(dest); } }, 30_000); it("fs.constants", () => { if (isWindows) { expect(constants).toEqual({ UV_FS_SYMLINK_DIR: 1, UV_FS_SYMLINK_JUNCTION: 2, O_RDONLY: 0, O_WRONLY: 1, O_RDWR: 2, UV_DIRENT_UNKNOWN: 0, UV_DIRENT_FILE: 1, UV_DIRENT_DIR: 2, UV_DIRENT_LINK: 3, UV_DIRENT_FIFO: 4, UV_DIRENT_SOCKET: 5, UV_DIRENT_CHAR: 6, UV_DIRENT_BLOCK: 7, S_IFMT: 61440, S_IFREG: 32768, S_IFDIR: 16384, S_IFCHR: 8192, S_IFIFO: 4096, S_IFLNK: 40960, O_CREAT: 256, O_EXCL: 1024, UV_FS_O_FILEMAP: 536870912, O_TRUNC: 512, O_APPEND: 8, S_IRUSR: 256, S_IWUSR: 128, F_OK: 0, R_OK: 4, W_OK: 2, X_OK: 1, UV_FS_COPYFILE_EXCL: 1, COPYFILE_EXCL: 1, UV_FS_COPYFILE_FICLONE: 2, COPYFILE_FICLONE: 2, UV_FS_COPYFILE_FICLONE_FORCE: 4, COPYFILE_FICLONE_FORCE: 4, EXTENSIONLESS_FORMAT_JAVASCRIPT: 0, EXTENSIONLESS_FORMAT_WASM: 1, } as any); return; } 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(); if (process.platform === "darwin") 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.promises.constants", () => { expect(promises.constants).toBeDefined(); expect(promises.constants).toBe(fs.constants); }); 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 = `${tmpdir()}/repro_1516.txt`; writeFileSync(path, "b", { flag: undefined }); // @ts-ignore-next-line expect(readFileSync(path, { encoding: "utf8", flag: null })).toBe("b"); rmSync(path); }); it("existsSync with invalid path doesn't throw", () => { expect(existsSync(null as any)).toBe(false); expect(existsSync(123 as any)).toBe(false); expect(existsSync(undefined as any)).toBe(false); expect(existsSync({ invalid: 1 } as any)).toBe(false); }); describe("utimesSync", () => { it("works", () => { const tmp = join(tmpdir(), "utimesSync-test-file-" + Math.random().toString(36).slice(2)); writeFileSync(tmp, "test"); const prevStats = fs.statSync(tmp); const prevModifiedTime = prevStats.mtime; const prevAccessTime = prevStats.atime; prevModifiedTime.setMilliseconds(0); prevAccessTime.setMilliseconds(0); prevModifiedTime.setFullYear(1996); prevAccessTime.setFullYear(1996); // Get the current time to change the timestamps const newModifiedTime = new Date(); const newAccessTime = new Date(); newModifiedTime.setMilliseconds(0); newAccessTime.setMilliseconds(0); fs.utimesSync(tmp, newAccessTime, newModifiedTime); const newStats = fs.statSync(tmp); expect(newStats.mtime).toEqual(newModifiedTime); expect(newStats.atime).toEqual(newAccessTime); fs.utimesSync(tmp, prevAccessTime, prevModifiedTime); const finalStats = fs.statSync(tmp); expect(finalStats.mtime).toEqual(prevModifiedTime); expect(finalStats.atime).toEqual(prevAccessTime); }); it("accepts a Number(value).toString()", () => { const tmp = join(tmpdir(), "utimesSync-test-file2-" + Math.random().toString(36).slice(2)); writeFileSync(tmp, "test"); const prevStats = fs.statSync(tmp); const prevModifiedTime = prevStats.mtime; const prevAccessTime = prevStats.atime; prevModifiedTime.setMilliseconds(0); prevAccessTime.setMilliseconds(0); prevModifiedTime.setFullYear(1996); prevAccessTime.setFullYear(1996); // Get the current time to change the timestamps const newModifiedTime = new Date(); const newAccessTime = new Date(); newModifiedTime.setMilliseconds(0); newAccessTime.setMilliseconds(0); fs.utimesSync(tmp, newAccessTime.getTime() / 1000 + "", newModifiedTime.getTime() / 1000 + ""); const newStats = fs.statSync(tmp); expect(newStats.mtime).toEqual(newModifiedTime); expect(newStats.atime).toEqual(newAccessTime); fs.utimesSync(tmp, prevAccessTime.getTime() / 1000 + "", prevModifiedTime.getTime() / 1000 + ""); const finalStats = fs.statSync(tmp); expect(finalStats.mtime).toEqual(prevModifiedTime); expect(finalStats.atime).toEqual(prevAccessTime); }); // TODO: make this work on Windows it.skipIf(isWindows)("works after 2038", () => { const tmp = join(tmpdir(), "utimesSync-test-file-" + Math.random().toString(36).slice(2)); writeFileSync(tmp, "test"); const prevStats = fs.statSync(tmp); const prevModifiedTime = prevStats.mtime; const prevAccessTime = prevStats.atime; prevModifiedTime.setMilliseconds(0); prevAccessTime.setMilliseconds(0); prevModifiedTime.setFullYear(1996); prevAccessTime.setFullYear(1996); // Get the current time to change the timestamps const newModifiedTime = new Date("2045-04-30 19:32:12.333"); const newAccessTime = new Date("2098-01-01 00:00:00"); fs.utimesSync(tmp, newAccessTime, newModifiedTime); const newStats = fs.statSync(tmp); expect(newStats.mtime).toEqual(newModifiedTime); expect(newStats.atime).toEqual(newAccessTime); fs.utimesSync(tmp, prevAccessTime, prevModifiedTime); const finalStats = fs.statSync(tmp); expect(finalStats.mtime).toEqual(prevModifiedTime); expect(finalStats.atime).toEqual(prevAccessTime); }); it("works with whole numbers", () => { const atime = Math.floor(Date.now() / 1000); const mtime = Math.floor(Date.now() / 1000); const tmp = join(tmpdir(), "utimesSync-test-file-" + Math.random().toString(36).slice(2)); writeFileSync(tmp, "test"); fs.utimesSync(tmp, atime, mtime); const newStats = fs.statSync(tmp); expect(newStats.mtime.getTime() / 1000).toEqual(mtime); expect(newStats.atime.getTime() / 1000).toEqual(atime); }); }); it("createReadStream on a large file emits readable event correctly", () => { return new Promise((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); } }); }); }); describe("fs.write", () => { it("should work with (fd, buffer, offset, length, position, callback)", done => { const path = `${tmpdir()}/bun-fs-write-1-${Date.now()}.txt`; const fd = fs.openSync(path, "w"); const buffer = Buffer.from("bun"); fs.write(fd, buffer, 0, buffer.length, 0, err => { try { expect(err).toBeNull(); expect(readFileSync(path, "utf8")).toStrictEqual("bun"); } catch (e) { return done(e); } finally { unlinkSync(path); closeSync(fd); } done(); }); }); it("should work with (fd, buffer, offset, length, callback)", done => { const path = `${tmpdir()}/bun-fs-write-2-${Date.now()}.txt`; const fd = fs.openSync(path, "w"); const buffer = Buffer.from("bun"); fs.write(fd, buffer, 0, buffer.length, (err, written, buffer) => { try { expect(err).toBeNull(); expect(written).toBe(3); expect(buffer.slice(0, written).toString()).toStrictEqual("bun"); expect(Buffer.isBuffer(buffer)).toBe(true); expect(readFileSync(path, "utf8")).toStrictEqual("bun"); } catch (e) { return done(e); } finally { unlinkSync(path); closeSync(fd); } done(); }); }); it("should work with (fd, string, position, encoding, callback)", done => { const path = `${tmpdir()}/bun-fs-write-3-${Date.now()}.txt`; const fd = fs.openSync(path, "w"); const string = "bun"; fs.write(fd, string, 0, "utf8", (err, written, string) => { try { expect(err).toBeNull(); expect(written).toBe(3); expect(string.slice(0, written).toString()).toStrictEqual("bun"); expect(string).toBeTypeOf("string"); expect(readFileSync(path, "utf8")).toStrictEqual("bun"); } catch (e) { return done(e); } finally { unlinkSync(path); closeSync(fd); } done(); }); }); it("should work with (fd, string, position, callback)", done => { const path = `${tmpdir()}/bun-fs-write-4-${Date.now()}.txt`; const fd = fs.openSync(path, "w"); const string = "bun"; fs.write(fd, string, 0, (err, written, string) => { try { expect(err).toBeNull(); expect(written).toBe(3); expect(string.slice(0, written).toString()).toStrictEqual("bun"); expect(string).toBeTypeOf("string"); expect(readFileSync(path, "utf8")).toStrictEqual("bun"); } catch (e) { return done(e); } finally { unlinkSync(path); closeSync(fd); } done(); }); }); it("should work with util.promisify", async () => { const path = `${tmpdir()}/bun-fs-write-5-${Date.now()}.txt`; const fd = fs.openSync(path, "w"); const string = "bun"; const fswrite = promisify(fs.write); const ret = await fswrite(fd, string, 0); expect(typeof ret === "object").toBeTrue(); expect(ret.bytesWritten === 3).toBeTrue(); expect(ret.buffer === string).toBeTrue(); expect(readFileSync(path, "utf8")).toStrictEqual("bun"); fs.closeSync(fd); }); }); describe("fs.read", () => { it("should work with (fd, callback)", done => { const path = `${tmpdir()}/bun-fs-read-1-${Date.now()}.txt`; fs.writeFileSync(path, "bun"); const fd = fs.openSync(path, "r"); fs.read(fd, (err, bytesRead, buffer) => { try { expect(err).toBeNull(); expect(bytesRead).toBe(3); expect(buffer).toStrictEqual(Buffer.concat([Buffer.from("bun"), Buffer.alloc(16381)])); } catch (e) { return done(e); } finally { unlinkSync(path); closeSync(fd); } done(); }); }); it("should work with (fd, options, callback)", done => { const path = `${tmpdir()}/bun-fs-read-2-${Date.now()}.txt`; fs.writeFileSync(path, "bun"); const fd = fs.openSync(path, "r"); const buffer = Buffer.alloc(16); fs.read(fd, { buffer: buffer }, (err, bytesRead, buffer) => { try { expect(err).toBeNull(); expect(bytesRead).toBe(3); expect(buffer.slice(0, bytesRead).toString()).toStrictEqual("bun"); } catch (e) { return done(e); } finally { unlinkSync(path); closeSync(fd); } done(); }); }); it("should work with (fd, buffer, offset, length, position, callback)", done => { const path = `${tmpdir()}/bun-fs-read-3-${Date.now()}.txt`; fs.writeFileSync(path, "bun"); const fd = fs.openSync(path, "r"); const buffer = Buffer.alloc(16); fs.read(fd, buffer, 0, buffer.length, 0, (err, bytesRead, buffer) => { try { expect(err).toBeNull(); expect(bytesRead).toBe(3); expect(buffer.slice(0, bytesRead).toString()).toStrictEqual("bun"); } catch (e) { return done(e); } finally { unlinkSync(path); closeSync(fd); } done(); }); }); it("should work with offset", done => { const path = `${tmpdir()}/bun-fs-read-4-${Date.now()}.txt`; fs.writeFileSync(path, "bun"); const fd = fs.openSync(path, "r"); const buffer = Buffer.alloc(16); fs.read(fd, buffer, 1, buffer.length - 1, 0, (err, bytesRead, buffer) => { try { expect(err).toBeNull(); expect(bytesRead).toBe(3); expect(buffer.slice(1, bytesRead + 1).toString()).toStrictEqual("bun"); } catch (e) { return done(e); } finally { unlinkSync(path); closeSync(fd); } done(); }); }); it("should work with position", done => { const path = `${tmpdir()}/bun-fs-read-5-${Date.now()}.txt`; fs.writeFileSync(path, "bun"); const fd = fs.openSync(path, "r"); const buffer = Buffer.alloc(16); fs.read(fd, buffer, 0, buffer.length, 1, (err, bytesRead, buffer) => { try { expect(err).toBeNull(); expect(bytesRead).toBe(2); expect(buffer.slice(0, bytesRead).toString()).toStrictEqual("un"); } catch (e) { return done(e); } finally { unlinkSync(path); closeSync(fd); } done(); }); }); it("should work with both position and offset", done => { const path = `${tmpdir()}/bun-fs-read-6-${Date.now()}.txt`; fs.writeFileSync(path, "bun"); const fd = fs.openSync(path, "r"); const buffer = Buffer.alloc(16); fs.read(fd, buffer, 1, buffer.length - 1, 1, (err, bytesRead, buffer) => { try { expect(err).toBeNull(); expect(bytesRead).toBe(2); expect(buffer.slice(1, bytesRead + 1).toString()).toStrictEqual("un"); } catch (e) { return done(e); } finally { unlinkSync(path); closeSync(fd); } done(); }); }); it("should work with util.promisify", async () => { const path = `${tmpdir()}/bun-fs-read-6-${Date.now()}.txt`; fs.writeFileSync(path, "bun bun bun bun"); const fd = fs.openSync(path, "r"); const buffer = Buffer.alloc(15); const fsread = promisify(fs.read) as any; const ret = await fsread(fd, buffer, 0, 15, 0); expect(typeof ret === "object").toBeTrue(); expect(ret.bytesRead === 15).toBeTrue(); expect(buffer.slice().toString() === "bun bun bun bun").toBeTrue(); fs.closeSync(fd); }); }); it("new Stats", () => { // @ts-expect-error const stats = new Stats(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14); expect(stats).toBeDefined(); // dev, mode, nlink, uid, gid, rdev, blksize, ino, size, blocks, atimeMs, mtimeMs, ctimeMs, birthtimeMs expect(stats.dev).toBe(1); expect(stats.mode).toBe(2); expect(stats.nlink).toBe(3); expect(stats.uid).toBe(4); expect(stats.gid).toBe(5); expect(stats.rdev).toBe(6); expect(stats.blksize).toBe(7); expect(stats.ino).toBe(8); expect(stats.size).toBe(9); expect(stats.blocks).toBe(10); expect(stats.atimeMs).toBe(11); expect(stats.mtimeMs).toBe(12); expect(stats.ctimeMs).toBe(13); expect(stats.birthtimeMs).toBe(14); expect(stats.atime).toEqual(new Date(11)); expect(stats.mtime).toEqual(new Date(12)); expect(stats.ctime).toEqual(new Date(13)); expect(stats.birthtime).toEqual(new Date(14)); }); /// TODO: why is `.ino` wrong on x86_64 MacOS? (isIntelMacOS ? it.todo : it)("BigIntStats", () => { const withoutBigInt = statSync(import.meta.path, { bigint: false }); const withBigInt = statSync(import.meta.path, { bigint: true }); expect(withoutBigInt.isFile() === withBigInt.isFile()).toBe(true); expect(withoutBigInt.isDirectory() === withBigInt.isDirectory()).toBe(true); expect(withoutBigInt.isBlockDevice() === withBigInt.isBlockDevice()).toBe(true); expect(withoutBigInt.isCharacterDevice() === withBigInt.isCharacterDevice()).toBe(true); expect(withoutBigInt.isSymbolicLink() === withBigInt.isSymbolicLink()).toBe(true); expect(withoutBigInt.isFIFO() === withBigInt.isFIFO()).toBe(true); expect(withoutBigInt.isSocket() === withBigInt.isSocket()).toBe(true); const expectclose = (a: bigint, b: bigint) => expect(Math.abs(Number(a - b))).toBeLessThan(1000); expectclose(BigInt(withoutBigInt.dev), withBigInt.dev); expectclose(BigInt(withoutBigInt.ino), withBigInt.ino); expectclose(BigInt(withoutBigInt.mode), withBigInt.mode); expectclose(BigInt(withoutBigInt.nlink), withBigInt.nlink); expectclose(BigInt(withoutBigInt.uid), withBigInt.uid); expectclose(BigInt(withoutBigInt.gid), withBigInt.gid); expectclose(BigInt(withoutBigInt.rdev), withBigInt.rdev); expectclose(BigInt(withoutBigInt.size), withBigInt.size); expectclose(BigInt(withoutBigInt.blksize), withBigInt.blksize); expectclose(BigInt(withoutBigInt.blocks), withBigInt.blocks); expectclose(BigInt(Math.floor(withoutBigInt.atimeMs)), withBigInt.atimeMs); expectclose(BigInt(Math.floor(withoutBigInt.mtimeMs)), withBigInt.mtimeMs); expectclose(BigInt(Math.floor(withoutBigInt.ctimeMs)), withBigInt.ctimeMs); expectclose(BigInt(Math.floor(withoutBigInt.birthtimeMs)), withBigInt.birthtimeMs); expect(withBigInt.atime.getTime()).toEqual(withoutBigInt.atime.getTime()); expect(withBigInt.mtime.getTime()).toEqual(withoutBigInt.mtime.getTime()); expect(withBigInt.ctime.getTime()).toEqual(withoutBigInt.ctime.getTime()); expect(withBigInt.birthtime.getTime()).toEqual(withoutBigInt.birthtime.getTime()); }); it("test syscall errno, issue#4198", () => { const path = `${tmpdir()}/non-existent-${Date.now()}.txt`; expect(() => openSync(path, "r")).toThrow("no such file or directory"); expect(() => readSync(2147483640, Buffer.alloc(1))).toThrow("bad file descriptor"); expect(() => readlinkSync(path)).toThrow("no such file or directory"); expect(() => realpathSync(path)).toThrow("no such file or directory"); expect(() => readFileSync(path)).toThrow("no such file or directory"); expect(() => renameSync(path, `${path}.2`)).toThrow("no such file or directory"); expect(() => statSync(path)).toThrow("no such file or directory"); expect(() => unlinkSync(path)).toThrow("no such file or directory"); expect(() => rmSync(path)).toThrow("no such file or directory"); expect(() => rmdirSync(path)).toThrow("no such file or directory"); expect(() => closeSync(2147483640)).toThrow("bad file descriptor"); mkdirSync(path); expect(() => mkdirSync(path)).toThrow("file already exists"); expect(() => unlinkSync(path)).toThrow( ( { "darwin": "operation not permitted", "linux": "illegal operation on a directory", "win32": "operation not permitted", } as any )[process.platform], ); rmdirSync(path); }); it.if(isWindows)("writing to windows hidden file is possible", () => { const temp = tmpdir(); writeFileSync(join(temp, "file.txt"), "FAIL"); const status = Bun.spawnSync(["cmd", "/C", "attrib +h file.txt"], { stdio: ["ignore", "ignore", "ignore"], cwd: temp, }); expect(status.exitCode).toBe(0); writeFileSync(join(temp, "file.txt"), "Hello World"); const content = readFileSync(join(temp, "file.txt"), "utf8"); expect(content).toBe("Hello World"); }); it("fs.ReadStream allows functions", () => { // @ts-expect-error expect(() => new fs.ReadStream(".", function lol() {})).not.toThrow(); // @ts-expect-error expect(() => new fs.ReadStream(".", {})).not.toThrow(); }); describe.if(isWindows)("windows path handling", () => { // dont call `it` because these paths wont make sense // the `it` in this branch makes something be printed on posix' if (!isWindows) return it("works", () => {}); const file = import.meta.path.slice(3); const drive = import.meta.path[0]; const filenames = [ `${drive}:\\${file}`, `\\\\127.0.0.1\\${drive}$\\${file}`, `\\\\LOCALHOST\\${drive}$\\${file}`, `\\\\.\\${drive}:\\${file}`, `\\\\?\\${drive}:\\${file}`, `\\\\.\\UNC\\LOCALHOST\\${drive}$\\${file}`, `\\\\?\\UNC\\LOCALHOST\\${drive}$\\${file}`, `\\\\127.0.0.1\\${drive}$\\${file}`, ]; for (const filename of filenames) { it(`Can read '${filename}' with node:fs`, async () => { const stats = await fs.promises.stat(filename); expect(stats.size).toBeGreaterThan(0); }); it(`Can read '${filename}' with Bun.file`, async () => { const stats = await Bun.file(filename).text(); expect(stats.length).toBeGreaterThan(0); }); } }); it("using writeFile on an fd does not truncate it", () => { const filepath = join(tmpdir(), `file-${Math.random().toString(32).slice(2)}.txt`); const fd = fs.openSync(filepath, "w+"); fs.writeFileSync(fd, "x"); fs.writeFileSync(fd, "x"); fs.closeSync(fd); const content = fs.readFileSync(filepath, "utf8"); expect(content).toBe("xx"); }); it("fs.close with one arg works", () => { const filepath = join(tmpdir(), `file-${Math.random().toString(32).slice(2)}.txt`); const fd = fs.openSync(filepath, "w+"); fs.close(fd); }); it("existsSync should never throw ENAMETOOLONG", () => { expect(existsSync(new Array(16).fill(new Array(64).fill("a")).join("/"))).toBeFalse(); }); it("promises exists should never throw ENAMETOOLONG", async () => { expect(await _promises.exists(new Array(16).fill(new Array(64).fill("a")).join("/"))).toBeFalse(); }); it("promises.fdatasync with a bad fd should include that in the error thrown", async () => { try { await _promises.fdatasync(50000); } catch (e) { expect(typeof e.fd).toBe("number"); expect(e.fd).toBe(50000); return; } expect.unreachable(); }); it("promises.cp should work even if dest does not exist", async () => { const x_dir = tmpdirSync(); const text_expected = "A".repeat(131073); let src = "package-lock.json"; let folder = "folder-not-exist"; let dst = join(folder, src); src = join(x_dir, src); folder = join(x_dir, folder); dst = join(x_dir, dst); await promises.writeFile(src, text_expected); await promises.rm(folder, { recursive: true, force: true }); await promises.cp(src, dst); const text_actual = await Bun.file(dst).text(); expect(text_actual).toBe(text_expected); }); it("promises.writeFile should accept a FileHandle", async () => { const x_dir = tmpdirSync(); const x_path = join(x_dir, "dummy.txt"); await using file = await fs.promises.open(x_path, "w"); await fs.promises.writeFile(file, "data"); expect(await Bun.file(x_path).text()).toBe("data"); }); it("promises.readFile should accept a FileHandle", async () => { const x_dir = tmpdirSync(); const x_path = join(x_dir, "dummy.txt"); await Bun.write(Bun.file(x_path), "data"); await using file = await fs.promises.open(x_path, "r"); expect((await fs.promises.readFile(file)).toString()).toBe("data"); }); it("promises.appendFile should accept a FileHandle", async () => { const x_dir = tmpdirSync(); const x_path = join(x_dir, "dummy.txt"); await using file = await fs.promises.open(x_path, "w"); await fs.promises.appendFile(file, "data"); expect(await Bun.file(x_path).text()).toBe("data"); await fs.promises.appendFile(file, "data"); expect(await Bun.file(x_path).text()).toBe("datadata"); }); it("chown should verify its arguments", () => { expect(() => fs.chown("doesnt-matter.txt", "a", 0)).toThrowWithCode(TypeError, "ERR_INVALID_ARG_TYPE"); expect(() => fs.chown("doesnt-matter.txt", 0, "a")).toThrowWithCode(TypeError, "ERR_INVALID_ARG_TYPE"); }); it("open flags verification", async () => { const invalid = 4_294_967_296; expect(() => fs.open(__filename, invalid, () => {})).toThrowWithCode(RangeError, "ERR_OUT_OF_RANGE"); expect(() => fs.openSync(__filename, invalid)).toThrowWithCode(RangeError, "ERR_OUT_OF_RANGE"); expect(async () => await fs.promises.open(__filename, invalid)).toThrow(RangeError); expect(() => fs.open(__filename, 4294967298.5, () => {})).toThrow( RangeError(`The value of "flags" is out of range. It must be an integer. Received 4294967298.5`), ); }); it("open mode verification", async () => { const invalid = 4_294_967_296; expect(() => fs.open(__filename, 0, invalid, () => {})).toThrowWithCode(RangeError, "ERR_OUT_OF_RANGE"); expect(() => fs.openSync(__filename, 0, invalid)).toThrowWithCode(RangeError, "ERR_OUT_OF_RANGE"); expect(async () => await fs.promises.open(__filename, 0, invalid)).toThrow(RangeError); expect(() => fs.open(__filename, 0, 4294967298.5, () => {})).toThrow( RangeError(`The value of "mode" is out of range. It must be an integer. Received 4294967298.5`), ); }); it("fs.mkdirSync recursive should not error when the directory already exists, but should error when its a file", () => { expect(() => mkdirSync(import.meta.dir, { recursive: true })).not.toThrowError(); expect(() => mkdirSync(import.meta.path, { recursive: true })).toThrowError(); }); it("fs.mkdirSync recursive: false should error when the directory already exists, regardless if its a file or dir", () => { expect(() => mkdirSync(import.meta.dir, { recursive: false })).toThrowError(); expect(() => mkdirSync(import.meta.path, { recursive: false })).toThrowError(); }); it("fs.statfsSync should work", () => { const stats = statfsSync(import.meta.path); ["type", "bsize", "blocks", "bfree", "bavail", "files", "ffree"].forEach(k => { expect(stats).toHaveProperty(k); expect(stats[k]).toBeNumber(); }); const bigIntStats = statfsSync(import.meta.path, { bigint: true }); ["type", "bsize", "blocks", "bfree", "bavail", "files", "ffree"].forEach(k => { expect(bigIntStats).toHaveProperty(k); expect(bigIntStats[k]).toBeTypeOf("bigint"); }); }); it("fs.promises.statfs should work", async () => { const stats = await fs.promises.statfs(import.meta.path); expect(stats).toBeDefined(); }); it("fs.promises.statfs should work with bigint", async () => { const stats = await fs.promises.statfs(import.meta.path, { bigint: true }); expect(stats).toBeDefined(); }); it("fs.statfs should work with bigint", async () => { const { promise, resolve } = Promise.withResolvers(); fs.statfs(import.meta.path, { bigint: true }, (err, stats) => { if (err) return resolve(err); resolve(stats); }); const stats = await promise; expect(stats).toBeDefined(); for (const k of ["type", "bsize", "blocks", "bfree", "bavail", "files", "ffree"]) { expect(stats).toHaveProperty(k); expect(stats[k]).toBeTypeOf("bigint"); } }); it("fs.statfs should work with bigint", async () => { const { promise, resolve } = Promise.withResolvers(); fs.statfs(import.meta.path, { bigint: true }, (err, stats) => { if (err) return resolve(err); resolve(stats); }); const stats = await promise; expect(stats).toBeDefined(); for (const k of ["type", "bsize", "blocks", "bfree", "bavail", "files", "ffree"]) { expect(stats).toHaveProperty(k); expect(stats[k]).toBeTypeOf("bigint"); } }); it("fs.Stat constructor", () => { expect(new Stats()).toMatchObject({ "atimeMs": undefined, "birthtimeMs": undefined, "blksize": undefined, "blocks": undefined, "ctimeMs": undefined, "dev": undefined, "gid": undefined, "ino": undefined, "mode": undefined, "mtimeMs": undefined, "nlink": undefined, "rdev": undefined, "size": undefined, "uid": undefined, }); }); it("fs.Stat constructor with options", () => { // @ts-ignore expect(new Stats(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)).toMatchObject({ atimeMs: 10, birthtimeMs: 13, blksize: 6, blocks: 9, ctimeMs: 12, dev: 0, gid: 4, ino: 7, mode: 1, mtimeMs: 11, nlink: 2, rdev: 5, size: 8, uid: 3, }); }); it("fs.Stat.atime reflects date matching Node.js behavior", () => { { const date = new Date(); const stats = new Stats(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); stats.atime = date; expect(stats.atime).toBe(date); } { const stats = new Stats(); expect(stats.atime.getTime()).toEqual(new Date(undefined).getTime()); } { const stats = new Stats(); const now = Date.now(); stats.atimeMs = now; expect(stats.atime).toEqual(new Date(now)); } { const stats = new Stats(); stats.atimeMs = 0; expect(stats.atime).toEqual(new Date(0)); const now = Date.now(); stats.atimeMs = now; expect(stats.atime).toEqual(new Date(0)); } }); describe('kernel32 long path conversion does not mangle "../../path" into "path"', () => { const tmp1 = tempDirWithFiles("longpath", { "a/b/config": "true", }); const tmp2 = tempDirWithFiles("longpath", { "a/b/hello": "true", "config": "true", }); const workingDir1 = path.join(tmp1, "a/b"); const workingDir2 = path.join(tmp2, "a/b"); const nonExistTests = [ ["existsSync", 'assert.strictEqual(fs.existsSync("../../config"), false)'], ["accessSync", 'assert.throws(() => fs.accessSync("../../config"), { code: "ENOENT" })'], ]; const existTests = [ ["existsSync", 'assert.strictEqual(fs.existsSync("../../config"), true)'], ["accessSync", 'assert.strictEqual(fs.accessSync("../../config"), null)'], ]; for (const [name, code] of nonExistTests) { it(`${name} (not existing)`, () => { const { success } = spawnSync({ cmd: [bunExe(), "-e", code], cwd: workingDir1, stdio: ["ignore", "inherit", "inherit"], env: bunEnv, }); expect(success).toBeTrue(); }); } for (const [name, code] of existTests) { it(`${name} (existing)`, () => { const { success } = spawnSync({ cmd: [bunExe(), "-e", code], cwd: workingDir2, stdio: ["ignore", "inherit", "inherit"], env: bunEnv, }); expect(success).toBeTrue(); }); } }); it("overflowing mode doesn't crash", () => { // this is easiest to test on windows since mode_t is a u16 there expect(() => openSync("./a.txt", 65 * 1024)).toThrow( expect.objectContaining({ name: "Error", message: `ENOENT: no such file or directory, open './a.txt'`, code: "ENOENT", syscall: "open", // errno: -4058, path: "./a.txt", }), ); });