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
375 lines
12 KiB
Swift
375 lines
12 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
|
|
|
|
struct TestCIDR {
|
|
|
|
// MARK: - Normalization Tests
|
|
|
|
struct Normalization {
|
|
let input: String
|
|
let expectedNetwork: String
|
|
let expectedLength: UInt8
|
|
}
|
|
|
|
@Test(arguments: [
|
|
Normalization(
|
|
input: "192.168.1.100/24",
|
|
expectedNetwork: "192.168.1.0",
|
|
expectedLength: 24
|
|
),
|
|
Normalization(
|
|
input: "10.1.2.3/16",
|
|
expectedNetwork: "10.1.0.0",
|
|
expectedLength: 16
|
|
),
|
|
Normalization(
|
|
input: "2001:db8::1234/64",
|
|
expectedNetwork: "2001:db8::",
|
|
expectedLength: 64
|
|
),
|
|
Normalization(
|
|
input: "172.16.0.1/12",
|
|
expectedNetwork: "172.16.0.0",
|
|
expectedLength: 12
|
|
),
|
|
])
|
|
func testNormalization(testCase: Normalization) throws {
|
|
let cidr = try CIDR(testCase.input)
|
|
#expect(cidr.lower.description == testCase.expectedNetwork)
|
|
#expect(cidr.prefix.length == testCase.expectedLength)
|
|
}
|
|
|
|
struct ParsePreservation {
|
|
let input: String
|
|
let expectedIP: String
|
|
let expectedLength: UInt8
|
|
}
|
|
|
|
// MARK: - Bounds Tests
|
|
|
|
struct Bounds {
|
|
let cidr: String
|
|
let lower: String
|
|
let upper: String
|
|
}
|
|
|
|
@Test(arguments: [
|
|
Bounds(
|
|
cidr: "192.168.1.0/24",
|
|
lower: "192.168.1.0",
|
|
upper: "192.168.1.255"
|
|
),
|
|
Bounds(
|
|
cidr: "10.0.0.0/8",
|
|
lower: "10.0.0.0",
|
|
upper: "10.255.255.255"
|
|
),
|
|
Bounds(
|
|
cidr: "2001:db8::/64",
|
|
lower: "2001:db8::",
|
|
upper: "2001:db8::ffff:ffff:ffff:ffff"
|
|
),
|
|
Bounds(
|
|
cidr: "192.168.1.0/32",
|
|
lower: "192.168.1.0",
|
|
upper: "192.168.1.0"
|
|
),
|
|
])
|
|
func testBounds(testCase: Bounds) throws {
|
|
let block = try CIDR(testCase.cidr)
|
|
#expect(block.lower.description == testCase.lower)
|
|
#expect(block.upper.description == testCase.upper)
|
|
}
|
|
|
|
// MARK: - Containment Tests
|
|
|
|
struct IPContainment {
|
|
let cidr: String
|
|
let ip: String
|
|
let shouldContain: Bool
|
|
}
|
|
|
|
@Test(arguments: [
|
|
IPContainment(
|
|
cidr: "192.168.1.0/24",
|
|
ip: "192.168.1.100",
|
|
shouldContain: true
|
|
),
|
|
IPContainment(
|
|
cidr: "192.168.1.0/24",
|
|
ip: "192.168.2.1",
|
|
shouldContain: false
|
|
),
|
|
IPContainment(
|
|
cidr: "10.0.0.0/8",
|
|
ip: "10.255.255.255",
|
|
shouldContain: true
|
|
),
|
|
IPContainment(
|
|
cidr: "10.0.0.0/8",
|
|
ip: "11.0.0.1",
|
|
shouldContain: false
|
|
),
|
|
IPContainment(
|
|
cidr: "2001:db8::/32",
|
|
ip: "2001:db8::1",
|
|
shouldContain: true
|
|
),
|
|
])
|
|
func testContainsIP(testCase: IPContainment) throws {
|
|
let block = try CIDR(testCase.cidr)
|
|
let address = try IPAddress(testCase.ip)
|
|
#expect(block.contains(address) == testCase.shouldContain)
|
|
}
|
|
|
|
@Test func testDoesNotContainDifferentIPv6Zone() throws {
|
|
let cidr = try CIDR("fe80::1/64")
|
|
let ip = try IPv6Address("fe80::2%eth1")
|
|
#expect(!cidr.contains(.v6(ip)))
|
|
}
|
|
|
|
// MARK: - Range Constructor
|
|
|
|
@Test func testRangeConstructorFindsSmallestBlock() throws {
|
|
let lower = try IPAddress("192.168.1.0")
|
|
let upper = try IPAddress("192.168.1.255")
|
|
let cidr = try CIDR(lower: lower, upper: upper)
|
|
#expect(cidr.prefix.length == 24)
|
|
#expect(cidr.address.description == "192.168.1.0")
|
|
}
|
|
|
|
@Test func testRangeConstructorSingleIPv4() throws {
|
|
let ip = try IPAddress("192.168.1.100")
|
|
let cidr = try CIDR(lower: ip, upper: ip)
|
|
#expect(cidr.prefix.length == 32)
|
|
#expect(cidr.address.description == "192.168.1.100")
|
|
}
|
|
|
|
@Test func testRangeConstructorIPv6() throws {
|
|
let lower = try IPAddress("2001:db8::")
|
|
let upper = try IPAddress("2001:db8::ffff:ffff:ffff:ffff")
|
|
let cidr = try CIDR(lower: lower, upper: upper)
|
|
#expect(cidr.prefix.length == 64)
|
|
#expect(cidr.address.description == "2001:db8::")
|
|
}
|
|
|
|
@Test func testRangeConstructorSingleIPv6() throws {
|
|
let ip = try IPAddress("2001:db8::1")
|
|
let cidr = try CIDR(lower: ip, upper: ip)
|
|
#expect(cidr.prefix.length == 128)
|
|
#expect(cidr.address.description == "2001:db8::1")
|
|
}
|
|
|
|
@Test func testRangeConstructorRejectsMixedVersions() throws {
|
|
#expect(throws: CIDR.Error.self) {
|
|
let v4 = try IPAddress("192.168.1.0")
|
|
let v6 = try IPAddress("2001:db8::1")
|
|
_ = try CIDR(lower: v4, upper: v6)
|
|
}
|
|
}
|
|
|
|
@Test func testRangeConstructorRejectsDifferentZones() throws {
|
|
#expect(throws: CIDR.Error.self) {
|
|
let lower = IPAddress.v6(try IPv6Address("fe80::1%eth0"))
|
|
let upper = IPAddress.v6(try IPv6Address("fe80::2%eth1"))
|
|
_ = try CIDR(lower: lower, upper: upper)
|
|
}
|
|
}
|
|
|
|
// MARK: - Validation Tests
|
|
|
|
struct InvalidInput {
|
|
let input: String
|
|
}
|
|
|
|
@Test(arguments: [
|
|
InvalidInput(input: "192.168.1.0/33"), // IPv4 prefix too large
|
|
InvalidInput(input: "2001:db8::/129"), // IPv6 prefix too large
|
|
InvalidInput(input: "192.168.1.0"), // Missing prefix
|
|
InvalidInput(input: "192.168.1.0/"), // Empty prefix
|
|
InvalidInput(input: "192.168.1.0/abc"), // Invalid prefix
|
|
])
|
|
func testRejectsInvalidInput(testCase: InvalidInput) throws {
|
|
#expect(throws: CIDR.Error.self) {
|
|
try CIDR(testCase.input)
|
|
}
|
|
}
|
|
|
|
@Test func testRejectsInvalidRangeOrder() throws {
|
|
#expect(throws: CIDR.Error.self) {
|
|
let lower = try IPAddress("192.168.1.255")
|
|
let upper = try IPAddress("192.168.1.0")
|
|
_ = try CIDR(lower: lower, upper: upper)
|
|
}
|
|
}
|
|
|
|
// MARK: - Range Constructor Validation Tests
|
|
|
|
@Test func testRangeConstructorValidatesContainment() throws {
|
|
// This should work: 192.168.1.64 to 192.168.1.127 -> /26
|
|
let lower1 = try IPAddress("192.168.1.64")
|
|
let upper1 = try IPAddress("192.168.1.127")
|
|
let cidr1 = try CIDR(lower: lower1, upper: upper1)
|
|
#expect(cidr1.prefix.length == 26)
|
|
#expect(cidr1.contains(lower1))
|
|
#expect(cidr1.contains(upper1))
|
|
}
|
|
|
|
@Test func testRangeConstructorValidatesIPv6Containment() throws {
|
|
// Test IPv6 range containment validation
|
|
let lower = try IPAddress("2001:db8::1000")
|
|
let upper = try IPAddress("2001:db8::1fff")
|
|
let cidr = try CIDR(lower: lower, upper: upper)
|
|
#expect(cidr.contains(lower))
|
|
#expect(cidr.contains(upper))
|
|
}
|
|
|
|
// MARK: - Version-Specific Range Constructors
|
|
|
|
@Test func testV4RangeConstructor() throws {
|
|
let lower = try IPAddress("192.168.1.0")
|
|
let upper = try IPAddress("192.168.1.255")
|
|
let cidr = try CIDR(lower: lower, upper: upper)
|
|
#expect(cidr.prefix.length == 24)
|
|
#expect(cidr.address.description == "192.168.1.0")
|
|
}
|
|
|
|
@Test func testV4RangeSingleAddress() throws {
|
|
let addr = try IPAddress("192.168.1.100")
|
|
let cidr = try CIDR(lower: addr, upper: addr)
|
|
#expect(cidr.prefix.length == 32)
|
|
#expect(cidr.address.description == "192.168.1.100")
|
|
}
|
|
|
|
@Test func testV6RangeConstructor() throws {
|
|
let lower = try IPAddress("2001:db8::")
|
|
let upper = try IPAddress("2001:db8::ffff:ffff:ffff:ffff")
|
|
let cidr = try CIDR(lower: lower, upper: upper)
|
|
#expect(cidr.prefix.length == 64)
|
|
#expect(cidr.address.description == "2001:db8::")
|
|
}
|
|
|
|
@Test func testV6RangeSingleAddress() throws {
|
|
let addr = try IPAddress("2001:db8::1")
|
|
let cidr = try CIDR(lower: addr, upper: addr)
|
|
#expect(cidr.prefix.length == 128)
|
|
#expect(cidr.address.description == "2001:db8::1")
|
|
}
|
|
|
|
@Test func testV4RangeRejectsInvalidOrder() throws {
|
|
let lower = try IPAddress("192.168.1.255")
|
|
let upper = try IPAddress("192.168.1.0")
|
|
#expect(throws: CIDR.Error.self) {
|
|
_ = try CIDR(lower: lower, upper: upper)
|
|
}
|
|
}
|
|
|
|
@Test func testV6RangeRejectsInvalidOrder() throws {
|
|
let lower = try IPAddress("2001:db8::ffff")
|
|
let upper = try IPAddress("2001:db8::1")
|
|
#expect(throws: CIDR.Error.self) {
|
|
_ = try CIDR(lower: lower, upper: upper)
|
|
}
|
|
}
|
|
|
|
@Test func testV6RangeRejectsDifferentZones() throws {
|
|
let lower = try IPAddress("fe80::1%eth0")
|
|
let upper = try IPAddress("fe80::2%eth1")
|
|
#expect(throws: CIDR.Error.self) {
|
|
_ = try CIDR(lower: lower, upper: upper)
|
|
}
|
|
}
|
|
|
|
// MARK: - Description Tests
|
|
|
|
@Test func testDescriptionFormat() throws {
|
|
let cidr = try CIDR("10.0.0.0/8")
|
|
#expect(cidr.description == "10.0.0.0/8")
|
|
}
|
|
|
|
@Test func testPreservesAddress() throws {
|
|
let cidr = try CIDR("192.168.1.100/24")
|
|
#expect(cidr.description == "192.168.1.100/24")
|
|
}
|
|
|
|
@Test(
|
|
"CIDRv4 Codable encodes to string representation",
|
|
arguments: [
|
|
"192.168.1.0/24",
|
|
"10.0.0.0/8",
|
|
"172.16.0.0/12",
|
|
]
|
|
)
|
|
func testCIDRv4CodableEncode(cidr: String) throws {
|
|
let original = try CIDRv4(cidr)
|
|
let encoded = try JSONEncoder().encode(original)
|
|
let jsonString = String(data: encoded, encoding: .utf8)!
|
|
#expect(jsonString.contains(original.address.description))
|
|
#expect(jsonString.contains("\(original.prefix.length)"))
|
|
}
|
|
|
|
@Test(
|
|
"CIDRv4 Codable decodes from string representation",
|
|
arguments: [
|
|
"192.168.1.0/24",
|
|
"10.0.0.0/8",
|
|
"172.16.0.0/12",
|
|
]
|
|
)
|
|
func testCIDRv4CodableDecode(cidr: String) throws {
|
|
let json = Data("\"\(cidr)\"".utf8)
|
|
let decoded = try JSONDecoder().decode(CIDRv4.self, from: json)
|
|
let expected = try CIDRv4(cidr)
|
|
#expect(decoded == expected)
|
|
}
|
|
|
|
@Test(
|
|
"CIDRv6 Codable encodes to string representation",
|
|
arguments: [
|
|
("2001:db8::/32", "2001:db8::", 32),
|
|
("fe80::/10", "fe80::", 10),
|
|
("::1/128", "::1", 128),
|
|
]
|
|
)
|
|
func testCIDRv6CodableEncode(cidr: String, expectedAddr: String, expectedPrefix: UInt8) throws {
|
|
let original = try CIDRv6(cidr)
|
|
let encoded = try JSONEncoder().encode(original)
|
|
let jsonString = String(data: encoded, encoding: .utf8)!
|
|
#expect(jsonString.contains(expectedAddr))
|
|
#expect(jsonString.contains("\(expectedPrefix)"))
|
|
}
|
|
|
|
@Test(
|
|
"CIDRv6 Codable decodes from string representation",
|
|
arguments: [
|
|
"2001:db8::/32",
|
|
"fe80::/10",
|
|
"::1/128",
|
|
]
|
|
)
|
|
func testCIDRv6CodableDecode(cidr: String) throws {
|
|
let json = Data("\"\(cidr)\"".utf8)
|
|
let decoded = try JSONDecoder().decode(CIDRv6.self, from: json)
|
|
let expected = try CIDRv6(cidr)
|
|
#expect(decoded == expected)
|
|
}
|
|
}
|