diff --git a/render_machine/actions/fix_conformance_test.py b/render_machine/actions/fix_conformance_test.py index 9a34357..dad9337 100644 --- a/render_machine/actions/fix_conformance_test.py +++ b/render_machine/actions/fix_conformance_test.py @@ -9,12 +9,17 @@ from render_machine.actions.base_action import BaseAction from render_machine.implementation_code_helpers import ImplementationCodeHelpers from render_machine.render_context import RenderContext -from render_machine.render_types import TestExecutionPhase +from render_machine.render_types import RenderError, TestExecutionPhase + +MAX_CONFORMANCE_TEST_FIX_ATTEMPTS = 20 +MAX_CONFORMANCE_TEST_RERENDER_ATTEMPTS = 1 class FixConformanceTest(BaseAction): IMPLEMENTATION_CODE_NOT_UPDATED = "implementation_code_not_updated" IMPLEMENTATION_CODE_UPDATED = "implementation_code_updated" + LIMIT_EXCEEDED_OUTCOME = "conformance_test_fix_limit_exceeded" + REGENERATE_CONFORMANCE_TESTS_OUTCOME = "regenerate_conformance_tests" ISSUE_REASON_CODE_CONFORMANCE_TESTS = 0 ISSUE_REASON_CODE_IMPLEMENTATION_CODE = 1 @@ -22,6 +27,23 @@ class FixConformanceTest(BaseAction): ISSUE_REASON_CODE_CONFLICTING_ACCEPTANCE_TESTS = 3 def execute(self, render_context: RenderContext, previous_action_payload: Any | None): + ctx = render_context.conformance_tests_running_context + ctx.fix_attempts += 1 + + if ctx.fix_attempts >= MAX_CONFORMANCE_TEST_FIX_ATTEMPTS: + if ctx.conformance_tests_render_attempts >= MAX_CONFORMANCE_TEST_RERENDER_ATTEMPTS: + error_msg = f"The renderer was unable to produce an implementation that passes conformance tests for functionality '{render_context.frid_context.frid}' after many attempts. Please review and rewrite the specification. (Render ID: {render_context.run_state.render_id})" + render_context.last_error_message = error_msg + return ( + self.LIMIT_EXCEEDED_OUTCOME, + RenderError.encode(message=error_msg).to_payload(), + ) + else: + ctx.regenerating_conformance_tests = True + return self.REGENERATE_CONFORMANCE_TESTS_OUTCOME, None + + console.info(f"Running conformance tests attempt {ctx.fix_attempts + 1}.") + console.info( f"Fixing conformance test for functionality {render_context.conformance_tests_running_context.current_testing_frid} in module {render_context.conformance_tests_running_context.current_testing_module_name}." ) diff --git a/render_machine/actions/prepare_testing_environment.py b/render_machine/actions/prepare_testing_environment.py index 437187c..3798ebf 100644 --- a/render_machine/actions/prepare_testing_environment.py +++ b/render_machine/actions/prepare_testing_environment.py @@ -12,6 +12,12 @@ class PrepareTestingEnvironment(BaseAction): FAILED_OUTCOME = "testing_environment_preparation_failed" def execute(self, render_context: RenderContext, _previous_action_payload: Any | None): + if ( + render_context.prepare_environment_script is None + or not render_context.conformance_tests_running_context.should_prepare_testing_environment + ): + return self.SUCCESSFUL_OUTCOME, None + console.info( f"Running testing environment preparation script {render_context.prepare_environment_script} for build folder {render_context.build_folder}." ) diff --git a/render_machine/actions/refactor_code.py b/render_machine/actions/refactor_code.py index a8d3ff6..60e95eb 100644 --- a/render_machine/actions/refactor_code.py +++ b/render_machine/actions/refactor_code.py @@ -5,13 +5,31 @@ from render_machine.actions.base_action import BaseAction from render_machine.implementation_code_helpers import ImplementationCodeHelpers from render_machine.render_context import RenderContext +from render_machine.render_types import RenderError + +MAX_REFACTORING_ITERATIONS = 5 class RefactorCode(BaseAction): SUCCESSFUL_OUTCOME = "refactoring_successful" NO_FILES_REFACTORED_OUTCOME = "no_files_refactored" + ITERATION_LIMIT_EXCEEDED_OUTCOME = "refactoring_iteration_limit_exceeded" def execute(self, render_context: RenderContext, _previous_action_payload: Any | None): + if render_context.frid_context.refactoring_iteration == 0: + console.info("Refactoring the generated code...") + + render_context.frid_context.refactoring_iteration += 1 + + if render_context.frid_context.refactoring_iteration >= MAX_REFACTORING_ITERATIONS: + error_message = "Refactoring iterations limit of {MAX_REFACTORING_ITERATIONS} reached for functionality {render_context.frid_context.frid}." + render_context.last_error_message = error_message + + return ( + self.ITERATION_LIMIT_EXCEEDED_OUTCOME, + RenderError.encode(message=error_message).to_payload(), + ) + existing_files, existing_files_content = ImplementationCodeHelpers.fetch_existing_files( render_context.build_folder ) diff --git a/render_machine/actions/render_functional_requirement.py b/render_machine/actions/render_functional_requirement.py index 60d36a1..ea20ad3 100644 --- a/render_machine/actions/render_functional_requirement.py +++ b/render_machine/actions/render_functional_requirement.py @@ -16,8 +16,22 @@ class RenderFunctionalRequirement(BaseAction): SUCCESSFUL_OUTCOME = "code_and_unit_tests_generated" FUNCTIONAL_REQUIREMENT_TOO_COMPLEX_OUTCOME = "functional_requirement_too_complex" + ITERATION_LIMIT_EXCEEDED_OUTCOME = "frid_iteration_limit_exceeded" def execute(self, render_context: RenderContext, _previous_action_payload: Any | None): + if render_context.frid_context.functional_requirement_render_attempts >= MAX_CODE_GENERATION_RETRIES: + error_msg = f"Unittests could not be fixed after rendering the functionality {render_context.frid_context.frid} for the {MAX_CODE_GENERATION_RETRIES} times." + render_context.last_error_message = error_msg + return self.ITERATION_LIMIT_EXCEEDED_OUTCOME, None + + render_context.frid_context.functional_requirement_render_attempts += 1 + + if render_context.frid_context.functional_requirement_render_attempts > 1: + console.info( + f"Unittests could not be fixed after rendering the functionality. " + f"Restarting rendering functionality {render_context.frid_context.frid} from scratch." + ) + render_utils.revert_changes_for_frid(render_context) existing_files, existing_files_content = ImplementationCodeHelpers.fetch_existing_files( render_context.build_folder diff --git a/render_machine/render_context.py b/render_machine/render_context.py index e7daf2e..b415aca 100644 --- a/render_machine/render_context.py +++ b/render_machine/render_context.py @@ -23,10 +23,6 @@ ) MAX_UNITTEST_FIX_ATTEMPTS = 20 -MAX_CODE_GENERATION_RETRIES = 2 -MAX_CONFORMANCE_TEST_RERENDER_ATTEMPTS = 1 -MAX_REFACTORING_ITERATIONS = 5 -MAX_CONFORMANCE_TEST_FIX_ATTEMPTS = 20 MAX_FUNCTIONAL_REQUIREMENT_RENDER_ATTEMPTS_FAILED_UNIT_DURING_CONFORMANCE_TESTS = 2 @@ -165,20 +161,6 @@ def start_implementing_frid(self): self.run_state.current_frid = frid return - def check_frid_iteration_limit(self): - if self.frid_context.functional_requirement_render_attempts >= MAX_CODE_GENERATION_RETRIES: - error_msg = f"Unittests could not be fixed after rendering the functionality {self.frid_context.frid} for the {MAX_CODE_GENERATION_RETRIES} times." - self.dispatch_error(error_msg) - - self.frid_context.functional_requirement_render_attempts += 1 - - if self.frid_context.functional_requirement_render_attempts > 1: - # this if is intended just for logging - console.info( - f"Unittests could not be fixed after rendering the functionality. " - f"Restarting rendering the functionality {self.frid_context.frid} from scratch." - ) - def has_next_frid(self) -> bool: next_frid = plain_spec.get_next_frid(self.plain_source_tree, self.frid_context.frid) if self.render_range is None or len(self.render_range) == 0: @@ -310,26 +292,6 @@ def _on_unit_test_limit_exceeded_in_refactoring(self): git_utils.revert_changes(self.build_folder) self.machine.dispatch(triggers.START_NEW_REFACTORING_ITERATION) - def start_refactoring_code(self): - - if self.frid_context.refactoring_iteration == 0: - console.info("Refactoring the generated code...") - - self.frid_context.refactoring_iteration += 1 - - if self.frid_context.refactoring_iteration >= MAX_REFACTORING_ITERATIONS: - console.info( - f"Refactoring iterations limit of {MAX_REFACTORING_ITERATIONS} reached for functionality {self.frid_context.frid}." - ) - self.machine.dispatch(triggers.PROCEED_FRID_PROCESSING) - - def start_testing_environment_preparation(self): - if ( - self.prepare_environment_script is None - or not self.conformance_tests_running_context.should_prepare_testing_environment - ): - self.machine.dispatch(triggers.MARK_TESTING_ENVIRONMENT_PREPARED) - def start_conformance_tests_processing(self): console.info("Implementing conformance tests...") current_frid_specifications, _ = plain_spec.get_specifications_for_frid( @@ -589,23 +551,6 @@ def start_conformance_tests_for_frid(self): # Should never reach here raise RuntimeError(f"Unexpected execution phase: {ctx.execution_phase}") - def start_fixing_conformance_tests(self): - self.conformance_tests_running_context.fix_attempts += 1 - - if self.conformance_tests_running_context.fix_attempts >= MAX_CONFORMANCE_TEST_FIX_ATTEMPTS: - if ( - self.conformance_tests_running_context.conformance_tests_render_attempts - >= MAX_CONFORMANCE_TEST_RERENDER_ATTEMPTS - ): - error_msg = f"The renderer was unable to produce an implementation that passes conformance tests for functionality '{self.frid_context.frid}' after many attempts. Please review and rewrite the specification. (Render ID: {self.run_state.render_id})" - self.dispatch_error(error_msg) - else: - self.conformance_tests_running_context.regenerating_conformance_tests = True - self.machine.dispatch(triggers.MARK_REGENERATION_OF_CONFORMANCE_TESTS) - - def finish_fixing_conformance_tests(self): - console.info(f"Running conformance tests attempt {self.conformance_tests_running_context.fix_attempts + 1}.") - def start_render_completed(self): self.run_state.set_render_succeeded(True) diff --git a/render_machine/state_machine_config.py b/render_machine/state_machine_config.py index 639e1eb..365e1d3 100644 --- a/render_machine/state_machine_config.py +++ b/render_machine/state_machine_config.py @@ -73,12 +73,14 @@ def get_action_result_triggers_map(self) -> Dict[str, str]: PrepareRepositories.SUCCESSFUL_OUTCOME: triggers.START_RENDER, RenderFunctionalRequirement.SUCCESSFUL_OUTCOME: triggers.RENDER_FUNCTIONAL_REQUIREMENT, RenderFunctionalRequirement.FUNCTIONAL_REQUIREMENT_TOO_COMPLEX_OUTCOME: triggers.HANDLE_ERROR, + RenderFunctionalRequirement.ITERATION_LIMIT_EXCEEDED_OUTCOME: triggers.HANDLE_ERROR, RunUnitTests.SUCCESSFUL_OUTCOME: triggers.MARK_UNIT_TESTS_PASSED, RunUnitTests.FAILED_OUTCOME: triggers.MARK_UNIT_TESTS_FAILED, RunUnitTests.UNRECOVERABLE_ERROR_OUTCOME: triggers.HANDLE_ERROR, FixUnitTests.SUCCESSFUL_OUTCOME: triggers.MARK_UNIT_TESTS_READY, RefactorCode.SUCCESSFUL_OUTCOME: triggers.REFACTOR_CODE, RefactorCode.NO_FILES_REFACTORED_OUTCOME: triggers.PROCEED_FRID_PROCESSING, + RefactorCode.ITERATION_LIMIT_EXCEEDED_OUTCOME: triggers.PROCEED_FRID_PROCESSING, CommitImplementationCodeChanges.SUCCESSFUL_OUTCOME: triggers.PROCEED_FRID_PROCESSING, FinishFunctionalRequirement.SUCCESSFUL_OUTCOME: triggers.PROCEED_FRID_PROCESSING, CreateDist.SUCCESSFUL_OUTCOME: triggers.FINISH_RENDER, @@ -90,6 +92,8 @@ def get_action_result_triggers_map(self) -> Dict[str, str]: RunConformanceTests.UNRECOVERABLE_ERROR_OUTCOME: triggers.HANDLE_ERROR, FixConformanceTest.IMPLEMENTATION_CODE_NOT_UPDATED: triggers.MARK_CONFORMANCE_TESTS_READY, FixConformanceTest.IMPLEMENTATION_CODE_UPDATED: triggers.MARK_UNIT_TESTS_READY, + FixConformanceTest.LIMIT_EXCEEDED_OUTCOME: triggers.HANDLE_ERROR, + FixConformanceTest.REGENERATE_CONFORMANCE_TESTS_OUTCOME: triggers.MARK_REGENERATION_OF_CONFORMANCE_TESTS, CommitConformanceTestsChanges.SUCCESSFUL_OUTCOME_IMPLEMENTATION_UPDATED: triggers.MARK_NEXT_CONFORMANCE_TESTS_POSTPROCESSING_STEP, CommitConformanceTestsChanges.SUCCESSFUL_OUTCOME_IMPLEMENTATION_NOT_UPDATED: triggers.PROCEED_FRID_PROCESSING, SummarizeConformanceTests.SUCCESSFUL_OUTCOME: triggers.MARK_NEXT_CONFORMANCE_TESTS_POSTPROCESSING_STEP, @@ -135,16 +139,9 @@ def get_processing_conformance_tests_states(self, render_context: RenderContext) "name": States.CONFORMANCE_TESTING_INITIALISED.value, "on_enter": render_context.start_conformance_tests_for_frid, }, - { - "name": States.CONFORMANCE_TEST_GENERATED.value, - "on_enter": render_context.start_testing_environment_preparation, - }, + States.CONFORMANCE_TEST_GENERATED.value, States.CONFORMANCE_TEST_ENV_PREPARED.value, - { - "name": States.CONFORMANCE_TEST_FAILED.value, - "on_enter": render_context.start_fixing_conformance_tests, - "on_exit": render_context.finish_fixing_conformance_tests, - }, + States.CONFORMANCE_TEST_FAILED.value, self.get_processing_unit_tests_states( render_context, render_context._on_unit_test_limit_exceeded_in_conformance_tests ), @@ -164,7 +161,6 @@ def get_states(self, render_context: RenderContext) -> List[Any]: refactoring_code_states = { "name": States.REFACTORING_CODE.value, "initial": States.READY_FOR_REFACTORING.value, - "on_enter": render_context.start_refactoring_code, "children": [ States.READY_FOR_REFACTORING.value, self.get_processing_unit_tests_states( @@ -183,10 +179,7 @@ def get_states(self, render_context: RenderContext) -> List[Any]: "on_exit": render_context.finish_implementing_frid, "children": [ {"name": States.STEP_COMPLETED.value}, - { - "name": States.READY_FOR_FRID_IMPLEMENTATION.value, - "on_enter": render_context.check_frid_iteration_limit, - }, + States.READY_FOR_FRID_IMPLEMENTATION.value, self.get_processing_unit_tests_states( render_context, render_context._on_unit_test_limit_exceeded_in_implementation ),