codeplain 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. codeplain-0.1.0.dist-info/METADATA +142 -0
  2. codeplain-0.1.0.dist-info/RECORD +51 -0
  3. codeplain-0.1.0.dist-info/WHEEL +5 -0
  4. codeplain-0.1.0.dist-info/entry_points.txt +2 -0
  5. codeplain-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. codeplain-0.1.0.dist-info/top_level.txt +36 -0
  7. codeplain_REST_api.py +370 -0
  8. config/__init__.py +2 -0
  9. config/system_config.yaml +27 -0
  10. file_utils.py +316 -0
  11. git_utils.py +304 -0
  12. hash_key.py +29 -0
  13. plain2code.py +218 -0
  14. plain2code_arguments.py +286 -0
  15. plain2code_console.py +107 -0
  16. plain2code_exceptions.py +45 -0
  17. plain2code_nodes.py +108 -0
  18. plain2code_read_config.py +74 -0
  19. plain2code_state.py +75 -0
  20. plain2code_utils.py +56 -0
  21. plain_spec.py +360 -0
  22. render_machine/actions/analyze_specification_ambiguity.py +50 -0
  23. render_machine/actions/base_action.py +19 -0
  24. render_machine/actions/commit_conformance_tests_changes.py +46 -0
  25. render_machine/actions/commit_implementation_code_changes.py +22 -0
  26. render_machine/actions/create_dist.py +26 -0
  27. render_machine/actions/exit_with_error.py +22 -0
  28. render_machine/actions/fix_conformance_test.py +121 -0
  29. render_machine/actions/fix_unit_tests.py +57 -0
  30. render_machine/actions/prepare_repositories.py +50 -0
  31. render_machine/actions/prepare_testing_environment.py +30 -0
  32. render_machine/actions/refactor_code.py +48 -0
  33. render_machine/actions/render_conformance_tests.py +169 -0
  34. render_machine/actions/render_functional_requirement.py +69 -0
  35. render_machine/actions/run_conformance_tests.py +44 -0
  36. render_machine/actions/run_unit_tests.py +38 -0
  37. render_machine/actions/summarize_conformance_tests.py +34 -0
  38. render_machine/code_renderer.py +50 -0
  39. render_machine/conformance_test_helpers.py +68 -0
  40. render_machine/implementation_code_helpers.py +20 -0
  41. render_machine/render_context.py +280 -0
  42. render_machine/render_types.py +36 -0
  43. render_machine/render_utils.py +92 -0
  44. render_machine/state_machine_config.py +408 -0
  45. render_machine/states.py +52 -0
  46. render_machine/triggers.py +27 -0
  47. standard_template_library/__init__.py +1 -0
  48. standard_template_library/golang-console-app-template.plain +36 -0
  49. standard_template_library/python-console-app-template.plain +32 -0
  50. standard_template_library/typescript-react-app-template.plain +22 -0
  51. system_config.py +49 -0
@@ -0,0 +1,69 @@
1
+ from typing import Any
2
+
3
+ import file_utils
4
+ import render_machine.render_utils as render_utils
5
+ from plain2code_console import console
6
+ from plain2code_exceptions import FunctionalRequirementTooComplex
7
+ from render_machine.actions.base_action import BaseAction
8
+ from render_machine.implementation_code_helpers import ImplementationCodeHelpers
9
+ from render_machine.render_context import RenderContext
10
+
11
+ MAX_CODE_GENERATION_RETRIES = 2
12
+
13
+
14
+ class RenderFunctionalRequirement(BaseAction):
15
+ SUCCESSFUL_OUTCOME = "code_and_unit_tests_generated"
16
+ FUNCTIONAL_REQUIREMENT_TOO_COMPLEX_OUTCOME = "functional_requirement_too_complex"
17
+
18
+ def execute(self, render_context: RenderContext, _previous_action_payload: Any | None):
19
+ render_utils.revert_uncommitted_changes(render_context)
20
+ existing_files, existing_files_content = ImplementationCodeHelpers.fetch_existing_files(render_context)
21
+
22
+ if render_context.args.verbose:
23
+ msg = f"Rendering functional requirement {render_context.frid_context.frid}"
24
+ if render_context.frid_context.functional_requirement_render_attempts > 1:
25
+ msg += f", attempt number {render_context.frid_context.functional_requirement_render_attempts}/{MAX_CODE_GENERATION_RETRIES}."
26
+ msg += f"\n[b]{render_context.frid_context.functional_requirement_text}[/b]"
27
+ console.info("\n-------------------------------------")
28
+ console.info(msg)
29
+ console.info("-------------------------------------\n")
30
+
31
+ try:
32
+ if render_context.args.verbose:
33
+ render_utils.print_inputs(
34
+ render_context, existing_files_content, "Files sent as input to code generation:"
35
+ )
36
+
37
+ with console.status(
38
+ f"[{console.INFO_STYLE}]Generating functional requirement {render_context.frid_context.frid}...\n"
39
+ ):
40
+ response_files = render_context.codeplain_api.render_functional_requirement(
41
+ render_context.frid_context.frid,
42
+ render_context.plain_source_tree,
43
+ render_context.frid_context.linked_resources,
44
+ existing_files_content,
45
+ render_context.run_state,
46
+ )
47
+ except FunctionalRequirementTooComplex as e:
48
+ error_message = f"The functional requirement:\n[b]{render_context.frid_context.functional_requirement_text}[/b]\n is too complex to be implemented. Please break down the functional requirement into smaller parts ({str(e)})."
49
+ if e.proposed_breakdown:
50
+ error_message += "\nProposed breakdown:"
51
+ for _, part in e.proposed_breakdown.items():
52
+ error_message += f"\n - {part}"
53
+
54
+ return self.FUNCTIONAL_REQUIREMENT_TOO_COMPLEX_OUTCOME, error_message
55
+
56
+ _, changed_files = file_utils.update_build_folder_with_rendered_files(
57
+ render_context.args.build_folder, existing_files, response_files
58
+ )
59
+ render_context.frid_context.changed_files.update(changed_files)
60
+
61
+ if render_context.args.verbose:
62
+ console.print_files(
63
+ "Files generated or updated:",
64
+ render_context.args.build_folder,
65
+ response_files,
66
+ style=console.OUTPUT_STYLE,
67
+ )
68
+
69
+ return self.SUCCESSFUL_OUTCOME, None
@@ -0,0 +1,44 @@
1
+ from typing import Any
2
+
3
+ import render_machine.render_utils as render_utils
4
+ from plain2code_console import console
5
+ from render_machine.actions.base_action import BaseAction
6
+ from render_machine.conformance_test_helpers import ConformanceTestHelpers
7
+ from render_machine.render_context import RenderContext
8
+
9
+ UNRECOVERABLE_ERROR_EXIT_CODES = [69]
10
+
11
+
12
+ class RunConformanceTests(BaseAction):
13
+
14
+ SUCCESSFUL_OUTCOME = "conformance_tests_passed"
15
+ FAILED_OUTCOME = "conformance_tests_failed"
16
+ UNRECOVERABLE_ERROR_OUTCOME = "unrecoverable_error_occurred"
17
+
18
+ def execute(self, render_context: RenderContext, _previous_action_payload: Any | None):
19
+ conformance_tests_folder_name = ConformanceTestHelpers.get_current_conformance_test_folder_name(
20
+ render_context.conformance_tests_running_context # type: ignore
21
+ )
22
+
23
+ if render_context.args.verbose:
24
+ console.info(
25
+ f"\n[b]Running conformance tests script {render_context.args.conformance_tests_script} for {conformance_tests_folder_name} (functional requirement {render_context.conformance_tests_running_context.current_testing_frid}).[/b]"
26
+ )
27
+ exit_code, conformance_tests_issue = render_utils.execute_script(
28
+ render_context.args.conformance_tests_script,
29
+ [render_context.args.build_folder, conformance_tests_folder_name],
30
+ render_context.args.verbose,
31
+ "Conformance Tests",
32
+ )
33
+
34
+ if exit_code == 0:
35
+ return self.SUCCESSFUL_OUTCOME, None
36
+
37
+ if exit_code in UNRECOVERABLE_ERROR_EXIT_CODES:
38
+ console.error(conformance_tests_issue)
39
+ return (
40
+ self.UNRECOVERABLE_ERROR_OUTCOME,
41
+ {"previous_conformance_tests_issue": conformance_tests_issue},
42
+ )
43
+
44
+ return self.FAILED_OUTCOME, {"previous_conformance_tests_issue": conformance_tests_issue}
@@ -0,0 +1,38 @@
1
+ from typing import Any
2
+
3
+ import render_machine.render_utils as render_utils
4
+ from plain2code_console import console
5
+ from render_machine.actions.base_action import BaseAction
6
+ from render_machine.render_context import RenderContext
7
+
8
+ UNRECOVERABLE_ERROR_EXIT_CODES = [69]
9
+
10
+
11
+ class RunUnitTests(BaseAction):
12
+ SUCCESSFUL_OUTCOME = "unit_tests_succeeded"
13
+ FAILED_OUTCOME = "unit_tests_failed"
14
+ UNRECOVERABLE_ERROR_OUTCOME = "unrecoverable_error_occurred"
15
+
16
+ def execute(self, render_context: RenderContext, _previous_action_payload: Any | None):
17
+ if render_context.args.verbose:
18
+ console.info(
19
+ f"[b]Running unit tests script {render_context.args.unittests_script}.[/b] (attempt: {render_context.unit_tests_running_context.fix_attempts + 1})"
20
+ )
21
+ exit_code, unittests_issue = render_utils.execute_script(
22
+ render_context.args.unittests_script,
23
+ [render_context.args.build_folder],
24
+ render_context.args.verbose,
25
+ "Unit Tests",
26
+ )
27
+
28
+ if exit_code == 0:
29
+ return self.SUCCESSFUL_OUTCOME, None
30
+
31
+ elif exit_code in UNRECOVERABLE_ERROR_EXIT_CODES:
32
+ console.error(unittests_issue)
33
+ return (
34
+ self.UNRECOVERABLE_ERROR_OUTCOME,
35
+ "Unit tests script failed due to problems in the environment setup. Please check the your environment or update the script for running unittests.",
36
+ )
37
+ else:
38
+ return self.FAILED_OUTCOME, {"previous_unittests_issue": unittests_issue}
@@ -0,0 +1,34 @@
1
+ from typing import Any
2
+
3
+ from plain2code_console import console
4
+ from render_machine.actions.base_action import BaseAction
5
+ from render_machine.conformance_test_helpers import ConformanceTestHelpers
6
+ from render_machine.render_context import RenderContext
7
+
8
+
9
+ class SummarizeConformanceTests(BaseAction):
10
+ SUCCESSFUL_OUTCOME = "conformance_tests_summarized"
11
+
12
+ def execute(self, render_context: RenderContext, _previous_action_payload: Any | None):
13
+ console.info(f"Summarizing conformance tests for functional requirement {render_context.frid_context.frid}.")
14
+
15
+ _, existing_conformance_test_files_content = ConformanceTestHelpers.fetch_existing_conformance_test_files(
16
+ render_context.conformance_tests_running_context # type: ignore
17
+ )
18
+
19
+ with console.status(
20
+ f"[{console.INFO_STYLE}]Summarizing finished conformance tests for functional requirement {render_context.frid_context.frid}...\n"
21
+ ):
22
+ summary = render_context.codeplain_api.summarize_finished_conformance_tests(
23
+ frid=render_context.frid_context.frid,
24
+ plain_source_tree=render_context.plain_source_tree,
25
+ linked_resources=render_context.frid_context.linked_resources,
26
+ conformance_test_files_content=existing_conformance_test_files_content,
27
+ run_state=render_context.run_state,
28
+ )
29
+
30
+ ConformanceTestHelpers.set_current_conformance_tests_summary(
31
+ render_context.conformance_tests_running_context, summary # type: ignore
32
+ )
33
+
34
+ return self.SUCCESSFUL_OUTCOME, None
@@ -0,0 +1,50 @@
1
+ from transitions.extensions.diagrams import HierarchicalGraphMachine
2
+
3
+ from plain2code_state import RunState
4
+ from render_machine.render_context import RenderContext
5
+ from render_machine.state_machine_config import StateMachineConfig, States
6
+
7
+
8
+ class CodeRenderer:
9
+ """Main code renderer class that orchestrates the code generation workflow using a hierarchical state machine."""
10
+
11
+ def __init__(self, codeplain_api, plain_source_tree: dict, args: dict, run_state: RunState):
12
+ self.render_context = RenderContext(codeplain_api, plain_source_tree, args, run_state)
13
+ self.state_machine_config = StateMachineConfig()
14
+
15
+ # Initialize the state machine
16
+ states = self.state_machine_config.get_states(self.render_context)
17
+ transitions = self.state_machine_config.get_transitions()
18
+
19
+ self.machine = HierarchicalGraphMachine(
20
+ model=self.render_context,
21
+ states=states,
22
+ transitions=transitions,
23
+ initial=States.RENDER_INITIALISED.value,
24
+ )
25
+ self.render_context.set_machine(self.machine)
26
+
27
+ # Get action mappings
28
+ self.action_map = self.state_machine_config.get_action_map()
29
+ self.action_result_triggers_map = self.state_machine_config.get_action_result_triggers_map()
30
+
31
+ def run(self):
32
+ """Execute the main rendering workflow."""
33
+ previous_action_payload = None
34
+ while True:
35
+ outcome, previous_action_payload = self.action_map[self.render_context.state].execute(
36
+ self.render_context, previous_action_payload
37
+ )
38
+
39
+ if self.render_context.state in [
40
+ States.RENDER_FAILED.value,
41
+ States.RENDER_COMPLETED.value,
42
+ ]:
43
+ break
44
+
45
+ next_trigger = self.action_result_triggers_map[outcome]
46
+ self.machine.dispatch(next_trigger)
47
+
48
+ def generate_render_machine_graph(self):
49
+ """Generate a visual diagram of the state machine."""
50
+ self.render_context.get_graph().draw("render_machine_diagram.png", prog="dot")
@@ -0,0 +1,68 @@
1
+ import os
2
+ from typing import Optional
3
+
4
+ import file_utils
5
+ import plain_spec
6
+ from render_machine.render_types import ConformanceTestsRunningContext
7
+
8
+
9
+ class ConformanceTestHelpers:
10
+ @staticmethod
11
+ def fetch_existing_conformance_test_folder_names(conformance_tests_folder: str):
12
+ if os.path.isdir(conformance_tests_folder):
13
+ existing_folder_names = file_utils.list_folders_in_directory(conformance_tests_folder)
14
+ else:
15
+ # This happens if we're rendering the first FRID (without previously created conformance tests)
16
+ existing_folder_names = []
17
+ return existing_folder_names
18
+
19
+ @staticmethod
20
+ def fetch_existing_conformance_test_files(conformance_tests_running_context: ConformanceTestsRunningContext):
21
+ conformance_test_folder_name = ConformanceTestHelpers.get_current_conformance_test_folder_name(
22
+ conformance_tests_running_context
23
+ )
24
+ existing_conformance_test_files = file_utils.list_all_text_files(conformance_test_folder_name)
25
+ existing_conformance_test_files_content = file_utils.get_existing_files_content(
26
+ conformance_test_folder_name, existing_conformance_test_files
27
+ )
28
+ return existing_conformance_test_files, existing_conformance_test_files_content
29
+
30
+ @staticmethod
31
+ def current_conformance_tests_exist(conformance_tests_running_context: ConformanceTestsRunningContext) -> bool:
32
+ return (
33
+ conformance_tests_running_context.conformance_tests_json.get(
34
+ conformance_tests_running_context.current_testing_frid
35
+ )
36
+ is not None
37
+ )
38
+
39
+ @staticmethod
40
+ def get_current_conformance_test_folder_name(
41
+ conformance_tests_running_context: ConformanceTestsRunningContext,
42
+ ) -> str:
43
+ return conformance_tests_running_context.conformance_tests_json[
44
+ conformance_tests_running_context.current_testing_frid
45
+ ]["folder_name"]
46
+
47
+ @staticmethod
48
+ def get_current_acceptance_tests(
49
+ conformance_tests_running_context: ConformanceTestsRunningContext,
50
+ ) -> Optional[list[str]]:
51
+ if (
52
+ plain_spec.ACCEPTANCE_TESTS
53
+ in conformance_tests_running_context.conformance_tests_json[
54
+ conformance_tests_running_context.current_testing_frid
55
+ ]
56
+ ):
57
+ return conformance_tests_running_context.conformance_tests_json[
58
+ conformance_tests_running_context.current_testing_frid
59
+ ][plain_spec.ACCEPTANCE_TESTS]
60
+ return None
61
+
62
+ @staticmethod
63
+ def set_current_conformance_tests_summary(
64
+ conformance_tests_running_context: ConformanceTestsRunningContext, summary: list[dict]
65
+ ):
66
+ conformance_tests_running_context.conformance_tests_json[
67
+ conformance_tests_running_context.current_testing_frid
68
+ ]["test_summary"] = summary
@@ -0,0 +1,20 @@
1
+ import file_utils
2
+ import git_utils
3
+ import plain_spec
4
+ from render_machine.render_context import RenderContext
5
+
6
+
7
+ class ImplementationCodeHelpers:
8
+ @staticmethod
9
+ def fetch_existing_files(render_context: RenderContext):
10
+ existing_files = file_utils.list_all_text_files(render_context.args.build_folder)
11
+ existing_files_content = file_utils.get_existing_files_content(render_context.args.build_folder, existing_files)
12
+ return existing_files, existing_files_content
13
+
14
+ @staticmethod
15
+ def get_code_diff(render_context: RenderContext):
16
+ previous_frid_code_diff = git_utils.diff(
17
+ render_context.args.build_folder,
18
+ plain_spec.get_previous_frid(render_context.plain_source_tree, render_context.frid_context.frid),
19
+ )
20
+ return previous_frid_code_diff
@@ -0,0 +1,280 @@
1
+ from typing import Optional
2
+
3
+ import file_utils
4
+ import git_utils
5
+ import plain_spec
6
+ from codeplain_REST_api import CodeplainAPI
7
+ from plain2code_console import console
8
+ from plain2code_state import CONFORMANCE_TESTS_DEFINITION_FILE_NAME, ConformanceTestsUtils, RunState
9
+ from render_machine import triggers
10
+ from render_machine.conformance_test_helpers import ConformanceTestHelpers
11
+ from render_machine.render_types import ConformanceTestsRunningContext, FridContext, UnitTestsRunningContext
12
+
13
+ DEFAULT_TEMPLATE_DIRS = "standard_template_library"
14
+
15
+ MAX_UNITTEST_FIX_ATTEMPTS = 20
16
+ MAX_CODE_GENERATION_RETRIES = 2
17
+ MAX_CONFORMANCE_TEST_RERENDER_ATTEMPTS = 1
18
+ MAX_REFACTORING_ITERATIONS = 5
19
+ MAX_CONFORMANCE_TEST_FIX_ATTEMPTS = 20
20
+ MAX_FUNCTIONAL_REQUIREMENT_RENDER_ATTEMPTS_FAILED_UNIT_DURING_CONFORMANCE_TESTS = 2
21
+
22
+
23
+ class RenderContext:
24
+ def __init__(self, codeplain_api, plain_source_tree: dict, args: dict, run_state: RunState):
25
+ self.codeplain_api: CodeplainAPI = codeplain_api
26
+ self.plain_source_tree = plain_source_tree
27
+ self.args = args
28
+ self.run_state = run_state
29
+ self.starting_frid = None
30
+
31
+ template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
32
+ resources_list = []
33
+ plain_spec.collect_linked_resources(plain_source_tree, resources_list, None, True)
34
+ self.all_linked_resources = file_utils.load_linked_resources(template_dirs, resources_list)
35
+
36
+ # Initialize context objects
37
+ self.frid_context: Optional[FridContext] = None
38
+ self.unit_tests_running_context: Optional[UnitTestsRunningContext] = None
39
+ self.conformance_tests_running_context: Optional[ConformanceTestsRunningContext] = None
40
+ # Constants that should remain for a single frid, but possible over multiple rerenderings of the same frid
41
+ self.functional_requirements_render_attempts_failed_unit_during_conformance_tests = 0
42
+ # Initialize conformance tests utilities
43
+ self.conformance_tests_utils = ConformanceTestsUtils(
44
+ conformance_tests_folder=args.conformance_tests_folder,
45
+ conformance_tests_definition_file_name=CONFORMANCE_TESTS_DEFINITION_FILE_NAME,
46
+ verbose=args.verbose,
47
+ )
48
+
49
+ self.machine = None
50
+
51
+ def set_machine(self, machine):
52
+ self.machine = machine
53
+
54
+ def start_implementing_frid(self):
55
+ if self.starting_frid is not None:
56
+ frid = self.starting_frid
57
+ self.starting_frid = None
58
+ elif self.frid_context is None:
59
+ frid = plain_spec.get_first_frid(self.plain_source_tree)
60
+ else:
61
+ frid = plain_spec.get_next_frid(self.plain_source_tree, self.frid_context.frid)
62
+
63
+ if frid is None:
64
+ # If frid context is empty, it means that all frids have been implemented
65
+ self.frid_context = None
66
+ self.machine.dispatch(triggers.PREPARE_FINAL_OUTPUT)
67
+ return
68
+
69
+ specifications, _ = plain_spec.get_specifications_for_frid(self.plain_source_tree, frid)
70
+ functional_requirement_text = specifications[plain_spec.FUNCTIONAL_REQUIREMENTS][-1]
71
+
72
+ resources_list = []
73
+ plain_spec.collect_linked_resources(self.plain_source_tree, resources_list, None, True, frid)
74
+
75
+ linked_resources = {}
76
+ for resource in resources_list:
77
+ linked_resources[resource["target"]] = self.all_linked_resources[resource["target"]]
78
+
79
+ self.frid_context = FridContext(
80
+ frid=frid,
81
+ specifications=specifications,
82
+ functional_requirement_text=functional_requirement_text,
83
+ linked_resources=linked_resources,
84
+ functional_requirement_render_attempts=0,
85
+ )
86
+
87
+ def check_frid_iteration_limit(self):
88
+ # If frid context is not set, it means that all frids have been implemented
89
+ if self.frid_context is None:
90
+ return
91
+
92
+ if self.frid_context.functional_requirement_render_attempts >= MAX_CODE_GENERATION_RETRIES:
93
+ console.error(
94
+ f"Unittests could not be fixed after rendering the functional requirement {self.frid_context.frid} for the {MAX_CODE_GENERATION_RETRIES} times."
95
+ )
96
+ self.machine.dispatch(triggers.HANDLE_ERROR)
97
+
98
+ self.frid_context.functional_requirement_render_attempts += 1
99
+
100
+ if self.frid_context.functional_requirement_render_attempts > 1:
101
+ # this if is intended just for logging
102
+ console.info(
103
+ f"Unittests could not be fixed after rendering the functional requirement. "
104
+ f"Restarting rendering the functional requirement {self.frid_context.frid} from scratch."
105
+ )
106
+
107
+ def finish_implementing_frid(self):
108
+ self.functional_requirements_render_attempts_failed_unit_during_conformance_tests = 0
109
+ pass
110
+
111
+ def start_unittests_processing(self):
112
+ self.unit_tests_running_context = UnitTestsRunningContext(fix_attempts=0)
113
+ self.run_state.increment_unittest_batch_id()
114
+
115
+ def start_unittests_processing_in_conformance_tests(self):
116
+ self.start_unittests_processing()
117
+ # set to first FRID
118
+ self.conformance_tests_running_context.current_testing_frid = plain_spec.get_first_frid(self.plain_source_tree)
119
+
120
+ def finish_unittests_processing(self):
121
+ existing_files = file_utils.list_all_text_files(self.args.build_folder)
122
+
123
+ # TODO: Double check if this logic is what we want
124
+ for file_name in self.unit_tests_running_context.changed_files:
125
+ if file_name not in existing_files:
126
+ self.frid_context.changed_files.discard(file_name)
127
+ else:
128
+ self.frid_context.changed_files.add(file_name)
129
+ self.unit_tests_running_context.fix_attempts = 1
130
+
131
+ def start_fixing_unit_tests(self):
132
+ self.unit_tests_running_context.fix_attempts += 1
133
+
134
+ if self.unit_tests_running_context.fix_attempts > MAX_UNITTEST_FIX_ATTEMPTS:
135
+ self.machine.dispatch(triggers.RESTART_FRID_PROCESSING)
136
+
137
+ def start_fixing_unit_tests_in_conformance_tests(self):
138
+ self.unit_tests_running_context.fix_attempts += 1
139
+
140
+ if self.unit_tests_running_context.fix_attempts > MAX_UNITTEST_FIX_ATTEMPTS:
141
+ self.functional_requirements_render_attempts_failed_unit_during_conformance_tests += 1
142
+ if (
143
+ self.functional_requirements_render_attempts_failed_unit_during_conformance_tests
144
+ >= MAX_FUNCTIONAL_REQUIREMENT_RENDER_ATTEMPTS_FAILED_UNIT_DURING_CONFORMANCE_TESTS
145
+ ):
146
+ console.error(
147
+ f"Failed to adjust unit tests after implementation code was update while fixing conformance tests for functional requirement {self.frid_context.frid} for the {MAX_FUNCTIONAL_REQUIREMENT_RENDER_ATTEMPTS_FAILED_UNIT_DURING_CONFORMANCE_TESTS} times."
148
+ )
149
+ self.machine.dispatch(triggers.HANDLE_ERROR)
150
+ else:
151
+ console.info(
152
+ f"Failed to adjust unit tests after implementation code was update while fixing conformance tests for functional requirement {self.frid_context.frid}."
153
+ )
154
+ console.info(f"Restarting rendering the functional requirement {self.frid_context.frid} from scratch.")
155
+ self.machine.dispatch(triggers.RESTART_FRID_PROCESSING)
156
+
157
+ def start_fixing_unit_tests_in_refactoring(self):
158
+ self.unit_tests_running_context.fix_attempts += 1
159
+
160
+ if self.unit_tests_running_context.fix_attempts > MAX_UNITTEST_FIX_ATTEMPTS:
161
+ git_utils.revert_changes(self.args.build_folder)
162
+ self.machine.dispatch(triggers.START_NEW_REFACTORING_ITERATION)
163
+
164
+ def start_refactoring_code(self):
165
+
166
+ if self.frid_context.refactoring_iteration == 0:
167
+ console.info("\n[b]Refactoring the generated code...[/b]\n")
168
+
169
+ self.frid_context.refactoring_iteration += 1
170
+
171
+ if self.frid_context.refactoring_iteration >= MAX_REFACTORING_ITERATIONS:
172
+ if self.args.verbose:
173
+ console.info(
174
+ f"Refactoring iterations limit of {MAX_REFACTORING_ITERATIONS} reached for functional requirement {self.frid_context.frid}."
175
+ )
176
+ self.machine.dispatch(triggers.PROCEED_FRID_PROCESSING)
177
+
178
+ def start_testing_environment_preparation(self):
179
+ if (
180
+ self.args.prepare_environment_script is None
181
+ or not self.conformance_tests_running_context.should_prepare_testing_environment
182
+ ):
183
+ self.machine.dispatch(triggers.MARK_TESTING_ENVIRONMENT_PREPARED)
184
+
185
+ def start_conformance_tests_processing(self):
186
+ console.info("\n[b]Implementing conformance tests...[/b]\n")
187
+ conformance_tests_json = self.conformance_tests_utils.get_conformance_tests_json()
188
+ self.conformance_tests_running_context = ConformanceTestsRunningContext(
189
+ current_testing_frid=None,
190
+ current_testing_frid_specifications=None,
191
+ conformance_test_phase_index=0,
192
+ fix_attempts=0,
193
+ conformance_tests_json=conformance_tests_json,
194
+ conformance_tests_render_attempts=0,
195
+ should_prepare_testing_environment=True,
196
+ )
197
+
198
+ def finish_conformance_tests_processing(self):
199
+ self.conformance_tests_running_context = None
200
+
201
+ def start_conformance_tests_for_frid(self):
202
+ if self.conformance_tests_running_context.regenerating_conformance_tests:
203
+ if self.args.verbose:
204
+ console.info(
205
+ f"Recreating conformance tests for functional requirement {self.conformance_tests_running_context.current_testing_frid}."
206
+ )
207
+
208
+ existing_conformance_tests_folder = self.conformance_tests_running_context.conformance_tests_json.pop(
209
+ self.conformance_tests_running_context.current_testing_frid
210
+ )
211
+
212
+ file_utils.delete_folder(existing_conformance_tests_folder["folder_name"])
213
+
214
+ self.conformance_tests_running_context.conformance_tests_render_attempts += 1
215
+ self.conformance_tests_running_context.fix_attempts = 0
216
+ self.conformance_tests_running_context.regenerating_conformance_tests = False
217
+ else:
218
+ if self.conformance_tests_running_context.current_testing_frid == self.frid_context.frid:
219
+
220
+ if not self.frid_context.specifications.get(
221
+ plain_spec.ACCEPTANCE_TESTS
222
+ ) or self.conformance_tests_running_context.conformance_test_phase_index == len(
223
+ self.frid_context.specifications[plain_spec.ACCEPTANCE_TESTS]
224
+ ):
225
+ self.machine.dispatch(triggers.MARK_ALL_CONFORMANCE_TESTS_PASSED)
226
+ return
227
+
228
+ should_reset_high_level_implementation_plan = (
229
+ self.conformance_tests_running_context.current_testing_frid == self.frid_context.frid
230
+ and self.conformance_tests_running_context.conformance_test_phase_index == 0
231
+ )
232
+ if should_reset_high_level_implementation_plan:
233
+ self.conformance_tests_running_context.current_testing_frid_high_level_implementation_plan = None
234
+
235
+ self.conformance_tests_running_context.conformance_test_phase_index += 1
236
+ current_acceptance_tests = self.frid_context.specifications[plain_spec.ACCEPTANCE_TESTS][
237
+ : self.conformance_tests_running_context.conformance_test_phase_index
238
+ ]
239
+ self.conformance_tests_running_context.conformance_tests_json[self.frid_context.frid][
240
+ plain_spec.ACCEPTANCE_TESTS
241
+ ] = current_acceptance_tests
242
+ return
243
+
244
+ if self.conformance_tests_running_context.current_testing_frid is None:
245
+ self.conformance_tests_running_context.current_testing_frid = plain_spec.get_first_frid(
246
+ self.plain_source_tree
247
+ )
248
+ else:
249
+ self.conformance_tests_running_context.current_testing_frid = plain_spec.get_next_frid(
250
+ self.plain_source_tree, self.conformance_tests_running_context.current_testing_frid
251
+ )
252
+ self.conformance_tests_running_context.current_testing_frid_specifications, _ = (
253
+ plain_spec.get_specifications_for_frid(
254
+ self.plain_source_tree, self.conformance_tests_running_context.current_testing_frid
255
+ )
256
+ )
257
+ if ConformanceTestHelpers.current_conformance_tests_exist(self.conformance_tests_running_context): # type: ignore
258
+ self.machine.dispatch(triggers.MARK_CONFORMANCE_TESTS_READY)
259
+
260
+ def start_fixing_conformance_tests(self):
261
+ self.conformance_tests_running_context.fix_attempts += 1
262
+
263
+ if self.conformance_tests_running_context.fix_attempts >= MAX_CONFORMANCE_TEST_FIX_ATTEMPTS:
264
+ if (
265
+ self.conformance_tests_running_context.conformance_tests_render_attempts
266
+ >= MAX_CONFORMANCE_TEST_RERENDER_ATTEMPTS
267
+ ):
268
+ console.error(
269
+ f"We've already tried to fix the issue by recreating the conformance tests but tests still fail. Please fix the issues manually. FRID: {self.frid_context.frid}, Render ID: {self.run_state.render_id}"
270
+ )
271
+ self.machine.dispatch(triggers.HANDLE_ERROR)
272
+ else:
273
+ self.conformance_tests_running_context.regenerating_conformance_tests = True
274
+ self.machine.dispatch(triggers.MARK_REGENERATION_OF_CONFORMANCE_TESTS)
275
+
276
+ def finish_fixing_conformance_tests(self):
277
+ if self.args.verbose:
278
+ console.info(
279
+ f"[b]Running conformance tests attempt {self.conformance_tests_running_context.fix_attempts + 1}.[/b]"
280
+ )
@@ -0,0 +1,36 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Optional
3
+
4
+
5
+ @dataclass
6
+ class FridContext:
7
+ frid: str
8
+ specifications: dict
9
+ functional_requirement_text: str
10
+ linked_resources: dict
11
+ functional_requirement_render_attempts: int = 0
12
+ changed_files: set[str] = field(default_factory=set)
13
+ refactoring_iteration: int = 0
14
+
15
+
16
+ @dataclass
17
+ class UnitTestsRunningContext:
18
+ fix_attempts: int
19
+ changed_files: set[str] = field(default_factory=set)
20
+
21
+
22
+ @dataclass
23
+ class ConformanceTestsRunningContext:
24
+ current_testing_frid: Optional[str]
25
+ fix_attempts: int
26
+ conformance_tests_json: dict
27
+ conformance_tests_render_attempts: int
28
+ current_testing_frid_specifications: Optional[dict[str, list]]
29
+ conformance_test_phase_index: int # 0 => conformance tests, 1 or more => acceptance tests
30
+ regenerating_conformance_tests: bool = False
31
+
32
+ # will be propagated only when:
33
+ # - current_testing_frid == frid noqa: E800
34
+ # - conformance_test_phase_index == 0 (conformance tests phase)
35
+ current_testing_frid_high_level_implementation_plan: Optional[str] = None
36
+ should_prepare_testing_environment: bool = False