@@ -25,23 +25,56 @@ import Testing
2525public actor Expectation {
2626 // MARK: Initialization
2727
28+ /// An expected outcome in an asynchronous test.
29+ /// - Parameters:
30+ /// - expectedCount: The number of times `fulfill()` must be called before the expectation is completely fulfilled.
31+ /// - requireAwaitingFulfillment: Controls whether `deinit` requires that a created expectation has its fulfillment awaited.
2832 public init (
29- expectedCount: UInt = 1
33+ expectedCount: UInt = 1 ,
34+ requireAwaitingFulfillment: Bool = true ,
35+ filePath: String = #filePath,
36+ fileID: String = #fileID,
37+ line: Int = #line,
38+ column: Int = #column
3039 ) {
3140 self . init (
3241 expectedCount: expectedCount,
3342 expect: { fulfilledWithExpectedCount, comment, sourceLocation in
3443 #expect( fulfilledWithExpectedCount, comment, sourceLocation: sourceLocation)
35- }
44+ } ,
45+ precondition: requireAwaitingFulfillment ? Swift . precondition : nil ,
46+ filePath: filePath,
47+ fileID: fileID,
48+ line: line,
49+ column: column
3650 )
3751 }
3852
3953 init (
4054 expectedCount: UInt ,
41- expect: @escaping ( Bool , Comment ? , SourceLocation ) -> Void
55+ expect: @escaping @Sendable ( Bool , Comment ? , SourceLocation ) -> Void ,
56+ precondition: ( @Sendable ( @autoclosure ( ) -> Bool , @autoclosure ( ) -> String , StaticString , UInt ) -> Void ) ? = nil ,
57+ filePath: String = #filePath,
58+ fileID: String = #fileID,
59+ line: Int = #line,
60+ column: Int = #column
4261 ) {
4362 self . expectedCount = expectedCount
4463 self . expect = expect
64+ self . precondition = precondition
65+ createdSourceLocation = . init(
66+ fileID: fileID,
67+ filePath: filePath,
68+ line: line,
69+ column: column
70+ )
71+ }
72+
73+ deinit {
74+ let fulfillmentAwaited = fulfillmentAwaited
75+ if let precondition {
76+ precondition ( fulfillmentAwaited, " Expectation created at \( createdSourceLocation) was never awaited " , #file, #line)
77+ }
4578 }
4679
4780 // MARK: Public
@@ -53,6 +86,7 @@ public actor Expectation {
5386 line: Int = #line,
5487 column: Int = #column
5588 ) async {
89+ fulfillmentAwaited = true
5690 guard !isComplete else { return }
5791 let wait = Task {
5892 try await Task . sleep ( for: duration)
@@ -93,8 +127,12 @@ public actor Expectation {
93127 expectedCount <= fulfillCount
94128 }
95129
130+ private var fulfillmentAwaited = false
131+
96132 private let expectedCount : UInt
97- private let expect : ( Bool , Comment ? , SourceLocation ) -> Void
133+ private let expect : @Sendable ( Bool , Comment ? , SourceLocation ) -> Void
134+ private let precondition : ( @Sendable ( @autoclosure ( ) -> Bool , @autoclosure ( ) -> String , StaticString , UInt ) -> Void ) ?
135+ private let createdSourceLocation : SourceLocation
98136
99137 private func _fulfill(
100138 filePath: String ,
0 commit comments