From dfaa3660f1315be7fea4f3df529218a2b015318e Mon Sep 17 00:00:00 2001 From: Anas Khan <83116240+anxkhn@users.noreply.github.com> Date: Fri, 3 Jul 2026 15:55:33 +0530 Subject: [PATCH] Preserve fractional memory sizes across MemorySize encoding. MemorySize.formatted computed Int64(measurement.value), truncating the fractional part while keeping the coarse unit, so a configured size such as "1.5gb" encoded to "1gb" (and "0.5gb" to "0gb"). Because MemorySize is the Codable persisted representation of container and machine memory config, the configured value did not survive an encode/decode (save and reload) cycle and memory was silently dropped. Emit the exact byte count when the value is fractional; binary units are integer multiples of a byte, so this keeps the round-trip lossless and leaves whole-number output unchanged. Add a fractional round-trip test. --- Sources/ContainerPersistence/MemorySize.swift | 10 ++++++++++ Tests/ContainerAPIClientTests/MemorySizeTests.swift | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/Sources/ContainerPersistence/MemorySize.swift b/Sources/ContainerPersistence/MemorySize.swift index e0762b37d..d5fc645f6 100644 --- a/Sources/ContainerPersistence/MemorySize.swift +++ b/Sources/ContainerPersistence/MemorySize.swift @@ -49,6 +49,16 @@ public struct MemorySize: Codable, Sendable, Equatable, CustomStringConvertible ] public var formatted: String { + // Only whole-number sizes can be represented in the stored unit without + // loss. A fractional value (e.g. "1.5gb") would truncate to "1gb" and + // silently drop memory when re-encoded, so emit the exact byte count + // instead. Binary units are integer multiples of a byte, so this keeps + // the encode/decode round-trip lossless while leaving whole-number + // output (e.g. "1gb", "2048mb") unchanged. + guard measurement.value == measurement.value.rounded() else { + let bytes = UInt64(measurement.converted(to: .bytes).value.rounded()) + return "\(bytes)\(Self.unitLabels[.bytes] ?? "b")" + } let value = Int64(measurement.value) let label = Self.unitLabels[measurement.unit] ?? "unknown" return "\(value)\(label)" diff --git a/Tests/ContainerAPIClientTests/MemorySizeTests.swift b/Tests/ContainerAPIClientTests/MemorySizeTests.swift index 1d518db72..6baa2be16 100644 --- a/Tests/ContainerAPIClientTests/MemorySizeTests.swift +++ b/Tests/ContainerAPIClientTests/MemorySizeTests.swift @@ -61,6 +61,15 @@ struct MemorySizeTests { #expect(original == decoded) } + @Test(arguments: ["1.5gb", "0.5gb", "2.25tb", "1.5mb"]) + func testRoundTripEncodingFractional(input: String) throws { + let original = try MemorySize(input) + let data = try JSONEncoder().encode(original) + let decoded = try JSONDecoder().decode(MemorySize.self, from: data) + #expect(original == decoded) + #expect(original.toUInt64(unit: .bytes) == decoded.toUInt64(unit: .bytes)) + } + @Test func testDecodingFromString() throws { let json = Data("\"512kb\"".utf8) let decoded = try JSONDecoder().decode(MemorySize.self, from: json)