StrictDoc Documentation
tests/unit/strictdoc/backend/sdoc_source_code/readers/test_reader_general.py
Source file coverage
Path:
tests/unit/strictdoc/backend/sdoc_source_code/readers/test_reader_general.py
Lines:
500
Non-empty lines:
392
Non-empty lines covered with requirements:
392 / 392 (100.0%)
Functions:
21
Functions covered by requirements:
21 / 21 (100.0%)
1
"""
2
@relation(SDOC-SRS-142, scope=file)
3
"""
4
 
5
from typing import List
6
 
7
import pytest
8
 
9
from strictdoc.backend.sdoc.error_handling import StrictDocSemanticError
10
from strictdoc.backend.sdoc_source_code.reader import (
11
    RangeMarker,
12
    SourceFileTraceabilityReader,
13
)
14
 
15
 
16
def test_001_one_range_marker():
17
    source_input = """
18
    # @relation(REQ-001, scope=range_start)
19
    CONTENT 1
20
    CONTENT 2
21
    CONTENT 3
22
    # @relation(REQ-001, scope=range_end)
23
    """.lstrip()
24
 
25
    reader = SourceFileTraceabilityReader()
26
 
27
    document = reader.read(source_input)
28
    markers = document.markers
29
    assert markers[0].reqs == ["REQ-001"]
30
    assert markers[0].is_begin()
31
    assert markers[0].ng_source_line_begin == 1
32
    assert markers[0].ng_range_line_begin == 1
33
    assert markers[0].ng_range_line_end == 5
34
 
35
    assert markers[1].reqs == ["REQ-001"]
36
    assert markers[1].is_end()
37
    assert markers[1].ng_source_line_begin == 5
38
    assert markers[1].ng_range_line_begin == 1
39
    assert markers[1].ng_range_line_end == 5
40
 
41
 
42
def test_002_two_range_markers():
43
    source_input = """
44
# @relation(REQ-001, scope=range_start)
45
CONTENT 1
46
CONTENT 2
47
CONTENT 3
48
# @relation(REQ-001, scope=range_end)
49
# @relation(REQ-002, scope=range_start)
50
CONTENT 4
51
CONTENT 5
52
CONTENT 6
53
# @relation(REQ-002, scope=range_end)
54
""".lstrip()
55
 
56
    reader = SourceFileTraceabilityReader()
57
 
58
    document = reader.read(source_input)
59
    markers = document.markers
60
    assert len(markers) == 4
61
    marker_1 = markers[0]
62
    marker_2 = markers[1]
63
    marker_3 = markers[2]
64
    marker_4 = markers[3]
65
    assert marker_1.reqs == ["REQ-001"]
66
    assert marker_2.reqs == ["REQ-001"]
67
    assert marker_3.reqs == ["REQ-002"]
68
    assert marker_4.reqs == ["REQ-002"]
69
 
70
    assert marker_1.ng_source_line_begin == 1
71
    assert marker_2.ng_source_line_begin == 5
72
    assert marker_3.ng_source_line_begin == 6
73
    assert marker_4.ng_source_line_begin == 10
74
 
75
    assert marker_1.ng_range_line_begin == 1
76
    assert marker_2.ng_range_line_begin == 1
77
    assert marker_3.ng_range_line_begin == 6
78
    assert marker_4.ng_range_line_begin == 6
79
 
80
 
81
def test_003_marker_with_dashes_and_underscores():
82
    """
83
    Verifies that SF_REQ-001 markers can be parsed (identifiers with mixed _ and -).
84
 
85
    Bug report: https://github.com/strictdoc-project/strictdoc/discussions/2568.
86
    """
87
 
88
    source_input = """\
89
# @relation(SF_REQ-001, scope=file)
90
CONTENT 1
91
CONTENT 2
92
CONTENT 3
93
""".lstrip()
94
 
95
    reader = SourceFileTraceabilityReader()
96
 
97
    document = reader.read(source_input)
98
    markers = document.markers
99
    assert markers[0].reqs == ["SF_REQ-001"]
100
    assert markers[0].is_begin()
101
    assert markers[0].ng_source_line_begin == 1
102
    assert markers[0].ng_range_line_begin == 1
103
    assert markers[0].ng_range_line_end == 4
104
 
105
 
106
def test_005_no_markers():
107
    source_input = """
108
def hello_world_2():
109
 
110
    print("hello world")  # noqa: T201
111
""".lstrip()
112
 
113
    reader = SourceFileTraceabilityReader()
114
    _ = reader.read(source_input)
115
 
116
 
117
def test_006_empty_file():
118
    source_input = ""
119
 
120
    reader = SourceFileTraceabilityReader()
121
    traceability_info = reader.read(source_input)
122
 
123
    assert traceability_info.markers == []
124
 
125
 
126
def test_007_single_line_with_no_newline():
127
    source_input = "Single line"
128
 
129
    reader = SourceFileTraceabilityReader()
130
    traceability_info = reader.read(source_input)
131
 
132
    assert traceability_info.markers == []
133
 
134
 
135
def test_008_three_nested_range_markers():
136
    source_input = """
137
CONTENT 1
138
# @relation(REQ-001, scope=range_start)
139
CONTENT 2
140
# @relation(REQ-002, scope=range_start)
141
CONTENT 3
142
# @relation(REQ-003, scope=range_start)
143
CONTENT 4
144
# @relation(REQ-003, scope=range_end)
145
CONTENT 5
146
# @relation(REQ-002, scope=range_end)
147
CONTENT 6
148
# @relation(REQ-001, scope=range_end)
149
CONTENT 7
150
# @relation(REQ-001, scope=range_start)
151
CONTENT 8
152
# @relation(REQ-001, scope=range_end)
153
CONTENT 9
154
""".lstrip()
155
 
156
    reader = SourceFileTraceabilityReader()
157
 
158
    document = reader.read(source_input)
159
    markers = document.markers
160
    assert len(markers) == 8
161
    marker_1 = markers[0]
162
    marker_2 = markers[1]
163
    marker_3 = markers[2]
164
    marker_4 = markers[3]
165
    marker_5 = markers[4]
166
    marker_6 = markers[5]
167
    marker_7 = markers[6]
168
    marker_8 = markers[7]
169
    assert marker_1.reqs == ["REQ-001"]
170
    assert marker_2.reqs == ["REQ-002"]
171
    assert marker_3.reqs == ["REQ-003"]
172
    assert marker_4.reqs == ["REQ-003"]
173
    assert marker_5.reqs == ["REQ-002"]
174
    assert marker_6.reqs == ["REQ-001"]
175
    assert marker_7.reqs == ["REQ-001"]
176
    assert marker_8.reqs == ["REQ-001"]
177
 
178
    assert marker_1.ng_source_line_begin == 2
179
    assert marker_2.ng_source_line_begin == 4
180
    assert marker_3.ng_source_line_begin == 6
181
    assert marker_4.ng_source_line_begin == 8
182
    assert marker_5.ng_source_line_begin == 10
183
    assert marker_6.ng_source_line_begin == 12
184
    assert marker_7.ng_source_line_begin == 14
185
    assert marker_8.ng_source_line_begin == 16
186
 
187
    assert marker_1.ng_range_line_begin == 2
188
    assert marker_2.ng_range_line_begin == 4
189
    assert marker_3.ng_range_line_begin == 6
190
    assert marker_4.ng_range_line_begin == 6
191
    assert marker_5.ng_range_line_begin == 4
192
    assert marker_6.ng_range_line_begin == 2
193
    assert marker_7.ng_range_line_begin == 14
194
    assert marker_8.ng_range_line_begin == 14
195
 
196
 
197
def test_009_two_requirements_in_one_marker():
198
    source_input = """
199
# @relation(REQ-001, REQ-002, scope=range_start)
200
CONTENT 1
201
CONTENT 2
202
CONTENT 3
203
# @relation(REQ-001, REQ-002, scope=range_end)
204
""".lstrip()
205
 
206
    reader = SourceFileTraceabilityReader()
207
 
208
    document = reader.read(source_input)
209
    markers = document.markers
210
    assert markers[0].reqs == ["REQ-001", "REQ-002"]
211
    assert markers[0].is_begin()
212
    assert markers[0].ng_source_line_begin == 1
213
    assert markers[0].ng_range_line_begin == 1
214
    assert markers[0].ng_range_line_end == 5
215
 
216
    assert markers[1].reqs == ["REQ-001", "REQ-002"]
217
    assert markers[1].is_end()
218
    assert markers[1].ng_source_line_begin == 5
219
    assert markers[1].ng_range_line_begin == 1
220
    assert markers[1].ng_range_line_end == 5
221
 
222
 
223
def test_010_relation_skip_keyword():
224
    source_input = """
225
# @relation(skip, scope=range_start)
226
# @relation(REQ-001, scope=range_start)
227
CONTENT 1
228
CONTENT 2
229
CONTENT 3
230
# @relation(REQ-001, scope=range_end)
231
# @relation(skip, scope=range_end)
232
""".lstrip()
233
 
234
    reader = SourceFileTraceabilityReader()
235
 
236
    document = reader.read(source_input)
237
    assert len(document.g_parts) == 7
238
    assert len(document.markers) == 0
239
 
240
 
241
def test_011_relation_skip_keyword_then_normal_marker():
242
    source_input = """
243
# @relation(skip, scope=range_start)
244
# @relation(REQ-001, scope=range_start)
245
CONTENT 1
246
CONTENT 2
247
CONTENT 3
248
# @relation(REQ-001, scope=range_end)
249
# @relation(skip, scope=range_end)
250
# @relation(REQ-001, scope=range_start)
251
CONTENT 1
252
CONTENT 2
253
CONTENT 3
254
# @relation(REQ-001, scope=range_end)
255
""".lstrip()
256
 
257
    reader = SourceFileTraceabilityReader()
258
 
259
    document = reader.read(source_input)
260
    assert len(document.g_parts) == 12
261
    assert len(document.markers) == 2
262
 
263
 
264
def test_012_relation_skip_keyword_then_normal_marker_4spaces_indent():
265
    source_input = """
266
    # @relation(skip, scope=range_start)
267
    # @relation(REQ-001, scope=range_start)
268
    CONTENT 1
269
    CONTENT 2
270
    CONTENT 3
271
    # @relation(REQ-001, scope=range_end)
272
    # @relation(skip, scope=range_end)
273
    # @relation(REQ-001, scope=range_start)
274
    CONTENT 1
275
    CONTENT 2
276
    CONTENT 3
277
    # @relation(REQ-001, scope=range_end)
278
""".lstrip()
279
 
280
    reader = SourceFileTraceabilityReader()
281
 
282
    document = reader.read(source_input)
283
    assert len(document.g_parts) == 12
284
    assert len(document.markers) == 2
285
 
286
 
287
def test_013_relation_skip_entire_file():
288
    source_input = """\
289
# @relation(skip, scope=file)
290
# @relation(REQ-001, scope=range_start)
291
CONTENT 1
292
CONTENT 2
293
CONTENT 3
294
# @relation(REQ-001, scope=range_end)
295
# @relation(REQ-001, scope=range_start)
296
CONTENT 1
297
CONTENT 2
298
CONTENT 3
299
# @relation(REQ-001, scope=range_end)
300
"""
301
 
302
    reader = SourceFileTraceabilityReader()
303
 
304
    document = reader.read(source_input)
305
    assert len(document.g_parts) == 11
306
    assert len(document.markers) == 0
307
 
308
 
309
# Testing that textx assigns correct line location when the marker is not on the
310
# first line.
311
def test_020_marker_not_first_line():
312
    source_input = """
313
 
314
 
315
# @relation(REQ-001, scope=range_start)
316
CONTENT 1
317
CONTENT 2
318
CONTENT 3
319
# @relation(REQ-001, scope=range_end)
320
 
321
 
322
 
323
"""
324
 
325
    reader = SourceFileTraceabilityReader()
326
 
327
    document = reader.read(source_input)
328
 
329
    markers: List[RangeMarker] = document.markers
330
    assert markers[0].reqs == ["REQ-001"]
331
    assert markers[0].is_begin()
332
    assert markers[0].ng_source_line_begin == 4
333
    assert markers[0].ng_range_line_begin == 4
334
    assert markers[0].ng_range_line_end == 8
335
 
336
    assert markers[1].reqs == ["REQ-001"]
337
    assert markers[1].is_end()
338
    assert markers[1].ng_source_line_begin == 8
339
    assert markers[1].ng_range_line_begin == 4
340
    assert markers[1].ng_range_line_end == 8
341
 
342
 
343
def test_031_one_range_marker():
344
    source_input = """
345
@relation(REQ-001, scope=range_start)
346
CONTENT 1
347
CONTENT 2
348
CONTENT 3
349
@relation(REQ-001, scope=range_end)
350
""".lstrip()
351
 
352
    reader = SourceFileTraceabilityReader()
353
 
354
    document = reader.read(source_input)
355
    markers = document.markers
356
    assert markers[0].reqs == ["REQ-001"]
357
    assert markers[0].is_begin()
358
    assert markers[0].ng_source_line_begin == 1
359
    assert markers[0].ng_range_line_begin == 1
360
    assert markers[0].ng_range_line_end == 5
361
 
362
    assert markers[1].reqs == ["REQ-001"]
363
    assert markers[1].is_end()
364
    assert markers[1].ng_source_line_begin == 5
365
    assert markers[1].ng_range_line_begin == 1
366
    assert markers[1].ng_range_line_end == 5
367
 
368
 
369
def test_050_line_marker():
370
    source_input = """
371
# @relation(REQ-001, scope=line)
372
CONTENT 1
373
# @relation(REQ-002, scope=line)
374
CONTENT 2
375
# @relation(REQ-003, scope=line)
376
CONTENT 3
377
""".lstrip()
378
 
379
    reader = SourceFileTraceabilityReader()
380
 
381
    document = reader.read(source_input)
382
    markers = document.markers
383
    assert markers[0].reqs == ["REQ-001"]
384
    assert markers[0].ng_source_line_begin == 1
385
    assert markers[0].ng_range_line_begin == 1
386
    assert markers[0].ng_range_line_end == 2
387
    assert markers[1].reqs == ["REQ-002"]
388
    assert markers[1].ng_source_line_begin == 3
389
    assert markers[1].ng_range_line_begin == 3
390
    assert markers[1].ng_range_line_end == 4
391
    assert markers[2].reqs == ["REQ-003"]
392
    assert markers[2].ng_source_line_begin == 5
393
    assert markers[2].ng_range_line_begin == 5
394
    assert markers[2].ng_range_line_end == 6
395
 
396
 
397
def test_060_file_level_marker():
398
    source_input = """\
399
\"\"\"
400
@relation(REQ-001, scope=file)
401
\"\"\"
402
 
403
def hello_world():
404
    pass
405
""".lstrip()
406
 
407
    reader = SourceFileTraceabilityReader()
408
 
409
    document = reader.read(source_input)
410
    assert len(document.markers) == 1
411
    markers = document.markers
412
    assert markers[0].reqs == ["REQ-001"]
413
    assert markers[0].ng_source_line_begin == 2
414
    assert markers[0].ng_source_column_begin == 1
415
    assert markers[0].ng_range_line_begin == 1
416
    assert markers[0].ng_range_line_end == 6
417
 
418
 
419
def test_validation_01_one_range_marker_begin_req_not_equal_to_end_req():
420
    source_input = """
421
# @relation(REQ-001, scope=range_start)
422
CONTENT 1
423
CONTENT 2
424
CONTENT 3
425
# @relation(REQ-002, scope=range_end)
426
""".lstrip()
427
 
428
    reader = SourceFileTraceabilityReader()
429
 
430
    with pytest.raises(Exception) as exc_info:
431
        _ = reader.read(source_input)
432
 
433
    assert exc_info.type is StrictDocSemanticError
434
    assert (
435
        exc_info.value.args[0]
436
        == "STRICTDOC RANGE: BEGIN and END requirements mismatch"
437
    )
438
 
439
 
440
def test_validation_02_one_range_marker_end_without_begin():
441
    source_input = """
442
# @relation(REQ-002, scope=range_end)
443
""".lstrip()
444
 
445
    reader = SourceFileTraceabilityReader()
446
 
447
    with pytest.raises(Exception) as exc_info:
448
        _ = reader.read(source_input)
449
 
450
    assert exc_info.type is StrictDocSemanticError
451
    assert (
452
        exc_info.value.args[0]
453
        == "STRICTDOC RANGE: END marker without preceding BEGIN marker"
454
    )
455
 
456
 
457
def test_validation_03_range_start_without_range_end():
458
    source_input = """
459
# @relation(REQ-001, scope=range_start)
460
CONTENT 1
461
CONTENT 2
462
CONTENT 3
463
""".lstrip()
464
 
465
    reader = SourceFileTraceabilityReader()
466
 
467
    with pytest.raises(Exception) as exc_info:
468
        _ = reader.read(source_input)
469
 
470
    assert exc_info.type is StrictDocSemanticError
471
    assert (
472
        exc_info.value.args[0]
473
        == "Unmatched @relation keyword found in source file."
474
    )
475
 
476
 
477
def test_validation_04_consecutive_line_markers():
478
    source_input = """
479
    # @relation(REQ-001, scope=line)
480
    # @relation(REQ-002, scope=line)
481
    """
482
    reader = SourceFileTraceabilityReader()
483
 
484
    with pytest.raises(Exception) as exc_info:
485
        _ = reader.read(source_input)
486
 
487
    assert exc_info.type is StrictDocSemanticError
488
    assert exc_info.value.args[0] == "Consecutive LineMarkers are not allowed"
489
 
490
 
491
def test_validation_05_line_marker_followed_by_eof():
492
    source_input = "# @relation(REQ-001, scope=line)"
493
 
494
    reader = SourceFileTraceabilityReader()
495
 
496
    with pytest.raises(Exception) as exc_info:
497
        _ = reader.read(source_input)
498
 
499
    assert exc_info.type is StrictDocSemanticError
500
    assert exc_info.value.args[0] == "LineMarker cannot be followed by EOF"