Skip to content

Commit

Permalink
Resolve warnings related to actor isolation that occurred during testing
Browse files Browse the repository at this point in the history
  • Loading branch information
DevYeom committed Aug 31, 2024
1 parent bc687d5 commit e1cb3c3
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode_15.2.app
run: sudo xcode-select -s /Applications/Xcode_15.4.app
- name: Build
run: make build-all
- name: Test
Expand Down
6 changes: 3 additions & 3 deletions Tests/OneWayTests/EffectTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -333,18 +333,18 @@ final class EffectTests: XCTestCase {
let clock = TestClock()

let values = Effects.Create { continuation in
Task { @MainActor in
Task {
try! await clock.sleep(for: .seconds(100))
continuation.yield(Action.first)
continuation.yield(Action.second)
}
Task { @MainActor in
Task {
try! await clock.sleep(for: .seconds(200))
continuation.yield(Action.third)
continuation.yield(Action.fourth)
continuation.yield(Action.fifth)
}
Task { @MainActor in
Task {
try! await clock.sleep(for: .seconds(300))
continuation.finish()
}
Expand Down
25 changes: 13 additions & 12 deletions Tests/OneWayTests/StoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ final class StoreTests: XCTestCase {
self.clock = clock
sut = Store(
reducer: TestReducer(clock: clock),
state: .init(count: 0, text: "")
state: TestReducer.State(count: 0, text: "")
)
}

Expand Down Expand Up @@ -103,10 +103,10 @@ final class StoreTests: XCTestCase {
// https://forums.swift.org/t/how-to-use-combine-publisher-with-swift-concurrency-publisher-values-could-miss-events/67193
Task {
try! await Task.sleep(nanoseconds: NSEC_PER_MSEC)
textPublisher.send("first")
numberPublisher.send(1)
textPublisher.send("second")
numberPublisher.send(2)
testPublisher.text.send("first")
testPublisher.number.send(1)
testPublisher.text.send("second")
testPublisher.number.send(2)
}

let states = await sut.states
Expand Down Expand Up @@ -259,13 +259,14 @@ final class StoreTests: XCTestCase {
}

#if canImport(Combine)
private let textPublisher = PassthroughSubject<String, Never>()
private let numberPublisher = PassthroughSubject<Int, Never>()
/// Just for testing
private struct TestPublisher: @unchecked Sendable {
let text = PassthroughSubject<String, Never>()
let number = PassthroughSubject<Int, Never>()
}
private let testPublisher = TestPublisher()
#endif

@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
private var _clock = TestClock()

@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
private struct TestReducer: Reducer {
enum Action: Sendable {
Expand Down Expand Up @@ -371,12 +372,12 @@ private struct TestReducer: Reducer {
func bind() -> AnyEffect<Action> {
return .merge(
.sequence { send in
for await text in textPublisher.stream {
for await text in testPublisher.text.stream {
send(Action.response(text))
}
},
.sequence { send in
for await number in numberPublisher.stream {
for await number in testPublisher.number.stream {
send(Action.response(String(number)))
}
}
Expand Down
28 changes: 25 additions & 3 deletions Tests/OneWayTests/TestHelper/XCTestCase+Expect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@
import XCTest

extension XCTestCase {
func expect(
compare: () async -> Bool,
timeout seconds: UInt64 = 1,
description: String = #function
) async {
let limit = NSEC_PER_SEC * seconds
let start = DispatchTime.now().uptimeNanoseconds
while true {
guard start.distance(to: DispatchTime.now().uptimeNanoseconds) < limit else {
XCTFail("Exceeded timeout of \(seconds) seconds")
break
}
if await compare() {
XCTAssert(true)
break
} else {
await Task.yield()
}
}
}

func sendableExpect(
compare: @Sendable () async -> Bool,
timeout seconds: UInt64 = 1,
Expand All @@ -28,9 +49,10 @@ extension XCTestCase {
}
}
}

func expect(
compare: () async -> Bool,

@MainActor
func sendableExpectWithMainActor(
compare: @Sendable () async -> Bool,
timeout seconds: UInt64 = 1,
description: String = #function
) async {
Expand Down
78 changes: 42 additions & 36 deletions Tests/OneWayTests/ViewStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,26 @@ import OneWay
import XCTest

#if !os(Linux)
@MainActor
final class ViewStoreTests: XCTestCase {
@MainActor
private var sut: ViewStore<TestReducer>!

@MainActor
override func setUp() {
super.setUp()
sut = ViewStore(
reducer: TestReducer(),
state: .init(count: 0)
state: TestReducer.State(count: 0)
)
}

@MainActor
override func tearDown() {
super.tearDown()
sut = nil
}

@MainActor
func test_initialState() async {
XCTAssertEqual(sut.initialState, TestReducer.State(count: 0))
XCTAssertEqual(sut.state.count, 0)
Expand All @@ -37,16 +40,21 @@ final class ViewStoreTests: XCTestCase {
}
}

#if swift(>=5.10)
@MainActor
func test_sendSeveralActions() async {
sut.send(.increment)
sut.send(.increment)
sut.send(.twice)

await sendableExpect { await sut.state.count == 4 }
nonisolated(unsafe) let sut = sut!
await sendableExpectWithMainActor { await sut.state.count == 4 }
}
#endif

@MainActor
func test_triggeredState() async {
actor Result {
actor TestResult {
var counts: [Int] = []
var triggeredCounts: [Int] = []
func appendCount(_ count: Int) {
Expand All @@ -56,7 +64,7 @@ final class ViewStoreTests: XCTestCase {
triggeredCounts.append(count)
}
}
let result = Result()
let result = TestResult()

Task { @MainActor in
for await state in sut.states {
Expand All @@ -73,12 +81,13 @@ final class ViewStoreTests: XCTestCase {
sut.send(.setTriggeredCount(10))
sut.send(.setTriggeredCount(10))

await sendableExpect { await result.counts == [0, 0, 0, 0] }
await sendableExpect { await result.triggeredCounts == [0, 10, 10, 10] }
await sendableExpectWithMainActor { await result.counts == [0, 0, 0, 0] }
await sendableExpectWithMainActor { await result.triggeredCounts == [0, 10, 10, 10] }
}

@MainActor
func test_ignoredState() async {
actor Result {
actor TestResult {
var counts: [Int] = []
var ignoredCounts: [Int] = []
func appendCount(_ count: Int) {
Expand All @@ -88,7 +97,7 @@ final class ViewStoreTests: XCTestCase {
ignoredCounts.append(count)
}
}
let result = Result()
let result = TestResult()

Task { @MainActor in
for await state in sut.states {
Expand All @@ -106,10 +115,11 @@ final class ViewStoreTests: XCTestCase {
sut.send(.setIgnoredCount(30))

// only initial value
await sendableExpect { await result.counts == [0] }
await sendableExpect { await result.ignoredCounts == [0] }
await sendableExpectWithMainActor { await result.counts == [0] }
await sendableExpectWithMainActor { await result.ignoredCounts == [0] }
}

@MainActor
func test_asyncViewStateSequence() async {
sut.send(.concat)

Expand All @@ -122,15 +132,30 @@ final class ViewStoreTests: XCTestCase {
XCTAssertEqual(result, [0, 1, 2, 3, 4])
}

#if swift(>=5.10)
@MainActor
func test_asyncViewStateSequenceForMultipleConsumers() async {
let expectation = expectation(description: #function)

let result = Result(expectation, expectedCount: 15)
nonisolated(unsafe) let sut = sut!
let result = TestResult(expectation, expectedCount: 15)
Task { @MainActor in
await withTaskGroup(of: Void.self) { group in
group.addTask { await self.consumeAsyncViewStateSequence1(result) }
group.addTask { await self.consumeAsyncViewStateSequence2(result) }
group.addTask { await self.consumeAsyncViewStateSequence3(result) }
group.addTask { @MainActor in
for await state in sut.states {
await result.insert(state.count)
}
}
group.addTask { @MainActor in
for await count in sut.states.count {
await result.insert(count)
}
}
group.addTask { @MainActor in
for await count in sut.states.count {
await result.insert(count)
}
}
}
}

Expand All @@ -151,26 +176,7 @@ final class ViewStoreTests: XCTestCase {
]
)
}
}

extension ViewStoreTests {
private func consumeAsyncViewStateSequence1(_ result: Result) async {
for await state in sut.states {
await result.insert(state.count)
}
}

private func consumeAsyncViewStateSequence2(_ result: Result) async {
for await count in sut.states.count {
await result.insert(count)
}
}

private func consumeAsyncViewStateSequence3(_ result: Result) async {
for await count in sut.states.count {
await result.insert(count)
}
}
#endif
}

private struct TestReducer: Reducer {
Expand Down Expand Up @@ -224,7 +230,7 @@ private struct TestReducer: Reducer {
}
}

private actor Result {
private actor TestResult {
let expectation: XCTestExpectation
let expectedCount: Int
var values: [Int] = [] {
Expand Down

0 comments on commit e1cb3c3

Please sign in to comment.