Skip to content

Commit 5a0cc37

Browse files
authored
Reimplement polling expectations to make them more reliable under heavy system loads (#1199)
* Reimplement polling expectations to make them more reliable under heavy system loads This trades wall-clock time accuracy for reliability. It's nowhere near as important for a polling expectation to finish within the specified duration as it is to actually poll as expected. Basically, under heavy system loads, the actual gap between polling attempts increases. Under sufficiently heavy loads, with a sufficiently low timeout, we might never have polled in the first place. Instead, this calculate how many times we should poll by dividing the timeout by the interval. We then poll exactly that many times, sleeping for however long the polling interval is. If polling interval is ever 0, then we set it to 1 nanosecond. WaitUntil is also re-implemented in both async and sync, but that's because the older infrastructure for doing polling was replaced. Starting in Swift 6.3, we will emit a warning when WaitUntil is used in Swift Testing. This change also removes the ability to detect a blocked runloop, so Nimble will no longer detect for that. In my opinion, this is a worthwhile tradeoff, as blocked runloop was a source of test flakiness. * Bump xcode version, drop < swift 6.0 * Update workflows to specify xcode versions including swift 6 Fix build errors on older xcodes/linux * Fix swiftlint errors * Fix async waitUntil waiting until whenever the closure finishes before returning * Remove unnecessary CoreFoundation import * Update the test watch simulator * Check for more CF imports, and guard it where necessary * Mitigate AsyncAwaitTests.testWaitUntilDoesNotCompleteBeforeRunLoopIsWaiting flakes some more
1 parent cc945f7 commit 5a0cc37

23 files changed

+409
-788
lines changed

.github/workflows/carthage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ on:
1313
jobs:
1414
carthage:
1515
name: Carthage Build
16-
runs-on: macos-14
16+
runs-on: macos-15
1717
steps:
1818
- uses: actions/checkout@v4
1919
- uses: ruby/setup-ruby@v1

.github/workflows/ci-swiftpm.yml

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,24 @@ on:
1111
- "*"
1212

1313
jobs:
14-
swiftpm_darwin_ventura:
14+
swiftpm_darwin_sonoma:
1515
name: SwiftPM, Darwin, Xcode ${{ matrix.xcode }}
16-
runs-on: macos-13
16+
runs-on: macos-14
1717
strategy:
1818
matrix:
19-
xcode: ["14.3.1"]
19+
xcode: ["16.1"]
2020
env:
2121
DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app"
2222
steps:
2323
- uses: actions/checkout@v4
2424
- run: ./test swiftpm
2525

26-
swiftpm_darwin_sonoma:
26+
swiftpm_darwin_sequoia:
2727
name: SwiftPM, Darwin, Xcode ${{ matrix.xcode }}
28-
runs-on: macos-14
28+
runs-on: macos-15
2929
strategy:
3030
matrix:
31-
xcode: ["15.3", "16.1"]
31+
xcode: ["16.3"]
3232
env:
3333
DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app"
3434
steps:
@@ -41,10 +41,9 @@ jobs:
4141
strategy:
4242
matrix:
4343
container:
44-
- swift:5.7
45-
- swift:5.8
46-
- swift:5.9
4744
- swift:6.0
45+
- swift:6.1
46+
- swift:6.2
4847
# - swiftlang/swift:nightly
4948
fail-fast: false
5049
container: ${{ matrix.container }}
@@ -61,7 +60,7 @@ jobs:
6160
- name: Install Swift
6261
uses: compnerd/gha-setup-swift@main
6362
with:
64-
branch: swift-5.9-release
65-
tag: 5.9-RELEASE
63+
branch: swift-6.2-release
64+
tag: 6.2-RELEASE
6665
- name: Test Windows
6766
run: swift test -Xswiftc -suppress-warnings

.github/workflows/ci-xcode.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ on:
1111
- "*"
1212

1313
jobs:
14-
xcode_ventura:
14+
xcode_sonoma:
1515
name: Xcode ${{ matrix.xcode }} (Xcode Project)
16-
runs-on: macos-13
16+
runs-on: macos-14
1717
strategy:
1818
matrix:
19-
xcode: ["14.3.1"]
19+
xcode: ["16.1"]
2020
fail-fast: false
2121
env:
2222
DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app"
@@ -27,12 +27,12 @@ jobs:
2727
- run: ./test tvos
2828
- run: ./test watchos
2929

30-
xcode_sonoma:
30+
xcode_sequoia:
3131
name: Xcode ${{ matrix.xcode }} (Xcode Project)
32-
runs-on: macos-14
32+
runs-on: macos-15
3333
strategy:
3434
matrix:
35-
xcode: ["15.4", "16.1"]
35+
xcode: ["16.3"]
3636
fail-fast: false
3737
env:
3838
DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app"
@@ -45,10 +45,10 @@ jobs:
4545

4646
xcode_spm:
4747
name: Xcode ${{ matrix.xcode }} (Swift Package)
48-
runs-on: macos-14
48+
runs-on: macos-15
4949
strategy:
5050
matrix:
51-
xcode: ["16.1"]
51+
xcode: ["16.3"]
5252
fail-fast: false
5353
env:
5454
DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app"

.github/workflows/cocoapods.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ on:
1313
jobs:
1414
cocoapods:
1515
name: CocoaPods Lint
16-
runs-on: macos-14
16+
runs-on: macos-15
1717
steps:
1818
- uses: actions/checkout@v4
1919
- uses: ruby/setup-ruby@v1

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ on:
66

77
jobs:
88
carthage_archive:
9-
name: Darwin, Xcode 14.0
10-
runs-on: macos-14
9+
name: Darwin, Xcode 16.x
10+
runs-on: macos-15
1111
strategy:
1212
matrix:
13-
xcode: ["16.1"]
13+
xcode: ["16.3"]
1414
env:
1515
DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app"
1616
steps:

Nimble.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,10 @@
142142
898F28B025D9F4C30052B8D0 /* AlwaysFailMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */; };
143143
899441EF2902EE4B00C1FAF9 /* AsyncAwaitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899441EE2902EE4B00C1FAF9 /* AsyncAwaitTest.swift */; };
144144
899441F82902EF2500C1FAF9 /* DSL+AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899441F32902EF0900C1FAF9 /* DSL+AsyncAwait.swift */; };
145+
89A5126C2E74790600423EDF /* NimbleTimeIntervalTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89A5126B2E74790200423EDF /* NimbleTimeIntervalTest.swift */; };
145146
89B8C60F2C6476A6001F12D3 /* Negation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89B8C60E2C6476A6001F12D3 /* Negation.swift */; };
146147
89B8C6112C6478F2001F12D3 /* NegationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89B8C6102C6478F2001F12D3 /* NegationTest.swift */; };
147148
89C297CC2A911CDA002A143F /* AsyncTimerSequenceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C297CB2A911CDA002A143F /* AsyncTimerSequenceTest.swift */; };
148-
89C297CE2A92AB34002A143F /* AsyncPromiseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C297CD2A92AB34002A143F /* AsyncPromiseTest.swift */; };
149149
89D8AC852B3211C600410644 /* CwlCatchException in Frameworks */ = {isa = PBXBuildFile; productRef = 89D8AC842B3211C600410644 /* CwlCatchException */; };
150150
89D8AC872B3211EA00410644 /* CwlPosixPreconditionTesting in Frameworks */ = {isa = PBXBuildFile; platformFilters = (tvos, watchos, ); productRef = 89D8AC862B3211EA00410644 /* CwlPosixPreconditionTesting */; };
151151
89D8AC892B3211EA00410644 /* CwlPreconditionTesting in Frameworks */ = {isa = PBXBuildFile; platformFilters = (driverkit, ios, maccatalyst, macos, xros, ); productRef = 89D8AC882B3211EA00410644 /* CwlPreconditionTesting */; };
@@ -333,10 +333,10 @@
333333
898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlwaysFailMatcher.swift; sourceTree = "<group>"; };
334334
899441EE2902EE4B00C1FAF9 /* AsyncAwaitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAwaitTest.swift; sourceTree = "<group>"; };
335335
899441F32902EF0900C1FAF9 /* DSL+AsyncAwait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DSL+AsyncAwait.swift"; sourceTree = "<group>"; };
336+
89A5126B2E74790200423EDF /* NimbleTimeIntervalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbleTimeIntervalTest.swift; sourceTree = "<group>"; };
336337
89B8C60E2C6476A6001F12D3 /* Negation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Negation.swift; sourceTree = "<group>"; };
337338
89B8C6102C6478F2001F12D3 /* NegationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NegationTest.swift; sourceTree = "<group>"; };
338339
89C297CB2A911CDA002A143F /* AsyncTimerSequenceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncTimerSequenceTest.swift; sourceTree = "<group>"; };
339-
89C297CD2A92AB34002A143F /* AsyncPromiseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncPromiseTest.swift; sourceTree = "<group>"; };
340340
89EEF5A42A03293100988224 /* AsyncMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncMatcher.swift; sourceTree = "<group>"; };
341341
89EEF5B22A032C2500988224 /* AsyncPredicateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncPredicateTest.swift; sourceTree = "<group>"; };
342342
89EEF5BB2A06210D00988224 /* AsyncHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHelpers.swift; sourceTree = "<group>"; };
@@ -484,6 +484,7 @@
484484
children = (
485485
CDC157902511957100EAA480 /* DSLTest.swift */,
486486
89F5E096290C37B8001F9377 /* OnFailureThrowsTest.swift */,
487+
89A5126B2E74790200423EDF /* NimbleTimeIntervalTest.swift */,
487488
89F5E06C290765BB001F9377 /* PollingTest.swift */,
488489
892282892B2833B7002DA355 /* PollingTest+Require.swift */,
489490
CDBC39B82462EA7D00069677 /* PredicateTest.swift */,
@@ -492,7 +493,6 @@
492493
899441EE2902EE4B00C1FAF9 /* AsyncAwaitTest.swift */,
493494
8922828E2B283956002DA355 /* AsyncAwaitTest+Require.swift */,
494495
89C297CB2A911CDA002A143F /* AsyncTimerSequenceTest.swift */,
495-
89C297CD2A92AB34002A143F /* AsyncPromiseTest.swift */,
496496
965B0D0B1B62C06D0005AE66 /* UserDescriptionTest.swift */,
497497
895644DE2C1B71DE0006EC12 /* SwiftTestingSupportTest.swift */,
498498
6CAEDD091CAEA86F003F1584 /* LinuxSupport.swift */,
@@ -939,7 +939,6 @@
939939
8969624A2A5FAD5F00A7929D /* AsyncAllPassTest.swift in Sources */,
940940
1F4A56671A3B305F009E1637 /* ObjCAsyncTest.m in Sources */,
941941
1F925EFD195C186800ED456B /* BeginWithTest.swift in Sources */,
942-
89C297CE2A92AB34002A143F /* AsyncPromiseTest.swift in Sources */,
943942
89F5E06D290765BB001F9377 /* PollingTest.swift in Sources */,
944943
DDB4D5F119FE442800E9D9FE /* MatchTest.swift in Sources */,
945944
1F4A56741A3B3210009E1637 /* ObjCBeginWithTest.m in Sources */,
@@ -1004,6 +1003,7 @@
10041003
DD72EC651A93874A002F7651 /* AllPassTest.swift in Sources */,
10051004
1F4A569E1A3B3565009E1637 /* ObjCMatchTest.m in Sources */,
10061005
1F925EEA195C124400ED456B /* BeAnInstanceOfTest.swift in Sources */,
1006+
89A5126C2E74790600423EDF /* NimbleTimeIntervalTest.swift in Sources */,
10071007
8923E6102B47D08300F3961A /* MapTest.swift in Sources */,
10081008
29EA59641B551ED2002D767E /* ThrowErrorTest.swift in Sources */,
10091009
6CAEDD0B1CAEA86F003F1584 /* LinuxSupport.swift in Sources */,

Package.swift

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
// swift-tools-version:5.7
1+
// swift-tools-version:6.0
22
import PackageDescription
33

44
let package = Package(
55
name: "Nimble",
66
platforms: [
7-
.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)
7+
.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)
88
],
99
products: [
1010
.library(
@@ -19,7 +19,7 @@ let package = Package(
1919
),
2020
],
2121
dependencies: [
22-
.package(url: "https://github.com/mattgallagher/CwlPreconditionTesting.git", .upToNextMajor(from: "2.1.0")),
22+
.package(url: "https://github.com/mattgallagher/CwlPreconditionTesting.git", .upToNextMajor(from: "2.2.0")),
2323
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
2424
],
2525
targets: {
@@ -32,14 +32,11 @@ let package = Package(
3232
name: "Nimble",
3333
dependencies: [
3434
.product(name: "CwlPreconditionTesting", package: "CwlPreconditionTesting",
35-
condition: .when(platforms: [.macOS, .iOS, .macCatalyst])),
35+
condition: .when(platforms: [.macOS, .iOS, .macCatalyst, .visionOS])),
3636
.product(name: "CwlPosixPreconditionTesting", package: "CwlPreconditionTesting",
3737
condition: .when(platforms: [.tvOS, .watchOS]))
3838
],
39-
exclude: ["Info.plist"],
40-
resources: [
41-
.copy("PrivacyInfo.xcprivacy")
42-
]
39+
exclude: ["Info.plist"]
4340
),
4441
.target(
4542
name: "NimbleSharedTestHelpers",

Sources/Nimble/Adapters/NimbleEnvironment.swift

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,4 @@ internal class NimbleEnvironment: NSObject {
3636

3737
var suppressTVOSAssertionWarning: Bool = false
3838
var suppressWatchOSAssertionWarning: Bool = false
39-
#if !os(WASI)
40-
var awaiter: Awaiter
41-
#endif
42-
43-
override init() {
44-
#if !os(WASI)
45-
let timeoutQueue = DispatchQueue.global(qos: .userInitiated)
46-
awaiter = Awaiter(
47-
waitLock: AssertionWaitLock(),
48-
asyncQueue: .main,
49-
timeoutQueue: timeoutQueue
50-
)
51-
#endif
52-
53-
super.init()
54-
}
5539
}

Sources/Nimble/DSL+AsyncAwait.swift

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -134,31 +134,17 @@ private enum ErrorResult {
134134
private func throwableUntil(
135135
timeout: NimbleTimeInterval,
136136
sourceLocation: SourceLocation,
137-
action: @escaping (@escaping @Sendable () -> Void) async throws -> Void) async {
137+
action: @escaping @Sendable (@escaping @Sendable () -> Void) async throws -> Void) async {
138138
let leeway = timeout.divided
139139
let result = await performBlock(
140-
timeoutInterval: timeout,
140+
timeout: timeout,
141141
leeway: leeway,
142-
sourceLocation: sourceLocation) { @MainActor (done: @escaping (ErrorResult) -> Void) async throws -> Void in
143-
do {
144-
try await action {
145-
done(.none)
146-
}
147-
} catch let e {
148-
done(.error(e))
149-
}
150-
}
142+
sourceLocation: sourceLocation,
143+
closure: action
144+
)
151145

152146
switch result {
153147
case .incomplete: internalError("Reached .incomplete state for waitUntil(...).")
154-
case .blockedRunLoop:
155-
fail(
156-
blockedRunLoopErrorMessageFor("-waitUntil()", leeway: leeway),
157-
fileID: sourceLocation.fileID,
158-
file: sourceLocation.filePath,
159-
line: sourceLocation.line,
160-
column: sourceLocation.column
161-
)
162148
case .timedOut:
163149
fail(
164150
"Waited more than \(timeout.description)",
@@ -175,15 +161,7 @@ private func throwableUntil(
175161
line: sourceLocation.line,
176162
column: sourceLocation.column
177163
)
178-
case .completed(.error(let error)):
179-
fail(
180-
"Unexpected error thrown: \(error)",
181-
fileID: sourceLocation.fileID,
182-
file: sourceLocation.filePath,
183-
line: sourceLocation.line,
184-
column: sourceLocation.column
185-
)
186-
case .completed(.none): // success
164+
case .completed: // success
187165
break
188166
}
189167
}

0 commit comments

Comments
 (0)