diff --git a/Benchmarks/Benchmarks/URL/BenchmarkURL.swift b/Benchmarks/Benchmarks/URL/BenchmarkURL.swift index c27950c63..ae9912d4e 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,21 +219,19 @@ 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) } } - // MARK: - URL.Template - - Benchmark("URL.TemplateParsing") { benchmark in + Benchmark("URL-Template-parsing") { 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")!) @@ -272,7 +270,7 @@ let benchmarks = { .init("empty_keys"): [:], ] - Benchmark("URL.TemplateExpansion") { benchmark in + Benchmark("URL-Template-expansion") { 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 17e4cd272..f8a07755d 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: URLComponentAllowedMask, percentEncodingAllowed: Bool = true) -> Bool { + private static func validate(string: some StringProtocol, component: URLComponentSet, 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 allowedMask: URLComponentAllowedMask, percentEncodingAllowed: Bool = true) -> Bool where T.Element: UnsignedInteger { + private static func validate(buffer: T, component: URLComponentSet, percentEncodingAllowed: Bool = true) -> Bool where T.Element: UnsignedInteger { guard percentEncodingAllowed else { - return buffer.allSatisfy { $0 < 128 && allowedMask.contains(UInt8($0)) } + return buffer.allSatisfy { $0 < 128 && UInt8($0).isAllowedIn(component) } } var hexDigitsRequired = 0 for v in buffer { @@ -392,7 +392,7 @@ internal struct RFC3986Parser { return false } hexDigitsRequired = 2 - } else if !allowedMask.contains(UInt8(v)) { + } else if !UInt8(v).isAllowedIn(component) { 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: URLComponentAllowedMask, skipAlreadyEncoded: Bool = false) -> String { + func addingPercentEncoding(forURLComponent component: URLComponentSet, 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 allowedMask: URLComponentAllowedMask, skipAlreadyEncoded: Bool = false) -> String { + func addingPercentEncoding(utf8Buffer: some Collection, component: URLComponentSet, 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 allowedMask.contains(v) { + if v.isAllowedIn(component) { 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 { URLComponentAllowedMask.path.contains($0) }) + precondition(including.allSatisfy { $0.isAllowedIn(.path) }) let encoded = pathComponent.addingPercentEncoding(forURLComponent: .path) if including.isEmpty { return encoded @@ -1166,69 +1166,137 @@ extension RFC3986Parser { // MARK: - Validation Extensions -// ===------------------------------------------------------------------------------------=== // -// 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) +fileprivate struct URLComponentSet: OptionSet { + let rawValue: UInt8 + static let scheme = URLComponentSet(rawValue: 1 << 0) // user, password, and hostIPvFuture use the same allowed character set. - static let user = Self(rawValue: 0x47fffffe87fffffe2fff7fd200000000) - static let password = Self(rawValue: 0x47fffffe87fffffe2fff7fd200000000) - static let hostIPvFuture = Self(rawValue: 0x47fffffe87fffffe2fff7fd200000000) + static let user = URLComponentSet(rawValue: 1 << 1) + static let password = URLComponentSet(rawValue: 1 << 1) + static let hostIPvFuture = URLComponentSet(rawValue: 1 << 1) - static let host = Self(rawValue: 0x47fffffe87fffffe2bff7fd200000000) - static let hostZoneID = Self(rawValue: 0x47fffffe87fffffe03ff600000000000) - static let path = Self(rawValue: 0x47fffffe87ffffff2fffffd200000000) - static let pathFirstSegment = Self(rawValue: 0x47fffffe87ffffff2bffffd200000000) + 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) // query and fragment use the same allowed character set. - static let query = Self(rawValue: 0x47fffffe87ffffffafffffd200000000) - static let fragment = Self(rawValue: 0x47fffffe87ffffffafffffd200000000) + static let query = URLComponentSet(rawValue: 1 << 6) + static let fragment = URLComponentSet(rawValue: 1 << 6) - static let queryItem = Self(rawValue: 0x47fffffe87ffffff8fffff9200000000) + static let queryItem = URLComponentSet(rawValue: 1 << 7) +} - // `unreserved` character set from RFC 3986. - static let unreserved = Self(rawValue: 0x47fffffe87fffffe03ff600000000000) +extension UTF8.CodeUnit { + fileprivate func isAllowedIn(_ component: URLComponentSet) -> Bool { + return allowedURLComponents & component.rawValue != 0 + } - // `unreserved` + `reserved` character sets from RFC 3986. - static let anyValid = Self(rawValue: 0x47fffffeafffffffafffffda00000000) + // ===------------------------------------------------------------------------------------=== // + // 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 + } + } - func contains(_ codeUnit: UInt8) -> Bool { - return codeUnit < 128 && ((rawValue & (UInt128(1) << codeUnit)) != 0) + /// 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 } } internal extension UInt8 { var isAlpha: Bool { - URLComponentAllowedMask.alpha.contains(self) + switch self { + case UInt8(ascii: "A")...UInt8(ascii: "Z"), UInt8(ascii: "a")...UInt8(ascii: "z"): + return true + default: + return false + } } } @@ -1409,7 +1477,7 @@ extension RFC3986Parser { return parseInfo } break - } else if !URLComponentAllowedMask.scheme.contains(v) { + } else if !v.isAllowedIn(.scheme) { // For compatibility, now treat this as a relative-ref. currentIndex = buffer.startIndex break @@ -1460,7 +1528,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({ URLComponentAllowedMask.anyValid.contains($0) }) else { + guard buffer[portRange].allSatisfy({ $0.isValidURLCharacter }) else { return nil } } diff --git a/Sources/FoundationEssentials/URL/URLTemplate_PercentEncoding.swift b/Sources/FoundationEssentials/URL/URLTemplate_PercentEncoding.swift index 94b8b82e0..6b1a760fe 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 URLComponentAllowedMask.unreserved.contains(unit) + return unit.isUnreservedURLCharacter case .unreservedReserved: - return URLComponentAllowedMask.anyValid.contains(unit) + return unit.isValidURLCharacter } } } diff --git a/Tests/FoundationEssentialsTests/URLTests.swift b/Tests/FoundationEssentialsTests/URLTests.swift index 4b1f75d5f..f26886957 100644 --- a/Tests/FoundationEssentialsTests/URLTests.swift +++ b/Tests/FoundationEssentialsTests/URLTests.swift @@ -242,106 +242,6 @@ 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 784ab658d..424886a55 100644 --- a/Tests/FoundationEssentialsTests/UUIDTests.swift +++ b/Tests/FoundationEssentialsTests/UUIDTests.swift @@ -124,8 +124,7 @@ private struct UUIDTests { #expect(uuid2 <= uuid1) #expect(uuid2 == uuid1) } - - @available(FoundationPreview 6.3, *) + @Test func randomVersionAndVariant() { var generator = SystemRandomNumberGenerator() for _ in 0..<10000 { @@ -134,8 +133,7 @@ private struct UUIDTests { #expect(uuid.varint == 0b10) } } - - @available(FoundationPreview 6.3, *) + @Test func deterministicRandomGeneration() { var generator = PCGRandomNumberGenerator(seed: 123456789)