StrictDoc Documentation
tests/unit/strictdoc/core/test_traceability_index.py
Source file coverage
Path:
tests/unit/strictdoc/core/test_traceability_index.py
Lines:
486
Non-empty lines:
427
Non-empty lines covered with requirements:
427 / 427 (100.0%)
Functions:
12
Functions covered by requirements:
12 / 12 (100.0%)
1
"""
2
@relation(SDOC-SRS-28, scope=file)
3
"""
4
 
5
from strictdoc.core.document_tree import DocumentTree
6
from strictdoc.core.traceability_index import TraceabilityIndex
7
from strictdoc.core.traceability_index_builder import TraceabilityIndexBuilder
8
from strictdoc.helpers.mid import MID
9
from tests.unit.helpers.document_builder import DocumentBuilder
10
 
11
 
12
def test_valid_01_one_document_with_1req():
13
    document_builder = DocumentBuilder()
14
    requirement = document_builder.add_requirement("REQ-001")
15
    document_1 = document_builder.build()
16
 
17
    file_tree = []
18
    document_list = [document_1]
19
    map_docs_by_paths = {}
20
    document_tree = DocumentTree(
21
        file_tree=file_tree,
22
        document_list=document_list,
23
        map_docs_by_paths=map_docs_by_paths,
24
        map_docs_by_rel_paths={},
25
        map_grammars_by_filenames={},
26
    )
27
    traceability_index = TraceabilityIndexBuilder.create_from_document_tree(
28
        document_tree, project_config=document_builder.project_config
29
    )
30
    parent_requirements = traceability_index.get_parent_requirements(
31
        requirement
32
    )
33
    assert parent_requirements == []
34
 
35
 
36
def test_valid_02_one_document_with_1req():
37
    document_builder = DocumentBuilder()
38
    requirement1 = document_builder.add_requirement("REQ-001")
39
    requirement2 = document_builder.add_requirement("REQ-002")
40
    requirement3 = document_builder.add_requirement("REQ-003")
41
    requirement4 = document_builder.add_requirement("REQ-004")
42
    document_builder.add_requirement_relation(
43
        relation_type="Parent",
44
        source_requirement_id="REQ-002",
45
        target_requirement_id="REQ-001",
46
        role=None,
47
    )
48
    document_builder.add_requirement_relation(
49
        relation_type="Parent",
50
        source_requirement_id="REQ-003",
51
        target_requirement_id="REQ-001",
52
        role=None,
53
    )
54
    document_builder.add_requirement_relation(
55
        relation_type="Parent",
56
        source_requirement_id="REQ-003",
57
        target_requirement_id="REQ-002",
58
        role=None,
59
    )
60
    document_builder.add_requirement_relation(
61
        relation_type="Parent",
62
        source_requirement_id="REQ-004",
63
        target_requirement_id="REQ-003",
64
        role=None,
65
    )
66
 
67
    document_1 = document_builder.build()
68
 
69
    file_tree = []
70
    document_list = [document_1]
71
    map_docs_by_paths = {}
72
    document_tree = DocumentTree(
73
        file_tree=file_tree,
74
        document_list=document_list,
75
        map_docs_by_paths=map_docs_by_paths,
76
        map_docs_by_rel_paths={},
77
        map_grammars_by_filenames={},
78
    )
79
    traceability_index = TraceabilityIndexBuilder.create_from_document_tree(
80
        document_tree, project_config=document_builder.project_config
81
    )
82
    requirement1_parents = traceability_index.get_parent_requirements(
83
        requirement1
84
    )
85
    assert requirement1_parents == []
86
 
87
    requirement2_parents = traceability_index.get_parent_requirements(
88
        requirement2
89
    )
90
    assert requirement2_parents == [requirement1]
91
 
92
    requirement3_parents = traceability_index.get_parent_requirements(
93
        requirement3
94
    )
95
    assert requirement3_parents == [requirement1, requirement2]
96
 
97
    requirement4_parents = traceability_index.get_parent_requirements(
98
        requirement4
99
    )
100
    assert requirement4_parents == [requirement3]
101
 
102
 
103
def test_reverse_role_display_label_for_parent_relation():
104
    document_builder = DocumentBuilder()
105
    parent_requirement = document_builder.add_requirement("REQ-001")
106
    child_requirement = document_builder.add_requirement("REQ-002")
107
    document_builder.add_requirement_relation(
108
        relation_type="Parent",
109
        source_requirement_id="REQ-002",
110
        target_requirement_id="REQ-001",
111
        role="Refines",
112
        reverse_role="Refined by",
113
    )
114
 
115
    document = document_builder.build()
116
    document_tree = DocumentTree(
117
        file_tree=[],
118
        document_list=[document],
119
        map_docs_by_paths={},
120
        map_docs_by_rel_paths={},
121
        map_grammars_by_filenames={},
122
    )
123
    traceability_index = TraceabilityIndexBuilder.create_from_document_tree(
124
        document_tree, project_config=document_builder.project_config
125
    )
126
 
127
    assert (
128
        traceability_index.get_display_role_for_parent_relation(
129
            child_requirement, parent_requirement, "Refines"
130
        )
131
        == "Refines"
132
    )
133
    assert (
134
        traceability_index.get_display_role_for_child_relation(
135
            parent_requirement, child_requirement, "Refines"
136
        )
137
        == "Refined by"
138
    )
139
 
140
 
141
def test_reverse_role_display_label_for_child_relation():
142
    document_builder = DocumentBuilder()
143
    parent_requirement = document_builder.add_requirement("REQ-001")
144
    child_requirement = document_builder.add_requirement("REQ-002")
145
    document_builder.add_requirement_relation(
146
        relation_type="Child",
147
        source_requirement_id="REQ-001",
148
        target_requirement_id="REQ-002",
149
        role="Verifies",
150
        reverse_role="Verified by",
151
    )
152
 
153
    document = document_builder.build()
154
    document_tree = DocumentTree(
155
        file_tree=[],
156
        document_list=[document],
157
        map_docs_by_paths={},
158
        map_docs_by_rel_paths={},
159
        map_grammars_by_filenames={},
160
    )
161
    traceability_index = TraceabilityIndexBuilder.create_from_document_tree(
162
        document_tree, project_config=document_builder.project_config
163
    )
164
 
165
    assert (
166
        traceability_index.get_display_role_for_child_relation(
167
            parent_requirement, child_requirement, "Verifies"
168
        )
169
        == "Verifies"
170
    )
171
    assert (
172
        traceability_index.get_display_role_for_parent_relation(
173
            child_requirement, parent_requirement, "Verifies"
174
        )
175
        == "Verified by"
176
    )
177
 
178
 
179
def test_relation_display_label_falls_back_to_role_for_parent_relation():
180
    document_builder = DocumentBuilder()
181
    parent_requirement = document_builder.add_requirement("REQ-001")
182
    child_requirement = document_builder.add_requirement("REQ-002")
183
    document_builder.add_requirement_relation(
184
        relation_type="Parent",
185
        source_requirement_id="REQ-002",
186
        target_requirement_id="REQ-001",
187
        role="Refines",
188
    )
189
 
190
    document = document_builder.build()
191
    document_tree = DocumentTree(
192
        file_tree=[],
193
        document_list=[document],
194
        map_docs_by_paths={},
195
        map_docs_by_rel_paths={},
196
        map_grammars_by_filenames={},
197
    )
198
    traceability_index = TraceabilityIndexBuilder.create_from_document_tree(
199
        document_tree, project_config=document_builder.project_config
200
    )
201
 
202
    assert (
203
        traceability_index.get_display_role_for_parent_relation(
204
            child_requirement, parent_requirement, "Refines"
205
        )
206
        == "Refines"
207
    )
208
    assert (
209
        traceability_index.get_display_role_for_child_relation(
210
            parent_requirement, child_requirement, "Refines"
211
        )
212
        == "Refines"
213
    )
214
 
215
 
216
def test_relation_display_label_falls_back_to_role_for_child_relation():
217
    document_builder = DocumentBuilder()
218
    parent_requirement = document_builder.add_requirement("REQ-001")
219
    child_requirement = document_builder.add_requirement("REQ-002")
220
    document_builder.add_requirement_relation(
221
        relation_type="Child",
222
        source_requirement_id="REQ-001",
223
        target_requirement_id="REQ-002",
224
        role="Verifies",
225
    )
226
 
227
    document = document_builder.build()
228
    document_tree = DocumentTree(
229
        file_tree=[],
230
        document_list=[document],
231
        map_docs_by_paths={},
232
        map_docs_by_rel_paths={},
233
        map_grammars_by_filenames={},
234
    )
235
    traceability_index = TraceabilityIndexBuilder.create_from_document_tree(
236
        document_tree, project_config=document_builder.project_config
237
    )
238
 
239
    assert (
240
        traceability_index.get_display_role_for_child_relation(
241
            parent_requirement, child_requirement, "Verifies"
242
        )
243
        == "Verifies"
244
    )
245
    assert (
246
        traceability_index.get_display_role_for_parent_relation(
247
            child_requirement, parent_requirement, "Verifies"
248
        )
249
        == "Verifies"
250
    )
251
 
252
 
253
def test__adding_parent_link__01__two_requirements_in_one_document():
254
    document_builder = DocumentBuilder()
255
    requirement1 = document_builder.add_requirement("REQ-001")
256
    requirement2 = document_builder.add_requirement("REQ-002")
257
    document_1 = document_builder.build()
258
 
259
    file_tree = []
260
    document_list = [document_1]
261
    map_docs_by_paths = {}
262
    document_tree = DocumentTree(
263
        file_tree=file_tree,
264
        document_list=document_list,
265
        map_docs_by_paths=map_docs_by_paths,
266
        map_docs_by_rel_paths={},
267
        map_grammars_by_filenames={},
268
    )
269
    traceability_index: TraceabilityIndex = (
270
        TraceabilityIndexBuilder.create_from_document_tree(
271
            document_tree, project_config=document_builder.project_config
272
        )
273
    )
274
    traceability_index.update_requirement_parent_uid(
275
        requirement2, "REQ-001", None
276
    )
277
 
278
    # REQ2 has REQ1 as its parent.
279
    req2_parent_requirements = traceability_index.get_parent_requirements(
280
        requirement2
281
    )
282
    assert req2_parent_requirements == [requirement1]
283
 
284
    # REQ1 has REQ2 as its child.
285
    req1_child_requirements = traceability_index.get_children_requirements(
286
        requirement1
287
    )
288
    assert req1_child_requirements == [requirement2]
289
 
290
 
291
def test__adding_parent_link__02__two_requirements_in_two_documents():
292
    document_builder_1 = DocumentBuilder("DOC-1")
293
    requirement1 = document_builder_1.add_requirement("REQ-001")
294
    document_1 = document_builder_1.build()
295
 
296
    document_builder_2 = DocumentBuilder("DOC-2")
297
    requirement2 = document_builder_2.add_requirement("REQ-002")
298
    document_2 = document_builder_2.build()
299
 
300
    file_tree = []
301
    document_list = [document_1, document_2]
302
    map_docs_by_paths = {}
303
    document_tree = DocumentTree(
304
        file_tree=file_tree,
305
        document_list=document_list,
306
        map_docs_by_paths=map_docs_by_paths,
307
        map_docs_by_rel_paths={},
308
        map_grammars_by_filenames={},
309
    )
310
    traceability_index: TraceabilityIndex = (
311
        TraceabilityIndexBuilder.create_from_document_tree(
312
            document_tree, project_config=document_builder_1.project_config
313
        )
314
    )
315
    traceability_index.update_requirement_parent_uid(
316
        requirement2, "REQ-001", None
317
    )
318
 
319
    # REQ2 has REQ1 as its parent.
320
    req2_parent_requirements = traceability_index.get_parent_requirements(
321
        requirement2
322
    )
323
    assert req2_parent_requirements == [requirement1]
324
 
325
    # REQ1 has REQ2 as its child.
326
    req1_child_requirements = traceability_index.get_children_requirements(
327
        requirement1
328
    )
329
    assert req1_child_requirements == [requirement2]
330
 
331
 
332
def test__adding_parent_link__04__two_requirements_remove_parent_link():
333
    document_builder = DocumentBuilder()
334
    requirement1 = document_builder.add_requirement("REQ-001")
335
    requirement2 = document_builder.add_requirement("REQ-002")
336
    document_builder.add_requirement_relation(
337
        relation_type="Parent",
338
        source_requirement_id="REQ-002",
339
        target_requirement_id="REQ-001",
340
        role=None,
341
    )
342
    document_1 = document_builder.build()
343
 
344
    file_tree = []
345
    document_list = [document_1]
346
    map_docs_by_paths = {}
347
    document_tree = DocumentTree(
348
        file_tree=file_tree,
349
        document_list=document_list,
350
        map_docs_by_paths=map_docs_by_paths,
351
        map_docs_by_rel_paths={},
352
        map_grammars_by_filenames={},
353
    )
354
    traceability_index: TraceabilityIndex = (
355
        TraceabilityIndexBuilder.create_from_document_tree(
356
            document_tree, project_config=document_builder.project_config
357
        )
358
    )
359
    traceability_index.remove_requirement_parent_uid(
360
        requirement2, "REQ-001", role=None
361
    )
362
 
363
    req2_parent_requirements = traceability_index.get_parent_requirements(
364
        requirement2
365
    )
366
    assert req2_parent_requirements == []
367
 
368
    req1_child_requirements = traceability_index.get_children_requirements(
369
        requirement1
370
    )
371
    assert req1_child_requirements == []
372
 
373
 
374
def test_get_node_by_mid():
375
    document_builder = DocumentBuilder()
376
    document_1 = document_builder.build()
377
 
378
    file_tree = []
379
    document_list = [document_1]
380
    map_docs_by_paths = {}
381
    document_tree = DocumentTree(
382
        file_tree=file_tree,
383
        document_list=document_list,
384
        map_docs_by_paths=map_docs_by_paths,
385
        map_docs_by_rel_paths={},
386
        map_grammars_by_filenames={},
387
    )
388
    traceability_index: TraceabilityIndex = (
389
        TraceabilityIndexBuilder.create_from_document_tree(
390
            document_tree, project_config=document_builder.project_config
391
        )
392
    )
393
    assert (
394
        traceability_index.get_node_by_mid(MID(document_1.reserved_mid))
395
        == document_1
396
    )
397
 
398
 
399
# Regression test: update_disconnect_two_documents_if_no_links_left used to
400
# iterate children as (node, _) tuples and access .document instead of
401
# .get_document(), crashing when a requirement in doc-A has a child in doc-B.
402
def test__disconnect_two_documents__child_in_other_document_does_not_crash():
403
    document_builder_1 = DocumentBuilder("DOC-1")
404
    requirement1 = document_builder_1.add_requirement("REQ-001")
405
    document_1 = document_builder_1.build()
406
 
407
    document_builder_2 = DocumentBuilder("DOC-2")
408
    _ = document_builder_2.add_requirement("REQ-002")
409
    document_builder_2.add_requirement_relation(
410
        relation_type="Parent",
411
        source_requirement_id="REQ-002",
412
        target_requirement_id="REQ-001",
413
        role=None,
414
    )
415
    document_2 = document_builder_2.build()
416
 
417
    document_tree = DocumentTree(
418
        file_tree=[],
419
        document_list=[document_1, document_2],
420
        map_docs_by_paths={},
421
        map_docs_by_rel_paths={},
422
        map_grammars_by_filenames={},
423
    )
424
    traceability_index: TraceabilityIndex = (
425
        TraceabilityIndexBuilder.create_from_document_tree(
426
            document_tree, project_config=document_builder_1.project_config
427
        )
428
    )
429
 
430
    # REQ-001 in DOC-1 has REQ-002 (in DOC-2) as its child.
431
    # This must not crash (previously crashed due to tuple-unpacking bug).
432
    # The method should detect the cross-document child link and return early
433
    # without deleting the document-to-document connection.
434
    children_before = traceability_index.get_children_requirements(requirement1)
435
    assert len(children_before) == 1
436
 
437
    traceability_index.update_disconnect_two_documents_if_no_links_left(
438
        document_1, document_2
439
    )
440
 
441
    # The link must still be intact because a cross-document child exists.
442
    children_after = traceability_index.get_children_requirements(requirement1)
443
    assert len(children_after) == 1
444
 
445
 
446
# Regression test: delete_requirement previously left stale NODE_TO_CHILD_NODES
447
# entries on parent nodes, so after deletion the parent still reported the
448
# deleted requirement as one of its children.
449
def test__delete_requirement__parent_child_links_cleaned_up_symmetrically():
450
    document_builder = DocumentBuilder()
451
    requirement1 = document_builder.add_requirement("REQ-001")
452
    requirement2 = document_builder.add_requirement("REQ-002")
453
    document_builder.add_requirement_relation(
454
        relation_type="Parent",
455
        source_requirement_id="REQ-002",
456
        target_requirement_id="REQ-001",
457
        role=None,
458
    )
459
    document_1 = document_builder.build()
460
 
461
    document_tree = DocumentTree(
462
        file_tree=[],
463
        document_list=[document_1],
464
        map_docs_by_paths={},
465
        map_docs_by_rel_paths={},
466
        map_grammars_by_filenames={},
467
    )
468
    traceability_index: TraceabilityIndex = (
469
        TraceabilityIndexBuilder.create_from_document_tree(
470
            document_tree, project_config=document_builder.project_config
471
        )
472
    )
473
 
474
    # Precondition: REQ-001 is parent of REQ-002.
475
    assert traceability_index.get_children_requirements(requirement1) == [
476
        requirement2
477
    ]
478
    assert traceability_index.get_parent_requirements(requirement2) == [
479
        requirement1
480
    ]
481
 
482
    traceability_index.delete_requirement(requirement2)
483
 
484
    # After deletion the stale NODE_TO_CHILD_NODES entry on REQ-001 must be
485
    # gone; previously it was left in place.
486
    assert traceability_index.get_children_requirements(requirement1) == []