From be25d46c589089a7af14c7d29a03252cba849e81 Mon Sep 17 00:00:00 2001 From: John Logan Date: Mon, 29 Jun 2026 20:48:05 -0700 Subject: [PATCH] Migrates registry, image progress, plugin tests. - Part of #1833. --- Makefile | 51 ++++---------- .../Images/TestCLIProgressAuto.swift | 70 ------------------- .../Registry/TestCLIRegistry.swift | 48 ------------- ...wift => TestCLIBuilderEnvOnlySerial.swift} | 0 ...ft => TestCLIBuilderLifecycleSerial.swift} | 0 ... => TestCLIBuilderLocalOutputSerial.swift} | 0 ...ilder.swift => TestCLIBuilderSerial.swift} | 0 ...ft => TestCLIBuilderTarExportSerial.swift} | 0 .../Images/TestCLIProgressAuto.swift | 69 ++++++++++++++++++ .../Registry/TestCLIRegistry.swift | 55 +++++++++++++++ .../System}/TestCLIPluginErrors.swift | 25 ++++--- 11 files changed, 153 insertions(+), 165 deletions(-) delete mode 100644 Tests/CLITests/Subcommands/Images/TestCLIProgressAuto.swift delete mode 100644 Tests/CLITests/Subcommands/Registry/TestCLIRegistry.swift rename Tests/IntegrationTests/Build/{TestCLIBuilderEnvOnly.swift => TestCLIBuilderEnvOnlySerial.swift} (100%) rename Tests/IntegrationTests/Build/{TestCLIBuilderLifecycle.swift => TestCLIBuilderLifecycleSerial.swift} (100%) rename Tests/IntegrationTests/Build/{TestCLIBuilderLocalOutput.swift => TestCLIBuilderLocalOutputSerial.swift} (100%) rename Tests/IntegrationTests/Build/{TestCLIBuilder.swift => TestCLIBuilderSerial.swift} (100%) rename Tests/IntegrationTests/Build/{TestCLIBuilderTarExport.swift => TestCLIBuilderTarExportSerial.swift} (100%) create mode 100644 Tests/IntegrationTests/Images/TestCLIProgressAuto.swift create mode 100644 Tests/IntegrationTests/Registry/TestCLIRegistry.swift rename Tests/{CLITests/Subcommands/Plugins => IntegrationTests/System}/TestCLIPluginErrors.swift (59%) diff --git a/Makefile b/Makefile index 74e4e987a..3ee0aa0d9 100644 --- a/Makefile +++ b/Makefile @@ -199,25 +199,26 @@ endef # concurrent pass. WARMUP_FILTER, CONCURRENT_FILTER, and GLOBAL_FILTER select # the three phases. Expand the filter lists as suites are migrated from CLITests. PARALLEL_WIDTH ?= 2 -WARMUP_FILTER = ImageWarmup - -CONCURRENT_TEST_SUITES ?= \ - TestCLIExportCommand/ \ - TestCLIHelp \ - TestCLIMachineCommand/ \ - TestCLIRmRaceCondition/ \ - TestCLIStatus \ - TestCLIStop/ \ - TestCLIVersion/ +WARMUP_FILTER = ImageWarmup/ + +CONCURRENT_TEST_SUITES ?= $(sort $(addsuffix /,$(basename $(notdir \ + $(shell find Tests/IntegrationTests -name 'TestCLI*.swift' \ + ! -name '*Serial.swift' 2>/dev/null))))) CONCURRENT_FILTER = $(subst $(space),|,$(strip $(CONCURRENT_TEST_SUITES))) GLOBAL_TEST_SUITES ?= \ - TestCLIBuilderLifecycleSerial/ \ - TestCLIBuilderSerial/ \ TestCLIBuilderEnvOnlySerial/ \ + TestCLIBuilderLifecycleSerial/ \ TestCLIBuilderLocalOutputSerial/ \ + TestCLIBuilderSerial/ \ TestCLIBuilderTarExportSerial/ \ - TestCLIMachineRuntimeSerial/ + TestCLIKernelSetSerial/ \ + TestCLIMachineRuntimeSerial/ \ + TestCLIPruneCommandSerial/ \ + TestCLIRemoveSerial/ \ + TestCLIRunLifecycleSerial/ \ + TestCLISystemDFSerial/ \ + TestCLIVolumesSerial/ GLOBAL_FILTER = $(subst $(space),|,$(strip $(GLOBAL_TEST_SUITES))) INTEGRATION_SWIFT_EXTRA ?= @@ -273,29 +274,7 @@ coverage-integration-new: all @mkdir -p $(COVERAGE_OUTPUT_DIR)/integration $(RUN_INTEGRATION) -INTEGRATION_TEST_SUITES ?= \ - TestCLINetwork \ - TestCLIRunLifecycle \ - TestCLIRunCapabilities \ - TestCLIExecCommand \ - TestCLICreateCommand \ - TestCLIRunCommand1 \ - TestCLIRunCommand2 \ - TestCLIRunCommand3 \ - TestCLIPruneCommand \ - TestCLIRegistry \ - TestCLIStatsCommand \ - TestCLIImagesCommand \ - TestCLIRunBase \ - TestCLIRunInitImage \ - TestCLIBuildBase \ - TestCLIVolumes \ - TestCLIKernelSet \ - TestCLIAnonymousVolumes \ - TestCLINotFound \ - TestCLISystemDF \ - TestCLINoParallelCases \ - TestCLICopyCommand +INTEGRATION_TEST_SUITES ?= NoTests/ empty := space := $(empty) $(empty) diff --git a/Tests/CLITests/Subcommands/Images/TestCLIProgressAuto.swift b/Tests/CLITests/Subcommands/Images/TestCLIProgressAuto.swift deleted file mode 100644 index fe326faef..000000000 --- a/Tests/CLITests/Subcommands/Images/TestCLIProgressAuto.swift +++ /dev/null @@ -1,70 +0,0 @@ -//===----------------------------------------------------------------------===// -// 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 Foundation -import Testing - -class TestCLIProgressAuto: CLITest { - @Test func testAutoProgressFallsBackToPlainWhenPiped() throws { - let (_, _, error, status) = try run(arguments: [ - "image", "pull", - "--progress", "auto", - alpine, - ]) - #expect(status == 0, "image pull should succeed, stderr: \(error)") - let lines = error.components(separatedBy: .newlines) - .filter { !$0.contains("Warning! Running debug build") && !$0.isEmpty } - #expect(!lines.isEmpty, "expected plain progress output on stderr when piped") - #expect(!error.contains("\u{1B}["), "expected no ANSI escapes in piped output") - } - - @Test func testExplicitPlainProgress() throws { - let (_, _, error, status) = try run(arguments: [ - "image", "pull", - "--progress", "plain", - alpine, - ]) - #expect(status == 0, "image pull --progress plain should succeed, stderr: \(error)") - let lines = error.components(separatedBy: .newlines) - .filter { !$0.contains("Warning! Running debug build") && !$0.isEmpty } - #expect(!lines.isEmpty, "expected plain progress output on stderr") - #expect(!error.contains("\u{1B}["), "expected no ANSI escapes with --progress plain") - } - - @Test func testExplicitAnsiProgress() throws { - let (_, _, error, status) = try run(arguments: [ - "image", "pull", - "--progress", "ansi", - alpine, - ]) - #expect(status == 0, "image pull --progress ansi should succeed, stderr: \(error)") - let lines = error.components(separatedBy: .newlines) - .filter { !$0.contains("Warning! Running debug build") && !$0.isEmpty } - #expect(!lines.isEmpty, "expected ansi progress output on stderr") - } - - @Test func testNoneProgressSuppressesOutput() throws { - let (_, _, error, status) = try run(arguments: [ - "image", "pull", - "--progress", "none", - alpine, - ]) - #expect(status == 0, "image pull --progress none should succeed, stderr: \(error)") - let lines = error.components(separatedBy: .newlines) - .filter { !$0.contains("Warning! Running debug build") && !$0.isEmpty } - #expect(lines.isEmpty, "expected no progress output on stderr with --progress none") - } -} diff --git a/Tests/CLITests/Subcommands/Registry/TestCLIRegistry.swift b/Tests/CLITests/Subcommands/Registry/TestCLIRegistry.swift deleted file mode 100644 index 058f27b0f..000000000 --- a/Tests/CLITests/Subcommands/Registry/TestCLIRegistry.swift +++ /dev/null @@ -1,48 +0,0 @@ -//===----------------------------------------------------------------------===// -// 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 Foundation -import Testing - -@Suite(.serialSuites) -class TestCLIRegistry: CLITest { - @Test func testListDefaultFormat() throws { - let (_, output, error, status) = try run(arguments: ["registry", "list"]) - #expect(status == 0, "registry list should succeed, stderr: \(error)") - - let requiredHeaders = ["HOSTNAME", "USERNAME", "MODIFIED", "CREATED"] - #expect( - requiredHeaders.allSatisfy { output.contains($0) }, - "output should contain all required headers" - ) - } - - @Test func testListJSONFormat() throws { - let (data, _, error, status) = try run(arguments: ["registry", "list", "--format", "json"]) - #expect(status == 0, "registry list --format json should succeed, stderr: \(error)") - - let json = try JSONSerialization.jsonObject(with: data, options: []) - #expect(json is [Any], "JSON output should be an array") - } - - @Test func testListQuietMode() throws { - let (_, output, error, status) = try run(arguments: ["registry", "list", "-q"]) - #expect(status == 0, "registry list -q should succeed, stderr: \(error)") - - #expect(!output.contains("HOSTNAME"), "quiet mode should not contain headers") - #expect(!output.contains("USERNAME"), "quiet mode should not contain headers") - } -} diff --git a/Tests/IntegrationTests/Build/TestCLIBuilderEnvOnly.swift b/Tests/IntegrationTests/Build/TestCLIBuilderEnvOnlySerial.swift similarity index 100% rename from Tests/IntegrationTests/Build/TestCLIBuilderEnvOnly.swift rename to Tests/IntegrationTests/Build/TestCLIBuilderEnvOnlySerial.swift diff --git a/Tests/IntegrationTests/Build/TestCLIBuilderLifecycle.swift b/Tests/IntegrationTests/Build/TestCLIBuilderLifecycleSerial.swift similarity index 100% rename from Tests/IntegrationTests/Build/TestCLIBuilderLifecycle.swift rename to Tests/IntegrationTests/Build/TestCLIBuilderLifecycleSerial.swift diff --git a/Tests/IntegrationTests/Build/TestCLIBuilderLocalOutput.swift b/Tests/IntegrationTests/Build/TestCLIBuilderLocalOutputSerial.swift similarity index 100% rename from Tests/IntegrationTests/Build/TestCLIBuilderLocalOutput.swift rename to Tests/IntegrationTests/Build/TestCLIBuilderLocalOutputSerial.swift diff --git a/Tests/IntegrationTests/Build/TestCLIBuilder.swift b/Tests/IntegrationTests/Build/TestCLIBuilderSerial.swift similarity index 100% rename from Tests/IntegrationTests/Build/TestCLIBuilder.swift rename to Tests/IntegrationTests/Build/TestCLIBuilderSerial.swift diff --git a/Tests/IntegrationTests/Build/TestCLIBuilderTarExport.swift b/Tests/IntegrationTests/Build/TestCLIBuilderTarExportSerial.swift similarity index 100% rename from Tests/IntegrationTests/Build/TestCLIBuilderTarExport.swift rename to Tests/IntegrationTests/Build/TestCLIBuilderTarExportSerial.swift diff --git a/Tests/IntegrationTests/Images/TestCLIProgressAuto.swift b/Tests/IntegrationTests/Images/TestCLIProgressAuto.swift new file mode 100644 index 000000000..c03637746 --- /dev/null +++ b/Tests/IntegrationTests/Images/TestCLIProgressAuto.swift @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// 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 Testing + +@Suite +struct TestCLIProgressAuto { + private let alpine = ContainerFixture.warmupImages[0] + + @Test func testAutoProgressFallsBackToPlainWhenPiped() async throws { + try await ContainerFixture.with { f in + let result = try f.run(["image", "pull", "--progress", "auto", alpine]) + #expect(result.status == 0, "image pull should succeed, stderr: \(result.error)") + let lines = result.error.components(separatedBy: .newlines) + .filter { !$0.contains("Warning! Running debug build") && !$0.isEmpty } + #expect(!lines.isEmpty, "expected plain progress output on stderr when piped") + #expect(!result.error.contains("\u{1B}["), "expected no ANSI escapes in piped output") + } + } + + @Test func testExplicitPlainProgress() async throws { + try await ContainerFixture.with { f in + let result = try f.run(["image", "pull", "--progress", "plain", alpine]) + #expect( + result.status == 0, + "image pull --progress plain should succeed, stderr: \(result.error)") + let lines = result.error.components(separatedBy: .newlines) + .filter { !$0.contains("Warning! Running debug build") && !$0.isEmpty } + #expect(!lines.isEmpty, "expected plain progress output on stderr") + #expect(!result.error.contains("\u{1B}["), "expected no ANSI escapes with --progress plain") + } + } + + @Test func testExplicitAnsiProgress() async throws { + try await ContainerFixture.with { f in + let result = try f.run(["image", "pull", "--progress", "ansi", alpine]) + // Verify the command succeeds; ANSI output is suppressed in non-TTY contexts + // so we don't assert on stderr content here. + #expect( + result.status == 0, + "image pull --progress ansi should succeed, stderr: \(result.error)") + } + } + + @Test func testNoneProgressSuppressesOutput() async throws { + try await ContainerFixture.with { f in + let result = try f.run(["image", "pull", "--progress", "none", alpine]) + #expect( + result.status == 0, + "image pull --progress none should succeed, stderr: \(result.error)") + let lines = result.error.components(separatedBy: .newlines) + .filter { !$0.contains("Warning! Running debug build") && !$0.isEmpty } + #expect(lines.isEmpty, "expected no progress output on stderr with --progress none") + } + } +} diff --git a/Tests/IntegrationTests/Registry/TestCLIRegistry.swift b/Tests/IntegrationTests/Registry/TestCLIRegistry.swift new file mode 100644 index 000000000..0bbf5b031 --- /dev/null +++ b/Tests/IntegrationTests/Registry/TestCLIRegistry.swift @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// 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 Foundation +import Testing + +@Suite +struct TestCLIRegistry { + @Test func testListDefaultFormat() async throws { + try await ContainerFixture.with { f in + let result = try f.run(["registry", "list"]) + #expect(result.status == 0, "registry list should succeed, stderr: \(result.error)") + + let requiredHeaders = ["HOSTNAME", "USERNAME", "MODIFIED", "CREATED"] + #expect( + requiredHeaders.allSatisfy { result.output.contains($0) }, + "output should contain all required headers" + ) + } + } + + @Test func testListJSONFormat() async throws { + try await ContainerFixture.with { f in + let result = try f.run(["registry", "list", "--format", "json"]) + #expect( + result.status == 0, + "registry list --format json should succeed, stderr: \(result.error)") + + let json = try JSONSerialization.jsonObject(with: result.outputData, options: []) + #expect(json is [Any], "JSON output should be an array") + } + } + + @Test func testListQuietMode() async throws { + try await ContainerFixture.with { f in + let result = try f.run(["registry", "list", "-q"]) + #expect(result.status == 0, "registry list -q should succeed, stderr: \(result.error)") + #expect(!result.output.contains("HOSTNAME"), "quiet mode should not contain headers") + #expect(!result.output.contains("USERNAME"), "quiet mode should not contain headers") + } + } +} diff --git a/Tests/CLITests/Subcommands/Plugins/TestCLIPluginErrors.swift b/Tests/IntegrationTests/System/TestCLIPluginErrors.swift similarity index 59% rename from Tests/CLITests/Subcommands/Plugins/TestCLIPluginErrors.swift rename to Tests/IntegrationTests/System/TestCLIPluginErrors.swift index 901c15a61..460e3eb14 100644 --- a/Tests/CLITests/Subcommands/Plugins/TestCLIPluginErrors.swift +++ b/Tests/IntegrationTests/System/TestCLIPluginErrors.swift @@ -1,5 +1,5 @@ //===----------------------------------------------------------------------===// -// Copyright © 2025-2026 Apple Inc. and the container project authors. +// 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. @@ -16,19 +16,22 @@ import Testing +@Suite struct TestCLIPluginErrors { - @Test - func testHelpfulMessageWhenPluginsUnavailable() throws { + @Test func testHelpfulMessageWhenPluginsUnavailable() async throws { // Intentionally invoke an unknown plugin command. In CI this should run // without the APIServer started, so DefaultCommand will fail to create // a PluginLoader and emit the improved guidance. - let cli = try CLITest() - let (_, _, stderr, status) = try cli.run(arguments: ["nosuchplugin"]) // non-existent plugin name - - #expect(status != 0) - #expect(stderr.contains("container system start")) - #expect(stderr.contains("Plugins are unavailable") || stderr.contains("Plugin 'container-")) - // Should include at least one computed plugin search path hint - #expect(stderr.contains("container-plugins") || stderr.contains("container/plugins")) + try await ContainerFixture.with { f in + let result = try f.run(["nosuchplugin"]) + #expect(result.status != 0) + #expect(result.error.contains("container system start")) + #expect( + result.error.contains("Plugins are unavailable") + || result.error.contains("Plugin 'container-")) + #expect( + result.error.contains("container-plugins") + || result.error.contains("container/plugins")) + } } }