Skip to content

Commit 40e9bb6

Browse files
authored
Adopt Swift Testing (#41)
* Adopt Swift Testing * Allow for slow CI machines * Get code coverage back up
1 parent b51448d commit 40e9bb6

File tree

9 files changed

+403
-150
lines changed

9 files changed

+403
-150
lines changed

Package.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,23 @@ let package = Package(
1616
products: [
1717
.library(
1818
name: "AsyncQueue",
19-
targets: ["AsyncQueue"]),
19+
targets: ["AsyncQueue"]
20+
),
2021
],
2122
targets: [
2223
.target(
2324
name: "AsyncQueue",
2425
dependencies: [],
2526
swiftSettings: [
2627
.swiftLanguageMode(.v6),
27-
]),
28+
]
29+
),
2830
.testTarget(
2931
name: "AsyncQueueTests",
3032
dependencies: ["AsyncQueue"],
3133
swiftSettings: [
3234
.swiftLanguageMode(.v6),
33-
]),
35+
]
36+
),
3437
]
3538
)

Tests/AsyncQueueTests/ActorQueueTests.swift

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,33 @@
2020
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
// SOFTWARE.
2222

23-
import XCTest
23+
import Foundation
24+
import Testing
2425

2526
@testable import AsyncQueue
2627

27-
final class ActorQueueTests: XCTestCase {
28+
struct ActorQueueTests {
2829

29-
// MARK: XCTestCase
30-
31-
override func setUp() async throws {
32-
try await super.setUp()
30+
// MARK: Initialization
3331

32+
init() {
3433
systemUnderTest = ActorQueue<Counter>()
3534
counter = Counter()
3635
systemUnderTest.adoptExecutionContext(of: counter)
3736
}
3837

3938
// MARK: Behavior Tests
4039

41-
func test_adoptExecutionContext_doesNotRetainActor() {
40+
@Test func test_adoptExecutionContext_doesNotRetainActor() {
4241
let systemUnderTest = ActorQueue<Counter>()
4342
var counter: Counter? = Counter()
4443
weak var weakCounter = counter
4544
systemUnderTest.adoptExecutionContext(of: counter!)
4645
counter = nil
47-
XCTAssertNil(weakCounter)
46+
#expect(weakCounter == nil)
4847
}
4948

50-
func test_enqueue_retainsAdoptedActorUntilEnqueuedTasksComplete() async {
49+
@Test func test_enqueue_retainsAdoptedActorUntilEnqueuedTasksComplete() async {
5150
let systemUnderTest = ActorQueue<Counter>()
5251
var counter: Counter? = Counter()
5352
weak var weakCounter = counter
@@ -59,27 +58,27 @@ final class ActorQueueTests: XCTestCase {
5958
}
6059

6160
counter = nil
62-
XCTAssertNotNil(weakCounter)
61+
#expect(weakCounter != nil)
6362
await semaphore.signal()
6463
}
6564

66-
func test_enqueue_taskParameterIsAdoptedActor() async {
65+
@Test func test_enqueue_taskParameterIsAdoptedActor() async {
6766
let semaphore = Semaphore()
6867
systemUnderTest.enqueue { [storedCounter = counter] counter in
69-
XCTAssertTrue(counter === storedCounter)
68+
#expect(counter === storedCounter)
7069
await semaphore.signal()
7170
}
7271

7372
await semaphore.wait()
7473
}
7574

76-
func test_enqueueAndWait_taskParameterIsAdoptedActor() async {
75+
@Test func test_enqueueAndWait_taskParameterIsAdoptedActor() async {
7776
await systemUnderTest.enqueueAndWait { [storedCounter = counter] counter in
78-
XCTAssertTrue(counter === storedCounter)
77+
#expect(counter === storedCounter)
7978
}
8079
}
8180

82-
func test_enqueue_sendsEventsInOrder() async {
81+
@Test func test_enqueue_sendsEventsInOrder() async {
8382
for iteration in 1...1_000 {
8483
systemUnderTest.enqueue { counter in
8584
counter.incrementAndExpectCount(equals: iteration)
@@ -88,7 +87,7 @@ final class ActorQueueTests: XCTestCase {
8887
await systemUnderTest.enqueueAndWait { _ in /* Drain the queue */ }
8988
}
9089

91-
func test_enqueue_startsExecutionOfNextTaskAfterSuspension() async {
90+
@Test func test_enqueue_startsExecutionOfNextTaskAfterSuspension() async {
9291
let systemUnderTest = ActorQueue<Semaphore>()
9392
let semaphore = Semaphore()
9493
systemUnderTest.adoptExecutionContext(of: semaphore)
@@ -105,7 +104,7 @@ final class ActorQueueTests: XCTestCase {
105104
await systemUnderTest.enqueueAndWait { _ in /* Drain the queue */ }
106105
}
107106

108-
func test_enqueueAndWait_allowsReentrancy() async {
107+
@Test func test_enqueueAndWait_allowsReentrancy() async {
109108
await systemUnderTest.enqueueAndWait { [systemUnderTest] counter in
110109
await systemUnderTest.enqueueAndWait { counter in
111110
counter.incrementAndExpectCount(equals: 1)
@@ -114,11 +113,11 @@ final class ActorQueueTests: XCTestCase {
114113
}
115114
}
116115

117-
func test_enqueue_executesEnqueuedTasksAfterReceiverIsDeallocated() async {
116+
@Test func test_enqueue_executesEnqueuedTasksAfterReceiverIsDeallocated() async {
118117
var systemUnderTest: ActorQueue<Counter>? = ActorQueue()
119118
systemUnderTest?.adoptExecutionContext(of: counter)
120119

121-
let expectation = self.expectation(description: #function)
120+
let expectation = Expectation()
122121
let semaphore = Semaphore()
123122
systemUnderTest?.enqueue { counter in
124123
// Make the task wait.
@@ -129,14 +128,13 @@ final class ActorQueueTests: XCTestCase {
129128
weak var queue = systemUnderTest
130129
// Nil out our reference to the queue to show that the enqueued tasks will still complete
131130
systemUnderTest = nil
132-
XCTAssertNil(queue)
131+
#expect(queue == nil)
133132
// Signal the semaphore to unlock the enqueued tasks.
134133
await semaphore.signal()
135-
136-
await fulfillment(of: [expectation], timeout: 1.0)
134+
await expectation.fulfillment(withinSeconds: 10)
137135
}
138136

139-
func test_enqueue_doesNotRetainTaskAfterExecution() async {
137+
@Test func test_enqueue_doesNotRetainTaskAfterExecution() async {
140138
final class Reference: Sendable {}
141139
final class ReferenceHolder: @unchecked Sendable {
142140
init() {
@@ -156,7 +154,7 @@ final class ActorQueueTests: XCTestCase {
156154
let systemUnderTest = ActorQueue<Semaphore>()
157155
systemUnderTest.adoptExecutionContext(of: syncSemaphore)
158156

159-
let expectation = self.expectation(description: #function)
157+
let expectation = Expectation()
160158
systemUnderTest.enqueue { [reference = referenceHolder.reference] syncSemaphore in
161159
// Now that we've started the task and captured the reference, release the synchronous code.
162160
syncSemaphore.signal()
@@ -173,16 +171,16 @@ final class ActorQueueTests: XCTestCase {
173171
// Wait for the asynchronous task to start.
174172
await syncSemaphore.wait()
175173
referenceHolder.clearReference()
176-
XCTAssertNotNil(referenceHolder.weakReference)
174+
#expect(referenceHolder.weakReference != nil)
177175
// Allow the enqueued task to complete.
178176
await asyncSemaphore.signal()
179177
// Make sure the task has completed.
180-
await fulfillment(of: [expectation], timeout: 1.0)
178+
await expectation.fulfillment(withinSeconds: 10)
181179

182-
XCTAssertNil(referenceHolder.weakReference)
180+
#expect(referenceHolder.weakReference == nil)
183181
}
184182

185-
func test_enqueueAndWait_sendsEventsInOrder() async {
183+
@Test func test_enqueueAndWait_sendsEventsInOrder() async {
186184
for iteration in 1...1_000 {
187185
systemUnderTest.enqueue { counter in
188186
counter.incrementAndExpectCount(equals: iteration)
@@ -194,32 +192,32 @@ final class ActorQueueTests: XCTestCase {
194192
}
195193

196194
await systemUnderTest.enqueueAndWait { counter in
197-
XCTAssertEqual(counter.count, iteration)
195+
#expect(counter.count == iteration)
198196
}
199197
}
200198
await systemUnderTest.enqueueAndWait { _ in /* Drain the queue */ }
201199
}
202200

203-
func test_enqueueAndWait_canReturn() async {
201+
@Test func test_enqueueAndWait_canReturn() async {
204202
let expectedValue = UUID()
205203
let returnedValue = await systemUnderTest.enqueueAndWait { _ in expectedValue }
206-
XCTAssertEqual(expectedValue, returnedValue)
204+
#expect(expectedValue == returnedValue)
207205
}
208206

209-
func test_enqueueAndWait_canThrow() async {
207+
@Test func test_enqueueAndWait_canThrow() async {
210208
struct TestError: Error, Equatable {
211209
private let identifier = UUID()
212210
}
213211
let expectedError = TestError()
214212
do {
215213
try await systemUnderTest.enqueueAndWait { _ in throw expectedError }
216214
} catch {
217-
XCTAssertEqual(error as? TestError, expectedError)
215+
#expect(error as? TestError == expectedError)
218216
}
219217
}
220218

221219
// MARK: Private
222220

223-
private var systemUnderTest = ActorQueue<Counter>()
224-
private var counter = Counter()
221+
private let systemUnderTest: ActorQueue<Counter>
222+
private let counter: Counter
225223
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// MIT License
2+
//
3+
// Copyright (c) 2024 Dan Federman
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
import Testing
24+
25+
struct ExpectationTests {
26+
27+
// MARK: Behavior Tests
28+
29+
@Test func test_fulfill_triggersExpectation() async {
30+
await confirmation { confirmation in
31+
let systemUnderTest = Expectation(
32+
expectedCount: 1,
33+
expect: { expectation, _, _ in
34+
#expect(expectation)
35+
confirmation()
36+
}
37+
)
38+
await systemUnderTest.fulfill().value
39+
}
40+
}
41+
42+
@Test func test_fulfill_triggersExpectationOnceWhenCalledTwiceAndExpectedCountIsTwo() async {
43+
await confirmation { confirmation in
44+
let systemUnderTest = Expectation(
45+
expectedCount: 2,
46+
expect: { expectation, _, _ in
47+
#expect(expectation)
48+
confirmation()
49+
}
50+
)
51+
await systemUnderTest.fulfill().value
52+
await systemUnderTest.fulfill().value
53+
}
54+
}
55+
56+
@Test func test_fulfill_triggersExpectationWhenExpectedCountIsZero() async {
57+
await confirmation { confirmation in
58+
let systemUnderTest = Expectation(
59+
expectedCount: 0,
60+
expect: { expectation, _, _ in
61+
#expect(!expectation)
62+
confirmation()
63+
}
64+
)
65+
await systemUnderTest.fulfill().value
66+
}
67+
}
68+
69+
@Test func test_fulfillment_doesNotWaitIfAlreadyFulfilled() async {
70+
let systemUnderTest = Expectation(expectedCount: 0)
71+
await systemUnderTest.fulfillment(withinSeconds: 10)
72+
}
73+
74+
@MainActor // Global actor ensures Task ordering.
75+
@Test func test_fulfillment_waitsForFulfillment() async {
76+
let systemUnderTest = Expectation(expectedCount: 1)
77+
var hasFulfilled = false
78+
let wait = Task {
79+
await systemUnderTest.fulfillment(withinSeconds: 10)
80+
#expect(hasFulfilled)
81+
}
82+
Task {
83+
systemUnderTest.fulfill()
84+
hasFulfilled = true
85+
}
86+
await wait.value
87+
}
88+
89+
@Test func test_fulfillment_triggersFalseExpectationWhenItTimesOut() async {
90+
await confirmation { confirmation in
91+
let systemUnderTest = Expectation(
92+
expectedCount: 1,
93+
expect: { expectation, _, _ in
94+
#expect(!expectation)
95+
confirmation()
96+
}
97+
)
98+
await systemUnderTest.fulfillment(withinSeconds: 0)
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)