Skip to content

Commit

Permalink
fix fs async test
Browse files Browse the repository at this point in the history
  • Loading branch information
lonnywong committed Oct 5, 2023
1 parent 6ba5bcd commit b665847
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 79 deletions.
92 changes: 47 additions & 45 deletions src/nodefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,57 +9,60 @@ 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<Uint8Array> {
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) {
if (!filePath) {
return false;
}

if (!await fsExistsAsync(filePath)) {
if (!(await fsExists(filePath))) {
throw new TrzszError(`No such directory: ${filePath}`);
}
const stats = await fs.statAsync(filePath);
Expand Down Expand Up @@ -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() {
Expand All @@ -137,7 +139,7 @@ async function checkPathReadable(
stats: any,
fileList: NodefsFileReader[],
relPath: string[],
visitedDir: Set<string>
visitedDir: Set<string>,
) {
if (!stats.isDirectory()) {
if (!stats.isFile()) {
Expand All @@ -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<TrzszFileReader[] | undefined> {
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);
Expand All @@ -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;
Expand Down Expand Up @@ -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}`);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
64 changes: 30 additions & 34 deletions test/nodefs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
Expand All @@ -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"]);
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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);
Expand Down Expand Up @@ -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");
}
});

Expand Down Expand Up @@ -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<string, string>() };
await expect(openSaveFile(saveParam, JSON.stringify(fileName), true, true)).rejects.toThrowError("Not a directory");
Expand Down

0 comments on commit b665847

Please sign in to comment.