Skip to content

Commit f926f32

Browse files
committed
Send an earlier functionality's passing conformance tests as a reference
The conformance tests generator only saw the implementation, so the tests of every functionality started with the same broken project setup: in airplain most conformance fixes added the same missing dependencies, parent POM or way of starting the application, one functionality after another. The client now sends the files of the most recent earlier functionality's conformance tests (of the current module, or of the closest required module for the first functionality of a module) as reference_conformance_tests, bounded to MAX_REFERENCE_TESTS_CHARS.
1 parent 60d4045 commit f926f32

3 files changed

Lines changed: 111 additions & 0 deletions

File tree

‎codeplain_REST_api.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,10 @@ def render_conformance_tests(
369369
conformance_tests_folder_name,
370370
conformance_tests_json,
371371
run_state: RunState,
372+
reference_conformance_tests: Optional[dict] = None,
372373
):
374+
"""`reference_conformance_tests`: files of an earlier functionality's passing conformance tests, by path, for
375+
the new tests to follow in their setup."""
373376
endpoint_url = f"{self.api_url}/render_conformance_tests"
374377
headers = {"X-API-Key": self.api_key, "Content-Type": "application/json"}
375378

@@ -384,6 +387,7 @@ def render_conformance_tests(
384387
"required_modules": required_modules,
385388
"conformance_tests_folder_name": conformance_tests_folder_name,
386389
"conformance_tests_json": conformance_tests_json,
390+
"reference_conformance_tests": reference_conformance_tests or {},
387391
}
388392

389393
response = self.post_request(endpoint_url, headers, payload, run_state)

‎render_machine/actions/render_conformance_tests.py‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
from render_machine.render_context import RenderContext
1111
from render_machine.render_types import AcceptanceTestPhase, TestExecutionPhase
1212

13+
# Bound on the reference conformance tests sent for generating new ones (see _reference_conformance_tests).
14+
MAX_REFERENCE_TESTS_CHARS = 40_000
15+
1316

1417
class RenderConformanceTests(BaseAction):
1518
SUCCESSFUL_OUTCOME = "conformance_test_rendered"
@@ -42,6 +45,28 @@ def _should_render_conformance_tests(self, render_context: RenderContext) -> boo
4245
# 3. All other cases (IN_PROGRESS, COMPLETED): Render acceptance tests incrementally
4346
return False
4447

48+
@staticmethod
49+
def _reference_conformance_tests(render_context: RenderContext, target_folder: str) -> dict[str, str]:
50+
"""The files of the most recent earlier functionality's conformance tests (they pass, or the render would
51+
not have moved on), as a reference for setting up new ones: of the current module if it has any, otherwise
52+
of the closest required module. Files by full path, smallest first, within MAX_REFERENCE_TESTS_CHARS."""
53+
running = render_context.conformance_tests_running_context
54+
candidates: list[dict] = []
55+
if running.conformance_tests_json_has_module_populated(render_context.module_name):
56+
candidates.append(running.get_conformance_tests_json(render_context.module_name))
57+
for module in reversed(render_context.plain_module.all_required_modules):
58+
candidates.append(render_context.conformance_tests.get_conformance_tests_json(module.module_name))
59+
target = os.path.normpath(os.path.abspath(target_folder))
60+
for tests_json in candidates:
61+
for entry in reversed(list(tests_json.values())):
62+
folder = os.path.normpath(os.path.abspath(entry.get("folder_name") or ""))
63+
if folder == target or not os.path.isdir(folder):
64+
continue
65+
files = _text_files(folder, MAX_REFERENCE_TESTS_CHARS)
66+
if files:
67+
return files
68+
return {}
69+
4570
def _render_conformance_tests(self, render_context: RenderContext):
4671
# Check if tests already exist (e.g., during regression) - if so, skip rendering
4772
if not render_context.conformance_tests_running_context.current_conformance_tests_exist():
@@ -109,6 +134,13 @@ def _render_conformance_tests(self, render_context: RenderContext):
109134
style=console.INPUT_STYLE,
110135
)
111136

137+
reference_conformance_tests = self._reference_conformance_tests(render_context, conformance_tests_folder_name)
138+
if reference_conformance_tests:
139+
console.debug(
140+
"Reference conformance tests sent for generating conformance tests: "
141+
+ ", ".join(sorted(reference_conformance_tests))
142+
)
143+
112144
response_files, traceability = render_context.codeplain_api.render_conformance_tests(
113145
render_context.frid_context.frid,
114146
render_context.conformance_tests_running_context.current_testing_frid,
@@ -123,6 +155,7 @@ def _render_conformance_tests(self, render_context: RenderContext):
123155
render_context.conformance_tests_running_context.current_testing_module_name
124156
),
125157
run_state=render_context.run_state,
158+
reference_conformance_tests=reference_conformance_tests,
126159
)
127160

128161
self._warn_about_untraced_scenarios(render_context, traceability)
@@ -223,3 +256,15 @@ def _render_acceptance_test(self, render_context: RenderContext):
223256
style=console.OUTPUT_STYLE,
224257
)
225258
return self.SUCCESSFUL_OUTCOME, None
259+
260+
261+
def _text_files(folder: str, max_chars: int) -> dict[str, str]:
262+
"""The text files of `folder` by full path, smallest first, within `max_chars`."""
263+
contents = file_utils.get_existing_files_content(folder, file_utils.list_all_text_files(folder))
264+
selected, total = {}, 0
265+
for name in sorted(contents, key=lambda n: (len(contents[n]), n)):
266+
if total + len(contents[name]) > max_chars:
267+
break
268+
selected[os.path.join(folder, name)] = contents[name]
269+
total += len(contents[name])
270+
return dict(sorted(selected.items()))
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from types import SimpleNamespace
2+
3+
from render_machine.actions.render_conformance_tests import RenderConformanceTests
4+
5+
6+
class FakeRunningContext:
7+
def __init__(self, tests_json):
8+
self.tests_json = tests_json
9+
10+
def conformance_tests_json_has_module_populated(self, module_name):
11+
return bool(self.tests_json)
12+
13+
def get_conformance_tests_json(self, module_name):
14+
return self.tests_json
15+
16+
17+
def make_render_context(tmp_path, current_json, required_jsons):
18+
return SimpleNamespace(
19+
module_name="messages",
20+
conformance_tests_running_context=FakeRunningContext(current_json),
21+
plain_module=SimpleNamespace(all_required_modules=[SimpleNamespace(module_name=m) for m in required_jsons]),
22+
conformance_tests=SimpleNamespace(get_conformance_tests_json=lambda module: required_jsons[module]),
23+
)
24+
25+
26+
def folder(tmp_path, name, files):
27+
path = tmp_path / name
28+
path.mkdir(parents=True)
29+
for file_name, content in files.items():
30+
(path / file_name).write_text(content)
31+
return str(path)
32+
33+
34+
def test_reference_is_the_latest_earlier_functionality_of_the_current_module(tmp_path):
35+
first = folder(tmp_path, "messages/publish", {"pom.xml": "first"})
36+
second = folder(tmp_path, "messages/fetch", {"pom.xml": "second"})
37+
target = str(tmp_path / "messages/list")
38+
current = {"publish": {"folder_name": first}, "fetch": {"folder_name": second}, "list": {"folder_name": target}}
39+
context = make_render_context(tmp_path, current, {"accounts": {}})
40+
41+
reference = RenderConformanceTests._reference_conformance_tests(context, target)
42+
43+
assert reference == {f"{second}/pom.xml": "second"}
44+
45+
46+
def test_first_functionality_of_a_module_uses_the_closest_required_module(tmp_path):
47+
healthcheck = folder(tmp_path, "healthcheck/health", {"pom.xml": "health"})
48+
accounts = folder(tmp_path, "accounts/delete", {"pom.xml": "accounts"})
49+
target = str(tmp_path / "messages/publish")
50+
required = {
51+
"healthcheck": {"health": {"folder_name": healthcheck}},
52+
"accounts": {"delete": {"folder_name": accounts}},
53+
}
54+
context = make_render_context(tmp_path, {"publish": {"folder_name": target}}, required)
55+
56+
assert RenderConformanceTests._reference_conformance_tests(context, target) == {f"{accounts}/pom.xml": "accounts"}
57+
58+
59+
def test_no_earlier_tests_give_no_reference(tmp_path):
60+
target = str(tmp_path / "healthcheck/health")
61+
context = make_render_context(tmp_path, {"health": {"folder_name": target}}, {})
62+
assert RenderConformanceTests._reference_conformance_tests(context, target) == {}

0 commit comments

Comments
 (0)