Skip to content

Commit 5292ee8

Browse files
authored
Fixed runSideEffect key in WorkflowConcurrency Worker (#202)
* Fixed runSideEffect key in Worker Changed WorkerWorkflow to use it’s state as the key for the runSideEffect call so that if the state changes the worker is re-run. Updated unit tests to test for this issue of the side effect not running. Added unit tests for testing that updating the key in the render call causes the worker to run. * Changed initial state to be more obvious Per CR changed intial state of the updated workflow to be a more obvious state change
1 parent 3e703ce commit 5292ee8

File tree

2 files changed

+130
-9
lines changed

2 files changed

+130
-9
lines changed

WorkflowConcurrency/Sources/Worker.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ struct WorkerWorkflow<WorkerType: Worker>: Workflow {
6161
func render(state: State, context: RenderContext<WorkerWorkflow>) -> Rendering {
6262
let logger = WorkerLogger<WorkerType>()
6363
let sink = context.makeOutputSink()
64-
context.runSideEffect(key: "") { lifetime in
64+
context.runSideEffect(key: state) { lifetime in
6565
let send: @MainActor(Output) -> Void = sink.send
6666
let task = Task {
6767
logger.logStarted()

WorkflowConcurrency/Tests/WorkerTests.swift

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import XCTest
2222
class WorkerTests: XCTestCase {
2323
func testWorkerOutput() {
2424
let host = WorkflowHost(
25-
workflow: TaskTestWorkerWorkflow(key: "")
25+
workflow: TaskTestWorkerWorkflow(key: "", initialState: 0)
2626
)
2727

2828
let expectation = XCTestExpectation()
@@ -38,8 +38,120 @@ class WorkerTests: XCTestCase {
3838
disposable?.dispose()
3939
}
4040

41+
func testWorkflowUpdate() {
42+
// Create the workflow which causes the TaskTestWorker to run.
43+
let host = WorkflowHost(
44+
workflow: TaskTestWorkerWorkflow(key: "", initialState: 0)
45+
)
46+
47+
var expectation = XCTestExpectation()
48+
// Set to observe renderings
49+
// This expectation should be called after the TaskTestWorker runs and
50+
// updates the state.
51+
var disposable = host.rendering.signal.observeValues { rendering in
52+
expectation.fulfill()
53+
}
54+
55+
// Test to make sure the initial state of the workflow is correct.
56+
XCTAssertEqual(0, host.rendering.value)
57+
58+
// Wait for the worker to run.
59+
wait(for: [expectation], timeout: 1.0)
60+
// Test to make sure the rendering after the worker runs is correct.
61+
XCTAssertEqual(1, host.rendering.value)
62+
63+
disposable?.dispose()
64+
65+
expectation = XCTestExpectation()
66+
// Set to observe renderings
67+
// This expectation should be called after the workflow is updated.
68+
// After the host is updated with a new workflow instance the
69+
// initial state should be 1.
70+
disposable = host.rendering.signal.observeValues { rendering in
71+
expectation.fulfill()
72+
}
73+
74+
// Updated the workflow to a new initial state.
75+
host.update(workflow: TaskTestWorkerWorkflow(key: "", initialState: 7))
76+
77+
// Wait for the workflow to render after being updated.
78+
wait(for: [expectation], timeout: 1.0)
79+
// Test to make sure the rendering matches the initial state.
80+
XCTAssertEqual(7, host.rendering.value)
81+
82+
expectation = XCTestExpectation()
83+
// Set to observe renderings
84+
// This expectation should be called when the worker runs.
85+
// The worker isEquivalent is false because we have changed the initialState.
86+
disposable = host.rendering.signal.observeValues { rendering in
87+
expectation.fulfill()
88+
}
89+
90+
// Wait for the worker to trigger a rendering.
91+
wait(for: [expectation], timeout: 1.0)
92+
// Check to make sure the rendering is correct.
93+
XCTAssertEqual(8, host.rendering.value)
94+
}
95+
96+
func testWorkflowKeyChange() {
97+
// Create the workflow which causes the TaskTestWorker to run.
98+
let host = WorkflowHost(
99+
workflow: TaskTestWorkerWorkflow(key: "", initialState: 0)
100+
)
101+
102+
var expectation = XCTestExpectation()
103+
// Set to observe renderings
104+
// This expectation should be called after the TaskTestWorker runs and
105+
// updates the state.
106+
var disposable = host.rendering.signal.observeValues { rendering in
107+
expectation.fulfill()
108+
}
109+
110+
// Test to make sure the initial state of the workflow is correct.
111+
XCTAssertEqual(0, host.rendering.value)
112+
113+
// Wait for the worker to run.
114+
wait(for: [expectation], timeout: 1.0)
115+
// Test to make sure the rendering after the worker runs is correct.
116+
XCTAssertEqual(1, host.rendering.value)
117+
118+
disposable?.dispose()
119+
120+
expectation = XCTestExpectation()
121+
// Set to observe renderings
122+
// This expectation should be called after the workflow is updated.
123+
// After the host is updated with a new workflow instance the
124+
// initial state should be 1.
125+
disposable = host.rendering.signal.observeValues { rendering in
126+
expectation.fulfill()
127+
}
128+
129+
// Update the workflow to a new key which should force the worker to run.
130+
host.update(workflow: TaskTestWorkerWorkflow(key: "key", initialState: 0))
131+
132+
// Wait for the workflow to render after being updated.
133+
wait(for: [expectation], timeout: 1.0)
134+
// Test to make sure the rendering matches the existing state
135+
// since the inititalState didn't change.
136+
XCTAssertEqual(1, host.rendering.value)
137+
138+
expectation = XCTestExpectation()
139+
// Set to observe renderings
140+
// This expectation should be called when the worker runs.
141+
// The worker should run because the key was changed for the workflow.
142+
disposable = host.rendering.signal.observeValues { rendering in
143+
expectation.fulfill()
144+
}
145+
146+
// Wait for the worker to trigger a rendering.
147+
wait(for: [expectation], timeout: 1.0)
148+
// Check to make sure the rendering is correct.
149+
// The worker adds one to the initialState so this should be 1.
150+
XCTAssertEqual(1, host.rendering.value)
151+
}
152+
41153
func testExpectedWorker() {
42-
TaskTestWorkerWorkflow(key: "123")
154+
TaskTestWorkerWorkflow(key: "123", initialState: 0)
43155
.renderTester()
44156
.expectWorkflow(
45157
type: WorkerWorkflow<TaskTestWorker>.self,
@@ -127,11 +239,11 @@ private struct TaskTestWorkerWorkflow: Workflow {
127239
typealias Rendering = Int
128240

129241
let key: String
130-
131-
func makeInitialState() -> Int { 0 }
242+
let initialState: Int
243+
func makeInitialState() -> Int { initialState }
132244

133245
func render(state: Int, context: RenderContext<TaskTestWorkerWorkflow>) -> Int {
134-
TaskTestWorker()
246+
TaskTestWorker(initialState: initialState)
135247
.mapOutput { output in
136248
AnyWorkflowAction { state in
137249
state = output
@@ -141,18 +253,27 @@ private struct TaskTestWorkerWorkflow: Workflow {
141253
.running(in: context, key: key)
142254
return state
143255
}
256+
257+
func workflowDidChange(from previousWorkflow: TaskTestWorkerWorkflow, state: inout Int) {
258+
if previousWorkflow.initialState != initialState {
259+
state = initialState
260+
}
261+
}
144262
}
145263

146264
private struct TaskTestWorker: Worker {
147265
typealias Output = Int
148-
266+
let initialState: Int
267+
149268
func run() async -> Int {
150269
do {
151270
try await Task.sleep(nanoseconds: 10000000)
152271
} catch {}
153272

154-
return 1
273+
return initialState + 1
155274
}
156275

157-
func isEquivalent(to otherWorker: TaskTestWorker) -> Bool { true }
276+
func isEquivalent(to otherWorker: TaskTestWorker) -> Bool {
277+
return otherWorker.initialState == initialState
278+
}
158279
}

0 commit comments

Comments
 (0)