@@ -26,8 +26,202 @@ import XCTest
2626
2727final class AsyncQueueTests : XCTestCase {
2828
29- func test_example( ) {
30- _ = AsyncQueue ( )
29+ // MARK: XCTestCase
30+
31+ override func setUp( ) async throws {
32+ try await super. setUp ( )
33+
34+ systemUnderTest = AsyncQueue ( )
35+ }
36+
37+ // MARK: Behavior Tests
38+
39+ func test_async_sendsEventsInOrder( ) async {
40+ let counter = Counter ( )
41+ for iteration in 1 ... 1_000 {
42+ systemUnderTest. async {
43+ await counter. incrementAndExpectCount ( equals: iteration)
44+ }
45+ }
46+ await systemUnderTest. await { /* Drain the queue */ }
47+ }
48+
49+ func test_async_executesAsyncBlocksAtomically( ) async {
50+ let semaphore = Semaphore ( )
51+ for _ in 1 ... 1_000 {
52+ systemUnderTest. async {
53+ let isWaiting = await semaphore. isWaiting
54+ // This test will fail occasionally if we aren't executing atomically.
55+ // You can prove this to yourself by replacing `systemUnderTest.async` above with `Task`.
56+ XCTAssertFalse ( isWaiting)
57+ // Signal the semaphore before or after we wait – let the scheduler decide.
58+ Task {
59+ await semaphore. signal ( )
60+ }
61+ // Wait for the concurrent task to complete.
62+ await semaphore. wait ( )
63+ }
64+ }
65+ await systemUnderTest. await { /* Drain the queue */ }
66+ }
67+
68+ func test_async_isNotReentrant( ) async {
69+ let counter = Counter ( )
70+ await systemUnderTest. await { [ systemUnderTest] in
71+ systemUnderTest. async {
72+ await counter. incrementAndExpectCount ( equals: 2 )
73+ }
74+ await counter. incrementAndExpectCount ( equals: 1 )
75+ systemUnderTest. async {
76+ await counter. incrementAndExpectCount ( equals: 3 )
77+ }
78+ }
79+ await systemUnderTest. await { /* Drain the queue */ }
80+ }
81+
82+ func test_async_retainsReceiverUntilFlushed( ) async {
83+ var systemUnderTest : AsyncQueue ? = AsyncQueue ( )
84+ let counter = Counter ( )
85+ let expectation = self . expectation ( description: #function)
86+ let semaphore = Semaphore ( )
87+ systemUnderTest? . async {
88+ // Make the queue wait.
89+ await semaphore. wait ( )
90+ await counter. incrementAndExpectCount ( equals: 1 )
91+ }
92+ systemUnderTest? . async {
93+ // This async task should not execute until the semaphore is released.
94+ await counter. incrementAndExpectCount ( equals: 2 )
95+ expectation. fulfill ( )
96+ }
97+ // Nil out our reference to the queue to show that the enqueued tasks will still complete
98+ systemUnderTest = nil
99+ // Signal the semaphore to unlock the remaining enqueued tasks.
100+ await semaphore. signal ( )
101+
102+ await waitForExpectations ( timeout: 1.0 )
103+ }
104+
105+ func test_async_doesNotRetainTaskAfterExecution( ) async {
106+ final class Reference : Sendable { }
107+ final class ReferenceHolder : @unchecked Sendable {
108+ var reference : Reference ? = Reference ( )
109+ }
110+ let referenceHolder = ReferenceHolder ( )
111+ weak var weakReference = referenceHolder. reference
112+ let asyncSemaphore = Semaphore ( )
113+ let syncSemaphore = Semaphore ( )
114+ systemUnderTest. async { [ reference = referenceHolder. reference] in
115+ // Now that we've started the task and captured the reference, release the synchronous code.
116+ await syncSemaphore. signal ( )
117+ // Wait for the synchronous setup to complete and the reference to be nil'd out.
118+ await asyncSemaphore. wait ( )
119+ // Retain the unsafe counter until the task is completed.
120+ _ = reference
121+ }
122+ // Wait for the asynchronous task to start.
123+ await syncSemaphore. wait ( )
124+ referenceHolder. reference = nil
125+ XCTAssertNotNil ( weakReference)
126+ // Allow the enqueued task to complete.
127+ await asyncSemaphore. signal ( )
128+ // Make sure the task has completed.
129+ await systemUnderTest. await { /* Drain the queue */ }
130+ XCTAssertNil ( weakReference)
131+ }
132+
133+ func test_await_sendsEventsInOrder( ) async {
134+ let counter = Counter ( )
135+ for iteration in 1 ... 1_000 {
136+ systemUnderTest. async {
137+ await counter. incrementAndExpectCount ( equals: iteration)
138+ }
139+
140+ guard iteration % 25 == 0 else {
141+ // Keep sending async events to the queue.
142+ continue
143+ }
144+
145+ await systemUnderTest. await {
146+ let count = await counter. count
147+ XCTAssertEqual ( count, iteration)
148+ }
149+ }
150+ await systemUnderTest. await { /* Drain the queue */ }
151+ }
152+
153+ func test_await_canReturn( ) async {
154+ let expectedValue = UUID ( )
155+ let returnedValue = await systemUnderTest. await { expectedValue }
156+ XCTAssertEqual ( expectedValue, returnedValue)
157+ }
158+
159+ func test_await_canThrow( ) async {
160+ struct TestError : Error , Equatable {
161+ private let identifier = UUID ( )
162+ }
163+ let expectedError = TestError ( )
164+ do {
165+ try await systemUnderTest. await { throw expectedError }
166+ } catch {
167+ XCTAssertEqual ( error as? TestError , expectedError)
168+ }
169+ }
170+
171+ // MARK: Private
172+
173+ private var systemUnderTest = AsyncQueue ( )
174+
175+ // MARK: - Counter
176+
177+ private actor Counter {
178+ func incrementAndExpectCount( equals expectedCount: Int ) {
179+ increment ( )
180+ XCTAssertEqual ( expectedCount, count)
181+ }
182+
183+ func increment( ) {
184+ count += 1
185+ }
186+
187+ var count = 0
31188 }
32189
190+ // MARK: - Semaphore
191+
192+ private actor Semaphore {
193+
194+ func wait( ) async {
195+ count -= 1
196+ guard count < 0 else {
197+ // We don't need to wait because count is greater than or equal to zero.
198+ return
199+ }
200+
201+ await withCheckedContinuation { continuation in
202+ continuations. append ( continuation)
203+ }
204+ }
205+
206+ func signal( ) {
207+ count += 1
208+ guard !isWaiting else {
209+ // Continue waiting.
210+ return
211+ }
212+
213+ for continuation in continuations {
214+ continuation. resume ( )
215+ }
216+
217+ continuations. removeAll ( )
218+ }
219+
220+ var isWaiting : Bool {
221+ count < 0
222+ }
223+
224+ private var continuations = [ CheckedContinuation < Void , Never > ] ( )
225+ private var count = 0
226+ }
33227}
0 commit comments