chore: import upstream snapshot with attribution
container project - merge build / Invoke build (push) Successful in 1s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:06:18 +08:00
commit e84fb4a79e
474 changed files with 68321 additions and 0 deletions
@@ -0,0 +1,546 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import Foundation
import Testing
@Suite
struct TestCLICopyCommand {
// MARK: - Basic host/container copy
@Test func testCopyHostToContainer() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let src = f.testDir.appending("testfile.txt")
let content = "hello from host"
try content.write(toFile: src.string, atomically: true, encoding: .utf8)
try f.run(["copy", src.string, "\(name):/tmp/"]).check()
let cat = try f.doExec(name, cmd: ["cat", "/tmp/testfile.txt"])
#expect(cat.trimmingCharacters(in: .whitespacesAndNewlines) == content)
}
}
}
@Test func testCopyContainerToHost() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let content = "hello from container"
try f.doExec(name, cmd: ["sh", "-c", "echo -n '\(content)' > /tmp/containerfile.txt"])
let dest = f.testDir.appending("containerfile.txt")
try f.run(["copy", "\(name):/tmp/containerfile.txt", dest.string]).check()
let hostContent = try String(contentsOfFile: dest.string, encoding: .utf8)
#expect(hostContent == content)
}
}
}
@Test func testCopyUsingCpAlias() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let src = f.testDir.appending("aliasfile.txt")
let content = "testing cp alias"
try content.write(toFile: src.string, atomically: true, encoding: .utf8)
try f.run(["cp", src.string, "\(name):/tmp/"]).check()
let cat = try f.doExec(name, cmd: ["cat", "/tmp/aliasfile.txt"])
#expect(cat.trimmingCharacters(in: .whitespacesAndNewlines) == content)
}
}
}
@Test func testCopyLocalToLocalFails() async throws {
try await ContainerFixture.with { f in
let result = try f.run(["copy", "/tmp/source.txt", "/tmp/dest.txt"])
#expect(result.status != 0, "expected local-to-local copy to fail")
}
}
@Test func testCopyContainerToContainerFails() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
try f.doCreate(name: name, image: image)
f.addCleanup { try f.doRemoveIfExists(name, ignoreFailure: true) }
let result = try f.run(["copy", "\(name):/tmp/file.txt", "\(name):/tmp/file2.txt"])
#expect(result.status != 0, "expected container-to-container copy to fail")
}
}
@Test func testCopyToNonRunningContainerFails() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
try f.doCreate(name: name, image: image)
f.addCleanup { try f.doRemoveIfExists(name, ignoreFailure: true) }
let src = f.testDir.appending("norun.txt")
try "test".write(toFile: src.string, atomically: true, encoding: .utf8)
let result = try f.run(["copy", src.string, "\(name):/tmp/"])
#expect(result.status != 0, "expected copy to non-running container to fail")
}
}
@Test func testCopyDirectoryHostToContainer() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let srcDir = f.testDir.appending("hostdir")
try FileManager.default.createDirectory(atPath: srcDir.string, withIntermediateDirectories: true, attributes: nil)
try "file1 content".write(toFile: srcDir.appending("file1.txt").string, atomically: true, encoding: .utf8)
try "file2 content".write(toFile: srcDir.appending("file2.txt").string, atomically: true, encoding: .utf8)
try f.run(["copy", srcDir.string, "\(name):/tmp/"]).check()
let cat1 = try f.doExec(name, cmd: ["cat", "/tmp/hostdir/file1.txt"])
#expect(cat1.trimmingCharacters(in: .whitespacesAndNewlines) == "file1 content")
let cat2 = try f.doExec(name, cmd: ["cat", "/tmp/hostdir/file2.txt"])
#expect(cat2.trimmingCharacters(in: .whitespacesAndNewlines) == "file2 content")
}
}
}
@Test func testCopyDirectoryContainerToHost() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/guestdir && echo -n 'aaa' > /tmp/guestdir/a.txt && echo -n 'bbb' > /tmp/guestdir/b.txt"])
let dest = f.testDir.appending("guestdir")
try f.run(["copy", "\(name):/tmp/guestdir", dest.string]).check()
let a = try String(contentsOfFile: dest.appending("a.txt").string, encoding: .utf8)
#expect(a == "aaa")
let b = try String(contentsOfFile: dest.appending("b.txt").string, encoding: .utf8)
#expect(b == "bbb")
}
}
}
@Test func testCopyNestedDirectoryHostToContainer() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let srcDir = f.testDir.appending("nested")
let subDir = srcDir.appending("sub")
try FileManager.default.createDirectory(atPath: subDir.string, withIntermediateDirectories: true, attributes: nil)
try "root file".write(toFile: srcDir.appending("root.txt").string, atomically: true, encoding: .utf8)
try "nested file".write(toFile: subDir.appending("deep.txt").string, atomically: true, encoding: .utf8)
try f.run(["copy", srcDir.string, "\(name):/tmp/"]).check()
let catRoot = try f.doExec(name, cmd: ["cat", "/tmp/nested/root.txt"])
#expect(catRoot.trimmingCharacters(in: .whitespacesAndNewlines) == "root file")
let catDeep = try f.doExec(name, cmd: ["cat", "/tmp/nested/sub/deep.txt"])
#expect(catDeep.trimmingCharacters(in: .whitespacesAndNewlines) == "nested file")
}
}
}
@Test func testCopyNestedDirectoryContainerToHost() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/nested/sub && echo -n 'root file' > /tmp/nested/root.txt && echo -n 'nested file' > /tmp/nested/sub/deep.txt"])
let dest = f.testDir.appending("nested")
try f.run(["copy", "\(name):/tmp/nested", dest.string]).check()
let root = try String(contentsOfFile: dest.appending("root.txt").string, encoding: .utf8)
#expect(root == "root file")
let deep = try String(contentsOfFile: dest.appending("sub").appending("deep.txt").string, encoding: .utf8)
#expect(deep == "nested file")
}
}
}
// MARK: - CopyOut S1: no trailing slash
@Test func testCopyOutFileToExistingFile() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let content = "container content"
try f.doExec(name, cmd: ["sh", "-c", "echo -n '\(content)' > /tmp/source.txt"])
let dest = f.testDir.appending("existing.txt")
try "old content".write(toFile: dest.string, atomically: true, encoding: .utf8)
try f.run(["copy", "\(name):/tmp/source.txt", dest.string]).check()
let result = try String(contentsOfFile: dest.string, encoding: .utf8)
#expect(result == content)
}
}
}
@Test func testCopyOutDirectoryToExistingFileFails() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/srcdir && echo -n 'x' > /tmp/srcdir/file.txt"])
let dest = f.testDir.appending("existing.txt")
try "x".write(toFile: dest.string, atomically: true, encoding: .utf8)
let result = try f.run(["copy", "\(name):/tmp/srcdir", dest.string])
#expect(result.status != 0, "expected directory-to-existing-file to fail")
}
}
}
@Test func testCopyOutFileToExistingDirectory() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let content = "container content"
try f.doExec(name, cmd: ["sh", "-c", "echo -n '\(content)' > /tmp/source.txt"])
let destDir = f.testDir.appending("dstdir")
try FileManager.default.createDirectory(atPath: destDir.string, withIntermediateDirectories: true, attributes: nil)
try f.run(["copy", "\(name):/tmp/source.txt", destDir.string]).check()
let result = try String(contentsOfFile: destDir.appending("source.txt").string, encoding: .utf8)
#expect(result == content)
}
}
}
@Test func testCopyOutDirectoryToExistingDirectory() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/srcdir && echo -n 'hello' > /tmp/srcdir/file.txt"])
let destDir = f.testDir.appending("dstdir")
try FileManager.default.createDirectory(atPath: destDir.string, withIntermediateDirectories: true, attributes: nil)
try f.run(["copy", "\(name):/tmp/srcdir", destDir.string]).check()
let result = try String(contentsOfFile: destDir.appending("srcdir").appending("file.txt").string, encoding: .utf8)
#expect(result == "hello")
}
}
}
// MARK: - CopyOut S2: trailing slash on dst
@Test func testCopyOutFileToNonExistingTrailingSlashFails() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "echo -n 'x' > /tmp/source.txt"])
let dest = f.testDir.appending("nonexistent").string + "/"
let result = try f.run(["copy", "\(name):/tmp/source.txt", dest])
#expect(result.status != 0, "expected file-to-nonexisting/ to fail")
}
}
}
@Test func testCopyOutDirectoryToNonExistingTrailingSlash() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/srcdir && echo -n 'hello' > /tmp/srcdir/file.txt"])
let destDir = f.testDir.appending("newdir")
try f.run(["copy", "\(name):/tmp/srcdir", destDir.string + "/"]).check()
var isDir: ObjCBool = false
#expect(FileManager.default.fileExists(atPath: destDir.string, isDirectory: &isDir) && isDir.boolValue)
let result = try String(contentsOfFile: destDir.appending("file.txt").string, encoding: .utf8)
#expect(result == "hello")
}
}
}
@Test func testCopyOutFileToExistingDirectoryTrailingSlash() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let content = "container content"
try f.doExec(name, cmd: ["sh", "-c", "echo -n '\(content)' > /tmp/source.txt"])
let destDir = f.testDir.appending("dstdir")
try FileManager.default.createDirectory(atPath: destDir.string, withIntermediateDirectories: true, attributes: nil)
try f.run(["copy", "\(name):/tmp/source.txt", destDir.string + "/"]).check()
let result = try String(contentsOfFile: destDir.appending("source.txt").string, encoding: .utf8)
#expect(result == content)
}
}
}
@Test func testCopyOutDirectoryToExistingDirectoryTrailingSlash() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/srcdir && echo -n 'hello' > /tmp/srcdir/file.txt"])
let destDir = f.testDir.appending("dstdir")
try FileManager.default.createDirectory(atPath: destDir.string, withIntermediateDirectories: true, attributes: nil)
try f.run(["copy", "\(name):/tmp/srcdir", destDir.string + "/"]).check()
let result = try String(contentsOfFile: destDir.appending("srcdir").appending("file.txt").string, encoding: .utf8)
#expect(result == "hello")
}
}
}
// MARK: - CopyOut S3: trailing slash on src
@Test func testCopyOutDirectoryContentsToNonExisting() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/srcdir/sub && echo -n 'hello' > /tmp/srcdir/file.txt"])
let destDir = f.testDir.appending("newdir")
try f.run(["copy", "\(name):/tmp/srcdir/", destDir.string]).check()
let result = try String(contentsOfFile: destDir.appending("file.txt").string, encoding: .utf8)
#expect(result == "hello")
var isDir: ObjCBool = false
#expect(FileManager.default.fileExists(atPath: destDir.appending("sub").string, isDirectory: &isDir) && isDir.boolValue)
}
}
}
@Test func testCopyOutDirectoryContentsToExistingFileFails() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/srcdir && echo -n 'x' > /tmp/srcdir/file.txt"])
let dest = f.testDir.appending("existing.txt")
try "x".write(toFile: dest.string, atomically: true, encoding: .utf8)
let result = try f.run(["copy", "\(name):/tmp/srcdir/", dest.string])
#expect(result.status != 0, "expected directory/-to-existing-file to fail")
}
}
}
@Test func testCopyOutDirectoryContentsToExistingDirectory() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/srcdir && echo -n 'hello' > /tmp/srcdir/file.txt"])
let destDir = f.testDir.appending("dstdir")
try FileManager.default.createDirectory(atPath: destDir.string, withIntermediateDirectories: true, attributes: nil)
try f.run(["copy", "\(name):/tmp/srcdir/", destDir.string]).check()
let result = try String(contentsOfFile: destDir.appending("srcdir").appending("file.txt").string, encoding: .utf8)
#expect(result == "hello")
}
}
}
// MARK: - CopyOut S4: trailing slash on both src and dst
@Test func testCopyOutDirectoryContentsToNonExistingTrailingSlash() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/srcdir && echo -n 'hello' > /tmp/srcdir/file.txt"])
let destDir = f.testDir.appending("newdir")
try f.run(["copy", "\(name):/tmp/srcdir/", destDir.string + "/"]).check()
let result = try String(contentsOfFile: destDir.appending("file.txt").string, encoding: .utf8)
#expect(result == "hello")
}
}
}
@Test func testCopyOutDirectoryContentsToExistingDirectoryTrailingSlash() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /tmp/srcdir && echo -n 'hello' > /tmp/srcdir/file.txt"])
let destDir = f.testDir.appending("dstdir")
try FileManager.default.createDirectory(atPath: destDir.string, withIntermediateDirectories: true, attributes: nil)
try f.run(["copy", "\(name):/tmp/srcdir/", destDir.string + "/"]).check()
let result = try String(contentsOfFile: destDir.appending("srcdir").appending("file.txt").string, encoding: .utf8)
#expect(result == "hello")
}
}
}
// MARK: - CopyIn S1: no trailing slash
@Test func testCopyInFileToExistingFile() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let content = "new content"
let src = f.testDir.appending("source.txt")
try content.write(toFile: src.string, atomically: true, encoding: .utf8)
try f.doExec(name, cmd: ["sh", "-c", "echo -n 'old content' > /tmp/existing.txt"])
try f.run(["copy", src.string, "\(name):/tmp/existing.txt"]).check()
let result = try f.doExec(name, cmd: ["cat", "/tmp/existing.txt"])
#expect(result.trimmingCharacters(in: .whitespacesAndNewlines) == content)
}
}
}
@Test func testCopyInDirectoryToExistingFileFails() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let srcDir = f.testDir.appending("srcdir")
try FileManager.default.createDirectory(atPath: srcDir.string, withIntermediateDirectories: true, attributes: nil)
try "x".write(toFile: srcDir.appending("file.txt").string, atomically: true, encoding: .utf8)
try f.doExec(name, cmd: ["sh", "-c", "echo -n 'x' > /tmp/existing.txt"])
let result = try f.run(["copy", srcDir.string, "\(name):/tmp/existing.txt"])
#expect(result.status != 0, "expected directory-to-existing-file to fail")
}
}
}
@Test func testCopyInFileToExistingDirectory() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let content = "host content"
let src = f.testDir.appending("source.txt")
try content.write(toFile: src.string, atomically: true, encoding: .utf8)
try f.doExec(name, cmd: ["mkdir", "-p", "/tmp/dstdir"])
try f.run(["copy", src.string, "\(name):/tmp/dstdir"]).check()
let result = try f.doExec(name, cmd: ["cat", "/tmp/dstdir/source.txt"])
#expect(result.trimmingCharacters(in: .whitespacesAndNewlines) == content)
}
}
}
@Test func testCopyInDirectoryToExistingDirectory() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let srcDir = f.testDir.appending("srcdir")
try FileManager.default.createDirectory(atPath: srcDir.string, withIntermediateDirectories: true, attributes: nil)
try "hello".write(toFile: srcDir.appending("file.txt").string, atomically: true, encoding: .utf8)
try f.doExec(name, cmd: ["mkdir", "-p", "/tmp/dstdir"])
try f.run(["copy", srcDir.string, "\(name):/tmp/dstdir"]).check()
let result = try f.doExec(name, cmd: ["cat", "/tmp/dstdir/srcdir/file.txt"])
#expect(result.trimmingCharacters(in: .whitespacesAndNewlines) == "hello")
}
}
}
// MARK: - CopyIn S2: trailing slash on dst
@Test func testCopyInFileToNonExistingTrailingSlashFails() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let src = f.testDir.appending("source.txt")
try "x".write(toFile: src.string, atomically: true, encoding: .utf8)
let result = try f.run(["copy", src.string, "\(name):/tmp/nonexistent/"])
#expect(result.status != 0, "expected file-to-nonexisting/ to fail")
}
}
}
@Test func testCopyInDirectoryToNonExistingTrailingSlash() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let srcDir = f.testDir.appending("srcdir")
try FileManager.default.createDirectory(atPath: srcDir.string, withIntermediateDirectories: true, attributes: nil)
try "hello".write(toFile: srcDir.appending("file.txt").string, atomically: true, encoding: .utf8)
try f.run(["copy", srcDir.string, "\(name):/tmp/newdir/"]).check()
let result = try f.doExec(name, cmd: ["cat", "/tmp/newdir/file.txt"])
#expect(result.trimmingCharacters(in: .whitespacesAndNewlines) == "hello")
}
}
}
// MARK: - CopyIn S3: trailing slash on src
@Test func testCopyInDirectoryContentsToNonExisting() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let srcDir = f.testDir.appending("srcdir")
let subDir = srcDir.appending("sub")
try FileManager.default.createDirectory(atPath: subDir.string, withIntermediateDirectories: true, attributes: nil)
try "hello".write(toFile: srcDir.appending("file.txt").string, atomically: true, encoding: .utf8)
try f.run(["copy", srcDir.string + "/", "\(name):/tmp/newdir"]).check()
let result = try f.doExec(name, cmd: ["cat", "/tmp/newdir/file.txt"])
#expect(result.trimmingCharacters(in: .whitespacesAndNewlines) == "hello")
}
}
}
@Test func testCopyInDirectoryContentsToExistingFileFails() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let srcDir = f.testDir.appending("srcdir")
try FileManager.default.createDirectory(atPath: srcDir.string, withIntermediateDirectories: true, attributes: nil)
try "x".write(toFile: srcDir.appending("file.txt").string, atomically: true, encoding: .utf8)
try f.doExec(name, cmd: ["sh", "-c", "echo -n 'x' > /tmp/existing.txt"])
let result = try f.run(["copy", srcDir.string + "/", "\(name):/tmp/existing.txt"])
#expect(result.status != 0, "expected directory/-to-existing-file to fail")
}
}
}
@Test func testCopyInDirectoryContentsToExistingDirectory() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let srcDir = f.testDir.appending("srcdir")
try FileManager.default.createDirectory(atPath: srcDir.string, withIntermediateDirectories: true, attributes: nil)
try "hello".write(toFile: srcDir.appending("file.txt").string, atomically: true, encoding: .utf8)
try f.doExec(name, cmd: ["mkdir", "-p", "/tmp/dstdir"])
try f.run(["copy", srcDir.string + "/", "\(name):/tmp/dstdir"]).check()
let result = try f.doExec(name, cmd: ["cat", "/tmp/dstdir/srcdir/file.txt"])
#expect(result.trimmingCharacters(in: .whitespacesAndNewlines) == "hello")
}
}
}
// MARK: - CopyIn S4: trailing slash on both src and dst
@Test func testCopyInDirectoryContentsToNonExistingTrailingSlash() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let srcDir = f.testDir.appending("srcdir")
try FileManager.default.createDirectory(atPath: srcDir.string, withIntermediateDirectories: true, attributes: nil)
try "hello".write(toFile: srcDir.appending("file.txt").string, atomically: true, encoding: .utf8)
try f.run(["copy", srcDir.string + "/", "\(name):/tmp/newdir/"]).check()
let result = try f.doExec(name, cmd: ["cat", "/tmp/newdir/file.txt"])
#expect(result.trimmingCharacters(in: .whitespacesAndNewlines) == "hello")
}
}
}
@Test func testCopyInDirectoryContentsToExistingDirectoryTrailingSlash() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let srcDir = f.testDir.appending("srcdir")
try FileManager.default.createDirectory(atPath: srcDir.string, withIntermediateDirectories: true, attributes: nil)
try "hello".write(toFile: srcDir.appending("file.txt").string, atomically: true, encoding: .utf8)
try f.doExec(name, cmd: ["mkdir", "-p", "/tmp/dstdir"])
try f.run(["copy", srcDir.string + "/", "\(name):/tmp/dstdir/"]).check()
let result = try f.doExec(name, cmd: ["cat", "/tmp/dstdir/srcdir/file.txt"])
#expect(result.trimmingCharacters(in: .whitespacesAndNewlines) == "hello")
}
}
}
// MARK: - Relative path resolution
@Test func testCopyInRelativeSourcePath() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let content = "relative source"
try content.write(toFile: f.testDir.appending("relfile.txt").string, atomically: true, encoding: .utf8)
try f.run(["copy", "./relfile.txt", "\(name):/tmp/"], currentDirectory: f.testDir).check()
let result = try f.doExec(name, cmd: ["cat", "/tmp/relfile.txt"])
#expect(result.trimmingCharacters(in: .whitespacesAndNewlines) == content)
}
}
}
@Test func testCopyOutRelativeDestinationPath() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let content = "relative dest"
try f.doExec(name, cmd: ["sh", "-c", "echo -n '\(content)' > /tmp/relfile.txt"])
try f.run(["copy", "\(name):/tmp/relfile.txt", "./"], currentDirectory: f.testDir).check()
let result = try String(contentsOfFile: f.testDir.appending("relfile.txt").string, encoding: .utf8)
#expect(result == content)
}
}
}
}
@@ -0,0 +1,108 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerizationExtras
import Foundation
import Testing
@Suite
struct TestCLICreateCommand {
@Test func testCreateArgsPassthrough() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
try f.doCreate(name: name, image: image, args: ["echo", "-n", "hello", "world"])
try f.doRemove(name)
}
}
@Test func testCreateWithMACAddress() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
let expectedMAC = try MACAddress("02:42:ac:11:00:03")
try f.doCreate(name: name, image: image, networks: ["default,mac=\(expectedMAC)"])
f.addCleanup { try? f.doStop(name) }
try f.doStart(name)
try await f.waitForContainerRunning(name)
let inspect = try f.inspectContainer(name)
#expect(inspect.networks.count > 0, "expected at least one network attachment")
let actualMAC = inspect.networks[0].macAddress?.description ?? "nil"
#expect(
actualMAC == expectedMAC.description,
"expected MAC address \(expectedMAC), got \(actualMAC)")
}
}
@Test func testPublishPortParserMaxPorts() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
var args: [String] = ["create", "--name", name]
for i in 0..<64 {
args += ["--publish", "127.0.0.1:\(8000 + i):\(9000 + i)"]
}
args += [image, "echo", "\"hello world\""]
let result = try f.run(args)
f.addCleanup { try? f.doRemove(name) }
#expect(result.status == 0, "expected create with 64 ports to succeed, stderr: \(result.error)")
}
}
@Test func testPublishPortParserTooManyPorts() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
var args: [String] = ["create", "--name", name]
for i in 0..<65 {
args += ["--publish", "127.0.0.1:\(8000 + i):\(9000 + i)"]
}
args += [image, "echo", "\"hello world\""]
let result = try f.run(args)
f.addCleanup { try? f.doRemove(name) }
#expect(result.status != 0, "expected create with 65 ports to fail")
}
}
@Test func testCreateWithFQDNName() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
// Prefix with testID to avoid name collisions; hostname is the first FQDN component.
let name = "\(f.testID).example.com"
let expectedHostname = f.testID
try f.doCreate(name: name, image: image)
f.addCleanup { try? f.doStop(name) }
try f.doStart(name)
try await f.waitForContainerRunning(name)
let inspect = try f.inspectContainer(name)
let attachmentHostname = inspect.networks.first?.hostname ?? ""
let gotHostname =
attachmentHostname
.split(separator: ".", maxSplits: 1, omittingEmptySubsequences: true)
.first
.map { String($0) } ?? attachmentHostname
#expect(
gotHostname == expectedHostname,
"expected hostname '\(expectedHostname)' from FQDN '\(name)', got '\(gotHostname)'")
}
}
}
@@ -0,0 +1,115 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import Testing
@Suite
struct TestCLIExecCommand {
@Test func testCreateExecCommand() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
try f.doCreate(name: name, image: image)
f.addCleanup { try? f.doStop(name) }
try f.doStart(name)
try await f.waitForContainerRunning(name)
let uname = try f.doExec(name, cmd: ["uname"])
.trimmingCharacters(in: .whitespacesAndNewlines)
#expect(uname == "Linux", "expected OS to be Linux, got \(uname)")
try f.doStop(name)
}
}
@Test func testExecDetach() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
try f.doCreate(name: name, image: image)
f.addCleanup { try? f.doStop(name) }
try f.doStart(name)
try await f.waitForContainerRunning(name)
let output = try f.doExec(name, cmd: ["sh", "-c", "touch /tmp/detach_test_marker"], detach: true)
try #require(
output.trimmingCharacters(in: .whitespacesAndNewlines) == name,
"exec --detach should print the container name")
let ls = try f.doExec(name, cmd: ["ls", "/"])
.trimmingCharacters(in: .whitespacesAndNewlines)
try #require(ls.contains("tmp"), "container should still be running after detached exec")
// Retry until the detached process creates the marker file.
var markerFound = false
for _ in 0..<3 {
let result = try f.run(["exec", name, "test", "-f", "/tmp/detach_test_marker"])
if result.status == 0 {
markerFound = true
break
}
try await Task.sleep(for: .seconds(1))
}
try #require(markerFound, "marker file should be created by detached process within 3 seconds")
try f.doStop(name)
}
}
@Test func testExecDetachProcessRunning() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
try f.doCreate(name: name, image: image)
f.addCleanup { try? f.doStop(name) }
try f.doStart(name)
try await f.waitForContainerRunning(name)
let output = try f.doExec(name, cmd: ["sleep", "10"], detach: true)
try #require(
output.trimmingCharacters(in: .whitespacesAndNewlines) == name,
"exec --detach should print the container name")
let ps = try f.doExec(name, cmd: ["ps", "aux"])
.trimmingCharacters(in: .whitespacesAndNewlines)
try #require(ps.contains("sleep 10"), "detached 'sleep 10' should appear in ps output")
try f.doStop(name)
}
}
@Test func testExecOnExitingContainer() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
// sh exits immediately in detached mode with no stdin; container stops on its own.
try f.doLongRun(name: name, image: image, containerArgs: ["sh"], autoRemove: false)
f.addCleanup { try? f.doRemove(name) }
try await Task.sleep(for: .seconds(1))
try f.doStart(name)
let execResult = try f.run(["exec", name, "sleep", "infinity"])
if execResult.status != 0 {
#expect(
execResult.error.contains("is not running")
|| execResult.error.contains("failed to create process"),
"expected 'not running' error, got: \(execResult.error)")
}
try await Task.sleep(for: .seconds(1))
// Container should still exist even if exec failed.
_ = try f.getContainerStatus(name)
}
}
}
@@ -0,0 +1,55 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerizationArchive
import Foundation
import Testing
@Suite
struct TestCLIExportCommand {
@Test func testExportCommand() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image, autoRemove: false) { name in
let mustBeInImage = "must-be-in-image"
try f.doExec(name, cmd: ["sh", "-c", "echo \(mustBeInImage) > /foo"])
try f.doExec(name, cmd: ["sh", "-c", "mkdir -p /parent/child"])
let hardlinkMustRemain = "hardlink-must-remain"
try f.doExec(name, cmd: ["sh", "-c", "echo \(hardlinkMustRemain) > /parent/child/bar"])
try f.doExec(name, cmd: ["sh", "-c", "ln /parent/child/bar /bar"])
let symlinkMustRemain = "symlink-must-remain"
try f.doExec(name, cmd: ["sh", "-c", "echo \(symlinkMustRemain) > /parent/child/baz"])
try f.doExec(name, cmd: ["sh", "-c", "ln /parent/child/baz /baz"])
try f.doStop(name)
let exportPath = f.testDir.appending("export.tar")
try f.doExport(name, to: exportPath)
let exportURL = URL(filePath: exportPath.string)
let attrs = try FileManager.default.attributesOfItem(atPath: exportPath.string)
let fileSize = attrs[.size] as! UInt64
#expect(fileSize > 0)
// TODO: verify foo bar baz are in tar file.
let reader = try ArchiveReader(file: exportURL)
let (foo, fooData) = try reader.extractFile(path: "/foo")
#expect(foo.fileType == .regular)
#expect(String(data: fooData, encoding: .utf8)?.starts(with: mustBeInImage) ?? false)
}
}
}
}
@@ -0,0 +1,44 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import Foundation
import Testing
/// Tests that stop, kill, and delete return errors for non-existent containers.
@Suite
struct TestCLINotFound {
@Test func testStopNonExistentContainer() async throws {
try await ContainerFixture.with { f in
let result = try f.run(["stop", "does-not-exist"])
#expect(result.status != 0, "stop should fail for a non-existent container")
}
}
@Test func testKillNonExistentContainer() async throws {
try await ContainerFixture.with { f in
let result = try f.run(["kill", "does-not-exist"])
#expect(result.status != 0, "kill should fail for a non-existent container")
}
}
@Test func testDeleteNonExistentContainer() async throws {
try await ContainerFixture.with { f in
let result = try f.run(["delete", "does-not-exist"])
#expect(result.status != 0, "delete should fail for a non-existent container")
}
}
}
@@ -0,0 +1,85 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import Foundation
import Testing
/// Serial prune tests `container prune` affects all stopped containers regardless of name.
@Suite(.serialized)
struct TestCLIPruneCommandSerial {
@Test func testContainerPruneNoContainers() async throws {
try await ContainerFixture.with { f in
// Establish empty state the serial global pass runs after the concurrent
// pass, and any test that failed mid-cleanup could leave stopped containers
// that would break the "reclaimed zero" assertion. Machine-backing containers
// are excluded from `container delete --all` by design so this doesn't
// interfere with the machine plugin.
_ = try? f.run(["delete", "--all", "--force"])
let result = try f.run(["prune"]).check()
#expect(result.error.contains("Reclaimed Zero KB in disk space"), "should show no containers message")
}
}
@Test func testContainerPruneStoppedContainers() async throws {
try await ContainerFixture.with { f in
let image = ContainerFixture.warmupImages[0]
if try !f.isImagePresent(image) { try f.doPull(image) }
// One running container that must survive the prune.
try await f.withContainer(image: image, tag: "running", containerArgs: ["sleep", "3600"]) { npcName in
// Two containers to stop and prune.
try await f.withContainer(
image: image, tag: "prune0", containerArgs: ["sleep", "3600"], autoRemove: false
) { pc0Name in
try await f.withContainer(
image: image, tag: "prune1", containerArgs: ["sleep", "3600"], autoRemove: false
) { pc1Name in
let pc0Id = try f.getContainerId(pc0Name)
let pc1Id = try f.getContainerId(pc1Name)
try f.doStop(pc0Name)
try f.doStop(pc1Name)
// Poll until both containers reach stopped state.
let deadline = Date().addingTimeInterval(30)
while true {
let s0 = try f.getContainerStatus(pc0Name)
let s1 = try f.getContainerStatus(pc1Name)
if s0 == "stopped" && s1 == "stopped" { break }
guard Date() < deadline else {
throw CommandError.executionFailed(
"Timeout waiting for containers to stop: pc0=\(s0), pc1=\(s1)")
}
try await Task.sleep(for: .milliseconds(200))
}
let result = try f.run(["prune"]).check()
#expect(
result.output.contains(pc0Id) && result.output.contains(pc1Id),
"prune output should list stopped container IDs")
#expect(
!result.error.contains("Reclaimed Zero KB in disk space"),
"reclaimed space should not be zero")
let npcStatus = try f.getContainerStatus(npcName)
#expect(npcStatus == "running", "running container should not be pruned")
}
}
}
}
}
}
@@ -0,0 +1,92 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import Foundation
import Testing
/// Concurrent removal tests all use testID-scoped names.
@Suite
struct TestCLIRemove {
@Test func testDeleteStopped() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
// create without --rm so the container persists after being stopped
try f.doCreate(name: name, image: image)
try f.doRemove(name)
let result = try f.run(["inspect", name])
#expect(result.status != 0, "container should not exist after delete")
}
}
@Test func testDeleteAlias() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
try f.doCreate(name: name, image: image)
try f.run(["rm", name]).check("rm alias failed")
let result = try f.run(["inspect", name])
#expect(result.status != 0, "container should not exist after rm")
}
}
@Test func testDeleteForceRunning() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
try f.doRemove(name, force: true)
let result = try f.run(["inspect", name])
#expect(result.status != 0, "container should not exist after force delete")
}
}
}
@Test func testDeleteNoArgs() async throws {
try await ContainerFixture.with { f in
let result = try f.run(["delete"])
#expect(result.status != 0, "delete with no args should fail")
}
}
@Test func testDeleteExplicitIdsConflictWithAll() async throws {
try await ContainerFixture.with { f in
let result = try f.run(["delete", "--all", "some-container"])
#expect(result.status != 0, "delete --all with explicit ID should fail")
#expect(result.error.contains("conflict"))
}
}
@Test func testDeleteDuplicateIds() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
let name = "\(f.testID)-c"
try f.doCreate(name: name, image: image)
f.addCleanup { try f.doRemoveIfExists(name, force: true, ignoreFailure: true) }
let result = try f.run(["delete", name, name])
#expect(result.status == 0, "delete with duplicate IDs should succeed, stderr: \(result.error)")
let lines = result.output.split(separator: "\n").filter { $0.contains(name) }
#expect(lines.count == 1, "container should be deleted exactly once, got \(lines.count) lines")
}
}
@Test func testInspectMissingContainerFails() async throws {
try await ContainerFixture.with { f in
let result = try f.run(["inspect", "definitely-missing-container"])
#expect(result.status != 0, "inspect of missing container should fail")
#expect(result.error.contains("container not found"))
}
}
}
@@ -0,0 +1,76 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import Foundation
import Testing
/// Serial removal tests that use `delete --all` and affect global container state.
@Suite(.serialized)
struct TestCLIRemoveSerial {
@Test func testDeleteAllStopped() async throws {
try await ContainerFixture.with { f in
let image = ContainerFixture.warmupImages[0]
if try !f.isImagePresent(image) { try f.doPull(image) }
let name1 = "\(f.testID)-c1"
let name2 = "\(f.testID)-c2"
try f.doCreate(name: name1, image: image)
f.addCleanup { try f.doRemoveIfExists(name1, ignoreFailure: true) }
try f.doCreate(name: name2, image: image)
f.addCleanup { try f.doRemoveIfExists(name2, ignoreFailure: true) }
try f.run(["delete", "--all"]).check()
#expect(try f.run(["inspect", name1]).status != 0, "name1 should be deleted")
#expect(try f.run(["inspect", name2]).status != 0, "name2 should be deleted")
}
}
@Test func testDeleteAllSkipsRunning() async throws {
try await ContainerFixture.with { f in
let image = ContainerFixture.warmupImages[0]
if try !f.isImagePresent(image) { try f.doPull(image) }
let runningName = "\(f.testID)-running"
let stoppedName = "\(f.testID)-stopped"
try f.doLongRun(name: runningName, image: image, autoRemove: false)
f.addCleanup {
try? f.doStop(runningName)
try? f.doRemove(runningName)
}
try f.doCreate(name: stoppedName, image: image)
f.addCleanup { try f.doRemoveIfExists(stoppedName, ignoreFailure: true) }
try f.run(["delete", "--all"]).check()
#expect(try f.getContainerStatus(runningName) == "running", "running container should survive delete --all")
#expect(try f.run(["inspect", stoppedName]).status != 0, "stopped container should be deleted")
}
}
@Test func testDeleteAllForce() async throws {
try await ContainerFixture.with { f in
let image = ContainerFixture.warmupImages[0]
if try !f.isImagePresent(image) { try f.doPull(image) }
let name = "\(f.testID)-c"
try f.doLongRun(name: name, image: image, autoRemove: false)
f.addCleanup { try f.doRemoveIfExists(name, force: true, ignoreFailure: true) }
try f.run(["delete", "--all", "--force"]).check()
#expect(try f.run(["inspect", name]).status != 0, "container should be deleted by --force")
}
}
}
@@ -0,0 +1,91 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import Testing
@Suite
struct TestCLIRmRaceCondition {
@Test func testStopRmRace() async throws {
try await ContainerFixture.with { f in
let name = "\(f.testID)-c"
f.addCleanup { try f.doRemoveIfExists(name, force: true, ignoreFailure: true) }
try f.doCreate(name: name)
try f.doStart(name)
try await f.waitForContainerRunning(name)
try f.doStop(name)
// Immediately attempt removal both outcomes are valid:
// 1. Success: race condition prevention working perfectly
// 2. "not yet stopped" error: race detected and controlled
var raceConditionPrevented = false
var raceConditionDetected = false
do {
try f.doRemove(name)
raceConditionPrevented = true
} catch CommandError.nonZeroExit(_, let message) {
if message.contains("is not yet stopped and can not be deleted") {
raceConditionDetected = true
} else if message.contains("not found")
|| message.contains("failed to delete one or more containers")
{
raceConditionPrevented = true
} else {
Issue.record("Unexpected error message: \(message)")
return
}
} catch {
Issue.record("Unexpected error type: \(error)")
return
}
#expect(
raceConditionPrevented || raceConditionDetected,
"Expected either immediate success (race prevented) or controlled failure (race detected)"
)
if raceConditionPrevented { return }
// Race detected wait for background cleanup then retry with backoff.
try await Task.sleep(for: .seconds(2))
var attempts = 0
let maxAttempts = 5
while attempts < maxAttempts {
guard (try? f.getContainerStatus(name)) != nil else { break }
do {
try f.doRemove(name)
break
} catch CommandError.nonZeroExit(_, let message) {
if message.contains("not found") { break }
guard attempts < maxAttempts - 1 else {
throw CommandError.executionFailed(
"Failed to remove container after \(maxAttempts) attempts: \(message)")
}
let delay = 1 << attempts
try await Task.sleep(for: .seconds(delay))
attempts += 1
} catch {
guard attempts < maxAttempts - 1 else { throw error }
let delay = 1 << attempts
try await Task.sleep(for: .seconds(delay))
attempts += 1
}
}
}
}
}
@@ -0,0 +1,112 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerResource
import Foundation
import Testing
@Suite
struct TestCLIStatsCommand {
@Test func testStatsNoStreamJSONFormat() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let result = try f.run(["stats", "--format", "json", "--no-stream", name]).check()
let stats = try JSONDecoder().decode([ContainerStats].self, from: result.outputData)
#expect(stats.count == 1, "expected stats for one container")
#expect(stats[0].id == name, "container ID should match")
let memoryUsageBytes = try #require(stats[0].memoryUsageBytes)
let numProcesses = try #require(stats[0].numProcesses)
#expect(memoryUsageBytes > 0, "memory usage should be non-zero")
#expect(numProcesses >= 1, "should have at least one process")
}
}
}
@Test func testStatsIdleCPUPercentage() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image, containerArgs: ["sleep", "3600"]) { name in
let result = try f.run(["stats", "--no-stream", name]).check()
let lines = result.output.components(separatedBy: .newlines)
#expect(lines.count >= 2, "should have at least header and one data row")
let dataLine = try #require(lines.first { $0.contains(name) }, "should find container data row")
let columns = dataLine.split(separator: " ").filter { !$0.isEmpty }
#expect(columns.count >= 2, "should have at least 2 columns")
let cpuString = String(columns[1])
#expect(cpuString.hasSuffix("%"), "CPU column should end with %")
let cpuValue = try #require(Double(cpuString.dropLast()), "should parse CPU percentage")
#expect(cpuValue < 5.0, "idle container CPU should be < 5%, got \(cpuValue)%")
}
}
}
@Test func testStatsHighCPUPercentage() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image, containerArgs: ["sh", "-c", "while true; do :; done"]) { name in
let result = try f.run(["stats", "--no-stream", name]).check()
let lines = result.output.components(separatedBy: .newlines)
#expect(lines.count >= 2, "should have at least header and one data row")
let dataLine = try #require(lines.first { $0.contains(name) }, "should find container data row")
let columns = dataLine.split(separator: " ").filter { !$0.isEmpty }
#expect(columns.count >= 2, "should have at least 2 columns")
let cpuString = String(columns[1])
#expect(cpuString.hasSuffix("%"), "CPU column should end with %")
let cpuValue = try #require(Double(cpuString.dropLast()), "should parse CPU percentage")
#expect(cpuValue > 50.0, "busy container CPU should be > 50%, got \(cpuValue)%")
#expect(cpuValue < 150.0, "single busy loop should not exceed 150%, got \(cpuValue)%")
}
}
}
@Test func testStatsTableFormat() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image) { name in
let result = try f.run(["stats", "--no-stream", name]).check()
#expect(result.output.contains("Container ID"), "output should contain table header")
#expect(result.output.contains("Cpu %"), "output should contain CPU column")
#expect(result.output.contains("Memory Usage"), "output should contain Memory column")
#expect(result.output.contains(name), "output should contain container name")
}
}
}
@Test func testStatsAllContainers() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
// Run two containers simultaneously so both appear in the global stats snapshot.
try await f.withContainer(image: image, tag: "c1") { name1 in
try await f.withContainer(image: image, tag: "c2") { name2 in
let result = try f.run(["stats", "--format", "json", "--no-stream"]).check()
let stats = try JSONDecoder().decode([ContainerStats].self, from: result.outputData)
try #require(stats.count >= 2, "should have stats for at least 2 containers")
let ids = stats.map { $0.id }
#expect(ids.contains(name1), "should include first container")
#expect(ids.contains(name2), "should include second container")
}
}
}
}
@Test func testStatsNonExistentContainer() async throws {
try await ContainerFixture.with { f in
let result = try f.run(["stats", "--no-stream", "nonexistent-container-xyz"])
#expect(result.status != 0, "stats should fail for non-existent container")
}
}
}
@@ -0,0 +1,63 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import Testing
@Suite
struct TestCLIStop {
@Test func testStopWithExplicitSignal() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image, autoRemove: false) { name in
try f.doStop(name, signal: "SIGTERM")
#expect(try f.getContainerStatus(name) == "stopped")
}
}
}
@Test func testStopWithoutSignal() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image, autoRemove: false) { name in
try f.doStop(name, signal: nil)
#expect(try f.getContainerStatus(name) == "stopped")
}
}
}
@Test func testStopSignalInInspect() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image, autoRemove: false) { name in
let inspect = try f.inspectContainer(name)
// Alpine doesn't set a STOPSIGNAL, so this should be nil.
#expect(inspect.configuration.stopSignal == nil)
}
}
}
@Test func testStopIdempotent() async throws {
try await ContainerFixture.with { f in
let image = try f.copyWarmupImage(ContainerFixture.warmupImages[0])
try await f.withContainer(image: image, autoRemove: false) { name in
try f.doStop(name, signal: "SIGKILL")
#expect(try f.getContainerStatus(name) == "stopped")
// Stopping an already-stopped container should not fail.
try f.doStop(name, signal: "SIGKILL")
}
}
}
}