Skip to content

Commit a64bd77

Browse files
authored
Fix broken image integration tests. (apple#944)
- Fixes apple#943. - Use images other than alpine:3.20 for image concurrency test so as not to interfere with tests using that image. - Rename test files to match suite names.
1 parent ab92f39 commit a64bd77

File tree

5 files changed

+115
-87
lines changed

5 files changed

+115
-87
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ integration: init-block
183183
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIVolumes || exit_code=1 ; \
184184
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIKernelSet || exit_code=1 ; \
185185
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIAnonymousVolumes || exit_code=1 ; \
186+
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --no-parallel --filter TestCLINoParallelCases || exit_code=1 ; \
186187
echo Ensuring apiserver stopped after the CLI integration tests ; \
187188
scripts/ensure-container-stopped.sh ; \
188189
exit $${exit_code} ; \

Tests/CLITests/Subcommands/Images/TestCLIImages.swift renamed to Tests/CLITests/Subcommands/Images/TestCLIImagesCommand.swift

Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -20,71 +20,6 @@ import Foundation
2020
import Testing
2121

2222
class TestCLIImagesCommand: CLITest {
23-
func doRemoveImages(images: [String]? = nil) throws {
24-
var args = [
25-
"image",
26-
"rm",
27-
]
28-
29-
if let images {
30-
args.append(contentsOf: images)
31-
} else {
32-
args.append("--all")
33-
}
34-
35-
let (_, _, error, status) = try run(arguments: args)
36-
if status != 0 {
37-
throw CLIError.executionFailed("command failed: \(error)")
38-
}
39-
}
40-
41-
func isImagePresent(targetImage: String) throws -> Bool {
42-
let images = try doListImages()
43-
return images.contains(where: { image in
44-
if image.reference == targetImage {
45-
return true
46-
}
47-
return false
48-
})
49-
}
50-
51-
func doListImages() throws -> [Image] {
52-
let (_, output, error, status) = try run(arguments: [
53-
"image",
54-
"list",
55-
"--format",
56-
"json",
57-
])
58-
if status != 0 {
59-
throw CLIError.executionFailed("command failed: \(error)")
60-
}
61-
62-
guard let jsonData = output.data(using: .utf8) else {
63-
throw CLIError.invalidOutput("image list output invalid \(output)")
64-
}
65-
66-
let decoder = JSONDecoder()
67-
return try decoder.decode([Image].self, from: jsonData)
68-
}
69-
70-
func doImageTag(image: String, newName: String) throws {
71-
let tagArgs = [
72-
"image",
73-
"tag",
74-
image,
75-
newName,
76-
]
77-
78-
let (_, _, error, status) = try run(arguments: tagArgs)
79-
if status != 0 {
80-
throw CLIError.executionFailed("command failed: \(error)")
81-
}
82-
}
83-
84-
}
85-
86-
extension TestCLIImagesCommand {
87-
8823
@Test func testPull() throws {
8924
do {
9025
try doPull(imageName: alpine)
@@ -375,26 +310,6 @@ extension TestCLIImagesCommand {
375310
"Expected validation error message in output")
376311
}
377312

378-
@Test func testMaxConcurrentDownloadsFlag() throws {
379-
// Test that the flag is accepted with valid values
380-
do {
381-
try doPull(imageName: alpine, args: ["--max-concurrent-downloads", "1"])
382-
let imagePresent = try isImagePresent(targetImage: alpine)
383-
#expect(imagePresent, "Expected image to be pulled with maxConcurrentDownloads=1")
384-
385-
// Clean up
386-
try? doRemoveImages(images: [alpine])
387-
388-
// Test with higher concurrency
389-
try doPull(imageName: alpine, args: ["--max-concurrent-downloads", "6"])
390-
let imagePresent2 = try isImagePresent(targetImage: alpine)
391-
#expect(imagePresent2, "Expected image to be pulled with maxConcurrentDownloads=6")
392-
} catch {
393-
Issue.record("failed to pull image with maxConcurrentDownloads flag: \(error)")
394-
return
395-
}
396-
}
397-
398313
@Test func testImageSaveAndLoadStdinStdout() throws {
399314
do {
400315
// 1. pull image

Tests/CLITests/Subcommands/Run/TestCLIRunOptions.swift renamed to Tests/CLITests/Subcommands/Run/TestCLIRunCommand.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,13 +513,14 @@ class TestCLIRunCommand: CLITest {
513513
let response = try await client.execute(request, timeout: .seconds(retryDelaySeconds))
514514
try #require(response.status == .ok)
515515
success = true
516+
print("request to \(url) succeeded")
516517
} catch {
517518
print("request to \(url) failed, error \(error)")
518519
try await Task.sleep(for: .seconds(retryDelaySeconds))
519520
}
520521
retriesRemaining -= 1
521522
}
522-
#expect(success, "Request to \(url) failed after \(retries - retriesRemaining) retries")
523+
try #require(success, "Request to \(url) failed after \(retries - retriesRemaining) retries")
523524
try doStop(name: name)
524525
} catch {
525526
Issue.record("failed to run container \(error)")
@@ -561,13 +562,14 @@ class TestCLIRunCommand: CLITest {
561562
let response = try await client.execute(request, timeout: .seconds(retryDelaySeconds))
562563
try #require(response.status == .ok)
563564
success = true
565+
print("request to \(url) succeeded")
564566
} catch {
565567
print("request to \(url) failed, error: \(error)")
566568
try await Task.sleep(for: .seconds(retryDelaySeconds))
567569
}
568570
retriesRemaining -= 1
569571
}
570-
#expect(success, "Request to \(url) failed after \(retries - retriesRemaining) retries")
572+
try #require(success, "Request to \(url) failed after \(retries - retriesRemaining) retries")
571573
try doStop(name: name)
572574
} catch {
573575
Issue.record("failed to run container \(error)")
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2025 Apple Inc. and the container project authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//===----------------------------------------------------------------------===//
16+
17+
import ContainerClient
18+
import ContainerizationOCI
19+
import Foundation
20+
import Testing
21+
22+
/// Tests that need total control over environment to avoid conflicts.
23+
class TestCLINoParallelCases: CLITest {
24+
@Test func testImageSingleConcurrentDownload() throws {
25+
// removing this image during parallel tests breaks stuff!
26+
_ = try? run(arguments: ["image", "rm", alpine])
27+
do {
28+
try doPull(imageName: alpine, args: ["--max-concurrent-downloads", "1"])
29+
let imagePresent = try isImagePresent(targetImage: alpine)
30+
#expect(imagePresent, "Expected image to be pulled with maxConcurrentDownloads=1")
31+
} catch {
32+
Issue.record("failed to pull image with maxConcurrentDownloads flag: \(error)")
33+
return
34+
}
35+
}
36+
37+
@Test func testImageManyConcurrentDownloads() throws {
38+
// removing this image during parallel tests breaks stuff!
39+
_ = try? run(arguments: ["image", "rm", alpine])
40+
do {
41+
try doPull(imageName: alpine, args: ["--max-concurrent-downloads", "64"])
42+
let imagePresent = try isImagePresent(targetImage: alpine)
43+
#expect(imagePresent, "Expected image to be pulled with maxConcurrentDownloads=64")
44+
} catch {
45+
Issue.record("failed to pull image with maxConcurrentDownloads flag: \(error)")
46+
return
47+
}
48+
}
49+
}

Tests/CLITests/Utilities/CLITest.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,4 +484,65 @@ class CLITest {
484484

485485
return try await body(tempDir)
486486
}
487+
488+
func doRemoveImages(images: [String]? = nil) throws {
489+
var args = [
490+
"image",
491+
"rm",
492+
]
493+
494+
if let images {
495+
args.append(contentsOf: images)
496+
} else {
497+
args.append("--all")
498+
}
499+
500+
let (_, _, error, status) = try run(arguments: args)
501+
if status != 0 {
502+
throw CLIError.executionFailed("command failed: \(error)")
503+
}
504+
}
505+
506+
func isImagePresent(targetImage: String) throws -> Bool {
507+
let images = try doListImages()
508+
return images.contains(where: { image in
509+
if image.reference == targetImage {
510+
return true
511+
}
512+
return false
513+
})
514+
}
515+
516+
func doListImages() throws -> [Image] {
517+
let (_, output, error, status) = try run(arguments: [
518+
"image",
519+
"list",
520+
"--format",
521+
"json",
522+
])
523+
if status != 0 {
524+
throw CLIError.executionFailed("command failed: \(error)")
525+
}
526+
527+
guard let jsonData = output.data(using: .utf8) else {
528+
throw CLIError.invalidOutput("image list output invalid \(output)")
529+
}
530+
531+
let decoder = JSONDecoder()
532+
return try decoder.decode([Image].self, from: jsonData)
533+
}
534+
535+
func doImageTag(image: String, newName: String) throws {
536+
let tagArgs = [
537+
"image",
538+
"tag",
539+
image,
540+
newName,
541+
]
542+
543+
let (_, _, error, status) = try run(arguments: tagArgs)
544+
if status != 0 {
545+
throw CLIError.executionFailed("command failed: \(error)")
546+
}
547+
}
487548
}

0 commit comments

Comments
 (0)