diff --git a/CustomDump.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CustomDump.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3a7a7b0..6ef6c77 100644 --- a/CustomDump.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CustomDump.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,16 +1,14 @@ { - "object": { - "pins": [ - { - "package": "xctest-dynamic-overlay", - "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state": { - "branch": null, - "revision": "23cbf2294e350076ea4dbd7d5d047c1e76b03631", - "version": "1.0.2" - } + "pins" : [ + { + "identity" : "swift-issue-reporting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-issue-reporting", + "state" : { + "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f", + "version" : "1.2.0" } - ] - }, - "version": 1 + } + ], + "version" : 2 } diff --git a/Package.resolved b/Package.resolved index 3a7a7b0..6ef6c77 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,16 +1,14 @@ { - "object": { - "pins": [ - { - "package": "xctest-dynamic-overlay", - "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state": { - "branch": null, - "revision": "23cbf2294e350076ea4dbd7d5d047c1e76b03631", - "version": "1.0.2" - } + "pins" : [ + { + "identity" : "swift-issue-reporting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-issue-reporting", + "state" : { + "revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f", + "version" : "1.2.0" } - ] - }, - "version": 1 + } + ], + "version" : 2 } diff --git a/Package.swift b/Package.swift index f41f022..a223f81 100644 --- a/Package.swift +++ b/Package.swift @@ -17,13 +17,14 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0") + .package(url: "https://github.com/pointfreeco/swift-issue-reporting", from: "1.2.0") ], targets: [ .target( name: "CustomDump", dependencies: [ - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") + .product(name: "IssueReporting", package: "swift-issue-reporting"), + .product(name: "XCTestDynamicOverlay", package: "swift-issue-reporting"), ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency") diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 018d21c..5eaf266 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -17,13 +17,14 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0") + .package(url: "https://github.com/pointfreeco/swift-issue-reporting", from: "1.2.0") ], targets: [ .target( name: "CustomDump", dependencies: [ - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") + .product(name: "IssueReporting", package: "swift-issue-reporting"), + .product(name: "XCTestDynamicOverlay", package: "swift-issue-reporting"), ] ), .testTarget( diff --git a/Sources/CustomDump/Conformances/Foundation.swift b/Sources/CustomDump/Conformances/Foundation.swift index 3158488..e9d74e3 100644 --- a/Sources/CustomDump/Conformances/Foundation.swift +++ b/Sources/CustomDump/Conformances/Foundation.swift @@ -34,14 +34,10 @@ extension Calendar: CustomDumpReflectable { #if !os(WASI) extension Data: CustomDumpStringConvertible { public var customDumpDescription: String { - "Data(\(Self.formatter.string(fromByteCount: .init(self.count))))" - } - - private static let formatter: ByteCountFormatter = { let formatter = ByteCountFormatter() formatter.allowedUnits = .useBytes - return formatter - }() + return "Data(\(formatter.string(fromByteCount: .init(self.count))))" + } } #endif diff --git a/Sources/CustomDump/Documentation.docc/CustomDump.md b/Sources/CustomDump/Documentation.docc/CustomDump.md index f3fa4e9..79ba292 100644 --- a/Sources/CustomDump/Documentation.docc/CustomDump.md +++ b/Sources/CustomDump/Documentation.docc/CustomDump.md @@ -446,11 +446,13 @@ customDump(ID(rawValue: "deadbeef") ### Test support -- ``XCTAssertNoDifference(_:_:_:file:line:)`` -- ``XCTAssertDifference(_:_:operation:changes:file:line:)-8xfxw`` +- ``expectNoDifference(_:_:_:fileID:filePath:line:column:)`` +- ``expectDifference(_:_:operation:changes:fileID:filePath:line:column:)-5fu8q`` ### Customizing output - ``CustomDumpStringConvertible`` - ``CustomDumpRepresentable`` - ``CustomDumpReflectable`` + +### Deprecations diff --git a/Sources/CustomDump/Documentation.docc/Deprecations.md b/Sources/CustomDump/Documentation.docc/Deprecations.md new file mode 100644 index 0000000..d7e679b --- /dev/null +++ b/Sources/CustomDump/Documentation.docc/Deprecations.md @@ -0,0 +1,16 @@ +# Deprecations + +Review unsupported APIs and their replacements. + +## Overview + +Avoid using deprecated APIs in your app. Select a method to see the replacement that you should use +instead. + +## Topics + +### Test support + +- ``XCTAssertNoDifference(_:_:_:file:line:)`` +- ``XCTAssertDifference(_:_:operation:changes:file:line:)-8xfxw`` +- ``XCTAssertDifference(_:_:operation:changes:file:line:)-3c9r9`` diff --git a/Sources/CustomDump/Documentation.docc/XCTAssertDifference.md b/Sources/CustomDump/Documentation.docc/XCTAssertDifference.md deleted file mode 100644 index 9fde120..0000000 --- a/Sources/CustomDump/Documentation.docc/XCTAssertDifference.md +++ /dev/null @@ -1,7 +0,0 @@ -# ``CustomDump/XCTAssertDifference(_:_:operation:changes:file:line:)-8xfxw`` - -## Topics - -### Async - -- ``XCTAssertDifference(_:_:operation:changes:file:line:)-3c9r9`` diff --git a/Sources/CustomDump/Documentation.docc/expectDifference.md b/Sources/CustomDump/Documentation.docc/expectDifference.md new file mode 100644 index 0000000..1cf0ee0 --- /dev/null +++ b/Sources/CustomDump/Documentation.docc/expectDifference.md @@ -0,0 +1,7 @@ +# ``CustomDump/expectDifference(_:_:operation:changes:fileID:filePath:line:column:)-5fu8q`` + +## Topics + +### Async + +- ``expectDifference(_:_:operation:changes:fileID:filePath:line:column:)-1xg1y`` diff --git a/Sources/CustomDump/ExpectDifference.swift b/Sources/CustomDump/ExpectDifference.swift new file mode 100644 index 0000000..da40141 --- /dev/null +++ b/Sources/CustomDump/ExpectDifference.swift @@ -0,0 +1,164 @@ +import IssueReporting + +/// Expects that a value has a set of changes. +/// +/// This function evaluates a given expression before and after a given operation and then compares +/// the results. The comparison is done by invoking the `changes` closure with a mutable version of +/// the initial value, and then asserting that the modifications made match the final value using +/// ``expectNoDifference``. +/// +/// For example, given a very simple counter structure, we can write a test against its incrementing +/// functionality: +/// ` +/// ```swift +/// struct Counter { +/// var count = 0 +/// var isOdd = false +/// mutating func increment() { +/// self.count += 1 +/// self.isOdd.toggle() +/// } +/// } +/// +/// var counter = Counter() +/// expectDifference(counter) { +/// counter.increment() +/// } changes: { +/// $0.count = 1 +/// $0.isOdd = true +/// } +/// ``` +/// +/// If the `changes` does not exhaustively describe all changed fields, the assertion will fail. +/// +/// By omitting the operation you can write a "non-exhaustive" assertion against a value by +/// describing just the fields you want to assert against in the `changes` closure: +/// +/// ```swift +/// counter.increment() +/// expectDifference(counter) { +/// $0.count = 1 +/// // Don't need to further describe how `isOdd` has changed +/// } +/// ``` +/// +/// - Parameters: +/// - expression: An expression that is evaluated before and after `operation`, and then compared. +/// - message: An optional description of a failure. +/// - operation: An optional operation that is performed in between an initial and final +/// evaluation of `operation`. By omitting this operation, you can write a "non-exhaustive" +/// assertion against an already-changed value by describing just the fields you want to assert +/// against in the `changes` closure. +/// - updateExpectingResult: A closure that asserts how the expression changed by supplying a +/// mutable version of the initial value. This value must be modified to match the final value. +public func expectDifference( + _ expression: @autoclosure () throws -> T, + _ message: @autoclosure () -> String? = nil, + operation: () throws -> Void = {}, + changes updateExpectingResult: (inout T) throws -> Void, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column +) { + do { + var expression1 = try expression() + try updateExpectingResult(&expression1) + try operation() + let expression2 = try expression() + guard expression1 != expression2 else { return } + let format = DiffFormat.proportional + guard let difference = diff(expression1, expression2, format: format) + else { + reportIssue( + """ + ("\(expression1)" is not equal to ("\(expression2)"), but no difference was detected. + """, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + return + } + reportIssue( + """ + \(message()?.appending(" - ") ?? "")Difference: … + + \(difference.indenting(by: 2)) + + (Expected: \(format.first), Actual: \(format.second)) + """, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } catch { + reportIssue( + error, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } +} + +/// Expect that two values have no difference. +/// +/// An async version of +/// ``expectDifference(_:_:operation:changes:fileID:filePath:line:column:)-5fu8q``. +public func expectDifference( + _ expression: @autoclosure @Sendable () throws -> T, + _ message: @autoclosure @Sendable () -> String? = nil, + operation: @Sendable () async throws -> Void = {}, + changes updateExpectingResult: @Sendable (inout T) throws -> Void, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column +) async { + do { + var expression1 = try expression() + try updateExpectingResult(&expression1) + try await operation() + let expression2 = try expression() + guard expression1 != expression2 else { return } + let format = DiffFormat.proportional + guard let difference = diff(expression1, expression2, format: format) + else { + reportIssue( + """ + ("\(expression1)" is not equal to ("\(expression2)"), but no difference was detected. + """, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + return + } + reportIssue( + """ + \(message()?.appending(" - ") ?? "")Difference: … + + \(difference.indenting(by: 2)) + + (Expected: \(format.first), Actual: \(format.second)) + """, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } catch { + reportIssue( + error, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } +} diff --git a/Sources/CustomDump/ExpectNoDifference.swift b/Sources/CustomDump/ExpectNoDifference.swift new file mode 100644 index 0000000..38f7802 --- /dev/null +++ b/Sources/CustomDump/ExpectNoDifference.swift @@ -0,0 +1,96 @@ +import IssueReporting + +/// Asserts that two values have no difference. +/// +/// Similar to `XCTAssertEqual`, but that function uses either `TextOutputStreamable`, +/// `CustomStringConvertible` or `CustomDebugStringConvertible` in order to display a failure +/// message: +/// +/// ```swift +/// XCTAssertEqual(user1, user2) +/// ``` +/// ```text +/// XCTAssertEqual failed: ("User(id: 42, name: "Blob")") is not equal to ("User(id: 42, name: "Blob, Esq.")") +/// ``` +/// +/// `XCTAssertNoDifference` uses the output of ``diff(_:_:format:)`` to display a failure message, +/// which helps highlight the differences between the given values: +/// +/// ```swift +/// XCTAssertNoDifference(user1, user2) +/// ``` +/// ```text +/// XCTAssertNoDifference failed: … +/// +/// User( +/// id: 42, +/// - name: "Blob" +/// + name: "Blob, Esq." +/// ) +/// +/// (First: -, Second: +) +/// ``` +/// +/// - Parameters: +/// - expression1: An expression of type `T`, where `T` is `Equatable`. +/// - expression2: A second expression of type `T`, where `T` is `Equatable`. +/// - message: An optional description of a failure. +/// - fileID: The file where the failure occurs. The default is the file ID of the test case where +/// you call this function. +/// - filePath: The file where the failure occurs. The default is the file path of the test case +/// where you call this function. +/// - line: The line number where the failure occurs. The default is the line number where you +/// call this function. +/// - line: The column where the failure occurs. The default is the column where you call this +/// function. +public func expectNoDifference( + _ expression1: @autoclosure () throws -> T, + _ expression2: @autoclosure () throws -> T, + _ message: @autoclosure () -> String? = nil, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column +) { + do { + let expression1 = try expression1() + let expression2 = try expression2() + let message = message() + guard expression1 != expression2 else { return } + let format = DiffFormat.proportional + guard let difference = diff(expression1, expression2, format: format) + else { + reportIssue( + """ + ("\(expression1)" is not equal to ("\(expression2)"), but no difference was detected. + """, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + return + } + reportIssue( + """ + \(message?.appending(" - ") ?? "")Difference: … + + \(difference.indenting(by: 2)) + + (First: \(format.first), Second: \(format.second)) + """, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } catch { + reportIssue( + error, + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } +} diff --git a/Sources/CustomDump/XCTAssertDifference.swift b/Sources/CustomDump/XCTAssertDifference.swift index dad583f..f5ea6c5 100644 --- a/Sources/CustomDump/XCTAssertDifference.swift +++ b/Sources/CustomDump/XCTAssertDifference.swift @@ -1,57 +1,6 @@ import XCTestDynamicOverlay -/// Asserts that a value has a set of changes. -/// -/// This function evaluates a given expression before and after a given operation and then compares -/// the results. The comparison is done by invoking the `changes` closure with a mutable version of -/// the initial value, and then asserting that the modifications made match the final value using -/// ``XCTAssertNoDifference(_:_:_:file:line:)``. -/// -/// For example, given a very simple counter structure, we can write a test against its incrementing -/// functionality: -/// ` -/// ```swift -/// struct Counter { -/// var count = 0 -/// var isOdd = false -/// mutating func increment() { -/// self.count += 1 -/// self.isOdd.toggle() -/// } -/// } -/// -/// var counter = Counter() -/// XCTAssertDifference(counter) { -/// counter.increment() -/// } changes: { -/// $0.count = 1 -/// $0.isOdd = true -/// } -/// ``` -/// -/// If the `changes` does not exhaustively describe all changed fields, the assertion will fail. -/// -/// By omitting the operation you can write a "non-exhaustive" assertion against a value by -/// describing just the fields you want to assert against in the `changes` closure: -/// -/// ```swift -/// counter.increment() -/// XCTAssertDifference(counter) { -/// $0.count = 1 -/// // Don't need to further describe how `isOdd` has changed -/// } -/// ``` -/// -/// - Parameters: -/// - expression: An expression that is evaluated before and after `operation`, and then compared. -/// - message: An optional description of a failure. -/// - operation: An optional operation that is performed in between an initial and final -/// evaluation of `operation`. By omitting this operation, you can write a "non-exhaustive" -/// assertion against an already-changed value by describing just the fields you want to assert -/// against in the `changes` closure. -/// - updateExpectingResult: A closure that asserts how the expression changed by supplying a -/// mutable version of the initial value. This value must be modified to match the final value. -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +@available(*, deprecated, renamed: "expectDifference") public func XCTAssertDifference( _ expression: @autoclosure () throws -> T, _ message: @autoclosure () -> String = "", @@ -103,10 +52,7 @@ public func XCTAssertDifference( } } -/// Asserts that a value has a set of changes. -/// -/// An async version of ``XCTAssertDifference(_:_:operation:changes:file:line:)-8xfxw``. -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +@available(*, deprecated, renamed: "expectDifference") public func XCTAssertDifference( _ expression: @autoclosure @Sendable () throws -> T, _ message: @autoclosure @Sendable () -> String = "", diff --git a/Sources/CustomDump/XCTAssertNoDifference.swift b/Sources/CustomDump/XCTAssertNoDifference.swift index 1ade183..6ce26cb 100644 --- a/Sources/CustomDump/XCTAssertNoDifference.swift +++ b/Sources/CustomDump/XCTAssertNoDifference.swift @@ -1,45 +1,6 @@ import XCTestDynamicOverlay -/// Asserts that two values have no difference. -/// -/// Similar to `XCTAssertEqual`, but that function uses either `TextOutputStreamable`, -/// `CustomStringConvertible` or `CustomDebugStringConvertible` in order to display a failure -/// message: -/// -/// ```swift -/// XCTAssertEqual(user1, user2) -/// ``` -/// ```text -/// XCTAssertEqual failed: ("User(id: 42, name: "Blob")") is not equal to ("User(id: 42, name: "Blob, Esq.")") -/// ``` -/// -/// `XCTAssertNoDifference` uses the output of ``diff(_:_:format:)`` to display a failure message, -/// which helps highlight the differences between the given values: -/// -/// ```swift -/// XCTAssertNoDifference(user1, user2) -/// ``` -/// ```text -/// XCTAssertNoDifference failed: … -/// -/// User( -/// id: 42, -/// - name: "Blob" -/// + name: "Blob, Esq." -/// ) -/// -/// (First: -, Second: +) -/// ``` -/// -/// - Parameters: -/// - expression1: An expression of type `T`, where `T` is `Equatable`. -/// - expression2: A second expression of type `T`, where `T` is `Equatable`. -/// - message: An optional description of a failure. -/// - file: The file where the failure occurs. The default is the filename of the test case where -/// you call this function. -/// - line: The line number where the failure occurs. The default is the line number where you -/// call this function. -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +@available(*, deprecated, renamed: "expectNoDifference") public func XCTAssertNoDifference( _ expression1: @autoclosure () throws -> T, _ expression2: @autoclosure () throws -> T, diff --git a/Tests/CustomDumpTests/DumpTests.swift b/Tests/CustomDumpTests/DumpTests.swift index b42f0f4..06893f6 100644 --- a/Tests/CustomDumpTests/DumpTests.swift +++ b/Tests/CustomDumpTests/DumpTests.swift @@ -730,15 +730,6 @@ final class DumpTests: XCTestCase { """# ) - dump = "" - customDump(\User.name.count, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \User.name.count - """# - ) - dump = "" customDump(\(x: Double, y: Double).x, to: &dump) XCTAssertNoDifference( @@ -748,23 +739,35 @@ final class DumpTests: XCTestCase { """# ) - dump = "" - customDump(\Item.$isInStock, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \Item.$isInStock - """# - ) + #if DEBUG + dump = "" + customDump(\User.name.count, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \User.name.count + """# + ) + + dump = "" + customDump(\Item.$isInStock, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \Item.$isInStock + """# + ) + + dump = "" + customDump(\Wrapped.count, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \Wrapped.subscript(dynamicMember: ) + """# + ) + #endif - dump = "" - customDump(\Wrapped.count, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \Wrapped.subscript(dynamicMember: ) - """# - ) return } else { dump = "" diff --git a/Tests/CustomDumpTests/ExpectNoDifference.swift b/Tests/CustomDumpTests/ExpectNoDifference.swift new file mode 100644 index 0000000..75f70b9 --- /dev/null +++ b/Tests/CustomDumpTests/ExpectNoDifference.swift @@ -0,0 +1,40 @@ +#if canImport(Testing) + import CustomDump + import Foundation + import Testing + + @Suite + struct ExpectNoDifferenceTests { + @Test func basics() { + struct User: Equatable { + var id: UUID + var name: String + var bio: String + } + let user = User( + id: UUID(uuidString: "DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF")!, + name: "Blob", + bio: "Blobbed around the world." + ) + var otherUser = user + otherUser.name += " Jr." + withKnownIssue { + expectNoDifference(user, otherUser) + } matching: { + $0.description == """ + Expectation failed: Difference: … + +   ExpectNoDifferenceTests.User( +   id: UUID(DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF), + − name: "Blob", + + name: "Blob Jr.", +   bio: "Blobbed around the world." +   ) + + (First: −, Second: +) + """ + } + } + } + +#endif diff --git a/Tests/CustomDumpTests/XCTAssertDifferenceTests.swift b/Tests/CustomDumpTests/XCTAssertDifferenceTests.swift index 77d3ab8..f9f134f 100644 --- a/Tests/CustomDumpTests/XCTAssertDifferenceTests.swift +++ b/Tests/CustomDumpTests/XCTAssertDifferenceTests.swift @@ -1,7 +1,7 @@ import CustomDump import XCTest -@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) +@available(*, deprecated) class XCTAssertDifferencesTests: XCTestCase { func testXCTAssertDifference() { var user = User(id: 42, name: "Blob")