Skip to content

Commit 33e6d39

Browse files
committed
PR and documentation feedback
1 parent 889c15d commit 33e6d39

File tree

2 files changed

+139
-113
lines changed

2 files changed

+139
-113
lines changed

Sources/Testing/Confirmations/Polling.swift

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ private let _defaultPollingConfiguration = (
1919
/// This also determines what happens if the duration elapses during polling.
2020
@_spi(Experimental)
2121
public 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

Comments
 (0)