aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorGravatar dave caruso <me@paperdave.net> 2023-08-30 18:30:06 -0700
committerGravatar GitHub <noreply@github.com> 2023-08-30 18:30:06 -0700
commit0a5d2a8195fbbaab7ff1f40ad54ba94726bcc104 (patch)
tree218b52f72b1170c8595800dcda34adc312adf08b /test
parent89f24e66fff37eab4b984847b73be0b69dbcd0a8 (diff)
downloadbun-0a5d2a8195fbbaab7ff1f40ad54ba94726bcc104.tar.gz
bun-0a5d2a8195fbbaab7ff1f40ad54ba94726bcc104.tar.zst
bun-0a5d2a8195fbbaab7ff1f40ad54ba94726bcc104.zip
feat(node:fs): add `cp`/`cpSync`/`promises.cp` + async `copyFile` (#4340)
* half working disaster code * this * async copyFile * . * its failing symlink tests * asdfg * asdf * hmm * okay i think ti works * small edits * fix test on linux * i hate atomics / atomics hate me back <3 * add a message in the builtins bundler that 0.8 is needed. it breaks on older versions lol. * fixed * rebase
Diffstat (limited to 'test')
-rw-r--r--test/harness.ts3
-rw-r--r--test/js/node/fs/cp.test.ts255
-rw-r--r--test/js/node/fs/fs.test.ts6
3 files changed, 261 insertions, 3 deletions
diff --git a/test/harness.ts b/test/harness.ts
index af8a13a75..f6c0f0e98 100644
--- a/test/harness.ts
+++ b/test/harness.ts
@@ -91,12 +91,13 @@ export function tempDirWithFiles(basename: string, files: Record<string, string
const dir = fs.mkdtempSync(path.join(fs.realpathSync(tmpdir()), basename + "_"));
for (const [name, contents] of Object.entries(files)) {
if (typeof contents === "object") {
- fs.mkdirSync(path.join(dir, name));
for (const [_name, _contents] of Object.entries(contents)) {
+ fs.mkdirSync(path.dirname(path.join(dir, name, _name)), { recursive: true });
fs.writeFileSync(path.join(dir, name, _name), _contents);
}
continue;
}
+ fs.mkdirSync(path.dirname(path.join(dir, name)), { recursive: true });
fs.writeFileSync(path.join(dir, name), contents);
}
return dir;
diff --git a/test/js/node/fs/cp.test.ts b/test/js/node/fs/cp.test.ts
new file mode 100644
index 000000000..37da58843
--- /dev/null
+++ b/test/js/node/fs/cp.test.ts
@@ -0,0 +1,255 @@
+import fs from "fs";
+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",
+ });
+
+ await copyShouldThrow(basename + "/from", basename + "/result");
+ });
+
+ 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",
+ });
+
+ await copyShouldThrow(basename + "/from/a.txt", basename + "/result/a.txt", { force: false, errorOnExist: true });
+
+ 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(basename + "/from", basename + "/result", {
+ filter,
+ recursive: true,
+ });
+
+ process.chdir(prev);
+
+ expect(filter.mock.calls.sort((a, b) => a[0].localeCompare(b[0]))).toEqual([
+ [basename + "/from", basename + "/result"],
+ [basename + "/from/a.txt", basename + "/result/a.txt"],
+ [basename + "/from/b.txt", basename + "/result/b.txt"],
+ ]);
+ });
+ });
+}
diff --git a/test/js/node/fs/fs.test.ts b/test/js/node/fs/fs.test.ts
index a3522b486..cdd14bb01 100644
--- a/test/js/node/fs/fs.test.ts
+++ b/test/js/node/fs/fs.test.ts
@@ -1307,7 +1307,8 @@ describe("fs.WriteStream", () => {
});
it("should use fd if provided", () => {
- const path = join(tmpdir(), "/not-used.txt");
+ const path = join(tmpdir(), `not-used-${Date.now()}.txt`);
+ expect(existsSync(path)).toBe(false);
// @ts-ignore-next-line
const ws = new WriteStream_(path, {
fd: 2,
@@ -1419,7 +1420,8 @@ describe("fs.ReadStream", () => {
});
it("should use fd if provided", () => {
- const path = join(tmpdir(), "not-used.txt");
+ const path = join(tmpdir(), `not-used-${Date.now()}.txt`);
+ expect(existsSync(path)).toBe(false);
// @ts-ignore-next-line
const ws = new ReadStream_(path, {
fd: 0,