codeplain 0.2.2__py3-none-any.whl → 0.2.4__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.2.2.dist-info → codeplain-0.2.4.dist-info}/METADATA +15 -16
- {codeplain-0.2.2.dist-info → codeplain-0.2.4.dist-info}/RECORD +24 -13
- {codeplain-0.2.2.dist-info → codeplain-0.2.4.dist-info}/WHEEL +1 -2
- codeplain_REST_api.py +0 -6
- diff_utils.py +32 -0
- docs/generate_cli.py +20 -0
- examples/example_hello_world_python/harness_tests/hello_world_display/test_hello_world.py +17 -0
- git_utils.py +1 -6
- memory_management.py +97 -0
- plain2code.py +5 -12
- plain2code_exceptions.py +16 -6
- plain_file.py +14 -36
- plain_modules.py +1 -4
- plain_spec.py +2 -4
- tests/__init__.py +1 -0
- tests/conftest.py +34 -0
- tests/test_git_utils.py +458 -0
- tests/test_imports.py +42 -0
- tests/test_plainfile.py +271 -0
- tests/test_plainfileparser.py +532 -0
- tests/test_plainspec.py +67 -0
- tests/test_requires.py +28 -0
- codeplain-0.2.2.dist-info/top_level.txt +0 -41
- {codeplain-0.2.2.dist-info → codeplain-0.2.4.dist-info}/entry_points.txt +0 -0
- {codeplain-0.2.2.dist-info → codeplain-0.2.4.dist-info}/licenses/LICENSE +0 -0
tests/test_plainfile.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from mistletoe.block_token import List, ListItem, Paragraph
|
|
5
|
+
from mistletoe.span_token import Emphasis, RawText, Strong
|
|
6
|
+
|
|
7
|
+
import plain_file
|
|
8
|
+
import plain_spec
|
|
9
|
+
from plain2code_exceptions import PlainSyntaxError
|
|
10
|
+
from plain_file import process_acceptance_tests
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MockParseBuffer(list):
|
|
14
|
+
def __init__(self, *args, loose=False):
|
|
15
|
+
super().__init__(*args)
|
|
16
|
+
self.loose = loose
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _create_mock_fr_content(content="Implement the entry point for ...", line_number=32):
|
|
20
|
+
"""Helper to create a mock functional requirement content paragraph."""
|
|
21
|
+
mock_fr_content_text = MagicMock(spec=RawText)
|
|
22
|
+
mock_fr_content_text.content = content
|
|
23
|
+
|
|
24
|
+
mock_fr_content = MagicMock(spec=Paragraph)
|
|
25
|
+
mock_fr_content.children = [mock_fr_content_text]
|
|
26
|
+
mock_fr_content.line_number = line_number
|
|
27
|
+
|
|
28
|
+
return mock_fr_content
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _create_mock_at_heading_paragraph(line_number=34):
|
|
32
|
+
"""Helper to create a mock acceptance test heading paragraph with proper structure."""
|
|
33
|
+
mock_at_heading_raw_text = MagicMock(spec=RawText)
|
|
34
|
+
mock_at_heading_raw_text.content = plain_spec.ACCEPTANCE_TEST_HEADING
|
|
35
|
+
|
|
36
|
+
mock_at_heading_strong = MagicMock(spec=Strong)
|
|
37
|
+
mock_at_heading_strong.children = [mock_at_heading_raw_text]
|
|
38
|
+
|
|
39
|
+
mock_at_heading_emphasis = MagicMock(spec=Emphasis)
|
|
40
|
+
mock_at_heading_emphasis.children = [mock_at_heading_strong]
|
|
41
|
+
|
|
42
|
+
mock_at_heading_paragraph = MagicMock(spec=Paragraph)
|
|
43
|
+
mock_at_heading_paragraph.children = [mock_at_heading_emphasis]
|
|
44
|
+
mock_at_heading_paragraph.line_number = line_number
|
|
45
|
+
|
|
46
|
+
return mock_at_heading_paragraph
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _create_mock_at_list(content="The App shouldn't show logging...", line_number=36):
|
|
50
|
+
"""Helper to create a mock acceptance test list with a single item."""
|
|
51
|
+
mock_at_item_raw_text = MagicMock(spec=RawText)
|
|
52
|
+
mock_at_item_raw_text.content = content
|
|
53
|
+
|
|
54
|
+
mock_at_item_paragraph = MagicMock(spec=Paragraph)
|
|
55
|
+
mock_at_item_paragraph.children = [mock_at_item_raw_text]
|
|
56
|
+
mock_at_item_paragraph.line_number = line_number
|
|
57
|
+
|
|
58
|
+
mock_at_item = MagicMock(spec=ListItem)
|
|
59
|
+
mock_at_item.children = [mock_at_item_paragraph]
|
|
60
|
+
mock_at_item.line_number = line_number
|
|
61
|
+
|
|
62
|
+
mock_at_list = MagicMock(spec=List)
|
|
63
|
+
mock_at_list.children = [mock_at_item]
|
|
64
|
+
mock_at_list.line_number = line_number
|
|
65
|
+
|
|
66
|
+
return mock_at_list
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@patch("plain_file._process_single_acceptance_test_requirement")
|
|
70
|
+
def test_process_acceptance_tests_no_functional_requirements_key(mock_process_single):
|
|
71
|
+
"""Tests early return when functional requirements key is missing."""
|
|
72
|
+
plain_source_tree = {}
|
|
73
|
+
|
|
74
|
+
process_acceptance_tests(plain_source_tree)
|
|
75
|
+
|
|
76
|
+
mock_process_single.assert_not_called()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@patch("plain_file._process_single_acceptance_test_requirement")
|
|
80
|
+
def test_process_acceptance_tests_frs_lacks_children_attr(mock_process_single):
|
|
81
|
+
"""Tests early return when frs object lacks children attribute."""
|
|
82
|
+
plain_source_tree = {plain_spec.FUNCTIONAL_REQUIREMENTS: None}
|
|
83
|
+
|
|
84
|
+
process_acceptance_tests(plain_source_tree)
|
|
85
|
+
|
|
86
|
+
mock_process_single.assert_not_called()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@patch("plain_file._process_single_acceptance_test_requirement")
|
|
90
|
+
def test_process_acceptance_tests_no_sections_direct_frs(mock_process_single):
|
|
91
|
+
"""Tests processing of top-level functional requirements when no sections exist."""
|
|
92
|
+
mock_fr_item1_with_children = MagicMock(name="FRItem1_WithChildren")
|
|
93
|
+
mock_fr_item1_with_children.children = MagicMock(name="ChildrenOfFRItem1")
|
|
94
|
+
|
|
95
|
+
mock_fr_item2_no_children = MagicMock(spec=[], name="FRItem2_NoChildren")
|
|
96
|
+
|
|
97
|
+
mock_fr_item3_with_children = MagicMock(name="FRItem3_WithChildren")
|
|
98
|
+
mock_fr_item3_with_children.children = MagicMock(name="ChildrenOfFRItem3")
|
|
99
|
+
|
|
100
|
+
mock_frs_top_level = MagicMock(name="TopLevelFRS")
|
|
101
|
+
mock_frs_top_level.children = [
|
|
102
|
+
mock_fr_item1_with_children,
|
|
103
|
+
mock_fr_item2_no_children,
|
|
104
|
+
mock_fr_item3_with_children,
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
plain_source_tree = {
|
|
108
|
+
plain_spec.FUNCTIONAL_REQUIREMENTS: mock_frs_top_level,
|
|
109
|
+
"definitions": MagicMock(name="TopLevelDefs"),
|
|
110
|
+
"technical specs": MagicMock(name="TopLevelNFRs"),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
process_acceptance_tests(plain_source_tree)
|
|
114
|
+
|
|
115
|
+
assert mock_process_single.call_count == 2
|
|
116
|
+
mock_process_single.assert_any_call(mock_fr_item1_with_children)
|
|
117
|
+
mock_process_single.assert_any_call(mock_fr_item3_with_children)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_psart_no_acceptance_tests_present():
|
|
121
|
+
"""Tests behavior when no 'Acceptance test:' heading is present."""
|
|
122
|
+
functional_requirement_mock = MagicMock(spec=ListItem)
|
|
123
|
+
functional_requirement_mock.line_number = 1
|
|
124
|
+
|
|
125
|
+
mock_paragraph1 = MagicMock(spec=Paragraph)
|
|
126
|
+
mock_paragraph1.children = []
|
|
127
|
+
mock_paragraph2 = MagicMock(spec=Paragraph)
|
|
128
|
+
mock_paragraph2.children = []
|
|
129
|
+
|
|
130
|
+
original_children = [mock_paragraph1, mock_paragraph2]
|
|
131
|
+
functional_requirement_mock.children = list(original_children)
|
|
132
|
+
|
|
133
|
+
plain_file._process_single_acceptance_test_requirement(functional_requirement_mock)
|
|
134
|
+
|
|
135
|
+
assert not hasattr(
|
|
136
|
+
functional_requirement_mock, plain_spec.ACCEPTANCE_TESTS
|
|
137
|
+
), "acceptance_tests attribute should not be added if no ATs are found."
|
|
138
|
+
assert list(functional_requirement_mock.children) == original_children, "FR children should remain unchanged."
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_psart_empty_children_list_in_fr():
|
|
142
|
+
"""Tests behavior when the functional requirement itself has no children."""
|
|
143
|
+
functional_requirement_mock = MagicMock(spec=ListItem)
|
|
144
|
+
functional_requirement_mock.line_number = 1
|
|
145
|
+
functional_requirement_mock.children = []
|
|
146
|
+
|
|
147
|
+
plain_file._process_single_acceptance_test_requirement(functional_requirement_mock)
|
|
148
|
+
|
|
149
|
+
assert not hasattr(functional_requirement_mock, plain_spec.ACCEPTANCE_TESTS)
|
|
150
|
+
assert list(functional_requirement_mock.children) == []
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_psart_heading_not_followed_by_list():
|
|
154
|
+
"""Tests behavior if AT heading is present but not followed by a list token."""
|
|
155
|
+
functional_requirement_mock = MagicMock(spec=ListItem)
|
|
156
|
+
functional_requirement_mock.line_number = 1
|
|
157
|
+
|
|
158
|
+
mock_heading_raw_text = MagicMock(spec=RawText)
|
|
159
|
+
mock_heading_raw_text.content = plain_spec.ACCEPTANCE_TEST_HEADING
|
|
160
|
+
mock_heading_paragraph = MagicMock(spec=Paragraph)
|
|
161
|
+
mock_heading_paragraph.children = [mock_heading_raw_text]
|
|
162
|
+
mock_heading_paragraph.line_number = 2
|
|
163
|
+
|
|
164
|
+
mock_not_a_list_token = MagicMock(spec=Paragraph)
|
|
165
|
+
mock_not_a_list_token.children = []
|
|
166
|
+
|
|
167
|
+
original_children = [mock_heading_paragraph, mock_not_a_list_token]
|
|
168
|
+
functional_requirement_mock.children = list(original_children)
|
|
169
|
+
|
|
170
|
+
plain_file._process_single_acceptance_test_requirement(functional_requirement_mock)
|
|
171
|
+
|
|
172
|
+
assert not hasattr(
|
|
173
|
+
functional_requirement_mock, plain_spec.ACCEPTANCE_TESTS
|
|
174
|
+
), "acceptance_tests attribute should not be added."
|
|
175
|
+
assert len(functional_requirement_mock.children) == 2, "Children count should remain the same as original."
|
|
176
|
+
assert (
|
|
177
|
+
list(functional_requirement_mock.children) == original_children
|
|
178
|
+
), "Children should be unchanged as AT block was malformed."
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_psart_with_valid_acceptance_tests():
|
|
182
|
+
# Main item to test - OuterListItem
|
|
183
|
+
functional_requirement_mock = MagicMock(spec=ListItem)
|
|
184
|
+
functional_requirement_mock.line_number = 1
|
|
185
|
+
|
|
186
|
+
# Create the three children using helper methods
|
|
187
|
+
mock_fr_content = _create_mock_fr_content()
|
|
188
|
+
mock_at_heading_paragraph = _create_mock_at_heading_paragraph()
|
|
189
|
+
mock_at_list = _create_mock_at_list()
|
|
190
|
+
|
|
191
|
+
# Set up children of the functional requirement
|
|
192
|
+
functional_requirement_mock.children = [mock_fr_content, mock_at_heading_paragraph, mock_at_list]
|
|
193
|
+
|
|
194
|
+
is_acceptance_test_heading, acceptance_test_heading_problem = plain_file._is_acceptance_test_heading(
|
|
195
|
+
mock_at_heading_paragraph
|
|
196
|
+
)
|
|
197
|
+
assert is_acceptance_test_heading
|
|
198
|
+
assert acceptance_test_heading_problem is None
|
|
199
|
+
|
|
200
|
+
# Call the function under test
|
|
201
|
+
plain_file._process_single_acceptance_test_requirement(functional_requirement_mock)
|
|
202
|
+
|
|
203
|
+
# Verify acceptance_tests were extracted correctly
|
|
204
|
+
assert hasattr(functional_requirement_mock, plain_spec.ACCEPTANCE_TESTS)
|
|
205
|
+
assert len(functional_requirement_mock.acceptance_tests) == 1
|
|
206
|
+
assert mock_at_list.children[0] in functional_requirement_mock.acceptance_tests
|
|
207
|
+
|
|
208
|
+
# Verify only the FR content paragraph remains in children
|
|
209
|
+
assert len(functional_requirement_mock.children) == 1
|
|
210
|
+
assert mock_fr_content in functional_requirement_mock.children
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_psart_with_invalid_acceptance_test_heading():
|
|
214
|
+
functional_requirement_mock = MagicMock(spec=ListItem)
|
|
215
|
+
functional_requirement_mock.line_number = 1
|
|
216
|
+
|
|
217
|
+
# Create a heading paragraph with wrong content
|
|
218
|
+
mock_at_heading_paragraph = _create_mock_at_heading_paragraph()
|
|
219
|
+
|
|
220
|
+
# Change the content of the raw text to be invalid
|
|
221
|
+
mock_at_heading_paragraph.children[0].children[0].children[0].content = "Acceptance test" # missing colon
|
|
222
|
+
|
|
223
|
+
# Set up children of the functional requirement with just the invalid heading
|
|
224
|
+
functional_requirement_mock.children = [mock_at_heading_paragraph]
|
|
225
|
+
|
|
226
|
+
# Verify that invalid heading is detected
|
|
227
|
+
is_acceptance_test_heading, acceptance_test_heading_problem = plain_file._is_acceptance_test_heading(
|
|
228
|
+
mock_at_heading_paragraph
|
|
229
|
+
)
|
|
230
|
+
assert not is_acceptance_test_heading
|
|
231
|
+
assert acceptance_test_heading_problem is not None
|
|
232
|
+
|
|
233
|
+
# Call the function under test and expect a PlainSyntaxError
|
|
234
|
+
with pytest.raises(PlainSyntaxError):
|
|
235
|
+
plain_file._process_single_acceptance_test_requirement(functional_requirement_mock)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def test_psart_with_duplicate_acceptance_test_heading():
|
|
239
|
+
functional_requirement_mock = MagicMock(spec=ListItem)
|
|
240
|
+
functional_requirement_mock.line_number = 1
|
|
241
|
+
|
|
242
|
+
# Create content and list mocks
|
|
243
|
+
mock_fr_content = _create_mock_fr_content()
|
|
244
|
+
mock_at_list1 = _create_mock_at_list("First acceptance test")
|
|
245
|
+
mock_at_list2 = _create_mock_at_list("Second acceptance test", line_number=40)
|
|
246
|
+
|
|
247
|
+
# Create two heading paragraphs with different line numbers
|
|
248
|
+
mock_at_heading_paragraph1 = _create_mock_at_heading_paragraph(line_number=34)
|
|
249
|
+
mock_at_heading_paragraph2 = _create_mock_at_heading_paragraph(line_number=38)
|
|
250
|
+
|
|
251
|
+
# Set up children of the functional requirement with duplicate AT headings
|
|
252
|
+
functional_requirement_mock.children = [
|
|
253
|
+
mock_fr_content,
|
|
254
|
+
mock_at_heading_paragraph1,
|
|
255
|
+
mock_at_list1,
|
|
256
|
+
mock_at_heading_paragraph2,
|
|
257
|
+
mock_at_list2,
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
# Call the function under test and expect a PlainSyntaxError about duplicate headings
|
|
261
|
+
expected_error_message = f"Syntax error at line {mock_at_heading_paragraph2.line_number}: Duplicate 'acceptance tests' heading found within the same functional requirement. Only one block of acceptance tests is allowed per functional requirement."
|
|
262
|
+
|
|
263
|
+
with pytest.raises(PlainSyntaxError) as exc_info:
|
|
264
|
+
plain_file._process_single_acceptance_test_requirement(functional_requirement_mock)
|
|
265
|
+
|
|
266
|
+
assert str(exc_info.value) == expected_error_message
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def test_invalid_plain_file_extension():
|
|
270
|
+
with pytest.raises(PlainSyntaxError):
|
|
271
|
+
plain_file.plain_file_parser("test.txt", [])
|