Skip to content

Commit 75a8f88

Browse files
committed
Integration test: cache warmup image tarfiles.
- 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.
1 parent bc50fcb commit 75a8f88

5 files changed

Lines changed: 52 additions & 8 deletions

File tree

Sources/ContainerTestSupport/ContainerFixture+ImageHelpers.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//===----------------------------------------------------------------------===//
1616

1717
import Foundation
18+
import SystemPackage
1819
import Testing
1920

2021
// MARK: - Image inspect types
@@ -69,6 +70,27 @@ extension ContainerFixture {
6970
try run(args).check()
7071
}
7172

73+
/// Saves `image` to ``WarmupImage/cacheTarPath``, overwriting any existing archive.
74+
///
75+
/// Called once per image by the `ImageWarmup` suite. No `--platform`/`--os`/`--arch`
76+
/// is passed, so `image save` captures every platform the image supports.
77+
public func cacheWarmupImage(_ image: WarmupImage) throws {
78+
try FileManager.default.createDirectory(
79+
atPath: WarmupImage.cacheDirectory.string, withIntermediateDirectories: true)
80+
try run(["image", "save", "--output", image.cacheTarPath.string, image.rawValue])
81+
.check("failed to cache \(image.rawValue)")
82+
}
83+
84+
/// Reloads `image` from its cached tar archive rather than pulling over the network.
85+
///
86+
/// Use this in serial tests to restore a warmup image after a destructive operation
87+
/// (`image rm --all`, `image prune`) removes it from the store. Requires the
88+
/// `ImageWarmup` suite to have already run and populated the cache.
89+
public func restoreWarmupImage(_ image: WarmupImage) throws {
90+
try run(["image", "load", "--input", image.cacheTarPath.string])
91+
.check("failed to restore \(image.rawValue) from cache")
92+
}
93+
7294
/// Returns the full inspect output for an image, including variant information.
7395
public func doInspectImages(_ name: String) throws -> [ImageInspectOutput] {
7496
let result = try run(["image", "inspect", name]).check()

Sources/ContainerTestSupport/WarmupImage.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,29 @@
1414
// limitations under the License.
1515
//===----------------------------------------------------------------------===//
1616

17+
import ContainerPersistence
18+
import SystemPackage
19+
1720
/// Images preloaded by the ``ImageWarmup`` suite before concurrent tests run.
1821
/// Add new commonly-used images here; the warmup pass pulls them in parallel.
1922
public enum WarmupImage: String, CaseIterable, Sendable {
2023
case alpine320 = "ghcr.io/linuxcontainers/alpine:3.20"
2124
case alpine318 = "ghcr.io/linuxcontainers/alpine:3.18"
2225
case busybox136 = "ghcr.io/containerd/busybox:1.36"
26+
27+
/// Directory under app-root holding OCI tar archives of each warmup image.
28+
///
29+
/// Living under app-root (rather than a scratch dir tied to a single
30+
/// fixture) means the cache survives across the warmup/concurrent/serial
31+
/// `swift test` invocations, which run as separate processes, and rides
32+
/// along whatever process clears app-root between full test runs — no
33+
/// dedicated cleanup needed.
34+
public static var cacheDirectory: FilePath {
35+
PathUtils.BaseConfigPath.appRoot.basePath().appending("test-image-cache")
36+
}
37+
38+
/// Path to this image's cached OCI tar archive.
39+
public var cacheTarPath: FilePath {
40+
Self.cacheDirectory.appending("\(self).tar")
41+
}
2342
}

Tests/IntegrationTests/Images/TestCLIImagePruneSerial.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ struct TestCLIImagePruneSerial {
5959
try? f.doRemoveImages()
6060
f.addCleanup { try? f.doRemoveImages() }
6161

62-
try f.doPull(alpine)
63-
try f.doPull(busybox)
62+
try f.restoreWarmupImage(.alpine320)
63+
try f.restoreWarmupImage(.busybox136)
6464
#expect(try f.isImagePresent(alpine), "expected \(alpine) to be pulled")
6565
#expect(try f.isImagePresent(busybox), "expected \(busybox) to be pulled")
6666

@@ -84,8 +84,8 @@ struct TestCLIImagePruneSerial {
8484
try? f.doRemoveImages()
8585
}
8686

87-
try f.doPull(alpine)
88-
try f.doPull(busybox)
87+
try f.restoreWarmupImage(.alpine320)
88+
try f.restoreWarmupImage(.busybox136)
8989
#expect(try f.isImagePresent(alpine), "expected \(alpine) to be pulled")
9090
#expect(try f.isImagePresent(busybox), "expected \(busybox) to be pulled")
9191

Tests/IntegrationTests/System/TestCLISystemDFSerial.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct TestCLISystemDFSerial {
3838
@Test func imageDiskUsageIsPopulatedAfterPull() async throws {
3939
try await ContainerFixture.with { f in
4040
try withCleanImageStore(f) {
41-
try f.doPull(self.alpine)
41+
try f.restoreWarmupImage(.alpine320)
4242
let stats = try systemDiskUsage(f)
4343
#expect(stats.images.total >= 1)
4444
#expect(stats.images.active == 0)
@@ -52,7 +52,7 @@ struct TestCLISystemDFSerial {
5252
@Test func tagsDoNotDoubleCountImageStorage() async throws {
5353
try await ContainerFixture.with { f in
5454
try withCleanImageStore(f) {
55-
try f.doPull(self.alpine)
55+
try f.restoreWarmupImage(.alpine320)
5656
let before = try systemDiskUsage(f)
5757
try f.doImageTag(self.alpine, newName: "local/system-df-alpine:tag-one")
5858
try f.doImageTag(self.alpine, newName: "local/system-df-alpine:tag-two")
@@ -69,7 +69,7 @@ struct TestCLISystemDFSerial {
6969
try await ContainerFixture.with { f in
7070
try withCleanImageStore(f) {
7171
let baseline = try systemDiskUsage(f)
72-
try f.doPull(self.alpine)
72+
try f.restoreWarmupImage(.alpine320)
7373
try f.doImageTag(self.alpine, newName: "local/system-df-alpine:delete-probe")
7474
let beforeDelete = try systemDiskUsage(f)
7575

Tests/IntegrationTests/Warmup/ImageWarmup.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ import Testing
2020
/// Pulls each image in ``WarmupImage`` in parallel before concurrent
2121
/// integration tests run. The Makefile's warmup pass runs this suite first
2222
/// so that ``ContainerFixture/copyWarmupImage(_:)`` can tag from a
23-
/// pre-populated store rather than pulling on demand.
23+
/// pre-populated store rather than pulling on demand, and so that
24+
/// ``ContainerFixture/restoreWarmupImage(_:)`` has a cached tar archive to
25+
/// reload from after a serial test wipes the image store.
2426
@Suite
2527
struct ImageWarmup {
2628
@Test(arguments: WarmupImage.allCases)
2729
func pull(image: WarmupImage) async throws {
2830
try await ContainerFixture.with { f in
2931
try f.run(["image", "pull", image.rawValue]).check("failed to pull \(image.rawValue)")
32+
try f.cacheWarmupImage(image)
3033
}
3134
}
3235
}

0 commit comments

Comments
 (0)