import { $ } from "bun"; import { shellInternals } from "bun:internal-for-testing"; import { describe, expect } from "bun:test"; import { tempDirWithFiles } from "harness"; import { bunExe, createTestBuilder } from "../test_builder"; import { sortedShellOutput } from "../util"; const { builtinDisabled } = shellInternals; const TestBuilder = createTestBuilder(import.meta.path); const p = process.platform === "win32" ? (s: string) => s.replaceAll("/", "\\") : (s: string) => s; $.nothrow(); describe.if(!builtinDisabled("cp"))("bunshell cp", async () => { TestBuilder.command`cat ${import.meta.filename} > lmao.txt; cp -v lmao.txt lmao2.txt` .stdout(p("$TEMP_DIR/lmao.txt -> $TEMP_DIR/lmao2.txt\n")) .ensureTempDir() .testMini() .fileEquals("lmao2.txt", () => $`cat ${import.meta.filename}`.text()) .runAsTest("file -> file"); TestBuilder.command`cat ${import.meta.filename} > lmao.txt; touch lmao2.txt; cp -v lmao.txt lmao2.txt` .stdout(p("$TEMP_DIR/lmao.txt -> $TEMP_DIR/lmao2.txt\n")) .ensureTempDir() .testMini() .fileEquals("lmao2.txt", () => $`cat ${import.meta.filename}`.text()) .runAsTest("file -> existing file replaces contents"); TestBuilder.command`cat ${import.meta.filename} > lmao.txt; mkdir lmao2; cp -v lmao.txt lmao2` .ensureTempDir() .stdout(p("$TEMP_DIR/lmao.txt -> $TEMP_DIR/lmao2/lmao.txt\n")) .fileEquals("lmao2/lmao.txt", () => $`cat ${import.meta.filename}`.text()) .testMini() .runAsTest("file -> dir"); TestBuilder.command`cat ${import.meta.filename} > lmao.txt; cp -v lmao.txt lmao2/` .ensureTempDir() .stderr("cp: lmao2/ is not a directory\n") .exitCode(1) .testMini() .runAsTest("file -> non-existent dir fails"); TestBuilder.command`cat ${import.meta.filename} > lmao.txt; cat ${import.meta.filename} > lmao2.txt; mkdir lmao3; cp -v lmao.txt lmao2.txt lmao3` .ensureTempDir() .stdout( expectSortedOutput( p("$TEMP_DIR/lmao.txt -> $TEMP_DIR/lmao3/lmao.txt\n$TEMP_DIR/lmao2.txt -> $TEMP_DIR/lmao3/lmao2.txt\n"), ), ) .fileEquals("lmao3/lmao.txt", () => $`cat ${import.meta.filename}`.text()) .fileEquals("lmao3/lmao2.txt", () => $`cat ${import.meta.filename}`.text()) .testMini() .runAsTest("file+ -> dir"); TestBuilder.command`mkdir lmao; mkdir lmao2; cp -v lmao lmao2 lmao3` .ensureTempDir() .stderr(expectSortedOutput("cp: lmao is a directory (not copied)\ncp: lmao2 is a directory (not copied)\n")) .exitCode(1) .testMini() .runAsTest("dir -> ? fails without -R"); describe("EBUSY windows", () => { TestBuilder.command /* sh */ ` echo hi! > hello.txt mkdir somedir cp ${{ raw: Array(50).fill("hello.txt").join(" ") }} somedir ` .ensureTempDir() .exitCode(0) .fileEquals("somedir/hello.txt", "hi!\n") .runAsTest("doesn't fail on EBUSY when copying multiple files that are the same"); }); describe("uutils ported", () => { const TEST_EXISTING_FILE: string = "existing_file.txt"; const TEST_HELLO_WORLD_SOURCE: string = "hello_world.txt"; const TEST_HELLO_WORLD_SOURCE_SYMLINK: string = "hello_world.txt.link"; const TEST_HELLO_WORLD_DEST: string = "copy_of_hello_world.txt"; const TEST_HELLO_WORLD_DEST_SYMLINK: string = "copy_of_hello_world.txt.link"; const TEST_HOW_ARE_YOU_SOURCE: string = "how_are_you.txt"; const TEST_HOW_ARE_YOU_DEST: string = "hello_dir/how_are_you.txt"; const TEST_COPY_TO_FOLDER: string = "hello_dir/"; const TEST_COPY_TO_FOLDER_FILE: string = "hello_dir/hello_world.txt"; const TEST_COPY_FROM_FOLDER: string = "hello_dir_with_file/"; const TEST_COPY_FROM_FOLDER_FILE: string = "hello_dir_with_file/hello_world.txt"; const TEST_COPY_TO_FOLDER_NEW: string = "hello_dir_new"; const TEST_COPY_TO_FOLDER_NEW_FILE: string = "hello_dir_new/hello_world.txt"; // beforeAll doesn't work beacuse of the way TestBuilder is setup const tempFiles = { "hello_world.txt": "Hello, World!", "existing_file.txt": "Cogito ergo sum.", "how_are_you.txt": "How are you?", "hello_dir": { "hello.txt": "", }, "hello_dir_with_file": { "hello_world.txt": "Hello, World!", }, "dir_with_10_files": { "0": "", "1": "", "2": "", "3": "", "4": "", "5": "", "6": "", "7": "", "8": "", "9": "", }, }; const tmpdir: string = tempDirWithFiles("cp-uutils", tempFiles); const mini_tmpdir: string = tempDirWithFiles("cp-uutils-mini", tempFiles); TestBuilder.command`cp ${TEST_HELLO_WORLD_SOURCE} ${TEST_HELLO_WORLD_DEST}` .ensureTempDir(tmpdir) .fileEquals(TEST_HELLO_WORLD_DEST, "Hello, World!") .testMini({ cwd: mini_tmpdir }) .runAsTest("cp_cp"); TestBuilder.command`cp ${TEST_HELLO_WORLD_SOURCE} ${TEST_EXISTING_FILE}` .ensureTempDir(tmpdir) .fileEquals(TEST_EXISTING_FILE, "Hello, World!") .testMini({ cwd: mini_tmpdir }) .runAsTest("cp_existing_target"); TestBuilder.command`cp ${TEST_HELLO_WORLD_SOURCE} ${TEST_HELLO_WORLD_SOURCE} ${TEST_COPY_TO_FOLDER}` .ensureTempDir(tmpdir) .file(TEST_EXISTING_FILE, "Hello, World!\n") .testMini({ cwd: mini_tmpdir }) .runAsTest("cp_duplicate_files"); TestBuilder.command`touch a; cp a a` .ensureTempDir(tmpdir) .stderr_contains("cp: a and a are identical (not copied)\n") .exitCode(1) .testMini({ cwd: mini_tmpdir }) .runAsTest("cp_same_file"); TestBuilder.command`cp ${TEST_HELLO_WORLD_SOURCE} ${TEST_HELLO_WORLD_SOURCE} ${TEST_EXISTING_FILE}` .ensureTempDir(tmpdir) .stderr_contains(`cp: ${TEST_EXISTING_FILE} is not a directory\n`) .exitCode(1) .testMini({ cwd: mini_tmpdir }) .runAsTest("cp_multiple_files_target_is_file"); TestBuilder.command`cp ${TEST_COPY_TO_FOLDER} ${TEST_HELLO_WORLD_DEST}` .ensureTempDir(tmpdir) .stderr_contains(`cp: ${TEST_COPY_TO_FOLDER} is a directory (not copied)\n`) .exitCode(1) .testMini({ cwd: mini_tmpdir }) .runAsTest("cp_directory_not_recursive"); TestBuilder.command`cp ${TEST_HELLO_WORLD_SOURCE} ${TEST_HOW_ARE_YOU_SOURCE} ${TEST_COPY_TO_FOLDER}` .ensureTempDir(tmpdir) .fileEquals(TEST_COPY_TO_FOLDER_FILE, "Hello, World!") .fileEquals(TEST_HOW_ARE_YOU_DEST, "How are you?") .testMini({ cwd: mini_tmpdir }) .runAsTest("cp_multiple_files"); TestBuilder.command`cp ${TEST_HELLO_WORLD_SOURCE} ${TEST_HOW_ARE_YOU_SOURCE} ${TEST_COPY_TO_FOLDER} && ${bunExe()} -e ${'console.log("HI")'}` .ensureTempDir(tmpdir) .stdout("HI\n") .runAsTest("cp_multiple_files"); TestBuilder.command`cp -R ${TEST_COPY_FROM_FOLDER} ${TEST_COPY_TO_FOLDER_NEW}` .ensureTempDir(tmpdir) .fileEquals(TEST_COPY_TO_FOLDER_NEW_FILE, "Hello, World!") .testMini({ cwd: mini_tmpdir }) .runAsTest("cp_recurse"); }); }); function expectSortedOutput(expected: string) { return (stdout: string, tempdir: string) => expect(sortedShellOutput(stdout).join("\n")).toEqual( sortedShellOutput(expected).join("\n").replaceAll("$TEMP_DIR", tempdir), ); }