Skip to content

Commit

Permalink
Add support for Swift Testing (#39)
Browse files Browse the repository at this point in the history
Makes it so `TestClock` and `UnimplementedClock` both report failures to
the new Swift Testing framework.

---------

Co-authored-by: Brandon Williams <[email protected]>
  • Loading branch information
stephencelis and mbrandonw authored Jul 22, 2024
1 parent 0d93ac9 commit eb64eac
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 46 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ jobs:
run: sudo xcode-select -s /Applications/Xcode_15.4.app
- name: Run ${{ matrix.config }} tests
run: CONFIG=${{ matrix.config }} make test-all
- name: Build for library evolution
run: CONFIG=${{ matrix.config }} make build-for-library-evolution
# - name: Build for library evolution
# run: CONFIG=${{ matrix.config }} make build-for-library-evolution

linux:
strategy:
Expand Down
19 changes: 14 additions & 5 deletions Clocks.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,26 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin",
"state" : {
"revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6",
"revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
"version" : "1.3.0"
}
},
{
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-symbolkit",
"state" : {
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
}
},
{
"identity" : "xctest-dynamic-overlay",
"identity" : "swift-issue-reporting",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"location" : "https://github.com/pointfreeco/swift-issue-reporting",
"state" : {
"revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631",
"version" : "1.0.2"
"revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f",
"version" : "1.2.0"
}
}
],
Expand Down
23 changes: 16 additions & 7 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,36 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
"state" : {
"revision" : "ea631ce892687f5432a833312292b80db238186a",
"version" : "1.0.0"
"revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71",
"version" : "1.1.0"
}
},
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin",
"state" : {
"revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6",
"version" : "1.0.0"
"revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
"version" : "1.3.0"
}
},
{
"identity" : "xctest-dynamic-overlay",
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"location" : "https://github.com/apple/swift-docc-symbolkit",
"state" : {
"revision" : "302891700c7fa3b92ebde9fe7b42933f8349f3c7",
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
}
},
{
"identity" : "swift-issue-reporting",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-issue-reporting",
"state" : {
"revision" : "926f43898706eaa127db79ac42138e1ad7e85a3f",
"version" : "1.2.0"
}
}
],
"version" : 2
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.0.0"),
.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: "Clocks",
dependencies: [
.product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"),
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
.product(name: "IssueReporting", package: "swift-issue-reporting"),
]
),
.testTarget(
Expand Down
4 changes: 2 additions & 2 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.0.0"),
.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: "Clocks",
dependencies: [
.product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"),
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
.product(name: "IssueReporting", package: "swift-issue-reporting"),
]
),
.testTarget(
Expand Down
16 changes: 10 additions & 6 deletions Sources/Clocks/TestClock.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#if (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst))
import ConcurrencyExtras
import Foundation
import XCTestDynamicOverlay
import IssueReporting

/// A clock whose time can be controlled in a deterministic manner.
///
Expand Down Expand Up @@ -225,8 +225,10 @@
/// - duration: The amount of time to allow for all work on the clock to finish.
public func run(
timeout duration: Swift.Duration = .milliseconds(500),
file: StaticString = #file,
line: UInt = #line
fileID: StaticString = #fileID,
filePath: StaticString = #filePath,
line: UInt = #line,
column: UInt = #column
) async {
do {
try await withThrowingTaskGroup(of: Void.self) { group in
Expand All @@ -248,7 +250,7 @@
group.cancelAll()
}
} catch {
XCTFail(
reportIssue(
"""
Expected all sleeps to finish, but some are still suspending after \(duration).
Expand All @@ -258,8 +260,10 @@
You can also increase the timeout of 'run' to be greater than \(duration).
""",
file: file,
line: line
fileID: fileID,
filePath: filePath,
line: line,
column: column
)
}
}
Expand Down
70 changes: 58 additions & 12 deletions Sources/Clocks/UnimplementedClock.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#if (canImport(RegexBuilder) || !os(macOS) && !targetEnvironment(macCatalyst))
import ConcurrencyExtras
import Foundation
import XCTestDynamicOverlay
import IssueReporting

/// A clock that causes an XCTest failure when any of its endpoints are invoked.
///
Expand Down Expand Up @@ -84,34 +84,75 @@

private let base: AnyClock<Duration>
private let name: String
private let fileID: StaticString
private let filePath: StaticString
private let line: UInt
private let column: UInt

public init<C: Clock>(
_ base: C,
name: String = "\(C.self)"
name: String = "\(C.self)",
fileID: StaticString = #fileID,
filePath: StaticString = #filePath,
line: UInt = #line,
column: UInt = #column
) where C.Duration == Duration {
self.base = AnyClock(base)
self.name = name
self.fileID = fileID
self.filePath = filePath
self.line = line
self.column = column
}

public init(
name: String = "Clock",
now: ImmediateClock<Duration>.Instant = .init()
now: ImmediateClock<Duration>.Instant = .init(),
fileID: StaticString = #fileID,
filePath: StaticString = #filePath,
line: UInt = #line,
column: UInt = #column
) {
self.init(ImmediateClock(now: now), name: name)
self.init(
ImmediateClock(now: now),
name: name,
fileID: fileID,
filePath: filePath,
line: line,
column: column
)
}

public var now: Instant {
XCTFail("Unimplemented: \(self.name).now")
reportIssue(
"Unimplemented: \(self.name).now",
fileID: fileID,
filePath: filePath,
line: line,
column: column
)
return Instant(rawValue: self.base.now)
}

public var minimumResolution: Duration {
XCTFail("Unimplemented: \(self.name).minimumResolution")
reportIssue(
"Unimplemented: \(self.name).minimumResolution",
fileID: fileID,
filePath: filePath,
line: line,
column: column
)
return self.base.minimumResolution
}

public func sleep(until deadline: Instant, tolerance: Duration?) async throws {
XCTFail("Unimplemented: \(self.name).sleep")
reportIssue(
"Unimplemented: \(self.name).sleep",
fileID: fileID,
filePath: filePath,
line: line,
column: column
)
try await self.base.sleep(until: deadline.rawValue, tolerance: tolerance)
}
}
Expand All @@ -129,20 +170,25 @@
///
/// Constructs and returns an ``UnimplementedClock``
///
/// > Important: Due to [a bug in Swift](https://github.com/apple/swift/issues/61645), this static
/// > value cannot be used in an existential context:
/// > Important: Due to [a bug in Swift <6](https://github.com/apple/swift/issues/61645), this
/// > static value cannot be used in an existential context:
/// >
/// > ```swift
/// > let clock: any Clock<Duration> = .unimplemented // 🛑
/// > let clock: any Clock<Duration> = .unimplemented() // 🛑
/// > ```
/// >
/// > To work around this bug, construct an unimplemented clock directly:
/// >
/// > ```swift
/// > let clock: any Clock<Duration> = UnimplementedClock() // ✅
/// > ```
public static var unimplemented: Self {
UnimplementedClock()
public static func unimplemented(
fileID: StaticString = #fileID,
filePath: StaticString = #filePath,
line: UInt = #line,
column: UInt = #column
) -> Self {
UnimplementedClock(fileID: fileID, filePath: filePath, line: line, column: column)
}
}
#endif
2 changes: 1 addition & 1 deletion Tests/ClocksTests/TestClocksTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ final class TestClockTests: XCTestCase, @unchecked Sendable {
func testRunWithTimeout() async throws {
XCTExpectFailure {
$0.compactDescription == """
Expected all sleeps to finish, but some are still suspending after 1.0 seconds.
failed - Expected all sleeps to finish, but some are still suspending after 1.0 seconds.
There are sleeps suspending. This could mean you are not advancing the test clock far \
enough for your feature to execute its logic, or there could be a bug in your feature's \
Expand Down
18 changes: 9 additions & 9 deletions Tests/ClocksTests/UnimplementedClockTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@
func testUnimplementedClock() async throws {
XCTExpectFailure {
[
"Unimplemented: Clock.sleep",
"Unimplemented: Clock.now",
"failed - Unimplemented: Clock.sleep",
"failed - Unimplemented: Clock.now",
]
.contains($0.compactDescription)
}

let clock: some Clock<Duration> = .unimplemented
let clock: some Clock<Duration> = .unimplemented()
try await clock.sleep(for: .seconds(1))
}

func testUnimplementedClock_WithName() async throws {
XCTExpectFailure {
[
"Unimplemented: ContinuousClock.sleep",
"Unimplemented: ContinuousClock.now",
"failed - Unimplemented: ContinuousClock.sleep",
"failed - Unimplemented: ContinuousClock.now",
]
.contains($0.compactDescription)
}
Expand All @@ -33,8 +33,8 @@
func testNow() async throws {
XCTExpectFailure {
[
"Unimplemented: Clock.sleep",
"Unimplemented: Clock.now",
"failed - Unimplemented: Clock.sleep",
"failed - Unimplemented: Clock.now",
]
.contains($0.compactDescription)
}
Expand All @@ -48,8 +48,8 @@
let task = Task {
XCTExpectFailure {
[
"Unimplemented: Clock.sleep",
"Unimplemented: Clock.now",
"failed - Unimplemented: Clock.sleep",
"failed - Unimplemented: Clock.now",
]
.contains($0.compactDescription)
}
Expand Down

0 comments on commit eb64eac

Please sign in to comment.