//===----------------------------------------------------------------------===// // Copyright © 2025-2026 Apple Inc. and the Containerization 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 @testable import ContainerizationExtras @Suite("IPv4Address Tests") struct IPv4AddressTests { // MARK: - Initializer Tests @Suite("Initializers") struct InitializerTests { @Test( "UInt32 initializer", arguments: [ (0x7F00_0001, "127.0.0.1"), // localhost (0x0000_0000, "0.0.0.0"), // zero address (0xFFFF_FFFF, "255.255.255.255"), // max address (0xC0A8_0101, "192.168.1.1"), // private network (0x0808_0808, "8.8.8.8"), // Google DNS ] ) func testUInt32Initializer(inputValue: UInt32, description: String) { let address = IPv4Address(inputValue) #expect(address.value == inputValue) } @Test( "String initializer - valid addresses", arguments: [ ("127.0.0.1", 0x7F00_0001), // localhost ("0.0.0.0", 0x0000_0000), // zero address ("255.255.255.255", 0xFFFF_FFFF), // broadcast ("10.0.0.1", 0x0A00_0001), // private network 10.x ("192.168.1.1", 0xC0A8_0101), // private network 192.168.x ("172.16.0.1", 0xAC10_0001), // private network 172.16.x ("1.2.3.4", 0x0102_0304), // single digits ("192.168.100.254", 0xC0A8_64FE), // mixed digits ] ) func testStringInitializerValid(addressString: String, expectedValue: UInt32) throws { let address = try IPv4Address(addressString) #expect(address.value == expectedValue) } @Test( "String initializer - invalid addresses", arguments: [ "", // empty string "1.2.3", // too short "1.2.3.4.5", // too many octets "192.168.1.256", // octet out of range "192.168.001.1", // leading zeros "01.2.3.4", // leading zero first octet " 192.168.1.1", // leading whitespace "192.168.1.1 ", // trailing whitespace "192. 168.1.1", // internal whitespace "192.168.1.a", // invalid character "192.168.1.-1", // negative number "192..1.1", // missing octet ".168.1.1", // missing first octet "192.168.1.", // missing last octet "192.168.1.1.extra", // too long ] ) func testStringInitializerInvalid(invalidAddress: String) { #expect(throws: AddressError.self) { try IPv4Address(invalidAddress) } } @Test( "byte array initializer - valid addresses", arguments: [ ([UInt8(127), 0, 0, 1], UInt32(0x7F00_0001)), // localhost ([UInt8(0), 0, 0, 0], UInt32(0x0000_0000)), // zero address ([UInt8(255), 255, 255, 255], UInt32(0xFFFF_FFFF)), // broadcast ([UInt8(192), 168, 1, 1], UInt32(0xC0A8_0101)), // private network ([UInt8(0x12), 0x34, 0x56, 0x78], UInt32(0x1234_5678)), // byte order test ] ) func testByteArrayInitializerValid(bytes: [UInt8], expectedValue: UInt32) throws { let address = try IPv4Address(bytes) #expect(address.value == expectedValue) // The byte array initializer must round-trip with the bytes property. #expect(address.bytes == bytes) } @Test( "byte array initializer - invalid lengths", arguments: [ [], // empty [UInt8(1)], // too short [UInt8(1), 2, 3], // too short [UInt8(1), 2, 3, 4, 5], // too long ] ) func testByteArrayInitializerInvalid(bytes: [UInt8]) { #expect(throws: AddressError.self) { try IPv4Address(bytes) } } } // MARK: - Property Tests @Suite("Properties") struct PropertyTests { @Test( "bytes property", arguments: [ (UInt32(0x7F00_0001), [UInt8(127), UInt8(0), UInt8(0), UInt8(1)]), // localhost (UInt32(0x0000_0000), [UInt8(0), UInt8(0), UInt8(0), UInt8(0)]), // zero (UInt32(0xFFFF_FFFF), [UInt8(255), UInt8(255), UInt8(255), UInt8(255)]), // broadcast (UInt32(0xC0A8_0101), [UInt8(192), UInt8(168), UInt8(1), UInt8(1)]), // private network (UInt32(0x1234_5678), [UInt8(0x12), UInt8(0x34), UInt8(0x56), UInt8(0x78)]), // byte order test ] ) func testBytesProperty(inputValue: UInt32, expectedBytes: [UInt8]) { let address = IPv4Address(inputValue) #expect(address.bytes == expectedBytes) } @Test( "description property", arguments: [ (0x7F00_0001, "127.0.0.1"), // localhost (0x0000_0000, "0.0.0.0"), // zero (0xFFFF_FFFF, "255.255.255.255"), // broadcast (0xC0A8_0101, "192.168.1.1"), // private network (0x0102_0304, "1.2.3.4"), // single digits ] ) func testDescriptionProperty(inputValue: UInt32, expectedDescription: String) { let address = IPv4Address(inputValue) #expect(address.description == expectedDescription) } @Test( "round-trip string conversion", arguments: [ "0.0.0.0", "127.0.0.1", "192.168.1.1", "10.0.0.1", "172.16.0.1", "255.255.255.255", "1.2.3.4", "8.8.8.8", "1.1.1.1", ] ) func testRoundTripStringConversion(addressString: String) throws { let address = try IPv4Address(addressString) #expect(address.description == addressString) } } // MARK: - Protocol Conformance Tests @Suite("Protocol Conformances") struct ProtocolConformanceTests { @Test("Equatable conformance") func testEquatableConformance() { let addr1 = IPv4Address(0x7F00_0001) let addr2 = IPv4Address(0x7F00_0001) let addr3 = IPv4Address(0xC0A8_0101) #expect(addr1 == addr2) #expect(addr1 != addr3) #expect(addr2 != addr3) } @Test("Hashable conformance") func testHashableConformance() { let addr1 = IPv4Address(0x7F00_0001) let addr2 = IPv4Address(0x7F00_0001) let addr3 = IPv4Address(0xC0A8_0101) // Equal objects should have equal hash values #expect(addr1.hashValue == addr2.hashValue) // Different objects should ideally have different hash values // (though this is not guaranteed, it's very likely for these values) #expect(addr1.hashValue != addr3.hashValue) // Test that addresses can be used in Sets and Dictionaries let addressSet: Set = [addr1, addr2, addr3] #expect(addressSet.count == 2) // addr1 and addr2 are equal let addressDict = [addr1: "localhost", addr3: "private"] #expect(addressDict[addr2] == "localhost") // addr2 equals addr1 } @Test("CustomStringConvertible conformance") func testCustomStringConvertibleConformance() { let address = IPv4Address(0x7F00_0001) let stringRepresentation = String(describing: address) #expect(stringRepresentation == "127.0.0.1") } @Test("Sendable conformance") func testSendableConformance() { // This test verifies that IPv4Address can be safely passed across concurrency boundaries let address = IPv4Address(0x7F00_0001) Task { let taskAddress = address #expect(taskAddress.value == 0x7F00_0001) } } @Test( "Codable encodes to string representation", arguments: [ "127.0.0.1", "192.168.1.1", "0.0.0.0", "255.255.255.255", ] ) func testCodableEncode(address: String) throws { let original = try IPv4Address(address) let encoded = try JSONEncoder().encode(original) #expect(String(data: encoded, encoding: .utf8) == "\"\(address)\"") } @Test( "Codable decodes from string representation", arguments: [ "127.0.0.1", "192.168.1.1", "0.0.0.0", "255.255.255.255", ] ) func testCodableDecode(address: String) throws { let json = Data("\"\(address)\"".utf8) let decoded = try JSONDecoder().decode(IPv4Address.self, from: json) let expected = try IPv4Address(address) #expect(decoded == expected) } } // MARK: - Edge Cases and Error Conditions @Suite("Edge Cases") struct EdgeCaseTests { @Test( "boundary values", arguments: [ ("0.0.0.0", 0x0000_0000), // minimum ("255.255.255.255", 0xFFFF_FFFF), // maximum ("255.0.0.0", 0xFF00_0000), // max first octet ("0.255.0.0", 0x00FF_0000), // max second octet ("0.0.255.0", 0x0000_FF00), // max third octet ("0.0.0.255", 0x0000_00FF), // max fourth octet ] ) func testBoundaryValues(addressString: String, expectedValue: UInt32) throws { let address = try IPv4Address(addressString) #expect(address.value == expectedValue) } @Test( "special addresses", arguments: [ "127.0.0.1", // loopback "255.255.255.255", // broadcast "0.0.0.0", // network address "8.8.8.8", // Google DNS "1.1.1.1", // Cloudflare DNS ] ) func testSpecialAddresses(addressString: String) throws { let address = try IPv4Address(addressString) #expect(address.description == addressString) } @Test( "leading zero validation - invalid", arguments: [ "01.0.0.0", "0.01.0.0", "0.0.01.0", "0.0.0.01", "192.168.001.1", "010.0.0.1", "00.0.0.1", ] ) func testLeadingZeroValidationInvalid(invalidAddress: String) { #expect(throws: AddressError.self) { try IPv4Address(invalidAddress) } } @Test("leading zero validation - valid single zeros") func testLeadingZeroValidationValid() { // Single "0" should be valid #expect(throws: Never.self) { try IPv4Address("0.0.0.0") } } @Test( "string length validation - too short", arguments: [ "", "1", "1.2", "1.2.3", "1.2.3.", ] ) func testStringLengthValidationTooShort(shortString: String) { #expect(throws: AddressError.self) { try IPv4Address(shortString) } } @Test( "string length validation - too long", arguments: [ "255.255.255.255.1", "1234.168.1.1", "192.1234.1.1", "192.168.1234.1", "192.168.1.1234", ] ) func testStringLengthValidationTooLong(longString: String) { #expect(throws: AddressError.self) { try IPv4Address(longString) } } } // MARK: - Performance Tests @Suite("Performance") struct PerformanceTests { @Test("parsing performance") func testParsingPerformance() throws { let testAddresses = [ "192.168.1.1", "10.0.0.1", "172.16.0.1", "127.0.0.1", "8.8.8.8", "1.1.1.1", "255.255.255.255", "0.0.0.0", ] // Warm up for _ in 0..<100 { for address in testAddresses { _ = try IPv4Address(address) } } // Measure performance let iterations = 10000 let startTime = Date() for _ in 0.. String let addressFromUInt32 = IPv4Address(expectedValue) #expect(addressFromUInt32.description == expectedString) // Test String -> UInt32 let addressFromString = try IPv4Address(expectedString) #expect(addressFromString.value == expectedValue) // Test equality #expect(addressFromUInt32 == addressFromString) } @Test( "error message consistency", arguments: [ "", "256.1.1.1", "1.2.3", "1.2.3.4.5", "192.168.001.1", " 192.168.1.1", "192.168.1.1 ", "192.168.1.a", ] ) func testErrorMessageConsistency(invalidInput: String) { do { _ = try IPv4Address(invalidInput) #expect(Bool(false), "Should have thrown for input: \(invalidInput)") } catch let error as AddressError { #expect(error == AddressError.unableToParse) } catch { #expect(Bool(false), "Should have thrown AddressError, got: \(error)") } } } }