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
406 lines
14 KiB
Swift
406 lines
14 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// Copyright © 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("MACAddress Tests")
|
|
struct MACAddressTests {
|
|
|
|
// MARK: - Initializer Tests
|
|
|
|
@Suite("Initializers")
|
|
struct InitializerTests {
|
|
|
|
@Test(
|
|
"UInt64 initializer - valid addresses",
|
|
arguments: [
|
|
//(0x0123_4567_89ab, "01:23:45:67:89:ab"), // a valid address
|
|
//(0x0000_0000_0000, "00:00:00:00:00:00"), // zero address
|
|
//(0xFFFF_FFFF_FFFF, "ff:ff:ff:ff:ff:ff"), // max address
|
|
(0xffff_0123_4567_89ab, "01:23:45:67:89:ab") // drops the most significant 16 bits
|
|
]
|
|
)
|
|
func testUInt64InitializerValid(inputValue: UInt64, description: String) {
|
|
let address = MACAddress(inputValue)
|
|
#expect(address.value == inputValue & 0x0000_ffff_ffff_ffff)
|
|
}
|
|
|
|
@Test(
|
|
"String initializer - valid addresses",
|
|
arguments: [
|
|
("01:23:45:67:89:ab", 0x0123_4567_89ab), // colon separators
|
|
("01-23-45-67-89-ab", 0x0123_4567_89ab), // dash separators
|
|
("ab:cd:ef:AB:CD:EF", 0xabcd_efab_cdef), // mixed case
|
|
("00:00:00:00:00:00", 0x0000_0000_0000), // zero address
|
|
("ff:ff:ff:ff:ff:ff", 0xffff_ffff_ffff), // max address
|
|
]
|
|
)
|
|
func testStringInitializerValid(addressString: String, expectedValue: UInt64) throws {
|
|
let address = try MACAddress(addressString)
|
|
#expect(address.value == expectedValue)
|
|
}
|
|
|
|
@Test(
|
|
"String initializer - invalid addresses",
|
|
arguments: [
|
|
"", // empty string
|
|
"01:23:45:67:89", // too few octets
|
|
"01:23:45:67:89:ab:cd", // too many octets
|
|
"01:23:45:67:89:", // empty octet
|
|
":23:45:67:89:ab", // empty octet
|
|
"01::45:67:89:ab", // empty octet
|
|
"01:23:45:67:89:a", // short octet
|
|
"1:23:45:67:89:ab", // short octet
|
|
"01:2:45:67:89:ab", // short octet
|
|
"01:23:45:67:89:abc", // long octet
|
|
"012:23:45:67:89:ab", // long octet
|
|
"01:234:45:67:89:ab", // long octet
|
|
"01:23:45:67:89:@G", // invalid content 0x40, 0x47
|
|
"`g:23:45:67:89:ab", // invalid content 0x60, 0x67
|
|
"01:hi:45:67:89:ab", // invalid content
|
|
" 01:23:45:67:89:ab", // leading whitespace
|
|
"01:23:45:67:89:ab ", // trailing whitespace
|
|
"01: 23:45:67:89:ab", // internal whitespace
|
|
]
|
|
)
|
|
func testStringInitializerInvalid(invalidAddress: String) {
|
|
#expect(throws: AddressError.self) {
|
|
try MACAddress(invalidAddress)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Property Tests
|
|
|
|
@Suite("Properties")
|
|
struct PropertyTests {
|
|
|
|
@Test(
|
|
"bytes property",
|
|
arguments: [
|
|
(
|
|
UInt64(0x0123_4567_89ab),
|
|
[UInt8(0x01), UInt8(0x23), UInt8(0x45), UInt8(0x67), UInt8(0x89), UInt8(0xab)]
|
|
),
|
|
(
|
|
UInt64(0x0000_0000_0000),
|
|
[UInt8(0x00), UInt8(0x00), UInt8(0x00), UInt8(0x00), UInt8(0x00), UInt8(0x00)]
|
|
),
|
|
(
|
|
UInt64(0xffff_ffff_ffff),
|
|
[UInt8(0xff), UInt8(0xff), UInt8(0xff), UInt8(0xff), UInt8(0xff), UInt8(0xff)]
|
|
),
|
|
(
|
|
UInt64(0xffff_0123_4567_89ab),
|
|
[UInt8(0x01), UInt8(0x23), UInt8(0x45), UInt8(0x67), UInt8(0x89), UInt8(0xab)]
|
|
),
|
|
]
|
|
)
|
|
func testBytesProperty(inputValue: UInt64, expectedBytes: [UInt8]) {
|
|
let address = MACAddress(inputValue)
|
|
#expect(address.bytes == expectedBytes)
|
|
}
|
|
|
|
@Test(
|
|
"description property",
|
|
arguments: [
|
|
(0x0123_4567_89ab, "01:23:45:67:89:ab"),
|
|
(0x0000_0000_0000, "00:00:00:00:00:00"),
|
|
(0xffff_ffff_ffff, "ff:ff:ff:ff:ff:ff"),
|
|
(0xffff_0123_4567_89ab, "01:23:45:67:89:ab"),
|
|
]
|
|
)
|
|
func testDescriptionProperty(inputValue: UInt64, expectedDescription: String) {
|
|
let address = MACAddress(inputValue)
|
|
#expect(address.description == expectedDescription)
|
|
}
|
|
|
|
@Test(
|
|
"isLocallyAdministered property",
|
|
arguments: [
|
|
(0x0000_1234_5678, false),
|
|
(0x0200_1234_5678, true),
|
|
]
|
|
)
|
|
func testIsLocallyAdministeredProperty(inputValue: UInt64, expectedValue: Bool) {
|
|
let address = MACAddress(inputValue)
|
|
#expect(address.isLocallyAdministered == expectedValue)
|
|
}
|
|
|
|
@Test(
|
|
"isMulticast property",
|
|
arguments: [
|
|
(0x0000_1234_5678, false),
|
|
(0x0100_1234_5678, true),
|
|
]
|
|
)
|
|
func testIsMulticastProperty(inputValue: UInt64, expectedValue: Bool) {
|
|
let address = MACAddress(inputValue)
|
|
#expect(address.isMulticast == expectedValue)
|
|
}
|
|
|
|
@Test(
|
|
"round-trip string conversion",
|
|
arguments: [
|
|
"01:23:45:67:89:ab",
|
|
"00:00:00:00:00:00",
|
|
"ff:ff:ff:ff:ff:ff",
|
|
"01-23-45-67-89-AB",
|
|
]
|
|
)
|
|
func testRoundTripStringConversion(addressString: String) throws {
|
|
let address = try MACAddress(addressString)
|
|
#expect(address.description == addressString.lowercased().replacingOccurrences(of: "-", with: ":"))
|
|
}
|
|
}
|
|
|
|
// MARK: - Link Local Address Tests
|
|
|
|
@Suite("Link Local Addresses")
|
|
struct LinkLocalAddressTests {
|
|
|
|
@Test(
|
|
"Link local address",
|
|
arguments: [
|
|
(0x39a7_9407_cbd0, 0xfd97_7b15_d62e_75ac_3ba7_94ff_fe07_cbd0),
|
|
(0x5e3b_68d7_e510, 0xfd97_7b15_d62e_75ac_5c3b_68ff_fed7_e510),
|
|
]
|
|
)
|
|
func testLinkLocalAddress(mac: UInt64, ipv6: UInt128) throws {
|
|
let mac = MACAddress(mac)
|
|
let ipv6Prefix = IPv6Address(ipv6 & 0xffff_ffff_ffff_ffff_0000_0000_0000_0000)
|
|
let ipv6Address = try mac.ipv6Address(network: ipv6Prefix)
|
|
#expect(ipv6Address == IPv6Address(ipv6))
|
|
}
|
|
}
|
|
|
|
// MARK: - Protocol Conformance Tests
|
|
|
|
@Suite("Protocol Conformances")
|
|
struct ProtocolConformanceTests {
|
|
|
|
@Test("Equatable conformance")
|
|
func testEquatableConformance() {
|
|
let addr1 = MACAddress(0x0123_4567_89ab)
|
|
let addr2 = MACAddress(0x0123_4567_89ab)
|
|
let addr3 = MACAddress(0x0123_4567_89ac)
|
|
|
|
#expect(addr1 == addr2)
|
|
#expect(addr1 != addr3)
|
|
#expect(addr2 != addr3)
|
|
}
|
|
|
|
@Test("Hashable conformance")
|
|
func testHashableConformance() {
|
|
let addr1 = MACAddress(0x0123_4567_89ab)
|
|
let addr2 = MACAddress(0x0123_4567_89ab)
|
|
let addr3 = MACAddress(0x0123_4567_89ac)
|
|
|
|
// 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<MACAddress> = [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 = MACAddress(0x0123_4567_89ab)
|
|
let stringRepresentation = String(describing: address)
|
|
#expect(stringRepresentation == "01:23:45:67:89:ab")
|
|
}
|
|
|
|
@Test("Sendable conformance")
|
|
func testSendableConformance() {
|
|
// This test verifies that MACAddress can be safely passed across concurrency boundaries
|
|
let address = MACAddress(0x0123_4567_89ab)
|
|
|
|
Task {
|
|
let taskAddress = address
|
|
#expect(taskAddress.value == 0x0123_4567_89ab)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Performance Tests
|
|
|
|
@Suite("Performance")
|
|
struct PerformanceTests {
|
|
|
|
@Test("parsing performance")
|
|
func testParsingPerformance() throws {
|
|
let testAddresses = [
|
|
"01:23:45:67:89:ab",
|
|
"01-23-45-67-89-ab",
|
|
"01-23-45-67-89-a",
|
|
"01-23-45-67-89-abc",
|
|
]
|
|
|
|
// Warm up
|
|
for _ in 0..<100 {
|
|
for address in testAddresses {
|
|
_ = try? MACAddress(address)
|
|
}
|
|
}
|
|
|
|
// Measure performance
|
|
let iterations = 10000
|
|
let startTime = Date()
|
|
|
|
for _ in 0..<iterations {
|
|
for address in testAddresses {
|
|
_ = try? MACAddress(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 = MACAddress(0x0123_4567_89ab)
|
|
|
|
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 = MACAddress(0x0123_4567_89ab)
|
|
|
|
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: [
|
|
(0x0123_4567_89ab, "01:23:45:67:89:ab"),
|
|
(0x0000_0000_0000, "00:00:00:00:00:00"),
|
|
(0xffff_ffff_ffff, "ff:ff:ff:ff:ff:ff"),
|
|
]
|
|
)
|
|
func testComprehensiveRoundTrip(expectedValue: UInt64, expectedString: String) throws {
|
|
// Test UInt32 -> String
|
|
let addressFromUInt32 = MACAddress(expectedValue)
|
|
#expect(addressFromUInt32.description == expectedString)
|
|
|
|
// Test String -> UInt32
|
|
let addressFromString = try MACAddress(expectedString)
|
|
#expect(addressFromString.value == expectedValue)
|
|
|
|
// Test equality
|
|
#expect(addressFromUInt32 == addressFromString)
|
|
}
|
|
|
|
@Test(
|
|
"error message consistency",
|
|
arguments: [
|
|
"",
|
|
"hi:00:00:00:00:00",
|
|
"01:23:45:67:89",
|
|
"01:23:45:67:89:ab:cd",
|
|
"001:23:45:67:89:ab:cd",
|
|
" 01:23:45:67:89:ab:cd",
|
|
"01:23:45:67:89:ab:cd ",
|
|
]
|
|
)
|
|
func testErrorMessageConsistency(invalidInput: String) {
|
|
do {
|
|
_ = try MACAddress(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)")
|
|
}
|
|
}
|
|
|
|
@Test(
|
|
"Codable encodes to string representation",
|
|
arguments: [
|
|
"01:23:45:67:89:ab",
|
|
"00:00:00:00:00:00",
|
|
"ff:ff:ff:ff:ff:ff",
|
|
]
|
|
)
|
|
func testCodableEncode(address: String) throws {
|
|
let original = try MACAddress(address)
|
|
let encoded = try JSONEncoder().encode(original)
|
|
#expect(String(data: encoded, encoding: .utf8) == "\"\(address)\"")
|
|
}
|
|
|
|
@Test(
|
|
"Codable decodes from string representation",
|
|
arguments: [
|
|
"01:23:45:67:89:ab",
|
|
"00:00:00:00:00:00",
|
|
"ff:ff:ff:ff:ff:ff",
|
|
]
|
|
)
|
|
func testCodableDecode(address: String) throws {
|
|
let json = Data("\"\(address)\"".utf8)
|
|
let decoded = try JSONDecoder().decode(MACAddress.self, from: json)
|
|
let expected = try MACAddress(address)
|
|
#expect(decoded == expected)
|
|
}
|
|
}
|
|
}
|