From 70ee4418067b2df96e0dda0333c2c1c22427f438 Mon Sep 17 00:00:00 2001 From: Lonny Wong Date: Thu, 5 Oct 2023 15:52:57 +0800 Subject: [PATCH] fix fs async test --- src/nodefs.ts | 92 +++++++++++++++++++++++---------------------- test/nodefs.test.ts | 64 +++++++++++++++---------------- 2 files changed, 77 insertions(+), 79 deletions(-) diff --git a/src/nodefs.ts b/src/nodefs.ts index 6d63c1c..3896952 100644 --- a/src/nodefs.ts +++ b/src/nodefs.ts @@ -9,49 +9,52 @@ const fs = requireSafely("fs"); const path = requireSafely("path"); import { TrzszError, TrzszFileReader, TrzszFileWriter } from "./comm"; -function requireSafely(name) { +function requireSafely(name: string) { try { return require(name); } catch (err) { - return undefined; + return {}; } } -function promisify (fs: any, funcs: string[]) { +function promisify(fs: any, funcs: string[]) { for (const func of funcs) { - fs[func + 'Async'] = (...args) => { + fs[func + "Async"] = (...args: any) => { return new Promise((resolve, reject) => { - fs[func](...args, (err, data) => { + fs[func](...args, (err: Error, data: any) => { if (err) { - return reject(err); + reject(err); } else { - resolve(data || true); + resolve(data); } - }) - }) - } + }); + }); + }; } } -promisify( - fs, - [ - 'stat', - 'access', - 'mkdir', - 'readdir', - 'read', - 'close', - 'open', - 'realpath', - 'write' - ] -); - -function fsExistsAsync (fp: string) { - return fs.accessAsync(fp) - .then(() => true) - .catch(() => false) +promisify(fs, ["stat", "access", "mkdir", "readdir", "close", "open", "realpath", "write"]); + +async function fsExists(path: string) { + return new Promise((resolve) => fs.exists(path, (exists: boolean) => resolve(exists))); +} + +async function fsRead( + fd: number, + buffer: Uint8Array, + offset: number, + length: number, + position: number | null, +): Promise { + return new Promise((resolve, reject) => + fs.read(fd, buffer, offset, length, position, (err: Error, bytesRead: number, buffer: Buffer) => { + if (err) { + reject(err); + } else { + resolve(buffer.subarray(0, bytesRead)); + } + }), + ); } export async function checkPathWritable(filePath: string) { @@ -59,7 +62,7 @@ export async function checkPathWritable(filePath: string) { return false; } - if (!await fsExistsAsync(filePath)) { + if (!(await fsExists(filePath))) { throw new TrzszError(`No such directory: ${filePath}`); } const stats = await fs.statAsync(filePath); @@ -116,8 +119,7 @@ class NodefsFileReader implements TrzszFileReader { this.fd = await fs.openAsync(this.absPath, "r"); } const uint8 = new Uint8Array(buf); - const n = await fs.readAsync(this.fd, uint8, 0, uint8.length, null); - return uint8.subarray(0, n); + return fsRead(this.fd, uint8, 0, uint8.length, null); } public async closeFile() { @@ -137,7 +139,7 @@ async function checkPathReadable( stats: any, fileList: NodefsFileReader[], relPath: string[], - visitedDir: Set + visitedDir: Set, ) { if (!stats.isDirectory()) { if (!stats.isFile()) { @@ -158,26 +160,26 @@ async function checkPathReadable( } visitedDir.add(realPath); fileList.push(new NodefsFileReader(pathId, absPath, relPath, true, 0)); - const arr = await fs.readdirAsync(absPath) + const arr = await fs.readdirAsync(absPath); for (const file of arr) { const filePath = path.join(absPath, file); - const stat = await fs.statAsync(filePath) + const stat = await fs.statAsync(filePath); await checkPathReadable(pathId, filePath, stat, fileList, [...relPath, file], visitedDir); } } export async function checkPathsReadable( filePaths: string[] | undefined, - directory: boolean = false + directory: boolean = false, ): Promise { if (!filePaths || !filePaths.length) { return undefined; } const fileList: NodefsFileReader[] = []; - const entries = filePaths.entries() + const entries = filePaths.entries(); for (const [idx, filePath] of entries) { const absPath = path.resolve(filePath); - if (!await fsExistsAsync(absPath)) { + if (!(await fsExists(absPath))) { throw new TrzszError(`No such file: ${absPath}`); } const stats = await fs.statAsync(absPath); @@ -197,7 +199,7 @@ class NodefsFileWriter implements TrzszFileWriter { private dir: boolean; private closed: boolean = false; - constructor(fileName, localName: string, fd: number | null, dir: boolean = false) { + constructor(fileName: string, localName: string, fd: number | null, dir: boolean = false) { this.fileName = fileName; this.localName = localName; this.fd = fd; @@ -232,21 +234,21 @@ class NodefsFileWriter implements TrzszFileWriter { } async function getNewName(savePath: string, fileName: string) { - if (!fsExistsAsync(path.join(savePath, fileName))) { + if (!(await fsExists(path.join(savePath, fileName)))) { return fileName; } for (let i = 0; i < 1000; i++) { const saveName = `${fileName}.${i}`; - if (!await fsExistsAsync(path.join(savePath, saveName))) { + if (!(await fsExists(path.join(savePath, saveName)))) { return saveName; } } throw new TrzszError("Fail to assign new file name"); } -function doCreateFile(absPath: string) { +async function doCreateFile(absPath: string) { try { - return fs.openAsync(absPath, "w"); + return await fs.openAsync(absPath, "w"); } catch (err) { if (err.errno === -13 || err.errno === -4048) { throw new TrzszError(`No permission to write: ${absPath}`); @@ -258,7 +260,7 @@ function doCreateFile(absPath: string) { } async function doCreateDirectory(absPath: string) { - if (!await fsExistsAsync(absPath)) { + if (!(await fsExists(absPath))) { await fs.mkdirAsync(absPath, { recursive: true, mode: 0o755 }); } const stats = await fs.statAsync(absPath); @@ -267,7 +269,7 @@ async function doCreateDirectory(absPath: string) { } } -async function createFile(savePath, fileName: string, overwrite: boolean) { +async function createFile(savePath: string, fileName: string, overwrite: boolean) { const localName = overwrite ? fileName : await getNewName(savePath, fileName); const fd = await doCreateFile(path.join(savePath, localName)); return new NodefsFileWriter(fileName, localName, fd); diff --git a/test/nodefs.test.ts b/test/nodefs.test.ts index c9c95ec..f99c728 100644 --- a/test/nodefs.test.ts +++ b/test/nodefs.test.ts @@ -52,42 +52,42 @@ test("require fs and path", () => { }); test("check paths readable", async () => { - expect(checkPathsReadable(undefined)).toBe(undefined); - expect(checkPathsReadable([])).toBe(undefined); + expect(await checkPathsReadable(undefined)).toBe(undefined); + expect(await checkPathsReadable([])).toBe(undefined); fs.chmodSync(tmpFile, 0o444); expect(((await checkPathsReadable([tmpFile])) as TrzszFileReader[]).length).toBe(1); expect(((await checkPathsReadable([linkPath])) as TrzszFileReader[]).length).toBe(1); - expect(() => checkPathsReadable([notExistFile])).toThrowError("No such file"); - expect(() => checkPathsReadable([tmpFile, notExistFile])).toThrowError("No such file"); + await expect(checkPathsReadable([notExistFile])).rejects.toThrowError("No such file"); + await expect(checkPathsReadable([tmpFile, notExistFile])).rejects.toThrowError("No such file"); - expect(() => checkPathsReadable([tmpDir])).toThrowError("Is a directory"); - expect(() => checkPathsReadable([tmpFile, tmpDir])).toThrowError("Is a directory"); + await expect(checkPathsReadable([tmpDir])).rejects.toThrowError("Is a directory"); + await expect(checkPathsReadable([tmpFile, tmpDir])).rejects.toThrowError("Is a directory"); if (process.platform !== "win32" && require("os").userInfo().uid != 0) { - expect(() => checkPathsReadable(["/dev/stdin"])).toThrowError("Not a regular file"); - expect(() => checkPathsReadable([tmpFile, "/dev/stdin"])).toThrowError("Not a regular file"); + await expect(checkPathsReadable(["/dev/stdin"])).rejects.toThrowError("Not a regular file"); + await expect(checkPathsReadable([tmpFile, "/dev/stdin"])).rejects.toThrowError("Not a regular file"); fs.chmodSync(tmpFile, 0o222); - expect(() => checkPathsReadable([tmpFile])).toThrowError("No permission to read"); + await expect(checkPathsReadable([tmpFile])).rejects.toThrowError("No permission to read"); fs.chmodSync(tmpFile, 0o444); } }); test("check path writable", async () => { - expect(await checkPathWritable(undefined)).toBe(false); - expect(await checkPathWritable(null)).toBe(false); + expect(await checkPathWritable(undefined as any)).toBe(false); + expect(await checkPathWritable(null as any)).toBe(false); fs.chmodSync(tmpDir, 0o777); expect(await checkPathWritable(tmpDir)).toBe(true); - expect(() => checkPathWritable(notExistFile)).toThrowError("No such directory"); - expect(() => checkPathWritable(tmpFile)).toThrowError("Not a directory"); + await expect(checkPathWritable(notExistFile)).rejects.toThrowError("No such directory"); + await expect(checkPathWritable(tmpFile)).rejects.toThrowError("Not a directory"); if (process.platform !== "win32" && require("os").userInfo().uid != 0) { fs.chmodSync(tmpDir, 0o444); - expect(() => checkPathWritable(tmpDir)).toThrowError("No permission to write"); + await expect(checkPathWritable(tmpDir)).rejects.toThrowError("No permission to write"); fs.chmodSync(tmpDir, 0o777); } }); @@ -98,7 +98,7 @@ test("open send files success", async () => { fs.writeSync(fd, "test file content"); fs.closeSync(fd); - const tfr = await checkPathsReadable([testPath])[0]; + const tfr = ((await checkPathsReadable([testPath])) as TrzszFileReader[])[0]; expect(tfr.getPathId()).toBe(0); expect(tfr.getRelPath()).toStrictEqual(["send.txt"]); @@ -114,12 +114,12 @@ test("open send files success", async () => { expect(await tfr.readFile(buf)).toStrictEqual(strToUint8("")); tfr.closeFile(); - await expect(await tfr.readFile(buf)).rejects.toThrowError("File closed"); + await expect(tfr.readFile(buf)).rejects.toThrowError("File closed"); }); test("open send files error", async () => { - expect(() => checkPathsReadable([notExistFile])).toThrowError("No such file"); - expect(() => checkPathsReadable([tmpFile, notExistFile])).toThrowError("No such file"); + await expect(checkPathsReadable([notExistFile])).rejects.toThrowError("No such file"); + await expect(checkPathsReadable([tmpFile, notExistFile])).rejects.toThrowError("No such file"); }); test("open save file success", async () => { @@ -145,11 +145,10 @@ test("open save file success", async () => { tfr.closeFile(); expect(fs.readFileSync(path.join(tmpDir, "save.txt")).toString()).toBe("test file content"); - const existsSync = fs.existsSync; - fs.existsSync = jest.fn(); - fs.existsSync.mockReturnValue(true); - await expect(await openSaveFile(saveParam, "save.txt", false, false)).rejects.toThrowError("Fail to assign new file name"); - fs.existsSync = existsSync; + const exists = fs.exists; + fs.exists = (_path: string, callback: Function) => callback(true); + await expect(openSaveFile(saveParam, "save.txt", false, false)).rejects.toThrowError("Fail to assign new file name"); + fs.exists = exists; }); test("open save file error", async () => { @@ -161,15 +160,12 @@ test("open save file error", async () => { } fs.mkdirSync(path.join(tmpDir, "isdir")); - await expect(await openSaveFile(saveParam, "isdir", false, true)).rejects.toThrowError("Is a directory"); + await expect(openSaveFile(saveParam, "isdir", false, true)).rejects.toThrowError("Is a directory"); - const openSync = fs.openSync; - fs.openSync = jest.fn(); - fs.openSync.mockImplementation(() => { - throw new Error("other error"); - }); - await expect(await openSaveFile(saveParam, "other.txt", false, false)).rejects.toThrowError("other error"); - fs.openSync = openSync; + const open = fs.open; + fs.open = (_path: string, _flags: string, callback: Function) => callback(new Error("other error")); + await expect(openSaveFile(saveParam, "other.txt", false, false)).rejects.toThrowError("other error"); + fs.open = open; }); test("open directory success", async () => { @@ -180,7 +176,7 @@ test("open directory success", async () => { fs.writeSync(fd, "test file content"); fs.closeSync(fd); - const fileList = await checkPathsReadable([testDir], true) as TrzszFileReader[]; + const fileList = (await checkPathsReadable([testDir], true)) as TrzszFileReader[]; expect(fileList[0].getPathId()).toBe(0); expect(fileList[0].getRelPath()).toStrictEqual(["testdir"]); expect(fileList[0].isDir()).toBe(true); @@ -209,7 +205,7 @@ test("open directory error", async () => { if (process.platform !== "win32") { const linkDir = path.join(testDir, "link"); fs.symlinkSync(testDir, linkDir); - expect(() => checkPathsReadable([testDir], true)).toThrowError("Duplicate link"); + await expect(checkPathsReadable([testDir], true)).rejects.toThrowError("Duplicate link"); } }); @@ -280,7 +276,7 @@ test("save directory error", async () => { const fileName = { path_id: 0, path_name: ["a"], - is_dir: true, + is_dir: true as boolean | undefined, }; const saveParam = { path: testDir, maps: new Map() }; await expect(openSaveFile(saveParam, JSON.stringify(fileName), true, true)).rejects.toThrowError("Not a directory");