//===----------------------------------------------------------------------===// // 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 ContainerPersistence import ContainerTestSupport import ContainerizationArchive import Foundation import Testing /// Tests for `container system kernel set`. Each test modifies the global default /// kernel binary, so the suite must run fully serialised. /// /// None of these tests touch the network: they capture the bytes of whatever /// kernel is already installed (from the previous test, or from the initial /// `system start --enable-kernel-install`) and repackage them into a fixture /// tar via ``KernelFixture``, so the real install/extract/digest-verify/ /// guest-boot code paths are still exercised end to end. @Suite(.serialized) struct TestCLIKernelSetSerial { private let fixture = KernelFixture() private let defaultBinaryPath = ContainerSystemConfig().kernel.binaryPath /// Kernel release string parsed from the binary filename. /// /// Kata names artifacts `vmlinux-{release}-{build}[-{variant}]` (e.g. /// `vmlinux-6.18.35-197-debug`) while `uname -r` in the guest reports only the /// release (`6.18.35`). We drop the first all-digit component after the release /// and everything past it, preserving non-numeric components like `-rc1` or `-rt` /// that ARE part of the upstream release string. A name with no build number is /// returned unchanged. private var expectedKernelRelease: String { let filename = URL(fileURLWithPath: defaultBinaryPath).lastPathComponent let prefix = "vmlinux-" let raw = filename.hasPrefix(prefix) ? String(filename.dropFirst(prefix.count)) : filename let components = raw.split(separator: "-", omittingEmptySubsequences: false) guard let buildIndex = components.dropFirst().firstIndex(where: { !$0.isEmpty && $0.allSatisfy(\.isNumber) }) else { return raw } return components[.. URL { let capturedBinary = URL(filePath: f.testDir.string).appending(path: "captured-kernel") try fixture.captureInstalledBinary(to: capturedBinary) f.addCleanup { Self.restoreCapturedKernel(f, from: capturedBinary) } return capturedBinary } /// Restores the kernel captured at the start of a test. Used as cleanup at the /// end of every test so a failure here doesn't silently affect the next test — /// the suite is serialised and the kernel is global state. The fixture's /// cleanup runner swallows throws with `try?`, so we record an issue against /// the current test rather than rely on error propagation. private static func restoreCapturedKernel(_ f: ContainerFixture, from capturedBinary: URL) { do { let result = try f.run(["system", "kernel", "set", "--force", "--binary", capturedBinary.path]) if result.status != 0 { Issue.record("kernel restore from captured binary failed (status \(result.status)): \(result.error)") } } catch { Issue.record("kernel restore from captured binary could not run: \(error)") } } /// Boots a container and verifies that the guest is running the kernel just set /// on the host — that is, `uname -r` matches the release parsed from the kernel /// binary filename (see ``expectedKernelRelease``). private func validateGuestKernel(_ f: ContainerFixture) async throws { let image = WarmupImage.alpine320.rawValue if try !f.isImagePresent(image) { try f.doPull(image) } try await f.withContainer(image: image) { name in let release = try f.doExec(name, cmd: ["uname", "-r"]) .trimmingCharacters(in: .whitespacesAndNewlines) #expect( release == expectedKernelRelease, "expected guest kernel \(expectedKernelRelease), got \(release)") } } }