Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Polling Confirmations
Preliminary implementation of polling expectations

Make Polling into a function along the lines of Confirmation
Additionally, make PollingBehavior an implementation detail of polling, instead of exposed publicly
Removes any timeouts involved for polling, as they become increasingly unreliable
as the system runs more and more tests

Take in configuration arguments, add polling interval

Add traits for configuring polling

Use consistent naming between confirmAlwaysPasses and the related configuration trait
Stop unnecessarily waiting after the last polling attempt has finished.
Allow for subsequent polling configuration traits which specified nil for a value to fall back to earlier polling configuration traits before falling back to the default.

Add requirePassesEventually and requireAlwaysPasses
These two mirror their confirm counterparts, only throwing an error (instead of recording an issue) when they fail.

Rewrite confirmPassesEventually when returning an optional to remove the PollingRecorder actor.
Now, this uses a separate method for evaluating polling to remove that actor.

Clean up the duplicate Poller.evaluate/Poller.poll methods
Removed the duplicate poll method, and made evaluate-returning-bool into a wrapper for evaluate-returning-optional

Configure polling confirmations as timeout & polling interval
This is less direct, but much more intuitive for test authors.
Also add exit tests confirming that these values are non-negative

Rename to actually use the confirmation name
Follow more english-sentence-like guidance for function naming
Simplify the polling confirmation API down to just 2 public functions, 1 enum, and 1 error type.
Always throw an error when polling fails, get rid of the separate issue recording.

Use a single polling confirmation configuration trait
Instead of mulitple traits per stop condition, just have a single trait
per stop condition.
  • Loading branch information
younata committed Oct 20, 2025
commit 30df1441d50f4fa6af73a86033165d65d48321b4
27 changes: 27 additions & 0 deletions Sources/Testing/Issues/Issue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ public struct Issue: Sendable {
/// confirmed too few or too many times.
indirect case confirmationMiscounted(actual: Int, expected: any RangeExpression & Sendable)

/// An issue due to a polling confirmation having failed.
///
/// This issue can occur when calling ``confirmation(_:until:within:pollingEvery:isolation:sourceLocation:_:)-455gr``
/// or
/// ``confirmation(_:until:within:pollingEvery:isolation:sourceLocation:_:)-5tnlk``
/// whenever the polling fails, as described in ``PollingStopCondition``.
@_spi(Experimental)
case pollingConfirmationFailed

/// An issue due to an `Error` being thrown by a test function and caught by
/// the testing library.
///
Expand Down Expand Up @@ -295,6 +304,8 @@ extension Issue.Kind: CustomStringConvertible {
}
}
return "Confirmation was confirmed \(actual.counting("time")), but expected to be confirmed \(String(describingForTest: expected)) time(s)"
case .pollingConfirmationFailed:
return "Polling confirmation failed"
case let .errorCaught(error):
return "Caught error: \(error)"
case let .timeLimitExceeded(timeLimitComponents: timeLimitComponents):
Expand Down Expand Up @@ -434,6 +445,15 @@ extension Issue.Kind {
/// too few or too many times.
indirect case confirmationMiscounted(actual: Int, expected: Int)

/// An issue due to a polling confirmation having failed.
///
/// This issue can occur when calling ``confirmation(_:until:within:pollingEvery:isolation:sourceLocation:_:)-455gr``
/// or
/// ``confirmation(_:until:within:pollingEvery:isolation:sourceLocation:_:)-5tnlk``
/// whenever the polling fails, as described in ``PollingStopCondition``.
@_spi(Experimental)
case pollingConfirmationFailed

/// An issue due to an `Error` being thrown by a test function and caught by
/// the testing library.
///
Expand Down Expand Up @@ -477,6 +497,8 @@ extension Issue.Kind {
.expectationFailed(Expectation.Snapshot(snapshotting: expectation))
case .confirmationMiscounted:
.unconditional
case .pollingConfirmationFailed:
.pollingConfirmationFailed
case let .errorCaught(error), let .valueAttachmentFailed(error):
.errorCaught(ErrorSnapshot(snapshotting: error))
case let .timeLimitExceeded(timeLimitComponents: timeLimitComponents):
Expand All @@ -495,6 +517,7 @@ extension Issue.Kind {
case unconditional
case expectationFailed
case confirmationMiscounted
case pollingConfirmationFailed
case errorCaught
case timeLimitExceeded
case knownIssueNotRecorded
Expand Down Expand Up @@ -567,6 +590,8 @@ extension Issue.Kind {
forKey: .confirmationMiscounted)
try confirmationMiscountedContainer.encode(actual, forKey: .actual)
try confirmationMiscountedContainer.encode(expected, forKey: .expected)
case .pollingConfirmationFailed:
try container.encode(true, forKey: .pollingConfirmationFailed)
case let .errorCaught(error):
var errorCaughtContainer = container.nestedContainer(keyedBy: _CodingKeys._ErrorCaughtKeys.self, forKey: .errorCaught)
try errorCaughtContainer.encode(error, forKey: .error)
Expand Down Expand Up @@ -622,6 +647,8 @@ extension Issue.Kind.Snapshot: CustomStringConvertible {
}
case let .confirmationMiscounted(actual: actual, expected: expected):
"Confirmation was confirmed \(actual.counting("time")), but expected to be confirmed \(expected.counting("time"))"
case .pollingConfirmationFailed:
"Polling confirmation failed"
case let .errorCaught(error):
"Caught error: \(error)"
case let .timeLimitExceeded(timeLimitComponents: timeLimitComponents):
Expand Down
Loading