"""Step definitions for subplan execution defensive code-path coverage. Targets the following uncovered lines in subplan_execution_service.py: - 298-303: _execute_parallel generic Exception handler - 331-334: _execute_parallel safety-net for uncaptured futures - 381: _execute_dependency_ordered empty-ready break - 573-575: _execute_wave CancelledError handler - 576-578: _execute_wave generic Exception handler """ from __future__ import annotations from concurrent.futures import CancelledError from unittest.mock import patch from behave import given, then, when from behave.runner import Context from cleveragents.application.services.subplan_execution_service import ( SubplanExecutionOutput, SubplanExecutionService, ) from cleveragents.domain.models.core.plan import ( ExecutionMode, ProcessingState, SubplanConfig, SubplanStatus, ) # Stable fake subplan IDs (valid ULIDs) _P1 = "01HGZ6FE0AQDYTR4BXVQZ6EA00" _P2 = "01HGZ6FE0AQDYTR4BXVQZ6EB00" _P3 = "01HGZ6FE0AQDYTR4BXVQZ6EC00" def _make_status(subplan_id: str) -> SubplanStatus: return SubplanStatus(subplan_id=subplan_id, action_name="test/action") def _ok_output(status: SubplanStatus) -> SubplanExecutionOutput: return SubplanExecutionOutput( subplan_id=status.subplan_id, success=True, files={f"src/{status.subplan_id[-4:]}.py": f"# ok {status.subplan_id}\n"}, files_changed=1, changeset_summary="ok", ) # ======================================================================== # Scenario: Parallel execution catches generic exception (lines 298-303) # ======================================================================== @given("a subplan execution service in parallel mode with 2 subplans") def step_parallel_2_subplans(context: Context) -> None: context.cb_statuses = [_make_status(_P1), _make_status(_P2)] context.cb_config = SubplanConfig( execution_mode=ExecutionMode.PARALLEL, max_parallel=2, ) @given( "the internal retry method is patched to raise a RuntimeError for the first subplan" ) def step_patch_retry_raises_runtime(context: Context) -> None: """Record that we want _execute_one_with_retry to raise for _P1.""" context.raise_for_subplan_id = _P1 context.raise_exception = RuntimeError("unexpected internal failure") @when("parallel execution is invoked") def step_invoke_parallel(context: Context) -> None: service = SubplanExecutionService( config=context.cb_config, executor_fn=_ok_output, ) target_id = context.raise_for_subplan_id target_exc = context.raise_exception original_method = service._execute_one_with_retry def _patched_retry(status: SubplanStatus): if status.subplan_id == target_id: raise target_exc return original_method(status) with patch.object(service, "_execute_one_with_retry", side_effect=_patched_retry): context.cb_result = service._execute_parallel(context.cb_statuses) @then("the first subplan should be marked ERRORED with the RuntimeError message") def step_first_errored_runtime(context: Context) -> None: updated_statuses, _outputs = context.cb_result p1_status = next(s for s in updated_statuses if s.subplan_id == _P1) assert p1_status.status == ProcessingState.ERRORED, ( f"Expected ERRORED, got {p1_status.status}" ) assert "unexpected internal failure" in (p1_status.error or ""), ( f"Expected RuntimeError message in error, got: {p1_status.error}" ) @then("the second subplan should complete normally") def step_second_completes(context: Context) -> None: updated_statuses, _outputs = context.cb_result p2_status = next(s for s in updated_statuses if s.subplan_id == _P2) assert p2_status.status == ProcessingState.COMPLETE, ( f"Expected COMPLETE, got {p2_status.status}" ) # ======================================================================== # Scenario: Parallel safety-net for uncaptured futures (lines 331-334) # ======================================================================== @given("a subplan execution service in parallel fail-fast mode with 3 subplans") def step_parallel_failfast_3(context: Context) -> None: context.cb_statuses = [_make_status(_P1), _make_status(_P2), _make_status(_P3)] context.cb_config = SubplanConfig( execution_mode=ExecutionMode.PARALLEL, max_parallel=3, fail_fast=True, ) @given("as_completed is patched to skip the third future and the first subplan fails") def step_patch_as_completed_skip_third(context: Context) -> None: """Record that the first subplan will fail and as_completed should skip _P3's future.""" context.skip_subplan_id = _P3 context.fail_subplan_id = _P1 @when("parallel execution is invoked with patched as_completed") def step_invoke_parallel_patched_as_completed(context: Context) -> None: skip_id = context.skip_subplan_id fail_id = context.fail_subplan_id def _failing_executor(status: SubplanStatus) -> SubplanExecutionOutput: if status.subplan_id == fail_id: return SubplanExecutionOutput( subplan_id=status.subplan_id, success=False, error="ValidationError: forced failure", ) return _ok_output(status) service = SubplanExecutionService( config=context.cb_config, executor_fn=_failing_executor, ) # We need to intercept as_completed to skip the future for _P3. # We patch it at the module level where it's imported. import cleveragents.application.services.subplan_execution_service as svc_mod original_as_completed = svc_mod.as_completed def _filtered_as_completed(fs, **kwargs): """Yield all futures except the one for the skipped subplan.""" for future in original_as_completed(fs, **kwargs): # fs is the future_to_id dict; check if this future maps to skip_id if isinstance(fs, dict) and fs.get(future) == skip_id: continue yield future with patch.object(svc_mod, "as_completed", _filtered_as_completed): context.cb_result = service._execute_parallel(context.cb_statuses) @then("the third subplan should be marked CANCELLED by the safety net") def step_third_cancelled_safety_net(context: Context) -> None: updated_statuses, _outputs = context.cb_result p3_status = next(s for s in updated_statuses if s.subplan_id == _P3) assert p3_status.status == ProcessingState.CANCELLED, ( f"Expected CANCELLED for P3 (safety net), got {p3_status.status}" ) @then("the results should contain all 3 subplans") def step_results_contain_all_3(context: Context) -> None: updated_statuses, _outputs = context.cb_result ids = {s.subplan_id for s in updated_statuses} assert _P1 in ids and _P2 in ids and _P3 in ids, ( f"Expected all 3 subplan IDs, got {ids}" ) # ======================================================================== # Scenario: Dependency-ordered empty ready list break (line 381) # ======================================================================== @given("a subplan execution service in dependency-ordered mode with 2 subplans") def step_dep_ordered_2(context: Context) -> None: context.cb_statuses = [_make_status(_P1), _make_status(_P2)] context.cb_config = SubplanConfig( execution_mode=ExecutionMode.DEPENDENCY_ORDERED, ) @given("topological sort is patched to accept a circular dependency graph") def step_patch_topo_sort(context: Context) -> None: """We'll provide a circular graph but patch _topological_sort to not raise.""" context.circular_graph = { _P1: [_P2], _P2: [_P1], } @when("dependency-ordered execution is invoked with the circular graph") def step_invoke_dep_ordered_circular(context: Context) -> None: service = SubplanExecutionService( config=context.cb_config, executor_fn=_ok_output, ) # Patch _topological_sort to accept the circular graph (return node_ids as-is) def _fake_topo_sort(node_ids, graph): return list(node_ids) with patch.object( SubplanExecutionService, "_topological_sort", staticmethod(_fake_topo_sort) ): context.cb_result = service._execute_dependency_ordered( context.cb_statuses, context.circular_graph ) @then("execution should complete without raising an error") def step_no_error(context: Context) -> None: # If we got here, no exception was raised assert context.cb_result is not None @then("the number of completed subplans should be less than 2") def step_fewer_completed(context: Context) -> None: updated_statuses, _outputs = context.cb_result # With a circular graph and patched topo sort, the while loop should break # when ready becomes empty. Neither _P1 nor _P2 can ever become ready because # each depends on the other (remaining_deps is never emptied). assert len(updated_statuses) < 2, ( f"Expected fewer than 2 completed statuses, got {len(updated_statuses)}" ) # ======================================================================== # Scenario: Wave CancelledError (lines 573-575) # ======================================================================== @given( "a subplan execution service in dependency-ordered mode with 3 independent subplans" ) def step_dep_ordered_3_independent(context: Context) -> None: context.cb_statuses = [_make_status(_P1), _make_status(_P2), _make_status(_P3)] context.cb_config = SubplanConfig( execution_mode=ExecutionMode.DEPENDENCY_ORDERED, max_parallel=3, ) # All independent (no dependencies) -> single wave of 3 context.dep_graph = {_P1: [], _P2: [], _P3: []} @given( "the internal retry method is patched to raise CancelledError " "for the second subplan in wave" ) def step_patch_retry_cancelled(context: Context) -> None: context.wave_cancel_subplan_id = _P2 @when("dependency-ordered execution is invoked for the wave scenario") def step_invoke_wave_cancelled(context: Context) -> None: cancel_id = context.wave_cancel_subplan_id service = SubplanExecutionService( config=context.cb_config, executor_fn=_ok_output, ) original_method = service._execute_one_with_retry def _patched_retry(status: SubplanStatus): if status.subplan_id == cancel_id: raise CancelledError("simulated cancellation") return original_method(status) with patch.object(service, "_execute_one_with_retry", side_effect=_patched_retry): context.cb_result = service._execute_dependency_ordered( context.cb_statuses, context.dep_graph ) @then("the second subplan should be marked CANCELLED") def step_second_cancelled(context: Context) -> None: updated_statuses, _outputs = context.cb_result p2_status = next(s for s in updated_statuses if s.subplan_id == _P2) assert p2_status.status == ProcessingState.CANCELLED, ( f"Expected CANCELLED, got {p2_status.status}" ) @then("the other subplans in the wave should complete successfully") def step_others_complete_wave(context: Context) -> None: updated_statuses, _outputs = context.cb_result for status in updated_statuses: if status.subplan_id == _P2: continue assert status.status == ProcessingState.COMPLETE, ( f"Expected COMPLETE for {status.subplan_id}, got {status.status}" ) # ======================================================================== # Scenario: Wave generic Exception (lines 576-578) # ======================================================================== @given( "a subplan execution service in dependency-ordered mode " "with 3 independent subplans for exception test" ) def step_dep_ordered_3_independent_exc(context: Context) -> None: context.cb_statuses = [_make_status(_P1), _make_status(_P2), _make_status(_P3)] context.cb_config = SubplanConfig( execution_mode=ExecutionMode.DEPENDENCY_ORDERED, max_parallel=3, ) context.dep_graph = {_P1: [], _P2: [], _P3: []} @given( "the internal retry method is patched to raise ValueError " "for the second subplan in wave" ) def step_patch_retry_valueerror(context: Context) -> None: context.wave_error_subplan_id = _P2 context.wave_error_exception = ValueError("bad value in wave") @when("dependency-ordered execution is invoked for the wave exception scenario") def step_invoke_wave_exception(context: Context) -> None: error_id = context.wave_error_subplan_id error_exc = context.wave_error_exception service = SubplanExecutionService( config=context.cb_config, executor_fn=_ok_output, ) original_method = service._execute_one_with_retry def _patched_retry(status: SubplanStatus): if status.subplan_id == error_id: raise error_exc return original_method(status) with patch.object(service, "_execute_one_with_retry", side_effect=_patched_retry): context.cb_result = service._execute_dependency_ordered( context.cb_statuses, context.dep_graph ) @then("the second subplan should be marked ERRORED with the ValueError message") def step_second_errored_valueerror(context: Context) -> None: updated_statuses, _outputs = context.cb_result p2_status = next(s for s in updated_statuses if s.subplan_id == _P2) assert p2_status.status == ProcessingState.ERRORED, ( f"Expected ERRORED, got {p2_status.status}" ) assert "bad value in wave" in (p2_status.error or ""), ( f"Expected ValueError message in error, got: {p2_status.error}" ) @then("the other wave subplans should still complete") def step_others_complete_wave_exc(context: Context) -> None: updated_statuses, _outputs = context.cb_result for status in updated_statuses: if status.subplan_id == _P2: continue assert status.status == ProcessingState.COMPLETE, ( f"Expected COMPLETE for {status.subplan_id}, got {status.status}" )