mirror of
https://github.com/apple/container.git
synced 2026-07-13 13:07:06 +00:00
9bd3c47345
- Part of #1833.
908 lines
36 KiB
Swift
908 lines
36 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// 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
|
|
|
|
/// Integration tests for container machine runtime commands: stop, inspect, run, set.
|
|
/// Serialized because VM operations share system resources (memory, CPU) and concurrent
|
|
/// VMs could interfere with each other or exhaust available resources.
|
|
@Suite(.serialized)
|
|
struct TestCLIMachineRuntimeSerial {
|
|
private let machineImage = "ghcr.io/linuxcontainers/alpine:3.20"
|
|
|
|
// MARK: - Stop tests
|
|
|
|
@Test func testStopRunningMachine() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let before = try f.doMachineInspect(name: name)
|
|
#expect(before.startedDate != nil, "running machine should have startedDate")
|
|
|
|
let output = try f.doMachineStop(name: name)
|
|
#expect(output == name, "stop should output the machine name")
|
|
|
|
let after = try f.doMachineInspect(name: name)
|
|
#expect(after.status == "stopped")
|
|
#expect(after.startedDate == nil, "stopped machine should not have startedDate")
|
|
}
|
|
}
|
|
|
|
@Test func testStopIdempotent() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
let output = try f.doMachineStop(name: name)
|
|
#expect(output == name, "stop on already-stopped machine should succeed")
|
|
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.status == "stopped")
|
|
}
|
|
}
|
|
|
|
@Test func testStopNonExistentMachine() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "nonexistent-\(f.testID)"
|
|
let result = try f.runMachine(["stop", name])
|
|
#expect(result.status != 0)
|
|
#expect(result.error.contains("not found"))
|
|
}
|
|
}
|
|
|
|
// MARK: - Inspect tests
|
|
|
|
@Test func testInspectStoppedMachine() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.status == "stopped")
|
|
#expect(snapshot.startedDate == nil)
|
|
#expect(snapshot.id == name)
|
|
#expect(snapshot.image.reference.contains("alpine"))
|
|
#expect(snapshot.createdDate != nil)
|
|
#expect(snapshot.ipAddress == nil)
|
|
#expect(snapshot.cpus > 0)
|
|
#expect(snapshot.memory > 0)
|
|
}
|
|
}
|
|
|
|
@Test func testInspectRunningMachine() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.status == "running")
|
|
#expect(snapshot.startedDate != nil)
|
|
#expect(snapshot.id == name)
|
|
#expect(snapshot.platform.os == "linux")
|
|
#expect(snapshot.ipAddress != nil)
|
|
}
|
|
}
|
|
|
|
@Test func testInspectNonExistentMachine() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "nonexistent-\(f.testID)"
|
|
let result = try f.runMachine(["inspect", name])
|
|
#expect(result.status != 0)
|
|
#expect(result.error.contains("not found"))
|
|
}
|
|
}
|
|
|
|
// MARK: - Run tests
|
|
|
|
@Test func testRunSimpleCommand() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let output = try f.doMachineRun(name: name, root: true, command: ["echo", "hello"])
|
|
#expect(output.trimmingCharacters(in: .whitespacesAndNewlines) == "hello")
|
|
}
|
|
}
|
|
|
|
@Test func testRunAutoBoots() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
let before = try f.doMachineInspect(name: name)
|
|
#expect(before.status == "stopped")
|
|
|
|
let output = try f.doMachineRun(name: name, root: true, command: ["echo", "autoboot"])
|
|
#expect(output.trimmingCharacters(in: .whitespacesAndNewlines) == "autoboot")
|
|
|
|
let after = try f.doMachineInspect(name: name)
|
|
#expect(after.status == "running")
|
|
}
|
|
}
|
|
|
|
@Test func testRunAsRoot() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let uid = try f.doMachineRun(name: name, root: true, command: ["id", "-u"])
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(uid == "0", "running with --root should execute as uid 0")
|
|
}
|
|
}
|
|
|
|
@Test func testRunAsHostUser() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let hostUid = getuid()
|
|
let uid = try f.doMachineRun(name: name, command: ["id", "-u"])
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(uid == "\(hostUid)", "default run should use host user's UID")
|
|
}
|
|
}
|
|
|
|
@Test func testRunWithEnvironment() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let output = try f.doMachineRun(
|
|
name: name, root: true, env: ["MY_VAR=hello_world"],
|
|
command: ["echo", "$MY_VAR"])
|
|
#expect(output.trimmingCharacters(in: .whitespacesAndNewlines) == "hello_world")
|
|
}
|
|
}
|
|
|
|
@Test func testRunWithCwd() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let output = try f.doMachineRun(name: name, root: true, cwd: "/tmp", command: ["pwd"])
|
|
#expect(output.trimmingCharacters(in: .whitespacesAndNewlines) == "/tmp")
|
|
}
|
|
}
|
|
|
|
@Test func testRunNonExistentMachine() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "nonexistent-\(f.testID)"
|
|
let result = try f.runMachine(["run", "-n", name, "echo", "test"])
|
|
#expect(result.status != 0)
|
|
#expect(result.error.contains("not found"))
|
|
}
|
|
}
|
|
|
|
@Test func testRunDefaultHostsEntries() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let inspect = try f.doMachineInspect(name: name)
|
|
let ip = try #require(inspect.ipAddress, "running machine should have an IP address")
|
|
|
|
let output = try f.doMachineRun(name: name, root: true, command: ["cat", "/etc/hosts"])
|
|
let lines = output.split(separator: "\n")
|
|
let expectedEntries = [("127.0.0.1", "localhost"), (ip, name)]
|
|
for (i, line) in lines.enumerated() {
|
|
guard i < expectedEntries.count else { break }
|
|
let words = line.split(separator: " ").map { String($0) }
|
|
#expect(words.count >= 2)
|
|
#expect(expectedEntries[i].0 == words[0])
|
|
#expect(expectedEntries[i].1 == words[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test func testRunCommandInShell() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let output = try f.doMachineRun(name: name, root: true, command: ["echo", "$0"])
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(output == "/bin/sh", "alpine shell should expand $0 to /bin/sh")
|
|
}
|
|
}
|
|
|
|
@Test func testRunCommandExitCode() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let result = try f.runMachine(["run", "-n", name, "--root", "exit", "42"])
|
|
#expect(result.status == 42, "exit code should propagate from command")
|
|
}
|
|
}
|
|
|
|
@Test func testRunMultipleEnvVars() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let result = try f.runMachine([
|
|
"run", "-n", name, "--root",
|
|
"-e", "VAR1=one", "-e", "VAR2=two", "-e", "VAR3=three",
|
|
"echo", "$VAR1-$VAR2-$VAR3",
|
|
])
|
|
#expect(result.status == 0)
|
|
#expect(result.output.trimmingCharacters(in: .whitespacesAndNewlines) == "one-two-three")
|
|
}
|
|
}
|
|
|
|
@Test func testRunWithUid() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let result = try f.runMachine(["run", "-n", name, "--uid", "1000", "id", "-u"])
|
|
#expect(result.status == 0)
|
|
#expect(result.output.trimmingCharacters(in: .whitespacesAndNewlines) == "1000")
|
|
}
|
|
}
|
|
|
|
@Test func testRunWithGid() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let result = try f.runMachine(["run", "-n", name, "--root", "--gid", "1000", "id", "-G"])
|
|
#expect(result.status == 0)
|
|
#expect(result.output.trimmingCharacters(in: .whitespacesAndNewlines) == "0 1000")
|
|
}
|
|
}
|
|
|
|
@Test func testRunWithUserFlag() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let result = try f.runMachine([
|
|
"run", "-n", name, "--user", "1000:1000",
|
|
"echo", "$(id -u):$(id -g)",
|
|
])
|
|
#expect(result.status == 0)
|
|
#expect(result.output.trimmingCharacters(in: .whitespacesAndNewlines) == "1000:1000")
|
|
}
|
|
}
|
|
|
|
@Test func testRunWithEnvFile() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let envFile = f.testDir.appending("test.env")
|
|
try "TEST_VAR=from_file\n".write(toFile: envFile.string, atomically: true, encoding: .utf8)
|
|
|
|
let result = try f.runMachine([
|
|
"run", "-n", name, "--root", "--env-file", envFile.string, "echo", "$TEST_VAR",
|
|
])
|
|
#expect(result.status == 0)
|
|
#expect(result.output.trimmingCharacters(in: .whitespacesAndNewlines) == "from_file")
|
|
}
|
|
}
|
|
|
|
// MARK: - List tests
|
|
|
|
@Test func testListRunningMachines() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let result = try f.runMachine(["ls"]).check()
|
|
#expect(result.output.contains(name))
|
|
}
|
|
}
|
|
|
|
@Test func testListAllMachines() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
let result = try f.runMachine(["ls"]).check()
|
|
#expect(result.output.contains(name), "stopped machine should appear in list")
|
|
}
|
|
}
|
|
|
|
@Test func testListQuietMode() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let result = try f.runMachine(["ls", "-q"]).check()
|
|
#expect(result.output.trimmingCharacters(in: .whitespacesAndNewlines) == name)
|
|
}
|
|
}
|
|
|
|
@Test func testListJsonFormat() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let result = try f.runMachine(["ls", "--format", "json"]).check()
|
|
let decoder = JSONDecoder()
|
|
decoder.dateDecodingStrategy = .iso8601
|
|
let items = try decoder.decode([MachineListItem].self, from: result.outputData)
|
|
let item = items.first { $0.id == name }
|
|
try #require(item != nil)
|
|
#expect(item?.status == "running")
|
|
#expect(item?.ipAddress != nil)
|
|
#expect(item?.cpus ?? 0 > 0)
|
|
#expect(item?.memory ?? 0 > 0)
|
|
#expect(item?.createdDate != nil)
|
|
}
|
|
}
|
|
|
|
// MARK: - set-default tests
|
|
|
|
@Test func testSetDefault() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
let result = try f.runMachine(["set-default", name]).check()
|
|
#expect(result.output.trimmingCharacters(in: .whitespacesAndNewlines) == name)
|
|
|
|
let list = try f.runMachine(["ls"]).check()
|
|
#expect(list.output.contains("*"), "machine should be marked as default in list")
|
|
}
|
|
}
|
|
|
|
@Test func testSetDefaultNonExistent() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let result = try f.runMachine(["set-default", "nonexistent-\(f.testID)"])
|
|
#expect(result.status != 0)
|
|
#expect(result.error.contains("not found"))
|
|
}
|
|
}
|
|
|
|
@Test func testSetDefaultSwitching() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name1 = "\(f.testID)-m1"
|
|
let name2 = "\(f.testID)-m2"
|
|
f.addCleanup {
|
|
f.cleanupMachine(name1)
|
|
f.cleanupMachine(name2)
|
|
}
|
|
try f.doMachineCreate(name: name1, image: machineImage)
|
|
try f.doMachineCreate(name: name2, image: machineImage)
|
|
|
|
try f.runMachine(["set-default", name1]).check()
|
|
let result = try f.runMachine(["set-default", name2]).check()
|
|
#expect(result.output.trimmingCharacters(in: .whitespacesAndNewlines) == name2)
|
|
|
|
let snapshot = try f.doMachineInspect()
|
|
#expect(snapshot.id == name2, "inspect without ID should use the new default")
|
|
}
|
|
}
|
|
|
|
@Test func testInspectUsesDefault() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.runMachine(["set-default", name]).check()
|
|
|
|
let snapshot = try f.doMachineInspect()
|
|
#expect(snapshot.id == name)
|
|
}
|
|
}
|
|
|
|
@Test func testRunUsesDefault() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.runMachine(["set-default", name]).check()
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let result = try f.runMachine(["run", "--root", "echo", "default-test"])
|
|
#expect(result.status == 0)
|
|
#expect(result.output.trimmingCharacters(in: .whitespacesAndNewlines) == "default-test")
|
|
}
|
|
}
|
|
|
|
// MARK: - User / home tests
|
|
|
|
@Test func testFirstBootCreatesSudoersEntry() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let username = NSUserName()
|
|
let output = try f.doMachineRun(
|
|
name: name, root: true,
|
|
command: ["cat", "/etc/sudoers.d/\(username)"]
|
|
)
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(output == "\(username) ALL=(ALL) NOPASSWD:ALL")
|
|
}
|
|
}
|
|
|
|
@Test func testUserSetupIdempotent() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let uid1 = try f.doMachineRun(name: name, command: ["id", "-u"])
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let uid2 = try f.doMachineRun(name: name, command: ["id", "-u"])
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(uid1 == uid2)
|
|
#expect(uid1 == "\(getuid())")
|
|
}
|
|
}
|
|
|
|
@Test func testHostUserHasCorrectHome() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let home = try f.doMachineRun(name: name, command: ["echo", "$HOME"])
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(home == "/home/\(NSUserName())")
|
|
}
|
|
}
|
|
|
|
// MARK: - set tests
|
|
|
|
@Test func testSetCpus() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
try f.runMachine(["set", "--name", name, "cpus=4"]).check()
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.cpus == 4)
|
|
}
|
|
}
|
|
|
|
@Test func testSetMemory() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
try f.runMachine(["set", "--name", name, "memory=8G"]).check()
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.memory == UInt64(8 * 1024 * 1024 * 1024))
|
|
}
|
|
}
|
|
|
|
@Test func testSetMultiple() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
try f.runMachine(["set", "--name", name, "cpus=2", "memory=4G"]).check()
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.cpus == 2)
|
|
#expect(snapshot.memory == UInt64(4 * 1024 * 1024 * 1024))
|
|
}
|
|
}
|
|
|
|
@Test func testSetInvalidKey() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
let result = try f.runMachine(["set", "--name", name, "bogus=value"])
|
|
#expect(result.status != 0)
|
|
}
|
|
}
|
|
|
|
@Test func testSetRunningWarning() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let result = try f.runMachine(["set", "--name", name, "cpus=2"])
|
|
#expect(result.status == 0)
|
|
#expect(result.error.contains("will take effect"))
|
|
}
|
|
}
|
|
|
|
@Test func testSetHomeMount() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
try f.runMachine(["set", "--name", name, "home-mount=ro"]).check()
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.homeMount == "ro")
|
|
}
|
|
}
|
|
|
|
@Test func testSetHomeMountInvalid() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
let result = try f.runMachine(["set", "--name", name, "home-mount=badvalue"])
|
|
#expect(result.status != 0)
|
|
}
|
|
}
|
|
|
|
// MARK: - Create config flag tests
|
|
|
|
@Test func testCreateWithCpus() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage, extraArgs: ["--cpus", "2"])
|
|
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.cpus == 2)
|
|
}
|
|
}
|
|
|
|
@Test func testGuestCpuCountMatchesRequested() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage, extraArgs: ["--cpus", "2"])
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let guestCpus = try f.doMachineRun(name: name, root: true, command: ["nproc"])
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(guestCpus == "2")
|
|
}
|
|
}
|
|
|
|
@Test func testCreateWithMemory() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage, extraArgs: ["--memory", "2G"])
|
|
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.memory == UInt64(2 * 1024 * 1024 * 1024))
|
|
}
|
|
}
|
|
|
|
@Test func testCreateWithHomeMount() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage, extraArgs: ["--home-mount", "none"])
|
|
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.homeMount == "none")
|
|
}
|
|
}
|
|
|
|
@Test func testCreateAutoBoots() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
// Omit --no-boot so the machine boots automatically.
|
|
try f.runMachine(["create", "--name", name, machineImage]).check()
|
|
|
|
let snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.status == "running")
|
|
#expect(snapshot.startedDate != nil)
|
|
}
|
|
}
|
|
|
|
@Test func testAmd64PlatformSupported() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(
|
|
name: name, image: "alpine:3.22",
|
|
extraArgs: ["--platform", "linux/amd64"])
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let arch = try f.doMachineRun(name: name, root: true, command: ["uname", "-m"])
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(arch == "x86_64")
|
|
}
|
|
}
|
|
|
|
// MARK: - Logs tests
|
|
|
|
@Test func testLogs() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
try f.doMachineStop(name: name)
|
|
|
|
let boot = try f.runMachine(["logs", "--boot", name]).check()
|
|
#expect(!boot.output.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
|
|
|
let stdio = try f.runMachine(["logs", name])
|
|
#expect(stdio.status == 0)
|
|
}
|
|
}
|
|
|
|
@Test func testLogsWhileRunning() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let boot = try f.runMachine(["logs", "--boot", name]).check()
|
|
#expect(!boot.output.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
|
|
|
let stdio = try f.runMachine(["logs", name])
|
|
#expect(stdio.status == 0)
|
|
}
|
|
}
|
|
|
|
@Test func testLogsNonExistentMachine() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let result = try f.runMachine(["logs", "nonexistent-\(f.testID)"])
|
|
#expect(result.status != 0)
|
|
#expect(result.error.contains("not found"))
|
|
}
|
|
}
|
|
|
|
// MARK: - Machine isolation from container commands
|
|
|
|
@Test func testMachineNotInContainerList() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let inspect = try f.doMachineInspect(name: name)
|
|
let containerId = try #require(inspect.containerId)
|
|
|
|
let running = try f.run(["ls", "-q"]).check()
|
|
#expect(!running.output.contains(containerId))
|
|
|
|
let all = try f.run(["ls", "-a", "-q"]).check()
|
|
#expect(!all.output.contains(containerId))
|
|
}
|
|
}
|
|
|
|
@Test func testMachineNotDeletedByRmAll() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let inspect = try f.doMachineInspect(name: name)
|
|
let containerId = try #require(inspect.containerId)
|
|
|
|
try f.run(["delete", "--all"]).check()
|
|
|
|
let containerInspect = try f.inspectContainer(containerId)
|
|
#expect(
|
|
containerInspect.status.state == "running",
|
|
"machine container should survive 'container delete --all'")
|
|
}
|
|
}
|
|
|
|
@Test func testMachineNotKilledByKillAll() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let inspect = try f.doMachineInspect(name: name)
|
|
let containerId = try #require(inspect.containerId)
|
|
|
|
try f.run(["kill", "--all"]).check()
|
|
|
|
let containerInspect = try f.inspectContainer(containerId)
|
|
#expect(
|
|
containerInspect.status.state == "running",
|
|
"machine container should survive 'container kill --all'")
|
|
}
|
|
}
|
|
|
|
@Test func testMachineNotStoppedByStopAll() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let inspect = try f.doMachineInspect(name: name)
|
|
let containerId = try #require(inspect.containerId)
|
|
|
|
try f.run(["stop", "--all"]).check()
|
|
|
|
let containerInspect = try f.inspectContainer(containerId)
|
|
#expect(
|
|
containerInspect.status.state == "running",
|
|
"machine container should survive 'container stop --all'")
|
|
}
|
|
}
|
|
|
|
@Test func testContainerExitState() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let inspect = try f.doMachineInspect(name: name)
|
|
let containerId = try #require(
|
|
inspect.containerId,
|
|
"running machine should have a containerId")
|
|
|
|
try f.doStop(containerId)
|
|
try await f.waitForMachineStatus(name, status: "stopped")
|
|
|
|
let after = try f.doMachineInspect(name: name)
|
|
#expect(after.status == "stopped")
|
|
#expect(after.containerId == nil)
|
|
#expect(after.startedDate == nil)
|
|
}
|
|
}
|
|
|
|
// MARK: - Lifecycle test
|
|
|
|
@Test func testFullLifecycle() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
|
|
var snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.status == "stopped")
|
|
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.status == "running")
|
|
|
|
let output = try f.doMachineRun(name: name, root: true, command: ["hostname"])
|
|
#expect(!output.isEmpty)
|
|
|
|
try f.doMachineStop(name: name)
|
|
snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.status == "stopped")
|
|
|
|
try f.doMachineBoot(name: name)
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
snapshot = try f.doMachineInspect(name: name)
|
|
#expect(snapshot.status == "running")
|
|
}
|
|
}
|
|
|
|
// MARK: - SSH forwarding
|
|
|
|
@Test func testSSHForwarding() async throws {
|
|
try await ContainerFixture.with { f in
|
|
let name = "\(f.testID)-machine"
|
|
f.addCleanup { f.cleanupMachine(name) }
|
|
|
|
let socketPath = try f.makeFakeSSHAgentSocket()
|
|
try await Task.sleep(for: .seconds(1))
|
|
|
|
try f.doMachineCreate(name: name, image: machineImage)
|
|
try f.runMachine(
|
|
["run", "--root", "-n", name, "true"],
|
|
env: ["SSH_AUTH_SOCK": socketPath]
|
|
).check()
|
|
try await f.waitForMachineStatus(name, status: "running")
|
|
|
|
let sockValue = try f.doMachineRun(
|
|
name: name, root: true,
|
|
command: ["echo", "$SSH_AUTH_SOCK"]
|
|
)
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(sockValue == "/var/host-services/ssh-auth.sock")
|
|
|
|
let exists = try f.doMachineRun(
|
|
name: name, root: true,
|
|
command: [
|
|
"[ -S /var/host-services/ssh-auth.sock ]", "&&", "echo", "exists", "||", "echo", "missing",
|
|
]
|
|
).trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(exists == "exists")
|
|
}
|
|
}
|
|
}
|