Skip to content

Commit d292801

Browse files
committed
feat(parsing): warn when acceptance tests found without conformance tests script
1 parent 3972ab4 commit d292801

4 files changed

Lines changed: 142 additions & 0 deletions

File tree

‎plain2code.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,31 @@ def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI):
158158
)
159159

160160

161+
def warn_if_acceptance_tests_without_conformance_script(plain_module, args) -> None:
162+
"""Warn when acceptance tests are specified but no conformance tests script is configured.
163+
164+
Acceptance tests are treated as conformance tests, so a conformance tests script is required
165+
to actually run them. Without it, the acceptance tests cannot be executed.
166+
"""
167+
if args.conformance_tests_script:
168+
return
169+
170+
modules_with_acceptance_tests = [
171+
module
172+
for module in plain_module.all_required_modules + [plain_module]
173+
if plain_spec.has_acceptance_tests(module.plain_source)
174+
]
175+
if not modules_with_acceptance_tests:
176+
return
177+
178+
module_names = ", ".join(module.module_name for module in modules_with_acceptance_tests)
179+
console.warning(
180+
f"Acceptance tests were found ({module_names}) but no conformance tests script is configured. "
181+
"Acceptance tests are treated as conformance tests and require a conformance tests script "
182+
"(--conformance-tests-script or 'conformance_tests_script' in config) to be executed."
183+
)
184+
185+
161186
def render(args, run_state: RunState, event_bus: EventBus, default_log_level: str = "INFO"): # noqa: C901
162187
template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
163188

@@ -185,6 +210,8 @@ def render(args, run_state: RunState, event_bus: EventBus, default_log_level: st
185210
template_dirs,
186211
)
187212

213+
warn_if_acceptance_tests_without_conformance_script(plain_module, args)
214+
188215
render_choice = None
189216
if render_range is None:
190217
plain_module_render_state = get_plain_module_render_state(plain_module)

‎plain_spec.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@
2525
ALLOWED_IMPORT_SPECIFICATION_HEADINGS = [DEFINITIONS, NON_FUNCTIONAL_REQUIREMENTS, TEST_REQUIREMENTS]
2626

2727

28+
def has_acceptance_tests(plain_source_tree) -> bool:
29+
"""Return True if any functional requirement in the plain source defines acceptance tests."""
30+
functional_requirements = plain_source_tree.get(FUNCTIONAL_REQUIREMENTS) or []
31+
for functional_requirement in functional_requirements:
32+
if functional_requirement.get(ACCEPTANCE_TESTS):
33+
return True
34+
return False
35+
36+
2837
def collect_specification_linked_resources(specification, specification_heading, linked_resources_list):
2938
linked_resources = []
3039
if "linked_resources" in specification:

‎tests/test_plain2code.py‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from argparse import Namespace
2+
from types import SimpleNamespace
3+
from unittest.mock import patch
4+
5+
import plain2code
6+
import plain_spec
7+
8+
9+
def _make_module(module_name, has_acceptance_tests, required_modules=None):
10+
"""Build a minimal stand-in for a PlainModule.
11+
12+
The functional requirements only need an `acceptance_tests` key when the module
13+
is expected to contain acceptance tests, because that is all
14+
`plain_spec.has_acceptance_tests` inspects.
15+
"""
16+
functional_requirement = {"markdown": f"- {module_name} functionality."}
17+
if has_acceptance_tests:
18+
functional_requirement[plain_spec.ACCEPTANCE_TESTS] = [{"markdown": "- Test it."}]
19+
20+
return SimpleNamespace(
21+
module_name=module_name,
22+
plain_source={plain_spec.FUNCTIONAL_REQUIREMENTS: [functional_requirement]},
23+
all_required_modules=required_modules or [],
24+
)
25+
26+
27+
def test_warns_when_acceptance_tests_present_and_no_conformance_script():
28+
plain_module = _make_module("top", has_acceptance_tests=True)
29+
args = Namespace(conformance_tests_script=None)
30+
31+
with patch("plain2code.console") as mock_console:
32+
plain2code.warn_if_acceptance_tests_without_conformance_script(plain_module, args)
33+
34+
mock_console.warning.assert_called_once()
35+
warning_message = mock_console.warning.call_args.args[0]
36+
assert "top" in warning_message
37+
assert "conformance tests script" in warning_message
38+
39+
40+
def test_no_warning_when_conformance_script_configured():
41+
plain_module = _make_module("top", has_acceptance_tests=True)
42+
args = Namespace(conformance_tests_script="run_conformance_tests.sh")
43+
44+
with patch("plain2code.console") as mock_console:
45+
plain2code.warn_if_acceptance_tests_without_conformance_script(plain_module, args)
46+
47+
mock_console.warning.assert_not_called()
48+
49+
50+
def test_no_warning_when_no_acceptance_tests():
51+
plain_module = _make_module("top", has_acceptance_tests=False)
52+
args = Namespace(conformance_tests_script=None)
53+
54+
with patch("plain2code.console") as mock_console:
55+
plain2code.warn_if_acceptance_tests_without_conformance_script(plain_module, args)
56+
57+
mock_console.warning.assert_not_called()
58+
59+
60+
def test_warns_when_acceptance_tests_only_in_required_module():
61+
required_module = _make_module("dependency", has_acceptance_tests=True)
62+
plain_module = _make_module("top", has_acceptance_tests=False, required_modules=[required_module])
63+
args = Namespace(conformance_tests_script=None)
64+
65+
with patch("plain2code.console") as mock_console:
66+
plain2code.warn_if_acceptance_tests_without_conformance_script(plain_module, args)
67+
68+
mock_console.warning.assert_called_once()
69+
warning_message = mock_console.warning.call_args.args[0]
70+
assert "dependency" in warning_message

‎tests/test_plainspec.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,42 @@ def test_get_linked_resources_invalid_input():
99
plain_spec.collect_linked_resources([], [], None, True)
1010

1111

12+
def test_has_acceptance_tests_true():
13+
plain_source = {
14+
plain_spec.FUNCTIONAL_REQUIREMENTS: [
15+
{"markdown": "- First functionality."},
16+
{
17+
"markdown": "- Second functionality.",
18+
plain_spec.ACCEPTANCE_TESTS: [{"markdown": "- Test the second functionality."}],
19+
},
20+
]
21+
}
22+
assert plain_spec.has_acceptance_tests(plain_source) is True
23+
24+
25+
def test_has_acceptance_tests_false_when_no_acceptance_tests():
26+
plain_source = {
27+
plain_spec.FUNCTIONAL_REQUIREMENTS: [
28+
{"markdown": "- First functionality."},
29+
{"markdown": "- Second functionality."},
30+
]
31+
}
32+
assert plain_spec.has_acceptance_tests(plain_source) is False
33+
34+
35+
def test_has_acceptance_tests_false_when_empty_acceptance_tests():
36+
plain_source = {
37+
plain_spec.FUNCTIONAL_REQUIREMENTS: [
38+
{"markdown": "- First functionality.", plain_spec.ACCEPTANCE_TESTS: []},
39+
]
40+
}
41+
assert plain_spec.has_acceptance_tests(plain_source) is False
42+
43+
44+
def test_has_acceptance_tests_false_when_no_functional_requirements():
45+
assert plain_spec.has_acceptance_tests({}) is False
46+
47+
1248
def test_get_frids_simple(get_test_data_path):
1349
_, plain_source, _ = plain_file.plain_file_parser("simple.plain", [get_test_data_path("data/")])
1450

0 commit comments

Comments
 (0)