mirror of
https://github.com/apple/container.git
synced 2026-08-24 02:24:19 -05:00
Integration test: cache warmup image tarfiles. (#2074)
- When pulling warmup images for concurrent tests, save the images to a cache directory under the application root. - Serial tests that aren't testing pull can save time by restoring a cached warmup image.
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Foundation
|
||||
import SystemPackage
|
||||
import Testing
|
||||
|
||||
// MARK: - Image inspect types
|
||||
@@ -69,6 +70,27 @@ extension ContainerFixture {
|
||||
try run(args).check()
|
||||
}
|
||||
|
||||
/// Saves `image` to ``WarmupImage/cacheTarPath``, overwriting any existing archive.
|
||||
///
|
||||
/// Called once per image by the `ImageWarmup` suite. No `--platform`/`--os`/`--arch`
|
||||
/// is passed, so `image save` captures every platform the image supports.
|
||||
public func cacheWarmupImage(_ image: WarmupImage) throws {
|
||||
try FileManager.default.createDirectory(
|
||||
atPath: WarmupImage.cacheDirectory.string, withIntermediateDirectories: true)
|
||||
try run(["image", "save", "--output", image.cacheTarPath.string, image.rawValue])
|
||||
.check("failed to cache \(image.rawValue)")
|
||||
}
|
||||
|
||||
/// Reloads `image` from its cached tar archive rather than pulling over the network.
|
||||
///
|
||||
/// Use this in serial tests to restore a warmup image after a destructive operation
|
||||
/// (`image rm --all`, `image prune`) removes it from the store. Requires the
|
||||
/// `ImageWarmup` suite to have already run and populated the cache.
|
||||
public func restoreWarmupImage(_ image: WarmupImage) throws {
|
||||
try run(["image", "load", "--input", image.cacheTarPath.string])
|
||||
.check("failed to restore \(image.rawValue) from cache")
|
||||
}
|
||||
|
||||
/// Returns the full inspect output for an image, including variant information.
|
||||
public func doInspectImages(_ name: String) throws -> [ImageInspectOutput] {
|
||||
let result = try run(["image", "inspect", name]).check()
|
||||
|
||||
@@ -14,10 +14,29 @@
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ContainerPersistence
|
||||
import SystemPackage
|
||||
|
||||
/// Images preloaded by the ``ImageWarmup`` suite before concurrent tests run.
|
||||
/// Add new commonly-used images here; the warmup pass pulls them in parallel.
|
||||
public enum WarmupImage: String, CaseIterable, Sendable {
|
||||
case alpine320 = "ghcr.io/linuxcontainers/alpine:3.20"
|
||||
case alpine318 = "ghcr.io/linuxcontainers/alpine:3.18"
|
||||
case busybox136 = "ghcr.io/containerd/busybox:1.36"
|
||||
|
||||
/// Directory under app-root holding OCI tar archives of each warmup image.
|
||||
///
|
||||
/// Living under app-root (rather than a scratch dir tied to a single
|
||||
/// fixture) means the cache survives across the warmup/concurrent/serial
|
||||
/// `swift test` invocations, which run as separate processes, and rides
|
||||
/// along whatever process clears app-root between full test runs — no
|
||||
/// dedicated cleanup needed.
|
||||
public static var cacheDirectory: FilePath {
|
||||
PathUtils.BaseConfigPath.appRoot.basePath().appending("test-image-cache")
|
||||
}
|
||||
|
||||
/// Path to this image's cached OCI tar archive.
|
||||
public var cacheTarPath: FilePath {
|
||||
Self.cacheDirectory.appending("\(self).tar")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ struct TestCLIImagePruneSerial {
|
||||
try? f.doRemoveImages()
|
||||
f.addCleanup { try? f.doRemoveImages() }
|
||||
|
||||
try f.doPull(alpine)
|
||||
try f.doPull(busybox)
|
||||
try f.restoreWarmupImage(.alpine320)
|
||||
try f.restoreWarmupImage(.busybox136)
|
||||
#expect(try f.isImagePresent(alpine), "expected \(alpine) to be pulled")
|
||||
#expect(try f.isImagePresent(busybox), "expected \(busybox) to be pulled")
|
||||
|
||||
@@ -84,8 +84,8 @@ struct TestCLIImagePruneSerial {
|
||||
try? f.doRemoveImages()
|
||||
}
|
||||
|
||||
try f.doPull(alpine)
|
||||
try f.doPull(busybox)
|
||||
try f.restoreWarmupImage(.alpine320)
|
||||
try f.restoreWarmupImage(.busybox136)
|
||||
#expect(try f.isImagePresent(alpine), "expected \(alpine) to be pulled")
|
||||
#expect(try f.isImagePresent(busybox), "expected \(busybox) to be pulled")
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ struct TestCLISystemDFSerial {
|
||||
@Test func imageDiskUsageIsPopulatedAfterPull() async throws {
|
||||
try await ContainerFixture.with { f in
|
||||
try withCleanImageStore(f) {
|
||||
try f.doPull(self.alpine)
|
||||
try f.restoreWarmupImage(.alpine320)
|
||||
let stats = try systemDiskUsage(f)
|
||||
#expect(stats.images.total >= 1)
|
||||
#expect(stats.images.active == 0)
|
||||
@@ -52,7 +52,7 @@ struct TestCLISystemDFSerial {
|
||||
@Test func tagsDoNotDoubleCountImageStorage() async throws {
|
||||
try await ContainerFixture.with { f in
|
||||
try withCleanImageStore(f) {
|
||||
try f.doPull(self.alpine)
|
||||
try f.restoreWarmupImage(.alpine320)
|
||||
let before = try systemDiskUsage(f)
|
||||
try f.doImageTag(self.alpine, newName: "local/system-df-alpine:tag-one")
|
||||
try f.doImageTag(self.alpine, newName: "local/system-df-alpine:tag-two")
|
||||
@@ -69,7 +69,7 @@ struct TestCLISystemDFSerial {
|
||||
try await ContainerFixture.with { f in
|
||||
try withCleanImageStore(f) {
|
||||
let baseline = try systemDiskUsage(f)
|
||||
try f.doPull(self.alpine)
|
||||
try f.restoreWarmupImage(.alpine320)
|
||||
try f.doImageTag(self.alpine, newName: "local/system-df-alpine:delete-probe")
|
||||
let beforeDelete = try systemDiskUsage(f)
|
||||
|
||||
|
||||
@@ -20,13 +20,16 @@ import Testing
|
||||
/// Pulls each image in ``WarmupImage`` in parallel before concurrent
|
||||
/// integration tests run. The Makefile's warmup pass runs this suite first
|
||||
/// so that ``ContainerFixture/copyWarmupImage(_:)`` can tag from a
|
||||
/// pre-populated store rather than pulling on demand.
|
||||
/// pre-populated store rather than pulling on demand, and so that
|
||||
/// ``ContainerFixture/restoreWarmupImage(_:)`` has a cached tar archive to
|
||||
/// reload from after a serial test wipes the image store.
|
||||
@Suite
|
||||
struct ImageWarmup {
|
||||
@Test(arguments: WarmupImage.allCases)
|
||||
func pull(image: WarmupImage) async throws {
|
||||
try await ContainerFixture.with { f in
|
||||
try f.run(["image", "pull", image.rawValue]).check("failed to pull \(image.rawValue)")
|
||||
try f.cacheWarmupImage(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user