680845cb1c
Linux build / Determine Swift version (push) Waiting to run
Linux build / Linux compile check (push) Blocked by required conditions
Build containerization / Verify commit signatures (push) Has been skipped
Build containerization / containerization (push) Successful in 0s
495 lines
17 KiB
Swift
495 lines
17 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// 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<IPv4Address> = [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..<iterations {
|
|
for address in testAddresses {
|
|
_ = try IPv4Address(address)
|
|
}
|
|
}
|
|
|
|
let endTime = Date()
|
|
let totalTime = endTime.timeIntervalSince(startTime)
|
|
let averageTime = totalTime / Double(iterations * testAddresses.count)
|
|
|
|
// Should be very fast - less than 1ms per parse on average
|
|
#expect(averageTime < 0.001, "Parsing should be fast: \(averageTime)s per address")
|
|
}
|
|
|
|
@Test("bytes property performance")
|
|
func testBytesPropertyPerformance() {
|
|
let address = IPv4Address(0xC0A8_0101)
|
|
|
|
let iterations = 100000
|
|
let startTime = Date()
|
|
|
|
for _ in 0..<iterations {
|
|
_ = address.bytes
|
|
}
|
|
|
|
let endTime = Date()
|
|
let totalTime = endTime.timeIntervalSince(startTime)
|
|
let averageTime = totalTime / Double(iterations)
|
|
|
|
// Should be very fast - less than 0.1ms per call on average
|
|
#expect(averageTime < 0.0001, "Bytes property should be fast: \(averageTime)s per call")
|
|
}
|
|
|
|
@Test("description property performance")
|
|
func testDescriptionPropertyPerformance() {
|
|
let address = IPv4Address(0xC0A8_0101)
|
|
|
|
let iterations = 10000
|
|
let startTime = Date()
|
|
|
|
for _ in 0..<iterations {
|
|
_ = address.description
|
|
}
|
|
|
|
let endTime = Date()
|
|
let totalTime = endTime.timeIntervalSince(startTime)
|
|
let averageTime = totalTime / Double(iterations)
|
|
|
|
// Should be reasonably fast - less than 1ms per call on average
|
|
#expect(averageTime < 0.001, "Description property should be fast: \(averageTime)s per call")
|
|
}
|
|
}
|
|
|
|
// MARK: - Integration Tests
|
|
|
|
@Suite("Integration")
|
|
struct IntegrationTests {
|
|
|
|
@Test(
|
|
"comprehensive round-trip test",
|
|
arguments: [
|
|
(0x0000_0000, "0.0.0.0"),
|
|
(0x7F00_0001, "127.0.0.1"),
|
|
(0xC0A8_0101, "192.168.1.1"),
|
|
(0x0A00_0001, "10.0.0.1"),
|
|
(0xAC10_0001, "172.16.0.1"),
|
|
(0xFFFF_FFFF, "255.255.255.255"),
|
|
(0x0808_0808, "8.8.8.8"),
|
|
(0x0101_0101, "1.1.1.1"),
|
|
(0x1234_5678, "18.52.86.120"),
|
|
(0xDEAD_BEEF, "222.173.190.239"),
|
|
]
|
|
)
|
|
func testComprehensiveRoundTrip(expectedValue: UInt32, expectedString: String) throws {
|
|
// Test UInt32 -> 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)")
|
|
}
|
|
}
|
|
}
|
|
}
|