Skip to content

Commit 74262d2

Browse files
committed
Use a single polling confirmation configuration trait
Instead of mulitple traits per stop condition, just have a single trait per stop condition.
1 parent 3328a8c commit 74262d2

File tree

3 files changed

+116
-114
lines changed

3 files changed

+116
-114
lines changed

Sources/Testing/Polling/Polling.swift

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ extension PollingFailedError: CustomIssueRepresentable {
5454

5555
/// A type defining when to stop polling early.
5656
/// This also determines what happens if the duration elapses during polling.
57-
public enum PollingStopCondition: Sendable {
57+
public enum PollingStopCondition: Sendable, Equatable {
5858
/// Evaluates the expression until the first time it returns true.
5959
/// If it does not pass once by the time the timeout is reached, then a
6060
/// failure will be reported.
@@ -78,17 +78,15 @@ public enum PollingStopCondition: Sendable {
7878
/// This value may not correspond to the wall-clock time that polling lasts
7979
/// for, especially on highly-loaded systems with a lot of tests running.
8080
/// If nil, this uses whatever value is specified under the last
81-
/// ``PollingUntilFirstPassConfigurationTrait`` or
82-
/// ``PollingUntilStopsPassingConfigurationTrait`` added to the test or
83-
/// suite.
81+
/// ``PollingConfirmationConfigurationTrait`` added to the test or suite
82+
/// with a matching stopCondition.
8483
/// If no such trait has been added, then polling will be attempted for
8584
/// about 1 second before recording an issue.
8685
/// `duration` must be greater than 0.
8786
/// - interval: The minimum amount of time to wait between polling attempts.
8887
/// If nil, this uses whatever value is specified under the last
89-
/// ``PollingUntilFirstPassConfigurationTrait`` or
90-
/// ``PollingUntilStopsPassingConfigurationTrait`` added to the test or
91-
/// suite.
88+
/// ``PollingConfirmationConfigurationTrait`` added to the test or suite
89+
/// with a matching stopCondition.
9290
/// If no such trait has been added, then polling will wait at least
9391
/// 1 millisecond between polling attempts.
9492
/// `interval` must be greater than 0.
@@ -142,17 +140,15 @@ public func confirmation(
142140
/// This value may not correspond to the wall-clock time that polling lasts
143141
/// for, especially on highly-loaded systems with a lot of tests running.
144142
/// If nil, this uses whatever value is specified under the last
145-
/// ``PollingUntilFirstPassConfigurationTrait`` or
146-
/// ``PollingUntilStopsPassingConfigurationTrait`` added to the test or
147-
/// suite.
143+
/// ``PollingConfirmationConfigurationTrait`` added to the test or suite
144+
/// with a matching stopCondition.
148145
/// If no such trait has been added, then polling will be attempted for
149146
/// about 1 second before recording an issue.
150147
/// `duration` must be greater than 0.
151148
/// - interval: The minimum amount of time to wait between polling attempts.
152149
/// If nil, this uses whatever value is specified under the last
153-
/// ``PollingUntilFirstPassConfigurationTrait`` or
154-
/// ``PollingUntilStopsPassingConfigurationTrait`` added to the test or
155-
/// suite.
150+
/// ``PollingConfirmationConfigurationTrait`` added to the test or suite
151+
/// with a matching stopCondition.
156152
/// If no such trait has been added, then polling will wait at least
157153
/// 1 millisecond between polling attempts.
158154
/// `interval` must be greater than 0.
@@ -221,11 +217,13 @@ public func confirmation<R>(
221217
private func getValueFromTrait<TraitKind, Value>(
222218
providedValue: Value?,
223219
default: Value,
224-
_ keyPath: KeyPath<TraitKind, Value?>
220+
_ keyPath: KeyPath<TraitKind, Value?>,
221+
where filter: @escaping (TraitKind) -> Bool
225222
) -> Value {
226223
if let providedValue { return providedValue }
227224
guard let test = Test.current else { return `default` }
228225
let possibleTraits = test.traits.compactMap { $0 as? TraitKind }
226+
.filter(filter)
229227
let traitValues = possibleTraits.compactMap { $0[keyPath: keyPath] }
230228
return traitValues.last ?? `default`
231229
}
@@ -257,20 +255,12 @@ extension PollingStopCondition {
257255
/// ``PollingUntilFirstPassConfigurationTrait``.
258256
@available(_clockAPI, *)
259257
fileprivate func duration(with provided: Duration?) -> Duration {
260-
switch self {
261-
case .firstPass:
262-
getValueFromTrait(
263-
providedValue: provided,
264-
default: defaultPollingConfiguration.pollingDuration,
265-
\PollingUntilFirstPassConfigurationTrait.duration
266-
)
267-
case .stopsPassing:
268-
getValueFromTrait(
269-
providedValue: provided,
270-
default: defaultPollingConfiguration.pollingDuration,
271-
\PollingUntilStopsPassingConfigurationTrait.duration
272-
)
273-
}
258+
getValueFromTrait(
259+
providedValue: provided,
260+
default: defaultPollingConfiguration.pollingDuration,
261+
\PollingConfirmationConfigurationTrait.duration,
262+
where: { $0.stopCondition == self }
263+
)
274264
}
275265

276266
/// Determine the polling interval to use for the given provided value.
@@ -279,20 +269,12 @@ extension PollingStopCondition {
279269
/// ``PollingUntilFirstPassConfigurationTrait``.
280270
@available(_clockAPI, *)
281271
fileprivate func interval(with provided: Duration?) -> Duration {
282-
switch self {
283-
case .firstPass:
284-
getValueFromTrait(
285-
providedValue: provided,
286-
default: defaultPollingConfiguration.pollingInterval,
287-
\PollingUntilFirstPassConfigurationTrait.interval
288-
)
289-
case .stopsPassing:
290-
getValueFromTrait(
291-
providedValue: provided,
292-
default: defaultPollingConfiguration.pollingInterval,
293-
\PollingUntilStopsPassingConfigurationTrait.interval
294-
)
295-
}
272+
getValueFromTrait(
273+
providedValue: provided,
274+
default: defaultPollingConfiguration.pollingInterval,
275+
\PollingConfirmationConfigurationTrait.interval,
276+
where: { $0.stopCondition == self }
277+
)
296278
}
297279
}
298280

Sources/Testing/Traits/PollingConfigurationTrait.swift

Lines changed: 27 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -9,86 +9,46 @@
99
/// ``confirmation(_:until:within:pollingEvery:isolation:sourceLocation:_:)-455gr``
1010
/// and
1111
/// ``confirmation(_:until:within:pollingEvery:isolation:sourceLocation:_:)-5tnlk``
12-
/// within a test or suite for the ``PollingStopCondition.firstPass``
13-
/// stop condition.
12+
/// within a test or suite using the specified stop condition.
1413
///
15-
/// To add this trait to a test, use the
16-
/// ``Trait/pollingUntilFirstPassDefaults`` function.
17-
@_spi(Experimental)
18-
@available(_clockAPI, *)
19-
public struct PollingUntilFirstPassConfigurationTrait: TestTrait, SuiteTrait {
20-
/// How long to continue polling for
21-
public var duration: Duration?
22-
/// The minimum amount of time to wait between polling attempts
23-
public var interval: Duration?
24-
25-
public var isRecursive: Bool { true }
26-
27-
public init(duration: Duration?, interval: Duration?) {
28-
self.duration = duration
29-
self.interval = interval
30-
}
31-
}
32-
33-
/// A trait to provide a default polling configuration to all usages of
34-
/// ``confirmation(_:until:within:pollingEvery:isolation:sourceLocation:_:)-455gr``
35-
/// and
36-
/// ``confirmation(_:until:within:pollingEvery:isolation:sourceLocation:_:)-5tnlk``
37-
/// within a test or suite for the ``PollingStopCondition.stopsPassing``
38-
/// stop condition.
39-
///
40-
/// To add this trait to a test, use the ``Trait/pollingUntilStopsPassingDefaults``
14+
/// To add this trait to a test, use the ``Trait/pollingConfirmationDefaults``
4115
/// function.
4216
@_spi(Experimental)
4317
@available(_clockAPI, *)
44-
public struct PollingUntilStopsPassingConfigurationTrait: TestTrait, SuiteTrait {
45-
/// How long to continue polling for
18+
public struct PollingConfirmationConfigurationTrait: TestTrait, SuiteTrait {
19+
/// The stop condition to this configuration is valid for
20+
public var stopCondition: PollingStopCondition
21+
22+
/// How long to continue polling for. If nil, this will fall back to the next
23+
/// inner-most `PollingUntilStopsPassingConfigurationTrait.duration` value.
24+
/// If no non-nil values are found, then it will use 1 second.
4625
public var duration: Duration?
47-
/// The minimum amount of time to wait between polling attempts
26+
27+
/// The minimum amount of time to wait between polling attempts. If nil, this
28+
/// will fall back to earlier `PollingUntilStopsPassingConfigurationTrait.interval`
29+
/// values. If no non-nil values are found, then it will use 1 millisecond.
4830
public var interval: Duration?
4931

5032
public var isRecursive: Bool { true }
5133

52-
public init(duration: Duration?, interval: Duration?) {
34+
public init(
35+
stopCondition: PollingStopCondition,
36+
duration: Duration?,
37+
interval: Duration?
38+
) {
39+
self.stopCondition = stopCondition
5340
self.duration = duration
5441
self.interval = interval
5542
}
5643
}
5744

5845
@_spi(Experimental)
5946
@available(_clockAPI, *)
60-
extension Trait where Self == PollingUntilFirstPassConfigurationTrait {
61-
/// Specifies defaults for ``confirmPassesEventually`` in the test or suite.
62-
///
63-
/// - Parameters:
64-
/// - duration: The expected length of time to continue polling for.
65-
/// This value may not correspond to the wall-clock time that polling
66-
/// lasts for, especially on highly-loaded systems with a lot of tests
67-
/// running.
68-
/// if nil, polling will be attempted for approximately 1 second.
69-
/// `duration` must be greater than 0.
70-
/// - interval: The minimum amount of time to wait between polling
71-
/// attempts.
72-
/// If nil, polling will wait at least 1 millisecond between polling
73-
/// attempts.
74-
/// `interval` must be greater than 0.
75-
public static func pollingUntilFirstPassDefaults(
76-
until duration: Duration? = nil,
77-
pollingEvery interval: Duration? = nil
78-
) -> Self {
79-
PollingUntilFirstPassConfigurationTrait(
80-
duration: duration,
81-
interval: interval
82-
)
83-
}
84-
}
85-
86-
@_spi(Experimental)
87-
@available(_clockAPI, *)
88-
extension Trait where Self == PollingUntilStopsPassingConfigurationTrait {
89-
/// Specifies defaults for ``confirmPassesAlways`` in the test or suite.
47+
extension Trait where Self == PollingConfirmationConfigurationTrait {
48+
/// Specifies defaults for polling confirmations in the test or suite.
9049
///
9150
/// - Parameters:
51+
/// - stopCondition: The `PollingStopCondition` this trait applies to.
9252
/// - duration: The expected length of time to continue polling for.
9353
/// This value may not correspond to the wall-clock time that polling
9454
/// lasts for, especially on highly-loaded systems with a lot of tests
@@ -100,11 +60,13 @@ extension Trait where Self == PollingUntilStopsPassingConfigurationTrait {
10060
/// If nil, polling will wait at least 1 millisecond between polling
10161
/// attempts.
10262
/// `interval` must be greater than 0.
103-
public static func pollingUntilStopsPassingDefaults(
104-
until duration: Duration? = nil,
63+
public static func pollingConfirmationDefaults(
64+
until stopCondition: PollingStopCondition,
65+
within duration: Duration? = nil,
10566
pollingEvery interval: Duration? = nil
10667
) -> Self {
107-
PollingUntilStopsPassingConfigurationTrait(
68+
PollingConfirmationConfigurationTrait(
69+
stopCondition: stopCondition,
10870
duration: duration,
10971
interval: interval
11072
)

Tests/TestingTests/PollingTests.swift

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,39 @@ struct PollingConfirmationTests {
8585

8686
@Suite(
8787
"Configuration traits",
88-
.pollingUntilFirstPassDefaults(until: .milliseconds(100))
88+
.pollingConfirmationDefaults(
89+
until: .firstPass,
90+
within: .milliseconds(100)
91+
)
8992
)
9093
struct WithConfigurationTraits {
9194
let stop = PollingStopCondition.firstPass
9295

9396
@available(_clockAPI, *)
9497
@Test("When no test or callsite configuration provided, uses the suite configuration")
95-
func testUsesSuiteConfiguration() async throws {
98+
func testUsesSuiteConfiguration() async {
99+
let incrementor = Incrementor()
100+
var test = Test {
101+
try await confirmation(until: stop, pollingEvery: .milliseconds(1)) {
102+
await incrementor.increment() == 0
103+
}
104+
}
105+
test.traits = Test.current?.traits ?? []
106+
await runTest(test: test)
107+
let count = await incrementor.count
108+
#expect(count == 100)
109+
}
110+
111+
@available(_clockAPI, *)
112+
@Test(
113+
"Ignore trait configurations that don't match the stop condition",
114+
.pollingConfirmationDefaults(
115+
until: .stopsPassing,
116+
within: .milliseconds(
117+
500
118+
)
119+
)
120+
) func testIgnoresTraitsWithNonmatchingStopConditions() async {
96121
let incrementor = Incrementor()
97122
var test = Test {
98123
try await confirmation(until: stop, pollingEvery: .milliseconds(1)) {
@@ -108,7 +133,10 @@ struct PollingConfirmationTests {
108133
@available(_clockAPI, *)
109134
@Test(
110135
"When test configuration provided, uses the test configuration",
111-
.pollingUntilFirstPassDefaults(until: .milliseconds(10))
136+
.pollingConfirmationDefaults(
137+
until: .firstPass,
138+
within: .milliseconds(10)
139+
)
112140
)
113141
func testUsesTestConfigurationOverSuiteConfiguration() async {
114142
let incrementor = Incrementor()
@@ -126,7 +154,10 @@ struct PollingConfirmationTests {
126154
@available(_clockAPI, *)
127155
@Test(
128156
"When callsite configuration provided, uses that",
129-
.pollingUntilFirstPassDefaults(until: .milliseconds(10))
157+
.pollingConfirmationDefaults(
158+
until: .firstPass,
159+
within: .milliseconds(10)
160+
)
130161
)
131162
func testUsesCallsiteConfiguration() async {
132163
let incrementor = Incrementor()
@@ -263,7 +294,10 @@ struct PollingConfirmationTests {
263294

264295
@Suite(
265296
"Configuration traits",
266-
.pollingUntilStopsPassingDefaults(until: .milliseconds(100))
297+
.pollingConfirmationDefaults(
298+
until: .stopsPassing,
299+
within: .milliseconds(100)
300+
)
267301
)
268302
struct WithConfigurationTraits {
269303
let stop = PollingStopCondition.stopsPassing
@@ -281,10 +315,31 @@ struct PollingConfirmationTests {
281315
#expect(count == 100)
282316
}
283317

318+
@available(_clockAPI, *)
319+
@Test(
320+
"Ignore trait configurations that don't match the stop condition",
321+
.pollingConfirmationDefaults(
322+
until: .firstPass,
323+
within: .milliseconds(
324+
500
325+
)
326+
)
327+
) func testIgnoresTraitsWithNonmatchingStopConditions() async throws {
328+
let incrementor = Incrementor()
329+
try await confirmation(until: stop, pollingEvery: .milliseconds(1)) {
330+
await incrementor.increment() != 0
331+
}
332+
let count = await incrementor.count
333+
#expect(count == 100)
334+
}
335+
284336
@available(_clockAPI, *)
285337
@Test(
286338
"When test configuration porvided, uses the test configuration",
287-
.pollingUntilStopsPassingDefaults(until: .milliseconds(10))
339+
.pollingConfirmationDefaults(
340+
until: .stopsPassing,
341+
within: .milliseconds(10)
342+
)
288343
)
289344
func testUsesTestConfigurationOverSuiteConfiguration() async throws {
290345
let incrementor = Incrementor()
@@ -298,7 +353,10 @@ struct PollingConfirmationTests {
298353
@available(_clockAPI, *)
299354
@Test(
300355
"When callsite configuration provided, uses that",
301-
.pollingUntilStopsPassingDefaults(until: .milliseconds(10))
356+
.pollingConfirmationDefaults(
357+
until: .stopsPassing,
358+
within: .milliseconds(10)
359+
)
302360
)
303361
func testUsesCallsiteConfiguration() async throws {
304362
let incrementor = Incrementor()

0 commit comments

Comments
 (0)