Skip to content

Commit 19405e3

Browse files
authored
test: improve async tests correctness (#14)
* test: improve async tests correctness * wip: fix tests fail due to race conditions * wip: fix test project build fail on older Swift versions * wip: fix actor data race runtime warings * wip: fix build fail on older Swift versions * wip: fix build fail on older Swift versions * wip: increase task timeout checks * wip: improve test performance * wip: remove `CFGetRetainCount` usage for non-darwin * wip: fix build fail on older Swift version * Revert "wip: fix build fail on older Swift version" This reverts commit af058e9. * wip: fix build fail on older Swift version * wip: improve test timeouts * wip: fix deinit and future tests * wip: fix test timeouts and deinit tests * wip: fix timeout tests fail * wip: fix timeout tests fail * wip: add logging to `CancellationSource` * wip: fix timeout tests fail * wip: fix timeout tests fail * wip: parallelize cocoapods tests accross multple runners * wip: refactor countdown tests
1 parent 3ae2525 commit 19405e3

26 files changed

+1691
-2646
lines changed

.github/workflows/main.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ jobs:
6868
cocoapods-test:
6969
name: CocoaPods
7070
uses: SwiftyLab/ci/.github/workflows/cocoapods.yml@main
71+
strategy:
72+
matrix:
73+
platforms: ['macos tvos', 'ios']
74+
with:
75+
platforms: ${{ matrix.platforms }}
7176

7277
xcode-test:
7378
name: Xcode

AsyncObjects.xcodeproj/project.pbxproj

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
/* End PBXAggregateTarget section */
2222

2323
/* Begin PBXBuildFile section */
24-
286654AE55C95B05A5086634 /* AsyncObjects.docc in Sources */ = {isa = PBXBuildFile; fileRef = 64070662BE047028E995157C /* AsyncObjects.docc */; };
24+
4628E75B869ACA19AA7E5BDE /* AsyncObjects.docc in Sources */ = {isa = PBXBuildFile; fileRef = B2A9446673C71002FDE80C4B /* AsyncObjects.docc */; };
2525
OBJ_122 /* AsyncCountdownEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* AsyncCountdownEvent.swift */; };
2626
OBJ_123 /* AsyncEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* AsyncEvent.swift */; };
2727
OBJ_124 /* AsyncSemaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* AsyncSemaphore.swift */; };
@@ -58,7 +58,7 @@
5858
OBJ_173 /* TaskQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_49 /* TaskQueueTests.swift */; };
5959
OBJ_174 /* ThrowingFutureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_50 /* ThrowingFutureTests.swift */; };
6060
OBJ_175 /* TrackedContinuationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_51 /* TrackedContinuationTests.swift */; };
61-
OBJ_176 /* XCTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_52 /* XCTestCase.swift */; };
61+
OBJ_176 /* XCAsyncTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_52 /* XCAsyncTestCase.swift */; };
6262
OBJ_178 /* AsyncObjects.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = asyncobjects::AsyncObjects::Product /* AsyncObjects.framework */; };
6363
OBJ_179 /* OrderedCollections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = swift-collections::OrderedCollections::Product /* OrderedCollections.framework */; };
6464
OBJ_186 /* _HashTable+Bucket.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_59 /* _HashTable+Bucket.swift */; };
@@ -115,7 +115,7 @@
115115
/* End PBXBuildFile section */
116116

117117
/* Begin PBXFileReference section */
118-
64070662BE047028E995157C /* AsyncObjects.docc */ = {isa = PBXFileReference; includeInIndex = 1; path = AsyncObjects.docc; sourceTree = "<group>"; };
118+
B2A9446673C71002FDE80C4B /* AsyncObjects.docc */ = {isa = PBXFileReference; includeInIndex = 1; path = AsyncObjects.docc; sourceTree = "<group>"; };
119119
OBJ_100 /* OrderedSet+Partial SetAlgebra+Operations.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Operations.swift"; sourceTree = "<group>"; };
120120
OBJ_101 /* OrderedSet+Partial SetAlgebra+Predicates.swift */ = {isa = PBXFileReference; path = "OrderedSet+Partial SetAlgebra+Predicates.swift"; sourceTree = "<group>"; };
121121
OBJ_102 /* OrderedSet+RandomAccessCollection.swift */ = {isa = PBXFileReference; path = "OrderedSet+RandomAccessCollection.swift"; sourceTree = "<group>"; };
@@ -162,7 +162,7 @@
162162
OBJ_49 /* TaskQueueTests.swift */ = {isa = PBXFileReference; path = TaskQueueTests.swift; sourceTree = "<group>"; };
163163
OBJ_50 /* ThrowingFutureTests.swift */ = {isa = PBXFileReference; path = ThrowingFutureTests.swift; sourceTree = "<group>"; };
164164
OBJ_51 /* TrackedContinuationTests.swift */ = {isa = PBXFileReference; path = TrackedContinuationTests.swift; sourceTree = "<group>"; };
165-
OBJ_52 /* XCTestCase.swift */ = {isa = PBXFileReference; path = XCTestCase.swift; sourceTree = "<group>"; };
165+
OBJ_52 /* XCAsyncTestCase.swift */ = {isa = PBXFileReference; path = XCAsyncTestCase.swift; sourceTree = "<group>"; };
166166
OBJ_59 /* _HashTable+Bucket.swift */ = {isa = PBXFileReference; path = "_HashTable+Bucket.swift"; sourceTree = "<group>"; };
167167
OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
168168
OBJ_60 /* _HashTable+BucketIterator.swift */ = {isa = PBXFileReference; path = "_HashTable+BucketIterator.swift"; sourceTree = "<group>"; };
@@ -247,7 +247,7 @@
247247
OBJ_35 /* TaskOperation.swift */,
248248
OBJ_36 /* TaskQueue.swift */,
249249
OBJ_37 /* TaskTracker.swift */,
250-
64070662BE047028E995157C /* AsyncObjects.docc */,
250+
B2A9446673C71002FDE80C4B /* AsyncObjects.docc */,
251251
);
252252
name = AsyncObjects;
253253
path = Sources/AsyncObjects;
@@ -352,7 +352,7 @@
352352
OBJ_49 /* TaskQueueTests.swift */,
353353
OBJ_50 /* ThrowingFutureTests.swift */,
354354
OBJ_51 /* TrackedContinuationTests.swift */,
355-
OBJ_52 /* XCTestCase.swift */,
355+
OBJ_52 /* XCAsyncTestCase.swift */,
356356
);
357357
name = AsyncObjectsTests;
358358
path = Tests/AsyncObjectsTests;
@@ -648,7 +648,7 @@
648648
OBJ_141 /* TaskOperation.swift in Sources */,
649649
OBJ_142 /* TaskQueue.swift in Sources */,
650650
OBJ_143 /* TaskTracker.swift in Sources */,
651-
286654AE55C95B05A5086634 /* AsyncObjects.docc in Sources */,
651+
4628E75B869ACA19AA7E5BDE /* AsyncObjects.docc in Sources */,
652652
);
653653
};
654654
OBJ_152 /* Sources */ = {
@@ -672,7 +672,7 @@
672672
OBJ_173 /* TaskQueueTests.swift in Sources */,
673673
OBJ_174 /* ThrowingFutureTests.swift in Sources */,
674674
OBJ_175 /* TrackedContinuationTests.swift in Sources */,
675-
OBJ_176 /* XCTestCase.swift in Sources */,
675+
OBJ_176 /* XCAsyncTestCase.swift in Sources */,
676676
);
677677
};
678678
OBJ_185 /* Sources */ = {
@@ -824,7 +824,7 @@
824824
isa = XCBuildConfiguration;
825825
buildSettings = {
826826
LD = /usr/bin/true;
827-
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/ManifestAPI -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.0.sdk -package-description-version 5.6.0";
827+
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/ManifestAPI -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk -package-description-version 5.6.0";
828828
SWIFT_VERSION = 5.0;
829829
};
830830
name = Debug;
@@ -833,7 +833,7 @@
833833
isa = XCBuildConfiguration;
834834
buildSettings = {
835835
LD = /usr/bin/true;
836-
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/ManifestAPI -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.0.sdk -package-description-version 5.6.0";
836+
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/ManifestAPI -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk -package-description-version 5.6.0";
837837
SWIFT_VERSION = 5.0;
838838
};
839839
name = Release;
@@ -972,7 +972,7 @@
972972
isa = XCBuildConfiguration;
973973
buildSettings = {
974974
LD = /usr/bin/true;
975-
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/ManifestAPI -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.0.sdk -package-description-version 5.3.0";
975+
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/ManifestAPI -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk -package-description-version 5.3.0";
976976
SWIFT_VERSION = 5.0;
977977
};
978978
name = Debug;
@@ -981,7 +981,7 @@
981981
isa = XCBuildConfiguration;
982982
buildSettings = {
983983
LD = /usr/bin/true;
984-
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/ManifestAPI -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.0.sdk -package-description-version 5.3.0";
984+
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/ManifestAPI -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk -package-description-version 5.3.0";
985985
SWIFT_VERSION = 5.0;
986986
};
987987
name = Release;

Sources/AsyncObjects/AsyncCountdownEvent.swift

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollectionActor,
7171
/// Indicates whether countdown event current count is within ``limit``.
7272
///
7373
/// Queued tasks are resumed from suspension when event is set and until current count exceeds limit.
74-
public var isSet: Bool { currentCount >= 0 && currentCount <= limit }
74+
public var isSet: Bool { currentCount <= limit }
7575

7676
// MARK: Internal
7777

@@ -178,11 +178,11 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollectionActor,
178178
@inlinable
179179
internal func decrementCount(
180180
by number: UInt = 1,
181-
file: String, function: String, line: UInt
181+
file: String = #fileID,
182+
function: String = #function,
183+
line: UInt = #line
182184
) {
183-
defer {
184-
resumeContinuations(file: file, function: function, line: line)
185-
}
185+
defer { resume(file: file, function: function, line: line) }
186186

187187
guard currentCount > 0 else {
188188
log("Least count", file: file, function: function, line: line)
@@ -203,9 +203,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollectionActor,
203203
/// - line: The line resume originates from (there's usually no need to pass it
204204
/// explicitly as it defaults to `#line`).
205205
@inlinable
206-
internal func resumeContinuations(
207-
file: String, function: String, line: UInt
208-
) {
206+
internal func resume(file: String, function: String, line: UInt) {
209207
while !continuations.isEmpty && isSet {
210208
let (key, continuation) = continuations.removeFirst()
211209
resumeContinuation(continuation)
@@ -247,10 +245,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollectionActor,
247245
to count: UInt?,
248246
file: String, function: String, line: UInt
249247
) {
250-
defer {
251-
resumeContinuations(file: file, function: function, line: line)
252-
}
253-
248+
defer { resume(file: file, function: function, line: line) }
254249
let count = count ?? initialCount
255250
initialCount = count
256251
self.currentCount = count
@@ -352,12 +347,7 @@ public actor AsyncCountdownEvent: AsyncObject, ContinuableCollectionActor,
352347
function: String = #function,
353348
line: UInt = #line
354349
) {
355-
Task {
356-
await decrementCount(
357-
by: 1,
358-
file: file, function: function, line: line
359-
)
360-
}
350+
self.signal(repeat: 1, file: file, function: function, line: line)
361351
}
362352

363353
/// Registers multiple signals (decrements by provided count) with the countdown event.

Sources/AsyncObjects/AsyncSemaphore.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,11 @@ public actor AsyncSemaphore: AsyncObject, ContinuableCollectionActor,
147147
/// - line: The line signal originates from (there's usually no need to pass it
148148
/// explicitly as it defaults to `#line`).
149149
@inlinable
150-
internal func signalSemaphore(file: String, function: String, line: UInt) {
150+
internal func signalSemaphore(
151+
file: String = #fileID,
152+
function: String = #function,
153+
line: UInt = #line
154+
) {
151155
incrementCount()
152156
guard !continuations.isEmpty else { return }
153157
log("Signalling", file: file, function: function, line: line)

Sources/AsyncObjects/Base/AsyncObject+Clock.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -230,27 +230,28 @@ public func waitForAny<C: Clock>(
230230
/// pass it explicitly as it defaults to `#function`).
231231
/// - line: The line task passed from (there's usually no need to pass it
232232
/// explicitly as it defaults to `#line`).
233-
/// - task: The task to execute and wait for completion.
233+
/// - task: The action to execute and wait for completion result.
234234
///
235+
/// - Returns: The result of the action provided.
235236
/// - Throws: `CancellationError` if cancelled
236237
/// or `TimeoutError<C>` if timed out.
237238
@available(swift 5.7)
238239
@available(macOS 13, iOS 16, macCatalyst 16, tvOS 16, watchOS 9, *)
239240
@Sendable
240-
public func waitForTaskCompletion<C: Clock>(
241+
public func waitForTaskCompletion<C: Clock, T: Sendable>(
241242
until deadline: C.Instant,
242243
tolerance: C.Instant.Duration? = nil,
243244
clock: C,
244245
file: String = #fileID,
245246
function: String = #function,
246247
line: UInt = #line,
247-
_ task: @escaping @Sendable () async throws -> Void
248-
) async throws {
249-
try await withThrowingTaskGroup(of: Void.self) { group in
248+
_ task: @escaping @Sendable () async throws -> T
249+
) async throws -> T {
250+
try await withThrowingTaskGroup(of: T.self) { group in
250251
await GlobalContinuation<Void, Never>.with { continuation in
251252
group.addTask {
252253
continuation.resume()
253-
try await task()
254+
return try await task()
254255
}
255256
}
256257
group.addTask {
@@ -263,7 +264,10 @@ public func waitForTaskCompletion<C: Clock>(
263264
throw TimeoutError<C>(until: deadline, tolerance: tolerance)
264265
}
265266
defer { group.cancelAll() }
266-
try await group.next()
267+
guard
268+
let result = try await group.next()
269+
else { throw CancellationError() }
270+
return result
267271
}
268272
}
269273

Sources/AsyncObjects/Base/AsyncObject+Duration.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,23 +187,24 @@ public func waitForAny(
187187
/// pass it explicitly as it defaults to `#function`).
188188
/// - line: The line task passed from (there's usually no need to pass it
189189
/// explicitly as it defaults to `#line`).
190-
/// - task: The task to execute and wait for completion.
190+
/// - task: The action to execute and wait for completion result.
191191
///
192+
/// - Returns: The result of the action provided.
192193
/// - Throws: `CancellationError` if cancelled
193194
/// or `DurationTimeoutError` if timed out.
194195
@Sendable
195-
public func waitForTaskCompletion(
196+
public func waitForTaskCompletion<T: Sendable>(
196197
withTimeoutInNanoseconds timeout: UInt64,
197198
file: String = #fileID,
198199
function: String = #function,
199200
line: UInt = #line,
200-
_ task: @escaping @Sendable () async throws -> Void
201-
) async throws {
202-
try await withThrowingTaskGroup(of: Void.self) { group in
201+
_ task: @escaping @Sendable () async throws -> T
202+
) async throws -> T {
203+
return try await withThrowingTaskGroup(of: T.self) { group in
203204
await GlobalContinuation<Void, Never>.with { continuation in
204205
group.addTask {
205206
continuation.resume()
206-
try await task()
207+
return try await task()
207208
}
208209
}
209210
group.addTask {
@@ -212,7 +213,10 @@ public func waitForTaskCompletion(
212213
throw DurationTimeoutError(for: timeout, tolerance: 1_000)
213214
}
214215
defer { group.cancelAll() }
215-
try await group.next()
216+
guard
217+
let result = try await group.next()
218+
else { throw CancellationError() }
219+
return result
216220
}
217221
}
218222

0 commit comments

Comments
 (0)