@@ -19,16 +19,16 @@ private let _defaultPollingConfiguration = (
1919/// This also determines what happens if the duration elapses during polling.
2020@_spi ( Experimental)
2121public enum PollingStopCondition : Sendable , Equatable , Codable {
22- /// Evaluates the expression until the first time it returns true.
23- /// If it does not pass once by the time the timeout is reached, then a
22+ /// Evaluates the expression until the first time it passes
23+ /// If it does not pass once by the time the duration is reached, then a
2424 /// failure will be reported.
2525 case firstPass
2626
27- /// Evaluates the expression until the first time it returns false .
28- /// If the expression returns false , then a failure will be reported.
29- /// If the expression only returns true before the timeout is reached, then
27+ /// Evaluates the expression until the first time it returns fails .
28+ /// If the expression fails , then a failure will be reported.
29+ /// If the expression only passes before the duration is reached, then
3030 /// no failure will be reported.
31- /// If the expression does not finish evaluating before the timeout is
31+ /// If the expression does not finish evaluating before the duration is
3232 /// reached, then a failure will be reported.
3333 case stopsPassing
3434}
@@ -93,14 +93,15 @@ extension PollingFailedError: CustomIssueRepresentable {
9393/// - comment: A user-specified comment describing this confirmation.
9494/// - stopCondition: When to stop polling.
9595/// - duration: The expected length of time to continue polling for.
96- /// This value may not correspond to the wall-clock time that polling lasts
97- /// for, especially on highly-loaded systems with a lot of tests running.
96+ /// This value does not incorporate the time to run `body`, and may not
97+ /// correspond to the wall-clock time that polling lasts for, especially on
98+ /// highly-loaded systems with a lot of tests running.
9899/// If nil, this uses whatever value is specified under the last
99100/// ``PollingConfirmationConfigurationTrait`` added to the test or suite
100101/// with a matching stopCondition.
101102/// If no such trait has been added, then polling will be attempted for
102103/// about 1 second before recording an issue.
103- /// `duration` must be greater than 0 .
104+ /// `duration` must be greater than or equal to `interval` .
104105/// - interval: The minimum amount of time to wait between polling attempts.
105106/// If nil, this uses whatever value is specified under the last
106107/// ``PollingConfirmationConfigurationTrait`` added to the test or suite
@@ -110,7 +111,9 @@ extension PollingFailedError: CustomIssueRepresentable {
110111/// `interval` must be greater than 0.
111112/// - isolation: The actor to which `body` is isolated, if any.
112113/// - sourceLocation: The location in source where the confirmation was called.
113- /// - body: The function to invoke.
114+ /// - body: The function to invoke. The expression is considered to pass if
115+ /// the `body` returns true. Similarly, the expression is considered to fail
116+ /// if `body` returns false.
114117///
115118/// - Throws: A `PollingFailedError` if the `body` does not return true within
116119/// the polling duration.
@@ -155,14 +158,15 @@ public func confirmation(
155158/// - comment: A user-specified comment describing this confirmation.
156159/// - stopCondition: When to stop polling.
157160/// - duration: The expected length of time to continue polling for.
158- /// This value may not correspond to the wall-clock time that polling lasts
159- /// for, especially on highly-loaded systems with a lot of tests running.
161+ /// This value does not incorporate the time to run `body`, and may not
162+ /// correspond to the wall-clock time that polling lasts for, especially on
163+ /// highly-loaded systems with a lot of tests running.
160164/// If nil, this uses whatever value is specified under the last
161165/// ``PollingConfirmationConfigurationTrait`` added to the test or suite
162166/// with a matching stopCondition.
163167/// If no such trait has been added, then polling will be attempted for
164168/// about 1 second before recording an issue.
165- /// `duration` must be greater than 0 .
169+ /// `duration` must be greater than or equal to `interval` .
166170/// - interval: The minimum amount of time to wait between polling attempts.
167171/// If nil, this uses whatever value is specified under the last
168172/// ``PollingConfirmationConfigurationTrait`` added to the test or suite
@@ -172,7 +176,9 @@ public func confirmation(
172176/// `interval` must be greater than 0.
173177/// - isolation: The actor to which `body` is isolated, if any.
174178/// - sourceLocation: The location in source where the confirmation was called.
175- /// - body: The function to invoke.
179+ /// - body: The function to invoke. The expression is considered to pass if
180+ /// the `body` returns a non-nil value. Similarly, the expression is
181+ /// considered to fail if `body` returns nil.
176182///
177183/// - Throws: A `PollingFailedError` if the `body` does not return true within
178184/// the polling duration.
@@ -274,6 +280,8 @@ extension PollingStopCondition {
274280 case . firstPass:
275281 if let result {
276282 return . succeeded( result)
283+ } else if wasLastPollingAttempt {
284+ return . failed
277285 } else {
278286 return . continuePolling
279287 }
@@ -340,7 +348,7 @@ private struct Poller {
340348 /// Evaluate polling, throwing an error if polling fails.
341349 ///
342350 /// - Parameters:
343- /// - isolation: The isolation to use.
351+ /// - isolation: The actor isolation to use.
344352 /// - body: The expression to poll.
345353 ///
346354 /// - Throws: A ``PollingFailedError`` if polling doesn't pass.
@@ -366,7 +374,7 @@ private struct Poller {
366374 /// Evaluate polling, throwing an error if polling fails.
367375 ///
368376 /// - Parameters:
369- /// - isolation: The isolation to use.
377+ /// - isolation: The actor isolation to use.
370378 /// - body: The expression to poll.
371379 ///
372380 /// - Throws: A ``PollingFailedError`` if polling doesn't pass.
@@ -379,9 +387,8 @@ private struct Poller {
379387 isolation: isolated ( any Actor ) ? ,
380388 _ body: @escaping ( ) async -> sending R?
381389 ) async throws -> R {
382- precondition ( duration > Duration . zero)
383390 precondition ( interval > Duration . zero)
384- precondition ( duration > interval)
391+ precondition ( duration >= interval)
385392
386393 let iterations = Int ( exactly:
387394 max ( duration. seconds ( ) / interval. seconds ( ) , 1 ) . rounded ( )
@@ -390,7 +397,11 @@ private struct Poller {
390397 // large. In which case, we should fall back to Int.max.
391398
392399 let failureReason : PollingFailedError . Reason
393- switch await poll ( iterations: iterations, expression: body) {
400+ switch await poll (
401+ iterations: iterations,
402+ isolation: isolation,
403+ expression: body
404+ ) {
394405 case let . succeeded( value) :
395406 return value
396407 case . cancelled:
@@ -420,12 +431,13 @@ private struct Poller {
420431 ///
421432 /// - Parameters:
422433 /// - iterations: The maximum amount of times to continue polling.
434+ /// - isolation: The actor isolation to use.
423435 /// - expression: An expression to continuously evaluate.
424436 ///
425437 /// - Returns: The most recent value if the polling succeeded, else nil.
426438 private func poll< R> (
427439 iterations: Int ,
428- isolation: isolated ( any Actor ) ? = #isolation ,
440+ isolation: isolated ( any Actor ) ? ,
429441 expression: @escaping ( ) async -> sending R?
430442 ) async -> PollingResult < R > {
431443 for iteration in 0 ..< iterations {
@@ -447,6 +459,9 @@ private struct Poller {
447459 return . cancelled
448460 }
449461 }
462+ // This is somewhat redundant and only here to satisfy the compiler.
463+ // `PollingStopCondition.process` will return either `.succeeded` or
464+ // `.failed` on the last polling attempt.
450465 return . failed
451466 }
452467}
0 commit comments