From 05240d090361567257c4a46ccab5b011af15405f Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 3 Jul 2025 15:57:31 -0700 Subject: [PATCH 1/2] Cleanup swift-testing migration --- Package.swift | 5 +- .../JSON/JSONDecoder.swift | 2 +- .../String/String+Encoding.swift | 7 - .../Formatting/Duration+TimeFormatStyle.swift | 3 - .../Duration+UnitsFormatStyle.swift | 3 - Sources/TestSupport/TestSupport.swift | 237 ------ Sources/TestSupport/Utilities.swift | 718 ------------------ .../AttributedStringTestSupport.swift | 6 +- .../FileManager/FileManagerTests.swift | 21 +- .../FoundationEssentialsTests.swift | 25 + .../GregorianCalendarTests.swift | 126 +-- .../ProcessInfoTests.swift | 4 +- .../ResourceUtilities.swift | 6 +- .../StringTests.swift | 148 ++-- .../FoundationEssentialsTests/URLTests.swift | 119 --- .../DurationTimeFormatStyleTests.swift | 6 +- .../DurationUnitsFormatStyleTests.swift | 28 +- .../Formatting/NumberFormatStyleTests.swift | 2 +- .../Formatting/NumberParseStrategyTests.swift | 4 +- .../LocaleComponentsTests.swift | 14 +- .../URLInternationalizationTests.swift | 154 ++++ .../URLTests+UIDNA.swift | 35 - Tests/TestSupport/Utilities.swift | 266 +++++++ 23 files changed, 573 insertions(+), 1366 deletions(-) delete mode 100644 Sources/TestSupport/TestSupport.swift delete mode 100644 Sources/TestSupport/Utilities.swift create mode 100644 Tests/FoundationEssentialsTests/FoundationEssentialsTests.swift create mode 100644 Tests/FoundationInternationalizationTests/URLInternationalizationTests.swift delete mode 100644 Tests/FoundationInternationalizationTests/URLTests+UIDNA.swift create mode 100644 Tests/TestSupport/Utilities.swift diff --git a/Package.swift b/Package.swift index cedb5599b..88a735fc1 100644 --- a/Package.swift +++ b/Package.swift @@ -99,10 +99,7 @@ let package = Package( // TestSupport (Internal) .target( name: "TestSupport", - dependencies: [ - "FoundationEssentials", - "FoundationInternationalization", - ], + path: "Tests/TestSupport", cSettings: wasiLibcCSettings, swiftSettings: availabilityMacros + featureSettings ), diff --git a/Sources/FoundationEssentials/JSON/JSONDecoder.swift b/Sources/FoundationEssentials/JSON/JSONDecoder.swift index b9e9a01cb..c7f5b7653 100644 --- a/Sources/FoundationEssentials/JSON/JSONDecoder.swift +++ b/Sources/FoundationEssentials/JSON/JSONDecoder.swift @@ -413,7 +413,7 @@ open class JSONDecoder { // Check for explicit BOM first, then check the first two bytes. Note that if there is a BOM, we have to create our string without it. // This isn't strictly part of the JSON spec but it's useful to do anyway. - let sourceEncoding : String._Encoding + let sourceEncoding : String.Encoding let bomLength : Int switch (byte0, byte1, byte2, byte3) { case (0, 0, 0xFE, 0xFF): diff --git a/Sources/FoundationEssentials/String/String+Encoding.swift b/Sources/FoundationEssentials/String/String+Encoding.swift index 563f5d137..7676b7290 100644 --- a/Sources/FoundationEssentials/String/String+Encoding.swift +++ b/Sources/FoundationEssentials/String/String+Encoding.swift @@ -50,13 +50,6 @@ extension String { public static let utf32LittleEndian = Encoding(rawValue: 0x9c000100) } - // This is a workaround for Clang importer's ambiguous lookup issue since - // - Swift doesn't allow typealias to nested type - // - Swift doesn't allow typealias to builtin types like String - // We therefore rename String.Encoding to String._Encoding for package - // internal use so we can use `String._Encoding` to disambiguate. - internal typealias _Encoding = Encoding - #if FOUNDATION_FRAMEWORK public typealias EncodingConversionOptions = NSString.EncodingConversionOptions public typealias EnumerationOptions = NSString.EnumerationOptions diff --git a/Sources/FoundationInternationalization/Formatting/Duration+TimeFormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Duration+TimeFormatStyle.swift index 2578c39d1..3484c19ba 100644 --- a/Sources/FoundationInternationalization/Formatting/Duration+TimeFormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Duration+TimeFormatStyle.swift @@ -147,9 +147,6 @@ extension Duration { } } - - // For testing purpose. See notes about String._Encoding - internal typealias _TimeFormatStyle = TimeFormatStyle } @available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) diff --git a/Sources/FoundationInternationalization/Formatting/Duration+UnitsFormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Duration+UnitsFormatStyle.swift index f17ce7548..a0dee8363 100644 --- a/Sources/FoundationInternationalization/Formatting/Duration+UnitsFormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Duration+UnitsFormatStyle.swift @@ -495,9 +495,6 @@ extension Duration { Attributed(innerStyle: self) } } - - // For testing purpose. See notes about String._Encoding - internal typealias _UnitsFormatStyle = UnitsFormatStyle } // `FormatStyle` static membership lookup diff --git a/Sources/TestSupport/TestSupport.swift b/Sources/TestSupport/TestSupport.swift deleted file mode 100644 index a8847500f..000000000 --- a/Sources/TestSupport/TestSupport.swift +++ /dev/null @@ -1,237 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2020 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -@_exported import XCTest - -// See this issue for more info on this file: https://github.com/apple/swift-foundation/issues/40 - -#if FOUNDATION_FRAMEWORK -@testable import Foundation - -public typealias Calendar = Foundation.Calendar -public typealias TimeZone = Foundation.TimeZone -public typealias Locale = Foundation.Locale -public typealias Data = Foundation.Data -public typealias UUID = Foundation.UUID -public typealias Date = Foundation.Date -public typealias DateInterval = Foundation.DateInterval -public typealias DateComponents = Foundation.DateComponents -public typealias Decimal = Foundation.Decimal -public typealias TimeInterval = Foundation.TimeInterval -public typealias JSONEncoder = Foundation.JSONEncoder -public typealias JSONDecoder = Foundation.JSONDecoder -public typealias PropertyListEncoder = Foundation.PropertyListEncoder -public typealias PropertyListDecoder = Foundation.PropertyListDecoder -public typealias ProcessInfo = Foundation.ProcessInfo -public typealias IndexPath = Foundation.IndexPath - -// XCTest implicitly imports Foundation -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias FormatStyle = Foundation.FormatStyle -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias ByteCountFormatStyle = Foundation.ByteCountFormatStyle -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias ListFormatStyle = Foundation.ListFormatStyle -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias IntegerFormatStyle = Foundation.IntegerFormatStyle -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias FloatingPointFormatStyle = Foundation.FloatingPointFormatStyle -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias NumberFormatStyleConfiguration = Foundation.NumberFormatStyleConfiguration -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias CurrencyFormatStyleConfiguration = Foundation.CurrencyFormatStyleConfiguration -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias IntegerParseStrategy = Foundation.IntegerParseStrategy - -@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) -public typealias DiscreteFormatStyle = Foundation.DiscreteFormatStyle - -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias StringStyle = Foundation.StringStyle - -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributedString = Foundation.AttributedString -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributeScope = Foundation.AttributeScope -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributeContainer = Foundation.AttributeContainer -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributeDynamicLookup = Foundation.AttributeDynamicLookup -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributeScopes = Foundation.AttributeScopes -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributedStringAttributeMutation = Foundation.AttributedStringAttributeMutation -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributedStringKey = Foundation.AttributedStringKey -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributedStringProtocol = Foundation.AttributedStringProtocol -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributedSubstring = Foundation.AttributedSubstring -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias ScopedAttributeContainer = Foundation.ScopedAttributeContainer -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias CodableAttributedStringKey = Foundation.CodableAttributedStringKey -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias EncodableAttributedStringKey = Foundation.EncodableAttributedStringKey -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias DecodableAttributedStringKey = Foundation.DecodableAttributedStringKey - -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias CodableWithConfiguration = Foundation.CodableWithConfiguration -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias EncodableWithConfiguration = Foundation.EncodableWithConfiguration -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias DecodableWithConfiguration = Foundation.DecodableWithConfiguration -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias EncodingConfigurationProviding = Foundation.EncodingConfigurationProviding -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias DecodingConfigurationProviding = Foundation.DecodingConfigurationProviding - -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias Predicate = Foundation.Predicate -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias PredicateBindings = Foundation.PredicateBindings -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias PredicateExpression = Foundation.PredicateExpression -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias PredicateExpressions = Foundation.PredicateExpressions -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias StandardPredicateExpression = Foundation.StandardPredicateExpression -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias PredicateError = Foundation.PredicateError -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias PredicateCodableConfiguration = Foundation.PredicateCodableConfiguration -@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) -public typealias Expression = Foundation.Expression -#else - -#if DEBUG -@_exported @testable import FoundationEssentials -@_exported @testable import FoundationInternationalization -// XCTest implicitly imports Foundation -#else -@_exported import FoundationEssentials -@_exported import FoundationInternationalization -// XCTest implicitly imports Foundation -#endif - -public typealias Data = FoundationEssentials.Data -public typealias UUID = FoundationEssentials.UUID -public typealias Date = FoundationEssentials.Date -public typealias DateInterval = FoundationEssentials.DateInterval -public typealias Decimal = FoundationEssentials.Decimal -public typealias TimeInterval = FoundationEssentials.TimeInterval -public typealias JSONEncoder = FoundationEssentials.JSONEncoder -public typealias JSONDecoder = FoundationEssentials.JSONDecoder -public typealias PropertyListEncoder = FoundationEssentials.PropertyListEncoder -public typealias PropertyListDecoder = FoundationEssentials.PropertyListDecoder - -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias FormatStyle = FoundationEssentials.FormatStyle -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias ByteCountFormatStyle = FoundationInternationalization.ByteCountFormatStyle -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias ListFormatStyle = FoundationInternationalization.ListFormatStyle -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias IntegerFormatStyle = FoundationInternationalization.IntegerFormatStyle -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias FloatingPointFormatStyle = FoundationInternationalization.FloatingPointFormatStyle -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias NumberFormatStyleConfiguration = FoundationInternationalization.NumberFormatStyleConfiguration -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias CurrencyFormatStyleConfiguration = FoundationInternationalization.CurrencyFormatStyleConfiguration -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias IntegerParseStrategy = FoundationInternationalization.IntegerParseStrategy - -@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) -public typealias DiscreteFormatStyle = FoundationEssentials.DiscreteFormatStyle - -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias StringStyle = FoundationInternationalization.StringStyle - -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributedString = FoundationEssentials.AttributedString -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributeScope = FoundationEssentials.AttributeScope -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributeContainer = FoundationEssentials.AttributeContainer -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributeDynamicLookup = FoundationEssentials.AttributeDynamicLookup -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributeScopes = FoundationEssentials.AttributeScopes -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributedStringAttributeMutation = FoundationEssentials.AttributedStringAttributeMutation -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributedStringKey = FoundationEssentials.AttributedStringKey -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributedStringProtocol = FoundationEssentials.AttributedStringProtocol -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias AttributedSubstring = FoundationEssentials.AttributedSubstring -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias ScopedAttributeContainer = FoundationEssentials.ScopedAttributeContainer -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias CodableAttributedStringKey = FoundationEssentials.CodableAttributedStringKey -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias EncodableAttributedStringKey = FoundationEssentials.EncodableAttributedStringKey -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias DecodableAttributedStringKey = FoundationEssentials.DecodableAttributedStringKey - -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias CodableWithConfiguration = FoundationEssentials.CodableWithConfiguration -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias EncodableWithConfiguration = FoundationEssentials.EncodableWithConfiguration -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias DecodableWithConfiguration = FoundationEssentials.DecodableWithConfiguration -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias EncodingConfigurationProviding = FoundationEssentials.EncodingConfigurationProviding -@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias DecodingConfigurationProviding = FoundationEssentials.DecodingConfigurationProviding - -public typealias Calendar = FoundationEssentials.Calendar -public typealias TimeZone = FoundationEssentials.TimeZone -public typealias Locale = FoundationEssentials.Locale -public typealias DateComponents = FoundationEssentials.DateComponents - -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias Predicate = FoundationEssentials.Predicate -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias PredicateBindings = FoundationEssentials.PredicateBindings -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias PredicateExpression = FoundationEssentials.PredicateExpression -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias PredicateExpressions = FoundationEssentials.PredicateExpressions -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias StandardPredicateExpression = FoundationEssentials.StandardPredicateExpression -@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) -public typealias PredicateError = FoundationEssentials.PredicateError -@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) -public typealias Expression = FoundationEssentials.Expression - -public typealias SortDescriptor = FoundationInternationalization.SortDescriptor -public typealias SortComparator = FoundationEssentials.SortComparator -public typealias ComparableComparator = FoundationEssentials.ComparableComparator -public typealias ComparisonResult = FoundationEssentials.ComparisonResult - -public typealias FileManager = FoundationEssentials.FileManager -public typealias FileAttributeKey = FoundationEssentials.FileAttributeKey -public typealias FileAttributeType = FoundationEssentials.FileAttributeType -public typealias CocoaError = FoundationEssentials.CocoaError -public typealias POSIXError = FoundationEssentials.POSIXError -public typealias FileManagerDelegate = FoundationEssentials.FileManagerDelegate -public typealias ProcessInfo = FoundationEssentials.ProcessInfo -public typealias OperatingSystemVersion = FoundationEssentials.OperatingSystemVersion -public typealias IndexPath = FoundationEssentials.IndexPath -public typealias URL = FoundationEssentials.URL -public typealias URLComponents = FoundationEssentials.URLComponents -public typealias URLQueryItem = FoundationEssentials.URLQueryItem - -#endif // FOUNDATION_FRAMEWORK diff --git a/Sources/TestSupport/Utilities.swift b/Sources/TestSupport/Utilities.swift deleted file mode 100644 index 3a8cb7587..000000000 --- a/Sources/TestSupport/Utilities.swift +++ /dev/null @@ -1,718 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2022 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import XCTest - -#if FOUNDATION_FRAMEWORK -import Foundation -#else -import FoundationEssentials -#endif - -extension Optional { - @available(*, unavailable, message: "Use XCTUnwrap() instead") - func unwrapped(_ fn: String = #function, file: StaticString = #filePath, line: UInt = #line) throws -> Wrapped { - return try XCTUnwrap(self, file: file, line: line) - } -} - -func expectThrows(_ expectedError: Error, _ test: () throws -> Void, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { - var caught = false - do { - try test() - } catch let error as Error { - caught = true - XCTAssertEqual(error, expectedError, message(), file: file, line: line) - } catch { - caught = true - XCTFail("Incorrect error thrown: \(error) -- \(message())", file: file, line: line) - } - XCTAssert(caught, "No error thrown -- \(message())", file: file, line: line) -} - -func expectDoesNotThrow(_ test: () throws -> Void, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { - XCTAssertNoThrow(try test(), message(), file: file, line: line) -} - -func expectTrue(_ actual: Bool, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { - XCTAssertTrue(actual, message(), file: file, line: line) -} - -func expectFalse(_ actual: Bool, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { - XCTAssertFalse(actual, message(), file: file, line: line) -} - -public func expectEqual(_ expected: T, _ actual: T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(expected, actual, message(), file: file, line: line) -} - -public func expectNotEqual(_ expected: T, _ actual: T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { - XCTAssertNotEqual(expected, actual, message(), file: file, line: line) -} - -public func expectEqual(_ expected: T, _ actual: T, within: T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(expected, actual, accuracy: within, message(), file: file, line: line) -} - -public func expectEqual(_ expected: T?, _ actual: T, within: T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { - XCTAssertNotNil(expected, message(), file: file, line: line) - if let expected = expected { - XCTAssertEqual(expected, actual, accuracy: within, message(), file: file, line: line) - } -} - -public func expectEqual( - _ expected: Any.Type, - _ actual: Any.Type, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line -) { - XCTAssertTrue(expected == actual, message(), file: file, line: line) -} - -public func expectEqualSequence< Expected: Sequence, Actual: Sequence>( - _ expected: Expected, _ actual: Actual, - _ message: @autoclosure () -> String = "", - file: String = #file, line: UInt = #line, - sameValue: (Expected.Element, Expected.Element) -> Bool -) where Expected.Element == Actual.Element { - if !expected.elementsEqual(actual, by: sameValue) { - XCTFail("expected elements: \"\(expected)\"\n" - + "actual: \"\(actual)\" (of type \(String(reflecting: type(of: actual)))), \(message())") - } -} - -public func expectEqualSequence< Expected: Sequence, Actual: Sequence>( - _ expected: Expected, _ actual: Actual, - _ message: @autoclosure () -> String = "", - file: String = #file, line: UInt = #line -) where Expected.Element == Actual.Element, Expected.Element: Equatable { - expectEqualSequence(expected, actual, message()) { - $0 == $1 - } -} - -func expectEqual(_ actual: Date, _ expected: Date , within: Double = 0.001, file: StaticString = #filePath, line: UInt = #line) { - let debugDescription = "\nactual: \(actual.formatted(.iso8601));\nexpected: \(expected.formatted(.iso8601))" - XCTAssertEqual(actual.timeIntervalSinceReferenceDate, expected.timeIntervalSinceReferenceDate, accuracy: within, debugDescription, file: file, line: line) -} - -// Compare two date components like the original equality, but compares nanosecond within a reasonable epsilon, and optionally ignores quarter and calendar equality since they were often not supported in the original implementation -public func expectEqual(_ first: DateComponents, _ second: DateComponents, within nanosecondAccuracy: Int = 5000, expectQuarter: Bool = true, expectCalendar: Bool = true, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(first.era, second.era, message(), file: file, line: line) - XCTAssertEqual(first.year, second.year, message(), file: file, line: line) - XCTAssertEqual(first.month, second.month, message(), file: file, line: line) - XCTAssertEqual(first.day, second.day, message(), file: file, line: line) - XCTAssertEqual(first.dayOfYear, second.dayOfYear, message(), file: file, line: line) - XCTAssertEqual(first.hour, second.hour, message(), file: file, line: line) - XCTAssertEqual(first.minute, second.minute, message(), file: file, line: line) - XCTAssertEqual(first.second, second.second, message(), file: file, line: line) - XCTAssertEqual(first.weekday, second.weekday, message(), file: file, line: line) - XCTAssertEqual(first.weekdayOrdinal, second.weekdayOrdinal, message(), file: file, line: line) - XCTAssertEqual(first.weekOfMonth, second.weekOfMonth, message(), file: file, line: line) - XCTAssertEqual(first.weekOfYear, second.weekOfYear, message(), file: file, line: line) - XCTAssertEqual(first.yearForWeekOfYear, second.yearForWeekOfYear, message(), file: file, line: line) - if expectQuarter { - XCTAssertEqual(first.quarter, second.quarter, message(), file: file, line: line) - } - - if let ns = first.nanosecond, let otherNS = second.nanosecond { - XCTAssertLessThanOrEqual(abs(ns - otherNS), nanosecondAccuracy, message(), file: file, line: line) - } else { - XCTAssertEqual(first.nanosecond, second.nanosecond, message(), file: file, line: line) - } - - XCTAssertEqual(first.isLeapMonth, second.isLeapMonth, message(), file: file, line: line) - - if expectCalendar { - XCTAssertEqual(first.calendar, second.calendar, message(), file: file, line: line) - } - - XCTAssertEqual(first.timeZone, second.timeZone, message(), file: file, line: line) - -} - -func expectChanges(_ check: @autoclosure () -> T, by difference: T? = nil, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line, _ expression: () throws -> ()) rethrows { - let valueBefore = check() - try expression() - let valueAfter = check() - if let difference = difference { - XCTAssertEqual(valueAfter, valueBefore + difference, message(), file: file, line: line) - } else { - XCTAssertNotEqual(valueAfter, valueBefore, message(), file: file, line: line) - } -} - -func expectNoChanges(_ check: @autoclosure () -> T, by difference: T? = nil, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line, _ expression: () throws -> ()) rethrows { - let valueBefore = check() - try expression() - let valueAfter = check() - if let difference = difference { - XCTAssertNotEqual(valueAfter, valueBefore + difference, message(), file: file, line: line) - } else { - XCTAssertEqual(valueAfter, valueBefore, message(), file: file, line: line) - } -} - -/// Test that the elements of `instances` satisfy the semantic -/// requirements of `Equatable`, using `oracle` to generate equality -/// expectations from pairs of positions in `instances`. -/// -/// - Note: `oracle` is also checked for conformance to the -/// laws. -public func XCTCheckEquatable( - _ instances: Instances, - oracle: (Instances.Index, Instances.Index) -> Bool, - allowBrokenTransitivity: Bool = false, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line -) where Instances.Element: Equatable { - let indices = Array(instances.indices) - _XCTCheckEquatableImpl( - Array(instances), - oracle: { oracle(indices[$0], indices[$1]) }, - allowBrokenTransitivity: allowBrokenTransitivity, - message(), - file: file, - line: line) -} - -internal func _XCTCheckEquatableImpl( - _ instances: [Instance], - oracle: (Int, Int) -> Bool, - allowBrokenTransitivity: Bool = false, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line -) { - // For each index (which corresponds to an instance being tested) track the - // set of equal instances. - var transitivityScoreboard: [Box>] = - instances.indices.map { _ in Box([]) } - - for i in instances.indices { - let x = instances[i] - expectTrue(oracle(i, i), "bad oracle: broken reflexivity at index \(i)") - - for j in instances.indices { - let y = instances[j] - - let predictedXY = oracle(i, j) - expectEqual( - predictedXY, oracle(j, i), - "bad oracle: broken symmetry between indices \(i), \(j)", - file: file, - line: line) - - let isEqualXY = x == y - expectEqual( - predictedXY, isEqualXY, - """ - \((predictedXY - ? "expected equal, found not equal" - : "expected not equal, found equal")) - lhs (at index \(i)): \(String(reflecting: x)) - rhs (at index \(j)): \(String(reflecting: y)) - """, - file: file, - line: line) - - // Not-equal is an inverse of equal. - expectNotEqual( - isEqualXY, x != y, - """ - lhs (at index \(i)): \(String(reflecting: x)) - rhs (at index \(j)): \(String(reflecting: y)) - """, - file: file, - line: line) - - if !allowBrokenTransitivity { - // Check transitivity of the predicate represented by the oracle. - // If we are adding the instance `j` into an equivalence set, check that - // it is equal to every other instance in the set. - if predictedXY && i < j && transitivityScoreboard[i].value.insert(j).inserted { - if transitivityScoreboard[i].value.count == 1 { - transitivityScoreboard[i].value.insert(i) - } - for k in transitivityScoreboard[i].value { - expectTrue( - oracle(j, k), - "bad oracle: broken transitivity at indices \(i), \(j), \(k)", - file: file, - line: line) - // No need to check equality between actual values, we will check - // them with the checks above. - } - precondition(transitivityScoreboard[j].value.isEmpty) - transitivityScoreboard[j] = transitivityScoreboard[i] - } - } - } - } -} - -public func XCTCheckHashable( - _ instances: Instances, - equalityOracle: (Instances.Index, Instances.Index) -> Bool, - allowIncompleteHashing: Bool = false, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) where Instances.Element: Hashable { - XCTCheckHashable( - instances, - equalityOracle: equalityOracle, - hashEqualityOracle: equalityOracle, - allowIncompleteHashing: allowIncompleteHashing, - message(), - file: file, - line: line) -} - - -public func XCTCheckHashable( - _ instances: Instances, - equalityOracle: (Instances.Index, Instances.Index) -> Bool, - hashEqualityOracle: (Instances.Index, Instances.Index) -> Bool, - allowIncompleteHashing: Bool = false, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, line: UInt = #line -) where Instances.Element: Hashable { - - XCTCheckEquatable( - instances, - oracle: equalityOracle, - message(), - file: file, - line: line) - - for i in instances.indices { - let x = instances[i] - for j in instances.indices { - let y = instances[j] - let predicted = hashEqualityOracle(i, j) - XCTAssertEqual( - predicted, - hashEqualityOracle(j, i), - "bad hash oracle: broken symmetry between indices \(i), \(j)", - file: file, line: line) - if x == y { - XCTAssertTrue( - predicted, - """ - bad hash oracle: equality must imply hash equality - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - file: file, line: line) - } - if predicted { - XCTAssertEqual( - hash(x), hash(y), - """ - hash(into:) expected to match, found to differ - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - file: file, line: line) - XCTAssertEqual( - x.hashValue, y.hashValue, - """ - hashValue expected to match, found to differ - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - file: file, line: line) - XCTAssertEqual( - x._rawHashValue(seed: 0), y._rawHashValue(seed: 0), - """ - _rawHashValue(seed:) expected to match, found to differ - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - file: file, line: line) - } else if !allowIncompleteHashing { - // Try a few different seeds; at least one of them should discriminate - // between the hashes. It is extremely unlikely this check will fail - // all ten attempts, unless the type's hash encoding is not unique, - // or unless the hash equality oracle is wrong. - XCTAssertTrue( - (0..<10).contains { hash(x, salt: $0) != hash(y, salt: $0) }, - """ - hash(into:) expected to differ, found to match - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - file: file, line: line) - XCTAssertTrue( - (0..<10).contains { i in - x._rawHashValue(seed: i) != y._rawHashValue(seed: i) - }, - """ - _rawHashValue(seed:) expected to differ, found to match - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - file: file, line: line) - } - } - } -} - -/// Test that the elements of `groups` consist of instances that satisfy the -/// semantic requirements of `Hashable`, with each group defining a distinct -/// equivalence class under `==`. -public func XCTCheckHashableGroups( - _ groups: Groups, - _ message: @autoclosure () -> String = "", - allowIncompleteHashing: Bool = false, - file: StaticString = #filePath, - line: UInt = #line -) where Groups.Element: Collection, Groups.Element.Element: Hashable { - let instances = groups.flatMap { $0 } - // groupIndices[i] is the index of the element in groups that contains - // instances[i]. - let groupIndices = - zip(0..., groups).flatMap { i, group in group.map { _ in i } } - func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { - return groupIndices[lhs] == groupIndices[rhs] - } - XCTCheckHashable( - instances, - equalityOracle: equalityOracle, - hashEqualityOracle: equalityOracle, - allowIncompleteHashing: allowIncompleteHashing, - file: file, - line: line) -} - -private var shouldRunXFailTests: Bool { - // FIXME: Reenable after ProcessInfo is migrated -// return ProcessInfo.processInfo.environment["NS_FOUNDATION_ATTEMPT_XFAIL_TESTS"] == "YES" - return false -} - -func shouldAttemptXFailTests(_ reason: String) -> Bool { - if shouldRunXFailTests { - return true - } else { - print("warning: Skipping test expected to fail with reason '\(reason)'\n") - return false - } -} - -func shouldAttemptWindowsXFailTests(_ reason: String) -> Bool { - #if os(Windows) - return shouldAttemptXFailTests(reason) - #else - return true - #endif -} - -func shouldAttemptAndroidXFailTests(_ reason: String) -> Bool { - #if os(Android) - return shouldAttemptXFailTests(reason) - #else - return true - #endif -} - -func shouldAttemptOpenBSDXFailTests(_ reason: String) -> Bool { - #if os(OpenBSD) - return shouldAttemptXFailTests(reason) - #else - return true - #endif -} - -func testExpectedToFail(_ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void { - testExpectedToFailWithCheck(check: shouldAttemptXFailTests(_:), test, reason) -} - -func testExpectedToFailOnWindows(_ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void { - testExpectedToFailWithCheck(check: shouldAttemptWindowsXFailTests(_:), test, reason) -} - -func testExpectedToFailOnAndroid(_ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void { - testExpectedToFailWithCheck(check: shouldAttemptAndroidXFailTests(_:), test, reason) -} - -func testExpectedToFailOnOpenBSD(_ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void { - testExpectedToFailWithCheck(check: shouldAttemptOpenBSDXFailTests(_:), test, reason) -} - -func testExpectedToFailWithCheck(check: (String) -> Bool, _ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void { - if check(reason) { - return test - } else { - return { _ in return { } } - } -} - -// MARK: - swift-testing Helpers - -import Testing - -/// Test that the elements of `instances` satisfy the semantic -/// requirements of `Equatable`, using `oracle` to generate equality -/// expectations from pairs of positions in `instances`. -/// -/// - Note: `oracle` is also checked for conformance to the -/// laws. -func checkEquatable( - _ instances: Instances, - oracle: (Instances.Index, Instances.Index) -> Bool, - allowBrokenTransitivity: Bool = false, - _ message: @autoclosure () -> String = "", - sourceLocation: SourceLocation = #_sourceLocation -) where Instances.Element: Equatable { - let indices = Array(instances.indices) - _checkEquatable( - instances, - oracle: { oracle(indices[$0], indices[$1]) }, - allowBrokenTransitivity: allowBrokenTransitivity, - message(), - sourceLocation: sourceLocation - ) -} - -func _checkEquatable( - _ _instances: Instances, - oracle: (Int, Int) -> Bool, - allowBrokenTransitivity: Bool = false, - _ message: @autoclosure () -> String = "", - sourceLocation: SourceLocation = #_sourceLocation -) where Instances.Element: Equatable { - let instances = Array(_instances) - - // For each index (which corresponds to an instance being tested) track the - // set of equal instances. - var transitivityScoreboard: [Box>] = - instances.indices.map { _ in Box([]) } - - for i in instances.indices { - let x = instances[i] - #expect(oracle(i, i), "bad oracle: broken reflexivity at index \(i)") - - for j in instances.indices { - let y = instances[j] - - let predictedXY = oracle(i, j) - #expect( - predictedXY == oracle(j, i), - "bad oracle: broken symmetry between indices \(i), \(j)", - sourceLocation: sourceLocation - ) - - let isEqualXY = x == y - #expect( - predictedXY == isEqualXY, - """ - \((predictedXY - ? "expected equal, found not equal" - : "expected not equal, found equal")) - lhs (at index \(i)): \(String(reflecting: x)) - rhs (at index \(j)): \(String(reflecting: y)) - """, - sourceLocation: sourceLocation - ) - - // Not-equal is an inverse of equal. - #expect( - isEqualXY != (x != y), - """ - lhs (at index \(i)): \(String(reflecting: x)) - rhs (at index \(j)): \(String(reflecting: y)) - """, - sourceLocation: sourceLocation - ) - - if !allowBrokenTransitivity { - // Check transitivity of the predicate represented by the oracle. - // If we are adding the instance `j` into an equivalence set, check that - // it is equal to every other instance in the set. - if predictedXY && i < j && transitivityScoreboard[i].value.insert(j).inserted { - if transitivityScoreboard[i].value.count == 1 { - transitivityScoreboard[i].value.insert(i) - } - for k in transitivityScoreboard[i].value { - #expect( - oracle(j, k), - "bad oracle: broken transitivity at indices \(i), \(j), \(k)", - sourceLocation: sourceLocation - ) - // No need to check equality between actual values, we will check - // them with the checks above. - } - precondition(transitivityScoreboard[j].value.isEmpty) - transitivityScoreboard[j] = transitivityScoreboard[i] - } - } - } - } -} - -public func checkHashable( - _ instances: Instances, - equalityOracle: (Instances.Index, Instances.Index) -> Bool, - allowIncompleteHashing: Bool = false, - _ message: @autoclosure () -> String = "", - sourceLocation: SourceLocation = #_sourceLocation -) where Instances.Element: Hashable { - checkHashable( - instances, - equalityOracle: equalityOracle, - hashEqualityOracle: equalityOracle, - allowIncompleteHashing: allowIncompleteHashing, - message(), - sourceLocation: sourceLocation) -} - -func checkHashable( - _ instances: Instances, - equalityOracle: (Instances.Index, Instances.Index) -> Bool, - hashEqualityOracle: (Instances.Index, Instances.Index) -> Bool, - allowIncompleteHashing: Bool = false, - _ message: @autoclosure () -> String = "", - sourceLocation: SourceLocation = #_sourceLocation -) where Instances.Element: Hashable { - checkEquatable( - instances, - oracle: equalityOracle, - message(), - sourceLocation: sourceLocation - ) - - for i in instances.indices { - let x = instances[i] - for j in instances.indices { - let y = instances[j] - let predicted = hashEqualityOracle(i, j) - #expect( - predicted == hashEqualityOracle(j, i), - "bad hash oracle: broken symmetry between indices \(i), \(j)", - sourceLocation: sourceLocation - ) - if x == y { - #expect( - predicted, - """ - bad hash oracle: equality must imply hash equality - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - sourceLocation: sourceLocation - ) - } - if predicted { - #expect( - hash(x) == hash(y), - """ - hash(into:) expected to match, found to differ - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - sourceLocation: sourceLocation - ) - #expect( - x.hashValue == y.hashValue, - """ - hashValue expected to match, found to differ - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - sourceLocation: sourceLocation - ) - #expect( - x._rawHashValue(seed: 0) == y._rawHashValue(seed: 0), - """ - _rawHashValue(seed:) expected to match, found to differ - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - sourceLocation: sourceLocation - ) - } else if !allowIncompleteHashing { - // Try a few different seeds; at least one of them should discriminate - // between the hashes. It is extremely unlikely this check will fail - // all ten attempts, unless the type's hash encoding is not unique, - // or unless the hash equality oracle is wrong. - #expect( - (0..<10).contains { hash(x, salt: $0) != hash(y, salt: $0) }, - """ - hash(into:) expected to differ, found to match - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - sourceLocation: sourceLocation - ) - #expect( - (0..<10).contains { i in - x._rawHashValue(seed: i) != y._rawHashValue(seed: i) - }, - """ - _rawHashValue(seed:) expected to differ, found to match - lhs (at index \(i)): \(x) - rhs (at index \(j)): \(y) - """, - sourceLocation: sourceLocation - ) - } - } - } -} - -/// Test that the elements of `groups` consist of instances that satisfy the -/// semantic requirements of `Hashable`, with each group defining a distinct -/// equivalence class under `==`. -public func checkHashableGroups( - _ groups: Groups, - _ message: @autoclosure () -> String = "", - allowIncompleteHashing: Bool = false, - sourceLocation: SourceLocation = #_sourceLocation -) where Groups.Element: Collection, Groups.Element.Element: Hashable { - let instances = groups.flatMap { $0 } - // groupIndices[i] is the index of the element in groups that contains - // instances[i]. - let groupIndices = - zip(0..., groups).flatMap { i, group in group.map { _ in i } } - func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { - return groupIndices[lhs] == groupIndices[rhs] - } - checkHashable( - instances, - equalityOracle: equalityOracle, - hashEqualityOracle: equalityOracle, - allowIncompleteHashing: allowIncompleteHashing, - sourceLocation: sourceLocation) -} - -// MARK: - Private Types - -private class Box { - var value: T - - init(_ value: T) { - self.value = value - } -} - -private func hash(_ value: H, salt: Int? = nil) -> Int { - var hasher = Hasher() - if let salt = salt { - hasher.combine(salt) - } - hasher.combine(value) - return hasher.finalize() -} diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTestSupport.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTestSupport.swift index df8f443d0..3935af2e5 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTestSupport.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTestSupport.swift @@ -10,8 +10,10 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation #endif #if FOUNDATION_FRAMEWORK diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift index 1c107c38e..44893c17e 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift @@ -12,10 +12,6 @@ import Testing -#if canImport(TestSupport) -import TestSupport -#endif - #if canImport(FoundationEssentials) @testable import FoundationEssentials #else @@ -26,6 +22,21 @@ import TestSupport @preconcurrency import Android #endif +#if canImport(Darwin) +import Darwin +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif os(Windows) +import CRT +import WinSDK +#elseif os(WASI) +import WASILibc +#endif + extension FileManager { fileprivate var delegateCaptures: DelegateCaptures { (self.delegate as! CapturingFileManagerDelegate).captures @@ -1058,7 +1069,7 @@ private struct FileManagerTests { do { let attrs = try $0.attributesOfItem(atPath: "fileWithContents") - XCTAssertGreaterThan(try #require(attrs[.size] as? UInt), 0) + #expect(try #require(attrs[.size] as? UInt) > 0) #expect(attrs[.type] as? FileAttributeType == FileAttributeType.typeRegular) } diff --git a/Tests/FoundationEssentialsTests/FoundationEssentialsTests.swift b/Tests/FoundationEssentialsTests/FoundationEssentialsTests.swift new file mode 100644 index 000000000..e81c32c75 --- /dev/null +++ b/Tests/FoundationEssentialsTests/FoundationEssentialsTests.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +import Testing + +@Suite("FoundationEssentials") +private struct FoundationEssentialsTests { + @Test func essentialsDoesNotImportInternationalization() { + // Ensures that targets that only import FoundationEssentials do not end up calling functionality in FoundationInternationalization + // We use a non-GMT TimeZone as proxy for whether FoundationInternationalization is loaded at runtime + #expect(TimeZone(identifier: "America/Los_Angeles") == nil) + } +} +#endif diff --git a/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift b/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift index 3e30b89f1..9e3d3c05f 100644 --- a/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift +++ b/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift @@ -878,130 +878,6 @@ private struct GregorianCalendarTests { test(.yearForWeekOfYear, date, expectedStart: nil, end: nil) } - @Test func testDateInterval_DST() { - let calendar = _CalendarGregorian(identifier: .gregorian, timeZone: TimeZone(identifier: "America/Los_Angeles")!, locale: nil, firstWeekday: 3, minimumDaysInFirstWeek: 5, gregorianStartDate: nil) - func test(_ c: Calendar.Component, _ date: Date, expectedStart start: Date, end: Date, sourceLocation: SourceLocation = #_sourceLocation) { - let new = calendar.dateInterval(of: c, for: date)! - let new_start = new.start - let new_end = new.end - let delta = 0.005 - #expect(abs(new_start.timeIntervalSinceReferenceDate - start.timeIntervalSinceReferenceDate) <= delta, sourceLocation: sourceLocation) - #expect(abs(new_end.timeIntervalSinceReferenceDate - end.timeIntervalSinceReferenceDate) <= delta, sourceLocation: sourceLocation) - } - var date: Date - date = Date(timeIntervalSince1970: 828867787.0) // 1996-04-07T01:03:07-0800 (1996-04-07T09:03:07Z) - test(.era, date, expectedStart: Date(timeIntervalSince1970: -62135596800.0), end: Date(timeIntervalSince1970: 4335910914304.0)) - test(.year, date, expectedStart: Date(timeIntervalSince1970: 820483200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.month, date, expectedStart: Date(timeIntervalSince1970: 828345600.0), end: Date(timeIntervalSince1970: 830934000.0)) - test(.day, date, expectedStart: Date(timeIntervalSince1970: 828864000.0), end: Date(timeIntervalSince1970: 828946800.0)) - test(.hour, date, expectedStart: Date(timeIntervalSince1970: 828867600.0), end: Date(timeIntervalSince1970: 828871200.0)) - test(.minute, date, expectedStart: Date(timeIntervalSince1970: 828867780.0), end: Date(timeIntervalSince1970: 828867840.0)) - test(.second, date, expectedStart: Date(timeIntervalSince1970: 828867787.0), end: Date(timeIntervalSince1970: 828867788.0)) - test(.nanosecond, date, expectedStart: Date(timeIntervalSince1970: 828867787.0), end: Date(timeIntervalSince1970: 828867787.0)) - test(.weekday, date, expectedStart: Date(timeIntervalSince1970: 828864000.0), end: Date(timeIntervalSince1970: 828946800.0)) - test(.weekdayOrdinal, date, expectedStart: Date(timeIntervalSince1970: 828864000.0), end: Date(timeIntervalSince1970: 828946800.0)) - test(.quarter, date, expectedStart: Date(timeIntervalSince1970: 828345600.0), end: Date(timeIntervalSince1970: 836204400.0)) - test(.weekOfMonth, date, expectedStart: Date(timeIntervalSince1970: 828432000.0), end: Date(timeIntervalSince1970: 829033200.0)) - test(.weekOfYear, date, expectedStart: Date(timeIntervalSince1970: 828432000.0), end: Date(timeIntervalSince1970: 829033200.0)) - test(.yearForWeekOfYear, date, expectedStart: Date(timeIntervalSince1970: 820569600.0), end: Date(timeIntervalSince1970: 852019200.0)) - - date = Date(timeIntervalSince1970: 828871387.0) // 1996-04-07T03:03:07-0700 (1996-04-07T10:03:07Z) - test(.era, date, expectedStart: Date(timeIntervalSince1970: -62135596800.0), end: Date(timeIntervalSince1970: 4335910914304.0)) - test(.year, date, expectedStart: Date(timeIntervalSince1970: 820483200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.month, date, expectedStart: Date(timeIntervalSince1970: 828345600.0), end: Date(timeIntervalSince1970: 830934000.0)) - test(.day, date, expectedStart: Date(timeIntervalSince1970: 828864000.0), end: Date(timeIntervalSince1970: 828946800.0)) - test(.hour, date, expectedStart: Date(timeIntervalSince1970: 828871200.0), end: Date(timeIntervalSince1970: 828874800.0)) - test(.minute, date, expectedStart: Date(timeIntervalSince1970: 828871380.0), end: Date(timeIntervalSince1970: 828871440.0)) - test(.second, date, expectedStart: Date(timeIntervalSince1970: 828871387.0), end: Date(timeIntervalSince1970: 828871388.0)) - test(.nanosecond, date, expectedStart: Date(timeIntervalSince1970: 828871387.0), end: Date(timeIntervalSince1970: 828871387.0)) - test(.weekday, date, expectedStart: Date(timeIntervalSince1970: 828864000.0), end: Date(timeIntervalSince1970: 828946800.0)) - test(.weekdayOrdinal, date, expectedStart: Date(timeIntervalSince1970: 828864000.0), end: Date(timeIntervalSince1970: 828946800.0)) - test(.quarter, date, expectedStart: Date(timeIntervalSince1970: 828345600.0), end: Date(timeIntervalSince1970: 836204400.0)) - test(.weekOfMonth, date, expectedStart: Date(timeIntervalSince1970: 828432000.0), end: Date(timeIntervalSince1970: 829033200.0)) - test(.weekOfYear, date, expectedStart: Date(timeIntervalSince1970: 828432000.0), end: Date(timeIntervalSince1970: 829033200.0)) - test(.yearForWeekOfYear, date, expectedStart: Date(timeIntervalSince1970: 820569600.0), end: Date(timeIntervalSince1970: 852019200.0)) - - date = Date(timeIntervalSince1970: 828874987.0) // 1996-04-07T04:03:07-0700 (1996-04-07T11:03:07Z) - test(.era, date, expectedStart: Date(timeIntervalSince1970: -62135596800.0), end: Date(timeIntervalSince1970: 4335910914304.0)) - test(.year, date, expectedStart: Date(timeIntervalSince1970: 820483200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.month, date, expectedStart: Date(timeIntervalSince1970: 828345600.0), end: Date(timeIntervalSince1970: 830934000.0)) - test(.day, date, expectedStart: Date(timeIntervalSince1970: 828864000.0), end: Date(timeIntervalSince1970: 828946800.0)) - test(.hour, date, expectedStart: Date(timeIntervalSince1970: 828874800.0), end: Date(timeIntervalSince1970: 828878400.0)) - test(.minute, date, expectedStart: Date(timeIntervalSince1970: 828874980.0), end: Date(timeIntervalSince1970: 828875040.0)) - test(.second, date, expectedStart: Date(timeIntervalSince1970: 828874987.0), end: Date(timeIntervalSince1970: 828874988.0)) - test(.nanosecond, date, expectedStart: Date(timeIntervalSince1970: 828874987.0), end: Date(timeIntervalSince1970: 828874987.0)) - test(.weekday, date, expectedStart: Date(timeIntervalSince1970: 828864000.0), end: Date(timeIntervalSince1970: 828946800.0)) - test(.weekdayOrdinal, date, expectedStart: Date(timeIntervalSince1970: 828864000.0), end: Date(timeIntervalSince1970: 828946800.0)) - test(.quarter, date, expectedStart: Date(timeIntervalSince1970: 828345600.0), end: Date(timeIntervalSince1970: 836204400.0)) - test(.weekOfMonth, date, expectedStart: Date(timeIntervalSince1970: 828432000.0), end: Date(timeIntervalSince1970: 829033200.0)) - test(.weekOfYear, date, expectedStart: Date(timeIntervalSince1970: 828432000.0), end: Date(timeIntervalSince1970: 829033200.0)) - test(.yearForWeekOfYear, date, expectedStart: Date(timeIntervalSince1970: 820569600.0), end: Date(timeIntervalSince1970: 852019200.0)) - - date = Date(timeIntervalSince1970: 846406987.0) // 1996-10-27T01:03:07-0800 (1996-10-27T09:03:07Z) - test(.era, date, expectedStart: Date(timeIntervalSince1970: -62135596800.0), end: Date(timeIntervalSince1970: 4335910914304.0)) - test(.year, date, expectedStart: Date(timeIntervalSince1970: 820483200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.month, date, expectedStart: Date(timeIntervalSince1970: 844153200.0), end: Date(timeIntervalSince1970: 846835200.0)) - test(.day, date, expectedStart: Date(timeIntervalSince1970: 846399600.0), end: Date(timeIntervalSince1970: 846489600.0)) - test(.hour, date, expectedStart: Date(timeIntervalSince1970: 846406800.0), end: Date(timeIntervalSince1970: 846410400.0)) - test(.minute, date, expectedStart: Date(timeIntervalSince1970: 846406980.0), end: Date(timeIntervalSince1970: 846407040.0)) - test(.second, date, expectedStart: Date(timeIntervalSince1970: 846406987.0), end: Date(timeIntervalSince1970: 846406988.0)) - test(.nanosecond, date, expectedStart: Date(timeIntervalSince1970: 846406987.0), end: Date(timeIntervalSince1970: 846406987.0)) - test(.weekday, date, expectedStart: Date(timeIntervalSince1970: 846399600.0), end: Date(timeIntervalSince1970: 846489600.0)) - test(.weekdayOrdinal, date, expectedStart: Date(timeIntervalSince1970: 846399600.0), end: Date(timeIntervalSince1970: 846489600.0)) - test(.quarter, date, expectedStart: Date(timeIntervalSince1970: 844153200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.weekOfMonth, date, expectedStart: Date(timeIntervalSince1970: 845967600.0), end: Date(timeIntervalSince1970: 846576000.0)) - test(.weekOfYear, date, expectedStart: Date(timeIntervalSince1970: 845967600.0), end: Date(timeIntervalSince1970: 846576000.0)) - test(.yearForWeekOfYear, date, expectedStart: Date(timeIntervalSince1970: 820569600.0), end: Date(timeIntervalSince1970: 852019200.0)) - - date = Date(timeIntervalSince1970: 846410587.0) // 1996-10-27T02:03:07-0800 (1996-10-27T10:03:07Z) - test(.era, date, expectedStart: Date(timeIntervalSince1970: -62135596800.0), end: Date(timeIntervalSince1970: 4335910914304.0)) - test(.year, date, expectedStart: Date(timeIntervalSince1970: 820483200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.month, date, expectedStart: Date(timeIntervalSince1970: 844153200.0), end: Date(timeIntervalSince1970: 846835200.0)) - test(.day, date, expectedStart: Date(timeIntervalSince1970: 846399600.0), end: Date(timeIntervalSince1970: 846489600.0)) - test(.hour, date, expectedStart: Date(timeIntervalSince1970: 846410400.0), end: Date(timeIntervalSince1970: 846414000.0)) - test(.minute, date, expectedStart: Date(timeIntervalSince1970: 846410580.0), end: Date(timeIntervalSince1970: 846410640.0)) - test(.second, date, expectedStart: Date(timeIntervalSince1970: 846410587.0), end: Date(timeIntervalSince1970: 846410588.0)) - test(.nanosecond, date, expectedStart: Date(timeIntervalSince1970: 846410587.0), end: Date(timeIntervalSince1970: 846410587.0)) - test(.weekday, date, expectedStart: Date(timeIntervalSince1970: 846399600.0), end: Date(timeIntervalSince1970: 846489600.0)) - test(.weekdayOrdinal, date, expectedStart: Date(timeIntervalSince1970: 846399600.0), end: Date(timeIntervalSince1970: 846489600.0)) - test(.quarter, date, expectedStart: Date(timeIntervalSince1970: 844153200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.weekOfMonth, date, expectedStart: Date(timeIntervalSince1970: 845967600.0), end: Date(timeIntervalSince1970: 846576000.0)) - test(.weekOfYear, date, expectedStart: Date(timeIntervalSince1970: 845967600.0), end: Date(timeIntervalSince1970: 846576000.0)) - test(.yearForWeekOfYear, date, expectedStart: Date(timeIntervalSince1970: 820569600.0), end: Date(timeIntervalSince1970: 852019200.0)) - - date = Date(timeIntervalSince1970: 846414187.0) // 1996-10-27T03:03:07-0800 (1996-10-27T11:03:07Z) - test(.era, date, expectedStart: Date(timeIntervalSince1970: -62135596800.0), end: Date(timeIntervalSince1970: 4335910914304.0)) - test(.year, date, expectedStart: Date(timeIntervalSince1970: 820483200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.month, date, expectedStart: Date(timeIntervalSince1970: 844153200.0), end: Date(timeIntervalSince1970: 846835200.0)) - test(.day, date, expectedStart: Date(timeIntervalSince1970: 846399600.0), end: Date(timeIntervalSince1970: 846489600.0)) - test(.hour, date, expectedStart: Date(timeIntervalSince1970: 846414000.0), end: Date(timeIntervalSince1970: 846417600.0)) - test(.minute, date, expectedStart: Date(timeIntervalSince1970: 846414180.0), end: Date(timeIntervalSince1970: 846414240.0)) - test(.second, date, expectedStart: Date(timeIntervalSince1970: 846414187.0), end: Date(timeIntervalSince1970: 846414188.0)) - test(.nanosecond, date, expectedStart: Date(timeIntervalSince1970: 846414187.0), end: Date(timeIntervalSince1970: 846414187.0)) - test(.weekday, date, expectedStart: Date(timeIntervalSince1970: 846399600.0), end: Date(timeIntervalSince1970: 846489600.0)) - test(.weekdayOrdinal, date, expectedStart: Date(timeIntervalSince1970: 846399600.0), end: Date(timeIntervalSince1970: 846489600.0)) - test(.quarter, date, expectedStart: Date(timeIntervalSince1970: 844153200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.weekOfMonth, date, expectedStart: Date(timeIntervalSince1970: 845967600.0), end: Date(timeIntervalSince1970: 846576000.0)) - test(.weekOfYear, date, expectedStart: Date(timeIntervalSince1970: 845967600.0), end: Date(timeIntervalSince1970: 846576000.0)) - test(.yearForWeekOfYear, date, expectedStart: Date(timeIntervalSince1970: 820569600.0), end: Date(timeIntervalSince1970: 852019200.0)) - - date = Date(timeIntervalSince1970: 845121787.0) // 1996-10-12T05:03:07-0700 (1996-10-12T12:03:07Z) - test(.era, date, expectedStart: Date(timeIntervalSince1970: -62135596800.0), end: Date(timeIntervalSince1970: 4335910914304.0)) - test(.year, date, expectedStart: Date(timeIntervalSince1970: 820483200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.month, date, expectedStart: Date(timeIntervalSince1970: 844153200.0), end: Date(timeIntervalSince1970: 846835200.0)) - test(.day, date, expectedStart: Date(timeIntervalSince1970: 845103600.0), end: Date(timeIntervalSince1970: 845190000.0)) - test(.hour, date, expectedStart: Date(timeIntervalSince1970: 845121600.0), end: Date(timeIntervalSince1970: 845125200.0)) - test(.minute, date, expectedStart: Date(timeIntervalSince1970: 845121780.0), end: Date(timeIntervalSince1970: 845121840.0)) - test(.second, date, expectedStart: Date(timeIntervalSince1970: 845121787.0), end: Date(timeIntervalSince1970: 845121788.0)) - test(.nanosecond, date, expectedStart: Date(timeIntervalSince1970: 845121787.0), end: Date(timeIntervalSince1970: 845121787.0)) - test(.weekday, date, expectedStart: Date(timeIntervalSince1970: 845103600.0), end: Date(timeIntervalSince1970: 845190000.0)) - test(.weekdayOrdinal, date, expectedStart: Date(timeIntervalSince1970: 845103600.0), end: Date(timeIntervalSince1970: 845190000.0)) - test(.quarter, date, expectedStart: Date(timeIntervalSince1970: 844153200.0), end: Date(timeIntervalSince1970: 852105600.0)) - test(.weekOfMonth, date, expectedStart: Date(timeIntervalSince1970: 844758000.0), end: Date(timeIntervalSince1970: 845362800.0)) - test(.weekOfYear, date, expectedStart: Date(timeIntervalSince1970: 844758000.0), end: Date(timeIntervalSince1970: 845362800.0)) - test(.yearForWeekOfYear, date, expectedStart: Date(timeIntervalSince1970: 820569600.0), end: Date(timeIntervalSince1970: 852019200.0)) - } - // MARK: - Day Of Year @Test func test_dayOfYear() throws { // An arbitrary date, for which we know the answers @@ -1596,7 +1472,7 @@ private struct GregorianCalendarTests { } @Test func testDifference_DST() { - let calendar = _CalendarGregorian(identifier: .gregorian, timeZone: TimeZone(identifier: "America/Los_Angeles")!, locale: nil, firstWeekday: 1, minimumDaysInFirstWeek: 4, gregorianStartDate: nil) + let calendar = _CalendarGregorian(identifier: .gregorian, timeZone: .gmt, locale: nil, firstWeekday: 1, minimumDaysInFirstWeek: 4, gregorianStartDate: nil) var start: Date! var end: Date! diff --git a/Tests/FoundationEssentialsTests/ProcessInfoTests.swift b/Tests/FoundationEssentialsTests/ProcessInfoTests.swift index 3661bb5d2..2dfe31ce0 100644 --- a/Tests/FoundationEssentialsTests/ProcessInfoTests.swift +++ b/Tests/FoundationEssentialsTests/ProcessInfoTests.swift @@ -201,8 +201,8 @@ extension ProcessInfoTests { $0.thermalState = .critical $0.powerState = .restricted } - XCTAssertEqual(ProcessInfo.processInfo.thermalState, .critical) - XCTAssertEqual(ProcessInfo.processInfo.isLowPowerModeEnabled, true) + #expect(ProcessInfo.processInfo.thermalState == .critical) + #expect(ProcessInfo.processInfo.isLowPowerModeEnabled == true) } } #endif // FOUDATION_FRAMEWORK diff --git a/Tests/FoundationEssentialsTests/ResourceUtilities.swift b/Tests/FoundationEssentialsTests/ResourceUtilities.swift index 57247e74a..6409c9163 100644 --- a/Tests/FoundationEssentialsTests/ResourceUtilities.swift +++ b/Tests/FoundationEssentialsTests/ResourceUtilities.swift @@ -11,10 +11,6 @@ //===----------------------------------------------------------------------===// // -#if canImport(TestSupport) -import TestSupport -#endif - #if canImport(Glibc) @preconcurrency import Glibc #endif @@ -25,6 +21,8 @@ import TestSupport @testable import FoundationEssentials #endif // FOUNDATION_FRAMEWORK +import class Foundation.Bundle + #if FOUNDATION_FRAMEWORK // Always compiled into the Tests project final internal class Canary { } diff --git a/Tests/FoundationEssentialsTests/StringTests.swift b/Tests/FoundationEssentialsTests/StringTests.swift index 1a06c6c43..1bb5fb510 100644 --- a/Tests/FoundationEssentialsTests/StringTests.swift +++ b/Tests/FoundationEssentialsTests/StringTests.swift @@ -953,27 +953,27 @@ private struct StringTests { // UTF16 - specific endianness let utf16BEExpected = Data([0, 104, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 216, 62, 221, 238]) - let utf16BEOutput = s.data(using: String._Encoding.utf16BigEndian) + let utf16BEOutput = s.data(using: .utf16BigEndian) #expect(utf16BEOutput == utf16BEExpected) - let utf16BEOutputSubstring = subString.data(using: String._Encoding.utf16BigEndian) + let utf16BEOutputSubstring = subString.data(using: .utf16BigEndian) #expect(utf16BEOutputSubstring == utf16BEExpected) let utf16LEExpected = Data([104, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 62, 216, 238, 221]) - let utf16LEOutput = s.data(using: String._Encoding.utf16LittleEndian) + let utf16LEOutput = s.data(using: .utf16LittleEndian) #expect(utf16LEOutput == utf16LEExpected) - let utf16LEOutputSubstring = subString.data(using: String._Encoding.utf16LittleEndian) + let utf16LEOutputSubstring = subString.data(using: .utf16LittleEndian) #expect(utf16LEOutputSubstring == utf16LEExpected) // UTF32 - specific endianness let utf32BEExpected = Data([0, 0, 0, 104, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111, 0, 0, 0, 32, 0, 1, 249, 238]) - let utf32BEOutput = s.data(using: String._Encoding.utf32BigEndian) + let utf32BEOutput = s.data(using: .utf32BigEndian) #expect(utf32BEOutput == utf32BEExpected) let utf32LEExpected = Data([104, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111, 0, 0, 0, 32, 0, 0, 0, 238, 249, 1, 0]) - let utf32LEOutput = s.data(using: String._Encoding.utf32LittleEndian) + let utf32LEOutput = s.data(using: .utf32LittleEndian) #expect(utf32LEOutput == utf32LEExpected) @@ -983,8 +983,8 @@ private struct StringTests { let utf16BEWithBOM = Data([0xFE, 0xFF]) + utf16BEExpected let utf32BEWithBOM = Data([0x00, 0x00, 0xFE, 0xFF]) + utf32BEExpected - let utf16Output = s.data(using: String._Encoding.utf16)! - let utf32Output = s.data(using: String._Encoding.utf32)! + let utf16Output = s.data(using: .utf16)! + let utf32Output = s.data(using: .utf32)! let bom = 0xFFFE @@ -1002,77 +1002,77 @@ private struct StringTests { // UTF16 - let utf16BEString = String(bytes: utf16BEExpected, encoding: String._Encoding.utf16BigEndian) + let utf16BEString = String(bytes: utf16BEExpected, encoding: .utf16BigEndian) #expect(s == utf16BEString) - let utf16LEString = String(bytes: utf16LEExpected, encoding: String._Encoding.utf16LittleEndian) + let utf16LEString = String(bytes: utf16LEExpected, encoding: .utf16LittleEndian) #expect(s == utf16LEString) - let utf16LEBOMString = String(bytes: utf16LEWithBOM, encoding: String._Encoding.utf16) + let utf16LEBOMString = String(bytes: utf16LEWithBOM, encoding: .utf16) #expect(s == utf16LEBOMString) - let utf16BEBOMString = String(bytes: utf16BEWithBOM, encoding: String._Encoding.utf16) + let utf16BEBOMString = String(bytes: utf16BEWithBOM, encoding: .utf16) #expect(s == utf16BEBOMString) // No BOM, no encoding specified. We assume the data is big endian, which leads to garbage (but not nil). - let utf16LENoBOMString = String(bytes: utf16LEExpected, encoding: String._Encoding.utf16) + let utf16LENoBOMString = String(bytes: utf16LEExpected, encoding: .utf16) #expect(utf16LENoBOMString != nil) // No BOM, no encoding specified. We assume the data is big endian, which leads to an expected value. - let utf16BENoBOMString = String(bytes: utf16BEExpected, encoding: String._Encoding.utf16) + let utf16BENoBOMString = String(bytes: utf16BEExpected, encoding: .utf16) #expect(s == utf16BENoBOMString) // UTF32 - let utf32BEString = String(bytes: utf32BEExpected, encoding: String._Encoding.utf32BigEndian) + let utf32BEString = String(bytes: utf32BEExpected, encoding: .utf32BigEndian) #expect(s == utf32BEString) - let utf32LEString = String(bytes: utf32LEExpected, encoding: String._Encoding.utf32LittleEndian) + let utf32LEString = String(bytes: utf32LEExpected, encoding: .utf32LittleEndian) #expect(s == utf32LEString) - let utf32BEBOMString = String(bytes: utf32BEWithBOM, encoding: String._Encoding.utf32) + let utf32BEBOMString = String(bytes: utf32BEWithBOM, encoding: .utf32) #expect(s == utf32BEBOMString) - let utf32LEBOMString = String(bytes: utf32LEWithBOM, encoding: String._Encoding.utf32) + let utf32LEBOMString = String(bytes: utf32LEWithBOM, encoding: .utf32) #expect(s == utf32LEBOMString) // No BOM, no encoding specified. We assume the data is big endian, which leads to a nil. - let utf32LENoBOMString = String(bytes: utf32LEExpected, encoding: String._Encoding.utf32) + let utf32LENoBOMString = String(bytes: utf32LEExpected, encoding: .utf32) #expect(utf32LENoBOMString == nil) // No BOM, no encoding specified. We assume the data is big endian, which leads to an expected value. - let utf32BENoBOMString = String(bytes: utf32BEExpected, encoding: String._Encoding.utf32) + let utf32BENoBOMString = String(bytes: utf32BEExpected, encoding: .utf32) #expect(s == utf32BENoBOMString) // Check what happens when we mismatch a string with a BOM and the encoding. The bytes are interpreted according to the specified encoding regardless of the BOM, the BOM is preserved, and the String will look garbled. However the bytes are preserved as-is. This is the expected behavior for UTF16. - let utf16LEBOMStringMismatch = String(bytes: utf16LEWithBOM, encoding: String._Encoding.utf16BigEndian) - let utf16LEBOMStringMismatchBytes = utf16LEBOMStringMismatch?.data(using: String._Encoding.utf16BigEndian) + let utf16LEBOMStringMismatch = String(bytes: utf16LEWithBOM, encoding: .utf16BigEndian) + let utf16LEBOMStringMismatchBytes = utf16LEBOMStringMismatch?.data(using: .utf16BigEndian) #expect(utf16LEWithBOM == utf16LEBOMStringMismatchBytes) - let utf16BEBOMStringMismatch = String(bytes: utf16BEWithBOM, encoding: String._Encoding.utf16LittleEndian) - let utf16BEBomStringMismatchBytes = utf16BEBOMStringMismatch?.data(using: String._Encoding.utf16LittleEndian) + let utf16BEBOMStringMismatch = String(bytes: utf16BEWithBOM, encoding: .utf16LittleEndian) + let utf16BEBomStringMismatchBytes = utf16BEBOMStringMismatch?.data(using: .utf16LittleEndian) #expect(utf16BEWithBOM == utf16BEBomStringMismatchBytes) // For a UTF32 mismatch, the string creation simply returns nil. - let utf32LEBOMStringMismatch = String(bytes: utf32LEWithBOM, encoding: String._Encoding.utf32BigEndian) + let utf32LEBOMStringMismatch = String(bytes: utf32LEWithBOM, encoding: .utf32BigEndian) #expect(utf32LEBOMStringMismatch == nil) - let utf32BEBOMStringMismatch = String(bytes: utf32BEWithBOM, encoding: String._Encoding.utf32LittleEndian) + let utf32BEBOMStringMismatch = String(bytes: utf32BEWithBOM, encoding: .utf32LittleEndian) #expect(utf32BEBOMStringMismatch == nil) // UTF-8 With BOM let utf8BOM = Data([0xEF, 0xBB, 0xBF]) let helloWorld = Data("Hello, world".utf8) - #expect(String(bytes: utf8BOM + helloWorld, encoding: String._Encoding.utf8) == "Hello, world") - #expect(String(bytes: helloWorld + utf8BOM, encoding: String._Encoding.utf8) == "Hello, world\u{FEFF}") + #expect(String(bytes: utf8BOM + helloWorld, encoding: .utf8) == "Hello, world") + #expect(String(bytes: helloWorld + utf8BOM, encoding: .utf8) == "Hello, world\u{FEFF}") } @Test func dataUsingEncoding_preservingBOM() { func roundTrip(_ data: Data) -> Bool { let str = String(data: data, encoding: .utf8)! - let strAsUTF16BE = str.data(using: String._Encoding.utf16BigEndian)! + let strAsUTF16BE = str.data(using: .utf16BigEndian)! let strRoundTripUTF16BE = String(data: strAsUTF16BE, encoding: .utf16BigEndian)! return strRoundTripUTF16BE == str } @@ -1089,8 +1089,8 @@ private struct StringTests { @Test func dataUsingEncoding_ascii() { #expect("abc".data(using: .ascii) == Data([UInt8(ascii: "a"), UInt8(ascii: "b"), UInt8(ascii: "c")])) #expect("abc".data(using: .nonLossyASCII) == Data([UInt8(ascii: "a"), UInt8(ascii: "b"), UInt8(ascii: "c")])) - #expect("e\u{301}\u{301}f".data(using: String._Encoding.ascii) == nil) - #expect("e\u{301}\u{301}f".data(using: String._Encoding.nonLossyASCII) == nil) + #expect("e\u{301}\u{301}f".data(using: .ascii) == nil) + #expect("e\u{301}\u{301}f".data(using: .nonLossyASCII) == nil) #expect("abc".data(using: .ascii, allowLossyConversion: true) == Data([UInt8(ascii: "a"), UInt8(ascii: "b"), UInt8(ascii: "c")])) #expect("abc".data(using: .nonLossyASCII, allowLossyConversion: true) == Data([UInt8(ascii: "a"), UInt8(ascii: "b"), UInt8(ascii: "c")])) @@ -1099,10 +1099,10 @@ private struct StringTests { } @Test func initWithBytes_ascii() { - #expect(String(bytes: "abc".utf8, encoding: String._Encoding.ascii) == "abc") - #expect(String(bytes: "abc".utf8, encoding: String._Encoding.nonLossyASCII) == "abc") - #expect(String(bytes: "e\u{301}\u{301}f".utf8, encoding: String._Encoding.ascii) == nil) - #expect(String(bytes: "e\u{301}\u{301}f".utf8, encoding: String._Encoding.nonLossyASCII) == nil) + #expect(String(bytes: "abc".utf8, encoding: .ascii) == "abc") + #expect(String(bytes: "abc".utf8, encoding: .nonLossyASCII) == "abc") + #expect(String(bytes: "e\u{301}\u{301}f".utf8, encoding: .ascii) == nil) + #expect(String(bytes: "e\u{301}\u{301}f".utf8, encoding: .nonLossyASCII) == nil) } @Test func compressingSlashes() { @@ -1156,18 +1156,18 @@ private struct StringTests { @Test func init_contentsOfFile_encoding() throws { try withTemporaryStringFile { existingURL, nonExistentURL in - let content = try String(contentsOfFile: existingURL.path, encoding: String._Encoding.ascii) + let content = try String(contentsOfFile: existingURL.path, encoding: .ascii) #expect(temporaryFileContents == content) #expect(throws: (any Error).self) { - _ = try String(contentsOfFile: nonExistentURL.path, encoding: String._Encoding.ascii) + _ = try String(contentsOfFile: nonExistentURL.path, encoding: .ascii) } } } @Test func init_contentsOfFile_usedEncoding() throws { try withTemporaryStringFile { existingURL, nonExistentURL in - var usedEncoding: String._Encoding = String._Encoding(rawValue: 0) + var usedEncoding = String.Encoding(rawValue: 0) let content = try String(contentsOfFile: existingURL.path(), usedEncoding: &usedEncoding) #expect(0 != usedEncoding.rawValue) #expect(temporaryFileContents == content) @@ -1178,11 +1178,11 @@ private struct StringTests { @Test func init_contentsOf_encoding() throws { try withTemporaryStringFile { existingURL, nonExistentURL in - let content = try String(contentsOf: existingURL, encoding: String._Encoding.ascii) + let content = try String(contentsOf: existingURL, encoding: .ascii) #expect(temporaryFileContents == content) #expect(throws: (any Error).self) { - _ = try String(contentsOf: nonExistentURL, encoding: String._Encoding.ascii) + _ = try String(contentsOf: nonExistentURL, encoding: .ascii) } } @@ -1190,7 +1190,7 @@ private struct StringTests { @Test func init_contentsOf_usedEncoding() throws { #if FOUNDATION_FRAMEWORK - let encs : [String._Encoding] = [ + let encs : [String.Encoding] = [ .ascii, .nextstep, .japaneseEUC, @@ -1215,7 +1215,7 @@ private struct StringTests { .utf32LittleEndian ] #else - var encs : [String._Encoding] = [ + var encs : [String.Encoding] = [ .utf8, .utf16, .utf32, @@ -1238,7 +1238,7 @@ private struct StringTests { for encoding in encs { try withTemporaryStringFile(encoding: encoding) { existingURL, _ in - var usedEncoding = String._Encoding(rawValue: 0) + var usedEncoding = String.Encoding(rawValue: 0) let content = try String(contentsOf: existingURL, usedEncoding: &usedEncoding) #expect(encoding == usedEncoding) @@ -1248,7 +1248,7 @@ private struct StringTests { // Test non-existent file try withTemporaryStringFile { _, nonExistentURL in - var usedEncoding: String._Encoding = String._Encoding(rawValue: 0) + var usedEncoding = String.Encoding(rawValue: 0) #expect(throws: (any Error).self) { _ = try String(contentsOf: nonExistentURL, usedEncoding: &usedEncoding) } @@ -1259,7 +1259,7 @@ private struct StringTests { #if FOUNDATION_FRAMEWORK @Test func extendedAttributeEncodings() throws { // XAttr is supported on some platforms, but not all. For now we just test this code on Darwin. - let encs : [String._Encoding] = [ + let encs : [String.Encoding] = [ .ascii, .nextstep, .japaneseEUC, @@ -1292,28 +1292,28 @@ private struct StringTests { #expect(back == encoding) } - #expect(encodingFromDataForExtendedAttribute("us-ascii;1536".data(using: .utf8)!)!.rawValue == String._Encoding.ascii.rawValue) - #expect(encodingFromDataForExtendedAttribute("x-nextstep;2817".data(using: .utf8)!)!.rawValue == String._Encoding.nextstep.rawValue) - #expect(encodingFromDataForExtendedAttribute("euc-jp;2336".data(using: .utf8)!)!.rawValue == String._Encoding.japaneseEUC.rawValue) - #expect(encodingFromDataForExtendedAttribute("utf-8;134217984".data(using: .utf8)!)!.rawValue == String._Encoding.utf8.rawValue) - #expect(encodingFromDataForExtendedAttribute("iso-8859-1;513".data(using: .utf8)!)!.rawValue == String._Encoding.isoLatin1.rawValue) - #expect(encodingFromDataForExtendedAttribute(";3071".data(using: .utf8)!)!.rawValue == String._Encoding.nonLossyASCII.rawValue) - #expect(encodingFromDataForExtendedAttribute("cp932;1056".data(using: .utf8)!)!.rawValue == String._Encoding.shiftJIS.rawValue) - #expect(encodingFromDataForExtendedAttribute("iso-8859-2;514".data(using: .utf8)!)!.rawValue == String._Encoding.isoLatin2.rawValue) - #expect(encodingFromDataForExtendedAttribute("utf-16;256".data(using: .utf8)!)!.rawValue == String._Encoding.unicode.rawValue) - #expect(encodingFromDataForExtendedAttribute("windows-1251;1282".data(using: .utf8)!)!.rawValue == String._Encoding.windowsCP1251.rawValue) - #expect(encodingFromDataForExtendedAttribute("windows-1252;1280".data(using: .utf8)!)!.rawValue == String._Encoding.windowsCP1252.rawValue) - #expect(encodingFromDataForExtendedAttribute("windows-1253;1283".data(using: .utf8)!)!.rawValue == String._Encoding.windowsCP1253.rawValue) - #expect(encodingFromDataForExtendedAttribute("windows-1254;1284".data(using: .utf8)!)!.rawValue == String._Encoding.windowsCP1254.rawValue) - #expect(encodingFromDataForExtendedAttribute("windows-1250;1281".data(using: .utf8)!)!.rawValue == String._Encoding.windowsCP1250.rawValue) - #expect(encodingFromDataForExtendedAttribute("iso-2022-jp;2080".data(using: .utf8)!)!.rawValue == String._Encoding.iso2022JP.rawValue) - #expect(encodingFromDataForExtendedAttribute("macintosh;0".data(using: .utf8)!)!.rawValue == String._Encoding.macOSRoman.rawValue) - #expect(encodingFromDataForExtendedAttribute("utf-16;256".data(using: .utf8)!)!.rawValue == String._Encoding.utf16.rawValue) - #expect(encodingFromDataForExtendedAttribute("utf-16be;268435712".data(using: .utf8)!)!.rawValue == String._Encoding.utf16BigEndian.rawValue) - #expect(encodingFromDataForExtendedAttribute("utf-16le;335544576".data(using: .utf8)!)!.rawValue == String._Encoding.utf16LittleEndian.rawValue) - #expect(encodingFromDataForExtendedAttribute("utf-32;201326848".data(using: .utf8)!)!.rawValue == String._Encoding.utf32.rawValue) - #expect(encodingFromDataForExtendedAttribute("utf-32be;402653440".data(using: .utf8)!)!.rawValue == String._Encoding.utf32BigEndian.rawValue) - #expect(encodingFromDataForExtendedAttribute("utf-32le;469762304".data(using: .utf8)!)!.rawValue == String._Encoding.utf32LittleEndian.rawValue) + #expect(encodingFromDataForExtendedAttribute("us-ascii;1536".data(using: .utf8)!)! == .ascii) + #expect(encodingFromDataForExtendedAttribute("x-nextstep;2817".data(using: .utf8)!)! == .nextstep) + #expect(encodingFromDataForExtendedAttribute("euc-jp;2336".data(using: .utf8)!)! == .japaneseEUC) + #expect(encodingFromDataForExtendedAttribute("utf-8;134217984".data(using: .utf8)!)! == .utf8) + #expect(encodingFromDataForExtendedAttribute("iso-8859-1;513".data(using: .utf8)!)! == .isoLatin1) + #expect(encodingFromDataForExtendedAttribute(";3071".data(using: .utf8)!)! == .nonLossyASCII) + #expect(encodingFromDataForExtendedAttribute("cp932;1056".data(using: .utf8)!)! == .shiftJIS) + #expect(encodingFromDataForExtendedAttribute("iso-8859-2;514".data(using: .utf8)!)! == .isoLatin2) + #expect(encodingFromDataForExtendedAttribute("utf-16;256".data(using: .utf8)!)! == .unicode) + #expect(encodingFromDataForExtendedAttribute("windows-1251;1282".data(using: .utf8)!)! == .windowsCP1251) + #expect(encodingFromDataForExtendedAttribute("windows-1252;1280".data(using: .utf8)!)! == .windowsCP1252) + #expect(encodingFromDataForExtendedAttribute("windows-1253;1283".data(using: .utf8)!)! == .windowsCP1253) + #expect(encodingFromDataForExtendedAttribute("windows-1254;1284".data(using: .utf8)!)! == .windowsCP1254) + #expect(encodingFromDataForExtendedAttribute("windows-1250;1281".data(using: .utf8)!)! == .windowsCP1250) + #expect(encodingFromDataForExtendedAttribute("iso-2022-jp;2080".data(using: .utf8)!)! == .iso2022JP) + #expect(encodingFromDataForExtendedAttribute("macintosh;0".data(using: .utf8)!)! == .macOSRoman) + #expect(encodingFromDataForExtendedAttribute("utf-16;256".data(using: .utf8)!)! == .utf16) + #expect(encodingFromDataForExtendedAttribute("utf-16be;268435712".data(using: .utf8)!)! == .utf16BigEndian) + #expect(encodingFromDataForExtendedAttribute("utf-16le;335544576".data(using: .utf8)!)! == .utf16LittleEndian) + #expect(encodingFromDataForExtendedAttribute("utf-32;201326848".data(using: .utf8)!)! == .utf32) + #expect(encodingFromDataForExtendedAttribute("utf-32be;402653440".data(using: .utf8)!)! == .utf32BigEndian) + #expect(encodingFromDataForExtendedAttribute("utf-32le;469762304".data(using: .utf8)!)! == .utf32LittleEndian) } #endif @@ -1321,9 +1321,9 @@ private struct StringTests { try withTemporaryStringFile { existingURL, nonExistentURL in let nonExistentPath = nonExistentURL.path() let s = "Lorem ipsum dolor sit amet, consectetur adipisicing elit" - try s.write(toFile: nonExistentPath, atomically: false, encoding: String._Encoding.ascii) + try s.write(toFile: nonExistentPath, atomically: false, encoding: .ascii) - let content = try String(contentsOfFile: nonExistentPath, encoding: String._Encoding.ascii) + let content = try String(contentsOfFile: nonExistentPath, encoding: .ascii) #expect(s == content) } @@ -1334,22 +1334,22 @@ private struct StringTests { try withTemporaryStringFile { existingURL, nonExistentURL in let nonExistentPath = nonExistentURL.path() let s = "Lorem ipsum dolor sit amet, consectetur adipisicing elit" - try s.write(to: nonExistentURL, atomically: false, encoding: String._Encoding.ascii) + try s.write(to: nonExistentURL, atomically: false, encoding: .ascii) - let content = try String(contentsOfFile: nonExistentPath, encoding: String._Encoding.ascii) + let content = try String(contentsOfFile: nonExistentPath, encoding: .ascii) #expect(s == content) } } - func verifyEncoding(_ encoding: String._Encoding, valid: [String], invalid: [String], sourceLocation: SourceLocation = #_sourceLocation) throws { + func verifyEncoding(_ encoding: String.Encoding, valid: [String], invalid: [String], sourceLocation: SourceLocation = #_sourceLocation) throws { for string in valid { let data = try #require(string.data(using: encoding), "Failed to encode \(string.debugDescription)", sourceLocation: sourceLocation) #expect(String(data: data, encoding: encoding) != nil, "Failed to decode \(data) (\(string.debugDescription))", sourceLocation: sourceLocation) } for string in invalid { - #expect(string.data(using: String._Encoding.macOSRoman) == nil, "Incorrectly successfully encoded \(string.debugDescription)", sourceLocation: sourceLocation) + #expect(string.data(using: .macOSRoman) == nil, "Incorrectly successfully encoded \(string.debugDescription)", sourceLocation: sourceLocation) } } @@ -1390,7 +1390,7 @@ private struct StringTests { let temporaryFileContents = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." -func withTemporaryStringFile(encoding: String._Encoding = .utf8, _ block: (_ existingURL: URL, _ nonExistentURL: URL) throws -> ()) throws { +func withTemporaryStringFile(encoding: String.Encoding = .utf8, _ block: (_ existingURL: URL, _ nonExistentURL: URL) throws -> ()) throws { let rootURL = URL.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) let fileURL = rootURL.appending(path: "NSStringTest.txt", directoryHint: .notDirectory) diff --git a/Tests/FoundationEssentialsTests/URLTests.swift b/Tests/FoundationEssentialsTests/URLTests.swift index cb8a92be1..f26886957 100644 --- a/Tests/FoundationEssentialsTests/URLTests.swift +++ b/Tests/FoundationEssentialsTests/URLTests.swift @@ -736,57 +736,6 @@ private struct URLTests { #expect(baseURL.deletingLastPathComponent().path == "/Users/foo-bar/Test1 Test2? Test3") } - @Test func encodedAbsoluteString() throws { - let base = URL(string: "http://user name:pass word@πŸ˜‚πŸ˜‚πŸ˜‚.com/pa th/p?qu ery#frag ment") - #expect(base?.absoluteString == "http://user%20name:pass%20word@xn--g28haa.com/pa%20th/p?qu%20ery#frag%20ment") - var url = URL(string: "relative", relativeTo: base) - #expect(url?.absoluteString == "http://user%20name:pass%20word@xn--g28haa.com/pa%20th/relative") - url = URL(string: "rela tive", relativeTo: base) - #expect(url?.absoluteString == "http://user%20name:pass%20word@xn--g28haa.com/pa%20th/rela%20tive") - url = URL(string: "relative?qu", relativeTo: base) - #expect(url?.absoluteString == "http://user%20name:pass%20word@xn--g28haa.com/pa%20th/relative?qu") - url = URL(string: "rela tive?q u", relativeTo: base) - #expect(url?.absoluteString == "http://user%20name:pass%20word@xn--g28haa.com/pa%20th/rela%20tive?q%20u") - - let fileBase = URL(filePath: "/Users/foo bar/more spaces/") - #expect(fileBase.absoluteString == "file:///Users/foo%20bar/more%20spaces/") - - url = URL(string: "relative", relativeTo: fileBase) - #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/relative") - #expect(url?.path == "/Users/foo bar/more spaces/relative") - - url = URL(string: "rela tive", relativeTo: fileBase) - #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/rela%20tive") - #expect(url?.path == "/Users/foo bar/more spaces/rela tive") - - // URL(string:) should count ? as the query delimiter - url = URL(string: "relative?query", relativeTo: fileBase) - #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/relative?query") - #expect(url?.path == "/Users/foo bar/more spaces/relative") - - url = URL(string: "rela tive?qu ery", relativeTo: fileBase) - #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/rela%20tive?qu%20ery") - #expect(url?.path == "/Users/foo bar/more spaces/rela tive") - - // URL(filePath:) should encode ? as part of the path - url = URL(filePath: "relative?query", relativeTo: fileBase) - #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/relative%3Fquery") - #expect(url?.path == "/Users/foo bar/more spaces/relative?query") - - url = URL(filePath: "rela tive?qu ery", relativeTo: fileBase) - #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/rela%20tive%3Fqu%20ery") - #expect(url?.path == "/Users/foo bar/more spaces/rela tive?qu ery") - - // URL(filePath:) should encode %3F as part of the path - url = URL(filePath: "relative%3Fquery", relativeTo: fileBase) - #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/relative%253Fquery") - #expect(url?.path == "/Users/foo bar/more spaces/relative%3Fquery") - - url = URL(filePath: "rela tive%3Fqu ery", relativeTo: fileBase) - #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/rela%20tive%253Fqu%20ery") - #expect(url?.path == "/Users/foo bar/more spaces/rela tive%3Fqu ery") - } - @Test func filePathDropsTrailingSlashes() throws { var url = URL(filePath: "/path/slashes///") #expect(url.path() == "/path/slashes///") @@ -1500,74 +1449,6 @@ private struct URLTests { #expect(urlComponents.string == nsURLComponents.string) } #endif - - @Test func componentsUnixDomainSocketOverHTTPScheme() { - var comp = URLComponents() - comp.scheme = "http+unix" - comp.host = "/path/to/socket" - comp.path = "/info" - #expect(comp.string == "http+unix://%2Fpath%2Fto%2Fsocket/info") - - comp.scheme = "https+unix" - #expect(comp.string == "https+unix://%2Fpath%2Fto%2Fsocket/info") - - comp.encodedHost = "%2Fpath%2Fto%2Fsocket" - #expect(comp.string == "https+unix://%2Fpath%2Fto%2Fsocket/info") - #expect(comp.encodedHost == "%2Fpath%2Fto%2Fsocket") - #expect(comp.host == "/path/to/socket") - #expect(comp.path == "/info") - - // "/path/to/socket" is not a valid host for schemes - // that IDNA-encode hosts instead of percent-encoding - comp.scheme = "http" - #expect(comp.string == nil) - - comp.scheme = "https" - #expect(comp.string == nil) - - comp.scheme = "https+unix" - #expect(comp.string == "https+unix://%2Fpath%2Fto%2Fsocket/info") - - // Check that we can parse a percent-encoded http+unix URL string - comp = URLComponents(string: "http+unix://%2Fpath%2Fto%2Fsocket/info")! - #expect(comp.encodedHost == "%2Fpath%2Fto%2Fsocket") - #expect(comp.host == "/path/to/socket") - #expect(comp.path == "/info") - } - - @Test func componentsUnixDomainSocketOverWebSocketScheme() { - var comp = URLComponents() - comp.scheme = "ws+unix" - comp.host = "/path/to/socket" - comp.path = "/info" - #expect(comp.string == "ws+unix://%2Fpath%2Fto%2Fsocket/info") - - comp.scheme = "wss+unix" - #expect(comp.string == "wss+unix://%2Fpath%2Fto%2Fsocket/info") - - comp.encodedHost = "%2Fpath%2Fto%2Fsocket" - #expect(comp.string == "wss+unix://%2Fpath%2Fto%2Fsocket/info") - #expect(comp.encodedHost == "%2Fpath%2Fto%2Fsocket") - #expect(comp.host == "/path/to/socket") - #expect(comp.path == "/info") - - // "/path/to/socket" is not a valid host for schemes - // that IDNA-encode hosts instead of percent-encoding - comp.scheme = "ws" - #expect(comp.string == nil) - - comp.scheme = "wss" - #expect(comp.string == nil) - - comp.scheme = "wss+unix" - #expect(comp.string == "wss+unix://%2Fpath%2Fto%2Fsocket/info") - - // Check that we can parse a percent-encoded ws+unix URL string - comp = URLComponents(string: "ws+unix://%2Fpath%2Fto%2Fsocket/info")! - #expect(comp.encodedHost == "%2Fpath%2Fto%2Fsocket") - #expect(comp.host == "/path/to/socket") - #expect(comp.path == "/info") - } @Test func filePathRelativeToBase() async throws { try await FilePlayground { diff --git a/Tests/FoundationInternationalizationTests/Formatting/DurationTimeFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/DurationTimeFormatStyleTests.swift index b161b68bd..28f2f9103 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/DurationTimeFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/DurationTimeFormatStyleTests.swift @@ -298,7 +298,7 @@ private struct DurationToMeasurementAdditionTests { private struct TestDurationTimeFormatStyle { let enUS = Locale(identifier: "en_US") - func assertFormattedWithPattern(seconds: Int, milliseconds: Int = 0, pattern: Duration._TimeFormatStyle.Pattern, grouping: NumberFormatStyleConfiguration.Grouping? = nil, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { + func assertFormattedWithPattern(seconds: Int, milliseconds: Int = 0, pattern: Duration.TimeFormatStyle.Pattern, grouping: NumberFormatStyleConfiguration.Grouping? = nil, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { var style = Duration.TimeFormatStyle(pattern: pattern).locale(enUS) if let grouping { style.grouping = grouping @@ -490,7 +490,7 @@ private struct DurationTimeAttributedStyleTests { typealias Segment = (String, AttributeScopes.FoundationAttributes.DurationFieldAttribute.Field?) let enUS = Locale(identifier: "en_US") - func assertWithPattern(seconds: Int, milliseconds: Int = 0, pattern: Duration._TimeFormatStyle.Pattern, expected: [Segment], locale: Locale = Locale(identifier: "en_US"), sourceLocation: SourceLocation = #_sourceLocation) { + func assertWithPattern(seconds: Int, milliseconds: Int = 0, pattern: Duration.TimeFormatStyle.Pattern, expected: [Segment], locale: Locale = Locale(identifier: "en_US"), sourceLocation: SourceLocation = #_sourceLocation) { #expect(Duration(seconds: Int64(seconds), milliseconds: Int64(milliseconds)).formatted(.time(pattern: pattern).locale(locale).attributed) == expected.attributedString, sourceLocation: sourceLocation) } @@ -643,7 +643,7 @@ private struct TestDurationTimeDiscreteConformance { } @Test func regressions() throws { - var style: Duration._TimeFormatStyle + var style: Duration.TimeFormatStyle style = .init(pattern: .hourMinute(padHourToLength: 0, roundSeconds: .toNearestOrAwayFromZero)) diff --git a/Tests/FoundationInternationalizationTests/Formatting/DurationUnitsFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/DurationUnitsFormatStyleTests.swift index c3bd0b1b6..13eb9d737 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/DurationUnitsFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/DurationUnitsFormatStyleTests.swift @@ -73,7 +73,7 @@ private struct DurationUnitsFormatStyleTests { #expect(Duration(minutes: 43, seconds: 24, milliseconds: 490).formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)) == "43 minutes, 24.49 seconds") } - func verify(seconds: Int, milliseconds: Int, allowedUnits: Set, fractionalSecondsLength: Int = 0, rounding: FloatingPointRoundingRule = .toNearestOrEven, increment: Double? = nil, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { + func verify(seconds: Int, milliseconds: Int, allowedUnits: Set, fractionalSecondsLength: Int = 0, rounding: FloatingPointRoundingRule = .toNearestOrEven, increment: Double? = nil, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let d = Duration(seconds: Int64(seconds), milliseconds: Int64(milliseconds)) #expect(d.formatted(.units(allowed: allowedUnits, zeroValueUnits: .show(length: 1), fractionalPart: .show(length: fractionalSecondsLength, rounded: rounding, increment: increment)).locale(enUS)) == expected, sourceLocation: sourceLocation) } @@ -287,13 +287,13 @@ private struct DurationUnitsFormatStyleTests { @Test func durationUnitsFormatStyleAPI_largerThanDay() { var duration: Duration! - let allowedUnits: Set = [.weeks, .days, .hours] - func assertZeroValueUnit(_ zeroFormat: Duration._UnitsFormatStyle.ZeroValueUnitsDisplayStrategy, _ expected: String, + let allowedUnits: Set = [.weeks, .days, .hours] + func assertZeroValueUnit(_ zeroFormat: Duration.UnitsFormatStyle.ZeroValueUnitsDisplayStrategy, _ expected: String, sourceLocation: SourceLocation = #_sourceLocation) { #expect(duration.formatted(.units(allowed: allowedUnits, width: .wide, zeroValueUnits: zeroFormat).locale(enUS)) == expected, sourceLocation: sourceLocation) } - func assertMaxUnitCount(_ maxUnitCount: Int, fractionalPart: Duration._UnitsFormatStyle.FractionalPartDisplayStrategy, _ expected: String, + func assertMaxUnitCount(_ maxUnitCount: Int, fractionalPart: Duration.UnitsFormatStyle.FractionalPartDisplayStrategy, _ expected: String, sourceLocation: SourceLocation = #_sourceLocation) { #expect(duration.formatted(.units(allowed: allowedUnits, width: .wide, maximumUnitCount: maxUnitCount, fractionalPart: fractionalPart).locale(enUS)) == expected, sourceLocation: sourceLocation) } @@ -331,8 +331,8 @@ private struct DurationUnitsFormatStyleTests { @Test func zeroValueUnits() { var duration: Duration - var allowedUnits: Set - func test(_ zeroFormat: Duration._UnitsFormatStyle.ZeroValueUnitsDisplayStrategy, _ expected: String, sourceLocation: SourceLocation = #_sourceLocation) { + var allowedUnits: Set + func test(_ zeroFormat: Duration.UnitsFormatStyle.ZeroValueUnitsDisplayStrategy, _ expected: String, sourceLocation: SourceLocation = #_sourceLocation) { #expect(duration.formatted(.units(allowed: allowedUnits, width: .wide, zeroValueUnits: zeroFormat).locale(enUS)) == expected, sourceLocation: sourceLocation) } @@ -430,11 +430,11 @@ private struct DurationUnitsFormatStyleTests { } func assertEqual(_ duration: Duration, - allowedUnits: Set, maximumUnitCount: Int? = nil, roundSmallerParts: FloatingPointRoundingRule = .toNearestOrEven, trailingFractionalPartLength: Int = Int.max, roundingIncrement: Double? = nil, dropZeroUnits: Bool = false, - expected: (units: [Duration._UnitsFormatStyle.Unit], values: [Double]), + allowedUnits: Set, maximumUnitCount: Int? = nil, roundSmallerParts: FloatingPointRoundingRule = .toNearestOrEven, trailingFractionalPartLength: Int = Int.max, roundingIncrement: Double? = nil, dropZeroUnits: Bool = false, + expected: (units: [Duration.UnitsFormatStyle.Unit], values: [Double]), sourceLocation: SourceLocation = #_sourceLocation) { - let (units, values) = Duration._UnitsFormatStyle.unitsToUse(duration: duration, allowedUnits: allowedUnits, maximumUnitCount: maximumUnitCount, roundSmallerParts: roundSmallerParts, trailingFractionalPartLength: trailingFractionalPartLength, roundingIncrement: roundingIncrement, dropZeroUnits: dropZeroUnits) + let (units, values) = Duration.UnitsFormatStyle.unitsToUse(duration: duration, allowedUnits: allowedUnits, maximumUnitCount: maximumUnitCount, roundSmallerParts: roundSmallerParts, trailingFractionalPartLength: trailingFractionalPartLength, roundingIncrement: roundingIncrement, dropZeroUnits: dropZeroUnits) guard values.count == expected.values.count else { Issue.record("\(values) is not equal to \(expected.values)", sourceLocation: sourceLocation) return @@ -490,10 +490,10 @@ private struct DurationUnitsFormatStyleTests { @Test func lengthRangeExpression() { var duration: Duration - var allowedUnits: Set + var allowedUnits: Set func verify(intLimits: R, fracLimits: R2, _ expected: String, sourceLocation: SourceLocation = #_sourceLocation) where R.Bound == Int, R2.Bound == Int { - let style = Duration._UnitsFormatStyle(allowedUnits: allowedUnits, width: .abbreviated, valueLengthLimits: intLimits, fractionalPart: .init(lengthLimits: fracLimits)).locale(enUS) + let style = Duration.UnitsFormatStyle(allowedUnits: allowedUnits, width: .abbreviated, valueLengthLimits: intLimits, fractionalPart: .init(lengthLimits: fracLimits)).locale(enUS) let formatted = style.format(duration) #expect(formatted == expected, sourceLocation: sourceLocation) } @@ -1198,7 +1198,7 @@ private struct TestDurationUnitsDiscreteConformance { } @Test func evaluation() { - func assertEvaluation(of style: Duration._UnitsFormatStyle, + func assertEvaluation(of style: Duration.UnitsFormatStyle, rounding roundingRules: [FloatingPointRoundingRule] = [.up, .down, .towardZero, .awayFromZero, .toNearestOrAwayFromZero, .toNearestOrEven], in range: ClosedRange, includes expectedExcerpts: [String]..., @@ -1374,7 +1374,7 @@ private struct TestDurationUnitsDiscreteConformance { } @Test func regressions() throws { - var style: Duration._UnitsFormatStyle + var style: Duration.UnitsFormatStyle style = .init(allowedUnits: [.minutes, .seconds], width: .narrow, maximumUnitCount: 1, zeroValueUnits: .hide, fractionalPart: .show(length: 1, rounded: .toNearestOrAwayFromZero)) @@ -1386,7 +1386,7 @@ private struct TestDurationUnitsDiscreteConformance { } @Test func randomSamples() throws { - let styles: [Duration._UnitsFormatStyle] = [ + let styles: [Duration.UnitsFormatStyle] = [ .init(allowedUnits: [.minutes, .seconds], width: .narrow, zeroValueUnits: .show(length: 1), fractionalPart: .hide), .init(allowedUnits: [.minutes, .seconds], width: .narrow, maximumUnitCount: 1, zeroValueUnits: .hide, fractionalPart: .hide), .init(allowedUnits: [.hours], width: .narrow, zeroValueUnits: .show(length: 1), fractionalPart: .hide), diff --git a/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleTests.swift index 5b4a4faef..67d840c6b 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleTests.swift @@ -614,7 +614,7 @@ private struct NumberFormatStyleTests { "identifier": "en_US" } } - """.data(using: String.Encoding.utf8)) + """.data(using: .utf8)) let decoded = try JSONDecoder().decode(Decimal.FormatStyle.Currency.self, from: previouslyEncoded) #expect(decoded == gbpInUS) diff --git a/Tests/FoundationInternationalizationTests/Formatting/NumberParseStrategyTests.swift b/Tests/FoundationInternationalizationTests/Formatting/NumberParseStrategyTests.swift index ad2896f6e..051e39dec 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/NumberParseStrategyTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/NumberParseStrategyTests.swift @@ -220,7 +220,7 @@ private struct NumberParseStrategyTests { {"formatStyle":{"locale":{"current":0,"identifier":"en_US"},"collection":{"presentation":{"option":1}},"currencyCode":"USD"},"numberFormatType":{"currency":{"_0":{"presentation":{"option":1}}}},"lenient":true,"locale":{"identifier":"en_US","current":0}} """ - let existingData = try #require(existingSerializedParseStrategy.data(using: String.Encoding.utf8)) + let existingData = try #require(existingSerializedParseStrategy.data(using: .utf8)) let decoded: IntegerParseStrategy.Currency> = try JSONDecoder().decode(IntegerParseStrategy.Currency>.self, from: existingData) #expect(decoded == p) @@ -236,7 +236,7 @@ private struct NumberParseStrategyTests { {"formatStyle":{"collection":{"presentation":{"option":1}},"locale":{"current":0,"identifier":"en_US"},"currencyCode":"GBP"},"lenient":true,"locale":{"current":0,"identifier":"en_US"},"numberFormatType":{"currency":{"_0":{"presentation":{"option":1}}}}} """ - let existingData = try #require(existingSerializedParseStrategy.data(using: String.Encoding.utf8)) + let existingData = try #require(existingSerializedParseStrategy.data(using: .utf8)) let decoded: IntegerParseStrategy.Currency> = try JSONDecoder().decode(IntegerParseStrategy.Currency>.self, from: existingData) #expect(decoded == p) #expect(decoded.formatStyle == fs) diff --git a/Tests/FoundationInternationalizationTests/LocaleComponentsTests.swift b/Tests/FoundationInternationalizationTests/LocaleComponentsTests.swift index ce05cb1d4..0157eb7fd 100644 --- a/Tests/FoundationInternationalizationTests/LocaleComponentsTests.swift +++ b/Tests/FoundationInternationalizationTests/LocaleComponentsTests.swift @@ -334,7 +334,7 @@ private struct LocaleCodableTests { // Test types that used to encode both `identifier` and `normalizdIdentifier` now only encodes `identifier` func _testRoundtripCoding(_ obj: T, identifier: String, normalizedIdentifier: String, sourceLocation: SourceLocation = #_sourceLocation) throws -> T? { let previousEncoded = "{\"_identifier\":\"\(identifier)\",\"_normalizedIdentifier\":\"\(normalizedIdentifier)\"}" - let previousEncodedData = previousEncoded.data(using: String._Encoding.utf8)! + let previousEncodedData = previousEncoded.data(using: .utf8)! let decoder = JSONDecoder() let decoded = try decoder.decode(T.self, from: previousEncodedData) @@ -484,7 +484,7 @@ private struct LocaleCodableTests { @Test func decode_compatible_localeComponents() throws { func expectDecode(_ encoded: String, _ expected: Locale.Components, sourceLocation: SourceLocation = #_sourceLocation) throws { - let data = try #require(encoded.data(using: String._Encoding.utf8)) + let data = try #require(encoded.data(using: .utf8)) let decoded = try JSONDecoder().decode(Locale.Components.self, from: data) #expect(decoded == expected, sourceLocation: sourceLocation) } @@ -516,7 +516,7 @@ private struct LocaleCodableTests { @Test func decode_compatible_language() throws { func expectDecode(_ encoded: String, _ expected: Locale.Language, sourceLocation: SourceLocation = #_sourceLocation) throws { - let data = try #require(encoded.data(using: String._Encoding.utf8)) + let data = try #require(encoded.data(using: .utf8)) let decoded = try JSONDecoder().decode(Locale.Language.self, from: data) #expect(decoded == expected, sourceLocation: sourceLocation) } @@ -532,7 +532,7 @@ private struct LocaleCodableTests { @Test func decode_compatible_languageComponents() throws { func expectDecode(_ encoded: String, _ expected: Locale.Language.Components, sourceLocation: SourceLocation = #_sourceLocation) throws { - let data = try #require(encoded.data(using: String._Encoding.utf8)) + let data = try #require(encoded.data(using: .utf8)) let decoded = try JSONDecoder().decode(Locale.Language.Components.self, from: data) #expect(decoded == expected, sourceLocation: sourceLocation) } @@ -572,7 +572,7 @@ private struct LocaleCodableTests { #expect(encoded == expectedEncoded, sourceLocation: sourceLocation) - let data = try #require(encoded.data(using: String._Encoding.utf8)) + let data = try #require(encoded.data(using: .utf8)) let decoded = try JSONDecoder().decode(Locale.Language.self, from: data) #expect(lang == decoded, sourceLocation: sourceLocation) @@ -611,7 +611,7 @@ private struct LocaleCodableTests { #expect(encoded == expectedEncoded, sourceLocation: sourceLocation) - let data = try #require(encoded.data(using: String._Encoding.utf8)) + let data = try #require(encoded.data(using: .utf8)) let decoded = try JSONDecoder().decode(Locale.Language.Components.self, from: data) #expect(lang == decoded, sourceLocation: sourceLocation) @@ -645,7 +645,7 @@ private struct LocaleCodableTests { #expect(encoded == expectedEncoded, sourceLocation: sourceLocation) - let data = try #require(encoded.data(using: String._Encoding.utf8)) + let data = try #require(encoded.data(using: .utf8)) let decoded = try JSONDecoder().decode(Locale.Components.self, from: data) #expect(lang == decoded, sourceLocation: sourceLocation) diff --git a/Tests/FoundationInternationalizationTests/URLInternationalizationTests.swift b/Tests/FoundationInternationalizationTests/URLInternationalizationTests.swift new file mode 100644 index 000000000..20d0cd4df --- /dev/null +++ b/Tests/FoundationInternationalizationTests/URLInternationalizationTests.swift @@ -0,0 +1,154 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Testing + +#if FOUNDATION_FRAMEWORK +@testable import Foundation +#else +@testable import FoundationEssentials +@testable import FoundationInternationalization +#endif // FOUNDATION_FRAMEWORK + +@Suite("URL (Internationalization)") +private struct URLInternationalizationTests { + @Test func urlHostUIDNAEncoding() { + let emojiURL = URL(string: "https://i❀️tacos.ws/πŸ³οΈβ€πŸŒˆ/ε†°ζ·‡ζ·‹") + let emojiURLEncoded = "https://xn--itacos-i50d.ws/%F0%9F%8F%B3%EF%B8%8F%E2%80%8D%F0%9F%8C%88/%E5%86%B0%E6%B7%87%E6%B7%8B" + #expect(emojiURL?.absoluteString == emojiURLEncoded) + #expect(emojiURL?.host(percentEncoded: false) == "xn--itacos-i50d.ws") + + let chineseURL = URL(string: "http://見.香港/ηƒ­η‹—/🌭") + let chineseURLEncoded = "http://xn--nw2a.xn--j6w193g/%E7%83%AD%E7%8B%97/%F0%9F%8C%AD" + #expect(chineseURL?.absoluteString == chineseURLEncoded) + #expect(chineseURL?.host(percentEncoded: false) == "xn--nw2a.xn--j6w193g") + } + + @Test func componentsUnixDomainSocketOverWebSocketScheme() { + var comp = URLComponents() + comp.scheme = "ws+unix" + comp.host = "/path/to/socket" + comp.path = "/info" + #expect(comp.string == "ws+unix://%2Fpath%2Fto%2Fsocket/info") + + comp.scheme = "wss+unix" + #expect(comp.string == "wss+unix://%2Fpath%2Fto%2Fsocket/info") + + comp.encodedHost = "%2Fpath%2Fto%2Fsocket" + #expect(comp.string == "wss+unix://%2Fpath%2Fto%2Fsocket/info") + #expect(comp.encodedHost == "%2Fpath%2Fto%2Fsocket") + #expect(comp.host == "/path/to/socket") + #expect(comp.path == "/info") + + // "/path/to/socket" is not a valid host for schemes + // that IDNA-encode hosts instead of percent-encoding + comp.scheme = "ws" + #expect(comp.string == nil) + + comp.scheme = "wss" + #expect(comp.string == nil) + + comp.scheme = "wss+unix" + #expect(comp.string == "wss+unix://%2Fpath%2Fto%2Fsocket/info") + + // Check that we can parse a percent-encoded ws+unix URL string + comp = URLComponents(string: "ws+unix://%2Fpath%2Fto%2Fsocket/info")! + #expect(comp.encodedHost == "%2Fpath%2Fto%2Fsocket") + #expect(comp.host == "/path/to/socket") + #expect(comp.path == "/info") + } + + @Test func componentsUnixDomainSocketOverHTTPScheme() { + var comp = URLComponents() + comp.scheme = "http+unix" + comp.host = "/path/to/socket" + comp.path = "/info" + #expect(comp.string == "http+unix://%2Fpath%2Fto%2Fsocket/info") + + comp.scheme = "https+unix" + #expect(comp.string == "https+unix://%2Fpath%2Fto%2Fsocket/info") + + comp.encodedHost = "%2Fpath%2Fto%2Fsocket" + #expect(comp.string == "https+unix://%2Fpath%2Fto%2Fsocket/info") + #expect(comp.encodedHost == "%2Fpath%2Fto%2Fsocket") + #expect(comp.host == "/path/to/socket") + #expect(comp.path == "/info") + + // "/path/to/socket" is not a valid host for schemes + // that IDNA-encode hosts instead of percent-encoding + comp.scheme = "http" + #expect(comp.string == nil) + + comp.scheme = "https" + #expect(comp.string == nil) + + comp.scheme = "https+unix" + #expect(comp.string == "https+unix://%2Fpath%2Fto%2Fsocket/info") + + // Check that we can parse a percent-encoded http+unix URL string + comp = URLComponents(string: "http+unix://%2Fpath%2Fto%2Fsocket/info")! + #expect(comp.encodedHost == "%2Fpath%2Fto%2Fsocket") + #expect(comp.host == "/path/to/socket") + #expect(comp.path == "/info") + } + + @Test func encodedAbsoluteString() throws { + let base = URL(string: "http://user name:pass word@πŸ˜‚πŸ˜‚πŸ˜‚.com/pa th/p?qu ery#frag ment") + #expect(base?.absoluteString == "http://user%20name:pass%20word@xn--g28haa.com/pa%20th/p?qu%20ery#frag%20ment") + var url = URL(string: "relative", relativeTo: base) + #expect(url?.absoluteString == "http://user%20name:pass%20word@xn--g28haa.com/pa%20th/relative") + url = URL(string: "rela tive", relativeTo: base) + #expect(url?.absoluteString == "http://user%20name:pass%20word@xn--g28haa.com/pa%20th/rela%20tive") + url = URL(string: "relative?qu", relativeTo: base) + #expect(url?.absoluteString == "http://user%20name:pass%20word@xn--g28haa.com/pa%20th/relative?qu") + url = URL(string: "rela tive?q u", relativeTo: base) + #expect(url?.absoluteString == "http://user%20name:pass%20word@xn--g28haa.com/pa%20th/rela%20tive?q%20u") + + let fileBase = URL(filePath: "/Users/foo bar/more spaces/") + #expect(fileBase.absoluteString == "file:///Users/foo%20bar/more%20spaces/") + + url = URL(string: "relative", relativeTo: fileBase) + #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/relative") + #expect(url?.path == "/Users/foo bar/more spaces/relative") + + url = URL(string: "rela tive", relativeTo: fileBase) + #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/rela%20tive") + #expect(url?.path == "/Users/foo bar/more spaces/rela tive") + + // URL(string:) should count ? as the query delimiter + url = URL(string: "relative?query", relativeTo: fileBase) + #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/relative?query") + #expect(url?.path == "/Users/foo bar/more spaces/relative") + + url = URL(string: "rela tive?qu ery", relativeTo: fileBase) + #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/rela%20tive?qu%20ery") + #expect(url?.path == "/Users/foo bar/more spaces/rela tive") + + // URL(filePath:) should encode ? as part of the path + url = URL(filePath: "relative?query", relativeTo: fileBase) + #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/relative%3Fquery") + #expect(url?.path == "/Users/foo bar/more spaces/relative?query") + + url = URL(filePath: "rela tive?qu ery", relativeTo: fileBase) + #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/rela%20tive%3Fqu%20ery") + #expect(url?.path == "/Users/foo bar/more spaces/rela tive?qu ery") + + // URL(filePath:) should encode %3F as part of the path + url = URL(filePath: "relative%3Fquery", relativeTo: fileBase) + #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/relative%253Fquery") + #expect(url?.path == "/Users/foo bar/more spaces/relative%3Fquery") + + url = URL(filePath: "rela tive%3Fqu ery", relativeTo: fileBase) + #expect(url?.absoluteString == "file:///Users/foo%20bar/more%20spaces/rela%20tive%253Fqu%20ery") + #expect(url?.path == "/Users/foo bar/more spaces/rela tive%3Fqu ery") + } +} diff --git a/Tests/FoundationInternationalizationTests/URLTests+UIDNA.swift b/Tests/FoundationInternationalizationTests/URLTests+UIDNA.swift deleted file mode 100644 index 489ccdc76..000000000 --- a/Tests/FoundationInternationalizationTests/URLTests+UIDNA.swift +++ /dev/null @@ -1,35 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import Testing - -#if FOUNDATION_FRAMEWORK -@testable import Foundation -#else -@testable import FoundationEssentials -@testable import FoundationInternationalization -#endif // FOUNDATION_FRAMEWORK - -@Suite("URL UIDNA") -private struct URLUIDNATests { - @Test func urlHostUIDNAEncoding() { - let emojiURL = URL(string: "https://i❀️tacos.ws/πŸ³οΈβ€πŸŒˆ/ε†°ζ·‡ζ·‹") - let emojiURLEncoded = "https://xn--itacos-i50d.ws/%F0%9F%8F%B3%EF%B8%8F%E2%80%8D%F0%9F%8C%88/%E5%86%B0%E6%B7%87%E6%B7%8B" - #expect(emojiURL?.absoluteString == emojiURLEncoded) - #expect(emojiURL?.host(percentEncoded: false) == "xn--itacos-i50d.ws") - - let chineseURL = URL(string: "http://見.香港/ηƒ­η‹—/🌭") - let chineseURLEncoded = "http://xn--nw2a.xn--j6w193g/%E7%83%AD%E7%8B%97/%F0%9F%8C%AD" - #expect(chineseURL?.absoluteString == chineseURLEncoded) - #expect(chineseURL?.host(percentEncoded: false) == "xn--nw2a.xn--j6w193g") - } -} diff --git a/Tests/TestSupport/Utilities.swift b/Tests/TestSupport/Utilities.swift new file mode 100644 index 000000000..43b748081 --- /dev/null +++ b/Tests/TestSupport/Utilities.swift @@ -0,0 +1,266 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Testing + +/// Test that the elements of `instances` satisfy the semantic +/// requirements of `Equatable`, using `oracle` to generate equality +/// expectations from pairs of positions in `instances`. +/// +/// - Note: `oracle` is also checked for conformance to the +/// laws. +func checkEquatable( + _ instances: Instances, + oracle: (Instances.Index, Instances.Index) -> Bool, + allowBrokenTransitivity: Bool = false, + _ message: @autoclosure () -> String = "", + sourceLocation: SourceLocation = #_sourceLocation +) where Instances.Element: Equatable { + let indices = Array(instances.indices) + _checkEquatable( + instances, + oracle: { oracle(indices[$0], indices[$1]) }, + allowBrokenTransitivity: allowBrokenTransitivity, + message(), + sourceLocation: sourceLocation + ) +} + +func _checkEquatable( + _ _instances: Instances, + oracle: (Int, Int) -> Bool, + allowBrokenTransitivity: Bool = false, + _ message: @autoclosure () -> String = "", + sourceLocation: SourceLocation = #_sourceLocation +) where Instances.Element: Equatable { + let instances = Array(_instances) + + // For each index (which corresponds to an instance being tested) track the + // set of equal instances. + var transitivityScoreboard: [Box>] = + instances.indices.map { _ in Box([]) } + + for i in instances.indices { + let x = instances[i] + #expect(oracle(i, i), "bad oracle: broken reflexivity at index \(i)") + + for j in instances.indices { + let y = instances[j] + + let predictedXY = oracle(i, j) + #expect( + predictedXY == oracle(j, i), + "bad oracle: broken symmetry between indices \(i), \(j)", + sourceLocation: sourceLocation + ) + + let isEqualXY = x == y + #expect( + predictedXY == isEqualXY, + """ + \((predictedXY + ? "expected equal, found not equal" + : "expected not equal, found equal")) + lhs (at index \(i)): \(String(reflecting: x)) + rhs (at index \(j)): \(String(reflecting: y)) + """, + sourceLocation: sourceLocation + ) + + // Not-equal is an inverse of equal. + #expect( + isEqualXY != (x != y), + """ + lhs (at index \(i)): \(String(reflecting: x)) + rhs (at index \(j)): \(String(reflecting: y)) + """, + sourceLocation: sourceLocation + ) + + if !allowBrokenTransitivity { + // Check transitivity of the predicate represented by the oracle. + // If we are adding the instance `j` into an equivalence set, check that + // it is equal to every other instance in the set. + if predictedXY && i < j && transitivityScoreboard[i].value.insert(j).inserted { + if transitivityScoreboard[i].value.count == 1 { + transitivityScoreboard[i].value.insert(i) + } + for k in transitivityScoreboard[i].value { + #expect( + oracle(j, k), + "bad oracle: broken transitivity at indices \(i), \(j), \(k)", + sourceLocation: sourceLocation + ) + // No need to check equality between actual values, we will check + // them with the checks above. + } + precondition(transitivityScoreboard[j].value.isEmpty) + transitivityScoreboard[j] = transitivityScoreboard[i] + } + } + } + } +} + +public func checkHashable( + _ instances: Instances, + equalityOracle: (Instances.Index, Instances.Index) -> Bool, + allowIncompleteHashing: Bool = false, + _ message: @autoclosure () -> String = "", + sourceLocation: SourceLocation = #_sourceLocation +) where Instances.Element: Hashable { + checkHashable( + instances, + equalityOracle: equalityOracle, + hashEqualityOracle: equalityOracle, + allowIncompleteHashing: allowIncompleteHashing, + message(), + sourceLocation: sourceLocation) +} + +func checkHashable( + _ instances: Instances, + equalityOracle: (Instances.Index, Instances.Index) -> Bool, + hashEqualityOracle: (Instances.Index, Instances.Index) -> Bool, + allowIncompleteHashing: Bool = false, + _ message: @autoclosure () -> String = "", + sourceLocation: SourceLocation = #_sourceLocation +) where Instances.Element: Hashable { + checkEquatable( + instances, + oracle: equalityOracle, + message(), + sourceLocation: sourceLocation + ) + + for i in instances.indices { + let x = instances[i] + for j in instances.indices { + let y = instances[j] + let predicted = hashEqualityOracle(i, j) + #expect( + predicted == hashEqualityOracle(j, i), + "bad hash oracle: broken symmetry between indices \(i), \(j)", + sourceLocation: sourceLocation + ) + if x == y { + #expect( + predicted, + """ + bad hash oracle: equality must imply hash equality + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + } + if predicted { + #expect( + hash(x) == hash(y), + """ + hash(into:) expected to match, found to differ + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + #expect( + x.hashValue == y.hashValue, + """ + hashValue expected to match, found to differ + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + #expect( + x._rawHashValue(seed: 0) == y._rawHashValue(seed: 0), + """ + _rawHashValue(seed:) expected to match, found to differ + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + } else if !allowIncompleteHashing { + // Try a few different seeds; at least one of them should discriminate + // between the hashes. It is extremely unlikely this check will fail + // all ten attempts, unless the type's hash encoding is not unique, + // or unless the hash equality oracle is wrong. + #expect( + (0..<10).contains { hash(x, salt: $0) != hash(y, salt: $0) }, + """ + hash(into:) expected to differ, found to match + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + #expect( + (0..<10).contains { i in + x._rawHashValue(seed: i) != y._rawHashValue(seed: i) + }, + """ + _rawHashValue(seed:) expected to differ, found to match + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + } + } + } +} + +/// Test that the elements of `groups` consist of instances that satisfy the +/// semantic requirements of `Hashable`, with each group defining a distinct +/// equivalence class under `==`. +public func checkHashableGroups( + _ groups: Groups, + _ message: @autoclosure () -> String = "", + allowIncompleteHashing: Bool = false, + sourceLocation: SourceLocation = #_sourceLocation +) where Groups.Element: Collection, Groups.Element.Element: Hashable { + let instances = groups.flatMap { $0 } + // groupIndices[i] is the index of the element in groups that contains + // instances[i]. + let groupIndices = + zip(0..., groups).flatMap { i, group in group.map { _ in i } } + func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { + return groupIndices[lhs] == groupIndices[rhs] + } + checkHashable( + instances, + equalityOracle: equalityOracle, + hashEqualityOracle: equalityOracle, + allowIncompleteHashing: allowIncompleteHashing, + sourceLocation: sourceLocation) +} + +// MARK: - Private Types + +private class Box { + var value: T + + init(_ value: T) { + self.value = value + } +} + +private func hash(_ value: H, salt: Int? = nil) -> Int { + var hasher = Hasher() + if let salt = salt { + hasher.combine(salt) + } + hasher.combine(value) + return hasher.finalize() +} From e788509d832a73fe71e04d1661c1dfbf6dea3a8a Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 3 Jul 2025 17:28:45 -0700 Subject: [PATCH 2/2] Remove FoundationEssentialsTests suite as it is incompatible with SwiftPM's build system --- .../FoundationEssentialsTests.swift | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 Tests/FoundationEssentialsTests/FoundationEssentialsTests.swift diff --git a/Tests/FoundationEssentialsTests/FoundationEssentialsTests.swift b/Tests/FoundationEssentialsTests/FoundationEssentialsTests.swift deleted file mode 100644 index e81c32c75..000000000 --- a/Tests/FoundationEssentialsTests/FoundationEssentialsTests.swift +++ /dev/null @@ -1,25 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -#if canImport(FoundationEssentials) -import FoundationEssentials -import Testing - -@Suite("FoundationEssentials") -private struct FoundationEssentialsTests { - @Test func essentialsDoesNotImportInternationalization() { - // Ensures that targets that only import FoundationEssentials do not end up calling functionality in FoundationInternationalization - // We use a non-GMT TimeZone as proxy for whether FoundationInternationalization is loaded at runtime - #expect(TimeZone(identifier: "America/Los_Angeles") == nil) - } -} -#endif