From d51641de6188b527e30f3a7bd8ef5c169aa4bdb8 Mon Sep 17 00:00:00 2001 From: Jonathan Flat Date: Tue, 9 Sep 2025 15:29:47 -0600 Subject: [PATCH] (160218245) Improve URL component validation performance --- Benchmarks/Benchmarks/URL/BenchmarkURL.swift | 40 ++-- .../FoundationEssentials/URL/URLParser.swift | 186 ++++++------------ .../URL/URLTemplate_PercentEncoding.swift | 4 +- .../FoundationEssentialsTests/URLTests.swift | 100 ++++++++++ .../FoundationEssentialsTests/UUIDTests.swift | 6 +- 5 files changed, 186 insertions(+), 150 deletions(-) diff --git a/Benchmarks/Benchmarks/URL/BenchmarkURL.swift b/Benchmarks/Benchmarks/URL/BenchmarkURL.swift index ae9912d4e..c27950c63 100644 --- a/Benchmarks/Benchmarks/URL/BenchmarkURL.swift +++ b/Benchmarks/Benchmarks/URL/BenchmarkURL.swift @@ -32,38 +32,38 @@ let benchmarks = { // MARK: - String Parsing - Benchmark("URL-ParseValidASCII") { benchmark in + Benchmark("URL.ParseValidASCII") { benchmark in for _ in benchmark.scaledIterations { blackHole(URL(string: validURLString)) } } - Benchmark("URLComponents-ParseValidASCII") { benchmark in + Benchmark("URLComponents.ParseValidASCII") { benchmark in for _ in benchmark.scaledIterations { blackHole(URLComponents(string: validURLString)) } } - Benchmark("URL-ParseInvalid") { benchmark in + Benchmark("URL.ParseInvalid") { benchmark in for _ in benchmark.scaledIterations { blackHole(URL(string: invalidURLString)) } } - Benchmark("URLComponents-ParseInvalid") { benchmark in + Benchmark("URLComponents.ParseInvalid") { benchmark in for _ in benchmark.scaledIterations { blackHole(URLComponents(string: invalidURLString)) } } #if os(macOS) || compiler(>=6) - Benchmark("URL-ParseAndEncode") { benchmark in + Benchmark("URL.ParseAndEncode") { benchmark in for _ in benchmark.scaledIterations { blackHole(URL(string: encodableURLString)) } } - Benchmark("URLComponents-ParseAndEncode") { benchmark in + Benchmark("URLComponents.ParseAndEncode") { benchmark in for _ in benchmark.scaledIterations { blackHole(URLComponents(string: encodableURLString)) } @@ -87,7 +87,7 @@ let benchmarks = { #if os(macOS) || compiler(>=6) // Component functions, e.g. path(), are available in macOS 13 and Swift 6 - Benchmark("URL-GetEncodedComponents") { benchmark in + Benchmark("URL.GetEncodedComponents") { benchmark in for _ in benchmark.scaledIterations { blackHole(encodedURL.scheme) blackHole(encodedURL.user()) @@ -100,7 +100,7 @@ let benchmarks = { } #endif - Benchmark("URLComponents-GetEncodedComponents") { benchmark in + Benchmark("URLComponents.GetEncodedComponents") { benchmark in for _ in benchmark.scaledIterations { blackHole(encodedComp.scheme) blackHole(encodedComp.percentEncodedUser) @@ -116,7 +116,7 @@ let benchmarks = { } } - Benchmark("URL-GetDecodedComponents") { benchmark in + Benchmark("URL.GetDecodedComponents") { benchmark in for _ in benchmark.scaledIterations { blackHole(encodedURL.scheme) blackHole(encodedURL.user) @@ -128,7 +128,7 @@ let benchmarks = { } } - Benchmark("URLComponents-GetDecodedComponents") { benchmark in + Benchmark("URLComponents.GetDecodedComponents") { benchmark in for _ in benchmark.scaledIterations { blackHole(encodedComp.scheme) blackHole(encodedComp.user) @@ -141,7 +141,7 @@ let benchmarks = { } let validComp = URLComponents(string: validURLString)! - Benchmark("URLComponents-GetComponentRanges") { benchmark in + Benchmark("URLComponents.GetComponentRanges") { benchmark in for _ in benchmark.scaledIterations { blackHole(validComp.rangeOfScheme) blackHole(validComp.rangeOfUser) @@ -156,7 +156,7 @@ let benchmarks = { // MARK: - Set URL Components - Benchmark("URLComponents-SetComponents") { benchmark in + Benchmark("URLComponents.SetComponents") { benchmark in for _ in benchmark.scaledIterations { var comp = URLComponents() comp.scheme = "scheme" @@ -171,7 +171,7 @@ let benchmarks = { } } - Benchmark("URLComponents-SetEncodableComponents") { benchmark in + Benchmark("URLComponents.SetEncodableComponents") { benchmark in for _ in benchmark.scaledIterations { var comp = URLComponents() comp.scheme = "scheme" @@ -200,7 +200,7 @@ let benchmarks = { URLQueryItem(name: "name with no value", value: nil) ] - Benchmark("URLComponents-SetQueryItems") { benchmark in + Benchmark("URLComponents.SetQueryItems") { benchmark in for _ in benchmark.scaledIterations { var comp = URLComponents() comp.queryItems = validQueryItems @@ -208,7 +208,7 @@ let benchmarks = { } } - Benchmark("URLComponents-SetEncodableQueryItems") { benchmark in + Benchmark("URLComponents.SetEncodableQueryItems") { benchmark in for _ in benchmark.scaledIterations { var comp = URLComponents() comp.queryItems = encodableQueryItems @@ -219,19 +219,21 @@ let benchmarks = { var queryComp = URLComponents() queryComp.queryItems = encodableQueryItems - Benchmark("URLComponents-GetEncodedQueryItems") { benchmark in + Benchmark("URLComponents.GetEncodedQueryItems") { benchmark in for _ in benchmark.scaledIterations { blackHole(queryComp.percentEncodedQueryItems) } } - Benchmark("URLComponents-GetDecodedQueryItems") { benchmark in + Benchmark("URLComponents.GetDecodedQueryItems") { benchmark in for _ in benchmark.scaledIterations { blackHole(queryComp.queryItems) } } - Benchmark("URL-Template-parsing") { benchmark in + // MARK: - URL.Template + + Benchmark("URL.TemplateParsing") { benchmark in for _ in benchmark.scaledIterations { blackHole(URL.Template("/api/{version}/accounts/{accountId}/transactions/{transactionId}{?expand*,fields*,embed*,format}")!) blackHole(URL.Template("/special/{+a}/details")!) @@ -270,7 +272,7 @@ let benchmarks = { .init("empty_keys"): [:], ] - Benchmark("URL-Template-expansion") { benchmark in + Benchmark("URL.TemplateExpansion") { benchmark in for _ in benchmark.scaledIterations { for t in templates { blackHole(URL(template: t, variables: variables)) diff --git a/Sources/FoundationEssentials/URL/URLParser.swift b/Sources/FoundationEssentials/URL/URLParser.swift index f8a07755d..17e4cd272 100644 --- a/Sources/FoundationEssentials/URL/URLParser.swift +++ b/Sources/FoundationEssentials/URL/URLParser.swift @@ -362,7 +362,7 @@ internal struct RFC3986Parser { } } - private static func validate(string: some StringProtocol, component: URLComponentSet, percentEncodingAllowed: Bool = true) -> Bool { + private static func validate(string: some StringProtocol, component: URLComponentAllowedMask, percentEncodingAllowed: Bool = true) -> Bool { let isValid = string.utf8.withContiguousStorageIfAvailable { validate(buffer: $0, component: component, percentEncodingAllowed: percentEncodingAllowed) } @@ -378,9 +378,9 @@ internal struct RFC3986Parser { return validate(buffer: string.utf8, component: component, percentEncodingAllowed: percentEncodingAllowed) } - private static func validate(buffer: T, component: URLComponentSet, percentEncodingAllowed: Bool = true) -> Bool where T.Element: UnsignedInteger { + private static func validate(buffer: T, component allowedMask: URLComponentAllowedMask, percentEncodingAllowed: Bool = true) -> Bool where T.Element: UnsignedInteger { guard percentEncodingAllowed else { - return buffer.allSatisfy { $0 < 128 && UInt8($0).isAllowedIn(component) } + return buffer.allSatisfy { $0 < 128 && allowedMask.contains(UInt8($0)) } } var hexDigitsRequired = 0 for v in buffer { @@ -392,7 +392,7 @@ internal struct RFC3986Parser { return false } hexDigitsRequired = 2 - } else if !UInt8(v).isAllowedIn(component) { + } else if !allowedMask.contains(UInt8(v)) { return false } else if hexDigitsRequired > 0 { guard UInt8(v).isValidHexDigit else { @@ -1040,7 +1040,7 @@ fileprivate func asciiToHex(_ ascii: UInt8) -> UInt8? { fileprivate extension StringProtocol { - func addingPercentEncoding(forURLComponent component: URLComponentSet, skipAlreadyEncoded: Bool = false) -> String { + func addingPercentEncoding(forURLComponent component: URLComponentAllowedMask, skipAlreadyEncoded: Bool = false) -> String { let fastResult = utf8.withContiguousStorageIfAvailable { addingPercentEncoding(utf8Buffer: $0, component: component, skipAlreadyEncoded: skipAlreadyEncoded) } @@ -1051,7 +1051,7 @@ fileprivate extension StringProtocol { } } - func addingPercentEncoding(utf8Buffer: some Collection, component: URLComponentSet, skipAlreadyEncoded: Bool = false) -> String { + func addingPercentEncoding(utf8Buffer: some Collection, component allowedMask: URLComponentAllowedMask, skipAlreadyEncoded: Bool = false) -> String { let percent = UInt8(ascii: "%") let maxLength = utf8Buffer.count * 3 return withUnsafeTemporaryAllocation(of: UInt8.self, capacity: maxLength) { outputBuffer -> String in @@ -1059,7 +1059,7 @@ fileprivate extension StringProtocol { var index = utf8Buffer.startIndex while index != utf8Buffer.endIndex { let v = utf8Buffer[index] - if v.isAllowedIn(component) { + if allowedMask.contains(v) { outputBuffer[i] = v i += 1 } else if skipAlreadyEncoded, v == percent, @@ -1137,7 +1137,7 @@ extension RFC3986Parser { /// characters like `;` or `/` to optionally be encoded, even though they're allowed in /// the path according to RFC 3986. static func percentEncode(pathComponent: some StringProtocol, including: Set = []) -> String { - precondition(including.allSatisfy { $0.isAllowedIn(.path) }) + precondition(including.allSatisfy { URLComponentAllowedMask.path.contains($0) }) let encoded = pathComponent.addingPercentEncoding(forURLComponent: .path) if including.isEmpty { return encoded @@ -1166,137 +1166,69 @@ extension RFC3986Parser { // MARK: - Validation Extensions -fileprivate struct URLComponentSet: OptionSet { - let rawValue: UInt8 - static let scheme = URLComponentSet(rawValue: 1 << 0) +// ===------------------------------------------------------------------------------------=== // +// URLComponentAllowedMask uses the following grammar from RFC 3986: +// +// let ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +// let DIGIT = "0123456789" +// let HEXDIG = DIGIT + "ABCDEFabcdef" +// let gen_delims = ":/?#[]@" +// let sub_delims = "!$&'()*+,;=" +// let unreserved = ALPHA + DIGIT + "-._~" +// let reserved = gen_delims + sub_delims +// NOTE: "%" is allowed in pchar and reg_name, but we must validate that 2 HEXDIG follow it +// let pchar = unreserved + sub_delims + ":" + "@" +// let reg_name = unreserved + sub_delims +// +// let schemeAllowed = CharacterSet(charactersIn: ALPHA + DIGIT + "+-.") +// let userinfoAllowed = CharacterSet(charactersIn: unreserved + sub_delims + ":") +// let hostAllowed = CharacterSet(charactersIn: reg_name) +// let hostIPvFutureAllowed = CharacterSet(charactersIn: unreserved + sub_delims + ":") +// let hostZoneIDAllowed = CharacterSet(charactersIn: unreserved) +// let portAllowed = CharacterSet(charactersIn: DIGIT) +// let pathAllowed = CharacterSet(charactersIn: pchar + "/") +// let pathFirstSegmentAllowed = pathAllowed.subtracting(CharacterSet(charactersIn: ":")) +// let queryAllowed = CharacterSet(charactersIn: pchar + "/?") +// let queryItemAllowed = queryAllowed.subtracting(CharacterSet(charactersIn: "=&")) +// let fragmentAllowed = CharacterSet(charactersIn: pchar + "/?") +// ===------------------------------------------------------------------------------------=== // + +internal struct URLComponentAllowedMask: RawRepresentable { + let rawValue: UInt128 + + static let alpha = Self(rawValue: 0x07fffffe07fffffe0000000000000000) + static let scheme = Self(rawValue: 0x07fffffe07fffffe03ff680000000000) // user, password, and hostIPvFuture use the same allowed character set. - static let user = URLComponentSet(rawValue: 1 << 1) - static let password = URLComponentSet(rawValue: 1 << 1) - static let hostIPvFuture = URLComponentSet(rawValue: 1 << 1) + static let user = Self(rawValue: 0x47fffffe87fffffe2fff7fd200000000) + static let password = Self(rawValue: 0x47fffffe87fffffe2fff7fd200000000) + static let hostIPvFuture = Self(rawValue: 0x47fffffe87fffffe2fff7fd200000000) - static let host = URLComponentSet(rawValue: 1 << 2) - static let hostZoneID = URLComponentSet(rawValue: 1 << 3) - static let path = URLComponentSet(rawValue: 1 << 4) - static let pathFirstSegment = URLComponentSet(rawValue: 1 << 5) + static let host = Self(rawValue: 0x47fffffe87fffffe2bff7fd200000000) + static let hostZoneID = Self(rawValue: 0x47fffffe87fffffe03ff600000000000) + static let path = Self(rawValue: 0x47fffffe87ffffff2fffffd200000000) + static let pathFirstSegment = Self(rawValue: 0x47fffffe87ffffff2bffffd200000000) // query and fragment use the same allowed character set. - static let query = URLComponentSet(rawValue: 1 << 6) - static let fragment = URLComponentSet(rawValue: 1 << 6) + static let query = Self(rawValue: 0x47fffffe87ffffffafffffd200000000) + static let fragment = Self(rawValue: 0x47fffffe87ffffffafffffd200000000) - static let queryItem = URLComponentSet(rawValue: 1 << 7) -} + static let queryItem = Self(rawValue: 0x47fffffe87ffffff8fffff9200000000) -extension UTF8.CodeUnit { - fileprivate func isAllowedIn(_ component: URLComponentSet) -> Bool { - return allowedURLComponents & component.rawValue != 0 - } + // `unreserved` character set from RFC 3986. + static let unreserved = Self(rawValue: 0x47fffffe87fffffe03ff600000000000) - // ===------------------------------------------------------------------------------------=== // - // allowedURLComponents was written programmatically using the following grammar from RFC 3986: - // - // let ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - // let DIGIT = "0123456789" - // let HEXDIG = DIGIT + "ABCDEFabcdef" - // let gen_delims = ":/?#[]@" - // let sub_delims = "!$&'()*+,;=" - // let unreserved = ALPHA + DIGIT + "-._~" - // let reserved = gen_delims + sub_delims - // NOTE: "%" is allowed in pchar and reg_name, but we must validate that 2 HEXDIG follow it - // let pchar = unreserved + sub_delims + ":" + "@" - // let reg_name = unreserved + sub_delims - // - // let schemeAllowed = CharacterSet(charactersIn: ALPHA + DIGIT + "+-.") - // let userinfoAllowed = CharacterSet(charactersIn: unreserved + sub_delims + ":") - // let hostAllowed = CharacterSet(charactersIn: reg_name) - // let hostIPvFutureAllowed = CharacterSet(charactersIn: unreserved + sub_delims + ":") - // let hostZoneIDAllowed = CharacterSet(charactersIn: unreserved) - // let portAllowed = CharacterSet(charactersIn: DIGIT) - // let pathAllowed = CharacterSet(charactersIn: pchar + "/") - // let pathFirstSegmentAllowed = pathAllowed.subtracting(CharacterSet(charactersIn: ":")) - // let queryAllowed = CharacterSet(charactersIn: pchar + "/?") - // let queryItemAllowed = queryAllowed.subtracting(CharacterSet(charactersIn: "=&")) - // let fragmentAllowed = CharacterSet(charactersIn: pchar + "/?") - // ===------------------------------------------------------------------------------------=== // - fileprivate var allowedURLComponents: URLComponentSet.RawValue { - switch self { - case UInt8(ascii: "!"): - return 0b11110110 - case UInt8(ascii: "$"): - return 0b11110110 - case UInt8(ascii: "&"): - return 0b01110110 - case UInt8(ascii: "'"): - return 0b11110110 - case UInt8(ascii: "("): - return 0b11110110 - case UInt8(ascii: ")"): - return 0b11110110 - case UInt8(ascii: "*"): - return 0b11110110 - case UInt8(ascii: "+"): - return 0b11110111 - case UInt8(ascii: ","): - return 0b11110110 - case UInt8(ascii: "-"): - return 0b11111111 - case UInt8(ascii: "."): - return 0b11111111 - case UInt8(ascii: "/"): - return 0b11110000 - case UInt8(ascii: "0")...UInt8(ascii: "9"): - return 0b11111111 - case UInt8(ascii: ":"): - return 0b11010010 - case UInt8(ascii: ";"): - return 0b11110110 - case UInt8(ascii: "="): - return 0b01110110 - case UInt8(ascii: "?"): - return 0b11000000 - case UInt8(ascii: "@"): - return 0b11110000 - case UInt8(ascii: "A")...UInt8(ascii: "Z"): - return 0b11111111 - case UInt8(ascii: "_"): - return 0b11111110 - case UInt8(ascii: "a")...UInt8(ascii: "z"): - return 0b11111111 - case UInt8(ascii: "~"): - return 0b11111110 - default: - return 0 - } - } + // `unreserved` + `reserved` character sets from RFC 3986. + static let anyValid = Self(rawValue: 0x47fffffeafffffffafffffda00000000) - /// Is the character in `unreserved + reserved` from RFC 3986. - internal var isValidURLCharacter: Bool { - guard self < 128 else { return false } - if self < 64 { - let allowed = UInt64(12682136387466559488) - return (allowed & (UInt64(1) << self)) != 0 - } else { - let allowed = UInt64(5188146765093666815) - return (allowed & (UInt64(1) << (self - 64))) != 0 - } - } - - /// Is the character in `unreserved` from RFC 3986. - internal var isUnreservedURLCharacter: Bool { - guard self < 128 else { return false } - let allowed: UInt128 = 0x47fffffe87fffffe03ff600000000000 - return allowed & (UInt128(1) << self) != 0 + func contains(_ codeUnit: UInt8) -> Bool { + return codeUnit < 128 && ((rawValue & (UInt128(1) << codeUnit)) != 0) } } internal extension UInt8 { var isAlpha: Bool { - switch self { - case UInt8(ascii: "A")...UInt8(ascii: "Z"), UInt8(ascii: "a")...UInt8(ascii: "z"): - return true - default: - return false - } + URLComponentAllowedMask.alpha.contains(self) } } @@ -1477,7 +1409,7 @@ extension RFC3986Parser { return parseInfo } break - } else if !v.isAllowedIn(.scheme) { + } else if !URLComponentAllowedMask.scheme.contains(v) { // For compatibility, now treat this as a relative-ref. currentIndex = buffer.startIndex break @@ -1528,7 +1460,7 @@ extension RFC3986Parser { if let portRange = parseInfo.portRange { // For compatibility, allow the port to have any ASCII // character you might see in some part of a URL. - guard buffer[portRange].allSatisfy({ $0.isValidURLCharacter }) else { + guard buffer[portRange].allSatisfy({ URLComponentAllowedMask.anyValid.contains($0) }) else { return nil } } diff --git a/Sources/FoundationEssentials/URL/URLTemplate_PercentEncoding.swift b/Sources/FoundationEssentials/URL/URLTemplate_PercentEncoding.swift index 6b1a760fe..94b8b82e0 100644 --- a/Sources/FoundationEssentials/URL/URLTemplate_PercentEncoding.swift +++ b/Sources/FoundationEssentials/URL/URLTemplate_PercentEncoding.swift @@ -192,9 +192,9 @@ extension URL.Template.Expression.Operator.AllowedCharacters { func isAllowedCodeUnit(_ unit: UTF8.CodeUnit) -> Bool { switch self { case .unreserved: - return unit.isUnreservedURLCharacter + return URLComponentAllowedMask.unreserved.contains(unit) case .unreservedReserved: - return unit.isValidURLCharacter + return URLComponentAllowedMask.anyValid.contains(unit) } } } diff --git a/Tests/FoundationEssentialsTests/URLTests.swift b/Tests/FoundationEssentialsTests/URLTests.swift index f26886957..4b1f75d5f 100644 --- a/Tests/FoundationEssentialsTests/URLTests.swift +++ b/Tests/FoundationEssentialsTests/URLTests.swift @@ -242,6 +242,106 @@ private struct URLTests { } } + @Test + func checkComponentValidation() throws { + struct ValidationResults: Equatable { + var lower: UInt128 = 0 + var upper: UInt128 = 0 + mutating func setAllowed(_ codeUnit: UInt8) { + if codeUnit < 128 { + lower |= (UInt128(1) << codeUnit) + } else { + upper |= (UInt128(1) << codeUnit) + } + } + } + + var schemeResults = ValidationResults() + var userResults = ValidationResults() + var passwordResults = ValidationResults() + var hostResults = ValidationResults() + var hostIPvFutureResults = ValidationResults() + var hostZoneIDResults = ValidationResults() + var portResults = ValidationResults() + var pathResults = ValidationResults() + var pathFirstSegmentResults = ValidationResults() + var queryResults = ValidationResults() + var queryItemResults = ValidationResults() + var fragmentResults = ValidationResults() + + for codeUnit in UInt8(0)...UInt8(255) { + let s = String(UnicodeScalar(codeUnit)) + // Scheme must start with ALPHA, so satisfy that here + if RFC3986Parser.validate("A\(s)", component: .scheme) { + schemeResults.setAllowed(codeUnit) + } + if RFC3986Parser.validate(s, component: .user) { + userResults.setAllowed(codeUnit) + } + if RFC3986Parser.validate(s, component: .password) { + passwordResults.setAllowed(codeUnit) + } + if RFC3986Parser.validate(s, component: .host) { + hostResults.setAllowed(codeUnit) + } + if RFC3986Parser.validate("[\(s)]", component: .host) { + hostIPvFutureResults.setAllowed(codeUnit) + } + if RFC3986Parser.validate("[::1%25\(s)]", component: .host) { + hostZoneIDResults.setAllowed(codeUnit) + } + if RFC3986Parser.validate(s, component: .port) { + portResults.setAllowed(codeUnit) + } + if RFC3986Parser.validate("/\(s)", component: .path) { + pathResults.setAllowed(codeUnit) + } + // URLComponents handles path first segment validation + var comp = URLComponents() + comp.path = s + if comp.percentEncodedPath == s { + pathFirstSegmentResults.setAllowed(codeUnit) + } + if RFC3986Parser.validate(s, component: .query) { + queryResults.setAllowed(codeUnit) + } + if RFC3986Parser.validate(s, component: .queryItem) { + queryItemResults.setAllowed(codeUnit) + } + if RFC3986Parser.validate(s, component: .fragment) { + fragmentResults.setAllowed(codeUnit) + } + } + + // Non-ASCII characters shouldn't be allowed in any component + #expect(schemeResults.upper == 0) + #expect(userResults.upper == 0) + #expect(passwordResults.upper == 0) + #expect(hostResults.upper == 0) + #expect(hostIPvFutureResults.upper == 0) + #expect(hostZoneIDResults.upper == 0) + #expect(portResults.upper == 0) + #expect(pathResults.upper == 0) + #expect(pathFirstSegmentResults.upper == 0) + #expect(queryResults.upper == 0) + #expect(queryItemResults.upper == 0) + #expect(fragmentResults.upper == 0) + + // Actual checks for valid ASCII characters + #expect(schemeResults.lower == 0x07fffffe07fffffe03ff680000000000) + #expect(userResults.lower == 0x47fffffe87fffffe2fff7fd200000000) + #expect(passwordResults.lower == 0x47fffffe87fffffe2fff7fd200000000) + #expect(hostResults.lower == 0x47fffffe87fffffe2bff7fd200000000) + #expect(hostIPvFutureResults.lower == 0x47fffffe87fffffe2fff7fd200000000) + #expect(hostZoneIDResults.lower == 0x47fffffe87fffffe03ff600000000000) + #expect(portResults.lower == 0x000000000000000003ff000000000000) + #expect(pathResults.lower == 0x47fffffe87ffffff2fffffd200000000) + #expect(pathFirstSegmentResults.lower == 0x47fffffe87ffffff2bffffd200000000) + #expect(queryResults.lower == 0x47fffffe87ffffffafffffd200000000) + #expect(queryItemResults.lower == 0x47fffffe87ffffff8fffff9200000000) + #expect(fragmentResults.lower == 0x47fffffe87ffffffafffffd200000000) + } + @Test(.enabled(if: foundation_swift_url_enabled())) func pathComponentsPercentEncodedSlash() throws { diff --git a/Tests/FoundationEssentialsTests/UUIDTests.swift b/Tests/FoundationEssentialsTests/UUIDTests.swift index 424886a55..784ab658d 100644 --- a/Tests/FoundationEssentialsTests/UUIDTests.swift +++ b/Tests/FoundationEssentialsTests/UUIDTests.swift @@ -124,7 +124,8 @@ private struct UUIDTests { #expect(uuid2 <= uuid1) #expect(uuid2 == uuid1) } - + + @available(FoundationPreview 6.3, *) @Test func randomVersionAndVariant() { var generator = SystemRandomNumberGenerator() for _ in 0..<10000 { @@ -133,7 +134,8 @@ private struct UUIDTests { #expect(uuid.varint == 0b10) } } - + + @available(FoundationPreview 6.3, *) @Test func deterministicRandomGeneration() { var generator = PCGRandomNumberGenerator(seed: 123456789)