@@ -190,9 +190,8 @@ public func confirmPassesEventually<R>(
190
190
pollingInterval: Duration ? = nil ,
191
191
isolation: isolated ( any Actor ) ? = #isolation,
192
192
sourceLocation: SourceLocation = #_sourceLocation,
193
- _ body: @escaping ( ) async throws -> R ?
194
- ) async throws -> R where R: Sendable {
195
- let recorder = PollingRecorder < R > ( )
193
+ _ body: @escaping ( ) async throws -> sending R?
194
+ ) async throws -> R {
196
195
let poller = Poller (
197
196
pollingBehavior: . passesOnce,
198
197
pollingIterations: getValueFromPollingTrait (
@@ -208,15 +207,15 @@ public func confirmPassesEventually<R>(
208
207
comment: comment,
209
208
sourceLocation: sourceLocation
210
209
)
211
- await poller. evaluate ( isolation: isolation) {
210
+ let recordedValue = await poller. evaluate ( isolation: isolation) {
212
211
do {
213
- return try await recorder . record ( value : body ( ) )
212
+ return try await body ( )
214
213
} catch {
215
- return false
214
+ return nil
216
215
}
217
216
}
218
217
219
- if let value = await recorder . lastValue {
218
+ if let value = recordedValue {
220
219
return value
221
220
}
222
221
throw PollingFailedError ( )
@@ -375,26 +374,6 @@ private func getValueFromPollingTrait<TraitKind, Value>(
375
374
return traitValues. last ?? `default`
376
375
}
377
376
378
- /// A type to record the last value returned by a closure returning an optional
379
- /// This is only used in the `confirm` polling functions evaluating an optional.
380
- private actor PollingRecorder < R: Sendable > {
381
- var lastValue : R ?
382
-
383
- /// Record a new value to be returned
384
- func record( value: R ) {
385
- self . lastValue = value
386
- }
387
-
388
- func record( value: R ? ) -> Bool {
389
- if let value {
390
- self . lastValue = value
391
- return true
392
- } else {
393
- return false
394
- }
395
- }
396
- }
397
-
398
377
/// A type for managing polling
399
378
@available ( macOS 13 , iOS 17 , watchOS 9 , tvOS 17 , visionOS 1 , * )
400
379
private struct Poller {
@@ -567,4 +546,82 @@ private struct Poller {
567
546
}
568
547
return . ranToCompletion
569
548
}
549
+
550
+ /// Evaluate polling, and process the result, raising an issue if necessary.
551
+ ///
552
+ /// - Note: This method is only intended to be used when pollingBehavior is
553
+ /// `.passesOnce`
554
+ ///
555
+ /// - Parameters:
556
+ /// - raiseIssue: Whether or not to raise an issue.
557
+ /// This should only be false for `requirePassesEventually` or
558
+ /// `requireAlwaysPasses`.
559
+ /// - isolation: The isolation to use
560
+ /// - body: The expression to poll
561
+ ///
562
+ /// - Returns: the value if polling passed, nil otherwise.
563
+ ///
564
+ /// - Side effects: If polling fails (see `PollingBehavior`), then this will
565
+ /// record an issue.
566
+ @discardableResult func evaluate< R> (
567
+ raiseIssue: Bool = true ,
568
+ isolation: isolated ( any Actor ) ? ,
569
+ _ body: @escaping ( ) async -> sending R?
570
+ ) async -> R ? {
571
+ precondition ( pollingIterations > 0 )
572
+ precondition ( pollingInterval > Duration . zero)
573
+ let ( result, value) = await poll (
574
+ expression: body
575
+ )
576
+ if let issue = result. issue (
577
+ comment: comment,
578
+ sourceContext: . init( backtrace: . current( ) , sourceLocation: sourceLocation) ,
579
+ pollingBehavior: pollingBehavior
580
+ ) {
581
+ if raiseIssue {
582
+ issue. record ( )
583
+ }
584
+ return value
585
+ } else {
586
+ return value
587
+ }
588
+ }
589
+
590
+ /// This function contains the logic for continuously polling an expression,
591
+ /// as well as processing the results of that expression
592
+ ///
593
+ /// - Note: This method is only intended to be used when pollingBehavior is
594
+ /// `.passesOnce`
595
+ ///
596
+ /// - Parameters:
597
+ /// - expression: An expression to continuously evaluate
598
+ /// - behavior: The polling behavior to use
599
+ /// - timeout: How long to poll for unitl the timeout triggers.
600
+ /// - Returns: The result of this polling and the most recent value if the
601
+ /// result is .finished, otherwise nil.
602
+ private func poll< R> (
603
+ isolation: isolated ( any Actor ) ? = #isolation,
604
+ expression: @escaping ( ) async -> sending R?
605
+ ) async -> ( PollResult , R ? ) {
606
+ for iteration in 0 ..< pollingIterations {
607
+ let lastResult = await expression ( )
608
+ if let result = pollingBehavior. processFinishedExpression (
609
+ expressionResult: lastResult != nil
610
+ ) {
611
+ return ( result, lastResult)
612
+ }
613
+ if iteration == ( pollingIterations - 1 ) {
614
+ // don't bother sleeping if it's the last iteration.
615
+ break
616
+ }
617
+ do {
618
+ try await Task . sleep ( for: pollingInterval)
619
+ } catch {
620
+ // `Task.sleep` should only throw an error if it's cancelled
621
+ // during the sleep period.
622
+ return ( . cancelled, nil )
623
+ }
624
+ }
625
+ return ( . ranToCompletion, nil )
626
+ }
570
627
}
0 commit comments