mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
### What does this PR do? ### How did you verify your code works? --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
3683 lines
114 KiB
TypeScript
3683 lines
114 KiB
TypeScript
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 <G <H <I <J <K",
|
|
},
|
|
{
|
|
"encoding": "latin1",
|
|
"text": "👍",
|
|
"correct": {
|
|
"type": "Buffer",
|
|
"data": [61, 77],
|
|
},
|
|
"out": "=M",
|
|
},
|
|
{
|
|
"encoding": "binary",
|
|
"text": "ascii",
|
|
"correct": {
|
|
"type": "Buffer",
|
|
"data": [97, 115, 99, 105, 105],
|
|
},
|
|
"out": "ascii",
|
|
},
|
|
{
|
|
"encoding": "binary",
|
|
"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 <G <H <I <J <K",
|
|
},
|
|
{
|
|
"encoding": "binary",
|
|
"text": "👍",
|
|
"correct": {
|
|
"type": "Buffer",
|
|
"data": [61, 77],
|
|
},
|
|
"out": "=M",
|
|
},
|
|
{
|
|
"encoding": "base64",
|
|
"text": "ascii",
|
|
"correct": {
|
|
"type": "Buffer",
|
|
"data": [106, 199, 34],
|
|
},
|
|
"out": "asci",
|
|
},
|
|
{
|
|
"encoding": "hex",
|
|
"text": "ascii",
|
|
"correct": {
|
|
"type": "Buffer",
|
|
"data": [],
|
|
},
|
|
"out": "",
|
|
},
|
|
{
|
|
"encoding": "hex",
|
|
"text": "utf16 🍇 🍈 🍉 🍊 🍋",
|
|
"correct": {
|
|
"type": "Buffer",
|
|
"data": [],
|
|
},
|
|
"out": "",
|
|
},
|
|
{
|
|
"encoding": "hex",
|
|
"text": "👍",
|
|
"correct": {
|
|
"type": "Buffer",
|
|
"data": [],
|
|
},
|
|
"out": "",
|
|
},
|
|
];
|
|
|
|
it("& fs.promises.writefile encodes & decodes", async () => {
|
|
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<Dir>();
|
|
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<void>((resolve, reject) => {
|
|
const tmp = mkdtempSync(`${tmpdir()}/readable`);
|
|
// write a 10mb file
|
|
writeFileSync(`${tmp}/large.txt`, "a".repeat(10 * 1024 * 1024));
|
|
var stream = createReadStream(`${tmp}/large.txt`);
|
|
var ended = false;
|
|
var timer: Timer;
|
|
stream.on("readable", () => {
|
|
const v = stream.read();
|
|
if (ended) {
|
|
clearTimeout(timer);
|
|
reject(new Error("readable emitted after end"));
|
|
} else if (v == null) {
|
|
ended = true;
|
|
timer = setTimeout(() => {
|
|
resolve();
|
|
}, 20);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
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",
|
|
}),
|
|
);
|
|
});
|