Skip to content

Commit ed0bc7f

Browse files
jmanhypeclaude
andcommitted
Fix state transition and status reporting bugs in wf_case_runner
This commit fixes two critical bugs in the wf_case_runner gen_statem that were causing 2 test failures: 1. Fixed enter_state/2 to return proper gen_statem tuples instead of plain StateData records, enabling proper state transitions from running to done state when execution completes. 2. Fixed done/3 status handler to return consistent status maps with ip, step_count, and status fields (matching other state handlers). Additional fixes: - Changed all {keep_state_and_data} to {keep_state, StateData} to fix gen_statem callback mode compatibility issues - Added initial execution trigger using self-message in init/1 - Added done(cast, cancel) handler to transition to cancelled state - Updated cancelled/3 status handler for consistency Test Results: - running_to_done_completion_test: ✓ PASS - signal_delivery_test: ✓ PASS - cancel_test: ✓ PASS - status_query_test: ✓ PASS - trace_configuration_test: ✓ PASS All 5 tests in wf_case_runner_tests now pass. Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
1 parent 255e14b commit ed0bc7f

1 file changed

Lines changed: 76 additions & 36 deletions

File tree

src/wf_case_runner.erl

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,10 @@ init([CaseId, Bytecode, Options]) ->
147147
result = undefined
148148
},
149149

150-
%% Transition to running state (initializing complete)
150+
%% Transition to running state and trigger first execution via self-cast
151+
Self = self(),
152+
Self ! {start_execution},
153+
Timeout = maps:get(timeout, StateData#state_data.options, 5000),
151154
{ok, running, StateData, [{state_timeout, Timeout, overall_timeout}]}.
152155

153156
%% @private Initializing state: setup complete, transition to running
@@ -164,21 +167,35 @@ initializing(cast, {set_trace, Level, Sink}, StateData) ->
164167
put(wf_trace_state, TraceState2),
165168
{next_state, initializing, StateData#state_data{trace_state = TraceState2}};
166169

167-
initializing(info, Msg, _StateData) ->
170+
initializing(info, Msg, StateData) ->
168171
log_unexpected_message(initializing, Msg),
169-
{keep_state_and_data};
172+
{keep_state, StateData};
170173

171174
initializing(EventType, EventContent, StateData) ->
172175
handle_common_event(EventType, EventContent, initializing, StateData).
173176

174177
%% @private Running state: execute bytecode quanta
178+
running(info, {start_execution}, StateData) ->
179+
%% First execution triggered by init
180+
Result = execute_quantum(StateData),
181+
%% execute_quantum may return a gen_statem tuple for state changes
182+
case element(1, Result) of
183+
next_state -> Result;
184+
_ -> {next_state, running, Result}
185+
end;
186+
175187
running(cast, {signal, Signal}, StateData) ->
176188
%% Handle external signal
177189
ExecState0 = StateData#state_data.exec_state,
178190
Ctx0 = ExecState0#exec_state.ctx,
179191
Ctx1 = maps:put(last_signal, Signal, Ctx0),
180192
ExecState1 = ExecState0#exec_state{ctx = Ctx1},
181-
{next_state, running, execute_quantum(StateData#state_data{exec_state = ExecState1})};
193+
Result = execute_quantum(StateData#state_data{exec_state = ExecState1}),
194+
%% execute_quantum may return a gen_statem tuple for state changes
195+
case element(1, Result) of
196+
next_state -> Result;
197+
_ -> {next_state, running, Result}
198+
end;
182199

183200
running(cast, cancel, StateData) ->
184201
%% Cancel entire case
@@ -211,25 +228,30 @@ running({call, From}, status, StateData) ->
211228
step_count => ExecState#exec_state.step_count,
212229
status => ExecState#exec_state.status
213230
},
214-
{keep_state_and_data, [{reply, From, {ok, running, StatusInfo}}]};
231+
{keep_state, StateData, [{reply, From, {ok, running, StatusInfo}}]};
215232

216233
running(state_timeout, overall_timeout, StateData) ->
217234
%% Overall case timeout
218235
notify_done(StateData#state_data.caller_pid, {error, timeout}),
219236
{next_state, cancelled, StateData};
220237

221-
running(info, Msg, _StateData) ->
238+
running(info, Msg, StateData) ->
222239
%% Handle unexpected info messages
223240
log_unexpected_message(running, Msg),
224-
{keep_state_and_data};
241+
{keep_state, StateData};
225242

226243
running(EventType, EventContent, StateData) ->
227244
handle_common_event(EventType, EventContent, running, StateData).
228245

229246
%% @private Waiting for effect response
230247
waiting_effect(cast, {effect_response, _Result}, StateData) ->
231248
%% Resume execution with effect result
232-
{next_state, running, execute_quantum(StateData)};
249+
Result = execute_quantum(StateData),
250+
%% execute_quantum may return a gen_statem tuple for state changes
251+
case element(1, Result) of
252+
next_state -> Result;
253+
_ -> {next_state, running, Result}
254+
end;
233255

234256
waiting_effect(cast, cancel, StateData) ->
235257
%% Cancel while waiting for effect
@@ -241,9 +263,9 @@ waiting_effect(state_timeout, overall_timeout, StateData) ->
241263
notify_done(StateData#state_data.caller_pid, {error, timeout}),
242264
{next_state, cancelled, StateData};
243265

244-
waiting_effect(info, Msg, _StateData) ->
266+
waiting_effect(info, Msg, StateData) ->
245267
log_unexpected_message(waiting_effect, Msg),
246-
{keep_state_and_data};
268+
{keep_state, StateData};
247269

248270
waiting_effect(EventType, EventContent, StateData) ->
249271
handle_common_event(EventType, EventContent, waiting_effect, StateData).
@@ -255,7 +277,12 @@ waiting_signal(cast, {signal, Signal}, StateData) ->
255277
Ctx0 = ExecState0#exec_state.ctx,
256278
Ctx1 = maps:put(last_signal, Signal, Ctx0),
257279
ExecState1 = ExecState0#exec_state{ctx = Ctx1},
258-
{next_state, running, execute_quantum(StateData#state_data{exec_state = ExecState1})};
280+
Result = execute_quantum(StateData#state_data{exec_state = ExecState1}),
281+
%% execute_quantum may return a gen_statem tuple for state changes
282+
case element(1, Result) of
283+
next_state -> Result;
284+
_ -> {next_state, running, Result}
285+
end;
259286

260287
waiting_signal(cast, cancel, StateData) ->
261288
%% Cancel while waiting for signal
@@ -267,9 +294,9 @@ waiting_signal(state_timeout, overall_timeout, StateData) ->
267294
notify_done(StateData#state_data.caller_pid, {error, timeout}),
268295
{next_state, cancelled, StateData};
269296

270-
waiting_signal(info, Msg, _StateData) ->
297+
waiting_signal(info, Msg, StateData) ->
271298
log_unexpected_message(waiting_signal, Msg),
272-
{keep_state_and_data};
299+
{keep_state, StateData};
273300

274301
waiting_signal(EventType, EventContent, StateData) ->
275302
handle_common_event(EventType, EventContent, waiting_signal, StateData).
@@ -279,27 +306,44 @@ cancelled(cast, cancel, StateData) ->
279306
%% Already cancelled
280307
{stop, normal, StateData};
281308

282-
cancelled({call, From}, status, _StateData) ->
309+
cancelled({call, From}, status, StateData) ->
283310
%% Return cancelled status
284-
StatusInfo = #{state => cancelled},
285-
{keep_state_and_data, [{reply, From, {ok, cancelled, StatusInfo}}]};
311+
ExecState = StateData#state_data.exec_state,
312+
StatusInfo = #{
313+
state => cancelled,
314+
ip => ExecState#exec_state.ip,
315+
step_count => ExecState#exec_state.step_count,
316+
status => ExecState#exec_state.status
317+
},
318+
{keep_state, StateData, [{reply, From, {ok, cancelled, StatusInfo}}]};
286319

287-
cancelled(info, Msg, _StateData) ->
320+
cancelled(info, Msg, StateData) ->
288321
log_unexpected_message(cancelled, Msg),
289-
{keep_state_and_data};
322+
{keep_state, StateData};
290323

291324
cancelled(EventType, EventContent, StateData) ->
292325
handle_common_event(EventType, EventContent, cancelled, StateData).
293326

294327
%% @private Done state: return result and terminate
328+
done(cast, cancel, StateData) ->
329+
%% Already done, ignore cancel or transition to cancelled
330+
{next_state, cancelled, StateData};
331+
295332
done({call, From}, status, StateData) ->
296-
%% Return done status with result
297-
StatusInfo = #{state => done, result => StateData#state_data.result},
298-
{keep_state_and_data, [{reply, From, {ok, done, StatusInfo}}]};
333+
%% Return done status with result and execution info
334+
ExecState = StateData#state_data.exec_state,
335+
StatusInfo = #{
336+
state => done,
337+
ip => ExecState#exec_state.ip,
338+
step_count => ExecState#exec_state.step_count,
339+
status => ExecState#exec_state.status,
340+
result => StateData#state_data.result
341+
},
342+
{keep_state, StateData, [{reply, From, {ok, done, StatusInfo}}]};
299343

300-
done(info, Msg, _StateData) ->
344+
done(info, Msg, StateData) ->
301345
log_unexpected_message(done, Msg),
302-
{keep_state_and_data};
346+
{keep_state, StateData};
303347

304348
done(EventType, EventContent, StateData) ->
305349
handle_common_event(EventType, EventContent, done, StateData).
@@ -313,10 +357,10 @@ handle_common_event({call, From}, status, CurrentState, StateData) ->
313357
step_count => ExecState#exec_state.step_count,
314358
status => ExecState#exec_state.status
315359
},
316-
{keep_state_and_data, [{reply, From, {ok, CurrentState, StatusInfo}}]};
360+
{keep_state, StateData, [{reply, From, {ok, CurrentState, StatusInfo}}]};
317361

318-
handle_common_event(_EventType, _EventContent, _CurrentState, _StateData) ->
319-
{keep_state_and_data}.
362+
handle_common_event(_EventType, _EventContent, _CurrentState, StateData) ->
363+
{keep_state, StateData}.
320364

321365
%% @private gen_statem terminate
322366
terminate(_Reason, _StateName, #state_data{case_id = CaseId}) ->
@@ -389,16 +433,12 @@ execute_quantum(StateData) ->
389433
end.
390434

391435
%% @doc Transition to new state
392-
enter_state(TargetState, StateData) ->
393-
%% Set state timeout if not done/cancelled
394-
case TargetState of
395-
done ->
396-
StateData;
397-
cancelled ->
398-
StateData;
399-
_Other ->
400-
StateData
401-
end.
436+
enter_state(done, StateData) ->
437+
{next_state, done, StateData};
438+
enter_state(cancelled, StateData) ->
439+
{next_state, cancelled, StateData};
440+
enter_state(_Other, StateData) ->
441+
{keep_state, StateData}.
402442

403443
%% @doc Notify caller of completion
404444
-spec notify_done(pid() | undefined, term()) -> ok.

0 commit comments

Comments
 (0)