@@ -190,9 +190,8 @@ public func confirmPassesEventually<R>(
190190 pollingInterval: Duration ? = nil ,
191191 isolation: isolated ( any Actor ) ? = #isolation,
192192 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 {
196195 let poller = Poller (
197196 pollingBehavior: . passesOnce,
198197 pollingIterations: getValueFromPollingTrait (
@@ -208,15 +207,15 @@ public func confirmPassesEventually<R>(
208207 comment: comment,
209208 sourceLocation: sourceLocation
210209 )
211- await poller. evaluate ( isolation: isolation) {
210+ let recordedValue = await poller. evaluate ( isolation: isolation) {
212211 do {
213- return try await recorder . record ( value : body ( ) )
212+ return try await body ( )
214213 } catch {
215- return false
214+ return nil
216215 }
217216 }
218217
219- if let value = await recorder . lastValue {
218+ if let value = recordedValue {
220219 return value
221220 }
222221 throw PollingFailedError ( )
@@ -375,26 +374,6 @@ private func getValueFromPollingTrait<TraitKind, Value>(
375374 return traitValues. last ?? `default`
376375}
377376
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-
398377/// A type for managing polling
399378@available ( macOS 13 , iOS 17 , watchOS 9 , tvOS 17 , visionOS 1 , * )
400379private struct Poller {
@@ -567,4 +546,82 @@ private struct Poller {
567546 }
568547 return . ranToCompletion
569548 }
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+ }
570627}
0 commit comments