From b469e5035161286abeb1a7726518d1afcc163a51 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Thu, 2 Mar 2023 17:36:47 -0800 Subject: Add fs/promises tests and migrate fs.test.js to TypeScript (#2279) --- packages/bun-types/bun.d.ts | 36 +- packages/bun-types/fs.d.ts | 7 +- packages/bun-types/fs/promises.d.ts | 37 +- packages/bun-types/tests/fs.test-d.ts | 6 + test/bun.js/event-emitter.test.ts | 2 +- test/bun.js/fs.test.js | 997 -------------------------------- test/bun.js/fs.test.ts | 1016 +++++++++++++++++++++++++++++++++ test/bun.js/gc.js | 26 - test/bun.js/gc.ts | 26 + 9 files changed, 1107 insertions(+), 1046 deletions(-) create mode 100644 packages/bun-types/tests/fs.test-d.ts delete mode 100644 test/bun.js/fs.test.js create mode 100644 test/bun.js/fs.test.ts delete mode 100644 test/bun.js/gc.js create mode 100644 test/bun.js/gc.ts diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 8f020dcfc..fc3bcbdb1 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -395,7 +395,9 @@ declare module "bun" { stream?: boolean; }): void; - write(chunk: string | ArrayBufferView | ArrayBuffer): number; + write( + chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, + ): number; /** * Flush the internal buffer * @@ -534,7 +536,9 @@ declare module "bun" { * * If the file descriptor is not writable yet, the data is buffered. */ - write(chunk: string | ArrayBufferView | ArrayBuffer): number; + write( + chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, + ): number; /** * Flush the internal buffer, committing the data to disk or the pipe. */ @@ -650,38 +654,38 @@ declare module "bun" { * @param seed The seed to use. */ export const hash: (( - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number, ) => number | bigint) & Hash; interface Hash { wyhash: ( - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number, ) => number | bigint; crc32: ( - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number, ) => number | bigint; adler32: ( - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number, ) => number | bigint; cityHash32: ( - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number, ) => number | bigint; cityHash64: ( - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number, ) => number | bigint; murmur32v3: ( - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number, ) => number | bigint; murmur64v2: ( - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number, ) => number | bigint; } @@ -989,7 +993,7 @@ declare module "bun" { * */ send( - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, compress?: boolean, ): ServerWebSocketSendStatus; @@ -1101,7 +1105,7 @@ declare module "bun" { */ publish( topic: string, - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, compress?: boolean, ): ServerWebSocketSendStatus; @@ -1739,7 +1743,7 @@ declare module "bun" { */ publish( topic: string, - data: string | ArrayBufferView | ArrayBuffer, + data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, compress?: boolean, ): ServerWebSocketSendStatus; @@ -1944,7 +1948,7 @@ declare module "bun" { * @param level * @returns The previous level */ - gcAggressionLevel(level: 0 | 1 | 2): 0 | 1 | 2; + gcAggressionLevel(level?: 0 | 1 | 2): 0 | 1 | 2; } export const unsafe: unsafe; @@ -2474,7 +2478,7 @@ declare module "bun" { /** * The source code of the module */ - contents: string | ArrayBufferView | ArrayBuffer; + contents: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer; /** * The loader to use for this file * @@ -3349,7 +3353,7 @@ type TimeLike = string | number | Date; type StringOrBuffer = string | TypedArray | ArrayBufferLike; type PathLike = string | TypedArray | ArrayBufferLike | URL; type PathOrFileDescriptor = PathLike | number; -type NoParamCallback = VoidFunction; +type NoParamCallback = (err: ErrnoException | null) => void; type BufferEncoding = | "buffer" | "utf8" diff --git a/packages/bun-types/fs.d.ts b/packages/bun-types/fs.d.ts index 7e34d5873..14c5c1d1d 100644 --- a/packages/bun-types/fs.d.ts +++ b/packages/bun-types/fs.d.ts @@ -20,10 +20,11 @@ declare module "fs" { import * as stream from "stream"; import type { SystemError, ArrayBufferView } from "bun"; - interface ObjectEncodingOptions { encoding?: BufferEncoding | null | undefined; } + + const promises: Awaited; type EncodingOption = | ObjectEncodingOptions | BufferEncoding @@ -1517,6 +1518,7 @@ declare module "fs" { * See the POSIX [`mkdir(2)`](http://man7.org/linux/man-pages/man2/mkdir.2.html) documentation for more details. * @since v0.0.67 */ + function mkdirSync( path: PathLike, options: MakeDirectoryOptions & { @@ -3930,6 +3932,5 @@ declare module "fs" { } declare module "node:fs" { - import * as fs from "fs"; - export = fs; + export * from "fs"; } diff --git a/packages/bun-types/fs/promises.d.ts b/packages/bun-types/fs/promises.d.ts index 164ef8db9..997896b01 100644 --- a/packages/bun-types/fs/promises.d.ts +++ b/packages/bun-types/fs/promises.d.ts @@ -9,7 +9,7 @@ */ declare module "fs/promises" { import { ArrayBufferView } from "bun"; - import { + import type { Stats, BigIntStats, StatOptions, @@ -25,6 +25,7 @@ declare module "fs/promises" { SimlinkType, Abortable, RmOptions, + RmDirOptions, } from "node:fs"; interface FlagAndOpenMode { @@ -677,9 +678,39 @@ declare module "fs/promises" { * @since v14.14.0 */ export function rm(path: PathLike, options?: RmOptions): Promise; + + /** + * Asynchronously test whether or not the given path exists by checking with the file system. + * + * ```ts + * import { exists } from 'fs/promises'; + * + * const e = await exists('/etc/passwd'); + * e; // boolean + * ``` + */ + function exists(path: PathLike): Promise; + + /** + * @deprecated Use `fs.promises.rm()` instead. + * + * Asynchronously remove a directory. + * + * ```ts + * import { rmdir } from 'fs/promises'; + * + * // remove a directory + * await rmdir('/tmp/mydir'); // Promise + * ``` + * + * To remove a directory recursively, use `fs.promises.rm()` instead, with the `recursive` option set to `true`. + */ + function rmdir( + path: PathLike, + options?: Omit, + ): Promise; } declare module "node:fs/promises" { - import * as fsPromises from "fs/promises"; - export = fsPromises; + export * from "fs/promises"; } diff --git a/packages/bun-types/tests/fs.test-d.ts b/packages/bun-types/tests/fs.test-d.ts new file mode 100644 index 000000000..1ef14a2f8 --- /dev/null +++ b/packages/bun-types/tests/fs.test-d.ts @@ -0,0 +1,6 @@ +import * as tsd from "tsd"; +import * as fs from "fs"; +import { exists } from "fs/promises"; + +tsd.expectType>(exists("/etc/passwd")); +tsd.expectType>(fs.promises.exists("/etc/passwd")); diff --git a/test/bun.js/event-emitter.test.ts b/test/bun.js/event-emitter.test.ts index cd1eaeaf2..2bb891778 100644 --- a/test/bun.js/event-emitter.test.ts +++ b/test/bun.js/event-emitter.test.ts @@ -150,7 +150,7 @@ test("EventEmitter GCs", () => { (function () { Bun.gc(true); - function EventEmitterSubclass() { + function EventEmitterSubclass(this: any) { EventEmitter.call(this); } diff --git a/test/bun.js/fs.test.js b/test/bun.js/fs.test.js deleted file mode 100644 index ce5209c53..000000000 --- a/test/bun.js/fs.test.js +++ /dev/null @@ -1,997 +0,0 @@ -import { beforeEach, describe, expect, it } from "bun:test"; -import { gc, gcTick } from "./gc"; -import fs, { - closeSync, - existsSync, - mkdirSync, - openSync, - readdirSync, - readFile, - readFileSync, - readSync, - writeFileSync, - writeSync, - statSync, - lstatSync, - copyFileSync, - rmSync, - rmdir, - rmdirSync, - createReadStream, - createWriteStream, - promises, - unlinkSync, - mkdtempSync, - constants, - Dirent, - Stats, -} from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -import { ReadStream as ReadStream_, WriteStream as WriteStream_ } from "../fixtures/export-lazy-fs-streams/export-from"; -import { - ReadStream as ReadStreamStar_, - WriteStream as WriteStreamStar_, -} from "../fixtures/export-lazy-fs-streams/export-*-from"; - -const Buffer = globalThis.Buffer || Uint8Array; - -if (!import.meta.dir) { - import.meta.dir = "."; -} - -function mkdirForce(path) { - if (!existsSync(path)) mkdirSync(path, { recursive: true }); -} - -describe("copyFileSync", () => { - it("should work for files < 128 KB", () => { - const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`; - expect(existsSync(tempdir)).toBe(false); - expect(tempdir.includes(mkdirSync(tempdir, { recursive: true }))).toBe(true); - - // that don't exist - copyFileSync(import.meta.path, tempdir + "/copyFileSync.js"); - expect(existsSync(tempdir + "/copyFileSync.js")).toBe(true); - expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe(readFileSync(import.meta.path, "utf-8")); - - // that do exist - copyFileSync(tempdir + "/copyFileSync.js", tempdir + "/copyFileSync.js1"); - writeFileSync(tempdir + "/copyFileSync.js1", "hello"); - copyFileSync(tempdir + "/copyFileSync.js1", tempdir + "/copyFileSync.js"); - - expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe("hello"); - }); - - it("should work for files > 128 KB ", () => { - const tempdir = `/tmp/fs.test.js/${Date.now()}-1/1234/hi`; - expect(existsSync(tempdir)).toBe(false); - expect(tempdir.includes(mkdirSync(tempdir, { recursive: true }))).toBe(true); - var buffer = new Int32Array(128 * 1024); - for (let i = 0; i < buffer.length; i++) { - buffer[i] = i % 256; - } - - const hash = Bun.hash(buffer.buffer); - writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer); - - expect(existsSync(tempdir + "/copyFileSync.dest.blob")).toBe(false); - expect(existsSync(tempdir + "/copyFileSync.src.blob")).toBe(true); - copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob"); - - expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(hash); - buffer[0] = 255; - writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer); - copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob"); - expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(Bun.hash(buffer.buffer)); - }); -}); - -describe("mkdirSync", () => { - it("should create a directory", () => { - const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`; - expect(existsSync(tempdir)).toBe(false); - expect(tempdir.includes(mkdirSync(tempdir, { recursive: true }))).toBe(true); - expect(existsSync(tempdir)).toBe(true); - }); -}); - -it("readdirSync on import.meta.dir", () => { - const dirs = readdirSync(import.meta.dir); - expect(dirs.length > 0).toBe(true); - var match = false; - gc(true); - for (let i = 0; i < dirs.length; i++) { - if (dirs[i] === import.meta.file) { - match = true; - } - } - gc(true); - expect(match).toBe(true); -}); - -// https://github.com/oven-sh/bun/issues/1887 -it("mkdtempSync, readdirSync, rmdirSync and unlinkSync with non-ascii", () => { - const tempdir = mkdtempSync(`/tmp/emoji-fruit-🍇 🍈 🍉 🍊 🍋`); - expect(existsSync(tempdir)).toBe(true); - writeFileSync(tempdir + "/non-ascii-👍.txt", "hello"); - const dirs = readdirSync(tempdir); - expect(dirs.length > 0).toBe(true); - var match = false; - gc(true); - for (let i = 0; i < dirs.length; i++) { - if (dirs[i].endsWith("non-ascii-👍.txt")) { - match = true; - break; - } - } - gc(true); - expect(match).toBe(true); - unlinkSync(tempdir + "/non-ascii-👍.txt"); - expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false); - rmdirSync(tempdir); - expect(existsSync(tempdir)).toBe(false); -}); - -it("mkdtempSync() empty name", () => { - const tempdir = mkdtempSync(); - expect(existsSync(tempdir)).toBe(true); - writeFileSync(tempdir + "/non-ascii-👍.txt", "hello"); - const dirs = readdirSync(tempdir); - expect(dirs.length > 0).toBe(true); - var match = false; - gc(true); - for (let i = 0; i < dirs.length; i++) { - if (dirs[i].endsWith("non-ascii-👍.txt")) { - match = true; - break; - } - } - gc(true); - expect(match).toBe(true); - unlinkSync(tempdir + "/non-ascii-👍.txt"); - expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false); - rmdirSync(tempdir); - expect(existsSync(tempdir)).toBe(false); -}); - -it("readdirSync on import.meta.dir with trailing slash", () => { - const dirs = readdirSync(import.meta.dir + "/"); - expect(dirs.length > 0).toBe(true); - // this file should exist in it - var match = false; - for (let i = 0; i < dirs.length; i++) { - if (dirs[i] === import.meta.file) { - match = true; - } - } - expect(match).toBe(true); -}); - -it("readdirSync works on empty directories", () => { - const path = `/tmp/fs-test-empty-dir-${(Math.random() * 100000 + 100).toString(32)}`; - mkdirSync(path, { recursive: true }); - expect(readdirSync(path).length).toBe(0); -}); - -it("readdirSync works on directories with under 32 files", () => { - const path = `/tmp/fs-test-one-dir-${(Math.random() * 100000 + 100).toString(32)}`; - mkdirSync(path, { recursive: true }); - writeFileSync(`${path}/a`, "a"); - const results = readdirSync(path); - expect(results.length).toBe(1); - expect(results[0]).toBe("a"); -}); - -it("readdirSync throws when given a file path", () => { - try { - readdirSync(import.meta.path); - throw new Error("should not get here"); - } catch (exception) { - expect(exception.name).toBe("ENOTDIR"); - } -}); - -it("readdirSync throws when given a path that doesn't exist", () => { - try { - readdirSync(import.meta.path + "/does-not-exist/really"); - throw new Error("should not get here"); - } catch (exception) { - expect(exception.name).toBe("ENOTDIR"); - } -}); - -it("readdirSync throws when given a file path with trailing slash", () => { - try { - readdirSync(import.meta.path + "/"); - throw new Error("should not get here"); - } catch (exception) { - expect(exception.name).toBe("ENOTDIR"); - } -}); - -describe("readSync", () => { - const firstFourBytes = new Uint32Array(new TextEncoder().encode("File").buffer)[0]; - it("works with a position set to 0", () => { - const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); - const four = new Uint8Array(4); - - { - const count = readSync(fd, four, 0, 4, 0); - const u32 = new Uint32Array(four.buffer)[0]; - expect(u32).toBe(firstFourBytes); - expect(count).toBe(4); - } - closeSync(fd); - }); - it("works without position set", () => { - const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); - const four = new Uint8Array(4); - { - const count = readSync(fd, four); - const u32 = new Uint32Array(four.buffer)[0]; - expect(u32).toBe(firstFourBytes); - expect(count).toBe(4); - } - closeSync(fd); - }); -}); - -describe("writeSync", () => { - it("works with a position set to 0", () => { - const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+"); - const four = new Uint8Array(4); - - { - const count = writeSync(fd, new TextEncoder().encode("File"), 0, 4, 0); - expect(count).toBe(4); - } - closeSync(fd); - }); - it("works without position set", () => { - const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+"); - const four = new Uint8Array(4); - { - const count = writeSync(fd, new TextEncoder().encode("File")); - expect(count).toBe(4); - } - closeSync(fd); - }); -}); - -describe("readFileSync", () => { - it("works", () => { - gc(); - const text = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8"); - gc(); - expect(text).toBe("File read successfully"); - gc(); - }); - - it("works with a file url", () => { - gc(); - const text = readFileSync(new URL("file://" + import.meta.dir + "/readFileSync.txt"), "utf8"); - gc(); - expect(text).toBe("File read successfully"); - }); - - it("works with special files in the filesystem", () => { - { - const text = readFileSync("/dev/null", "utf8"); - gc(); - expect(text).toBe(""); - } - - if (process.platform === "linux") { - const text = readFileSync("/proc/filesystems"); - gc(); - expect(text.length > 0).toBe(true); - } - }); - - it("returning Buffer works", () => { - const text = readFileSync(import.meta.dir + "/readFileSync.txt"); - const encoded = [ - 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, - ]; - for (let i = 0; i < encoded.length; i++) { - expect(text[i]).toBe(encoded[i]); - } - }); -}); - -describe("readFile", () => { - it("works", async () => { - gc(); - await new Promise((resolve, reject) => { - readFile(import.meta.dir + "/readFileSync.txt", "utf8", (err, text) => { - gc(); - expect(text).toBe("File read successfully"); - resolve(true); - }); - }); - }); - - it("returning Buffer works", async () => { - gc(); - await new Promise((resolve, reject) => { - gc(); - readFile(import.meta.dir + "/readFileSync.txt", (err, text) => { - const encoded = [ - 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, - ]; - gc(); - for (let i = 0; i < encoded.length; i++) { - expect(text[i]).toBe(encoded[i]); - } - resolve(true); - }); - }); - }); -}); - -describe("writeFileSync", () => { - it("works", () => { - const path = `/tmp/${Date.now()}.writeFileSync.txt`; - writeFileSync(path, "File written successfully", "utf8"); - - expect(readFileSync(path, "utf8")).toBe("File written successfully"); - }); - - it("returning Buffer works", () => { - const buffer = new Buffer([ - 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, - 121, - ]); - const path = `/tmp/${Date.now()}.blob.writeFileSync.txt`; - writeFileSync(path, buffer); - const out = readFileSync(path); - - for (let i = 0; i < buffer.length; i++) { - expect(buffer[i]).toBe(out[i]); - } - }); - it("returning ArrayBuffer works", () => { - const buffer = new Buffer([ - 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, - 121, - ]); - const path = `/tmp/${Date.now()}.blob2.writeFileSync.txt`; - writeFileSync(path, buffer); - const out = readFileSync(path); - - for (let i = 0; i < buffer.length; i++) { - expect(buffer[i]).toBe(out[i]); - } - }); -}); - -function triggerDOMJIT(target, fn, result) { - for (let i = 0; i < 9999; i++) { - if (fn.apply(target) !== result) { - throw new Error("DOMJIT failed"); - } - } -} - -describe("lstat", () => { - it("file metadata is correct", () => { - const fileStats = lstatSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1)); - expect(fileStats.isSymbolicLink()).toBe(false); - expect(fileStats.isFile()).toBe(true); - expect(fileStats.isDirectory()).toBe(false); - - triggerDOMJIT(fileStats, fileStats.isFile, true); - triggerDOMJIT(fileStats, fileStats.isDirectory, false); - triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); - }); - - it("folder metadata is correct", () => { - const fileStats = lstatSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1)); - expect(fileStats.isSymbolicLink()).toBe(false); - expect(fileStats.isFile()).toBe(false); - expect(fileStats.isDirectory()).toBe(true); - - triggerDOMJIT(fileStats, fileStats.isFile, false); - triggerDOMJIT(fileStats, fileStats.isDirectory, true); - triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); - }); - - it("symlink metadata is correct", () => { - const linkStats = lstatSync(new URL("./fs-stream.link.js", import.meta.url).toString().slice("file://".length - 1)); - expect(linkStats.isSymbolicLink()).toBe(true); - expect(linkStats.isFile()).toBe(false); - expect(linkStats.isDirectory()).toBe(false); - - triggerDOMJIT(linkStats, linkStats.isFile, false); - triggerDOMJIT(linkStats, linkStats.isDirectory, false); - triggerDOMJIT(linkStats, linkStats.isSymbolicLink, true); - }); -}); - -describe("stat", () => { - it("file metadata is correct", () => { - const fileStats = statSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1)); - expect(fileStats.isSymbolicLink()).toBe(false); - expect(fileStats.isFile()).toBe(true); - expect(fileStats.isDirectory()).toBe(false); - - triggerDOMJIT(fileStats, fileStats.isFile, true); - triggerDOMJIT(fileStats, fileStats.isDirectory, false); - triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); - }); - - it("folder metadata is correct", () => { - const fileStats = statSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1)); - expect(fileStats.isSymbolicLink()).toBe(false); - expect(fileStats.isFile()).toBe(false); - expect(fileStats.isDirectory()).toBe(true); - expect(typeof fileStats.dev).toBe("number"); - expect(typeof fileStats.ino).toBe("number"); - expect(typeof fileStats.mode).toBe("number"); - expect(typeof fileStats.nlink).toBe("number"); - expect(typeof fileStats.uid).toBe("number"); - expect(typeof fileStats.gid).toBe("number"); - expect(typeof fileStats.rdev).toBe("number"); - expect(typeof fileStats.size).toBe("number"); - expect(typeof fileStats.blksize).toBe("number"); - expect(typeof fileStats.blocks).toBe("number"); - expect(typeof fileStats.atimeMs).toBe("number"); - expect(typeof fileStats.mtimeMs).toBe("number"); - expect(typeof fileStats.ctimeMs).toBe("number"); - expect(typeof fileStats.birthtimeMs).toBe("number"); - expect(typeof fileStats.atime).toBe("object"); - expect(typeof fileStats.mtime).toBe("object"); - expect(typeof fileStats.ctime).toBe("object"); - expect(typeof fileStats.birthtime).toBe("object"); - - triggerDOMJIT(fileStats, fileStats.isFile, false); - triggerDOMJIT(fileStats, fileStats.isDirectory, true); - triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); - }); - - it("stat returns ENOENT", () => { - try { - statSync("/tmp/doesntexist"); - throw "statSync should throw"; - } catch (e) { - expect(e.code).toBe("ENOENT"); - } - }); -}); - -describe("rm", () => { - it("removes a file", () => { - const path = `/tmp/${Date.now()}.rm.txt`; - writeFileSync(path, "File written successfully", "utf8"); - expect(existsSync(path)).toBe(true); - rmSync(path); - expect(existsSync(path)).toBe(false); - }); - - it("removes a dir", () => { - const path = `/tmp/${Date.now()}.rm.dir`; - try { - mkdirSync(path); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmSync(path); - expect(existsSync(path)).toBe(false); - }); - - it("removes a dir recursively", () => { - const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; - try { - mkdirSync(path, { recursive: true }); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmSync(join(path, "../../"), { recursive: true }); - expect(existsSync(path)).toBe(false); - }); -}); - -describe("rmdir", () => { - it("removes a file", done => { - const path = `/tmp/${Date.now()}.rm.txt`; - writeFileSync(path, "File written successfully", "utf8"); - expect(existsSync(path)).toBe(true); - rmdir(path, err => { - try { - expect(err).toBeDefined(); - expect(err.code).toBe("EPERM"); - expect(err.message).toBe("Operation not permitted"); - expect(existsSync(path)).toBe(true); - } catch (e) { - return done(e); - } finally { - done(); - } - }); - }); - - it("removes a dir", done => { - const path = `/tmp/${Date.now()}.rm.dir`; - try { - mkdirSync(path); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmdir(path, err => { - if (err) return done(err); - expect(existsSync(path)).toBe(false); - done(); - }); - }); - // TODO support `recursive: true` - it("removes a dir recursively", done => { - const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; - try { - mkdirSync(path, { recursive: true }); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmdir(join(path, "../../"), { recursive: true }, err => { - try { - expect(existsSync(path)).toBe(false); - done(err); - } catch (e) { - return done(e); - } finally { - done(); - } - }); - }); -}); - -describe("rmdirSync", () => { - it("removes a file", () => { - const path = `/tmp/${Date.now()}.rm.txt`; - writeFileSync(path, "File written successfully", "utf8"); - expect(existsSync(path)).toBe(true); - expect(() => { - rmdirSync(path); - }).toThrow("Operation not permitted"); - expect(existsSync(path)).toBe(true); - }); - it("removes a dir", () => { - const path = `/tmp/${Date.now()}.rm.dir`; - try { - mkdirSync(path); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmdirSync(path); - expect(existsSync(path)).toBe(false); - }); - // TODO support `recursive: true` - it("removes a dir recursively", () => { - const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; - try { - mkdirSync(path, { recursive: true }); - } catch (e) {} - expect(existsSync(path)).toBe(true); - rmdirSync(join(path, "../../"), { recursive: true }); - expect(existsSync(path)).toBe(false); - }); -}); - -describe("createReadStream", () => { - it("works (1 chunk)", async () => { - return await new Promise((resolve, reject) => { - var stream = createReadStream(import.meta.dir + "/readFileSync.txt", {}); - - stream.on("error", e => { - reject(e); - }); - - stream.on("data", chunk => { - expect(chunk instanceof Buffer).toBe(true); - expect(chunk.length).toBe("File read successfully".length); - expect(chunk.toString()).toBe("File read successfully"); - }); - - stream.on("close", () => { - resolve(true); - }); - }); - }); - - it("works (22 chunk)", async () => { - var stream = createReadStream(import.meta.dir + "/readFileSync.txt", { - highWaterMark: 1, - }); - - var data = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8"); - var i = 0; - return await new Promise(resolve => { - stream.on("data", chunk => { - expect(chunk instanceof Buffer).toBe(true); - expect(chunk.length).toBe(1); - expect(chunk.toString()).toBe(data[i++]); - }); - - stream.on("end", () => { - resolve(true); - }); - }); - }); -}); - -describe("fs.WriteStream", () => { - it("should be exported", () => { - expect(fs.WriteStream).toBeDefined(); - }); - - it("should be constructable", () => { - 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`); - - const stream = new fs.WriteStream(path, { flags: "w+" }); - stream.write("Test file written successfully"); - stream.end(); - - stream.on("error", e => { - done(e instanceof Error ? e : new Error(e)); - }); - - stream.on("finish", () => { - expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); - done(); - }); - }); - - it("should work if re-exported by name", () => { - 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", () => { - 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 ...", () => { - 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", () => { - 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`); - - const stream = new WriteStream_(path, { flags: "w+" }); - stream.write("Test file written successfully"); - stream.end(); - - stream.on("error", e => { - done(e instanceof Error ? e : new Error(e)); - }); - - stream.on("finish", () => { - expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); - done(); - }); - }); -}); - -describe("fs.ReadStream", () => { - it("should be exported", () => { - expect(fs.ReadStream).toBeDefined(); - }); - - it("should be constructable", () => { - 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+", - }); - - 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", () => { - 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", () => { - 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 ...", () => { - 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", () => { - 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+", - }); - - const stream = new ReadStream_(path); - stream.setEncoding("utf8"); - stream.on("error", e => { - done(e instanceof Error ? e : new Error(e)); - }); - - let data = ""; - - stream.on("data", chunk => { - data += chunk; - }); - - stream.on("end", () => { - expect(data).toBe("Test file written successfully"); - done(); - }); - }); -}); - -describe("createWriteStream", () => { - it("simple write stream finishes", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.createWriteStream.txt`; - const stream = createWriteStream(path); - stream.write("Test file written successfully"); - stream.end(); - - return await new Promise((resolve, reject) => { - stream.on("error", e => { - reject(e); - }); - - stream.on("finish", () => { - expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); - resolve(true); - }); - }); - }); - - it("writing null throws ERR_STREAM_NULL_VALUES", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`; - const stream = createWriteStream(path); - try { - stream.write(null); - expect(() => {}).toThrow(Error); - } catch (exception) { - expect(exception.code).toBe("ERR_STREAM_NULL_VALUES"); - } - }); - - it("writing null throws ERR_STREAM_NULL_VALUES (objectMode: true)", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`; - const stream = createWriteStream(path, { - objectMode: true, - }); - try { - stream.write(null); - expect(() => {}).toThrow(Error); - } catch (exception) { - expect(exception.code).toBe("ERR_STREAM_NULL_VALUES"); - } - }); - - it("writing false throws ERR_INVALID_ARG_TYPE", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`; - const stream = createWriteStream(path); - try { - stream.write(false); - expect(() => {}).toThrow(Error); - } catch (exception) { - expect(exception.code).toBe("ERR_INVALID_ARG_TYPE"); - } - }); - - it("writing false throws ERR_INVALID_ARG_TYPE (objectMode: true)", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`; - const stream = createWriteStream(path, { - objectMode: true, - }); - try { - stream.write(false); - expect(() => {}).toThrow(Error); - } catch (exception) { - expect(exception.code).toBe("ERR_INVALID_ARG_TYPE"); - } - }); -}); - -describe("fs/promises", () => { - const { exists, mkdir, readFile, rmdir, stat, writeFile } = promises; - - it("should not segfault on exception", async () => { - try { - await stat("foo/bar"); - } catch (e) {} - }); - - it("readFile", async () => { - const data = await readFile(import.meta.dir + "/readFileSync.txt", "utf8"); - expect(data).toBe("File read successfully"); - }); - - it("writeFile", async () => { - const path = `/tmp/fs.test.js/${Date.now()}.writeFile.txt`; - await writeFile(path, "File written successfully"); - expect(readFileSync(path, "utf8")).toBe("File written successfully"); - }); - - it("readdir()", async () => { - const files = await promises.readdir(import.meta.dir); - expect(files.length).toBeGreaterThan(0); - }); - - it("readdir() no args doesnt segfault", async () => { - const fizz = [ - [], - [Symbol("ok")], - [Symbol("ok"), Symbol("ok")], - [Symbol("ok"), Symbol("ok"), Symbol("ok")], - [Infinity, -NaN, -Infinity], - "\0\0\0\0", - "\r\n", - ]; - for (const args of fizz) { - try { - // check it doens't segfault when called with invalid arguments - await promises.readdir(...args); - } catch (e) { - // check that producing the error doesn't cause any crashes - Bun.inspect(e); - } - } - }); - - describe("rmdir", () => { - it("removes a file", async () => { - const path = `/tmp/${Date.now()}.rm.txt`; - await writeFile(path, "File written successfully", "utf8"); - expect(await exists(path)).toBe(true); - try { - await rmdir(path); - expect(() => {}).toThrow(); - } catch (err) { - expect(err.code).toBe("ENOTDIR"); - // expect(err.message).toBe("Operation not permitted"); - expect(await exists(path)).toBe(true); - } - }); - - it("removes a dir", async () => { - const path = `/tmp/${Date.now()}.rm.dir`; - try { - await mkdir(path); - } catch (e) {} - expect(await exists(path)).toBe(true); - await rmdir(path); - expect(await exists(path)).toBe(false); - }); - // TODO support `recursive: true` - // it("removes a dir recursively", async () => { - // const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; - // try { - // await mkdir(path, { recursive: true }); - // } catch (e) {} - // expect(await exists(path)).toBe(true); - // await rmdir(join(path, "../../"), { recursive: true }); - // expect(await exists(path)).toBe(false); - // }); - }); -}); - -it("fs.constants", () => { - expect(constants).toBeDefined(); - expect(constants.F_OK).toBeDefined(); - expect(constants.R_OK).toBeDefined(); - expect(constants.W_OK).toBeDefined(); - expect(constants.X_OK).toBeDefined(); - expect(constants.O_RDONLY).toBeDefined(); - expect(constants.O_WRONLY).toBeDefined(); - expect(constants.O_RDWR).toBeDefined(); - expect(constants.O_CREAT).toBeDefined(); - expect(constants.O_EXCL).toBeDefined(); - expect(constants.O_NOCTTY).toBeDefined(); - expect(constants.O_TRUNC).toBeDefined(); - expect(constants.O_APPEND).toBeDefined(); - expect(constants.O_DIRECTORY).toBeDefined(); - expect(constants.O_NOATIME).toBeDefined(); - expect(constants.O_NOFOLLOW).toBeDefined(); - expect(constants.O_SYNC).toBeDefined(); - expect(constants.O_DSYNC).toBeDefined(); - expect(constants.O_SYMLINK).toBeDefined(); - expect(constants.O_DIRECT).toBeDefined(); - expect(constants.O_NONBLOCK).toBeDefined(); - expect(constants.S_IFMT).toBeDefined(); - expect(constants.S_IFREG).toBeDefined(); - expect(constants.S_IFDIR).toBeDefined(); - expect(constants.S_IFCHR).toBeDefined(); - expect(constants.S_IFBLK).toBeDefined(); - expect(constants.S_IFIFO).toBeDefined(); - expect(constants.S_IFLNK).toBeDefined(); - expect(constants.S_IFSOCK).toBeDefined(); - expect(constants.S_IRWXU).toBeDefined(); - expect(constants.S_IRUSR).toBeDefined(); - expect(constants.S_IWUSR).toBeDefined(); - expect(constants.S_IXUSR).toBeDefined(); - expect(constants.S_IRWXG).toBeDefined(); - expect(constants.S_IRGRP).toBeDefined(); - expect(constants.S_IWGRP).toBeDefined(); - expect(constants.S_IXGRP).toBeDefined(); - expect(constants.S_IRWXO).toBeDefined(); - expect(constants.S_IROTH).toBeDefined(); - expect(constants.S_IWOTH).toBeDefined(); -}); - -it("fs.Dirent", () => { - expect(Dirent).toBeDefined(); -}); - -it("fs.Stats", () => { - expect(Stats).toBeDefined(); -}); - -it("repro 1516: can use undefined/null to specify default flag", () => { - const path = "/tmp/repro_1516.txt"; - writeFileSync(path, "b", { flag: undefined }); - expect(readFileSync(path, { encoding: "utf8", flag: null })).toBe("b"); - rmSync(path); -}); diff --git a/test/bun.js/fs.test.ts b/test/bun.js/fs.test.ts new file mode 100644 index 000000000..4c847d25a --- /dev/null +++ b/test/bun.js/fs.test.ts @@ -0,0 +1,1016 @@ +import { beforeEach, describe, expect, it } from "bun:test"; +import { gc, gcTick } from "./gc"; +import fs, { + closeSync, + existsSync, + mkdirSync, + openSync, + readdirSync, + readFile, + readFileSync, + readSync, + writeFileSync, + writeSync, + statSync, + lstatSync, + copyFileSync, + rmSync, + rmdir, + rmdirSync, + createReadStream, + createWriteStream, + promises, + unlinkSync, + mkdtempSync, + constants, + Dirent, + Stats, +} from "node:fs"; + +import _promises from "node:fs/promises"; + +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { ReadStream as ReadStream_, WriteStream as WriteStream_ } from "../fixtures/export-lazy-fs-streams/export-from"; +import { + ReadStream as ReadStreamStar_, + WriteStream as WriteStreamStar_, +} from "../fixtures/export-lazy-fs-streams/export-*-from"; + +const Buffer = globalThis.Buffer || Uint8Array; + +if (!import.meta.dir) { + import.meta.dir = "."; +} + +function mkdirForce(path) { + if (!existsSync(path)) mkdirSync(path, { recursive: true }); +} + +describe("copyFileSync", () => { + it("should work for files < 128 KB", () => { + const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`; + expect(existsSync(tempdir)).toBe(false); + expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true); + + // that don't exist + copyFileSync(import.meta.path, tempdir + "/copyFileSync.js"); + expect(existsSync(tempdir + "/copyFileSync.js")).toBe(true); + expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe(readFileSync(import.meta.path, "utf-8")); + + // that do exist + copyFileSync(tempdir + "/copyFileSync.js", tempdir + "/copyFileSync.js1"); + writeFileSync(tempdir + "/copyFileSync.js1", "hello"); + copyFileSync(tempdir + "/copyFileSync.js1", tempdir + "/copyFileSync.js"); + + expect(readFileSync(tempdir + "/copyFileSync.js", "utf-8")).toBe("hello"); + }); + + it("should work for files > 128 KB ", () => { + const tempdir = `/tmp/fs.test.js/${Date.now()}-1/1234/hi`; + expect(existsSync(tempdir)).toBe(false); + expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true); + var buffer = new Int32Array(128 * 1024); + for (let i = 0; i < buffer.length; i++) { + buffer[i] = i % 256; + } + + const hash = Bun.hash(buffer.buffer); + writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer); + + expect(existsSync(tempdir + "/copyFileSync.dest.blob")).toBe(false); + expect(existsSync(tempdir + "/copyFileSync.src.blob")).toBe(true); + copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob"); + + expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(hash); + buffer[0] = 255; + writeFileSync(tempdir + "/copyFileSync.src.blob", buffer.buffer); + copyFileSync(tempdir + "/copyFileSync.src.blob", tempdir + "/copyFileSync.dest.blob"); + expect(Bun.hash(readFileSync(tempdir + "/copyFileSync.dest.blob"))).toBe(Bun.hash(buffer.buffer)); + }); +}); + +describe("mkdirSync", () => { + it("should create a directory", () => { + const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`; + expect(existsSync(tempdir)).toBe(false); + expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true); + expect(existsSync(tempdir)).toBe(true); + }); +}); + +it("readdirSync on import.meta.dir", () => { + const dirs = readdirSync(import.meta.dir); + expect(dirs.length > 0).toBe(true); + var match = false; + gc(true); + for (let i = 0; i < dirs.length; i++) { + if (dirs[i] === import.meta.file) { + match = true; + } + } + gc(true); + expect(match).toBe(true); +}); + +// https://github.com/oven-sh/bun/issues/1887 +it("mkdtempSync, readdirSync, rmdirSync and unlinkSync with non-ascii", () => { + const tempdir = mkdtempSync(`/tmp/emoji-fruit-🍇 🍈 🍉 🍊 🍋`); + expect(existsSync(tempdir)).toBe(true); + writeFileSync(tempdir + "/non-ascii-👍.txt", "hello"); + const dirs = readdirSync(tempdir); + expect(dirs.length > 0).toBe(true); + var match = false; + gc(true); + for (let i = 0; i < dirs.length; i++) { + if (dirs[i].endsWith("non-ascii-👍.txt")) { + match = true; + break; + } + } + gc(true); + expect(match).toBe(true); + unlinkSync(tempdir + "/non-ascii-👍.txt"); + expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false); + rmdirSync(tempdir); + expect(existsSync(tempdir)).toBe(false); +}); + +it("mkdtempSync() empty name", () => { + // @ts-ignore-next-line + const tempdir = mkdtempSync(); + expect(existsSync(tempdir)).toBe(true); + writeFileSync(tempdir + "/non-ascii-👍.txt", "hello"); + const dirs = readdirSync(tempdir); + expect(dirs.length > 0).toBe(true); + var match = false; + gc(true); + for (let i = 0; i < dirs.length; i++) { + if (dirs[i].endsWith("non-ascii-👍.txt")) { + match = true; + break; + } + } + gc(true); + expect(match).toBe(true); + unlinkSync(tempdir + "/non-ascii-👍.txt"); + expect(existsSync(tempdir + "/non-ascii-👍.txt")).toBe(false); + rmdirSync(tempdir); + expect(existsSync(tempdir)).toBe(false); +}); + +it("readdirSync on import.meta.dir with trailing slash", () => { + const dirs = readdirSync(import.meta.dir + "/"); + expect(dirs.length > 0).toBe(true); + // this file should exist in it + var match = false; + for (let i = 0; i < dirs.length; i++) { + if (dirs[i] === import.meta.file) { + match = true; + } + } + expect(match).toBe(true); +}); + +it("readdirSync works on empty directories", () => { + const path = `/tmp/fs-test-empty-dir-${(Math.random() * 100000 + 100).toString(32)}`; + mkdirSync(path, { recursive: true }); + expect(readdirSync(path).length).toBe(0); +}); + +it("readdirSync works on directories with under 32 files", () => { + const path = `/tmp/fs-test-one-dir-${(Math.random() * 100000 + 100).toString(32)}`; + mkdirSync(path, { recursive: true }); + writeFileSync(`${path}/a`, "a"); + const results = readdirSync(path); + expect(results.length).toBe(1); + expect(results[0]).toBe("a"); +}); + +it("readdirSync throws when given a file path", () => { + try { + readdirSync(import.meta.path); + throw new Error("should not get here"); + } catch (exception: any) { + expect(exception.name).toBe("ENOTDIR"); + } +}); + +it("readdirSync throws when given a path that doesn't exist", () => { + try { + readdirSync(import.meta.path + "/does-not-exist/really"); + throw new Error("should not get here"); + } catch (exception: any) { + expect(exception.name).toBe("ENOTDIR"); + } +}); + +it("readdirSync throws when given a file path with trailing slash", () => { + try { + readdirSync(import.meta.path + "/"); + throw new Error("should not get here"); + } catch (exception: any) { + expect(exception.name).toBe("ENOTDIR"); + } +}); + +describe("readSync", () => { + const firstFourBytes = new Uint32Array(new TextEncoder().encode("File").buffer)[0]; + it("works with a position set to 0", () => { + const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); + const four = new Uint8Array(4); + + { + const count = readSync(fd, four, 0, 4, 0); + const u32 = new Uint32Array(four.buffer)[0]; + expect(u32).toBe(firstFourBytes); + expect(count).toBe(4); + } + closeSync(fd); + }); + it("works without position set", () => { + const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); + const four = new Uint8Array(4); + { + const count = readSync(fd, four); + const u32 = new Uint32Array(four.buffer)[0]; + expect(u32).toBe(firstFourBytes); + expect(count).toBe(4); + } + closeSync(fd); + }); +}); + +describe("writeSync", () => { + it("works with a position set to 0", () => { + const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+"); + const four = new Uint8Array(4); + + { + const count = writeSync(fd, new TextEncoder().encode("File"), 0, 4, 0); + expect(count).toBe(4); + } + closeSync(fd); + }); + it("works without position set", () => { + const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+"); + const four = new Uint8Array(4); + { + const count = writeSync(fd, new TextEncoder().encode("File")); + expect(count).toBe(4); + } + closeSync(fd); + }); +}); + +describe("readFileSync", () => { + it("works", () => { + gc(); + const text = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8"); + gc(); + expect(text).toBe("File read successfully"); + gc(); + }); + + it("works with a file url", () => { + gc(); + const text = readFileSync(new URL("file://" + import.meta.dir + "/readFileSync.txt"), "utf8"); + gc(); + expect(text).toBe("File read successfully"); + }); + + it("works with special files in the filesystem", () => { + { + const text = readFileSync("/dev/null", "utf8"); + gc(); + expect(text).toBe(""); + } + + if (process.platform === "linux") { + const text = readFileSync("/proc/filesystems"); + gc(); + expect(text.length > 0).toBe(true); + } + }); + + it("returning Buffer works", () => { + const text = readFileSync(import.meta.dir + "/readFileSync.txt"); + const encoded = [ + 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, + ]; + for (let i = 0; i < encoded.length; i++) { + expect(text[i]).toBe(encoded[i]); + } + }); +}); + +describe("readFile", () => { + it("works", async () => { + gc(); + await new Promise((resolve, reject) => { + readFile(import.meta.dir + "/readFileSync.txt", "utf8", (err, text) => { + gc(); + expect(text).toBe("File read successfully"); + resolve(true); + }); + }); + }); + + it("returning Buffer works", async () => { + gc(); + await new Promise((resolve, reject) => { + gc(); + readFile(import.meta.dir + "/readFileSync.txt", (err, text) => { + const encoded = [ + 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, + ]; + gc(); + for (let i = 0; i < encoded.length; i++) { + expect(text[i]).toBe(encoded[i]); + } + resolve(true); + }); + }); + }); +}); + +describe("writeFileSync", () => { + it("works", () => { + const path = `/tmp/${Date.now()}.writeFileSync.txt`; + writeFileSync(path, "File written successfully", "utf8"); + + expect(readFileSync(path, "utf8")).toBe("File written successfully"); + }); + + it("returning Buffer works", () => { + const buffer = new Buffer([ + 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, + 121, + ]); + const path = `/tmp/${Date.now()}.blob.writeFileSync.txt`; + writeFileSync(path, buffer); + const out = readFileSync(path); + + for (let i = 0; i < buffer.length; i++) { + expect(buffer[i]).toBe(out[i]); + } + }); + it("returning ArrayBuffer works", () => { + const buffer = new Buffer([ + 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, 99, 99, 101, 115, 115, 102, 117, 108, 108, + 121, + ]); + const path = `/tmp/${Date.now()}.blob2.writeFileSync.txt`; + writeFileSync(path, buffer); + const out = readFileSync(path); + + for (let i = 0; i < buffer.length; i++) { + expect(buffer[i]).toBe(out[i]); + } + }); +}); + +function triggerDOMJIT(target, fn, result) { + for (let i = 0; i < 9999; i++) { + if (fn.apply(target) !== result) { + throw new Error("DOMJIT failed"); + } + } +} + +describe("lstat", () => { + it("file metadata is correct", () => { + const fileStats = lstatSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1)); + expect(fileStats.isSymbolicLink()).toBe(false); + expect(fileStats.isFile()).toBe(true); + expect(fileStats.isDirectory()).toBe(false); + + triggerDOMJIT(fileStats, fileStats.isFile, true); + triggerDOMJIT(fileStats, fileStats.isDirectory, false); + triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); + }); + + it("folder metadata is correct", () => { + const fileStats = lstatSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1)); + expect(fileStats.isSymbolicLink()).toBe(false); + expect(fileStats.isFile()).toBe(false); + expect(fileStats.isDirectory()).toBe(true); + + triggerDOMJIT(fileStats, fileStats.isFile, false); + triggerDOMJIT(fileStats, fileStats.isDirectory, true); + triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); + }); + + it("symlink metadata is correct", () => { + const linkStats = lstatSync(new URL("./fs-stream.link.js", import.meta.url).toString().slice("file://".length - 1)); + expect(linkStats.isSymbolicLink()).toBe(true); + expect(linkStats.isFile()).toBe(false); + expect(linkStats.isDirectory()).toBe(false); + + triggerDOMJIT(linkStats, linkStats.isFile, false); + triggerDOMJIT(linkStats, linkStats.isDirectory, false); + triggerDOMJIT(linkStats, linkStats.isSymbolicLink, true); + }); +}); + +describe("stat", () => { + it("file metadata is correct", () => { + const fileStats = statSync(new URL("./fs-stream.js", import.meta.url).toString().slice("file://".length - 1)); + expect(fileStats.isSymbolicLink()).toBe(false); + expect(fileStats.isFile()).toBe(true); + expect(fileStats.isDirectory()).toBe(false); + + triggerDOMJIT(fileStats, fileStats.isFile, true); + triggerDOMJIT(fileStats, fileStats.isDirectory, false); + triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); + }); + + it("folder metadata is correct", () => { + const fileStats = statSync(new URL("../../test", import.meta.url).toString().slice("file://".length - 1)); + expect(fileStats.isSymbolicLink()).toBe(false); + expect(fileStats.isFile()).toBe(false); + expect(fileStats.isDirectory()).toBe(true); + expect(typeof fileStats.dev).toBe("number"); + expect(typeof fileStats.ino).toBe("number"); + expect(typeof fileStats.mode).toBe("number"); + expect(typeof fileStats.nlink).toBe("number"); + expect(typeof fileStats.uid).toBe("number"); + expect(typeof fileStats.gid).toBe("number"); + expect(typeof fileStats.rdev).toBe("number"); + expect(typeof fileStats.size).toBe("number"); + expect(typeof fileStats.blksize).toBe("number"); + expect(typeof fileStats.blocks).toBe("number"); + expect(typeof fileStats.atimeMs).toBe("number"); + expect(typeof fileStats.mtimeMs).toBe("number"); + expect(typeof fileStats.ctimeMs).toBe("number"); + expect(typeof fileStats.birthtimeMs).toBe("number"); + expect(typeof fileStats.atime).toBe("object"); + expect(typeof fileStats.mtime).toBe("object"); + expect(typeof fileStats.ctime).toBe("object"); + expect(typeof fileStats.birthtime).toBe("object"); + + triggerDOMJIT(fileStats, fileStats.isFile, false); + triggerDOMJIT(fileStats, fileStats.isDirectory, true); + triggerDOMJIT(fileStats, fileStats.isSymbolicLink, false); + }); + + it("stat returns ENOENT", () => { + try { + statSync("/tmp/doesntexist"); + throw "statSync should throw"; + } catch (e: any) { + expect(e.code).toBe("ENOENT"); + } + }); +}); + +describe("rm", () => { + it("removes a file", () => { + const path = `/tmp/${Date.now()}.rm.txt`; + writeFileSync(path, "File written successfully", "utf8"); + expect(existsSync(path)).toBe(true); + rmSync(path); + expect(existsSync(path)).toBe(false); + }); + + it("removes a dir", () => { + const path = `/tmp/${Date.now()}.rm.dir`; + try { + mkdirSync(path); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmSync(path); + expect(existsSync(path)).toBe(false); + }); + + it("removes a dir recursively", () => { + const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; + try { + mkdirSync(path, { recursive: true }); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmSync(join(path, "../../"), { recursive: true }); + expect(existsSync(path)).toBe(false); + }); +}); + +describe("rmdir", () => { + it("removes a file", done => { + const path = `/tmp/${Date.now()}.rm.txt`; + writeFileSync(path, "File written successfully", "utf8"); + expect(existsSync(path)).toBe(true); + rmdir(path, err => { + try { + expect(err).toBeDefined(); + expect(err!.code).toBe("EPERM"); + expect(err!.message).toBe("Operation not permitted"); + expect(existsSync(path)).toBe(true); + } catch (e) { + return done(e); + } finally { + done(); + } + }); + }); + + it("removes a dir", done => { + const path = `/tmp/${Date.now()}.rm.dir`; + try { + mkdirSync(path); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmdir(path, err => { + if (err) return done(err); + expect(existsSync(path)).toBe(false); + done(); + }); + }); + // TODO support `recursive: true` + it("removes a dir recursively", done => { + const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; + try { + mkdirSync(path, { recursive: true }); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmdir(join(path, "../../"), { recursive: true }, err => { + try { + expect(existsSync(path)).toBe(false); + done(err); + } catch (e) { + return done(e); + } finally { + done(); + } + }); + }); +}); + +describe("rmdirSync", () => { + it("removes a file", () => { + const path = `/tmp/${Date.now()}.rm.txt`; + writeFileSync(path, "File written successfully", "utf8"); + expect(existsSync(path)).toBe(true); + expect(() => { + rmdirSync(path); + }).toThrow("Operation not permitted"); + expect(existsSync(path)).toBe(true); + }); + it("removes a dir", () => { + const path = `/tmp/${Date.now()}.rm.dir`; + try { + mkdirSync(path); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmdirSync(path); + expect(existsSync(path)).toBe(false); + }); + // TODO support `recursive: true` + it("removes a dir recursively", () => { + const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; + try { + mkdirSync(path, { recursive: true }); + } catch (e) {} + expect(existsSync(path)).toBe(true); + rmdirSync(join(path, "../../"), { recursive: true }); + expect(existsSync(path)).toBe(false); + }); +}); + +describe("createReadStream", () => { + it("works (1 chunk)", async () => { + return await new Promise((resolve, reject) => { + var stream = createReadStream(import.meta.dir + "/readFileSync.txt", {}); + + stream.on("error", e => { + reject(e); + }); + + stream.on("data", chunk => { + expect(chunk instanceof Buffer).toBe(true); + expect(chunk.length).toBe("File read successfully".length); + expect(chunk.toString()).toBe("File read successfully"); + }); + + stream.on("close", () => { + resolve(true); + }); + }); + }); + + it("works (22 chunk)", async () => { + var stream = createReadStream(import.meta.dir + "/readFileSync.txt", { + highWaterMark: 1, + }); + + var data = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8"); + var i = 0; + return await new Promise(resolve => { + stream.on("data", chunk => { + expect(chunk instanceof Buffer).toBe(true); + expect(chunk.length).toBe(1); + expect(chunk.toString()).toBe(data[i++]); + }); + + stream.on("end", () => { + resolve(true); + }); + }); + }); +}); + +describe("fs.WriteStream", () => { + it("should be exported", () => { + expect(fs.WriteStream).toBeDefined(); + }); + + it("should be constructable", () => { + // @ts-ignore-next-line + const stream = new fs.WriteStream("test.txt"); + expect(stream instanceof fs.WriteStream).toBe(true); + }); + + it("should be able to write to a file", done => { + const pathToDir = `${tmpdir()}/${Date.now()}`; + mkdirForce(pathToDir); + const path = join(pathToDir, `fs-writestream-test.txt`); + + // @ts-ignore-next-line + const stream = new fs.WriteStream(path, { flags: "w+" }); + stream.write("Test file written successfully"); + stream.end(); + + stream.on("error", e => { + done(e instanceof Error ? e : new Error(e)); + }); + + stream.on("finish", () => { + expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); + done(); + }); + }); + + it("should work if re-exported by name", () => { + // @ts-ignore-next-line + const stream = new WriteStream_("test.txt"); + expect(stream instanceof WriteStream_).toBe(true); + expect(stream instanceof WriteStreamStar_).toBe(true); + expect(stream instanceof fs.WriteStream).toBe(true); + }); + + it("should work if re-exported by name, called without new", () => { + // @ts-ignore-next-line + const stream = WriteStream_("test.txt"); + expect(stream instanceof WriteStream_).toBe(true); + expect(stream instanceof WriteStreamStar_).toBe(true); + expect(stream instanceof fs.WriteStream).toBe(true); + }); + + it("should work if re-exported, as export * from ...", () => { + // @ts-ignore-next-line + const stream = new WriteStreamStar_("test.txt"); + expect(stream instanceof WriteStream_).toBe(true); + expect(stream instanceof WriteStreamStar_).toBe(true); + expect(stream instanceof fs.WriteStream).toBe(true); + }); + + it("should work if re-exported, as export * from..., called without new", () => { + // @ts-ignore-next-line + const stream = WriteStreamStar_("test.txt"); + expect(stream instanceof WriteStream_).toBe(true); + expect(stream instanceof WriteStreamStar_).toBe(true); + expect(stream instanceof fs.WriteStream).toBe(true); + }); + + it("should be able to write to a file with re-exported WriteStream", done => { + const pathToDir = `${tmpdir()}/${Date.now()}`; + mkdirForce(pathToDir); + const path = join(pathToDir, `fs-writestream-re-exported-test.txt`); + // @ts-ignore-next-line + const stream = new WriteStream_(path, { flags: "w+" }); + stream.write("Test file written successfully"); + stream.end(); + + stream.on("error", e => { + done(e instanceof Error ? e : new Error(e)); + }); + + stream.on("finish", () => { + expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); + done(); + }); + }); +}); + +describe("fs.ReadStream", () => { + it("should be exported", () => { + expect(fs.ReadStream).toBeDefined(); + }); + + it("should be constructable", () => { + // @ts-ignore-next-line + const stream = new fs.ReadStream("test.txt"); + expect(stream instanceof fs.ReadStream).toBe(true); + }); + + it("should be able to read from a file", done => { + const pathToDir = `${tmpdir()}/${Date.now()}`; + mkdirForce(pathToDir); + const path = join(pathToDir, `fs-readstream-test.txt`); + + writeFileSync(path, "Test file written successfully", { + encoding: "utf8", + flag: "w+", + }); + // @ts-ignore-next-line + const stream = new fs.ReadStream(path); + stream.setEncoding("utf8"); + stream.on("error", e => { + done(e instanceof Error ? e : new Error(e)); + }); + + let data = ""; + + stream.on("data", chunk => { + data += chunk; + }); + + stream.on("end", () => { + expect(data).toBe("Test file written successfully"); + done(); + }); + }); + + it("should work if re-exported by name", () => { + // @ts-ignore-next-line + const stream = new ReadStream_("test.txt"); + expect(stream instanceof ReadStream_).toBe(true); + expect(stream instanceof ReadStreamStar_).toBe(true); + expect(stream instanceof fs.ReadStream).toBe(true); + }); + + it("should work if re-exported by name, called without new", () => { + // @ts-ignore-next-line + const stream = ReadStream_("test.txt"); + expect(stream instanceof ReadStream_).toBe(true); + expect(stream instanceof ReadStreamStar_).toBe(true); + expect(stream instanceof fs.ReadStream).toBe(true); + }); + + it("should work if re-exported as export * from ...", () => { + // @ts-ignore-next-line + const stream = new ReadStreamStar_("test.txt"); + expect(stream instanceof ReadStreamStar_).toBe(true); + expect(stream instanceof ReadStream_).toBe(true); + expect(stream instanceof fs.ReadStream).toBe(true); + }); + + it("should work if re-exported as export * from ..., called without new", () => { + // @ts-ignore-next-line + const stream = ReadStreamStar_("test.txt"); + expect(stream instanceof ReadStreamStar_).toBe(true); + expect(stream instanceof ReadStream_).toBe(true); + expect(stream instanceof fs.ReadStream).toBe(true); + }); + + it("should be able to read from a file, with re-exported ReadStream", done => { + const pathToDir = `${tmpdir()}/${Date.now()}`; + mkdirForce(pathToDir); + const path = join(pathToDir, `fs-readstream-re-exported-test.txt`); + + writeFileSync(path, "Test file written successfully", { + encoding: "utf8", + flag: "w+", + }); + + // @ts-ignore-next-line + const stream = new ReadStream_(path); + stream.setEncoding("utf8"); + stream.on("error", e => { + done(e instanceof Error ? e : new Error(e)); + }); + + let data = ""; + + stream.on("data", chunk => { + data += chunk; + }); + + stream.on("end", () => { + expect(data).toBe("Test file written successfully"); + done(); + }); + }); +}); + +describe("createWriteStream", () => { + it("simple write stream finishes", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.createWriteStream.txt`; + const stream = createWriteStream(path); + stream.write("Test file written successfully"); + stream.end(); + + return await new Promise((resolve, reject) => { + stream.on("error", e => { + reject(e); + }); + + stream.on("finish", () => { + expect(readFileSync(path, "utf8")).toBe("Test file written successfully"); + resolve(true); + }); + }); + }); + + it("writing null throws ERR_STREAM_NULL_VALUES", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`; + const stream = createWriteStream(path); + try { + stream.write(null); + expect(() => {}).toThrow(Error); + } catch (exception: any) { + expect(exception.code).toBe("ERR_STREAM_NULL_VALUES"); + } + }); + + it("writing null throws ERR_STREAM_NULL_VALUES (objectMode: true)", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamNulls.txt`; + const stream = createWriteStream(path, { + // @ts-ignore-next-line + objectMode: true, + }); + try { + stream.write(null); + expect(() => {}).toThrow(Error); + } catch (exception: any) { + expect(exception.code).toBe("ERR_STREAM_NULL_VALUES"); + } + }); + + it("writing false throws ERR_INVALID_ARG_TYPE", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`; + const stream = createWriteStream(path); + try { + stream.write(false); + expect(() => {}).toThrow(Error); + } catch (exception: any) { + expect(exception.code).toBe("ERR_INVALID_ARG_TYPE"); + } + }); + + it("writing false throws ERR_INVALID_ARG_TYPE (objectMode: true)", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.createWriteStreamFalse.txt`; + const stream = createWriteStream(path, { + // @ts-ignore-next-line + objectMode: true, + }); + try { + stream.write(false); + expect(() => {}).toThrow(Error); + } catch (exception: any) { + expect(exception.code).toBe("ERR_INVALID_ARG_TYPE"); + } + }); +}); + +describe("fs/promises", () => { + const { exists, mkdir, readFile, rmdir, stat, writeFile } = promises; + + it("should not segfault on exception", async () => { + try { + await stat("foo/bar"); + } catch (e) {} + }); + + it("readFile", async () => { + const data = await readFile(import.meta.dir + "/readFileSync.txt", "utf8"); + expect(data).toBe("File read successfully"); + }); + + it("writeFile", async () => { + const path = `/tmp/fs.test.js/${Date.now()}.writeFile.txt`; + await writeFile(path, "File written successfully"); + expect(readFileSync(path, "utf8")).toBe("File written successfully"); + }); + + it("readdir()", async () => { + const files = await promises.readdir(import.meta.dir); + expect(files.length).toBeGreaterThan(0); + }); + + it("readdir() no args doesnt segfault", async () => { + const fizz = [ + [], + [Symbol("ok")], + [Symbol("ok"), Symbol("ok")], + [Symbol("ok"), Symbol("ok"), Symbol("ok")], + [Infinity, -NaN, -Infinity], + "\0\0\0\0", + "\r\n", + ]; + for (const args of fizz) { + try { + // check it doens't segfault when called with invalid arguments + await promises.readdir(...(args as [any, ...any[]])); + } catch (e) { + // check that producing the error doesn't cause any crashes + Bun.inspect(e); + } + } + }); + + describe("rmdir", () => { + it("removes a file", async () => { + const path = `/tmp/${Date.now()}.rm.txt`; + await writeFile(path, "File written successfully", "utf8"); + expect(await exists(path)).toBe(true); + try { + await rmdir(path); + expect(() => {}).toThrow(); + } catch (err: any) { + expect(err.code).toBe("ENOTDIR"); + // expect(err.message).toBe("Operation not permitted"); + expect(await exists(path)).toBe(true); + } + }); + + it("removes a dir", async () => { + const path = `/tmp/${Date.now()}.rm.dir`; + try { + await mkdir(path); + } catch (e) {} + expect(await exists(path)).toBe(true); + await rmdir(path); + expect(await exists(path)).toBe(false); + }); + // TODO support `recursive: true` + // it("removes a dir recursively", async () => { + // const path = `/tmp/${Date.now()}.rm.dir/foo/bar`; + // try { + // await mkdir(path, { recursive: true }); + // } catch (e) {} + // expect(await exists(path)).toBe(true); + // await rmdir(join(path, "../../"), { recursive: true }); + // expect(await exists(path)).toBe(false); + // }); + }); +}); + +it("fs.constants", () => { + expect(constants).toBeDefined(); + expect(constants.F_OK).toBeDefined(); + expect(constants.R_OK).toBeDefined(); + expect(constants.W_OK).toBeDefined(); + expect(constants.X_OK).toBeDefined(); + expect(constants.O_RDONLY).toBeDefined(); + expect(constants.O_WRONLY).toBeDefined(); + expect(constants.O_RDWR).toBeDefined(); + expect(constants.O_CREAT).toBeDefined(); + expect(constants.O_EXCL).toBeDefined(); + expect(constants.O_NOCTTY).toBeDefined(); + expect(constants.O_TRUNC).toBeDefined(); + expect(constants.O_APPEND).toBeDefined(); + expect(constants.O_DIRECTORY).toBeDefined(); + expect(constants.O_NOATIME).toBeDefined(); + expect(constants.O_NOFOLLOW).toBeDefined(); + expect(constants.O_SYNC).toBeDefined(); + expect(constants.O_DSYNC).toBeDefined(); + expect(constants.O_SYMLINK).toBeDefined(); + expect(constants.O_DIRECT).toBeDefined(); + expect(constants.O_NONBLOCK).toBeDefined(); + expect(constants.S_IFMT).toBeDefined(); + expect(constants.S_IFREG).toBeDefined(); + expect(constants.S_IFDIR).toBeDefined(); + expect(constants.S_IFCHR).toBeDefined(); + expect(constants.S_IFBLK).toBeDefined(); + expect(constants.S_IFIFO).toBeDefined(); + expect(constants.S_IFLNK).toBeDefined(); + expect(constants.S_IFSOCK).toBeDefined(); + expect(constants.S_IRWXU).toBeDefined(); + expect(constants.S_IRUSR).toBeDefined(); + expect(constants.S_IWUSR).toBeDefined(); + expect(constants.S_IXUSR).toBeDefined(); + expect(constants.S_IRWXG).toBeDefined(); + expect(constants.S_IRGRP).toBeDefined(); + expect(constants.S_IWGRP).toBeDefined(); + expect(constants.S_IXGRP).toBeDefined(); + expect(constants.S_IRWXO).toBeDefined(); + expect(constants.S_IROTH).toBeDefined(); + expect(constants.S_IWOTH).toBeDefined(); +}); + +it("fs.Dirent", () => { + expect(Dirent).toBeDefined(); +}); + +it("fs.Stats", () => { + expect(Stats).toBeDefined(); +}); + +it("repro 1516: can use undefined/null to specify default flag", () => { + const path = "/tmp/repro_1516.txt"; + writeFileSync(path, "b", { flag: undefined }); + // @ts-ignore-next-line + expect(readFileSync(path, { encoding: "utf8", flag: null })).toBe("b"); + rmSync(path); +}); diff --git a/test/bun.js/gc.js b/test/bun.js/gc.js deleted file mode 100644 index 3f9678f92..000000000 --- a/test/bun.js/gc.js +++ /dev/null @@ -1,26 +0,0 @@ -export function gc() { - Bun.gc(true); -} - -// we must ensure that finalizers are run -// so that the reference-counting logic is exercised -export function gcTick(trace = false) { - trace && console.trace(""); - // console.trace("hello"); - gc(); - return new Promise(resolve => { - setTimeout(resolve, 0); - }); -} - -export function withoutAggressiveGC(block) { - if (!Bun.unsafe.gcAggressionLevel) return block(); - - const origGC = Bun.unsafe.gcAggressionLevel(); - Bun.unsafe.gcAggressionLevel(0); - try { - return block(); - } finally { - Bun.unsafe.gcAggressionLevel(origGC); - } -} diff --git a/test/bun.js/gc.ts b/test/bun.js/gc.ts new file mode 100644 index 000000000..b9d80116d --- /dev/null +++ b/test/bun.js/gc.ts @@ -0,0 +1,26 @@ +export function gc(force: boolean = true) { + Bun.gc(force); +} + +// we must ensure that finalizers are run +// so that the reference-counting logic is exercised +export function gcTick(trace = false) { + trace && console.trace(""); + // console.trace("hello"); + gc(); + return new Promise(resolve => { + setTimeout(resolve, 0); + }); +} + +export function withoutAggressiveGC(block) { + if (!Bun.unsafe.gcAggressionLevel) return block(); + + const origGC = Bun.unsafe.gcAggressionLevel(); + Bun.unsafe.gcAggressionLevel(0); + try { + return block(); + } finally { + Bun.unsafe.gcAggressionLevel(origGC); + } +} -- cgit v1.2.3