Skip to content

Commit d4fc3e5

Browse files
author
Julian Lettner
committed
Duplicate select Swift Concurrency tests for TSan
I am putting these tests into a new `Sanitizers/tsan` subfolder so we can keep the same test file name (avoid need for `tsan-` prefix). This should increase the chance that changes to the tests make it to the TSan duplicate. I will move and rename existing sanitizer tests (including ASan) tests to keep consistency in a follow-up PR.
1 parent 03e0c4a commit d4fc3e5

File tree

4 files changed

+296
-0
lines changed

4 files changed

+296
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch -parse-as-library)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
7+
#if canImport(Darwin)
8+
import Darwin
9+
#elseif canImport(Glibc)
10+
import Glibc
11+
#endif
12+
13+
actor Counter {
14+
private var value = 0
15+
private let scratchBuffer: UnsafeMutableBufferPointer<Int>
16+
17+
init(maxCount: Int) {
18+
scratchBuffer = .allocate(capacity: maxCount)
19+
scratchBuffer.initialize(repeating: 0)
20+
}
21+
22+
func next() -> Int {
23+
let current = value
24+
25+
// Make sure we haven't produced this value before
26+
assert(scratchBuffer[current] == 0)
27+
scratchBuffer[current] = 1
28+
29+
value = value + 1
30+
return current
31+
}
32+
}
33+
34+
35+
func worker(identity: Int, counters: [Counter], numIterations: Int) async {
36+
for i in 0..<numIterations {
37+
let counterIndex = Int.random(in: 0 ..< counters.count)
38+
let counter = counters[counterIndex]
39+
let nextValue = await counter.next()
40+
print("Worker \(identity) calling counter \(counterIndex) produced \(nextValue)")
41+
}
42+
}
43+
44+
func runTest(numCounters: Int, numWorkers: Int, numIterations: Int) async {
45+
// Create counter actors.
46+
var counters: [Counter] = []
47+
for i in 0..<numCounters {
48+
counters.append(Counter(maxCount: numWorkers * numIterations))
49+
}
50+
51+
// Create a bunch of worker threads.
52+
var workers: [Task.Handle<Void, Error>] = []
53+
for i in 0..<numWorkers {
54+
workers.append(
55+
Task.runDetached { [counters] in
56+
usleep(UInt32.random(in: 0..<100) * 1000)
57+
await worker(identity: i, counters: counters, numIterations: numIterations)
58+
}
59+
)
60+
}
61+
62+
// Wait until all of the workers have finished.
63+
for worker in workers {
64+
try! await worker.get()
65+
}
66+
67+
print("DONE!")
68+
}
69+
70+
@main struct Main {
71+
static func main() async {
72+
await runTest(numCounters: 10, numWorkers: 100, numIterations: 1000)
73+
}
74+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch -parse-as-library)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
7+
#if canImport(Darwin)
8+
import Darwin
9+
#elseif canImport(Glibc)
10+
import Glibc
11+
#endif
12+
13+
func fib(_ n: Int) -> Int {
14+
var first = 0
15+
var second = 1
16+
for _ in 0..<n {
17+
let temp = first
18+
first = second
19+
second = temp + first
20+
}
21+
return first
22+
}
23+
24+
func asyncFib(_ n: Int) async -> Int {
25+
if n == 0 || n == 1 {
26+
return n
27+
}
28+
29+
async let first = await asyncFib(n-2)
30+
async let second = await asyncFib(n-1)
31+
32+
// Sleep a random amount of time waiting on the result producing a result.
33+
usleep(UInt32.random(in: 0..<100) * 1000)
34+
35+
let result = await first + second
36+
37+
// Sleep a random amount of time before producing a result.
38+
usleep(UInt32.random(in: 0..<100) * 1000)
39+
40+
return result
41+
}
42+
43+
func runFibonacci(_ n: Int) async {
44+
let result = await asyncFib(n)
45+
46+
print()
47+
print("Async fib = \(result), sequential fib = \(fib(n))")
48+
assert(result == fib(n))
49+
}
50+
51+
@main struct Main {
52+
static func main() async {
53+
await runFibonacci(10)
54+
}
55+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch -parse-as-library)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
7+
import Dispatch
8+
9+
#if canImport(Darwin)
10+
import Darwin
11+
#elseif canImport(Glibc)
12+
import Glibc
13+
#endif
14+
15+
enum HomeworkError: Error, Equatable {
16+
case dogAteIt(String)
17+
}
18+
19+
func formGreeting(name: String) async -> String {
20+
return "Hello \(name) from async world"
21+
}
22+
23+
func testSimple(
24+
name: String, dogName: String, shouldThrow: Bool, doSuspend: Bool
25+
) async {
26+
print("Testing name: \(name), dog: \(dogName), shouldThrow: \(shouldThrow) doSuspend: \(doSuspend)")
27+
28+
var completed = false
29+
30+
let taskHandle: Task.Handle<String, Error> = Task.runDetached {
31+
let greeting = await formGreeting(name: name)
32+
33+
// If the intent is to test suspending, wait a bit so the second task
34+
// can complete.
35+
if doSuspend {
36+
print("- Future sleeping")
37+
sleep(1)
38+
}
39+
40+
if (shouldThrow) {
41+
print("- Future throwing")
42+
throw HomeworkError.dogAteIt(dogName + " the dog")
43+
}
44+
45+
print("- Future returning normally")
46+
return greeting + "!"
47+
}
48+
49+
// If the intent is not to test suspending, wait a bit so the first task
50+
// can complete.
51+
if !doSuspend {
52+
print("+ Reader sleeping")
53+
sleep(1)
54+
}
55+
56+
do {
57+
print("+ Reader waiting for the result")
58+
let result = try await taskHandle.get()
59+
completed = true
60+
print("+ Normal return: \(result)")
61+
assert(result == "Hello \(name) from async world!")
62+
} catch HomeworkError.dogAteIt(let badDog) {
63+
completed = true
64+
print("+ Error return: HomeworkError.dogAteIt(\(badDog))")
65+
assert(badDog == dogName + " the dog")
66+
} catch {
67+
fatalError("Caught a different exception?")
68+
}
69+
70+
assert(completed)
71+
print("Finished test")
72+
}
73+
74+
75+
@main struct Main {
76+
static func main() async {
77+
await testSimple(name: "Ted", dogName: "Hazel", shouldThrow: false, doSuspend: false)
78+
await testSimple(name: "Ted", dogName: "Hazel", shouldThrow: true, doSuspend: false)
79+
await testSimple(name: "Ted", dogName: "Hazel", shouldThrow: false, doSuspend: true)
80+
await testSimple(name: "Ted", dogName: "Hazel", shouldThrow: true, doSuspend: true)
81+
82+
print("Done")
83+
}
84+
}

test/Sanitizers/tsan/mainactor.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// RUN: %target-run-simple-swift(-parse-as-library -Xfrontend -enable-experimental-concurrency %import-libdispatch) | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
7+
import Dispatch
8+
9+
/// @returns true iff the expected answer is actually the case, i.e., correct.
10+
/// If the current queue does not match expectations, this function may return
11+
/// false or just crash the program with non-zero exit code, depending on SDK.
12+
func checkIfMainQueue(expectedAnswer expected: Bool) -> Bool {
13+
if #available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *) {
14+
dispatchPrecondition(condition: expected ? .onQueue(DispatchQueue.main)
15+
: .notOnQueue(DispatchQueue.main))
16+
}
17+
return true
18+
}
19+
20+
actor A {
21+
func onCorrectQueue(_ count : Int) -> Int {
22+
if checkIfMainQueue(expectedAnswer: false) {
23+
print("on actor instance's queue")
24+
return count + 1
25+
}
26+
print("ERROR: not on actor instance's queue")
27+
return -10
28+
}
29+
}
30+
31+
@MainActor func checkAnotherFn(_ count : Int) -> Int {
32+
if checkIfMainQueue(expectedAnswer: true) {
33+
print("on main queue again!")
34+
return count + 1
35+
} else {
36+
print("ERROR: left the main queue?")
37+
return -10
38+
}
39+
}
40+
41+
@MainActor func enterMainActor(_ initialCount : Int) async -> Int {
42+
if checkIfMainQueue(expectedAnswer: true) {
43+
print("hello from main actor!")
44+
} else {
45+
print("ERROR: not on correct queue!")
46+
}
47+
48+
// try calling a function on another actor.
49+
let count = await A().onCorrectQueue(initialCount)
50+
51+
guard checkIfMainQueue(expectedAnswer: true) else {
52+
print("ERROR: did not switch back to main actor!")
53+
return -10
54+
}
55+
56+
return checkAnotherFn(count) + 1
57+
}
58+
59+
@concurrent func someFunc() async -> Int {
60+
// NOTE: the "return" counter is just to make sure we're properly returning values.
61+
// the expected number should be equal to the number of "plus-one" expressions.
62+
// since there are no loops or duplicate function calls
63+
return await enterMainActor(0) + 1
64+
}
65+
66+
67+
// CHECK: starting
68+
// CHECK-NOT: ERROR
69+
// CHECK: hello from main actor!
70+
// CHECK-NOT: ERROR
71+
// CHECK: on actor instance's queue
72+
// CHECK-NOT: ERROR
73+
// CHECK: on main queue again!
74+
// CHECK-NOT: ERROR
75+
// CHECK: finished with return counter = 4
76+
77+
@main struct RunIt {
78+
static func main() async {
79+
print("starting")
80+
let result = await someFunc()
81+
print("finished with return counter = \(result)")
82+
}
83+
}

0 commit comments

Comments
 (0)