Add fs/promises tests and migrate fs.test.js to TypeScript (#2279)

This commit is contained in:
Colin McDonnell
2023-03-02 17:36:47 -08:00
committed by GitHub
parent 27c3579118
commit b469e50351
7 changed files with 103 additions and 42 deletions

View File

@@ -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"

View File

@@ -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<typeof import("fs/promises")>;
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";
}

View File

@@ -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<void>;
/**
* 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<boolean>;
/**
* @deprecated Use `fs.promises.rm()` instead.
*
* Asynchronously remove a directory.
*
* ```ts
* import { rmdir } from 'fs/promises';
*
* // remove a directory
* await rmdir('/tmp/mydir'); // Promise<void>
* ```
*
* To remove a directory recursively, use `fs.promises.rm()` instead, with the `recursive` option set to `true`.
*/
function rmdir(
path: PathLike,
options?: Omit<RmDirOptions, "recursive">,
): Promise<void>;
}
declare module "node:fs/promises" {
import * as fsPromises from "fs/promises";
export = fsPromises;
export * from "fs/promises";
}

View File

@@ -0,0 +1,6 @@
import * as tsd from "tsd";
import * as fs from "fs";
import { exists } from "fs/promises";
tsd.expectType<Promise<boolean>>(exists("/etc/passwd"));
tsd.expectType<Promise<boolean>>(fs.promises.exists("/etc/passwd"));

View File

@@ -150,7 +150,7 @@ test("EventEmitter GCs", () => {
(function () {
Bun.gc(true);
function EventEmitterSubclass() {
function EventEmitterSubclass(this: any) {
EventEmitter.call(this);
}

View File

@@ -26,6 +26,9 @@ import fs, {
Dirent,
Stats,
} from "node:fs";
import _promises from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -49,7 +52,7 @@ 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);
expect(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
// that don't exist
copyFileSync(import.meta.path, tempdir + "/copyFileSync.js");
@@ -67,7 +70,7 @@ describe("copyFileSync", () => {
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);
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;
@@ -92,7 +95,7 @@ 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(tempdir.includes(mkdirSync(tempdir, { recursive: true })!)).toBe(true);
expect(existsSync(tempdir)).toBe(true);
});
});
@@ -135,6 +138,7 @@ it("mkdtempSync, readdirSync, rmdirSync and unlinkSync with non-ascii", () => {
});
it("mkdtempSync() empty name", () => {
// @ts-ignore-next-line
const tempdir = mkdtempSync();
expect(existsSync(tempdir)).toBe(true);
writeFileSync(tempdir + "/non-ascii-👍.txt", "hello");
@@ -188,7 +192,7 @@ it("readdirSync throws when given a file path", () => {
try {
readdirSync(import.meta.path);
throw new Error("should not get here");
} catch (exception) {
} catch (exception: any) {
expect(exception.name).toBe("ENOTDIR");
}
});
@@ -197,7 +201,7 @@ 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) {
} catch (exception: any) {
expect(exception.name).toBe("ENOTDIR");
}
});
@@ -206,7 +210,7 @@ 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) {
} catch (exception: any) {
expect(exception.name).toBe("ENOTDIR");
}
});
@@ -455,7 +459,7 @@ describe("stat", () => {
try {
statSync("/tmp/doesntexist");
throw "statSync should throw";
} catch (e) {
} catch (e: any) {
expect(e.code).toBe("ENOENT");
}
});
@@ -499,8 +503,8 @@ describe("rmdir", () => {
rmdir(path, err => {
try {
expect(err).toBeDefined();
expect(err.code).toBe("EPERM");
expect(err.message).toBe("Operation not permitted");
expect(err!.code).toBe("EPERM");
expect(err!.message).toBe("Operation not permitted");
expect(existsSync(path)).toBe(true);
} catch (e) {
return done(e);
@@ -621,6 +625,7 @@ describe("fs.WriteStream", () => {
});
it("should be constructable", () => {
// @ts-ignore-next-line
const stream = new fs.WriteStream("test.txt");
expect(stream instanceof fs.WriteStream).toBe(true);
});
@@ -630,6 +635,7 @@ describe("fs.WriteStream", () => {
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();
@@ -645,6 +651,7 @@ describe("fs.WriteStream", () => {
});
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);
@@ -652,6 +659,7 @@ describe("fs.WriteStream", () => {
});
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);
@@ -659,6 +667,7 @@ describe("fs.WriteStream", () => {
});
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);
@@ -666,6 +675,7 @@ describe("fs.WriteStream", () => {
});
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);
@@ -676,7 +686,7 @@ describe("fs.WriteStream", () => {
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();
@@ -698,6 +708,7 @@ describe("fs.ReadStream", () => {
});
it("should be constructable", () => {
// @ts-ignore-next-line
const stream = new fs.ReadStream("test.txt");
expect(stream instanceof fs.ReadStream).toBe(true);
});
@@ -711,7 +722,7 @@ describe("fs.ReadStream", () => {
encoding: "utf8",
flag: "w+",
});
// @ts-ignore-next-line
const stream = new fs.ReadStream(path);
stream.setEncoding("utf8");
stream.on("error", e => {
@@ -731,6 +742,7 @@ describe("fs.ReadStream", () => {
});
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);
@@ -738,6 +750,7 @@ describe("fs.ReadStream", () => {
});
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);
@@ -745,6 +758,7 @@ describe("fs.ReadStream", () => {
});
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);
@@ -752,6 +766,7 @@ describe("fs.ReadStream", () => {
});
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);
@@ -768,6 +783,7 @@ describe("fs.ReadStream", () => {
flag: "w+",
});
// @ts-ignore-next-line
const stream = new ReadStream_(path);
stream.setEncoding("utf8");
stream.on("error", e => {
@@ -812,7 +828,7 @@ describe("createWriteStream", () => {
try {
stream.write(null);
expect(() => {}).toThrow(Error);
} catch (exception) {
} catch (exception: any) {
expect(exception.code).toBe("ERR_STREAM_NULL_VALUES");
}
});
@@ -820,12 +836,13 @@ describe("createWriteStream", () => {
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) {
} catch (exception: any) {
expect(exception.code).toBe("ERR_STREAM_NULL_VALUES");
}
});
@@ -836,7 +853,7 @@ describe("createWriteStream", () => {
try {
stream.write(false);
expect(() => {}).toThrow(Error);
} catch (exception) {
} catch (exception: any) {
expect(exception.code).toBe("ERR_INVALID_ARG_TYPE");
}
});
@@ -844,12 +861,13 @@ describe("createWriteStream", () => {
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) {
} catch (exception: any) {
expect(exception.code).toBe("ERR_INVALID_ARG_TYPE");
}
});
@@ -893,7 +911,7 @@ describe("fs/promises", () => {
for (const args of fizz) {
try {
// check it doens't segfault when called with invalid arguments
await promises.readdir(...args);
await promises.readdir(...(args as [any, ...any[]]));
} catch (e) {
// check that producing the error doesn't cause any crashes
Bun.inspect(e);
@@ -909,7 +927,7 @@ describe("fs/promises", () => {
try {
await rmdir(path);
expect(() => {}).toThrow();
} catch (err) {
} catch (err: any) {
expect(err.code).toBe("ENOTDIR");
// expect(err.message).toBe("Operation not permitted");
expect(await exists(path)).toBe(true);
@@ -992,6 +1010,7 @@ it("fs.Stats", () => {
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);
});

View File

@@ -1,5 +1,5 @@
export function gc() {
Bun.gc(true);
export function gc(force: boolean = true) {
Bun.gc(force);
}
// we must ensure that finalizers are run