Skip to content

Commit 1a1a266

Browse files
committed
Add test that crashes due to read and writes from multiple threads
1 parent c2aa0af commit 1a1a266

File tree

7 files changed

+50
-9
lines changed

7 files changed

+50
-9
lines changed

Sources/MockingKit/Mock.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Foundation
1616
///
1717
/// Inherit this type instead of implementing the ``Mockable``
1818
/// protocol, to save some code for every mock you create.
19-
open class Mock: Mockable {
19+
open class Mock: Mockable, @unchecked Sendable {
2020

2121
public init() {}
2222

Sources/MockingKit/Mockable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import Foundation
2222
///
2323
/// Implement this protocol instead of inheriting the ``Mock``
2424
/// base class, to save some code for every mock you create.
25-
public protocol Mockable {
25+
public protocol Mockable: Sendable {
2626

2727
typealias Function = Any
2828

Sources/MockingKit/Mocks/MockPasteboard.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import AppKit
3232
This mock only mocks `setValue(_:forKey:)` for now, but you
3333
can subclass this class and mock more functionality.
3434
*/
35-
public class MockPasteboard: NSPasteboard, Mockable {
35+
public class MockPasteboard: NSPasteboard, Mockable, @unchecked Sendable {
3636

3737
public lazy var setValueForKeyRef = MockReference(setValueForKey)
3838

Sources/MockingKit/Mocks/MockUserDefaults.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import Foundation
1010

1111
/// This class can be used to mock `UserDefaults`.
12-
open class MockUserDefaults: UserDefaults, Mockable {
12+
open class MockUserDefaults: UserDefaults, Mockable, @unchecked Sendable {
1313

1414
public lazy var boolRef = MockReference(bool)
1515
public lazy var arrayRef = MockReference(array)

Tests/MockingKitTests/GenericTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ final class GenericTests: XCTestCase {
2121
}
2222
}
2323

24-
private class GenericMock<T>: Mock {
24+
private class GenericMock<T>: Mock, @unchecked Sendable {
2525

2626
lazy var doitRef = MockReference(doit)
2727

Tests/MockingKitTests/MockableAsyncTests.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,33 @@ class MockableAsyncTests: XCTestCase {
252252
XCTAssertFalse(mock.hasCalled(mock.functionWithIntResultRef))
253253
XCTAssertTrue(mock.hasCalled(\.functionWithStringResultRef))
254254
}
255+
256+
func testMultiThreadedAccess_doesNotCorruptState() async throws {
257+
let expectation = XCTestExpectation()
258+
expectation.expectedFulfillmentCount = 2
259+
let mock = TestClass()
260+
261+
Task {
262+
for index in 0..<100 {
263+
await mock.functionWithVoidResult(arg1: "Test", arg2: index)
264+
}
265+
expectation.fulfill()
266+
}
267+
268+
Task {
269+
for _ in 0..<100 {
270+
_ = mock.hasCalled(\.functionWithIntResultRef)
271+
}
272+
expectation.fulfill()
273+
}
274+
275+
await fulfillment(of: [expectation])
276+
}
255277
}
256278

257-
private class TestClass: AsyncTestProtocol, Mockable {
279+
private final class TestClass: AsyncTestProtocol, Mockable, @unchecked Sendable {
258280

259-
var mock = Mock()
281+
let mock = Mock()
260282

261283
lazy var functionWithIntResultRef = AsyncMockReference(functionWithIntResult)
262284
lazy var functionWithStringResultRef = AsyncMockReference(functionWithStringResult)

Tests/MockingKitTests/MockableTests.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,30 @@ class MockableTests: XCTestCase {
256256
XCTAssertFalse(mock.hasCalled(mock.functionWithIntResultRef))
257257
XCTAssertTrue(mock.hasCalled(\.functionWithStringResultRef))
258258
}
259+
260+
func testMultiThreadedAccess_doesNotCorruptState() {
261+
let queueA = DispatchQueue(label: "QueueA")
262+
let queueB = DispatchQueue(label: "QueueB")
263+
264+
let mock = TestClass()
265+
266+
queueA.async {
267+
for index in 0..<100 {
268+
mock.functionWithVoidResult(arg1: "Something", arg2: index)
269+
}
270+
}
271+
272+
queueB.async {
273+
for _ in 0..<100 {
274+
_ = mock.hasCalled(\.functionWithIntResultRef)
275+
}
276+
}
277+
}
259278
}
260279

261-
private class TestClass: AsyncTestProtocol, Mockable {
280+
private final class TestClass: AsyncTestProtocol, Mockable, @unchecked Sendable {
262281

263-
var mock = Mock()
282+
let mock = Mock()
264283

265284
lazy var functionWithIntResultRef = MockReference(functionWithIntResult)
266285
lazy var functionWithStringResultRef = MockReference(functionWithStringResult)

0 commit comments

Comments
 (0)