diff --git a/Tests/FoundationInternationalizationTests/CurrentInternationalizationPreferencesActor.swift b/Tests/FoundationInternationalizationTests/CurrentInternationalizationPreferencesActor.swift new file mode 100644 index 000000000..5cb85ac7d --- /dev/null +++ b/Tests/FoundationInternationalizationTests/CurrentInternationalizationPreferencesActor.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// 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(FoundationInternationalization) +@testable import FoundationEssentials +@testable import FoundationInternationalization +#else +@testable import Foundation +#endif + +// This actor is private and not exposed to tests to prevent accidentally writing tests annotated with @CurrentInternationalizationPreferencesActor which may have suspension points +// Using the global helper function below ensures that only synchronous work with no suspension points is queued +@globalActor +private actor CurrentInternationalizationPreferencesActor: GlobalActor { + static let shared = CurrentInternationalizationPreferencesActor() + + private init() {} + + @CurrentInternationalizationPreferencesActor + static func usingCurrentInternationalizationPreferences( + body: () throws -> Void // Must be synchronous to prevent suspension points within body which could introduce a change in the preferences + ) rethrows { + try body() + + // Reset everything after the test runs to ensure custom values don't persist + LocaleCache.cache.reset() + CalendarCache.cache.reset() + _ = TimeZoneCache.cache.reset() + _ = TimeZone.resetSystemTimeZone() + } +} + +internal func usingCurrentInternationalizationPreferences(_ body: sending () throws -> Void) async rethrows { + try await CurrentInternationalizationPreferencesActor.usingCurrentInternationalizationPreferences(body: body) +} diff --git a/Tests/FoundationInternationalizationTests/DecimalTests+Locale.swift b/Tests/FoundationInternationalizationTests/DecimalTests+Locale.swift index c39b7ff16..da47bcfee 100644 --- a/Tests/FoundationInternationalizationTests/DecimalTests+Locale.swift +++ b/Tests/FoundationInternationalizationTests/DecimalTests+Locale.swift @@ -10,58 +10,75 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationInternationalization) +@testable import FoundationEssentials +@testable import FoundationInternationalization +#elseif FOUNDATION_FRAMEWORK +@testable import Foundation #endif -final class DecimalLocaleTests : XCTestCase { - func test_stringWithLocale() { +@Suite("Decimal (Locale)") +private struct DecimalLocaleTests { + + @Test func stringWithLocale() { + let en_US = Locale(identifier: "en_US") let fr_FR = Locale(identifier: "fr_FR") - XCTAssertEqual(Decimal(string: "1,234.56")! * 1000, Decimal(1000)) - XCTAssertEqual(Decimal(string: "1,234.56", locale: en_US)! * 1000, Decimal(1000)) - XCTAssertEqual(Decimal(string: "1,234.56", locale: fr_FR)! * 1000, Decimal(1234)) - XCTAssertEqual(Decimal(string: "1.234,56", locale: en_US)! * 1000, Decimal(1234)) - XCTAssertEqual(Decimal(string: "1.234,56", locale: fr_FR)! * 1000, Decimal(1000)) + #expect(Decimal(string: "1,234.56")! * 1000 == Decimal(1000)) + #expect(Decimal(string: "1,234.56", locale: en_US)! * 1000 == Decimal(1000)) + #expect(Decimal(string: "1,234.56", locale: fr_FR)! * 1000 == Decimal(1234)) + #expect(Decimal(string: "1.234,56", locale: en_US)! * 1000 == Decimal(1234)) + #expect(Decimal(string: "1.234,56", locale: fr_FR)! * 1000 == Decimal(1000)) - XCTAssertEqual(Decimal(string: "-1,234.56")! * 1000, Decimal(-1000)) - XCTAssertEqual(Decimal(string: "+1,234.56")! * 1000, Decimal(1000)) - XCTAssertEqual(Decimal(string: "+1234.56e3"), Decimal(1234560)) - XCTAssertEqual(Decimal(string: "+1234.56E3"), Decimal(1234560)) - XCTAssertEqual(Decimal(string: "+123456000E-3"), Decimal(123456)) + #expect(Decimal(string: "-1,234.56")! * 1000 == Decimal(-1000)) + #expect(Decimal(string: "+1,234.56")! * 1000 == Decimal(1000)) + #expect(Decimal(string: "+1234.56e3") == Decimal(1234560)) + #expect(Decimal(string: "+1234.56E3") == Decimal(1234560)) + #expect(Decimal(string: "+123456000E-3") == Decimal(123456)) - XCTAssertNil(Decimal(string: "")) - XCTAssertNil(Decimal(string: "x")) - XCTAssertEqual(Decimal(string: "-x"), Decimal.zero) - XCTAssertEqual(Decimal(string: "+x"), Decimal.zero) - XCTAssertEqual(Decimal(string: "-"), Decimal.zero) - XCTAssertEqual(Decimal(string: "+"), Decimal.zero) - XCTAssertEqual(Decimal(string: "-."), Decimal.zero) - XCTAssertEqual(Decimal(string: "+."), Decimal.zero) + #expect(Decimal(string: "") == nil) + #expect(Decimal(string: "x") == nil) + #expect(Decimal(string: "-x") == Decimal.zero) + #expect(Decimal(string: "+x") == Decimal.zero) + #expect(Decimal(string: "-") == Decimal.zero) + #expect(Decimal(string: "+") == Decimal.zero) + #expect(Decimal(string: "-.") == Decimal.zero) + #expect(Decimal(string: "+.") == Decimal.zero) - XCTAssertEqual(Decimal(string: "-0"), Decimal.zero) - XCTAssertEqual(Decimal(string: "+0"), Decimal.zero) - XCTAssertEqual(Decimal(string: "-0."), Decimal.zero) - XCTAssertEqual(Decimal(string: "+0."), Decimal.zero) - XCTAssertEqual(Decimal(string: "e1"), Decimal.zero) - XCTAssertEqual(Decimal(string: "e-5"), Decimal.zero) - XCTAssertEqual(Decimal(string: ".3e1"), Decimal(3)) + #expect(Decimal(string: "-0") == Decimal.zero) + #expect(Decimal(string: "+0") == Decimal.zero) + #expect(Decimal(string: "-0.") == Decimal.zero) + #expect(Decimal(string: "+0.") == Decimal.zero) + #expect(Decimal(string: "e1") == Decimal.zero) + #expect(Decimal(string: "e-5") == Decimal.zero) + #expect(Decimal(string: ".3e1") == Decimal(3)) - XCTAssertEqual(Decimal(string: "."), Decimal.zero) - XCTAssertEqual(Decimal(string: ".", locale: en_US), Decimal.zero) - XCTAssertNil(Decimal(string: ".", locale: fr_FR)) + #expect(Decimal(string: ".") == Decimal.zero) + #expect(Decimal(string: ".", locale: en_US) == Decimal.zero) + #expect(Decimal(string: ".", locale: fr_FR) == nil) - XCTAssertNil(Decimal(string: ",")) - XCTAssertEqual(Decimal(string: ",", locale: fr_FR), Decimal.zero) - XCTAssertNil(Decimal(string: ",", locale: en_US)) + #expect(Decimal(string: ",") == nil) + #expect(Decimal(string: ",", locale: fr_FR) == Decimal.zero) + #expect(Decimal(string: ",", locale: en_US) == nil) let s1 = "1234.5678" - XCTAssertEqual(Decimal(string: s1, locale: en_US)?.description, s1) - XCTAssertEqual(Decimal(string: s1, locale: fr_FR)?.description, "1234") + #expect(Decimal(string: s1, locale: en_US)?.description == s1) + #expect(Decimal(string: s1, locale: fr_FR)?.description == "1234") let s2 = "1234,5678" - XCTAssertEqual(Decimal(string: s2, locale: en_US)?.description, "1234") - XCTAssertEqual(Decimal(string: s2, locale: fr_FR)?.description, s1) + #expect(Decimal(string: s2, locale: en_US)?.description == "1234") + #expect(Decimal(string: s2, locale: fr_FR)?.description == s1) + } + + @Test func descriptionWithLocale() throws { + let decimal = Decimal(string: "-123456.789")! + #expect(decimal._toString(withDecimalSeparator: ".") == "-123456.789") + let en = decimal._toString(withDecimalSeparator: try #require(Locale(identifier: "en_GB").decimalSeparator)) + #expect(en == "-123456.789") + let fr = decimal._toString(withDecimalSeparator: try #require(Locale(identifier: "fr_FR").decimalSeparator)) + #expect(fr == "-123456,789") } } diff --git a/Tests/FoundationInternationalizationTests/DurationExtensionTests.swift b/Tests/FoundationInternationalizationTests/DurationExtensionTests.swift index 75235ab31..14d878ea5 100644 --- a/Tests/FoundationInternationalizationTests/DurationExtensionTests.swift +++ b/Tests/FoundationInternationalizationTests/DurationExtensionTests.swift @@ -10,31 +10,28 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) @testable import FoundationEssentials @testable import FoundationInternationalization -#endif - -#if FOUNDATION_FRAMEWORK +#elseif FOUNDATION_FRAMEWORK @testable import Foundation #endif -final class DurationExtensionTests : XCTestCase { +@Suite("Duration Extension") +private struct DurationExtensionTests { - func testRoundingMode() { + @Test func roundingMode() { - func verify(_ tests: [Int64], increment: Int64, expected: [FloatingPointRoundingRule: [Int64]], file: StaticString = #filePath, line: UInt = #line) { + func verify(_ tests: [Int64], increment: Int64, expected: [FloatingPointRoundingRule: [Int64]], sourceLocation: SourceLocation = #_sourceLocation) { let modes: [FloatingPointRoundingRule] = [.down, .up, .towardZero, .awayFromZero, .toNearestOrEven, .toNearestOrAwayFromZero] for mode in modes { var actual: [Duration] = [] for test in tests { actual.append(Duration.seconds(test).rounded(increment: Duration.seconds(increment), rule: mode)) } - XCTAssertEqual(actual, expected[mode]?.map { Duration.seconds($0) }, "\(mode) does not match", file: file, line: line) + #expect(actual == expected[mode]?.map { Duration.seconds($0) }, "\(mode) does not match", sourceLocation: sourceLocation) } } diff --git a/Tests/FoundationInternationalizationTests/Formatting/DateIntervalFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/DateIntervalFormatStyleTests.swift index d00b4c57b..6ed9c6129 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/DateIntervalFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/DateIntervalFormatStyleTests.swift @@ -5,129 +5,124 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_intero -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) @testable import FoundationEssentials @testable import FoundationInternationalization -#endif - -#if FOUNDATION_FRAMEWORK +#elseif FOUNDATION_FRAMEWORK @testable import Foundation #endif -final class DateIntervalFormatStyleTests: XCTestCase { - +@Suite("Date.IntervalFormatStyle") +private struct DateIntervalFormatStyleTests { + let minute: TimeInterval = 60 let hour: TimeInterval = 60 * 60 let day: TimeInterval = 60 * 60 * 24 let enUSLocale = Locale(identifier: "en_US") - let calendar = Calendar(identifier: .gregorian) + let calendar = { + var c = Calendar(identifier: .gregorian) + c.timeZone = .gmt + return c + }() let timeZone = TimeZone(abbreviation: "GMT")! - + let date = Date(timeIntervalSinceReferenceDate: 0) - - let expectedSeparator = "\u{202f}" - - func testDefaultFormatStyle() throws { + + @Test func defaultFormatStyle() throws { var style = Date.IntervalFormatStyle() style.timeZone = timeZone // Make sure the default style does produce some output - XCTAssertGreaterThan(style.format(date ..< date + hour).count, 0) + #expect(style.format(date ..< date + hour).count > 0) } - - func testBasicFormatStyle() throws { + + @Test func basicFormatStyle() throws { let style = Date.IntervalFormatStyle(locale: enUSLocale, calendar: calendar, timeZone: timeZone) - XCTAssertEqual(style.format(date.. (Date.IntervalFormatStyle)) { - + func verify(_ tests: (locale: Locale, expected: String, expectedAfternoon: String)..., sourceLocation: SourceLocation = #_sourceLocation, customStyle: (Date.IntervalFormatStyle) -> (Date.IntervalFormatStyle)) { + let style = customStyle(Date.IntervalFormatStyle(locale: enUSLocale, calendar: calendar, timeZone: timeZone)) for (i, (locale, expected, expectedAfternoon)) in tests.enumerated() { let localizedStyle = style.locale(locale) - XCTAssertEqual(localizedStyle.format(range), expected, file: file, line: line + UInt(i)) - XCTAssertEqual(localizedStyle.format(afternoon), expectedAfternoon, file: file, line: line + UInt(i)) + var loc = sourceLocation + loc.line += i + #expect(localizedStyle.format(range) == expected, sourceLocation: loc) + #expect(localizedStyle.format(afternoon) == expectedAfternoon, sourceLocation: loc) } } verify((default12, "12:00 – 1:00 AM", "1:00 – 3:00 PM"), @@ -164,36 +161,36 @@ final class DateIntervalFormatStyleTests: XCTestCase { style.hour().minute() } - verify((default24, "00:00 – 01:00", "13:00 – 15:00"), - (default24force12, "12:00 – 1:00 AM", "1:00 – 3:00 PM")) { style in + verify((default24, "00:00 – 01:00", "13:00 – 15:00"), + (default24force12, "12:00 – 1:00 AM", "1:00 – 3:00 PM")) { style in style.hour().minute() } - + #if FIXED_96909465 // ICU does not yet support two-digit hour configuration - verify((default12, "12:00 – 1:00 AM", "01:00 – 03:00 PM"), + verify((default12, "12:00 – 1:00 AM", "01:00 – 03:00 PM"), (default12force24, "00:00 – 01:00", "13:00 – 15:00"), (default24, "00:00 – 01:00", "13:00 – 15:00"), - (default24force12, "12:00 – 1:00 AM", "01:00 – 03:00 PM")) { style in + (default24force12, "12:00 – 1:00 AM", "01:00 – 03:00 PM")) { style in style.hour(.twoDigits(amPM: .abbreviated)).minute() } #endif - + verify((default12, "12:00 – 1:00", "1:00 – 3:00"), (default12force24, "00:00 – 01:00", "13:00 – 15:00")) { style in style.hour(.twoDigits(amPM: .omitted)).minute() } - + verify((default24, "00:00 – 01:00", "13:00 – 15:00")) { style in style.hour(.twoDigits(amPM: .omitted)).minute() } - + #if FIXED_97447020 verify() { style in style.hour(.twoDigits(amPM: .omitted)).minute() } #endif - + verify((default12, "Jan 1, 12:00 – 1:00 AM", "Jan 1, 1:00 – 3:00 PM"), (default12force24, "Jan 1, 00:00 – 01:00", "Jan 1, 13:00 – 15:00"), (default24, "1 Jan, 00:00 – 01:00", "1 Jan, 13:00 – 15:00"), @@ -202,28 +199,30 @@ final class DateIntervalFormatStyleTests: XCTestCase { } } #endif // FIXED_ICU_74_DAYPERIOD - - func testAutoupdatingCurrentChangesFormatResults() { - let locale = Locale.autoupdatingCurrent - let range = Date.now..<(Date.now + 3600) - - // Get a formatted result from es-ES - var prefs = LocalePreferences() - prefs.languages = ["es-ES"] - prefs.locale = "es_ES" - LocaleCache.cache.resetCurrent(to: prefs) - let formattedSpanish = range.formatted(.interval.locale(locale)) - - // Get a formatted result from en-US - prefs.languages = ["en-US"] - prefs.locale = "en_US" - LocaleCache.cache.resetCurrent(to: prefs) - let formattedEnglish = range.formatted(.interval.locale(locale)) - - // Reset to current preferences before any possibility of failing this test - LocaleCache.cache.reset() - - // No matter what 'current' was before this test was run, formattedSpanish and formattedEnglish should be different. - XCTAssertNotEqual(formattedSpanish, formattedEnglish) + + @Test func testAutoupdatingCurrentChangesFormatResults() async { + await usingCurrentInternationalizationPreferences { + let locale = Locale.autoupdatingCurrent + let range = Date.now..<(Date.now + 3600) + + // Get a formatted result from es-ES + var prefs = LocalePreferences() + prefs.languages = ["es-ES"] + prefs.locale = "es_ES" + LocaleCache.cache.resetCurrent(to: prefs) + let formattedSpanish = range.formatted(.interval.locale(locale)) + + // Get a formatted result from en-US + prefs.languages = ["en-US"] + prefs.locale = "en_US" + LocaleCache.cache.resetCurrent(to: prefs) + let formattedEnglish = range.formatted(.interval.locale(locale)) + + // Reset to current preferences before any possibility of failing this test + LocaleCache.cache.reset() + + // No matter what 'current' was before this test was run, formattedSpanish and formattedEnglish should be different. + #expect(formattedSpanish != formattedEnglish) + } } } diff --git a/Tests/FoundationInternationalizationTests/Formatting/FormatterCacheTests.swift b/Tests/FoundationInternationalizationTests/Formatting/FormatterCacheTests.swift index e5e463ad1..450267995 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/FormatterCacheTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/FormatterCacheTests.swift @@ -5,25 +5,18 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_interop -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) @testable import FoundationEssentials @testable import FoundationInternationalization -#endif - -#if FOUNDATION_FRAMEWORK +#elseif FOUNDATION_FRAMEWORK @testable import Foundation #endif -final class FormatterCacheTests: XCTestCase { +@Suite("FormatterCache") +private struct FormatterCacheTests { final class TestCacheItem: Equatable, Sendable { static func == (lhs: FormatterCacheTests.TestCacheItem, rhs: FormatterCacheTests.TestCacheItem) -> Bool { @@ -43,7 +36,7 @@ final class FormatterCacheTests: XCTestCase { } - func testCreateItem() { + @Test func createItem() { let cache = FormatterCache() var initializerBlockInvocationCount = 0 @@ -54,20 +47,20 @@ final class FormatterCacheTests: XCTestCase { initializerBlockInvocationCount += 1 return -i } - XCTAssertEqual(item, -i) + #expect(item == -i) } // `creator` block has been called 101 times - XCTAssertEqual(initializerBlockInvocationCount, cache.countLimit + 1) + #expect(initializerBlockInvocationCount == cache.countLimit + 1) // `creator` block does not get executed when the key exists for i in 0..() - let group = DispatchGroup() - let queue = DispatchQueue(label: "formatter cache test", qos: .default, attributes: .concurrent) - - - for i in 0 ..< 5 { - queue.async(group: group) { - let cached = cache.formatter(for: i) { - return .init(value: -i, deinitBlock: { - // Test that `removeAllObjects` beneath does not trigger `deinit` of the removed objects in the locked scope. - // If it does cause the deinitialization of this instance where this block is run, we would deadlock here because the subscript getter is performed in the same locked scope as the enclosing `formatter(for:creator:)`. - _ = cache[i] - }) - } - XCTAssertEqual(cached.value, -i) - } - queue.async(group: group) { - cache.removeAllObjects() + await withDiscardingTaskGroup { group in + for i in 0 ..< 5 { + group.addTask { + let cached = cache.formatter(for: i) { + return .init(value: -i, deinitBlock: { + // Test that `removeAllObjects` beneath does not trigger `deinit` of the removed objects in the locked scope. + // If it does cause the deinitialization of this instance where this block is run, we would deadlock here because the subscript getter is performed in the same locked scope as the enclosing `formatter(for:creator:)`. + _ = cache[i] + }) + } + #expect(cached.value == -i) + } + + group.addTask { + cache.removeAllObjects() + } } } - - XCTAssertEqual(group.wait(timeout: .now().advanced(by: .seconds(3))), .success) } -#endif // FOUNDATION_FRAMEWORK } diff --git a/Tests/FoundationInternationalizationTests/Formatting/ICUPatternGeneratorTests.swift b/Tests/FoundationInternationalizationTests/Formatting/ICUPatternGeneratorTests.swift index ffebfe7ba..beb04a4f8 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/ICUPatternGeneratorTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/ICUPatternGeneratorTests.swift @@ -6,33 +6,30 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) @testable import FoundationEssentials @testable import FoundationInternationalization -#endif - -#if FOUNDATION_FRAMEWORK +#elseif FOUNDATION_FRAMEWORK @testable import Foundation #endif -final class ICUPatternGeneratorTests: XCTestCase { +@Suite("ICUPatternGenerator") +private struct ICUPatternGeneratorTests { typealias DateFieldCollection = Date.FormatStyle.DateFieldCollection - func testConversationalDayPeriodsOverride() { + @Test func conversationalDayPeriodsOverride() { var locale: Locale var calendar: Calendar - func test(symbols: Date.FormatStyle.DateFieldCollection, expectedPattern: String, file: StaticString = #filePath, line: UInt = #line) { + func test(symbols: Date.FormatStyle.DateFieldCollection, expectedPattern: String, sourceLocation: SourceLocation = #_sourceLocation) { let pattern = ICUPatternGenerator.localizedPattern(symbols: symbols, locale: locale, calendar: calendar) - XCTAssertEqual(pattern, expectedPattern, file: file, line: line) + #expect(pattern == expectedPattern, sourceLocation: sourceLocation) // We should not see any kind of day period designator ("a" or "B") when showing 24-hour hour ("H"). if (expectedPattern.contains("H") || pattern.contains("H")) && (pattern.contains("a") || pattern.contains("B")) { - XCTFail("Pattern should not contain day period", file: file, line: line) + Issue.record("Pattern should not contain day period", sourceLocation: sourceLocation) } } @@ -40,6 +37,7 @@ final class ICUPatternGeneratorTests: XCTestCase { do { locale = Locale(identifier: "zh_TW") calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .gmt test(symbols: .init(year: .defaultDigits, month: .defaultDigits, day: .defaultDigits, hour: .defaultDigitsWithWideAMPM), expectedPattern: "y/M/d BBBBh時") @@ -83,6 +81,7 @@ final class ICUPatternGeneratorTests: XCTestCase { do { locale = Locale(identifier: "zh_TW") calendar = Calendar(identifier: .republicOfChina) + calendar.timeZone = .gmt test(symbols: .init(year: .defaultDigits, month: .defaultDigits, day: .defaultDigits, hour: .defaultDigitsWithWideAMPM), expectedPattern: "G y/M/d BBBBh時") @@ -113,6 +112,7 @@ final class ICUPatternGeneratorTests: XCTestCase { do { locale = Locale(identifier: "zh_TW") calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .gmt test(symbols: .init(year: .defaultDigits, month: .defaultDigits, day: .defaultDigits, hour: .defaultDigitsWithWideAMPM), expectedPattern: "y/M/d BBBBh時") @@ -159,6 +159,7 @@ final class ICUPatternGeneratorTests: XCTestCase { locale = Locale(components: localeUsing24hour) calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .gmt test(symbols: .init(year: .defaultDigits, month: .defaultDigits, day: .defaultDigits, hour: .defaultDigitsWithWideAMPM), expectedPattern: "y/M/d H時") @@ -201,6 +202,7 @@ final class ICUPatternGeneratorTests: XCTestCase { do { locale = Locale(identifier: "zh_HK") calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .gmt test(symbols: .init(year: .defaultDigits, month: .defaultDigits, day: .defaultDigits, hour: .defaultDigitsWithWideAMPM), expectedPattern: "d/M/y aaaah時") @@ -243,6 +245,7 @@ final class ICUPatternGeneratorTests: XCTestCase { // So there should be no "B" in the pattern locale = Locale(identifier: "en_TW") calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .gmt test(symbols: .init(hour: .defaultDigitsWithAbbreviatedAMPM, minute: .defaultDigits), expectedPattern: "h:mm a") diff --git a/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleICUSkeletonTests.swift b/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleICUSkeletonTests.swift index 0d9ca0637..ea89d6212 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleICUSkeletonTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleICUSkeletonTests.swift @@ -5,128 +5,121 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_interop -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) @testable import FoundationEssentials @testable import FoundationInternationalization -#endif - -#if FOUNDATION_FRAMEWORK +#elseif FOUNDATION_FRAMEWORK @testable import Foundation #endif -final class NumberFormatStyleICUSkeletonTests: XCTestCase { +@Suite("NumberFormatStyle ICU Skeleton") +private struct NumberFormatStyleICUSkeletonTests { - func testNumberConfigurationSkeleton() throws { + @Test func numberConfigurationSkeleton() throws { typealias Configuration = NumberFormatStyleConfiguration.Collection - XCTAssertEqual(Configuration().skeleton, "") + #expect(Configuration().skeleton == "") } - func testPrecisionSkeleton() throws { + @Test func precisionSkeleton() throws { typealias Precision = NumberFormatStyleConfiguration.Precision - XCTAssertEqual(Precision.significantDigits(3...3).skeleton, "@@@") - XCTAssertEqual(Precision.significantDigits(2...).skeleton, "@@+") - XCTAssertEqual(Precision.significantDigits(...4).skeleton, "@###") - XCTAssertEqual(Precision.significantDigits(3...4).skeleton, "@@@#") + #expect(Precision.significantDigits(3...3).skeleton == "@@@") + #expect(Precision.significantDigits(2...).skeleton == "@@+") + #expect(Precision.significantDigits(...4).skeleton == "@###") + #expect(Precision.significantDigits(3...4).skeleton == "@@@#") // Invalid configuration. We'll force at least one significant digits. - XCTAssertEqual(Precision.significantDigits(0...0).skeleton, "@") - XCTAssertEqual(Precision.significantDigits(...0).skeleton, "@") - - XCTAssertEqual(Precision.fractionLength(...0).skeleton, "precision-integer") - XCTAssertEqual(Precision.fractionLength(0...0).skeleton, "precision-integer") - XCTAssertEqual(Precision.fractionLength(3...3).skeleton, ".000") - XCTAssertEqual(Precision.fractionLength(1...).skeleton, ".0+") - XCTAssertEqual(Precision.fractionLength(...1).skeleton, ".#") - XCTAssertEqual(Precision.fractionLength(1...3).skeleton, ".0##") - - XCTAssertEqual(Precision.integerLength(0...).skeleton, "integer-width/+") - XCTAssertEqual(Precision.integerLength(1...).skeleton, "integer-width/+0") - XCTAssertEqual(Precision.integerLength(3...).skeleton, "integer-width/+000") - XCTAssertEqual(Precision.integerLength(1...3).skeleton, "integer-width/##0") - XCTAssertEqual(Precision.integerLength(2...2).skeleton, "integer-width/00") - XCTAssertEqual(Precision.integerLength(...3).skeleton, "integer-width/###") + #expect(Precision.significantDigits(0...0).skeleton == "@") + #expect(Precision.significantDigits(...0).skeleton == "@") + + #expect(Precision.fractionLength(...0).skeleton == "precision-integer") + #expect(Precision.fractionLength(0...0).skeleton == "precision-integer") + #expect(Precision.fractionLength(3...3).skeleton == ".000") + #expect(Precision.fractionLength(1...).skeleton == ".0+") + #expect(Precision.fractionLength(...1).skeleton == ".#") + #expect(Precision.fractionLength(1...3).skeleton == ".0##") + + #expect(Precision.integerLength(0...).skeleton == "integer-width/+") + #expect(Precision.integerLength(1...).skeleton == "integer-width/+0") + #expect(Precision.integerLength(3...).skeleton == "integer-width/+000") + #expect(Precision.integerLength(1...3).skeleton == "integer-width/##0") + #expect(Precision.integerLength(2...2).skeleton == "integer-width/00") + #expect(Precision.integerLength(...3).skeleton == "integer-width/###") // Special case - XCTAssertEqual(Precision.integerLength(...0).skeleton, "integer-width/*") + #expect(Precision.integerLength(...0).skeleton == "integer-width/*") } - func testSignDisplaySkeleton() throws { + @Test func signDisplaySkeleton() throws { typealias SignDisplay = NumberFormatStyleConfiguration.SignDisplayStrategy - XCTAssertEqual(SignDisplay.never.skeleton, "sign-never") - XCTAssertEqual(SignDisplay.always().skeleton, "sign-always") - XCTAssertEqual(SignDisplay.always(includingZero: true).skeleton, "sign-always") - XCTAssertEqual(SignDisplay.always(includingZero: false).skeleton, "sign-except-zero") - XCTAssertEqual(SignDisplay.automatic.skeleton, "sign-auto") + #expect(SignDisplay.never.skeleton == "sign-never") + #expect(SignDisplay.always().skeleton == "sign-always") + #expect(SignDisplay.always(includingZero: true).skeleton == "sign-always") + #expect(SignDisplay.always(includingZero: false).skeleton == "sign-except-zero") + #expect(SignDisplay.automatic.skeleton == "sign-auto") } - func testCurrencySkeleton() throws { + @Test func currencySkeleton() throws { typealias SignDisplay = CurrencyFormatStyleConfiguration.SignDisplayStrategy - XCTAssertEqual(SignDisplay.automatic.skeleton, "sign-auto") - XCTAssertEqual(SignDisplay.always().skeleton, "sign-always") - XCTAssertEqual(SignDisplay.always(showZero: true).skeleton, "sign-always") - XCTAssertEqual(SignDisplay.always(showZero: false).skeleton, "sign-except-zero") - XCTAssertEqual(SignDisplay.accounting.skeleton, "sign-accounting") - XCTAssertEqual(SignDisplay.accountingAlways().skeleton, "sign-accounting-except-zero") - XCTAssertEqual(SignDisplay.accountingAlways(showZero: true).skeleton, "sign-accounting-always") - XCTAssertEqual(SignDisplay.accountingAlways(showZero: false).skeleton, "sign-accounting-except-zero") - XCTAssertEqual(SignDisplay.never.skeleton, "sign-never") + #expect(SignDisplay.automatic.skeleton == "sign-auto") + #expect(SignDisplay.always().skeleton == "sign-always") + #expect(SignDisplay.always(showZero: true).skeleton == "sign-always") + #expect(SignDisplay.always(showZero: false).skeleton == "sign-except-zero") + #expect(SignDisplay.accounting.skeleton == "sign-accounting") + #expect(SignDisplay.accountingAlways().skeleton == "sign-accounting-except-zero") + #expect(SignDisplay.accountingAlways(showZero: true).skeleton == "sign-accounting-always") + #expect(SignDisplay.accountingAlways(showZero: false).skeleton == "sign-accounting-except-zero") + #expect(SignDisplay.never.skeleton == "sign-never") let style: IntegerFormatStyle.Currency = .init(code: "USD", locale: Locale(identifier: "en_US")) let formatter = ICUCurrencyNumberFormatter.create(for: style)! - XCTAssertEqual(formatter.skeleton, "currency/USD unit-width-short") + #expect(formatter.skeleton == "currency/USD unit-width-short") let accountingStyle = style.sign(strategy: .accounting) let accountingFormatter = ICUCurrencyNumberFormatter.create(for: accountingStyle)! - XCTAssertEqual(accountingFormatter.skeleton, "currency/USD unit-width-short sign-accounting") + #expect(accountingFormatter.skeleton == "currency/USD unit-width-short sign-accounting") let isoCodeStyle = style.sign(strategy: .never).presentation(.isoCode) let isoCodeFormatter = ICUCurrencyNumberFormatter.create(for: isoCodeStyle)! - XCTAssertEqual(isoCodeFormatter.skeleton, "currency/USD unit-width-iso-code sign-never") + #expect(isoCodeFormatter.skeleton == "currency/USD unit-width-iso-code sign-never") } - func testStyleSkeleton_integer_precisionAndRounding() throws { + @Test func styleSkeleton_integer_precisionAndRounding() throws { let style: IntegerFormatStyle = .init(locale: Locale(identifier: "en_US")) - XCTAssertEqual(style.precision(.fractionLength(3...3)).rounded(increment: 5).collection.skeleton, "precision-increment/5.000 rounding-mode-half-even") - XCTAssertEqual(style.precision(.fractionLength(3...)).rounded(increment: 5).collection.skeleton, "precision-increment/5.000 rounding-mode-half-even") - XCTAssertEqual(style.precision(.fractionLength(...3)).rounded(increment: 5).collection.skeleton, "precision-increment/5 rounding-mode-half-even") + #expect(style.precision(.fractionLength(3...3)).rounded(increment: 5).collection.skeleton == "precision-increment/5.000 rounding-mode-half-even") + #expect(style.precision(.fractionLength(3...)).rounded(increment: 5).collection.skeleton == "precision-increment/5.000 rounding-mode-half-even") + #expect(style.precision(.fractionLength(...3)).rounded(increment: 5).collection.skeleton == "precision-increment/5 rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerLength(2...2)).rounded(increment: 5).collection.skeleton, "precision-increment/5 integer-width/00 rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerLength(2...)).rounded(increment: 5).collection.skeleton, "precision-increment/5 integer-width/+00 rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerLength(...2)).rounded(increment: 5).collection.skeleton, "precision-increment/5 integer-width/## rounding-mode-half-even") + #expect(style.precision(.integerLength(2...2)).rounded(increment: 5).collection.skeleton == "precision-increment/5 integer-width/00 rounding-mode-half-even") + #expect(style.precision(.integerLength(2...)).rounded(increment: 5).collection.skeleton == "precision-increment/5 integer-width/+00 rounding-mode-half-even") + #expect(style.precision(.integerLength(...2)).rounded(increment: 5).collection.skeleton == "precision-increment/5 integer-width/## rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerAndFractionLength(integerLimits: 2...2, fractionLimits: 3...3)).rounded(increment: 5).collection.skeleton, "precision-increment/5.000 integer-width/00 rounding-mode-half-even") + #expect(style.precision(.integerAndFractionLength(integerLimits: 2...2, fractionLimits: 3...3)).rounded(increment: 5).collection.skeleton == "precision-increment/5.000 integer-width/00 rounding-mode-half-even") } - func testStyleSkeleton_floatingPoint_precisionAndRounding() throws { + @Test func styleSkeleton_floatingPoint_precisionAndRounding() throws { let style: FloatingPointFormatStyle = .init(locale: Locale(identifier: "en_US")) - XCTAssertEqual(style.precision(.fractionLength(3...3)).rounded(increment: 0.314).collection.skeleton, "precision-increment/0.314 rounding-mode-half-even") - XCTAssertEqual(style.precision(.fractionLength(3...)).rounded(increment: 0.314).collection.skeleton, "precision-increment/0.314 rounding-mode-half-even") - XCTAssertEqual(style.precision(.fractionLength(...3)).rounded(increment: 0.314).collection.skeleton, "precision-increment/0.314 rounding-mode-half-even") - XCTAssertEqual(style.precision(.fractionLength(...1)).rounded(increment: 0.314).collection.skeleton, "precision-increment/0.314 rounding-mode-half-even") + #expect(style.precision(.fractionLength(3...3)).rounded(increment: 0.314).collection.skeleton == "precision-increment/0.314 rounding-mode-half-even") + #expect(style.precision(.fractionLength(3...)).rounded(increment: 0.314).collection.skeleton == "precision-increment/0.314 rounding-mode-half-even") + #expect(style.precision(.fractionLength(...3)).rounded(increment: 0.314).collection.skeleton == "precision-increment/0.314 rounding-mode-half-even") + #expect(style.precision(.fractionLength(...1)).rounded(increment: 0.314).collection.skeleton == "precision-increment/0.314 rounding-mode-half-even") - XCTAssertEqual(style.precision(.fractionLength(3...3)).rounded(increment: 0.3).collection.skeleton, "precision-increment/0.300 rounding-mode-half-even") - XCTAssertEqual(style.precision(.fractionLength(3...)).rounded(increment: 0.3).collection.skeleton, "precision-increment/0.300 rounding-mode-half-even") + #expect(style.precision(.fractionLength(3...3)).rounded(increment: 0.3).collection.skeleton == "precision-increment/0.300 rounding-mode-half-even") + #expect(style.precision(.fractionLength(3...)).rounded(increment: 0.3).collection.skeleton == "precision-increment/0.300 rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerLength(2...2)).rounded(increment: 0.314).collection.skeleton, "precision-increment/0.314 integer-width/00 rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerLength(2...)).rounded(increment: 0.314).collection.skeleton, "precision-increment/0.314 integer-width/+00 rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerLength(...2)).rounded(increment: 0.314).collection.skeleton, "precision-increment/0.314 integer-width/## rounding-mode-half-even") + #expect(style.precision(.integerLength(2...2)).rounded(increment: 0.314).collection.skeleton == "precision-increment/0.314 integer-width/00 rounding-mode-half-even") + #expect(style.precision(.integerLength(2...)).rounded(increment: 0.314).collection.skeleton == "precision-increment/0.314 integer-width/+00 rounding-mode-half-even") + #expect(style.precision(.integerLength(...2)).rounded(increment: 0.314).collection.skeleton == "precision-increment/0.314 integer-width/## rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerAndFractionLength(integerLimits: 2...2, fractionLimits: 3...3)).rounded(increment: 0.314).collection.skeleton, "precision-increment/0.314 integer-width/00 rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerAndFractionLength(integerLimits: 2..., fractionLimits: 3...3)).rounded(increment: 0.314).collection.skeleton, "precision-increment/0.314 integer-width/+00 rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerAndFractionLength(integerLimits: ...2, fractionLimits: 3...3)).rounded(increment: 0.314).collection.skeleton, "precision-increment/0.314 integer-width/## rounding-mode-half-even") + #expect(style.precision(.integerAndFractionLength(integerLimits: 2...2, fractionLimits: 3...3)).rounded(increment: 0.314).collection.skeleton == "precision-increment/0.314 integer-width/00 rounding-mode-half-even") + #expect(style.precision(.integerAndFractionLength(integerLimits: 2..., fractionLimits: 3...3)).rounded(increment: 0.314).collection.skeleton == "precision-increment/0.314 integer-width/+00 rounding-mode-half-even") + #expect(style.precision(.integerAndFractionLength(integerLimits: ...2, fractionLimits: 3...3)).rounded(increment: 0.314).collection.skeleton == "precision-increment/0.314 integer-width/## rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerAndFractionLength(integerLimits: 2...2, fractionLimits: 3...3)).rounded(increment: 0.3).collection.skeleton, "precision-increment/0.300 integer-width/00 rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerAndFractionLength(integerLimits: 2..., fractionLimits: 3...3)).rounded(increment: 0.3).collection.skeleton, "precision-increment/0.300 integer-width/+00 rounding-mode-half-even") - XCTAssertEqual(style.precision(.integerAndFractionLength(integerLimits: ...2, fractionLimits: 3...3)).rounded(increment: 0.3).collection.skeleton, "precision-increment/0.300 integer-width/## rounding-mode-half-even") + #expect(style.precision(.integerAndFractionLength(integerLimits: 2...2, fractionLimits: 3...3)).rounded(increment: 0.3).collection.skeleton == "precision-increment/0.300 integer-width/00 rounding-mode-half-even") + #expect(style.precision(.integerAndFractionLength(integerLimits: 2..., fractionLimits: 3...3)).rounded(increment: 0.3).collection.skeleton == "precision-increment/0.300 integer-width/+00 rounding-mode-half-even") + #expect(style.precision(.integerAndFractionLength(integerLimits: ...2, fractionLimits: 3...3)).rounded(increment: 0.3).collection.skeleton == "precision-increment/0.300 integer-width/## rounding-mode-half-even") } } diff --git a/Tests/FoundationInternationalizationTests/Formatting/ParseStrategy+RegexComponentTests.swift b/Tests/FoundationInternationalizationTests/Formatting/ParseStrategy+RegexComponentTests.swift index 0b9019c8f..6e684a957 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/ParseStrategy+RegexComponentTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/ParseStrategy+RegexComponentTests.swift @@ -5,42 +5,39 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_interop import RegexBuilder +import Testing -#if canImport(TestSupport) -import TestSupport +#if canImport(FoundationInternationalization) +import FoundationEssentials +import FoundationInternationalization +#elseif FOUNDATION_FRAMEWORK +import Foundation #endif -final class ParseStrategyMatchTests: XCTestCase { - +@Suite("ParseStrategy Match") +private struct ParseStrategyMatchTests { let enUS = Locale(identifier: "en_US") let enGB = Locale(identifier: "en_GB") let gmt = TimeZone(secondsFromGMT: 0)! let pst = TimeZone(secondsFromGMT: -3600*8)! - func testDate() { + @Test func date() throws { let regex = Regex { OneOrMore { Capture { Date.ISO8601FormatStyle() } } } - guard let res = "💁🏽🏳️‍🌈2021-07-01T15:56:32Z".firstMatch(of: regex) else { - XCTFail() - return - } + let res = try #require("💁🏽🏳️‍🌈2021-07-01T15:56:32Z".firstMatch(of: regex)) - XCTAssertEqual(res.output.0, "2021-07-01T15:56:32Z") + #expect(res.output.0 == "2021-07-01T15:56:32Z") // dateFormatter.date(from: "2021-07-01 15:56:32.000")! - XCTAssertEqual(res.output.1, Date(timeIntervalSinceReferenceDate: 646847792.0)) + #expect(res.output.1 == Date(timeIntervalSinceReferenceDate: 646847792.0)) } - func testAPIHTTPHeader() { + @Test func apiHTTPHeader() throws { let header = """ HTTP/1.1 301 Redirect @@ -57,20 +54,17 @@ final class ParseStrategyMatchTests: XCTestCase { } } - guard let res = header.firstMatch(of: regex) else { - XCTFail() - return - } + let res = try #require(header.firstMatch(of: regex)) // dateFormatter.date(from: "2022-02-16 00:00:00.000")! let expectedDate = Date(timeIntervalSinceReferenceDate: 666662400.0) - XCTAssertEqual(res.output.0, "16 Feb 2022") - XCTAssertEqual(res.output.1, expectedDate) + #expect(res.output.0 == "16 Feb 2022") + #expect(res.output.1 == expectedDate) } // https://github.com/apple/swift-foundation/issues/60 #if FOUNDATION_FRAMEWORK - func testAPIStatement() { + @Test func apiStatement() { let statement = """ CREDIT 04/06/2020 Paypal transfer $4.99 @@ -100,8 +94,8 @@ DEBIT 03/24/2020 IRX tax payment ($52,249.98) let money = statement.matches(of: regex) - XCTAssertEqual(money.map(\.output.0), ["$4.99", "$3,020.85", "$69.73", "($38.25)", "($27.44)", "($52,249.98)"]) - XCTAssertEqual(money.map(\.output.1), expectedAmounts) + #expect(money.map(\.output.0) == ["$4.99", "$3,020.85", "$69.73", "($38.25)", "($27.44)", "($52,249.98)"]) + #expect(money.map(\.output.1) == expectedAmounts) let dateRegex = Regex { Capture { @@ -109,8 +103,8 @@ DEBIT 03/24/2020 IRX tax payment ($52,249.98) } } let dateMatches = statement.matches(of: dateRegex) - XCTAssertEqual(dateMatches.map(\.output.0), expectedDateStrings) - XCTAssertEqual(dateMatches.map(\.output.1), expectedDates) + #expect(dateMatches.map(\.output.0) == expectedDateStrings) + #expect(dateMatches.map(\.output.1) == expectedDates) let dot = try! Regex(#"."#) let dateCurrencyRegex = Regex { @@ -126,7 +120,7 @@ DEBIT 03/24/2020 IRX tax payment ($52,249.98) } let matches = statement.matches(of: dateCurrencyRegex) - XCTAssertEqual(matches.map(\.output.0), [ + #expect(matches.map(\.output.0) == [ "04/06/2020 Paypal transfer $4.99", "04/06/2020 REMOTE ONLINE DEPOSIT $3,020.85", "04/03/2020 PAYROLL $69.73", @@ -134,18 +128,18 @@ DEBIT 03/24/2020 IRX tax payment ($52,249.98) "03/31/2020 Payment to BoA card ($27.44)", "03/24/2020 IRX tax payment ($52,249.98)", ]) - XCTAssertEqual(matches.map(\.output.1), expectedDates) - XCTAssertEqual(matches.map(\.output.2), expectedAmounts) + #expect(matches.map(\.output.1) == expectedDates) + #expect(matches.map(\.output.2) == expectedAmounts) let numericMatches = statement.matches(of: Regex { Capture(.date(.numeric, locale: enUS, timeZone: gmt)) }) - XCTAssertEqual(numericMatches.map(\.output.0), expectedDateStrings) - XCTAssertEqual(numericMatches.map(\.output.1), expectedDates) + #expect(numericMatches.map(\.output.0) == expectedDateStrings) + #expect(numericMatches.map(\.output.1) == expectedDates) } - func testAPIStatements2() { + @Test func apiStatements2() { // Test dates and numbers appearing in unexpeted places let statement = """ CREDIT Apr 06/20 Zombie 5.29lb@$3.99/lb USD 21.11 @@ -178,18 +172,18 @@ DEBIT Mar 31/20 March Payment to BoA -USD 52,249.98 let expectedAmounts = [Decimal(string:"21.11")!, Decimal(string:"3020.85")!, Decimal(string:"69.73")!, Decimal(string:"-38.25")!, Decimal(string:"-52249.98")!] let matches = statement.matches(of: dateCurrencyRegex) - XCTAssertEqual(matches.map(\.output.0), [ + #expect(matches.map(\.output.0) == [ "Apr 06/20 Zombie 5.29lb@$3.99/lb USD 21.11", "Apr 06/20 GMT gain USD 3,020.85", "Apr 03/20 PAYROLL 03/29/20-04/02/20 USD 69.73", "Apr 02/20 ACH TRNSFR Apr 02/20 -USD 38.25", "Mar 31/20 March Payment to BoA -USD 52,249.98", ]) - XCTAssertEqual(matches.map(\.output.1), expectedDates) - XCTAssertEqual(matches.map(\.output.3), expectedAmounts) + #expect(matches.map(\.output.1) == expectedDates) + #expect(matches.map(\.output.3) == expectedAmounts) } - func testAPITestSuites() { + @Test func apiTestSuites() throws { let input = "Test Suite 'MergeableSetTests' started at 2021-07-08 10:19:35.418" let testSuiteLog = Regex { @@ -213,37 +207,34 @@ DEBIT Mar 31/20 March Payment to BoA -USD 52,249.98 } - guard let match = input.wholeMatch(of: testSuiteLog) else { - XCTFail() - return - } + let match = try #require(input.wholeMatch(of: testSuiteLog)) - XCTAssertEqual(match.output.0, "Test Suite 'MergeableSetTests' started at 2021-07-08 10:19:35.418") - XCTAssertEqual(match.output.1, "MergeableSetTests") - XCTAssertEqual(match.output.2, "started") + #expect(match.output.0 == "Test Suite 'MergeableSetTests' started at 2021-07-08 10:19:35.418") + #expect(match.output.1 == "MergeableSetTests") + #expect(match.output.2 == "started") // dateFormatter.date(from: "2021-07-08 10:19:35.418")! - XCTAssertEqual(match.output.3, Date(timeIntervalSinceReferenceDate: 647432375.418)) + #expect(match.output.3 == Date(timeIntervalSinceReferenceDate: 647432375.418)) } #endif - func testVariousDatesAndTimes() { - func verify(_ str: String, _ strategy: Date.ParseStrategy, _ expected: String?, file: StaticString = #filePath, line: UInt = #line) { + @Test func variousDatesAndTimes() { + func verify(_ str: String, _ strategy: Date.ParseStrategy, _ expected: String?, sourceLocation: SourceLocation = #_sourceLocation) { let match = str.wholeMatch(of: strategy) // Regex.Match? if let expected { guard let match else { - XCTFail("<\(str)> did not match, but it should", file: file, line: line) + var explanation = "" do { _ = try strategy.parse(str) } catch { - print(error) + explanation = String(describing: error) } - + Issue.record("<\(str)> did not match, but it should: \(explanation)", sourceLocation: sourceLocation) return } let expectedDate = try! Date(expected, strategy: .iso8601) - XCTAssertEqual(match.0, expectedDate, file: file, line: line) + #expect(match.0 == expectedDate, sourceLocation: sourceLocation) } else { - XCTAssertNil(match, "<\(str)> should not match, but it did", file: file, line: line) + #expect(match == nil, "<\(str)> should not match, but it did", sourceLocation: sourceLocation) } } @@ -269,8 +260,8 @@ DEBIT Mar 31/20 March Payment to BoA -USD 52,249.98 verify("03/05/2020", .date(.numeric, locale: enGB, timeZone: pst), "2020-05-03T00:00:00-08:00") } - func testMatchISO8601String() { - func verify(_ str: String, _ strategy: Date.ISO8601FormatStyle, _ expected: String?, file: StaticString = #filePath, line: UInt = #line) { + @Test func matchISO8601String() { + func verify(_ str: String, _ strategy: Date.ISO8601FormatStyle, _ expected: String?, sourceLocation: SourceLocation = #_sourceLocation) { let match = str.wholeMatch(of: strategy) // Regex.Match? if let expected { @@ -287,13 +278,13 @@ DEBIT Mar 31/20 March Payment to BoA -USD 52,249.98 message += "error: \(error)" } - XCTFail("<\(str)> did not match, but it should. Information: \(message)", file: file, line: line) + Issue.record("<\(str)> did not match, but it should. Information: \(message)", sourceLocation: sourceLocation) return } let expectedDate = try! Date(expected, strategy: .iso8601) - XCTAssertEqual(match.0, expectedDate, file: file, line: line) + #expect(match.0 == expectedDate, sourceLocation: sourceLocation) } else { - XCTAssertNil(match, "<\(str)> should not match, but it did", file: file, line: line) + #expect(match == nil, "<\(str)> should not match, but it did", sourceLocation: sourceLocation) } } diff --git a/Tests/FoundationInternationalizationTests/GegorianCalendarInternationalizationTests.swift b/Tests/FoundationInternationalizationTests/GegorianCalendarInternationalizationTests.swift index 62a8ecab6..195c18912 100644 --- a/Tests/FoundationInternationalizationTests/GegorianCalendarInternationalizationTests.swift +++ b/Tests/FoundationInternationalizationTests/GegorianCalendarInternationalizationTests.swift @@ -23,7 +23,7 @@ import Testing @Suite("Gregorian Calendar (Internationalization)") private struct GregorianCalendarInternationalizationTests { @Test func copy() { - let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: nil, locale: nil, firstWeekday: 5, minimumDaysInFirstWeek: 3, gregorianStartDate: nil) + let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: .gmt, locale: nil, firstWeekday: 5, minimumDaysInFirstWeek: 3, gregorianStartDate: nil) let newLocale = Locale(identifier: "new locale") let tz = TimeZone(identifier: "America/Los_Angeles")! @@ -35,7 +35,7 @@ private struct GregorianCalendarInternationalizationTests { #expect(copied.firstWeekday == 5) #expect(copied.minimumDaysInFirstWeek == 3) - let copied2 = gregorianCalendar.copy(changingLocale: nil, changingTimeZone: nil, changingFirstWeekday: 1, changingMinimumDaysInFirstWeek: 1) + let copied2 = gregorianCalendar.copy(changingLocale: nil, changingTimeZone: .gmt, changingFirstWeekday: 1, changingMinimumDaysInFirstWeek: 1) // unset values stay the same #expect(copied2.locale == gregorianCalendar.locale) @@ -2464,7 +2464,7 @@ private struct GregorianCalendarInternationalizationTests { // expect local time in dc.timeZone (UTC+8) #expect(gregorianCalendar.date(from: dc_customTimeZone)! == Date(timeIntervalSinceReferenceDate: 679024975.891)) // 2022-07-09T02:02:55Z - let dcCalendar_noTimeZone = Calendar(identifier: .japanese, locale: Locale(identifier: ""), timeZone: nil, firstWeekday: 1, minimumDaysInFirstWeek: 1, gregorianStartDate: nil) + let dcCalendar_noTimeZone = Calendar(identifier: .japanese, locale: Locale(identifier: ""), timeZone: .gmt, firstWeekday: 1, minimumDaysInFirstWeek: 1, gregorianStartDate: nil) var dc_customCalendarNoTimeZone_customTimeZone = dc dc_customCalendarNoTimeZone_customTimeZone.calendar = dcCalendar_noTimeZone dc_customCalendarNoTimeZone_customTimeZone.timeZone = .init(secondsFromGMT: 28800) diff --git a/Tests/FoundationInternationalizationTests/LocaleComponentsTests.swift b/Tests/FoundationInternationalizationTests/LocaleComponentsTests.swift index 23781d851..ce05cb1d4 100644 --- a/Tests/FoundationInternationalizationTests/LocaleComponentsTests.swift +++ b/Tests/FoundationInternationalizationTests/LocaleComponentsTests.swift @@ -10,9 +10,7 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if FOUNDATION_FRAMEWORK @testable import Foundation @@ -21,103 +19,104 @@ import TestSupport @testable import FoundationInternationalization #endif // FOUNDATION_FRAMEWORK -final class LocaleComponentsTests: XCTestCase { +@Suite("Locale.Components") +private struct LocaleComponentsTests { - func testRegions() { + @Test func regions() { let region = Locale.Region("US") - XCTAssertTrue(region.isISORegion) - XCTAssertEqual(region.identifier, "US") - XCTAssertEqual(region.continent, Locale.Region("019")) - XCTAssertEqual(region.containingRegion, Locale.Region("021")) - XCTAssertEqual(region.subRegions.count, 0) - XCTAssert(Locale.Region.isoRegions.count > 0) + #expect(region.isISORegion) + #expect(region.identifier == "US") + #expect(region.continent == Locale.Region("019")) + #expect(region.containingRegion == Locale.Region("021")) + #expect(region.subRegions.count == 0) + #expect(Locale.Region.isoRegions.count > 0) let world = Locale.Region("001") - XCTAssertEqual(world.subRegions.count, 5) + #expect(world.subRegions.count == 5) let predefinedRegions: [Locale.Region] = [ .aruba, .belize, .chad, .côteDIvoire, .frenchSouthernTerritories, .heardMcdonaldIslands, .réunion ] for predefinedRegion in predefinedRegions { - XCTAssertTrue(predefinedRegion.isISORegion) + #expect(predefinedRegion.isISORegion) } } - func testCurrency() { + @Test func currency() { let usd = Locale.Currency("usd") - XCTAssertTrue(usd.isISOCurrency) - XCTAssertTrue(Locale.Currency.isoCurrencies.count > 0) + #expect(usd.isISOCurrency) + #expect(Locale.Currency.isoCurrencies.count > 0) } - func testLanguageCode() { + @Test func languageCode() { let isoLanguageCodes = Locale.LanguageCode.isoLanguageCodes - XCTAssertTrue(isoLanguageCodes.count > 0) + #expect(isoLanguageCodes.count > 0) let isoCodes: [Locale.LanguageCode] = [ "de", "ar", "en", "es", "ja", "und", "DE", "AR" ] for isoCode in isoCodes { - XCTAssertTrue(isoCode.isISOLanguage, "\(isoCode.identifier)") - XCTAssertTrue(isoLanguageCodes.contains(isoCode), "\(isoCode.identifier)") + #expect(isoCode.isISOLanguage, "\(isoCode.identifier)") + #expect(isoLanguageCodes.contains(isoCode), "\(isoCode.identifier)") } let invalidCodes: [Locale.LanguageCode] = [ "unk", "bogus", "foo", "root", "jp" ] for invalidCode in invalidCodes { - XCTAssertFalse(invalidCode.isISOLanguage, "\(invalidCode.identifier)") - XCTAssertNil(invalidCode.identifier(.alpha2)) - XCTAssertNil(invalidCode.identifier(.alpha3)) - XCTAssertFalse(isoLanguageCodes.contains(invalidCode)) + #expect(!invalidCode.isISOLanguage, "\(invalidCode.identifier)") + #expect(invalidCode.identifier(.alpha2) == nil) + #expect(invalidCode.identifier(.alpha3) == nil) + #expect(!isoLanguageCodes.contains(invalidCode)) } let isoCodes3: [Locale.LanguageCode] = [ "deu", "ara", "eng", "spa", "jpn", "und", "deu", "ara" ] for (alpha2, alpha3) in zip(isoCodes, isoCodes3) { let actualAlpha2 = alpha3.identifier(.alpha2) let actualAlpha3 = alpha2.identifier(.alpha3) - XCTAssertEqual(actualAlpha2, alpha2.identifier.lowercased()) - XCTAssertEqual(actualAlpha3, alpha3.identifier.lowercased()) + #expect(actualAlpha2 == alpha2.identifier.lowercased()) + #expect(actualAlpha3 == alpha3.identifier.lowercased()) } let reservedCodes: [Locale.LanguageCode] = [ .unidentified, .uncoded, .multiple, .unavailable ] for reservedCode in reservedCodes { - XCTAssertTrue(reservedCode.isISOLanguage, "\(reservedCode.identifier)") - XCTAssertEqual(reservedCode.identifier(.alpha2), reservedCode.identifier) - XCTAssertEqual(reservedCode.identifier(.alpha3), reservedCode.identifier) - XCTAssertTrue(isoLanguageCodes.contains(reservedCode)) + #expect(reservedCode.isISOLanguage, "\(reservedCode.identifier)") + #expect(reservedCode.identifier(.alpha2) == reservedCode.identifier) + #expect(reservedCode.identifier(.alpha3) == reservedCode.identifier) + #expect(isoLanguageCodes.contains(reservedCode)) } let predefinedCodes: [Locale.LanguageCode] = [ .arabic, .norwegianBokmål, .bulgarian, .māori, .norwegianNynorsk, .lithuanian ] for predefinedCode in predefinedCodes { - XCTAssertTrue(predefinedCode.isISOLanguage) + #expect(predefinedCode.isISOLanguage) } } - func testScript() { + @Test func script() { let someISOScripts: [Locale.Script] = [ "Latn", "Hani", "Hira", "Egyh", "Hans", "Arab", "Cyrl", "Deva", "Zzzz" ] for script in someISOScripts { - XCTAssertTrue(script.isISOScript) + #expect(script.isISOScript) } let notISOScripts: [Locale.Script] = [ "Wave", "Zombie", "Head", "Heart" ] for script in notISOScripts { - XCTAssertFalse(script.isISOScript) + #expect(!script.isISOScript) } let predefinedScripts: [Locale.Script] = [ .latin, .hanSimplified, .hanifiRohingya, .hiragana, .arabic, .cyrillic, .devanagari, .unknown, .hanTraditional, .kannada ] for script in predefinedScripts { - XCTAssertTrue(script.isISOScript) + #expect(script.isISOScript) } } - func testMisc() { - XCTAssertTrue(Locale.Collation.availableCollations.count > 0) + @Test func misc() { + #expect(Locale.Collation.availableCollations.count > 0) - XCTAssertEqual(Set(Locale.Collation.availableCollations(for: Locale.Language(identifier:"en"))), [ .standard, .searchRules, Locale.Collation("emoji"), Locale.Collation("eor") ]) + #expect(Set(Locale.Collation.availableCollations(for: Locale.Language(identifier:"en"))) == [ .standard, .searchRules, Locale.Collation("emoji"), Locale.Collation("eor") ]) - XCTAssertEqual(Set(Locale.Collation.availableCollations(for: Locale.Language(identifier:"de"))), [ .standard, .searchRules, Locale.Collation("emoji"), Locale.Collation("eor"), Locale.Collation("phonebook") ]) - XCTAssertEqual(Set(Locale.Collation.availableCollations(for: Locale.Language(identifier:"bogus"))), [ .standard, .searchRules, Locale.Collation("emoji"), Locale.Collation("eor") ]) + #expect(Set(Locale.Collation.availableCollations(for: Locale.Language(identifier:"de"))) == [ .standard, .searchRules, Locale.Collation("emoji"), Locale.Collation("eor"), Locale.Collation("phonebook") ]) + #expect(Set(Locale.Collation.availableCollations(for: Locale.Language(identifier:"bogus"))) == [ .standard, .searchRules, Locale.Collation("emoji"), Locale.Collation("eor") ]) - XCTAssertTrue(Locale.NumberingSystem.availableNumberingSystems.count > 0) - XCTAssertTrue(Locale.NumberingSystem.availableNumberingSystems.contains(Locale.NumberingSystem("java"))) + #expect(Locale.NumberingSystem.availableNumberingSystems.count > 0) + #expect(Locale.NumberingSystem.availableNumberingSystems.contains(Locale.NumberingSystem("java"))) } // The internal identifier getter would ignore invalid keywords and returns ICU-style identifier - func testInternalIdentifier() { + @Test func internalIdentifier() { // In previous versions Locale.Components(identifier:) would not include @va=posix and en_US_POSIX would result in simply en_US_POSIX. We now return the @va=posix for compatibility with CFLocale. let expectations = [ "en_GB" : "en_GB", @@ -133,15 +132,15 @@ final class LocaleComponentsTests: XCTestCase { ] for (key, value) in expectations { let comps = Locale.Components(identifier: key) - XCTAssertEqual(comps.icuIdentifier, value, "locale identifier: \(key)") + #expect(comps.icuIdentifier == value, "locale identifier: \(key)") } } - func testCreation_identifier() { - func verify(_ identifier: String, file: StaticString = #filePath, line: UInt = #line, expected components: () -> Locale.Components ) { + @Test func creation_identifier() { + func verify(_ identifier: String, sourceLocation: SourceLocation = #_sourceLocation, expected components: () -> Locale.Components ) { let comps = Locale.Components(identifier: identifier) let expected = components() - XCTAssertEqual(comps, expected, "expect: \"\(expected.icuIdentifier)\", actual: \"\(comps.icuIdentifier)\"", file: file, line: line) + #expect(comps == expected, "expect: \"\(expected.icuIdentifier)\", actual: \"\(comps.icuIdentifier)\"", sourceLocation: sourceLocation) } // keywords @@ -236,8 +235,8 @@ final class LocaleComponentsTests: XCTestCase { } } - func testCreation_roundTripLocale() { - func verify(_ identifier: String, file: StaticString = #filePath, line: UInt = #line) { + @Test func creation_roundTripLocale() { + func verify(_ identifier: String, sourceLocation: SourceLocation = #_sourceLocation) { let locale = Locale(identifier: identifier) @@ -247,8 +246,8 @@ final class LocaleComponentsTests: XCTestCase { let compsFromLocale = Locale.Components(locale: locale) let compsFromLocaleIdentifier = Locale.Components(identifier: locale.identifier) - XCTAssertEqual(compsFromLocale, comps, file: file, line: line) - XCTAssertEqual(compsFromLocale, compsFromLocaleIdentifier, file: file, line: line) + #expect(compsFromLocale == comps, sourceLocation: sourceLocation) + #expect(compsFromLocale == compsFromLocaleIdentifier, sourceLocation: sourceLocation) } verify("en_GB") @@ -256,11 +255,10 @@ final class LocaleComponentsTests: XCTestCase { verify("en-Latn_GB") } - func testLocaleComponentInitNoCrash() { + @Test func localeComponentInitNoCrash() { // Test that parsing invalid identifiers does not crash - func test(_ identifier: String, file: StaticString = #filePath, line: UInt = #line) { - let comp = Locale.Components(identifier: identifier) - XCTAssertNotNil(comp, file: file, line: line) + func test(_ identifier: String, sourceLocation: SourceLocation = #_sourceLocation) { + _ = Locale.Components(identifier: identifier) } test("en_US@calendar=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") @@ -268,24 +266,24 @@ final class LocaleComponentsTests: XCTestCase { test("en_US@aaaaaaaaaaaaaaaaaaaaaaaaaaaa=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") } - func test_userPreferenceOverride() { + @Test func userPreferenceOverride() { - func verifyHourCycle(_ localeID: String, _ expectDefault: Locale.HourCycle?, shouldRespectUserPref: Bool, file: StaticString = #filePath, line: UInt = #line) { + func verifyHourCycle(_ localeID: String, _ expectDefault: Locale.HourCycle?, shouldRespectUserPref: Bool, sourceLocation: SourceLocation = #_sourceLocation) { let loc = Locale(identifier: localeID) let nonCurrentDefault = Locale.Components(locale: loc) - XCTAssertEqual(nonCurrentDefault.hourCycle, expectDefault, "default did not match", file: file, line: line) + #expect(nonCurrentDefault.hourCycle == expectDefault, "default did not match", sourceLocation: sourceLocation) let defaultLoc = Locale.localeAsIfCurrent(name: localeID, overrides: .init()) let defaultComp = Locale.Components(locale: defaultLoc) - XCTAssertEqual(defaultComp.hourCycle, expectDefault, "explicit no override did not match", file: file, line: line) + #expect(defaultComp.hourCycle == expectDefault, "explicit no override did not match", sourceLocation: sourceLocation) let force24 = Locale.localeAsIfCurrent(name: localeID, overrides: .init(force24Hour: true)) let force24Comp = Locale.Components(locale: force24) - XCTAssertEqual(force24Comp.hourCycle, shouldRespectUserPref ? .zeroToTwentyThree : expectDefault, "force 24-hr did not match", file: file, line: line) + #expect(force24Comp.hourCycle == (shouldRespectUserPref ? .zeroToTwentyThree : expectDefault), "force 24-hr did not match", sourceLocation: sourceLocation) let force12 = Locale.localeAsIfCurrent(name: localeID, overrides: .init(force12Hour: true)) let force12Comp = Locale.Components(locale: force12) - XCTAssertEqual(force12Comp.hourCycle, shouldRespectUserPref ? .oneToTwelve : expectDefault, "force 12-hr did not match", file: file, line: line) + #expect(force12Comp.hourCycle == (shouldRespectUserPref ? .oneToTwelve : expectDefault), "force 12-hr did not match", sourceLocation: sourceLocation) } // expecting "nil" for hourCycle because no such information in the identifier @@ -300,202 +298,195 @@ final class LocaleComponentsTests: XCTestCase { verifyHourCycle("en_GB@hours=x", nil, shouldRespectUserPref: true) // invalid hour cycle is ignored } - func test_userPreferenceOverrideRoundtrip() { + @Test func userPreferenceOverrideRoundtrip() { let customLocale = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(metricUnits: true, firstWeekday: [.gregorian: Locale.Weekday.wednesday.icuIndex], measurementUnits: .centimeters, force24Hour: true)) - XCTAssertEqual(customLocale.identifier, "en_US") - XCTAssertEqual(customLocale.hourCycle, .zeroToTwentyThree) - XCTAssertEqual(customLocale.firstDayOfWeek, .wednesday) - XCTAssertEqual(customLocale.measurementSystem, .metric) + #expect(customLocale.identifier == "en_US") + #expect(customLocale.hourCycle == .zeroToTwentyThree) + #expect(customLocale.firstDayOfWeek == .wednesday) + #expect(customLocale.measurementSystem == .metric) let components = Locale.Components(locale: customLocale) - XCTAssertEqual(components.icuIdentifier, "en_US@fw=wed;hours=h23;measure=metric") - XCTAssertEqual(components.hourCycle, .zeroToTwentyThree) - XCTAssertEqual(components.firstDayOfWeek, .wednesday) - XCTAssertEqual(components.measurementSystem, .metric) + #expect(components.icuIdentifier == "en_US@fw=wed;hours=h23;measure=metric") + #expect(components.hourCycle == .zeroToTwentyThree) + #expect(components.firstDayOfWeek == .wednesday) + #expect(components.measurementSystem == .metric) let locFromComp = Locale(components: components) - XCTAssertEqual(locFromComp.identifier, "en_US@fw=wed;hours=h23;measure=metric") - XCTAssertEqual(locFromComp.hourCycle, .zeroToTwentyThree) - XCTAssertEqual(locFromComp.firstDayOfWeek, .wednesday) - XCTAssertEqual(locFromComp.measurementSystem, .metric) + #expect(locFromComp.identifier == "en_US@fw=wed;hours=h23;measure=metric") + #expect(locFromComp.hourCycle == .zeroToTwentyThree) + #expect(locFromComp.firstDayOfWeek == .wednesday) + #expect(locFromComp.measurementSystem == .metric) var updatedComponents = components updatedComponents.firstDayOfWeek = .friday let locFromUpdatedComponents = Locale(components: updatedComponents) - XCTAssertEqual(locFromUpdatedComponents.identifier, "en_US@fw=fri;hours=h23;measure=metric") - XCTAssertEqual(locFromUpdatedComponents.hourCycle, .zeroToTwentyThree) - XCTAssertEqual(locFromUpdatedComponents.firstDayOfWeek, .friday) - XCTAssertEqual(locFromUpdatedComponents.measurementSystem, .metric) + #expect(locFromUpdatedComponents.identifier == "en_US@fw=fri;hours=h23;measure=metric") + #expect(locFromUpdatedComponents.hourCycle == .zeroToTwentyThree) + #expect(locFromUpdatedComponents.firstDayOfWeek == .friday) + #expect(locFromUpdatedComponents.measurementSystem == .metric) } } -final class LocaleCodableTests: XCTestCase { +@Suite("Locale Codable") +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, file: StaticString = #filePath, line: UInt = #line) -> T? { + 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 decoder = JSONDecoder() - guard let decoded = try? decoder.decode(T.self, from: previousEncodedData) else { - XCTFail("Decoding \(obj) failed", file: file, line: line) - return nil - } + let decoded = try decoder.decode(T.self, from: previousEncodedData) let encoder = JSONEncoder() - guard let newEncoded = try? encoder.encode(decoded) else { - XCTFail("Encoding \(obj) failed", file: file, line: line) - return nil - } - XCTAssertEqual(String(data: newEncoded, encoding: .utf8)!, "\"\(identifier)\"") + let newEncoded = try encoder.encode(decoded) + #expect(String(data: newEncoded, encoding: .utf8)! == "\"\(identifier)\"") return decoded } - func test_compatibilityCoding() { + @Test func compatibilityCoding() throws { do { let codableObj = Locale.LanguageCode("HELLO") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.LanguageCode.armenian - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.LanguageCode("") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Region("My home") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Region.uganda - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Region("") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Script("BOGUS") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Script.hebrew - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Collation("BOGUS") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Collation.searchRules - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Currency("EXAMPLE") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Currency.unknown - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.NumberingSystem("UNKNOWN") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.NumberingSystem.latn - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.MeasurementSystem.metric - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.MeasurementSystem("EXAMPLE") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Subdivision("usca") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Variant("EXAMPLE") - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } do { let codableObj = Locale.Variant.posix - let decoded = _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) - XCTAssertEqual(decoded?.identifier, codableObj.identifier) - XCTAssertEqual(decoded?._normalizedIdentifier, codableObj._normalizedIdentifier) + let decoded = try _testRoundtripCoding(codableObj, identifier: codableObj.identifier, normalizedIdentifier: codableObj._normalizedIdentifier) + #expect(decoded?.identifier == codableObj.identifier) + #expect(decoded?._normalizedIdentifier == codableObj._normalizedIdentifier) } } - func test_decode_compatible_localeComponents() { - func expectDecode(_ encoded: String, _ expected: Locale.Components, file: StaticString = #filePath, line: UInt = #line) { - guard let data = encoded.data(using: String._Encoding.utf8), let decoded = try? JSONDecoder().decode(Locale.Components.self, from: data) else { - XCTFail(file: file, line: line) - return - } - XCTAssertEqual(decoded, expected, file: file, line: line) + @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 decoded = try JSONDecoder().decode(Locale.Components.self, from: data) + #expect(decoded == expected, sourceLocation: sourceLocation) } do { @@ -510,178 +501,154 @@ final class LocaleCodableTests: XCTestCase { expected.currency = "GBP" expected.measurementSystem = .us - expectDecode(""" + try expectDecode(""" {"region":{"_identifier":"HK","_normalizedIdentifier":"HK"},"firstDayOfWeek":"mon","languageComponents":{"region":{"_identifier":"TW","_normalizedIdentifier":"TW"},"languageCode":{"_identifier":"zh","_normalizedIdentifier":"zh"}},"hourCycle":"h12","timeZone":{"identifier":"GMT"},"calendar":{"buddhist":{}},"currency":{"_identifier":"GBP","_normalizedIdentifier":"gbp"},"measurementSystem":{"_identifier":"ussystem","_normalizedIdentifier":"ussystem"}} """, expected) } do { - expectDecode(""" + try expectDecode(""" {"languageComponents":{}} """, Locale.Components(identifier: "")) } } - func test_decode_compatible_language() { + @Test func decode_compatible_language() throws { - func expectDecode(_ encoded: String, _ expected: Locale.Language, file: StaticString = #filePath, line: UInt = #line) { - guard let data = encoded.data(using: String._Encoding.utf8), let decoded = try? JSONDecoder().decode(Locale.Language.self, from: data) else { - XCTFail(file: file, line: line) - return - } - XCTAssertEqual(decoded, expected, file: file, line: line) + func expectDecode(_ encoded: String, _ expected: Locale.Language, sourceLocation: SourceLocation = #_sourceLocation) throws { + let data = try #require(encoded.data(using: String._Encoding.utf8)) + let decoded = try JSONDecoder().decode(Locale.Language.self, from: data) + #expect(decoded == expected, sourceLocation: sourceLocation) } - expectDecode(""" + try expectDecode(""" {"components":{"script":{"_identifier":"Hans","_normalizedIdentifier":"Hans"},"languageCode":{"_identifier":"zh","_normalizedIdentifier":"zh"},"region":{"_identifier":"HK","_normalizedIdentifier":"HK"}}} """, Locale.Language(identifier: "zh-Hans-HK")) - expectDecode(""" + try expectDecode(""" {"components":{}} """, Locale.Language(identifier: "")) } - func test_decode_compatible_languageComponents() { - func expectDecode(_ encoded: String, _ expected: Locale.Language.Components, file: StaticString = #filePath, line: UInt = #line) { - guard let data = encoded.data(using: String._Encoding.utf8), let decoded = try? JSONDecoder().decode(Locale.Language.Components.self, from: data) else { - XCTFail(file: file, line: line) - return - } - XCTAssertEqual(decoded, expected, file: file, line: line) + @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 decoded = try JSONDecoder().decode(Locale.Language.Components.self, from: data) + #expect(decoded == expected, sourceLocation: sourceLocation) } - expectDecode(""" + try expectDecode(""" {"script":{"_identifier":"Hans","_normalizedIdentifier":"Hans"},"languageCode":{"_identifier":"zh","_normalizedIdentifier":"zh"},"region":{"_identifier":"HK","_normalizedIdentifier":"HK"}} """, Locale.Language.Components(identifier: "zh-Hans-HK")) - expectDecode("{}", Locale.Language.Components(identifier: "")) + try expectDecode("{}", Locale.Language.Components(identifier: "")) } // Locale components are considered equal regardless of the identifier's case - func testCaseInsensitiveEquality() { - XCTAssertEqual(Locale.Collation("search"), Locale.Collation("SEARCH")) - XCTAssertEqual(Locale.NumberingSystem("latn"), Locale.NumberingSystem("Latn")) - XCTAssertEqual( - [ Locale.NumberingSystem("latn"), Locale.NumberingSystem("ARAB") ], + @Test func caseInsensitiveEquality() { + #expect(Locale.Collation("search") == Locale.Collation("SEARCH")) + #expect(Locale.NumberingSystem("latn") == Locale.NumberingSystem("Latn")) + #expect( + [ Locale.NumberingSystem("latn"), Locale.NumberingSystem("ARAB") ] == [ Locale.NumberingSystem("Latn"), Locale.NumberingSystem("arab") ]) - XCTAssertEqual( - Set([ Locale.NumberingSystem("latn"), Locale.NumberingSystem("ARAB") ]), + #expect( + Set([ Locale.NumberingSystem("latn"), Locale.NumberingSystem("ARAB") ]) == Set([ Locale.NumberingSystem("arab"), Locale.NumberingSystem("Latn") ])) - XCTAssertEqual(Locale.Region("US"), Locale.Region("us")) - XCTAssertEqual(Locale.Script("Hant"), Locale.Script("hant")) - XCTAssertEqual(Locale.LanguageCode("EN"), Locale.LanguageCode("en")) + #expect(Locale.Region("US") == Locale.Region("us")) + #expect(Locale.Script("Hant") == Locale.Script("hant")) + #expect(Locale.LanguageCode("EN") == Locale.LanguageCode("en")) } - func _encodeAsJSON(_ t: T) -> String? { + func _encodeAsJSON(_ t: T) throws -> String { let encoder = JSONEncoder() encoder.outputFormatting = [ .sortedKeys ] - guard let encoded = try? encoder.encode(t) else { - return nil - } - return String(data: encoded, encoding: .utf8) + let encoded = try encoder.encode(t) + return try #require(String(data: encoded, encoding: .utf8)) } - func test_encode_language() { - func expectEncode(_ lang: Locale.Language, _ expectedEncoded: String, file: StaticString = #filePath, line: UInt = #line) { - guard let encoded = _encodeAsJSON(lang) else { - XCTFail(file: file, line: line) - return - } + @Test func encode_language() throws { + func expectEncode(_ lang: Locale.Language, _ expectedEncoded: String, sourceLocation: SourceLocation = #_sourceLocation) throws { + let encoded = try _encodeAsJSON(lang) - XCTAssertEqual(encoded, expectedEncoded, file: file, line: line) + #expect(encoded == expectedEncoded, sourceLocation: sourceLocation) - let data = encoded.data(using: String._Encoding.utf8) - guard let data, let decoded = try? JSONDecoder().decode(Locale.Language.self, from: data) else { - XCTFail(file: file, line: line) - return - } + let data = try #require(encoded.data(using: String._Encoding.utf8)) + let decoded = try JSONDecoder().decode(Locale.Language.self, from: data) - XCTAssertEqual(lang, decoded, file: file, line: line) + #expect(lang == decoded, sourceLocation: sourceLocation) } - expectEncode(Locale.Language(identifier: "zh-Hans-hk"), """ + try expectEncode(Locale.Language(identifier: "zh-Hans-hk"), """ {"components":{"languageCode":"zh","region":"HK","script":"Hans"}} """) - expectEncode(Locale.Language(languageCode: .chinese, script: .hanSimplified, region: .hongKong), """ + try expectEncode(Locale.Language(languageCode: .chinese, script: .hanSimplified, region: .hongKong), """ {"components":{"languageCode":"zh","region":"HK","script":"Hans"}} """) let langComp = Locale.Language.Components(identifier: "zh-Hans-hk") - expectEncode(Locale.Language(components: langComp), """ + try expectEncode(Locale.Language(components: langComp), """ {"components":{"languageCode":"zh","region":"HK","script":"Hans"}} """) - expectEncode(Locale.Language(identifier: ""), """ + try expectEncode(Locale.Language(identifier: ""), """ {"components":{}} """) - expectEncode(Locale.Language(languageCode: nil), """ + try expectEncode(Locale.Language(languageCode: nil), """ {"components":{}} """) let empty = Locale.Language.Components(identifier: "") - expectEncode(Locale.Language(components: empty), """ + try expectEncode(Locale.Language(components: empty), """ {"components":{}} """) } - func test_encode_languageComponents() { - func expectEncode(_ lang: Locale.Language.Components, _ expectedEncoded: String, file: StaticString = #filePath, line: UInt = #line) { - guard let encoded = _encodeAsJSON(lang) else { - XCTFail(file: file, line: line) - return - } + @Test func encode_languageComponents() throws { + func expectEncode(_ lang: Locale.Language.Components, _ expectedEncoded: String, sourceLocation: SourceLocation = #_sourceLocation) throws { + let encoded = try _encodeAsJSON(lang) - XCTAssertEqual(encoded, expectedEncoded, file: file, line: line) + #expect(encoded == expectedEncoded, sourceLocation: sourceLocation) - let data = encoded.data(using: String._Encoding.utf8) - guard let data, let decoded = try? JSONDecoder().decode(Locale.Language.Components.self, from: data) else { - XCTFail(file: file, line: line) - return - } + let data = try #require(encoded.data(using: String._Encoding.utf8)) + let decoded = try JSONDecoder().decode(Locale.Language.Components.self, from: data) - XCTAssertEqual(lang, decoded, file: file, line: line) + #expect(lang == decoded, sourceLocation: sourceLocation) } - expectEncode(Locale.Language.Components(identifier: "zh-Hans-hk"), """ + try expectEncode(Locale.Language.Components(identifier: "zh-Hans-hk"), """ {"languageCode":"zh","region":"HK","script":"Hans"} """) - expectEncode(Locale.Language.Components(languageCode: .chinese, script: .hanSimplified, region: .hongKong), """ + try expectEncode(Locale.Language.Components(languageCode: .chinese, script: .hanSimplified, region: .hongKong), """ {"languageCode":"zh","region":"HK","script":"Hans"} """) let lang = Locale.Language(identifier: "zh-Hans-hk") - expectEncode(Locale.Language.Components(language: lang), """ + try expectEncode(Locale.Language.Components(language: lang), """ {"languageCode":"zh","region":"HK","script":"Hans"} """) - expectEncode(Locale.Language.Components(identifier: ""), """ + try expectEncode(Locale.Language.Components(identifier: ""), """ {} """) - expectEncode(Locale.Language.Components(languageCode: nil), "{}") + try expectEncode(Locale.Language.Components(languageCode: nil), "{}") } - func test_encode_localeComponents() { + @Test func encode_localeComponents() throws { - func expectEncode(_ lang: Locale.Components, _ expectedEncoded: String, file: StaticString = #filePath, line: UInt = #line) { - guard let encoded = _encodeAsJSON(lang) else { - XCTFail(file: file, line: line) - return - } + func expectEncode(_ lang: Locale.Components, _ expectedEncoded: String, sourceLocation: SourceLocation = #_sourceLocation) throws { + let encoded = try _encodeAsJSON(lang) - XCTAssertEqual(encoded, expectedEncoded, file: file, line: line) + #expect(encoded == expectedEncoded, sourceLocation: sourceLocation) - let data = encoded.data(using: String._Encoding.utf8) - guard let data, let decoded = try? JSONDecoder().decode(Locale.Components.self, from: data) else { - XCTFail(file: file, line: line) - return - } + let data = try #require(encoded.data(using: String._Encoding.utf8)) + let decoded = try JSONDecoder().decode(Locale.Components.self, from: data) - XCTAssertEqual(lang, decoded, file: file, line: line) + #expect(lang == decoded, sourceLocation: sourceLocation) } var comp = Locale.Components(languageCode: .chinese, languageRegion: .taiwan) @@ -693,15 +660,15 @@ final class LocaleCodableTests: XCTestCase { comp.measurementSystem = .us comp.timeZone = .gmt - expectEncode(comp, """ + try expectEncode(comp, """ {"calendar":{"buddhist":{}},"currency":"GBP","firstDayOfWeek":"mon","hourCycle":"h12","languageComponents":{"languageCode":"zh","region":"TW"},"measurementSystem":"ussystem","region":"HK","timeZone":{"identifier":"GMT"}} """) - expectEncode(Locale.Components(languageCode: nil), """ + try expectEncode(Locale.Components(languageCode: nil), """ {"languageComponents":{}} """) - expectEncode(Locale.Components(identifier: ""), """ + try expectEncode(Locale.Components(identifier: ""), """ {"languageComponents":{}} """) } diff --git a/Tests/FoundationInternationalizationTests/LocaleLanguageTests.swift b/Tests/FoundationInternationalizationTests/LocaleLanguageTests.swift index a6e421141..09177baac 100644 --- a/Tests/FoundationInternationalizationTests/LocaleLanguageTests.swift +++ b/Tests/FoundationInternationalizationTests/LocaleLanguageTests.swift @@ -10,9 +10,7 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if FOUNDATION_FRAMEWORK @testable import Foundation @@ -21,20 +19,21 @@ import TestSupport @testable import FoundationInternationalization #endif // FOUNDATION_FRAMEWORK -final class LocaleLanguageComponentsTests : XCTestCase { +@Suite("Locale.Language.Components") +private struct LocaleLanguageComponentsTests { func verifyComponents(_ identifier: String, expectedLanguageCode: String?, expectedScriptCode: String?, expectedRegionCode: String?, - file: StaticString = #filePath, line: UInt = #line) { + sourceLocation: SourceLocation = #_sourceLocation) { let comp = Locale.Language.Components(identifier: identifier) - XCTAssertEqual(comp.languageCode?.identifier, expectedLanguageCode, file: file, line: line) - XCTAssertEqual(comp.script?.identifier, expectedScriptCode, file: file, line: line) - XCTAssertEqual(comp.region?.identifier, expectedRegionCode, file: file, line: line) + #expect(comp.languageCode?.identifier == expectedLanguageCode, sourceLocation: sourceLocation) + #expect(comp.script?.identifier == expectedScriptCode, sourceLocation: sourceLocation) + #expect(comp.region?.identifier == expectedRegionCode, sourceLocation: sourceLocation) } - func testCreateFromIdentifier() { + @Test func createFromIdentifier() { verifyComponents("en-US", expectedLanguageCode: "en", expectedScriptCode: nil, expectedRegionCode: "US") verifyComponents("en_US", expectedLanguageCode: "en", expectedScriptCode: nil, expectedRegionCode: "US") verifyComponents("en_US@rg=GBzzzz", expectedLanguageCode: "en", expectedScriptCode: nil, expectedRegionCode: "US") @@ -43,36 +42,37 @@ final class LocaleLanguageComponentsTests : XCTestCase { verifyComponents("hans-cn", expectedLanguageCode: "hans", expectedScriptCode: nil, expectedRegionCode: "CN") } - func testCreateFromInvalidIdentifier() { + @Test func createFromInvalidIdentifier() { verifyComponents("HANS", expectedLanguageCode: "hans", expectedScriptCode: nil, expectedRegionCode: nil) verifyComponents("zh-CN-Hant", expectedLanguageCode: "zh", expectedScriptCode: nil, expectedRegionCode: "CN") verifyComponents("bleh", expectedLanguageCode: "bleh", expectedScriptCode: nil, expectedRegionCode: nil) } // The internal identifier uses the ICU-style identifier - func testInternalIdentifier() { - XCTAssertEqual(Locale.Language.Components(languageCode: "en", script: "Hant", region: "US").identifier, "en-Hant_US") - XCTAssertEqual(Locale.Language.Components(languageCode: "en", script: nil, region: "US").identifier, "en_US") - XCTAssertEqual(Locale.Language.Components(languageCode: "EN", script: nil, region: "us").identifier, "en_US") - XCTAssertEqual(Locale.Language.Components(languageCode: "EN", script: "Latn").identifier, "en-Latn") + @Test func internalIdentifier() { + #expect(Locale.Language.Components(languageCode: "en", script: "Hant", region: "US").identifier == "en-Hant_US") + #expect(Locale.Language.Components(languageCode: "en", script: nil, region: "US").identifier == "en_US") + #expect(Locale.Language.Components(languageCode: "EN", script: nil, region: "us").identifier == "en_US") + #expect(Locale.Language.Components(languageCode: "EN", script: "Latn").identifier == "en-Latn") } } -class LocaleLanguageTests: XCTestCase { +@Suite("Locale.Language") +private struct LocaleLanguageTests { - func verify(_ identifier: String, expectedParent: Locale.Language, minBCP47: String, maxBCP47: String, langCode: Locale.LanguageCode?, script: Locale.Script?, region: Locale.Region?, lineDirection: Locale.LanguageDirection, characterDirection: Locale.LanguageDirection, file: StaticString = #filePath, line: UInt = #line) { + func verify(_ identifier: String, expectedParent: Locale.Language, minBCP47: String, maxBCP47: String, langCode: Locale.LanguageCode?, script: Locale.Script?, region: Locale.Region?, lineDirection: Locale.LanguageDirection, characterDirection: Locale.LanguageDirection, sourceLocation: SourceLocation = #_sourceLocation) { let lan = Locale.Language(identifier: identifier) - XCTAssertEqual(lan.parent, expectedParent, "Parents should be equal", file: file, line: line) - XCTAssertEqual(lan.minimalIdentifier, minBCP47, "minimalIdentifiers should be equal", file: file, line: line) - XCTAssertEqual(lan.maximalIdentifier, maxBCP47, "maximalIdentifiers should be equal", file: file, line: line) - XCTAssertEqual(lan.languageCode, langCode, "languageCodes should be equal", file: file, line: line) - XCTAssertEqual(lan.script, script, "languageCodes should be equal", file: file, line: line) - XCTAssertEqual(lan.region, region, "regions should be equal", file: file, line: line) - XCTAssertEqual(lan.lineLayoutDirection, lineDirection, "lineDirection should be equal", file: file, line: line) - XCTAssertEqual(lan.characterDirection, characterDirection, "characterDirection should be equal", file: file, line: line) + #expect(lan.parent == expectedParent, "Parents should be equal", sourceLocation: sourceLocation) + #expect(lan.minimalIdentifier == minBCP47, "minimalIdentifiers should be equal", sourceLocation: sourceLocation) + #expect(lan.maximalIdentifier == maxBCP47, "maximalIdentifiers should be equal", sourceLocation: sourceLocation) + #expect(lan.languageCode == langCode, "languageCodes should be equal", sourceLocation: sourceLocation) + #expect(lan.script == script, "languageCodes should be equal", sourceLocation: sourceLocation) + #expect(lan.region == region, "regions should be equal", sourceLocation: sourceLocation) + #expect(lan.lineLayoutDirection == lineDirection, "lineDirection should be equal", sourceLocation: sourceLocation) + #expect(lan.characterDirection == characterDirection, "characterDirection should be equal", sourceLocation: sourceLocation) } - func testProperties() { + @Test func properties() { verify("en-US", expectedParent: .init(identifier: "en"), minBCP47: "en", maxBCP47: "en-Latn-US", langCode: "en", script: "Latn", region: "US", lineDirection: .topToBottom, characterDirection: .leftToRight) verify("de-DE", expectedParent: .init(identifier: "de"), minBCP47: "de", maxBCP47: "de-Latn-DE", langCode: "de", script: "Latn", region: "DE", lineDirection: .topToBottom, characterDirection: .leftToRight) verify("en-Kore-US", expectedParent: .init(identifier: "en-Kore"), minBCP47: "en-Kore", maxBCP47: "en-Kore-US", langCode: "en", script: "Kore", region: "US", lineDirection: .topToBottom, characterDirection: .leftToRight) @@ -85,13 +85,13 @@ class LocaleLanguageTests: XCTestCase { verify("root", expectedParent: .init(identifier: "root"), minBCP47: "root", maxBCP47: "root", langCode: "root", script: nil, region: nil, lineDirection: .topToBottom, characterDirection: .leftToRight) } - func testEquivalent() { - func verify(lang1: String, lang2: String, isEqual: Bool, file: StaticString = #filePath, line: UInt = #line) { + @Test func equivalent() { + func verify(lang1: String, lang2: String, isEqual: Bool, sourceLocation: SourceLocation = #_sourceLocation) { let language1 = Locale.Language(identifier: lang1) let language2 = Locale.Language(identifier: lang2) - XCTAssert(language1.isEquivalent(to: language2) == isEqual, file: file, line: line) - XCTAssert(language2.isEquivalent(to: language1) == isEqual, file: file, line: line) + #expect(language1.isEquivalent(to: language2) == isEqual, sourceLocation: sourceLocation) + #expect(language2.isEquivalent(to: language1) == isEqual, sourceLocation: sourceLocation) } verify(lang1: "en", lang2: "en-Latn", isEqual: true) diff --git a/Tests/FoundationInternationalizationTests/LocaleTestUtilities.swift b/Tests/FoundationInternationalizationTests/LocaleTestUtilities.swift deleted file mode 100644 index 8922fc8eb..000000000 --- a/Tests/FoundationInternationalizationTests/LocaleTestUtilities.swift +++ /dev/null @@ -1,31 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2023 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 -// -//===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_interop - -#if FOUNDATION_FRAMEWORK -@testable import Foundation -#else -@testable import FoundationEssentials -@testable import FoundationInternationalization -#endif // FOUNDATION_FRAMEWORK - -// MARK: - Stubs - -#if !FOUNDATION_FRAMEWORK -internal enum UnitTemperature { - case celsius - case fahrenheit -} -#endif // !FOUNDATION_FRAMEWORK diff --git a/Tests/FoundationInternationalizationTests/StringTests+Data.swift b/Tests/FoundationInternationalizationTests/StringICUEncodingTests.swift similarity index 75% rename from Tests/FoundationInternationalizationTests/StringTests+Data.swift rename to Tests/FoundationInternationalizationTests/StringICUEncodingTests.swift index 56e4e4944..f7f8edd5a 100644 --- a/Tests/FoundationInternationalizationTests/StringTests+Data.swift +++ b/Tests/FoundationInternationalizationTests/StringICUEncodingTests.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import Testing + #if FOUNDATION_FRAMEWORK @testable import Foundation #else @@ -17,29 +19,25 @@ @testable import FoundationInternationalization #endif // FOUNDATION_FRAMEWORK -#if canImport(TestSupport) -import TestSupport -#endif - -final class StringConverterTests: XCTestCase { +@Suite("String (ICU Encoding)") +private struct StringICUEncodingTests { private func _test_roundTripConversion( string: String, data: Data, - encoding: String._Encoding, - file: StaticString = #filePath, - line: UInt = #line + encoding: String.Encoding, + sourceLocation: SourceLocation = #_sourceLocation ) { - XCTAssertEqual( - string.data(using: encoding), data, "Failed to convert string to data.", - file: file, line: line + #expect( + string.data(using: encoding) == data, "Failed to convert string to data.", + sourceLocation: sourceLocation ) - XCTAssertEqual( - string, String(data: data, encoding: encoding), "Failed to convert data to string.", - file: file, line: line + #expect( + string == String(data: data, encoding: encoding), "Failed to convert data to string.", + sourceLocation: sourceLocation ) } - func test_japaneseEUC() { + @Test func japaneseEUC() { // Confirm that https://github.com/swiftlang/swift-foundation/issues/1016 is fixed. // ASCII @@ -117,22 +115,22 @@ final class StringConverterTests: XCTestCase { // Unsupported characters let onsen = "Onsen♨" // BMP emoji let sushi = "Sushi🍣" // non-BMP emoji - XCTAssertNil(onsen.data(using: String._Encoding.japaneseEUC)) - XCTAssertNil(sushi.data(using: String._Encoding.japaneseEUC)) - XCTAssertEqual( - onsen.data(using: String._Encoding.japaneseEUC, allowLossyConversion: true), + #expect(onsen.data(using: .japaneseEUC) == nil) + #expect(sushi.data(using: .japaneseEUC) == nil) + #expect( + onsen.data(using: .japaneseEUC, allowLossyConversion: true) == "Onsen?".data(using: .utf8) ) #if FOUNDATION_FRAMEWORK // NOTE: Foundation framework replaces an unsupported non-BMP character // with "??"(two question marks). - XCTAssertEqual( - sushi.data(using: String._Encoding.japaneseEUC, allowLossyConversion: true), + #expect( + sushi.data(using: .japaneseEUC, allowLossyConversion: true) == "Sushi??".data(using: .utf8) ) #else - XCTAssertEqual( - sushi.data(using: String._Encoding.japaneseEUC, allowLossyConversion: true), + #expect( + sushi.data(using: .japaneseEUC, allowLossyConversion: true) == "Sushi?".data(using: .utf8) ) #endif diff --git a/Tests/FoundationInternationalizationTests/StringTests+Locale.swift b/Tests/FoundationInternationalizationTests/StringTests+Locale.swift index daf412a54..32ed89aa9 100644 --- a/Tests/FoundationInternationalizationTests/StringTests+Locale.swift +++ b/Tests/FoundationInternationalizationTests/StringTests+Locale.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import Testing + #if FOUNDATION_FRAMEWORK @testable import Foundation #else @@ -17,26 +19,19 @@ @testable import FoundationInternationalization #endif // FOUNDATION_FRAMEWORK -#if canImport(TestSupport) -import TestSupport -#endif - extension String { var _scalarViewDescription: String { return unicodeScalars.map { "\\u{\(String($0.value, radix: 16, uppercase: true))}" }.joined() } } -final class StringLocaleTests: XCTestCase { +@Suite("String (Locale)") +private struct StringLocaleTests { - func testCapitalize_localized() { + @Test func capitalize_localized() { var locale: Locale? - // `extension StringProtocol { func capitalized(with: Locale) }` is - // declared twice on Darwin: once in FoundationInternationalization - // and once in SDK. Therefore it is ambiguous when building the package - // on Darwin. Workaround it by testing the internal implementation. - func test(_ string: String, _ expected: String, file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(string._capitalized(with: locale), expected, file: file, line: line) + func test(_ string: String, _ expected: String, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(string.capitalized(with: locale) == expected, sourceLocation: sourceLocation) } do { @@ -84,18 +79,18 @@ final class StringLocaleTests: XCTestCase { } } - func testUppercase_localized() { + @Test func uppercase_localized() { - func test(_ localeID: String?, _ string: String, _ expected: String, file: StaticString = #filePath, line: UInt = #line) { + func test(_ localeID: String?, _ string: String, _ expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let locale: Locale? if let localeID { locale = Locale(identifier: localeID) } else { locale = nil } - let actual = string._uppercased(with: locale) + let actual = string.uppercased(with: locale) - XCTAssertEqual(actual, expected, "actual: \(actual._scalarViewDescription), expected: \(expected._scalarViewDescription)", file: file, line: line) + #expect(actual == expected, "actual: \(actual._scalarViewDescription), expected: \(expected._scalarViewDescription)", sourceLocation: sourceLocation) } test(nil, "ffl", "FFL") // 0xFB04 @@ -128,17 +123,17 @@ final class StringLocaleTests: XCTestCase { test("el_GR", "\u{03B9}\u{0308}\u{0301}", "\u{0399}\u{0308}") } - func testLowercase_localized() { - func test(_ localeID: String?, _ string: String, _ expected: String, file: StaticString = #filePath, line: UInt = #line) { + @Test func lowercase_localized() { + func test(_ localeID: String?, _ string: String, _ expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let locale: Locale? if let localeID { locale = Locale(identifier: localeID) } else { locale = nil } - let actual = string._lowercased(with: locale) + let actual = string.lowercased(with: locale) - XCTAssertEqual(actual, expected, "actual: \(actual._scalarViewDescription), expected: \(expected._scalarViewDescription)", file: file, line: line) + #expect(actual == expected, "actual: \(actual._scalarViewDescription), expected: \(expected._scalarViewDescription)", sourceLocation: sourceLocation) } test(nil, "ᾈ", "ᾀ") // 0x1F88 @@ -156,8 +151,9 @@ final class StringLocaleTests: XCTestCase { test("tr", "İİ", "ii") } - func testFuzzFailure() throws { - let input = String(data: Data(base64Encoded: "77+977+977+977+977+977+977+977+977+977+9Cg==")!, encoding: .utf8)! + @Test func fuzzFailure() throws { + let data = try #require(Data(base64Encoded: "77+977+977+977+977+977+977+977+977+977+9Cg==")) + let input = try #require(String(data: data, encoding: .utf8)) _ = input.lowercased(with: Locale(identifier: "en_US")) _ = input.capitalized(with: Locale(identifier: "en_US")) _ = input.capitalized(with: Locale(identifier: "en_US")) diff --git a/Tests/FoundationInternationalizationTests/TimeZoneTests.swift b/Tests/FoundationInternationalizationTests/TimeZoneTests.swift index 31991d16f..724b15799 100644 --- a/Tests/FoundationInternationalizationTests/TimeZoneTests.swift +++ b/Tests/FoundationInternationalizationTests/TimeZoneTests.swift @@ -10,63 +10,64 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if FOUNDATION_FRAMEWORK @testable import Foundation #elseif canImport(FoundationInternationalization) @testable import FoundationInternationalization +@testable import FoundationEssentials #endif -final class TimeZoneTests : XCTestCase { - - func test_timeZoneBasics() { +@Suite("TimeZone") +private struct TimeZoneTests { + @Test func basics() { let tz = TimeZone(identifier: "America/Los_Angeles")! - XCTAssertTrue(!tz.identifier.isEmpty) + #expect(!tz.identifier.isEmpty) } - func test_equality() { - let autoupdating = TimeZone.autoupdatingCurrent - let autoupdating2 = TimeZone.autoupdatingCurrent - - XCTAssertEqual(autoupdating, autoupdating2) - - let current = TimeZone.current - - XCTAssertNotEqual(autoupdating, current) + @Test func equality() async { + await usingCurrentInternationalizationPreferences { + let autoupdating = TimeZone.autoupdatingCurrent + let autoupdating2 = TimeZone.autoupdatingCurrent + + #expect(autoupdating == autoupdating2) + + let current = TimeZone.current + + #expect(autoupdating != current) + } } - func test_AnyHashableContainingTimeZone() { + @Test func anyHashableContainingTimeZone() { let values: [TimeZone] = [ TimeZone(identifier: "America/Los_Angeles")!, TimeZone(identifier: "Europe/Kiev")!, TimeZone(identifier: "Europe/Kiev")!, ] let anyHashables = values.map(AnyHashable.init) - expectEqual(TimeZone.self, type(of: anyHashables[0].base)) - expectEqual(TimeZone.self, type(of: anyHashables[1].base)) - expectEqual(TimeZone.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(TimeZone.self == type(of: anyHashables[0].base)) + #expect(TimeZone.self == type(of: anyHashables[1].base)) + #expect(TimeZone.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } - func testPredefinedTimeZone() { - XCTAssertEqual(TimeZone.gmt, TimeZone(identifier: "GMT")) + @Test func predefinedTimeZone() { + #expect(TimeZone.gmt == TimeZone(identifier: "GMT")) } - func testLocalizedName_103036605() { - func test(_ tzIdentifier: String, _ localeIdentifier: String, _ style: TimeZone.NameStyle, _ expected: String?, file: StaticString = #filePath, line: UInt = #line) { + @Test func localizedName_103036605() { + func test(_ tzIdentifier: String, _ localeIdentifier: String, _ style: TimeZone.NameStyle, _ expected: String?, sourceLocation: SourceLocation = #_sourceLocation) { let tz = TimeZone(identifier: tzIdentifier) guard let expected else { - XCTAssertNil(tz, file: file, line: line) + #expect(tz == nil, sourceLocation: sourceLocation) return } let locale = Locale(identifier: localeIdentifier) - XCTAssertEqual(tz?.localizedName(for: .generic, locale: locale), expected, file: file, line: line) + #expect(tz?.localizedName(for: .generic, locale: locale) == expected, sourceLocation: sourceLocation) } test("America/Los_Angeles", "en_US", .generic, "Pacific Time") @@ -91,235 +92,237 @@ final class TimeZoneTests : XCTestCase { test("BOGUS/BOGUS", "en_US", .standard, nil) } - func testTimeZoneName_103097012() { + @Test func timeZoneName_103097012() throws { - func _verify(_ tz: TimeZone?, _ expectedOffset: Int?, _ createdId: String?, file: StaticString = #filePath, line: UInt = #line) { + func _verify(_ tz: TimeZone?, _ expectedOffset: Int?, _ createdId: String?, sourceLocation: SourceLocation = #_sourceLocation) throws { if let expectedOffset { - XCTAssertNotNil(tz, file: file, line: line) - XCTAssertEqual(tz!.secondsFromGMT(for: Date(timeIntervalSince1970: 0)), expectedOffset, file: file, line: line) - XCTAssertEqual(tz!.identifier, createdId, file: file, line: line) + #expect(tz?.secondsFromGMT(for: Date(timeIntervalSince1970: 0)) == expectedOffset, sourceLocation: sourceLocation) + #expect(tz?.identifier == createdId, sourceLocation: sourceLocation) } else { - XCTAssertNil(tz, file: file, line: line) + #expect(tz == nil, sourceLocation: sourceLocation) } } - func testIdentifier(_ tzID: String, _ expectedOffset: Int?, _ createdId: String?, file: StaticString = #filePath, line: UInt = #line) { - _verify(TimeZone(identifier: tzID), expectedOffset, createdId, file: file, line: line) + func testIdentifier(_ tzID: String, _ expectedOffset: Int?, _ createdId: String?, sourceLocation: SourceLocation = #_sourceLocation) throws { + try _verify(TimeZone(identifier: tzID), expectedOffset, createdId, sourceLocation: sourceLocation) } - func testAbbreviation(_ abb: String, _ expectedOffset: Int?, _ createdId: String?, file: StaticString = #filePath, line: UInt = #line) { - _verify(TimeZone(abbreviation: abb), expectedOffset, createdId, file: file, line: line) + func testAbbreviation(_ abb: String, _ expectedOffset: Int?, _ createdId: String?, sourceLocation: SourceLocation = #_sourceLocation) throws { + try _verify(TimeZone(abbreviation: abb), expectedOffset, createdId, sourceLocation: sourceLocation) } - testIdentifier("America/Los_Angeles", -28800, "America/Los_Angeles") - testIdentifier("GMT", 0, "GMT") - testIdentifier("PST", -28800, "PST") - testIdentifier("GMT+8", 28800, "GMT+0800") - testIdentifier("GMT+8:00", 28800, "GMT+0800") - testIdentifier("BOGUS", nil, nil) - testIdentifier("XYZ", nil, nil) - testIdentifier("UTC", 0, "GMT") - - testAbbreviation("America/Los_Angeles", nil, nil) - testAbbreviation("XYZ", nil, nil) - testAbbreviation("GMT", 0, "GMT") - testAbbreviation("PST", -28800, "America/Los_Angeles") - testAbbreviation("GMT+8", 28800, "GMT+0800") - testAbbreviation("GMT+8:00", 28800, "GMT+0800") - testAbbreviation("GMT+0800", 28800, "GMT+0800") - testAbbreviation("UTC", 0, "GMT") + try testIdentifier("America/Los_Angeles", -28800, "America/Los_Angeles") + try testIdentifier("GMT", 0, "GMT") + try testIdentifier("PST", -28800, "PST") + try testIdentifier("GMT+8", 28800, "GMT+0800") + try testIdentifier("GMT+8:00", 28800, "GMT+0800") + try testIdentifier("BOGUS", nil, nil) + try testIdentifier("XYZ", nil, nil) + try testIdentifier("UTC", 0, "GMT") + + try testAbbreviation("America/Los_Angeles", nil, nil) + try testAbbreviation("XYZ", nil, nil) + try testAbbreviation("GMT", 0, "GMT") + try testAbbreviation("PST", -28800, "America/Los_Angeles") + try testAbbreviation("GMT+8", 28800, "GMT+0800") + try testAbbreviation("GMT+8:00", 28800, "GMT+0800") + try testAbbreviation("GMT+0800", 28800, "GMT+0800") + try testAbbreviation("UTC", 0, "GMT") } - func testSecondsFromGMT_RemoteDates() { + @Test func secondsFromGMT_RemoteDates() { let date = Date(timeIntervalSinceReferenceDate: -5001243627) // "1842-07-09T05:39:33+0000" let europeRome = TimeZone(identifier: "Europe/Rome")! let secondsFromGMT = europeRome.secondsFromGMT(for: date) - XCTAssertEqual(secondsFromGMT, 2996) // Before 1893 the time zone is UTC+00:49:56 + #expect(secondsFromGMT == 2996) // Before 1893 the time zone is UTC+00:49:56 + } + + func decodeHelper(_ l: TimeZone) throws -> TimeZone { + let je = JSONEncoder() + let data = try je.encode(l) + let jd = JSONDecoder() + return try jd.decode(TimeZone.self, from: data) + } + + @Test func serializationOfCurrent() async throws { + try await usingCurrentInternationalizationPreferences { + let current = TimeZone.current + let decodedCurrent = try decodeHelper(current) + #expect(decodedCurrent == current) + + let autoupdatingCurrent = TimeZone.autoupdatingCurrent + let decodedAutoupdatingCurrent = try decodeHelper(autoupdatingCurrent) + #expect(decodedAutoupdatingCurrent == autoupdatingCurrent) + + #expect(decodedCurrent != decodedAutoupdatingCurrent) + #expect(current != autoupdatingCurrent) + #expect(decodedCurrent != autoupdatingCurrent) + #expect(current != decodedAutoupdatingCurrent) + } } } -final class TimeZoneGMTTests : XCTestCase { +@Suite("TimeZone GMT") +private struct TimeZoneGMTTests { var tz: TimeZone { TimeZone(identifier: "GMT")! } - func testIdentifier() { - XCTAssertEqual(tz.identifier, "GMT") + @Test func identifier() { + #expect(tz.identifier == "GMT") } - func testSecondsFromGMT() { - XCTAssertEqual(tz.secondsFromGMT(), 0) + @Test func secondsFromGMT() { + #expect(tz.secondsFromGMT() == 0) } - func testSecondsFromGMTForDate() { - XCTAssertEqual(tz.secondsFromGMT(for: Date.now), 0) - XCTAssertEqual(tz.secondsFromGMT(for: Date.distantFuture), 0) - XCTAssertEqual(tz.secondsFromGMT(for: Date.distantPast), 0) + @Test func secondsFromGMTForDate() { + #expect(tz.secondsFromGMT(for: Date.now) == 0) + #expect(tz.secondsFromGMT(for: Date.distantFuture) == 0) + #expect(tz.secondsFromGMT(for: Date.distantPast) == 0) } - func testAbbreviationForDate() { - XCTAssertEqual(tz.abbreviation(for: Date.now), "GMT") - XCTAssertEqual(tz.abbreviation(for: Date.distantFuture), "GMT") - XCTAssertEqual(tz.abbreviation(for: Date.distantPast), "GMT") + @Test func abbreviationForDate() { + #expect(tz.abbreviation(for: Date.now) == "GMT") + #expect(tz.abbreviation(for: Date.distantFuture) == "GMT") + #expect(tz.abbreviation(for: Date.distantPast) == "GMT") } - func testDaylightSavingTimeOffsetForDate() { - XCTAssertEqual(tz.daylightSavingTimeOffset(for: Date.now), 0) - XCTAssertEqual(tz.daylightSavingTimeOffset(for: Date.distantFuture), 0) - XCTAssertEqual(tz.daylightSavingTimeOffset(for: Date.distantPast), 0) + @Test func daylightSavingTimeOffsetForDate() { + #expect(tz.daylightSavingTimeOffset(for: Date.now) == 0) + #expect(tz.daylightSavingTimeOffset(for: Date.distantFuture) == 0) + #expect(tz.daylightSavingTimeOffset(for: Date.distantPast) == 0) } - func testNextDaylightSavingTimeTransitionAfterDate() { - XCTAssertNil(tz.nextDaylightSavingTimeTransition(after: Date.now)) - XCTAssertNil(tz.nextDaylightSavingTimeTransition(after: Date.distantFuture)) - XCTAssertNil(tz.nextDaylightSavingTimeTransition(after: Date.distantPast)) + @Test func nextDaylightSavingTimeTransitionAfterDate() { + #expect(tz.nextDaylightSavingTimeTransition(after: Date.now) == nil) + #expect(tz.nextDaylightSavingTimeTransition(after: Date.distantFuture) == nil) + #expect(tz.nextDaylightSavingTimeTransition(after: Date.distantPast) == nil) } - func testNextDaylightSavingTimeTransition() { - XCTAssertNil(tz.nextDaylightSavingTimeTransition) - XCTAssertNil(tz.nextDaylightSavingTimeTransition) - XCTAssertNil(tz.nextDaylightSavingTimeTransition) + @Test func nextDaylightSavingTimeTransition() { + #expect(tz.nextDaylightSavingTimeTransition == nil) + #expect(tz.nextDaylightSavingTimeTransition == nil) + #expect(tz.nextDaylightSavingTimeTransition == nil) } - func testLocalizedName() { - XCTAssertEqual(tz.localizedName(for: .standard, locale: Locale(identifier: "en_US")), "Greenwich Mean Time") - XCTAssertEqual(tz.localizedName(for: .shortStandard, locale: Locale(identifier: "en_US")), "GMT") - XCTAssertEqual(tz.localizedName(for: .daylightSaving, locale: Locale(identifier: "en_US")), "Greenwich Mean Time") - XCTAssertEqual(tz.localizedName(for: .shortDaylightSaving, locale: Locale(identifier: "en_US")), "GMT") - XCTAssertEqual(tz.localizedName(for: .generic, locale: Locale(identifier: "en_US")), "Greenwich Mean Time") - XCTAssertEqual(tz.localizedName(for: .shortGeneric, locale: Locale(identifier: "en_US")), "GMT") + @Test func localizedName() { + #expect(tz.localizedName(for: .standard, locale: Locale(identifier: "en_US")) == "Greenwich Mean Time") + #expect(tz.localizedName(for: .shortStandard, locale: Locale(identifier: "en_US")) == "GMT") + #expect(tz.localizedName(for: .daylightSaving, locale: Locale(identifier: "en_US")) == "Greenwich Mean Time") + #expect(tz.localizedName(for: .shortDaylightSaving, locale: Locale(identifier: "en_US")) == "GMT") + #expect(tz.localizedName(for: .generic, locale: Locale(identifier: "en_US")) == "Greenwich Mean Time") + #expect(tz.localizedName(for: .shortGeneric, locale: Locale(identifier: "en_US")) == "GMT") // TODO: In non-framework, no FoundationInternationalization cases, return nil for all of tehse } - func testEqual() { - XCTAssertEqual(TimeZone(identifier: "UTC"), TimeZone(identifier: "UTC")) + @Test func equal() { + #expect(TimeZone(identifier: "UTC") == TimeZone(identifier: "UTC")) } - func test_abbreviated() { + @Test func abbreviated() throws { // A sampling of expected values for abbreviated GMT names let expected : [(Int, String)] = [(-64800, "GMT-18"), (-64769, "GMT-17:59"), (-64709, "GMT-17:58"), (-61769, "GMT-17:09"), (-61229, "GMT-17"), (-36029, "GMT-10"), (-35969, "GMT-9:59"), (-35909, "GMT-9:58"), (-32489, "GMT-9:01"), (-32429, "GMT-9"), (-3629, "GMT-1"), (-1829, "GMT-0:30"), (-89, "GMT-0:01"), (-29, "GMT"), (-1, "GMT"), (0, "GMT"), (29, "GMT"), (30, "GMT+0:01"), (90, "GMT+0:02"), (1770, "GMT+0:30"), (3570, "GMT+1"), (3630, "GMT+1:01"), (34170, "GMT+9:30"), (35910, "GMT+9:59"), (35970, "GMT+10"), (36030, "GMT+10:01"), (64650, "GMT+17:58"), (64710, "GMT+17:59"), (64770, "GMT+18")] for (offset, expect) in expected { - let tz = TimeZone(secondsFromGMT: offset)! - XCTAssertEqual(tz.abbreviation(), expect) + let tz = try #require(TimeZone(secondsFromGMT: offset)) + #expect(tz.abbreviation() == expect) } } } -final class TimeZoneICUTests: XCTestCase { - func testTimeZoneOffset() { +@Suite("TimeZone ICU") +private struct TimeZoneICUTests { + @Test func timeZoneOffset() throws { let tz = _TimeZoneICU(identifier: "America/Los_Angeles")! var c = Calendar(identifier: .gregorian) c.timeZone = TimeZone(identifier: "America/Los_Angeles")! var gmt_calendar = Calendar(identifier: .gregorian) gmt_calendar.timeZone = .gmt - func test(_ dateComponent: DateComponents, expectedRawOffset: Int, expectedDSTOffset: TimeInterval, file: StaticString = #filePath, line: UInt = #line) { - let d = gmt_calendar.date(from: dateComponent)! // date in GMT + func test(_ dateComponent: DateComponents, expectedRawOffset: Int, expectedDSTOffset: TimeInterval, sourceLocation: SourceLocation = #_sourceLocation) throws { + let d = try #require(gmt_calendar.date(from: dateComponent)) // date in GMT let (rawOffset, dstOffset) = tz.rawAndDaylightSavingTimeOffset(for: d) - XCTAssertEqual(rawOffset, expectedRawOffset, file: file, line: line) - XCTAssertEqual(dstOffset, expectedDSTOffset, file: file, line: line) + #expect(rawOffset == expectedRawOffset, sourceLocation: sourceLocation) + #expect(dstOffset == expectedDSTOffset, sourceLocation: sourceLocation) } // Not in DST - test(.init(year: 2023, month: 3, day: 12, hour: 1, minute: 00, second: 00), expectedRawOffset: -28800, expectedDSTOffset: 0) - test(.init(year: 2023, month: 3, day: 12, hour: 1, minute: 00, second: 00, nanosecond: 1), expectedRawOffset: -28800, expectedDSTOffset: 0) - test(.init(year: 2023, month: 3, day: 12, hour: 1, minute: 00, second: 01), expectedRawOffset: -28800, expectedDSTOffset: 0) - test(.init(year: 2023, month: 3, day: 12, hour: 1, minute: 59, second: 59), expectedRawOffset: -28800, expectedDSTOffset: 0) + try test(.init(year: 2023, month: 3, day: 12, hour: 1, minute: 00, second: 00), expectedRawOffset: -28800, expectedDSTOffset: 0) + try test(.init(year: 2023, month: 3, day: 12, hour: 1, minute: 00, second: 00, nanosecond: 1), expectedRawOffset: -28800, expectedDSTOffset: 0) + try test(.init(year: 2023, month: 3, day: 12, hour: 1, minute: 00, second: 01), expectedRawOffset: -28800, expectedDSTOffset: 0) + try test(.init(year: 2023, month: 3, day: 12, hour: 1, minute: 59, second: 59), expectedRawOffset: -28800, expectedDSTOffset: 0) // These times do not exist; we treat it as if in the previous time zone, i.e. not in DST - test(.init(year: 2023, month: 3, day: 12, hour: 2, minute: 00, second: 00), expectedRawOffset: -28800, expectedDSTOffset: 0) - test(.init(year: 2023, month: 3, day: 12, hour: 2, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 0) + try test(.init(year: 2023, month: 3, day: 12, hour: 2, minute: 00, second: 00), expectedRawOffset: -28800, expectedDSTOffset: 0) + try test(.init(year: 2023, month: 3, day: 12, hour: 2, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 0) // After DST starts - test(.init(year: 2023, month: 3, day: 12, hour: 3, minute: 00, second: 00), expectedRawOffset: -28800, expectedDSTOffset: 3600) - test(.init(year: 2023, month: 3, day: 12, hour: 3, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 3600) - test(.init(year: 2023, month: 3, day: 12, hour: 4, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 3600) + try test(.init(year: 2023, month: 3, day: 12, hour: 3, minute: 00, second: 00), expectedRawOffset: -28800, expectedDSTOffset: 3600) + try test(.init(year: 2023, month: 3, day: 12, hour: 3, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 3600) + try test(.init(year: 2023, month: 3, day: 12, hour: 4, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 3600) // These times happen twice; we treat it as if in the previous time zone, i.e. still in DST - test(.init(year: 2023, month: 11, day: 5, hour: 1, minute: 00, second: 00), expectedRawOffset: -28800, expectedDSTOffset: 3600) - test(.init(year: 2023, month: 11, day: 5, hour: 1, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 3600) + try test(.init(year: 2023, month: 11, day: 5, hour: 1, minute: 00, second: 00), expectedRawOffset: -28800, expectedDSTOffset: 3600) + try test(.init(year: 2023, month: 11, day: 5, hour: 1, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 3600) // Clock should turn right back as this moment, so if we insist on being at this point, then we've moved past the transition point -- hence not DST - test(.init(year: 2023, month: 11, day: 5, hour: 2, minute: 00, second: 00), expectedRawOffset: -28800, expectedDSTOffset: 0) + try test(.init(year: 2023, month: 11, day: 5, hour: 2, minute: 00, second: 00), expectedRawOffset: -28800, expectedDSTOffset: 0) // Not in DST - test(.init(year: 2023, month: 11, day: 5, hour: 2, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 0) - test(.init(year: 2023, month: 11, day: 5, hour: 3, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 0) + try test(.init(year: 2023, month: 11, day: 5, hour: 2, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 0) + try test(.init(year: 2023, month: 11, day: 5, hour: 3, minute: 34, second: 52), expectedRawOffset: -28800, expectedDSTOffset: 0) } } // MARK: - FoundationPreview disabled tests -#if FOUNDATION_FRAMEWORK -extension TimeZoneTests { - func decodeHelper(_ l: TimeZone) -> TimeZone { - let je = JSONEncoder() - let data = try! je.encode(l) - let jd = JSONDecoder() - return try! jd.decode(TimeZone.self, from: data) - } - - // Reenable once JSONEncoder/Decoder are moved - func test_serializationOfCurrent() { - let current = TimeZone.current - let decodedCurrent = decodeHelper(current) - XCTAssertEqual(decodedCurrent, current) - - let autoupdatingCurrent = TimeZone.autoupdatingCurrent - let decodedAutoupdatingCurrent = decodeHelper(autoupdatingCurrent) - XCTAssertEqual(decodedAutoupdatingCurrent, autoupdatingCurrent) - - XCTAssertNotEqual(decodedCurrent, decodedAutoupdatingCurrent) - XCTAssertNotEqual(current, autoupdatingCurrent) - XCTAssertNotEqual(decodedCurrent, autoupdatingCurrent) - XCTAssertNotEqual(current, decodedAutoupdatingCurrent) - } -} -#endif // FOUNDATION_FRAMEWORK // MARK: - Bridging Tests #if FOUNDATION_FRAMEWORK -final class TimeZoneBridgingTests : XCTestCase { - func testCustomNSTimeZone() { +@Suite("TimeZone Bridging") +private struct TimeZoneBridgingTests { + @Test func customNSTimeZone() { // This test verifies that a custom ObjC subclass of NSTimeZone, bridged into Swift, still calls back into ObjC. `customTimeZone` returns an instances of "MyCustomTimeZone : NSTimeZone". let myTZ = customTimeZone() - XCTAssertEqual(myTZ.identifier, "MyCustomTimeZone") - XCTAssertEqual(myTZ.nextDaylightSavingTimeTransition(after: Date.now), Date(timeIntervalSince1970: 1000000)) - XCTAssertEqual(myTZ.secondsFromGMT(), 42) - XCTAssertEqual(myTZ.abbreviation(), "hello") - XCTAssertEqual(myTZ.isDaylightSavingTime(), true) - XCTAssertEqual(myTZ.daylightSavingTimeOffset(), 12345) - } - - func testCustomNSTimeZoneAsDefault() { - // Set a custom subclass of NSTimeZone as the default time zone - setCustomTimeZoneAsDefault() - - // Calendar uses the default time zone - let defaultTZ = Calendar.current.timeZone - XCTAssertEqual(defaultTZ.identifier, "MyCustomTimeZone") - XCTAssertEqual(defaultTZ.nextDaylightSavingTimeTransition(after: Date.now), Date(timeIntervalSince1970: 1000000)) - XCTAssertEqual(defaultTZ.secondsFromGMT(), 42) - XCTAssertEqual(defaultTZ.abbreviation(), "hello") - XCTAssertEqual(defaultTZ.isDaylightSavingTime(), true) - XCTAssertEqual(defaultTZ.daylightSavingTimeOffset(), 12345) + #expect(myTZ.identifier == "MyCustomTimeZone") + #expect(myTZ.nextDaylightSavingTimeTransition(after: Date.now) == Date(timeIntervalSince1970: 1000000)) + #expect(myTZ.secondsFromGMT() == 42) + #expect(myTZ.abbreviation() == "hello") + #expect(myTZ.isDaylightSavingTime() == true) + #expect(myTZ.daylightSavingTimeOffset() == 12345) } - func test_AnyHashableCreatedFromNSTimeZone() { + @Test func anyHashableCreatedFromNSTimeZone() { let values: [NSTimeZone] = [ NSTimeZone(name: "America/Los_Angeles")!, NSTimeZone(name: "Europe/Kiev")!, NSTimeZone(name: "Europe/Kiev")!, ] let anyHashables = values.map(AnyHashable.init) - expectEqual(TimeZone.self, type(of: anyHashables[0].base)) - expectEqual(TimeZone.self, type(of: anyHashables[1].base)) - expectEqual(TimeZone.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(TimeZone.self == type(of: anyHashables[0].base)) + #expect(TimeZone.self == type(of: anyHashables[1].base)) + #expect(TimeZone.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) + } + + @Test func customNSTimeZoneAsDefault() async { + await usingCurrentInternationalizationPreferences { + // Set a custom subclass of NSTimeZone as the default time zone + setCustomTimeZoneAsDefault() + + // Calendar uses the default time zone + let defaultTZ = Calendar.current.timeZone + #expect(defaultTZ.identifier == "MyCustomTimeZone") + #expect(defaultTZ.nextDaylightSavingTimeTransition(after: Date.now) == Date(timeIntervalSince1970: 1000000)) + #expect(defaultTZ.secondsFromGMT() == 42) + #expect(defaultTZ.abbreviation() == "hello") + #expect(defaultTZ.isDaylightSavingTime() == true) + #expect(defaultTZ.daylightSavingTimeOffset() == 12345) + } } } #endif // FOUNDATION_FRAMEWORK diff --git a/Tests/FoundationInternationalizationTests/URLTests+UIDNA.swift b/Tests/FoundationInternationalizationTests/URLTests+UIDNA.swift index 142e66bdf..489ccdc76 100644 --- a/Tests/FoundationInternationalizationTests/URLTests+UIDNA.swift +++ b/Tests/FoundationInternationalizationTests/URLTests+UIDNA.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import Testing + #if FOUNDATION_FRAMEWORK @testable import Foundation #else @@ -17,20 +19,17 @@ @testable import FoundationInternationalization #endif // FOUNDATION_FRAMEWORK -#if canImport(TestSupport) -import TestSupport -#endif - -final class URLUIDNATests: XCTestCase { - func testURLHostUIDNAEncoding() { +@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" - XCTAssertEqual(emojiURL?.absoluteString, emojiURLEncoded) - XCTAssertEqual(emojiURL?.host(percentEncoded: false), "xn--itacos-i50d.ws") + #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" - XCTAssertEqual(chineseURL?.absoluteString, chineseURLEncoded) - XCTAssertEqual(chineseURL?.host(percentEncoded: false), "xn--nw2a.xn--j6w193g") + #expect(chineseURL?.absoluteString == chineseURLEncoded) + #expect(chineseURL?.host(percentEncoded: false) == "xn--nw2a.xn--j6w193g") } }