Skip to content

Commit

Permalink
Swift Testing support (#124)
Browse files Browse the repository at this point in the history
* Swift testing

* wip

* wip

* wip

* test

* wip

* wip

* wip

* wip

---------

Co-authored-by: Brandon Williams <mbrandonw@hey.com>
  • Loading branch information
stephencelis and mbrandonw authored Jul 22, 2024
1 parent e6a364d commit d237304
Show file tree
Hide file tree
Showing 16 changed files with 389 additions and 167 deletions.
24 changes: 11 additions & 13 deletions CustomDump.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
@@ -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
}
24 changes: 11 additions & 13 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 3 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
5 changes: 3 additions & 2 deletions Package@swift-6.0.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 2 additions & 6 deletions Sources/CustomDump/Conformances/Foundation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 4 additions & 2 deletions Sources/CustomDump/Documentation.docc/CustomDump.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 16 additions & 0 deletions Sources/CustomDump/Documentation.docc/Deprecations.md
Original file line number Diff line number Diff line change
@@ -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``
7 changes: 0 additions & 7 deletions Sources/CustomDump/Documentation.docc/XCTAssertDifference.md

This file was deleted.

7 changes: 7 additions & 0 deletions Sources/CustomDump/Documentation.docc/expectDifference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# ``CustomDump/expectDifference(_:_:operation:changes:fileID:filePath:line:column:)-5fu8q``

## Topics

### Async

- ``expectDifference(_:_:operation:changes:fileID:filePath:line:column:)-1xg1y``
164 changes: 164 additions & 0 deletions Sources/CustomDump/ExpectDifference.swift
Original file line number Diff line number Diff line change
@@ -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<T: Equatable>(
_ 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<T: Equatable & Sendable>(
_ 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
)
}
}
Loading

0 comments on commit d237304

Please sign in to comment.