From 70735b6425c14abfca39426486252aaef6c0d408 Mon Sep 17 00:00:00 2001 From: saehejkang <20051028+saehejkang@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:12:02 -0700 Subject: [PATCH 1/2] add tests for copy from --- .../Subcommands/Build/CLIBuilderTest.swift | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/Tests/CLITests/Subcommands/Build/CLIBuilderTest.swift b/Tests/CLITests/Subcommands/Build/CLIBuilderTest.swift index 67dd27c25..6c18b4518 100644 --- a/Tests/CLITests/Subcommands/Build/CLIBuilderTest.swift +++ b/Tests/CLITests/Subcommands/Build/CLIBuilderTest.swift @@ -1351,5 +1351,65 @@ extension TestCLIBuildBase { ) #expect(try self.inspectImage(imageName) == imageName, "expected to have successfully built \(imageName)") } + + @Test func testCopyFromLocalImage() throws { + let baseTempDir: URL = try createTempDir() + let tempDir: URL = try createTempDir() + defer { + try! FileManager.default.removeItem(at: baseTempDir) + try! FileManager.default.removeItem(at: tempDir) + } + + let baseImageName = "local-base:\(UUID().uuidString)" + let baseDockerfile = + """ + FROM scratch + ADD hello.txt /hello.txt + """ + let baseContext: [FileSystemEntry] = [ + .file("hello.txt", content: .data("hello\n".data(using: .utf8)!)) + ] + try createContext(tempDir: baseTempDir, dockerfile: baseDockerfile, context: baseContext) + + try self.build(tag: baseImageName, tempDir: baseTempDir) + #expect(try self.inspectImage(baseImageName) == baseImageName, "expected to have successfully built \(baseImageName)") + + let dockerfile = + """ + FROM ghcr.io/linuxcontainers/alpine:3.20 + COPY --from=\(baseImageName) /hello.txt /copied.txt + RUN cat /copied.txt + """ + try createContext(tempDir: tempDir, dockerfile: dockerfile) + + let imageName = "registry.local/copy-from-local:\(UUID().uuidString)" + try self.build(tag: imageName, tempDir: tempDir) + #expect(try self.inspectImage(imageName) == imageName, "expected to have successfully built \(imageName)") + } + + @Test func testCopyFromBuildStage() throws { + let tempDir: URL = try createTempDir() + defer { + try! FileManager.default.removeItem(at: tempDir) + } + + let dockerfile = + """ + FROM scratch AS builder + ADD hello.txt /hello.txt + + FROM ghcr.io/linuxcontainers/alpine:3.20 + COPY --from=builder /hello.txt /copied.txt + RUN cat /copied.txt + """ + let context: [FileSystemEntry] = [ + .file("hello.txt", content: .data("hello\n".data(using: .utf8)!)) + ] + try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context) + + let imageName = "registry.local/copy-from-stage:\(UUID().uuidString)" + try self.build(tag: imageName, tempDir: tempDir) + #expect(try self.inspectImage(imageName) == imageName, "expected to have successfully built \(imageName)") + } } } From 89137844bcf8b48fb591ca6f44c5e401dcf40b4f Mon Sep 17 00:00:00 2001 From: saehejkang <20051028+saehejkang@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:08:25 -0700 Subject: [PATCH 2/2] add more test cases --- .../Subcommands/Build/CLIBuilderTest.swift | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/Tests/CLITests/Subcommands/Build/CLIBuilderTest.swift b/Tests/CLITests/Subcommands/Build/CLIBuilderTest.swift index 6c18b4518..30823a3bd 100644 --- a/Tests/CLITests/Subcommands/Build/CLIBuilderTest.swift +++ b/Tests/CLITests/Subcommands/Build/CLIBuilderTest.swift @@ -1411,5 +1411,89 @@ extension TestCLIBuildBase { try self.build(tag: imageName, tempDir: tempDir) #expect(try self.inspectImage(imageName) == imageName, "expected to have successfully built \(imageName)") } + + @Test func testCopyRenameFromStage() throws { + let tempDir: URL = try createTempDir() + defer { + try! FileManager.default.removeItem(at: tempDir) + } + + let dockerfile = + """ + FROM scratch AS builder + ADD hello.txt /hello.txt + + FROM ghcr.io/linuxcontainers/alpine:3.20 + COPY --from=builder /hello.txt /renamed.txt + RUN cat /renamed.txt + """ + let context: [FileSystemEntry] = [ + .file("hello.txt", content: .data("hello\n".data(using: .utf8)!)) + ] + try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context) + + let imageName = "registry.local/copy-rename:\(UUID().uuidString)" + try self.build(tag: imageName, tempDir: tempDir) + #expect(try self.inspectImage(imageName) == imageName, "expected to have successfully built \(imageName)") + } + + @Test func testCopyMissingFileFails() throws { + let tempDir: URL = try createTempDir() + defer { + try! FileManager.default.removeItem(at: tempDir) + } + + let dockerfile = + """ + FROM scratch AS builder + + FROM ghcr.io/linuxcontainers/alpine:3.20 + COPY --from=builder /does-not-exist.txt /copied.txt + """ + try createContext(tempDir: tempDir, dockerfile: dockerfile) + + let imageName = "registry.local/copy-missing:\(UUID().uuidString)" + #expect(throws: Error.self) { + try self.build(tag: imageName, tempDir: tempDir) + } + } + } + + @Test func testCopyInvalidStageFails() throws { + let tempDir: URL = try createTempDir() + defer { + try! FileManager.default.removeItem(at: tempDir) + } + + let dockerfile = + """ + FROM ghcr.io/linuxcontainers/alpine:3.20 + COPY --from=not_a_stage /hello.txt /copied.txt + """ + try createContext(tempDir: tempDir, dockerfile: dockerfile) + + let imageName = "registry.local/copy-invalid-stage:\(UUID().uuidString)" + #expect(throws: Error.self) { + try self.build(tag: imageName, tempDir: tempDir) + } + } + + @Test func testCopyFromNonexistentImageFails() throws { + let tempDir: URL = try createTempDir() + defer { + try! FileManager.default.removeItem(at: tempDir) + } + + let dockerfile = + """ + FROM ghcr.io/linuxcontainers/alpine:3.20 + COPY --from=doesnotexist:latest /hello.txt /copied.txt + """ + try createContext(tempDir: tempDir, dockerfile: dockerfile) + + let imageName = "registry.local/copy-bad-image:\(UUID().uuidString)" + #expect(throws: Error.self) { + try self.build(tag: imageName, tempDir: tempDir) + } } }