mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
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>
326 lines
10 KiB
TypeScript
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/);
|
|
});
|