From f92c0cd8c67d0c46d4e464dee84bf8da863371cc Mon Sep 17 00:00:00 2001 From: Eric Ernst Date: Tue, 26 Aug 2025 13:13:05 -0700 Subject: [PATCH] containerization: Add additional tests (#273) Add additional tests, with varying levels of utility. Before: Overall Code Coverage: 41.73% After: Overall Code Coverage: 42.95% Signed-off-by: Eric Ernst --- Tests/ContainerizationTests/DNSTests.swift | 54 +++++++++++++++ Tests/ContainerizationTests/HashTests.swift | 38 ++++++++++ Tests/ContainerizationTests/HostsTests.swift | 68 ++++++++++++++++++ Tests/ContainerizationTests/ImageTests.swift | 37 ++++++++++ Tests/ContainerizationTests/KernelTests.swift | 16 +++++ .../LinuxContainerTests.swift | 69 +++++++++++++++++++ Tests/ContainerizationTests/MountTests.swift | 43 ++++++++++++ 7 files changed, 325 insertions(+) create mode 100644 Tests/ContainerizationTests/DNSTests.swift create mode 100644 Tests/ContainerizationTests/HashTests.swift create mode 100644 Tests/ContainerizationTests/HostsTests.swift create mode 100644 Tests/ContainerizationTests/ImageTests.swift create mode 100644 Tests/ContainerizationTests/LinuxContainerTests.swift create mode 100644 Tests/ContainerizationTests/MountTests.swift diff --git a/Tests/ContainerizationTests/DNSTests.swift b/Tests/ContainerizationTests/DNSTests.swift new file mode 100644 index 00000000..a4ab157e --- /dev/null +++ b/Tests/ContainerizationTests/DNSTests.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// 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 + +@testable import Containerization + +struct DNSTests { + + @Test func dnsResolvConfWithAllFields() { + let dns = DNS( + nameservers: ["8.8.8.8", "1.1.1.1"], + domain: "example.com", + searchDomains: ["internal.com", "test.com"], + options: ["ndots:2", "timeout:1"] + ) + + let expected = "nameserver 8.8.8.8\nnameserver 1.1.1.1\ndomain example.com\nsearch internal.com test.com\nopts ndots:2 timeout:1\n" + #expect(dns.resolvConf == expected) + } + + @Test func dnsResolvConfWithEmptyFields() { + let dns = DNS( + nameservers: [], + domain: nil, + searchDomains: [], + options: [] + ) + + // Should return empty string when all fields are empty + #expect(dns.resolvConf == "") + } + + @Test func dnsResolvConfWithOnlyNameservers() { + let dns = DNS(nameservers: ["8.8.8.8"]) + + let expected = "nameserver 8.8.8.8\n" + #expect(dns.resolvConf == expected) + } +} diff --git a/Tests/ContainerizationTests/HashTests.swift b/Tests/ContainerizationTests/HashTests.swift new file mode 100644 index 00000000..f18183cf --- /dev/null +++ b/Tests/ContainerizationTests/HashTests.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// 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 + +@testable import Containerization + +struct HashTests { + + @Test func hashMountSourceWithValidString() throws { + let result = try hashMountSource(source: "/valid/path") + + // Should produce a non-empty hash + #expect(!result.isEmpty) + + // Same input should produce same hash (deterministic) + let result2 = try hashMountSource(source: "/valid/path") + #expect(result == result2) + + // Different inputs should produce different hashes + let result3 = try hashMountSource(source: "/different/path") + #expect(result != result3) + } +} diff --git a/Tests/ContainerizationTests/HostsTests.swift b/Tests/ContainerizationTests/HostsTests.swift new file mode 100644 index 00000000..e9116d4c --- /dev/null +++ b/Tests/ContainerizationTests/HostsTests.swift @@ -0,0 +1,68 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// 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 + +@testable import Containerization + +struct HostsTests { + + @Test func hostsEntryRenderedWithAllFields() { + let entry = Hosts.Entry( + ipAddress: "192.168.1.100", + hostnames: ["myserver", "server.local"], + comment: "My local server" + ) + + let expected = "192.168.1.100 myserver server.local # My local server " + #expect(entry.rendered == expected) + } + + @Test func hostsEntryRenderedWithoutComment() { + let entry = Hosts.Entry( + ipAddress: "10.0.0.1", + hostnames: ["gateway"] + ) + + let expected = "10.0.0.1 gateway" + #expect(entry.rendered == expected) + } + + @Test func hostsEntryRenderedWithEmptyHostnames() { + let entry = Hosts.Entry( + ipAddress: "172.16.0.1", + hostnames: [], + comment: "Empty hostnames" + ) + + let expected = "172.16.0.1 # Empty hostnames " + #expect(entry.rendered == expected) + } + + @Test func hostsFileWithCommentAndEntries() { + let hosts = Hosts( + entries: [ + Hosts.Entry(ipAddress: "127.0.0.1", hostnames: ["localhost"]), + Hosts.Entry(ipAddress: "192.168.1.10", hostnames: ["server"], comment: "Main server"), + ], + comment: "Generated hosts file" + ) + + let expected = "# Generated hosts file\n127.0.0.1 localhost\n192.168.1.10 server # Main server \n" + #expect(hosts.hostsFile == expected) + } +} diff --git a/Tests/ContainerizationTests/ImageTests.swift b/Tests/ContainerizationTests/ImageTests.swift new file mode 100644 index 00000000..d77582cc --- /dev/null +++ b/Tests/ContainerizationTests/ImageTests.swift @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// 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 ContainerizationOCI +import Foundation +import Testing + +@testable import Containerization + +struct ImageTests { + + @Test func imageDescriptionComputedProperties() { + let descriptor = Descriptor( + mediaType: "application/vnd.oci.image.manifest.v1+json", + digest: "sha256:abc123def456", + size: 1024 + ) + let description = Image.Description(reference: "myapp:latest", descriptor: descriptor) + + #expect(description.digest == "sha256:abc123def456") + #expect(description.mediaType == "application/vnd.oci.image.manifest.v1+json") + #expect(description.reference == "myapp:latest") + } +} diff --git a/Tests/ContainerizationTests/KernelTests.swift b/Tests/ContainerizationTests/KernelTests.swift index 835e49eb..65a794bf 100644 --- a/Tests/ContainerizationTests/KernelTests.swift +++ b/Tests/ContainerizationTests/KernelTests.swift @@ -39,4 +39,20 @@ final class KernelTests { let cmdline = kernel.commandLine.kernelArgs.joined(separator: " ") #expect(cmdline == expected) } + + @Test func kernelCommandLineInitWithDebugTrue() { + let commandLine = Kernel.CommandLine(debug: true, panic: 5, initArgs: ["--verbose"]) + + #expect(commandLine.kernelArgs == ["console=hvc0", "tsc=reliable", "debug", "panic=5"]) + #expect(commandLine.initArgs == ["--verbose"]) + } + + @Test func kernelCommandLineMutatingMethods() { + var commandLine = Kernel.CommandLine(kernelArgs: ["console=hvc0"], initArgs: []) + + commandLine.addDebug() + commandLine.addPanic(level: 10) + + #expect(commandLine.kernelArgs == ["console=hvc0", "debug", "panic=10"]) + } } diff --git a/Tests/ContainerizationTests/LinuxContainerTests.swift b/Tests/ContainerizationTests/LinuxContainerTests.swift new file mode 100644 index 00000000..d183d787 --- /dev/null +++ b/Tests/ContainerizationTests/LinuxContainerTests.swift @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// 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 ContainerizationOCI +import Foundation +import Testing + +@testable import Containerization + +struct LinuxContainerTests { + + @Test func processInitFromImageConfigWithAllFields() { + let imageConfig = ImageConfig( + user: "appuser", + env: ["NODE_ENV=production", "PORT=3000"], + entrypoint: ["/usr/bin/node"], + cmd: ["app.js", "--verbose"], + workingDir: "/app" + ) + + let process = LinuxContainer.Configuration.Process(from: imageConfig) + + #expect(process.workingDirectory == "/app") + #expect(process.environmentVariables == ["NODE_ENV=production", "PORT=3000"]) + #expect(process.arguments == ["/usr/bin/node", "app.js", "--verbose"]) + #expect(process.user.username == "appuser") + } + + @Test func processInitFromImageConfigWithNilValues() { + let imageConfig = ImageConfig( + user: nil, + env: nil, + entrypoint: nil, + cmd: nil, + workingDir: nil + ) + + let process = LinuxContainer.Configuration.Process(from: imageConfig) + + #expect(process.workingDirectory == "/") + #expect(process.environmentVariables == []) + #expect(process.arguments == []) + #expect(process.user.username == "") // Default User() has empty string username + } + + @Test func processInitFromImageConfigEntrypointAndCmdConcatenation() { + let imageConfig = ImageConfig( + entrypoint: ["/bin/sh", "-c"], + cmd: ["echo 'hello'", "&&", "sleep 10"] + ) + + let process = LinuxContainer.Configuration.Process(from: imageConfig) + + #expect(process.arguments == ["/bin/sh", "-c", "echo 'hello'", "&&", "sleep 10"]) + } +} diff --git a/Tests/ContainerizationTests/MountTests.swift b/Tests/ContainerizationTests/MountTests.swift new file mode 100644 index 00000000..85db4c5b --- /dev/null +++ b/Tests/ContainerizationTests/MountTests.swift @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. +// +// 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 + +@testable import Containerization + +struct MountTests { + + @Test func mountShareCreatesVirtiofsMount() { + let mount = Mount.share( + source: "/host/shared", + destination: "/guest/shared", + options: ["rw", "noatime"], + runtimeOptions: ["tag=shared"] + ) + + #expect(mount.type == "virtiofs") + #expect(mount.source == "/host/shared") + #expect(mount.destination == "/guest/shared") + #expect(mount.options == ["rw", "noatime"]) + + if case .virtiofs(let opts) = mount.runtimeOptions { + #expect(opts == ["tag=shared"]) + } else { + #expect(Bool(false), "Expected virtiofs runtime options") + } + } +}