Files
bun.sh/test/js/node/fs/cp.test.ts
Zack Radisic 4b581b011c feat: Add cp builtin to shell for Windows (#10174)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: Jarred-Sumner <Jarred-Sumner@users.noreply.github.com>
Co-authored-by: zackradisic <zackradisic@users.noreply.github.com>
2024-05-08 17:01:00 -07:00

326 lines
10 KiB
TypeScript

import fs from "fs";
import { join } from "path";
import { describe, test, expect, jest } from "bun:test";
import { tempDirWithFiles } from "harness";
const impls = [
["cpSync", fs.cpSync],
["cp", fs.promises.cp],
] as const;
for (const [name, copy] of impls) {
async function copyShouldThrow(...args: Parameters<typeof copy>) {
try {
await (copy as any)(...args);
} catch (e: any) {
if (e?.code?.toUpperCase() === "TODO") {
throw new Error("Expected " + name + "() to throw non TODO error");
}
return e;
}
throw new Error("Expected " + name + "() to throw");
}
function assertContent(path: string, content: string) {
expect(fs.readFileSync(path, "utf8")).toBe(content);
}
describe("fs." + name, () => {
test("single file", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
});
await copy(basename + "/from/a.txt", basename + "/to.txt");
expect(fs.readFileSync(basename + "/to.txt", "utf8")).toBe("a");
});
test("refuse to copy directory with 'recursive: false'", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
});
const e = await copyShouldThrow(basename + "/from", basename + "/result");
expect(e.code).toBe("EISDIR");
expect(e.path).toBe(basename + "/from");
});
test("recursive directory structure - no destination", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
"from/b/e.txt": "e",
"from/c.txt": "c",
"from/w/y/x/z.txt": "z",
});
await copy(basename + "/from", basename + "/result", { recursive: true });
assertContent(basename + "/result/a.txt", "a");
assertContent(basename + "/result/b/e.txt", "e");
assertContent(basename + "/result/c.txt", "c");
assertContent(basename + "/result/w/y/x/z.txt", "z");
});
test("recursive directory structure - overwrite existing files by default", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
"from/b/e.txt": "e",
"from/c.txt": "c",
"from/w/y/x/z.txt": "z",
"result/a.txt": "fail",
"result/w/y/x/z.txt": "lose",
"result/w/y/v.txt": "keep this",
});
await copy(basename + "/from", basename + "/result", { recursive: true });
assertContent(basename + "/result/a.txt", "a");
assertContent(basename + "/result/b/e.txt", "e");
assertContent(basename + "/result/c.txt", "c");
assertContent(basename + "/result/w/y/x/z.txt", "z");
assertContent(basename + "/result/w/y/v.txt", "keep this");
});
test("recursive directory structure - 'force: false' does not overwrite existing files", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "lose",
"from/b/e.txt": "e",
"from/c.txt": "c",
"from/w/y/x/z.txt": "lose",
"result/a.txt": "win",
"result/w/y/x/z.txt": "win",
"result/w/y/v.txt": "keep this",
});
await copy(basename + "/from", basename + "/result", { recursive: true, force: false });
assertContent(basename + "/result/a.txt", "win");
assertContent(basename + "/result/b/e.txt", "e");
assertContent(basename + "/result/c.txt", "c");
assertContent(basename + "/result/w/y/x/z.txt", "win");
assertContent(basename + "/result/w/y/v.txt", "keep this");
});
test("'force: false' on a single file doesn't override", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "lose",
"result/a.txt": "win",
});
await copy(basename + "/from/a.txt", basename + "/result/a.txt", { force: false });
assertContent(basename + "/result/a.txt", "win");
});
test("'force: true' on a single file does override", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "win",
"result/a.txt": "lose",
});
await copy(basename + "/from/a.txt", basename + "/result/a.txt", { force: true });
assertContent(basename + "/result/a.txt", "win");
});
test("'force: false' + 'errorOnExist: true' can throw", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "lose",
"result/a.txt": "win",
});
const e = await copyShouldThrow(basename + "/from/a.txt", basename + "/result/a.txt", {
force: false,
errorOnExist: true,
});
expect(e.code).toBe("EEXIST");
expect(e.path).toBe(basename + "/result/a.txt");
assertContent(basename + "/result/a.txt", "win");
});
test("symlinks - single file", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
});
fs.symlinkSync(basename + "/from/a.txt", basename + "/from/a_symlink.txt");
await copy(basename + "/from/a_symlink.txt", basename + "/result.txt");
await copy(basename + "/from/a_symlink.txt", basename + "/result2.txt", { recursive: false });
const stats = fs.lstatSync(basename + "/result.txt");
expect(stats.isSymbolicLink()).toBe(true);
expect(fs.readFileSync(basename + "/result.txt", "utf8")).toBe("a");
const stats2 = fs.lstatSync(basename + "/result2.txt");
expect(stats2.isSymbolicLink()).toBe(true);
expect(fs.readFileSync(basename + "/result2.txt", "utf8")).toBe("a");
});
test("symlinks - single file recursive", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
});
fs.symlinkSync(basename + "/from/a.txt", basename + "/from/a_symlink.txt");
await copy(basename + "/from/a_symlink.txt", basename + "/result.txt", { recursive: true });
const stats = fs.lstatSync(basename + "/result.txt");
expect(stats.isSymbolicLink()).toBe(true);
expect(fs.readFileSync(basename + "/result.txt", "utf8")).toBe("a");
});
test("symlinks - directory recursive", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
"from/b.txt": "b",
"from/dir/c.txt": "c",
});
fs.symlinkSync(basename + "/from/a.txt", basename + "/from/a_symlink.txt");
fs.symlinkSync(basename + "/from/dir", basename + "/from/dir_symlink");
await copy(basename + "/from", basename + "/result", { recursive: true });
const statsFile = fs.lstatSync(basename + "/result/a_symlink.txt");
expect(statsFile.isSymbolicLink()).toBe(true);
expect(fs.readFileSync(basename + "/result/a_symlink.txt", "utf8")).toBe("a");
const statsDir = fs.lstatSync(basename + "/result/dir_symlink");
expect(statsDir.isSymbolicLink()).toBe(true);
expect(fs.readdirSync(basename + "/result/dir_symlink")).toEqual(["c.txt"]);
});
test("symlinks - directory recursive 2", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
"from/b.txt": "b",
"from/dir/c.txt": "c",
});
fs.symlinkSync(basename + "/from/a.txt", basename + "/from/a_symlink.txt");
fs.symlinkSync(basename + "/from/dir", basename + "/from/dir_symlink");
fs.mkdirSync(basename + "/result");
await copy(basename + "/from", basename + "/result", { recursive: true });
const statsFile = fs.lstatSync(basename + "/result/a_symlink.txt");
expect(statsFile.isSymbolicLink()).toBe(true);
expect(fs.readFileSync(basename + "/result/a_symlink.txt", "utf8")).toBe("a");
const statsDir = fs.lstatSync(basename + "/result/dir_symlink");
expect(statsDir.isSymbolicLink()).toBe(true);
expect(fs.readdirSync(basename + "/result/dir_symlink")).toEqual(["c.txt"]);
});
test("filter - works", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
"from/b.txt": "b",
});
await copy(basename + "/from", basename + "/result", {
filter: (src: string) => {
return src.endsWith("/from") || src.includes("a.txt");
},
recursive: true,
});
expect(fs.existsSync(basename + "/result/a.txt")).toBe(true);
expect(fs.existsSync(basename + "/result/b.txt")).toBe(false);
});
test("filter - paths given are correct and relative", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
"from/b.txt": "b",
});
const filter = jest.fn((src: string) => true);
let prev = process.cwd();
process.chdir(basename);
await copy(join(basename, "from"), join(basename, "result"), {
filter,
recursive: true,
});
process.chdir(prev);
expect(filter.mock.calls.sort((a, b) => a[0].localeCompare(b[0]))).toEqual([
[join(basename, "from"), join(basename, "result")],
[join(basename, "from", "a.txt"), join(basename, "result", "a.txt")],
[join(basename, "from", "b.txt"), join(basename, "result", "b.txt")],
]);
});
test("trailing slash", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
"from/b.txt": "b",
});
await copy(basename + "/from/", basename + "/result/", { recursive: true });
assertContent(basename + "/result/a.txt", "a");
assertContent(basename + "/result/b.txt", "b");
});
test("copy directory will ensure directory exists", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
"from/b.txt": "b",
});
fs.mkdirSync(basename + "/result/");
await copy(basename + "/from/", basename + "/hello/world/", { recursive: true });
assertContent(basename + "/hello/world/a.txt", "a");
assertContent(basename + "/hello/world/b.txt", "b");
});
test("relative paths for directories", async () => {
const basename = tempDirWithFiles("cp", {
"from/a.txt": "a",
"from/b.txt": "b",
"from/a.dir": { "c.txt": "c" },
});
const filter = jest.fn((src: string) => true);
let prev = process.cwd();
process.chdir(basename);
await copy("from", "result", {
recursive: true,
});
process.chdir(prev);
assertContent(basename + "/result/a.dir/c.txt", "c");
});
test.if(process.platform === "win32")("should not throw EBUSY when copying the same file on windows", async () => {
const basename = tempDirWithFiles("cp", {
"hey": "hi",
});
await copy(basename + "/hey", basename + "/hey");
});
});
}
test("cp with missing callback throws", () => {
expect(() => {
// @ts-expect-error
fs.cp("a", "b" as any);
}).toThrow(/Callback/);
});