Skip to content

Commit 40b798d

Browse files
committed
chore: Add some basic testing.
1 parent 64036b9 commit 40b798d

File tree

4 files changed

+241
-0
lines changed

4 files changed

+241
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Testing
14+
@testable import DispatchAsync
15+
16+
@Test
17+
func dispatchGroupOrderCleanliness() async throws {
18+
// Repeating this 100 times to help rule out
19+
// edge cases that only show up some of the time
20+
for index in 0 ..< 100 {
21+
Task {
22+
actor Result {
23+
private(set) var value = ""
24+
25+
func append(value: String) {
26+
self.value.append(value)
27+
}
28+
}
29+
30+
let result = Result()
31+
32+
let group = DispatchGroup()
33+
await result.append(value: "|🔵\(index)")
34+
35+
group.enter()
36+
Task {
37+
await result.append(value: "🟣/")
38+
group.leave()
39+
}
40+
41+
group.enter()
42+
Task {
43+
await result.append(value: "🟣^")
44+
group.leave()
45+
}
46+
47+
group.enter()
48+
Task {
49+
await result.append(value: "🟣\\")
50+
group.leave()
51+
}
52+
53+
await withCheckedContinuation { continuation in
54+
group.notify(queue: .main) {
55+
Task {
56+
await result.append(value: "🟢\(index)=")
57+
continuation.resume()
58+
}
59+
}
60+
}
61+
62+
let finalValue = await result.value
63+
64+
/// NOTE: If you need to visually debug issues, you can uncomment
65+
/// the following to watch a visual representation of the group ordering.
66+
///
67+
/// In general, you'll see something like the following printed over and over
68+
/// to the console:
69+
///
70+
/// ```
71+
/// |🔵42🟣/🟣^🟣\🟢42=
72+
/// ```
73+
///
74+
/// What you should observe:
75+
///
76+
/// - The index number be the same at the beginning and end of each line, and it
77+
/// should always increment by one.
78+
/// - The 🔵 should always be first, and the 🟢 should always be last for each line.
79+
/// - There should always be 3 🟣's in between the 🔵 and 🟢.
80+
/// - The ordering of the 🟣 can be random, and that is fine.
81+
///
82+
/// For example, for of the following are valid outputs:
83+
///
84+
/// ```
85+
/// // GOOD
86+
/// |🔵42🟣/🟣^🟣\🟢42=
87+
/// ```
88+
///
89+
/// ```
90+
/// // GOOD
91+
/// |🔵42🟣/🟣\🟣^🟢42=
92+
/// ```
93+
///
94+
/// But the following would not be valid:
95+
///
96+
/// ```
97+
/// // BAD!
98+
/// |🔵43🟣/🟣^🟣\🟢43=
99+
/// |🔵42🟣/🟣^🟣\🟢42=
100+
/// |🔵44🟣/🟣^🟣\🟢44=
101+
/// ```
102+
///
103+
/// ```
104+
/// // BAD!
105+
/// |🔵42🟣/🟣^🟢42🟣\=
106+
/// ```
107+
///
108+
109+
// Uncomment to use troubleshooting method above:
110+
// print(finalValue)
111+
112+
#expect(finalValue.prefix(1) == "|")
113+
#expect(finalValue.count { $0 == "🟣"} == 3)
114+
#expect(finalValue.count { $0 == "🟢"} == 1)
115+
#expect(finalValue.lastIndex(of: "🟣")! < finalValue.firstIndex(of: "🟢")!)
116+
#expect(finalValue.suffix(1) == "=")
117+
}
118+
}
119+
}
120+
121+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Testing
14+
@testable import DispatchAsync
15+
16+
#if !os(WASI)
17+
import class Foundation.Thread
18+
#endif
19+
20+
@Test
21+
func testBasicDispatchQueueMain() async throws {
22+
let asyncValue = await withCheckedContinuation { continuation in
23+
DispatchQueue.main.async {
24+
// Main queue should be on main thread.
25+
#if !os(WASI)
26+
#expect(Thread.isMainThread)
27+
#endif
28+
continuation.resume(returning: true)
29+
}
30+
}
31+
#expect(asyncValue == true)
32+
}
33+
34+
@Test
35+
func testBasicDispatchQueueGlobal() async throws {
36+
let asyncValue = await withCheckedContinuation { continuation in
37+
DispatchQueue.global().async {
38+
// Global queue should NOT be on main thread.
39+
#if !os(WASI)
40+
#expect(!Thread.isMainThread)
41+
#endif
42+
continuation.resume(returning: true)
43+
}
44+
}
45+
#expect(asyncValue == true)
46+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Testing
14+
@testable import DispatchAsync
15+
16+
nonisolated(unsafe) private var sharedPoolCompletionCount = 0
17+
18+
@Test func basicDispatchSemaphoreTest() async throws {
19+
let totalConcurrentPools = 10
20+
21+
let semaphore = DispatchSemaphore(value: 1)
22+
23+
await withTaskGroup(of: Void.self) { group in
24+
for _ in 0 ..< totalConcurrentPools {
25+
group.addTask {
26+
// Wait for any other pools currently holding the semaphore
27+
await semaphore.wait()
28+
29+
// Only one task should mutate counter at a time
30+
//
31+
// If there are issues with the semaphore, then
32+
// we would expect to grab incorrect values here occasionally,
33+
// which would result in an incorrect final completion count.
34+
//
35+
let existingPoolCompletionCount = sharedPoolCompletionCount
36+
37+
// Add artificial delay to amplify race conditions
38+
// Pools started shortly after this "semaphore-locked"
39+
// pool starts will run before this line, unless
40+
// this pool contains a valid lock.
41+
try? await Task.sleep(nanoseconds: 100)
42+
43+
sharedPoolCompletionCount = existingPoolCompletionCount + 1
44+
45+
// When we exit this flow, release our hold on the semaphore
46+
await semaphore.signal()
47+
}
48+
}
49+
}
50+
51+
// After all tasks are done, counter should be 10
52+
#expect(sharedPoolCompletionCount == totalConcurrentPools)
53+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Testing
14+
@testable import DispatchAsync
15+
16+
@Test
17+
func testDispatchTimeContinousClockBasics() async throws {
18+
let a = DispatchTime.now().uptimeNanoseconds
19+
let b = DispatchTime.now().uptimeNanoseconds
20+
#expect(a <= b)
21+
}

0 commit comments

Comments
 (0)