Skip to content

Commit

Permalink
Infrastructure: Address concurrency warnings in tests (#3438)
Browse files Browse the repository at this point in the history
* Infrastructure: Address concurrency warnings in tests

* wip

---------

Co-authored-by: Brandon Williams <mbrandonw@hey.com>
  • Loading branch information
stephencelis and mbrandonw authored Oct 15, 2024
1 parent f43a154 commit fc5cbee
Show file tree
Hide file tree
Showing 14 changed files with 76 additions and 43 deletions.
6 changes: 3 additions & 3 deletions Sources/ComposableArchitecture/Effect.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Combine
@preconcurrency import Combine
import Foundation
import SwiftUI

public struct Effect<Action> {
public struct Effect<Action>: Sendable {
@usableFromInline
enum Operation {
enum Operation: Sendable {
case none
case publisher(AnyPublisher<Action, Never>)
case run(TaskPriority? = nil, @Sendable (_ send: Send<Action>) async -> Void)
Expand Down
1 change: 1 addition & 0 deletions Tests/ComposableArchitectureTests/BindingLocalTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@testable import ComposableArchitecture

final class BindingLocalTests: BaseTCATestCase {
@MainActor
public func testBindingLocalIsActive() {
XCTAssertFalse(BindingLocal.isActive)

Expand Down
2 changes: 2 additions & 0 deletions Tests/ComposableArchitectureTests/CompatibilityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ final class CompatibilityTests: BaseTCATestCase {
// Actions can be re-entrantly sent into the store if an action is sent that holds an object
// which sends an action on deinit. In order to prevent a simultaneous access exception for this
// case we need to use `withExtendedLifetime` on the buffered actions when clearing them out.
@MainActor
func testCaseStudy_ActionReentranceFromClearedBufferCausingDeinitAction() {
let cancelID = UUID()

Expand Down Expand Up @@ -78,6 +79,7 @@ final class CompatibilityTests: BaseTCATestCase {
// In particular, this means that in the implementation of `Store.send` we need to flip
// `isSending` to false _after_ the store's state mutation is made so that re-entrant actions
// are buffered rather than immediately handled.
@MainActor
func testCaseStudy_ActionReentranceFromStateObservation() {
var cancellables: Set<AnyCancellable> = []
defer { _ = cancellables }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

await withTaskGroup(of: Void.self) { group in
for index in 1...1_000 {
group.addTask {
group.addTask { @Sendable in
subject.send(index)
}
}
Expand Down
8 changes: 4 additions & 4 deletions Tests/ComposableArchitectureTests/EffectTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ final class EffectTests: BaseTCATestCase {
}

func testConcatenateOneEffect() async {
await withMainSerialExecutor {
await withMainSerialExecutor { [mainQueue] in
let values = LockIsolated<[Int]>([])

let effect = Effect<Int>.concatenate(
.publisher { Just(1).delay(for: 1, scheduler: self.mainQueue) }
.publisher { Just(1).delay(for: 1, scheduler: mainQueue) }
)

let task = Task {
Expand All @@ -62,10 +62,10 @@ final class EffectTests: BaseTCATestCase {

XCTAssertEqual(values.value, [])

await self.mainQueue.advance(by: 1)
await mainQueue.advance(by: 1)
XCTAssertEqual(values.value, [1])

await self.mainQueue.run()
await mainQueue.run()
XCTAssertEqual(values.value, [1])

await task.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ final class BindingTests: BaseTCATestCase {
)
}

@MainActor
func testViewEquality() {
struct Feature: Reducer {
struct State: Equatable {
Expand Down
5 changes: 5 additions & 0 deletions Tests/ComposableArchitectureTests/ScopeCacheTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ final class ScopeCacheTests: BaseTCATestCase {
store.send(.child(.dismiss))
}

@MainActor
func testOptionalScope_CachedStore() {
#if DEBUG
let store = StoreOf<Feature>(initialState: Feature.State(child: Feature.State())) {
Expand All @@ -46,6 +47,7 @@ final class ScopeCacheTests: BaseTCATestCase {
#endif
}

@MainActor
func testOptionalScope_StoreIfLet() {
#if DEBUG
let store = StoreOf<Feature>(initialState: Feature.State(child: Feature.State())) {
Expand All @@ -62,6 +64,7 @@ final class ScopeCacheTests: BaseTCATestCase {
}

@available(*, deprecated)
@MainActor
func testOptionalScope_StoreIfLet_UncachedStore() {
let store = StoreOf<Feature>(initialState: Feature.State(child: Feature.State())) {
}
Expand Down Expand Up @@ -93,6 +96,7 @@ final class ScopeCacheTests: BaseTCATestCase {
}
}

@MainActor
func testIdentifiedArrayScope_CachedStore() {
#if DEBUG
let store = StoreOf<Feature>(initialState: Feature.State(rows: [Feature.State()])) {
Expand All @@ -108,6 +112,7 @@ final class ScopeCacheTests: BaseTCATestCase {
}

@available(*, deprecated)
@MainActor
func testIdentifiedArrayScope_UncachedStore() {
let store = StoreOf<Feature>(initialState: Feature.State(rows: [Feature.State()])) {
Feature()
Expand Down
15 changes: 8 additions & 7 deletions Tests/ComposableArchitectureTests/SharedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,11 @@ final class SharedTests: XCTestCase {
XCTAssertEqual(store.state.sharedCount, 2)
}

@MainActor
func testMultiSharing() async {
@Shared(Stats()) var stats

let store = await TestStore(
let store = TestStore(
initialState: SharedFeature.State(
profile: Shared(Profile(stats: $stats)),
sharedCount: Shared(0),
Expand Down Expand Up @@ -721,7 +722,7 @@ final class SharedTests: XCTestCase {

func testSharedDefaults_Used() {
let didAccess = LockIsolated(false)
let logDefault: () -> Bool = {
let logDefault: @Sendable () -> Bool = {
didAccess.setValue(true)
return true
}
Expand All @@ -732,7 +733,7 @@ final class SharedTests: XCTestCase {

func testSharedDefaults_Unused() {
let didAccess = LockIsolated(false)
let logDefault: () -> Bool = {
let logDefault: @Sendable () -> Bool = {
didAccess.setValue(true)
return true
}
Expand Down Expand Up @@ -761,7 +762,7 @@ final class SharedTests: XCTestCase {
func testSharedOverrideDefault() {
let accessedActive1 = LockIsolated(false)
let accessedDefault = LockIsolated(false)
let logDefault: () -> Bool = {
let logDefault: @Sendable () -> Bool = {
accessedDefault.setValue(true)
return true
}
Expand Down Expand Up @@ -796,7 +797,7 @@ final class SharedTests: XCTestCase {
func testSharedReaderOverrideDefault() {
let accessedActive1 = LockIsolated(false)
let accessedDefault = LockIsolated(false)
let logDefault: () -> Bool = {
let logDefault: @Sendable () -> Bool = {
accessedDefault.setValue(true)
return true
}
Expand Down Expand Up @@ -1141,7 +1142,7 @@ private struct SimpleFeature {
}

@Perceptible
class SharedObject {
class SharedObject: @unchecked Sendable {
var count = 0
}

Expand Down Expand Up @@ -1244,7 +1245,7 @@ extension PersistenceReaderKey where Self == PersistenceKeyDefault<AppStorageKey
PersistenceKeyDefault(.appStorage("isOn"), false)
}

static func isActive(default keyDefault: @escaping () -> Bool) -> Self {
static func isActive(default keyDefault: @escaping @Sendable () -> Bool) -> Self {
PersistenceKeyDefault(.appStorage("isActive"), keyDefault())
}
}
Expand Down
1 change: 1 addition & 0 deletions Tests/ComposableArchitectureTests/StoreFilterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Combine
import XCTest

final class StoreInvalidationTests: BaseTCATestCase {
@MainActor
func testInvalidation() {
var cancellables: Set<AnyCancellable> = []

Expand Down
3 changes: 3 additions & 0 deletions Tests/ComposableArchitectureTests/StoreLifetimeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import XCTest

final class StoreLifetimeTests: BaseTCATestCase {
@available(*, deprecated)
@MainActor
func testStoreCaching() {
let grandparentStore = Store(initialState: Grandparent.State()) {
Grandparent()
Expand All @@ -21,6 +22,7 @@ final class StoreLifetimeTests: BaseTCATestCase {
}

@available(*, deprecated)
@MainActor
func testStoreInvalidation() {
let grandparentStore = Store(initialState: Grandparent.State()) {
Grandparent()
Expand Down Expand Up @@ -48,6 +50,7 @@ final class StoreLifetimeTests: BaseTCATestCase {
}

#if DEBUG
@MainActor
func testStoreDeinit() {
Logger.shared.isEnabled = true
do {
Expand Down
15 changes: 13 additions & 2 deletions Tests/ComposableArchitectureTests/StoreTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Combine
@preconcurrency import Combine
@_spi(Internals) import ComposableArchitecture
import XCTest

Expand Down Expand Up @@ -47,6 +47,7 @@ final class StoreTests: BaseTCATestCase {
}

@available(*, deprecated)
@MainActor
func testScopedStoreReceivesUpdatesFromParent() {
let counterReducer = Reduce<Int, Void>({ state, _ in
state += 1
Expand All @@ -71,6 +72,7 @@ final class StoreTests: BaseTCATestCase {
}

@available(*, deprecated)
@MainActor
func testParentStoreReceivesUpdatesFromChild() {
let counterReducer = Reduce<Int, Void>({ state, _ in
state += 1
Expand All @@ -95,6 +97,7 @@ final class StoreTests: BaseTCATestCase {
}

@available(*, deprecated)
@MainActor
func testScopeCallCount_OneLevel_NoSubscription() {
var numCalls1 = 0
let store = Store<Int, Void>(initialState: 0) {}
Expand All @@ -112,6 +115,7 @@ final class StoreTests: BaseTCATestCase {
}

@available(*, deprecated)
@MainActor
func testScopeCallCount_OneLevel_Subscribing() {
var numCalls1 = 0
let store = Store<Int, Void>(initialState: 0) {}
Expand All @@ -130,6 +134,7 @@ final class StoreTests: BaseTCATestCase {
}

@available(*, deprecated)
@MainActor
func testScopeCallCount_TwoLevels_Subscribing() {
var numCalls1 = 0
var numCalls2 = 0
Expand Down Expand Up @@ -158,6 +163,7 @@ final class StoreTests: BaseTCATestCase {
}

@available(*, deprecated)
@MainActor
func testScopeCallCount_ThreeLevels_ViewStoreSubscribing() {
var numCalls1 = 0
var numCalls2 = 0
Expand Down Expand Up @@ -280,6 +286,7 @@ final class StoreTests: BaseTCATestCase {
XCTAssertEqual(values, [1, 2, 3, 4])
}

@MainActor
func testLotsOfSynchronousActions() {
enum Action { case incr, noop }
let reducer = Reduce<Int, Action>({ state, action in
Expand Down Expand Up @@ -350,6 +357,7 @@ final class StoreTests: BaseTCATestCase {
XCTAssertEqual(outputs, [nil, 1, nil, 1, nil, 1, nil])
}

@MainActor
func testIfLetTwo() {
let parentStore = Store(initialState: 0) {
Reduce<Int?, Bool> { state, action in
Expand Down Expand Up @@ -382,6 +390,7 @@ final class StoreTests: BaseTCATestCase {
.store(in: &self.cancellables)
}

@MainActor
func testActionQueuing() async {
let subject = PassthroughSubject<Void, Never>()

Expand All @@ -391,7 +400,7 @@ final class StoreTests: BaseTCATestCase {
case doIncrement
}

let store = await TestStore(initialState: 0) {
let store = TestStore(initialState: 0) {
Reduce<Int, Action> { state, action in
switch action {
case .incrementTapped:
Expand Down Expand Up @@ -420,6 +429,7 @@ final class StoreTests: BaseTCATestCase {
subject.send(completion: .finished)
}

@MainActor
func testCoalesceSynchronousActions() {
let store = Store(initialState: 0) {
Reduce<Int, Int> { state, action in
Expand Down Expand Up @@ -451,6 +461,7 @@ final class StoreTests: BaseTCATestCase {
}

@available(*, deprecated)
@MainActor
func testBufferedActionProcessing() {
struct ChildState: Equatable {
var count: Int?
Expand Down
8 changes: 5 additions & 3 deletions Tests/ComposableArchitectureTests/TestStoreTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Combine
@preconcurrency import Combine
import ComposableArchitecture
import XCTest

Expand Down Expand Up @@ -396,6 +396,7 @@ final class TestStoreTests: BaseTCATestCase {
}
}

@MainActor
func testPrepareDependenciesCalledOnce() {
var count = 0
let store = TestStore(initialState: 0) {
Expand Down Expand Up @@ -469,6 +470,7 @@ final class TestStoreTests: BaseTCATestCase {
}
}

@MainActor
func testSubscribeReceiveCombineScheduler() async {
let subject = PassthroughSubject<Void, Never>()
let scheduler = DispatchQueue.test
Expand All @@ -482,7 +484,7 @@ final class TestStoreTests: BaseTCATestCase {
case start
}

let store = await TestStore(initialState: State()) {
let store = TestStore(initialState: State()) {
Reduce<State, Action> { state, action in
switch action {
case .start:
Expand Down Expand Up @@ -659,7 +661,7 @@ final class TestStoreTests: BaseTCATestCase {
}

private struct Client: DependencyKey {
var fetch: () -> Int
var fetch: @Sendable () -> Int
static let liveValue = Client(fetch: { 42 })
}
extension DependencyValues {
Expand Down
Loading

0 comments on commit fc5cbee

Please sign in to comment.