Skip to content

Commit 84c06cd

Browse files
committed
feat(031-fix-the-2-remaining-test-failures-in-testwfcaserun): implement Fix the 2 remaining test failures in test/wf_case_runner_tests.erl:
1 parent ed0bc7f commit 84c06cd

9 files changed

Lines changed: 800 additions & 3 deletions

File tree

.wreckit/items/025-fix-the-remaining-6-test-failures-and-7-cancelled-/item.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
"schema_version": 1,
33
"id": "025-fix-the-remaining-6-test-failures-and-7-cancelled-",
44
"title": "Fix the remaining 6 test failures and 7 cancelled tests in rebar3 eunit. The specific failures are:",
5-
"state": "critique",
5+
"state": "done",
66
"overview": "",
7-
"branch": null,
7+
"branch": "wreckit/025-fix-the-remaining-6-test-failures-and-7-cancelled-",
88
"pr_url": null,
99
"pr_number": null,
1010
"last_error": null,
1111
"created_at": "2026-02-11T20:24:09.596Z",
12-
"updated_at": "2026-02-11T20:39:33.928Z"
12+
"updated_at": "2026-02-11T20:39:36.323Z",
13+
"rollback_sha": "7d44f12119f3117a05b1e6a0bb803f507b631478",
14+
"completed_at": "2026-02-11T20:39:36.323Z"
1315
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"schema_version": 1,
3+
"id": "031-fix-the-2-remaining-test-failures-in-testwfcaserun",
4+
"title": "Fix the 2 remaining test failures in test/wf_case_runner_tests.erl:",
5+
"state": "critique",
6+
"overview": "",
7+
"branch": null,
8+
"pr_url": null,
9+
"pr_number": null,
10+
"last_error": null,
11+
"created_at": "2026-02-11T20:40:39.996Z",
12+
"updated_at": "2026-02-11T20:49:23.375Z"
13+
}
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
# Fix the 2 remaining test failures in test/wf_case_runner_tests.erl: Implementation Plan
2+
3+
## Implementation Plan Title
4+
5+
Fix State Transition and Status Reporting in wf_case_runner gen_statem
6+
7+
## Overview
8+
9+
This plan fixes two bugs in the `wf_case_runner` gen_statem implementation that cause test failures:
10+
11+
1. **State Transition Bug**: The `enter_state/2` function returns a plain `StateData` record instead of a gen_statem tuple, preventing proper state transitions from `running` to `done`.
12+
2. **Status Map Inconsistency**: The `done/3` state handler returns a status map missing `ip` and `step_count` keys that tests expect.
13+
14+
These bugs prevent workflow cases from completing properly and cause the `status_query_test` to fail when querying the `ip` key.
15+
16+
## Current State
17+
18+
**Test Failures:**
19+
- `running_to_done_completion_test/1` (line 46-56): Expects case to reach `done` state within 100ms, but it remains in `running` state indefinitely
20+
- `status_query_test/1` (line 82-88): Expects status map to contain `ip` and `step_count` keys, but `done/3` handler returns map with only `state` and `result`
21+
22+
**Root Causes:**
23+
24+
**Issue 1: `enter_state/2` Returns Wrong Type** (`src/wf_case_runner.erl:391-401`)
25+
```erlang
26+
enter_state(TargetState, StateData) ->
27+
case TargetState of
28+
done -> StateData; %% Returns StateData instead of {next_state, done, StateData}
29+
cancelled -> StateData; %% Returns StateData instead of {next_state, cancelled, StateData}
30+
_Other -> StateData
31+
end.
32+
```
33+
34+
The function is called from `execute_quantum/1` (lines 358, 365) when execution completes. The return value is used directly as the gen_statem return value:
35+
```erlang
36+
running(cast, {signal, Signal}, StateData) ->
37+
...
38+
{next_state, running, execute_quantum(StateData)}.
39+
```
40+
41+
When `execute_quantum/1` calls `enter_state(done, StateData1)`, it returns `StateData1` (a record), but the caller expects a gen_statem tuple like `{next_state, done, StateData1}`. This causes the gen_statem to remain in the `running` state instead of transitioning to `done`.
42+
43+
**Issue 2: `done/3` Status Handler Missing Fields** (`src/wf_case_runner.erl:295-298`)
44+
```erlang
45+
done({call, From}, status, StateData) ->
46+
StatusInfo = #{state => done, result => StateData#state_data.result},
47+
{keep_state_and_data, [{reply, From, {ok, done, StatusInfo}}]}.
48+
```
49+
50+
The `running/3` state handler (lines 205-214) returns a richer status map:
51+
```erlang
52+
running({call, From}, status, StateData) ->
53+
ExecState = StateData#state_data.exec_state,
54+
StatusInfo = #{
55+
state => running,
56+
ip => ExecState#exec_state.ip,
57+
step_count => ExecState#exec_state.step_count,
58+
status => ExecState#exec_state.status
59+
},
60+
{keep_state_and_data, [{reply, From, {ok, running, StatusInfo}}]}.
61+
```
62+
63+
The test at line 86 asserts `maps:is_key(ip, StatusInfo)`, which fails when the case is in the `done` state because the status map lacks the `ip` key.
64+
65+
## Desired End State
66+
67+
**Functional Requirements:**
68+
1. Workflow cases executing the simple task bytecode (`[{'TASK_EXEC', mock_task}, {'DONE'}]`) transition from `running` to `done` state upon completion
69+
2. Status queries return consistent map structure across all states (`running`, `done`, `cancelled`)
70+
3. All 5 tests in `wf_case_runner_tests` pass:
71+
- `running_to_done_completion_test`
72+
- `signal_delivery_test`
73+
- `cancel_test`
74+
- `status_query_test`
75+
- `trace_configuration_test`
76+
77+
**Verification:**
78+
```bash
79+
rebar3 compile
80+
rebar3 eunit --module=wf_case_runner_tests
81+
```
82+
83+
Expected output: All tests pass with 0 failures.
84+
85+
### Key Discoveries:
86+
87+
- **`execute_quantum/1` Return Type is Wrong** (`src/wf_case_runner.erl:340`): The spec says it returns `state_data()`, but it actually returns gen_statem tuples like `{next_state, waiting_effect, StateData, Actions}` in the yield case (lines 376, 380). This is actually correct for the callers, but the spec needs updating.
88+
89+
- **`enter_state/2` is a State Transition Helper**: The function is designed to abstract state transitions, but it's currently implemented as a no-op that always returns `StateData`. It should return gen_statem tuples for state changes.
90+
91+
- **Status Map Consistency Pattern**: The `handle_common_event/3` function (lines 308-316) provides a fallback status handler that returns consistent maps with `ip`, `step_count`, and `status` keys. State-specific handlers should follow this pattern.
92+
93+
- **Test Timing is Sufficient**: The mock task completes immediately (no I/O or blocking operations), so 100ms is more than adequate for the case to complete. The failure is due to the state transition bug, not timing.
94+
95+
## What We're NOT Doing
96+
97+
1. **Fixing the function spec for `execute_quantum/1`**: While the spec is technically wrong (it says `state_data()` but returns gen_statem tuples), fixing the spec is not required to make the tests pass. The spec can be updated in a separate cleanup task.
98+
99+
2. **Refactoring status query handling**: Currently there are three places where status queries are handled:
100+
- `running/3` (lines 205-214)
101+
- `done/3` (lines 295-298)
102+
- `cancelled/3` (lines 282-285)
103+
- `handle_common_event/3` (lines 308-316, fallback)
104+
105+
We're NOT consolidating these into a single handler. We're only fixing the `done/3` handler to return the required fields.
106+
107+
3. **Adding automatic termination for completed cases**: The `done` state currently keeps the process alive (returns `{keep_state_and_data, ...}`). We're NOT changing this behavior to `{stop, normal, ...}` as that's a design decision outside the scope of this bug fix.
108+
109+
4. **Fixing ETS table name collision risk**: The `trace_configuration_test` uses table name `wf_trace_events`. If multiple tests run concurrently, there could be ETS table name conflicts. This is a known issue but not causing test failures currently, so it's out of scope.
110+
111+
5. **Implementing retry logic or explicit synchronization**: The 100ms sleep in tests is sufficient for the mock task. We're NOT adding complex synchronization primitives.
112+
113+
## Implementation Approach
114+
115+
The fix consists of two minimal changes to `src/wf_case_runner.erl`:
116+
117+
**Phase 1: Fix `enter_state/2` to return gen_statem tuples**
118+
- Change the function to return `{next_state, TargetState, StateData}` for state transitions
119+
- Keep the function signature the same
120+
- This fixes the root cause of `running_to_done_completion_test` failure
121+
122+
**Phase 2: Fix `done/3` status handler to include `ip` and `step_count`**
123+
- Extract fields from `ExecState` in `StateData`
124+
- Add `ip`, `step_count`, and `status` to the status map
125+
- Keep the `result` field for backward compatibility
126+
- This fixes the `status_query_test` failure
127+
128+
The fixes are minimal, targeted, and follow existing patterns in the codebase. Phase 1 must be completed before Phase 2, but both phases can be tested independently.
129+
130+
---
131+
132+
## Phases
133+
134+
### Phase 1: Fix State Transition in `enter_state/2`
135+
136+
#### Overview
137+
138+
Fix the `enter_state/2` function to return proper gen_statem tuples instead of plain `StateData` records. This allows the gen_statem to transition from `running` to `done` state when execution completes.
139+
140+
#### Changes Required:
141+
142+
##### 1. Update `enter_state/2` function
143+
144+
**File**: `src/wf_case_runner.erl:391-401`
145+
146+
**Current Implementation:**
147+
```erlang
148+
%% @doc Transition to new state
149+
enter_state(TargetState, StateData) ->
150+
%% Set state timeout if not done/cancelled
151+
case TargetState of
152+
done ->
153+
StateData;
154+
cancelled ->
155+
StateData;
156+
_Other ->
157+
StateData
158+
end.
159+
```
160+
161+
**New Implementation:**
162+
```erlang
163+
%% @doc Transition to new state
164+
enter_state(done, StateData) ->
165+
{next_state, done, StateData};
166+
enter_state(cancelled, StateData) ->
167+
{next_state, cancelled, StateData};
168+
enter_state(_Other, StateData) ->
169+
{keep_state, StateData}.
170+
```
171+
172+
**Rationale:**
173+
- The function is called from `execute_quantum/1` when execution completes (lines 358, 365)
174+
- The return value is used as the gen_statem return value in state handlers
175+
- Must return `{next_state, TargetState, StateData}` for actual state transitions
176+
- Must return `{keep_state, StateData}` for staying in current state
177+
- Pattern matching on `TargetState` is clearer than case statement
178+
179+
#### Success Criteria:
180+
181+
##### Automated Verification:
182+
183+
- [ ] Test passes: `rebar3 eunit --module=wf_case_runner_tests --test=running_to_done_completion_test`
184+
- [ ] Compilation succeeds: `rebar3 compile`
185+
- [ ] No dialyzer warnings: `rebar3 dialyzer`
186+
187+
##### Manual Verification:
188+
189+
- [ ] Inspect test output to confirm case reaches `done` state
190+
- [ ] Verify no other tests are broken by the change
191+
- [ ] Confirm state transition happens within 100ms timeout
192+
193+
**Note**: After this phase, `running_to_done_completion_test` should pass, but `status_query_test` may still fail (that's Phase 2).
194+
195+
---
196+
197+
### Phase 2: Fix Status Map Consistency in `done/3`
198+
199+
#### Overview
200+
201+
Update the `done/3` state handler to return a status map with the same structure as other state handlers, including `ip`, `step_count`, and `status` fields.
202+
203+
#### Changes Required:
204+
205+
##### 1. Update `done/3` status query handler
206+
207+
**File**: `src/wf_case_runner.erl:295-298`
208+
209+
**Current Implementation:**
210+
```erlang
211+
done({call, From}, status, StateData) ->
212+
%% Return done status with result
213+
StatusInfo = #{state => done, result => StateData#state_data.result},
214+
{keep_state_and_data, [{reply, From, {ok, done, StatusInfo}}]}.
215+
```
216+
217+
**New Implementation:**
218+
```erlang
219+
done({call, From}, status, StateData) ->
220+
%% Return done status with result and execution info
221+
ExecState = StateData#state_data.exec_state,
222+
StatusInfo = #{
223+
state => done,
224+
ip => ExecState#exec_state.ip,
225+
step_count => ExecState#exec_state.step_count,
226+
status => ExecState#exec_state.status,
227+
result => StateData#state_data.result
228+
},
229+
{keep_state_and_data, [{reply, From, {ok, done, StatusInfo}}]}.
230+
```
231+
232+
**Rationale:**
233+
- Aligns with `running/3` handler (lines 205-214) which includes `ip`, `step_count`, `status`
234+
- Maintains backward compatibility by keeping `result` field
235+
- Test at line 86 checks `maps:is_key(ip, StatusInfo)`
236+
- Test at line 87 checks `maps:is_key(step_count, StatusInfo)`
237+
- Pattern matches the fallback `handle_common_event/3` structure (lines 308-316)
238+
239+
#### Success Criteria:
240+
241+
##### Automated Verification:
242+
243+
- [ ] Test passes: `rebar3 eunit --module=wf_case_runner_tests --test=status_query_test`
244+
- [ ] Full test suite passes: `rebar3 eunit --module=wf_case_runner_tests`
245+
- [ ] All 5 tests in module pass:
246+
- `running_to_done_completion_test`
247+
- `signal_delivery_test`
248+
- `cancel_test`
249+
- `status_query_test`
250+
- `trace_configuration_test`
251+
252+
##### Manual Verification:
253+
254+
- [ ] Verify status query returns map with all expected keys: `state`, `ip`, `step_count`, `status`, `result`
255+
- [ ] Confirm no regressions in other tests
256+
- [ ] Check that `ip` value is correct (should be 2 after executing both opcodes)
257+
- [ ] Check that `step_count` value is correct (should be 2)
258+
259+
**Note**: After this phase, both failing tests should pass and the full test suite should succeed.
260+
261+
---
262+
263+
## Testing Strategy
264+
265+
### Unit Tests:
266+
267+
The existing EUnit tests in `test/wf_case_runner_tests.erl` provide coverage for the fixes:
268+
269+
1. **`running_to_done_completion_test/1`**: Verifies state transition from `running` to `done`
270+
- Tests Phase 1 fix
271+
- Waits 100ms for completion
272+
- Queries status and expects `{ok, done, StatusInfo}`
273+
274+
2. **`status_query_test/1`**: Verifies status map contains required keys
275+
- Tests Phase 2 fix
276+
- Queries status immediately (case may still be in `running` or `done`)
277+
- Asserts `ip` and `step_count` keys exist
278+
279+
3. **`signal_delivery_test/1`**: Regression test for signal handling
280+
- Ensures Phase 1 changes don't break cast event handling
281+
282+
4. **`cancel_test/1`**: Regression test for cancellation
283+
- Ensures Phase 1 changes don't break cancel flow
284+
- Tests both cancel from `running` and `enter_state(cancelled, ...)` path
285+
286+
5. **`trace_configuration_test/1`**: Regression test for trace configuration
287+
- Ensures changes don't break trace state updates
288+
289+
### Integration Tests:
290+
291+
Run the full test suite to ensure no regressions:
292+
293+
```bash
294+
rebar3 eunit
295+
```
296+
297+
This will execute all test modules and catch any unintended side effects.
298+
299+
### Manual Testing Steps:
300+
301+
1. **Verify state transition timing:**
302+
- Start a wf_case_runner with simple task bytecode
303+
- Query status immediately (should be `running`)
304+
- Query status after 100ms (should be `done`)
305+
- Confirm transition happens within timeout
306+
307+
2. **Verify status map consistency:**
308+
- Query status in `running` state, check keys: `state`, `ip`, `step_count`, `status`
309+
- Query status in `done` state, check keys: `state`, `ip`, `step_count`, `status`, `result`
310+
- Query status in `cancelled` state, check keys: `state`
311+
- Verify `ip` increments correctly (0 → 1 → 2)
312+
- Verify `step_count` increments correctly (0 → 1 → 2)
313+
314+
3. **Verify error handling:**
315+
- Start case with invalid bytecode (should fail gracefully)
316+
- Cancel case during execution (should reach `cancelled` state)
317+
- Timeout case (should transition to `cancelled` with timeout reason)
318+
319+
## Migration Notes
320+
321+
No data migration or API changes required. The fixes are internal implementation details that don't affect the external API:
322+
323+
- `wf_case_runner:start_link/3` - unchanged
324+
- `wf_case_runner:signal/2` - unchanged
325+
- `wf_case_runner:cancel/1` - unchanged
326+
- `wf_case_runner:set_trace/2` - unchanged
327+
- `wf_case_runner:status/1` - unchanged (still returns `{ok, StateName, StatusInfo}`)
328+
329+
The only visible change is that status maps now include additional fields (`ip`, `step_count`, `status`) in the `done` state, which is backward compatible (adding fields to a map doesn't break existing code).
330+
331+
## References
332+
333+
- Research: `/Users/speed/wf-substrate/.wreckit/items/031-fix-the-2-remaining-test-failures-in-testwfcaserun/research.md`
334+
- Test file: `/Users/speed/wf-substrate/test/wf_case_runner_tests.erl`
335+
- Implementation: `/Users/speed/wf-substrate/src/wf_case_runner.erl`
336+
- Related: Item 025 (fixed scheduler policy selection in `wf_sched:select_action/2`)
337+
338+
### Key Code Locations:
339+
340+
- **Bug 1**: `src/wf_case_runner.erl:391-401` - `enter_state/2` function
341+
- **Bug 2**: `src/wf_case_runner.erl:295-298` - `done/3` status handler
342+
- **Test 1**: `test/wf_case_runner_tests.erl:46-56` - `running_to_done_completion_test/1`
343+
- **Test 2**: `test/wf_case_runner_tests.erl:82-88` - `status_query_test/1`
344+
- **Caller**: `src/wf_case_runner.erl:340-389` - `execute_quantum/1` function
345+
- **Pattern**: `src/wf_case_runner.erl:205-214` - `running/3` status handler (correct pattern)

0 commit comments

Comments
 (0)