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
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from liquid2 import Environment, RenderContext, TemplateSource
|
|
8
|
+
|
|
9
|
+
import file_utils
|
|
10
|
+
import plain_file
|
|
11
|
+
import plain_spec
|
|
12
|
+
from plain2code_exceptions import PlainSyntaxError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_regular_plain_source(get_test_data_path):
|
|
16
|
+
_, plain_sections, _ = plain_file.plain_file_parser(
|
|
17
|
+
"regular_plain_source.plain",
|
|
18
|
+
[get_test_data_path("data/plainfileparser")],
|
|
19
|
+
)
|
|
20
|
+
assert plain_sections == {
|
|
21
|
+
"definitions": [],
|
|
22
|
+
"technical specs": [
|
|
23
|
+
{"markdown": "- First non-functional requirement."},
|
|
24
|
+
{"markdown": "- Second non-functional requirement."},
|
|
25
|
+
],
|
|
26
|
+
"functional specs": [{"markdown": '- Display "hello, world"'}],
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_unknown_section():
|
|
31
|
+
plain_source = """
|
|
32
|
+
***definitions***
|
|
33
|
+
|
|
34
|
+
***Unknown Section:***
|
|
35
|
+
"""
|
|
36
|
+
with pytest.raises(
|
|
37
|
+
Exception,
|
|
38
|
+
match=re.escape(
|
|
39
|
+
"Syntax error at line 3: Invalid specification heading (`Unknown Section:`). Allowed headings: definitions, technical specs, test specs, functional specs, acceptance tests"
|
|
40
|
+
),
|
|
41
|
+
):
|
|
42
|
+
plain_file.parse_plain_source(plain_source, {}, [], [], [])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_duplicate_section():
|
|
46
|
+
plain_source = """
|
|
47
|
+
***definitions***
|
|
48
|
+
|
|
49
|
+
***definitions***
|
|
50
|
+
"""
|
|
51
|
+
with pytest.raises(
|
|
52
|
+
Exception,
|
|
53
|
+
match=re.escape("Syntax error at line 3: Duplicate specification heading (`definitions`)"),
|
|
54
|
+
):
|
|
55
|
+
plain_file.parse_plain_source(plain_source, {}, [], [], [])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_invalid_top_level_element():
|
|
59
|
+
plain_source = """
|
|
60
|
+
***definitions***
|
|
61
|
+
```
|
|
62
|
+
code block
|
|
63
|
+
```
|
|
64
|
+
"""
|
|
65
|
+
with pytest.raises(
|
|
66
|
+
Exception,
|
|
67
|
+
match=re.escape("Syntax error at line 2: Invalid source structure (`code block`)"),
|
|
68
|
+
):
|
|
69
|
+
plain_file.parse_plain_source(plain_source, {}, [], [], [])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_plain_file_parser_with_comments(get_test_data_path):
|
|
73
|
+
_, plain_sections, _ = plain_file.plain_file_parser(
|
|
74
|
+
"plain_file_parser_with_comments.plain",
|
|
75
|
+
[get_test_data_path("data/plainfileparser")],
|
|
76
|
+
)
|
|
77
|
+
assert plain_sections == {
|
|
78
|
+
"definitions": [],
|
|
79
|
+
"technical specs": [{"markdown": "- Second non-functional requirement."}],
|
|
80
|
+
"functional specs": [{"markdown": '- Display "hello, world"'}],
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_plain_file_parser_with_comments_indented(get_test_data_path):
|
|
85
|
+
_, plain_sections, _ = plain_file.plain_file_parser(
|
|
86
|
+
"plain_file_with_comments_indented.plain",
|
|
87
|
+
[get_test_data_path("data/plainfileparser")],
|
|
88
|
+
)
|
|
89
|
+
assert plain_sections == {
|
|
90
|
+
"definitions": [],
|
|
91
|
+
"technical specs": [
|
|
92
|
+
{"markdown": "- First non-functional requirement."},
|
|
93
|
+
{"markdown": "- Second non-functional requirement."},
|
|
94
|
+
],
|
|
95
|
+
"functional specs": [{"markdown": '- Display "hello, world"'}],
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_invalid_url_link(get_test_data_path):
|
|
100
|
+
with pytest.raises(Exception, match="Only relative links are allowed."):
|
|
101
|
+
plain_file.plain_file_parser("plain_source_with_url_link.plain", [get_test_data_path("data/plainfile")])
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_invalid_absolute_link(get_test_data_path):
|
|
105
|
+
with pytest.raises(Exception, match="Only relative links are allowed."):
|
|
106
|
+
plain_file.plain_file_parser(
|
|
107
|
+
"plain_source_with_absolute_link.plain",
|
|
108
|
+
[get_test_data_path("data/plainfile")],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_reference_link_parsing(get_test_data_path):
|
|
113
|
+
_, plain_sections, _ = plain_file.plain_file_parser(
|
|
114
|
+
"task_manager_with_reference_links.plain",
|
|
115
|
+
[get_test_data_path("data/plainfile")],
|
|
116
|
+
)
|
|
117
|
+
asserted_resources = [
|
|
118
|
+
"task_list_ui_specification.yaml",
|
|
119
|
+
"add_new_task_modal_specification.yaml",
|
|
120
|
+
]
|
|
121
|
+
for functional_requirement in plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS]:
|
|
122
|
+
if "linked_resources" not in functional_requirement:
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
for resource in functional_requirement["linked_resources"]:
|
|
126
|
+
assert resource["target"] in asserted_resources
|
|
127
|
+
del asserted_resources[asserted_resources.index(resource["target"])]
|
|
128
|
+
|
|
129
|
+
parsed_url = urlparse(resource["target"])
|
|
130
|
+
assert parsed_url.scheme == ""
|
|
131
|
+
|
|
132
|
+
assert not os.path.isabs(resource["target"])
|
|
133
|
+
|
|
134
|
+
assert asserted_resources == []
|
|
135
|
+
|
|
136
|
+
assert "`[resource]task_list_ui_specification.yaml`" in "\n".join(
|
|
137
|
+
[item["markdown"] for item in plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS]]
|
|
138
|
+
)
|
|
139
|
+
assert "`[resource]add_new_task_modal_specification.yaml`" in "\n".join(
|
|
140
|
+
[item["markdown"] for item in plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS]]
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
assert plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS][1]["linked_resources"] == [
|
|
144
|
+
{
|
|
145
|
+
"text": "task_list_ui_specification.yaml",
|
|
146
|
+
"target": "task_list_ui_specification.yaml",
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
assert plain_sections[plain_spec.FUNCTIONAL_REQUIREMENTS][2]["linked_resources"] == [
|
|
150
|
+
{
|
|
151
|
+
"text": "add_new_task_modal_specification.yaml",
|
|
152
|
+
"target": "add_new_task_modal_specification.yaml",
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_invalid_specification_order(get_test_data_path):
|
|
158
|
+
with pytest.raises(
|
|
159
|
+
Exception,
|
|
160
|
+
match="Syntax error at line 6: Definitions specification must be the first specification in the section.",
|
|
161
|
+
):
|
|
162
|
+
plain_file.plain_file_parser("invalid_specification_order.plain", [get_test_data_path("data/plainfile")])
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_duplicate_specification_heading(get_test_data_path):
|
|
166
|
+
with pytest.raises(
|
|
167
|
+
Exception,
|
|
168
|
+
match=re.escape("Syntax error at line 6: Duplicate specification heading (`definitions`)"),
|
|
169
|
+
):
|
|
170
|
+
plain_file.plain_file_parser(
|
|
171
|
+
"duplicate_specification_heading.plain",
|
|
172
|
+
[get_test_data_path("data/plainfile")],
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_missing_non_functional_requirements(get_test_data_path):
|
|
177
|
+
with pytest.raises(
|
|
178
|
+
Exception,
|
|
179
|
+
match="Syntax error: Functional requirement with no non-functional requirements specified.",
|
|
180
|
+
):
|
|
181
|
+
plain_file.plain_file_parser(
|
|
182
|
+
"missing_non_functional_requirements.plain",
|
|
183
|
+
[get_test_data_path("data/plainfile")],
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_without_non_functional_requirement(get_test_data_path):
|
|
188
|
+
with pytest.raises(
|
|
189
|
+
Exception,
|
|
190
|
+
match="Syntax error: Functional requirement with no non-functional requirements specified.",
|
|
191
|
+
):
|
|
192
|
+
plain_file.plain_file_parser(
|
|
193
|
+
"without_non_functional_requirement.plain",
|
|
194
|
+
[get_test_data_path("data/plainfile")],
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def test_indented_include_tags():
|
|
199
|
+
plain_source = """# Main
|
|
200
|
+
|
|
201
|
+
***definitions***
|
|
202
|
+
|
|
203
|
+
- This is a definition.
|
|
204
|
+
|
|
205
|
+
***technical specs***
|
|
206
|
+
- First non-functional requirement.
|
|
207
|
+
- Second non-functional requirement.
|
|
208
|
+
|
|
209
|
+
> This is a comment
|
|
210
|
+
> This is a with an include tag {% include "template.plain" %}
|
|
211
|
+
|
|
212
|
+
***functional specs***
|
|
213
|
+
- Implement {% include "implement.plain" %}
|
|
214
|
+
{% include "template.plain" %}
|
|
215
|
+
- Display "hello, world"
|
|
216
|
+
{% include "template.plain" %}
|
|
217
|
+
{% include "template.plain" %}
|
|
218
|
+
- Implement {% include "implement.plain" %}
|
|
219
|
+
"""
|
|
220
|
+
loaded_templates = {
|
|
221
|
+
"template.plain": "- This is a functional requirement inside a template.",
|
|
222
|
+
"implement.plain": """something nice and useful
|
|
223
|
+
- the nice thing should be really nice
|
|
224
|
+
- the useful thing should be really useful""",
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
expected_rendered_plain_source = """# Main
|
|
228
|
+
|
|
229
|
+
***definitions***
|
|
230
|
+
|
|
231
|
+
- This is a definition.
|
|
232
|
+
|
|
233
|
+
***technical specs***
|
|
234
|
+
- First non-functional requirement.
|
|
235
|
+
- Second non-functional requirement.
|
|
236
|
+
|
|
237
|
+
> This is a comment
|
|
238
|
+
> This is a with an include tag {% include 'template.plain' %}
|
|
239
|
+
|
|
240
|
+
***functional specs***
|
|
241
|
+
- Implement something nice and useful
|
|
242
|
+
- the nice thing should be really nice
|
|
243
|
+
- the useful thing should be really useful
|
|
244
|
+
- This is a functional requirement inside a template.
|
|
245
|
+
- Display "hello, world"
|
|
246
|
+
- This is a functional requirement inside a template.
|
|
247
|
+
- This is a functional requirement inside a template.
|
|
248
|
+
- Implement something nice and useful
|
|
249
|
+
- the nice thing should be really nice
|
|
250
|
+
- the useful thing should be really useful
|
|
251
|
+
"""
|
|
252
|
+
rendered_plain_source = plain_file.render_plain_source(plain_source, loaded_templates, {})
|
|
253
|
+
assert rendered_plain_source == expected_rendered_plain_source
|
|
254
|
+
|
|
255
|
+
def mock_get_source(
|
|
256
|
+
env: Environment,
|
|
257
|
+
template_name: str,
|
|
258
|
+
*,
|
|
259
|
+
context: RenderContext | None = None,
|
|
260
|
+
**kwargs: object,
|
|
261
|
+
):
|
|
262
|
+
return TemplateSource(
|
|
263
|
+
loaded_templates[template_name],
|
|
264
|
+
template_name,
|
|
265
|
+
lambda: True,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
with patch(
|
|
269
|
+
"liquid2.builtin.loaders.file_system_loader.FileSystemLoader.get_source",
|
|
270
|
+
side_effect=mock_get_source,
|
|
271
|
+
):
|
|
272
|
+
plain_source_result, loaded_templates_result = file_utils.get_loaded_templates(["."], plain_source)
|
|
273
|
+
assert plain_source_result == expected_rendered_plain_source
|
|
274
|
+
assert loaded_templates_result == loaded_templates
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_code_variables(load_test_data, get_test_data_path):
|
|
278
|
+
plain_source = load_test_data("data/templates/code_variables.plain")
|
|
279
|
+
loaded_templates = {
|
|
280
|
+
"implement.plain": load_test_data("data/templates/implement.plain"),
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
code_variables = {}
|
|
284
|
+
rendered_plain_source = plain_file.render_plain_source(plain_source, loaded_templates, code_variables)
|
|
285
|
+
keys = list(code_variables.keys())
|
|
286
|
+
|
|
287
|
+
expected_rendered_plain_source = f"""***definitions***
|
|
288
|
+
|
|
289
|
+
- :concept: is a concept.
|
|
290
|
+
|
|
291
|
+
***technical specs***
|
|
292
|
+
- First non-functional requirement.
|
|
293
|
+
- Second non-functional requirement.
|
|
294
|
+
|
|
295
|
+
***functional specs***
|
|
296
|
+
- Implement something nice and useful
|
|
297
|
+
- the nice thing should be really {keys[0]}
|
|
298
|
+
- the useful thing should be really useful
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
assert rendered_plain_source == expected_rendered_plain_source
|
|
302
|
+
|
|
303
|
+
_, plain_source, _ = plain_file.plain_file_parser("code_variables.plain", [get_test_data_path("data/templates")])
|
|
304
|
+
expected_plain_source = {
|
|
305
|
+
"definitions": [{"markdown": "- :concept: is a concept."}],
|
|
306
|
+
"technical specs": [
|
|
307
|
+
{"markdown": "- First non-functional requirement."},
|
|
308
|
+
{"markdown": "- Second non-functional requirement."},
|
|
309
|
+
],
|
|
310
|
+
"functional specs": [
|
|
311
|
+
{
|
|
312
|
+
"markdown": "- Implement something nice and useful\n - the nice thing should be really {{ variable_name }}\n - the useful thing should be really useful",
|
|
313
|
+
"code_variables": [{"name": "variable_name", "value": "nice"}],
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
assert plain_source == expected_plain_source
|
|
319
|
+
|
|
320
|
+
plain_source = load_test_data("data/templates/template_include.plain")
|
|
321
|
+
loaded_templates = {
|
|
322
|
+
"header.plain": load_test_data("data/templates/header.plain"),
|
|
323
|
+
"implement_2.plain": load_test_data("data/templates/implement_2.plain"),
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
code_variables = {}
|
|
327
|
+
rendered_plain_source = plain_file.render_plain_source(plain_source, loaded_templates, code_variables)
|
|
328
|
+
keys = list(code_variables.keys())
|
|
329
|
+
expected_rendered_plain_source = f"""***definitions***
|
|
330
|
+
|
|
331
|
+
- :concept: is a concept.
|
|
332
|
+
|
|
333
|
+
***technical specs***
|
|
334
|
+
- First non-functional requirement {keys[0]}.
|
|
335
|
+
- Second non-functional requirement {keys[1]}.
|
|
336
|
+
|
|
337
|
+
***functional specs***
|
|
338
|
+
- Implement something nice and useful
|
|
339
|
+
- the nice thing should be really {keys[2]}
|
|
340
|
+
- the useful thing should be really useful
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
assert rendered_plain_source == expected_rendered_plain_source
|
|
344
|
+
|
|
345
|
+
_, plain_source, _ = plain_file.plain_file_parser("template_include.plain", [get_test_data_path("data/templates")])
|
|
346
|
+
expected_plain_source = {
|
|
347
|
+
"definitions": [{"markdown": "- :concept: is a concept."}],
|
|
348
|
+
"technical specs": [
|
|
349
|
+
{
|
|
350
|
+
"markdown": "- First non-functional requirement {{ variable_name_1 }}.",
|
|
351
|
+
"code_variables": [{"name": "variable_name_1", "value": "nice_1"}],
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"markdown": "- Second non-functional requirement {{ variable_name_1 }}.",
|
|
355
|
+
"code_variables": [{"name": "variable_name_1", "value": "nice_2"}],
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
"functional specs": [
|
|
359
|
+
{
|
|
360
|
+
"markdown": "- Implement something nice and useful\n - the nice thing should be really {{ variable_name_1 }}\n - the useful thing should be really useful",
|
|
361
|
+
"code_variables": [{"name": "variable_name_1", "value": "nice"}],
|
|
362
|
+
}
|
|
363
|
+
],
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
assert plain_source == expected_plain_source
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def test_acceptance_tests_block_include_with_trailing_newline_keeps_structure_and_ignores_quote(get_test_data_path):
|
|
370
|
+
"""
|
|
371
|
+
Ensures that a block-level include inside ***acceptance tests*** whose template ends
|
|
372
|
+
with a trailing newline does not terminate the list, and that a following '> ...' line
|
|
373
|
+
is treated as a comment and ignored. The parser should not raise a syntax error.
|
|
374
|
+
It's an error we encountered while implementing custom rendering and should break in case of regression.
|
|
375
|
+
"""
|
|
376
|
+
plain_file.plain_file_parser("block_level_include.plain", [get_test_data_path("data/templates")])
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def test_concept_validation_definitions(get_test_data_path):
|
|
380
|
+
plain_file.plain_file_parser(
|
|
381
|
+
"concept_validation_definition.plain",
|
|
382
|
+
[get_test_data_path("data/plainfileparser")],
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
with pytest.raises(PlainSyntaxError):
|
|
386
|
+
plain_file.plain_file_parser(
|
|
387
|
+
"concept_validation_noconcepts.plain",
|
|
388
|
+
[get_test_data_path("data/plainfileparser")],
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def test_concept_validation_usage(get_test_data_path):
|
|
393
|
+
plain_file.plain_file_parser(
|
|
394
|
+
"concept_validation_valid.plain",
|
|
395
|
+
[get_test_data_path("data/plainfileparser")],
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
with pytest.raises(PlainSyntaxError):
|
|
399
|
+
plain_file.plain_file_parser(
|
|
400
|
+
"concept_validation_nondefined.plain",
|
|
401
|
+
[get_test_data_path("data/plainfileparser")],
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
with pytest.raises(PlainSyntaxError):
|
|
405
|
+
plain_file.plain_file_parser(
|
|
406
|
+
"concept_validation_defined_nondefined.plain",
|
|
407
|
+
[get_test_data_path("data/plainfileparser")],
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
with pytest.raises(PlainSyntaxError):
|
|
411
|
+
plain_file.plain_file_parser(
|
|
412
|
+
"concept_validation_defined_nondefined_2.plain",
|
|
413
|
+
[get_test_data_path("data/plainfileparser")],
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def test_concept_validation_redefinition(get_test_data_path):
|
|
418
|
+
with pytest.raises(PlainSyntaxError):
|
|
419
|
+
plain_file.plain_file_parser(
|
|
420
|
+
"concept_validation_redefinition.plain",
|
|
421
|
+
[get_test_data_path("data/plainfileparser")],
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def test_concept_validation_non_concept_usage(get_test_data_path):
|
|
426
|
+
plain_file.plain_file_parser(
|
|
427
|
+
"concept_validation_nonconcept.plain",
|
|
428
|
+
[get_test_data_path("data/plainfileparser")],
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def test_concept_validation_several_concepts(get_test_data_path):
|
|
433
|
+
plain_file.plain_file_parser(
|
|
434
|
+
"concept_validation_several_concepts.plain",
|
|
435
|
+
[get_test_data_path("data/plainfileparser")],
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def test_concept_validation_acceptance_tests(get_test_data_path):
|
|
440
|
+
plain_file.plain_file_parser(
|
|
441
|
+
"concept_validation_acceptance_tests.plain",
|
|
442
|
+
[get_test_data_path("data/plainfileparser")],
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
with pytest.raises(PlainSyntaxError):
|
|
446
|
+
plain_file.plain_file_parser(
|
|
447
|
+
"concept_validation_acceptance_tests_nondefined.plain",
|
|
448
|
+
[get_test_data_path("data/plainfileparser")],
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def test_required_concepts(get_test_data_path):
|
|
453
|
+
plain_file.plain_file_parser(
|
|
454
|
+
"required_concepts_example.plain",
|
|
455
|
+
[get_test_data_path("data/plainfileparser")],
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
plain_file.plain_file_parser(
|
|
459
|
+
"required_concepts_module.plain",
|
|
460
|
+
[get_test_data_path("data/plainfileparser")],
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
plain_file.plain_file_parser(
|
|
464
|
+
"required_concepts_partial.plain",
|
|
465
|
+
[get_test_data_path("data/plainfileparser")],
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
with pytest.raises(PlainSyntaxError):
|
|
469
|
+
plain_file.plain_file_parser(
|
|
470
|
+
"required_concepts_missing.plain",
|
|
471
|
+
[get_test_data_path("data/plainfileparser")],
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
with pytest.raises(PlainSyntaxError):
|
|
475
|
+
plain_file.plain_file_parser(
|
|
476
|
+
"required_concepts_partial_duplicate.plain",
|
|
477
|
+
[get_test_data_path("data/plainfileparser")],
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def test_exported_concepts(get_test_data_path):
|
|
482
|
+
plain_file.plain_file_parser(
|
|
483
|
+
"exported_concepts_example.plain",
|
|
484
|
+
[get_test_data_path("data/plainfileparser")],
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
plain_file.plain_file_parser(
|
|
488
|
+
"exported_concepts_nested_example.plain",
|
|
489
|
+
[get_test_data_path("data/plainfileparser")],
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
with pytest.raises(PlainSyntaxError):
|
|
493
|
+
plain_file.plain_file_parser(
|
|
494
|
+
"exported_concepts_missing_example.plain",
|
|
495
|
+
[get_test_data_path("data/plainfileparser")],
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
with pytest.raises(PlainSyntaxError):
|
|
499
|
+
plain_file.plain_file_parser(
|
|
500
|
+
"exported_concepts_transitive_example.plain",
|
|
501
|
+
[get_test_data_path("data/plainfileparser")],
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def test_topological_sort(get_test_data_path):
|
|
506
|
+
_, plain_source, _ = plain_file.plain_file_parser(
|
|
507
|
+
"topological_sort.plain", [get_test_data_path("data/plainfileparser")]
|
|
508
|
+
)
|
|
509
|
+
assert plain_spec.DEFINITIONS in plain_source
|
|
510
|
+
assert plain_source[plain_spec.DEFINITIONS] == [
|
|
511
|
+
{"markdown": "- :Concept1: is a concept."},
|
|
512
|
+
{"markdown": "- :Concept2: is a concept that depends on the :Concept1: concept."},
|
|
513
|
+
{"markdown": "- :Concept3: is a concept that depends on both the :Concept1: and :Concept2: concepts."},
|
|
514
|
+
]
|
|
515
|
+
|
|
516
|
+
_, plain_source, _ = plain_file.plain_file_parser(
|
|
517
|
+
"topological_sort_not_referenced.plain",
|
|
518
|
+
[get_test_data_path("data/plainfileparser")],
|
|
519
|
+
)
|
|
520
|
+
assert plain_spec.DEFINITIONS in plain_source
|
|
521
|
+
assert plain_source[plain_spec.DEFINITIONS] == [
|
|
522
|
+
{"markdown": "- :Concept1: is a concept."},
|
|
523
|
+
{"markdown": "- :NonReferencedConcept: is a concept."},
|
|
524
|
+
{"markdown": "- :Concept7: is a concept."},
|
|
525
|
+
{"markdown": "- :Concept2: is a concept that depends on the :Concept1: concept."},
|
|
526
|
+
{"markdown": "- :Concept8: is a concept that depends on the :Concept1: concept."},
|
|
527
|
+
{"markdown": "- :Concept5: is a concept that depends on the :Concept1: concept."},
|
|
528
|
+
{"markdown": "- :Concept3: is a concept that depends on both the :Concept1: and :Concept2: concepts."},
|
|
529
|
+
{"markdown": "- :Concept9: is a concept that depends on the :Concept8:."},
|
|
530
|
+
{"markdown": "- :Concept4: is a concept that depends on the :Concept3: concept."},
|
|
531
|
+
{"markdown": "- :Concept6: is a concept that depends on the :Concept1: and :Concept4: concepts."},
|
|
532
|
+
]
|
tests/test_plainspec.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import plain_file
|
|
4
|
+
import plain_spec
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_get_linked_resources_invalid_input():
|
|
8
|
+
with pytest.raises(ValueError, match="plain_source_tree must be a dictionary."):
|
|
9
|
+
plain_spec.collect_linked_resources([], [], None, True)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_get_frids_simple(get_test_data_path):
|
|
13
|
+
_, plain_source, _ = plain_file.plain_file_parser("simple.plain", [get_test_data_path("data/")])
|
|
14
|
+
|
|
15
|
+
frids = list(plain_spec.get_frids(plain_source))
|
|
16
|
+
|
|
17
|
+
assert frids == ["1"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_get_first_functional_requirement_simple(get_test_data_path):
|
|
21
|
+
_, plain_source, _ = plain_file.plain_file_parser("simple.plain", [get_test_data_path("data/")])
|
|
22
|
+
|
|
23
|
+
assert plain_spec.get_first_frid(plain_source) == "1"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_get_next_frid_not_exists(get_test_data_path):
|
|
27
|
+
_, plain_source, _ = plain_file.plain_file_parser("simple.plain", [get_test_data_path("data/")])
|
|
28
|
+
|
|
29
|
+
with pytest.raises(Exception, match="Functional requirement 2 does not exist."):
|
|
30
|
+
plain_spec.get_next_frid(plain_source, "2")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_get_next_frid_simple(get_test_data_path):
|
|
34
|
+
_, plain_source, _ = plain_file.plain_file_parser("simple.plain", [get_test_data_path("data/")])
|
|
35
|
+
|
|
36
|
+
assert plain_spec.get_next_frid(plain_source, "1") is None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_get_previous_frid_not_exists(get_test_data_path):
|
|
40
|
+
_, plain_source, _ = plain_file.plain_file_parser("simple.plain", [get_test_data_path("data/")])
|
|
41
|
+
|
|
42
|
+
with pytest.raises(Exception, match="Functional requirement 2 does not exist."):
|
|
43
|
+
plain_spec.get_previous_frid(plain_source, "2")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_get_previous_frid_no_previous_frid(get_test_data_path):
|
|
47
|
+
_, plain_source, _ = plain_file.plain_file_parser("simple.plain", [get_test_data_path("data/")])
|
|
48
|
+
|
|
49
|
+
assert plain_spec.get_previous_frid(plain_source, "1") is None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_get_specifications_simple(get_test_data_path):
|
|
53
|
+
_, plain_source, _ = plain_file.plain_file_parser("simple.plain", [get_test_data_path("data/")])
|
|
54
|
+
|
|
55
|
+
with pytest.raises(Exception, match="Functional requirement a does not exist."):
|
|
56
|
+
plain_spec.get_specifications_for_frid(plain_source, "a")
|
|
57
|
+
|
|
58
|
+
frid = plain_spec.get_first_frid(plain_source)
|
|
59
|
+
|
|
60
|
+
specifications, _ = plain_spec.get_specifications_for_frid(plain_source, frid)
|
|
61
|
+
|
|
62
|
+
assert specifications == {
|
|
63
|
+
"definitions": [],
|
|
64
|
+
"technical specs": ["- Simple non-functional requirement"],
|
|
65
|
+
"test specs": [],
|
|
66
|
+
"functional specs": ["- Simple functional requirement"],
|
|
67
|
+
}
|
tests/test_requires.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
import plain_file
|
|
4
|
+
from plain2code_exceptions import PlainSyntaxError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_non_existent_require(get_test_data_path):
|
|
8
|
+
with pytest.raises(PlainSyntaxError, match="Module does not exist"):
|
|
9
|
+
plain_file.plain_file_parser("non_existent_require.plain", [get_test_data_path("data/requires")])
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_independent_requires(get_test_data_path):
|
|
13
|
+
with pytest.raises(Exception, match="There must be a fixed order how required modules are dependent"):
|
|
14
|
+
plain_file.plain_file_parser("independent_requires_main.plain", [get_test_data_path("data/requires")])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_diamond_requires(get_test_data_path):
|
|
18
|
+
with pytest.raises(Exception, match="There must be a fixed order how required modules are dependent"):
|
|
19
|
+
plain_file.plain_file_parser("diamond_requires_main.plain", [get_test_data_path("data/requires")])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_circular_requires(get_test_data_path):
|
|
23
|
+
with pytest.raises(Exception, match="Circular required module detected"):
|
|
24
|
+
plain_file.plain_file_parser("circular_requires_main.plain", [get_test_data_path("data/requires")])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_normal_requires(get_test_data_path):
|
|
28
|
+
plain_file.plain_file_parser("normal_requires_main.plain", [get_test_data_path("data/requires")])
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
code_complexity
|
|
2
|
-
codeplain
|
|
3
|
-
codeplain_REST_api
|
|
4
|
-
codeplain_constants
|
|
5
|
-
codeplain_local_api
|
|
6
|
-
codeplain_models
|
|
7
|
-
codeplain_types
|
|
8
|
-
codeplain_utils
|
|
9
|
-
concept_utils
|
|
10
|
-
config
|
|
11
|
-
content_extractor
|
|
12
|
-
event_bus
|
|
13
|
-
file_utils
|
|
14
|
-
git_utils
|
|
15
|
-
hash_key
|
|
16
|
-
llm
|
|
17
|
-
llm_exceptions
|
|
18
|
-
llm_handler
|
|
19
|
-
llm_selector
|
|
20
|
-
module_renderer
|
|
21
|
-
plain2code
|
|
22
|
-
plain2code_arguments
|
|
23
|
-
plain2code_console
|
|
24
|
-
plain2code_events
|
|
25
|
-
plain2code_exceptions
|
|
26
|
-
plain2code_logger
|
|
27
|
-
plain2code_nodes
|
|
28
|
-
plain2code_read_config
|
|
29
|
-
plain2code_state
|
|
30
|
-
plain2code_tui
|
|
31
|
-
plain2code_utils
|
|
32
|
-
plain_file
|
|
33
|
-
plain_modules
|
|
34
|
-
plain_spec
|
|
35
|
-
render_cache
|
|
36
|
-
render_machine
|
|
37
|
-
spinner
|
|
38
|
-
standard_template_library
|
|
39
|
-
system_config
|
|
40
|
-
tui
|
|
41
|
-
tui_components
|
|
File without changes
|
|
File without changes
|