From 83e3abd53769add2a433d30680c176a05ae96060 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Mon, 9 Jun 2025 11:35:26 -0700 Subject: [PATCH 01/14] Reenable AttributedString index tracking swift testing tests (#1338) * Revert "Revert "Add tests for AttributedString Index Tracking preconditions (#1326)" (#1337)" This reverts commit 2df5199661f026ca0255b8f256ae2fd35940ad3e. * Disable exit tests on Ubuntu 20.04 for now --- Package.swift | 20 +- .../AttributedStringIndexTrackingTests.swift | 226 ++++++++++++------ 2 files changed, 172 insertions(+), 74 deletions(-) diff --git a/Package.swift b/Package.swift index e9a3dec09..eaf8fc851 100644 --- a/Package.swift +++ b/Package.swift @@ -75,6 +75,20 @@ let wasiLibcCSettings: [CSetting] = [ .define("_WASI_EMULATED_MMAN", .when(platforms: [.wasi])), ] +var testOnlySwiftSettings: [SwiftSetting] = [ + // The latest Windows toolchain does not yet have exit tests in swift-testing + .define("FOUNDATION_EXIT_TESTS", .when(platforms: [.macOS, .linux, .openbsd])) +] + +#if os(Linux) +import FoundationEssentials + +if ProcessInfo.processInfo.operatingSystemVersionString.hasPrefix("Ubuntu 20.") { + // Exit tests currently hang indefinitely on Ubuntu 20. + testOnlySwiftSettings.removeFirst() +} +#endif + let package = Package( name: "swift-foundation", platforms: [.macOS("15"), .iOS("18"), .tvOS("18"), .watchOS("11")], @@ -171,7 +185,7 @@ let package = Package( "LifetimeDependenceMutableAccessors", .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux]) ), - ] + availabilityMacros + featureSettings + ] + availabilityMacros + featureSettings + testOnlySwiftSettings ), // FoundationInternationalization @@ -204,7 +218,7 @@ let package = Package( "TestSupport", "FoundationInternationalization", ], - swiftSettings: availabilityMacros + featureSettings + swiftSettings: availabilityMacros + featureSettings + testOnlySwiftSettings ), // FoundationMacros @@ -236,7 +250,7 @@ package.targets.append(contentsOf: [ "FoundationMacros", "TestSupport" ], - swiftSettings: availabilityMacros + featureSettings + swiftSettings: availabilityMacros + featureSettings + testOnlySwiftSettings ) ]) #endif diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexTrackingTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexTrackingTests.swift index 01f2c5bf1..e89f1a4a0 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexTrackingTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexTrackingTests.swift @@ -10,202 +10,286 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation #endif -final class AttributedStringIndexTrackingTests: XCTestCase { - func testBasic() throws { +@Suite("AttributedString Index Tracking") +private struct AttributedStringIndexTrackingTests { + @Test + func basics() throws { var text = AttributedString("ABC. Hello, world!") let original = text - let helloRange = try XCTUnwrap(text.range(of: "Hello")) - let worldRange = try XCTUnwrap(text.range(of: "world")) + let helloRange = try #require(text.range(of: "Hello")) + let worldRange = try #require(text.range(of: "world")) - let updatedRanges = try XCTUnwrap(text.transform(updating: [helloRange, worldRange]) { + let updatedRanges = try #require(text.transform(updating: [helloRange, worldRange]) { $0.insert(AttributedString("Goodbye. "), at: $0.startIndex) }) - XCTAssertEqual(updatedRanges.count, 2) - XCTAssertEqual(text[updatedRanges[0]], original[helloRange]) - XCTAssertEqual(text[updatedRanges[1]], original[worldRange]) + #expect(updatedRanges.count == 2) + #expect(text[updatedRanges[0]] == original[helloRange]) + #expect(text[updatedRanges[1]] == original[worldRange]) } - func testInsertionWithinRange() throws { + @Test + func insertionWithinRange() throws { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: "Hello")) + var helloRange = try #require(text.range(of: "Hello")) text.transform(updating: &helloRange) { $0.insert(AttributedString("_Goodbye_"), at: $0.index($0.startIndex, offsetByCharacters: 3)) } - XCTAssertEqual(String(text[helloRange].characters), "Hel_Goodbye_lo") + #expect(String(text[helloRange].characters) == "Hel_Goodbye_lo") } - func testInsertionAtStartOfRange() throws { + @Test + func insertionAtStartOfRange() throws { var text = AttributedString("Hello, world") - let helloRange = try XCTUnwrap(text.range(of: "llo")) + let helloRange = try #require(text.range(of: "llo")) - let updatedHelloRange = try XCTUnwrap(text.transform(updating: helloRange) { + let updatedHelloRange = try #require(text.transform(updating: helloRange) { $0.insert(AttributedString("_"), at: helloRange.lowerBound) }) - XCTAssertEqual(String(text[updatedHelloRange].characters), "llo") + #expect(String(text[updatedHelloRange].characters) == "llo") } - func testInsertionAtEndOfRange() throws { + @Test + func insertionAtEndOfRange() throws { var text = AttributedString("Hello, world") - let helloRange = try XCTUnwrap(text.range(of: "llo")) + let helloRange = try #require(text.range(of: "llo")) - let updatedHelloRange = try XCTUnwrap(text.transform(updating: helloRange) { + let updatedHelloRange = try #require(text.transform(updating: helloRange) { $0.insert(AttributedString("_"), at: helloRange.upperBound) }) - XCTAssertEqual(String(text[updatedHelloRange].characters), "llo") + #expect(String(text[updatedHelloRange].characters) == "llo") } - func testInsertionAtEmptyRange() throws { + @Test + func insertionAtEmptyRange() throws { var text = AttributedString("ABCDE") let idx = text.index(text.startIndex, offsetByCharacters: 3) - let updatedRange = try XCTUnwrap(text.transform(updating: idx ..< idx) { + let updatedRange = try #require(text.transform(updating: idx ..< idx) { $0.insert(AttributedString("_"), at: idx) }) - XCTAssertEqual(updatedRange.lowerBound, updatedRange.upperBound) - XCTAssertEqual(text.characters[updatedRange.lowerBound], "D") + #expect(updatedRange.lowerBound == updatedRange.upperBound) + #expect(text.characters[updatedRange.lowerBound] == "D") } - func testRemovalWithinRange() throws { + @Test + func removalWithinRange() throws { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: "Hello")) + var helloRange = try #require(text.range(of: "Hello")) try text.transform(updating: &helloRange) { - $0.removeSubrange(try XCTUnwrap($0.range(of: "ll"))) + $0.removeSubrange(try #require($0.range(of: "ll"))) } - XCTAssertEqual(String(text[helloRange].characters), "Heo") + #expect(String(text[helloRange].characters) == "Heo") } - func testFullCollapse() throws { + @Test + func fullCollapse() throws { do { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: "Hello")) + var helloRange = try #require(text.range(of: "Hello")) text.transform(updating: &helloRange) { $0.removeSubrange($0.startIndex ..< $0.endIndex) } - XCTAssertEqual(String(text[helloRange].characters), "") + #expect(String(text[helloRange].characters) == "") } do { var text = AttributedString("Hello, world") - let helloRange = try XCTUnwrap(text.range(of: "Hello")) + let helloRange = try #require(text.range(of: "Hello")) - let updatedHelloRange = try XCTUnwrap(text.transform(updating: helloRange) { + let updatedHelloRange = try #require(text.transform(updating: helloRange) { $0.removeSubrange(helloRange) }) - XCTAssertEqual(String(text[updatedHelloRange].characters), "") + #expect(String(text[updatedHelloRange].characters) == "") } do { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: ", ")) + var helloRange = try #require(text.range(of: ", ")) try text.transform(updating: &helloRange) { - $0.removeSubrange(try XCTUnwrap($0.range(of: "o, w"))) + $0.removeSubrange(try #require($0.range(of: "o, w"))) } - XCTAssertEqual(String(text[helloRange].characters), "") + #expect(String(text[helloRange].characters) == "") let collapsedIdx = text.index(text.startIndex, offsetByCharacters: 4) - XCTAssertEqual(helloRange, collapsedIdx ..< collapsedIdx) + #expect(helloRange == collapsedIdx ..< collapsedIdx) } } - func testCollapseLeft() throws { + @Test + func collapseLeft() throws { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: "Hello")) + var helloRange = try #require(text.range(of: "Hello")) try text.transform(updating: &helloRange) { - $0.removeSubrange(try XCTUnwrap($0.range(of: "llo, wo"))) + $0.removeSubrange(try #require($0.range(of: "llo, wo"))) } - XCTAssertEqual(String(text[helloRange].characters), "He") + #expect(String(text[helloRange].characters) == "He") } - func testCollapseRight() throws { + @Test + func collapseRight() throws { var text = AttributedString("Hello, world") - var worldRange = try XCTUnwrap(text.range(of: "world")) + var worldRange = try #require(text.range(of: "world")) try text.transform(updating: &worldRange) { - $0.removeSubrange(try XCTUnwrap($0.range(of: "llo, wo"))) + $0.removeSubrange(try #require($0.range(of: "llo, wo"))) } - XCTAssertEqual(String(text[worldRange].characters), "rld") + #expect(String(text[worldRange].characters) == "rld") } - func testNesting() throws { + @Test + func nesting() throws { var text = AttributedString("Hello, world") - var helloRange = try XCTUnwrap(text.range(of: "Hello")) + var helloRange = try #require(text.range(of: "Hello")) try text.transform(updating: &helloRange) { - var worldRange = try XCTUnwrap($0.range(of: "world")) + var worldRange = try #require($0.range(of: "world")) try $0.transform(updating: &worldRange) { - $0.removeSubrange(try XCTUnwrap($0.range(of: "llo, wo"))) + $0.removeSubrange(try #require($0.range(of: "llo, wo"))) } - XCTAssertEqual(String($0[worldRange].characters), "rld") + #expect(String($0[worldRange].characters) == "rld") } - XCTAssertEqual(String(text[helloRange].characters), "He") + #expect(String(text[helloRange].characters) == "He") } - func testTrackingLost() throws { + #if FOUNDATION_EXIT_TESTS + @Test + func trackingLostPreconditions() async { + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + var helloRange = try #require(text.range(of: "Hello")) + text.transform(updating: &helloRange) { + $0 = AttributedString("Foo") + } + } + + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + var helloRange = try #require(text.range(of: "Hello")) + text.transform(updating: &helloRange) { + $0 = AttributedString("Hello world") + } + } + + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + var ranges = [try #require(text.range(of: "Hello"))] + text.transform(updating: &ranges) { + $0 = AttributedString("Foo") + } + } + + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + var ranges = [try #require(text.range(of: "Hello"))] + text.transform(updating: &ranges) { + $0 = AttributedString("Hello world") + } + } + } + #endif + + @Test + func trackingLost() throws { let text = AttributedString("Hello, world") - let helloRange = try XCTUnwrap(text.range(of: "Hello")) + let helloRange = try #require(text.range(of: "Hello")) do { var copy = text - XCTAssertNil(copy.transform(updating: helloRange) { + #expect(copy.transform(updating: helloRange) { $0 = AttributedString("Foo") - }) + } == nil) } do { var copy = text - XCTAssertNil(copy.transform(updating: helloRange) { + #expect(copy.transform(updating: helloRange) { $0 = AttributedString("Hello world") - }) + } == nil) } do { var copy = text - XCTAssertNotNil(copy.transform(updating: helloRange) { + #expect(copy.transform(updating: helloRange) { $0 = $0 - }) + } != nil) } do { var copy = text - XCTAssertNotNil(copy.transform(updating: helloRange) { + #expect(copy.transform(updating: helloRange) { var reference = $0 reference.testInt = 2 $0 = $0 - }) - XCTAssertNil(copy.testInt) + } != nil) + #expect(copy.testInt == nil) } } - func testAttributeMutation() throws { + @Test + func attributeMutation() throws { var text = AttributedString("Hello, world!") let original = text - let helloRange = try XCTUnwrap(text.range(of: "Hello")) - let worldRange = try XCTUnwrap(text.range(of: "world")) + let helloRange = try #require(text.range(of: "Hello")) + let worldRange = try #require(text.range(of: "world")) - let updatedRanges = try XCTUnwrap(text.transform(updating: [helloRange, worldRange]) { + let updatedRanges = try #require(text.transform(updating: [helloRange, worldRange]) { $0.testInt = 2 }) - XCTAssertEqual(updatedRanges.count, 2) - XCTAssertEqual(AttributedString(text[updatedRanges[0]]), original[helloRange].settingAttributes(AttributeContainer.testInt(2))) - XCTAssertEqual(AttributedString(text[updatedRanges[1]]), original[worldRange].settingAttributes(AttributeContainer.testInt(2))) + #expect(updatedRanges.count == 2) + #expect(AttributedString(text[updatedRanges[0]]) == original[helloRange].settingAttributes(AttributeContainer.testInt(2))) + #expect(AttributedString(text[updatedRanges[1]]) == original[worldRange].settingAttributes(AttributeContainer.testInt(2))) + } + + #if FOUNDATION_EXIT_TESTS + @Test + func invalidInputRanges() async { + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + let other = text + AttributedString("Extra text") + let range = other.startIndex ..< other.endIndex + _ = text.transform(updating: range) { _ in + + } + } + + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + let other = text + AttributedString("Extra text") + let range = other.endIndex ..< other.endIndex + _ = text.transform(updating: range) { _ in + + } + } + + await #expect(processExitsWith: .failure) { + var text = AttributedString("Hello, world") + _ = text.transform(updating: []) { _ in + + } + } } + #endif } From 276dd51928d6978d4a2e0e5667909661867a9bd8 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Mon, 9 Jun 2025 12:31:39 -0700 Subject: [PATCH 02/14] Adopt swift-testing for AttributedString tests (#1334) * Convert CoW tests to swift-testing * Convert discontiguous tests to swift-testing * Convert index validity tests to swift-testing * Convert constraining behavior tests to swift-testing * Convert base attributed string tests to swift-testing * Fix build failure * Fix test crash --- .../AttributedStringCOWTests.swift | 60 +- ...butedStringConstrainingBehaviorTests.swift | 97 +- .../AttributedStringDiscontiguousTests.swift | 130 +- .../AttributedStringIndexValidityTests.swift | 194 +-- .../AttributedStringTests.swift | 1351 +++++++++-------- 5 files changed, 944 insertions(+), 888 deletions(-) diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringCOWTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringCOWTests.swift index 2f8fd4b52..d31a835c4 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringCOWTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringCOWTests.swift @@ -10,14 +10,12 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing -#if FOUNDATION_FRAMEWORK -@testable import Foundation -#else +#if canImport(FoundationEssentials) @testable import FoundationEssentials +#else +@testable import Foundation #endif extension AttributedStringProtocol { @@ -27,7 +25,8 @@ extension AttributedStringProtocol { } /// Tests for `AttributedString` to confirm expected CoW behavior -final class TestAttributedStringCOW: XCTestCase { +@Suite("AttributedString Copy on Write") +private struct AttributedStringCOWTests { // MARK: - Utility Functions @@ -38,32 +37,32 @@ final class TestAttributedStringCOW: XCTestCase { return str } - func assertCOWCopy(file: StaticString = #filePath, line: UInt = #line, _ operation: (inout AttributedString) -> Void) { + func assertCOWCopy(sourceLocation: SourceLocation = #_sourceLocation, _ operation: (inout AttributedString) -> Void) { let str = createAttributedString() var copy = str operation(©) - XCTAssertNotEqual(str, copy, "Mutation operation did not copy when multiple references exist", file: file, line: line) + #expect(str != copy, "Mutation operation did not copy when multiple references exist", sourceLocation: sourceLocation) } - func assertCOWCopyManual(file: StaticString = #filePath, line: UInt = #line, _ operation: (inout AttributedString) -> Void) { + func assertCOWCopyManual(sourceLocation: SourceLocation = #_sourceLocation, _ operation: (inout AttributedString) -> Void) { var str = createAttributedString() let gutsPtr = Unmanaged.passUnretained(str._guts) operation(&str) let newGutsPtr = Unmanaged.passUnretained(str._guts) - XCTAssertNotEqual(gutsPtr.toOpaque(), newGutsPtr.toOpaque(), "Mutation operation with manual copy did not perform copy", file: file, line: line) + #expect(gutsPtr.toOpaque() != newGutsPtr.toOpaque(), "Mutation operation with manual copy did not perform copy", sourceLocation: sourceLocation) } - func assertCOWNoCopy(file: StaticString = #filePath, line: UInt = #line, _ operation: (inout AttributedString) -> Void) { + func assertCOWNoCopy(sourceLocation: SourceLocation = #_sourceLocation, _ operation: (inout AttributedString) -> Void) { var str = createAttributedString() let gutsPtr = Unmanaged.passUnretained(str._guts) operation(&str) let newGutsPtr = Unmanaged.passUnretained(str._guts) - XCTAssertEqual(gutsPtr.toOpaque(), newGutsPtr.toOpaque(), "Mutation operation copied when only one reference exists", file: file, line: line) + #expect(gutsPtr.toOpaque() == newGutsPtr.toOpaque(), "Mutation operation copied when only one reference exists", sourceLocation: sourceLocation) } - func assertCOWBehavior(file: StaticString = #filePath, line: UInt = #line, _ operation: (inout AttributedString) -> Void) { - assertCOWCopy(file: file, line: line, operation) - assertCOWNoCopy(file: file, line: line, operation) + func assertCOWBehavior(sourceLocation: SourceLocation = #_sourceLocation, _ operation: (inout AttributedString) -> Void) { + assertCOWCopy(sourceLocation: sourceLocation, operation) + assertCOWNoCopy(sourceLocation: sourceLocation, operation) } func makeSubrange(_ str: AttributedString) -> Range { @@ -76,13 +75,13 @@ final class TestAttributedStringCOW: XCTestCase { return RangeSet([rangeA, rangeB]) } - lazy var container: AttributeContainer = { + let container: AttributeContainer = { var container = AttributeContainer() container.testInt = 2 return container }() - lazy var containerB: AttributeContainer = { + let containerB: AttributeContainer = { var container = AttributeContainer() container.testBool = true return container @@ -90,7 +89,8 @@ final class TestAttributedStringCOW: XCTestCase { // MARK: - Tests - func testTopLevelType() { + @Test + func topLevelType() { assertCOWBehavior { (str) in str.setAttributes(container) } @@ -126,7 +126,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testSubstring() { + @Test + func substring() { assertCOWBehavior { (str) in str[makeSubrange(str)].setAttributes(container) } @@ -147,7 +148,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testDiscontiguousSubstring() { + @Test + func discontiguousSubstring() { assertCOWBehavior { (str) in str[makeSubranges(str)].setAttributes(container) } @@ -172,7 +174,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testCharacters() { + @Test + func characters() { let char: Character = "a" assertCOWBehavior { (str) in @@ -195,7 +198,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testUnicodeScalars() { + @Test + func unicodeScalars() { let scalar: UnicodeScalar = "a" assertCOWBehavior { (str) in @@ -203,7 +207,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testGenericProtocol() { + @Test + func genericProtocol() { assertCOWBehavior { $0.genericSetAttribute() } @@ -212,7 +217,8 @@ final class TestAttributedStringCOW: XCTestCase { } } - func testIndexTracking() { + @Test + func indexTracking() { assertCOWBehavior { _ = $0.transform(updating: $0.startIndex ..< $0.endIndex) { $0.testInt = 2 @@ -243,7 +249,7 @@ final class TestAttributedStringCOW: XCTestCase { storage = $0 } } - XCTAssertNotEqual(storage, "") + #expect(storage != "") // Ensure the same semantics hold even when the closure throws storage = AttributedString() @@ -255,6 +261,6 @@ final class TestAttributedStringCOW: XCTestCase { throw CocoaError(.fileReadUnknown) } } - XCTAssertNotEqual(storage, "") + #expect(storage != "") } } diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringConstrainingBehaviorTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringConstrainingBehaviorTests.swift index 24698e8ed..f3f41d360 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringConstrainingBehaviorTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringConstrainingBehaviorTests.swift @@ -10,73 +10,78 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation #endif -class TestAttributedStringConstrainingBehavior: XCTestCase { +@Suite("AttributedString Constraining Behavior") +private struct AttributedStringConstrainingBehaviorTests { func verify( string: AttributedString, matches expected: [(String, K.Value?)], for key: KeyPath, - file: StaticString = #filePath, line: UInt = #line - ) + sourceLocation: SourceLocation = #_sourceLocation + ) where K.Value : Sendable { let runs = string.runs[key] - XCTAssertEqual(runs.count, expected.count, "Unexpected number of runs", file: file, line: line) + #expect(runs.count == expected.count, "Unexpected number of runs", sourceLocation: sourceLocation) for ((val, range), expectation) in zip(runs, expected) { let slice = String.UnicodeScalarView(string.unicodeScalars[range]) - XCTAssertTrue(slice.elementsEqual(expectation.0.unicodeScalars), "Unexpected range of run: \(slice.debugDescription) vs \(expectation.0.debugDescription)", file: file, line: line) - XCTAssertEqual(val, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", file: file, line: line) + #expect(slice.elementsEqual(expectation.0.unicodeScalars), "Unexpected range of run: \(slice.debugDescription) vs \(expectation.0.debugDescription)", sourceLocation: sourceLocation) + #expect(val == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", sourceLocation: sourceLocation) } for ((val, range), expectation) in zip(runs.reversed(), expected.reversed()) { let slice = String.UnicodeScalarView(string.unicodeScalars[range]) - XCTAssertTrue(slice.elementsEqual(expectation.0.unicodeScalars), "Unexpected range of run while reverse iterating: \(slice.debugDescription) vs \(expectation.0.debugDescription)", file: file, line: line) - XCTAssertEqual(val, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", file: file, line: line) + #expect(slice.elementsEqual(expectation.0.unicodeScalars), "Unexpected range of run while reverse iterating: \(slice.debugDescription) vs \(expectation.0.debugDescription)", sourceLocation: sourceLocation) + #expect(val == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) } } - func verify(string: AttributedString, matches expected: [(String, K.Value?, K2.Value?)], for key: KeyPath, _ key2: KeyPath, file: StaticString = #filePath, line: UInt = #line) + func verify(string: AttributedString, matches expected: [(String, K.Value?, K2.Value?)], for key: KeyPath, _ key2: KeyPath, sourceLocation: SourceLocation = #_sourceLocation) where K.Value : Sendable, K2.Value : Sendable { let runs = string.runs[key, key2] - XCTAssertEqual(runs.count, expected.count, "Unexpected number of runs", file: file, line: line) + #expect(runs.count == expected.count, "Unexpected number of runs", sourceLocation: sourceLocation) for ((val1, val2, range), expectation) in zip(runs, expected) { - XCTAssertEqual(String(string.characters[range]),expectation.0, "Unexpected range of run", file: file, line: line) - XCTAssertEqual(val1, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", file: file, line: line) - XCTAssertEqual(val2, expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0)", file: file, line: line) + #expect(String(string.characters[range]) == expectation.0, "Unexpected range of run", sourceLocation: sourceLocation) + #expect(val1 == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", sourceLocation: sourceLocation) + #expect(val2 == expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0)", sourceLocation: sourceLocation) } for ((val1, val2, range), expectation) in zip(runs.reversed(), expected.reversed()) { - XCTAssertEqual(String(string.characters[range]), expectation.0, "Unexpected range of run while reverse iterating", file: file, line: line) - XCTAssertEqual(val1, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", file: file, line: line) - XCTAssertEqual(val2, expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0) while reverse iterating", file: file, line: line) + #expect(String(string.characters[range]) == expectation.0, "Unexpected range of run while reverse iterating", sourceLocation: sourceLocation) + #expect(val1 == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) + #expect(val2 == expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) } } - func verify(string: AttributedString, matches expected: [(String, K.Value?, K2.Value?, K3.Value?)], for key: KeyPath, _ key2: KeyPath, _ key3: KeyPath, file: StaticString = #filePath, line: UInt = #line) + func verify(string: AttributedString, matches expected: [(String, K.Value?, K2.Value?, K3.Value?)], for key: KeyPath, _ key2: KeyPath, _ key3: KeyPath, sourceLocation: SourceLocation = #_sourceLocation) where K.Value : Sendable, K2.Value : Sendable, K3.Value : Sendable { let runs = string.runs[key, key2, key3] - XCTAssertEqual(runs.count, expected.count, "Unexpected number of runs", file: file, line: line) + #expect(runs.count == expected.count, "Unexpected number of runs", sourceLocation: sourceLocation) for ((val1, val2, val3, range), expectation) in zip(runs, expected) { - XCTAssertEqual(String(string.characters[range]),expectation.0, "Unexpected range of run", file: file, line: line) - XCTAssertEqual(val1, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", file: file, line: line) - XCTAssertEqual(val2, expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0)", file: file, line: line) - XCTAssertEqual(val3, expectation.3, "Unexpected value of attribute \(K3.self) for range \(expectation.0)", file: file, line: line) + #expect(String(string.characters[range]) == expectation.0, "Unexpected range of run", sourceLocation: sourceLocation) + #expect(val1 == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0)", sourceLocation: sourceLocation) + #expect(val2 == expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0)", sourceLocation: sourceLocation) + #expect(val3 == expectation.3, "Unexpected value of attribute \(K3.self) for range \(expectation.0)", sourceLocation: sourceLocation) } for ((val1, val2, val3, range), expectation) in zip(runs.reversed(), expected.reversed()) { - XCTAssertEqual(String(string.characters[range]), expectation.0, "Unexpected range of run while reverse iterating", file: file, line: line) - XCTAssertEqual(val1, expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", file: file, line: line) - XCTAssertEqual(val2, expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0) while reverse iterating", file: file, line: line) - XCTAssertEqual(val3, expectation.3, "Unexpected value of attribute \(K3.self) for range \(expectation.0) while reverse iterating", file: file, line: line) + #expect(String(string.characters[range]) == expectation.0, "Unexpected range of run while reverse iterating", sourceLocation: sourceLocation) + #expect(val1 == expectation.1, "Unexpected value of attribute \(K.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) + #expect(val2 == expectation.2, "Unexpected value of attribute \(K2.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) + #expect(val3 == expectation.3, "Unexpected value of attribute \(K3.self) for range \(expectation.0) while reverse iterating", sourceLocation: sourceLocation) } } // MARK: Extending Run Tests - func testExtendingRunAddCharacters() { + @Test func extendingRunAddCharacters() { let str = AttributedString("Hello, world", attributes: .init().testInt(2).testNonExtended(1)) var result = str @@ -103,7 +108,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("He", 2, 1), ("Hi!", 2, nil), ("rld", 2, 1)], for: \.testInt, \.testNonExtended) } - func testExtendingRunAddUnicodeScalars() { + @Test func extendingRunAddUnicodeScalars() { let str = AttributedString("Hello, world", attributes: .init().testInt(2).testNonExtended(1)) let scalarsStr = "A\u{0301}B" @@ -127,7 +132,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { // MARK: - Paragraph Constrained Tests - func testParagraphAttributeExpanding() { + @Test func paragraphAttributeExpanding() { var str = AttributedString("Hello, world\nNext Paragraph") var range = str.index(afterCharacter: str.startIndex) ..< str.index(str.startIndex, offsetByCharacters: 3) str[range].testParagraphConstrained = 2 @@ -148,7 +153,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: str, matches: [("Hello, world\n", 4), ("Next Paragraph", 4)], for: \.testParagraphConstrained) } - func testParagraphAttributeRemoval() { + @Test func paragraphAttributeRemoval() { var str = AttributedString("Hello, world\nNext Paragraph", attributes: .init().testParagraphConstrained(2)) var range = str.index(afterCharacter: str.startIndex) ..< str.index(str.startIndex, offsetByCharacters: 3) str[range].testParagraphConstrained = nil @@ -167,7 +172,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: str, matches: [("Hello, world\n", nil), ("Next Paragraph", nil)], for: \.testParagraphConstrained) } - func testParagraphAttributeContainerApplying() { + @Test func paragraphAttributeContainerApplying() { var container = AttributeContainer.testParagraphConstrained(2).testString("Hello") var str = AttributedString("Hello, world\nNext Paragraph") var range = str.index(afterCharacter: str.startIndex) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -195,7 +200,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: str, matches: [("H", 4, nil, 1), ("el", 4, "Hello", 1), ("lo, w", 4, nil, 1), ("orld\n", 4, nil, 2), ("N", 4, nil, 2), ("ext Paragrap", 4, nil, 1), ("h", 4, "Hello", 2)], for: \.testParagraphConstrained, \.testString, \.testInt) } - func testParagraphAttributeContainerReplacing() { + @Test func paragraphAttributeContainerReplacing() { var str = AttributedString("Hello, world\nNext Paragraph") let range = str.index(afterCharacter: str.startIndex) ..< str.index(str.startIndex, offsetByCharacters: 3) str[range].testInt = 2 @@ -216,7 +221,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("H", 3, 2, nil), ("el", 3, nil, true), ("lo, world\n", 3, 2, nil), ("Next Paragraph", nil, 2, nil)], for: \.testParagraphConstrained, \.testInt, \.testBool) } - func testParagraphTextMutation() { + @Test func paragraphTextMutation() { let str = AttributedString("Hello, world\n", attributes: .init().testParagraphConstrained(1)) + AttributedString("Next Paragraph", attributes: .init().testParagraphConstrained(2)) var result = str @@ -260,7 +265,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("Hello, wTest\n", 1), ("Replacementxt Paragraph", 1)], for: \.testParagraphConstrained) } - func testParagraphAttributedTextMutation() { + @Test func paragraphAttributedTextMutation() { let str = AttributedString("Hello, world\n", attributes: .init().testParagraphConstrained(1)) + AttributedString("Next Paragraph", attributes: .init().testParagraphConstrained(2)) let singleReplacement = AttributedString("Test", attributes: .init().testParagraphConstrained(5).testSecondParagraphConstrained(6).testBool(true)) let multiReplacement = AttributedString("Test\nInserted", attributes: .init().testParagraphConstrained(5).testSecondParagraphConstrained(6).testBool(true)) @@ -311,7 +316,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { } #if FOUNDATION_FRAMEWORK - func testParagraphFromUntrustedRuns() throws { + @Test func paragraphFromUntrustedRuns() throws { let str = NSMutableAttributedString(string: "Hello ", attributes: [.testParagraphConstrained : NSNumber(2)]) str.append(NSAttributedString(string: "World", attributes: [.testParagraphConstrained : NSNumber(3), .testSecondParagraphConstrained : NSNumber(4)])) @@ -320,7 +325,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { } #endif // FOUNDATION_FRAMEWORK - func testParagraphFromReplacedSubrange() { + @Test func paragraphFromReplacedSubrange() { let str = AttributedString("Before\nHello, world\nNext Paragraph\nAfter", attributes: .init().testParagraphConstrained(1)) // Range of "world\nNext" @@ -344,7 +349,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { // MARK: - Character Constrained Tests - func testCharacterAttributeApply() { + @Test func characterAttributeApply() { let str = AttributedString("*__*__**__*") var result = str @@ -362,7 +367,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("*", nil, 1), ("__", nil, 1), ("*", nil, 1), ("__", nil, 1), ("*", nil, 1), ("*", nil, 1), ("__", nil, 1), ("*", 3, 1)], for: \.testCharacterConstrained, \.testInt) } - func testCharacterAttributeSubCharacterApply() { + @Test func characterAttributeSubCharacterApply() { let str = AttributedString("ABC \u{FFFD} DEF") var result = str @@ -394,7 +399,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { } - func testCharacterAttributeContainerReplacing() { + @Test func characterAttributeContainerReplacing() { var str = AttributedString("*__*__**__*") let range = str.index(afterCharacter: str.startIndex) ..< str.index(str.startIndex, offsetByCharacters: 4) str[range].testInt = 2 @@ -415,7 +420,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("*", nil, 2, nil), ("__", nil, nil, true), ("*", 3, nil, true), ("__", nil, 2, nil), ("*", nil, 2, nil), ("*", nil, 2, nil), ("__", nil, 2, nil), ("*", nil, 2, nil)], for: \.testCharacterConstrained, \.testInt, \.testBool) } - func testCharacterTextMutation() { + @Test func characterTextMutation() { let str = AttributedString("*__*__**__*", attributes: .init().testCharacterConstrained(2)) var result = str @@ -444,7 +449,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { } #if FOUNDATION_FRAMEWORK - func testCharacterFromUntrustedRuns() throws { + @Test func characterFromUntrustedRuns() throws { let str = NSMutableAttributedString(string: "*__*__**__*", attributes: [.testCharacterConstrained : NSNumber(2)]) str.append(NSAttributedString(string: "_*")) @@ -455,7 +460,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { // MARK: Invalidation Tests - func testInvalidationAttributeChange() { + @Test func invalidationAttributeChange() { let str = AttributedString("Hello, world", attributes: .init().testInt(1).testAttributeDependent(2)) var result = str @@ -489,7 +494,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("Hello, world", 2, nil)], for: \.testInt, \.testAttributeDependent) } - func testInvalidationCharacterChange() { + @Test func invalidationCharacterChange() { let str = AttributedString("Hello, world", attributes: .init().testInt(1).testCharacterDependent(2)) var result = str @@ -575,7 +580,7 @@ class TestAttributedStringConstrainingBehavior: XCTestCase { verify(string: result, matches: [("H", nil, nil, "Hello"), ("ello, world", 1, nil, nil)], for: \.testInt, \.testCharacterDependent, \.testString) } - func testInvalidationCharacterInsertionBetweenRuns() { + @Test func invalidationCharacterInsertionBetweenRuns() { var str = AttributedString("Hello", attributes: .init().testInt(1).testCharacterDependent(2)) str += AttributedString("World", attributes: .init().testInt(1).testCharacterDependent(3)) diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringDiscontiguousTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringDiscontiguousTests.swift index 79b7fa344..ae247167b 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringDiscontiguousTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringDiscontiguousTests.swift @@ -10,61 +10,70 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation #endif -final class AttributedStringDiscontiguousTests: XCTestCase { - func testEmptySlice() { +@Suite("Discontiguous AttributedString") +private struct AttributedStringDiscontiguousTests { + @Test + func emptySlice() { let str = AttributedString() let slice = str[RangeSet()] - XCTAssertTrue(slice.runs.isEmpty) - XCTAssertTrue(slice.characters.isEmpty) - XCTAssertTrue(slice.unicodeScalars.isEmpty) - XCTAssertEqual(slice, slice) - XCTAssertEqual(slice.runs.startIndex, slice.runs.endIndex) - XCTAssertEqual(slice.characters.startIndex, slice.characters.endIndex) - XCTAssertEqual(slice.unicodeScalars.startIndex, slice.unicodeScalars.endIndex) - XCTAssertEqual(AttributedString("abc")[RangeSet()], AttributedString("def")[RangeSet()]) + #expect(slice.runs.isEmpty) + #expect(slice.characters.isEmpty) + #expect(slice.unicodeScalars.isEmpty) + #expect(slice == slice) + #expect(slice.runs.startIndex == slice.runs.endIndex) + #expect(slice.characters.startIndex == slice.characters.endIndex) + #expect(slice.unicodeScalars.startIndex == slice.unicodeScalars.endIndex) + #expect(AttributedString("abc")[RangeSet()] == AttributedString("def")[RangeSet()]) for r in slice.runs { - XCTFail("Enumerating empty runs should not have produced \(r)") + Issue.record("Enumerating empty runs should not have produced \(r)") } for c in slice.characters { - XCTFail("Enumerating empty characters should not have produced \(c)") + Issue.record("Enumerating empty characters should not have produced \(c)") } for s in slice.unicodeScalars { - XCTFail("Enumerating empty unicode scalars should not have produced \(s)") + Issue.record("Enumerating empty unicode scalars should not have produced \(s)") } } - func testCharacters() { + @Test + func characters() { let str = AttributedString("abcdefgabc") let fullSlice = str[str.startIndex ..< str.endIndex].characters let fullDiscontiguousSlice = str[RangeSet(str.startIndex ..< str.endIndex)].characters - XCTAssertTrue(fullSlice.elementsEqual(fullDiscontiguousSlice)) + #expect(fullSlice.elementsEqual(fullDiscontiguousSlice)) let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 3) let rangeB = str.index(str.endIndex, offsetByCharacters: -3) ..< str.endIndex let rangeSet = RangeSet([rangeA, rangeB]) let slice = str[rangeSet].characters - XCTAssertEqual(Array(slice), ["a", "b", "c", "a", "b", "c"]) + #expect(Array(slice) == ["a", "b", "c", "a", "b", "c"]) } - func testUnicodeScalars() { + @Test + func unicodeScalars() { let str = AttributedString("abcdefgabc") let fullSlice = str[str.startIndex ..< str.endIndex].unicodeScalars let fullDiscontiguousSlice = str[RangeSet(str.startIndex ..< str.endIndex)].unicodeScalars - XCTAssertTrue(fullSlice.elementsEqual(fullDiscontiguousSlice)) + #expect(fullSlice.elementsEqual(fullDiscontiguousSlice)) let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByUnicodeScalars: 3) let rangeB = str.index(str.endIndex, offsetByUnicodeScalars: -3) ..< str.endIndex let rangeSet = RangeSet([rangeA, rangeB]) let slice = str[rangeSet].unicodeScalars - XCTAssertEqual(Array(slice), ["a", "b", "c", "a", "b", "c"]) + #expect(Array(slice) == ["a", "b", "c", "a", "b", "c"]) } - func testAttributes() { + @Test + func attributes() { let str = AttributedString("abcdefg") let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 1) let rangeB = str.index(str.startIndex, offsetByCharacters: 2) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -78,7 +87,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].testInt = 2 } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -88,7 +97,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].test.testInt = 2 } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -98,7 +107,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range][AttributeScopes.TestAttributes.TestIntAttribute.self] = 2 } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -110,15 +119,15 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].testInt = nil } - XCTAssertEqual(a, b) + #expect(a == b) } do { var a = str a.testInt = 2 - XCTAssertEqual(a[ranges].testInt, 2) + #expect(a[ranges].testInt == 2) a[rangeA].testInt = 3 - XCTAssertEqual(a[ranges].testInt, nil) + #expect(a[ranges].testInt == nil) } do { @@ -130,7 +139,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].mergeAttributes(AttributeContainer.testInt(2)) } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -142,7 +151,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].setAttributes(AttributeContainer.testInt(2)) } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -154,7 +163,7 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].replaceAttributes(AttributeContainer(), with: AttributeContainer.testInt(2)) } - XCTAssertEqual(a, b) + #expect(a == b) } do { @@ -166,11 +175,12 @@ final class AttributedStringDiscontiguousTests: XCTestCase { for range in ranges.ranges { b[range].replaceAttributes(AttributeContainer.testString("foo"), with: AttributeContainer.testInt(2)) } - XCTAssertEqual(a, b) + #expect(a == b) } } - func testReinitialization() { + @Test + func reinitialization() { var str = AttributedString("abcdefg") let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 1) let rangeB = str.index(str.startIndex, offsetByCharacters: 2) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -179,10 +189,11 @@ final class AttributedStringDiscontiguousTests: XCTestCase { str[ranges].testInt = 2 let reinitialized = AttributedString(str[ranges]) - XCTAssertEqual(reinitialized, AttributedString("ace", attributes: AttributeContainer.testInt(2))) + #expect(reinitialized == AttributedString("ace", attributes: AttributeContainer.testInt(2))) } - func testReslicing() { + @Test + func reslicing() { var str = AttributedString("abcdefg") let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 1) let rangeB = str.index(str.startIndex, offsetByCharacters: 2) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -190,14 +201,15 @@ final class AttributedStringDiscontiguousTests: XCTestCase { let ranges = RangeSet([rangeA, rangeB, rangeC]) str[ranges].testInt = 2 - XCTAssertEqual(str[ranges], str[ranges][ranges]) - XCTAssertEqual(AttributedString(str[ranges][RangeSet([rangeA, rangeB])]), AttributedString("ac", attributes: AttributeContainer.testInt(2))) - XCTAssertEqual(AttributedString(str[ranges][rangeA.lowerBound ..< rangeB.upperBound]), AttributedString("ac", attributes: AttributeContainer.testInt(2))) + #expect(str[ranges] == str[ranges][ranges]) + #expect(AttributedString(str[ranges][RangeSet([rangeA, rangeB])]) == AttributedString("ac", attributes: AttributeContainer.testInt(2))) + #expect(AttributedString(str[ranges][rangeA.lowerBound ..< rangeB.upperBound]) == AttributedString("ac", attributes: AttributeContainer.testInt(2))) - XCTAssertEqual(str[RangeSet()][RangeSet()], str[RangeSet()]) + #expect(str[RangeSet()][RangeSet()] == str[RangeSet()]) } - func testRuns() { + @Test + func runs() { var str = AttributedString("AAA", attributes: AttributeContainer.testInt(2)) str += AttributedString("BBB", attributes: AttributeContainer.testInt(3).testString("foo")) str += AttributedString("CC", attributes: AttributeContainer.testInt(3).testString("bar")) @@ -216,13 +228,14 @@ final class AttributedStringDiscontiguousTests: XCTestCase { let runs = str[rangeSet].runs let expectedRanges = [rangeA, rangeB_first, rangeB_second, rangeC, rangeD, rangeE] - XCTAssertEqual(runs.count, expectedRanges.count) - XCTAssertEqual(runs.reversed().count, expectedRanges.reversed().count) - XCTAssertEqual(runs.map(\.range), expectedRanges) - XCTAssertEqual(runs.reversed().map(\.range), expectedRanges.reversed()) + #expect(runs.count == expectedRanges.count) + #expect(runs.reversed().count == expectedRanges.reversed().count) + #expect(runs.map(\.range) == expectedRanges) + #expect(runs.reversed().map(\.range) == expectedRanges.reversed()) } - func testCoalescedRuns() { + @Test + func coalescedRuns() { struct EquatableBox: Equatable, CustomStringConvertible { let t: T let u: U @@ -260,15 +273,16 @@ final class AttributedStringDiscontiguousTests: XCTestCase { let runs = str[rangeSet].runs let testIntExpectation = [EquatableBox(2, rangeA), EquatableBox(3, rangeB), EquatableBox(3, rangeC), EquatableBox(nil, rangeD), EquatableBox(nil, rangeE)] - XCTAssertEqual(runs[\.testInt].map(EquatableBox.init), testIntExpectation) - XCTAssertEqual(runs[\.testInt].reversed().map(EquatableBox.init), testIntExpectation.reversed()) + #expect(runs[\.testInt].map(EquatableBox.init) == testIntExpectation) + #expect(runs[\.testInt].reversed().map(EquatableBox.init) == testIntExpectation.reversed()) let testStringExpectation = [EquatableBox(nil, rangeA), EquatableBox("foo", rangeB_first), EquatableBox("bar", rangeB_second), EquatableBox("baz", rangeC), EquatableBox(nil, rangeD), EquatableBox(nil, rangeE)] - XCTAssertEqual(runs[\.testString].map(EquatableBox.init), testStringExpectation) - XCTAssertEqual(runs[\.testString].reversed().map(EquatableBox.init), testStringExpectation.reversed()) + #expect(runs[\.testString].map(EquatableBox.init) == testStringExpectation) + #expect(runs[\.testString].reversed().map(EquatableBox.init) == testStringExpectation.reversed()) } - func testRemoveSubranges() { + @Test + func removeSubranges() { var str = AttributedString("abcdefg") let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 1) let rangeB = str.index(str.startIndex, offsetByCharacters: 2) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -280,10 +294,11 @@ final class AttributedStringDiscontiguousTests: XCTestCase { str.removeSubranges(ranges) let result = AttributedString("bdfg", attributes: AttributeContainer.testBool(true)) - XCTAssertEqual(str, result) + #expect(str == result) } - func testSliceSetter() { + @Test + func sliceSetter() { var str = AttributedString("abcdefg") let rangeA = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 1) let rangeB = str.index(str.startIndex, offsetByCharacters: 2) ..< str.index(str.startIndex, offsetByCharacters: 3) @@ -296,13 +311,13 @@ final class AttributedStringDiscontiguousTests: XCTestCase { do { var copy = str copy[ranges] = copy[ranges] - XCTAssertEqual(copy, str) + #expect(copy == str) } do { var copy = str copy[ranges] = str[ranges] - XCTAssertEqual(copy, str) + #expect(copy == str) } do { @@ -313,11 +328,12 @@ final class AttributedStringDiscontiguousTests: XCTestCase { let ranges2 = RangeSet([rangeA2, rangeB2, rangeC2]) var copy = str copy[ranges] = str2[ranges2] - XCTAssertEqual(String(copy.characters), "ZbYdXfg") + #expect(String(copy.characters) == "ZbYdXfg") } } - func testGraphemesAcrossDiscontiguousRanges() { + @Test + func graphemesAcrossDiscontiguousRanges() { let str = "a\n\u{301}" let attrStr = AttributedString(str) let strRangeA = str.startIndex ..< str.index(after: str.startIndex) // Range of 'a' @@ -335,6 +351,6 @@ final class AttributedStringDiscontiguousTests: XCTestCase { // (2) The behavior is consistent between String and AttributedString.CharacterView let strSlice = str[strRanges] let attrStrSlice = attrStr[attrStrRanges].characters - XCTAssert(strSlice.elementsEqual(attrStrSlice), "Characters \(Array(strSlice)) and \(Array(attrStrSlice)) do not match") + #expect(strSlice.elementsEqual(attrStrSlice), "Characters \(Array(strSlice)) and \(Array(attrStrSlice)) do not match") } } diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexValidityTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexValidityTests.swift index 9237c0fbc..5369467ea 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexValidityTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringIndexValidityTests.swift @@ -10,116 +10,125 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation #endif -final class AttributedStringIndexValidityTests: XCTestCase { - public func testStartEndRange() { +@Suite("AttributedString Index Validity") +private struct AttributedStringIndexValidityTests { + @Test + public func startEndRange() { let str = AttributedString("Hello, world") - XCTAssertTrue(str.startIndex.isValid(within: str)) - XCTAssertFalse(str.endIndex.isValid(within: str)) - XCTAssertTrue((str.startIndex ..< str.endIndex).isValid(within: str)) - XCTAssertTrue((str.startIndex ..< str.startIndex).isValid(within: str)) - XCTAssertTrue((str.endIndex ..< str.endIndex).isValid(within: str)) + #expect(str.startIndex.isValid(within: str)) + #expect(!str.endIndex.isValid(within: str)) + #expect((str.startIndex ..< str.endIndex).isValid(within: str)) + #expect((str.startIndex ..< str.startIndex).isValid(within: str)) + #expect((str.endIndex ..< str.endIndex).isValid(within: str)) let subStart = str.index(afterCharacter: str.startIndex) let subEnd = str.index(beforeCharacter: str.endIndex) do { let substr = str[str.startIndex ..< str.endIndex] - XCTAssertTrue(substr.startIndex.isValid(within: substr)) - XCTAssertFalse(substr.endIndex.isValid(within: substr)) - XCTAssertTrue((substr.startIndex ..< substr.endIndex).isValid(within: substr)) + #expect(substr.startIndex.isValid(within: substr)) + #expect(!substr.endIndex.isValid(within: substr)) + #expect((substr.startIndex ..< substr.endIndex).isValid(within: substr)) } do { let substr = str[subStart ..< str.endIndex] - XCTAssertTrue(substr.startIndex.isValid(within: substr)) - XCTAssertFalse(substr.endIndex.isValid(within: substr)) - XCTAssertTrue((substr.startIndex ..< substr.endIndex).isValid(within: substr)) + #expect(substr.startIndex.isValid(within: substr)) + #expect(!substr.endIndex.isValid(within: substr)) + #expect((substr.startIndex ..< substr.endIndex).isValid(within: substr)) } do { let substr = str[str.startIndex ..< subEnd] - XCTAssertTrue(substr.startIndex.isValid(within: substr)) - XCTAssertFalse(substr.endIndex.isValid(within: substr)) - XCTAssertTrue((substr.startIndex ..< substr.endIndex).isValid(within: substr)) + #expect(substr.startIndex.isValid(within: substr)) + #expect(!substr.endIndex.isValid(within: substr)) + #expect((substr.startIndex ..< substr.endIndex).isValid(within: substr)) } do { let substr = str[subStart ..< subEnd] - XCTAssertTrue(substr.startIndex.isValid(within: substr)) - XCTAssertFalse(substr.endIndex.isValid(within: substr)) - XCTAssertTrue((substr.startIndex ..< substr.endIndex).isValid(within: substr)) - XCTAssertTrue((substr.startIndex ..< substr.startIndex).isValid(within: substr)) - XCTAssertTrue((substr.endIndex ..< substr.endIndex).isValid(within: substr)) + #expect(substr.startIndex.isValid(within: substr)) + #expect(!substr.endIndex.isValid(within: substr)) + #expect((substr.startIndex ..< substr.endIndex).isValid(within: substr)) + #expect((substr.startIndex ..< substr.startIndex).isValid(within: substr)) + #expect((substr.endIndex ..< substr.endIndex).isValid(within: substr)) } do { let substr = str[RangeSet(str.startIndex ..< str.endIndex)] - XCTAssertTrue(str.startIndex.isValid(within: substr)) - XCTAssertFalse(str.endIndex.isValid(within: substr)) - XCTAssertTrue((str.startIndex ..< str.endIndex).isValid(within: substr)) + #expect(str.startIndex.isValid(within: substr)) + #expect(!str.endIndex.isValid(within: substr)) + #expect((str.startIndex ..< str.endIndex).isValid(within: substr)) } do { let substr = str[RangeSet(subStart ..< str.endIndex)] - XCTAssertTrue(subStart.isValid(within: substr)) - XCTAssertFalse(str.endIndex.isValid(within: substr)) - XCTAssertTrue((subStart ..< str.endIndex).isValid(within: substr)) + #expect(subStart.isValid(within: substr)) + #expect(!str.endIndex.isValid(within: substr)) + #expect((subStart ..< str.endIndex).isValid(within: substr)) } do { let substr = str[RangeSet(str.startIndex ..< subEnd)] - XCTAssertTrue(str.startIndex.isValid(within: substr)) - XCTAssertFalse(subEnd.isValid(within: substr)) - XCTAssertTrue((str.startIndex ..< subEnd).isValid(within: substr)) + #expect(str.startIndex.isValid(within: substr)) + #expect(!subEnd.isValid(within: substr)) + #expect((str.startIndex ..< subEnd).isValid(within: substr)) } do { let substr = str[RangeSet(subStart ..< subEnd)] - XCTAssertTrue(subStart.isValid(within: substr)) - XCTAssertFalse(subEnd.isValid(within: substr)) - XCTAssertTrue((subStart ..< subEnd).isValid(within: substr)) - XCTAssertTrue((subStart ..< subStart).isValid(within: substr)) - XCTAssertTrue((subEnd ..< subEnd).isValid(within: substr)) + #expect(subStart.isValid(within: substr)) + #expect(!subEnd.isValid(within: substr)) + #expect((subStart ..< subEnd).isValid(within: substr)) + #expect((subStart ..< subStart).isValid(within: substr)) + #expect((subEnd ..< subEnd).isValid(within: substr)) } } - public func testExhaustiveIndices() { + @Test + public func exhaustiveIndices() { let str = AttributedString("Hello Cafe\u{301} 👍🏻🇺🇸 World") for idx in str.characters.indices { - XCTAssertTrue(idx.isValid(within: str)) + #expect(idx.isValid(within: str)) } for idx in str.unicodeScalars.indices { - XCTAssertTrue(idx.isValid(within: str)) + #expect(idx.isValid(within: str)) } for idx in str.utf8.indices { - XCTAssertTrue(idx.isValid(within: str)) + #expect(idx.isValid(within: str)) } for idx in str.utf16.indices { - XCTAssertTrue(idx.isValid(within: str)) + #expect(idx.isValid(within: str)) } } - public func testOutOfBoundsContiguous() { + @Test + public func outOfBoundsContiguous() { let str = AttributedString("Hello, world") let subStart = str.index(afterCharacter: str.startIndex) let subEnd = str.index(beforeCharacter: str.endIndex) let substr = str[subStart ..< subEnd] - XCTAssertFalse(str.startIndex.isValid(within: substr)) - XCTAssertFalse(str.endIndex.isValid(within: substr)) - XCTAssertFalse((str.startIndex ..< str.endIndex).isValid(within: substr)) - XCTAssertFalse((str.startIndex ..< substr.startIndex).isValid(within: substr)) - XCTAssertFalse((substr.startIndex ..< str.endIndex).isValid(within: substr)) - XCTAssertFalse((str.startIndex ..< str.startIndex).isValid(within: substr)) - XCTAssertFalse((str.endIndex ..< str.endIndex).isValid(within: substr)) + #expect(!str.startIndex.isValid(within: substr)) + #expect(!str.endIndex.isValid(within: substr)) + #expect(!(str.startIndex ..< str.endIndex).isValid(within: substr)) + #expect(!(str.startIndex ..< substr.startIndex).isValid(within: substr)) + #expect(!(substr.startIndex ..< str.endIndex).isValid(within: substr)) + #expect(!(str.startIndex ..< str.startIndex).isValid(within: substr)) + #expect(!(str.endIndex ..< str.endIndex).isValid(within: substr)) } - public func testOutOfBoundsDiscontiguous() { + @Test + public func outOfBoundsDiscontiguous() { let str = AttributedString("Hello, world") let idxA = str.index(afterCharacter: str.startIndex) let idxB = str.index(afterCharacter: idxA) @@ -128,66 +137,67 @@ final class AttributedStringIndexValidityTests: XCTestCase { let middleIdx = str.index(afterCharacter: idxB) let substr = str[RangeSet([idxA ..< idxB, idxC ..< idxD])] - XCTAssertFalse(str.startIndex.isValid(within: substr)) - XCTAssertFalse(str.endIndex.isValid(within: substr)) - XCTAssertFalse(idxD.isValid(within: substr)) - XCTAssertFalse(middleIdx.isValid(within: substr)) - XCTAssertFalse((str.startIndex ..< idxA).isValid(within: substr)) - XCTAssertFalse((idxA ..< middleIdx).isValid(within: substr)) - XCTAssertFalse((middleIdx ..< idxD).isValid(within: substr)) - XCTAssertFalse((str.startIndex ..< str.startIndex).isValid(within: substr)) - XCTAssertFalse((str.endIndex ..< str.endIndex).isValid(within: substr)) + #expect(!str.startIndex.isValid(within: substr)) + #expect(!str.endIndex.isValid(within: substr)) + #expect(!idxD.isValid(within: substr)) + #expect(!middleIdx.isValid(within: substr)) + #expect(!(str.startIndex ..< idxA).isValid(within: substr)) + #expect(!(idxA ..< middleIdx).isValid(within: substr)) + #expect(!(middleIdx ..< idxD).isValid(within: substr)) + #expect(!(str.startIndex ..< str.startIndex).isValid(within: substr)) + #expect(!(str.endIndex ..< str.endIndex).isValid(within: substr)) } - public func testMutationInvalidation() { - func checkInPlace(_ mutation: (inout AttributedString) -> (), file: StaticString = #filePath, line: UInt = #line) { + @Test + public func mutationInvalidation() { + func checkInPlace(_ mutation: (inout AttributedString) -> (), sourceLocation: SourceLocation = #_sourceLocation) { var str = AttributedString("Hello World") let idxA = str.startIndex let idxB = str.index(afterCharacter: idxA) - XCTAssertTrue(idxA.isValid(within: str), "Initial index A was invalid in original", file: file, line: line) - XCTAssertTrue(idxB.isValid(within: str), "Initial index B was invalid in original", file: file, line: line) - XCTAssertTrue((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original", file: file, line: line) - XCTAssertTrue(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original", file: file, line: line) + #expect(idxA.isValid(within: str), "Initial index A was invalid in original", sourceLocation: sourceLocation) + #expect(idxB.isValid(within: str), "Initial index B was invalid in original", sourceLocation: sourceLocation) + #expect((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original", sourceLocation: sourceLocation) + #expect(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original", sourceLocation: sourceLocation) mutation(&str) - XCTAssertFalse(idxA.isValid(within: str), "Initial index A was valid in in-place mutated", file: file, line: line) - XCTAssertFalse(idxB.isValid(within: str), "Initial index B was valid in in-place mutated", file: file, line: line) - XCTAssertFalse((idxA ..< idxB).isValid(within: str), "Initial range was valid in in-place mutated", file: file, line: line) - XCTAssertFalse(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was valid in in-place mutated", file: file, line: line) + #expect(!idxA.isValid(within: str), "Initial index A was valid in in-place mutated", sourceLocation: sourceLocation) + #expect(!idxB.isValid(within: str), "Initial index B was valid in in-place mutated", sourceLocation: sourceLocation) + #expect(!(idxA ..< idxB).isValid(within: str), "Initial range was valid in in-place mutated", sourceLocation: sourceLocation) + #expect(!RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was valid in in-place mutated", sourceLocation: sourceLocation) } - func checkCopy(_ mutation: (inout AttributedString) -> (), file: StaticString = #filePath, line: UInt = #line) { + func checkCopy(_ mutation: (inout AttributedString) -> (), sourceLocation: SourceLocation = #_sourceLocation) { let str = AttributedString("Hello World") let idxA = str.startIndex let idxB = str.index(afterCharacter: idxA) var copy = str - XCTAssertTrue(idxA.isValid(within: str), "Initial index A was invalid in original", file: file, line: line) - XCTAssertTrue(idxB.isValid(within: str), "Initial index B was invalid in original", file: file, line: line) - XCTAssertTrue((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original", file: file, line: line) - XCTAssertTrue(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original", file: file, line: line) - XCTAssertTrue(idxA.isValid(within: copy), "Initial index A was invalid in copy", file: file, line: line) - XCTAssertTrue(idxB.isValid(within: copy), "Initial index B was invalid in copy", file: file, line: line) - XCTAssertTrue((idxA ..< idxB).isValid(within: copy), "Initial range was invalid in copy", file: file, line: line) - XCTAssertTrue(RangeSet(idxA ..< idxB).isValid(within: copy), "Initial range set was invalid in copy", file: file, line: line) + #expect(idxA.isValid(within: str), "Initial index A was invalid in original", sourceLocation: sourceLocation) + #expect(idxB.isValid(within: str), "Initial index B was invalid in original", sourceLocation: sourceLocation) + #expect((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original", sourceLocation: sourceLocation) + #expect(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original", sourceLocation: sourceLocation) + #expect(idxA.isValid(within: copy), "Initial index A was invalid in copy", sourceLocation: sourceLocation) + #expect(idxB.isValid(within: copy), "Initial index B was invalid in copy", sourceLocation: sourceLocation) + #expect((idxA ..< idxB).isValid(within: copy), "Initial range was invalid in copy", sourceLocation: sourceLocation) + #expect(RangeSet(idxA ..< idxB).isValid(within: copy), "Initial range set was invalid in copy", sourceLocation: sourceLocation) mutation(©) - XCTAssertTrue(idxA.isValid(within: str), "Initial index A was invalid in original after copy", file: file, line: line) - XCTAssertTrue(idxB.isValid(within: str), "Initial index B was invalid in original after copy", file: file, line: line) - XCTAssertTrue((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original after copy", file: file, line: line) - XCTAssertTrue(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original after copy", file: file, line: line) - XCTAssertFalse(idxA.isValid(within: copy), "Initial index A was valid in copy", file: file, line: line) - XCTAssertFalse(idxB.isValid(within: copy), "Initial index B was valid in copy", file: file, line: line) - XCTAssertFalse((idxA ..< idxB).isValid(within: copy), "Initial range was valid in copy", file: file, line: line) - XCTAssertFalse(RangeSet(idxA ..< idxB).isValid(within: copy), "Initial range set was valid in copy", file: file, line: line) - } - - func check(_ mutation: (inout AttributedString) -> (), file: StaticString = #filePath, line: UInt = #line) { - checkInPlace(mutation, file: file, line: line) - checkCopy(mutation, file: file, line: line) + #expect(idxA.isValid(within: str), "Initial index A was invalid in original after copy", sourceLocation: sourceLocation) + #expect(idxB.isValid(within: str), "Initial index B was invalid in original after copy", sourceLocation: sourceLocation) + #expect((idxA ..< idxB).isValid(within: str), "Initial range was invalid in original after copy", sourceLocation: sourceLocation) + #expect(RangeSet(idxA ..< idxB).isValid(within: str), "Initial range set was invalid in original after copy", sourceLocation: sourceLocation) + #expect(!idxA.isValid(within: copy), "Initial index A was valid in copy", sourceLocation: sourceLocation) + #expect(!idxB.isValid(within: copy), "Initial index B was valid in copy", sourceLocation: sourceLocation) + #expect(!(idxA ..< idxB).isValid(within: copy), "Initial range was valid in copy", sourceLocation: sourceLocation) + #expect(!RangeSet(idxA ..< idxB).isValid(within: copy), "Initial range set was valid in copy", sourceLocation: sourceLocation) + } + + func check(_ mutation: (inout AttributedString) -> (), sourceLocation: SourceLocation = #_sourceLocation) { + checkInPlace(mutation, sourceLocation: sourceLocation) + checkCopy(mutation, sourceLocation: sourceLocation) } check { diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift index 25a44fe90..670aabf97 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift @@ -10,13 +10,11 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationEssentials) @testable import FoundationEssentials -#endif // FOUNDATION_FRAMEWORK +#endif #if FOUNDATION_FRAMEWORK @testable @_spi(AttributedString) import Foundation @@ -33,21 +31,22 @@ import UIKit #if canImport(AppKit) import AppKit #endif -#endif // FOUNDATION_FRAMEWORK +#endif /// Regression and coverage tests for `AttributedString` and its associated objects -final class TestAttributedString: XCTestCase { +@Suite("AttributedString") +private struct AttributedStringTests { // MARK: - Enumeration Tests - func testEmptyEnumeration() { + @Test func emptyEnumeration() { for _ in AttributedString().runs { - XCTFail("Empty AttributedString should not enumerate any attributes") + Issue.record("Empty AttributedString should not enumerate any attributes") } do { let str = AttributedString("Foo") for _ in str[str.startIndex ..< str.startIndex].runs { - XCTFail("Empty AttributedSubstring should not enumerate any attributes") + Issue.record("Empty AttributedSubstring should not enumerate any attributes") } } @@ -55,76 +54,94 @@ final class TestAttributedString: XCTestCase { let str = AttributedString("Foo", attributes: AttributeContainer.testInt(2)) let i = str.index(afterCharacter: str.startIndex) for _ in str[i ..< i].runs { - XCTFail("Empty AttributedSubstring should not enumerate any attributes") + Issue.record("Empty AttributedSubstring should not enumerate any attributes") } } } - func verifyAttributes(_ runs: AttributedString.Runs.AttributesSlice1, string: AttributedString, expectation: [(String, T.Value?)]) where T.Value : Sendable { + func verifyAttributes(_ runs: AttributedString.Runs.AttributesSlice1, string: AttributedString, expectation: [(String, T.Value?)], sourceLocation: SourceLocation = #_sourceLocation) where T.Value : Sendable { // Test that the attribute is correct when iterating through attribute runs var expectIterator = expectation.makeIterator() for (attribute, range) in runs { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation") - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation") + guard let expected = expectIterator.next() else { + Issue.record("Additional runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found") + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) // Test that the attribute is correct when iterating through reversed attribute runs expectIterator = expectation.reversed().makeIterator() for (attribute, range) in runs.reversed() { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation") - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation") + guard let expected = expectIterator.next() else { + Issue.record("Additional reversed runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found") + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) } - func verifyAttributes(_ runs: AttributedString.Runs.AttributesSlice2, string: AttributedString, expectation: [(String, T.Value?, U.Value?)]) where T.Value : Sendable, U.Value : Sendable { + func verifyAttributes(_ runs: AttributedString.Runs.AttributesSlice2, string: AttributedString, expectation: [(String, T.Value?, U.Value?)], sourceLocation: SourceLocation = #_sourceLocation) where T.Value : Sendable, U.Value : Sendable { // Test that the attributes are correct when iterating through attribute runs var expectIterator = expectation.makeIterator() for (attribute, attribute2, range) in runs { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation") - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation") - XCTAssertEqual(attribute2, expected.2, "Attribute of run did not match expectation") + guard let expected = expectIterator.next() else { + Issue.record("Additional runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute2 == expected.2, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found") + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) // Test that the attributes are correct when iterating through reversed attribute runs expectIterator = expectation.reversed().makeIterator() for (attribute, attribute2, range) in runs.reversed() { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation") - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation") - XCTAssertEqual(attribute2, expected.2, "Attribute of run did not match expectation") + guard let expected = expectIterator.next() else { + Issue.record("Additional reversed runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute2 == expected.2, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found") + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) } #if FOUNDATION_FRAMEWORK - func verifyAttributes(_ runs: AttributedString.Runs.NSAttributesSlice, string: AttributedString, expectation: [(String, AttributeContainer)], file: StaticString = #filePath, line: UInt = #line) { + func verifyAttributes(_ runs: AttributedString.Runs.NSAttributesSlice, string: AttributedString, expectation: [(String, AttributeContainer)], sourceLocation: SourceLocation = #_sourceLocation) { // Test that the attribute is correct when iterating through attribute runs var expectIterator = expectation.makeIterator() for (attribute, range) in runs { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation", file: file, line: line) - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation", file: file, line: line) + guard let expected = expectIterator.next() else { + Issue.record("Additional runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found", file: file, line: line) + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) // Test that the attribute is correct when iterating through reversed attribute runs expectIterator = expectation.reversed().makeIterator() for (attribute, range) in runs.reversed() { - let expected = expectIterator.next()! - XCTAssertEqual(String(string[range].characters), expected.0, "Substring of AttributedString characters for range of run did not match expectation", file: file, line: line) - XCTAssertEqual(attribute, expected.1, "Attribute of run did not match expectation", file: file, line: line) + guard let expected = expectIterator.next() else { + Issue.record("Additional reversed runs found but not expected", sourceLocation: sourceLocation) + break + } + #expect(String(string[range].characters) == expected.0, "Substring of AttributedString characters for range of run did not match expectation", sourceLocation: sourceLocation) + #expect(attribute == expected.1, "Attribute of run did not match expectation", sourceLocation: sourceLocation) } - XCTAssertNil(expectIterator.next(), "Additional runs expected but not found", file: file, line: line) + #expect(expectIterator.next() == nil, "Additional runs expected but not found", sourceLocation: sourceLocation) } #endif // FOUNDATION_FRAMEWORK - func testSimpleEnumeration() { + @Test func simpleEnumeration() throws { var attrStr = AttributedString("Hello", attributes: AttributeContainer().testInt(1)) attrStr += " " attrStr += AttributedString("World", attributes: AttributeContainer().testDouble(2.0)) @@ -132,23 +149,29 @@ final class TestAttributedString: XCTestCase { let expectation = [("Hello", 1, nil), (" ", nil, nil), ("World", nil, 2.0)] var expectationIterator = expectation.makeIterator() for run in attrStr.runs { - let expected = expectationIterator.next()! - XCTAssertEqual(String(attrStr[run.range].characters), expected.0) - XCTAssertEqual(run.testInt, expected.1) - XCTAssertEqual(run.testDouble, expected.2) - XCTAssertNil(run.testString) + guard let expected = expectationIterator.next() else { + Issue.record("Found extra unexpected runs") + break + } + #expect(String(attrStr[run.range].characters) == expected.0) + #expect(run.testInt == expected.1) + #expect(run.testDouble == expected.2) + #expect(run.testString == nil) } - XCTAssertNil(expectationIterator.next()) + #expect(expectationIterator.next() == nil) expectationIterator = expectation.reversed().makeIterator() for run in attrStr.runs.reversed() { - let expected = expectationIterator.next()! - XCTAssertEqual(String(attrStr[run.range].characters), expected.0) - XCTAssertEqual(run.testInt, expected.1) - XCTAssertEqual(run.testDouble, expected.2) - XCTAssertNil(run.testString) + guard let expected = expectationIterator.next() else { + Issue.record("Found extra unexpected runs") + break + } + #expect(String(attrStr[run.range].characters) == expected.0) + #expect(run.testInt == expected.1) + #expect(run.testDouble == expected.2) + #expect(run.testString == nil) } - XCTAssertNil(expectationIterator.next()) + #expect(expectationIterator.next() == nil) let attrView = attrStr.runs verifyAttributes(attrView[\.testInt], string: attrStr, expectation: [("Hello", 1), (" World", nil)]) @@ -157,7 +180,7 @@ final class TestAttributedString: XCTestCase { verifyAttributes(attrView[\.testInt, \.testDouble], string: attrStr, expectation: [("Hello", 1, nil), (" ", nil, nil), ("World", nil, 2.0)]) } - func testSliceEnumeration() { + @Test func sliceEnumeration() throws { var attrStr = AttributedString("Hello", attributes: AttributeContainer().testInt(1)) attrStr += AttributedString(" ") attrStr += AttributedString("World", attributes: AttributeContainer().testDouble(2.0)) @@ -167,23 +190,29 @@ final class TestAttributedString: XCTestCase { let expectation = [("lo", 1, nil), (" ", nil, nil), ("Wo", nil, 2.0)] var expectationIterator = expectation.makeIterator() for run in attrStrSlice.runs { - let expected = expectationIterator.next()! - XCTAssertEqual(String(attrStr[run.range].characters), expected.0) - XCTAssertEqual(run.testInt, expected.1) - XCTAssertEqual(run.testDouble, expected.2) - XCTAssertNil(run.testString) + guard let expected = expectationIterator.next() else { + Issue.record("Found extra unexpected runs") + break + } + #expect(String(attrStr[run.range].characters) == expected.0) + #expect(run.testInt == expected.1) + #expect(run.testDouble == expected.2) + #expect(run.testString == nil) } - XCTAssertNil(expectationIterator.next()) + #expect(expectationIterator.next() == nil) expectationIterator = expectation.reversed().makeIterator() for run in attrStrSlice.runs.reversed() { - let expected = expectationIterator.next()! - XCTAssertEqual(String(attrStr[run.range].characters), expected.0) - XCTAssertEqual(run.testInt, expected.1) - XCTAssertEqual(run.testDouble, expected.2) - XCTAssertNil(run.testString) + guard let expected = expectationIterator.next() else { + Issue.record("Found extra unexpected runs") + break + } + #expect(String(attrStr[run.range].characters) == expected.0) + #expect(run.testInt == expected.1) + #expect(run.testDouble == expected.2) + #expect(run.testString == nil) } - XCTAssertNil(expectationIterator.next()) + #expect(expectationIterator.next() == nil) let attrView = attrStrSlice.runs verifyAttributes(attrView[\.testInt], string: attrStr, expectation: [("lo", 1), (" Wo", nil)]) @@ -193,7 +222,7 @@ final class TestAttributedString: XCTestCase { } #if FOUNDATION_FRAMEWORK - func testNSSliceEnumeration() { + @Test func nsSliceEnumeration() { var attrStr = AttributedString("Hello", attributes: AttributeContainer().testInt(1)) attrStr += AttributedString(" ") attrStr += AttributedString("World", attributes: AttributeContainer().testDouble(2.0)) @@ -221,23 +250,23 @@ final class TestAttributedString: XCTestCase { // MARK: - Attribute Tests - func testSimpleAttribute() { + @Test func simpleAttribute() { let attrStr = AttributedString("Foo", attributes: AttributeContainer().testInt(42)) let (value, range) = attrStr.runs[\.testInt][attrStr.startIndex] - XCTAssertEqual(value, 42) - XCTAssertEqual(range, attrStr.startIndex ..< attrStr.endIndex) + #expect(value == 42) + #expect(range == attrStr.startIndex ..< attrStr.endIndex) } - func testConstructorAttribute() { + @Test func constructorAttribute() { // TODO: Re-evaluate whether we want these. let attrStr = AttributedString("Hello", attributes: AttributeContainer().testString("Helvetica").testInt(2)) var expected = AttributedString("Hello") expected.testString = "Helvetica" expected.testInt = 2 - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) } - func testAddAndRemoveAttribute() { + @Test func addAndRemoveAttribute() { let attr : Int = 42 let attr2 : Double = 1.0 var attrStr = AttributedString("Test") @@ -245,70 +274,70 @@ final class TestAttributedString: XCTestCase { attrStr.testDouble = attr2 let expected1 = AttributedString("Test", attributes: AttributeContainer().testInt(attr).testDouble(attr2)) - XCTAssertEqual(attrStr, expected1) + #expect(attrStr == expected1) attrStr.testDouble = nil let expected2 = AttributedString("Test", attributes: AttributeContainer().testInt(attr)) - XCTAssertEqual(attrStr, expected2) + #expect(attrStr == expected2) } - func testAddingAndRemovingAttribute() { + @Test func addingAndRemovingAttribute() { let container = AttributeContainer().testInt(1).testDouble(2.2) let attrStr = AttributedString("Test").mergingAttributes(container) let expected = AttributedString("Test", attributes: AttributeContainer().testInt(1).testDouble(2.2)) - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) var doubleRemoved = attrStr doubleRemoved.testDouble = nil - XCTAssertEqual(doubleRemoved, AttributedString("Test", attributes: AttributeContainer().testInt(1))) + #expect(doubleRemoved == AttributedString("Test", attributes: AttributeContainer().testInt(1))) } - func testScopedAttributes() { + @Test func scopedAttributes() { var str = AttributedString("Hello, world", attributes: AttributeContainer().testInt(2).testDouble(3.4)) - XCTAssertEqual(str.test.testInt, 2) - XCTAssertEqual(str.test.testDouble, 3.4) - XCTAssertEqual(str.runs[str.runs.startIndex].test.testInt, 2) + #expect(str.test.testInt == 2) + #expect(str.test.testDouble == 3.4) + #expect(str.runs[str.runs.startIndex].test.testInt == 2) str.test.testInt = 4 - XCTAssertEqual(str, AttributedString("Hello, world", attributes: AttributeContainer.testInt(4).testDouble(3.4))) + #expect(str == AttributedString("Hello, world", attributes: AttributeContainer.testInt(4).testDouble(3.4))) let range = str.startIndex ..< str.characters.index(after: str.startIndex) str[range].test.testBool = true - XCTAssertNil(str.test.testBool) - XCTAssertNotNil(str[range].test.testBool) - XCTAssertTrue(str[range].test.testBool!) + #expect(str.test.testBool == nil) + #expect(str[range].test.testBool != nil) + #expect(str[range].test.testBool == true) } - func testRunAttributes() { + @Test func runAttributes() { var str = AttributedString("String", attributes: .init().testString("test1")) str += "None" str += AttributedString("String+Int", attributes: .init().testString("test2").testInt(42)) let attributes = str.runs.map { $0.attributes } - XCTAssertEqual(attributes.count, 3) - XCTAssertEqual(attributes[0], .init().testString("test1")) - XCTAssertEqual(attributes[1], .init()) - XCTAssertEqual(attributes[2], .init().testString("test2").testInt(42)) + #expect(attributes.count == 3) + #expect(attributes[0] == .init().testString("test1")) + #expect(attributes[1] == .init()) + #expect(attributes[2] == .init().testString("test2").testInt(42)) } // MARK: - Comparison Tests - func testAttributedStringEquality() { - XCTAssertEqual(AttributedString(), AttributedString()) - XCTAssertEqual(AttributedString("abc"), AttributedString("abc")) - XCTAssertEqual(AttributedString("abc", attributes: AttributeContainer().testInt(1)), AttributedString("abc", attributes: AttributeContainer().testInt(1))) - XCTAssertNotEqual(AttributedString("abc", attributes: AttributeContainer().testInt(1)), AttributedString("abc", attributes: AttributeContainer().testInt(2))) - XCTAssertNotEqual(AttributedString("abc", attributes: AttributeContainer().testInt(1)), AttributedString("def", attributes: AttributeContainer().testInt(1))) + @Test func attributedStringEquality() { + #expect(AttributedString() == AttributedString()) + #expect(AttributedString("abc") == AttributedString("abc")) + #expect(AttributedString("abc", attributes: AttributeContainer().testInt(1)) == AttributedString("abc", attributes: AttributeContainer().testInt(1))) + #expect(AttributedString("abc", attributes: AttributeContainer().testInt(1)) != AttributedString("abc", attributes: AttributeContainer().testInt(2))) + #expect(AttributedString("abc", attributes: AttributeContainer().testInt(1)) != AttributedString("def", attributes: AttributeContainer().testInt(1))) var a = AttributedString("abc", attributes: AttributeContainer().testInt(1)) a += AttributedString("def", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(a, AttributedString("abcdef", attributes: AttributeContainer().testInt(1))) + #expect(a == AttributedString("abcdef", attributes: AttributeContainer().testInt(1))) a = AttributedString("ab", attributes: AttributeContainer().testInt(1)) a += AttributedString("cdef", attributes: AttributeContainer().testInt(2)) var b = AttributedString("abcd", attributes: AttributeContainer().testInt(1)) b += AttributedString("ef", attributes: AttributeContainer().testInt(2)) - XCTAssertNotEqual(a, b) + #expect(a != b) a = AttributedString("abc") a += AttributedString("defghi", attributes: AttributeContainer().testInt(2)) @@ -316,22 +345,22 @@ final class TestAttributedString: XCTestCase { b = AttributedString("abc") b += AttributedString("def", attributes: AttributeContainer().testInt(2)) b += "ghijkl" - XCTAssertNotEqual(a, b) + #expect(a != b) let a1 = AttributedString("Café", attributes: AttributeContainer().testInt(1)) let a2 = AttributedString("Cafe\u{301}", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(a1, a2) + #expect(a1 == a2) let a3 = (AttributedString("Cafe", attributes: AttributeContainer().testInt(1)) + AttributedString("\u{301}", attributes: AttributeContainer().testInt(2))) - XCTAssertNotEqual(a1, a3) - XCTAssertNotEqual(a2, a3) - XCTAssertTrue(a1.characters.elementsEqual(a3.characters)) - XCTAssertTrue(a2.characters.elementsEqual(a3.characters)) + #expect(a1 != a3) + #expect(a2 != a3) + #expect(a1.characters.elementsEqual(a3.characters)) + #expect(a2.characters.elementsEqual(a3.characters)) } - func testAttributedSubstringEquality() { + @Test func attributedSubstringEquality() { let emptyStr = AttributedString("01234567890123456789") let index0 = emptyStr.characters.startIndex @@ -346,22 +375,22 @@ final class TestAttributedString: XCTestCase { halfhalfStr[index0 ..< index10].testInt = 1 halfhalfStr[index10 ..< index20].testDouble = 2.0 - XCTAssertEqual(emptyStr[index0 ..< index0], emptyStr[index0 ..< index0]) - XCTAssertEqual(emptyStr[index0 ..< index5], emptyStr[index0 ..< index5]) - XCTAssertEqual(emptyStr[index0 ..< index20], emptyStr[index0 ..< index20]) - XCTAssertEqual(singleAttrStr[index0 ..< index20], singleAttrStr[index0 ..< index20]) - XCTAssertEqual(halfhalfStr[index0 ..< index20], halfhalfStr[index0 ..< index20]) + #expect(emptyStr[index0 ..< index0] == emptyStr[index0 ..< index0]) + #expect(emptyStr[index0 ..< index5] == emptyStr[index0 ..< index5]) + #expect(emptyStr[index0 ..< index20] == emptyStr[index0 ..< index20]) + #expect(singleAttrStr[index0 ..< index20] == singleAttrStr[index0 ..< index20]) + #expect(halfhalfStr[index0 ..< index20] == halfhalfStr[index0 ..< index20]) - XCTAssertEqual(emptyStr[index0 ..< index10], singleAttrStr[index10 ..< index20]) - XCTAssertEqual(halfhalfStr[index0 ..< index10], singleAttrStr[index0 ..< index10]) + #expect(emptyStr[index0 ..< index10] == singleAttrStr[index10 ..< index20]) + #expect(halfhalfStr[index0 ..< index10] == singleAttrStr[index0 ..< index10]) - XCTAssertNotEqual(emptyStr[index0 ..< index10], singleAttrStr[index0 ..< index10]) - XCTAssertNotEqual(emptyStr[index0 ..< index10], singleAttrStr[index0 ..< index20]) + #expect(emptyStr[index0 ..< index10] != singleAttrStr[index0 ..< index10]) + #expect(emptyStr[index0 ..< index10] != singleAttrStr[index0 ..< index20]) - XCTAssertTrue(emptyStr[index0 ..< index5] == AttributedString("01234")) + #expect(emptyStr[index0 ..< index5] == AttributedString("01234")) } - func testRunEquality() { + @Test func runEquality() { var attrStr = AttributedString("Hello", attributes: AttributeContainer().testInt(1)) attrStr += AttributedString(" ") attrStr += AttributedString("World", attributes: AttributeContainer().testInt(2)) @@ -380,25 +409,25 @@ final class TestAttributedString: XCTestCase { } // Same string, same range, different attributes - XCTAssertNotEqual(run(0, in: attrStr), run(0, in: attrStr2)) + #expect(run(0, in: attrStr) != run(0, in: attrStr2)) // Different strings, same range, same attributes - XCTAssertEqual(run(1, in: attrStr), run(1, in: attrStr2)) + #expect(run(1, in: attrStr) == run(1, in: attrStr2)) // Same string, same range, same attributes - XCTAssertEqual(run(2, in: attrStr), run(2, in: attrStr2)) + #expect(run(2, in: attrStr) == run(2, in: attrStr2)) // Different string, different range, same attributes - XCTAssertEqual(run(2, in: attrStr), run(0, in: attrStr2)) + #expect(run(2, in: attrStr) == run(0, in: attrStr2)) // Same string, different range, same attributes - XCTAssertEqual(run(0, in: attrStr), run(3, in: attrStr2)) + #expect(run(0, in: attrStr) == run(3, in: attrStr2)) // A runs collection of the same order but different run lengths - XCTAssertNotEqual(attrStr.runs, attrStr3.runs) + #expect(attrStr.runs != attrStr3.runs) } - func testSubstringRunEquality() { + @Test func substringRunEquality() { var attrStr = AttributedString("Hello", attributes: AttributeContainer().testInt(1)) attrStr += AttributedString(" ") attrStr += AttributedString("World", attributes: AttributeContainer().testInt(2)) @@ -407,16 +436,16 @@ final class TestAttributedString: XCTestCase { attrStr2 += AttributedString("_") attrStr2 += AttributedString("World", attributes: AttributeContainer().testInt(2)) - XCTAssertEqual(attrStr[attrStr.runs.last!.range].runs, attrStr2[attrStr2.runs.first!.range].runs) - XCTAssertEqual(attrStr[attrStr.runs.last!.range].runs, attrStr2[attrStr2.runs.last!.range].runs) + #expect(attrStr[attrStr.runs.last!.range].runs == attrStr2[attrStr2.runs.first!.range].runs) + #expect(attrStr[attrStr.runs.last!.range].runs == attrStr2[attrStr2.runs.last!.range].runs) let rangeA = attrStr.runs.first!.range.upperBound ..< attrStr.endIndex let rangeB = attrStr2.runs.first!.range.upperBound ..< attrStr.endIndex let rangeC = attrStr.startIndex ..< attrStr.runs.last!.range.lowerBound let rangeD = attrStr.runs.first!.range - XCTAssertEqual(attrStr[rangeA].runs, attrStr2[rangeB].runs) - XCTAssertNotEqual(attrStr[rangeC].runs, attrStr2[rangeB].runs) - XCTAssertNotEqual(attrStr[rangeD].runs, attrStr2[rangeB].runs) + #expect(attrStr[rangeA].runs == attrStr2[rangeB].runs) + #expect(attrStr[rangeC].runs != attrStr2[rangeB].runs) + #expect(attrStr[rangeD].runs != attrStr2[rangeB].runs) // Test starting/ending runs that only differ outside of the range do not prevent equality attrStr2[attrStr.runs.first!.range].testInt = 1 @@ -424,29 +453,29 @@ final class TestAttributedString: XCTestCase { attrStr2.characters.append(contentsOf: "45") let rangeE = attrStr.startIndex ..< attrStr.endIndex let rangeF = attrStr2.characters.index(attrStr2.startIndex, offsetBy: 3) ..< attrStr2.characters.index(attrStr2.startIndex, offsetBy: 14) - XCTAssertEqual(attrStr[rangeE].runs, attrStr2[rangeF].runs) + #expect(attrStr[rangeE].runs == attrStr2[rangeF].runs) } // MARK: - Mutation Tests - func testDirectMutationCopyOnWrite() { + @Test func directMutationCopyOnWrite() { var attrStr = AttributedString("ABC") let copy = attrStr attrStr += "D" - XCTAssertEqual(copy, AttributedString("ABC")) - XCTAssertNotEqual(attrStr, copy) + #expect(copy == AttributedString("ABC")) + #expect(attrStr != copy) } - func testAttributeMutationCopyOnWrite() { + @Test func attributeMutationCopyOnWrite() { var attrStr = AttributedString("ABC") let copy = attrStr attrStr.testInt = 1 - XCTAssertNotEqual(attrStr, copy) + #expect(attrStr != copy) } - func testSliceAttributeMutation() { + @Test func sliceAttributeMutation() { let attr : Int = 42 let attr2 : Double = 1.0 @@ -460,12 +489,12 @@ final class TestAttributedString: XCTestCase { var expected = AttributedString("Hello", attributes: AttributeContainer().testInt(attr).testDouble(attr2)) expected += AttributedString(" World", attributes: AttributeContainer().testInt(attr)) - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) - XCTAssertNotEqual(copy, attrStr) + #expect(copy != attrStr) } - func testEnumerationAttributeMutation() { + @Test func enumerationAttributeMutation() { var attrStr = AttributedString("A", attributes: AttributeContainer().testInt(1)) attrStr += AttributedString("B", attributes: AttributeContainer().testDouble(2.0)) attrStr += AttributedString("C", attributes: AttributeContainer().testInt(3)) @@ -479,10 +508,10 @@ final class TestAttributedString: XCTestCase { var expected = AttributedString("A") expected += AttributedString("B", attributes: AttributeContainer().testDouble(2.0)) expected += "C" - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testMutateMultipleAttributes() { + @Test func mutateMultipleAttributes() { var attrStr = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) attrStr += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) attrStr += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) @@ -494,7 +523,7 @@ final class TestAttributedString: XCTestCase { $2.value = nil } let removal1expected = AttributedString("ABC") - XCTAssertEqual(removal1expected, removal1) + #expect(removal1expected == removal1) // Test change value, same attribute. let changeSame1 = attrStr.transformingAttributes(\.testInt, \.testDouble, \.testBool) { @@ -511,7 +540,7 @@ final class TestAttributedString: XCTestCase { var changeSame1expected = AttributedString("A", attributes: AttributeContainer().testInt(42).testBool(false)) changeSame1expected += AttributedString("B", attributes: AttributeContainer().testInt(42).testDouble(3)) changeSame1expected += AttributedString("C", attributes: AttributeContainer().testDouble(3).testBool(true)) - XCTAssertEqual(changeSame1expected, changeSame1) + #expect(changeSame1expected == changeSame1) // Test change value, different attribute let changeDifferent1 = attrStr.transformingAttributes(\.testInt, \.testDouble, \.testBool) { @@ -529,7 +558,7 @@ final class TestAttributedString: XCTestCase { var changeDifferent1expected = AttributedString("A", attributes: AttributeContainer().testDouble(2).testInt(42)) changeDifferent1expected += AttributedString("B", attributes: AttributeContainer().testDouble(2).testBool(false)) changeDifferent1expected += AttributedString("C", attributes: AttributeContainer().testBool(false).testInt(42)) - XCTAssertEqual(changeDifferent1expected, changeDifferent1) + #expect(changeDifferent1expected == changeDifferent1) // Test change range var changeRange1First = true @@ -546,10 +575,10 @@ final class TestAttributedString: XCTestCase { var changeRange1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) changeRange1expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testBool(true)) changeRange1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) - XCTAssertEqual(changeRange1expected, changeRange1) + #expect(changeRange1expected == changeRange1) } - func testMutateAttributes() { + @Test func mutateAttributes() { var attrStr = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) attrStr += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) attrStr += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) @@ -561,7 +590,7 @@ final class TestAttributedString: XCTestCase { var removal1expected = AttributedString("A", attributes: AttributeContainer().testBool(true)) removal1expected += AttributedString("B", attributes: AttributeContainer().testDouble(2)) removal1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) - XCTAssertEqual(removal1expected, removal1) + #expect(removal1expected == removal1) // Test change value, same attribute. let changeSame1 = attrStr.transformingAttributes(\.testBool) { @@ -572,7 +601,7 @@ final class TestAttributedString: XCTestCase { var changeSame1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(false)) changeSame1expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) changeSame1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(true)) - XCTAssertEqual(changeSame1expected, changeSame1) + #expect(changeSame1expected == changeSame1) // Test change value, different attribute let changeDifferent1 = attrStr.transformingAttributes(\.testBool) { @@ -583,7 +612,7 @@ final class TestAttributedString: XCTestCase { var changeDifferent1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testDouble(42)) changeDifferent1expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) changeDifferent1expected += AttributedString("C", attributes: AttributeContainer().testDouble(43)) - XCTAssertEqual(changeDifferent1expected, changeDifferent1) + #expect(changeDifferent1expected == changeDifferent1) // Test change range let changeRange1 = attrStr.transformingAttributes(\.testInt) { @@ -595,7 +624,7 @@ final class TestAttributedString: XCTestCase { var changeRange1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) changeRange1expected += AttributedString("B", attributes: AttributeContainer().testDouble(2)) changeRange1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) - XCTAssertEqual(changeRange1expected, changeRange1) + #expect(changeRange1expected == changeRange1) // Now try extending it let changeRange2 = attrStr.transformingAttributes(\.testInt) { @@ -607,10 +636,10 @@ final class TestAttributedString: XCTestCase { var changeRange2expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) changeRange2expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) changeRange2expected += AttributedString("C", attributes: AttributeContainer().testInt(1).testDouble(2).testBool(false)) - XCTAssertEqual(changeRange2expected, changeRange2) + #expect(changeRange2expected == changeRange2) } - func testReplaceAttributes() { + @Test func replaceAttributes() { var attrStr = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) attrStr += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) attrStr += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) @@ -624,7 +653,7 @@ final class TestAttributedString: XCTestCase { var removal1expected = AttributedString("A", attributes: AttributeContainer().testBool(true)) removal1expected += AttributedString("B", attributes: AttributeContainer().testDouble(2)) removal1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(false)) - XCTAssertEqual(removal1expected, removal1) + #expect(removal1expected == removal1) // Test change value, same attribute. let changeSame1Find = AttributeContainer().testBool(false) @@ -635,7 +664,7 @@ final class TestAttributedString: XCTestCase { var changeSame1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) changeSame1expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) changeSame1expected += AttributedString("C", attributes: AttributeContainer().testDouble(2).testBool(true)) - XCTAssertEqual(changeSame1expected, changeSame1) + #expect(changeSame1expected == changeSame1) // Test change value, different attribute let changeDifferent1Find = AttributeContainer().testBool(false) @@ -646,26 +675,26 @@ final class TestAttributedString: XCTestCase { var changeDifferent1expected = AttributedString("A", attributes: AttributeContainer().testInt(1).testBool(true)) changeDifferent1expected += AttributedString("B", attributes: AttributeContainer().testInt(1).testDouble(2)) changeDifferent1expected += AttributedString("C", attributes: AttributeContainer().testDouble(43)) - XCTAssertEqual(changeDifferent1expected, changeDifferent1) + #expect(changeDifferent1expected == changeDifferent1) } - func testSliceMutation() { + @Test func sliceMutation() { var attrStr = AttributedString("Hello World", attributes: AttributeContainer().testInt(1)) let start = attrStr.characters.index(attrStr.startIndex, offsetBy: 6) attrStr.replaceSubrange(start ..< attrStr.characters.index(start, offsetBy:5), with: AttributedString("Goodbye", attributes: AttributeContainer().testInt(2))) var expected = AttributedString("Hello ", attributes: AttributeContainer().testInt(1)) expected += AttributedString("Goodbye", attributes: AttributeContainer().testInt(2)) - XCTAssertEqual(attrStr, expected) - XCTAssertNotEqual(attrStr, AttributedString("Hello Goodbye", attributes: AttributeContainer().testInt(1))) + #expect(attrStr == expected) + #expect(attrStr != AttributedString("Hello Goodbye", attributes: AttributeContainer().testInt(1))) } - func testOverlappingSliceMutation() { + @Test func overlappingSliceMutation() throws { var attrStr = AttributedString("Hello, world!") - attrStr[attrStr.range(of: "Hello")!].testInt = 1 - attrStr[attrStr.range(of: "world")!].testInt = 2 - attrStr[attrStr.range(of: "o, wo")!].testBool = true + attrStr[try #require(attrStr.range(of: "Hello"))].testInt = 1 + attrStr[try #require(attrStr.range(of: "world"))].testInt = 2 + attrStr[try #require(attrStr.range(of: "o, wo"))].testBool = true var expected = AttributedString("Hell", attributes: AttributeContainer().testInt(1)) expected += AttributedString("o", attributes: AttributeContainer().testInt(1).testBool(true)) @@ -673,43 +702,43 @@ final class TestAttributedString: XCTestCase { expected += AttributedString("wo", attributes: AttributeContainer().testBool(true).testInt(2)) expected += AttributedString("rld", attributes: AttributeContainer().testInt(2)) expected += AttributedString("!") - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) } - func testCharacters_replaceSubrange() { + @Test func characters_replaceSubrange() throws { var attrStr = AttributedString("Hello World", attributes: AttributeContainer().testInt(1)) - attrStr.characters.replaceSubrange(attrStr.range(of: " ")!, with: " Good ") + attrStr.characters.replaceSubrange(try #require(attrStr.range(of: " ")), with: " Good ") let expected = AttributedString("Hello Good World", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testCharactersMutation_append() { + @Test func charactersMutation_append() { var attrStr = AttributedString("Hello World", attributes: AttributeContainer().testInt(1)) attrStr.characters.append(contentsOf: " Goodbye") let expected = AttributedString("Hello World Goodbye", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testUnicodeScalars_replaceSubrange() { + @Test func unicodeScalars_replaceSubrange() { var attrStr = AttributedString("La Cafe\u{301}", attributes: AttributeContainer().testInt(1)) let unicode = attrStr.unicodeScalars attrStr.unicodeScalars.replaceSubrange(unicode.index(unicode.startIndex, offsetBy: 3) ..< unicode.index(unicode.startIndex, offsetBy: 7), with: "Ole".unicodeScalars) let expected = AttributedString("La Ole\u{301}", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testUnicodeScalarsMutation_append() { + @Test func unicodeScalarsMutation_append() { var attrStr = AttributedString("Cafe", attributes: AttributeContainer().testInt(1)) attrStr.unicodeScalars.append("\u{301}") let expected = AttributedString("Cafe\u{301}", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testSubCharacterAttributeSetting() { + @Test func subCharacterAttributeSetting() { var attrStr = AttributedString("Cafe\u{301}", attributes: AttributeContainer().testInt(1)) let cafRange = attrStr.characters.startIndex ..< attrStr.characters.index(attrStr.characters.startIndex, offsetBy: 3) let eRange = cafRange.upperBound ..< attrStr.unicodeScalars.index(after: cafRange.upperBound) @@ -721,10 +750,10 @@ final class TestAttributedString: XCTestCase { var expected = AttributedString("Caf", attributes: AttributeContainer().testInt(1).testDouble(1.5)) expected += AttributedString("e", attributes: AttributeContainer().testInt(1).testDouble(2.5)) expected += AttributedString("\u{301}", attributes: AttributeContainer().testInt(1).testDouble(3.5)) - XCTAssertEqual(expected, attrStr) + #expect(expected == attrStr) } - func testReplaceSubrange_rangeExpression() { + @Test func replaceSubrange_rangeExpression() { var attrStr = AttributedString("Hello World", attributes: AttributeContainer().testInt(1)) // Test with PartialRange, which conforms to RangeExpression but is not a Range @@ -733,20 +762,20 @@ final class TestAttributedString: XCTestCase { var expected = AttributedString("Goodbye") expected += AttributedString(" World", attributes: AttributeContainer().testInt(1)) - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) } - func testSettingAttributes() { + @Test func settingAttributes() { var attrStr = AttributedString("Hello World", attributes: .init().testInt(1)) attrStr += AttributedString(". My name is Foundation!", attributes: .init().testBool(true)) let result = attrStr.settingAttributes(.init().testBool(false)) let expected = AttributedString("Hello World. My name is Foundation!", attributes: .init().testBool(false)) - XCTAssertEqual(result, expected) + #expect(result == expected) } - func testAddAttributedString() { + @Test func addAttributedString() { let attrStr = AttributedString("Hello ", attributes: .init().testInt(1)) let attrStr2 = AttributedString("World", attributes: .init().testInt(2)) let original = attrStr @@ -755,22 +784,22 @@ final class TestAttributedString: XCTestCase { var concat = AttributedString("Hello ", attributes: .init().testInt(1)) concat += AttributedString("World", attributes: .init().testInt(2)) let combine = attrStr + attrStr2 - XCTAssertEqual(attrStr, original) - XCTAssertEqual(attrStr2, original2) - XCTAssertEqual(String(combine.characters), "Hello World") - XCTAssertEqual(String(concat.characters), "Hello World") + #expect(attrStr == original) + #expect(attrStr2 == original2) + #expect(String(combine.characters) == "Hello World") + #expect(String(concat.characters) == "Hello World") let testInts = [1, 2] for str in [concat, combine] { var i = 0 for run in str.runs { - XCTAssertEqual(run.testInt, testInts[i]) + #expect(run.testInt == testInts[i]) i += 1 } } } - func testReplaceSubrangeWithSubstrings() { + @Test func replaceSubrangeWithSubstrings() { let baseString = AttributedString("A", attributes: .init().testInt(1)) + AttributedString("B", attributes: .init().testInt(2)) + AttributedString("C", attributes: .init().testInt(3)) @@ -788,7 +817,7 @@ final class TestAttributedString: XCTestCase { + AttributedString("D", attributes: .init().testInt(4)) + AttributedString("Z", attributes: .init().testString("foo")) - XCTAssertEqual(targetString, expected) + #expect(targetString == expected) targetString = AttributedString("XYZ", attributes: .init().testString("foo")) targetString.append(substring) @@ -797,20 +826,20 @@ final class TestAttributedString: XCTestCase { + AttributedString("C", attributes: .init().testInt(3)) + AttributedString("D", attributes: .init().testInt(4)) - XCTAssertEqual(targetString, expected) + #expect(targetString == expected) } func assertStringIsCoalesced(_ str: AttributedString) { var prev: AttributedString.Runs.Run? for run in str.runs { if let prev = prev { - XCTAssertNotEqual(prev.attributes, run.attributes) + #expect(prev.attributes != run.attributes) } prev = run } } - func testCoalescing() { + @Test func coalescing() { let str = AttributedString("Hello", attributes: .init().testInt(1)) let appendSame = str + AttributedString("World", attributes: .init().testInt(1)) let appendDifferent = str + AttributedString("World", attributes: .init().testInt(2)) @@ -818,67 +847,67 @@ final class TestAttributedString: XCTestCase { assertStringIsCoalesced(str) assertStringIsCoalesced(appendSame) assertStringIsCoalesced(appendDifferent) - XCTAssertEqual(appendSame.runs.count, 1) - XCTAssertEqual(appendDifferent.runs.count, 2) + #expect(appendSame.runs.count == 1) + #expect(appendDifferent.runs.count == 2) // Ensure replacing whole string keeps coalesced var str2 = str str2.replaceSubrange(str2.startIndex ..< str2.endIndex, with: AttributedString("Hello", attributes: .init().testInt(2))) assertStringIsCoalesced(str2) - XCTAssertEqual(str2.runs.count, 1) + #expect(str2.runs.count == 1) // Ensure replacing subranges splits runs and doesn't coalesce when not equal var str3 = str str3.replaceSubrange(str3.characters.index(after: str3.startIndex) ..< str3.endIndex, with: AttributedString("ello", attributes: .init().testInt(2))) assertStringIsCoalesced(str3) - XCTAssertEqual(str3.runs.count, 2) + #expect(str3.runs.count == 2) var str4 = str str4.replaceSubrange(str4.startIndex ..< str4.characters.index(before: str4.endIndex), with: AttributedString("Hell", attributes: .init().testInt(2))) assertStringIsCoalesced(str4) - XCTAssertEqual(str4.runs.count, 2) + #expect(str4.runs.count == 2) var str5 = str str5.replaceSubrange(str5.characters.index(after: str5.startIndex) ..< str5.characters.index(before: str4.endIndex), with: AttributedString("ell", attributes: .init().testInt(2))) assertStringIsCoalesced(str5) - XCTAssertEqual(str5.runs.count, 3) + #expect(str5.runs.count == 3) // Ensure changing attributes back to match bordering runs coalesces with edge of subrange var str6 = str5 str6.replaceSubrange(str6.characters.index(after: str6.startIndex) ..< str6.endIndex, with: AttributedString("ello", attributes: .init().testInt(1))) assertStringIsCoalesced(str6) - XCTAssertEqual(str6.runs.count, 1) + #expect(str6.runs.count == 1) var str7 = str5 str7.replaceSubrange(str7.startIndex ..< str7.characters.index(before: str7.endIndex), with: AttributedString("Hell", attributes: .init().testInt(1))) assertStringIsCoalesced(str7) - XCTAssertEqual(str7.runs.count, 1) + #expect(str7.runs.count == 1) var str8 = str5 str8.replaceSubrange(str8.characters.index(after: str8.startIndex) ..< str8.characters.index(before: str8.endIndex), with: AttributedString("ell", attributes: .init().testInt(1))) assertStringIsCoalesced(str8) - XCTAssertEqual(str8.runs.count, 1) + #expect(str8.runs.count == 1) var str9 = str5 str9.testInt = 1 assertStringIsCoalesced(str9) - XCTAssertEqual(str9.runs.count, 1) + #expect(str9.runs.count == 1) var str10 = str5 str10[str10.characters.index(after: str10.startIndex) ..< str10.characters.index(before: str10.endIndex)].testInt = 1 assertStringIsCoalesced(str10) - XCTAssertEqual(str10.runs.count, 1) + #expect(str10.runs.count == 1) } - func testReplaceWithEmptyElements() { + @Test func replaceWithEmptyElements() { var str = AttributedString("Hello, world") let range = str.startIndex ..< str.characters.index(str.startIndex, offsetBy: 5) str.characters.replaceSubrange(range, with: []) - XCTAssertEqual(str, AttributedString(", world")) + #expect(str == AttributedString(", world")) } - func testDescription() { + @Test func description() { let string = AttributedString("A", attributes: .init().testInt(1)) + AttributedString("B", attributes: .init().testInt(2)) + AttributedString("C", attributes: .init().testInt(3)) @@ -903,27 +932,27 @@ E { \tTestInt = 5 } """ - XCTAssertEqual(desc, expected) + #expect(desc == expected) let runsDesc = String(describing: string.runs) - XCTAssertEqual(runsDesc, expected) + #expect(runsDesc == expected) } - func testContainerDescription() { + @Test func containerDescription() { let cont = AttributeContainer().testBool(false).testInt(1).testDouble(2.0).testString("3") let desc = String(describing: cont) // Don't get bitten by any potential changes in the hashing algorithm. - XCTAssertTrue(desc.hasPrefix("{\n")) - XCTAssertTrue(desc.hasSuffix("\n}")) - XCTAssertTrue(desc.contains("\tTestDouble = 2.0\n")) - XCTAssertTrue(desc.contains("\tTestInt = 1\n")) - XCTAssertTrue(desc.contains("\tTestString = 3\n")) - XCTAssertTrue(desc.contains("\tTestBool = false\n")) + #expect(desc.hasPrefix("{\n")) + #expect(desc.hasSuffix("\n}")) + #expect(desc.contains("\tTestDouble = 2.0\n")) + #expect(desc.contains("\tTestInt = 1\n")) + #expect(desc.contains("\tTestString = 3\n")) + #expect(desc.contains("\tTestBool = false\n")) } - func testRunAndSubstringDescription() { + @Test func runAndSubstringDescription() { let string = AttributedString("A", attributes: .init().testInt(1)) + AttributedString("B", attributes: .init().testInt(2)) + AttributedString("C", attributes: .init().testInt(3)) @@ -952,134 +981,134 @@ E { \tTestInt = 5 } """] - XCTAssertEqual(runsDescs, expected) + #expect(runsDescs == expected) let subDescs = string.runs.map() { String(describing: string[$0.range]) } - XCTAssertEqual(subDescs, expected) + #expect(subDescs == expected) } - func testReplacingAttributes() { + @Test func replacingAttributes() { var str = AttributedString("Hello", attributes: .init().testInt(2)) str += AttributedString("World", attributes: .init().testString("Test")) var result = str.replacingAttributes(.init().testInt(2).testString("NotTest"), with: .init().testBool(false)) - XCTAssertEqual(result, str) + #expect(result == str) result = str.replacingAttributes(.init().testInt(2), with: .init().testBool(false)) var expected = AttributedString("Hello", attributes: .init().testBool(false)) expected += AttributedString("World", attributes: .init().testString("Test")) - XCTAssertEqual(result, expected) + #expect(result == expected) } - func testScopedAttributeContainer() { + @Test func scopedAttributeContainer() { var str = AttributedString("Hello, world") - XCTAssertNil(str.test.testInt) - XCTAssertNil(str.testInt) + #expect(str.test.testInt == nil) + #expect(str.testInt == nil) str.test.testInt = 2 - XCTAssertEqual(str.test.testInt, 2) - XCTAssertEqual(str.testInt, 2) + #expect(str.test.testInt == 2) + #expect(str.testInt == 2) str.test.testInt = nil - XCTAssertNil(str.test.testInt) - XCTAssertNil(str.testInt) + #expect(str.test.testInt == nil) + #expect(str.testInt == nil) let range = str.startIndex ..< str.index(str.startIndex, offsetByCharacters: 5) let otherRange = range.upperBound ..< str.endIndex str[range].test.testBool = true - XCTAssertEqual(str[range].test.testBool, true) - XCTAssertEqual(str[range].testBool, true) - XCTAssertNil(str.test.testBool) - XCTAssertNil(str.testBool) + #expect(str[range].test.testBool == true) + #expect(str[range].testBool == true) + #expect(str.test.testBool == nil) + #expect(str.testBool == nil) str[range].test.testBool = nil - XCTAssertNil(str[range].test.testBool) - XCTAssertNil(str[range].testBool) - XCTAssertNil(str.test.testBool) - XCTAssertNil(str.testBool) + #expect(str[range].test.testBool == nil) + #expect(str[range].testBool == nil) + #expect(str.test.testBool == nil) + #expect(str.testBool == nil) str.test.testBool = true str[range].test.testBool = nil - XCTAssertNil(str[range].test.testBool) - XCTAssertNil(str[range].testBool) - XCTAssertNil(str.test.testBool) - XCTAssertNil(str.testBool) - XCTAssertEqual(str[otherRange].test.testBool, true) - XCTAssertEqual(str[otherRange].testBool, true) + #expect(str[range].test.testBool == nil) + #expect(str[range].testBool == nil) + #expect(str.test.testBool == nil) + #expect(str.testBool == nil) + #expect(str[otherRange].test.testBool == true) + #expect(str[otherRange].testBool == true) } - func testMergeAttributes() { + @Test func mergeAttributes() { let originalAttributes = AttributeContainer.testInt(2).testBool(true) let newAttributes = AttributeContainer.testString("foo") let overlappingAttributes = AttributeContainer.testInt(3).testDouble(4.3) let str = AttributedString("Hello, world", attributes: originalAttributes) - XCTAssertEqual(str.mergingAttributes(newAttributes, mergePolicy: .keepNew), AttributedString("Hello, world", attributes: newAttributes.testInt(2).testBool(true))) - XCTAssertEqual(str.mergingAttributes(newAttributes, mergePolicy: .keepCurrent), AttributedString("Hello, world", attributes: newAttributes.testInt(2).testBool(true))) - XCTAssertEqual(str.mergingAttributes(overlappingAttributes, mergePolicy: .keepNew), AttributedString("Hello, world", attributes: overlappingAttributes.testBool(true))) - XCTAssertEqual(str.mergingAttributes(overlappingAttributes, mergePolicy: .keepCurrent), AttributedString("Hello, world", attributes: originalAttributes.testDouble(4.3))) + #expect(str.mergingAttributes(newAttributes, mergePolicy: .keepNew) == AttributedString("Hello, world", attributes: newAttributes.testInt(2).testBool(true))) + #expect(str.mergingAttributes(newAttributes, mergePolicy: .keepCurrent) == AttributedString("Hello, world", attributes: newAttributes.testInt(2).testBool(true))) + #expect(str.mergingAttributes(overlappingAttributes, mergePolicy: .keepNew) == AttributedString("Hello, world", attributes: overlappingAttributes.testBool(true))) + #expect(str.mergingAttributes(overlappingAttributes, mergePolicy: .keepCurrent) == AttributedString("Hello, world", attributes: originalAttributes.testDouble(4.3))) } - func testMergeAttributeContainers() { + @Test func mergeAttributeContainers() { let originalAttributes = AttributeContainer.testInt(2).testBool(true) let newAttributes = AttributeContainer.testString("foo") let overlappingAttributes = AttributeContainer.testInt(3).testDouble(4.3) - XCTAssertEqual(originalAttributes.merging(newAttributes, mergePolicy: .keepNew), newAttributes.testInt(2).testBool(true)) - XCTAssertEqual(originalAttributes.merging(newAttributes, mergePolicy: .keepCurrent), newAttributes.testInt(2).testBool(true)) - XCTAssertEqual(originalAttributes.merging(overlappingAttributes, mergePolicy: .keepNew), overlappingAttributes.testBool(true)) - XCTAssertEqual(originalAttributes.merging(overlappingAttributes, mergePolicy: .keepCurrent), originalAttributes.testDouble(4.3)) + #expect(originalAttributes.merging(newAttributes, mergePolicy: .keepNew) == newAttributes.testInt(2).testBool(true)) + #expect(originalAttributes.merging(newAttributes, mergePolicy: .keepCurrent) == newAttributes.testInt(2).testBool(true)) + #expect(originalAttributes.merging(overlappingAttributes, mergePolicy: .keepNew) == overlappingAttributes.testBool(true)) + #expect(originalAttributes.merging(overlappingAttributes, mergePolicy: .keepCurrent) == originalAttributes.testDouble(4.3)) } - func testChangingSingleCharacterUTF8Length() { + @Test func changingSingleCharacterUTF8Length() throws { var attrstr = AttributedString("\u{1F3BA}\u{1F3BA}") // UTF-8 Length of 8 attrstr.characters[attrstr.startIndex] = "A" // Changes UTF-8 Length to 5 - XCTAssertEqual(attrstr.runs.count, 1) - let runRange = attrstr.runs.first!.range + #expect(attrstr.runs.count == 1) + let runRange = try #require(attrstr.runs.first).range let substring = String(attrstr[runRange].characters) - XCTAssertEqual(substring, "A\u{1F3BA}") + #expect(substring == "A\u{1F3BA}") } // MARK: - Substring Tests - func testSubstringBase() { + @Test func substringBase() { let str = AttributedString("Hello World", attributes: .init().testInt(1)) var substr = str[str.startIndex ..< str.characters.index(str.startIndex, offsetBy: 5)] - XCTAssertEqual(substr.base, str) + #expect(substr.base == str) substr.testInt = 3 - XCTAssertNotEqual(substr.base, str) + #expect(substr.base != str) var str2 = AttributedString("Hello World", attributes: .init().testInt(1)) let range = str2.startIndex ..< str2.characters.index(str2.startIndex, offsetBy: 5) - XCTAssertEqual(str2[range].base, str2) + #expect(str2[range].base == str2) str2[range].testInt = 3 - XCTAssertEqual(str2[range].base, str2) + #expect(str2[range].base == str2) } - func testSubstringGetAttribute() { + @Test func substringGetAttribute() { let str = AttributedString("Hello World", attributes: .init().testInt(1)) let range = str.startIndex ..< str.characters.index(str.startIndex, offsetBy: 5) - XCTAssertEqual(str[range].testInt, 1) - XCTAssertNil(str[range].testString) + #expect(str[range].testInt == 1) + #expect(str[range].testString == nil) var str2 = AttributedString("Hel", attributes: .init().testInt(1)) str2 += AttributedString("lo World", attributes: .init().testInt(2).testBool(true)) let range2 = str2.startIndex ..< str2.characters.index(str2.startIndex, offsetBy: 5) - XCTAssertNil(str2[range2].testInt) - XCTAssertNil(str2[range2].testBool) + #expect(str2[range2].testInt == nil) + #expect(str2[range2].testBool == nil) } - func testSubstringDescription() { + @Test func substringDescription() { var str = AttributedString("Hello", attributes: .init().testInt(2)) str += " " str += AttributedString("World", attributes: .init().testInt(3)) for run in str.runs { let desc = str[run.range].description - XCTAssertFalse(desc.isEmpty) + #expect(!desc.isEmpty) } } - func testSubstringReplaceAttributes() { + @Test func substringReplaceAttributes() { var str = AttributedString("Hello", attributes: .init().testInt(2).testString("Foundation")) str += " " str += AttributedString("World", attributes: .init().testInt(3)) @@ -1091,32 +1120,32 @@ E { expected += AttributedString("llo", attributes: .init().testBool(true)) expected += " " expected += AttributedString("World", attributes: .init().testInt(3)) - XCTAssertEqual(str, expected) + #expect(str == expected) } - func testSubstringEquality() { + @Test func substringEquality() { let str = AttributedString("") let range = str.startIndex ..< str.endIndex - XCTAssertEqual(str[range], str[range]) + #expect(str[range] == str[range]) let str2 = "A" + AttributedString("A", attributes: .init().testInt(2)) let substringA = str2[str2.startIndex ..< str2.index(afterCharacter: str2.startIndex)] let substringB = str2[str2.index(afterCharacter: str2.startIndex) ..< str2.endIndex] - XCTAssertNotEqual(substringA, substringB) - XCTAssertEqual(substringA, substringA) - XCTAssertEqual(substringB, substringB) + #expect(substringA != substringB) + #expect(substringA == substringA) + #expect(substringB == substringB) } - func testInitializationFromSubstring() { + @Test func initializationFromSubstring() throws { var attrStr = AttributedString("yolo^+1 result<:s>^", attributes: AttributeContainer.testInt(2).testString("Hello")) - attrStr.replaceSubrange(attrStr.range(of: "<:s>")!, with: AttributedString("")) - attrStr[attrStr.range(of: "1 result")!].testInt = 3 + attrStr.replaceSubrange(try #require(attrStr.range(of: "<:s>")), with: AttributedString("")) + attrStr[try #require(attrStr.range(of: "1 result"))].testInt = 3 - let range = attrStr.range(of: "+1 result")! + let range = try #require(attrStr.range(of: "+1 result")) let subFinal = attrStr[range] let attrFinal = AttributedString(subFinal) - XCTAssertTrue(attrFinal.characters.elementsEqual(subFinal.characters)) - XCTAssertEqual(attrFinal.runs, subFinal.runs) + #expect(attrFinal.characters.elementsEqual(subFinal.characters)) + #expect(attrFinal.runs == subFinal.runs) var attrStr2 = AttributedString("xxxxxxxx", attributes: .init().testInt(1)) attrStr2 += AttributedString("y", attributes: .init().testInt(2)) @@ -1125,7 +1154,7 @@ E { let subrange = attrStr2.index(attrStr2.startIndex, offsetByCharacters: 5) ..< attrStr2.endIndex let substring2 = attrStr2[subrange] let recreated = AttributedString(substring2) - XCTAssertEqual(recreated.runs.count, 3) + #expect(recreated.runs.count == 3) } #if FOUNDATION_FRAMEWORK @@ -1137,7 +1166,7 @@ E { var attributedString = AttributedString() } - func testJSONEncoding() throws { + @Test func jsonEncoding() throws { let encoder = JSONEncoder() var attrStr = AttributedString("Hello", attributes: AttributeContainer().testBool(true).testString("blue").testInt(1)) attrStr += AttributedString(" World", attributes: AttributeContainer().testInt(2).testDouble(3.0).testString("http://www.apple.com")) @@ -1147,10 +1176,10 @@ E { let decoder = JSONDecoder() let decoded = try decoder.decode(CodableType.self, from: json) - XCTAssertEqual(decoded.attributedString, attrStr) + #expect(decoded.attributedString == attrStr) } - func testDecodingThenConvertingToNSAttributedString() throws { + @Test func decodingThenConvertingToNSAttributedString() throws { let encoder = JSONEncoder() var attrStr = AttributedString("Hello", attributes: AttributeContainer().testBool(true)) attrStr += AttributedString(" World", attributes: AttributeContainer().testInt(2)) @@ -1161,10 +1190,10 @@ E { let decoded = try decoder.decode(CodableType.self, from: json) let decodedns = try NSAttributedString(decoded.attributedString, including: AttributeScopes.TestAttributes.self) let ns = try NSAttributedString(attrStr, including: AttributeScopes.TestAttributes.self) - XCTAssertEqual(ns, decodedns) + #expect(ns == decodedns) } - func testCustomAttributeCoding() throws { + @Test func customAttributeCoding() throws { struct MyAttributes : AttributeScope { var customCodable : AttributeScopes.TestAttributes.CustomCodableAttribute } @@ -1183,10 +1212,10 @@ E { let decoder = JSONDecoder() let decoded = try decoder.decode(CodableType.self, from: json) - XCTAssertEqual(decoded.attributedString, attrStr) + #expect(decoded.attributedString == attrStr) } - func testCustomCodableTypeWithCodableAttributedString() throws { + @Test func customCodableTypeWithCodableAttributedString() throws { struct MyType : Codable, Equatable { var other: NonCodableType var str: AttributedString @@ -1222,10 +1251,10 @@ E { let data = try encoder.encode(type) let decoder = JSONDecoder() let decoded = try decoder.decode(MyType.self, from: data) - XCTAssertEqual(type, decoded) + #expect(type == decoded) } - func testCodingErrorsPropagateUpToCallSite() { + @Test func codingErrorsPropagateUpToCallSite() { enum CustomAttribute : CodableAttributedStringKey { typealias Value = String static let name = "CustomAttribute" @@ -1250,12 +1279,12 @@ E { var str = AttributedString("Hello, world") str[CustomAttribute.self] = "test" let encoder = JSONEncoder() - XCTAssertThrowsError(try encoder.encode(Obj(str: str)), "Attribute encoding error did not throw at call site") { err in - XCTAssert(err is TestError, "Encoding did not throw the proper error") + #expect(throws: TestError.self) { + try encoder.encode(Obj(str: str)) } } - func testEncodeWithPartiallyCodableScope() throws { + @Test func encodeWithPartiallyCodableScope() throws { enum NonCodableAttribute : AttributedStringKey { typealias Value = Int static let name = "NonCodableAttributes" @@ -1279,10 +1308,10 @@ E { var expected = str expected[NonCodableAttribute.self] = nil - XCTAssertEqual(decoded.str, expected) + #expect(decoded.str == expected) } - func testAutomaticCoding() throws { + @Test func automaticCoding() throws { struct Obj : Codable, Equatable { @CodableConfiguration(from: AttributeScopes.TestAttributes.self) var attrStr = AttributedString() @CodableConfiguration(from: AttributeScopes.TestAttributes.self) var optAttrStr : AttributedString? = nil @@ -1313,7 +1342,7 @@ E { let decoder = JSONDecoder() let decoded = try decoder.decode(Obj.self, from: data) - XCTAssertEqual(decoded, val) + #expect(decoded == val) } // non-nil @@ -1321,17 +1350,16 @@ E { let val = Obj(testValueWithNils: false) let encoder = JSONEncoder() let data = try encoder.encode(val) - print(String(data: data, encoding: .utf8)!) let decoder = JSONDecoder() let decoded = try decoder.decode(Obj.self, from: data) - XCTAssertEqual(decoded, val) + #expect(decoded == val) } } - func testManualCoding() throws { + @Test func manualCoding() throws { struct Obj : Codable, Equatable { var attrStr : AttributedString var optAttrStr : AttributedString? @@ -1381,11 +1409,10 @@ E { let val = Obj(testValueWithNils: true) let encoder = JSONEncoder() let data = try encoder.encode(val) - print(String(data: data, encoding: .utf8)!) let decoder = JSONDecoder() let decoded = try decoder.decode(Obj.self, from: data) - XCTAssertEqual(decoded, val) + #expect(decoded == val) } // non-nil @@ -1393,43 +1420,39 @@ E { let val = Obj(testValueWithNils: false) let encoder = JSONEncoder() let data = try encoder.encode(val) - print(String(data: data, encoding: .utf8)!) let decoder = JSONDecoder() let decoded = try decoder.decode(Obj.self, from: data) - XCTAssertEqual(decoded, val) - } - - } - - func testDecodingCorruptedData() throws { - let jsonStrings = [ - "{\"attributedString\": 2}", - "{\"attributedString\": []}", - "{\"attributedString\": [\"Test\"]}", - "{\"attributedString\": [\"Test\", 0]}", - "{\"attributedString\": [\"\", {}, \"Test\", {}]}", - "{\"attributedString\": [\"Test\", {}, \"\", {}]}", - "{\"attributedString\": [\"\", {\"TestInt\": 1}]}", - "{\"attributedString\": {}}", - "{\"attributedString\": {\"attributeTable\": []}}", - "{\"attributedString\": {\"runs\": []}}", - "{\"attributedString\": {\"runs\": [], \"attributeTable\": []}}", - "{\"attributedString\": {\"runs\": [\"\"], \"attributeTable\": []}}", - "{\"attributedString\": {\"runs\": [\"\", 1], \"attributeTable\": []}}", - "{\"attributedString\": {\"runs\": [\"\", {}, \"Test\", {}], \"attributeTable\": []}}", - "{\"attributedString\": {\"runs\": \"Test\", {}, \"\", {}, \"attributeTable\": []}}", - ] - + #expect(decoded == val) + } + + } + + @Test(arguments: [ + "{\"attributedString\": 2}", + "{\"attributedString\": []}", + "{\"attributedString\": [\"Test\"]}", + "{\"attributedString\": [\"Test\", 0]}", + "{\"attributedString\": [\"\", {}, \"Test\", {}]}", + "{\"attributedString\": [\"Test\", {}, \"\", {}]}", + "{\"attributedString\": [\"\", {\"TestInt\": 1}]}", + "{\"attributedString\": {}}", + "{\"attributedString\": {\"attributeTable\": []}}", + "{\"attributedString\": {\"runs\": []}}", + "{\"attributedString\": {\"runs\": [], \"attributeTable\": []}}", + "{\"attributedString\": {\"runs\": [\"\"], \"attributeTable\": []}}", + "{\"attributedString\": {\"runs\": [\"\", 1], \"attributeTable\": []}}", + "{\"attributedString\": {\"runs\": [\"\", {}, \"Test\", {}], \"attributeTable\": []}}", + "{\"attributedString\": {\"runs\": \"Test\", {}, \"\", {}, \"attributeTable\": []}}", + ]) + func decodingCorruptedData(string: String) throws { let decoder = JSONDecoder() - for string in jsonStrings { - XCTAssertThrowsError(try decoder.decode(CodableType.self, from: string.data(using: .utf8)!), "Corrupt data did not throw error for json data: \(string)") { err in - XCTAssertTrue(err is DecodingError, "Decoding threw an error that was not a DecodingError") - } + #expect(throws: DecodingError.self) { + try decoder.decode(CodableType.self, from: string.data(using: .utf8)!) } } - func testCodableRawRepresentableAttribute() throws { + @Test func codableRawRepresentableAttribute() throws { struct Attribute : CodableAttributedStringKey { static let name = "MyAttribute" enum Value: String, Codable, Hashable { @@ -1454,24 +1477,23 @@ E { let encoded = try encoder.encode(Object(str: str)) let decoder = JSONDecoder() let decoded = try decoder.decode(Object.self, from: encoded) - XCTAssertEqual(decoded.str[Attribute.self], .two) + #expect(decoded.str[Attribute.self] == .two) } - func testContainerEncoding() throws { + @Test func containerEncoding() throws { struct ContainerContainer : Codable { @CodableConfiguration(from: AttributeScopes.TestAttributes.self) var container = AttributeContainer() } let obj = ContainerContainer(container: AttributeContainer().testInt(1).testBool(true)) let encoder = JSONEncoder() let data = try encoder.encode(obj) - print(String(data: data, encoding: .utf8)!) let decoder = JSONDecoder() let decoded = try decoder.decode(ContainerContainer.self, from: data) - XCTAssertEqual(obj.container, decoded.container) + #expect(obj.container == decoded.container) } - func testDefaultAttributesCoding() throws { + @Test func defaultAttributesCoding() throws { struct DefaultContainer : Codable, Equatable { var str : AttributedString } @@ -1481,25 +1503,25 @@ E { let encoded = try encoder.encode(cont) let decoder = JSONDecoder() let decoded = try decoder.decode(DefaultContainer.self, from: encoded) - XCTAssertEqual(cont, decoded) + #expect(cont == decoded) } - func testDecodingMultibyteCharacters() throws { + @Test func decodingMultibyteCharacters() throws { let json = "{\"str\": [\"🎺ABC\", {\"TestInt\": 2}]}" struct Object : Codable { @CodableConfiguration(from: AttributeScopes.TestAttributes.self) var str: AttributedString = AttributedString() } let decoder = JSONDecoder() let str = try decoder.decode(Object.self, from: json.data(using: .utf8)!).str - XCTAssertEqual(str.runs.count, 1) - XCTAssertEqual(str.testInt, 2) + #expect(str.runs.count == 1) + #expect(str.testInt == 2) let idx = str.index(beforeCharacter: str.endIndex) - XCTAssertEqual(str.runs[idx].testInt, 2) + #expect(str.runs[idx].testInt == 2) } // MARK: - Conversion Tests - func testConversionToObjC() throws { + @Test func conversionToObjC() throws { var ourString = AttributedString("Hello", attributes: AttributeContainer().testInt(2)) ourString += AttributedString(" ") ourString += AttributedString("World", attributes: AttributeContainer().testString("Courier")) @@ -1507,10 +1529,10 @@ E { let theirString = NSMutableAttributedString(string: "Hello World") theirString.addAttributes([.testInt: NSNumber(value: 2)], range: NSMakeRange(0, 5)) theirString.addAttributes([.testString: "Courier"], range: NSMakeRange(6, 5)) - XCTAssertEqual(theirString, ourObjCString) + #expect(theirString == ourObjCString) } - func testConversionFromObjC() throws { + @Test func conversionFromObjC() throws { let nsString = NSMutableAttributedString(string: "Hello!") let rangeA = NSMakeRange(0, 3) let rangeB = NSMakeRange(3, 3) @@ -1520,10 +1542,10 @@ E { var string = AttributedString("Hel") string.testString = "Courier" string += AttributedString("lo!", attributes: AttributeContainer().testBool(true)) - XCTAssertEqual(string, convertedString) + #expect(string == convertedString) } - func testRoundTripConversion_boxed() throws { + @Test func roundTripConversion_boxed() throws { struct MyCustomType : Hashable { var num: Int var str: String @@ -1544,10 +1566,10 @@ E { let nsString = try NSAttributedString(attrString, including: MyCustomScope.self) let converted = try AttributedString(nsString, including: MyCustomScope.self) - XCTAssertEqual(converted[MyCustomAttribute.self], customVal) + #expect(converted[MyCustomAttribute.self] == customVal) } - func testRoundTripConversion_customConversion() throws { + @Test func roundTripConversion_customConversion() throws { struct MyCustomType : Hashable { } enum MyCustomAttribute : ObjectiveCConvertibleAttributedStringKey { @@ -1567,13 +1589,13 @@ E { attrString[MyCustomAttribute.self] = customVal let nsString = try NSAttributedString(attrString, including: MyCustomScope.self) - XCTAssertTrue(nsString.attribute(.init(MyCustomAttribute.name), at: 0, effectiveRange: nil) is NSUUID) + #expect(nsString.attribute(.init(MyCustomAttribute.name), at: 0, effectiveRange: nil) is NSUUID) let converted = try AttributedString(nsString, including: MyCustomScope.self) - XCTAssertEqual(converted[MyCustomAttribute.self], customVal) + #expect(converted[MyCustomAttribute.self] == customVal) } - func testIncompleteConversionFromObjC() throws { + @Test func incompleteConversionFromObjC() throws { struct TestStringAttributeOnly : AttributeScope { var testString: AttributeScopes.TestAttributes.TestStringAttribute // Missing TestBoolAttribute } @@ -1587,10 +1609,10 @@ E { var expected = AttributedString("Hel", attributes: AttributeContainer().testString("Courier")) expected += AttributedString("lo!") - XCTAssertEqual(converted, expected) + #expect(converted == expected) } - func testIncompleteConversionToObjC() throws { + @Test func incompleteConversionToObjC() throws { struct TestStringAttributeOnly : AttributeScope { var testString: AttributeScopes.TestAttributes.TestStringAttribute // Missing TestBoolAttribute } @@ -1600,10 +1622,10 @@ E { let converted = try NSAttributedString(attrStr, including: TestStringAttributeOnly.self) let attrs = converted.attributes(at: 0, effectiveRange: nil) - XCTAssertFalse(attrs.keys.contains(.testBool)) + #expect(!attrs.keys.contains(.testBool)) } - func testConversionNestedScope() throws { + @Test func conversionNestedScope() throws { struct SuperScope : AttributeScope { var subscope : SubScope var testString: AttributeScopes.TestAttributes.TestStringAttribute @@ -1622,10 +1644,10 @@ E { var expected = AttributedString("Hel", attributes: AttributeContainer().testString("Courier")) expected += AttributedString("lo!", attributes: AttributeContainer().testBool(true)) - XCTAssertEqual(converted, expected) + #expect(converted == expected) } - func testConversionAttributeContainers() throws { + @Test func conversionAttributeContainers() throws { let container = AttributeContainer.testInt(2).testDouble(3.1).testString("Hello") let dictionary = try Dictionary(container, including: \.test) @@ -1634,18 +1656,20 @@ E { .testDouble: 3.1, .testString: "Hello" ] - XCTAssertEqual(dictionary.keys, expected.keys) - XCTAssertEqual(dictionary[.testInt] as! Int, expected[.testInt] as! Int) - XCTAssertEqual(dictionary[.testDouble] as! Double, expected[.testDouble] as! Double) - XCTAssertEqual(dictionary[.testString] as! String, expected[.testString] as! String) + #expect(dictionary.keys == expected.keys) + #expect(dictionary[.testInt] as? Int == expected[.testInt] as? Int) + #expect(dictionary[.testDouble] as? Double == expected[.testDouble] as? Double) + #expect(dictionary[.testString] as? String == expected[.testString] as? String) let container2 = try AttributeContainer(dictionary, including: \.test) - XCTAssertEqual(container, container2) + #expect(container == container2) } - func testConversionFromInvalidObjectiveCValueTypes() throws { + @Test func conversionFromInvalidObjectiveCValueTypes() throws { let nsStr = NSAttributedString(string: "Hello", attributes: [.testInt : "I am not an Int"]) - XCTAssertThrowsError(try AttributedString(nsStr, including: AttributeScopes.TestAttributes.self)) + #expect(throws: (any Error).self) { + try AttributedString(nsStr, including: AttributeScopes.TestAttributes.self) + } struct ConvertibleAttribute: ObjectiveCConvertibleAttributedStringKey { struct Value : Hashable { @@ -1667,10 +1691,12 @@ E { } let nsStr2 = NSAttributedString(string: "Hello", attributes: [NSAttributedString.Key(ConvertibleAttribute.name) : 12345]) - XCTAssertThrowsError(try AttributedString(nsStr2, including: Scope.self)) + #expect(throws: (any Error).self) { + try AttributedString(nsStr2, including: Scope.self) + } } - func testConversionToUTF16() throws { + @Test func conversionToUTF16() throws { // Ensure that we're correctly using UTF16 offsets with NSAS and UTF8 offsets with AS without mixing the two let multiByteCharacters = ["\u{2029}", "\u{1D11E}", "\u{1D122}", "\u{1F91A}\u{1F3FB}"] @@ -1679,28 +1705,28 @@ E { let nsStr = NSAttributedString(string: str, attributes: [.testInt: 2]) let convertedAttrStr = try AttributedString(nsStr, including: AttributeScopes.TestAttributes.self) - XCTAssertEqual(str.utf8.count, convertedAttrStr._guts.runs.first!.length) - XCTAssertEqual(attrStr, convertedAttrStr) + #expect(str.utf8.count == convertedAttrStr._guts.runs.first!.length) + #expect(attrStr == convertedAttrStr) let convertedNSStr = try NSAttributedString(attrStr, including: AttributeScopes.TestAttributes.self) - XCTAssertEqual(nsStr, convertedNSStr) + #expect(nsStr == convertedNSStr) } } - func testConversionWithoutScope() throws { + @Test func conversionWithoutScope() throws { // Ensure simple conversion works (no errors when loading AppKit/UIKit/SwiftUI) let attrStr = AttributedString() let nsStr = NSAttributedString(attrStr) - XCTAssertEqual(nsStr, NSAttributedString()) + #expect(nsStr == NSAttributedString()) let attrStrReverse = AttributedString(nsStr) - XCTAssertEqual(attrStrReverse, attrStr) + #expect(attrStrReverse == attrStr) // Ensure foundation attributes are converted let attrStr2 = AttributedString("Hello", attributes: .init().link(URL(string: "http://apple.com")!)) let nsStr2 = NSAttributedString(attrStr2) - XCTAssertEqual(nsStr2, NSAttributedString(string: "Hello", attributes: [.link : URL(string: "http://apple.com")! as NSURL])) + #expect(nsStr2 == NSAttributedString(string: "Hello", attributes: [.link : URL(string: "http://apple.com")! as NSURL])) let attrStr2Reverse = AttributedString(nsStr2) - XCTAssertEqual(attrStr2Reverse, attrStr2) + #expect(attrStr2Reverse == attrStr2) // Ensure attributes that throw are dropped enum Attribute : ObjectiveCConvertibleAttributedStringKey { @@ -1727,13 +1753,11 @@ E { container[Attribute.self] = 3 let str = AttributedString("Hello", attributes: container) let result = try? NSAttributedString(str, attributeTable: Scope.attributeKeyTypes(), options: .dropThrowingAttributes) // The same call that the no-scope initializer will make - XCTAssertEqual(result, NSAttributedString(string: "Hello", attributes: [NSAttributedString.Key("TestInt") : 2])) + #expect(result == NSAttributedString(string: "Hello", attributes: [NSAttributedString.Key("TestInt") : 2])) } - func testConversionWithoutScope_Accessibility() throws { -#if !canImport(Accessibility) - throw XCTSkip("Unable to import the Accessibility framework") -#else + #if canImport(Accessibility) + @Test func conversionWithoutScope_Accessibility() throws { let attributedString = AttributedString("Hello", attributes: .init().accessibilityTextCustom(["ABC"])) let nsAttributedString = NSAttributedString(attributedString) #if os(macOS) @@ -1741,66 +1765,60 @@ E { #else let attribute = NSAttributedString.Key.accessibilityTextCustom #endif - XCTAssertEqual(nsAttributedString, NSAttributedString(string: "Hello", attributes: [attribute : ["ABC"]])) + #expect(nsAttributedString == NSAttributedString(string: "Hello", attributes: [attribute : ["ABC"]])) let attributedStringReverse = AttributedString(nsAttributedString) - XCTAssertEqual(attributedStringReverse, attributedString) -#endif + #expect(attributedStringReverse == attributedString) } + #endif - func testConversionWithoutScope_AppKit() throws { -#if !canImport(AppKit) - throw XCTSkip("Unable to import the AppKit framework") -#else + #if canImport(AppKit) + @Test func conversionWithoutScope_AppKit() throws { var container = AttributeContainer() container.appKit.kern = 2.3 let attributedString = AttributedString("Hello", attributes: container) let nsAttributedString = NSAttributedString(attributedString) - XCTAssertEqual(nsAttributedString, NSAttributedString(string: "Hello", attributes: [.kern : CGFloat(2.3)])) + #expect(nsAttributedString == NSAttributedString(string: "Hello", attributes: [.kern : CGFloat(2.3)])) let attributedStringReverse = AttributedString(nsAttributedString) - XCTAssertEqual(attributedStringReverse, attributedString) -#endif + #expect(attributedStringReverse == attributedString) } + #endif - func testConversionWithoutScope_UIKit() throws { -#if !canImport(UIKit) - throw XCTSkip("Unable to import the UIKit framework") -#else + #if canImport(UIKit) + @Test func conversionWithoutScope_UIKit() throws { var container = AttributeContainer() container.uiKit.kern = 2.3 let attributedString = AttributedString("Hello", attributes: container) let nsAttributedString = NSAttributedString(attributedString) - XCTAssertEqual(nsAttributedString, NSAttributedString(string: "Hello", attributes: [.kern : CGFloat(2.3)])) + #expect(nsAttributedString == NSAttributedString(string: "Hello", attributes: [.kern : CGFloat(2.3)])) let attributedStringReverse = AttributedString(nsAttributedString) - XCTAssertEqual(attributedStringReverse, attributedString) -#endif + #expect(attributedStringReverse == attributedString) } + #endif - func testConversionWithoutScope_SwiftUI() throws { -#if !canImport(SwiftUI) - throw XCTSkip("Unable to import the SwiftUI framework") -#else + #if canImport(SwiftUI) + @Test func conversionWithoutScope_SwiftUI() throws { var container = AttributeContainer() container.swiftUI.kern = 2.3 let attributedString = AttributedString("Hello", attributes: container) let nsAttributedString = NSAttributedString(attributedString) - XCTAssertEqual(nsAttributedString, NSAttributedString(string: "Hello", attributes: [.init("SwiftUI.Kern") : CGFloat(2.3)])) + #expect(nsAttributedString == NSAttributedString(string: "Hello", attributes: [.init("SwiftUI.Kern") : CGFloat(2.3)])) let attributedStringReverse = AttributedString(nsAttributedString) - XCTAssertEqual(attributedStringReverse, attributedString) -#endif + #expect(attributedStringReverse == attributedString) } + #endif - func testConversionCoalescing() throws { + @Test func conversionCoalescing() throws { let nsStr = NSMutableAttributedString("Hello, world") nsStr.setAttributes([.link : NSURL(string: "http://apple.com")!, .testInt : NSNumber(integerLiteral: 2)], range: NSRange(location: 0, length: 6)) nsStr.setAttributes([.testInt : NSNumber(integerLiteral: 2)], range: NSRange(location: 6, length: 6)) let attrStr = try AttributedString(nsStr, including: \.test) - XCTAssertEqual(attrStr.runs.count, 1) - XCTAssertEqual(attrStr.runs.first!.range, attrStr.startIndex ..< attrStr.endIndex) - XCTAssertEqual(attrStr.testInt, 2) - XCTAssertNil(attrStr.link) + #expect(attrStr.runs.count == 1) + #expect(attrStr.runs.first!.range == attrStr.startIndex ..< attrStr.endIndex) + #expect(attrStr.testInt == 2) + #expect(attrStr.link == nil) } - func testUnalignedConversion() throws { + @Test func unalignedConversion() throws { let tests: [(NSRange, Int)] = [ (NSRange(location: 0, length: 12), 1), (NSRange(location: 5, length: 2), 3), @@ -1816,7 +1834,7 @@ E { let nsAttributedString = NSMutableAttributedString("Test \u{1F3BA} Test") nsAttributedString.addAttribute(.testInt, value: NSNumber(1), range: test.0) let attrStr = try AttributedString(nsAttributedString, including: \.test) - XCTAssertEqual(attrStr.runs.count, test.1, "Replacement of range \(NSStringFromRange(test.0)) caused a run count of \(attrStr.runs.count)") + #expect(attrStr.runs.count == test.1, "Replacement of range \(NSStringFromRange(test.0)) caused a run count of \(attrStr.runs.count)") } } @@ -1824,14 +1842,14 @@ E { // MARK: - View Tests - func testCharViewIndexing_backwardsFromEndIndex() { + @Test func charViewIndexing_backwardsFromEndIndex() { let testString = AttributedString("abcdefghi") let testChars = testString.characters let testIndex = testChars.index(testChars.endIndex, offsetBy: -1) - XCTAssertEqual(testChars[testIndex], "i") + #expect(testChars[testIndex] == "i") } - func testAttrViewIndexing() { + @Test func attrViewIndexing() { var attrStr = AttributedString("A") attrStr += "B" attrStr += "C" @@ -1845,28 +1863,28 @@ E { i += 1 curIdx = attrStrRuns.index(after: curIdx) } - XCTAssertEqual(i, 1) - XCTAssertEqual(attrStrRuns.count, 1) + #expect(i == 1) + #expect(attrStrRuns.count == 1) } - func testUnicodeScalarsViewIndexing() { + @Test func unicodeScalarsViewIndexing() { let attrStr = AttributedString("Cafe\u{301}", attributes: AttributeContainer().testInt(1)) let unicode = attrStr.unicodeScalars - XCTAssertEqual(unicode[unicode.index(before: unicode.endIndex)], "\u{301}") - XCTAssertEqual(unicode[unicode.index(unicode.endIndex, offsetBy: -2)], "e") + #expect(unicode[unicode.index(before: unicode.endIndex)] == "\u{301}") + #expect(unicode[unicode.index(unicode.endIndex, offsetBy: -2)] == "e") } - func testCharacterSlicing() { + @Test func characterSlicing() { let a: AttributedString = "\u{1f1fa}\u{1f1f8}" // Regional indicators U & S let i = a.unicodeScalars.index(after: a.startIndex) let b = a.characters[..( _ a: some Sequence, _ b: some Sequence, - file: StaticString = #filePath, line: UInt = #line + sourceLocation: SourceLocation = #_sourceLocation ) { - XCTAssertTrue( + #expect( a.elementsEqual(b), "'\(Array(a))' does not equal '\(Array(b))'", - file: file, line: line) + sourceLocation: sourceLocation) } check(str, astr.characters) @@ -1923,28 +1941,28 @@ E { } } - func testUnicodeScalarsSlicing() { + @Test func unicodeScalarsSlicing() { let attrStr = AttributedString("Cafe\u{301}", attributes: AttributeContainer().testInt(1)) let range = attrStr.startIndex ..< attrStr.endIndex let substringScalars = attrStr[range].unicodeScalars let slicedScalars = attrStr.unicodeScalars[range] let expected: [UnicodeScalar] = ["C", "a", "f", "e", "\u{301}"] - XCTAssertEqual(substringScalars.count, expected.count) - XCTAssertEqual(slicedScalars.count, expected.count) + #expect(substringScalars.count == expected.count) + #expect(slicedScalars.count == expected.count) var indexA = substringScalars.startIndex var indexB = slicedScalars.startIndex var indexExpect = expected.startIndex while indexA != substringScalars.endIndex && indexB != slicedScalars.endIndex { - XCTAssertEqual(substringScalars[indexA], expected[indexExpect]) - XCTAssertEqual(slicedScalars[indexB], expected[indexExpect]) + #expect(substringScalars[indexA] == expected[indexExpect]) + #expect(slicedScalars[indexB] == expected[indexExpect]) indexA = substringScalars.index(after: indexA) indexB = slicedScalars.index(after: indexB) indexExpect = expected.index(after: indexExpect) } } - func testProtocolRunIndexing() { + @Test func protocolRunIndexing() { var str = AttributedString("Foo", attributes: .init().testInt(1)) str += AttributedString("Bar", attributes: .init().testInt(2)) str += AttributedString("Baz", attributes: .init().testInt(3)) @@ -1952,38 +1970,38 @@ E { let runIndices = str.runs.map(\.range.lowerBound) + [str.endIndex] for (i, index) in runIndices.enumerated().dropLast() { - XCTAssertEqual(str.index(afterRun: index), runIndices[i + 1]) + #expect(str.index(afterRun: index) == runIndices[i + 1]) } for (i, index) in runIndices.enumerated().reversed().dropLast() { - XCTAssertEqual(str.index(beforeRun: index), runIndices[i - 1]) + #expect(str.index(beforeRun: index) == runIndices[i - 1]) } for (i, a) in runIndices.enumerated() { for (j, b) in runIndices.enumerated() { - XCTAssertEqual(str.index(a, offsetByRuns: j - i), b) + #expect(str.index(a, offsetByRuns: j - i) == b) } } } // MARK: - Other Tests - func testInitWithSequence() { + @Test func initWithSequence() { let expected = AttributedString("Hello World", attributes: AttributeContainer().testInt(2)) let sequence: [Character] = ["H", "e", "l", "l", "o", " ", "W", "o", "r", "l", "d"] let container = AttributeContainer().testInt(2) let attrStr = AttributedString(sequence, attributes: container) - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) let attrStr2 = AttributedString(sequence, attributes: AttributeContainer().testInt(2)) - XCTAssertEqual(attrStr2, expected) + #expect(attrStr2 == expected) let attrStr3 = AttributedString(sequence, attributes: AttributeContainer().testInt(2)) - XCTAssertEqual(attrStr3, expected) + #expect(attrStr3 == expected) } - func testLongestEffectiveRangeOfAttribute() { + @Test func longestEffectiveRangeOfAttribute() { var str = AttributedString("Abc") str += AttributedString("def", attributes: AttributeContainer.testInt(2).testString("World")) str += AttributedString("ghi", attributes: AttributeContainer.testInt(2).testBool(true)) @@ -1994,27 +2012,27 @@ E { let expectedRange = str.characters.index(str.startIndex, offsetBy: 3) ..< str.characters.index(str.startIndex, offsetBy: 12) let (value, range) = str.runs[\.testInt][idx] - XCTAssertEqual(value, 2) - XCTAssertEqual(range, expectedRange) + #expect(value == 2) + #expect(range == expectedRange) } - func testAttributeContainer() { + @Test func attributeContainer() { var container = AttributeContainer().testBool(true).testInt(1) - XCTAssertEqual(container.testBool, true) - XCTAssertNil(container.testString) + #expect(container.testBool == true) + #expect(container.testString == nil) let attrString = AttributedString("Hello", attributes: container) for run in attrString.runs { - XCTAssertEqual("Hello", String(attrString.characters[run.range])) - XCTAssertEqual(run.testBool, true) - XCTAssertEqual(run.testInt, 1) + #expect("Hello" == String(attrString.characters[run.range])) + #expect(run.testBool == true) + #expect(run.testInt == 1) } container.testBool = nil - XCTAssertNil(container.testBool) + #expect(container.testBool == nil) } - func testAttributeContainerEquality() { + @Test func attributeContainerEquality() { let containerA = AttributeContainer().testInt(2).testString("test") let containerB = AttributeContainer().testInt(2).testString("test") let containerC = AttributeContainer().testInt(3).testString("test") @@ -2022,13 +2040,13 @@ E { var containerE = AttributeContainer() containerE.testInt = 4 - XCTAssertEqual(containerA, containerB) - XCTAssertNotEqual(containerB, containerC) - XCTAssertNotEqual(containerC, containerD) - XCTAssertEqual(containerD, containerE) + #expect(containerA == containerB) + #expect(containerB != containerC) + #expect(containerC != containerD) + #expect(containerD == containerE) } - func testAttributeContainerSetOnSubstring() { + @Test func attributeContainerSetOnSubstring() { let container = AttributeContainer().testBool(true).testInt(1) var attrString = AttributedString("Hello world", attributes: container) @@ -2038,19 +2056,19 @@ E { let runs = attrString.runs let run = runs[ runs.startIndex ] - XCTAssertEqual(String(attrString.characters[run.range]), "Hell") - XCTAssertEqual(run.testString, "yellow") + #expect(String(attrString.characters[run.range]) == "Hell") + #expect(run.testString == "yellow") } - func testSlice() { + @Test func slice() { let attrStr = AttributedString("Hello World") let chars = attrStr.characters let start = chars.index(chars.startIndex, offsetBy: 6) let slice = attrStr[start ..< chars.index(start, offsetBy:5)] - XCTAssertEqual(AttributedString(slice), AttributedString("World")) + #expect(AttributedString(slice) == AttributedString("World")) } - func testCreateStringsFromCharactersWithUnicodeScalarIndexes() { + @Test func createStringsFromCharactersWithUnicodeScalarIndexes() { var attrStr = AttributedString("Caf", attributes: AttributeContainer().testString("a")) attrStr += AttributedString("e", attributes: AttributeContainer().testString("b")) attrStr += AttributedString("\u{301}", attributes: AttributeContainer().testString("c")) @@ -2059,14 +2077,14 @@ E { let strs1 = attrStr.runs.map { String(String.UnicodeScalarView(attrStr.unicodeScalars[$0.range])) } - XCTAssertEqual(strs1, ["Caf", "e", "\u{301}"]) + #expect(strs1 == ["Caf", "e", "\u{301}"]) // The characters view rounds indices down to the nearest character boundary. let strs2 = attrStr.runs.map { String(attrStr.characters[$0.range]) } - XCTAssertEqual(strs2, ["Caf", "", "e\u{301}"]) + #expect(strs2 == ["Caf", "", "e\u{301}"]) } - func testSettingAttributeOnSlice() throws { + @Test func settingAttributeOnSlice() throws { var attrString = AttributedString("This is a string.") var range = attrString.startIndex ..< attrString.characters.index(attrString.startIndex, offsetBy: 1) var myInt = 1 @@ -2082,7 +2100,7 @@ E { myInt = 8 for (attribute, _) in attrString.runs[\.testInt] { if let value = attribute { - XCTAssertEqual(myInt, value) + #expect(myInt == value) myInt += 1 } } @@ -2091,25 +2109,25 @@ E { newAttrString.testInt = nil for (attribute, _) in newAttrString.runs[\.testInt] { - XCTAssertEqual(attribute, nil) + #expect(attribute == nil) } let startIndex = attrString.startIndex attrString.characters[startIndex] = "D" - XCTAssertEqual(attrString.characters[startIndex], "D") + #expect(attrString.characters[startIndex] == "D") } - func testExpressibleByStringLiteral() { + @Test func expressibleByStringLiteral() { let variable : AttributedString = "Test" - XCTAssertEqual(variable, AttributedString("Test")) + #expect(variable == AttributedString("Test")) func takesAttrStr(_ str: AttributedString) { - XCTAssertEqual(str, AttributedString("Test")) + #expect(str == AttributedString("Test")) } takesAttrStr("Test") } - func testHashing() { + @Test func hashing() { let attrStr = AttributedString("Hello, world.", attributes: .init().testInt(2).testBool(false)) let attrStr2 = AttributedString("Hello, world.", attributes: .init().testInt(2).testBool(false)) @@ -2119,12 +2137,12 @@ E { dictionary[attrStr2] = 456 - XCTAssertEqual(attrStr, attrStr2) - XCTAssertEqual(dictionary[attrStr], 456) - XCTAssertEqual(dictionary[attrStr2], 456) + #expect(attrStr == attrStr2) + #expect(dictionary[attrStr] == 456) + #expect(dictionary[attrStr2] == 456) } - func testHashingSubstring() { + @Test func hashingSubstring() { let a: AttributedString = "aXa" let b: AttributedString = "bXb" @@ -2137,16 +2155,16 @@ E { let substrA = a[i1 ..< i2] let substrB = b[j1 ..< j2] - XCTAssertEqual(substrA, substrB) + #expect(substrA == substrB) var hasherA = Hasher() hasherA.combine(substrA) var hasherB = Hasher() hasherB.combine(substrB) - XCTAssertEqual(hasherA.finalize(), hasherB.finalize()) + #expect(hasherA.finalize() == hasherB.finalize()) } - func testHashingContainer() { + @Test func hashingContainer() { let containerA = AttributeContainer.testInt(2).testBool(false) let containerB = AttributeContainer.testInt(2).testBool(false) @@ -2156,172 +2174,172 @@ E { dictionary[containerB] = 456 - XCTAssertEqual(containerA, containerB) - XCTAssertEqual(dictionary[containerA], 456) - XCTAssertEqual(dictionary[containerB], 456) + #expect(containerA == containerB) + #expect(dictionary[containerA] == 456) + #expect(dictionary[containerB] == 456) } - func testUTF16String() { + @Test func utf16String() { let multiByteCharacters = ["\u{2029}", "\u{1D11E}", "\u{1D122}", "\u{1F91A}\u{1F3FB}"] for str in multiByteCharacters { var attrStr = AttributedString("A" + str) attrStr += AttributedString("B", attributes: .init().testInt(2)) attrStr += AttributedString("C", attributes: .init().testInt(3)) - XCTAssertTrue(attrStr == attrStr) - XCTAssertTrue(attrStr.runs == attrStr.runs) + #expect(attrStr == attrStr) + #expect(attrStr.runs == attrStr.runs) } } - func testPlusOperators() { + @Test func plusOperators() { let ab = AttributedString("a") + AttributedString("b") - XCTAssertEqual(ab, AttributedString("ab")) + #expect(ab == AttributedString("ab")) let ab_sub = AttributedString("a") + ab[ab.characters.index(before: ab.endIndex) ..< ab.endIndex] - XCTAssertEqual(ab_sub, ab) + #expect(ab_sub == ab) let ab_lit = AttributedString("a") + "b" - XCTAssertEqual(ab_lit, ab) + #expect(ab_lit == ab) var abc = ab abc += AttributedString("c") - XCTAssertEqual(abc, AttributedString("abc")) + #expect(abc == AttributedString("abc")) var abc_sub = ab abc_sub += abc[abc.characters.index(before: abc.endIndex) ..< abc.endIndex] - XCTAssertEqual(abc_sub, abc) + #expect(abc_sub == abc) var abc_lit = ab abc_lit += "c" - XCTAssertEqual(abc_lit, abc) + #expect(abc_lit == abc) } - func testSearch() { + @Test func search() throws { let testString = AttributedString("abcdefghi") - XCTAssertNil(testString.range(of: "baba")) + #expect(testString.range(of: "baba") == nil) - let abc = testString.range(of: "abc")! - XCTAssertEqual(abc.lowerBound, testString.startIndex) - XCTAssertEqual(String(testString[abc].characters), "abc") + let abc = try #require(testString.range(of: "abc")) + #expect(abc.lowerBound == testString.startIndex) + #expect(String(testString[abc].characters) == "abc") - let def = testString.range(of: "def")! - XCTAssertEqual(def.lowerBound, testString.index(testString.startIndex, offsetByCharacters: 3)) - XCTAssertEqual(String(testString[def].characters), "def") + let def = try #require(testString.range(of: "def")) + #expect(def.lowerBound == testString.index(testString.startIndex, offsetByCharacters: 3)) + #expect(String(testString[def].characters) == "def") - let ghi = testString.range(of: "ghi")! - XCTAssertEqual(ghi.lowerBound, testString.index(testString.startIndex, offsetByCharacters: 6)) - XCTAssertEqual(String(testString[ghi].characters), "ghi") + let ghi = try #require(testString.range(of: "ghi")) + #expect(ghi.lowerBound == testString.index(testString.startIndex, offsetByCharacters: 6)) + #expect(String(testString[ghi].characters) == "ghi") - XCTAssertNil(testString.range(of: "ghij")) + #expect(testString.range(of: "ghij") == nil) let substring = testString[testString.index(afterCharacter: testString.startIndex)..(nsRange, in: str) - XCTAssertNotNil(strRange) - XCTAssertEqual(strRange, str.unicodeScalars.index(str.startIndex, offsetBy: 8) ..< str.unicodeScalars.index(str.startIndex, offsetBy: 9)) - XCTAssertEqual(str[strRange!], "e") + #expect(strRange != nil) + #expect(strRange == str.unicodeScalars.index(str.startIndex, offsetBy: 8) ..< str.unicodeScalars.index(str.startIndex, offsetBy: 9)) + #expect(str[strRange!] == "e") var attrStrRange = Range(nsRange, in: attrStr) - XCTAssertNotNil(attrStrRange) - XCTAssertEqual(attrStrRange, attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 8) ..< attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 9)) - XCTAssertEqual(AttributedString(attrStr[attrStrRange!]), AttributedString("e")) + #expect(attrStrRange != nil) + #expect(attrStrRange == attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 8) ..< attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 9)) + #expect(AttributedString(attrStr[attrStrRange!]) == AttributedString("e")) attrStrRange = Range(strRange!, in: attrStr) - XCTAssertNotNil(attrStrRange) - XCTAssertEqual(attrStrRange, attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 8) ..< attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 9)) - XCTAssertEqual(AttributedString(attrStr[attrStrRange!]), AttributedString("e")) + #expect(attrStrRange != nil) + #expect(attrStrRange == attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 8) ..< attrStr.unicodeScalars.index(attrStr.startIndex, offsetBy: 9)) + #expect(AttributedString(attrStr[attrStrRange!]) == AttributedString("e")) - XCTAssertEqual(NSRange(strRange!, in: str), nsRange) - XCTAssertEqual(NSRange(attrStrRange!, in: attrStr), nsRange) - XCTAssertEqual(Range(attrStrRange!, in: str), strRange!) + #expect(NSRange(strRange!, in: str) == nsRange) + #expect(NSRange(attrStrRange!, in: attrStr) == nsRange) + #expect(Range(attrStrRange!, in: str) == strRange!) } do { @@ -2355,43 +2373,43 @@ E { let nsRange = NSRange(location: 5, length: 3) // The whole first U+1F3BA and the leading surrogate character of the second U+1F3BA let strRange = Range(nsRange, in: str) - XCTAssertNotNil(strRange) - XCTAssertEqual(str[strRange!], "\u{1F3BA}") + #expect(strRange != nil) + #expect(str[strRange!] == "\u{1F3BA}") var attrStrRange = Range(nsRange, in: attrStr) - XCTAssertNotNil(attrStrRange) - XCTAssertEqual(AttributedString(attrStr[attrStrRange!]), AttributedString("\u{1F3BA}")) + #expect(attrStrRange != nil) + #expect(AttributedString(attrStr[attrStrRange!]) == AttributedString("\u{1F3BA}")) attrStrRange = Range(strRange!, in: attrStr) - XCTAssertNotNil(attrStrRange) - XCTAssertEqual(AttributedString(attrStr[attrStrRange!]), AttributedString("\u{1F3BA}")) + #expect(attrStrRange != nil) + #expect(AttributedString(attrStr[attrStrRange!]) == AttributedString("\u{1F3BA}")) - XCTAssertEqual(NSRange(strRange!, in: str), nsRange) - XCTAssertEqual(NSRange(attrStrRange!, in: attrStr), nsRange) - XCTAssertEqual(Range(attrStrRange!, in: str), strRange!) + #expect(NSRange(strRange!, in: str) == nsRange) + #expect(NSRange(attrStrRange!, in: attrStr) == nsRange) + #expect(Range(attrStrRange!, in: str) == strRange!) } } - func testNSRangeConversionOnSlice() throws { + @Test func nsRangeConversionOnSlice() throws { let str = AttributedString("012345") let slice = str[str.index(str.startIndex, offsetByCharacters: 3) ..< str.endIndex] let nsRange = NSRange(location: 0, length: 2) - let range = try XCTUnwrap(Range(nsRange, in: slice)) - XCTAssertEqual(String(slice[range].characters), "34") + let range = try #require(Range(nsRange, in: slice)) + #expect(String(slice[range].characters) == "34") } #endif // FOUNDATION_FRAMEWORK - func testOOBRangeConversion() { + @Test func oobRangeConversion() { let attrStr = AttributedString("") let str = "Hello" let range = str.index(before: str.endIndex) ..< str.endIndex - XCTAssertNil(Range(range, in: attrStr)) + #expect(Range(range, in: attrStr) == nil) } #if FOUNDATION_FRAMEWORK // TODO: Support scope-specific AttributedString initialization in FoundationPreview - func testScopedCopy() { + @Test func scopedCopy() { var str = AttributedString("A") str += AttributedString("B", attributes: .init().testInt(2)) str += AttributedString("C", attributes: .init().link(URL(string: "http://apple.com")!)) @@ -2401,60 +2419,61 @@ E { let foundation: AttributeScopes.FoundationAttributes let test: AttributeScopes.TestAttributes } - XCTAssertEqual(AttributedString(str, including: FoundationAndTest.self), str) + #expect(AttributedString(str, including: FoundationAndTest.self) == str) struct None : AttributeScope { } - XCTAssertEqual(AttributedString(str, including: None.self), AttributedString("ABCD")) + #expect(AttributedString(str, including: None.self) == AttributedString("ABCD")) var expected = AttributedString("AB") expected += AttributedString("CD", attributes: .init().link(URL(string: "http://apple.com")!)) - XCTAssertEqual(AttributedString(str, including: \.foundation), expected) + #expect(AttributedString(str, including: \.foundation) == expected) expected = AttributedString("A") expected += AttributedString("B", attributes: .init().testInt(2)) expected += "C" expected += AttributedString("D", attributes: .init().testInt(3)) - XCTAssertEqual(AttributedString(str, including: \.test), expected) + #expect(AttributedString(str, including: \.test) == expected) let range = str.index(afterCharacter: str.startIndex) ..< str.index(beforeCharacter: str.endIndex) expected = AttributedString("B", attributes: .init().testInt(2)) + "C" - XCTAssertEqual(AttributedString(str[range], including: \.test), expected) + #expect(AttributedString(str[range], including: \.test) == expected) expected = "B" + AttributedString("C", attributes: .init().link(URL(string: "http://apple.com")!)) - XCTAssertEqual(AttributedString(str[range], including: \.foundation), expected) + #expect(AttributedString(str[range], including: \.foundation) == expected) - XCTAssertEqual(AttributedString(str[range], including: None.self), AttributedString("BC")) + #expect(AttributedString(str[range], including: None.self) == AttributedString("BC")) } - func testScopeIterationAPI() { + @Test func scopeIterationAPI() { struct TestScope : AttributeScope { let testInt: AttributeScopes.TestAttributes.TestIntAttribute let testBool: AttributeScopes.TestAttributes.TestBoolAttribute } let testNames = TestScope.attributeKeys.map { $0.name }.sorted() - XCTAssertEqual(testNames, [AttributeScopes.TestAttributes.TestBoolAttribute.name, AttributeScopes.TestAttributes.TestIntAttribute.name].sorted()) + #expect(testNames == [AttributeScopes.TestAttributes.TestBoolAttribute.name, AttributeScopes.TestAttributes.TestIntAttribute.name].sorted()) struct EmptyScope : AttributeScope { } - var emptyIterator = EmptyScope.attributeKeys.makeIterator() - XCTAssertNil(emptyIterator.next()) + for key in EmptyScope.attributeKeys { + Issue.record("Empty scope should not have produced key \(key)") + } } #endif // FOUNDATION_FRAMEWORK - func testAssignDifferentSubstring() { + @Test func assignDifferentSubstring() { var attrStr1 = AttributedString("ABCDE") let attrStr2 = AttributedString("XYZ") attrStr1[ attrStr1.range(of: "BCD")! ] = attrStr2[ attrStr2.range(of: "X")! ] - XCTAssertEqual(attrStr1, "AXE") + #expect(attrStr1 == "AXE") } - func testCOWDuringSubstringMutation() { + @Test func cowDuringSubstringMutation() { func frobnicate(_ sub: inout AttributedSubstring) { var new = sub new.testInt = 2 @@ -2465,31 +2484,31 @@ E { frobnicate(&attrStr[ attrStr.range(of: "BCD")! ]) let expected = AttributedString("A") + AttributedString("BCD", attributes: .init().testInt(2).testString("Hello")) + AttributedString("E") - XCTAssertEqual(attrStr, expected) + #expect(attrStr == expected) } -#if false // This causes an intentional fatalError(), which we can't test for yet, so unfortunately this test can't be enabled. - func testReassignmentDuringMutation() { - func frobnicate(_ sub: inout AttributedSubstring) { - let other = AttributedString("XYZ") - sub = other[ other.range(of: "X")! ] + #if FOUNDATION_EXIT_TESTS + @Test func reassignmentDuringMutation() async { + await #expect(processExitsWith: .failure) { + func frobnicate(_ sub: inout AttributedSubstring) { + let other = AttributedString("XYZ") + sub = other[ other.range(of: "X")! ] + } + var attrStr = AttributedString("ABCDE") + frobnicate(&attrStr[ attrStr.range(of: "BCD")! ]) } - var attrStr = AttributedString("ABCDE") - frobnicate(&attrStr[ attrStr.range(of: "BCD")! ]) - - XCTAssertEqual(attrStr, "AXE") } -#endif + #endif - func testAssignDifferentCharacterView() { + @Test func assignDifferentCharacterView() { var attrStr1 = AttributedString("ABC", attributes: .init().testInt(1)) + AttributedString("DE", attributes: .init().testInt(3)) let attrStr2 = AttributedString("XYZ", attributes: .init().testInt(2)) attrStr1.characters = attrStr2.characters - XCTAssertEqual(attrStr1, AttributedString("XYZ", attributes: .init().testInt(1))) + #expect(attrStr1 == AttributedString("XYZ", attributes: .init().testInt(1))) } - func testCOWDuringCharactersMutation() { + @Test func cowDuringCharactersMutation() { func frobnicate(_ chars: inout AttributedString.CharacterView) { var new = chars new.replaceSubrange(chars.startIndex ..< chars.endIndex, with: "XYZ") @@ -2498,18 +2517,18 @@ E { var attrStr = AttributedString("ABCDE", attributes: .init().testInt(1)) frobnicate(&attrStr.characters) - XCTAssertEqual(attrStr, AttributedString("XYZ", attributes: .init().testInt(1))) + #expect(attrStr == AttributedString("XYZ", attributes: .init().testInt(1))) } - func testAssignDifferentUnicodeScalarView() { + @Test func assignDifferentUnicodeScalarView() { var attrStr1 = AttributedString("ABC", attributes: .init().testInt(1)) + AttributedString("DE", attributes: .init().testInt(3)) let attrStr2 = AttributedString("XYZ", attributes: .init().testInt(2)) attrStr1.unicodeScalars = attrStr2.unicodeScalars - XCTAssertEqual(attrStr1, AttributedString("XYZ", attributes: .init().testInt(1))) + #expect(attrStr1 == AttributedString("XYZ", attributes: .init().testInt(1))) } - func testCOWDuringUnicodeScalarsMutation() { + @Test func cowDuringUnicodeScalarsMutation() { func frobnicate(_ chars: inout AttributedString.CharacterView) { var new = chars new.replaceSubrange(chars.startIndex ..< chars.endIndex, with: "XYZ") @@ -2518,10 +2537,10 @@ E { var attrStr = AttributedString("ABCDE", attributes: .init().testInt(1)) frobnicate(&attrStr.characters) - XCTAssertEqual(attrStr, AttributedString("XYZ", attributes: .init().testInt(1))) + #expect(attrStr == AttributedString("XYZ", attributes: .init().testInt(1))) } - func testUTF8View() { + @Test func utf88View() { let testStrings = [ "Hello, world", "🎺😄abc🎶def", @@ -2531,26 +2550,26 @@ E { for string in testStrings { let attrStr = AttributedString(string) - XCTAssertEqual(attrStr.utf8.count, string.utf8.count, "Counts are not equal for string \(string)") - XCTAssertTrue(attrStr.utf8.elementsEqual(string.utf8), "Full elements are not equal for string \(string)") + #expect(attrStr.utf8.count == string.utf8.count, "Counts are not equal for string \(string)") + #expect(attrStr.utf8.elementsEqual(string.utf8), "Full elements are not equal for string \(string)") for offset in 0 ..< string.utf8.count { let idxInString = string.utf8.index(string.startIndex, offsetBy: offset) let idxInAttrStr = attrStr.utf8.index(attrStr.startIndex, offsetBy: offset) - XCTAssertEqual( - string.utf8.distance(from: string.startIndex, to: idxInString), + #expect( + string.utf8.distance(from: string.startIndex, to: idxInString) == attrStr.utf8.distance(from: attrStr.startIndex, to: idxInAttrStr), "Offsets to \(idxInString) are not equal for string \(string)" ) - XCTAssertEqual(string.utf8[idxInString], attrStr.utf8[idxInAttrStr], "Elements at offset \(offset) are not equal for string \(string)") - XCTAssertTrue(string.utf8[.. Date: Tue, 10 Jun 2025 14:41:55 -0700 Subject: [PATCH 03/14] Add a proposal for the definition of a UTCClock (#1342) * Add a proposal for the definition of a UTCClock * Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> * Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> * Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> * Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> * Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> * Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> * Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> * Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> * Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> * Update Proposals/NNNN-UTCClock.md Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --------- Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/NNNN-UTCClock.md | 113 +++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 Proposals/NNNN-UTCClock.md diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/NNNN-UTCClock.md new file mode 100644 index 000000000..b98ec8182 --- /dev/null +++ b/Proposals/NNNN-UTCClock.md @@ -0,0 +1,113 @@ +# UTCClock and Epochs + +* Proposal: [SF-0027](0027-UTCClock.md) +* Authors: [Philippe Hausler](https://github.com/phausler) +* Review Manager: [Tina L](https://github.com/itingliu) +* Status: Review: Jun 10...Jun 17, 2025 +* Implementation: Pending +* Review: [Pitch](https://forums.swift.org/t/pitch-utcclock/78018) + + ## Introduction + +[The proposal for Clock, Instant and Duration](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0329-clock-instant-duration.md) was left with a future direction to address feedback for the need of clocks based upon the time measured by interacting with the user displayed clock, otherwise known as the "wall clock". + +This proposal introduces a new clock type for interacting with the user displayed clock, transacts in instants that are representations of offsets from an epoch and defined by the advancement from a UTC time. + +## Motivation + +Clocks in general can express many different mechanisms for time. That time can be strictly increasing, increasing but paused when the computer is not active, or in a clock in the traditional non-computing sense that one schedules according to a given time of day. The latter one must have some sort of mechanism to interact with calendarical and localized calculations. + +All three of the aforementioned clocks all have a concept of a starting point of reference. This is not a distinct requirement for creating a clock, but all three share this additional convention of a property. + +## Proposed solution and example + +In short, a new clock will be added: `UTCClock`. This clock will have its `Instant` type defined as `Date`. There will also be affordances added for calculations that account for the edge cases of [leap seconds](https://en.wikipedia.org/wiki/Leap_second) (which currently `Date` on its own does not currently offer any sort of mechanism either on itself or via `Calendar`). `Date` has facilities for expressing a starting point from an epoch, however that mechanism is not shared to `ContinuousClock.Instant` or `SuspendingClock.Instant`. All three types will have an added new static property for fetching the `epoch` - and it is suggested that any adopters of `InstantProtocol` should add a new property to their types to match this convention where it fits. + +Usage of this `UTCClock` can be illustrated by awaiting to perform a task at a given time of day. This has a number of interesting wrinkles that the `SuspendingClock` and `ContinousClock` wouldn't be able to handle. Example cases include where the deadline might be beyond a daylight savings time. Since `Date` directly interacts with `Calendar` it then ensures appropriate handling of these edges and is able to respect the user's settings and localization. + +```swift +let calendar = Calendar.current +var when = calendar.dateComponents([.day, .month, .year], from: .now) +when.day = when.day.map { $0 + 1 } +when.hour = 8 + +if let tomorrowMorning8AM = calendar.date(from: when) { + try await UTCClock().sleep(until: tomorrowMorning8AM) + playAlarmSound() +} +``` + +This can be used not only for alarms, but also scheduled maintenance routines or other such chronological tasks. The applications for which span from mobile to desktop to server environments and have a wide but specific set of use cases. It is worth noting that this new type should not be viewed as a replacement since those others have key functionality for representing behavior where the concept of time would be inappropriate to be non-monotonic. + + +## Detailed design + +These additions can be broken down into three categories; the `UTCClock` definition, the conformance of `Date` to `InstantProtocol`, and the extensions for vending epochs. + +The structure of the `UTCClock` is trivially sendable since it houses no specific state and has the defined typealias of its `Instant` as `Date`. The minimum feasible resolution of `Date` is 1 nanosecond (however that may vary from platform to platform where Foundation is implemented). + +```swift +@available(FoundationPreview 6.2, *) +public struct UTCClock: Sendable { + public typealias Instant = Date + public init() +} + +@available(FoundationPreview 6.2, *) +extension UTCClock: Clock { + public func sleep(until deadline: Date, tolerance: Duration? = nil) async throws + public var now: Date { get } + public var minimumResolution: Duration { get } +} +``` + +The extension of `Date` conforms it to `InstantProtocol` and adds one addition "near miss" of the protocol as an additional function that in practice feels like a default parameter. This `duration(to:includingLeapSeconds:)` function provides the calculation of the duration from one point in time to another and calculates if the span between the two points includes a leap second or not. This calculation can be used for historical astronomical data since the irregularity of the rotation causes variation in the observed solar time. Those points are historically fixed and are a known series of events at specific dates (in UTC)[^utclist]. + +```swift +@available(FoundationPreview 6.2, *) +extension Date: InstantProtocol { + public func advanced(by duration: Duration) -> Date + public func duration(to other: Date) -> Duration +} + +@available(FoundationPreview 6.2, *) +extension Date { + public static func leapSeconds(from start: Date, to end: Date) -> Duration +} +``` + +Usage of the `duration(to:)` and `leapSeconds(from:to:)` works as follows to calculate the total number of leap seconds: + +```swift +let start = Calendar.current.date(from: DateComponents(timeZone: .gmt, year: 1971, month: 1, day: 1))! +let end = Calendar.current.date(from: DateComponents(timeZone: .gmt, year: 2017, month: 1, day: 1))! +let leaps = Date.leapSeconds(from: start, to: end) +print(leaps) // prints 27.0 seconds +print(start.duration(to: end) + leaps) // prints 1451692827.0 seconds +``` + +It is worth noting that the usages of leap seconds for a given range is not a common use in most every-day computing; this is intended for special cases where data-sets or historical leap second including durations are strictly needed. The general usages should only require the normal `duration(to:)` api without adding any additional values. Documentation of this function will reflect that more "niche" use case. + +An extension to `UTCClock` will be made in `Foundation` for exposing an `systemEpoch` similarly to the properties proposed for the `SuspendingClock` and `ContinousClock`. This epoch will be defined as the `Date(timeIntervalSinceReferenceDate: 0)` which is Jan 1 2001. + +```swift + +@available(FoundationPreview 6.2, *) +extension UTCClock { + public static var systemEpoch: Date { get } +} +``` + +## Impact on existing code + +This is a purely additive set of changes. + +## Alternatives considered + +It was considered to add a protocol joining the epochs as a "EpochInstant" but that offers no real algorithmic advantage worth introducing a new protocol. Specialization of functions should likely just use where clauses to the specific instant or clock types. + +It was considered to add a new `Instant` type instead of depending on `Date` however this was rejected since it would mean re-implementing a vast swath of the supporting APIs for `Calendar` and such. The advantage of this is minimal and does not counteract the additional API complexity. + +It was considered to add a near-miss overload for `duration(to:)` that had an additional parameter of `includingLeapSeconds` this was dismissed because it was considered to be too confusing and may lead to bugs where that data was not intended to be used. + +[^utclist] If there are any updates to the list that will be considered a data table update and require a change to Foundation. \ No newline at end of file From 7037e2543080ba4cf4af04cae6c00fa49845db6b Mon Sep 17 00:00:00 2001 From: Tina L <49205802+itingliu@users.noreply.github.com> Date: Tue, 10 Jun 2025 17:19:34 -0700 Subject: [PATCH 04/14] Update and rename NNNN-UTCClock.md to 0027-UTCClock.md (#1346) --- Proposals/{NNNN-UTCClock.md => 0027-UTCClock.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename Proposals/{NNNN-UTCClock.md => 0027-UTCClock.md} (98%) diff --git a/Proposals/NNNN-UTCClock.md b/Proposals/0027-UTCClock.md similarity index 98% rename from Proposals/NNNN-UTCClock.md rename to Proposals/0027-UTCClock.md index b98ec8182..98d348ba9 100644 --- a/Proposals/NNNN-UTCClock.md +++ b/Proposals/0027-UTCClock.md @@ -4,7 +4,7 @@ * Authors: [Philippe Hausler](https://github.com/phausler) * Review Manager: [Tina L](https://github.com/itingliu) * Status: Review: Jun 10...Jun 17, 2025 -* Implementation: Pending +* Implementation: [Pull request](https://github.com/swiftlang/swift-foundation/pull/1344) * Review: [Pitch](https://forums.swift.org/t/pitch-utcclock/78018) ## Introduction @@ -110,4 +110,4 @@ It was considered to add a new `Instant` type instead of depending on `Date` how It was considered to add a near-miss overload for `duration(to:)` that had an additional parameter of `includingLeapSeconds` this was dismissed because it was considered to be too confusing and may lead to bugs where that data was not intended to be used. -[^utclist] If there are any updates to the list that will be considered a data table update and require a change to Foundation. \ No newline at end of file +[^utclist] If there are any updates to the list that will be considered a data table update and require a change to Foundation. From 77f4c3abb2ebafa934c78bed3768e2070a921080 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 12 Jun 2025 02:25:05 +0900 Subject: [PATCH 05/14] [wasm] Fix WASI build by using `time_t` for tv_sec (#1348) `time_t` is defined as `Int64` in wasi-libc, not `Int`. --- .../FoundationEssentials/FileManager/FileManager+Files.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift index 51a9bd750..0b3b4e4f3 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift @@ -965,7 +965,7 @@ extension _FileManagerImpl { if let date = attributes[.modificationDate] as? Date { let (isecs, fsecs) = modf(date.timeIntervalSince1970) - if let tv_sec = Int(exactly: isecs), + if let tv_sec = time_t(exactly: isecs), let tv_nsec = Int(exactly: round(fsecs * 1000000000.0)) { var timespecs = (timespec(), timespec()) timespecs.0.tv_sec = tv_sec From 33cae0421e5d4f3c73a9ea74322724f12739ecf8 Mon Sep 17 00:00:00 2001 From: Tina L <49205802+itingliu@users.noreply.github.com> Date: Wed, 11 Jun 2025 13:11:26 -0700 Subject: [PATCH 06/14] [Proposal] Category support for Locale.Region (#1247) * Add a proposal for category support in Locale.Region * Add Locale.Region.Category.grouping * Update 'grouping' discussion * Update var category type. Conform Category to CustomDebugStringConvertible * Update implementation link * Rename NNNN-locale-region-category.md to 0028-locale-region-category.md * Apply review manager's suggestion --- Proposals/0028-locale-region-category.md | 198 +++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 Proposals/0028-locale-region-category.md diff --git a/Proposals/0028-locale-region-category.md b/Proposals/0028-locale-region-category.md new file mode 100644 index 000000000..791e8d944 --- /dev/null +++ b/Proposals/0028-locale-region-category.md @@ -0,0 +1,198 @@ +# `Locale.Region.Category` + +* Proposal: [SF-0028](0028-locale-region-category.md) +* Authors: [Tina Liu](https://github.com/itingliu) +* Review Manager: [Jeremy Schonfeld](https://github.com/jmschonfeld) +* Implementation: [https://github.com/swiftlang/swift-foundation/pull/1253]() +* Status: **Review: Jun 11, 2025...Jun 18, 2025** +* Reviews: [Pitch](https://forums.swift.org/t/pitch-category-support-for-locale-region/79240) + +## Introduction + +Currently, `Locale.Region` offers a few functionalities to query information about a region: + +```swift +extension Locale.Region { + /// An array of regions defined by ISO. + public static var isoRegions: [Locale.Region] + + /// The region that contains this region, if any. + public var containingRegion: Locale.Region? { get } + + /// The continent that contains this region, if any. + public var continent: Locale.Region? { get } + + /// An array of all the sub-regions of the region. + public var subRegions: [Locale.Region] { get } +} +``` + +Here are some examples of how you can use it: + +```swift +let argentina = Locale.Region.argentina + +_ = argentina.continent // "019" (Americas) +_ = argentina.containingRegion // "005" (South America) +``` + +We'd like to propose extending `Locale.Region` to support grouping the return result by types, such as whether it's a territory or continent. + +One use case for this is for UI applications to display supported regions or offer UI to select a region, similar to the Language & Region system settings on mac and iOS. Instead of showing all supported regions returned by `Locale.Region.isoRegions` as a flat list, clients will be able to have a hierarchical list that groups the regions by types such as continents. + +## Proposed solution and example + +We propose adding `struct Locale.Region.Category` to represent different categories of `Locale.Region`, and companion methods to return results matching the specified category. There are also a few existing API that provides information about the region. We propose adding `subcontinent` to complement the existing `.continent` property. + +You can use it to get regions of specific categories: + +```swift +let argentina = Locale.Region.argentina +_ = argentina.category // .territory +_ = argentina.subcontinent // "005" (South America) + +let americas = Locale.Region("019") +_ = americas.category // .continent +_ = americas.subRegions(ofCategory: .subcontinent) // All subcontinents in Americas: ["005", "013", "021", "029"] (South America, Central America, Northern America, Caribbean) +_ = americas.subRegions(ofCategory: .territory) // All territories in Americas + +_ = Locale.Region.isoRegions(ofCategory: .continent) // All continents: ["002", "009", "019", "142", "150"] (Africa, Oceania, Americas, Asia, Europe) +``` + + +## Detailed design + +```swift +@available(FoundationPreview 6.2, *) +extension Locale.Region { + + /// Categories of a region. See https://www.unicode.org/reports/tr35/tr35-35/tr35-info.html#Territory_Data + public struct Category: Codable, Sendable, Hashable, CustomDebugStringConvertible { + /// Category representing the whole world. + public static let world: Category + + /// Category representing a continent, regions contained directly by world. + public static let continent: Category + + /// Category representing a sub-continent, regions contained directly by a continent. + public static let subcontinent: Category + + /// Category representing a territory. + public static let territory: Category + + /// Category representing a grouping, regions that has a well defined membership. + public static let grouping: Category + } + + /// An array of regions matching the specified categories. + public static func isoRegions(ofCategory category: Category) -> [Locale.Region] + + /// The category of the region, if any. + public var category: Category? { get } + + /// An array of the sub-regions, matching the specified category of the region. + /// If `category` is higher in the hierarchy than `self`, returns an empty array. + public func subRegions(ofCategory category: Category) -> [Locale.Region] + + /// The subcontinent that contains this region, if any. + public var subcontinent: Locale.Region? +} +``` + +### `Locale.Region.Category` + +This type represents the territory containment levels as defined in [Unicode LDML #35](https://www.unicode.org/reports/tr35/tr35-35/tr35-info.html#Territory_Data). An overview of the latest categorization is available [here](https://www.unicode.org/cldr/charts/46/supplemental/territory_containment_un_m_49.html). + +Currently, `.world` is only associated with `Locale.Region("001")`. `.territory` includes, but is not limited to, countries, as it also includes regions such as Antarctica (code "AQ"). `.grouping` is a region that has well defined membership, such as European Union (code "EU") and Eurozone (code "EZ"). It isn't part of the hierarchy formed by other categories. + +### Getting sub-regions matching the specified category + +```swift +extension Locale.Region { + public func subRegions(ofCategory category: Category) -> [Locale.Region] +} +``` + +If the value is higher up in the hierarchy than that of `self`, the function returns an empty array. + +```swift +argentina.subRegions(in: .world) // [] +``` + +On the other hand, the specified `category` that is more than one level down than that of `self` is still valid, as seen previously in the ["Proposed solution and example" section](#proposed-solution-and-example) + +```swift +// Passing both `.subcontinent` and `.territory` as the argument are valid +_ = americas.subRegions(ofCategory: .subcontinent) // All subcontinents in Americas +_ = americas.subRegions(ofCategory: .territory) // All territories in Americas +``` + +## Impact on existing code + +### `Locale.Region.isoRegions` starts to include regions of the "grouping" category + +Currently `Locale.Region.isoRegions` does not return regions that fall into the `.grouping` category. Those fall under the grouping category don't fit into the tree-structured containment hierarchy like the others. Given that it is not yet possible to filter ISO regions by category, these regions are not included in the return values of API. + +With the introduction of `Locale.Region.isoRegions(ofCategory:)`, we propose changing the behavior of `Locale.Region.isoRegions` to include all ISO regions, including those of the grouping category. Those who wish to exclude those of the "grouping" category can do so with `Locale.Region.isoRegions(of:)`. + +Please refer to the Alternative Considered section for more discussion. + +## Alternatives considered + +### Naming consideration: `Locale.Region.Category` + +ICU uses `URegionType` to represent the categories, while Unicode uses the term "territory containment (level)". We considered introducing `Category` as `Type`, `Containment`, `ContainmentLevel`, or `GroupingLevel`. + +`Type` was not the optimal choice because not only it is a language keyword, but also overloaded. `Containment`, `ContainmentLevel` or `GroupingLevel` would all be good fits for modeling regions as a tree hierarchy, but we never intend to force the hierarchy idea onto `Locale.Region`, and the "grouping" category is not strictly a containment level either. + +`Category` shares similar meanings to `Type`, with less strict containment notion, and is typically used in API names. + +### Introduce `containingRegion(ofCategory:)` + +An alternative is to introduce a method such as `containingRegion(ofCategory:)` to return the containing region of the specified category: + +```swift +extension Locale.Region { + /// The containing region, matching the specified category of the region. + public func containingRegion(ofCategory category: Category) -> Locale.Region? +} +``` + +Developers would use it like this: + +```swift +// The continent containing Argentina, equivalent to `argentina.continent` +_ = argentina.containingRegion(ofCategory: .continent) // "019" (Americas) + +// The sub-continent containing Argentina, equivalent to `argentina.subcontinent` +_ = argentina.containingRegion(ofCategory: .subcontinent) // "005" (South America) +``` + +Functionally it would be equivalent to existing `public var continent: Locale.Region?`. Having two entry points for the same purpose would be more confusing than helpful, so it was left out for simplicity. + +### Naming consideration: `ofCategory` argument label + +Since the "category" in the argument label in the proposed functions is the name of the type, it would be acceptable to omit it from the label, so + +```swift +public func subRegions(ofCategory category: Category) -> [Locale.Region] +``` + +would become + +```swift +public func subRegions(of category: Category) -> [Locale.Region] +``` + +However, this reads less fluent from the call-site: + +```swift +let continent = Locale.Region() +let territories = continent.subRegions(of: .territory) +``` + +It seems to indicate `territories` is "the continent's subregions of (some) territory", as opposed to the intended "the content's subregions **of category** 'territory'". Therefore it is left in place to promote fluent usage. + +### Do not change the behavior of `Locale.Region.isoRegions` + +It was considered to continue to omit regions that fall into the "grouping" category from `Locale.Region.isoRegions` for compatibility. However, just like all the other Locale related API, the list of ISO regions is never guaranteed to be constant. We do not expect users to rely on its exact values, so compatibility isn't a concern. From 84270c1b5097e6ff5c69e81a72610a0ef62ca35b Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Wed, 11 Jun 2025 13:20:09 -0700 Subject: [PATCH 07/14] Enable macOS GitHub Action CI (#1311) * Enable macOS GitHub Action CI * Add explicit macOS version list --- .github/workflows/pull_request.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index fb871584b..7b35cf1ee 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -14,6 +14,9 @@ jobs: with: linux_swift_versions: '["nightly-main"]' windows_swift_versions: '["nightly-main"]' + enable_macos_checks: true + macos_xcode_versions: '["16.3"]' + macos_versions: '["sequoia"]' soundness: name: Soundness From 0a30fcde43a0eb4c12dfa817aa9e86b9f25bb5f7 Mon Sep 17 00:00:00 2001 From: Tina L <49205802+itingliu@users.noreply.github.com> Date: Thu, 12 Jun 2025 13:44:08 -0700 Subject: [PATCH 08/14] ASAN error in _CalendarGregorian calendar implementation (#1347) Use proper overflow add function. Resolves 144178080 --- .../Calendar/Calendar_Gregorian.swift | 10 +++++++++- .../CalendarTests.swift | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift b/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift index 03b9dd4bf..1060a9cfa 100644 --- a/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift +++ b/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift @@ -2309,7 +2309,15 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable case .month: var dc = dateComponents(monthBasedComponents, from: dateInWholeSecond, in: timeZone) - dc.month = (dc.month ?? 0) + amount + if let month = dc.month { + let (res, overflow) = month.addingReportingOverflow(amount) + guard !overflow else { + throw .overflow(field, date, nil) + } + dc.month = res + } else { + dc.month = amount + } capDay(in: &dc) // adding 1 month to Jan 31 should return Feb 29, not Feb 31 resultInWholeSeconds = try self.date(from: dc, inTimeZone: timeZone, dstRepeatedTimePolicy: .latter) case .quarter: diff --git a/Tests/FoundationInternationalizationTests/CalendarTests.swift b/Tests/FoundationInternationalizationTests/CalendarTests.swift index b04456ec6..170fc2434 100644 --- a/Tests/FoundationInternationalizationTests/CalendarTests.swift +++ b/Tests/FoundationInternationalizationTests/CalendarTests.swift @@ -1232,6 +1232,14 @@ final class CalendarTests : XCTestCase { let added = calendar.date(byAdding: components, to: date) XCTAssertNil(added) } + + do { + let date = Date(timeIntervalSinceReferenceDate: 1710419015.233922) + let calendar = Calendar(identifier: .gregorian) + let value = 9223372036854775806 + let added = calendar.date(byAdding: .month, value: value, to: date) + XCTAssertNil(added) + } } func test_dateComponentsFromDateOverflow() { From ff0459d97080115750c2b6cb367cc0e6e3db8867 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 12 Jun 2025 16:36:19 -0700 Subject: [PATCH 09/14] Continued swift testing adoption (#1350) * Convert buffer view tests to swift-testing * Convert BuiltInUnicodeScalarSet tests * Convert ErrorTests * Convert LockedStateTests * Move aside old XCTest-based equatable/hashable utilities and define new swift-testing utilities * Convert UUID tests * Convert DateInterval tests * Convert date tests * Convert SortComparator tests * Convert decimal tests * Convert IndexPath tests * Fix build failures --- Sources/TestSupport/Utilities.swift | 291 +++- .../BufferViewTests.swift | 193 ++- ...ift => BuiltInUnicodeScalarSetTests.swift} | 22 +- .../DateIntervalTests.swift | 86 +- .../FoundationEssentialsTests/DateTests.swift | 140 +- .../DecimalTests.swift | 1285 ++++++++--------- .../ErrorTests.swift | 27 +- .../IndexPathTests.swift | 705 +++++---- .../LockedStateTests.swift | 44 +- .../SortComparatorTests.swift | 60 +- .../StringTests.swift | 2 +- .../FoundationEssentialsTests/UUIDTests.swift | 100 +- .../CalendarTests.swift | 4 +- .../DateTests+Locale.swift | 2 +- .../StringSortComparatorTests.swift | 47 +- 15 files changed, 1544 insertions(+), 1464 deletions(-) rename Tests/FoundationEssentialsTests/{BuiltInUnicodeScalarSetTest.swift => BuiltInUnicodeScalarSetTests.swift} (85%) diff --git a/Sources/TestSupport/Utilities.swift b/Sources/TestSupport/Utilities.swift index 3ef31c5b0..3a8cb7587 100644 --- a/Sources/TestSupport/Utilities.swift +++ b/Sources/TestSupport/Utilities.swift @@ -170,7 +170,7 @@ func expectNoChanges(_ check: @autoclosure () -> T, by differe /// /// - Note: `oracle` is also checked for conformance to the /// laws. -public func checkEquatable( +public func XCTCheckEquatable( _ instances: Instances, oracle: (Instances.Index, Instances.Index) -> Bool, allowBrokenTransitivity: Bool = false, @@ -179,7 +179,7 @@ public func checkEquatable( line: UInt = #line ) where Instances.Element: Equatable { let indices = Array(instances.indices) - _checkEquatableImpl( + _XCTCheckEquatableImpl( Array(instances), oracle: { oracle(indices[$0], indices[$1]) }, allowBrokenTransitivity: allowBrokenTransitivity, @@ -188,15 +188,7 @@ public func checkEquatable( line: line) } -private class Box { - var value: T - - init(_ value: T) { - self.value = value - } -} - -internal func _checkEquatableImpl( +internal func _XCTCheckEquatableImpl( _ instances: [Instance], oracle: (Int, Int) -> Bool, allowBrokenTransitivity: Bool = false, @@ -271,23 +263,14 @@ internal func _checkEquatableImpl( } } -func hash(_ value: H, salt: Int? = nil) -> Int { - var hasher = Hasher() - if let salt = salt { - hasher.combine(salt) - } - hasher.combine(value) - return hasher.finalize() -} - -public func checkHashable( +public func XCTCheckHashable( _ instances: Instances, equalityOracle: (Instances.Index, Instances.Index) -> Bool, allowIncompleteHashing: Bool = false, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line ) where Instances.Element: Hashable { - checkHashable( + XCTCheckHashable( instances, equalityOracle: equalityOracle, hashEqualityOracle: equalityOracle, @@ -298,7 +281,7 @@ public func checkHashable( } -public func checkHashable( +public func XCTCheckHashable( _ instances: Instances, equalityOracle: (Instances.Index, Instances.Index) -> Bool, hashEqualityOracle: (Instances.Index, Instances.Index) -> Bool, @@ -307,7 +290,7 @@ public func checkHashable( file: StaticString = #filePath, line: UInt = #line ) where Instances.Element: Hashable { - checkEquatable( + XCTCheckEquatable( instances, oracle: equalityOracle, message(), @@ -390,7 +373,7 @@ public func checkHashable( /// Test that the elements of `groups` consist of instances that satisfy the /// semantic requirements of `Hashable`, with each group defining a distinct /// equivalence class under `==`. -public func checkHashableGroups( +public func XCTCheckHashableGroups( _ groups: Groups, _ message: @autoclosure () -> String = "", allowIncompleteHashing: Bool = false, @@ -405,7 +388,7 @@ public func checkHashableGroups( func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { return groupIndices[lhs] == groupIndices[rhs] } - checkHashable( + XCTCheckHashable( instances, equalityOracle: equalityOracle, hashEqualityOracle: equalityOracle, @@ -477,3 +460,259 @@ func testExpectedToFailWithCheck(check: (String) -> Bool, _ test: @escaping } } +// MARK: - swift-testing Helpers + +import Testing + +/// Test that the elements of `instances` satisfy the semantic +/// requirements of `Equatable`, using `oracle` to generate equality +/// expectations from pairs of positions in `instances`. +/// +/// - Note: `oracle` is also checked for conformance to the +/// laws. +func checkEquatable( + _ instances: Instances, + oracle: (Instances.Index, Instances.Index) -> Bool, + allowBrokenTransitivity: Bool = false, + _ message: @autoclosure () -> String = "", + sourceLocation: SourceLocation = #_sourceLocation +) where Instances.Element: Equatable { + let indices = Array(instances.indices) + _checkEquatable( + instances, + oracle: { oracle(indices[$0], indices[$1]) }, + allowBrokenTransitivity: allowBrokenTransitivity, + message(), + sourceLocation: sourceLocation + ) +} + +func _checkEquatable( + _ _instances: Instances, + oracle: (Int, Int) -> Bool, + allowBrokenTransitivity: Bool = false, + _ message: @autoclosure () -> String = "", + sourceLocation: SourceLocation = #_sourceLocation +) where Instances.Element: Equatable { + let instances = Array(_instances) + + // For each index (which corresponds to an instance being tested) track the + // set of equal instances. + var transitivityScoreboard: [Box>] = + instances.indices.map { _ in Box([]) } + + for i in instances.indices { + let x = instances[i] + #expect(oracle(i, i), "bad oracle: broken reflexivity at index \(i)") + + for j in instances.indices { + let y = instances[j] + + let predictedXY = oracle(i, j) + #expect( + predictedXY == oracle(j, i), + "bad oracle: broken symmetry between indices \(i), \(j)", + sourceLocation: sourceLocation + ) + + let isEqualXY = x == y + #expect( + predictedXY == isEqualXY, + """ + \((predictedXY + ? "expected equal, found not equal" + : "expected not equal, found equal")) + lhs (at index \(i)): \(String(reflecting: x)) + rhs (at index \(j)): \(String(reflecting: y)) + """, + sourceLocation: sourceLocation + ) + + // Not-equal is an inverse of equal. + #expect( + isEqualXY != (x != y), + """ + lhs (at index \(i)): \(String(reflecting: x)) + rhs (at index \(j)): \(String(reflecting: y)) + """, + sourceLocation: sourceLocation + ) + + if !allowBrokenTransitivity { + // Check transitivity of the predicate represented by the oracle. + // If we are adding the instance `j` into an equivalence set, check that + // it is equal to every other instance in the set. + if predictedXY && i < j && transitivityScoreboard[i].value.insert(j).inserted { + if transitivityScoreboard[i].value.count == 1 { + transitivityScoreboard[i].value.insert(i) + } + for k in transitivityScoreboard[i].value { + #expect( + oracle(j, k), + "bad oracle: broken transitivity at indices \(i), \(j), \(k)", + sourceLocation: sourceLocation + ) + // No need to check equality between actual values, we will check + // them with the checks above. + } + precondition(transitivityScoreboard[j].value.isEmpty) + transitivityScoreboard[j] = transitivityScoreboard[i] + } + } + } + } +} + +public func checkHashable( + _ instances: Instances, + equalityOracle: (Instances.Index, Instances.Index) -> Bool, + allowIncompleteHashing: Bool = false, + _ message: @autoclosure () -> String = "", + sourceLocation: SourceLocation = #_sourceLocation +) where Instances.Element: Hashable { + checkHashable( + instances, + equalityOracle: equalityOracle, + hashEqualityOracle: equalityOracle, + allowIncompleteHashing: allowIncompleteHashing, + message(), + sourceLocation: sourceLocation) +} + +func checkHashable( + _ instances: Instances, + equalityOracle: (Instances.Index, Instances.Index) -> Bool, + hashEqualityOracle: (Instances.Index, Instances.Index) -> Bool, + allowIncompleteHashing: Bool = false, + _ message: @autoclosure () -> String = "", + sourceLocation: SourceLocation = #_sourceLocation +) where Instances.Element: Hashable { + checkEquatable( + instances, + oracle: equalityOracle, + message(), + sourceLocation: sourceLocation + ) + + for i in instances.indices { + let x = instances[i] + for j in instances.indices { + let y = instances[j] + let predicted = hashEqualityOracle(i, j) + #expect( + predicted == hashEqualityOracle(j, i), + "bad hash oracle: broken symmetry between indices \(i), \(j)", + sourceLocation: sourceLocation + ) + if x == y { + #expect( + predicted, + """ + bad hash oracle: equality must imply hash equality + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + } + if predicted { + #expect( + hash(x) == hash(y), + """ + hash(into:) expected to match, found to differ + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + #expect( + x.hashValue == y.hashValue, + """ + hashValue expected to match, found to differ + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + #expect( + x._rawHashValue(seed: 0) == y._rawHashValue(seed: 0), + """ + _rawHashValue(seed:) expected to match, found to differ + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + } else if !allowIncompleteHashing { + // Try a few different seeds; at least one of them should discriminate + // between the hashes. It is extremely unlikely this check will fail + // all ten attempts, unless the type's hash encoding is not unique, + // or unless the hash equality oracle is wrong. + #expect( + (0..<10).contains { hash(x, salt: $0) != hash(y, salt: $0) }, + """ + hash(into:) expected to differ, found to match + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + #expect( + (0..<10).contains { i in + x._rawHashValue(seed: i) != y._rawHashValue(seed: i) + }, + """ + _rawHashValue(seed:) expected to differ, found to match + lhs (at index \(i)): \(x) + rhs (at index \(j)): \(y) + """, + sourceLocation: sourceLocation + ) + } + } + } +} + +/// Test that the elements of `groups` consist of instances that satisfy the +/// semantic requirements of `Hashable`, with each group defining a distinct +/// equivalence class under `==`. +public func checkHashableGroups( + _ groups: Groups, + _ message: @autoclosure () -> String = "", + allowIncompleteHashing: Bool = false, + sourceLocation: SourceLocation = #_sourceLocation +) where Groups.Element: Collection, Groups.Element.Element: Hashable { + let instances = groups.flatMap { $0 } + // groupIndices[i] is the index of the element in groups that contains + // instances[i]. + let groupIndices = + zip(0..., groups).flatMap { i, group in group.map { _ in i } } + func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool { + return groupIndices[lhs] == groupIndices[rhs] + } + checkHashable( + instances, + equalityOracle: equalityOracle, + hashEqualityOracle: equalityOracle, + allowIncompleteHashing: allowIncompleteHashing, + sourceLocation: sourceLocation) +} + +// MARK: - Private Types + +private class Box { + var value: T + + init(_ value: T) { + self.value = value + } +} + +private func hash(_ value: H, salt: Int? = nil) -> Int { + var hasher = Hasher() + if let salt = salt { + hasher.combine(salt) + } + hasher.combine(value) + return hasher.finalize() +} diff --git a/Tests/FoundationEssentialsTests/BufferViewTests.swift b/Tests/FoundationEssentialsTests/BufferViewTests.swift index 704ee470f..a644e13d1 100644 --- a/Tests/FoundationEssentialsTests/BufferViewTests.swift +++ b/Tests/FoundationEssentialsTests/BufferViewTests.swift @@ -10,33 +10,30 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationEssentials) @testable import FoundationEssentials -#endif - -#if FOUNDATION_FRAMEWORK +#else @testable import Foundation #endif -final class BufferViewTests: XCTestCase { +@Suite("BufferView") +private struct BufferViewTests { - func testOptionalStorage() { - XCTAssertEqual( - MemoryLayout>.size, MemoryLayout?>.size + @Test func optionalStorage() { + #expect( + MemoryLayout>.size == MemoryLayout?>.size ) - XCTAssertEqual( - MemoryLayout>.stride, MemoryLayout?>.stride + #expect( + MemoryLayout>.stride == MemoryLayout?>.stride ) - XCTAssertEqual( - MemoryLayout>.alignment, MemoryLayout?>.alignment + #expect( + MemoryLayout>.alignment == MemoryLayout?>.alignment ) } - func testInitBufferViewOrdinaryElement() { + @Test func initBufferViewOrdinaryElement() { let capacity = 4 let s = (0..(start: nil, count: 0) @@ -58,17 +55,17 @@ final class BufferViewTests: XCTestCase { a.withUnsafeBytes { let b = BufferView(unsafeRawBufferPointer: $0)! - XCTAssertEqual(b.count, capacity) + #expect(b.count == capacity) let r = BufferView(unsafeRawBufferPointer: $0)! - XCTAssertEqual(r.count, capacity * MemoryLayout.stride) + #expect(r.count == capacity * MemoryLayout.stride) } let v = UnsafeRawBufferPointer(start: nil, count: 0) _ = BufferView(unsafeRawBufferPointer: v) } - func testIndex() { + @Test func index() { let count = 4 let strings = (1...count).map({ "This String is not BitwiseCopyable (\($0))." }) strings.withUnsafeBufferPointer { @@ -76,12 +73,12 @@ final class BufferViewTests: XCTestCase { let first = buffer.startIndex let second = first.advanced(by: 1) - XCTAssertLessThan(first, second) - XCTAssertEqual(1, first.distance(to: second)) + #expect(first < second) + #expect(1 == first.distance(to: second)) } } - func testIteratorOrdinaryElement() { + @Test func iteratorOrdinaryElement() { let capacity = 4 let s = (0...stride + offset var a = Array(repeating: UInt8.zero, count: bytes) - XCTAssertLessThan(offset, MemoryLayout.stride) + #expect(offset < MemoryLayout.stride) a.withUnsafeMutableBytes { for i in 0..<$0.count where i % 8 == offset { @@ -110,7 +107,7 @@ final class BufferViewTests: XCTestCase { } let orig = $0.loadUnaligned(as: Int64.self) - XCTAssertNotEqual(orig, 1) + #expect(orig != 1) // BufferView doesn't need to be aligned for accessing `BitwiseCopyable` types. let buffer = BufferView( @@ -121,14 +118,14 @@ final class BufferViewTests: XCTestCase { var iterator = buffer.makeIterator() var buffered = 0 while let value = iterator.next() { - XCTAssertEqual(value, 1) + #expect(value == 1) buffered += 1 } - XCTAssertEqual(buffered, count) + #expect(buffered == count) } } - func testBufferViewSequence() { + @Test func bufferViewSequence() { let capacity = 4 let a = Array(0...stride let s0 = view.load(as: String.self) - XCTAssertEqual(s0.contains("0"), true) + #expect(s0.contains("0")) let i1 = view.startIndex.advanced(by: stride / 2) let s1 = view.load(from: i1, as: String.self) - XCTAssertEqual(s1.contains("1"), true) + #expect(s1.contains("1")) let s2 = view.load(fromByteOffset: 2 * stride, as: String.self) - XCTAssertEqual(s2.contains("2"), true) + #expect(s2.contains("2")) } } - func testLoadUnaligned() { + @Test func loadUnaligned() { let capacity = 64 let a = Array(0..(unsafeRawBufferPointer: $0)! let u0 = view.dropFirst(1).loadUnaligned(as: UInt64.self) - XCTAssertEqual(u0 & 0xff, 2) - XCTAssertEqual(u0.byteSwapped & 0xff, 9) + #expect(u0 & 0xff == 2) + #expect(u0.byteSwapped & 0xff == 9) let i1 = view.startIndex.advanced(by: 3) let u1 = view.loadUnaligned(from: i1, as: UInt64.self) - XCTAssertEqual(u1 & 0xff, 6) + #expect(u1 & 0xff == 6) let u3 = view.loadUnaligned(fromByteOffset: 7, as: UInt32.self) - XCTAssertEqual(u3 & 0xff, 7) + #expect(u3 & 0xff == 7) } } - func testOffsetSubscript() { + @Test func offsetSubscript() { let capacity = 4 let a = Array(0.. testInterval1) + #expect(testInterval1 < testInterval3) + #expect(testInterval3 > testInterval1) // dateWithString("2009-05-17 14:49:47 -0700") let earlierStart = Date(timeIntervalSinceReferenceDate: 264289787.0) let testInterval4 = DateInterval(start: earlierStart, duration: duration) - XCTAssertTrue(testInterval4 < testInterval1) - XCTAssertTrue(testInterval1 > testInterval4) + #expect(testInterval4 < testInterval1) + #expect(testInterval1 > testInterval4) } - func test_isEqualToDateInterval() { + @Test func isEqualToDateInterval() { // dateWithString("2010-05-17 14:49:47 -0700") let start = Date(timeIntervalSinceReferenceDate: 295825787.0) let duration = 10000000.0 let testInterval1 = DateInterval(start: start, duration: duration) let testInterval2 = DateInterval(start: start, duration: duration) - XCTAssertEqual(testInterval1, testInterval2) + #expect(testInterval1 == testInterval2) let testInterval3 = DateInterval(start: start, duration: 100.0) - XCTAssertNotEqual(testInterval1, testInterval3) + #expect(testInterval1 != testInterval3) } - func test_hashing() { + @Test func hashing() { // dateWithString("2019-04-04 17:09:23 -0700") let start1a = Date(timeIntervalSinceReferenceDate: 576115763.0) let start1b = Date(timeIntervalSinceReferenceDate: 576115763.0) @@ -80,7 +85,7 @@ final class DateIntervalTests : XCTestCase { checkHashableGroups(intervals) } - func test_checkIntersection() { + @Test func checkIntersection() { // dateWithString("2010-05-17 14:49:47 -0700") let start1 = Date(timeIntervalSinceReferenceDate: 295825787.0) // dateWithString("2010-08-17 14:49:47 -0700") @@ -95,7 +100,7 @@ final class DateIntervalTests : XCTestCase { let testInterval2 = DateInterval(start: start2, end: end2) - XCTAssertTrue(testInterval1.intersects(testInterval2)) + #expect(testInterval1.intersects(testInterval2)) // dateWithString("2010-10-17 14:49:47 -0700") let start3 = Date(timeIntervalSinceReferenceDate: 309044987.0) @@ -104,10 +109,10 @@ final class DateIntervalTests : XCTestCase { let testInterval3 = DateInterval(start: start3, end: end3) - XCTAssertFalse(testInterval1.intersects(testInterval3)) + #expect(!testInterval1.intersects(testInterval3)) } - func test_validIntersections() { + @Test func validIntersections() { // dateWithString("2010-05-17 14:49:47 -0700") let start1 = Date(timeIntervalSinceReferenceDate: 295825787.0) // dateWithString("2010-08-17 14:49:47 -0700") @@ -130,15 +135,13 @@ final class DateIntervalTests : XCTestCase { let testInterval3 = DateInterval(start: start3, end: end3) let intersection1 = testInterval2.intersection(with: testInterval1) - XCTAssertNotNil(intersection1) - XCTAssertEqual(testInterval3, intersection1) + #expect(testInterval3 == intersection1) let intersection2 = testInterval1.intersection(with: testInterval2) - XCTAssertNotNil(intersection2) - XCTAssertEqual(intersection1, intersection2) + #expect(intersection1 == intersection2) } - func test_containsDate() { + @Test func containsDate() { // dateWithString("2010-05-17 14:49:47 -0700") let start = Date(timeIntervalSinceReferenceDate: 295825787.0) let duration = 10000000.0 @@ -147,14 +150,14 @@ final class DateIntervalTests : XCTestCase { // dateWithString("2010-05-17 20:49:47 -0700") let containedDate = Date(timeIntervalSinceReferenceDate: 295847387.0) - XCTAssertTrue(testInterval.contains(containedDate)) + #expect(testInterval.contains(containedDate)) // dateWithString("2009-05-17 14:49:47 -0700") let earlierStart = Date(timeIntervalSinceReferenceDate: 264289787.0) - XCTAssertFalse(testInterval.contains(earlierStart)) + #expect(!testInterval.contains(earlierStart)) } - func test_AnyHashableContainingDateInterval() { + @Test func anyHashableContainingDateInterval() { // dateWithString("2010-05-17 14:49:47 -0700") let start = Date(timeIntervalSinceReferenceDate: 295825787.0) let duration = 10000000.0 @@ -164,18 +167,19 @@ final class DateIntervalTests : XCTestCase { DateInterval(start: start, duration: duration / 2), ] let anyHashables = values.map(AnyHashable.init) - expectEqual(DateInterval.self, type(of: anyHashables[0].base)) - expectEqual(DateInterval.self, type(of: anyHashables[1].base)) - expectEqual(DateInterval.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(DateInterval.self == type(of: anyHashables[0].base)) + #expect(DateInterval.self == type(of: anyHashables[1].base)) + #expect(DateInterval.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } } // MARK: - Bridging Tests #if FOUNDATION_FRAMEWORK -extension DateIntervalTests { - func test_AnyHashableCreatedFromNSDateInterval() { +@Suite("DateInterval Bridging") +private struct DateIntervalBridgingTests { + @Test func anyHashableCreatedFromNSDateInterval() { // dateWithString("2010-05-17 14:49:47 -0700") let start = Date(timeIntervalSinceReferenceDate: 295825787.0) let duration = 10000000.0 @@ -185,11 +189,11 @@ extension DateIntervalTests { NSDateInterval(start: start, duration: duration / 2), ] let anyHashables = values.map(AnyHashable.init) - expectEqual(DateInterval.self, type(of: anyHashables[0].base)) - expectEqual(DateInterval.self, type(of: anyHashables[1].base)) - expectEqual(DateInterval.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(DateInterval.self == type(of: anyHashables[0].base)) + #expect(DateInterval.self == type(of: anyHashables[1].base)) + #expect(DateInterval.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } } #endif diff --git a/Tests/FoundationEssentialsTests/DateTests.swift b/Tests/FoundationEssentialsTests/DateTests.swift index 9558f580f..c28cf4fdf 100644 --- a/Tests/FoundationEssentialsTests/DateTests.swift +++ b/Tests/FoundationEssentialsTests/DateTests.swift @@ -10,154 +10,158 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationEssentials) -@testable import FoundationEssentials +import FoundationEssentials +#elseif FOUNDATION_FRAMEWORK +import Foundation #endif -final class DateTests : XCTestCase { +@Suite("Date") +private struct DateTests { - func testDateComparison() { + @Test func comparison() { let d1 = Date() let d2 = d1 + 1 - XCTAssertGreaterThan(d2, d1) - XCTAssertLessThan(d1, d2) + #expect(d2 > d1) + #expect(d1 < d2) let d3 = Date(timeIntervalSince1970: 12345) let d4 = Date(timeIntervalSince1970: 12345) - XCTAssertEqual(d3, d4) - XCTAssertLessThanOrEqual(d3, d4) - XCTAssertGreaterThanOrEqual(d4, d3) + #expect(d3 == d4) + #expect(d3 <= d4) + #expect(d4 >= d3) } - func testDateMutation() { + @Test func mutation() { let d0 = Date() var d1 = Date() d1 = d1 + 1.0 let d2 = Date(timeIntervalSinceNow: 10) - XCTAssertGreaterThan(d2, d1) - XCTAssertNotEqual(d1, d0) + #expect(d2 > d1) + #expect(d1 != d0) let d3 = d1 d1 += 10 - XCTAssertGreaterThan(d1, d3) + #expect(d1 > d3) } - func testDistantPast() { + @Test func distantPast() { let distantPast = Date.distantPast let currentDate = Date() - XCTAssertLessThan(distantPast, currentDate) - XCTAssertGreaterThan(currentDate, distantPast) - XCTAssertLessThan(distantPast.timeIntervalSince(currentDate), + #expect(distantPast < currentDate) + #expect(currentDate > distantPast) + #expect(distantPast.timeIntervalSince(currentDate) < 3600.0 * 24 * 365 * 100) /* ~1 century in seconds */ } - func testDistantFuture() { + @Test func distantFuture() { let distantFuture = Date.distantFuture let currentDate = Date() - XCTAssertLessThan(currentDate, distantFuture) - XCTAssertGreaterThan(distantFuture, currentDate) - XCTAssertGreaterThan(distantFuture.timeIntervalSince(currentDate), + #expect(currentDate < distantFuture) + #expect(distantFuture > currentDate) + #expect(distantFuture.timeIntervalSince(currentDate) > 3600.0 * 24 * 365 * 100) /* ~1 century in seconds */ } - func test_now() { + @Test func now() { let date1 : Date = .now let date2 : Date = .now - XCTAssertLessThanOrEqual(date1, date2) + #expect(date1 <= date2) } - func testDescriptionReferenceDate() { + @Test func descriptionReferenceDate() { let date = Date(timeIntervalSinceReferenceDate: TimeInterval(0)) - XCTAssertEqual("2001-01-01 00:00:00 +0000", date.description) + #expect("2001-01-01 00:00:00 +0000" == date.description) } - func testDescription1970() { + @Test func description1970() { let date = Date(timeIntervalSince1970: TimeInterval(0)) - XCTAssertEqual("1970-01-01 00:00:00 +0000", date.description) + #expect("1970-01-01 00:00:00 +0000" == date.description) } - func testDescriptionDistantPast() throws { -#if os(Windows) - throw XCTSkip("ucrt does not support distant past") -#else + #if os(Windows) + @Test(.disabled("ucrt does not support distant past")) + #else + @Test + #endif + func descriptionDistantPast() throws { #if FOUNDATION_FRAMEWORK - XCTAssertEqual("0001-01-01 00:00:00 +0000", Date.distantPast.description) + #expect("0001-01-01 00:00:00 +0000" == Date.distantPast.description) #else - XCTAssertEqual("0000-12-30 00:00:00 +0000", Date.distantPast.description) -#endif + #expect("0000-12-30 00:00:00 +0000" == Date.distantPast.description) #endif } - func testDescriptionDistantFuture() throws { -#if os(Windows) - throw XCTSkip("ucrt does not support distant future") -#else - XCTAssertEqual("4001-01-01 00:00:00 +0000", Date.distantFuture.description) -#endif + #if os(Windows) + @Test(.disabled("ucrt does not support distant past")) + #else + @Test + #endif + func descriptionDistantFuture() throws { + #expect("4001-01-01 00:00:00 +0000" == Date.distantFuture.description) } - func testDescriptionBeyondDistantPast() { + @Test func descriptionBeyondDistantPast() { let date = Date.distantPast.addingTimeInterval(TimeInterval(-1)) #if FOUNDATION_FRAMEWORK - XCTAssertEqual("0000-12-31 23:59:59 +0000", date.description) + #expect("0000-12-31 23:59:59 +0000" == date.description) #else - XCTAssertEqual("", date.description) + #expect("" == date.description) #endif } - func testDescriptionBeyondDistantFuture() { + @Test func descriptionBeyondDistantFuture() { let date = Date.distantFuture.addingTimeInterval(TimeInterval(1)) #if FOUNDATION_FRAMEWORK - XCTAssertEqual("4001-01-01 00:00:01 +0000", date.description) + #expect("4001-01-01 00:00:01 +0000" == date.description) #else - XCTAssertEqual("", date.description) + #expect("" == date.description) #endif } - func testNowIsAfterReasonableDate() { + @Test func nowIsAfterReasonableDate() { let date = Date.now - XCTAssert(date.timeIntervalSinceReferenceDate > 742100000.0) // "2024-07-08T02:53:20Z" - XCTAssert(date.timeIntervalSinceReferenceDate < 3896300000.0) // "2124-06-21T01:33:20Z" + #expect(date.timeIntervalSinceReferenceDate > 742100000.0) // "2024-07-08T02:53:20Z" + #expect(date.timeIntervalSinceReferenceDate < 3896300000.0) // "2124-06-21T01:33:20Z" } } // MARK: - Bridging #if FOUNDATION_FRAMEWORK -final class DateBridgingTests : XCTestCase { - func testCast() { +@Suite("Date Bridging") +private struct DateBridgingTests { + @Test func cast() { let d0 = NSDate() let d1 = d0 as Date - XCTAssertEqual(d0.timeIntervalSinceReferenceDate, d1.timeIntervalSinceReferenceDate) + #expect(d0.timeIntervalSinceReferenceDate == d1.timeIntervalSinceReferenceDate) } - func test_AnyHashableCreatedFromNSDate() { + @Test func anyHashableCreatedFromNSDate() { let values: [NSDate] = [ NSDate(timeIntervalSince1970: 1000000000), NSDate(timeIntervalSince1970: 1000000001), NSDate(timeIntervalSince1970: 1000000001), ] let anyHashables = values.map(AnyHashable.init) - expectEqual(Date.self, type(of: anyHashables[0].base)) - expectEqual(Date.self, type(of: anyHashables[1].base)) - expectEqual(Date.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(Date.self == type(of: anyHashables[0].base)) + #expect(Date.self == type(of: anyHashables[1].base)) + #expect(Date.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } - func test_AnyHashableCreatedFromNSDateComponents() { + @Test func anyHashableCreatedFromNSDateComponents() { func makeNSDateComponents(year: Int) -> NSDateComponents { let result = NSDateComponents() result.year = year @@ -169,15 +173,15 @@ final class DateBridgingTests : XCTestCase { makeNSDateComponents(year: 1995), ] let anyHashables = values.map(AnyHashable.init) - expectEqual(DateComponents.self, type(of: anyHashables[0].base)) - expectEqual(DateComponents.self, type(of: anyHashables[1].base)) - expectEqual(DateComponents.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(DateComponents.self == type(of: anyHashables[0].base)) + #expect(DateComponents.self == type(of: anyHashables[1].base)) + #expect(DateComponents.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } - func test_dateComponents_unconditionallyBridgeFromObjectiveC() { - XCTAssertEqual(DateComponents(), DateComponents._unconditionallyBridgeFromObjectiveC(nil)) + @Test func dateComponents_unconditionallyBridgeFromObjectiveC() { + #expect(DateComponents() == DateComponents._unconditionallyBridgeFromObjectiveC(nil)) } } #endif // FOUNDATION_FRAMEWORK diff --git a/Tests/FoundationEssentialsTests/DecimalTests.swift b/Tests/FoundationEssentialsTests/DecimalTests.swift index 6f7d08ff3..a20d634b4 100644 --- a/Tests/FoundationEssentialsTests/DecimalTests.swift +++ b/Tests/FoundationEssentialsTests/DecimalTests.swift @@ -10,148 +10,147 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif // canImport(TestSupport) +import Testing + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif os(WASI) +import WASILibc +#elseif os(Windows) +import CRT +#endif -#if FOUNDATION_FRAMEWORK -@testable import Foundation -#else +#if canImport(FoundationEssentials) @_spi(SwiftCorelibsFoundation) @testable import FoundationEssentials +#else +@testable import Foundation #endif -final class DecimalTests : XCTestCase { +@Suite("Decimal") +private struct DecimalTests { #if !FOUNDATION_FRAMEWORK // These tests tests the stub implementations - func assertMantissaEquals(lhs: Decimal, rhs: Decimal.Mantissa) { - XCTAssertEqual(lhs[0], rhs.0, "Mantissa.0 does not equal: \(lhs[0]) vs \(rhs.0)") - XCTAssertEqual(lhs[1], rhs.1, "Mantissa.1 does not equal: \(lhs[1]) vs \(rhs.1)") - XCTAssertEqual(lhs[2], rhs.2, "Mantissa.2 does not equal: \(lhs[2]) vs \(rhs.2)") - XCTAssertEqual(lhs[3], rhs.3, "Mantissa.3 does not equal: \(lhs[3]) vs \(rhs.3)") - XCTAssertEqual(lhs[4], rhs.4, "Mantissa.4 does not equal: \(lhs[4]) vs \(rhs.4)") - XCTAssertEqual(lhs[5], rhs.5, "Mantissa.5 does not equal: \(lhs[5]) vs \(rhs.5)") - XCTAssertEqual(lhs[6], rhs.6, "Mantissa.6 does not equal: \(lhs[6]) vs \(rhs.6)") - XCTAssertEqual(lhs[7], rhs.7, "Mantissa.7 does not equal: \(lhs[7]) vs \(rhs.7)") + func assertMantissaEquals(lhs: Decimal, rhs: Decimal.Mantissa, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(lhs[0] == rhs.0, "Mantissa.0 does not equal: \(lhs[0]) vs \(rhs.0)", sourceLocation: sourceLocation) + #expect(lhs[1] == rhs.1, "Mantissa.1 does not equal: \(lhs[1]) vs \(rhs.1)", sourceLocation: sourceLocation) + #expect(lhs[2] == rhs.2, "Mantissa.2 does not equal: \(lhs[2]) vs \(rhs.2)", sourceLocation: sourceLocation) + #expect(lhs[3] == rhs.3, "Mantissa.3 does not equal: \(lhs[3]) vs \(rhs.3)", sourceLocation: sourceLocation) + #expect(lhs[4] == rhs.4, "Mantissa.4 does not equal: \(lhs[4]) vs \(rhs.4)", sourceLocation: sourceLocation) + #expect(lhs[5] == rhs.5, "Mantissa.5 does not equal: \(lhs[5]) vs \(rhs.5)", sourceLocation: sourceLocation) + #expect(lhs[6] == rhs.6, "Mantissa.6 does not equal: \(lhs[6]) vs \(rhs.6)", sourceLocation: sourceLocation) + #expect(lhs[7] == rhs.7, "Mantissa.7 does not equal: \(lhs[7]) vs \(rhs.7)", sourceLocation: sourceLocation) } - func testDecimalRoundtripFuzzing() { - let iterations = 100 - for _ in 0 ..< iterations { - // Exponent is only 8 bits long - let exponent: CInt = CInt(Int8.random(in: Int8.min ..< Int8.max)) - // Length is only 4 bits long - var length: CUnsignedInt = .random(in: 0 ..< 0xF) - let isNegative: CUnsignedInt = .random(in: 0 ..< 1) - let isCompact: CUnsignedInt = .random(in: 0 ..< 1) - // Reserved is 18 bits long - let reserved: CUnsignedInt = .random(in: 0 ..< 0x3FFFF) - let mantissa: Decimal.Mantissa = ( - .random(in: 0 ..< UInt16.max), - .random(in: 0 ..< UInt16.max), - .random(in: 0 ..< UInt16.max), - .random(in: 0 ..< UInt16.max), - .random(in: 0 ..< UInt16.max), - .random(in: 0 ..< UInt16.max), - .random(in: 0 ..< UInt16.max), - .random(in: 0 ..< UInt16.max) - ) - - var decimal = Decimal( - _exponent: exponent, - _length: length, - _isNegative: isNegative, - _isCompact: isCompact, - _reserved: reserved, - _mantissa: mantissa - ) - - XCTAssertEqual(decimal._exponent, exponent) - XCTAssertEqual(decimal._length, length) - XCTAssertEqual(decimal._isNegative, isNegative) - XCTAssertEqual(decimal._isCompact, isCompact) - XCTAssertEqual(decimal._reserved, reserved) - assertMantissaEquals( - lhs: decimal, - rhs: mantissa - ) - - // Update invidividual values - length = .random(in: 0 ..< 0xF) - decimal._length = length - XCTAssertEqual(decimal._length, length) - } + @Test(arguments: 0 ..< 100) + func roundtripFuzzing(iteration: Int) { + // Exponent is only 8 bits long + let exponent: CInt = CInt(Int8.random(in: Int8.min ..< Int8.max)) + // Length is only 4 bits long + var length: CUnsignedInt = .random(in: 0 ..< 0xF) + let isNegative: CUnsignedInt = .random(in: 0 ..< 1) + let isCompact: CUnsignedInt = .random(in: 0 ..< 1) + // Reserved is 18 bits long + let reserved: CUnsignedInt = .random(in: 0 ..< 0x3FFFF) + let mantissa: Decimal.Mantissa = ( + .random(in: 0 ..< UInt16.max), + .random(in: 0 ..< UInt16.max), + .random(in: 0 ..< UInt16.max), + .random(in: 0 ..< UInt16.max), + .random(in: 0 ..< UInt16.max), + .random(in: 0 ..< UInt16.max), + .random(in: 0 ..< UInt16.max), + .random(in: 0 ..< UInt16.max) + ) + + var decimal = Decimal( + _exponent: exponent, + _length: length, + _isNegative: isNegative, + _isCompact: isCompact, + _reserved: reserved, + _mantissa: mantissa + ) + + #expect(decimal._exponent == exponent) + #expect(decimal._length == length) + #expect(decimal._isNegative == isNegative) + #expect(decimal._isCompact == isCompact) + #expect(decimal._reserved == reserved) + assertMantissaEquals( + lhs: decimal, + rhs: mantissa + ) + + // Update invidividual values + length = .random(in: 0 ..< 0xF) + decimal._length = length + #expect(decimal._length == length) } - #endif - func testAbusiveCompact() { + @Test func abusiveCompact() { var decimal = Decimal() decimal._exponent = 5 decimal._length = 5 decimal.compact() - XCTAssertEqual(Decimal.zero, decimal); - } - - func test_Description() { - XCTAssertEqual("0", Decimal().description) - XCTAssertEqual("0", Decimal(0).description) - XCTAssertEqual("10", Decimal(_exponent: 1, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (1, 0, 0, 0, 0, 0, 0, 0)).description) - XCTAssertEqual("10", Decimal(10).description) - XCTAssertEqual("123.458", Decimal(_exponent: -3, _length: 2, _isNegative: 0, _isCompact:1, _reserved: 0, _mantissa: (57922, 1, 0, 0, 0, 0, 0, 0)).description) - XCTAssertEqual("123.458", Decimal(123.458).description) - XCTAssertEqual("123", Decimal(UInt8(123)).description) - XCTAssertEqual("45", Decimal(Int8(45)).description) - XCTAssertEqual("3.14159265358979323846264338327950288419", Decimal.pi.description) - XCTAssertEqual("-30000000000", Decimal(sign: .minus, exponent: 10, significand: Decimal(3)).description) - XCTAssertEqual("300000", Decimal(sign: .plus, exponent: 5, significand: Decimal(3)).description) - XCTAssertEqual("5", Decimal(signOf: Decimal(3), magnitudeOf: Decimal(5)).description) - XCTAssertEqual("-5", Decimal(signOf: Decimal(-3), magnitudeOf: Decimal(5)).description) - XCTAssertEqual("5", Decimal(signOf: Decimal(3), magnitudeOf: Decimal(-5)).description) - XCTAssertEqual("-5", Decimal(signOf: Decimal(-3), magnitudeOf: Decimal(-5)).description) + #expect(Decimal.zero == decimal) } - func test_DescriptionWithLocale() { - let decimal = Decimal(string: "-123456.789")! - XCTAssertEqual(decimal._toString(withDecimalSeparator: "."), "-123456.789") - let en = decimal._toString(withDecimalSeparator: Locale(identifier: "en_GB").decimalSeparator!) - XCTAssertEqual(en, "-123456.789") - let fr = decimal._toString(withDecimalSeparator: Locale(identifier: "fr_FR").decimalSeparator!) - XCTAssertEqual(fr, "-123456,789") + @Test func description() { + #expect("0" == Decimal().description) + #expect("0" == Decimal(0).description) + #expect("10" == Decimal(_exponent: 1, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (1, 0, 0, 0, 0, 0, 0, 0)).description) + #expect("10" == Decimal(10).description) + #expect("123.458" == Decimal(_exponent: -3, _length: 2, _isNegative: 0, _isCompact:1, _reserved: 0, _mantissa: (57922, 1, 0, 0, 0, 0, 0, 0)).description) + #expect("123.458" == Decimal(123.458).description) + #expect("123" == Decimal(UInt8(123)).description) + #expect("45" == Decimal(Int8(45)).description) + #expect("3.14159265358979323846264338327950288419" == Decimal.pi.description) + #expect("-30000000000" == Decimal(sign: .minus, exponent: 10, significand: Decimal(3)).description) + #expect("300000" == Decimal(sign: .plus, exponent: 5, significand: Decimal(3)).description) + #expect("5" == Decimal(signOf: Decimal(3), magnitudeOf: Decimal(5)).description) + #expect("-5" == Decimal(signOf: Decimal(-3), magnitudeOf: Decimal(5)).description) + #expect("5" == Decimal(signOf: Decimal(3), magnitudeOf: Decimal(-5)).description) + #expect("-5" == Decimal(signOf: Decimal(-3), magnitudeOf: Decimal(-5)).description) } - func test_BasicConstruction() { + @Test func basicConstruction() { let zero = Decimal() - XCTAssertEqual(20, MemoryLayout.size) - XCTAssertEqual(0, zero._exponent) - XCTAssertEqual(0, zero._length) - XCTAssertEqual(0, zero._isNegative) - XCTAssertEqual(0, zero._isCompact) - XCTAssertEqual(0, zero._reserved) + #expect(20 == MemoryLayout.size) + #expect(0 == zero._exponent) + #expect(0 == zero._length) + #expect(0 == zero._isNegative) + #expect(0 == zero._isCompact) + #expect(0 == zero._reserved) let (m0, m1, m2, m3, m4, m5, m6, m7) = zero._mantissa - XCTAssertEqual(0, m0) - XCTAssertEqual(0, m1) - XCTAssertEqual(0, m2) - XCTAssertEqual(0, m3) - XCTAssertEqual(0, m4) - XCTAssertEqual(0, m5) - XCTAssertEqual(0, m6) - XCTAssertEqual(0, m7) - XCTAssertEqual(8, Decimal.maxSize) - XCTAssertEqual(32767, CShort.max) - XCTAssertFalse(zero.isNormal) - XCTAssertTrue(zero.isFinite) - XCTAssertTrue(zero.isZero) - XCTAssertFalse(zero.isSubnormal) - XCTAssertFalse(zero.isInfinite) - XCTAssertFalse(zero.isNaN) - XCTAssertFalse(zero.isSignaling) + #expect(0 == m0) + #expect(0 == m1) + #expect(0 == m2) + #expect(0 == m3) + #expect(0 == m4) + #expect(0 == m5) + #expect(0 == m6) + #expect(0 == m7) + #expect(8 == Decimal.maxSize) + #expect(32767 == CShort.max) + #expect(!zero.isNormal) + #expect(zero.isFinite) + #expect(zero.isZero) + #expect(!zero.isSubnormal) + #expect(!zero.isInfinite) + #expect(!zero.isNaN) + #expect(!zero.isSignaling) let d1 = Decimal(1234567890123456789 as UInt64) - XCTAssertEqual(d1._exponent, 0) - XCTAssertEqual(d1._length, 4) + #expect(d1._exponent == 0) + #expect(d1._length == 4) } - func test_ExplicitConstruction() { + @Test func explicitConstruction() { var explicit = Decimal( _exponent: 0x17f, _length: 0xff, @@ -160,50 +159,50 @@ final class DecimalTests : XCTestCase { _reserved: UInt32(1<<18 + 1<<17 + 1), _mantissa: (6, 7, 8, 9, 10, 11, 12, 13) ) - XCTAssertEqual(0x7f, explicit._exponent) - XCTAssertEqual(0x7f, explicit.exponent) - XCTAssertEqual(0x0f, explicit._length) - XCTAssertEqual(1, explicit._isNegative) - XCTAssertEqual(FloatingPointSign.minus, explicit.sign) - XCTAssertTrue(explicit.isSignMinus) - XCTAssertEqual(0, explicit._isCompact) - XCTAssertEqual(UInt32(1<<17 + 1), explicit._reserved) + #expect(0x7f == explicit._exponent) + #expect(0x7f == explicit.exponent) + #expect(0x0f == explicit._length) + #expect(1 == explicit._isNegative) + #expect(FloatingPointSign.minus == explicit.sign) + #expect(explicit.isSignMinus) + #expect(0 == explicit._isCompact) + #expect(UInt32(1<<17 + 1) == explicit._reserved) let (m0, m1, m2, m3, m4, m5, m6, m7) = explicit._mantissa - XCTAssertEqual(6, m0) - XCTAssertEqual(7, m1) - XCTAssertEqual(8, m2) - XCTAssertEqual(9, m3) - XCTAssertEqual(10, m4) - XCTAssertEqual(11, m5) - XCTAssertEqual(12, m6) - XCTAssertEqual(13, m7) + #expect(6 == m0) + #expect(7 == m1) + #expect(8 == m2) + #expect(9 == m3) + #expect(10 == m4) + #expect(11 == m5) + #expect(12 == m6) + #expect(13 == m7) explicit._isCompact = 5 explicit._isNegative = 6 - XCTAssertEqual(0, explicit._isNegative) - XCTAssertEqual(1, explicit._isCompact) - XCTAssertEqual(FloatingPointSign.plus, explicit.sign) - XCTAssertFalse(explicit.isSignMinus) - XCTAssertTrue(explicit.isNormal) + #expect(0 == explicit._isNegative) + #expect(1 == explicit._isCompact) + #expect(FloatingPointSign.plus == explicit.sign) + #expect(!explicit.isSignMinus) + #expect(explicit.isNormal) let significand = explicit.significand - XCTAssertEqual(0, significand._exponent) - XCTAssertEqual(0, significand.exponent) - XCTAssertEqual(0x0f, significand._length) - XCTAssertEqual(0, significand._isNegative) - XCTAssertEqual(1, significand._isCompact) - XCTAssertEqual(0, significand._reserved) + #expect(0 == significand._exponent) + #expect(0 == significand.exponent) + #expect(0x0f == significand._length) + #expect(0 == significand._isNegative) + #expect(1 == significand._isCompact) + #expect(0 == significand._reserved) let (sm0, sm1, sm2, sm3, sm4, sm5, sm6, sm7) = significand._mantissa - XCTAssertEqual(6, sm0) - XCTAssertEqual(7, sm1) - XCTAssertEqual(8, sm2) - XCTAssertEqual(9, sm3) - XCTAssertEqual(10, sm4) - XCTAssertEqual(11, sm5) - XCTAssertEqual(12, sm6) - XCTAssertEqual(13, sm7) + #expect(6 == sm0) + #expect(7 == sm1) + #expect(8 == sm2) + #expect(9 == sm3) + #expect(10 == sm4) + #expect(11 == sm5) + #expect(12 == sm6) + #expect(13 == sm7) } - func test_ScanDecimal() throws { + @Test func scanDecimal() throws { let testCases = [ // expected, value ( 123.456e78, "123.456e78", "123456000000000000000000000000000000000000000000000000000000000000000000000000000" ), @@ -218,181 +217,122 @@ final class DecimalTests : XCTestCase { ] for testCase in testCases { let (expected, string, _) = testCase - let decimal = Decimal(string:string)! + let decimal = try #require(Decimal(string: string)) let aboutOne = Decimal(expected) / decimal - let approximatelyRight = aboutOne >= Decimal(0.99999) && aboutOne <= Decimal(1.00001) - XCTAssertTrue(approximatelyRight, "\(expected) ~= \(decimal) : \(aboutOne) \(aboutOne >= Decimal(0.99999)) \(aboutOne <= Decimal(1.00001))" ) - } - guard let answer = Decimal(string:"12345679012345679012345679012345679012.3") else { - XCTFail("Unable to parse Decimal(string:'12345679012345679012345679012345679012.3')") - return - } - guard let ones = Decimal(string:"111111111111111111111111111111111111111") else { - XCTFail("Unable to parse Decimal(string:'111111111111111111111111111111111111111')") - return + #expect(aboutOne >= Decimal(0.99999) && aboutOne <= Decimal(1.00001), "\(expected) ~= \(decimal)") } + let answer = try #require(Decimal(string:"12345679012345679012345679012345679012.3")) + let ones = try #require(Decimal(string:"111111111111111111111111111111111111111")) let num = ones / Decimal(9) - XCTAssertEqual(answer,num,"\(ones) / 9 = \(answer) \(num)") + #expect(answer == num, "\(ones) / 9 = \(answer) \(num)") // Exponent overflow, returns nil - XCTAssertNil(Decimal(string: "1e200")) - XCTAssertNil(Decimal(string: "1e-200")) - XCTAssertNil(Decimal(string: "1e300")) - XCTAssertNil(Decimal(string: "1" + String(repeating: "0", count: 170))) - XCTAssertNil(Decimal(string: "0." + String(repeating: "0", count: 170) + "1")) - XCTAssertNil(Decimal(string: "0e200")) + #expect(Decimal(string: "1e200") == nil) + #expect(Decimal(string: "1e-200") == nil) + #expect(Decimal(string: "1e300") == nil) + #expect(Decimal(string: "1" + String(repeating: "0", count: 170)) == nil) + #expect(Decimal(string: "0." + String(repeating: "0", count: 170) + "1") == nil) + #expect(Decimal(string: "0e200") == nil) // Parsing zero in different forms - let zero1 = try XCTUnwrap(Decimal(string: "000.000e123")) - XCTAssertTrue(zero1.isZero) - XCTAssertEqual(zero1._isNegative, 0) - XCTAssertEqual(zero1._length, 0) - XCTAssertEqual(zero1.description, "0") - - let zero2 = try XCTUnwrap(Decimal(string: "+000.000e-123")) - XCTAssertTrue(zero2.isZero) - XCTAssertEqual(zero2._isNegative, 0) - XCTAssertEqual(zero2._length, 0) - XCTAssertEqual(zero2.description, "0") - - let zero3 = try XCTUnwrap(Decimal(string: "-0.0e1")) - XCTAssertTrue(zero3.isZero) - XCTAssertEqual(zero3._isNegative, 0) - XCTAssertEqual(zero3._length, 0) - XCTAssertEqual(zero3.description, "0") + let zero1 = try #require(Decimal(string: "000.000e123")) + #expect(zero1.isZero) + #expect(zero1._isNegative == 0) + #expect(zero1._length == 0) + #expect(zero1.description == "0") + + let zero2 = try #require(Decimal(string: "+000.000e-123")) + #expect(zero2.isZero) + #expect(zero2._isNegative == 0) + #expect(zero2._length == 0) + #expect(zero2.description == "0") + + let zero3 = try #require(Decimal(string: "-0.0e1")) + #expect(zero3.isZero) + #expect(zero3._isNegative == 0) + #expect(zero3._length == 0) + #expect(zero3.description == "0") // Bin compat: invalid strings starting with E should be parsed as 0 - var zeroE = try XCTUnwrap(Decimal(string: "en")) - XCTAssertTrue(zeroE.isZero) - zeroE = try XCTUnwrap(Decimal(string: "e")) - XCTAssertTrue(zeroE.isZero) + var zeroE = try #require(Decimal(string: "en")) + #expect(zeroE.isZero) + zeroE = try #require(Decimal(string: "e")) + #expect(zeroE.isZero) // Partitally valid strings ending with e shold be parsed - let notZero = try XCTUnwrap(Decimal(string: "123e")) - XCTAssertEqual(notZero, Decimal(123)) - } - - func test_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)) - - 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)) - - 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) - - 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)) - - XCTAssertEqual(Decimal(string: "."), Decimal.zero) - XCTAssertEqual(Decimal(string: ".", locale: en_US), Decimal.zero) - XCTAssertNil(Decimal(string: ".", locale: fr_FR)) - - XCTAssertNil(Decimal(string: ",")) - XCTAssertEqual(Decimal(string: ",", locale: fr_FR), Decimal.zero) - XCTAssertNil(Decimal(string: ",", locale: en_US)) - - let s1 = "1234.5678" - XCTAssertEqual(Decimal(string: s1, locale: en_US)?.description, s1) - XCTAssertEqual(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) + let notZero = try #require(Decimal(string: "123e")) + #expect(notZero == Decimal(123)) } - func testStringPartialMatch() { + @Test func stringPartialMatch() throws { // This tests makes sure Decimal still has the // same behavior that it only requires the beginning // of the string to be valid number - let decimal = Decimal(string: "3.14notanumber") - XCTAssertNotNil(decimal) - XCTAssertEqual(decimal!.description, "3.14") + let decimal = try #require(Decimal(string: "3.14notanumber")) + #expect(decimal.description == "3.14") } - func testStringNoMatch() { + @Test func stringNoMatch() { // This test makes sure Decimal returns nil // if the does not start with a number var notDecimal = Decimal(string: "A Flamingo's head has to be upside down when it eats.") - XCTAssertNil(notDecimal) + #expect(notDecimal == nil) // Same if the number does not appear at the beginning notDecimal = Decimal(string: "Jump 22 Street") - XCTAssertNil(notDecimal) + #expect(notDecimal == nil) } - func testNormalize() throws { + @Test func normalize() throws { var one = Decimal(1) var ten = Decimal(-10) var lossPrecision = try Decimal._normalize(a: &one, b: &ten, roundingMode: .plain) - XCTAssertFalse(lossPrecision) - XCTAssertEqual(Decimal(1), one) - XCTAssertEqual(Decimal(-10), ten) - XCTAssertEqual(1, one._length) - XCTAssertEqual(1, ten._length) + #expect(!lossPrecision) + #expect(Decimal(1) == one) + #expect(Decimal(-10) == ten) + #expect(1 == one._length) + #expect(1 == ten._length) one = Decimal(1) ten = Decimal(10) lossPrecision = try Decimal._normalize(a: &one, b: &ten, roundingMode: .plain) - XCTAssertFalse(lossPrecision) - XCTAssertEqual(Decimal(1), one) - XCTAssertEqual(Decimal(10), ten) - XCTAssertEqual(1, one._length) - XCTAssertEqual(1, ten._length) + #expect(!lossPrecision) + #expect(Decimal(1) == one) + #expect(Decimal(10) == ten) + #expect(1 == one._length) + #expect(1 == ten._length) // Normalise with loss of precision - let a = try XCTUnwrap(Decimal(string: "498.7509045")) - let b = try XCTUnwrap(Decimal(string: "8.453441368210501065891847765109162027")) + let a = try #require(Decimal(string: "498.7509045")) + let b = try #require(Decimal(string: "8.453441368210501065891847765109162027")) var aNormalized = a var bNormalized = b lossPrecision = try Decimal._normalize( a: &aNormalized, b: &bNormalized, roundingMode: .plain) - XCTAssertTrue(lossPrecision) - - XCTAssertEqual(aNormalized.exponent, -31) - XCTAssertEqual(aNormalized._mantissa.0, 0) - XCTAssertEqual(aNormalized._mantissa.1, 21760) - XCTAssertEqual(aNormalized._mantissa.2, 45355) - XCTAssertEqual(aNormalized._mantissa.3, 11455) - XCTAssertEqual(aNormalized._mantissa.4, 62709) - XCTAssertEqual(aNormalized._mantissa.5, 14050) - XCTAssertEqual(aNormalized._mantissa.6, 62951) - XCTAssertEqual(aNormalized._mantissa.7, 0) - XCTAssertEqual(bNormalized.exponent, -31) - XCTAssertEqual(bNormalized._mantissa.0, 56467) - XCTAssertEqual(bNormalized._mantissa.1, 17616) - XCTAssertEqual(bNormalized._mantissa.2, 59987) - XCTAssertEqual(bNormalized._mantissa.3, 21635) - XCTAssertEqual(bNormalized._mantissa.4, 5988) - XCTAssertEqual(bNormalized._mantissa.5, 63852) - XCTAssertEqual(bNormalized._mantissa.6, 1066) - XCTAssertEqual(bNormalized._length, 7) - XCTAssertEqual(a, aNormalized) - XCTAssertNotEqual(b, bNormalized) // b had a loss Of Precision when normalising + #expect(lossPrecision) + + #expect(aNormalized.exponent == -31) + #expect(aNormalized._mantissa.0 == 0) + #expect(aNormalized._mantissa.1 == 21760) + #expect(aNormalized._mantissa.2 == 45355) + #expect(aNormalized._mantissa.3 == 11455) + #expect(aNormalized._mantissa.4 == 62709) + #expect(aNormalized._mantissa.5 == 14050) + #expect(aNormalized._mantissa.6 == 62951) + #expect(aNormalized._mantissa.7 == 0) + #expect(bNormalized.exponent == -31) + #expect(bNormalized._mantissa.0 == 56467) + #expect(bNormalized._mantissa.1 == 17616) + #expect(bNormalized._mantissa.2 == 59987) + #expect(bNormalized._mantissa.3 == 21635) + #expect(bNormalized._mantissa.4 == 5988) + #expect(bNormalized._mantissa.5 == 63852) + #expect(bNormalized._mantissa.6 == 1066) + #expect(bNormalized._length == 7) + #expect(a == aNormalized) + #expect(b != bNormalized) // b had a loss Of Precision when normalising } - func testAdditionWithNormalization() throws { + @Test func additionWithNormalization() throws { let one: Decimal = Decimal(1) var addend: Decimal = one // 2 digits @@ -404,7 +344,7 @@ final class DecimalTests : XCTestCase { expected._exponent = -1 expected._length = 1 expected._mantissa.0 = 11 - XCTAssertTrue(Decimal._compare(lhs: result, rhs: expected) == .orderedSame) + #expect(Decimal._compare(lhs: result, rhs: expected) == .orderedSame) // 38 digits addend._exponent = -37 expected._exponent = -37; @@ -418,7 +358,7 @@ final class DecimalTests : XCTestCase { expected._mantissa.6 = 0xee10; expected._mantissa.7 = 0x0785; (result, _) = try one._add(rhs: addend, roundingMode: .plain) - XCTAssertTrue(Decimal._compare(lhs: expected, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: expected, rhs: result) == .orderedSame) // 39 Digits -- not guaranteed to work addend._exponent = -38 (result, lostPrecision) = try one._add(rhs: addend, roundingMode: .plain) @@ -433,19 +373,19 @@ final class DecimalTests : XCTestCase { expected._mantissa.5 = 0x5a86; expected._mantissa.6 = 0x4ca8; expected._mantissa.7 = 0x4b3b; - XCTAssertTrue(Decimal._compare(lhs: expected, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: expected, rhs: result) == .orderedSame) } else { - XCTAssertTrue(Decimal._compare(lhs: one, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: one, rhs: result) == .orderedSame) } // 40 Digits -- does NOT work, make sure we round addend._exponent = -39 (result, lostPrecision) = try one._add(rhs: addend, roundingMode: .plain) - XCTAssertTrue(lostPrecision) - XCTAssertEqual("1", result.description) - XCTAssertTrue(Decimal._compare(lhs: one, rhs: result) == .orderedSame) + #expect(lostPrecision) + #expect("1" == result.description) + #expect(Decimal._compare(lhs: one, rhs: result) == .orderedSame) } - func testSimpleMultiplication() throws { + @Test func simpleMultiplication() throws { var multiplicand = Decimal() multiplicand._isNegative = 0 multiplicand._isCompact = 0 @@ -469,12 +409,12 @@ final class DecimalTests : XCTestCase { let result = try multiplicand._multiply( by: multiplier, roundingMode: .plain ) - XCTAssertTrue(Decimal._compare(lhs: expected, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: expected, rhs: result) == .orderedSame) } } } - func testNegativeAndZeroMultiplication() throws { + @Test func negativeAndZeroMultiplication() throws { let one = Decimal(1) let zero = Decimal(0) var negativeOne = one @@ -482,25 +422,25 @@ final class DecimalTests : XCTestCase { // 1 * 1 var result = try one._multiply(by: one, roundingMode: .plain) - XCTAssertTrue(Decimal._compare(lhs: one, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: one, rhs: result) == .orderedSame) // 1 * -1 result = try one._multiply(by: negativeOne, roundingMode: .plain) - XCTAssertTrue(Decimal._compare(lhs: negativeOne, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: negativeOne, rhs: result) == .orderedSame) // -1 * 1 result = try negativeOne._multiply(by: one, roundingMode: .plain) - XCTAssertTrue(Decimal._compare(lhs: negativeOne, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: negativeOne, rhs: result) == .orderedSame) // -1 * -1 result = try negativeOne._multiply(by: negativeOne, roundingMode: .plain) - XCTAssertTrue(Decimal._compare(lhs: one, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: one, rhs: result) == .orderedSame) // 1 * 0 result = try one._multiply(by: zero, roundingMode: .plain) - XCTAssertTrue(Decimal._compare(lhs: zero, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: zero, rhs: result) == .orderedSame) // 0 * 1 result = try zero._multiply(by: negativeOne, roundingMode: .plain) - XCTAssertTrue(Decimal._compare(lhs: zero, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: zero, rhs: result) == .orderedSame) } - func testMultiplicationOverflow() throws { + @Test func multiplicationOverflow() throws { let multiplicand = Decimal( _exponent: 0, _length: 8, @@ -523,70 +463,50 @@ final class DecimalTests : XCTestCase { // The following should throw .overlow multiplier._exponent = 0x7F - do { + #expect { // 2e127 * max_mantissa _ = try multiplicand._multiply( by: multiplier, roundingMode: .plain) - XCTFail("Expected _CalculationError.overflow to be thrown") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } - do { + #expect { // max_mantissa * 2e127 _ = try multiplier._multiply( by: multiplicand, roundingMode: .plain) - XCTFail("Expected _CalculationError.overflow to be thrown") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } } - func testMultiplyByPowerOfTen() throws { + @Test func multiplyByPowerOfTen() throws { let a = Decimal(1234) var result = try a._multiplyByPowerOfTen(power: 1, roundingMode: .plain) - XCTAssertEqual(result, Decimal(12340)) + #expect(result == Decimal(12340)) result = try a._multiplyByPowerOfTen(power: 2, roundingMode: .plain) - XCTAssertEqual(result, Decimal(123400)) + #expect(result == Decimal(123400)) result = try a._multiplyByPowerOfTen(power: 0, roundingMode: .plain) - XCTAssertEqual(result, Decimal(1234)) + #expect(result == Decimal(1234)) result = try a._multiplyByPowerOfTen(power: -2, roundingMode: .plain) - XCTAssertEqual(result, Decimal(12.34)) + #expect(result == Decimal(12.34)) // Overflow - do { + #expect { _ = try a._multiplyByPowerOfTen(power: 128, roundingMode: .plain) - XCTFail("Expected overflow to have been thrown") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } // Underflow - do { + #expect { _ = try Decimal(12.34)._multiplyByPowerOfTen(power: -128, roundingMode: .plain) - XCTFail("Expected underflow to have been thrown") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .underflow) + } throws: { + ($0 as? Decimal._CalculationError) == .underflow } } - func testRepeatingDivision() throws { + @Test func repeatingDivision() throws { let repeatingNumerator = Decimal(16) let repeatingDenominator = Decimal(9) let repeating = try repeatingNumerator._divide( @@ -610,12 +530,12 @@ final class DecimalTests : XCTestCase { expected._mantissa.5 = 55436 expected._mantissa.6 = 45186 expected._mantissa.7 = 10941 - XCTAssertTrue(Decimal._compare(lhs: expected, rhs: result) == .orderedSame) + #expect(Decimal._compare(lhs: expected, rhs: result) == .orderedSame) } #if _pointerBitWidth(_64) // This test require Int to be Int64 - func testCrashingDivision() throws { + @Test func crashingDivision() throws { // This test makes sure the following division // does not crash let first: Decimal = Decimal(1147858867) @@ -638,33 +558,33 @@ final class DecimalTests : XCTestCase { 5147 ) ) - XCTAssertEqual(result, expected) + #expect(result == expected) } #endif - func testPower() throws { + @Test func power() throws { var a = Decimal(1234) var result = try a._power(exponent: 0, roundingMode: .plain) - XCTAssert(Decimal._compare(lhs: result, rhs: Decimal(1)) == .orderedSame) + #expect(Decimal._compare(lhs: result, rhs: Decimal(1)) == .orderedSame) a = Decimal(8) result = try a._power(exponent: 2, roundingMode: .plain) - XCTAssert(Decimal._compare(lhs: result, rhs: Decimal(64)) == .orderedSame) + #expect(Decimal._compare(lhs: result, rhs: Decimal(64)) == .orderedSame) a = Decimal(-2) result = try a._power(exponent: 3, roundingMode: .plain) - XCTAssert(Decimal._compare(lhs: result, rhs: Decimal(-8)) == .orderedSame) + #expect(Decimal._compare(lhs: result, rhs: Decimal(-8)) == .orderedSame) result = try a._power(exponent: 0, roundingMode: .plain) - XCTAssert(Decimal._compare(lhs: result, rhs: Decimal(1)) == .orderedSame) + #expect(Decimal._compare(lhs: result, rhs: Decimal(1)) == .orderedSame) // Positive base let six = Decimal(6) for exponent in 1 ..< 10 { result = try six._power(exponent: exponent, roundingMode: .plain) - XCTAssertEqual(result.doubleValue, pow(6.0, Double(exponent))) + #expect(result.doubleValue == pow(6.0, Double(exponent))) } // Negative base let negativeSix = Decimal(-6) for exponent in 1 ..< 10 { result = try negativeSix._power(exponent: exponent, roundingMode: .plain) - XCTAssertEqual(result.doubleValue, pow(-6.0, Double(exponent))) + #expect(result.doubleValue == pow(-6.0, Double(exponent))) } for i in -2 ... 10 { for j in 0 ... 5 { @@ -673,169 +593,119 @@ final class DecimalTests : XCTestCase { exponent: j, roundingMode: .plain ) let expected = Decimal(pow(Double(i), Double(j))) - XCTAssertEqual(expected, result, "\(result) == \(i)^\(j)") + #expect(expected == result, "\(result) == \(i)^\(j)") } } } - func testNaNInput() throws { + @Test func nanInput() throws { let nan = Decimal.nan let one = Decimal(1) - do { + #expect { // NaN + 1 _ = try nan._add(rhs: one, roundingMode: .plain) - XCTFail("Expected to throw error") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } - do { + #expect { // 1 + NaN _ = try one._add(rhs: nan, roundingMode: .plain) - XCTFail("Expected to throw error") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } - do { + #expect { // NaN - 1 _ = try nan._subtract(rhs: one, roundingMode: .plain) - XCTFail("Expected to throw error") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } - do { + #expect { // 1 - NaN _ = try one._subtract(rhs: nan, roundingMode: .plain) - XCTFail("Expected to throw error") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } - do { + #expect { // NaN * 1 _ = try nan._multiply(by: one, roundingMode: .plain) - XCTFail("Expected to throw error") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } - do { + #expect { // 1 * NaN _ = try one._multiply(by: nan, roundingMode: .plain) - XCTFail("Expected to throw error") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } - do { + #expect { // NaN / 1 _ = try nan._divide(by: one, roundingMode: .plain) - XCTFail("Expected to throw error") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } - do { + #expect { // 1 / NaN _ = try one._divide(by: nan, roundingMode: .plain) - XCTFail("Expected to throw error") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } - do { + #expect { // NaN ^ 0 _ = try nan._power(exponent: 0, roundingMode: .plain) - XCTFail("Expected to throw error") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } - do { + #expect { // NaN ^ 1 _ = try nan._power(exponent: 1, roundingMode: .plain) - XCTFail("Expected to throw error") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Wrong error thrown") - return - } - XCTAssertEqual(calculationError, .overflow) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } // Overflow doubles - XCTAssertTrue(Decimal(Double.leastNonzeroMagnitude).isNaN) - XCTAssertTrue(Decimal(Double.leastNormalMagnitude).isNaN) - XCTAssertTrue(Decimal(Double.greatestFiniteMagnitude).isNaN) - XCTAssertTrue(Decimal(Double("1e-129")!).isNaN) - XCTAssertTrue(Decimal(Double("0.1e-128")!).isNaN) + #expect(Decimal(Double.leastNonzeroMagnitude).isNaN) + #expect(Decimal(Double.leastNormalMagnitude).isNaN) + #expect(Decimal(Double.greatestFiniteMagnitude).isNaN) + #expect(Decimal(Double("1e-129")!).isNaN) + #expect(Decimal(Double("0.1e-128")!).isNaN) } - func testDecimalRoundBankers() throws { + @Test func roundBankers() throws { let onePointTwo = Decimal(1.2) var result = try onePointTwo._round(scale: 1, roundingMode: .bankers) - XCTAssertEqual(1.2, result.doubleValue, accuracy: 0.0001) + #expect((1.1009 ... 1.2001).contains(result.doubleValue)) let onePointTwoOne = Decimal(1.21) result = try onePointTwoOne._round(scale: 1, roundingMode: .bankers) - XCTAssertEqual(1.2, result.doubleValue, accuracy: 0.0001) + #expect((1.1009 ... 1.2001).contains(result.doubleValue)) let onePointTwoFive = Decimal(1.25) result = try onePointTwoFive._round(scale: 1, roundingMode: .bankers) - XCTAssertEqual(1.2, result.doubleValue, accuracy: 0.0001) + #expect((1.1009 ... 1.2001).contains(result.doubleValue)) let onePointThreeFive = Decimal(1.35) result = try onePointThreeFive._round(scale: 1, roundingMode: .bankers) - XCTAssertEqual(1.4, result.doubleValue, accuracy: 0.0001) + #expect((1.3009 ... 1.4001).contains(result.doubleValue)) let onePointTwoSeven = Decimal(1.27) result = try onePointTwoSeven._round(scale: 1, roundingMode: .bankers) - XCTAssertEqual(1.3, result.doubleValue, accuracy: 0.0001) + #expect((1.2009 ... 3.2001).contains(result.doubleValue)) let minusEightPointFourFive = Decimal(-8.45) result = try minusEightPointFourFive._round(scale: 1, roundingMode: .bankers) - XCTAssertEqual(-8.4, result.doubleValue, accuracy: 0.0001) + #expect((-8.4001 ... -8.3009).contains(result.doubleValue)) let minusFourPointNineEightFive = Decimal(-4.985) result = try minusFourPointNineEightFive._round(scale: 2, roundingMode: .bankers) - XCTAssertEqual(-4.98, result.doubleValue, accuracy: 0.0001) + #expect((-4.9801 ... -4.9709).contains(result.doubleValue)) } - func test_Round() throws { + @Test func round() throws { let testCases: [(Double, Double, Int, Decimal.RoundingMode)] = [ // expected, start, scale, round ( 0, 0.5, 0, .down ), @@ -862,16 +732,16 @@ final class DecimalTests : XCTestCase { let (expected, start, scale, mode) = testCase let num = Decimal(start) let actual = try num._round(scale: scale, roundingMode: mode) - XCTAssertEqual(Decimal(expected), actual, "Failed test case: \(testCase)") + #expect(Decimal(expected) == actual, "Failed test case: \(testCase)") } } - func test_Maths() { + @Test func maths() { for i in -2...10 { for j in 0...5 { - XCTAssertEqual(Decimal(i*j), Decimal(i) * Decimal(j), "\(Decimal(i*j)) == \(i) * \(j)") - XCTAssertEqual(Decimal(i+j), Decimal(i) + Decimal(j), "\(Decimal(i+j)) == \(i)+\(j)") - XCTAssertEqual(Decimal(i-j), Decimal(i) - Decimal(j), "\(Decimal(i-j)) == \(i)-\(j)") + #expect(Decimal(i*j) == Decimal(i) * Decimal(j), "\(Decimal(i*j)) == \(i) * \(j)") + #expect(Decimal(i+j) == Decimal(i) + Decimal(j), "\(Decimal(i+j)) == \(i)+\(j)") + #expect(Decimal(i-j) == Decimal(i) - Decimal(j), "\(Decimal(i-j)) == \(i)-\(j)") if j != 0 { let approximation = Decimal(Double(i)/Double(j)) let answer = Decimal(i) / Decimal(j) @@ -893,185 +763,175 @@ final class DecimalTests : XCTestCase { } count += 1 } - XCTAssertFalse(failed, "\(Decimal(i/j)) == \(i)/\(j)") + #expect(!failed, "\(Decimal(i/j)) == \(i)/\(j)") } } } - XCTAssertEqual(Decimal(186243 * 15673 as Int64), Decimal(186243) * Decimal(15673)) + #expect(Decimal(186243 * 15673 as Int64) == Decimal(186243) * Decimal(15673)) - XCTAssertEqual(Decimal(string: "5538")! + Decimal(string: "2880.4")!, Decimal(string: "8418.4")!) + #expect(Decimal(string: "5538")! + Decimal(string: "2880.4")! == Decimal(string: "8418.4")!) - XCTAssertEqual(Decimal(string: "5538.0")! - Decimal(string: "2880.4")!, Decimal(string: "2657.6")!) - XCTAssertEqual(Decimal(string: "2880.4")! - Decimal(5538), Decimal(string: "-2657.6")!) - XCTAssertEqual(Decimal(0x10000) - Decimal(0x1000), Decimal(0xf000)) + #expect(Decimal(string: "5538.0")! - Decimal(string: "2880.4")! == Decimal(string: "2657.6")!) + #expect(Decimal(string: "2880.4")! - Decimal(5538) == Decimal(string: "-2657.6")!) + #expect(Decimal(0x10000) - Decimal(0x1000) == Decimal(0xf000)) #if !os(watchOS) - XCTAssertEqual(Decimal(0x1_0000_0000) - Decimal(0x1000), Decimal(0xFFFFF000)) - XCTAssertEqual(Decimal(0x1_0000_0000_0000) - Decimal(0x1000), Decimal(0xFFFFFFFFF000)) + #expect(Decimal(0x1_0000_0000) - Decimal(0x1000) == Decimal(0xFFFFF000)) + #expect(Decimal(0x1_0000_0000_0000) - Decimal(0x1000) == Decimal(0xFFFFFFFFF000)) #endif - XCTAssertEqual(Decimal(1234_5678_9012_3456_7899 as UInt64) - Decimal(1234_5678_9012_3456_7890 as UInt64), Decimal(9)) - XCTAssertEqual(Decimal(0xffdd_bb00_8866_4422 as UInt64) - Decimal(0x7777_7777), Decimal(0xFFDD_BB00_10EE_CCAB as UInt64)) + #expect(Decimal(1234_5678_9012_3456_7899 as UInt64) - Decimal(1234_5678_9012_3456_7890 as UInt64) == Decimal(9)) + #expect(Decimal(0xffdd_bb00_8866_4422 as UInt64) - Decimal(0x7777_7777) == Decimal(0xFFDD_BB00_10EE_CCAB as UInt64)) let highBit = Decimal(_exponent: 0, _length: 8, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8000)) let otherBits = Decimal(_exponent: 0, _length: 8, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x7fff)) - XCTAssertEqual(highBit - otherBits, Decimal(1)) - XCTAssertEqual(otherBits + Decimal(1), highBit) + #expect(highBit - otherBits == Decimal(1)) + #expect(otherBits + Decimal(1) == highBit) } - func testMisc() throws { - XCTAssertEqual(Decimal(-5.2).sign, .minus) - XCTAssertEqual(Decimal(5.2).sign, .plus) + @Test func misc() throws { + #expect(Decimal(-5.2).sign == .minus) + #expect(Decimal(5.2).sign == .plus) var d = Decimal(5.2) - XCTAssertEqual(d.sign, .plus) + #expect(d.sign == .plus) d.negate() - XCTAssertEqual(d.sign, .minus) + #expect(d.sign == .minus) d.negate() - XCTAssertEqual(d.sign, .plus) + #expect(d.sign == .plus) var e = Decimal(0) e.negate() - XCTAssertEqual(e, Decimal(0)) - XCTAssertTrue(Decimal(3.5).isEqual(to: Decimal(3.5))) - XCTAssertTrue(Decimal.nan.isEqual(to: Decimal.nan)) - XCTAssertTrue(Decimal(1.28).isLess(than: Decimal(2.24))) - XCTAssertFalse(Decimal(2.28).isLess(than: Decimal(2.24))) - XCTAssertTrue(Decimal(1.28).isTotallyOrdered(belowOrEqualTo: Decimal(2.24))) - XCTAssertFalse(Decimal(2.28).isTotallyOrdered(belowOrEqualTo: Decimal(2.24))) - XCTAssertTrue(Decimal(1.2).isTotallyOrdered(belowOrEqualTo: Decimal(1.2))) - XCTAssertTrue(Decimal.nan.isEqual(to: Decimal.nan)) - XCTAssertTrue(Decimal.nan.isLess(than: Decimal(0))) - XCTAssertFalse(Decimal.nan.isLess(than: Decimal.nan)) - XCTAssertTrue(Decimal.nan.isLessThanOrEqualTo(Decimal(0))) - XCTAssertTrue(Decimal.nan.isLessThanOrEqualTo(Decimal.nan)) - XCTAssertFalse(Decimal.nan.isTotallyOrdered(belowOrEqualTo: Decimal.nan)) - XCTAssertFalse(Decimal.nan.isTotallyOrdered(belowOrEqualTo: Decimal(2.3))) - XCTAssertTrue(Decimal(2) < Decimal(3)) - XCTAssertTrue(Decimal(3) > Decimal(2)) - XCTAssertEqual(Decimal(-9), Decimal(1) - Decimal(10)) - XCTAssertEqual(Decimal(476), Decimal(1024).distance(to: Decimal(1500))) - XCTAssertEqual(Decimal(68040), Decimal(386).advanced(by: Decimal(67654))) - XCTAssertEqual(Decimal(1.234), abs(Decimal(1.234))) - XCTAssertEqual(Decimal(1.234), abs(Decimal(-1.234))) - XCTAssertTrue(Decimal.nan.magnitude.isNaN) - XCTAssertEqual(Decimal.leastFiniteMagnitude.magnitude, -Decimal.leastFiniteMagnitude) - - XCTAssertEqual(Decimal(-9), Decimal(1) - Decimal(10)) - XCTAssertEqual(Decimal(1.234), abs(Decimal(1.234))) - XCTAssertEqual(Decimal(1.234), abs(Decimal(-1.234))) - XCTAssertEqual((0 as Decimal).magnitude, 0 as Decimal) - XCTAssertEqual((1 as Decimal).magnitude, 1 as Decimal) - XCTAssertEqual((1 as Decimal).magnitude, abs(1 as Decimal)) - XCTAssertEqual((1 as Decimal).magnitude, abs(-1 as Decimal)) - XCTAssertEqual((-1 as Decimal).magnitude, abs(-1 as Decimal)) - XCTAssertEqual((-1 as Decimal).magnitude, abs(1 as Decimal)) - XCTAssertEqual(Decimal.greatestFiniteMagnitude.magnitude, Decimal.greatestFiniteMagnitude) + #expect(e == Decimal(0)) + #expect(Decimal(3.5).isEqual(to: Decimal(3.5))) + #expect(Decimal.nan.isEqual(to: Decimal.nan)) + #expect(Decimal(1.28).isLess(than: Decimal(2.24))) + #expect(!Decimal(2.28).isLess(than: Decimal(2.24))) + #expect(Decimal(1.28).isTotallyOrdered(belowOrEqualTo: Decimal(2.24))) + #expect(!Decimal(2.28).isTotallyOrdered(belowOrEqualTo: Decimal(2.24))) + #expect(Decimal(1.2).isTotallyOrdered(belowOrEqualTo: Decimal(1.2))) + #expect(Decimal.nan.isEqual(to: Decimal.nan)) + #expect(Decimal.nan.isLess(than: Decimal(0))) + #expect(!Decimal.nan.isLess(than: Decimal.nan)) + #expect(Decimal.nan.isLessThanOrEqualTo(Decimal(0))) + #expect(Decimal.nan.isLessThanOrEqualTo(Decimal.nan)) + #expect(!Decimal.nan.isTotallyOrdered(belowOrEqualTo: Decimal.nan)) + #expect(!Decimal.nan.isTotallyOrdered(belowOrEqualTo: Decimal(2.3))) + #expect(Decimal(2) < Decimal(3)) + #expect(Decimal(3) > Decimal(2)) + #expect(Decimal(-9) == Decimal(1) - Decimal(10)) + #expect(Decimal(476) == Decimal(1024).distance(to: Decimal(1500))) + #expect(Decimal(68040) == Decimal(386).advanced(by: Decimal(67654))) + #expect(Decimal(1.234) == abs(Decimal(1.234))) + #expect(Decimal(1.234) == abs(Decimal(-1.234))) + #expect(Decimal.nan.magnitude.isNaN) + #expect(Decimal.leastFiniteMagnitude.magnitude == -Decimal.leastFiniteMagnitude) + + #expect(Decimal(-9) == Decimal(1) - Decimal(10)) + #expect(Decimal(1.234) == abs(Decimal(1.234))) + #expect(Decimal(1.234) == abs(Decimal(-1.234))) + #expect((0 as Decimal).magnitude == 0 as Decimal) + #expect((1 as Decimal).magnitude == 1 as Decimal) + #expect((1 as Decimal).magnitude == abs(1 as Decimal)) + #expect((1 as Decimal).magnitude == abs(-1 as Decimal)) + #expect((-1 as Decimal).magnitude == abs(-1 as Decimal)) + #expect((-1 as Decimal).magnitude == abs(1 as Decimal)) + #expect(Decimal.greatestFiniteMagnitude.magnitude == Decimal.greatestFiniteMagnitude) var a = Decimal(1234) var result = try a._multiplyByPowerOfTen(power: 1, roundingMode: .plain) - XCTAssertEqual(Decimal(12340), result) + #expect(Decimal(12340) == result) a = Decimal(1234) result = try a._multiplyByPowerOfTen(power: 2, roundingMode: .plain) - XCTAssertEqual(Decimal(123400), result) + #expect(Decimal(123400) == result) a = result - do { + #expect { result = try a._multiplyByPowerOfTen(power: 128, roundingMode: .plain) - XCTFail("Expected to throw _CalcuationError.overflow") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Expected Decimal._CalculationError, got \(error)") - return - } - XCTAssertEqual(.overflow, calculationError) + } throws: { + ($0 as? Decimal._CalculationError) == .overflow } a = Decimal(1234) result = try a._multiplyByPowerOfTen(power: -2, roundingMode: .plain) - XCTAssertEqual(Decimal(12.34), result) + #expect(Decimal(12.34) == result) a = result - do { + #expect { result = try a._multiplyByPowerOfTen(power: -128, roundingMode: .plain) - XCTFail("Expected to throw _CalcuationError.underflow") - } catch { - guard let calculationError = error as? Decimal._CalculationError else { - XCTFail("Expected Decimal._CalculationError, got \(error)") - return - } - XCTAssertEqual(.underflow, calculationError) + } throws: { + ($0 as? Decimal._CalculationError) == .underflow } a = Decimal(1234) result = try a._power(exponent: 0, roundingMode: .plain) - XCTAssertEqual(Decimal(1), result) + #expect(Decimal(1) == result) a = Decimal(8) result = try a._power(exponent: 2, roundingMode: .plain) - XCTAssertEqual(Decimal(64), result) + #expect(Decimal(64) == result) a = Decimal(-2) result = try a._power(exponent: 3, roundingMode: .plain) - XCTAssertEqual(Decimal(-8), result) + #expect(Decimal(-8) == result) for i in -2...10 { for j in 0...5 { let power = Decimal(i) let actual = try power._power(exponent: j, roundingMode: .plain) let expected = Decimal(pow(Double(i), Double(j))) - XCTAssertEqual(expected, actual, "\(actual) == \(i)^\(j)") - XCTAssertEqual(expected, try power._power(exponent: j, roundingMode: .plain)) + #expect(expected == actual, "\(actual) == \(i)^\(j)") + #expect(try expected == power._power(exponent: j, roundingMode: .plain)) } } do { // SR-13015 - let a = try XCTUnwrap(Decimal(string: "119.993")) - let b = try XCTUnwrap(Decimal(string: "4.1565")) - let c = try XCTUnwrap(Decimal(string: "18.209")) - let d = try XCTUnwrap(Decimal(string: "258.469")) + let a = try #require(Decimal(string: "119.993")) + let b = try #require(Decimal(string: "4.1565")) + let c = try #require(Decimal(string: "18.209")) + let d = try #require(Decimal(string: "258.469")) let ab = a * b let aDivD = a / d let caDivD = c * aDivD - XCTAssertEqual(ab, try XCTUnwrap(Decimal(string: "498.7509045"))) - XCTAssertEqual(aDivD, try XCTUnwrap(Decimal(string: "0.46424522863476857959755328492004843907"))) - XCTAssertEqual(caDivD, try XCTUnwrap(Decimal(string: "8.453441368210501065891847765109162027"))) + #expect(try ab == #require(Decimal(string: "498.7509045"))) + #expect(try aDivD == #require(Decimal(string: "0.46424522863476857959755328492004843907"))) + #expect(try caDivD == #require(Decimal(string: "8.453441368210501065891847765109162027"))) let result = (a * b) + (c * (a / d)) - XCTAssertEqual(result, try XCTUnwrap(Decimal(string: "507.2043458682105010658918477651091"))) + #expect(try result == #require(Decimal(string: "507.2043458682105010658918477651091"))) } } - func test_Constants() { + @Test func constants() { let smallest = Decimal(_exponent: 127, _length: 8, _isNegative: 1, _isCompact: 1, _reserved: 0, _mantissa: (UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max)) - XCTAssertEqual(smallest, Decimal.leastFiniteMagnitude) + #expect(smallest == Decimal.leastFiniteMagnitude) let biggest = Decimal(_exponent: 127, _length: 8, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max, UInt16.max)) - XCTAssertEqual(biggest, Decimal.greatestFiniteMagnitude) + #expect(biggest == Decimal.greatestFiniteMagnitude) let leastNormal = Decimal(_exponent: -127, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (1, 0, 0, 0, 0, 0, 0, 0)) - XCTAssertEqual(leastNormal, Decimal.leastNormalMagnitude) + #expect(leastNormal == Decimal.leastNormalMagnitude) let leastNonzero = Decimal(_exponent: -127, _length: 1, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (1, 0, 0, 0, 0, 0, 0, 0)) - XCTAssertEqual(leastNonzero, Decimal.leastNonzeroMagnitude) + #expect(leastNonzero == Decimal.leastNonzeroMagnitude) let pi = Decimal(_exponent: -38, _length: 8, _isNegative: 0, _isCompact: 1, _reserved: 0, _mantissa: (0x6623, 0x7d57, 0x16e7, 0xad0d, 0xaf52, 0x4641, 0xdfa7, 0xec58)) - XCTAssertEqual(pi, Decimal.pi) - XCTAssertEqual(10, Decimal.radix) - XCTAssertTrue(Decimal().isCanonical) - XCTAssertFalse(Decimal().isSignalingNaN) - XCTAssertFalse(Decimal.nan.isSignalingNaN) - XCTAssertTrue(Decimal.nan.isNaN) - XCTAssertEqual(.quietNaN, Decimal.nan.floatingPointClass) - XCTAssertEqual(.positiveZero, Decimal().floatingPointClass) - XCTAssertEqual(.negativeNormal, smallest.floatingPointClass) - XCTAssertEqual(.positiveNormal, biggest.floatingPointClass) - XCTAssertFalse(Double.nan.isFinite) - XCTAssertFalse(Double.nan.isInfinite) + #expect(pi == Decimal.pi) + #expect(10 == Decimal.radix) + #expect(Decimal().isCanonical) + #expect(!Decimal().isSignalingNaN) + #expect(!Decimal.nan.isSignalingNaN) + #expect(Decimal.nan.isNaN) + #expect(.quietNaN == Decimal.nan.floatingPointClass) + #expect(.positiveZero == Decimal().floatingPointClass) + #expect(.negativeNormal == smallest.floatingPointClass) + #expect(.positiveNormal == biggest.floatingPointClass) + #expect(!Double.nan.isFinite) + #expect(!Double.nan.isInfinite) } - func test_parseDouble() throws { - XCTAssertEqual(Decimal(Double(0.0)), Decimal(Int.zero)) - XCTAssertEqual(Decimal(Double(-0.0)), Decimal(Int.zero)) + @Test func parseDouble() throws { + #expect(Decimal(Double(0.0)) == Decimal(Int.zero)) + #expect(Decimal(Double(-0.0)) == Decimal(Int.zero)) // These values can only be represented as Decimal.nan - XCTAssertEqual(Decimal(Double.nan), Decimal.nan) - XCTAssertEqual(Decimal(Double.signalingNaN), Decimal.nan) + #expect(Decimal(Double.nan) == Decimal.nan) + #expect(Decimal(Double.signalingNaN) == Decimal.nan) // These values are out out range for Decimal - XCTAssertEqual(Decimal(-Double.leastNonzeroMagnitude), Decimal.nan) - XCTAssertEqual(Decimal(Double.leastNonzeroMagnitude), Decimal.nan) - XCTAssertEqual(Decimal(-Double.leastNormalMagnitude), Decimal.nan) - XCTAssertEqual(Decimal(Double.leastNormalMagnitude), Decimal.nan) - XCTAssertEqual(Decimal(-Double.greatestFiniteMagnitude), Decimal.nan) - XCTAssertEqual(Decimal(Double.greatestFiniteMagnitude), Decimal.nan) + #expect(Decimal(-Double.leastNonzeroMagnitude) == Decimal.nan) + #expect(Decimal(Double.leastNonzeroMagnitude) == Decimal.nan) + #expect(Decimal(-Double.leastNormalMagnitude) == Decimal.nan) + #expect(Decimal(Double.leastNormalMagnitude) == Decimal.nan) + #expect(Decimal(-Double.greatestFiniteMagnitude) == Decimal.nan) + #expect(Decimal(Double.greatestFiniteMagnitude) == Decimal.nan) // SR-13837 let testDoubles: [(Double, String)] = [ @@ -1098,124 +958,116 @@ final class DecimalTests : XCTestCase { ] for (d, s) in testDoubles { - XCTAssertEqual(Decimal(d), Decimal(string: s)) - XCTAssertEqual(Decimal(d).description, try XCTUnwrap(Decimal(string: s)).description) + #expect(Decimal(d) == Decimal(string: s)) + #expect(try Decimal(d).description == #require(Decimal(string: s)).description) } } - func test_initExactly() { + @Test func initExactly() throws { // This really requires some tests using a BinaryInteger of bitwidth > 128 to test failures. - let d1 = Decimal(exactly: UInt64.max) - XCTAssertNotNil(d1) - XCTAssertEqual(d1?.description, UInt64.max.description) - XCTAssertEqual(d1?._length, 4) - - let d2 = Decimal(exactly: Int64.min) - XCTAssertNotNil(d2) - XCTAssertEqual(d2?.description, Int64.min.description) - XCTAssertEqual(d2?._length, 4) - - let d3 = Decimal(exactly: Int64.max) - XCTAssertNotNil(d3) - XCTAssertEqual(d3?.description, Int64.max.description) - XCTAssertEqual(d3?._length, 4) - - let d4 = Decimal(exactly: Int32.min) - XCTAssertNotNil(d4) - XCTAssertEqual(d4?.description, Int32.min.description) - XCTAssertEqual(d4?._length, 2) - - let d5 = Decimal(exactly: Int32.max) - XCTAssertNotNil(d5) - XCTAssertEqual(d5?.description, Int32.max.description) - XCTAssertEqual(d5?._length, 2) - - let d6 = Decimal(exactly: 0) - XCTAssertNotNil(d6) - XCTAssertEqual(d6, Decimal.zero) - XCTAssertEqual(d6?.description, "0") - XCTAssertEqual(d6?._length, 0) - - let d7 = Decimal(exactly: 1) - XCTAssertNotNil(d7) - XCTAssertEqual(d7?.description, "1") - XCTAssertEqual(d7?._length, 1) - - let d8 = Decimal(exactly: -1) - XCTAssertNotNil(d8) - XCTAssertEqual(d8?.description, "-1") - XCTAssertEqual(d8?._length, 1) + let d1 = try #require(Decimal(exactly: UInt64.max)) + #expect(d1.description == UInt64.max.description) + #expect(d1._length == 4) + + let d2 = try #require(Decimal(exactly: Int64.min)) + #expect(d2.description == Int64.min.description) + #expect(d2._length == 4) + + let d3 = try #require(Decimal(exactly: Int64.max)) + #expect(d3.description == Int64.max.description) + #expect(d3._length == 4) + + let d4 = try #require(Decimal(exactly: Int32.min)) + #expect(d4.description == Int32.min.description) + #expect(d4._length == 2) + + let d5 = try #require(Decimal(exactly: Int32.max)) + #expect(d5.description == Int32.max.description) + #expect(d5._length == 2) + + let d6 = try #require(Decimal(exactly: 0)) + #expect(d6 == Decimal.zero) + #expect(d6.description == "0") + #expect(d6._length == 0) + + let d7 = try #require(Decimal(exactly: 1)) + #expect(d7.description == "1") + #expect(d7._length == 1) + + let d8 = try #require(Decimal(exactly: -1)) + #expect(d8.description == "-1") + #expect(d8._length == 1) } - func test_Strideable() { + @Test func strideable() { let x = 42 as Decimal - XCTAssertEqual(x.distance(to: 43), 1) - XCTAssertEqual(x.advanced(by: 1), 43) - XCTAssertEqual(x.distance(to: 41), -1) - XCTAssertEqual(x.advanced(by: -1), 41) + #expect(x.distance(to: 43) == 1) + #expect(x.advanced(by: 1) == 43) + #expect(x.distance(to: 41) == -1) + #expect(x.advanced(by: -1) == 41) } - func test_Significand() { + @Test func significand() { var x = -42 as Decimal - XCTAssertEqual(x.significand.sign, .plus) + #expect(x.significand.sign == .plus) var y = Decimal(sign: .plus, exponent: 0, significand: x) #if FOUNDATION_FRAMEWORK if Decimal.compatibility1 { - XCTAssertEqual(y, 42) + #expect(y == 42) y = Decimal(sign: .minus, exponent: 0, significand: x) - XCTAssertEqual(y, -42) + #expect(y == -42) } else { - XCTAssertEqual(y, -42) + #expect(y == -42) y = Decimal(sign: .minus, exponent: 0, significand: x) - XCTAssertEqual(y, 42) + #expect(y == 42) } #else - XCTAssertEqual(y, -42) + #expect(y == -42) y = Decimal(sign: .minus, exponent: 0, significand: x) - XCTAssertEqual(y, 42) + #expect(y == 42) #endif x = 42 as Decimal - XCTAssertEqual(x.significand.sign, .plus) + #expect(x.significand.sign == .plus) y = Decimal(sign: .plus, exponent: 0, significand: x) - XCTAssertEqual(y, 42) + #expect(y == 42) y = Decimal(sign: .minus, exponent: 0, significand: x) - XCTAssertEqual(y, -42) + #expect(y == -42) let a = Decimal.leastNonzeroMagnitude - XCTAssertEqual(Decimal(sign: .plus, exponent: -10, significand: a), 0) - XCTAssertEqual(Decimal(sign: .plus, exponent: .min, significand: a), 0) + #expect(Decimal(sign: .plus, exponent: -10, significand: a) == 0) + #expect(Decimal(sign: .plus, exponent: .min, significand: a) == 0) let b = Decimal.greatestFiniteMagnitude - XCTAssertTrue(Decimal(sign: .plus, exponent: 10, significand: b).isNaN) - XCTAssertTrue(Decimal(sign: .plus, exponent: .max, significand: b).isNaN) + #expect(Decimal(sign: .plus, exponent: 10, significand: b).isNaN) + #expect(Decimal(sign: .plus, exponent: .max, significand: b).isNaN) } - func test_ULP() { + @Test func ULP() { var x = 0.1 as Decimal - XCTAssertFalse(x.ulp > x) + #expect(!(x.ulp > x)) x = .nan - XCTAssertTrue(x.ulp.isNaN) - XCTAssertTrue(x.nextDown.isNaN) - XCTAssertTrue(x.nextUp.isNaN) + #expect(x.ulp.isNaN) + #expect(x.nextDown.isNaN) + #expect(x.nextUp.isNaN) x = .greatestFiniteMagnitude - XCTAssertEqual(x.ulp, Decimal(string: "1e127")!) - XCTAssertEqual(x.nextDown, x - Decimal(string: "1e127")!) - XCTAssertTrue(x.nextUp.isNaN) + #expect(x.ulp == Decimal(string: "1e127")!) + #expect(x.nextDown == x - Decimal(string: "1e127")!) + #expect(x.nextUp.isNaN) // '4' is an important value to test because the max supported // significand of this type is not 10 ** 38 - 1 but rather 2 ** 128 - 1, // for which reason '4.ulp' is not equal to '1.ulp' despite having the // same decimal exponent. x = 4 - XCTAssertEqual(x.ulp, Decimal(string: "1e-37")!) - XCTAssertEqual(x.nextDown, x - Decimal(string: "1e-37")!) - XCTAssertEqual(x.nextUp, x + Decimal(string: "1e-37")!) - XCTAssertEqual(x.nextDown.nextUp, x) - XCTAssertEqual(x.nextUp.nextDown, x) - XCTAssertNotEqual(x.nextDown, x) - XCTAssertNotEqual(x.nextUp, x) + #expect(x.ulp == Decimal(string: "1e-37")!) + #expect(x.nextDown == x - Decimal(string: "1e-37")!) + #expect(x.nextUp == x + Decimal(string: "1e-37")!) + #expect(x.nextDown.nextUp == x) + #expect(x.nextUp.nextDown == x) + #expect(x.nextDown != x) + #expect(x.nextUp != x) // For similar reasons, '3.40282366920938463463374607431768211455', // which has the same significand as 'Decimal.greatestFiniteMagnitude', @@ -1223,85 +1075,75 @@ final class DecimalTests : XCTestCase { // representable value is more than 'ulp' and instead requires // incrementing '_exponent'. x = Decimal(string: "3.40282366920938463463374607431768211455")! - XCTAssertEqual(x.ulp, Decimal(string: "0.00000000000000000000000000000000000001")!) - XCTAssertEqual(x.nextUp, Decimal(string: "3.4028236692093846346337460743176821146")!) + #expect(x.ulp == Decimal(string: "0.00000000000000000000000000000000000001")!) + #expect(x.nextUp == Decimal(string: "3.4028236692093846346337460743176821146")!) x = Decimal(string: "3.4028236692093846346337460743176821146")! - XCTAssertEqual(x.ulp, Decimal(string: "0.0000000000000000000000000000000000001")!) - XCTAssertEqual(x.nextDown, Decimal(string: "3.40282366920938463463374607431768211455")!) + #expect(x.ulp == Decimal(string: "0.0000000000000000000000000000000000001")!) + #expect(x.nextDown == Decimal(string: "3.40282366920938463463374607431768211455")!) x = 1 - XCTAssertEqual(x.ulp, Decimal(string: "1e-38")!) - XCTAssertEqual(x.nextDown, x - Decimal(string: "1e-38")!) - XCTAssertEqual(x.nextUp, x + Decimal(string: "1e-38")!) - XCTAssertEqual(x.nextDown.nextUp, x) - XCTAssertEqual(x.nextUp.nextDown, x) - XCTAssertNotEqual(x.nextDown, x) - XCTAssertNotEqual(x.nextUp, x) + #expect(x.ulp == Decimal(string: "1e-38")!) + #expect(x.nextDown == x - Decimal(string: "1e-38")!) + #expect(x.nextUp == x + Decimal(string: "1e-38")!) + #expect(x.nextDown.nextUp == x) + #expect(x.nextUp.nextDown == x) + #expect(x.nextDown != x) + #expect(x.nextUp != x) x = 0 - XCTAssertEqual(x.ulp, Decimal(string: "1e-128")!) - XCTAssertEqual(x.nextDown, -Decimal(string: "1e-128")!) - XCTAssertEqual(x.nextUp, Decimal(string: "1e-128")!) - XCTAssertEqual(x.nextDown.nextUp, x) - XCTAssertEqual(x.nextUp.nextDown, x) - XCTAssertNotEqual(x.nextDown, x) - XCTAssertNotEqual(x.nextUp, x) + #expect(x.ulp == Decimal(string: "1e-128")!) + #expect(x.nextDown == -Decimal(string: "1e-128")!) + #expect(x.nextUp == Decimal(string: "1e-128")!) + #expect(x.nextDown.nextUp == x) + #expect(x.nextUp.nextDown == x) + #expect(x.nextDown != x) + #expect(x.nextUp != x) x = -1 - XCTAssertEqual(x.ulp, Decimal(string: "1e-38")!) - XCTAssertEqual(x.nextDown, x - Decimal(string: "1e-38")!) - XCTAssertEqual(x.nextUp, x + Decimal(string: "1e-38")!) + #expect(x.ulp == Decimal(string: "1e-38")!) + #expect(x.nextDown == x - Decimal(string: "1e-38")!) + #expect(x.nextUp == x + Decimal(string: "1e-38")!) let y = x - x.ulp + x.ulp - XCTAssertEqual(x, y) - XCTAssertEqual(x.nextDown.nextUp, x) - XCTAssertEqual(x.nextUp.nextDown, x) - XCTAssertNotEqual(x.nextDown, x) - XCTAssertNotEqual(x.nextUp, x) - } - - #if FOUNDATION_FRAMEWORK - #else - func test_toString() { - let decimal = Decimal(string: "-123456.789")! - XCTAssertEqual(decimal._toString(withDecimalSeparator: "."), "-123456.789") - let en = decimal._toString(withDecimalSeparator: Locale(identifier: "en_GB").decimalSeparator!) - XCTAssertEqual(en, "-123456.789") - let fr = decimal._toString(withDecimalSeparator: Locale(identifier: "fr_FR").decimalSeparator!) - XCTAssertEqual(fr, "-123456,789") + #expect(x == y) + #expect(x.nextDown.nextUp == x) + #expect(x.nextUp.nextDown == x) + #expect(x.nextDown != x) + #expect(x.nextUp != x) } - func test_int64Value() { - XCTAssertEqual(Decimal(-1).int64Value, -1) - XCTAssertEqual(Decimal(0).int64Value, 0) - XCTAssertEqual(Decimal(1).int64Value, 1) - XCTAssertEqual(Decimal.nan.int64Value, 0) - XCTAssertEqual(Decimal(1e50).int64Value, 0) - XCTAssertEqual(Decimal(1e-50).int64Value, 0) + #if !FOUNDATION_FRAMEWORK + @Test func int64Value() { + #expect(Decimal(-1).int64Value == -1) + #expect(Decimal(0).int64Value == 0) + #expect(Decimal(1).int64Value == 1) + #expect(Decimal.nan.int64Value == 0) + #expect(Decimal(1e50).int64Value == 0) + #expect(Decimal(1e-50).int64Value == 0) - XCTAssertEqual(Decimal(UInt64.max).uint64Value, UInt64.max) - XCTAssertEqual((Decimal(UInt64.max) + 1).uint64Value, 0) - XCTAssertEqual(Decimal(Int64.max).int64Value, Int64.max) - XCTAssertEqual((Decimal(Int64.max) + 1 ).int64Value, Int64.min) - XCTAssertEqual((Decimal(Int64.max) + 1 ).uint64Value, UInt64(Int64.max) + 1) - XCTAssertEqual(Decimal(Int64.min).int64Value, Int64.min) + #expect(Decimal(UInt64.max).uint64Value == UInt64.max) + #expect((Decimal(UInt64.max) + 1).uint64Value == 0) + #expect(Decimal(Int64.max).int64Value == Int64.max) + #expect((Decimal(Int64.max) + 1 ).int64Value == Int64.min) + #expect((Decimal(Int64.max) + 1 ).uint64Value == UInt64(Int64.max) + 1) + #expect(Decimal(Int64.min).int64Value == Int64.min) - XCTAssertEqual(Decimal(Int.min).int64Value, Int64(Int.min)) + #expect(Decimal(Int.min).int64Value == Int64(Int.min)) let div3 = Decimal(10) / 3 - XCTAssertEqual(div3.int64Value, 3) + #expect(div3.int64Value == 3) let pi = Decimal(Double.pi) - XCTAssertEqual(pi.int64Value, 3) + #expect(pi.int64Value == 3) } - func test_doubleValue() { - XCTAssertEqual(Decimal(0).doubleValue, 0) - XCTAssertEqual(Decimal(1).doubleValue, 1) - XCTAssertEqual(Decimal(-1).doubleValue, -1) - XCTAssertTrue(Decimal.nan.doubleValue.isNaN) - XCTAssertEqual(Decimal(UInt64.max).doubleValue, Double(1.8446744073709552e+19)) + @Test func doubleValue() { + #expect(Decimal(0).doubleValue == 0) + #expect(Decimal(1).doubleValue == 1) + #expect(Decimal(-1).doubleValue == -1) + #expect(Decimal.nan.doubleValue.isNaN) + #expect(Decimal(UInt64.max).doubleValue == Double(1.8446744073709552e+19)) } - func test_decimalFromString() { + @Test func decimalFromString() { let string = "x123x" let scanLocation = 1 @@ -1309,41 +1151,42 @@ final class DecimalTests : XCTestCase { let substring = string[start..= ip1, true) - XCTAssertEqual(ip1 > ip1, false) - - XCTAssertEqual(ip1.compare(ip2), ComparisonResult.orderedAscending) - XCTAssertEqual(ip1 < ip2, true) - XCTAssertEqual(ip1 <= ip2, true) - XCTAssertEqual(ip1 == ip2, false) - XCTAssertEqual(ip1 >= ip2, false) - XCTAssertEqual(ip1 > ip2, false) - - XCTAssertEqual(ip1.compare(ip3), ComparisonResult.orderedAscending) - XCTAssertEqual(ip1 < ip3, true) - XCTAssertEqual(ip1 <= ip3, true) - XCTAssertEqual(ip1 == ip3, false) - XCTAssertEqual(ip1 >= ip3, false) - XCTAssertEqual(ip1 > ip3, false) - - XCTAssertEqual(ip1.compare(ip4), ComparisonResult.orderedDescending) - XCTAssertEqual(ip1 < ip4, false) - XCTAssertEqual(ip1 <= ip4, false) - XCTAssertEqual(ip1 == ip4, false) - XCTAssertEqual(ip1 >= ip4, true) - XCTAssertEqual(ip1 > ip4, true) - - XCTAssertEqual(ip1.compare(ip5), ComparisonResult.orderedDescending) - XCTAssertEqual(ip1 < ip5, false) - XCTAssertEqual(ip1 <= ip5, false) - XCTAssertEqual(ip1 == ip5, false) - XCTAssertEqual(ip1 >= ip5, true) - XCTAssertEqual(ip1 > ip5, true) - - XCTAssertEqual(ip2.compare(ip1), ComparisonResult.orderedDescending) - XCTAssertEqual(ip2 < ip1, false) - XCTAssertEqual(ip2 <= ip1, false) - XCTAssertEqual(ip2 == ip1, false) - XCTAssertEqual(ip2 >= ip1, true) - XCTAssertEqual(ip2 > ip1, true) - - XCTAssertEqual(ip2.compare(ip2), ComparisonResult.orderedSame) - XCTAssertEqual(ip2 < ip2, false) - XCTAssertEqual(ip2 <= ip2, true) - XCTAssertEqual(ip2 == ip2, true) - XCTAssertEqual(ip2 >= ip2, true) - XCTAssertEqual(ip2 > ip2, false) - - XCTAssertEqual(ip2.compare(ip3), ComparisonResult.orderedAscending) - XCTAssertEqual(ip2 < ip3, true) - XCTAssertEqual(ip2 <= ip3, true) - XCTAssertEqual(ip2 == ip3, false) - XCTAssertEqual(ip2 >= ip3, false) - XCTAssertEqual(ip2 > ip3, false) - - XCTAssertEqual(ip2.compare(ip4), ComparisonResult.orderedDescending) - XCTAssertEqual(ip2.compare(ip5), ComparisonResult.orderedDescending) - XCTAssertEqual(ip3.compare(ip1), ComparisonResult.orderedDescending) - XCTAssertEqual(ip3.compare(ip2), ComparisonResult.orderedDescending) - XCTAssertEqual(ip3.compare(ip3), ComparisonResult.orderedSame) - XCTAssertEqual(ip3.compare(ip4), ComparisonResult.orderedDescending) - XCTAssertEqual(ip3.compare(ip5), ComparisonResult.orderedDescending) - XCTAssertEqual(ip4.compare(ip1), ComparisonResult.orderedAscending) - XCTAssertEqual(ip4.compare(ip2), ComparisonResult.orderedAscending) - XCTAssertEqual(ip4.compare(ip3), ComparisonResult.orderedAscending) - XCTAssertEqual(ip4.compare(ip4), ComparisonResult.orderedSame) - XCTAssertEqual(ip4.compare(ip5), ComparisonResult.orderedAscending) - XCTAssertEqual(ip5.compare(ip1), ComparisonResult.orderedAscending) - XCTAssertEqual(ip5.compare(ip2), ComparisonResult.orderedAscending) - XCTAssertEqual(ip5.compare(ip3), ComparisonResult.orderedAscending) - XCTAssertEqual(ip5.compare(ip4), ComparisonResult.orderedDescending) - XCTAssertEqual(ip5.compare(ip5), ComparisonResult.orderedSame) + #expect(ip1.compare(ip1) == .orderedSame) + #expect(!(ip1 < ip1)) + #expect(ip1 <= ip1) + #expect(ip1 == ip1) + #expect(ip1 >= ip1) + #expect(!(ip1 > ip1)) + + #expect(ip1.compare(ip2) == .orderedAscending) + #expect(ip1 < ip2) + #expect(ip1 <= ip2) + #expect(!(ip1 == ip2)) + #expect(!(ip1 >= ip2)) + #expect(!(ip1 > ip2)) + + #expect(ip1.compare(ip3) == .orderedAscending) + #expect(ip1 < ip3) + #expect(ip1 <= ip3) + #expect(!(ip1 == ip3)) + #expect(!(ip1 >= ip3)) + #expect(!(ip1 > ip3)) + + #expect(ip1.compare(ip4) == .orderedDescending) + #expect(!(ip1 < ip4)) + #expect(!(ip1 <= ip4)) + #expect(!(ip1 == ip4)) + #expect(ip1 >= ip4) + #expect(ip1 > ip4) + + #expect(ip1.compare(ip5) == .orderedDescending) + #expect(!(ip1 < ip5)) + #expect(!(ip1 <= ip5)) + #expect(!(ip1 == ip5)) + #expect(ip1 >= ip5) + #expect(ip1 > ip5) + + #expect(ip2.compare(ip1) == .orderedDescending) + #expect(!(ip2 < ip1)) + #expect(!(ip2 <= ip1)) + #expect(!(ip2 == ip1)) + #expect(ip2 >= ip1) + #expect(ip2 > ip1) + + #expect(ip2.compare(ip2) == .orderedSame) + #expect(!(ip2 < ip2)) + #expect(ip2 <= ip2) + #expect(ip2 == ip2) + #expect(ip2 >= ip2) + #expect(!(ip2 > ip2)) + + #expect(ip2.compare(ip3) == .orderedAscending) + #expect(ip2 < ip3) + #expect(ip2 <= ip3) + #expect(!(ip2 == ip3)) + #expect(!(ip2 >= ip3)) + #expect(!(ip2 > ip3)) + + #expect(ip2.compare(ip4) == .orderedDescending) + #expect(ip2.compare(ip5) == .orderedDescending) + #expect(ip3.compare(ip1) == .orderedDescending) + #expect(ip3.compare(ip2) == .orderedDescending) + #expect(ip3.compare(ip3) == .orderedSame) + #expect(ip3.compare(ip4) == .orderedDescending) + #expect(ip3.compare(ip5) == .orderedDescending) + #expect(ip4.compare(ip1) == .orderedAscending) + #expect(ip4.compare(ip2) == .orderedAscending) + #expect(ip4.compare(ip3) == .orderedAscending) + #expect(ip4.compare(ip4) == .orderedSame) + #expect(ip4.compare(ip5) == .orderedAscending) + #expect(ip5.compare(ip1) == .orderedAscending) + #expect(ip5.compare(ip2) == .orderedAscending) + #expect(ip5.compare(ip3) == .orderedAscending) + #expect(ip5.compare(ip4) == .orderedDescending) + #expect(ip5.compare(ip5) == .orderedSame) let ip6: IndexPath = [1, 1] - XCTAssertEqual(ip6.compare(ip5), ComparisonResult.orderedAscending) - XCTAssertEqual(ip5.compare(ip6), ComparisonResult.orderedDescending) + #expect(ip6.compare(ip5) == .orderedAscending) + #expect(ip5.compare(ip6) == .orderedDescending) } - func testHashing() { + @Test func hashing() { let samples: [IndexPath] = [ [], [1], @@ -245,314 +250,314 @@ class TestIndexPath: XCTestCase { _ = IndexPath(indexes: [Int.max >> 8, 2, Int.max >> 36]).hashValue } - func testEquality() { + @Test func equality() { let ip1: IndexPath = [1, 1] let ip2: IndexPath = [1, 1] let ip3: IndexPath = [1, 1, 1] let ip4: IndexPath = [] let ip5: IndexPath = [1] - XCTAssertTrue(ip1 == ip2) - XCTAssertFalse(ip1 == ip3) - XCTAssertFalse(ip1 == ip4) - XCTAssertFalse(ip4 == ip1) - XCTAssertFalse(ip5 == ip1) - XCTAssertFalse(ip5 == ip4) - XCTAssertTrue(ip4 == ip4) - XCTAssertTrue(ip5 == ip5) + #expect(ip1 == ip2) + #expect(ip1 != ip3) + #expect(ip1 != ip4) + #expect(ip4 != ip1) + #expect(ip5 != ip1) + #expect(ip5 != ip4) + #expect(ip4 == ip4) + #expect(ip5 == ip5) } - func testSubscripting() { + @Test func subscripting() { var ip1: IndexPath = [1] var ip2: IndexPath = [1, 2] var ip3: IndexPath = [1, 2, 3] - XCTAssertEqual(ip1[0], 1) + #expect(ip1[0] == 1) - XCTAssertEqual(ip2[0], 1) - XCTAssertEqual(ip2[1], 2) + #expect(ip2[0] == 1) + #expect(ip2[1] == 2) - XCTAssertEqual(ip3[0], 1) - XCTAssertEqual(ip3[1], 2) - XCTAssertEqual(ip3[2], 3) + #expect(ip3[0] == 1) + #expect(ip3[1] == 2) + #expect(ip3[2] == 3) ip1[0] = 2 - XCTAssertEqual(ip1[0], 2) + #expect(ip1[0] == 2) ip2[0] = 2 ip2[1] = 3 - XCTAssertEqual(ip2[0], 2) - XCTAssertEqual(ip2[1], 3) + #expect(ip2[0] == 2) + #expect(ip2[1] == 3) ip3[0] = 2 ip3[1] = 3 ip3[2] = 4 - XCTAssertEqual(ip3[0], 2) - XCTAssertEqual(ip3[1], 3) - XCTAssertEqual(ip3[2], 4) + #expect(ip3[0] == 2) + #expect(ip3[1] == 3) + #expect(ip3[2] == 4) let ip4 = ip3[0..<2] - XCTAssertEqual(ip4.count, 2) - XCTAssertEqual(ip4[0], 2) - XCTAssertEqual(ip4[1], 3) + #expect(ip4.count == 2) + #expect(ip4[0] == 2) + #expect(ip4[1] == 3) let ip5 = ip3[1...] - XCTAssertEqual(ip5.count, 2) - XCTAssertEqual(ip5[0], 3) - XCTAssertEqual(ip5[1], 4) + #expect(ip5.count == 2) + #expect(ip5[0] == 3) + #expect(ip5[1] == 4) let ip6 = ip3[2...] - XCTAssertEqual(ip6.count, 1) - XCTAssertEqual(ip6[0], 4) + #expect(ip6.count == 1) + #expect(ip6[0] == 4) } - func testAppending() { + @Test func appending() { var ip : IndexPath = [1, 2, 3, 4] let ip2 = IndexPath(indexes: [5, 6, 7]) ip.append(ip2) - XCTAssertEqual(ip.count, 7) - XCTAssertEqual(ip[0], 1) - XCTAssertEqual(ip[6], 7) + #expect(ip.count == 7) + #expect(ip[0] == 1) + #expect(ip[6] == 7) let ip3 = ip.appending(IndexPath(indexes: [8, 9])) - XCTAssertEqual(ip3.count, 9) - XCTAssertEqual(ip3[7], 8) - XCTAssertEqual(ip3[8], 9) + #expect(ip3.count == 9) + #expect(ip3[7] == 8) + #expect(ip3[8] == 9) let ip4 = ip3.appending([10, 11]) - XCTAssertEqual(ip4.count, 11) - XCTAssertEqual(ip4[9], 10) - XCTAssertEqual(ip4[10], 11) + #expect(ip4.count == 11) + #expect(ip4[9] == 10) + #expect(ip4[10] == 11) let ip5 = ip.appending(8) - XCTAssertEqual(ip5.count, 8) - XCTAssertEqual(ip5[7], 8) + #expect(ip5.count == 8) + #expect(ip5[7] == 8) } - func testAppendEmpty() { + @Test func appendEmpty() { var ip: IndexPath = [] ip.append(1) - XCTAssertEqual(ip.count, 1) - XCTAssertEqual(ip[0], 1) + #expect(ip.count == 1) + #expect(ip[0] == 1) ip.append(2) - XCTAssertEqual(ip.count, 2) - XCTAssertEqual(ip[0], 1) - XCTAssertEqual(ip[1], 2) + #expect(ip.count == 2) + #expect(ip[0] == 1) + #expect(ip[1] == 2) ip.append(3) - XCTAssertEqual(ip.count, 3) - XCTAssertEqual(ip[0], 1) - XCTAssertEqual(ip[1], 2) - XCTAssertEqual(ip[2], 3) + #expect(ip.count == 3) + #expect(ip[0] == 1) + #expect(ip[1] == 2) + #expect(ip[2] == 3) ip.append(4) - XCTAssertEqual(ip.count, 4) - XCTAssertEqual(ip[0], 1) - XCTAssertEqual(ip[1], 2) - XCTAssertEqual(ip[2], 3) - XCTAssertEqual(ip[3], 4) + #expect(ip.count == 4) + #expect(ip[0] == 1) + #expect(ip[1] == 2) + #expect(ip[2] == 3) + #expect(ip[3] == 4) } - func testAppendEmptyIndexPath() { + @Test func appendEmptyIndexPath() { var ip: IndexPath = [] ip.append(IndexPath(indexes: [])) - XCTAssertEqual(ip.count, 0) + #expect(ip.count == 0) } - func testAppendManyIndexPath() { + @Test func appendManyIndexPath() { var ip: IndexPath = [] ip.append(IndexPath(indexes: [1, 2, 3])) - XCTAssertEqual(ip.count, 3) - XCTAssertEqual(ip[0], 1) - XCTAssertEqual(ip[1], 2) - XCTAssertEqual(ip[2], 3) + #expect(ip.count == 3) + #expect(ip[0] == 1) + #expect(ip[1] == 2) + #expect(ip[2] == 3) } - func testAppendEmptyIndexPathToSingle() { + @Test func appendEmptyIndexPathToSingle() { var ip: IndexPath = [1] ip.append(IndexPath(indexes: [])) - XCTAssertEqual(ip.count, 1) - XCTAssertEqual(ip[0], 1) + #expect(ip.count == 1) + #expect(ip[0] == 1) } - func testAppendSingleIndexPath() { + @Test func appendSingleIndexPath() { var ip: IndexPath = [] ip.append(IndexPath(indexes: [1])) - XCTAssertEqual(ip.count, 1) - XCTAssertEqual(ip[0], 1) + #expect(ip.count == 1) + #expect(ip[0] == 1) } - func testAppendSingleIndexPathToSingle() { + @Test func appendSingleIndexPathToSingle() { var ip: IndexPath = [1] ip.append(IndexPath(indexes: [1])) - XCTAssertEqual(ip.count, 2) - XCTAssertEqual(ip[0], 1) - XCTAssertEqual(ip[1], 1) + #expect(ip.count == 2) + #expect(ip[0] == 1) + #expect(ip[1] == 1) } - func testAppendPairIndexPath() { + @Test func appendPairIndexPath() { var ip: IndexPath = [] ip.append(IndexPath(indexes: [1, 2])) - XCTAssertEqual(ip.count, 2) - XCTAssertEqual(ip[0], 1) - XCTAssertEqual(ip[1], 2) + #expect(ip.count == 2) + #expect(ip[0] == 1) + #expect(ip[1] == 2) } - func testAppendManyIndexPathToEmpty() { + @Test func appendManyIndexPathToEmpty() { var ip: IndexPath = [] ip.append(IndexPath(indexes: [1, 2, 3])) - XCTAssertEqual(ip.count, 3) - XCTAssertEqual(ip[0], 1) - XCTAssertEqual(ip[1], 2) - XCTAssertEqual(ip[2], 3) + #expect(ip.count == 3) + #expect(ip[0] == 1) + #expect(ip[1] == 2) + #expect(ip[2] == 3) } - func testAppendByOperator() { + @Test func appendByOperator() { let ip1: IndexPath = [] let ip2: IndexPath = [] let ip3 = ip1 + ip2 - XCTAssertEqual(ip3.count, 0) + #expect(ip3.count == 0) let ip4: IndexPath = [1] let ip5: IndexPath = [2] let ip6 = ip4 + ip5 - XCTAssertEqual(ip6.count, 2) - XCTAssertEqual(ip6[0], 1) - XCTAssertEqual(ip6[1], 2) + #expect(ip6.count == 2) + #expect(ip6[0] == 1) + #expect(ip6[1] == 2) var ip7: IndexPath = [] ip7 += ip6 - XCTAssertEqual(ip7.count, 2) - XCTAssertEqual(ip7[0], 1) - XCTAssertEqual(ip7[1], 2) + #expect(ip7.count == 2) + #expect(ip7[0] == 1) + #expect(ip7[1] == 2) } - func testAppendArray() { + @Test func appendArray() { var ip: IndexPath = [1, 2, 3, 4] let indexes = [5, 6, 7] ip.append(indexes) - XCTAssertEqual(ip.count, 7) - XCTAssertEqual(ip[0], 1) - XCTAssertEqual(ip[6], 7) + #expect(ip.count == 7) + #expect(ip[0] == 1) + #expect(ip[6] == 7) } - func testRanges() { + @Test func ranges() { let ip1 = IndexPath(indexes: [1, 2, 3]) let ip2 = IndexPath(indexes: [6, 7, 8]) // Replace the whole range var mutateMe = ip1 mutateMe[0..<3] = ip2 - XCTAssertEqual(mutateMe, ip2) + #expect(mutateMe == ip2) // Insert at the beginning mutateMe = ip1 mutateMe[0..<0] = ip2 - XCTAssertEqual(mutateMe, IndexPath(indexes: [6, 7, 8, 1, 2, 3])) + #expect(mutateMe == IndexPath(indexes: [6, 7, 8, 1, 2, 3])) // Insert at the end mutateMe = ip1 mutateMe[3..<3] = ip2 - XCTAssertEqual(mutateMe, IndexPath(indexes: [1, 2, 3, 6, 7, 8])) + #expect(mutateMe == IndexPath(indexes: [1, 2, 3, 6, 7, 8])) // Insert in middle mutateMe = ip1 mutateMe[2..<2] = ip2 - XCTAssertEqual(mutateMe, IndexPath(indexes: [1, 2, 6, 7, 8, 3])) + #expect(mutateMe == IndexPath(indexes: [1, 2, 6, 7, 8, 3])) } - func testRangeFromEmpty() { + @Test func rangeFromEmpty() { let ip1 = IndexPath() let ip2 = ip1[0..<0] - XCTAssertEqual(ip2.count, 0) + #expect(ip2.count == 0) } - func testRangeFromSingle() { + @Test func rangeFromSingle() { let ip1 = IndexPath(indexes: [1]) let ip2 = ip1[0..<0] - XCTAssertEqual(ip2.count, 0) + #expect(ip2.count == 0) let ip3 = ip1[0..<1] - XCTAssertEqual(ip3.count, 1) - XCTAssertEqual(ip3[0], 1) + #expect(ip3.count == 1) + #expect(ip3[0] == 1) } - func testRangeFromPair() { + @Test func rangeFromPair() { let ip1 = IndexPath(indexes: [1, 2]) let ip2 = ip1[0..<0] - XCTAssertEqual(ip2.count, 0) + #expect(ip2.count == 0) let ip3 = ip1[0..<1] - XCTAssertEqual(ip3.count, 1) - XCTAssertEqual(ip3[0], 1) + #expect(ip3.count == 1) + #expect(ip3[0] == 1) let ip4 = ip1[1..<1] - XCTAssertEqual(ip4.count, 0) + #expect(ip4.count == 0) let ip5 = ip1[0..<2] - XCTAssertEqual(ip5.count, 2) - XCTAssertEqual(ip5[0], 1) - XCTAssertEqual(ip5[1], 2) + #expect(ip5.count == 2) + #expect(ip5[0] == 1) + #expect(ip5[1] == 2) let ip6 = ip1[1..<2] - XCTAssertEqual(ip6.count, 1) - XCTAssertEqual(ip6[0], 2) + #expect(ip6.count == 1) + #expect(ip6[0] == 2) let ip7 = ip1[2..<2] - XCTAssertEqual(ip7.count, 0) + #expect(ip7.count == 0) } - func testRangeFromMany() { + @Test func rangeFromMany() { let ip1 = IndexPath(indexes: [1, 2, 3]) let ip2 = ip1[0..<0] - XCTAssertEqual(ip2.count, 0) + #expect(ip2.count == 0) let ip3 = ip1[0..<1] - XCTAssertEqual(ip3.count, 1) + #expect(ip3.count == 1) let ip4 = ip1[0..<2] - XCTAssertEqual(ip4.count, 2) + #expect(ip4.count == 2) let ip5 = ip1[0..<3] - XCTAssertEqual(ip5.count, 3) + #expect(ip5.count == 3) } - func testRangeReplacementSingle() { + @Test func rangeReplacementSingle() { var ip1 = IndexPath(indexes: [1]) ip1[0..<1] = IndexPath(indexes: [2]) - XCTAssertEqual(ip1[0], 2) + #expect(ip1[0] == 2) ip1[0..<1] = IndexPath(indexes: []) - XCTAssertEqual(ip1.count, 0) + #expect(ip1.count == 0) } - func testRangeReplacementPair() { + @Test func rangeReplacementPair() { var ip1 = IndexPath(indexes: [1, 2]) ip1[0..<1] = IndexPath(indexes: [2, 3]) - XCTAssertEqual(ip1.count, 3) - XCTAssertEqual(ip1[0], 2) - XCTAssertEqual(ip1[1], 3) - XCTAssertEqual(ip1[2], 2) + #expect(ip1.count == 3) + #expect(ip1[0] == 2) + #expect(ip1[1] == 3) + #expect(ip1[2] == 2) ip1[0..<1] = IndexPath(indexes: []) - XCTAssertEqual(ip1.count, 2) + #expect(ip1.count == 2) } - func testMoreRanges() { + @Test func moreRanges() { var ip = IndexPath(indexes: [1, 2, 3]) let ip2 = IndexPath(indexes: [5, 6, 7, 8, 9, 10]) ip[1..<2] = ip2 - XCTAssertEqual(ip, IndexPath(indexes: [1, 5, 6, 7, 8, 9, 10, 3])) + #expect(ip == IndexPath(indexes: [1, 5, 6, 7, 8, 9, 10, 3])) } - func testIteration() { + @Test func iteration() { let ip = IndexPath(indexes: [1, 2, 3]) var count = 0 @@ -560,50 +565,50 @@ class TestIndexPath: XCTestCase { count += 1 } - XCTAssertEqual(3, count) + #expect(3 == count) } - func testDescription() { + @Test func description() { let ip1: IndexPath = [] let ip2: IndexPath = [1] let ip3: IndexPath = [1, 2] let ip4: IndexPath = [1, 2, 3] - XCTAssertEqual(ip1.description, "[]") - XCTAssertEqual(ip2.description, "[1]") - XCTAssertEqual(ip3.description, "[1, 2]") - XCTAssertEqual(ip4.description, "[1, 2, 3]") + #expect(ip1.description == "[]") + #expect(ip2.description == "[1]") + #expect(ip3.description == "[1, 2]") + #expect(ip4.description == "[1, 2, 3]") - XCTAssertEqual(ip1.debugDescription, ip1.description) - XCTAssertEqual(ip2.debugDescription, ip2.description) - XCTAssertEqual(ip3.debugDescription, ip3.description) - XCTAssertEqual(ip4.debugDescription, ip4.description) + #expect(ip1.debugDescription == ip1.description) + #expect(ip2.debugDescription == ip2.description) + #expect(ip3.debugDescription == ip3.description) + #expect(ip4.debugDescription == ip4.description) } - func test_AnyHashableContainingIndexPath() { + @Test func anyHashableContainingIndexPath() { let values: [IndexPath] = [ IndexPath(indexes: [1, 2]), IndexPath(indexes: [1, 2, 3]), IndexPath(indexes: [1, 2, 3]), ] let anyHashables = values.map(AnyHashable.init) - expectEqual(IndexPath.self, type(of: anyHashables[0].base)) - expectEqual(IndexPath.self, type(of: anyHashables[1].base)) - expectEqual(IndexPath.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(IndexPath.self == type(of: anyHashables[0].base)) + #expect(IndexPath.self == type(of: anyHashables[1].base)) + #expect(IndexPath.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } - func test_slice_1ary() { + @Test func slice_1ary() { let indexPath: IndexPath = [0] let res = indexPath.dropFirst() - XCTAssertEqual(0, res.count) + #expect(0 == res.count) let slice = indexPath[1..<1] - XCTAssertEqual(0, slice.count) + #expect(0 == slice.count) } - func test_dropFirst() { + @Test func dropFirst() { var pth = IndexPath(indexes:[1,2,3,4]) while !pth.isEmpty { // this should not crash @@ -614,8 +619,9 @@ class TestIndexPath: XCTestCase { #if FOUNDATION_FRAMEWORK -class TestIndexPathBridging: XCTestCase { - func testBridgeToObjC() { +@Suite("IndexPath Bridging") +private struct IndexPathBridgingTests { + @Test func bridgeToObjC() { let ip1: IndexPath = [] let ip2: IndexPath = [1] let ip3: IndexPath = [1, 2] @@ -626,13 +632,13 @@ class TestIndexPathBridging: XCTestCase { let nsip3 = ip3 as NSIndexPath let nsip4 = ip4 as NSIndexPath - XCTAssertEqual(nsip1.length, 0) - XCTAssertEqual(nsip2.length, 1) - XCTAssertEqual(nsip3.length, 2) - XCTAssertEqual(nsip4.length, 3) + #expect(nsip1.length == 0) + #expect(nsip2.length == 1) + #expect(nsip3.length == 2) + #expect(nsip4.length == 3) } - func testForceBridgeFromObjC() { + @Test func forceBridgeFromObjC() { let nsip1 = NSIndexPath() let nsip2 = NSIndexPath(index: 1) let nsip3 = [1, 2].withUnsafeBufferPointer { (buffer: UnsafeBufferPointer) -> NSIndexPath in @@ -644,32 +650,28 @@ class TestIndexPathBridging: XCTestCase { var ip1: IndexPath? = IndexPath() IndexPath._forceBridgeFromObjectiveC(nsip1, result: &ip1) - XCTAssertNotNil(ip1) - XCTAssertEqual(ip1!.count, 0) + #expect(ip1?.count == 0) var ip2: IndexPath? = IndexPath() IndexPath._forceBridgeFromObjectiveC(nsip2, result: &ip2) - XCTAssertNotNil(ip2) - XCTAssertEqual(ip2!.count, 1) - XCTAssertEqual(ip2![0], 1) + #expect(ip2?.count == 1) + #expect(ip2?[0] == 1) var ip3: IndexPath? = IndexPath() IndexPath._forceBridgeFromObjectiveC(nsip3, result: &ip3) - XCTAssertNotNil(ip3) - XCTAssertEqual(ip3!.count, 2) - XCTAssertEqual(ip3![0], 1) - XCTAssertEqual(ip3![1], 2) + #expect(ip3?.count == 2) + #expect(ip3?[0] == 1) + #expect(ip3?[1] == 2) var ip4: IndexPath? = IndexPath() IndexPath._forceBridgeFromObjectiveC(nsip4, result: &ip4) - XCTAssertNotNil(ip4) - XCTAssertEqual(ip4!.count, 3) - XCTAssertEqual(ip4![0], 1) - XCTAssertEqual(ip4![1], 2) - XCTAssertEqual(ip4![2], 3) + #expect(ip4?.count == 3) + #expect(ip4?[0] == 1) + #expect(ip4?[1] == 2) + #expect(ip4?[2] == 3) } - func testConditionalBridgeFromObjC() { + @Test func conditionalBridgeFromObjC() { let nsip1 = NSIndexPath() let nsip2 = NSIndexPath(index: 1) let nsip3 = [1, 2].withUnsafeBufferPointer { (buffer: UnsafeBufferPointer) -> NSIndexPath in @@ -680,33 +682,29 @@ class TestIndexPathBridging: XCTestCase { } var ip1: IndexPath? = IndexPath() - XCTAssertTrue(IndexPath._conditionallyBridgeFromObjectiveC(nsip1, result: &ip1)) - XCTAssertNotNil(ip1) - XCTAssertEqual(ip1!.count, 0) + #expect(IndexPath._conditionallyBridgeFromObjectiveC(nsip1, result: &ip1)) + #expect(ip1?.count == 0) var ip2: IndexPath? = IndexPath() - XCTAssertTrue(IndexPath._conditionallyBridgeFromObjectiveC(nsip2, result: &ip2)) - XCTAssertNotNil(ip2) - XCTAssertEqual(ip2!.count, 1) - XCTAssertEqual(ip2![0], 1) + #expect(IndexPath._conditionallyBridgeFromObjectiveC(nsip2, result: &ip2)) + #expect(ip2?.count == 1) + #expect(ip2?[0] == 1) var ip3: IndexPath? = IndexPath() - XCTAssertTrue(IndexPath._conditionallyBridgeFromObjectiveC(nsip3, result: &ip3)) - XCTAssertNotNil(ip3) - XCTAssertEqual(ip3!.count, 2) - XCTAssertEqual(ip3![0], 1) - XCTAssertEqual(ip3![1], 2) + #expect(IndexPath._conditionallyBridgeFromObjectiveC(nsip3, result: &ip3)) + #expect(ip3?.count == 2) + #expect(ip3?[0] == 1) + #expect(ip3?[1] == 2) var ip4: IndexPath? = IndexPath() - XCTAssertTrue(IndexPath._conditionallyBridgeFromObjectiveC(nsip4, result: &ip4)) - XCTAssertNotNil(ip4) - XCTAssertEqual(ip4!.count, 3) - XCTAssertEqual(ip4![0], 1) - XCTAssertEqual(ip4![1], 2) - XCTAssertEqual(ip4![2], 3) + #expect(IndexPath._conditionallyBridgeFromObjectiveC(nsip4, result: &ip4)) + #expect(ip4?.count == 3) + #expect(ip4?[0] == 1) + #expect(ip4?[1] == 2) + #expect(ip4?[2] == 3) } - func testUnconditionalBridgeFromObjC() { + @Test func unconditionalBridgeFromObjC() { let nsip1 = NSIndexPath() let nsip2 = NSIndexPath(index: 1) let nsip3 = [1, 2].withUnsafeBufferPointer { (buffer: UnsafeBufferPointer) -> NSIndexPath in @@ -717,44 +715,45 @@ class TestIndexPathBridging: XCTestCase { } let ip1: IndexPath = IndexPath._unconditionallyBridgeFromObjectiveC(nsip1) - XCTAssertEqual(ip1.count, 0) + #expect(ip1.count == 0) let ip2: IndexPath = IndexPath._unconditionallyBridgeFromObjectiveC(nsip2) - XCTAssertEqual(ip2.count, 1) - XCTAssertEqual(ip2[0], 1) + #expect(ip2.count == 1) + #expect(ip2[0] == 1) let ip3: IndexPath = IndexPath._unconditionallyBridgeFromObjectiveC(nsip3) - XCTAssertEqual(ip3.count, 2) - XCTAssertEqual(ip3[0], 1) - XCTAssertEqual(ip3[1], 2) + #expect(ip3.count == 2) + #expect(ip3[0] == 1) + #expect(ip3[1] == 2) let ip4: IndexPath = IndexPath._unconditionallyBridgeFromObjectiveC(nsip4) - XCTAssertEqual(ip4.count, 3) - XCTAssertEqual(ip4[0], 1) - XCTAssertEqual(ip4[1], 2) - XCTAssertEqual(ip4[2], 3) + #expect(ip4.count == 3) + #expect(ip4[0] == 1) + #expect(ip4[1] == 2) + #expect(ip4[2] == 3) } - func testObjcBridgeType() { - XCTAssertTrue(IndexPath._getObjectiveCType() == NSIndexPath.self) + @Test func objcBridgeType() { + let typeIsExpected = IndexPath._getObjectiveCType() == NSIndexPath.self + #expect(typeIsExpected) } - func test_AnyHashableCreatedFromNSIndexPath() { + @Test func anyHashableCreatedFromNSIndexPath() { let values: [NSIndexPath] = [ NSIndexPath(index: 1), NSIndexPath(index: 2), NSIndexPath(index: 2), ] let anyHashables = values.map(AnyHashable.init) - expectEqual(IndexPath.self, type(of: anyHashables[0].base)) - expectEqual(IndexPath.self, type(of: anyHashables[1].base)) - expectEqual(IndexPath.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(IndexPath.self == type(of: anyHashables[0].base)) + #expect(IndexPath.self == type(of: anyHashables[1].base)) + #expect(IndexPath.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } - func test_unconditionallyBridgeFromObjectiveC() { - XCTAssertEqual(IndexPath(), IndexPath._unconditionallyBridgeFromObjectiveC(nil)) + @Test func unconditionallyBridgeFromObjectiveC() { + #expect(IndexPath() == IndexPath._unconditionallyBridgeFromObjectiveC(nil)) } } diff --git a/Tests/FoundationEssentialsTests/LockedStateTests.swift b/Tests/FoundationEssentialsTests/LockedStateTests.swift index 3b1d59c89..9eaea70b8 100644 --- a/Tests/FoundationEssentialsTests/LockedStateTests.swift +++ b/Tests/FoundationEssentialsTests/LockedStateTests.swift @@ -10,17 +10,16 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing -#if FOUNDATION_FRAMEWORK -@testable import Foundation -#else +#if canImport(FoundationEssentials) @testable import FoundationEssentials +#else +@testable import Foundation #endif -final class LockedStateTests : XCTestCase { +@Suite("LockedState") +private struct LockedStateTests { final class TestObject { var deinitBlock: () -> Void = {} @@ -31,7 +30,7 @@ final class LockedStateTests : XCTestCase { struct TestError: Error {} - func testWithLockDoesNotExtendLifetimeOfState() { + @Test func withLockDoesNotExtendLifetimeOfState() { weak var state: TestObject? let lockedState: LockedState @@ -43,23 +42,23 @@ final class LockedStateTests : XCTestCase { lockedState.withLock { state in weak var oldState = state state = TestObject() - XCTAssertNil(oldState, "State object lifetime was extended after reassignment within body") + #expect(oldState == nil, "State object lifetime was extended after reassignment within body") } - XCTAssertNil(state, "State object lifetime was extended beyond end of call") + #expect(state == nil, "State object lifetime was extended beyond end of call") } - func testWithLockExtendingLifetimeExtendsLifetimeOfStatePastReassignment() { + @Test func withLockExtendingLifetimeExtendsLifetimeOfStatePastReassignment() { let lockedState = LockedState(initialState: TestObject()) lockedState.withLockExtendingLifetimeOfState { state in weak var oldState = state state = TestObject() - XCTAssertNotNil(oldState, "State object lifetime was not extended after reassignment within body") + #expect(oldState != nil, "State object lifetime was not extended after reassignment within body") } } - func testWithLockExtendingLifetimeExtendsLifetimeOfStatePastEndOfLockedScope() { + @Test func withLockExtendingLifetimeExtendsLifetimeOfStatePastEndOfLockedScope() { let lockedState: LockedState = { let state = TestObject() let lockedState = LockedState(initialState: state) @@ -79,7 +78,7 @@ final class LockedStateTests : XCTestCase { } } - func testWithLockExtendingLifetimeDoesNotExtendLifetimeOfStatePastEndOfCall() { + @Test func withLockExtendingLifetimeDoesNotExtendLifetimeOfStatePastEndOfCall() { weak var state: TestObject? let lockedState: LockedState @@ -92,23 +91,22 @@ final class LockedStateTests : XCTestCase { state = TestObject() } - XCTAssertNil(state, "State object lifetime was extended beyond end of call") + #expect(state == nil, "State object lifetime was extended beyond end of call") } - func testWithLockExtendingLifetimeReleasesLockWhenBodyThrows() { + @Test func withLockExtendingLifetimeReleasesLockWhenBodyThrows() { let lockedState = LockedState(initialState: TestObject()) - XCTAssertThrowsError( + #expect(throws: TestError.self, "The body was expected to throw an error, but it did not.") { try lockedState.withLockExtendingLifetimeOfState { _ in throw TestError() - }, - "The body was expected to throw an error, but it did not." - ) + } + } assertLockNotHeld(lockedState, "Lock was not properly released by withLockExtendingLifetimeOfState()") } - func testWithLockExtendingLifespanDoesExtendLifetimeOfState() { + @Test func withLockExtendingLifespanDoesExtendLifetimeOfState() { weak var state: TestObject? let lockedState: LockedState @@ -120,10 +118,10 @@ final class LockedStateTests : XCTestCase { lockedState.withLockExtendingLifetimeOfState { state in weak var oldState = state state = TestObject() - XCTAssertNotNil(oldState, "State object lifetime was not extended after reassignment within body") + #expect(oldState != nil, "State object lifetime was not extended after reassignment within body") } - XCTAssertNil(state, "State object lifetime was extended beyond end of call") + #expect(state == nil, "State object lifetime was extended beyond end of call") } } diff --git a/Tests/FoundationEssentialsTests/SortComparatorTests.swift b/Tests/FoundationEssentialsTests/SortComparatorTests.swift index 36f2206bb..935f77c57 100644 --- a/Tests/FoundationEssentialsTests/SortComparatorTests.swift +++ b/Tests/FoundationEssentialsTests/SortComparatorTests.swift @@ -10,56 +10,46 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif -#if FOUNDATION_FRAMEWORK -@testable import Foundation -#else +import Testing + +#if canImport(FoundationEssentials) @testable import FoundationEssentials -#endif // FOUNDATION_FRAMEWORK +#else +@testable import Foundation +#endif -class SortComparatorTests: XCTestCase { - func test_comparable_descriptors() { +@Suite("SortComparator") +private struct SortComparatorTests { + @Test func comparableDescriptors() { let intDesc: ComparableComparator = ComparableComparator() - XCTAssertEqual(intDesc.compare(0, 1), .orderedAscending) + #expect(intDesc.compare(0, 1) == .orderedAscending) let result = intDesc.compare(1000, -10) - XCTAssertEqual(result, .orderedDescending) + #expect(result == .orderedDescending) } - func test_order() { + @Test func order() { var intDesc: ComparableComparator = ComparableComparator(order: .reverse) - XCTAssertEqual(intDesc.compare(0, 1), .orderedDescending) - XCTAssertEqual(intDesc.compare(1000, -10), .orderedAscending) - XCTAssertEqual(intDesc.compare(100, 100), .orderedSame) + #expect(intDesc.compare(0, 1) == .orderedDescending) + #expect(intDesc.compare(1000, -10) == .orderedAscending) + #expect(intDesc.compare(100, 100) == .orderedSame) intDesc.order = .forward - XCTAssertEqual(intDesc.compare(0, 1), .orderedAscending) - XCTAssertEqual(intDesc.compare(1000, -10), .orderedDescending) - XCTAssertEqual(intDesc.compare(100, 100), .orderedSame) - } - - func test_compare_options_descriptor() { - let compareOptions = String.Comparator(options: [.numeric]) - XCTAssertEqual( - compareOptions.compare("ttestest005", "test2"), - "test005".compare("test2", options: [.numeric])) - XCTAssertEqual( - compareOptions.compare("test2", "test005"), - "test2".compare("test005", options: [.numeric])) + #expect(intDesc.compare(0, 1) == .orderedAscending) + #expect(intDesc.compare(1000, -10) == .orderedDescending) + #expect(intDesc.compare(100, 100) == .orderedSame) } - func testAnySortComparatorEquality() { + @Test func anySortComparatorEquality() { let a: ComparableComparator = ComparableComparator() let b: ComparableComparator = ComparableComparator(order: .reverse) let c: ComparableComparator = ComparableComparator() - XCTAssertEqual(AnySortComparator(a), AnySortComparator(a)) - XCTAssertEqual(AnySortComparator(b), AnySortComparator(b)) - XCTAssertEqual(AnySortComparator(c), AnySortComparator(c)) - XCTAssertNotEqual(AnySortComparator(a), AnySortComparator(b)) - XCTAssertNotEqual(AnySortComparator(b), AnySortComparator(c)) - XCTAssertNotEqual(AnySortComparator(a), AnySortComparator(c)) + #expect(AnySortComparator(a) == AnySortComparator(a)) + #expect(AnySortComparator(b) == AnySortComparator(b)) + #expect(AnySortComparator(c) == AnySortComparator(c)) + #expect(AnySortComparator(a) != AnySortComparator(b)) + #expect(AnySortComparator(b) != AnySortComparator(c)) + #expect(AnySortComparator(a) != AnySortComparator(c)) } } diff --git a/Tests/FoundationEssentialsTests/StringTests.swift b/Tests/FoundationEssentialsTests/StringTests.swift index 39b5d3672..50a81618d 100644 --- a/Tests/FoundationEssentialsTests/StringTests.swift +++ b/Tests/FoundationEssentialsTests/StringTests.swift @@ -1463,7 +1463,7 @@ final class StringTestsStdlib: XCTestCase { .ascii, .utf8, ] - checkHashable(instances, equalityOracle: { $0 == $1 }) + XCTCheckHashable(instances, equalityOracle: { $0 == $1 }) } func test_localizedStringWithFormat() { diff --git a/Tests/FoundationEssentialsTests/UUIDTests.swift b/Tests/FoundationEssentialsTests/UUIDTests.swift index a21707f55..22ad824ce 100644 --- a/Tests/FoundationEssentialsTests/UUIDTests.swift +++ b/Tests/FoundationEssentialsTests/UUIDTests.swift @@ -10,42 +10,51 @@ // //===----------------------------------------------------------------------===// +import Testing + +#if canImport(FoundationEssentials) +import FoundationEssentials +#elseif FOUNDATION_FRAMEWORK +import Foundation +#endif + #if canImport(TestSupport) import TestSupport #endif -final class UUIDTests : XCTestCase { - func test_UUIDEquality() { +@Suite("UUID") +private struct UUIDTests { + @Test func equality() { let uuidA = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F") let uuidB = UUID(uuidString: "e621e1f8-c36c-495a-93fc-0c247a3e6e5f") let uuidC = UUID(uuid: (0xe6,0x21,0xe1,0xf8,0xc3,0x6c,0x49,0x5a,0x93,0xfc,0x0c,0x24,0x7a,0x3e,0x6e,0x5f)) let uuidD = UUID() - XCTAssertEqual(uuidA, uuidB, "String case must not matter.") - XCTAssertEqual(uuidA, uuidC, "A UUID initialized with a string must be equal to the same UUID initialized with its UnsafePointer equivalent representation.") - XCTAssertNotEqual(uuidC, uuidD, "Two different UUIDs must not be equal.") + #expect(uuidA == uuidB, "String case must not matter.") + #expect(uuidA == uuidC, "A UUID initialized with a string must be equal to the same UUID initialized with its UnsafePointer equivalent representation.") + #expect(uuidC != uuidD, "Two different UUIDs must not be equal.") } - func test_UUIDInvalid() { + @Test func invalidString() { let invalid = UUID(uuidString: "Invalid UUID") - XCTAssertNil(invalid, "The convenience initializer `init?(uuidString string:)` must return nil for an invalid UUID string.") + #expect(invalid == nil, "The convenience initializer `init?(uuidString string:)` must return nil for an invalid UUID string.") } // `uuidString` should return an uppercase string // See: https://bugs.swift.org/browse/SR-865 - func test_UUIDuuidString() { + @Test func uuidString() { let uuid = UUID(uuid: (0xe6,0x21,0xe1,0xf8,0xc3,0x6c,0x49,0x5a,0x93,0xfc,0x0c,0x24,0x7a,0x3e,0x6e,0x5f)) - XCTAssertEqual(uuid.uuidString, "E621E1F8-C36C-495A-93FC-0C247A3E6E5F", "The uuidString representation must be uppercase.") + #expect(uuid.uuidString == "E621E1F8-C36C-495A-93FC-0C247A3E6E5F", "The uuidString representation must be uppercase.") } - func test_UUIDdescription() { + @Test func description() { let uuid = UUID() let description: String = uuid.description let uuidString: String = uuid.uuidString - XCTAssertEqual(description, uuidString, "The description must be the same as the uuidString.") + #expect(description == uuidString, "The description must be the same as the uuidString.") } - func test_hash() { + @Test func hash() { let values: [UUID] = [ // This list takes a UUID and tweaks every byte while // leaving the version/variant intact. @@ -70,49 +79,50 @@ final class UUIDTests : XCTestCase { checkHashable(values, equalityOracle: { $0 == $1 }) } - func test_AnyHashableContainingUUID() { + @Test func anyHashableContainingUUID() { let values: [UUID] = [ UUID(uuidString: "e621e1f8-c36c-495a-93fc-0c247a3e6e5f")!, UUID(uuidString: "f81d4fae-7dec-11d0-a765-00a0c91e6bf6")!, UUID(uuidString: "f81d4fae-7dec-11d0-a765-00a0c91e6bf6")!, ] let anyHashables = values.map(AnyHashable.init) - expectEqual(UUID.self, type(of: anyHashables[0].base)) - expectEqual(UUID.self, type(of: anyHashables[1].base)) - expectEqual(UUID.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(UUID.self == type(of: anyHashables[0].base)) + #expect(UUID.self == type(of: anyHashables[1].base)) + #expect(UUID.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } // rdar://71190003 (UUID has no customMirror) - func test_UUID_custom_mirror() { - let uuid = UUID(uuidString: "89E90DC6-5EBA-41A8-A64D-81D3576EE46E")! - XCTAssertEqual(String(reflecting: uuid), "89E90DC6-5EBA-41A8-A64D-81D3576EE46E") + @Test func customMirror() throws { + let uuid = try #require(UUID(uuidString: "89E90DC6-5EBA-41A8-A64D-81D3576EE46E")) + #expect(String(reflecting: uuid) == "89E90DC6-5EBA-41A8-A64D-81D3576EE46E") } - func test_UUID_Comparable() throws { - var uuid1 = try XCTUnwrap(UUID(uuidString: "00000000-0000-0000-0000-000000000001")) - var uuid2 = try XCTUnwrap(UUID(uuidString: "00000000-0000-0000-0000-000000000002")) - XCTAssertTrue(uuid1 < uuid2) - XCTAssertFalse(uuid2 < uuid1) - XCTAssertFalse(uuid2 == uuid1) - - uuid1 = try XCTUnwrap(UUID(uuidString: "9707CE8D-251F-4858-8BF9-C9EC3D690FCE")) - uuid2 = try XCTUnwrap(UUID(uuidString: "9807CE8D-251F-4858-8BF9-C9EC3D690FCE")) - XCTAssertTrue(uuid1 < uuid2) - XCTAssertFalse(uuid2 < uuid1) - XCTAssertFalse(uuid2 == uuid1) - - uuid1 = try XCTUnwrap(UUID(uuidString: "9707CE8D-261F-4858-8BF9-C9EC3D690FCE")) - uuid2 = try XCTUnwrap(UUID(uuidString: "9707CE8D-251F-4858-8BF9-C9EC3D690FCE")) - XCTAssertTrue(uuid1 > uuid2) - XCTAssertFalse(uuid2 > uuid1) - XCTAssertFalse(uuid2 == uuid1) - - uuid1 = try XCTUnwrap(UUID(uuidString: "9707CE8D-251F-4858-8BF9-C9EC3D690FCE")) - uuid2 = try XCTUnwrap(UUID(uuidString: "9707CE8D-251F-4858-8BF9-C9EC3D690FCE")) - XCTAssertFalse(uuid1 > uuid2) - XCTAssertFalse(uuid2 > uuid1) - XCTAssertTrue(uuid2 == uuid1) + @available(FoundationPreview 0.1, *) + @Test func comparable() throws { + var uuid1 = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000001")) + var uuid2 = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000002")) + #expect(uuid1 < uuid2) + #expect(uuid2 >= uuid1) + #expect(uuid2 != uuid1) + + uuid1 = try #require(UUID(uuidString: "9707CE8D-251F-4858-8BF9-C9EC3D690FCE")) + uuid2 = try #require(UUID(uuidString: "9807CE8D-251F-4858-8BF9-C9EC3D690FCE")) + #expect(uuid1 < uuid2) + #expect(uuid2 >= uuid1) + #expect(uuid2 != uuid1) + + uuid1 = try #require(UUID(uuidString: "9707CE8D-261F-4858-8BF9-C9EC3D690FCE")) + uuid2 = try #require(UUID(uuidString: "9707CE8D-251F-4858-8BF9-C9EC3D690FCE")) + #expect(uuid1 > uuid2) + #expect(uuid2 <= uuid1) + #expect(uuid2 != uuid1) + + uuid1 = try #require(UUID(uuidString: "9707CE8D-251F-4858-8BF9-C9EC3D690FCE")) + uuid2 = try #require(UUID(uuidString: "9707CE8D-251F-4858-8BF9-C9EC3D690FCE")) + #expect(uuid1 <= uuid2) + #expect(uuid2 <= uuid1) + #expect(uuid2 == uuid1) } } diff --git a/Tests/FoundationInternationalizationTests/CalendarTests.swift b/Tests/FoundationInternationalizationTests/CalendarTests.swift index 170fc2434..7bd3d2cf4 100644 --- a/Tests/FoundationInternationalizationTests/CalendarTests.swift +++ b/Tests/FoundationInternationalizationTests/CalendarTests.swift @@ -161,7 +161,7 @@ final class CalendarTests : XCTestCase { Calendar(identifier: .islamic), Calendar(identifier: .iso8601), ] - checkHashable(calendars, equalityOracle: { $0 == $1 }) + XCTCheckHashable(calendars, equalityOracle: { $0 == $1 }) // autoupdating calendar isn't equal to the current, even though it's // likely to be the same. @@ -169,7 +169,7 @@ final class CalendarTests : XCTestCase { Calendar.autoupdatingCurrent, Calendar.current, ] - checkHashable(calendars2, equalityOracle: { $0 == $1 }) + XCTCheckHashable(calendars2, equalityOracle: { $0 == $1 }) } func test_AnyHashableContainingCalendar() { diff --git a/Tests/FoundationInternationalizationTests/DateTests+Locale.swift b/Tests/FoundationInternationalizationTests/DateTests+Locale.swift index 609e47476..df2c58181 100644 --- a/Tests/FoundationInternationalizationTests/DateTests+Locale.swift +++ b/Tests/FoundationInternationalizationTests/DateTests+Locale.swift @@ -71,7 +71,7 @@ final class DateLocaleTests : XCTestCase { dateWithString("2010-05-17 14:50:47 -0700"), dateWithString("2010-05-17 14:49:48 -0700"), ] - checkHashable(values, equalityOracle: { $0 == $1 }) + XCTCheckHashable(values, equalityOracle: { $0 == $1 }) } func test_AnyHashableContainingDate() { diff --git a/Tests/FoundationInternationalizationTests/StringSortComparatorTests.swift b/Tests/FoundationInternationalizationTests/StringSortComparatorTests.swift index cb6649531..a4bf70d11 100644 --- a/Tests/FoundationInternationalizationTests/StringSortComparatorTests.swift +++ b/Tests/FoundationInternationalizationTests/StringSortComparatorTests.swift @@ -10,43 +10,48 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing -#if FOUNDATION_FRAMEWORK -@testable import Foundation -#else +#if canImport(FoundationEssentials) @testable import FoundationEssentials @testable import FoundationInternationalization -#endif // FOUNDATION_FRAMEWORK +#else +@testable import Foundation +#endif -class StringSortComparatorTests: XCTestCase { +@Suite("String SortComparator") +private struct StringSortComparatorTests { + @Test func compareOptionsDescriptor() { + let compareOptions = String.Comparator(options: [.numeric]) + #expect( + compareOptions.compare("ttestest005", "test2") == + "test005".compare("test2", options: [.numeric])) + #expect( + compareOptions.compare("test2", "test005") == + "test2".compare("test005", options: [.numeric])) + } + #if FOUNDATION_FRAMEWORK // TODO: Until we support String.compare(_:options:locale:) in FoundationInternationalization, only support unlocalized comparisons // https://github.com/apple/swift-foundation/issues/284 - func test_locale() { + @Test func locale() { let swedishComparator = String.Comparator(options: [], locale: Locale(identifier: "sv")) - XCTAssertEqual(swedishComparator.compare("ă", "ã"), .orderedAscending) - XCTAssertEqual(swedishComparator.locale, Locale(identifier: "sv")) + #expect(swedishComparator.compare("ă", "ã") == .orderedAscending) + #expect(swedishComparator.locale == Locale(identifier: "sv")) } - func test_nil_locale() { + @Test func nilLocale() { let swedishComparator = String.Comparator(options: [], locale: nil) - XCTAssertEqual(swedishComparator.compare("ă", "ã"), .orderedDescending) + #expect(swedishComparator.compare("ă", "ã") == .orderedDescending) } - func test_standard_localized() throws { - // This test is only verified to work with en - guard Locale.current.language.languageCode == .english else { - throw XCTSkip("Test only verified to work with English as current language") - } - + @Test(.enabled(if: Locale.current.language.languageCode == .english, "Test only verified to work with English as current language")) + func standardLocalized() throws { let localizedStandard = String.StandardComparator.localizedStandard - XCTAssertEqual(localizedStandard.compare("ă", "ã"), .orderedAscending) + #expect(localizedStandard.compare("ă", "ã") == .orderedAscending) let unlocalizedStandard = String.StandardComparator.lexical - XCTAssertEqual(unlocalizedStandard.compare("ă", "ã"), .orderedDescending) + #expect(unlocalizedStandard.compare("ă", "ã") == .orderedDescending) } #endif } From 1255d73ab06b707558d9ad3cc27999c4c83ace53 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 12 Jun 2025 19:09:23 -0700 Subject: [PATCH 10/14] Disable macOS GitHub Actions CI (#1353) --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 7b35cf1ee..cd31a9a6c 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -14,7 +14,7 @@ jobs: with: linux_swift_versions: '["nightly-main"]' windows_swift_versions: '["nightly-main"]' - enable_macos_checks: true + enable_macos_checks: false macos_xcode_versions: '["16.3"]' macos_versions: '["sequoia"]' From dcbdbf6430c402a0a7c9fba0ef26b71ac47c8eeb Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Fri, 13 Jun 2025 09:16:26 -0700 Subject: [PATCH 11/14] Convert Macro tests to swift-testing (#1352) --- Package.swift | 3 +- .../BundleMacroTests.swift | 9 ++-- .../MacroTestUtilities.swift | 47 ++++++++++++------- .../PredicateMacroBasicTests.swift | 21 +++++---- .../PredicateMacroFunctionCallTests.swift | 31 ++++++------ .../PredicateMacroLanguageOperatorTests.swift | 37 ++++++++------- .../PredicateMacroLanguageTokenTests.swift | 19 ++++---- .../PredicateMacroUsageTests.swift | 11 +++-- 8 files changed, 97 insertions(+), 81 deletions(-) diff --git a/Package.swift b/Package.swift index eaf8fc851..4ec820250 100644 --- a/Package.swift +++ b/Package.swift @@ -247,8 +247,7 @@ package.targets.append(contentsOf: [ .testTarget( name: "FoundationMacrosTests", dependencies: [ - "FoundationMacros", - "TestSupport" + "FoundationMacros" ], swiftSettings: availabilityMacros + featureSettings + testOnlySwiftSettings ) diff --git a/Tests/FoundationMacrosTests/BundleMacroTests.swift b/Tests/FoundationMacrosTests/BundleMacroTests.swift index 5b466f422..b9c1add51 100644 --- a/Tests/FoundationMacrosTests/BundleMacroTests.swift +++ b/Tests/FoundationMacrosTests/BundleMacroTests.swift @@ -10,12 +10,13 @@ // //===----------------------------------------------------------------------===// -import XCTest +import Testing import FoundationMacros -final class BundleMacroTests: XCTestCase { +@Suite("#bundle Macro") +private struct BundleMacroTests { - func testSimple() { + @Test func testSimple() { AssertMacroExpansion( macros: ["bundle": BundleMacro.self], """ @@ -35,7 +36,7 @@ final class BundleMacroTests: XCTestCase { ) } - func testUsingParenthesis() { + @Test func testUsingParenthesis() { AssertMacroExpansion( macros: ["bundle": BundleMacro.self], """ diff --git a/Tests/FoundationMacrosTests/MacroTestUtilities.swift b/Tests/FoundationMacrosTests/MacroTestUtilities.swift index 275e255c8..bfc544e82 100644 --- a/Tests/FoundationMacrosTests/MacroTestUtilities.swift +++ b/Tests/FoundationMacrosTests/MacroTestUtilities.swift @@ -10,7 +10,8 @@ // //===----------------------------------------------------------------------===// -import XCTest +import Testing +import Foundation import FoundationMacros import SwiftSyntax import SwiftSyntaxMacros @@ -46,9 +47,9 @@ struct DiagnosticTest : ExpressibleByStringLiteral, Hashable, CustomStringConver var mappedToExpression: Self { DiagnosticTest( - message.replacing("Predicate", with: "Expression").replacing("predicate", with: "expression"), + message._replacing("Predicate", with: "Expression")._replacing("predicate", with: "expression"), fixIts: fixIts.map { - FixItTest($0.message, result: $0.result.replacing("#Predicate", with: "#Expression")) + FixItTest($0.message, result: $0.result._replacing("#Predicate", with: "#Expression")) } ) } @@ -99,7 +100,7 @@ extension Diagnostic { } else { var result = "Message: \(debugDescription)\nFix-Its:\n" for fixIt in fixIts { - result += "\t\(fixIt.message.message)\n\t\(fixIt.changes.first!._result.replacingOccurrences(of: "\n", with: "\n\t"))" + result += "\t\(fixIt.message.message)\n\t\(fixIt.changes.first!._result._replacing("\n", with: "\n\t"))" } return result } @@ -113,14 +114,14 @@ extension DiagnosticTest { } else { var result = "Message: \(message)\nFix-Its:\n" for fixIt in fixIts { - result += "\t\(fixIt.message)\n\t\(fixIt.result.replacingOccurrences(of: "\n", with: "\n\t"))" + result += "\t\(fixIt.message)\n\t\(fixIt.result._replacing("\n", with: "\n\t"))" } return result } } } -func AssertMacroExpansion(macros: [String : Macro.Type], testModuleName: String = "TestModule", testFileName: String = "test.swift", _ source: String, _ result: String = "", diagnostics: Set = [], file: StaticString = #filePath, line: UInt = #line) { +func AssertMacroExpansion(macros: [String : Macro.Type], testModuleName: String = "TestModule", testFileName: String = "test.swift", _ source: String, _ result: String = "", diagnostics: Set = [], sourceLocation: Testing.SourceLocation = #_sourceLocation) { let origSourceFile = Parser.parse(source: source) let expandedSourceFile: Syntax let context: BasicMacroExpansionContext @@ -131,43 +132,53 @@ func AssertMacroExpansion(macros: [String : Macro.Type], testModuleName: String BasicMacroExpansionContext(sharingWith: context, lexicalContext: [$0]) } } catch { - XCTFail("Operator folding on input source failed with error \(error)") + Issue.record("Operator folding on input source failed with error \(error)") return } let expansionResult = expandedSourceFile.description if !context.diagnostics.contains(where: { $0.diagMessage.severity == .error }) { - XCTAssertEqual(expansionResult, result, file: file, line: line) + #expect(expansionResult == result, sourceLocation: sourceLocation) } for diagnostic in context.diagnostics { if !diagnostics.contains(where: { $0.matches(diagnostic) }) { - XCTFail("Produced extra diagnostic:\n\(diagnostic._assertionDescription)", file: file, line: line) + Issue.record("Produced extra diagnostic:\n\(diagnostic._assertionDescription)", sourceLocation: sourceLocation) } else { let location = context.location(of: diagnostic.node, at: .afterLeadingTrivia, filePathMode: .fileID) - XCTAssertNotNil(location, "Produced diagnostic without attached source information:\n\(diagnostic._assertionDescription)", file: file, line: line) + #expect(location != nil, "Produced diagnostic without attached source information:\n\(diagnostic._assertionDescription)", sourceLocation: sourceLocation) } } for diagnostic in diagnostics { if !context.diagnostics.contains(where: { diagnostic.matches($0) }) { - XCTFail("Failed to produce diagnostic:\n\(diagnostic._assertionDescription)", file: file, line: line) + Issue.record("Failed to produce diagnostic:\n\(diagnostic._assertionDescription)", sourceLocation: sourceLocation) } } } -func AssertPredicateExpansion(_ source: String, _ result: String = "", diagnostics: Set = [], file: StaticString = #filePath, line: UInt = #line) { +func AssertPredicateExpansion(_ source: String, _ result: String = "", diagnostics: Set = [], sourceLocation: Testing.SourceLocation = #_sourceLocation) { AssertMacroExpansion( macros: ["Predicate": PredicateMacro.self], source, result, diagnostics: diagnostics, - file: file, - line: line + sourceLocation: sourceLocation ) AssertMacroExpansion( macros: ["Expression" : FoundationMacros.ExpressionMacro.self], - source.replacing("#Predicate", with: "#Expression"), - result.replacing(".Predicate", with: ".Expression"), + source._replacing("#Predicate", with: "#Expression"), + result._replacing(".Predicate", with: ".Expression"), diagnostics: Set(diagnostics.map(\.mappedToExpression)), - file: file, - line: line + sourceLocation: sourceLocation ) } + +extension String { + func _replacing(_ text: String, with other: String) -> Self { + if #available(macOS 13.0, *) { + // Use the stdlib API if available + self.replacing(text, with: other) + } else { + // Use the Foundation API on older OSes + self.replacingOccurrences(of: text, with: other, options: [.literal]) + } + } +} diff --git a/Tests/FoundationMacrosTests/PredicateMacroBasicTests.swift b/Tests/FoundationMacrosTests/PredicateMacroBasicTests.swift index fbd338f54..aa904197f 100644 --- a/Tests/FoundationMacrosTests/PredicateMacroBasicTests.swift +++ b/Tests/FoundationMacrosTests/PredicateMacroBasicTests.swift @@ -10,10 +10,11 @@ // //===----------------------------------------------------------------------===// -import XCTest +import Testing -final class PredicateMacroBasicTests: XCTestCase { - func testSimple() { +@Suite("#Predicate Macro Basics") +private struct PredicateMacroBasicTests { + @Test func simple() { AssertPredicateExpansion( """ #Predicate { input in @@ -30,7 +31,7 @@ final class PredicateMacroBasicTests: XCTestCase { ) } - func testImplicitReturn() { + @Test func implicitReturn() { AssertPredicateExpansion( """ #Predicate { input in @@ -47,7 +48,7 @@ final class PredicateMacroBasicTests: XCTestCase { ) } - func testInferredGenerics() { + @Test func inferredGenerics() { AssertPredicateExpansion( """ #Predicate { input in @@ -64,7 +65,7 @@ final class PredicateMacroBasicTests: XCTestCase { ) } - func testShorthandArgumentNames() { + @Test func shorthandArgumentNames() { AssertPredicateExpansion( """ #Predicate { @@ -81,7 +82,7 @@ final class PredicateMacroBasicTests: XCTestCase { ) } - func testExplicitClosureArgumentTypes() { + @Test func explicitClosureArgumentTypes() { AssertPredicateExpansion( """ #Predicate { (a: Int, b: String) -> Bool in @@ -98,7 +99,7 @@ final class PredicateMacroBasicTests: XCTestCase { ) } - func testDiagnoseMissingTrailingClosure() { + @Test func diagnoseMissingTrailingClosure() { AssertPredicateExpansion( """ #Predicate @@ -141,7 +142,7 @@ final class PredicateMacroBasicTests: XCTestCase { ) } - func testKeyPath() { + @Test func keyPath() { AssertPredicateExpansion( """ #Predicate { @@ -192,7 +193,7 @@ final class PredicateMacroBasicTests: XCTestCase { ) } - func testComments() { + @Test func comments() { AssertPredicateExpansion( """ // comment diff --git a/Tests/FoundationMacrosTests/PredicateMacroFunctionCallTests.swift b/Tests/FoundationMacrosTests/PredicateMacroFunctionCallTests.swift index 0380f733d..32679f11a 100644 --- a/Tests/FoundationMacrosTests/PredicateMacroFunctionCallTests.swift +++ b/Tests/FoundationMacrosTests/PredicateMacroFunctionCallTests.swift @@ -10,10 +10,11 @@ // //===----------------------------------------------------------------------===// -import XCTest +import Testing -final class PredicateMacroFunctionCallTests: XCTestCase { - func testSubscript() { +@Suite("#Predicate Macro Function Calls") +private struct PredicateMacroFunctionCallTests { + @Test func `subscript`() { AssertPredicateExpansion( """ #Predicate { input in @@ -96,7 +97,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { ) } - func testContains() { + @Test func contains() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -130,7 +131,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { ) } - func testContainsWhere() { + @Test func containsWhere() { AssertPredicateExpansion( """ #Predicate { inputA in @@ -175,7 +176,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { ) } - func testAllSatisfy() { + @Test func allSatisfy() { AssertPredicateExpansion( """ #Predicate { inputA in @@ -220,7 +221,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { ) } - func testFilter() { + @Test func filter() { AssertPredicateExpansion( """ #Predicate { inputA in @@ -347,7 +348,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { ) } - func testStartsWith() { + @Test func startsWith() { AssertPredicateExpansion( """ #Predicate { inputA in @@ -386,7 +387,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { ) } - func testMin() { + @Test func min() { AssertPredicateExpansion( """ #Predicate<[Int]> { inputA in @@ -406,7 +407,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { ) } - func testMax() { + @Test func max() { AssertPredicateExpansion( """ #Predicate<[Int]> { inputA in @@ -426,7 +427,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { ) } - func testLocalizedStandardContains() { + @Test func localizedStandardContains() { AssertPredicateExpansion( """ #Predicate { inputA in @@ -462,7 +463,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { ) } - func testLocalizedStandardCompare() { + @Test func localizedStandardCompare() { AssertPredicateExpansion( """ #Predicate { inputA in @@ -516,7 +517,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { ) } - func testCaseInsensitiveCompare() { + @Test func caseInsensitiveCompare() { AssertPredicateExpansion( """ #Predicate { inputA in @@ -535,7 +536,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { } #if FOUNDATION_FRAMEWORK - func testEvaluate() { + @Test func evaluate() { AssertPredicateExpansion( """ #Predicate { input in @@ -584,7 +585,7 @@ final class PredicateMacroFunctionCallTests: XCTestCase { } #endif - func testDiagnoseUnsupportedFunction() { + @Test func diagnoseUnsupportedFunction() { AssertPredicateExpansion( """ #Predicate { inputA in diff --git a/Tests/FoundationMacrosTests/PredicateMacroLanguageOperatorTests.swift b/Tests/FoundationMacrosTests/PredicateMacroLanguageOperatorTests.swift index aa7a00e5f..2b4bf09d4 100644 --- a/Tests/FoundationMacrosTests/PredicateMacroLanguageOperatorTests.swift +++ b/Tests/FoundationMacrosTests/PredicateMacroLanguageOperatorTests.swift @@ -10,10 +10,11 @@ // //===----------------------------------------------------------------------===// -import XCTest +import Testing -final class PredicateMacroLanguageOperatorTests: XCTestCase { - func testEqual() { +@Suite("#Predicate Macro Language Operators") +private struct PredicateMacroLanguageOperatorTests { + @Test func equal() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -31,7 +32,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testEqualExplicitReturn() { + @Test func equalExplicitReturn() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -49,7 +50,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testNotEqual() { + @Test func notEqual() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -67,7 +68,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testComparison() { + @Test func comparison() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -137,7 +138,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testConjunction() { + @Test func conjunction() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -155,7 +156,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testDisjunction() { + @Test func disjunction() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -173,7 +174,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testArithmetic() { + @Test func arithmetic() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -226,7 +227,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testDivision() { + @Test func division() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -244,7 +245,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testRemainder() { + @Test func remainder() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -262,7 +263,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testNegation() { + @Test func negation() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -279,7 +280,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testUnaryMinus() { + @Test func unaryMinus() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -296,7 +297,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testNilCoalesce() { + @Test func nilCoalesce() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -314,7 +315,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testRanges() { + @Test func ranges() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in @@ -348,7 +349,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testOptionalChaining() { + @Test func optionalChaining() { AssertPredicateExpansion( """ #Predicate { inputA in @@ -533,7 +534,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testForceUnwrap() { + @Test func forceUnwrap() { AssertPredicateExpansion( """ #Predicate { inputA in @@ -668,7 +669,7 @@ final class PredicateMacroLanguageOperatorTests: XCTestCase { ) } - func testDiagnoseUnknownOperator() { + @Test func diagnoseUnknownOperator() { AssertPredicateExpansion( """ #Predicate { inputA, inputB in diff --git a/Tests/FoundationMacrosTests/PredicateMacroLanguageTokenTests.swift b/Tests/FoundationMacrosTests/PredicateMacroLanguageTokenTests.swift index fa2f1574e..5648b80cd 100644 --- a/Tests/FoundationMacrosTests/PredicateMacroLanguageTokenTests.swift +++ b/Tests/FoundationMacrosTests/PredicateMacroLanguageTokenTests.swift @@ -10,10 +10,11 @@ // //===----------------------------------------------------------------------===// -import XCTest +import Testing -final class PredicateMacroLanguageTokenTests: XCTestCase { - func testConditional() { +@Suite("#Predicate Macro Language Tokens") +private struct PredicateMacroLanguageTokenTests { + @Test func conditional() { AssertPredicateExpansion( """ #Predicate { inputA, inputB, inputC in @@ -32,7 +33,7 @@ final class PredicateMacroLanguageTokenTests: XCTestCase { ) } - func testTypeCheck() { + @Test func typeCheck() { AssertPredicateExpansion( """ #Predicate { input in @@ -49,7 +50,7 @@ final class PredicateMacroLanguageTokenTests: XCTestCase { ) } - func testConditionalCast() { + @Test func conditionalCast() { AssertPredicateExpansion( """ #Predicate { input in @@ -105,7 +106,7 @@ final class PredicateMacroLanguageTokenTests: XCTestCase { ) } - func testIfExpressions() { + @Test func ifExpressions() { AssertPredicateExpansion( """ #Predicate { input in @@ -381,7 +382,7 @@ final class PredicateMacroLanguageTokenTests: XCTestCase { ) } - func testNilLiterals() { + @Test func nilLiterals() { AssertPredicateExpansion( """ #Predicate { input in @@ -399,7 +400,7 @@ final class PredicateMacroLanguageTokenTests: XCTestCase { ) } - func testDiagnoseDeclarations() { + @Test func diagnoseDeclarations() { AssertPredicateExpansion( """ #Predicate { input in @@ -461,7 +462,7 @@ final class PredicateMacroLanguageTokenTests: XCTestCase { ) } - func testDiagnoseMiscellaneousStatements() { + @Test func diagnoseMiscellaneousStatements() { AssertPredicateExpansion( """ #Predicate { input in diff --git a/Tests/FoundationMacrosTests/PredicateMacroUsageTests.swift b/Tests/FoundationMacrosTests/PredicateMacroUsageTests.swift index b69a128dd..815ca0c80 100644 --- a/Tests/FoundationMacrosTests/PredicateMacroUsageTests.swift +++ b/Tests/FoundationMacrosTests/PredicateMacroUsageTests.swift @@ -12,12 +12,12 @@ #if FOUNDATION_FRAMEWORK -import XCTest +import Testing +import Foundation // MARK: - Stubs @inline(never) -@available(macOS 14, iOS 17, watchOS 10, tvOS 17, *) fileprivate func _blackHole(_ t: T) {} @inline(never) @@ -26,9 +26,10 @@ fileprivate func _blackHoleExplicitInput(_ predicate: Predicate) {} // MARK: - Tests -@available(macOS 14, iOS 17, watchOS 10, tvOS 17, *) -final class PredicateMacroUsageTests: XCTestCase { - func testUsage() { +@Suite("#Predicate Macro Usage") +private struct PredicateMacroUsageTests { + @available(macOS 14, iOS 17, watchOS 10, tvOS 17, *) + @Test func usage() { _blackHole(#Predicate { return $0 }) From 5af94abeed723989842534e0a451ae07e4bd22ba Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Fri, 13 Jun 2025 13:24:12 -0700 Subject: [PATCH 12/14] Convert FileManager and URL tests to swift-testing (#1351) * Convert FileManager and URL tests to swift-testing * Fix Linux/Windows build failures * Fix Windows build failures * Remove URL prefix from other test functions * Fix Windows build failures --- .../FileManager/FileManagerTests.swift | 931 +++++++------ ...rPlayground.swift => FilePlayground.swift} | 68 +- .../FoundationEssentialsTests/URLTests.swift | 1207 ++++++++--------- 3 files changed, 1120 insertions(+), 1086 deletions(-) rename Tests/FoundationEssentialsTests/FileManager/{FileManagerPlayground.swift => FilePlayground.swift} (64%) diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift index 29c94c5fa..4d59b9e66 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift @@ -10,16 +10,15 @@ // //===----------------------------------------------------------------------===// +import Testing #if canImport(TestSupport) import TestSupport -#endif // canImport(TestSupport) +#endif #if canImport(FoundationEssentials) @testable import FoundationEssentials -#endif - -#if FOUNDATION_FRAMEWORK +#else @testable import Foundation #endif @@ -179,22 +178,48 @@ final class CapturingFileManagerDelegate : FileManagerDelegate, Sendable { } #endif -final class FileManagerTests : XCTestCase { +@Suite("FileManager") +private struct FileManagerTests { + + private static var isUnixRoot: Bool { + #if !os(Windows) + getuid() == 0 + #else + false + #endif + } + + private static var isWindows: Bool { + #if os(Windows) + true + #else + false + #endif + } + + private static var isDarwin: Bool { + #if canImport(Darwin) + true + #else + false + #endif + } + private func randomData(count: Int = 10000) -> Data { Data((0 ..< count).map { _ in UInt8.random(in: .min ..< .max) }) } - func testContentsAtPath() throws { + @Test func contentsAtPath() async throws { let data = randomData() - try FileManagerPlayground { + try await FilePlayground { File("test", contents: data) }.test { - XCTAssertEqual($0.contents(atPath: "test"), data) + #expect($0.contents(atPath: "test") == data) } } - func testContentsEqualAtPaths() throws { - try FileManagerPlayground { + @Test func contentsEqualAtPaths() async throws { + try await FilePlayground { Directory("dir1") { Directory("dir2") { "Foo" @@ -229,18 +254,18 @@ final class FileManagerTests : XCTestCase { Directory("EmptyDirectory") {} "EmptyFile" }.test { - XCTAssertTrue($0.contentsEqual(atPath: "dir1", andPath: "dir1_copy")) - XCTAssertFalse($0.contentsEqual(atPath: "dir1/dir2", andPath: "dir1/dir3")) - XCTAssertFalse($0.contentsEqual(atPath: "dir1", andPath: "dir1_diffdata")) - XCTAssertFalse($0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "symlinks/Foo"), "Symbolic link should not be equal to its destination") - XCTAssertFalse($0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "EmptyFile"), "Symbolic link should not be equal to an empty file") - XCTAssertFalse($0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "EmptyDirectory"), "Symbolic link should not be equal to an empty directory") - XCTAssertFalse($0.contentsEqual(atPath: "symlinks/EmptyDirectory", andPath: "EmptyFile"), "Empty directory should not be equal to empty file") + #expect($0.contentsEqual(atPath: "dir1", andPath: "dir1_copy")) + #expect(!$0.contentsEqual(atPath: "dir1/dir2", andPath: "dir1/dir3")) + #expect(!$0.contentsEqual(atPath: "dir1", andPath: "dir1_diffdata")) + #expect(!$0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "symlinks/Foo"), "Symbolic link should not be equal to its destination") + #expect(!$0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "EmptyFile"), "Symbolic link should not be equal to an empty file") + #expect(!$0.contentsEqual(atPath: "symlinks/LinkToFoo", andPath: "EmptyDirectory"), "Symbolic link should not be equal to an empty directory") + #expect(!$0.contentsEqual(atPath: "symlinks/EmptyDirectory", andPath: "EmptyFile"), "Empty directory should not be equal to empty file") } } - - func testDirectoryContentsAtPath() throws { - try FileManagerPlayground { + + @Test func directoryContentsAtPath() async throws { + try await FilePlayground { Directory("dir1") { Directory("dir2") { "Foo" @@ -250,18 +275,21 @@ final class FileManagerTests : XCTestCase { "Baz" } } - }.test { - XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir1").sorted(), ["dir2", "dir3"]) - XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir1/dir2").sorted(), ["Bar", "Foo"]) - XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir1/dir3").sorted(), ["Baz"]) - XCTAssertThrowsError(try $0.contentsOfDirectory(atPath: "does_not_exist")) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile) + }.test { fileManager in + #expect(try fileManager.contentsOfDirectory(atPath: "dir1").sorted() == ["dir2", "dir3"]) + #expect(try fileManager.contentsOfDirectory(atPath: "dir1/dir2").sorted() == ["Bar", "Foo"]) + let contents = try fileManager.contentsOfDirectory(atPath: "dir1/dir3").sorted() + #expect(contents == ["Baz"]) + #expect { + try fileManager.contentsOfDirectory(atPath: "does_not_exist") + } throws: { + ($0 as? CocoaError)?.code == .fileReadNoSuchFile } } } - - func testSubpathsOfDirectoryAtPath() throws { - try FileManagerPlayground { + + @Test func subpathsOfDirectoryAtPath() async throws { + try await FilePlayground { Directory("dir1") { Directory("dir2") { "Foo" @@ -276,166 +304,187 @@ final class FileManagerTests : XCTestCase { SymbolicLink("Bar", destination: "Foo") SymbolicLink("Parent", destination: "..") } - }.test { - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: "dir1").sorted(), ["dir2", "dir2/Bar", "dir2/Foo", "dir3", "dir3/Baz"]) - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: "dir1/dir2").sorted(), ["Bar", "Foo"]) - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: "dir1/dir3").sorted(), ["Baz"]) - - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: "symlinks").sorted(), ["Bar", "Foo", "Parent"]) - - XCTAssertThrowsError(try $0.subpathsOfDirectory(atPath: "does_not_exist")) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile) + }.test { fileManager in + #expect(try fileManager.subpathsOfDirectory(atPath: "dir1").sorted() == ["dir2", "dir2/Bar", "dir2/Foo", "dir3", "dir3/Baz"]) + #expect(try fileManager.subpathsOfDirectory(atPath: "dir1/dir2").sorted() == ["Bar", "Foo"]) + #expect(try fileManager.subpathsOfDirectory(atPath: "dir1/dir3").sorted() == ["Baz"]) + + #expect(try fileManager.subpathsOfDirectory(atPath: "symlinks").sorted() == ["Bar", "Foo", "Parent"]) + + #expect { + try fileManager.subpathsOfDirectory(atPath: "does_not_exist") + } throws: { + ($0 as? CocoaError)?.code == .fileReadNoSuchFile } - XCTAssertThrowsError(try $0.subpathsOfDirectory(atPath: "")) { + #expect { + try fileManager.subpathsOfDirectory(atPath: "") + } throws: { #if os(Windows) - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadInvalidFileName) + ($0 as? CocoaError)?.code == .fileReadInvalidFileName #else - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile) + ($0 as? CocoaError)?.code == .fileReadNoSuchFile #endif } - + let fullContents = ["dir1", "dir1/dir2", "dir1/dir2/Bar", "dir1/dir2/Foo", "dir1/dir3", "dir1/dir3/Baz", "symlinks", "symlinks/Bar", "symlinks/Foo", "symlinks/Parent"] - let cwd = $0.currentDirectoryPath - XCTAssertNotEqual(cwd.last, "/") + let cwd = fileManager.currentDirectoryPath + #expect(cwd.last != "/") let paths = [cwd, "\(cwd)/", "\(cwd)//", ".", "./", ".//"] for path in paths { - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: path).sorted(), fullContents) + let subpaths = try fileManager.subpathsOfDirectory(atPath: path).sorted() + #expect(subpaths == fullContents) } - + } } - - func testCreateDirectoryAtPath() throws { - try FileManagerPlayground { + + @Test func createDirectoryAtPath() async throws { + try await FilePlayground { "preexisting_file" - }.test { - try $0.createDirectory(atPath: "create_dir_test", withIntermediateDirectories: false) - XCTAssertEqual(try $0.contentsOfDirectory(atPath: ".").sorted(), ["create_dir_test", "preexisting_file"]) - try $0.createDirectory(atPath: "create_dir_test2/nested", withIntermediateDirectories: true) - XCTAssertEqual(try $0.contentsOfDirectory(atPath: "create_dir_test2"), ["nested"]) - try $0.createDirectory(atPath: "create_dir_test2/nested2", withIntermediateDirectories: true) - XCTAssertEqual(try $0.contentsOfDirectory(atPath: "create_dir_test2").sorted(), ["nested", "nested2"]) - XCTAssertNoThrow(try $0.createDirectory(atPath: "create_dir_test2/nested2", withIntermediateDirectories: true)) - + }.test { fileManager in + try fileManager.createDirectory(atPath: "create_dir_test", withIntermediateDirectories: false) + #expect(try fileManager.contentsOfDirectory(atPath: ".").sorted() == ["create_dir_test", "preexisting_file"]) + try fileManager.createDirectory(atPath: "create_dir_test2/nested", withIntermediateDirectories: true) + #expect(try fileManager.contentsOfDirectory(atPath: "create_dir_test2") == ["nested"]) + try fileManager.createDirectory(atPath: "create_dir_test2/nested2", withIntermediateDirectories: true) + #expect(try fileManager.contentsOfDirectory(atPath: "create_dir_test2").sorted() == ["nested", "nested2"]) + #expect(throws: Never.self) { + try fileManager.createDirectory(atPath: "create_dir_test2/nested2", withIntermediateDirectories: true) + } + #if os(Windows) - try $0.createDirectory(atPath: "create_dir_test3\\nested", withIntermediateDirectories: true) - XCTAssertEqual(try $0.contentsOfDirectory(atPath: "create_dir_test3"), ["nested"]) + try fileManager.createDirectory(atPath: "create_dir_test3\\nested", withIntermediateDirectories: true) + #expect(try fileManager.contentsOfDirectory(atPath: "create_dir_test3") == ["nested"]) #endif - - XCTAssertThrowsError(try $0.createDirectory(atPath: "create_dir_test", withIntermediateDirectories: false)) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists) + + #expect { + try fileManager.createDirectory(atPath: "create_dir_test", withIntermediateDirectories: false) + } throws: { + ($0 as? CocoaError)?.code == .fileWriteFileExists } - XCTAssertThrowsError(try $0.createDirectory(atPath: "create_dir_test4/nested", withIntermediateDirectories: false)) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileNoSuchFile) + #expect { + try fileManager.createDirectory(atPath: "create_dir_test4/nested", withIntermediateDirectories: false) + } throws: { + ($0 as? CocoaError)?.code == .fileNoSuchFile } - XCTAssertThrowsError(try $0.createDirectory(atPath: "preexisting_file", withIntermediateDirectories: false)) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists) + #expect { + try fileManager.createDirectory(atPath: "preexisting_file", withIntermediateDirectories: false) + } throws: { + ($0 as? CocoaError)?.code == .fileWriteFileExists } - XCTAssertThrowsError(try $0.createDirectory(atPath: "preexisting_file", withIntermediateDirectories: true)) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists) + #expect { + try fileManager.createDirectory(atPath: "preexisting_file", withIntermediateDirectories: true) + } throws: { + ($0 as? CocoaError)?.code == .fileWriteFileExists } } } - - func testLinkFileAtPathToPath() throws { - try FileManagerPlayground { + + @Test func linkFileAtPathToPath() async throws { + try await FilePlayground { "foo" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.linkItem(atPath: "foo", toPath: "bar") - XCTAssertEqual($0.delegateCaptures.shouldLink, [.init("foo", "bar")]) + #expect($0.delegateCaptures.shouldLink == [.init("foo", "bar")]) #if os(Android) // Hard links are not normally allowed on Android. - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterLinkError, [.init("foo", "bar", code: .fileWriteNoPermission)]) - XCTAssertFalse($0.fileExists(atPath: "bar")) + #expect($0.delegateCaptures.shouldProceedAfterLinkError == [.init("foo", "bar", code: .fileWriteNoPermission)]) + #expect(!$0.fileExists(atPath: "bar")) #else - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterLinkError, []) - XCTAssertTrue($0.fileExists(atPath: "bar")) + #expect($0.delegateCaptures.shouldProceedAfterLinkError == []) + #expect($0.fileExists(atPath: "bar")) #endif } - - try FileManagerPlayground { + + try await FilePlayground { "foo" "bar" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.linkItem(atPath: "foo", toPath: "bar") - XCTAssertEqual($0.delegateCaptures.shouldLink, [.init("foo", "bar")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterLinkError, [.init("foo", "bar", code: .fileWriteFileExists)]) + #expect($0.delegateCaptures.shouldLink == [.init("foo", "bar")]) + #expect($0.delegateCaptures.shouldProceedAfterLinkError == [.init("foo", "bar", code: .fileWriteFileExists)]) } } - - func testCopyFileAtPathToPath() throws { - try FileManagerPlayground { + + @Test func copyFileAtPathToPath() async throws { + try await FilePlayground { "foo" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.copyItem(atPath: "foo", toPath: "bar") - XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("foo", "bar")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterCopyError, []) - XCTAssertTrue($0.fileExists(atPath: "bar")) + #expect($0.delegateCaptures.shouldCopy == [.init("foo", "bar")]) + #expect($0.delegateCaptures.shouldProceedAfterCopyError == []) + #expect($0.fileExists(atPath: "bar")) } - - try FileManagerPlayground { + + try await FilePlayground { "foo" "bar" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.copyItem(atPath: "foo", toPath: "bar") - XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("foo", "bar")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterCopyError, [.init("foo", "bar", code: .fileWriteFileExists)]) + #expect($0.delegateCaptures.shouldCopy == [.init("foo", "bar")]) + #expect($0.delegateCaptures.shouldProceedAfterCopyError == [.init("foo", "bar", code: .fileWriteFileExists)]) } - - try FileManagerPlayground { + + try await FilePlayground { "foo" SymbolicLink("bar", destination: "foo") }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.copyItem(atPath: "bar", toPath: "copy") - XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("bar", "copy")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterCopyError, []) + #expect($0.delegateCaptures.shouldCopy == [.init("bar", "copy")]) + #expect($0.delegateCaptures.shouldProceedAfterCopyError == []) let copyDestination = try $0.destinationOfSymbolicLink(atPath: "copy") - XCTAssertEqual(copyDestination.lastPathComponent, "foo", "Copied symbolic link points at \(copyDestination) instead of foo") + #expect(copyDestination.lastPathComponent == "foo", "Copied symbolic link points at \(copyDestination) instead of foo") } - try FileManagerPlayground { + try await FilePlayground { Directory("dir") { "foo" } SymbolicLink("link", destination: "dir") }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.copyItem(atPath: "link", toPath: "copy") - XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("link", "copy")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterCopyError, []) + #expect($0.delegateCaptures.shouldCopy == [.init("link", "copy")]) + #expect($0.delegateCaptures.shouldProceedAfterCopyError == []) let copyDestination = try $0.destinationOfSymbolicLink(atPath: "copy") - XCTAssertEqual(copyDestination.lastPathComponent, "dir", "Copied symbolic link points at \(copyDestination) instead of foo") + #expect(copyDestination.lastPathComponent == "dir", "Copied symbolic link points at \(copyDestination) instead of foo") } } - - func testCreateSymbolicLinkAtPath() throws { - try FileManagerPlayground { + + @Test func createSymbolicLinkAtPath() async throws { + try await FilePlayground { "foo" Directory("dir") {} - }.test { - try $0.createSymbolicLink(atPath: "bar", withDestinationPath: "foo") - XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: "bar"), "foo") - - try $0.createSymbolicLink(atPath: "dir_link", withDestinationPath: "dir") - XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: "dir_link"), "dir") - - XCTAssertThrowsError(try $0.createSymbolicLink(atPath: "bar", withDestinationPath: "foo")) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists) + }.test { fileManager in + try fileManager.createSymbolicLink(atPath: "bar", withDestinationPath: "foo") + #expect(try fileManager.destinationOfSymbolicLink(atPath: "bar") == "foo") + + try fileManager.createSymbolicLink(atPath: "dir_link", withDestinationPath: "dir") + #expect(try fileManager.destinationOfSymbolicLink(atPath: "dir_link") == "dir") + + #expect { + try fileManager.createSymbolicLink(atPath: "bar", withDestinationPath: "foo") + } throws: { + ($0 as? CocoaError)?.code == .fileWriteFileExists } - XCTAssertThrowsError(try $0.createSymbolicLink(atPath: "foo", withDestinationPath: "baz")) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteFileExists) + #expect { + try fileManager.createSymbolicLink(atPath: "foo", withDestinationPath: "baz") + } throws: { + ($0 as? CocoaError)?.code == .fileWriteFileExists } - XCTAssertThrowsError(try $0.destinationOfSymbolicLink(atPath: "foo")) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadUnknown) + #expect { + try fileManager.destinationOfSymbolicLink(atPath: "foo") + } throws: { + ($0 as? CocoaError)?.code == .fileReadUnknown } } - try FileManagerPlayground { + try await FilePlayground { Directory("dir") { Directory("other_dir") { "file" @@ -446,120 +495,122 @@ final class FileManagerTests : XCTestCase { try $0.createSymbolicLink(atPath: "dir/link", withDestinationPath: "other_dir") // Ensure it is created successfully - XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: "dir/link"), "other_dir") - XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir/link"), ["file"]) - + #expect(try $0.destinationOfSymbolicLink(atPath: "dir/link") == "other_dir") + #expect(try $0.contentsOfDirectory(atPath: "dir/link") == ["file"]) + do { // Second symlink creation with an absolute path let absolute = URL(filePath: "dir/link2", relativeTo: URL(filePath: $0.currentDirectoryPath, directoryHint: .isDirectory)).path try $0.createSymbolicLink(atPath: absolute, withDestinationPath: "other_dir") - + // Ensure it is created successfully - XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: "dir/link2"), "other_dir") - XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir/link2"), ["file"]) + #expect(try $0.destinationOfSymbolicLink(atPath: "dir/link2") == "other_dir") + #expect(try $0.contentsOfDirectory(atPath: "dir/link2") == ["file"]) } - + do { // And lastly a symlink to an absolute path let absolute = URL(filePath: "dir/other_dir", relativeTo: URL(filePath: $0.currentDirectoryPath, directoryHint: .isDirectory)).path try $0.createSymbolicLink(atPath: "dir/link3", withDestinationPath: absolute) - + // Ensure it is created successfully - XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: "dir/link3"), absolute.withFileSystemRepresentation { String(cString: $0!) }) - XCTAssertEqual(try $0.contentsOfDirectory(atPath: "dir/link3"), ["file"]) + #expect(try $0.destinationOfSymbolicLink(atPath: "dir/link3") == absolute.withFileSystemRepresentation { String(cString: $0!) }) + #expect(try $0.contentsOfDirectory(atPath: "dir/link3") == ["file"]) } } } - - func testMoveItemAtPathToPath() throws { + + @Test func moveItemAtPathToPath() async throws { let data = randomData() - try FileManagerPlayground { + try await FilePlayground { Directory("dir") { File("foo", contents: data) "bar" } "other_file" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.moveItem(atPath: "dir", toPath: "dir2") - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["dir2", "dir2/bar", "dir2/foo", "other_file"]) - XCTAssertEqual($0.contents(atPath: "dir2/foo"), data) + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == ["dir2", "dir2/bar", "dir2/foo", "other_file"]) + #expect($0.contents(atPath: "dir2/foo") == data) let rootDir = URL(fileURLWithPath: $0.currentDirectoryPath).path - XCTAssertEqual($0.delegateCaptures.shouldMove, [.init("\(rootDir)/dir", "\(rootDir)/dir2")]) + #expect($0.delegateCaptures.shouldMove == [.init("\(rootDir)/dir", "\(rootDir)/dir2")]) try $0.moveItem(atPath: "does_not_exist", toPath: "dir3") - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterMoveError, [.init("\(rootDir)/does_not_exist", "\(rootDir)/dir3", code: .fileNoSuchFile)]) + #expect($0.delegateCaptures.shouldProceedAfterMoveError == [.init("\(rootDir)/does_not_exist", "\(rootDir)/dir3", code: .fileNoSuchFile)]) try $0.moveItem(atPath: "dir2", toPath: "other_file") - XCTAssertTrue($0.delegateCaptures.shouldProceedAfterMoveError.contains(.init("\(rootDir)/dir2", "\(rootDir)/other_file", code: .fileWriteFileExists))) + #expect($0.delegateCaptures.shouldProceedAfterMoveError.contains(.init("\(rootDir)/dir2", "\(rootDir)/other_file", code: .fileWriteFileExists))) } } - - func testCopyItemAtPathToPath() throws { + + @Test func copyItemAtPathToPath() async throws { let data = randomData() - try FileManagerPlayground { + try await FilePlayground { Directory("dir", attributes: [.posixPermissions : 0o777]) { File("foo", contents: data) "bar" } "other_file" - }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) - try $0.copyItem(atPath: "dir", toPath: "dir2") - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["dir", "dir/bar", "dir/foo", "dir2", "dir2/bar", "dir2/foo", "other_file"]) - XCTAssertEqual($0.contents(atPath: "dir/foo"), data) - XCTAssertEqual($0.contents(atPath: "dir2/foo"), data) + }.test(captureDelegateCalls: true) { fileManager in + #expect(fileManager.delegateCaptures.isEmpty) + try fileManager.copyItem(atPath: "dir", toPath: "dir2") + #expect(try fileManager.subpathsOfDirectory(atPath: ".").sorted() == ["dir", "dir/bar", "dir/foo", "dir2", "dir2/bar", "dir2/foo", "other_file"]) + #expect(fileManager.contents(atPath: "dir/foo") == data) + #expect(fileManager.contents(atPath: "dir2/foo") == data) #if os(Windows) - XCTAssertEqual($0.delegateCaptures.shouldCopy, [.init("dir", "dir2"), .init("dir/bar", "dir2/bar"), .init("dir/foo", "dir2/foo")]) + #expect(fileManager.delegateCaptures.shouldCopy == [.init("dir", "dir2"), .init("dir/bar", "dir2/bar"), .init("dir/foo", "dir2/foo")]) #else - var shouldCopy = $0.delegateCaptures.shouldCopy - XCTAssertEqual(shouldCopy.removeFirst(), .init("dir", "dir2")) - XCTAssertEqual(shouldCopy.sorted(), [.init("dir/foo", "dir2/foo"), .init("dir/bar", "dir2/bar")].sorted()) - + var shouldCopy = fileManager.delegateCaptures.shouldCopy + #expect(shouldCopy.removeFirst() == .init("dir", "dir2")) + #expect(shouldCopy.sorted() == [.init("dir/foo", "dir2/foo"), .init("dir/bar", "dir2/bar")].sorted()) + // Specifically for non-Windows (where copying directory metadata takes a special path) double check that the metadata was copied exactly - XCTAssertEqual(try $0.attributesOfItem(atPath: "dir2")[.posixPermissions] as? UInt, 0o777) + #expect(try fileManager.attributesOfItem(atPath: "dir2")[.posixPermissions] as? UInt == 0o777) #endif - XCTAssertThrowsError(try $0.copyItem(atPath: "does_not_exist", toPath: "dir3")) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile) + #expect { + try fileManager.copyItem(atPath: "does_not_exist", toPath: "dir3") + } throws: { + ($0 as? CocoaError)?.code == .fileReadNoSuchFile } - - try $0.copyItem(atPath: "dir", toPath: "other_file") - XCTAssertTrue($0.delegateCaptures.shouldProceedAfterCopyError.contains(.init("dir", "other_file", code: .fileWriteFileExists))) + + try fileManager.copyItem(atPath: "dir", toPath: "other_file") + #expect(fileManager.delegateCaptures.shouldProceedAfterCopyError.contains(.init("dir", "other_file", code: .fileWriteFileExists))) } } - - func testRemoveItemAtPath() throws { - try FileManagerPlayground { + + @Test func removeItemAtPath() async throws { + try await FilePlayground { Directory("dir") { "foo" "bar" } "other" }.test(captureDelegateCalls: true) { - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.removeItem(atPath: "dir/bar") - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["dir", "dir/foo", "other"]) - XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("dir/bar")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, []) + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == ["dir", "dir/foo", "other"]) + #expect($0.delegateCaptures.shouldRemove == [.init("dir/bar")]) + #expect($0.delegateCaptures.shouldProceedAfterRemoveError == []) let rootDir = URL(fileURLWithPath: $0.currentDirectoryPath).path try $0.removeItem(atPath: "dir") - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["other"]) - XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, []) - + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == ["other"]) + #expect($0.delegateCaptures.shouldRemove == [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo")]) + #expect($0.delegateCaptures.shouldProceedAfterRemoveError == []) + try $0.removeItem(atPath: "other") - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), []) - XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo"), .init("other")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, []) - + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == []) + #expect($0.delegateCaptures.shouldRemove == [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo"), .init("other")]) + #expect($0.delegateCaptures.shouldProceedAfterRemoveError == []) + try $0.removeItem(atPath: "does_not_exist") - XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo"), .init("other"), .init("does_not_exist")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, [.init("does_not_exist", code: .fileNoSuchFile)]) + #expect($0.delegateCaptures.shouldRemove == [.init("dir/bar"), .init("\(rootDir)/dir"), .init("\(rootDir)/dir/foo"), .init("other"), .init("does_not_exist")]) + #expect($0.delegateCaptures.shouldProceedAfterRemoveError == [.init("does_not_exist", code: .fileNoSuchFile)]) } - try FileManagerPlayground { + try await FilePlayground { Directory("dir") { Directory("dir2") { "file" @@ -568,65 +619,67 @@ final class FileManagerTests : XCTestCase { }.test(captureDelegateCalls: true) { let rootDir = URL(fileURLWithPath: $0.currentDirectoryPath).path - XCTAssertTrue($0.delegateCaptures.isEmpty) + #expect($0.delegateCaptures.isEmpty) try $0.removeItem(atPath: "dir") - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), []) - XCTAssertEqual($0.delegateCaptures.shouldRemove, [.init("\(rootDir)/dir"), .init("\(rootDir)/dir/dir2"), .init("\(rootDir)/dir/dir2/file")]) - XCTAssertEqual($0.delegateCaptures.shouldProceedAfterRemoveError, []) + #expect(try $0.subpathsOfDirectory(atPath: ".").sorted() == []) + #expect($0.delegateCaptures.shouldRemove == [.init("\(rootDir)/dir"), .init("\(rootDir)/dir/dir2"), .init("\(rootDir)/dir/dir2/file")]) + #expect($0.delegateCaptures.shouldProceedAfterRemoveError == []) } #if canImport(Darwin) // not supported on linux as the test depends on FileManager.removeItem calling removefile(3) - try FileManagerPlayground { - }.test { + try await FilePlayground { + }.test { fileManager in // Create hierarchy in which the leaf is a long path (length > PATH_MAX) - let rootDir = $0.currentDirectoryPath + let rootDir = fileManager.currentDirectoryPath let aas = Array(repeating: "a", count: Int(NAME_MAX) - 3).joined() let bbs = Array(repeating: "b", count: Int(NAME_MAX) - 3).joined() let ccs = Array(repeating: "c", count: Int(NAME_MAX) - 3).joined() let dds = Array(repeating: "d", count: Int(NAME_MAX) - 3).joined() let ees = Array(repeating: "e", count: Int(NAME_MAX) - 3).joined() let leaf = "longpath" - - try $0.createDirectory(atPath: aas, withIntermediateDirectories: true) - XCTAssertTrue($0.changeCurrentDirectoryPath(aas)) - try $0.createDirectory(atPath: bbs, withIntermediateDirectories: true) - XCTAssertTrue($0.changeCurrentDirectoryPath(bbs)) - try $0.createDirectory(atPath: ccs, withIntermediateDirectories: true) - XCTAssertTrue($0.changeCurrentDirectoryPath(ccs)) - try $0.createDirectory(atPath: dds, withIntermediateDirectories: true) - XCTAssertTrue($0.changeCurrentDirectoryPath(dds)) - try $0.createDirectory(atPath: ees, withIntermediateDirectories: true) - XCTAssertTrue($0.changeCurrentDirectoryPath(ees)) - try $0.createDirectory(atPath: leaf, withIntermediateDirectories: true) - - XCTAssertTrue($0.changeCurrentDirectoryPath(rootDir)) + + try fileManager.createDirectory(atPath: aas, withIntermediateDirectories: true) + #expect(fileManager.changeCurrentDirectoryPath(aas)) + try fileManager.createDirectory(atPath: bbs, withIntermediateDirectories: true) + #expect(fileManager.changeCurrentDirectoryPath(bbs)) + try fileManager.createDirectory(atPath: ccs, withIntermediateDirectories: true) + #expect(fileManager.changeCurrentDirectoryPath(ccs)) + try fileManager.createDirectory(atPath: dds, withIntermediateDirectories: true) + #expect(fileManager.changeCurrentDirectoryPath(dds)) + try fileManager.createDirectory(atPath: ees, withIntermediateDirectories: true) + #expect(fileManager.changeCurrentDirectoryPath(ees)) + try fileManager.createDirectory(atPath: leaf, withIntermediateDirectories: true) + + #expect(fileManager.changeCurrentDirectoryPath(rootDir)) let fullPath = "\(aas)/\(bbs)/\(ccs)/\(dds)/\(ees)/\(leaf)" - XCTAssertThrowsError(try $0.removeItem(atPath: fullPath)) { + #expect { + try fileManager.removeItem(atPath: fullPath) + } throws: { let underlyingPosixError = ($0 as? CocoaError)?.underlying as? POSIXError - XCTAssertEqual(underlyingPosixError?.code, .ENAMETOOLONG, "removeItem didn't fail with ENAMETOOLONG; produced error: \($0)") + return underlyingPosixError?.code == .ENAMETOOLONG } - + // Clean up - XCTAssertTrue($0.changeCurrentDirectoryPath(aas)) - XCTAssertTrue($0.changeCurrentDirectoryPath(bbs)) - XCTAssertTrue($0.changeCurrentDirectoryPath(ccs)) - XCTAssertTrue($0.changeCurrentDirectoryPath(dds)) - try $0.removeItem(atPath: ees) - XCTAssertTrue($0.changeCurrentDirectoryPath("..")) - try $0.removeItem(atPath: dds) - XCTAssertTrue($0.changeCurrentDirectoryPath("..")) - try $0.removeItem(atPath: ccs) - XCTAssertTrue($0.changeCurrentDirectoryPath("..")) - try $0.removeItem(atPath: bbs) - XCTAssertTrue($0.changeCurrentDirectoryPath("..")) - try $0.removeItem(atPath: aas) + #expect(fileManager.changeCurrentDirectoryPath(aas)) + #expect(fileManager.changeCurrentDirectoryPath(bbs)) + #expect(fileManager.changeCurrentDirectoryPath(ccs)) + #expect(fileManager.changeCurrentDirectoryPath(dds)) + try fileManager.removeItem(atPath: ees) + #expect(fileManager.changeCurrentDirectoryPath("..")) + try fileManager.removeItem(atPath: dds) + #expect(fileManager.changeCurrentDirectoryPath("..")) + try fileManager.removeItem(atPath: ccs) + #expect(fileManager.changeCurrentDirectoryPath("..")) + try fileManager.removeItem(atPath: bbs) + #expect(fileManager.changeCurrentDirectoryPath("..")) + try fileManager.removeItem(atPath: aas) } #endif } - - func testFileExistsAtPath() throws { - try FileManagerPlayground { + + @Test func fileExistsAtPath() async throws { + try await FilePlayground { Directory("dir") { "foo" "bar" @@ -647,32 +700,26 @@ final class FileManagerTests : XCTestCase { isDir } #endif - XCTAssertTrue($0.fileExists(atPath: "dir/foo", isDirectory: &isDir)) - XCTAssertFalse(isDirBool()) - XCTAssertTrue($0.fileExists(atPath: "dir/bar", isDirectory: &isDir)) - XCTAssertFalse(isDirBool()) - XCTAssertTrue($0.fileExists(atPath: "dir", isDirectory: &isDir)) - XCTAssertTrue(isDirBool()) - XCTAssertTrue($0.fileExists(atPath: "other", isDirectory: &isDir)) - XCTAssertFalse(isDirBool()) - XCTAssertTrue($0.fileExists(atPath: "link_to_file", isDirectory: &isDir)) - XCTAssertFalse(isDirBool()) - XCTAssertTrue($0.fileExists(atPath: "link_to_dir", isDirectory: &isDir)) - XCTAssertTrue(isDirBool()) - XCTAssertFalse($0.fileExists(atPath: "does_not_exist")) - XCTAssertFalse($0.fileExists(atPath: "link_to_nonexistent")) + #expect($0.fileExists(atPath: "dir/foo", isDirectory: &isDir)) + #expect(!isDirBool()) + #expect($0.fileExists(atPath: "dir/bar", isDirectory: &isDir)) + #expect(!isDirBool()) + #expect($0.fileExists(atPath: "dir", isDirectory: &isDir)) + #expect(isDirBool()) + #expect($0.fileExists(atPath: "other", isDirectory: &isDir)) + #expect(!isDirBool()) + #expect($0.fileExists(atPath: "link_to_file", isDirectory: &isDir)) + #expect(!isDirBool()) + #expect($0.fileExists(atPath: "link_to_dir", isDirectory: &isDir)) + #expect(isDirBool()) + #expect(!$0.fileExists(atPath: "does_not_exist")) + #expect(!$0.fileExists(atPath: "link_to_nonexistent")) } } - - func testFileAccessAtPath() throws { - #if !os(Windows) - guard getuid() != 0 else { - // Root users can always access anything, so this test will not function when run as root - throw XCTSkip("This test is not available when running as the root user") - } - #endif - - try FileManagerPlayground { + + @Test(.disabled(if: isUnixRoot, "This test is not available when running as the root user - root users can always access anything")) + func fileAccessAtPath() async throws { + try await FilePlayground { File("000", attributes: [.posixPermissions: 0o000]) File("111", attributes: [.posixPermissions: 0o111]) File("222", attributes: [.posixPermissions: 0o222]) @@ -694,63 +741,67 @@ final class FileManagerTests : XCTestCase { let writable = ["222", "333", "666", "777"] for number in 0...7 { let file = "\(number)\(number)\(number)" - XCTAssertEqual($0.isReadableFile(atPath: file), readable.contains(file), "'\(file)' failed readable check") - XCTAssertEqual($0.isWritableFile(atPath: file), writable.contains(file), "'\(file)' failed writable check") - XCTAssertEqual($0.isExecutableFile(atPath: file), executable.contains(file), "'\(file)' failed executable check") + #expect($0.isReadableFile(atPath: file) == readable.contains(file), "'\(file)' failed readable check") + #expect($0.isWritableFile(atPath: file) == writable.contains(file), "'\(file)' failed writable check") + #expect($0.isExecutableFile(atPath: file) == executable.contains(file), "'\(file)' failed executable check") #if os(Windows) // Only writable files are deletable on Windows - XCTAssertEqual($0.isDeletableFile(atPath: file), writable.contains(file), "'\(file)' failed deletable check") + #expect($0.isDeletableFile(atPath: file) == writable.contains(file), "'\(file)' failed deletable check") #else - XCTAssertTrue($0.isDeletableFile(atPath: file), "'\(file)' failed deletable check") + #expect($0.isDeletableFile(atPath: file), "'\(file)' failed deletable check") #endif } } } - func testFileSystemAttributesAtPath() throws { - try FileManagerPlayground { + @Test func fileSystemAttributesAtPath() async throws { + try await FilePlayground { "Foo" - }.test { - let dict = try $0.attributesOfFileSystem(forPath: "Foo") - XCTAssertNotNil(dict[.systemSize]) - XCTAssertThrowsError(try $0.attributesOfFileSystem(forPath: "does_not_exist")) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileReadNoSuchFile) + }.test { fileManager in + let dict = try fileManager.attributesOfFileSystem(forPath: "Foo") + #expect(dict[.systemSize] != nil) + #expect { + try fileManager.attributesOfFileSystem(forPath: "does_not_exist") + } throws: { + ($0 as? CocoaError)?.code == .fileReadNoSuchFile } } } - - func testCurrentWorkingDirectory() throws { - try FileManagerPlayground { + + @Test func currentWorkingDirectory() async throws { + try await FilePlayground { Directory("dir") { "foo" } "bar" - }.test { - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["bar", "dir", "dir/foo"]) - XCTAssertTrue($0.changeCurrentDirectoryPath("dir")) - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: "."), ["foo"]) - XCTAssertFalse($0.changeCurrentDirectoryPath("foo")) - XCTAssertTrue($0.changeCurrentDirectoryPath("..")) - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: ".").sorted(), ["bar", "dir", "dir/foo"]) - XCTAssertFalse($0.changeCurrentDirectoryPath("does_not_exist")) - + }.test { (fileManager) throws in + #expect(try fileManager.subpathsOfDirectory(atPath: ".").sorted() == ["bar", "dir", "dir/foo"]) + #expect(fileManager.changeCurrentDirectoryPath("dir")) + #expect(try fileManager.subpathsOfDirectory(atPath: ".") == ["foo"]) + #expect(!fileManager.changeCurrentDirectoryPath("foo")) + #expect(fileManager.changeCurrentDirectoryPath("..")) + #expect(try fileManager.subpathsOfDirectory(atPath: ".").sorted() == ["bar", "dir", "dir/foo"]) + #expect(!fileManager.changeCurrentDirectoryPath("does_not_exist")) + // Test get current directory path when it's parent directory was removed. - XCTAssertTrue($0.changeCurrentDirectoryPath("dir")) + #expect(fileManager.changeCurrentDirectoryPath("dir")) #if os(Windows) // Removing the current working directory fails on Windows because the directory is in use. - XCTAssertThrowsError(try $0.removeItem(atPath: $0.currentDirectoryPath)) { - XCTAssertEqual(($0 as? CocoaError)?.code, .fileWriteNoPermission) + #expect { + try fileManager.removeItem(atPath: fileManager.currentDirectoryPath) + } throws: { + ($0 as? CocoaError)?.code == .fileWriteNoPermission } #else - try $0.removeItem(atPath: $0.currentDirectoryPath) - XCTAssertEqual($0.currentDirectoryPath, "") + try fileManager.removeItem(atPath: fileManager.currentDirectoryPath) + #expect(fileManager.currentDirectoryPath == "") #endif } } - - func testBooleanFileAttributes() throws { - #if canImport(Darwin) - try FileManagerPlayground { + + @Test(.enabled(if: isDarwin, "This test is not applicable on this platform")) + func booleanFileAttributes() async throws { + try await FilePlayground { "none" File("immutable", attributes: [.immutable: true]) File("appendOnly", attributes: [.appendOnly: true]) @@ -762,132 +813,124 @@ final class FileManagerTests : XCTestCase { ("appendOnly", false, true), ("immutable_appendOnly", true, true) ] - + for test in tests { let result = try $0.attributesOfItem(atPath: test.path) - XCTAssertEqual(result[.immutable] as? Bool, test.immutable, "Item at path '\(test.path)' did not provide expected result for immutable key") - XCTAssertEqual(result[.appendOnly] as? Bool, test.appendOnly, "Item at path '\(test.path)' did not provide expected result for appendOnly key") - - XCTAssertNil(result[.busy], "Item at path '\(test.path)' has non-nil value for .busy attribute") // Should only be set when true - + #expect(result[.immutable] as? Bool == test.immutable, "Item at path '\(test.path)' did not provide expected result for immutable key") + #expect(result[.appendOnly] as? Bool == test.appendOnly, "Item at path '\(test.path)' did not provide expected result for appendOnly key") + + #expect(result[.busy] == nil, "Item at path '\(test.path)' has non-nil value for .busy attribute") // Should only be set when true + // Manually clean up attributes so removal does not fail try $0.setAttributes([.immutable: false, .appendOnly: false], ofItemAtPath: test.path) } } - #else - throw XCTSkip("This test is not applicable on this platform") - #endif } - - func testMalformedModificationDateAttribute() throws { + + @Test func malformedModificationDateAttribute() async throws { let sentinelDate = Date(timeIntervalSince1970: 100) - try FileManagerPlayground { + try await FilePlayground { File("foo", attributes: [.modificationDate: sentinelDate]) }.test { - XCTAssertEqual(try $0.attributesOfItem(atPath: "foo")[.modificationDate] as? Date, sentinelDate) + #expect(try $0.attributesOfItem(atPath: "foo")[.modificationDate] as? Date == sentinelDate) for value in [Double.infinity, -Double.infinity, Double.nan] { // Malformed modification dates should be dropped instead of throwing or crashing try $0.setAttributes([.modificationDate : Date(timeIntervalSince1970: value)], ofItemAtPath: "foo") } - XCTAssertEqual(try $0.attributesOfItem(atPath: "foo")[.modificationDate] as? Date, sentinelDate) + #expect(try $0.attributesOfItem(atPath: "foo")[.modificationDate] as? Date == sentinelDate) } } - - func testRoundtripModificationDate() throws { - try FileManagerPlayground { + + @Test func roundtripModificationDate() async throws { + try await FilePlayground { "foo" }.test { // Precision of modification dates is dependent not only on the platform, but on the file system used // Ensure that roundtripping supports at least millisecond-level precision, but some file systems may support more up to nanosecond precision let date = Date(timeIntervalSince1970: 10.003) try $0.setAttributes([.modificationDate : date], ofItemAtPath: "foo") - let readValue = try XCTUnwrap($0.attributesOfItem(atPath: "foo")[.modificationDate], "No value provided for file modification date") - let readDate = try XCTUnwrap(readValue as? Date, "File modification date was not a date (found type \(type(of: readValue)))") - XCTAssertEqual(readDate.timeIntervalSince1970, date.timeIntervalSince1970, accuracy: 0.0005, "File modification date (\(readDate.timeIntervalSince1970)) does not match expected modification date (\(date.timeIntervalSince1970))") + let readValue = try #require($0.attributesOfItem(atPath: "foo")[.modificationDate], "No value provided for file modification date") + let possibleDate = readValue as? Date + let readDate = try #require(possibleDate, "File modification date was not a date (found type \(type(of: readValue)))") + #expect(abs(readDate.timeIntervalSince1970 - date.timeIntervalSince1970) <= 0.0005, "File modification date (\(readDate.timeIntervalSince1970)) does not match expected modification date (\(date.timeIntervalSince1970))") } } - - func testImplicitlyConvertibleFileAttributes() throws { - try FileManagerPlayground { + + @Test func implicitlyConvertibleFileAttributes() async throws { + try await FilePlayground { File("foo", attributes: [.posixPermissions : UInt16(0o644)]) }.test { let attributes = try $0.attributesOfItem(atPath: "foo") // Ensure the unconventional UInt16 was accepted as input #if os(Windows) - XCTAssertEqual(attributes[.posixPermissions] as? UInt, 0o600) + #expect(attributes[.posixPermissions] as? UInt == 0o600) #else - XCTAssertEqual(attributes[.posixPermissions] as? UInt, 0o644) + #expect(attributes[.posixPermissions] as? UInt == 0o644) #endif #if FOUNDATION_FRAMEWORK // Where we have NSNumber, ensure that we can get the value back as an unconventional Double value - XCTAssertEqual(attributes[.posixPermissions] as? Double, Double(0o644)) + #expect(attributes[.posixPermissions] as? Double == Double(0o644)) // Ensure that the file type can be converted to a String when it is an ObjC enum - XCTAssertEqual(attributes[.type] as? String, FileAttributeType.typeRegular.rawValue) + #expect(attributes[.type] as? String == FileAttributeType.typeRegular.rawValue) #endif // Ensure that the file type can be converted to a FileAttributeType when it is an ObjC enum and in swift-foundation - XCTAssertEqual(attributes[.type] as? FileAttributeType, .typeRegular) - + #expect(attributes[.type] as? FileAttributeType == .typeRegular) + } } - - func testStandardizingPathAutomount() throws { - #if canImport(Darwin) + + @Test(.enabled(if: isDarwin, "This test is not applicable on this platform")) + func standardizingPathAutomount() async throws { let tests = [ "/private/System" : "/private/System", "/private/tmp" : "/tmp", "/private/System/foo" : "/private/System/foo" ] for (input, expected) in tests { - XCTAssertEqual(input.standardizingPath, expected, "Standardizing the path '\(input)' did not produce the expected result") + #expect(input.standardizingPath == expected, "Standardizing the path '\(input)' did not produce the expected result") } - #else - throw XCTSkip("This test is not applicable to this platform") - #endif } - - func testResolveSymlinksViaGetAttrList() throws { - #if !canImport(Darwin) - throw XCTSkip("This test is not applicable on this platform") - #else - try FileManagerPlayground { + + @Test(.enabled(if: isDarwin, "This test is not applicable on this platform")) + func resolveSymlinksViaGetAttrList() async throws { + try await FilePlayground { "destination" }.test { try $0.createSymbolicLink(atPath: "link", withDestinationPath: "destination") let absolutePath = $0.currentDirectoryPath.appendingPathComponent("link") let resolved = absolutePath._resolvingSymlinksInPath() // Call internal function to avoid path standardization - XCTAssertEqual(resolved, $0.currentDirectoryPath.appendingPathComponent("destination").withFileSystemRepresentation { String(cString: $0!) }) + #expect(resolved == $0.currentDirectoryPath.appendingPathComponent("destination").withFileSystemRepresentation { String(cString: $0!) }) } - #endif } - + #if os(macOS) && FOUNDATION_FRAMEWORK - func testSpecialTrashDirectoryTruncation() throws { - try FileManagerPlayground {}.test { + @Test func specialTrashDirectoryTruncation() async throws { + try await FilePlayground {}.test { if let trashURL = try? $0.url(for: .trashDirectory, in: .allDomainsMask, appropriateFor: nil, create: false) { - XCTAssertEqual(trashURL.pathComponents.last, ".Trash") + #expect(trashURL.pathComponents.last == ".Trash") } } } - - func testSpecialTrashDirectoryDuplication() throws { - try FileManagerPlayground {}.test { fileManager in + + @Test func specialTrashDirectoryDuplication() async throws { + try await FilePlayground {}.test { fileManager in let trashURLs = fileManager.urls(for: .trashDirectory, in: .userDomainMask) - XCTAssertEqual(trashURLs.count, 1, "There should only be one trash directory for the user domain, found \(trashURLs)") + #expect(trashURLs.count == 1, "There should only be one trash directory for the user domain, found \(trashURLs)") } } #endif - - func testSearchPaths() throws { - func assertSearchPaths(_ directories: [FileManager.SearchPathDirectory], exists: Bool, file: StaticString = #filePath, line: UInt = #line) { + + @Test func searchPaths() async throws { + func assertSearchPaths(_ directories: [FileManager.SearchPathDirectory], exists: Bool, sourceLocation: SourceLocation = #_sourceLocation) { for directory in directories { let paths = FileManager.default.urls(for: directory, in: .allDomainsMask) - XCTAssertEqual(!paths.isEmpty, exists, "Directory \(directory) produced an unexpected number of paths (expected to exist: \(exists), produced: \(paths))", file: file, line: line) + #expect(!paths.isEmpty == exists, "Directory \(directory) produced an unexpected number of paths (expected to exist: \(exists), produced: \(paths))", sourceLocation: sourceLocation) } } - + // Cross platform paths that always exist assertSearchPaths([ .userDirectory, @@ -902,13 +945,13 @@ final class FileManagerTests : XCTestCase { .musicDirectory, .sharedPublicDirectory ], exists: true) - + #if canImport(Darwin) let isDarwin = true #else let isDarwin = false #endif - + // Darwin-only paths assertSearchPaths([ .applicationDirectory, @@ -925,13 +968,13 @@ final class FileManagerTests : XCTestCase { .allLibrariesDirectory, .printerDescriptionDirectory ], exists: isDarwin) - + #if os(macOS) let isMacOS = true #else let isMacOS = false #endif - + #if FOUNDATION_FRAMEWORK let isFramework = true #else @@ -943,7 +986,7 @@ final class FileManagerTests : XCTestCase { #else let isWindows = false #endif - + // .trashDirectory is unavailable on watchOS/tvOS and only produces paths on macOS (the framework build) + non-Darwin #if !os(watchOS) && !os(tvOS) assertSearchPaths([.trashDirectory], exists: (isMacOS && isFramework) || (!isDarwin && !isWindows)) @@ -954,28 +997,23 @@ final class FileManagerTests : XCTestCase { #if !os(Windows) assertSearchPaths([.picturesDirectory], exists: true) #endif - + // .applicationScriptsDirectory is only available on macOS and only produces paths in the framework build #if os(macOS) assertSearchPaths([.applicationScriptsDirectory], exists: isFramework) #endif - + // .itemReplacementDirectory never exists assertSearchPaths([.itemReplacementDirectory], exists: false) } - - func testSearchPaths_XDGEnvironmentVariables() throws { - #if canImport(Darwin) || os(Windows) - throw XCTSkip("This test is not applicable on this platform") - #else - if let key = ProcessInfo.processInfo.environment.keys.first(where: { $0.starts(with: "XDG") }) { - throw XCTSkip("Skipping due to presence of '\(key)' environment variable which may affect this test") - } - - try FileManagerPlayground { + + #if !canImport(Darwin) && !os(Windows) + @Test(.disabled(if: ProcessInfo.processInfo.environment.keys.contains(where: { $0.starts(with: "XDG") }), "Skipping due to presence of XDG environment variables which may affect this test")) + func searchPaths_XDGEnvironmentVariables() async throws { + try await FilePlayground { Directory("TestPath") {} }.test { fileManager in - func validate(_ key: String, suffix: String? = nil, directory: FileManager.SearchPathDirectory, domain: FileManager.SearchPathDomainMask, file: StaticString = #filePath, line: UInt = #line) { + func validate(_ key: String, suffix: String? = nil, directory: FileManager.SearchPathDirectory, domain: FileManager.SearchPathDomainMask, sourceLocation: SourceLocation = #_sourceLocation) { let oldValue = ProcessInfo.processInfo.environment[key] ?? "" var knownPath = fileManager.currentDirectoryPath.appendingPathComponent("TestPath") setenv(key, knownPath, 1) @@ -986,7 +1024,7 @@ final class FileManagerTests : XCTestCase { } let knownURL = URL(filePath: knownPath, directoryHint: .isDirectory) let results = fileManager.urls(for: directory, in: domain) - XCTAssertTrue(results.contains(knownURL), "Results \(results.map(\.path)) did not contain known directory \(knownURL.path) for \(directory)/\(domain) while setting the \(key) environment variable", file: file, line: line) + #expect(results.contains(knownURL), "Results \(results.map(\.path)) did not contain known directory \(knownURL.path) for \(directory)/\(domain) while setting the \(key) environment variable", sourceLocation: sourceLocation) } validate("XDG_DATA_HOME", suffix: "Autosave Information", directory: .autosavedInformationDirectory, domain: .userDomainMask) @@ -994,17 +1032,17 @@ final class FileManagerTests : XCTestCase { validate("XDG_CACHE_HOME", directory: .cachesDirectory, domain: .userDomainMask) validate("HOME", suffix: ".cache", directory: .cachesDirectory, domain: .userDomainMask) - + validate("XDG_DATA_HOME", directory: .applicationSupportDirectory, domain: .userDomainMask) validate("HOME", suffix: ".local/share", directory: .applicationSupportDirectory, domain: .userDomainMask) - + validate("HOME", directory: .userDirectory, domain: .localDomainMask) } - #endif } - - func testGetSetAttributes() throws { - try FileManagerPlayground { + #endif + + @Test func getSetAttributes() async throws { + try await FilePlayground { File("foo", contents: randomData()) }.test { let attrs = try $0.attributesOfItem(atPath: "foo") @@ -1012,17 +1050,15 @@ final class FileManagerTests : XCTestCase { } } - func testCurrentUserHomeDirectory() throws { - #if canImport(Darwin) && !os(macOS) - throw XCTSkip("This test is not applicable on this platform") - #else + #if !canImport(Darwin) || os(macOS) + @Test func currentUserHomeDirectory() async throws { let userName = ProcessInfo.processInfo.userName - XCTAssertEqual(FileManager.default.homeDirectory(forUser: userName), FileManager.default.homeDirectoryForCurrentUser) - #endif + #expect(FileManager.default.homeDirectory(forUser: userName) == FileManager.default.homeDirectoryForCurrentUser) } - - func testAttributesOfItemAtPath() throws { - try FileManagerPlayground { + #endif + + @Test func attributesOfItemAtPath() async throws { + try await FilePlayground { "file" File("fileWithContents", contents: randomData()) Directory("directory") { @@ -1031,147 +1067,148 @@ final class FileManagerTests : XCTestCase { }.test { do { let attrs = try $0.attributesOfItem(atPath: "file") - XCTAssertEqual(attrs[.size] as? UInt, 0) - XCTAssertEqual(attrs[.type] as? FileAttributeType, FileAttributeType.typeRegular) + #expect(attrs[.size] as? UInt == 0) + #expect(attrs[.type] as? FileAttributeType == FileAttributeType.typeRegular) } - + do { let attrs = try $0.attributesOfItem(atPath: "fileWithContents") - XCTAssertGreaterThan(try XCTUnwrap(attrs[.size] as? UInt), 0) - XCTAssertEqual(attrs[.type] as? FileAttributeType, FileAttributeType.typeRegular) + XCTAssertGreaterThan(try #require(attrs[.size] as? UInt), 0) + #expect(attrs[.type] as? FileAttributeType == FileAttributeType.typeRegular) } - + do { let attrs = try $0.attributesOfItem(atPath: "directory") - XCTAssertEqual(attrs[.type] as? FileAttributeType, FileAttributeType.typeDirectory) + #expect(attrs[.type] as? FileAttributeType == FileAttributeType.typeDirectory) } - + do { try $0.createSymbolicLink(atPath: "symlink", withDestinationPath: "file") let attrs = try $0.attributesOfItem(atPath: "symlink") - XCTAssertEqual(attrs[.type] as? FileAttributeType, FileAttributeType.typeSymbolicLink) + #expect(attrs[.type] as? FileAttributeType == FileAttributeType.typeSymbolicLink) } } } - - func testHomeDirectoryForNonExistantUser() throws { - #if canImport(Darwin) && !os(macOS) - throw XCTSkip("This test is not applicable on this platform") - #else - XCTAssertNil(FileManager.default.homeDirectory(forUser: "")) - XCTAssertNil(FileManager.default.homeDirectory(forUser: UUID().uuidString)) - #endif + + #if !canImport(Darwin) || os(macOS) + @Test func homeDirectoryForNonExistantUser() async throws { + #expect(FileManager.default.homeDirectory(forUser: "") == nil) + #expect(FileManager.default.homeDirectory(forUser: UUID().uuidString) == nil) } - - func testSearchPathsWithoutExpandingTilde() throws { - #if !canImport(Darwin) - throw XCTSkip("This test is not applicable for this platform") - #else + #endif + + #if canImport(Darwin) + @Test func SearchPathsWithoutExpandingTilde() async throws { for path in _DarwinSearchPaths(for: .libraryDirectory, in: .userDomainMask, expandTilde: false) { - XCTAssertTrue(path.starts(with: "~/"), "Path '\(path)' did not start with ~/ as expected") + #expect(path.starts(with: "~/"), "Path '\(path)' did not start with ~/ as expected") } - #endif } + #endif - func testWindowsDirectoryCreationCrash() throws { - try FileManagerPlayground { + @Test func windowsDirectoryCreationCrash() async throws { + try await FilePlayground { Directory("a\u{301}") { } }.test { - XCTAssertTrue($0.fileExists(atPath: "a\u{301}")) + #expect($0.fileExists(atPath: "a\u{301}")) let data = randomData() - XCTAssertTrue($0.createFile(atPath: "a\u{301}/test", contents: data)) - XCTAssertTrue($0.fileExists(atPath: "a\u{301}/test")) - XCTAssertEqual($0.contents(atPath: "a\u{301}/test"), data) + #expect($0.createFile(atPath: "a\u{301}/test", contents: data)) + #expect($0.fileExists(atPath: "a\u{301}/test")) + #expect($0.contents(atPath: "a\u{301}/test") == data) } } /// Tests that Foundation can correctly handle "long paths" (paths of 260 to 32767 chacters long) on Windows. - func testWindowsLongPathSupport() throws { - #if !os(Windows) - throw XCTSkip("This test is not applicable for this platform") - #else + @Test(.enabled(if: isWindows, "This test is not applicable on this platform")) + func windowsLongPathSupport() async throws { // Create a directory with the absolute maximum path _component_ length of 255; // this will guarantee the full playground path is well over 260 characters. // Throw some Unicode in there for good measure, since only wide-character APIs support it. let dirName = String(repeating: UUID().uuidString, count: 7) + "你好!" - XCTAssertEqual(dirName.count, 255) - XCTAssertEqual(dirName.utf16.count, 255) + #expect(dirName.count == 255) + #expect(dirName.utf16.count == 255) - try FileManagerPlayground { + try await FilePlayground { Directory(dirName) { } - }.test { + }.test { fileManager in // Call every function that can call into withNTPathRepresentation with an overlong path and ensure it succeeds. let fileName = UUID().uuidString - let cwd = try XCTUnwrap($0.currentDirectoryPath) + let cwd = fileManager.currentDirectoryPath - XCTAssertTrue($0.createFile(atPath: dirName + "/" + fileName, contents: nil)) + #expect(fileManager.createFile(atPath: dirName + "/" + fileName, contents: nil)) let dirURL = URL(filePath: dirName, directoryHint: .checkFileSystem) - XCTAssertTrue(dirURL.hasDirectoryPath) + #expect(dirURL.hasDirectoryPath) let fileURL = URL(filePath: dirName + "/" + fileName, directoryHint: .checkFileSystem) - XCTAssertFalse(fileURL.hasDirectoryPath) + #expect(!fileURL.hasDirectoryPath) - XCTAssertTrue($0.fileExists(atPath: dirName + "/" + fileName)) - XCTAssertTrue($0.isReadableFile(atPath: dirName + "/" + fileName)) - XCTAssertTrue($0.isWritableFile(atPath: dirName + "/" + fileName)) + #expect(fileManager.fileExists(atPath: dirName + "/" + fileName)) + #expect(fileManager.isReadableFile(atPath: dirName + "/" + fileName)) + #expect(fileManager.isWritableFile(atPath: dirName + "/" + fileName)) // SHGetFileInfoW is documented to be limited to MAX_PATH, but appears to support long paths anyways (or at least does for SHGFI_EXETYPE). // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetfileinfow - XCTAssertNoThrow(try Data().write(to: URL(fileURLWithPath: dirName + "/" + fileName + ".bat"))) - XCTAssertTrue($0.isExecutableFile(atPath: dirName + "/" + fileName + ".bat")) - XCTAssertFalse($0.isExecutableFile(atPath: dirName + "/" + fileName)) + #expect(throws: Never.self) { try Data().write(to: URL(fileURLWithPath: dirName + "/" + fileName + ".bat")) } + #expect(fileManager.isExecutableFile(atPath: dirName + "/" + fileName + ".bat")) + #expect(!fileManager.isExecutableFile(atPath: dirName + "/" + fileName)) - XCTAssertNoThrow(try $0.attributesOfItem(atPath: dirName + "/" + fileName)) - XCTAssertNoThrow(try $0.setAttributes([.modificationDate: Date()], ofItemAtPath: dirName + "/" + fileName)) - XCTAssertNoThrow(try $0.attributesOfFileSystem(forPath: dirName + "/" + fileName)) + #expect(throws: Never.self) { try fileManager.attributesOfItem(atPath: dirName + "/" + fileName) } + #expect(throws: Never.self) { try fileManager.setAttributes([.modificationDate: Date()], ofItemAtPath: dirName + "/" + fileName) } + #expect(throws: Never.self) { try fileManager.attributesOfFileSystem(forPath: dirName + "/" + fileName) } - XCTAssertNoThrow(try Data(contentsOf: URL(fileURLWithPath: dirName + "/" + fileName))) + #expect(throws: Never.self) { try Data(contentsOf: URL(fileURLWithPath: dirName + "/" + fileName)) } - XCTAssertNoThrow(try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName))) - XCTAssertNoThrow(try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName), options: .atomic)) + #expect(throws: Never.self) { try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName)) } + #expect(throws: Never.self) { try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName), options: .atomic) } - XCTAssertNoThrow(try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName + ".v2"))) - XCTAssertTrue($0.contentsEqual(atPath: dirName + "/" + fileName, andPath: dirName + "/" + fileName + ".v2")) + #expect(throws: Never.self) { try Data("hello".utf8).write(to: URL(fileURLWithPath: dirName + "/" + fileName + ".v2")) } + #expect(fileManager.contentsEqual(atPath: dirName + "/" + fileName, andPath: dirName + "/" + fileName + ".v2")) - XCTAssertEqual(try $0.subpathsOfDirectory(atPath: dirName).sorted(), [ + #expect(try fileManager.subpathsOfDirectory(atPath: dirName).sorted() == [ fileName, fileName + ".bat", fileName + ".v2" ]) - XCTAssertNoThrow(try $0.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir1"), withIntermediateDirectories: false)) + #expect(throws: Never.self) { try fileManager.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir1"), withIntermediateDirectories: false) } // SHCreateDirectoryExW's path argument is limited to 248 characters, and the \\?\ prefix doesn't help. // https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shcreatedirectoryexw - XCTAssertThrowsError(try $0.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3"), withIntermediateDirectories: true)) + #expect(throws: (any Error).self) { + try fileManager.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3"), withIntermediateDirectories: true) + } // SetCurrentDirectory seems to be limited to MAX_PATH unconditionally, counter to the documentation. // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setcurrentdirectory // https://github.com/MicrosoftDocs/feedback/issues/1441 - XCTAssertFalse($0.changeCurrentDirectoryPath(dirName + "/" + "subdir1")) - - XCTAssertNoThrow(try $0.createSymbolicLink(atPath: dirName + "/" + "lnk", withDestinationPath: fileName)) - XCTAssertNoThrow(try $0.createSymbolicLink(atPath: dirName + "/" + "lnk2", withDestinationPath: cwd + "/" + dirName + "/" + fileName)) - XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: dirName + "/" + "lnk"), fileName) - XCTAssertEqual(try $0.destinationOfSymbolicLink(atPath: dirName + "/" + "lnk2"), cwd + "\\" + dirName + "\\" + fileName) - - XCTAssertEqual((cwd + "/" + dirName + "/" + "lnk").resolvingSymlinksInPath, (cwd + "/" + dirName + "/" + fileName).resolvingSymlinksInPath) - - XCTAssertNoThrow(try $0.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2"), withIntermediateDirectories: false)) - XCTAssertNoThrow(try $0.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3"), withIntermediateDirectories: false)) - XCTAssertNoThrow(try Data().write(to: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile"))) - XCTAssertNoThrow(try Data().write(to: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile2"))) - XCTAssertNoThrow(try $0.moveItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile2", toPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile3")) - XCTAssertNoThrow(try $0.moveItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3", toPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete")) - XCTAssertNoThrow(try $0.linkItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete", toPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete.lnk")) - XCTAssertNoThrow(try $0.linkItem(atPath: dirName + "/" + "subdir2", toPath: dirName + "/" + "subdir2.lnk")) - XCTAssertNoThrow(try $0.removeItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete" + "/" + "somefile3")) - XCTAssertNoThrow(try $0.removeItem(atPath: dirName + "/" + "subdir2")) + #expect(!fileManager.changeCurrentDirectoryPath(dirName + "/" + "subdir1")) + + #expect(throws: Never.self) { try fileManager.createSymbolicLink(atPath: dirName + "/" + "lnk", withDestinationPath: fileName) } + #expect(throws: Never.self) { try fileManager.createSymbolicLink(atPath: dirName + "/" + "lnk2", withDestinationPath: cwd + "/" + dirName + "/" + fileName) } + do { + let dest = try fileManager.destinationOfSymbolicLink(atPath: dirName + "/" + "lnk") + #expect(dest == fileName) + } + do { + let dest = try fileManager.destinationOfSymbolicLink(atPath: dirName + "/" + "lnk2") + #expect(dest == cwd + "\\" + dirName + "\\" + fileName) + } + + #expect((cwd + "/" + dirName + "/" + "lnk").resolvingSymlinksInPath == (cwd + "/" + dirName + "/" + fileName).resolvingSymlinksInPath) + + #expect(throws: Never.self) { try fileManager.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2"), withIntermediateDirectories: false) } + #expect(throws: Never.self) { try fileManager.createDirectory(at: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3"), withIntermediateDirectories: false) } + #expect(throws: Never.self) { try Data().write(to: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile")) } + #expect(throws: Never.self) { try Data().write(to: URL(fileURLWithPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile2")) } + #expect(throws: Never.self) { try fileManager.moveItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile2", toPath: dirName + "/" + "subdir2" + "/" + "subdir3" + "/" + "somefile3") } + #expect(throws: Never.self) { try fileManager.moveItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3", toPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete") } + #expect(throws: Never.self) { try fileManager.linkItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete", toPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete.lnk") } + #expect(throws: Never.self) { try fileManager.linkItem(atPath: dirName + "/" + "subdir2", toPath: dirName + "/" + "subdir2.lnk") } + #expect(throws: Never.self) { try fileManager.removeItem(atPath: dirName + "/" + "subdir2" + "/" + "subdir3.delete" + "/" + "somefile3") } + #expect(throws: Never.self) { try fileManager.removeItem(atPath: dirName + "/" + "subdir2") } } - #endif } } diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerPlayground.swift b/Tests/FoundationEssentialsTests/FileManager/FilePlayground.swift similarity index 64% rename from Tests/FoundationEssentialsTests/FileManager/FileManagerPlayground.swift rename to Tests/FoundationEssentialsTests/FileManager/FilePlayground.swift index 26a96919f..38fb4355e 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerPlayground.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FilePlayground.swift @@ -10,20 +10,18 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing -#if FOUNDATION_FRAMEWORK -@testable import Foundation -#else +#if canImport(FoundationEssentials) @testable import FoundationEssentials +#else +@testable import Foundation #endif private protocol Buildable { func build(in path: String, using fileManager: FileManager) throws } - + struct File : ExpressibleByStringLiteral, Buildable { private let name: String private let attributes: [FileAttributeKey : Any]? @@ -65,9 +63,9 @@ struct SymbolicLink : Buildable { struct Directory : Buildable { fileprivate let name: String private let attributes: [FileAttributeKey : Any]? - private let contents: [FileManagerPlayground.Item] + private let contents: [FilePlayground.Item] - init(_ name: String, attributes: [FileAttributeKey : Any]? = nil, @FileManagerPlayground.DirectoryBuilder _ contentsClosure: () -> [FileManagerPlayground.Item]) { + init(_ name: String, attributes: [FileAttributeKey : Any]? = nil, @FilePlayground.DirectoryBuilder _ contentsClosure: () -> [FilePlayground.Item]) { self.name = name self.attributes = attributes self.contents = contentsClosure() @@ -82,7 +80,29 @@ struct Directory : Buildable { } } -struct FileManagerPlayground { +@globalActor +actor CurrentWorkingDirectoryActor: GlobalActor { + static let shared = CurrentWorkingDirectoryActor() + + private init() {} + + @CurrentWorkingDirectoryActor + static func withCurrentWorkingDirectory( + _ path: String, + fileManager: FileManager = .default, + sourceLocation: SourceLocation = #_sourceLocation, + body: @CurrentWorkingDirectoryActor () throws -> Void // Must be synchronous to prevent suspension points within body which could introduce a change in the CWD + ) throws { + let previousCWD = fileManager.currentDirectoryPath + try #require(fileManager.changeCurrentDirectoryPath(path), "Failed to change CWD to '\(path)'", sourceLocation: sourceLocation) + defer { + #expect(fileManager.changeCurrentDirectoryPath(previousCWD), "Failed to change CWD back to the original directory '\(previousCWD)'", sourceLocation: sourceLocation) + } + try body() + } +} + +struct FilePlayground { enum Item : Buildable { case file(File) case directory(Directory) @@ -119,25 +139,23 @@ struct FileManagerPlayground { private let directory: Directory init(@DirectoryBuilder _ contentsClosure: () -> [Item]) { - self.directory = Directory("FileManagerPlayground_\(UUID().uuidString)", contentsClosure) + self.directory = Directory("FilePlayground_\(UUID().uuidString)", contentsClosure) } - func test(captureDelegateCalls: Bool = false, file: StaticString = #filePath, line: UInt = #line, _ tester: (FileManager) throws -> Void) throws { + func test(captureDelegateCalls: Bool = false, sourceLocation: SourceLocation = #_sourceLocation, _ tester: sending (FileManager) throws -> Void) async throws { let capturingDelegate = CapturingFileManagerDelegate() - try withExtendedLifetime(capturingDelegate) { - let fileManager = FileManager() - let tempDir = String.temporaryDirectoryPath - try directory.build(in: tempDir, using: fileManager) - let previousCWD = fileManager.currentDirectoryPath - if captureDelegateCalls { - // Add the delegate after the call to `build` to ensure that the builder doesn't mutate the delegate - fileManager.delegate = capturingDelegate - } - let createdDir = tempDir.appendingPathComponent(directory.name) - XCTAssertTrue(fileManager.changeCurrentDirectoryPath(createdDir), "Failed to change CWD to the newly created playground directory", file: file, line: line) + let fileManager = FileManager() + let tempDir = String.temporaryDirectoryPath + try directory.build(in: tempDir, using: fileManager) + if captureDelegateCalls { + // Add the delegate after the call to `build` to ensure that the builder doesn't mutate the delegate + fileManager.delegate = capturingDelegate + } + let createdDir = tempDir.appendingPathComponent(directory.name) + try await CurrentWorkingDirectoryActor.withCurrentWorkingDirectory(createdDir, fileManager: fileManager, sourceLocation: sourceLocation) { try tester(fileManager) - XCTAssertTrue(fileManager.changeCurrentDirectoryPath(previousCWD), "Failed to change CWD back to the original directory", file: file, line: line) - try fileManager.removeItem(atPath: createdDir) } + try fileManager.removeItem(atPath: createdDir) + extendLifetime(capturingDelegate) // Ensure capturingDelegate lives beyond the tester body } } diff --git a/Tests/FoundationEssentialsTests/URLTests.swift b/Tests/FoundationEssentialsTests/URLTests.swift index c8eb77fb7..8c49e3bcf 100644 --- a/Tests/FoundationEssentialsTests/URLTests.swift +++ b/Tests/FoundationEssentialsTests/URLTests.swift @@ -10,106 +10,102 @@ // //===----------------------------------------------------------------------===// - -#if canImport(TestSupport) -import TestSupport -#endif // canImport(TestSupport) +import Testing #if canImport(FoundationEssentials) @testable import FoundationEssentials -#endif - -#if FOUNDATION_FRAMEWORK +#else @testable import Foundation #endif -private func checkBehavior(_ result: T, new: T, old: T, file: StaticString = #filePath, line: UInt = #line) { +private func checkBehavior(_ result: T, new: T, old: T, sourceLocation: SourceLocation = #_sourceLocation) { #if FOUNDATION_FRAMEWORK if foundation_swift_url_enabled() { - XCTAssertEqual(result, new, file: file, line: line) + #expect(result == new, sourceLocation: sourceLocation) } else { - XCTAssertEqual(result, old, file: file, line: line) + #expect(result == old, sourceLocation: sourceLocation) } #else - XCTAssertEqual(result, new, file: file, line: line) + #expect(result == new, sourceLocation: sourceLocation) #endif } -final class URLTests : XCTestCase { +@Suite("URL") +private struct URLTests { - func testURLBasics() throws { + @Test func basics() throws { let string = "https://username:password@example.com:80/path/path?query=value&q=v#fragment" - let url = try XCTUnwrap(URL(string: string)) - - XCTAssertEqual(url.scheme, "https") - XCTAssertEqual(url.user(), "username") - XCTAssertEqual(url.password(), "password") - XCTAssertEqual(url.host(), "example.com") - XCTAssertEqual(url.port, 80) - XCTAssertEqual(url.path(), "/path/path") - XCTAssertEqual(url.relativePath, "/path/path") - XCTAssertEqual(url.query(), "query=value&q=v") - XCTAssertEqual(url.fragment(), "fragment") - XCTAssertEqual(url.absoluteString, string) - XCTAssertEqual(url.absoluteURL, url) - XCTAssertEqual(url.relativeString, string) - XCTAssertNil(url.baseURL) + let url = try #require(URL(string: string)) + + #expect(url.scheme == "https") + #expect(url.user() == "username") + #expect(url.password() == "password") + #expect(url.host() == "example.com") + #expect(url.port == 80) + #expect(url.path() == "/path/path") + #expect(url.relativePath == "/path/path") + #expect(url.query() == "query=value&q=v") + #expect(url.fragment() == "fragment") + #expect(url.absoluteString == string) + #expect(url.absoluteURL == url) + #expect(url.relativeString == string) + #expect(url.baseURL == nil) let baseString = "https://user:pass@base.example.com:8080/base/" - let baseURL = try XCTUnwrap(URL(string: baseString)) - let absoluteURLWithBase = try XCTUnwrap(URL(string: string, relativeTo: baseURL)) + let baseURL = try #require(URL(string: baseString)) + let absoluteURLWithBase = try #require(URL(string: string, relativeTo: baseURL)) // The URL is already absolute, so .baseURL is nil, and the components are unchanged - XCTAssertEqual(absoluteURLWithBase.scheme, "https") - XCTAssertEqual(absoluteURLWithBase.user(), "username") - XCTAssertEqual(absoluteURLWithBase.password(), "password") - XCTAssertEqual(absoluteURLWithBase.host(), "example.com") - XCTAssertEqual(absoluteURLWithBase.port, 80) - XCTAssertEqual(absoluteURLWithBase.path(), "/path/path") - XCTAssertEqual(absoluteURLWithBase.relativePath, "/path/path") - XCTAssertEqual(absoluteURLWithBase.query(), "query=value&q=v") - XCTAssertEqual(absoluteURLWithBase.fragment(), "fragment") - XCTAssertEqual(absoluteURLWithBase.absoluteString, string) - XCTAssertEqual(absoluteURLWithBase.absoluteURL, url) - XCTAssertEqual(absoluteURLWithBase.relativeString, string) - XCTAssertNil(absoluteURLWithBase.baseURL) - XCTAssertEqual(absoluteURLWithBase.absoluteURL, url) + #expect(absoluteURLWithBase.scheme == "https") + #expect(absoluteURLWithBase.user() == "username") + #expect(absoluteURLWithBase.password() == "password") + #expect(absoluteURLWithBase.host() == "example.com") + #expect(absoluteURLWithBase.port == 80) + #expect(absoluteURLWithBase.path() == "/path/path") + #expect(absoluteURLWithBase.relativePath == "/path/path") + #expect(absoluteURLWithBase.query() == "query=value&q=v") + #expect(absoluteURLWithBase.fragment() == "fragment") + #expect(absoluteURLWithBase.absoluteString == string) + #expect(absoluteURLWithBase.absoluteURL == url) + #expect(absoluteURLWithBase.relativeString == string) + #expect(absoluteURLWithBase.baseURL == nil) + #expect(absoluteURLWithBase.absoluteURL == url) let relativeString = "relative/path?query#fragment" - let relativeURL = try XCTUnwrap(URL(string: relativeString)) - - XCTAssertNil(relativeURL.scheme) - XCTAssertNil(relativeURL.user()) - XCTAssertNil(relativeURL.password()) - XCTAssertNil(relativeURL.host()) - XCTAssertNil(relativeURL.port) - XCTAssertEqual(relativeURL.path(), "relative/path") - XCTAssertEqual(relativeURL.relativePath, "relative/path") - XCTAssertEqual(relativeURL.query(), "query") - XCTAssertEqual(relativeURL.fragment(), "fragment") - XCTAssertEqual(relativeURL.absoluteString, relativeString) - XCTAssertEqual(relativeURL.absoluteURL, relativeURL) - XCTAssertEqual(relativeURL.relativeString, relativeString) - XCTAssertNil(relativeURL.baseURL) - - let relativeURLWithBase = try XCTUnwrap(URL(string: relativeString, relativeTo: baseURL)) - - XCTAssertEqual(relativeURLWithBase.scheme, baseURL.scheme) - XCTAssertEqual(relativeURLWithBase.user(), baseURL.user()) - XCTAssertEqual(relativeURLWithBase.password(), baseURL.password()) - XCTAssertEqual(relativeURLWithBase.host(), baseURL.host()) - XCTAssertEqual(relativeURLWithBase.port, baseURL.port) - XCTAssertEqual(relativeURLWithBase.path(), "/base/relative/path") - XCTAssertEqual(relativeURLWithBase.relativePath, "relative/path") - XCTAssertEqual(relativeURLWithBase.query(), "query") - XCTAssertEqual(relativeURLWithBase.fragment(), "fragment") - XCTAssertEqual(relativeURLWithBase.absoluteString, "https://user:pass@base.example.com:8080/base/relative/path?query#fragment") - XCTAssertEqual(relativeURLWithBase.absoluteURL, URL(string: "https://user:pass@base.example.com:8080/base/relative/path?query#fragment")) - XCTAssertEqual(relativeURLWithBase.relativeString, relativeString) - XCTAssertEqual(relativeURLWithBase.baseURL, baseURL) + let relativeURL = try #require(URL(string: relativeString)) + + #expect(relativeURL.scheme == nil) + #expect(relativeURL.user() == nil) + #expect(relativeURL.password() == nil) + #expect(relativeURL.host() == nil) + #expect(relativeURL.port == nil) + #expect(relativeURL.path() == "relative/path") + #expect(relativeURL.relativePath == "relative/path") + #expect(relativeURL.query() == "query") + #expect(relativeURL.fragment() == "fragment") + #expect(relativeURL.absoluteString == relativeString) + #expect(relativeURL.absoluteURL == relativeURL) + #expect(relativeURL.relativeString == relativeString) + #expect(relativeURL.baseURL == nil) + + let relativeURLWithBase = try #require(URL(string: relativeString, relativeTo: baseURL)) + + #expect(relativeURLWithBase.scheme == baseURL.scheme) + #expect(relativeURLWithBase.user() == baseURL.user()) + #expect(relativeURLWithBase.password() == baseURL.password()) + #expect(relativeURLWithBase.host() == baseURL.host()) + #expect(relativeURLWithBase.port == baseURL.port) + #expect(relativeURLWithBase.path() == "/base/relative/path") + #expect(relativeURLWithBase.relativePath == "relative/path") + #expect(relativeURLWithBase.query() == "query") + #expect(relativeURLWithBase.fragment() == "fragment") + #expect(relativeURLWithBase.absoluteString == "https://user:pass@base.example.com:8080/base/relative/path?query#fragment") + #expect(relativeURLWithBase.absoluteURL == URL(string: "https://user:pass@base.example.com:8080/base/relative/path?query#fragment")) + #expect(relativeURLWithBase.relativeString == relativeString) + #expect(relativeURLWithBase.baseURL == baseURL) } - func testURLResolvingAgainstBase() throws { + @Test func resolvingAgainstBase() throws { let base = URL(string: "http://a/b/c/d;p?q") let tests = [ // RFC 3986 5.4.1. Normal Examples @@ -175,14 +171,13 @@ final class URLTests : XCTestCase { continue } - let url = URL(stringOrEmpty: test.key, relativeTo: base) - XCTAssertNotNil(url, "Got nil url for string: \(test.key)") - XCTAssertEqual(url?.absoluteString, test.value, "Failed test for string: \(test.key)") + let url = try #require(URL(stringOrEmpty: test.key, relativeTo: base), "Got nil url for string: \(test.key)") + #expect(url.absoluteString == test.value, "Failed test for string: \(test.key)") } } - func testURLPathAPIsResolveAgainstBase() throws { - try XCTSkipIf(!foundation_swift_url_enabled()) + @Test(.enabled(if: foundation_swift_url_enabled())) + func pathAPIsResolveAgainstBase() throws { // Borrowing the same test cases from RFC 3986, but checking paths let base = URL(string: "http://a/b/c/d;p?q") let tests = [ @@ -237,41 +232,41 @@ final class URLTests : XCTestCase { ] for test in tests { let url = URL(stringOrEmpty: test.key, relativeTo: base)! - XCTAssertEqual(url.absolutePath(), test.value) + #expect(url.absolutePath() == test.value) if (url.hasDirectoryPath && url.absolutePath().count > 1) { // The trailing slash is stripped in .path for file system compatibility - XCTAssertEqual(String(url.absolutePath().dropLast()), url.path) + #expect(String(url.absolutePath().dropLast()) == url.path) } else { - XCTAssertEqual(url.absolutePath(), url.path) + #expect(url.absolutePath() == url.path) } } } - func testURLPathComponentsPercentEncodedSlash() throws { - try XCTSkipIf(!foundation_swift_url_enabled()) + + @Test(.enabled(if: foundation_swift_url_enabled())) + func pathComponentsPercentEncodedSlash() throws { + var url = try #require(URL(string: "https://example.com/https%3A%2F%2Fexample.com")) + #expect(url.pathComponents == ["/", "https://example.com"]) - var url = try XCTUnwrap(URL(string: "https://example.com/https%3A%2F%2Fexample.com")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com"]) + url = try #require(URL(string: "https://example.com/https:%2f%2fexample.com")) + #expect(url.pathComponents == ["/", "https://example.com"]) - url = try XCTUnwrap(URL(string: "https://example.com/https:%2f%2fexample.com")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com"]) + url = try #require(URL(string: "https://example.com/https:%2F%2Fexample.com%2Fpath")) + #expect(url.pathComponents == ["/", "https://example.com/path"]) - url = try XCTUnwrap(URL(string: "https://example.com/https:%2F%2Fexample.com%2Fpath")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com/path"]) + url = try #require(URL(string: "https://example.com/https:%2F%2Fexample.com/path")) + #expect(url.pathComponents == ["/", "https://example.com", "path"]) - url = try XCTUnwrap(URL(string: "https://example.com/https:%2F%2Fexample.com/path")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com", "path"]) + url = try #require(URL(string: "https://example.com/https%3A%2F%2Fexample.com%2Fpath%3Fquery%23fragment")) + #expect(url.pathComponents == ["/", "https://example.com/path?query#fragment"]) - url = try XCTUnwrap(URL(string: "https://example.com/https%3A%2F%2Fexample.com%2Fpath%3Fquery%23fragment")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com/path?query#fragment"]) - - url = try XCTUnwrap(URL(string: "https://example.com/https%3A%2F%2Fexample.com%2Fpath?query#fragment")) - XCTAssertEqual(url.pathComponents, ["/", "https://example.com/path"]) + url = try #require(URL(string: "https://example.com/https%3A%2F%2Fexample.com%2Fpath?query#fragment")) + #expect(url.pathComponents == ["/", "https://example.com/path"]) } - func testURLRootlessPath() throws { - try XCTSkipIf(!foundation_swift_url_enabled()) - + + @Test(.enabled(if: foundation_swift_url_enabled())) + func rootlessPath() throws { let paths = ["", "path"] let queries = [nil, "query"] let fragments = [nil, "fragment"] @@ -282,87 +277,87 @@ final class URLTests : XCTestCase { let queryString = query != nil ? "?\(query!)" : "" let fragmentString = fragment != nil ? "#\(fragment!)" : "" let urlString = "scheme:\(path)\(queryString)\(fragmentString)" - let url = try XCTUnwrap(URL(string: urlString)) - XCTAssertEqual(url.absoluteString, urlString) - XCTAssertEqual(url.scheme, "scheme") - XCTAssertNil(url.host()) - XCTAssertEqual(url.path(), path) - XCTAssertEqual(url.query(), query) - XCTAssertEqual(url.fragment(), fragment) + let url = try #require(URL(string: urlString)) + #expect(url.absoluteString == urlString) + #expect(url.scheme == "scheme") + #expect(url.host() == nil) + #expect(url.path() == path) + #expect(url.query() == query) + #expect(url.fragment() == fragment) } } } } - func testURLNonSequentialIPLiteralAndPort() { + @Test func nonSequentialIPLiteralAndPort() { let urlString = "https://[fe80::3221:5634:6544]invalid:433/" let url = URL(string: urlString) - XCTAssertNil(url) + #expect(url == nil) } - func testURLFilePathInitializer() throws { + @Test func filePathInitializer() throws { let directory = URL(filePath: "/some/directory", directoryHint: .isDirectory) - XCTAssertTrue(directory.hasDirectoryPath) + #expect(directory.hasDirectoryPath) let notDirectory = URL(filePath: "/some/file", directoryHint: .notDirectory) - XCTAssertFalse(notDirectory.hasDirectoryPath) + #expect(!notDirectory.hasDirectoryPath) // directoryHint defaults to .inferFromPath let directoryAgain = URL(filePath: "/some/directory.framework/") - XCTAssertTrue(directoryAgain.hasDirectoryPath) + #expect(directoryAgain.hasDirectoryPath) let notDirectoryAgain = URL(filePath: "/some/file") - XCTAssertFalse(notDirectoryAgain.hasDirectoryPath) + #expect(!notDirectoryAgain.hasDirectoryPath) // Test .checkFileSystem by creating a directory let tempDirectory = URL.temporaryDirectory let urlBeforeCreation = URL(filePath: "\(tempDirectory.path)/tmp-dir", directoryHint: .checkFileSystem) - XCTAssertFalse(urlBeforeCreation.hasDirectoryPath) + #expect(!urlBeforeCreation.hasDirectoryPath) try FileManager.default.createDirectory( at: URL(filePath: "\(tempDirectory.path)/tmp-dir"), withIntermediateDirectories: true ) let urlAfterCreation = URL(filePath: "\(tempDirectory.path)/tmp-dir", directoryHint: .checkFileSystem) - XCTAssertTrue(urlAfterCreation.hasDirectoryPath) + #expect(urlAfterCreation.hasDirectoryPath) try FileManager.default.removeItem(at: URL(filePath: "\(tempDirectory.path)/tmp-dir")) } #if os(Windows) - func testURLWindowsDriveLetterPath() throws { + @Test func windowsDriveLetterPath() throws { var url = URL(filePath: #"C:\test\path"#, directoryHint: .notDirectory) // .absoluteString and .path() use the RFC 8089 URL path - XCTAssertEqual(url.absoluteString, "file:///C:/test/path") - XCTAssertEqual(url.path(), "/C:/test/path") + #expect(url.absoluteString == "file:///C:/test/path") + #expect(url.path() == "/C:/test/path") // .path and .fileSystemPath() strip the leading slash - XCTAssertEqual(url.path, "C:/test/path") - XCTAssertEqual(url.fileSystemPath(), "C:/test/path") + #expect(url.path == "C:/test/path") + #expect(url.fileSystemPath() == "C:/test/path") url = URL(filePath: #"C:\"#, directoryHint: .isDirectory) - XCTAssertEqual(url.absoluteString, "file:///C:/") - XCTAssertEqual(url.path(), "/C:/") - XCTAssertEqual(url.path, "C:/") - XCTAssertEqual(url.fileSystemPath(), "C:/") + #expect(url.absoluteString == "file:///C:/") + #expect(url.path() == "/C:/") + #expect(url.path == "C:/") + #expect(url.fileSystemPath() == "C:/") url = URL(filePath: #"C:\\\"#, directoryHint: .isDirectory) - XCTAssertEqual(url.absoluteString, "file:///C:///") - XCTAssertEqual(url.path(), "/C:///") - XCTAssertEqual(url.path, "C:/") - XCTAssertEqual(url.fileSystemPath(), "C:/") + #expect(url.absoluteString == "file:///C:///") + #expect(url.path() == "/C:///") + #expect(url.path == "C:/") + #expect(url.fileSystemPath() == "C:/") url = URL(filePath: #"\C:\"#, directoryHint: .isDirectory) - XCTAssertEqual(url.absoluteString, "file:///C:/") - XCTAssertEqual(url.path(), "/C:/") - XCTAssertEqual(url.path, "C:/") - XCTAssertEqual(url.fileSystemPath(), "C:/") + #expect(url.absoluteString == "file:///C:/") + #expect(url.path() == "/C:/") + #expect(url.path == "C:/") + #expect(url.fileSystemPath() == "C:/") let base = URL(filePath: #"\d:\path\"#, directoryHint: .isDirectory) url = URL(filePath: #"%43:\fake\letter"#, directoryHint: .notDirectory, relativeTo: base) // ":" is encoded to "%3A" in the first path segment so it's not mistaken as the scheme separator - XCTAssertEqual(url.relativeString, "%2543%3A/fake/letter") - XCTAssertEqual(url.path(), "/d:/path/%2543%3A/fake/letter") - XCTAssertEqual(url.path, "d:/path/%43:/fake/letter") - XCTAssertEqual(url.fileSystemPath(), "d:/path/%43:/fake/letter") + #expect(url.relativeString == "%2543%3A/fake/letter") + #expect(url.path() == "/d:/path/%2543%3A/fake/letter") + #expect(url.path == "d:/path/%43:/fake/letter") + #expect(url.fileSystemPath() == "d:/path/%43:/fake/letter") let cwd = URL.currentDirectory() var iter = cwd.path().utf8.makeIterator() @@ -371,105 +366,45 @@ final class URLTests : XCTestCase { iter.next() == ._colon { let path = #"\\?\"# + "\(Unicode.Scalar(driveLetter))" + #":\"# url = URL(filePath: path, directoryHint: .isDirectory) - XCTAssertEqual(url.path.last, "/") - XCTAssertEqual(url.fileSystemPath().last, "/") + #expect(url.path.last == "/") + #expect(url.fileSystemPath().last == "/") } } #endif - func testURLFilePathRelativeToBase() throws { - try FileManagerPlayground { - Directory("dir") { - "Foo" - "Bar" - } - }.test { - let currentDirectoryPath = $0.currentDirectoryPath - let baseURL = URL(filePath: currentDirectoryPath, directoryHint: .isDirectory) - let relativePath = "dir" - - let url1 = URL(filePath: relativePath, directoryHint: .isDirectory, relativeTo: baseURL) - - let url2 = URL(filePath: relativePath, directoryHint: .checkFileSystem, relativeTo: baseURL) - XCTAssertEqual(url1, url2, "\(url1) was not equal to \(url2)") - - // directoryHint is `.inferFromPath` by default - let url3 = URL(filePath: relativePath + "/", relativeTo: baseURL) - XCTAssertEqual(url1, url3, "\(url1) was not equal to \(url3)") - } - } - - func testURLFilePathDoesNotFollowLastSymlink() throws { - try FileManagerPlayground { - Directory("dir") { - "Foo" - SymbolicLink("symlink", destination: "../dir") - } - }.test { - let currentDirectoryPath = $0.currentDirectoryPath - let baseURL = URL(filePath: currentDirectoryPath, directoryHint: .isDirectory) - - let dirURL = baseURL.appending(path: "dir", directoryHint: .checkFileSystem) - XCTAssertTrue(dirURL.hasDirectoryPath) - - var symlinkURL = dirURL.appending(path: "symlink", directoryHint: .notDirectory) - - // FileManager uses stat(), which will follow the symlink to the directory. - - #if FOUNDATION_FRAMEWORK - var isDirectory: ObjCBool = false - XCTAssertTrue(FileManager.default.fileExists(atPath: symlinkURL.path, isDirectory: &isDirectory)) - XCTAssertTrue(isDirectory.boolValue) - #else - var isDirectory = false - XCTAssertTrue(FileManager.default.fileExists(atPath: symlinkURL.path, isDirectory: &isDirectory)) - XCTAssertTrue(isDirectory) - #endif - - // URL uses lstat(), which will not follow the symlink at the end of the path. - // Check that URL(filePath:) and .appending(path:) preserve this behavior. - - symlinkURL = URL(filePath: symlinkURL.path, directoryHint: .checkFileSystem) - XCTAssertFalse(symlinkURL.hasDirectoryPath) - - symlinkURL = dirURL.appending(path: "symlink", directoryHint: .checkFileSystem) - XCTAssertFalse(symlinkURL.hasDirectoryPath) - } - } - - func testURLRelativeDotDotResolution() throws { + @Test func relativeDotDotResolution() throws { let baseURL = URL(filePath: "/docs/src/") var result = URL(filePath: "../images/foo.png", relativeTo: baseURL) - XCTAssertEqual(result.path, "/docs/images/foo.png") + #expect(result.path == "/docs/images/foo.png") result = URL(filePath: "/../images/foo.png", relativeTo: baseURL) - XCTAssertEqual(result.path, "/../images/foo.png") + #expect(result.path == "/../images/foo.png") } - func testAppendFamily() throws { + @Test func appendFamily() throws { let base = URL(string: "https://www.example.com")! // Appending path - XCTAssertEqual( - base.appending(path: "/api/v2").absoluteString, + #expect( + base.appending(path: "/api/v2").absoluteString == "https://www.example.com/api/v2" ) var testAppendPath = base testAppendPath.append(path: "/api/v3") - XCTAssertEqual( - testAppendPath.absoluteString, + #expect( + testAppendPath.absoluteString == "https://www.example.com/api/v3" ) // Appending component - XCTAssertEqual( - base.appending(component: "AC/DC").absoluteString, + #expect( + base.appending(component: "AC/DC").absoluteString == "https://www.example.com/AC%2FDC" ) var testAppendComponent = base testAppendComponent.append(component: "AC/DC") - XCTAssertEqual( - testAppendComponent.absoluteString, + #expect( + testAppendComponent.absoluteString == "https://www.example.com/AC%2FDC" ) @@ -478,26 +413,26 @@ final class URLTests : XCTestCase { URLQueryItem(name: "id", value: "42"), URLQueryItem(name: "color", value: "blue") ] - XCTAssertEqual( - base.appending(queryItems: queryItems).absoluteString, + #expect( + base.appending(queryItems: queryItems).absoluteString == "https://www.example.com?id=42&color=blue" ) var testAppendQueryItems = base testAppendQueryItems.append(queryItems: queryItems) - XCTAssertEqual( - testAppendQueryItems.absoluteString, + #expect( + testAppendQueryItems.absoluteString == "https://www.example.com?id=42&color=blue" ) // Appending components - XCTAssertEqual( - base.appending(components: "api", "artist", "AC/DC").absoluteString, + #expect( + base.appending(components: "api", "artist", "AC/DC").absoluteString == "https://www.example.com/api/artist/AC%2FDC" ) var testAppendComponents = base testAppendComponents.append(components: "api", "artist", "AC/DC") - XCTAssertEqual( - testAppendComponents.absoluteString, + #expect( + testAppendComponents.absoluteString == "https://www.example.com/api/artist/AC%2FDC" ) @@ -509,28 +444,28 @@ final class URLTests : XCTestCase { URLQueryItem(name: "color", value: "blue") ]) .appending(components: "get", "products") - XCTAssertEqual( - chained.absoluteString, + #expect( + chained.absoluteString == "https://www.example.com/api/v2/get/products?magic=42&color=blue" ) } - func testAppendFamilyDirectoryHint() throws { + @Test func appendFamilyDirectoryHint() throws { // Make sure directoryHint values are propagated correctly let base = URL(string: "file:///var/mobile")! // Appending path var url = base.appending(path: "/folder/item", directoryHint: .isDirectory) - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(path: "folder/item", directoryHint: .notDirectory) - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) url = base.appending(path: "/folder/item.framework/") - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(path: "/folder/item") - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) try runDirectoryHintCheckFilesystemTest { $0.appending(path: "/folder/item", directoryHint: .checkFileSystem) @@ -538,16 +473,16 @@ final class URLTests : XCTestCase { // Appending component url = base.appending(component: "AC/DC", directoryHint: .isDirectory) - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(component: "AC/DC", directoryHint: .notDirectory) - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) url = base.appending(component: "AC/DC/", directoryHint: .isDirectory) - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(component: "AC/DC") - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) try runDirectoryHintCheckFilesystemTest { $0.appending(component: "AC/DC", directoryHint: .checkFileSystem) @@ -555,16 +490,16 @@ final class URLTests : XCTestCase { // Appending components url = base.appending(components: "api", "v2", "AC/DC", directoryHint: .isDirectory) - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(components: "api", "v2", "AC/DC", directoryHint: .notDirectory) - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) url = base.appending(components: "api", "v2", "AC/DC/", directoryHint: .isDirectory) - XCTAssertTrue(url.hasDirectoryPath) + #expect(url.hasDirectoryPath) url = base.appending(components: "api", "v2", "AC/DC") - XCTAssertFalse(url.hasDirectoryPath) + #expect(!url.hasDirectoryPath) try runDirectoryHintCheckFilesystemTest { $0.appending(components: "api", "v2", "AC/DC", directoryHint: .checkFileSystem) @@ -574,40 +509,38 @@ final class URLTests : XCTestCase { private func runDirectoryHintCheckFilesystemTest(_ builder: (URL) -> URL) throws { let tempDirectory = URL.temporaryDirectory // We should not have directory path before it's created - XCTAssertFalse(builder(tempDirectory).hasDirectoryPath) + #expect(!builder(tempDirectory).hasDirectoryPath) // Create the folder try FileManager.default.createDirectory( at: builder(tempDirectory), withIntermediateDirectories: true ) - XCTAssertTrue(builder(tempDirectory).hasDirectoryPath) + #expect(builder(tempDirectory).hasDirectoryPath) try FileManager.default.removeItem(at: builder(tempDirectory)) } - func testURLEncodingInvalidCharacters() throws { - let urlStrings = [ - " ", - "path space", - "/absolute path space", - "scheme:path space", - "scheme://host/path space", - "scheme://host/path space?query space#fragment space", - "scheme://user space:pass space@host/", - "unsafe\"<>%{}\\|^~[]`##", - "http://example.com/unsafe\"<>%{}\\|^~[]`##", - "mailto:\"Your Name\" ", - "[This is not a valid URL without encoding.]", - "Encoding a relative path! 😎", - ] - for urlString in urlStrings { - var url = URL(string: urlString, encodingInvalidCharacters: true) - XCTAssertNotNil(url, "Expected a percent-encoded url for string \(urlString)") - url = URL(string: urlString, encodingInvalidCharacters: false) - XCTAssertNil(url, "Expected to fail strict url parsing for string \(urlString)") - } + @Test(arguments: [ + " ", + "path space", + "/absolute path space", + "scheme:path space", + "scheme://host/path space", + "scheme://host/path space?query space#fragment space", + "scheme://user space:pass space@host/", + "unsafe\"<>%{}\\|^~[]`##", + "http://example.com/unsafe\"<>%{}\\|^~[]`##", + "mailto:\"Your Name\" ", + "[This is not a valid URL without encoding.]", + "Encoding a relative path! 😎", + ]) + func encodingInvalidCharacters(urlString: String) throws { + var url = URL(string: urlString, encodingInvalidCharacters: true) + #expect(url != nil, "Expected a percent-encoded url for string \(urlString)") + url = URL(string: urlString, encodingInvalidCharacters: false) + #expect(url == nil, "Expected to fail strict url parsing for string \(urlString)") } - func testURLAppendingPathDoesNotEncodeColon() throws { + @Test func appendingPathDoesNotEncodeColon() throws { let baseURL = URL(string: "file:///var/mobile/")! let url = URL(string: "relative", relativeTo: baseURL)! let component = "no:slash" @@ -615,55 +548,55 @@ final class URLTests : XCTestCase { // Make sure we don't encode ":" since `component` is not the first path segment var appended = url.appending(path: component, directoryHint: .notDirectory) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/no:slash") - XCTAssertEqual(appended.relativePath, "relative/no:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/no:slash") + #expect(appended.relativePath == "relative/no:slash") appended = url.appending(path: slashComponent, directoryHint: .notDirectory) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/with:slash") - XCTAssertEqual(appended.relativePath, "relative/with:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/with:slash") + #expect(appended.relativePath == "relative/with:slash") appended = url.appending(component: component, directoryHint: .notDirectory) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/no:slash") - XCTAssertEqual(appended.relativePath, "relative/no:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/no:slash") + #expect(appended.relativePath == "relative/no:slash") // .appending(component:) should explicitly treat slashComponent as a single // path component, meaning "/" should be encoded to "%2F" before appending. // However, the old behavior didn't do this for file URLs, so we maintain the // old behavior to prevent breakage. appended = url.appending(component: slashComponent, directoryHint: .notDirectory) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/with:slash") - XCTAssertEqual(appended.relativePath, "relative/with:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/with:slash") + #expect(appended.relativePath == "relative/with:slash") appended = url.appendingPathComponent(component, isDirectory: false) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/no:slash") - XCTAssertEqual(appended.relativePath, "relative/no:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/no:slash") + #expect(appended.relativePath == "relative/no:slash") // Test deprecated API, which acts like `appending(path:)` appended = url.appendingPathComponent(slashComponent, isDirectory: false) - XCTAssertEqual(appended.absoluteString, "file:///var/mobile/relative/with:slash") - XCTAssertEqual(appended.relativePath, "relative/with:slash") + #expect(appended.absoluteString == "file:///var/mobile/relative/with:slash") + #expect(appended.relativePath == "relative/with:slash") } - func testURLDeletingLastPathComponent() throws { + @Test func deletingLastPathComponent() throws { var absolute = URL(filePath: "/absolute/path", directoryHint: .notDirectory) // Note: .relativePath strips the trailing slash for compatibility - XCTAssertEqual(absolute.relativePath, "/absolute/path") - XCTAssertFalse(absolute.hasDirectoryPath) + #expect(absolute.relativePath == "/absolute/path") + #expect(!absolute.hasDirectoryPath) absolute.deleteLastPathComponent() - XCTAssertEqual(absolute.relativePath, "/absolute") - XCTAssertTrue(absolute.hasDirectoryPath) + #expect(absolute.relativePath == "/absolute") + #expect(absolute.hasDirectoryPath) absolute.deleteLastPathComponent() - XCTAssertEqual(absolute.relativePath, "/") - XCTAssertTrue(absolute.hasDirectoryPath) + #expect(absolute.relativePath == "/") + #expect(absolute.hasDirectoryPath) // The old .deleteLastPathComponent() implementation appends ".." to the // root directory "/", resulting in "/../". This resolves back to "/". // The new implementation simply leaves "/" as-is. absolute.deleteLastPathComponent() checkBehavior(absolute.relativePath, new: "/", old: "/..") - XCTAssertTrue(absolute.hasDirectoryPath) + #expect(absolute.hasDirectoryPath) absolute.append(path: "absolute", directoryHint: .isDirectory) checkBehavior(absolute.path, new: "/absolute", old: "/../absolute") @@ -673,114 +606,114 @@ final class URLTests : XCTestCase { absolute = URL(filePath: "/absolute", directoryHint: .isDirectory) var relative = URL(filePath: "relative/path", directoryHint: .notDirectory, relativeTo: absolute) - XCTAssertEqual(relative.relativePath, "relative/path") - XCTAssertFalse(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute/relative/path") + #expect(relative.relativePath == "relative/path") + #expect(!relative.hasDirectoryPath) + #expect(relative.path == "/absolute/relative/path") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "relative") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute/relative") + #expect(relative.relativePath == "relative") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute/relative") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, ".") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.relativePath == ".") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "..") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/") + #expect(relative.relativePath == "..") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "../..") - XCTAssertTrue(relative.hasDirectoryPath) + #expect(relative.relativePath == "../..") + #expect(relative.hasDirectoryPath) checkBehavior(relative.path, new:"/", old: "/..") relative.append(path: "path", directoryHint: .isDirectory) - XCTAssertEqual(relative.relativePath, "../../path") - XCTAssertTrue(relative.hasDirectoryPath) + #expect(relative.relativePath == "../../path") + #expect(relative.hasDirectoryPath) checkBehavior(relative.path, new: "/path", old: "/../path") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "../..") - XCTAssertTrue(relative.hasDirectoryPath) + #expect(relative.relativePath == "../..") + #expect(relative.hasDirectoryPath) checkBehavior(relative.path, new: "/", old: "/..") relative = URL(filePath: "", relativeTo: absolute) - XCTAssertEqual(relative.relativePath, ".") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.relativePath == ".") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "..") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/") + #expect(relative.relativePath == "..") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "../..") - XCTAssertTrue(relative.hasDirectoryPath) + #expect(relative.relativePath == "../..") + #expect(relative.hasDirectoryPath) checkBehavior(relative.path, new: "/", old: "/..") relative = URL(filePath: "relative/./", relativeTo: absolute) // According to RFC 3986, "." and ".." segments should not be removed // until the path is resolved against the base URL (when calling .path) checkBehavior(relative.relativePath, new: "relative/.", old: "relative") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute/relative") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute/relative") relative.deleteLastPathComponent() checkBehavior(relative.relativePath, new: "relative/..", old: ".") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute") relative = URL(filePath: "relative/.", directoryHint: .isDirectory, relativeTo: absolute) checkBehavior(relative.relativePath, new: "relative/.", old: "relative") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute/relative") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute/relative") relative.deleteLastPathComponent() checkBehavior(relative.relativePath, new: "relative/..", old: ".") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute") relative = URL(filePath: "relative/..", relativeTo: absolute) - XCTAssertEqual(relative.relativePath, "relative/..") + #expect(relative.relativePath == "relative/..") checkBehavior(relative.hasDirectoryPath, new: true, old: false) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.path == "/absolute") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "relative/../..") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/") + #expect(relative.relativePath == "relative/../..") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/") relative = URL(filePath: "relative/..", directoryHint: .isDirectory, relativeTo: absolute) - XCTAssertEqual(relative.relativePath, "relative/..") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/absolute") + #expect(relative.relativePath == "relative/..") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/absolute") relative.deleteLastPathComponent() - XCTAssertEqual(relative.relativePath, "relative/../..") - XCTAssertTrue(relative.hasDirectoryPath) - XCTAssertEqual(relative.path, "/") + #expect(relative.relativePath == "relative/../..") + #expect(relative.hasDirectoryPath) + #expect(relative.path == "/") - var url = try XCTUnwrap(URL(string: "scheme://host.with.no.path")) - XCTAssertTrue(url.path().isEmpty) + var url = try #require(URL(string: "scheme://host.with.no.path")) + #expect(url.path().isEmpty) url.deleteLastPathComponent() - XCTAssertEqual(url.absoluteString, "scheme://host.with.no.path") - XCTAssertTrue(url.path().isEmpty) + #expect(url.absoluteString == "scheme://host.with.no.path") + #expect(url.path().isEmpty) let unusedBase = URL(string: "base://url") - url = try XCTUnwrap(URL(string: "scheme://host.with.no.path", relativeTo: unusedBase)) - XCTAssertEqual(url.absoluteString, "scheme://host.with.no.path") - XCTAssertTrue(url.path().isEmpty) + url = try #require(URL(string: "scheme://host.with.no.path", relativeTo: unusedBase)) + #expect(url.absoluteString == "scheme://host.with.no.path") + #expect(url.path().isEmpty) url.deleteLastPathComponent() - XCTAssertEqual(url.absoluteString, "scheme://host.with.no.path") - XCTAssertTrue(url.path().isEmpty) + #expect(url.absoluteString == "scheme://host.with.no.path") + #expect(url.path().isEmpty) - var schemeRelative = try XCTUnwrap(URL(string: "scheme:relative/path")) + var schemeRelative = try #require(URL(string: "scheme:relative/path")) // Bug in the old implementation where a relative path is not recognized checkBehavior(schemeRelative.relativePath, new: "relative/path", old: "") @@ -788,111 +721,111 @@ final class URLTests : XCTestCase { checkBehavior(schemeRelative.relativePath, new: "relative", old: "") schemeRelative.deleteLastPathComponent() - XCTAssertEqual(schemeRelative.relativePath, "") + #expect(schemeRelative.relativePath == "") schemeRelative.deleteLastPathComponent() - XCTAssertEqual(schemeRelative.relativePath, "") + #expect(schemeRelative.relativePath == "") } - func testURLFilePathDropsTrailingSlashes() throws { + @Test func filePathDropsTrailingSlashes() throws { var url = URL(filePath: "/path/slashes///") - XCTAssertEqual(url.path(), "/path/slashes///") + #expect(url.path() == "/path/slashes///") // TODO: Update this once .fileSystemPath uses backslashes for Windows - XCTAssertEqual(url.fileSystemPath(), "/path/slashes") + #expect(url.fileSystemPath() == "/path/slashes") url = URL(filePath: "/path/slashes/") - XCTAssertEqual(url.path(), "/path/slashes/") - XCTAssertEqual(url.fileSystemPath(), "/path/slashes") + #expect(url.path() == "/path/slashes/") + #expect(url.fileSystemPath() == "/path/slashes") url = URL(filePath: "/path/slashes") - XCTAssertEqual(url.path(), "/path/slashes") - XCTAssertEqual(url.fileSystemPath(), "/path/slashes") + #expect(url.path() == "/path/slashes") + #expect(url.fileSystemPath() == "/path/slashes") } - func testURLNotDirectoryHintStripsTrailingSlash() throws { + @Test func notDirectoryHintStripsTrailingSlash() throws { // Supply a path with a trailing slash but say it's not a direcotry var url = URL(filePath: "/path/", directoryHint: .notDirectory) - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/path") url = URL(fileURLWithPath: "/path/", isDirectory: false) - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/path") url = URL(filePath: "/path///", directoryHint: .notDirectory) - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/path") url = URL(fileURLWithPath: "/path///", isDirectory: false) - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/path") // With .checkFileSystem, don't modify the path for a non-existent file url = URL(filePath: "/my/non/existent/path/", directoryHint: .checkFileSystem) - XCTAssertTrue(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/my/non/existent/path/") + #expect(url.hasDirectoryPath) + #expect(url.path() == "/my/non/existent/path/") url = URL(fileURLWithPath: "/my/non/existent/path/") - XCTAssertTrue(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/my/non/existent/path/") + #expect(url.hasDirectoryPath) + #expect(url.path() == "/my/non/existent/path/") url = URL(filePath: "/my/non/existent/path", directoryHint: .checkFileSystem) - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/my/non/existent/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/my/non/existent/path") url = URL(fileURLWithPath: "/my/non/existent/path") - XCTAssertFalse(url.hasDirectoryPath) - XCTAssertEqual(url.path(), "/my/non/existent/path") + #expect(!url.hasDirectoryPath) + #expect(url.path() == "/my/non/existent/path") } - func testURLHostRetainsIDNAEncoding() throws { + @Test func hostRetainsIDNAEncoding() throws { let url = URL(string: "ftp://user:password@*.xn--poema-9qae5a.com.br:4343/cat.txt")! - XCTAssertEqual(url.host, "*.xn--poema-9qae5a.com.br") + #expect(url.host == "*.xn--poema-9qae5a.com.br") } - func testURLHostIPLiteralCompatibility() throws { + @Test func hostIPLiteralCompatibility() throws { var url = URL(string: "http://[::]")! - XCTAssertEqual(url.host, "::") - XCTAssertEqual(url.host(), "::") + #expect(url.host == "::") + #expect(url.host() == "::") url = URL(string: "https://[::1]:433/")! - XCTAssertEqual(url.host, "::1") - XCTAssertEqual(url.host(), "::1") + #expect(url.host == "::1") + #expect(url.host() == "::1") url = URL(string: "https://[2001:db8::]/")! - XCTAssertEqual(url.host, "2001:db8::") - XCTAssertEqual(url.host(), "2001:db8::") + #expect(url.host == "2001:db8::") + #expect(url.host() == "2001:db8::") url = URL(string: "https://[2001:db8::]:433")! - XCTAssertEqual(url.host, "2001:db8::") - XCTAssertEqual(url.host(), "2001:db8::") + #expect(url.host == "2001:db8::") + #expect(url.host() == "2001:db8::") url = URL(string: "http://[fe80::a%25en1]")! - XCTAssertEqual(url.absoluteString, "http://[fe80::a%25en1]") - XCTAssertEqual(url.host, "fe80::a%en1") - XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25en1") - XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%en1") + #expect(url.absoluteString == "http://[fe80::a%25en1]") + #expect(url.host == "fe80::a%en1") + #expect(url.host(percentEncoded: true) == "fe80::a%25en1") + #expect(url.host(percentEncoded: false) == "fe80::a%en1") url = URL(string: "http://[fe80::a%en1]")! - XCTAssertEqual(url.absoluteString, "http://[fe80::a%25en1]") - XCTAssertEqual(url.host, "fe80::a%en1") - XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25en1") - XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%en1") + #expect(url.absoluteString == "http://[fe80::a%25en1]") + #expect(url.host == "fe80::a%en1") + #expect(url.host(percentEncoded: true) == "fe80::a%25en1") + #expect(url.host(percentEncoded: false) == "fe80::a%en1") url = URL(string: "http://[fe80::a%100%CustomZone]")! - XCTAssertEqual(url.absoluteString, "http://[fe80::a%25100%25CustomZone]") - XCTAssertEqual(url.host, "fe80::a%100%CustomZone") - XCTAssertEqual(url.host(percentEncoded: true), "fe80::a%25100%25CustomZone") - XCTAssertEqual(url.host(percentEncoded: false), "fe80::a%100%CustomZone") + #expect(url.absoluteString == "http://[fe80::a%25100%25CustomZone]") + #expect(url.host == "fe80::a%100%CustomZone") + #expect(url.host(percentEncoded: true) == "fe80::a%25100%25CustomZone") + #expect(url.host(percentEncoded: false) == "fe80::a%100%CustomZone") // Make sure an IP-literal with invalid characters `{` and `}` // returns `nil` even if we can percent-encode the zone-ID. let invalid = URL(string: "http://[{Invalid}%100%EncodableZone]") - XCTAssertNil(invalid) + #expect(invalid == nil) } #if !os(Windows) - func testURLTildeFilePath() throws { + @Test func tildeFilePath() throws { func urlIsAbsolute(_ url: URL) -> Bool { if url.relativePath.utf8.first == ._slash { return true @@ -905,34 +838,34 @@ final class URLTests : XCTestCase { // "~" must either be expanded to an absolute path or resolved against a base URL var url = URL(filePath: "~") - XCTAssertTrue(urlIsAbsolute(url)) + #expect(urlIsAbsolute(url)) url = URL(filePath: "~", directoryHint: .isDirectory) - XCTAssertTrue(urlIsAbsolute(url)) - XCTAssertEqual(url.path().utf8.last, ._slash) + #expect(urlIsAbsolute(url)) + #expect(url.path().utf8.last == ._slash) url = URL(filePath: "~/") - XCTAssertTrue(urlIsAbsolute(url)) - XCTAssertEqual(url.path().utf8.last, ._slash) + #expect(urlIsAbsolute(url)) + #expect(url.path().utf8.last == ._slash) } #endif // !os(Windows) - func testURLPathExtensions() throws { + @Test func pathExtensions() throws { var url = URL(filePath: "/path", directoryHint: .notDirectory) url.appendPathExtension("foo") - XCTAssertEqual(url.path(), "/path.foo") + #expect(url.path() == "/path.foo") url.deletePathExtension() - XCTAssertEqual(url.path(), "/path") + #expect(url.path() == "/path") url = URL(filePath: "/path", directoryHint: .isDirectory) url.appendPathExtension("foo") - XCTAssertEqual(url.path(), "/path.foo/") + #expect(url.path() == "/path.foo/") url.deletePathExtension() - XCTAssertEqual(url.path(), "/path/") + #expect(url.path() == "/path/") url = URL(filePath: "/path/", directoryHint: .inferFromPath) url.appendPathExtension("foo") - XCTAssertEqual(url.path(), "/path.foo/") + #expect(url.path() == "/path.foo/") url.append(path: "/////") url.deletePathExtension() // Old behavior only searches the last empty component, so the extension isn't actually removed @@ -940,161 +873,161 @@ final class URLTests : XCTestCase { url = URL(filePath: "/tmp/x") url.appendPathExtension("") - XCTAssertEqual(url.path(), "/tmp/x") - XCTAssertEqual(url, url.deletingPathExtension().appendingPathExtension(url.pathExtension)) + #expect(url.path() == "/tmp/x") + #expect(url == url.deletingPathExtension().appendingPathExtension(url.pathExtension)) url = URL(filePath: "/tmp/x.") url.deletePathExtension() - XCTAssertEqual(url.path(), "/tmp/x.") + #expect(url.path() == "/tmp/x.") } - func testURLAppendingToEmptyPath() throws { + @Test func appendingToEmptyPath() throws { let baseURL = URL(filePath: "/base/directory", directoryHint: .isDirectory) let emptyPathURL = URL(filePath: "", relativeTo: baseURL) let url = emptyPathURL.appending(path: "main.swift") - XCTAssertEqual(url.relativePath, "./main.swift") - XCTAssertEqual(url.path, "/base/directory/main.swift") + #expect(url.relativePath == "./main.swift") + #expect(url.path == "/base/directory/main.swift") - var example = try XCTUnwrap(URL(string: "https://example.com")) - XCTAssertEqual(example.host(), "example.com") - XCTAssertTrue(example.path().isEmpty) + var example = try #require(URL(string: "https://example.com")) + #expect(example.host() == "example.com") + #expect(example.path().isEmpty) // Appending to an empty path should add a slash if an authority exists // The appended path should never become part of the host example.append(path: "foo") - XCTAssertEqual(example.host(), "example.com") - XCTAssertEqual(example.path(), "/foo") - XCTAssertEqual(example.absoluteString, "https://example.com/foo") + #expect(example.host() == "example.com") + #expect(example.path() == "/foo") + #expect(example.absoluteString == "https://example.com/foo") // Maintain old behavior, where appending an empty path // to an empty host does not add a slash, but appending // an empty path to a non-empty host does - example = try XCTUnwrap(URL(string: "https://example.com")) + example = try #require(URL(string: "https://example.com")) example.append(path: "") - XCTAssertEqual(example.host(), "example.com") - XCTAssertEqual(example.path(), "/") - XCTAssertEqual(example.absoluteString, "https://example.com/") + #expect(example.host() == "example.com") + #expect(example.path() == "/") + #expect(example.absoluteString == "https://example.com/") - var emptyHost = try XCTUnwrap(URL(string: "scheme://")) - XCTAssertNil(emptyHost.host()) - XCTAssertTrue(emptyHost.path().isEmpty) + var emptyHost = try #require(URL(string: "scheme://")) + #expect(emptyHost.host() == nil) + #expect(emptyHost.path().isEmpty) emptyHost.append(path: "") - XCTAssertNil(emptyHost.host()) - XCTAssertTrue(emptyHost.path().isEmpty) + #expect(emptyHost.host() == nil) + #expect(emptyHost.path().isEmpty) emptyHost.append(path: "foo") - XCTAssertTrue(emptyHost.host()?.isEmpty ?? true) + #expect(emptyHost.host()?.isEmpty ?? true) // Old behavior failed to append correctly to an empty host // Modern parsers agree that "foo" relative to "scheme://" is "scheme:///foo" checkBehavior(emptyHost.path(), new: "/foo", old: "") checkBehavior(emptyHost.absoluteString, new: "scheme:///foo", old: "scheme://") - var schemeOnly = try XCTUnwrap(URL(string: "scheme:")) - XCTAssertTrue(schemeOnly.host()?.isEmpty ?? true) - XCTAssertTrue(schemeOnly.path().isEmpty) + var schemeOnly = try #require(URL(string: "scheme:")) + #expect(schemeOnly.host()?.isEmpty ?? true) + #expect(schemeOnly.path().isEmpty) schemeOnly.append(path: "foo") - XCTAssertTrue(schemeOnly.host()?.isEmpty ?? true) + #expect(schemeOnly.host()?.isEmpty ?? true) // Old behavior appends to the string, but is missing the path checkBehavior(schemeOnly.path(), new: "foo", old: "") - XCTAssertEqual(schemeOnly.absoluteString, "scheme:foo") + #expect(schemeOnly.absoluteString == "scheme:foo") } - func testURLEmptySchemeCompatibility() throws { - var url = try XCTUnwrap(URL(string: ":memory:")) - XCTAssertEqual(url.scheme, "") + @Test func emptySchemeCompatibility() throws { + var url = try #require(URL(string: ":memory:")) + #expect(url.scheme == "") - let base = try XCTUnwrap(URL(string: "://home")) - XCTAssertEqual(base.host(), "home") + let base = try #require(URL(string: "://home")) + #expect(base.host() == "home") - url = try XCTUnwrap(URL(string: "/path", relativeTo: base)) - XCTAssertEqual(url.scheme, "") - XCTAssertEqual(url.host(), "home") - XCTAssertEqual(url.path, "/path") - XCTAssertEqual(url.absoluteString, "://home/path") - XCTAssertEqual(url.absoluteURL.scheme, "") + url = try #require(URL(string: "/path", relativeTo: base)) + #expect(url.scheme == "") + #expect(url.host() == "home") + #expect(url.path == "/path") + #expect(url.absoluteString == "://home/path") + #expect(url.absoluteURL.scheme == "") } - func testURLComponentsPercentEncodedUnencodedProperties() throws { + @Test func componentsPercentEncodedUnencodedProperties() throws { var comp = URLComponents() comp.user = "%25" - XCTAssertEqual(comp.user, "%25") - XCTAssertEqual(comp.percentEncodedUser, "%2525") + #expect(comp.user == "%25") + #expect(comp.percentEncodedUser == "%2525") comp.password = "%25" - XCTAssertEqual(comp.password, "%25") - XCTAssertEqual(comp.percentEncodedPassword, "%2525") + #expect(comp.password == "%25") + #expect(comp.percentEncodedPassword == "%2525") // Host behavior differs since the addition of IDNA-encoding comp.host = "%25" - XCTAssertEqual(comp.host, "%") - XCTAssertEqual(comp.percentEncodedHost, "%25") + #expect(comp.host == "%") + #expect(comp.percentEncodedHost == "%25") comp.path = "%25" - XCTAssertEqual(comp.path, "%25") - XCTAssertEqual(comp.percentEncodedPath, "%2525") + #expect(comp.path == "%25") + #expect(comp.percentEncodedPath == "%2525") comp.query = "%25" - XCTAssertEqual(comp.query, "%25") - XCTAssertEqual(comp.percentEncodedQuery, "%2525") + #expect(comp.query == "%25") + #expect(comp.percentEncodedQuery == "%2525") comp.fragment = "%25" - XCTAssertEqual(comp.fragment, "%25") - XCTAssertEqual(comp.percentEncodedFragment, "%2525") + #expect(comp.fragment == "%25") + #expect(comp.percentEncodedFragment == "%2525") comp.queryItems = [URLQueryItem(name: "name", value: "a%25b")] - XCTAssertEqual(comp.queryItems, [URLQueryItem(name: "name", value: "a%25b")]) - XCTAssertEqual(comp.percentEncodedQueryItems, [URLQueryItem(name: "name", value: "a%2525b")]) - XCTAssertEqual(comp.query, "name=a%25b") - XCTAssertEqual(comp.percentEncodedQuery, "name=a%2525b") + #expect(comp.queryItems == [URLQueryItem(name: "name", value: "a%25b")]) + #expect(comp.percentEncodedQueryItems == [URLQueryItem(name: "name", value: "a%2525b")]) + #expect(comp.query == "name=a%25b") + #expect(comp.percentEncodedQuery == "name=a%2525b") } - func testURLPercentEncodedProperties() throws { + @Test func percentEncodedProperties() throws { var url = URL(string: "https://%3Auser:%3Apassword@%3A.com/%3Apath?%3Aquery=%3A#%3Afragment")! - XCTAssertEqual(url.user(), "%3Auser") - XCTAssertEqual(url.user(percentEncoded: false), ":user") + #expect(url.user() == "%3Auser") + #expect(url.user(percentEncoded: false) == ":user") - XCTAssertEqual(url.password(), "%3Apassword") - XCTAssertEqual(url.password(percentEncoded: false), ":password") + #expect(url.password() == "%3Apassword") + #expect(url.password(percentEncoded: false) == ":password") - XCTAssertEqual(url.host(), "%3A.com") - XCTAssertEqual(url.host(percentEncoded: false), ":.com") + #expect(url.host() == "%3A.com") + #expect(url.host(percentEncoded: false) == ":.com") - XCTAssertEqual(url.path(), "/%3Apath") - XCTAssertEqual(url.path(percentEncoded: false), "/:path") + #expect(url.path() == "/%3Apath") + #expect(url.path(percentEncoded: false) == "/:path") - XCTAssertEqual(url.query(), "%3Aquery=%3A") - XCTAssertEqual(url.query(percentEncoded: false), ":query=:") + #expect(url.query() == "%3Aquery=%3A") + #expect(url.query(percentEncoded: false) == ":query=:") - XCTAssertEqual(url.fragment(), "%3Afragment") - XCTAssertEqual(url.fragment(percentEncoded: false), ":fragment") + #expect(url.fragment() == "%3Afragment") + #expect(url.fragment(percentEncoded: false) == ":fragment") // Lowercase input url = URL(string: "https://%3auser:%3apassword@%3a.com/%3apath?%3aquery=%3a#%3afragment")! - XCTAssertEqual(url.user(), "%3auser") - XCTAssertEqual(url.user(percentEncoded: false), ":user") + #expect(url.user() == "%3auser") + #expect(url.user(percentEncoded: false) == ":user") - XCTAssertEqual(url.password(), "%3apassword") - XCTAssertEqual(url.password(percentEncoded: false), ":password") + #expect(url.password() == "%3apassword") + #expect(url.password(percentEncoded: false) == ":password") - XCTAssertEqual(url.host(), "%3a.com") - XCTAssertEqual(url.host(percentEncoded: false), ":.com") + #expect(url.host() == "%3a.com") + #expect(url.host(percentEncoded: false) == ":.com") - XCTAssertEqual(url.path(), "/%3apath") - XCTAssertEqual(url.path(percentEncoded: false), "/:path") + #expect(url.path() == "/%3apath") + #expect(url.path(percentEncoded: false) == "/:path") - XCTAssertEqual(url.query(), "%3aquery=%3a") - XCTAssertEqual(url.query(percentEncoded: false), ":query=:") + #expect(url.query() == "%3aquery=%3a") + #expect(url.query(percentEncoded: false) == ":query=:") - XCTAssertEqual(url.fragment(), "%3afragment") - XCTAssertEqual(url.fragment(percentEncoded: false), ":fragment") + #expect(url.fragment() == "%3afragment") + #expect(url.fragment(percentEncoded: false) == ":fragment") } - func testURLComponentsUppercasePercentEncoding() throws { + @Test func componentsUppercasePercentEncoding() throws { // Always use uppercase percent-encoding when unencoded components are assigned var comp = URLComponents() comp.scheme = "https" @@ -1103,18 +1036,16 @@ final class URLTests : XCTestCase { comp.path = "?path" comp.query = "#query" comp.fragment = "#fragment" - XCTAssertEqual(comp.percentEncodedUser, "%3Fuser") - XCTAssertEqual(comp.percentEncodedPassword, "%3Fpassword") - XCTAssertEqual(comp.percentEncodedPath, "%3Fpath") - XCTAssertEqual(comp.percentEncodedQuery, "%23query") - XCTAssertEqual(comp.percentEncodedFragment, "%23fragment") + #expect(comp.percentEncodedUser == "%3Fuser") + #expect(comp.percentEncodedPassword == "%3Fpassword") + #expect(comp.percentEncodedPath == "%3Fpath") + #expect(comp.percentEncodedQuery == "%23query") + #expect(comp.percentEncodedFragment == "%23fragment") } - - func testURLComponentsRangeCombinations() throws { - // This brute forces many combinations and takes a long time. - // Skip this for automated testing purposes and test manually when needed. - try XCTSkipIf(true) - + + // This brute forces many combinations and takes a long time. + @Test(.disabled("Disabled in automated testing - enable manually when needed")) + func componentsRangeCombinations() throws { let schemes = [nil, "a", "aa"] let users = [nil, "b", "bb"] let passwords = [nil, "c", "cc"] @@ -1145,16 +1076,16 @@ final class URLTests : XCTestCase { } func validateRanges(_ comp: URLComponents, scheme: String?, user: String?, password: String?, host: String?, port: Int?, path: String, query: String?, fragment: String?) throws { - let string = try XCTUnwrap(comp.string) + let string = try #require(comp.string) if let scheme { - let range = try XCTUnwrap(comp.rangeOfScheme) - XCTAssertTrue(string[range] == scheme) + let range = try #require(comp.rangeOfScheme) + #expect(string[range] == scheme) } else { - XCTAssertNil(comp.rangeOfScheme) + #expect(comp.rangeOfScheme == nil) } if let user { - let range = try XCTUnwrap(comp.rangeOfUser) - XCTAssertTrue(string[range] == user) + let range = try #require(comp.rangeOfUser) + #expect(string[range] == user) } else { // Even if we set comp.user = nil, a non-nil password // implies that user exists as the empty string. @@ -1163,17 +1094,17 @@ final class URLTests : XCTestCase { comp.rangeOfUser?.isEmpty ?? false && comp.password != nil ) - XCTAssertTrue(comp.rangeOfUser == nil || isEmptyUserWithPassword) + #expect(comp.rangeOfUser == nil || isEmptyUserWithPassword) } if let password { - let range = try XCTUnwrap(comp.rangeOfPassword) - XCTAssertTrue(string[range] == password) + let range = try #require(comp.rangeOfPassword) + #expect(string[range] == password) } else { - XCTAssertNil(comp.rangeOfPassword) + #expect(comp.rangeOfPassword == nil) } if let host { - let range = try XCTUnwrap(comp.rangeOfHost) - XCTAssertTrue(string[range] == host) + let range = try #require(comp.rangeOfHost) + #expect(string[range] == host) } else { // Even if we set comp.host = nil, any non-nil authority component // implies that host exists as the empty string. @@ -1182,28 +1113,28 @@ final class URLTests : XCTestCase { comp.rangeOfHost?.isEmpty ?? false && (user != nil || password != nil || port != nil) ) - XCTAssertTrue(comp.rangeOfHost == nil || isEmptyHostWithAuthorityComponent) + #expect(comp.rangeOfHost == nil || isEmptyHostWithAuthorityComponent) } if let port { - let range = try XCTUnwrap(comp.rangeOfPort) - XCTAssertTrue(string[range] == String(port)) + let range = try #require(comp.rangeOfPort) + #expect(string[range] == String(port)) } else { - XCTAssertNil(comp.rangeOfPort) + #expect(comp.rangeOfPort == nil) } // rangeOfPath should never be nil. - let pathRange = try XCTUnwrap(comp.rangeOfPath) - XCTAssertTrue(string[pathRange] == path) + let pathRange = try #require(comp.rangeOfPath) + #expect(string[pathRange] == path) if let query { - let range = try XCTUnwrap(comp.rangeOfQuery) - XCTAssertTrue(string[range] == query) + let range = try #require(comp.rangeOfQuery) + #expect(string[range] == query) } else { - XCTAssertNil(comp.rangeOfQuery) + #expect(comp.rangeOfQuery == nil) } if let fragment { - let range = try XCTUnwrap(comp.rangeOfFragment) - XCTAssertTrue(string[range] == fragment) + let range = try #require(comp.rangeOfFragment) + #expect(string[range] == fragment) } else { - XCTAssertNil(comp.rangeOfFragment) + #expect(comp.rangeOfFragment == nil) } } @@ -1222,8 +1153,8 @@ final class URLTests : XCTestCase { comp.fragment = fragment try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) - let string = try XCTUnwrap(comp.string) - let fullComponents = URLComponents(string: string)! + let string = try #require(comp.string) + let fullComponents = try #require(URLComponents(string: string)) // Get the ranges directly from URLParseInfo @@ -1270,8 +1201,8 @@ final class URLTests : XCTestCase { comp.scheme = nil try validateRanges(comp, scheme: nil, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) - let stringWithoutScheme = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutScheme)! + let stringWithoutScheme = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutScheme)) comp.scheme = scheme try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1285,8 +1216,8 @@ final class URLTests : XCTestCase { comp.user = nil try validateRanges(comp, scheme: scheme, user: nil, password: password, host: expectedHost, port: port, path: path, query: query, fragment: fragment) - let stringWithoutUser = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutUser)! + let stringWithoutUser = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutUser)) comp.user = user try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1300,8 +1231,8 @@ final class URLTests : XCTestCase { comp.password = nil try validateRanges(comp, scheme: scheme, user: expectedUser, password: nil, host: host, port: port, path: path, query: query, fragment: fragment) - let stringWithoutPassword = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutPassword)! + let stringWithoutPassword = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutPassword)) comp.password = password try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1309,8 +1240,8 @@ final class URLTests : XCTestCase { comp.host = nil try validateRanges(comp, scheme: scheme, user: user, password: password, host: nil, port: port, path: path, query: query, fragment: fragment) - let stringWithoutHost = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutHost)! + let stringWithoutHost = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutHost)) comp.host = host try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1324,8 +1255,8 @@ final class URLTests : XCTestCase { comp.port = nil try validateRanges(comp, scheme: scheme, user: user, password: password, host: expectedHost, port: nil, path: path, query: query, fragment: fragment) - let stringWithoutPort = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutPort)! + let stringWithoutPort = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutPort)) comp.port = port try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1333,8 +1264,8 @@ final class URLTests : XCTestCase { comp.path = "" try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: "", query: query, fragment: fragment) - let stringWithoutPath = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutPath)! + let stringWithoutPath = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutPath)) comp.path = path try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1342,8 +1273,8 @@ final class URLTests : XCTestCase { comp.query = nil try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: nil, fragment: fragment) - let stringWithoutQuery = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutQuery)! + let stringWithoutQuery = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutQuery)) comp.query = query try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) @@ -1351,242 +1282,290 @@ final class URLTests : XCTestCase { comp.fragment = nil try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: nil) - let stringWithoutFragment = try XCTUnwrap(comp.string) - comp = URLComponents(string: stringWithoutFragment)! + let stringWithoutFragment = try #require(comp.string) + comp = try #require(URLComponents(string: stringWithoutFragment)) comp.fragment = fragment try validateRanges(comp, scheme: scheme, user: user, password: password, host: host, port: port, path: path, query: query, fragment: fragment) } } - func testURLComponentsEncodesFirstPathColon() throws { + @Test func componentsEncodesFirstPathColon() throws { let path = "first:segment:with:colons/second:segment:with:colons" var comp = URLComponents() comp.path = path - guard let compString = comp.string else { - XCTFail("compString was nil") - return - } - guard let slashIndex = compString.firstIndex(of: "/") else { - XCTFail("Could not find slashIndex") - return - } + let compString = try #require(comp.string) + let slashIndex = try #require(compString.firstIndex(of: "/")) let firstSegment = compString[.. Date: Fri, 13 Jun 2025 16:46:32 -0700 Subject: [PATCH 13/14] Reenable exit tests on Ubuntu 20.04 (#1343) --- Package.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Package.swift b/Package.swift index 4ec820250..327c8cfbb 100644 --- a/Package.swift +++ b/Package.swift @@ -75,20 +75,11 @@ let wasiLibcCSettings: [CSetting] = [ .define("_WASI_EMULATED_MMAN", .when(platforms: [.wasi])), ] -var testOnlySwiftSettings: [SwiftSetting] = [ +let testOnlySwiftSettings: [SwiftSetting] = [ // The latest Windows toolchain does not yet have exit tests in swift-testing .define("FOUNDATION_EXIT_TESTS", .when(platforms: [.macOS, .linux, .openbsd])) ] -#if os(Linux) -import FoundationEssentials - -if ProcessInfo.processInfo.operatingSystemVersionString.hasPrefix("Ubuntu 20.") { - // Exit tests currently hang indefinitely on Ubuntu 20. - testOnlySwiftSettings.removeFirst() -} -#endif - let package = Package( name: "swift-foundation", platforms: [.macOS("15"), .iOS("18"), .tvOS("18"), .watchOS("11")], From 4ed5eb40f87b920522543ca1697caf3beb6e48e8 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Mon, 16 Jun 2025 09:38:32 -0700 Subject: [PATCH 14/14] Revert "Reenable exit tests on Ubuntu 20.04 (#1343)" This reverts commit a88365294f351fcc1b5581525bf9d4a8dabbe66f. --- Package.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 327c8cfbb..4ec820250 100644 --- a/Package.swift +++ b/Package.swift @@ -75,11 +75,20 @@ let wasiLibcCSettings: [CSetting] = [ .define("_WASI_EMULATED_MMAN", .when(platforms: [.wasi])), ] -let testOnlySwiftSettings: [SwiftSetting] = [ +var testOnlySwiftSettings: [SwiftSetting] = [ // The latest Windows toolchain does not yet have exit tests in swift-testing .define("FOUNDATION_EXIT_TESTS", .when(platforms: [.macOS, .linux, .openbsd])) ] +#if os(Linux) +import FoundationEssentials + +if ProcessInfo.processInfo.operatingSystemVersionString.hasPrefix("Ubuntu 20.") { + // Exit tests currently hang indefinitely on Ubuntu 20. + testOnlySwiftSettings.removeFirst() +} +#endif + let package = Package( name: "swift-foundation", platforms: [.macOS("15"), .iOS("18"), .tvOS("18"), .watchOS("11")],