mirror of https://git.tuxpa.in/a/code-server.git
Move and refactor fs tests
This commit is contained in:
parent
2889b3fede
commit
d80f82ab98
|
@ -1,5 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "@coder/ide",
|
"name": "@coder/ide",
|
||||||
"description": "Browser-based IDE client abstraction.",
|
"description": "Browser-based IDE client abstraction.",
|
||||||
"main": "src/index.ts"
|
"main": "src/index.ts",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/rimraf": "^2.0.2",
|
||||||
|
"rimraf": "^2.6.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { retry } from "../retry";
|
||||||
* A connection based on a web socket. Automatically reconnects and buffers
|
* A connection based on a web socket. Automatically reconnects and buffers
|
||||||
* messages during connection.
|
* messages during connection.
|
||||||
*/
|
*/
|
||||||
class Connection implements ReadWriteConnection {
|
class WebsocketConnection implements ReadWriteConnection {
|
||||||
private activeSocket: WebSocket | undefined;
|
private activeSocket: WebSocket | undefined;
|
||||||
private readonly messageBuffer = <Uint8Array[]>[];
|
private readonly messageBuffer = <Uint8Array[]>[];
|
||||||
private readonly socketTimeoutDelay = 60 * 1000;
|
private readonly socketTimeoutDelay = 60 * 1000;
|
||||||
|
@ -129,4 +129,4 @@ class Connection implements ReadWriteConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global instance so all fills can use the same client.
|
// Global instance so all fills can use the same client.
|
||||||
export const client = new Client(new Connection());
|
export const client = new Client(new WebsocketConnection());
|
||||||
|
|
|
@ -116,7 +116,7 @@ class FS {
|
||||||
const ae = this.client.run((ae, path, options) => {
|
const ae = this.client.run((ae, path, options) => {
|
||||||
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
const fs = __non_webpack_require__("fs") as typeof import("fs");
|
||||||
const str = fs.createWriteStream(path, options);
|
const str = fs.createWriteStream(path, options);
|
||||||
ae.on("write", (d) => str.write(_Buffer.from(d, "utf8")));
|
ae.on("write", (d: string) => str.write(_Buffer.from(d, "utf8")));
|
||||||
ae.on("close", () => str.close());
|
ae.on("close", () => str.close());
|
||||||
str.on("close", () => ae.emit("close"));
|
str.on("close", () => ae.emit("close"));
|
||||||
str.on("open", (fd) => ae.emit("open", fd));
|
str.on("open", (fd) => ae.emit("open", fd));
|
||||||
|
@ -141,7 +141,7 @@ class FS {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
ae.on("open", (a) => this.emit("open", a));
|
ae.on("open", (fd: number) => this.emit("open", fd));
|
||||||
ae.on("close", () => this.emit("close"));
|
ae.on("close", () => this.emit("close"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,7 +597,15 @@ class FS {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public write = <TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number | undefined, length: number | undefined, position: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void), callback?: (err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void): void => {
|
public write = <TBuffer extends Buffer | Uint8Array>(fd: number, buffer: TBuffer, offset: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void), length: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void), position: number | undefined | ((err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void), callback?: (err: NodeJS.ErrnoException, written: number, buffer: TBuffer) => void): void => {
|
||||||
|
if (typeof offset === "function") {
|
||||||
|
callback = offset;
|
||||||
|
offset = undefined;
|
||||||
|
}
|
||||||
|
if (typeof length === "function") {
|
||||||
|
callback = length;
|
||||||
|
length = undefined;
|
||||||
|
}
|
||||||
if (typeof position === "function") {
|
if (typeof position === "function") {
|
||||||
callback = position;
|
callback = position;
|
||||||
position = undefined;
|
position = undefined;
|
||||||
|
@ -662,9 +670,9 @@ class FS {
|
||||||
return new class Watcher extends EventEmitter implements fs.FSWatcher {
|
return new class Watcher extends EventEmitter implements fs.FSWatcher {
|
||||||
public constructor() {
|
public constructor() {
|
||||||
super();
|
super();
|
||||||
ae.on("change", (event, filename) => this.emit("change", event, filename));
|
ae.on("change", (event: string, filename: string) => this.emit("change", event, filename));
|
||||||
ae.on("error", (error) => this.emit("error", error));
|
ae.on("error", (error: Error) => this.emit("error", error));
|
||||||
ae.on("listener", (event, filename) => listener && listener(event, filename));
|
ae.on("listener", (event: string, filename: string) => listener && listener(event, filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
public close(): void {
|
public close(): void {
|
||||||
|
|
|
@ -0,0 +1,584 @@
|
||||||
|
import * as nativeFs from "fs";
|
||||||
|
import * as os from "os";
|
||||||
|
import * as path from "path";
|
||||||
|
import * as util from "util";
|
||||||
|
import * as rimraf from "rimraf";
|
||||||
|
import { createClient } from "@coder/protocol/test";
|
||||||
|
|
||||||
|
const client = createClient();
|
||||||
|
jest.mock("../src/fill/client", () => ({ client }));
|
||||||
|
const fs = require("../src/fill/fs") as typeof import("fs");
|
||||||
|
|
||||||
|
describe("fs", () => {
|
||||||
|
let i = 0;
|
||||||
|
const coderDir = path.join(os.tmpdir(), "coder");
|
||||||
|
const testFile = path.join(__dirname, "fs.test.ts");
|
||||||
|
const tmpFile = (): string => path.join(coderDir, `${i++}`);
|
||||||
|
const createTmpFile = async (): Promise<string> => {
|
||||||
|
const tf = tmpFile();
|
||||||
|
await util.promisify(nativeFs.writeFile)(tf, "");
|
||||||
|
|
||||||
|
return tf;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await util.promisify(rimraf)(coderDir);
|
||||||
|
await util.promisify(nativeFs.mkdir)(coderDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("access", () => {
|
||||||
|
it("should access existing file", async () => {
|
||||||
|
await expect(util.promisify(fs.access)(testFile))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to access nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.access)(tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("append", () => {
|
||||||
|
it("should append to existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
await expect(util.promisify(fs.appendFile)(file, "howdy"))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
expect(await util.promisify(nativeFs.readFile)(file, "utf8"))
|
||||||
|
.toEqual("howdy");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create then append to nonexistent file", async () => {
|
||||||
|
const file = tmpFile();
|
||||||
|
await expect(util.promisify(fs.appendFile)(file, "howdy"))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
expect(await util.promisify(nativeFs.readFile)(file, "utf8"))
|
||||||
|
.toEqual("howdy");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to append to file in nonexistent directory", async () => {
|
||||||
|
const file = path.join(tmpFile(), "nope");
|
||||||
|
await expect(util.promisify(fs.appendFile)(file, "howdy"))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
expect(await util.promisify(nativeFs.exists)(file))
|
||||||
|
.toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("chmod", () => {
|
||||||
|
it("should chmod existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
await expect(util.promisify(fs.chmod)(file, "755"))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to chmod nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.chmod)(tmpFile(), "755"))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("chown", () => {
|
||||||
|
it("should chown existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
await expect(util.promisify(fs.chown)(file, 1, 1))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to chown nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.chown)(tmpFile(), 1, 1))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("close", () => {
|
||||||
|
it("should close opened file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
const fd = await util.promisify(nativeFs.open)(file, "r");
|
||||||
|
await expect(util.promisify(fs.close)(fd))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to close non-opened file", async () => {
|
||||||
|
await expect(util.promisify(fs.close)(99999999))
|
||||||
|
.rejects.toThrow("EBADF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("copyFile", () => {
|
||||||
|
it("should copy existing file", async () => {
|
||||||
|
const source = await createTmpFile();
|
||||||
|
const destination = tmpFile();
|
||||||
|
await expect(util.promisify(fs.copyFile)(source, destination))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await expect(util.promisify(fs.exists)(destination))
|
||||||
|
.resolves.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to copy nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.copyFile)(tmpFile(), tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("createWriteStream", () => {
|
||||||
|
it("should write to file", async () => {
|
||||||
|
const file = tmpFile();
|
||||||
|
const content = "howdy\nhow\nr\nu";
|
||||||
|
const stream = fs.createWriteStream(file);
|
||||||
|
stream.on("open", (fd) => {
|
||||||
|
expect(fd).toBeDefined();
|
||||||
|
stream.write(content);
|
||||||
|
stream.close();
|
||||||
|
});
|
||||||
|
await expect(new Promise((resolve): void => {
|
||||||
|
stream.on("close", async () => {
|
||||||
|
resolve(await util.promisify(nativeFs.readFile)(file, "utf8"));
|
||||||
|
});
|
||||||
|
})).resolves.toBe(content);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("exists", () => {
|
||||||
|
it("should output file exists", async () => {
|
||||||
|
await expect(util.promisify(fs.exists)(testFile))
|
||||||
|
.resolves.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should output file does not exist", async () => {
|
||||||
|
await expect(util.promisify(fs.exists)(tmpFile()))
|
||||||
|
.resolves.toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fchmod", () => {
|
||||||
|
it("should fchmod existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
const fd = await util.promisify(nativeFs.open)(file, "r");
|
||||||
|
await expect(util.promisify(fs.fchmod)(fd, "755"))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await util.promisify(nativeFs.close)(fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to fchmod nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.fchmod)(2242342, "755"))
|
||||||
|
.rejects.toThrow("EBADF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fchown", () => {
|
||||||
|
it("should fchown existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
const fd = await util.promisify(nativeFs.open)(file, "r");
|
||||||
|
await expect(util.promisify(fs.fchown)(fd, 1, 1))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await util.promisify(nativeFs.close)(fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to fchown nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.fchown)(99999, 1, 1))
|
||||||
|
.rejects.toThrow("EBADF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fdatasync", () => {
|
||||||
|
it("should fdatasync existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
const fd = await util.promisify(nativeFs.open)(file, "r");
|
||||||
|
await expect(util.promisify(fs.fdatasync)(fd))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await util.promisify(nativeFs.close)(fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to fdatasync nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.fdatasync)(99999))
|
||||||
|
.rejects.toThrow("EBADF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fstat", () => {
|
||||||
|
it("should fstat existing file", async () => {
|
||||||
|
const fd = await util.promisify(nativeFs.open)(testFile, "r");
|
||||||
|
const stat = await util.promisify(nativeFs.fstat)(fd);
|
||||||
|
await expect(util.promisify(fs.fstat)(fd))
|
||||||
|
.resolves.toMatchObject({
|
||||||
|
size: stat.size,
|
||||||
|
});
|
||||||
|
await util.promisify(nativeFs.close)(fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to fstat", async () => {
|
||||||
|
await expect(util.promisify(fs.fstat)(9999))
|
||||||
|
.rejects.toThrow("EBADF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fsync", () => {
|
||||||
|
it("should fsync existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
const fd = await util.promisify(nativeFs.open)(file, "r");
|
||||||
|
await expect(util.promisify(fs.fsync)(fd))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await util.promisify(nativeFs.close)(fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to fsync nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.fsync)(99999))
|
||||||
|
.rejects.toThrow("EBADF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("ftruncate", () => {
|
||||||
|
it("should ftruncate existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
const fd = await util.promisify(nativeFs.open)(file, "w");
|
||||||
|
await expect(util.promisify(fs.ftruncate)(fd, 1))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await util.promisify(nativeFs.close)(fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to ftruncate nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.ftruncate)(99999, 9999))
|
||||||
|
.rejects.toThrow("EBADF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("futimes", () => {
|
||||||
|
it("should futimes existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
const fd = await util.promisify(nativeFs.open)(file, "w");
|
||||||
|
await expect(util.promisify(fs.futimes)(fd, 1, 1))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await util.promisify(nativeFs.close)(fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to futimes nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.futimes)(99999, 9999, 9999))
|
||||||
|
.rejects.toThrow("EBADF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("lchmod", () => {
|
||||||
|
it("should lchmod existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
await expect(util.promisify(fs.lchmod)(file, "755"))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Doesn't fail on my system?
|
||||||
|
it("should fail to lchmod nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.lchmod)(tmpFile(), "755"))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("lchown", () => {
|
||||||
|
it("should lchown existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
await expect(util.promisify(fs.lchown)(file, 1, 1))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Doesn't fail on my system?
|
||||||
|
it("should fail to lchown nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.lchown)(tmpFile(), 1, 1))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("link", () => {
|
||||||
|
it("should link existing file", async () => {
|
||||||
|
const source = await createTmpFile();
|
||||||
|
const destination = tmpFile();
|
||||||
|
await expect(util.promisify(fs.link)(source, destination))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await expect(util.promisify(fs.exists)(destination))
|
||||||
|
.resolves.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to link nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.link)(tmpFile(), tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("lstat", () => {
|
||||||
|
it("should lstat existing file", async () => {
|
||||||
|
const stat = await util.promisify(nativeFs.lstat)(testFile);
|
||||||
|
await expect(util.promisify(fs.lstat)(testFile))
|
||||||
|
.resolves.toMatchObject({
|
||||||
|
size: stat.size,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to lstat non-existent file", async () => {
|
||||||
|
await expect(util.promisify(fs.lstat)(tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("mkdir", () => {
|
||||||
|
const target = tmpFile();
|
||||||
|
it("should create nonexistent directory", async () => {
|
||||||
|
await expect(util.promisify(fs.mkdir)(target))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to create existing directory", async () => {
|
||||||
|
await expect(util.promisify(fs.mkdir)(target))
|
||||||
|
.rejects.toThrow("EEXIST");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("mkdtemp", () => {
|
||||||
|
it("should create temp dir", async () => {
|
||||||
|
await expect(util.promisify(fs.mkdtemp)(coderDir + "/"))
|
||||||
|
.resolves.toMatch(/^\/tmp\/coder\/[a-zA-Z0-9]{6}/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("open", () => {
|
||||||
|
it("should open existing file", async () => {
|
||||||
|
const fd = await util.promisify(fs.open)(testFile, "r");
|
||||||
|
expect(fd).not.toBeNaN();
|
||||||
|
await expect(util.promisify(fs.close)(fd))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to open nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.open)(tmpFile(), "r"))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("read", () => {
|
||||||
|
it("should read existing file", async () => {
|
||||||
|
const fd = await util.promisify(nativeFs.open)(testFile, "r");
|
||||||
|
const stat = await util.promisify(nativeFs.fstat)(fd);
|
||||||
|
const buffer = new Buffer(stat.size);
|
||||||
|
let bytesRead = 0;
|
||||||
|
let chunkSize = 2048;
|
||||||
|
while (bytesRead < stat.size) {
|
||||||
|
if ((bytesRead + chunkSize) > stat.size) {
|
||||||
|
chunkSize = stat.size - bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
await util.promisify(fs.read)(fd, buffer, bytesRead, chunkSize, bytesRead);
|
||||||
|
bytesRead += chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await util.promisify(nativeFs.readFile)(testFile, "utf8");
|
||||||
|
expect(buffer.toString()).toEqual(content);
|
||||||
|
await util.promisify(nativeFs.close)(fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to read nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.read)(99999, new Buffer(10), 9999, 999, 999))
|
||||||
|
.rejects.toThrow("EBADF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("readFile", () => {
|
||||||
|
it("should read existing file", async () => {
|
||||||
|
const content = await util.promisify(nativeFs.readFile)(testFile, "utf8");
|
||||||
|
await expect(util.promisify(fs.readFile)(testFile, "utf8"))
|
||||||
|
.resolves.toEqual(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to read nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.readFile)(tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("readdir", () => {
|
||||||
|
it("should read existing directory", async () => {
|
||||||
|
const paths = await util.promisify(nativeFs.readdir)(coderDir);
|
||||||
|
await expect(util.promisify(fs.readdir)(coderDir))
|
||||||
|
.resolves.toEqual(paths);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to read nonexistent directory", async () => {
|
||||||
|
await expect(util.promisify(fs.readdir)(tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("readlink", () => {
|
||||||
|
it("should read existing link", async () => {
|
||||||
|
const source = await createTmpFile();
|
||||||
|
const destination = tmpFile();
|
||||||
|
await util.promisify(nativeFs.symlink)(source, destination);
|
||||||
|
await expect(util.promisify(fs.readlink)(destination))
|
||||||
|
.resolves.toBe(source);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to read nonexistent link", async () => {
|
||||||
|
await expect(util.promisify(fs.readlink)(tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("realpath", () => {
|
||||||
|
it("should read real path of existing file", async () => {
|
||||||
|
const source = await createTmpFile();
|
||||||
|
const destination = tmpFile();
|
||||||
|
nativeFs.symlinkSync(source, destination);
|
||||||
|
await expect(util.promisify(fs.realpath)(destination))
|
||||||
|
.resolves.toBe(source);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to read real path of nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.realpath)(tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("rename", () => {
|
||||||
|
it("should rename existing file", async () => {
|
||||||
|
const source = await createTmpFile();
|
||||||
|
const destination = tmpFile();
|
||||||
|
await expect(util.promisify(fs.rename)(source, destination))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await expect(util.promisify(nativeFs.exists)(source))
|
||||||
|
.resolves.toBe(false);
|
||||||
|
await expect(util.promisify(nativeFs.exists)(destination))
|
||||||
|
.resolves.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to rename nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.rename)(tmpFile(), tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("rmdir", () => {
|
||||||
|
it("should rmdir existing directory", async () => {
|
||||||
|
const dir = tmpFile();
|
||||||
|
await util.promisify(nativeFs.mkdir)(dir);
|
||||||
|
await expect(util.promisify(fs.rmdir)(dir))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await expect(util.promisify(nativeFs.exists)(dir))
|
||||||
|
.resolves.toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to rmdir nonexistent directory", async () => {
|
||||||
|
await expect(util.promisify(fs.rmdir)(tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("stat", () => {
|
||||||
|
it("should stat existing file", async () => {
|
||||||
|
const nativeStat = await util.promisify(nativeFs.stat)(testFile);
|
||||||
|
const stat = await util.promisify(fs.stat)(testFile);
|
||||||
|
expect(stat).toMatchObject({
|
||||||
|
size: nativeStat.size,
|
||||||
|
});
|
||||||
|
expect(stat.isFile()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should stat existing folder", async () => {
|
||||||
|
const dir = tmpFile();
|
||||||
|
await util.promisify(nativeFs.mkdir)(dir);
|
||||||
|
const nativeStat = await util.promisify(nativeFs.stat)(dir);
|
||||||
|
const stat = await util.promisify(fs.stat)(dir);
|
||||||
|
expect(stat).toMatchObject({
|
||||||
|
size: nativeStat.size,
|
||||||
|
});
|
||||||
|
expect(stat.isDirectory()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to stat nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.stat)(tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("symlink", () => {
|
||||||
|
it("should symlink existing file", async () => {
|
||||||
|
const source = await createTmpFile();
|
||||||
|
const destination = tmpFile();
|
||||||
|
await expect(util.promisify(fs.symlink)(source, destination))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
expect(util.promisify(nativeFs.exists)(source))
|
||||||
|
.resolves.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Seems to be happy to do this on my system?
|
||||||
|
it("should fail to symlink nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.symlink)(tmpFile(), tmpFile()))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("truncate", () => {
|
||||||
|
it("should truncate existing file", async () => {
|
||||||
|
const file = tmpFile();
|
||||||
|
await util.promisify(nativeFs.writeFile)(file, "hiiiiii");
|
||||||
|
await expect(util.promisify(fs.truncate)(file, 2))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await expect(util.promisify(nativeFs.readFile)(file, "utf8"))
|
||||||
|
.resolves.toBe("hi");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to truncate nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.truncate)(tmpFile(), 0))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("unlink", () => {
|
||||||
|
it("should unlink existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
await expect(util.promisify(fs.unlink)(file))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
expect(util.promisify(nativeFs.exists)(file))
|
||||||
|
.resolves.toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to unlink nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.unlink)(tmpFile()))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("utimes", () => {
|
||||||
|
it("should update times on existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
await expect(util.promisify(fs.utimes)(file, 100, 100))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to update times on nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.utimes)(tmpFile(), 100, 100))
|
||||||
|
.rejects.toThrow("ENOENT");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("write", () => {
|
||||||
|
it("should write to existing file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
const fd = await util.promisify(nativeFs.open)(file, "w");
|
||||||
|
await expect(util.promisify(fs.write)(fd, Buffer.from("hi")))
|
||||||
|
.resolves.toBe(2);
|
||||||
|
await expect(util.promisify(nativeFs.readFile)(file, "utf8"))
|
||||||
|
.resolves.toBe("hi");
|
||||||
|
await util.promisify(nativeFs.close)(fd);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to write to nonexistent file", async () => {
|
||||||
|
await expect(util.promisify(fs.write)(100000, Buffer.from("wowow")))
|
||||||
|
.rejects.toThrow("EBADF");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("writeFile", () => {
|
||||||
|
it("should write file", async () => {
|
||||||
|
const file = await createTmpFile();
|
||||||
|
await expect(util.promisify(fs.writeFile)(file, "howdy"))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
await expect(util.promisify(nativeFs.readFile)(file, "utf8"))
|
||||||
|
.resolves.toBe("howdy");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,3 +2,113 @@
|
||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@types/events@*":
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||||
|
integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
|
||||||
|
|
||||||
|
"@types/glob@*":
|
||||||
|
version "7.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
|
||||||
|
integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
|
||||||
|
dependencies:
|
||||||
|
"@types/events" "*"
|
||||||
|
"@types/minimatch" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/minimatch@*":
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
|
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||||
|
|
||||||
|
"@types/node@*":
|
||||||
|
version "11.9.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.4.tgz#ceb0048a546db453f6248f2d1d95e937a6f00a14"
|
||||||
|
integrity sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA==
|
||||||
|
|
||||||
|
"@types/rimraf@^2.0.2":
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e"
|
||||||
|
integrity sha512-Hm/bnWq0TCy7jmjeN5bKYij9vw5GrDFWME4IuxV08278NtU/VdGbzsBohcCUJ7+QMqmUq5hpRKB39HeQWJjztQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/glob" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
|
balanced-match@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||||
|
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||||
|
|
||||||
|
brace-expansion@^1.1.7:
|
||||||
|
version "1.1.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||||
|
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||||
|
dependencies:
|
||||||
|
balanced-match "^1.0.0"
|
||||||
|
concat-map "0.0.1"
|
||||||
|
|
||||||
|
concat-map@0.0.1:
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
|
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||||
|
|
||||||
|
fs.realpath@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
|
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||||
|
|
||||||
|
glob@^7.1.3:
|
||||||
|
version "7.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
|
||||||
|
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
|
||||||
|
dependencies:
|
||||||
|
fs.realpath "^1.0.0"
|
||||||
|
inflight "^1.0.4"
|
||||||
|
inherits "2"
|
||||||
|
minimatch "^3.0.4"
|
||||||
|
once "^1.3.0"
|
||||||
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
|
inflight@^1.0.4:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||||
|
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
|
||||||
|
dependencies:
|
||||||
|
once "^1.3.0"
|
||||||
|
wrappy "1"
|
||||||
|
|
||||||
|
inherits@2:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
|
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||||
|
|
||||||
|
minimatch@^3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||||
|
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||||
|
dependencies:
|
||||||
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
|
once@^1.3.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
|
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||||
|
dependencies:
|
||||||
|
wrappy "1"
|
||||||
|
|
||||||
|
path-is-absolute@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||||
|
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||||
|
|
||||||
|
rimraf@^2.6.3:
|
||||||
|
version "2.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||||
|
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
|
||||||
|
dependencies:
|
||||||
|
glob "^7.1.3"
|
||||||
|
|
||||||
|
wrappy@1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
|
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
],
|
],
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"^.+\\.(s?css|png|svg)$": "<rootDir>/../scripts/dummy.js",
|
"^.+\\.(s?css|png|svg)$": "<rootDir>/../scripts/dummy.js",
|
||||||
"@coder/(.*)/testing": "<rootDir>/$1/testing",
|
"@coder/(.*)/test": "<rootDir>/$1/test",
|
||||||
"@coder/(.*)": "<rootDir>/$1/src"
|
"@coder/(.*)": "<rootDir>/$1/src"
|
||||||
},
|
},
|
||||||
"transform": {
|
"transform": {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export * from "./browser/client";
|
export * from "./browser/client";
|
||||||
export { ActiveEval } from "./browser/command";
|
export * from "./browser/evaluate";
|
||||||
export * from "./common/connection";
|
export * from "./common/connection";
|
||||||
export * from "./common/util";
|
export * from "./common/util";
|
||||||
|
|
|
@ -80,7 +80,14 @@ export const evaluate = (connection: SendableConnection, message: NewEvalMessage
|
||||||
// tslint:enable no-any
|
// tslint:enable no-any
|
||||||
} : undefined,
|
} : undefined,
|
||||||
_Buffer: Buffer,
|
_Buffer: Buffer,
|
||||||
|
// When the client is ran from Webpack, it will replace
|
||||||
|
// __non_webpack_require__ with require, which we then need to provide to
|
||||||
|
// the sandbox. Since the server might also be using Webpack, we need to set
|
||||||
|
// it to the non-Webpack version when that's the case. Then we need to also
|
||||||
|
// provide __non_webpack_require__ for when the client doesn't run through
|
||||||
|
// Webpack meaning it doesn't get replaced with require (Jest for example).
|
||||||
require: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require,
|
require: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require,
|
||||||
|
__non_webpack_require__: typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require,
|
||||||
setTimeout,
|
setTimeout,
|
||||||
setInterval,
|
setInterval,
|
||||||
clearTimeout,
|
clearTimeout,
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./helpers";
|
|
@ -1,671 +0,0 @@
|
||||||
import * as nativeFs from "fs";
|
|
||||||
import * as os from "os";
|
|
||||||
import * as path from "path";
|
|
||||||
import { createClient } from "../helpers";
|
|
||||||
import { FS } from "../../src/browser/modules/fs";
|
|
||||||
|
|
||||||
describe("fs", () => {
|
|
||||||
const client = createClient();
|
|
||||||
const fs = new FS(client);
|
|
||||||
const testFile = path.join(__dirname, "fs.test.ts");
|
|
||||||
const tmpFile = () => path.join(os.tmpdir(), `tmpfile-${Math.random()}`);
|
|
||||||
const createTmpFile = (): string => {
|
|
||||||
const tf = tmpFile();
|
|
||||||
nativeFs.writeFileSync(tf, "");
|
|
||||||
return tf;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("access", () => {
|
|
||||||
it("should access file", (done) => {
|
|
||||||
fs.access(testFile, undefined, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to access file", (done) => {
|
|
||||||
fs.access(tmpFile(), undefined, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("append", () => {
|
|
||||||
it("should append to file", (done) => {
|
|
||||||
const file = createTmpFile();
|
|
||||||
fs.appendFile(file, "howdy", undefined, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
const content = nativeFs.readFileSync(file).toString();
|
|
||||||
expect(content).toEqual("howdy");
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to append to file", (done) => {
|
|
||||||
fs.appendFile(tmpFile(), "howdy", undefined, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("chmod", () => {
|
|
||||||
it("should chmod file", (done) => {
|
|
||||||
fs.chmod(createTmpFile(), "755", (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to chmod file", (done) => {
|
|
||||||
fs.chmod(tmpFile(), "755", (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("chown", () => {
|
|
||||||
it("should chown file", (done) => {
|
|
||||||
fs.chown(createTmpFile(), 1, 1, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to chown file", (done) => {
|
|
||||||
fs.chown(tmpFile(), 1, 1, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("close", () => {
|
|
||||||
it("should close file", (done) => {
|
|
||||||
const file = createTmpFile();
|
|
||||||
const id = nativeFs.openSync(file, "r");
|
|
||||||
fs.close(id, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to close file", (done) => {
|
|
||||||
fs.close(99999999, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("copyFile", () => {
|
|
||||||
it("should copy file", (done) => {
|
|
||||||
const file = createTmpFile();
|
|
||||||
fs.copyFile(file, tmpFile(), (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to copy file", (done) => {
|
|
||||||
fs.copyFile(tmpFile(), tmpFile(), (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("createWriteStream", () => {
|
|
||||||
it("should write to file", (done) => {
|
|
||||||
const file = tmpFile();
|
|
||||||
const content = "howdy\nhow\nr\nu";
|
|
||||||
const stream = fs.createWriteStream(file);
|
|
||||||
stream.on("open", (fd) => {
|
|
||||||
expect(fd).toBeDefined();
|
|
||||||
stream.write(content);
|
|
||||||
stream.close();
|
|
||||||
});
|
|
||||||
stream.on("close", () => {
|
|
||||||
expect(nativeFs.readFileSync(file).toString()).toEqual(content);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("exists", () => {
|
|
||||||
it("should output file exists", (done) => {
|
|
||||||
fs.exists(testFile, (exists) => {
|
|
||||||
expect(exists).toBeTruthy();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should output file does not exist", (done) => {
|
|
||||||
fs.exists(tmpFile(), (exists) => {
|
|
||||||
expect(exists).toBeFalsy();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fchmod", () => {
|
|
||||||
it("should fchmod", (done) => {
|
|
||||||
const file = createTmpFile();
|
|
||||||
const id = nativeFs.openSync(file, "r");
|
|
||||||
fs.fchmod(id, "755", (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to fchmod", (done) => {
|
|
||||||
fs.fchmod(2242342, "755", (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fchown", () => {
|
|
||||||
it("should fchown", (done) => {
|
|
||||||
const file = createTmpFile();
|
|
||||||
const id = nativeFs.openSync(file, "r");
|
|
||||||
fs.fchown(id, 1, 1, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to fchown", (done) => {
|
|
||||||
fs.fchown(99999, 1, 1, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fdatasync", () => {
|
|
||||||
it("should fdatasync", (done) => {
|
|
||||||
const file = createTmpFile();
|
|
||||||
const id = nativeFs.openSync(file, "r");
|
|
||||||
fs.fdatasync(id, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to fdatasync", (done) => {
|
|
||||||
fs.fdatasync(99999, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fstat", () => {
|
|
||||||
it("should fstat", (done) => {
|
|
||||||
const id = nativeFs.openSync(testFile, "r");
|
|
||||||
fs.fstat(id, (err, stats) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(stats.size).toBeGreaterThan(0);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to fstat", (done) => {
|
|
||||||
fs.fstat(9999, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fsync", () => {
|
|
||||||
it("should fsync", (done) => {
|
|
||||||
const file = createTmpFile();
|
|
||||||
const id = nativeFs.openSync(file, "r");
|
|
||||||
fs.fsync(id, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to fsync", (done) => {
|
|
||||||
fs.fsync(99999, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("ftruncate", () => {
|
|
||||||
it("should ftruncate", (done) => {
|
|
||||||
const file = createTmpFile();
|
|
||||||
const id = nativeFs.openSync(file, "w");
|
|
||||||
fs.ftruncate(id, 1, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to ftruncate", (done) => {
|
|
||||||
fs.ftruncate(99999, 9999, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("futimes", () => {
|
|
||||||
it("should futimes", (done) => {
|
|
||||||
const file = createTmpFile();
|
|
||||||
const id = nativeFs.openSync(file, "w");
|
|
||||||
fs.futimes(id, 1, 1, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to futimes", (done) => {
|
|
||||||
fs.futimes(99999, 9999, 9999, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("lchmod", () => {
|
|
||||||
it("should lchmod file", (done) => {
|
|
||||||
fs.lchmod(createTmpFile(), "755", (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to lchmod file", (done) => {
|
|
||||||
fs.lchmod(tmpFile(), "755", (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("lchown", () => {
|
|
||||||
it("should lchown file", (done) => {
|
|
||||||
fs.lchown(createTmpFile(), 1, 1, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to lchown file", (done) => {
|
|
||||||
fs.lchown(tmpFile(), 1, 1, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("link", () => {
|
|
||||||
it("should link file", (done) => {
|
|
||||||
const newFile = createTmpFile();
|
|
||||||
const targetFile = tmpFile();
|
|
||||||
fs.link(newFile, targetFile, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(nativeFs.existsSync(targetFile)).toBeTruthy();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to link file", (done) => {
|
|
||||||
fs.link(tmpFile(), tmpFile(), (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("lstat", () => {
|
|
||||||
it("should lstat", (done) => {
|
|
||||||
fs.lstat(testFile, (err, stats) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(stats.size).toBeGreaterThan(0);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to lstat", (done) => {
|
|
||||||
fs.lstat(path.join(__dirname, "no-exist"), (err, stats) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
expect(stats).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("mkdir", () => {
|
|
||||||
const target = tmpFile();
|
|
||||||
it("should create directory", (done) => {
|
|
||||||
fs.mkdir(target, undefined, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to create directory", (done) => {
|
|
||||||
fs.mkdir(target, undefined, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("mkdtemp", () => {
|
|
||||||
it("should create temp dir", (done) => {
|
|
||||||
fs.mkdtemp(path.join(os.tmpdir(), "example"), undefined, (err, folder) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("open", () => {
|
|
||||||
it("should open file", (done) => {
|
|
||||||
fs.open(testFile, "r", undefined, (err, fd) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(fd).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to open file", (done) => {
|
|
||||||
fs.open("asdfoksfg", "r", undefined, (err, fd) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("read", () => {
|
|
||||||
it("should read file", async () => {
|
|
||||||
const fd = nativeFs.openSync(testFile, "r");
|
|
||||||
const stat = nativeFs.fstatSync(fd);
|
|
||||||
const buffer = new Buffer(stat.size);
|
|
||||||
let bytesRead = 0;
|
|
||||||
let chunkSize = 2048;
|
|
||||||
while (bytesRead < stat.size) {
|
|
||||||
if ((bytesRead + chunkSize) > stat.size) {
|
|
||||||
chunkSize = stat.size - bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise((res, rej) => {
|
|
||||||
fs.read(fd, buffer, bytesRead, chunkSize, bytesRead, (err) => {
|
|
||||||
if (err) {
|
|
||||||
rej(err);
|
|
||||||
} else {
|
|
||||||
res();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
bytesRead += chunkSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(buffer.toString()).toEqual(nativeFs.readFileSync(testFile).toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to read file", (done) => {
|
|
||||||
fs.read(99999, new Buffer(10), 9999, 999, 999, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("readFile", () => {
|
|
||||||
it("should read file", (done) => {
|
|
||||||
fs.readFile(testFile, undefined, (err, data) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(data.toString()).toEqual(nativeFs.readFileSync(testFile).toString());
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to read file", (done) => {
|
|
||||||
fs.readFile("donkey", undefined, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("readdir", () => {
|
|
||||||
it("should read directory", (done) => {
|
|
||||||
fs.readdir(__dirname, undefined, (err, paths) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(paths.length).toBeGreaterThan(0);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to read directory", (done) => {
|
|
||||||
fs.readdir("moocow", undefined, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("readlink", () => {
|
|
||||||
it("should read link", (done) => {
|
|
||||||
const srcFile = createTmpFile();
|
|
||||||
const linkedFile = tmpFile();
|
|
||||||
nativeFs.symlinkSync(srcFile, linkedFile);
|
|
||||||
fs.readlink(linkedFile, undefined, (err, link) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(link).toEqual(srcFile);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to read link", (done) => {
|
|
||||||
fs.readlink(tmpFile(), undefined, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("realpath", () => {
|
|
||||||
it("should read real path", (done) => {
|
|
||||||
const srcFile = createTmpFile();
|
|
||||||
const linkedFile = tmpFile();
|
|
||||||
nativeFs.symlinkSync(srcFile, linkedFile);
|
|
||||||
fs.realpath(linkedFile, undefined, (err, link) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(link).toEqual(srcFile);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to read real path", (done) => {
|
|
||||||
fs.realpath(tmpFile(), undefined, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("rename", () => {
|
|
||||||
it("should rename file", (done) => {
|
|
||||||
const srcFile = createTmpFile();
|
|
||||||
const targetFile = tmpFile();
|
|
||||||
fs.rename(srcFile, targetFile, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(nativeFs.existsSync(targetFile)).toBeTruthy();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to rename file", (done) => {
|
|
||||||
fs.rename(tmpFile(), tmpFile(), (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("rmdir", () => {
|
|
||||||
it("should rmdir", (done) => {
|
|
||||||
const srcFile = tmpFile();
|
|
||||||
nativeFs.mkdirSync(srcFile);
|
|
||||||
fs.rmdir(srcFile, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(nativeFs.existsSync(srcFile)).toBeFalsy();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to rmdir", (done) => {
|
|
||||||
fs.rmdir(tmpFile(), (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("stat", () => {
|
|
||||||
it("should stat file", (done) => {
|
|
||||||
fs.stat(testFile, (err, stats) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(stats.size).toBeGreaterThan(0);
|
|
||||||
expect(stats.isFile()).toBeTruthy();
|
|
||||||
expect(stats.isFIFO()).toBeFalsy();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should stat folder", (done) => {
|
|
||||||
const dir = tmpFile();
|
|
||||||
nativeFs.mkdirSync(dir);
|
|
||||||
|
|
||||||
fs.stat(dir, (err, stats) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(stats.isDirectory()).toBeTruthy();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to stat", (done) => {
|
|
||||||
fs.stat(path.join(__dirname, "no-exist"), (err, stats) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
expect(stats).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("symlink", () => {
|
|
||||||
it("should symlink file", (done) => {
|
|
||||||
const newFile = createTmpFile();
|
|
||||||
const targetFile = tmpFile();
|
|
||||||
fs.symlink(newFile, targetFile, "file", (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(nativeFs.existsSync(targetFile)).toBeTruthy();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to symlink file", (done) => {
|
|
||||||
fs.symlink(tmpFile(), tmpFile(), "file", (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("truncate", () => {
|
|
||||||
it("should truncate file", (done) => {
|
|
||||||
const newFile = tmpFile();
|
|
||||||
nativeFs.writeFileSync(newFile, "hiiiiii");
|
|
||||||
fs.truncate(newFile, 2, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(nativeFs.statSync(newFile).size).toEqual(2);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to truncate file", (done) => {
|
|
||||||
fs.truncate(tmpFile(), 0, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("unlink", () => {
|
|
||||||
it("should unlink file", (done) => {
|
|
||||||
const newFile = createTmpFile();
|
|
||||||
const targetFile = tmpFile();
|
|
||||||
nativeFs.symlinkSync(newFile, targetFile, "file");
|
|
||||||
fs.unlink(targetFile, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to unlink file", (done) => {
|
|
||||||
fs.unlink(tmpFile(), (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("utimes", () => {
|
|
||||||
it("should update times on file", (done) => {
|
|
||||||
fs.utimes(createTmpFile(), 100, 100, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to update times", (done) => {
|
|
||||||
fs.utimes(tmpFile(), 100, 100, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("write", () => {
|
|
||||||
it("should write to file", (done) => {
|
|
||||||
const file = createTmpFile();
|
|
||||||
const fd = nativeFs.openSync(file, "w");
|
|
||||||
fs.write(fd, Buffer.from("hi"), undefined, undefined, undefined, (err, written) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
expect(written).toEqual(2);
|
|
||||||
nativeFs.closeSync(fd);
|
|
||||||
expect(nativeFs.readFileSync(file).toString()).toEqual("hi");
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fail to write to file", (done) => {
|
|
||||||
fs.write(100000, Buffer.from("wowow"), undefined, undefined, undefined, (err) => {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("writeFile", () => {
|
|
||||||
it("should write file", (done) => {
|
|
||||||
fs.writeFile(createTmpFile(), "howdy", undefined, (err) => {
|
|
||||||
expect(err).toBeUndefined();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in New Issue