diff --git a/Sources/ContainerizationOCI/Platform.swift b/Sources/ContainerizationOCI/Platform.swift index 7455e011..63edf598 100644 --- a/Sources/ContainerizationOCI/Platform.swift +++ b/Sources/ContainerizationOCI/Platform.swift @@ -47,15 +47,27 @@ public struct Platform: Sendable, Equatable { return .init(arch: normalized.arch, os: "linux", variant: normalized.variant) } - /// The computed description, for example, `linux/arm64/v8`. + /// The computed description, for example, `linux/amd64` or `linux/arm/v7`. + /// + /// `arm64`'s only defined variant is `v8`, which `==` and `hash` already treat as + /// equivalent to a `nil` variant. The redundant `v8` is therefore omitted so that + /// two equal arm64 platforms (one with `variant == nil`, one with `"v8"`) describe + /// identically as `linux/arm64`, rather than drifting between `arm64` and + /// `arm64/v8`. See apple/container#1542. public var description: String { let architecture = architecture - if let variant = variant { + if let variant, !Self.isRedundantVariant(variant, for: architecture) { return "\(os)/\(architecture)/\(variant)" } return "\(os)/\(architecture)" } + /// Whether `variant` is the canonical default for `architecture` and can be omitted + /// from the rendered description without losing information. + private static func isRedundantVariant(_ variant: String, for architecture: String) -> Bool { + architecture == "arm64" && variant == "v8" + } + /// The CPU architecture, for example, `amd64` or `arm64`. public var architecture: String { Self.normalizeArch(_rawArch).arch diff --git a/Tests/ContainerizationOCITests/OCIPlatformTests.swift b/Tests/ContainerizationOCITests/OCIPlatformTests.swift index 7ffebedc..5a9c5aa7 100644 --- a/Tests/ContainerizationOCITests/OCIPlatformTests.swift +++ b/Tests/ContainerizationOCITests/OCIPlatformTests.swift @@ -81,4 +81,40 @@ struct OCIPlatformTests { set.insert(withoutVariant) #expect(set.contains(withV8), "arm64/v8 must be found in a Set that contains arm64 with nil variant") } + + // MARK: - description consistency (issue apple/container#1542) + + @Test func arm64_nilAndV8_sameDescription() { + let withoutVariant = Platform(arch: "arm64", os: "linux", variant: nil) + let withV8 = Platform(arch: "arm64", os: "linux", variant: "v8") + #expect( + withoutVariant.description == withV8.description, + "equal arm64 platforms must produce the same description" + ) + } + + @Test func arm64_descriptionDropsRedundantV8() { + let withV8 = Platform(arch: "arm64", os: "linux", variant: "v8") + #expect(withV8.description == "linux/arm64", "arm64/v8 is canonical arm64, rendered without the redundant variant") + } + + @Test func arm64_nilVariantDescription() { + let withoutVariant = Platform(arch: "arm64", os: "linux", variant: nil) + #expect(withoutVariant.description == "linux/arm64") + } + + @Test func arm64_fromStringWithV8DescriptionIsCanonical() throws { + let parsed = try Platform(from: "linux/arm64/v8") + #expect(parsed.description == "linux/arm64", "parsing arm64/v8 then describing must yield the canonical short form") + } + + @Test func arm_v7_descriptionKeepsVariant() { + let armv7 = Platform(arch: "arm", os: "linux", variant: "v7") + #expect(armv7.description == "linux/arm/v7", "non-redundant variants such as arm/v7 must be preserved") + } + + @Test func amd64_descriptionUnaffected() { + let amd64 = Platform(arch: "amd64", os: "linux") + #expect(amd64.description == "linux/amd64") + } }