diff --git a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethod.swift b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethod.swift index 0ee24e35..4f1bb6d6 100644 --- a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethod.swift @@ -103,13 +103,17 @@ public final class MockReturningNonParameterizedAsyncMethod { /// - Returns: A value, if ``implementation-swift.property`` returns a /// value. private func invoke() async -> ReturnValue { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } guard let returnValue = await self.implementation() else { fatalError("Unimplemented: \(self.exposedMethodDescription)") } - self.returnedValues.append(returnValue) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(returnValue) + } return returnValue } @@ -118,9 +122,15 @@ public final class MockReturningNonParameterizedAsyncMethod { /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncThrowingMethod/MockReturningNonParameterizedAsyncThrowingMethod.swift b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncThrowingMethod/MockReturningNonParameterizedAsyncThrowingMethod.swift index e5058749..3329fe1a 100644 --- a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncThrowingMethod/MockReturningNonParameterizedAsyncThrowingMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncThrowingMethod/MockReturningNonParameterizedAsyncThrowingMethod.swift @@ -105,7 +105,9 @@ public final class MockReturningNonParameterizedAsyncThrowingMethod /// - Returns: A value, if ``implementation-swift.property`` returns a /// value. private func invoke() async throws -> ReturnValue { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } let returnValue = await Result { guard let returnValue = try await self.implementation() else { @@ -115,7 +117,9 @@ public final class MockReturningNonParameterizedAsyncThrowingMethod return returnValue } - self.returnedValues.append(returnValue) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(returnValue) + } return try returnValue.get() } @@ -124,9 +128,15 @@ public final class MockReturningNonParameterizedAsyncThrowingMethod /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethod.swift b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethod.swift index 87b85b8f..1e36d7c5 100644 --- a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethod.swift @@ -101,13 +101,17 @@ public final class MockReturningNonParameterizedMethod { /// - Returns: A value, if ``implementation-swift.property`` returns a /// value. private func invoke() -> ReturnValue { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } guard let returnValue = self.implementation() else { fatalError("Unimplemented: \(self.exposedMethodDescription)") } - self.returnedValues.append(returnValue) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(returnValue) + } return returnValue } @@ -116,9 +120,15 @@ public final class MockReturningNonParameterizedMethod { /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedThrowingMethod/MockReturningNonParameterizedThrowingMethod.swift b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedThrowingMethod/MockReturningNonParameterizedThrowingMethod.swift index 200e6550..fa320f24 100644 --- a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedThrowingMethod/MockReturningNonParameterizedThrowingMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedThrowingMethod/MockReturningNonParameterizedThrowingMethod.swift @@ -105,7 +105,9 @@ public final class MockReturningNonParameterizedThrowingMethod { /// - Returns: A value, if ``implementation-swift.property`` returns a /// value. private func invoke() throws -> ReturnValue { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } let returnValue = Result { guard let returnValue = try self.implementation() else { @@ -115,7 +117,9 @@ public final class MockReturningNonParameterizedThrowingMethod { return returnValue } - self.returnedValues.append(returnValue) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(returnValue) + } return try returnValue.get() } @@ -124,9 +128,15 @@ public final class MockReturningNonParameterizedThrowingMethod { /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncMethod/MockReturningParameterizedAsyncMethod.swift b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncMethod/MockReturningParameterizedAsyncMethod.swift index 92d955de..d6b8777e 100644 --- a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncMethod/MockReturningParameterizedAsyncMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncMethod/MockReturningParameterizedAsyncMethod.swift @@ -140,8 +140,12 @@ public final class MockReturningParameterizedAsyncMethod< /// - Parameter arguments: The arguments with which the method is being /// invoked. private func recordInput(arguments: Arguments) { - self.callCount += 1 - self.invocations.append(arguments) + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } + self._invocations.withLockUnchecked { invocations in + invocations.append(arguments) + } } /// Returns the method's implementation as a closure, or triggers a fatal @@ -160,17 +164,27 @@ public final class MockReturningParameterizedAsyncMethod< /// /// - Parameter returnValue: The value returned by the method. private func recordOutput(returnValue: ReturnValue) { - self.returnedValues.append(returnValue) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(returnValue) + } } // MARK: Reset /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.invocations.removeAll() - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._invocations.withLockUnchecked { invocations in + invocations.removeAll() + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncThrowingMethod/MockReturningParameterizedAsyncThrowingMethod.swift b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncThrowingMethod/MockReturningParameterizedAsyncThrowingMethod.swift index 05d35cf7..cfe5b934 100644 --- a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncThrowingMethod/MockReturningParameterizedAsyncThrowingMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncThrowingMethod/MockReturningParameterizedAsyncThrowingMethod.swift @@ -148,8 +148,12 @@ public final class MockReturningParameterizedAsyncThrowingMethod< /// - Parameter arguments: The arguments with which the method is being /// invoked. private func recordInput(arguments: Arguments) { - self.callCount += 1 - self.invocations.append(arguments) + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } + self._invocations.withLockUnchecked { invocations in + invocations.append(arguments) + } } /// Returns the method's implementation as a closure, or triggers a fatal @@ -168,17 +172,27 @@ public final class MockReturningParameterizedAsyncThrowingMethod< /// /// - Parameter returnValue: The value returned by the method. private func recordOutput(returnValue: Result) { - self.returnedValues.append(returnValue) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(returnValue) + } } // MARK: Reset /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.invocations.removeAll() - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._invocations.withLockUnchecked { invocations in + invocations.removeAll() + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedMethod/MockReturningParameterizedMethod.swift b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedMethod/MockReturningParameterizedMethod.swift index 241d151a..9f044205 100644 --- a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedMethod/MockReturningParameterizedMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedMethod/MockReturningParameterizedMethod.swift @@ -140,8 +140,12 @@ public final class MockReturningParameterizedMethod< /// - Parameter arguments: The arguments with which the method is being /// invoked. private func recordInput(arguments: Arguments) { - self.callCount += 1 - self.invocations.append(arguments) + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } + self._invocations.withLockUnchecked { invocations in + invocations.append(arguments) + } } /// Returns the method's implementation as a closure, or triggers a fatal @@ -160,17 +164,27 @@ public final class MockReturningParameterizedMethod< /// /// - Parameter returnValue: The value returned by the method. private func recordOutput(returnValue: ReturnValue) { - self.returnedValues.append(returnValue) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(returnValue) + } } // MARK: Reset /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.invocations.removeAll() - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._invocations.withLockUnchecked { invocations in + invocations.removeAll() + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedThrowingMethod/MockReturningParameterizedThrowingMethod.swift b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedThrowingMethod/MockReturningParameterizedThrowingMethod.swift index a9c35810..2cf16a1b 100644 --- a/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedThrowingMethod/MockReturningParameterizedThrowingMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockReturningMethods/MockReturningParameterizedThrowingMethod/MockReturningParameterizedThrowingMethod.swift @@ -147,8 +147,12 @@ public final class MockReturningParameterizedThrowingMethod< /// - Parameter arguments: The arguments with which the method is being /// invoked. private func recordInput(arguments: Arguments) { - self.callCount += 1 - self.invocations.append(arguments) + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } + self._invocations.withLockUnchecked { invocations in + invocations.append(arguments) + } } /// Returns the method's implementation as a closure, or triggers a fatal @@ -167,17 +171,27 @@ public final class MockReturningParameterizedThrowingMethod< /// /// - Parameter returnValue: The value returned by the method. private func recordOutput(returnValue: Result) { - self.returnedValues.append(returnValue) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(returnValue) + } } // MARK: Reset /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.invocations.removeAll() - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._invocations.withLockUnchecked { invocations in + invocations.removeAll() + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethod.swift b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethod.swift index cd72c21f..18483b79 100644 --- a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethod.swift @@ -68,7 +68,9 @@ public final class MockVoidNonParameterizedAsyncMethod: Sendable { /// Records the invocation of the method and invokes /// ``implementation-swift.property``. private func invoke() async { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } await self.implementation() } @@ -76,7 +78,11 @@ public final class MockVoidNonParameterizedAsyncMethod: Sendable { /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethod.swift b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethod.swift index e70dcf8b..50f5188a 100644 --- a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethod.swift @@ -78,12 +78,16 @@ public final class MockVoidNonParameterizedAsyncThrowingMethod: Sendable { /// Records the invocation of the method and invokes /// ``implementation-swift.property``. private func invoke() async throws { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } do { try await self.implementation() } catch { - self.thrownErrors.append(error) + self._thrownErrors.withLockUnchecked { thrownErrors in + thrownErrors.append(error) + } throw error } } @@ -92,8 +96,14 @@ public final class MockVoidNonParameterizedAsyncThrowingMethod: Sendable { /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.thrownErrors.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._thrownErrors.withLockUnchecked { thrownErrors in + thrownErrors.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethod.swift b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethod.swift index 902d785f..eead8d5b 100644 --- a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethod.swift @@ -67,7 +67,9 @@ public final class MockVoidNonParameterizedMethod: Sendable { /// Records the invocation of the method and invokes /// ``implementation-swift.property``. private func invoke() { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } self.implementation() } @@ -75,7 +77,11 @@ public final class MockVoidNonParameterizedMethod: Sendable { /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethod.swift b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethod.swift index 0dfef0af..7ea74c5b 100644 --- a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethod.swift @@ -76,12 +76,16 @@ public final class MockVoidNonParameterizedThrowingMethod: Sendable { /// Records the invocation of the method and invokes /// ``implementation-swift.property``. private func invoke() throws { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } do { try self.implementation() } catch { - self.thrownErrors.append(error) + self._thrownErrors.withLockUnchecked { thrownErrors in + thrownErrors.append(error) + } throw error } } @@ -90,8 +94,14 @@ public final class MockVoidNonParameterizedThrowingMethod: Sendable { /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.thrownErrors.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._thrownErrors.withLockUnchecked { thrownErrors in + thrownErrors.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncMethod/MockVoidParameterizedAsyncMethod.swift b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncMethod/MockVoidParameterizedAsyncMethod.swift index e036b7e0..673c797c 100644 --- a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncMethod/MockVoidParameterizedAsyncMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncMethod/MockVoidParameterizedAsyncMethod.swift @@ -101,8 +101,12 @@ public final class MockVoidParameterizedAsyncMethod< /// - Parameter arguments: The arguments with which the method is being /// invoked. private func recordInput(arguments: Arguments) { - self.callCount += 1 - self.invocations.append(arguments) + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } + self._invocations.withLockUnchecked { invocations in + invocations.append(arguments) + } } /// Returns the method's implementation as a closure, or `nil` if @@ -118,9 +122,15 @@ public final class MockVoidParameterizedAsyncMethod< /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.invocations.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._invocations.withLockUnchecked { invocations in + invocations.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncThrowingMethod/MockVoidParameterizedAsyncThrowingMethod.swift b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncThrowingMethod/MockVoidParameterizedAsyncThrowingMethod.swift index c35e56b2..66968cc6 100644 --- a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncThrowingMethod/MockVoidParameterizedAsyncThrowingMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncThrowingMethod/MockVoidParameterizedAsyncThrowingMethod.swift @@ -120,8 +120,12 @@ public final class MockVoidParameterizedAsyncThrowingMethod< /// - Parameter arguments: The arguments with which the method is being /// invoked. private func recordInput(arguments: Arguments) { - self.callCount += 1 - self.invocations.append(arguments) + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } + self._invocations.withLockUnchecked { invocations in + invocations.append(arguments) + } } /// Returns the method's implementation as a closure, or `nil` if @@ -137,17 +141,27 @@ public final class MockVoidParameterizedAsyncThrowingMethod< /// /// - Parameter error: The error thrown by the method. private func recordOutput(error: Error) { - self.thrownErrors.append(error) + self._thrownErrors.withLockUnchecked { thrownErrors in + thrownErrors.append(error) + } } // MARK: Reset /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.invocations.removeAll() - self.thrownErrors.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._invocations.withLockUnchecked { invocations in + invocations.removeAll() + } + self._thrownErrors.withLockUnchecked { thrownErrors in + thrownErrors.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedMethod/MockVoidParameterizedMethod.swift b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedMethod/MockVoidParameterizedMethod.swift index 1c6a1d89..314a0253 100644 --- a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedMethod/MockVoidParameterizedMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedMethod/MockVoidParameterizedMethod.swift @@ -101,8 +101,12 @@ public final class MockVoidParameterizedMethod< /// - Parameter arguments: The arguments with which the method is being /// invoked. private func recordInput(arguments: Arguments) { - self.callCount += 1 - self.invocations.append(arguments) + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } + self._invocations.withLockUnchecked { invocations in + invocations.append(arguments) + } } /// Returns the method's implementation as a closure, or `nil` if @@ -118,9 +122,15 @@ public final class MockVoidParameterizedMethod< /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.invocations.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._invocations.withLockUnchecked { invocations in + invocations.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedThrowingMethod/MockVoidParameterizedThrowingMethod.swift b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedThrowingMethod/MockVoidParameterizedThrowingMethod.swift index fb30ad5a..5c4e8e9a 100644 --- a/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedThrowingMethod/MockVoidParameterizedThrowingMethod.swift +++ b/Sources/Mocking/Models/MockMethods/MockVoidMethods/MockVoidParameterizedThrowingMethod/MockVoidParameterizedThrowingMethod.swift @@ -120,8 +120,12 @@ public final class MockVoidParameterizedThrowingMethod< /// - Parameter arguments: The arguments with which the method is being /// invoked. private func recordInput(arguments: Arguments) { - self.callCount += 1 - self.invocations.append(arguments) + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } + self._invocations.withLockUnchecked { invocations in + invocations.append(arguments) + } } /// Returns the method's implementation as a closure, or `nil` if @@ -137,17 +141,27 @@ public final class MockVoidParameterizedThrowingMethod< /// /// - Parameter error: The error thrown by the method. private func recordOutput(error: Error) { - self.thrownErrors.append(error) + self._thrownErrors.withLockUnchecked { thrownErrors in + thrownErrors.append(error) + } } // MARK: Reset /// Resets the method's implementation and invocation records. private func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.invocations.removeAll() - self.thrownErrors.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._invocations.withLockUnchecked { invocations in + invocations.removeAll() + } + self._thrownErrors.withLockUnchecked { thrownErrors in + thrownErrors.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncGetter/MockPropertyAsyncGetter.swift b/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncGetter/MockPropertyAsyncGetter.swift index 4afe1c3a..9aef8632 100644 --- a/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncGetter/MockPropertyAsyncGetter.swift +++ b/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncGetter/MockPropertyAsyncGetter.swift @@ -56,13 +56,17 @@ public final class MockPropertyAsyncGetter { /// - Returns: A value, if ``implementation-swift.property`` returns a /// value. func get() async -> Value { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } guard let value = await self.implementation() else { fatalError("Unimplemented: \(self.exposedPropertyDescription)") } - self.returnedValues.append(value) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(value) + } return value } @@ -71,9 +75,15 @@ public final class MockPropertyAsyncGetter { /// Resets the getter's implementation and invocation records. func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncThrowingGetter/MockPropertyAsyncThrowingGetter.swift b/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncThrowingGetter/MockPropertyAsyncThrowingGetter.swift index a3cd47cc..4d51d598 100644 --- a/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncThrowingGetter/MockPropertyAsyncThrowingGetter.swift +++ b/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyAsyncThrowingGetter/MockPropertyAsyncThrowingGetter.swift @@ -58,7 +58,9 @@ public final class MockPropertyAsyncThrowingGetter { /// - Returns: A value, if ``implementation-swift.property`` returns a /// value. func get() async throws -> Value { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } let value = await Result { guard let value = try await self.implementation() else { @@ -68,7 +70,9 @@ public final class MockPropertyAsyncThrowingGetter { return value } - self.returnedValues.append(value) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(value) + } return try value.get() } @@ -77,9 +81,15 @@ public final class MockPropertyAsyncThrowingGetter { /// Resets the getter's implementation and invocation records. func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyGetter/MockPropertyGetter.swift b/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyGetter/MockPropertyGetter.swift index ac4618fa..9c2094fd 100644 --- a/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyGetter/MockPropertyGetter.swift +++ b/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyGetter/MockPropertyGetter.swift @@ -56,13 +56,17 @@ public final class MockPropertyGetter { /// - Returns: A value, if ``implementation-swift.property`` returns a /// value. func get() -> Value { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } guard let value = self.implementation() else { fatalError("Unimplemented: \(self.exposedPropertyDescription)") } - self.returnedValues.append(value) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(value) + } return value } @@ -71,9 +75,15 @@ public final class MockPropertyGetter { /// Resets the getter's implementation and invocation records. func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertySetter/MockPropertySetter.swift b/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertySetter/MockPropertySetter.swift index 8eb587d0..8b81f2ac 100644 --- a/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertySetter/MockPropertySetter.swift +++ b/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertySetter/MockPropertySetter.swift @@ -37,8 +37,12 @@ public final class MockPropertySetter { /// /// - Parameter value: The value with which the setter is being invoked. func set(_ value: Value) { - self.callCount += 1 - self.invocations.append(value) + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } + self._invocations.withLockUnchecked { invocations in + invocations.append(value) + } self.implementation(value) } @@ -46,9 +50,15 @@ public final class MockPropertySetter { /// Resets the setter's implementation and invocation records. func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.invocations.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._invocations.withLockUnchecked { invocations in + invocations.removeAll() + } } } diff --git a/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyThrowingGetter/MockPropertyThrowingGetter.swift b/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyThrowingGetter/MockPropertyThrowingGetter.swift index fad883df..fbc27b89 100644 --- a/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyThrowingGetter/MockPropertyThrowingGetter.swift +++ b/Sources/Mocking/Models/MockProperties/MockAccessors/MockPropertyThrowingGetter/MockPropertyThrowingGetter.swift @@ -58,7 +58,9 @@ public final class MockPropertyThrowingGetter { /// - Returns: A value, if ``implementation-swift.property`` returns a /// value. func get() throws -> Value { - self.callCount += 1 + self._callCount.withLockUnchecked { callCount in + callCount += 1 + } let value = Result { guard let value = try self.implementation() else { @@ -68,7 +70,9 @@ public final class MockPropertyThrowingGetter { return value } - self.returnedValues.append(value) + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.append(value) + } return try value.get() } @@ -77,9 +81,15 @@ public final class MockPropertyThrowingGetter { /// Resets the getter's implementation and invocation records. func reset() { - self.implementation = .unimplemented - self.callCount = .zero - self.returnedValues.removeAll() + self._implementation.withLockUnchecked { implementation in + implementation = .unimplemented + } + self._callCount.withLockUnchecked { callCount in + callCount = .zero + } + self._returnedValues.withLockUnchecked { returnedValues in + returnedValues.removeAll() + } } } diff --git a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethodTests.swift index 5dfbacb0..267e801d 100644 --- a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncMethod/MockReturningNonParameterizedAsyncMethodTests.swift @@ -45,61 +45,92 @@ struct MockReturningNonParameterizedAsyncMethodTests { // MARK: Call Count Tests @Test - func callCount() async { + func callCount() async throws { let (sut, invoke, reset) = self.sut() sut.implementation = .uncheckedInvokes { 5 } #expect(sut.callCount == .zero) - _ = await invoke() - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + _ = await invoke() + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } // MARK: Returned Values Tests @Test - func returnedValues() async { + func returnedValues() async throws { let (sut, invoke, reset) = self.sut() sut.implementation = .uncheckedInvokes { 5 } #expect(sut.returnedValues.isEmpty) - _ = await invoke() - #expect(sut.returnedValues == [5]) + try await TestBarrier.executeConcurrently { + _ = await invoke() + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + returnedValue == 5 + } + ) sut.implementation = .uncheckedInvokes { 10 } - _ = await invoke() - #expect(sut.returnedValues == [5, 10]) + try await TestBarrier.executeConcurrently { + _ = await invoke() + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.returnedValues.prefix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + returnedValue == 5 + } + ) + #expect( + sut.returnedValues.suffix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + returnedValue == 10 + } + ) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.returnedValues.isEmpty) } // MARK: Last Returned Value Tests @Test - func lastReturnedValue() async { + func lastReturnedValue() async throws { let (sut, invoke, reset) = self.sut() sut.implementation = .uncheckedInvokes { 5 } #expect(sut.lastReturnedValue == nil) - _ = await invoke() + try await TestBarrier.executeConcurrently { + _ = await invoke() + } #expect(sut.lastReturnedValue == 5) sut.implementation = .uncheckedInvokes { 10 } - _ = await invoke() + try await TestBarrier.executeConcurrently { + _ = await invoke() + } #expect(sut.lastReturnedValue == 10) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastReturnedValue == nil) } } @@ -109,8 +140,8 @@ struct MockReturningNonParameterizedAsyncMethodTests { extension MockReturningNonParameterizedAsyncMethodTests { private func sut() -> ( method: SUT, - invoke: () async -> ReturnValue, - reset: () -> Void + invoke: @Sendable () async -> ReturnValue, + reset: @Sendable () -> Void ) { SUT.makeMethod( exposedMethodDescription: MockImplementationDescription( diff --git a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncThrowingMethod/MockReturningNonParameterizedAsyncThrowingMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncThrowingMethod/MockReturningNonParameterizedAsyncThrowingMethodTests.swift index a6d2d93d..eb0334c4 100644 --- a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncThrowingMethod/MockReturningNonParameterizedAsyncThrowingMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedAsyncThrowingMethod/MockReturningNonParameterizedAsyncThrowingMethodTests.swift @@ -53,19 +53,25 @@ struct MockReturningNonParameterizedAsyncThrowingMethodTests { #expect(sut.callCount == .zero) - _ = try await invoke() - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + _ = try await invoke() + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) sut.implementation = .uncheckedInvokes { throw URLError(.badURL) } await #expect(throws: URLError(.badURL)) { - _ = try await invoke() + try await TestBarrier.executeConcurrently { + _ = try await invoke() + } } - #expect(sut.callCount == 2) + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } @@ -79,24 +85,47 @@ struct MockReturningNonParameterizedAsyncThrowingMethodTests { #expect(sut.returnedValues.isEmpty) - _ = try await invoke() - #expect(sut.returnedValues.count == 1) - #expect(try sut.returnedValues.first?.get() == 5) + try await TestBarrier.executeConcurrently { + _ = try await invoke() + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) sut.implementation = .uncheckedInvokes { throw URLError(.badURL) } await #expect(throws: URLError(.badURL)) { - _ = try await invoke() - } - #expect(sut.returnedValues.count == 2) - #expect(try sut.returnedValues.first?.get() == 5) - #expect(throws: URLError(.badURL)) { - _ = try sut.returnedValues.last?.get() + try await TestBarrier.executeConcurrently { + _ = try await invoke() + } } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.returnedValues.prefix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) + #expect( + sut.returnedValues.suffix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + do { + _ = try returnedValue.get() + return false + } catch URLError.badURL { + return true + } catch { + return false + } + } + ) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.returnedValues.isEmpty) } @@ -110,7 +139,9 @@ struct MockReturningNonParameterizedAsyncThrowingMethodTests { #expect(sut.lastReturnedValue == nil) - _ = try await invoke() + try await TestBarrier.executeConcurrently { + _ = try await invoke() + } #expect(try sut.lastReturnedValue?.get() == 5) sut.implementation = .uncheckedInvokes { @@ -118,13 +149,17 @@ struct MockReturningNonParameterizedAsyncThrowingMethodTests { } await #expect(throws: URLError(.badURL)) { - _ = try await invoke() + try await TestBarrier.executeConcurrently { + _ = try await invoke() + } } #expect(throws: URLError(.badURL)) { - _ = try sut.lastReturnedValue?.get() + try sut.lastReturnedValue?.get() } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastReturnedValue == nil) } } @@ -134,8 +169,8 @@ struct MockReturningNonParameterizedAsyncThrowingMethodTests { extension MockReturningNonParameterizedAsyncThrowingMethodTests { private func sut() -> ( method: SUT, - invoke: () async throws -> ReturnValue, - reset: () -> Void + invoke: @Sendable () async throws -> ReturnValue, + reset: @Sendable () -> Void ) { SUT.makeMethod( exposedMethodDescription: MockImplementationDescription( diff --git a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethodTests.swift index c4161cf0..0ba45145 100644 --- a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedMethod/MockReturningNonParameterizedMethodTests.swift @@ -45,61 +45,92 @@ struct MockReturningNonParameterizedMethodTests { // MARK: Call Count Tests @Test - func callCount() { + func callCount() async throws { let (sut, invoke, reset) = self.sut() sut.implementation = .uncheckedInvokes { 5 } #expect(sut.callCount == .zero) - _ = invoke() - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + _ = invoke() + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } // MARK: Returned Values Tests @Test - func returnedValues() { + func returnedValues() async throws { let (sut, invoke, reset) = self.sut() sut.implementation = .uncheckedInvokes { 5 } #expect(sut.returnedValues.isEmpty) - _ = invoke() - #expect(sut.returnedValues == [5]) + try await TestBarrier.executeConcurrently { + _ = invoke() + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + returnedValue == 5 + } + ) sut.implementation = .uncheckedInvokes { 10 } - _ = invoke() - #expect(sut.returnedValues == [5, 10]) + try await TestBarrier.executeConcurrently { + _ = invoke() + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.returnedValues.prefix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + returnedValue == 5 + } + ) + #expect( + sut.returnedValues.suffix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + returnedValue == 10 + } + ) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.returnedValues.isEmpty) } // MARK: Last Returned Value Tests @Test - func lastReturnedValue() { + func lastReturnedValue() async throws { let (sut, invoke, reset) = self.sut() sut.implementation = .uncheckedInvokes { 5 } #expect(sut.lastReturnedValue == nil) - _ = invoke() + try await TestBarrier.executeConcurrently { + _ = invoke() + } #expect(sut.lastReturnedValue == 5) sut.implementation = .uncheckedInvokes { 10 } - _ = invoke() + try await TestBarrier.executeConcurrently { + _ = invoke() + } #expect(sut.lastReturnedValue == 10) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastReturnedValue == nil) } } @@ -109,8 +140,8 @@ struct MockReturningNonParameterizedMethodTests { extension MockReturningNonParameterizedMethodTests { private func sut() -> ( method: SUT, - invoke: () -> ReturnValue, - reset: () -> Void + invoke: @Sendable () -> ReturnValue, + reset: @Sendable () -> Void ) { SUT.makeMethod( exposedMethodDescription: MockImplementationDescription( diff --git a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedThrowingMethod/MockReturningNonParameterizedThrowingMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedThrowingMethod/MockReturningNonParameterizedThrowingMethodTests.swift index b44a46e7..28f26df5 100644 --- a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedThrowingMethod/MockReturningNonParameterizedThrowingMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningNonParameterizedThrowingMethod/MockReturningNonParameterizedThrowingMethodTests.swift @@ -46,85 +46,120 @@ struct MockReturningNonParameterizedThrowingMethodTests { // MARK: Call Count Tests @Test - func callCount() throws { + func callCount() async throws { let (sut, invoke, reset) = self.sut() sut.implementation = .uncheckedInvokes { 5 } #expect(sut.callCount == .zero) - _ = try invoke() - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + _ = try invoke() + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) sut.implementation = .uncheckedInvokes { throw URLError(.badURL) } - #expect(throws: URLError(.badURL)) { - _ = try invoke() + await #expect(throws: URLError(.badURL)) { + try await TestBarrier.executeConcurrently { + _ = try invoke() + } } - #expect(sut.callCount == 2) + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } // MARK: Returned Values Tests @Test - func returnedValues() throws { + func returnedValues() async throws { let (sut, invoke, reset) = self.sut() sut.implementation = .uncheckedInvokes { 5 } #expect(sut.returnedValues.isEmpty) - _ = try invoke() - #expect(sut.returnedValues.count == 1) - #expect(try sut.returnedValues.first?.get() == 5) + try await TestBarrier.executeConcurrently { + _ = try invoke() + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) sut.implementation = .uncheckedInvokes { throw URLError(.badURL) } - #expect(throws: URLError(.badURL)) { - _ = try invoke() - } - #expect(sut.returnedValues.count == 2) - #expect(try sut.returnedValues.first?.get() == 5) - #expect(throws: URLError(.badURL)) { - _ = try sut.returnedValues.last?.get() + await #expect(throws: URLError(.badURL)) { + try await TestBarrier.executeConcurrently { + _ = try invoke() + } } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.returnedValues.prefix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) + #expect( + sut.returnedValues.suffix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + do { + _ = try returnedValue.get() + return false + } catch URLError.badURL { + return true + } catch { + return false + } + } + ) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.returnedValues.isEmpty) } // MARK: Last Returned Value Tests @Test - func lastReturnedValue() throws { + func lastReturnedValue() async throws { let (sut, invoke, reset) = self.sut() sut.implementation = .uncheckedInvokes { 5 } #expect(sut.lastReturnedValue == nil) - _ = try invoke() + try await TestBarrier.executeConcurrently { + _ = try invoke() + } #expect(try sut.lastReturnedValue?.get() == 5) sut.implementation = .uncheckedInvokes { throw URLError(.badURL) } - #expect(throws: URLError(.badURL)) { - _ = try invoke() + await #expect(throws: URLError(.badURL)) { + try await TestBarrier.executeConcurrently { + _ = try invoke() + } } #expect(throws: URLError(.badURL)) { - _ = try sut.lastReturnedValue?.get() + try sut.lastReturnedValue?.get() } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastReturnedValue == nil) } } @@ -134,8 +169,8 @@ struct MockReturningNonParameterizedThrowingMethodTests { extension MockReturningNonParameterizedThrowingMethodTests { private func sut() -> ( method: SUT, - invoke: () throws -> ReturnValue, - reset: () -> Void + invoke: @Sendable () throws -> ReturnValue, + reset: @Sendable () -> Void ) { SUT.makeMethod( exposedMethodDescription: MockImplementationDescription( diff --git a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncMethod/MockReturningParameterizedAsyncMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncMethod/MockReturningParameterizedAsyncMethodTests.swift index 5594ceaa..d6a7b7a0 100644 --- a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncMethod/MockReturningParameterizedAsyncMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncMethod/MockReturningParameterizedAsyncMethodTests.swift @@ -16,7 +16,7 @@ struct MockReturningParameterizedAsyncMethodTests { > typealias Arguments = (string: String, boolean: Bool) typealias ReturnValue = Int - typealias Closure = (String, Bool) async -> ReturnValue + typealias Closure = @Sendable (String, Bool) async -> ReturnValue // MARK: Implementation Tests @@ -51,7 +51,7 @@ struct MockReturningParameterizedAsyncMethodTests { // MARK: Call Count Tests @Test - func callCount() async { + func callCount() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } @@ -59,32 +59,38 @@ struct MockReturningParameterizedAsyncMethodTests { let invoke = closure() #expect(sut.callCount == .zero) - recordInput(("a", true)) - #expect(sut.callCount == 1) - - var returnValue = await invoke("a", true) - #expect(sut.callCount == 1) - - recordOutput(returnValue) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - recordInput(("b", false)) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + let returnValue = await invoke("a", true) + recordOutput(returnValue) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - returnValue = await invoke("b", false) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - recordOutput(returnValue) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + let returnValue = await invoke("b", false) + recordOutput(returnValue) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } // MARK: Invocations Tests @Test - func invocations() async { + func invocations() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } @@ -92,50 +98,68 @@ struct MockReturningParameterizedAsyncMethodTests { let invoke = closure() #expect(sut.invocations.isEmpty) - recordInput(("a", true)) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - var returnValue = await invoke("a", true) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - recordOutput(returnValue) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - recordInput(("b", false)) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) - - returnValue = await invoke("b", false) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) - - recordOutput(returnValue) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) + #expect( + sut.invocations.allSatisfy { invocation in + invocation == ("a", true) + } + ) - reset() + try await TestBarrier.executeConcurrently { + let returnValue = await invoke("a", true) + recordOutput(returnValue) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) + #expect( + sut.invocations.allSatisfy { invocation in + invocation == ("a", true) + } + ) + + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.invocations.prefix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("a", true) + } + ) + #expect( + sut.invocations.suffix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("b", false) + } + ) + + try await TestBarrier.executeConcurrently { + let returnValue = await invoke("b", false) + recordOutput(returnValue) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.invocations.prefix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("a", true) + } + ) + #expect( + sut.invocations.suffix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("b", false) + } + ) + + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.invocations.isEmpty) } // MARK: Last Invocation Tests @Test - func lastInvocation() async { + func lastInvocation() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } @@ -143,103 +167,139 @@ struct MockReturningParameterizedAsyncMethodTests { let invoke = closure() #expect(sut.lastInvocation == nil) - recordInput(("a", true)) - #expect(sut.lastInvocation?.string == "a") - #expect(sut.lastInvocation?.boolean == true) - - var returnValue = await invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordOutput(returnValue) + try await TestBarrier.executeConcurrently { + let returnValue = await invoke("a", true) + recordOutput(returnValue) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordInput(("b", false)) - #expect(sut.lastInvocation?.string == "b") - #expect(sut.lastInvocation?.boolean == false) - - returnValue = await invoke("b", false) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - recordOutput(returnValue) + try await TestBarrier.executeConcurrently { + let returnValue = await invoke("b", false) + recordOutput(returnValue) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastInvocation == nil) } // MARK: Returned Values Tests @Test - func returnedValues() async { + func returnedValues() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } - var invoke = closure() - #expect(sut.returnedValues.isEmpty) - - recordInput(("a", true)) + let invoke1 = closure() #expect(sut.returnedValues.isEmpty) - var returnValue = await invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.returnedValues.isEmpty) - recordOutput(returnValue) - #expect(sut.returnedValues == [5]) + try await TestBarrier.executeConcurrently { + let returnValue = await invoke1("a", true) + recordOutput(returnValue) + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + returnedValue == 5 + } + ) sut.implementation = .uncheckedInvokes { _, _ in 10 } - invoke = closure() - recordInput(("b", false)) - #expect(sut.returnedValues == [5]) - - returnValue = await invoke("b", false) - #expect(sut.returnedValues == [5]) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + returnedValue == 5 + } + ) - recordOutput(returnValue) - #expect(sut.returnedValues == [5, 10]) + try await TestBarrier.executeConcurrently { + let returnValue = await invoke2("b", false) + recordOutput(returnValue) + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.returnedValues.prefix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + returnedValue == 5 + } + ) + #expect( + sut.returnedValues.suffix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + returnedValue == 10 + } + ) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.returnedValues.isEmpty) } // MARK: Last Returned Value Tests @Test - func lastReturnedValue() async { + func lastReturnedValue() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } - var invoke = closure() - #expect(sut.lastReturnedValue == nil) - - recordInput(("a", true)) + let invoke1 = closure() #expect(sut.lastReturnedValue == nil) - var returnValue = await invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastReturnedValue == nil) - recordOutput(returnValue) + try await TestBarrier.executeConcurrently { + let returnValue = await invoke1("a", true) + recordOutput(returnValue) + } #expect(sut.lastReturnedValue == 5) sut.implementation = .uncheckedInvokes { _, _ in 10 } - invoke = closure() - recordInput(("b", false)) - #expect(sut.lastReturnedValue == 5) - - returnValue = await invoke("b", false) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastReturnedValue == 5) - recordOutput(returnValue) + try await TestBarrier.executeConcurrently { + let returnValue = await invoke2("b", false) + recordOutput(returnValue) + } #expect(sut.lastReturnedValue == 10) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastReturnedValue == nil) } } @@ -251,7 +311,7 @@ extension MockReturningParameterizedAsyncMethodTests { Arguments, ReturnValue >: @unchecked Sendable, MockReturningParameterizedAsyncMethodImplementation { - typealias Closure = (String, Bool) async -> ReturnValue + typealias Closure = @Sendable (String, Bool) async -> ReturnValue case unimplemented case uncheckedInvokes(_ closure: Closure) @@ -272,10 +332,10 @@ extension MockReturningParameterizedAsyncMethodTests { extension MockReturningParameterizedAsyncMethodTests { private func sut() -> ( method: SUT, - recordInput: (Arguments) -> Void, - closure: () -> Closure, - recordOutput: (ReturnValue) -> Void, - reset: () -> Void + recordInput: @Sendable (Arguments) -> Void, + closure: @Sendable () -> Closure, + recordOutput: @Sendable (ReturnValue) -> Void, + reset: @Sendable () -> Void ) { SUT.makeMethod( exposedMethodDescription: MockImplementationDescription( diff --git a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncThrowingMethod/MockReturningParameterizedAsyncThrowingMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncThrowingMethod/MockReturningParameterizedAsyncThrowingMethodTests.swift index 0713b064..b1ac4fde 100644 --- a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncThrowingMethod/MockReturningParameterizedAsyncThrowingMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedAsyncThrowingMethod/MockReturningParameterizedAsyncThrowingMethodTests.swift @@ -17,7 +17,7 @@ struct MockReturningParameterizedAsyncThrowingMethodTests { > typealias Arguments = (string: String, boolean: Bool) typealias ReturnValue = Int - typealias Closure = (String, Bool) async throws -> ReturnValue + typealias Closure = @Sendable (String, Bool) async throws -> ReturnValue // MARK: Implementation Tests @@ -60,25 +60,31 @@ struct MockReturningParameterizedAsyncThrowingMethodTests { let invoke = closure() #expect(sut.callCount == .zero) - recordInput(("a", true)) - #expect(sut.callCount == 1) - - var returnValue = try await invoke("a", true) - #expect(sut.callCount == 1) - - recordOutput(.success(returnValue)) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - recordInput(("b", false)) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + let returnValue = try await invoke("a", true) + recordOutput(.success(returnValue)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - returnValue = try await invoke("b", false) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - recordOutput(.success(returnValue)) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + let returnValue = try await invoke("b", false) + recordOutput(.success(returnValue)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } @@ -93,43 +99,61 @@ struct MockReturningParameterizedAsyncThrowingMethodTests { let invoke = closure() #expect(sut.invocations.isEmpty) - recordInput(("a", true)) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - var returnValue = try await invoke("a", true) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - recordOutput(.success(returnValue)) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - recordInput(("b", false)) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) - - returnValue = try await invoke("b", false) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) - - recordOutput(.success(returnValue)) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) + #expect( + sut.invocations.allSatisfy { invocation in + invocation == ("a", true) + } + ) - reset() + try await TestBarrier.executeConcurrently { + let returnValue = try await invoke("a", true) + recordOutput(.success(returnValue)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) + #expect( + sut.invocations.allSatisfy { invocation in + invocation == ("a", true) + } + ) + + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.invocations.prefix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("a", true) + } + ) + #expect( + sut.invocations.suffix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("b", false) + } + ) + + try await TestBarrier.executeConcurrently { + let returnValue = try await invoke("b", false) + recordOutput(.success(returnValue)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.invocations.prefix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("a", true) + } + ) + #expect( + sut.invocations.suffix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("b", false) + } + ) + + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.invocations.isEmpty) } @@ -144,31 +168,35 @@ struct MockReturningParameterizedAsyncThrowingMethodTests { let invoke = closure() #expect(sut.lastInvocation == nil) - recordInput(("a", true)) - #expect(sut.lastInvocation?.string == "a") - #expect(sut.lastInvocation?.boolean == true) - - var returnValue = try await invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordOutput(.success(returnValue)) + try await TestBarrier.executeConcurrently { + let returnValue = try await invoke("a", true) + recordOutput(.success(returnValue)) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordInput(("b", false)) - #expect(sut.lastInvocation?.string == "b") - #expect(sut.lastInvocation?.boolean == false) - - returnValue = try await invoke("b", false) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - recordOutput(.success(returnValue)) + try await TestBarrier.executeConcurrently { + let returnValue = try await invoke("b", false) + recordOutput(.success(returnValue)) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastInvocation == nil) } @@ -180,39 +208,72 @@ struct MockReturningParameterizedAsyncThrowingMethodTests { sut.implementation = .uncheckedInvokes { _, _ in 5 } - var invoke = closure() - #expect(sut.returnedValues.isEmpty) - - recordInput(("a", true)) + let invoke1 = closure() #expect(sut.returnedValues.isEmpty) - let returnValue = try await invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.returnedValues.isEmpty) - recordOutput(.success(returnValue)) - #expect(try sut.returnedValues.last?.get() == 5) + try await TestBarrier.executeConcurrently { + let returnValue = try await invoke1("a", true) + recordOutput(.success(returnValue)) + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) sut.implementation = .uncheckedInvokes { _, _ in throw URLError(.badURL) } - invoke = closure() - recordInput(("b", false)) - #expect(try sut.returnedValues.last?.get() == 5) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) do { - _ = try await invoke("b", false) + try await TestBarrier.executeConcurrently { + _ = try await invoke2("b", false) + } Issue.record("Expected invoke to throw error.") } catch { - #expect(try sut.returnedValues.last?.get() == 5) - recordOutput(.failure(error)) + try await TestBarrier.executeConcurrently { + recordOutput(.failure(error)) + } } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.returnedValues.prefix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) + #expect( + sut.returnedValues.suffix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + do { + _ = try returnedValue.get() + return false + } catch URLError.badURL { + return true + } catch { + return false + } + } + ) - #expect(throws: URLError(.badURL)) { - try sut.returnedValues.last?.get() + try await TestBarrier.executeConcurrently { + reset() } - - reset() #expect(sut.returnedValues.isEmpty) } @@ -224,39 +285,49 @@ struct MockReturningParameterizedAsyncThrowingMethodTests { sut.implementation = .uncheckedInvokes { _, _ in 5 } - var invoke = closure() + let invoke1 = closure() #expect(sut.lastReturnedValue == nil) - recordInput(("a", true)) - #expect(sut.lastReturnedValue == nil) - - let returnValue = try await invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastReturnedValue == nil) - recordOutput(.success(returnValue)) + try await TestBarrier.executeConcurrently { + let returnValue = try await invoke1("a", true) + recordOutput(.success(returnValue)) + } #expect(try sut.lastReturnedValue?.get() == 5) sut.implementation = .uncheckedInvokes { _, _ in throw URLError(.badURL) } - invoke = closure() - recordInput(("b", false)) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(try sut.lastReturnedValue?.get() == 5) do { - _ = try await invoke("b", false) + try await TestBarrier.executeConcurrently { + _ = try await invoke2("b", false) + } Issue.record("Expected invoke to throw error.") } catch { #expect(try sut.lastReturnedValue?.get() == 5) - recordOutput(.failure(error)) + try await TestBarrier.executeConcurrently { + recordOutput(.failure(error)) + } } #expect(throws: URLError(.badURL)) { try sut.lastReturnedValue?.get() } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastReturnedValue == nil) } } @@ -268,7 +339,7 @@ extension MockReturningParameterizedAsyncThrowingMethodTests { Arguments, ReturnValue >: @unchecked Sendable, MockReturningParameterizedAsyncThrowingMethodImplementation { - typealias Closure = (String, Bool) async throws -> ReturnValue + typealias Closure = @Sendable (String, Bool) async throws -> ReturnValue case unimplemented case uncheckedInvokes(_ closure: Closure) @@ -289,10 +360,10 @@ extension MockReturningParameterizedAsyncThrowingMethodTests { extension MockReturningParameterizedAsyncThrowingMethodTests { private func sut() -> ( method: SUT, - recordInput: (Arguments) -> Void, - closure: () -> Closure, - recordOutput: (Result) -> Void, - reset: () -> Void + recordInput: @Sendable (Arguments) -> Void, + closure: @Sendable () -> Closure, + recordOutput: @Sendable (Result) -> Void, + reset: @Sendable () -> Void ) { SUT.makeMethod( exposedMethodDescription: MockImplementationDescription( diff --git a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedMethod/MockReturningParameterizedMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedMethod/MockReturningParameterizedMethodTests.swift index 2e1b2d64..0784a68a 100644 --- a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedMethod/MockReturningParameterizedMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedMethod/MockReturningParameterizedMethodTests.swift @@ -16,7 +16,7 @@ struct MockReturningParameterizedMethodTests { > typealias Arguments = (string: String, boolean: Bool) typealias ReturnValue = Int - typealias Closure = (String, Bool) -> ReturnValue + typealias Closure = @Sendable (String, Bool) -> ReturnValue // MARK: Implementation Tests @@ -51,7 +51,7 @@ struct MockReturningParameterizedMethodTests { // MARK: Call Count Tests @Test - func callCount() { + func callCount() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } @@ -59,32 +59,38 @@ struct MockReturningParameterizedMethodTests { let invoke = closure() #expect(sut.callCount == .zero) - recordInput(("a", true)) - #expect(sut.callCount == 1) - - var returnValue = invoke("a", true) - #expect(sut.callCount == 1) - - recordOutput(returnValue) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - recordInput(("b", false)) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + let returnValue = invoke("a", true) + recordOutput(returnValue) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - returnValue = invoke("b", false) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - recordOutput(returnValue) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + let returnValue = invoke("b", false) + recordOutput(returnValue) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } // MARK: Invocations Tests @Test - func invocations() { + func invocations() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } @@ -92,50 +98,68 @@ struct MockReturningParameterizedMethodTests { let invoke = closure() #expect(sut.invocations.isEmpty) - recordInput(("a", true)) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - var returnValue = invoke("a", true) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - recordOutput(returnValue) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - recordInput(("b", false)) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) - - returnValue = invoke("b", false) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) - - recordOutput(returnValue) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) + #expect( + sut.invocations.allSatisfy { invocation in + invocation == ("a", true) + } + ) - reset() + try await TestBarrier.executeConcurrently { + let returnValue = invoke("a", true) + recordOutput(returnValue) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) + #expect( + sut.invocations.allSatisfy { invocation in + invocation == ("a", true) + } + ) + + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.invocations.prefix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("a", true) + } + ) + #expect( + sut.invocations.suffix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("b", false) + } + ) + + try await TestBarrier.executeConcurrently { + let returnValue = invoke("b", false) + recordOutput(returnValue) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.invocations.prefix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("a", true) + } + ) + #expect( + sut.invocations.suffix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("b", false) + } + ) + + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.invocations.isEmpty) } // MARK: Last Invocation Tests @Test - func lastInvocation() { + func lastInvocation() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } @@ -143,103 +167,139 @@ struct MockReturningParameterizedMethodTests { let invoke = closure() #expect(sut.lastInvocation == nil) - recordInput(("a", true)) - #expect(sut.lastInvocation?.string == "a") - #expect(sut.lastInvocation?.boolean == true) - - var returnValue = invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordOutput(returnValue) + try await TestBarrier.executeConcurrently { + let returnValue = invoke("a", true) + recordOutput(returnValue) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordInput(("b", false)) - #expect(sut.lastInvocation?.string == "b") - #expect(sut.lastInvocation?.boolean == false) - - returnValue = invoke("b", false) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - recordOutput(returnValue) + try await TestBarrier.executeConcurrently { + let returnValue = invoke("b", false) + recordOutput(returnValue) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastInvocation == nil) } // MARK: Returned Values Tests @Test - func returnedValues() { + func returnedValues() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } - var invoke = closure() - #expect(sut.returnedValues.isEmpty) - - recordInput(("a", true)) + let invoke1 = closure() #expect(sut.returnedValues.isEmpty) - var returnValue = invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.returnedValues.isEmpty) - recordOutput(returnValue) - #expect(sut.returnedValues == [5]) + try await TestBarrier.executeConcurrently { + let returnValue = invoke1("a", true) + recordOutput(returnValue) + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + returnedValue == 5 + } + ) sut.implementation = .uncheckedInvokes { _, _ in 10 } - invoke = closure() - recordInput(("b", false)) - #expect(sut.returnedValues == [5]) - - returnValue = invoke("b", false) - #expect(sut.returnedValues == [5]) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + returnedValue == 5 + } + ) - recordOutput(returnValue) - #expect(sut.returnedValues == [5, 10]) + try await TestBarrier.executeConcurrently { + let returnValue = invoke2("b", false) + recordOutput(returnValue) + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.returnedValues.prefix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + returnedValue == 5 + } + ) + #expect( + sut.returnedValues.suffix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + returnedValue == 10 + } + ) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.returnedValues.isEmpty) } // MARK: Last Returned Value Tests @Test - func lastReturnedValue() { + func lastReturnedValue() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } - var invoke = closure() - #expect(sut.lastReturnedValue == nil) - - recordInput(("a", true)) + let invoke1 = closure() #expect(sut.lastReturnedValue == nil) - var returnValue = invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastReturnedValue == nil) - recordOutput(returnValue) + try await TestBarrier.executeConcurrently { + let returnValue = invoke1("a", true) + recordOutput(returnValue) + } #expect(sut.lastReturnedValue == 5) sut.implementation = .uncheckedInvokes { _, _ in 10 } - invoke = closure() - recordInput(("b", false)) - #expect(sut.lastReturnedValue == 5) - - returnValue = invoke("b", false) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastReturnedValue == 5) - recordOutput(returnValue) + try await TestBarrier.executeConcurrently { + let returnValue = invoke2("b", false) + recordOutput(returnValue) + } #expect(sut.lastReturnedValue == 10) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastReturnedValue == nil) } } @@ -251,7 +311,7 @@ extension MockReturningParameterizedMethodTests { Arguments, ReturnValue >: @unchecked Sendable, MockReturningParameterizedMethodImplementation { - typealias Closure = (String, Bool) -> ReturnValue + typealias Closure = @Sendable (String, Bool) -> ReturnValue case unimplemented case uncheckedInvokes(_ closure: Closure) @@ -272,10 +332,10 @@ extension MockReturningParameterizedMethodTests { extension MockReturningParameterizedMethodTests { private func sut() -> ( method: SUT, - recordInput: (Arguments) -> Void, - closure: () -> Closure, - recordOutput: (ReturnValue) -> Void, - reset: () -> Void + recordInput: @Sendable (Arguments) -> Void, + closure: @Sendable () -> Closure, + recordOutput: @Sendable (ReturnValue) -> Void, + reset: @Sendable () -> Void ) { SUT.makeMethod( exposedMethodDescription: MockImplementationDescription( diff --git a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedThrowingMethod/MockReturningParameterizedThrowingMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedThrowingMethod/MockReturningParameterizedThrowingMethodTests.swift index e0037c40..1c66270e 100644 --- a/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedThrowingMethod/MockReturningParameterizedThrowingMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockReturningMethods/MockReturningParameterizedThrowingMethod/MockReturningParameterizedThrowingMethodTests.swift @@ -17,7 +17,7 @@ struct MockReturningParameterizedThrowingMethodTests { > typealias Arguments = (string: String, boolean: Bool) typealias ReturnValue = Int - typealias Closure = (String, Bool) throws -> ReturnValue + typealias Closure = @Sendable (String, Bool) throws -> ReturnValue // MARK: Implementation Tests @@ -52,7 +52,7 @@ struct MockReturningParameterizedThrowingMethodTests { // MARK: Call Count Tests @Test - func callCount() throws { + func callCount() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } @@ -60,32 +60,38 @@ struct MockReturningParameterizedThrowingMethodTests { let invoke = closure() #expect(sut.callCount == .zero) - recordInput(("a", true)) - #expect(sut.callCount == 1) - - var returnValue = try invoke("a", true) - #expect(sut.callCount == 1) - - recordOutput(.success(returnValue)) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - recordInput(("b", false)) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + let returnValue = try invoke("a", true) + recordOutput(.success(returnValue)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - returnValue = try invoke("b", false) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - recordOutput(.success(returnValue)) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + let returnValue = try invoke("b", false) + recordOutput(.success(returnValue)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } // MARK: Invocations Tests @Test - func invocations() throws { + func invocations() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } @@ -93,50 +99,68 @@ struct MockReturningParameterizedThrowingMethodTests { let invoke = closure() #expect(sut.invocations.isEmpty) - recordInput(("a", true)) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - var returnValue = try invoke("a", true) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - recordOutput(.success(returnValue)) - #expect(sut.invocations.count == 1) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - - recordInput(("b", false)) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) - - returnValue = try invoke("b", false) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) - - recordOutput(.success(returnValue)) - #expect(sut.invocations.count == 2) - #expect(sut.invocations.first?.string == "a") - #expect(sut.invocations.first?.boolean == true) - #expect(sut.invocations.last?.string == "b") - #expect(sut.invocations.last?.boolean == false) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) + #expect( + sut.invocations.allSatisfy { invocation in + invocation == ("a", true) + } + ) + + try await TestBarrier.executeConcurrently { + let returnValue = try invoke("a", true) + recordOutput(.success(returnValue)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) + #expect( + sut.invocations.allSatisfy { invocation in + invocation == ("a", true) + } + ) - reset() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.invocations.prefix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("a", true) + } + ) + #expect( + sut.invocations.suffix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("b", false) + } + ) + + try await TestBarrier.executeConcurrently { + let returnValue = try invoke("b", false) + recordOutput(.success(returnValue)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.invocations.prefix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("a", true) + } + ) + #expect( + sut.invocations.suffix(TestBarrier.defaultTaskCount).allSatisfy { invocation in + invocation == ("b", false) + } + ) + + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.invocations.isEmpty) } // MARK: Last Invocation Tests @Test - func lastInvocation() throws { + func lastInvocation() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } @@ -144,119 +168,172 @@ struct MockReturningParameterizedThrowingMethodTests { let invoke = closure() #expect(sut.lastInvocation == nil) - recordInput(("a", true)) - #expect(sut.lastInvocation?.string == "a") - #expect(sut.lastInvocation?.boolean == true) - - var returnValue = try invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordOutput(.success(returnValue)) + try await TestBarrier.executeConcurrently { + let returnValue = try invoke("a", true) + recordOutput(.success(returnValue)) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordInput(("b", false)) - #expect(sut.lastInvocation?.string == "b") - #expect(sut.lastInvocation?.boolean == false) - - returnValue = try invoke("b", false) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - recordOutput(.success(returnValue)) + try await TestBarrier.executeConcurrently { + let returnValue = try invoke("b", false) + recordOutput(.success(returnValue)) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastInvocation == nil) } // MARK: Returned Values Tests @Test - func returnedValues() throws { + func returnedValues() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } - var invoke = closure() - #expect(sut.returnedValues.isEmpty) - - recordInput(("a", true)) + let invoke1 = closure() #expect(sut.returnedValues.isEmpty) - let returnValue = try invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.returnedValues.isEmpty) - recordOutput(.success(returnValue)) - #expect(try sut.returnedValues.last?.get() == 5) + try await TestBarrier.executeConcurrently { + let returnValue = try invoke1("a", true) + recordOutput(.success(returnValue)) + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) sut.implementation = .uncheckedInvokes { _, _ in throw URLError(.badURL) } - invoke = closure() - recordInput(("b", false)) - #expect(try sut.returnedValues.last?.get() == 5) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) do { - _ = try invoke("b", false) + try await TestBarrier.executeConcurrently { + _ = try invoke2("b", false) + } Issue.record("Expected invoke to throw error.") } catch { - #expect(try sut.returnedValues.last?.get() == 5) - recordOutput(.failure(error)) + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount) + #expect( + sut.returnedValues.allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) + try await TestBarrier.executeConcurrently { + recordOutput(.failure(error)) + } } + #expect(sut.returnedValues.count == TestBarrier.defaultTaskCount * 2) + #expect( + sut.returnedValues.prefix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + (try? returnedValue.get()) == 5 + } + ) + #expect( + sut.returnedValues.suffix(TestBarrier.defaultTaskCount).allSatisfy { returnedValue in + do { + _ = try returnedValue.get() + return false + } catch URLError.badURL { + return true + } catch { + return false + } + } + ) - #expect(throws: URLError(.badURL)) { - try sut.returnedValues.last?.get() + try await TestBarrier.executeConcurrently { + reset() } - - reset() #expect(sut.returnedValues.isEmpty) } // MARK: Last Returned Value Tests @Test - func lastReturnedValue() throws { + func lastReturnedValue() async throws { let (sut, recordInput, closure, recordOutput, reset) = self.sut() sut.implementation = .uncheckedInvokes { _, _ in 5 } - var invoke = closure() + let invoke1 = closure() #expect(sut.lastReturnedValue == nil) - recordInput(("a", true)) - #expect(sut.lastReturnedValue == nil) - - let returnValue = try invoke("a", true) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastReturnedValue == nil) - recordOutput(.success(returnValue)) + try await TestBarrier.executeConcurrently { + let returnValue = try invoke1("a", true) + recordOutput(.success(returnValue)) + } #expect(try sut.lastReturnedValue?.get() == 5) sut.implementation = .uncheckedInvokes { _, _ in throw URLError(.badURL) } - invoke = closure() - recordInput(("b", false)) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(try sut.lastReturnedValue?.get() == 5) do { - _ = try invoke("b", false) + try await TestBarrier.executeConcurrently { + _ = try invoke2("b", false) + } Issue.record("Expected invoke to throw error.") } catch { #expect(try sut.lastReturnedValue?.get() == 5) - recordOutput(.failure(error)) + try await TestBarrier.executeConcurrently { + recordOutput(.failure(error)) + } } #expect(throws: URLError(.badURL)) { try sut.lastReturnedValue?.get() } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastReturnedValue == nil) } } @@ -268,7 +345,7 @@ extension MockReturningParameterizedThrowingMethodTests { Arguments, ReturnValue >: @unchecked Sendable, MockReturningParameterizedThrowingMethodImplementation { - typealias Closure = (String, Bool) throws -> ReturnValue + typealias Closure = @Sendable (String, Bool) throws -> ReturnValue case unimplemented case uncheckedInvokes(_ closure: Closure) @@ -289,10 +366,10 @@ extension MockReturningParameterizedThrowingMethodTests { extension MockReturningParameterizedThrowingMethodTests { private func sut() -> ( method: SUT, - recordInput: (Arguments) -> Void, - closure: () -> Closure, - recordOutput: (Result) -> Void, - reset: () -> Void + recordInput: @Sendable (Arguments) -> Void, + closure: @Sendable () -> Closure, + recordOutput: @Sendable (Result) -> Void, + reset: @Sendable () -> Void ) { SUT.makeMethod( exposedMethodDescription: MockImplementationDescription( diff --git a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethodTests.swift index f69af21a..60abdab7 100644 --- a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncMethod/MockVoidNonParameterizedAsyncMethodTests.swift @@ -43,15 +43,19 @@ struct MockVoidNonParameterizedAsyncMethodTests { // MARK: Call Count Tests @Test - func callCount() async { + func callCount() async throws { let (sut, invoke, reset) = SUT.makeMethod() #expect(sut.callCount == .zero) - await invoke() - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + await invoke() + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } } diff --git a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethodTests.swift index 87e586dd..f7452d3b 100644 --- a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedAsyncThrowingMethod/MockVoidNonParameterizedAsyncThrowingMethodTests.swift @@ -49,19 +49,25 @@ struct MockVoidNonParameterizedAsyncThrowingMethodTests { #expect(sut.callCount == .zero) - try await invoke() - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + try await invoke() + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) sut.implementation = .uncheckedInvokes { throw URLError(.badURL) } await #expect(throws: URLError(.badURL)) { - try await invoke() + try await TestBarrier.executeConcurrently { + try await invoke() + } } - #expect(sut.callCount == 2) + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } @@ -73,7 +79,9 @@ struct MockVoidNonParameterizedAsyncThrowingMethodTests { #expect(sut.thrownErrors.isEmpty) - try await invoke() + try await TestBarrier.executeConcurrently { + try await invoke() + } #expect(sut.thrownErrors.isEmpty) sut.implementation = .uncheckedInvokes { @@ -81,9 +89,11 @@ struct MockVoidNonParameterizedAsyncThrowingMethodTests { } await #expect(throws: URLError(.badURL)) { - try await invoke() + try await TestBarrier.executeConcurrently { + try await invoke() + } } - #expect(sut.thrownErrors.count == 1) + #expect(sut.thrownErrors.count == TestBarrier.defaultTaskCount) var firstThrownError = try #require(sut.thrownErrors.first) @@ -96,9 +106,11 @@ struct MockVoidNonParameterizedAsyncThrowingMethodTests { } await #expect(throws: URLError(.badServerResponse)) { - try await invoke() + try await TestBarrier.executeConcurrently { + try await invoke() + } } - #expect(sut.thrownErrors.count == 2) + #expect(sut.thrownErrors.count == TestBarrier.defaultTaskCount * 2) firstThrownError = try #require(sut.thrownErrors.first) let lastThrownError = try #require(sut.thrownErrors.last) @@ -110,7 +122,9 @@ struct MockVoidNonParameterizedAsyncThrowingMethodTests { throw lastThrownError } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.thrownErrors.isEmpty) } @@ -122,7 +136,9 @@ struct MockVoidNonParameterizedAsyncThrowingMethodTests { #expect(sut.lastThrownError == nil) - try await invoke() + try await TestBarrier.executeConcurrently { + try await invoke() + } #expect(sut.lastThrownError == nil) sut.implementation = .uncheckedInvokes { @@ -130,7 +146,9 @@ struct MockVoidNonParameterizedAsyncThrowingMethodTests { } await #expect(throws: URLError(.badURL)) { - try await invoke() + try await TestBarrier.executeConcurrently { + try await invoke() + } } var lastThrownError = try #require(sut.lastThrownError) @@ -144,7 +162,9 @@ struct MockVoidNonParameterizedAsyncThrowingMethodTests { } await #expect(throws: URLError(.badServerResponse)) { - try await invoke() + try await TestBarrier.executeConcurrently { + try await invoke() + } } lastThrownError = try #require(sut.lastThrownError) @@ -153,7 +173,9 @@ struct MockVoidNonParameterizedAsyncThrowingMethodTests { throw lastThrownError } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastThrownError == nil) } } diff --git a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethodTests.swift index 11ef37ac..e9d6baae 100644 --- a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedMethod/MockVoidNonParameterizedMethodTests.swift @@ -43,15 +43,21 @@ struct MockVoidNonParameterizedMethodTests { // MARK: Call Count Tests @Test - func callCount() { + func callCount() async throws { let (sut, invoke, reset) = SUT.makeMethod() #expect(sut.callCount == .zero) - invoke() - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + invoke() + } + + #expect(sut.callCount == TestBarrier.defaultTaskCount) + + try await TestBarrier.executeConcurrently { + reset() + } - reset() #expect(sut.callCount == .zero) } } diff --git a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethodTests.swift index 049bce4d..ef6f5f9e 100644 --- a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidNonParameterizedThrowingMethod/MockVoidNonParameterizedThrowingMethodTests.swift @@ -44,46 +44,56 @@ struct MockVoidNonParameterizedThrowingMethodTests { // MARK: Call Count Tests @Test - func callCount() throws { + func callCount() async throws { let (sut, invoke, reset) = SUT.makeMethod() #expect(sut.callCount == .zero) - try invoke() - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + try invoke() + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) sut.implementation = .uncheckedInvokes { throw URLError(.badURL) } - #expect(throws: URLError(.badURL)) { - try invoke() + await #expect(throws: URLError(.badURL)) { + try await TestBarrier.executeConcurrently { + try invoke() + } } - #expect(sut.callCount == 2) + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } // MARK: Thrown Errors Tests @Test - func thrownErrors() throws { + func thrownErrors() async throws { let (sut, invoke, reset) = SUT.makeMethod() #expect(sut.thrownErrors.isEmpty) - try invoke() + try await TestBarrier.executeConcurrently { + try invoke() + } #expect(sut.thrownErrors.isEmpty) sut.implementation = .uncheckedInvokes { throw URLError(.badURL) } - #expect(throws: URLError(.badURL)) { - try invoke() + await #expect(throws: URLError(.badURL)) { + try await TestBarrier.executeConcurrently { + try invoke() + } } - #expect(sut.thrownErrors.count == 1) + #expect(sut.thrownErrors.count == TestBarrier.defaultTaskCount) var firstThrownError = try #require(sut.thrownErrors.first) @@ -95,10 +105,12 @@ struct MockVoidNonParameterizedThrowingMethodTests { throw URLError(.badServerResponse) } - #expect(throws: URLError(.badServerResponse)) { - try invoke() + await #expect(throws: URLError(.badServerResponse)) { + try await TestBarrier.executeConcurrently { + try invoke() + } } - #expect(sut.thrownErrors.count == 2) + #expect(sut.thrownErrors.count == TestBarrier.defaultTaskCount * 2) firstThrownError = try #require(sut.thrownErrors.first) let lastThrownError = try #require(sut.lastThrownError) @@ -110,27 +122,33 @@ struct MockVoidNonParameterizedThrowingMethodTests { throw lastThrownError } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.thrownErrors.isEmpty) } // MARK: Last Thrown Error Tests @Test - func lastThrownError() throws { + func lastThrownError() async throws { let (sut, invoke, reset) = SUT.makeMethod() #expect(sut.lastThrownError == nil) - try invoke() + try await TestBarrier.executeConcurrently { + try invoke() + } #expect(sut.lastThrownError == nil) sut.implementation = .uncheckedInvokes { throw URLError(.badURL) } - #expect(throws: URLError(.badURL)) { - try invoke() + await #expect(throws: URLError(.badURL)) { + try await TestBarrier.executeConcurrently { + try invoke() + } } var lastThrownError = try #require(sut.lastThrownError) @@ -143,8 +161,10 @@ struct MockVoidNonParameterizedThrowingMethodTests { throw URLError(.badServerResponse) } - #expect(throws: URLError(.badServerResponse)) { - try invoke() + await #expect(throws: URLError(.badServerResponse)) { + try await TestBarrier.executeConcurrently { + try invoke() + } } lastThrownError = try #require(sut.lastThrownError) @@ -153,7 +173,9 @@ struct MockVoidNonParameterizedThrowingMethodTests { throw lastThrownError } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastThrownError == nil) } } diff --git a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncMethod/MockVoidParameterizedAsyncMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncMethod/MockVoidParameterizedAsyncMethodTests.swift index 8463d0e6..4a181b91 100644 --- a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncMethod/MockVoidParameterizedAsyncMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncMethod/MockVoidParameterizedAsyncMethodTests.swift @@ -49,7 +49,7 @@ struct MockVoidParameterizedAsyncMethodTests { // MARK: Call Count Tests @Test - func callCount() async { + func callCount() async throws { let (sut, recordInput, closure, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in } @@ -57,26 +57,36 @@ struct MockVoidParameterizedAsyncMethodTests { let invoke = closure() #expect(sut.callCount == .zero) - recordInput(("a", true)) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - await invoke?("a", true) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + await invoke?("a", true) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - recordInput(("b", false)) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - await invoke?("b", false) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + await invoke?("b", false) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } // MARK: Invocations Tests @Test - func invocations() async { + func invocations() async throws { let (sut, recordInput, closure, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in } @@ -84,38 +94,48 @@ struct MockVoidParameterizedAsyncMethodTests { let invoke = closure() #expect(sut.invocations.isEmpty) - recordInput(("a", true)) - #expect(sut.invocations.count == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) - await invoke?("a", true) - #expect(sut.invocations.count == 1) + try await TestBarrier.executeConcurrently { + await invoke?("a", true) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) - recordInput(("b", false)) - #expect(sut.invocations.count == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) #expect(sut.invocations.last?.string == "b") #expect(sut.invocations.last?.boolean == false) - await invoke?("b", false) - #expect(sut.invocations.count == 2) + try await TestBarrier.executeConcurrently { + await invoke?("b", false) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) #expect(sut.invocations.last?.string == "b") #expect(sut.invocations.last?.boolean == false) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.invocations.isEmpty) } // MARK: Last Invocation Tests @Test - func lastInvocation() async { + func lastInvocation() async throws { let (sut, recordInput, closure, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in } @@ -123,23 +143,33 @@ struct MockVoidParameterizedAsyncMethodTests { let invoke = closure() #expect(sut.lastInvocation == nil) - recordInput(("a", true)) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - await invoke?("a", true) + try await TestBarrier.executeConcurrently { + await invoke?("a", true) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordInput(("b", false)) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - await invoke?("b", false) + try await TestBarrier.executeConcurrently { + await invoke?("b", false) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastInvocation == nil) } } @@ -150,7 +180,7 @@ extension MockVoidParameterizedAsyncMethodTests { enum Implementation< Arguments >: @unchecked Sendable, MockVoidParameterizedAsyncMethodImplementation { - typealias Closure = (String, Bool) async -> Void + typealias Closure = @Sendable (String, Bool) async -> Void case unimplemented case uncheckedInvokes(_ closure: Closure) diff --git a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncThrowingMethod/MockVoidParameterizedAsyncThrowingMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncThrowingMethod/MockVoidParameterizedAsyncThrowingMethodTests.swift index f99440e7..ab5b49d5 100644 --- a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncThrowingMethod/MockVoidParameterizedAsyncThrowingMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedAsyncThrowingMethod/MockVoidParameterizedAsyncThrowingMethodTests.swift @@ -61,33 +61,47 @@ struct MockVoidParameterizedAsyncThrowingMethodTests { let invoke = closure() #expect(sut.callCount == .zero) - recordInput(("a", true)) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) do { - try await invoke?("a", true) + try await TestBarrier.executeConcurrently { + try await invoke?("a", true) + } Issue.record("Expected invoke to throw error.") } catch { - #expect(sut.callCount == 1) + #expect(sut.callCount == TestBarrier.defaultTaskCount) - recordOutput(error) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) } - recordInput(("b", false)) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) do { - try await invoke?("b", false) + try await TestBarrier.executeConcurrently { + try await invoke?("b", false) + } Issue.record("Expected invoke to throw error.") } catch { - #expect(sut.callCount == 2) + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - recordOutput(error) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } @@ -104,51 +118,65 @@ struct MockVoidParameterizedAsyncThrowingMethodTests { let invoke = closure() #expect(sut.invocations.isEmpty) - recordInput(("a", true)) - #expect(sut.invocations.count == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) do { - try await invoke?("a", true) + try await TestBarrier.executeConcurrently { + try await invoke?("a", true) + } Issue.record("Expected invoke to throw error.") } catch { - #expect(sut.invocations.count == 1) + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) - recordOutput(error) - #expect(sut.invocations.count == 1) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) } - recordInput(("b", false)) - #expect(sut.invocations.count == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) #expect(sut.invocations.last?.string == "b") #expect(sut.invocations.last?.boolean == false) do { - try await invoke?("b", false) + try await TestBarrier.executeConcurrently { + try await invoke?("b", false) + } Issue.record("Expected invoke to throw error.") } catch { - #expect(sut.invocations.count == 2) + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) #expect(sut.invocations.last?.string == "b") #expect(sut.invocations.last?.boolean == false) - recordOutput(error) - #expect(sut.invocations.count == 2) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) #expect(sut.invocations.last?.string == "b") #expect(sut.invocations.last?.boolean == false) } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.invocations.isEmpty) } @@ -165,39 +193,53 @@ struct MockVoidParameterizedAsyncThrowingMethodTests { let invoke = closure() #expect(sut.lastInvocation == nil) - recordInput(("a", true)) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) do { - try await invoke?("a", true) + try await TestBarrier.executeConcurrently { + try await invoke?("a", true) + } Issue.record("Expected invoke to throw error.") } catch { #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordOutput(error) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) } - recordInput(("b", false)) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) do { - try await invoke?("b", false) + try await TestBarrier.executeConcurrently { + try await invoke?("b", false) + } Issue.record("Expected invoke to throw error.") } catch { #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - recordOutput(error) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastInvocation == nil) } @@ -209,30 +251,40 @@ struct MockVoidParameterizedAsyncThrowingMethodTests { sut.implementation = .uncheckedInvokes { _, _ in } - var invoke = closure() + let invoke1 = closure() #expect(sut.thrownErrors.isEmpty) - recordInput(("a", true)) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.thrownErrors.isEmpty) - try await invoke?("a", true) + try await TestBarrier.executeConcurrently { + try await invoke1?("a", true) + } #expect(sut.thrownErrors.isEmpty) sut.implementation = .uncheckedInvokes { _, _ in throw URLError(.badURL) } - invoke = closure() - recordInput(("b", false)) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.thrownErrors.isEmpty) do { - try await invoke?("b", false) + try await TestBarrier.executeConcurrently { + try await invoke2?("b", false) + } Issue.record("Expected invoke to throw error.") } catch { #expect(sut.thrownErrors.isEmpty) - recordOutput(error) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } let lastThrownError = try #require(sut.thrownErrors.last) @@ -241,7 +293,9 @@ struct MockVoidParameterizedAsyncThrowingMethodTests { } } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.thrownErrors.isEmpty) } @@ -253,30 +307,40 @@ struct MockVoidParameterizedAsyncThrowingMethodTests { sut.implementation = .uncheckedInvokes { _, _ in } - var invoke = closure() + let invoke1 = closure() #expect(sut.lastThrownError == nil) - recordInput(("a", true)) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastThrownError == nil) - try await invoke?("a", true) + try await TestBarrier.executeConcurrently { + try await invoke1?("a", true) + } #expect(sut.lastThrownError == nil) sut.implementation = .uncheckedInvokes { _, _ in throw URLError(.badURL) } - invoke = closure() - recordInput(("b", false)) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastThrownError == nil) do { - try await invoke?("b", false) + try await TestBarrier.executeConcurrently { + try await invoke2?("b", false) + } Issue.record("Expected invoke to throw error.") } catch { #expect(sut.lastThrownError == nil) - recordOutput(error) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } let lastThrownError = try #require(sut.lastThrownError) @@ -285,7 +349,9 @@ struct MockVoidParameterizedAsyncThrowingMethodTests { } } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastThrownError == nil) } } @@ -296,7 +362,7 @@ extension MockVoidParameterizedAsyncThrowingMethodTests { enum Implementation< Arguments >: @unchecked Sendable, MockVoidParameterizedAsyncThrowingMethodImplementation { - typealias Closure = (String, Bool) async throws -> Void + typealias Closure = @Sendable (String, Bool) async throws -> Void case unimplemented case uncheckedInvokes(_ closure: Closure) diff --git a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedMethod/MockVoidParameterizedMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedMethod/MockVoidParameterizedMethodTests.swift index c887a0cb..35d1a91f 100644 --- a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedMethod/MockVoidParameterizedMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedMethod/MockVoidParameterizedMethodTests.swift @@ -49,7 +49,7 @@ struct MockVoidParameterizedMethodTests { // MARK: Call Count Tests @Test - func callCount() { + func callCount() async throws { let (sut, recordInput, closure, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in } @@ -57,26 +57,36 @@ struct MockVoidParameterizedMethodTests { let invoke = closure() #expect(sut.callCount == .zero) - recordInput(("a", true)) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - invoke?("a", true) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + invoke?("a", true) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) - recordInput(("b", false)) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - invoke?("b", false) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + invoke?("b", false) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } // MARK: Invocations Tests @Test - func invocations() { + func invocations() async throws { let (sut, recordInput, closure, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in } @@ -84,38 +94,48 @@ struct MockVoidParameterizedMethodTests { let invoke = closure() #expect(sut.invocations.isEmpty) - recordInput(("a", true)) - #expect(sut.invocations.count == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) - invoke?("a", true) - #expect(sut.invocations.count == 1) + try await TestBarrier.executeConcurrently { + invoke?("a", true) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) - recordInput(("b", false)) - #expect(sut.invocations.count == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) #expect(sut.invocations.last?.string == "b") #expect(sut.invocations.last?.boolean == false) - invoke?("b", false) - #expect(sut.invocations.count == 2) + try await TestBarrier.executeConcurrently { + invoke?("b", false) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) #expect(sut.invocations.last?.string == "b") #expect(sut.invocations.last?.boolean == false) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.invocations.isEmpty) } // MARK: Last Invocation Tests @Test - func lastInvocation() { + func lastInvocation() async throws { let (sut, recordInput, closure, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in } @@ -123,23 +143,33 @@ struct MockVoidParameterizedMethodTests { let invoke = closure() #expect(sut.lastInvocation == nil) - recordInput(("a", true)) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - invoke?("a", true) + try await TestBarrier.executeConcurrently { + invoke?("a", true) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordInput(("b", false)) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - invoke?("b", false) + try await TestBarrier.executeConcurrently { + invoke?("b", false) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastInvocation == nil) } } @@ -150,7 +180,7 @@ extension MockVoidParameterizedMethodTests { enum Implementation< Arguments >: @unchecked Sendable, MockVoidParameterizedMethodImplementation { - typealias Closure = (String, Bool) -> Void + typealias Closure = @Sendable (String, Bool) -> Void case unimplemented case uncheckedInvokes(_ closure: Closure) diff --git a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedThrowingMethod/MockVoidParameterizedThrowingMethodTests.swift b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedThrowingMethod/MockVoidParameterizedThrowingMethodTests.swift index 5e51a414..887c4408 100644 --- a/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedThrowingMethod/MockVoidParameterizedThrowingMethodTests.swift +++ b/Tests/MockingTests/Models/MockMethods/MockVoidMethods/MockVoidParameterizedThrowingMethod/MockVoidParameterizedThrowingMethodTests.swift @@ -51,7 +51,7 @@ struct MockVoidParameterizedThrowingMethodTests { // MARK: Call Count Tests @Test - func callCount() throws { + func callCount() async throws { let (sut, recordInput, closure, recordOutput, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in @@ -61,40 +61,54 @@ struct MockVoidParameterizedThrowingMethodTests { let invoke = closure() #expect(sut.callCount == .zero) - recordInput(("a", true)) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) do { - try invoke?("a", true) + try await TestBarrier.executeConcurrently { + try invoke?("a", true) + } Issue.record("Expected invoke to throw error.") } catch { - #expect(sut.callCount == 1) + #expect(sut.callCount == TestBarrier.defaultTaskCount) - recordOutput(error) - #expect(sut.callCount == 1) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount) } - recordInput(("b", false)) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) do { - try invoke?("b", false) + try await TestBarrier.executeConcurrently { + try invoke?("b", false) + } Issue.record("Expected invoke to throw error.") } catch { - #expect(sut.callCount == 2) + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) - recordOutput(error) - #expect(sut.callCount == 2) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } + #expect(sut.callCount == TestBarrier.defaultTaskCount * 2) } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.callCount == .zero) } // MARK: Invocations Tests @Test - func invocations() throws { + func invocations() async throws { let (sut, recordInput, closure, recordOutput, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in @@ -104,58 +118,72 @@ struct MockVoidParameterizedThrowingMethodTests { let invoke = closure() #expect(sut.invocations.isEmpty) - recordInput(("a", true)) - #expect(sut.invocations.count == 1) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) do { - try invoke?("a", true) + try await TestBarrier.executeConcurrently { + try invoke?("a", true) + } Issue.record("Expected invoke to throw error.") } catch { - #expect(sut.invocations.count == 1) + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) - recordOutput(error) - #expect(sut.invocations.count == 1) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) } - recordInput(("b", false)) - #expect(sut.invocations.count == 2) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) #expect(sut.invocations.last?.string == "b") #expect(sut.invocations.last?.boolean == false) do { - try invoke?("b", false) + try await TestBarrier.executeConcurrently { + try invoke?("b", false) + } Issue.record("Expected invoke to throw error.") } catch { - #expect(sut.invocations.count == 2) + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) #expect(sut.invocations.last?.string == "b") #expect(sut.invocations.last?.boolean == false) - recordOutput(error) - #expect(sut.invocations.count == 2) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } + #expect(sut.invocations.count == TestBarrier.defaultTaskCount * 2) #expect(sut.invocations.first?.string == "a") #expect(sut.invocations.first?.boolean == true) #expect(sut.invocations.last?.string == "b") #expect(sut.invocations.last?.boolean == false) } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.invocations.isEmpty) } // MARK: Last Invocation Tests @Test - func lastInvocation() throws { + func lastInvocation() async throws { let (sut, recordInput, closure, recordOutput, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in @@ -165,74 +193,98 @@ struct MockVoidParameterizedThrowingMethodTests { let invoke = closure() #expect(sut.lastInvocation == nil) - recordInput(("a", true)) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) do { - try invoke?("a", true) + try await TestBarrier.executeConcurrently { + try invoke?("a", true) + } Issue.record("Expected invoke to throw error.") } catch { #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) - recordOutput(error) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } #expect(sut.lastInvocation?.string == "a") #expect(sut.lastInvocation?.boolean == true) } - recordInput(("b", false)) + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) do { - try invoke?("b", false) + try await TestBarrier.executeConcurrently { + try invoke?("b", false) + } Issue.record("Expected invoke to throw error.") } catch { #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) - recordOutput(error) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } #expect(sut.lastInvocation?.string == "b") #expect(sut.lastInvocation?.boolean == false) } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastInvocation == nil) } // MARK: Thrown Errors Tests @Test - func thrownErrors() throws { + func thrownErrors() async throws { let (sut, recordInput, closure, recordOutput, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in } - var invoke = closure() + let invoke1 = closure() #expect(sut.thrownErrors.isEmpty) - recordInput(("a", true)) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.thrownErrors.isEmpty) - try invoke?("a", true) + try await TestBarrier.executeConcurrently { + try invoke1?("a", true) + } #expect(sut.thrownErrors.isEmpty) sut.implementation = .uncheckedInvokes { _, _ in throw URLError(.badURL) } - invoke = closure() - recordInput(("b", false)) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.thrownErrors.isEmpty) do { - try invoke?("b", false) + try await TestBarrier.executeConcurrently { + try invoke2?("b", false) + } Issue.record("Expected invoke to throw error.") } catch { #expect(sut.thrownErrors.isEmpty) - recordOutput(error) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } let lastThrownError = try #require(sut.thrownErrors.last) @@ -241,42 +293,54 @@ struct MockVoidParameterizedThrowingMethodTests { } } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.thrownErrors.isEmpty) } // MARK: Last Thrown Error Tests @Test - func lastThrownError() throws { + func lastThrownError() async throws { let (sut, recordInput, closure, recordOutput, reset) = SUT.makeMethod() sut.implementation = .uncheckedInvokes { _, _ in } - var invoke = closure() + let invoke1 = closure() #expect(sut.lastThrownError == nil) - recordInput(("a", true)) + try await TestBarrier.executeConcurrently { + recordInput(("a", true)) + } #expect(sut.lastThrownError == nil) - try invoke?("a", true) + try await TestBarrier.executeConcurrently { + try invoke1?("a", true) + } #expect(sut.lastThrownError == nil) sut.implementation = .uncheckedInvokes { _, _ in throw URLError(.badURL) } - invoke = closure() - recordInput(("b", false)) + let invoke2 = closure() + try await TestBarrier.executeConcurrently { + recordInput(("b", false)) + } #expect(sut.lastThrownError == nil) do { - try invoke?("b", false) + try await TestBarrier.executeConcurrently { + try invoke2?("b", false) + } Issue.record("Expected invoke to throw error.") } catch { #expect(sut.lastThrownError == nil) - recordOutput(error) + try await TestBarrier.executeConcurrently { + recordOutput(error) + } let lastThrownError = try #require(sut.lastThrownError) @@ -285,7 +349,9 @@ struct MockVoidParameterizedThrowingMethodTests { } } - reset() + try await TestBarrier.executeConcurrently { + reset() + } #expect(sut.lastThrownError == nil) } } @@ -296,7 +362,7 @@ extension MockVoidParameterizedThrowingMethodTests { enum Implementation< Arguments >: @unchecked Sendable, MockVoidParameterizedThrowingMethodImplementation { - typealias Closure = (String, Bool) throws -> Void + typealias Closure = @Sendable (String, Bool) throws -> Void case unimplemented case uncheckedInvokes(_ closure: Closure) diff --git a/Tests/MockingTests/TestHelpers/TestBarrier.swift b/Tests/MockingTests/TestHelpers/TestBarrier.swift new file mode 100644 index 00000000..e40e9b68 --- /dev/null +++ b/Tests/MockingTests/TestHelpers/TestBarrier.swift @@ -0,0 +1,179 @@ +// +// TestBarrier.swift +// +// Copyright © 2025 Fetch. +// + +import Foundation + +/// A synchronization primitive that ensures a specific number of async tasks +/// all start executing simultaneously, useful for testing race conditions. +/// +/// The barrier works by collecting continuations from tasks as they arrive, and +/// only releasing all of them once the specified count is reached. This +/// guarantees maximum concurrency for race condition testing. +/// +/// ```swift +/// let taskCount = 100 +/// let barrier = TestBarrier(totalTasks: taskCount) +/// +/// await withTaskGroup(of: Void.self) { group in +/// for _ in 0..] = [] + + /// The number of tasks still expected to arrive at the barrier. + private var remainingTasks: Int + + // MARK: Initializers + + /// Creates a barrier that will release all tasks once the specified number + /// of tasks have called `wait()`. + /// + /// - Parameter totalTasks: The number of tasks that must call `wait()` + /// before any are released. + init(totalTasks: Int) { + self.remainingTasks = totalTasks + } + + // MARK: Wait + + /// Suspends the current task until all expected tasks have called this + /// method. + /// + /// This method decrements the remaining task count and stores the current + /// task's continuation. When the count reaches zero, all stored + /// continuations are resumed simultaneously, allowing all tasks to proceed + /// together. + /// + /// - Warning: This method should only be called by the exact number of + /// tasks specified in `totalTasks`. Calling it more times will have no + /// effect, but calling it fewer times will cause tasks to wait + /// indefinitely. + func wait() async { + await withCheckedContinuation { continuation in + self.remainingTasks -= 1 + self.continuations.append(continuation) + + guard self.remainingTasks == .zero else { + return + } + + for continuation in self.continuations { + continuation.resume() + } + + self.continuations.removeAll() + } + } + + // MARK: Execute Concurrently + + /// Executes a block concurrently across multiple tasks, all starting + /// simultaneously. + /// + /// This is a convenience method that creates a barrier, spawns the + /// specified number of tasks in a task group, and ensures they all execute + /// the provided block at exactly the same time. This is particularly useful + /// for testing race conditions in concurrent code. + /// + /// ```swift + /// // Test race condition with default 1,000 tasks + /// await TestBarrier.executeConcurrently { + /// unsafeCounter += 1 + /// } + /// + /// // Test race condition with custom task count + /// await TestBarrier.executeConcurrently(taskCount: 500) { + /// someSharedResource.modify() + /// } + /// ``` + /// + /// - Parameters: + /// - taskCount: The number of concurrent tasks to execute. + /// - block: The code to execute simultaneously across all tasks. + /// - Throws: An error if any error is thrown by the block from any of the + /// tasks. + static func executeConcurrently( + taskCount: Int = TestBarrier.defaultTaskCount, + _ block: @escaping @Sendable () async throws -> Void + ) async throws { + let barrier = TestBarrier(totalTasks: taskCount) + let errorStorage = ErrorStorage() + + await withTaskGroup(of: Void.self) { group in + for _ in 0.. (any Error)? { + self.error + } +}