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.
- codeplain-0.1.0.dist-info/METADATA +142 -0
- codeplain-0.1.0.dist-info/RECORD +51 -0
- codeplain-0.1.0.dist-info/WHEEL +5 -0
- codeplain-0.1.0.dist-info/entry_points.txt +2 -0
- codeplain-0.1.0.dist-info/licenses/LICENSE +21 -0
- codeplain-0.1.0.dist-info/top_level.txt +36 -0
- codeplain_REST_api.py +370 -0
- config/__init__.py +2 -0
- config/system_config.yaml +27 -0
- file_utils.py +316 -0
- git_utils.py +304 -0
- hash_key.py +29 -0
- plain2code.py +218 -0
- plain2code_arguments.py +286 -0
- plain2code_console.py +107 -0
- plain2code_exceptions.py +45 -0
- plain2code_nodes.py +108 -0
- plain2code_read_config.py +74 -0
- plain2code_state.py +75 -0
- plain2code_utils.py +56 -0
- plain_spec.py +360 -0
- render_machine/actions/analyze_specification_ambiguity.py +50 -0
- render_machine/actions/base_action.py +19 -0
- render_machine/actions/commit_conformance_tests_changes.py +46 -0
- render_machine/actions/commit_implementation_code_changes.py +22 -0
- render_machine/actions/create_dist.py +26 -0
- render_machine/actions/exit_with_error.py +22 -0
- render_machine/actions/fix_conformance_test.py +121 -0
- render_machine/actions/fix_unit_tests.py +57 -0
- render_machine/actions/prepare_repositories.py +50 -0
- render_machine/actions/prepare_testing_environment.py +30 -0
- render_machine/actions/refactor_code.py +48 -0
- render_machine/actions/render_conformance_tests.py +169 -0
- render_machine/actions/render_functional_requirement.py +69 -0
- render_machine/actions/run_conformance_tests.py +44 -0
- render_machine/actions/run_unit_tests.py +38 -0
- render_machine/actions/summarize_conformance_tests.py +34 -0
- render_machine/code_renderer.py +50 -0
- render_machine/conformance_test_helpers.py +68 -0
- render_machine/implementation_code_helpers.py +20 -0
- render_machine/render_context.py +280 -0
- render_machine/render_types.py +36 -0
- render_machine/render_utils.py +92 -0
- render_machine/state_machine_config.py +408 -0
- render_machine/states.py +52 -0
- render_machine/triggers.py +27 -0
- standard_template_library/__init__.py +1 -0
- standard_template_library/golang-console-app-template.plain +36 -0
- standard_template_library/python-console-app-template.plain +32 -0
- standard_template_library/typescript-react-app-template.plain +22 -0
- 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
|