diff --git a/strictdoc/backend/sdoc_source_code/comment_parser/marker_lexer.py b/strictdoc/backend/sdoc_source_code/comment_parser/marker_lexer.py index 44aeb91db..d7f002132 100644 --- a/strictdoc/backend/sdoc_source_code/comment_parser/marker_lexer.py +++ b/strictdoc/backend/sdoc_source_code/comment_parser/marker_lexer.py @@ -22,12 +22,12 @@ class GrammarTemplate(Template): NODE_GRAMMAR_EXTENSION = GrammarTemplate(""" node_field: node_name ":" node_multiline_value node_name: /##CUSTOM_TAGS/ -node_multiline_value: (_WS_INLINE | _NL) (NODE_FIRST_STRING_VALUE _NL) (NODE_STRING_VALUE _NL)* +node_multiline_value: (_WS_INLINE | _NL) (NODE_FIRST_STRING_VALUE NEWLINE) (NODE_STRING_VALUE NEWLINE)* NODE_FIRST_STRING_VALUE.2: /\\s*[^\n\r]+/x -NODE_STRING_VALUE.2: /(?![ ]*##RELATION_MARKER_START)(?!\\s*[A-Z_]+: )[^\n\r]+/x +NODE_STRING_VALUE.2: /(?![ ]*##RELATION_MARKER_START)(?!\\s*(##CUSTOM_TAGS):\\s)(?!\\s*##NODE_FIELD_END_MARKER)[^\n\r]+/x -_NORMAL_STRING_NO_MARKER_NO_NODE: /(?!\\s*##RELATION_MARKER_START)((?!\\s*(##CUSTOM_TAGS): )|(##RESERVED_KEYWORDS)).+/ +_NORMAL_STRING_NO_MARKER_NO_NODE: /(?!\\s*##RELATION_MARKER_START)((?!\\s*(##CUSTOM_TAGS):\\s)|(##RESERVED_KEYWORDS)).+/ """) GRAMMAR = GrammarTemplate(""" @@ -74,6 +74,7 @@ def parse( CUSTOM_TAGS="|".join(f"{tag}(?=:)" for tag in custom_tags), RESERVED_KEYWORDS=RESERVED_KEYWORDS, RELATION_MARKER_START=RELATION_MARKER_START, + NODE_FIELD_END_MARKER="SPDX-Req-End", ) start = "(relation_marker | node_field | _NORMAL_STRING_NO_MARKER_NO_NODE | _WS)*" else: diff --git a/strictdoc/backend/sdoc_source_code/marker_parser.py b/strictdoc/backend/sdoc_source_code/marker_parser.py index b3229d842..ed35c9db4 100644 --- a/strictdoc/backend/sdoc_source_code/marker_parser.py +++ b/strictdoc/backend/sdoc_source_code/marker_parser.py @@ -216,17 +216,11 @@ def _parse_node_field( assert isinstance(node_value_node, Tree) assert node_value_node.data == "node_multiline_value" - processed_node_values = [] + node_value = "" for node_value_component_ in node_value_node.children: assert isinstance(node_value_component_, Token) - processed_node_value = node_value_component_.value.strip() - if "\\n\\n" in processed_node_value: - processed_node_value = processed_node_value.replace( - "\\n\\n", "" - ) - - processed_node_values.append(processed_node_value) + node_value += node_value_component_.value - node_value = "\n".join(processed_node_values) + node_value = node_value.rstrip() return node_name, node_value diff --git a/strictdoc/core/file_traceability_index.py b/strictdoc/core/file_traceability_index.py index e97854752..40b1ea7ef 100644 --- a/strictdoc/core/file_traceability_index.py +++ b/strictdoc/core/file_traceability_index.py @@ -47,6 +47,7 @@ from strictdoc.helpers.cast import assert_cast from strictdoc.helpers.exception import StrictDocException from strictdoc.helpers.google_test import convert_function_name_to_gtest_macro +from strictdoc.helpers.mid import MID from strictdoc.helpers.ordered_set import OrderedSet if TYPE_CHECKING: @@ -601,15 +602,36 @@ def validate_and_resolve( continue assert source_node_.entity_name is not None + sdoc_node = None sdoc_node_uid = source_node_.get_sdoc_field( "UID", relevant_source_node_entry ) - if sdoc_node_uid is None: - sdoc_node_uid = f"{document_uid}/{path_to_source_file_}/{source_node_.entity_name}" - sdoc_node = traceability_index.get_node_by_uid_weak( - sdoc_node_uid + mid = source_node_.get_sdoc_field( + "MID", relevant_source_node_entry ) + # First merge criterion: Merge if SDoc node with same MID exists. + if mid is not None: + sdoc_node_mid = MID(mid) + merge_candidate_sdoc_node = ( + traceability_index.get_node_by_mid_weak(sdoc_node_mid) + ) + if isinstance(merge_candidate_sdoc_node, SDocNode): + sdoc_node = merge_candidate_sdoc_node + sdoc_node_uid = sdoc_node.reserved_uid + + if sdoc_node is None: + # If no UID from source code field or merge-by-MID, create UID by conventional scheme. + if sdoc_node_uid is None: + sdoc_node_uid = f"{document_uid}/{path_to_source_file_}/{source_node_.entity_name}" + # Second merge criterion: Merge if SDoc node with same UID exists. + tmp_sdoc_node = traceability_index.get_node_by_uid_weak( + sdoc_node_uid + ) + if isinstance(tmp_sdoc_node, SDocNode): + sdoc_node = tmp_sdoc_node + + assert sdoc_node_uid is not None if sdoc_node is not None: sdoc_node = assert_cast(sdoc_node, SDocNode) self.merge_sdoc_node_with_source_node( @@ -626,11 +648,6 @@ def validate_and_resolve( document, ) sdoc_node_uid = assert_cast(sdoc_node.reserved_uid, str) - traceability_index.graph_database.create_link( - link_type=GraphLinkType.UID_TO_NODE, - lhs_node=sdoc_node_uid, - rhs_node=sdoc_node, - ) if current_top_node is None: current_top_node = ( FileTraceabilityIndex.create_source_node_section( @@ -998,6 +1015,25 @@ def merge_sdoc_node_with_source_node( ) # Merge strategy: overwrite any field if there's a field with same name from custom tags. sdoc_node_fields = source_node.get_sdoc_fields(source_node_config_entry) + + # Sanity check: Nor UID neither MID must conflict (early auto-MID is allowed to be overwritten) + if ( + "MID" in sdoc_node.ordered_fields_lookup + and "MID" in sdoc_node_fields + ): + sdoc_mid_field = sdoc_node.get_field_by_name("MID").get_text_value() + if sdoc_mid_field != sdoc_node_fields["MID"]: + raise StrictDocException( + f"Can't merge node by UID {sdoc_node.reserved_uid}: " + f"Conflicting MID: {sdoc_mid_field} != {sdoc_node_fields['MID']}" + ) + if sdoc_node.reserved_uid is not None and "UID" in sdoc_node_fields: + if sdoc_node.reserved_uid != sdoc_node_fields["UID"]: + raise StrictDocException( + f"Can't merge node by MID {sdoc_node.reserved_mid}: " + f"Conflicting UID: {sdoc_node.reserved_uid} != {sdoc_node_fields['UID']}" + ) + FileTraceabilityIndex.set_sdoc_node_fields(sdoc_node, sdoc_node_fields) @staticmethod @@ -1081,6 +1117,45 @@ def connect_source_node_requirements( Here we link REQ and sdoc_node bidirectional. """ + if ( + sdoc_node.reserved_uid is not None + and not traceability_index.graph_database.has_link( + link_type=GraphLinkType.UID_TO_NODE, + lhs_node=sdoc_node.reserved_uid, + rhs_node=sdoc_node, + ) + ): + traceability_index.graph_database.create_link( + link_type=GraphLinkType.UID_TO_NODE, + lhs_node=sdoc_node.reserved_uid, + rhs_node=sdoc_node, + ) + + # A merge procedure may have overwritten the MID, + # in which case the graph database and search index needs an update. + if "MID" in sdoc_node.ordered_fields_lookup != sdoc_node.reserved_mid: + sdoc_mid_field = sdoc_node.get_field_by_name("MID").get_text_value() + if sdoc_mid_field != sdoc_node.reserved_mid: + # TODO: + # If we really want to support changing the auto-assigned MID, + # at least the graph database and the document search index need an update (remove old MID, add new MID). + # I currently struggle to update the search index. + parent_document = sdoc_node.get_parent_or_including_document() + sdoc_node.reserved_mid = MID(sdoc_mid_field) + if parent_document.config.enable_mid: + sdoc_node.mid_permanent = True + + if not traceability_index.graph_database.has_link( + link_type=GraphLinkType.MID_TO_NODE, + lhs_node=sdoc_node.reserved_mid, + rhs_node=sdoc_node, + ): + traceability_index.graph_database.create_link( + link_type=GraphLinkType.MID_TO_NODE, + lhs_node=sdoc_node.reserved_mid, + rhs_node=sdoc_node, + ) + for marker_ in source_node.markers: if not isinstance(marker_, FunctionRangeMarker): continue diff --git a/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/parent.sdoc b/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/parent.sdoc similarity index 100% rename from tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/parent.sdoc rename to tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/parent.sdoc diff --git a/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/source_node_base.sdoc b/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/source_node_base.sdoc similarity index 93% rename from tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/source_node_base.sdoc rename to tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/source_node_base.sdoc index 22caeec03..cd74e7c5b 100644 --- a/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/source_node_base.sdoc +++ b/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/source_node_base.sdoc @@ -39,7 +39,7 @@ ELEMENTS: TITLE: Merge example.c into static nodes [REQUIREMENT] -UID: SRC-NODES-BASE/src/example/example.c/example_1 +UID: SRC-NODES-BASE/src/example.c/example_1 TITLE: TITLE from sdoc FOO: FOO text from sdoc BAR: BAR text from sdoc diff --git a/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/src/example/example.c b/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/src/example.c similarity index 100% rename from tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/src/example/example.c rename to tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/src/example.c diff --git a/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/strictdoc.toml b/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/strictdoc.toml similarity index 100% rename from tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/strictdoc.toml rename to tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/strictdoc.toml diff --git a/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/test.itest b/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/test.itest new file mode 100644 index 000000000..37db9c53f --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_sdoc_by_default_uid/test.itest @@ -0,0 +1,30 @@ +# +# This test verifies that a source nodes is merged with a static SDoc node if +# - the source nodes is not marked up with a UID field (i.e., default UID is effective), and +# - static SDoc node was explicitly given the default UID. +# +# @relation(SDOC-SRS-141, scope=file) +# + +RUN: %strictdoc --debug export %S --output-dir %T | filecheck %s + +CHECK: Published: Hello world doc + +RUN: %check_exists --file "%T/html/_source_files/src/example.c.html" + +RUN: %cat %T/html/%THIS_TEST_FOLDER/source_node_base.html | filecheck %s --check-prefix CHECK-HTML +CHECK-HTML: Requirements from Source Nodes +CHECK-HTML: SRC-NODES-BASE/src/example.c/example_1 +CHECK-HTML: TITLE from sdoc +CHECK-HTML: class="requirement__link-parent" href="../30_merge_with_sdoc_by_default_uid/parent.html#REQ-1" +CHECK-HTML: src/example.c, lines: 3-14, function example_1() +CHECK-HTML-NOT: FOO text from sdoc +CHECK-HTML: FOO text from example.c +CHECK-HTML-NOT: BAR text from sdoc +CHECK-HTML: BAR text from example.c + +RUN: %cat %T/html/_source_files/src/example.c.html | filecheck %s --check-prefix CHECK-SOURCE-FILE +CHECK-SOURCE-FILE: SRC-NODES-BASE/src/example.c/example_1 + +RUN: %cat %T/html/source_coverage.html | filecheck %s --check-prefix CHECK-SOURCE-COVERAGE +CHECK-SOURCE-COVERAGE: 100.0 diff --git a/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/test.itest b/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/test.itest deleted file mode 100644 index edd3709f2..000000000 --- a/tests/integration/features/source_code_traceability/_source_nodes/30_merge_with_static_node/test.itest +++ /dev/null @@ -1,24 +0,0 @@ -# -# @relation(SDOC-SRS-141, scope=file) -# - -RUN: %strictdoc --debug export %S --output-dir %T | filecheck %s - -CHECK: Published: Hello world doc - -RUN: %check_exists --file "%T/html/_source_files/src/example/example.c.html" - -RUN: %cat %T/html/%THIS_TEST_FOLDER/source_node_base.html | filecheck %s --check-prefix CHECK-HTML -CHECK-HTML: Requirements from Source Nodes -CHECK-HTML: SRC-NODES-BASE/src/example/example.c/example_1 -CHECK-HTML: {{.*}}TITLE from sdoc -CHECK-HTML: class="requirement__link-parent" href="../30_merge_with_static_node/parent.html#REQ-1" -CHECK-HTML: src/example/example.c, lines: 3-14, function example_1() -CHECK-HTML: FOO text from example.c -CHECK-HTML: BAR text from example.c - -RUN: %cat %T/html/_source_files/src/example/example.c.html | filecheck %s --check-prefix CHECK-SOURCE-FILE -CHECK-SOURCE-FILE: SRC-NODES-BASE/src/example/example.c/example_1 - -RUN: %cat %T/html/source_coverage.html | filecheck %s --check-prefix CHECK-SOURCE-COVERAGE -CHECK-SOURCE-COVERAGE: 100.0 diff --git a/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/parent.sdoc b/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/parent.sdoc new file mode 100644 index 000000000..7eff27bf2 --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/parent.sdoc @@ -0,0 +1,17 @@ +[DOCUMENT] +TITLE: Hello world doc + +[REQUIREMENT] +UID: REQ-1 +TITLE: Requirement Title +STATEMENT: Requirement Statement + +[REQUIREMENT] +UID: REQ-2 +TITLE: Requirement Title #2 +STATEMENT: Requirement Statement #2 + +[REQUIREMENT] +UID: REQ-3 +TITLE: Requirement Title #3 +STATEMENT: Requirement Statement #3 diff --git a/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/source_node_base.sdoc b/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/source_node_base.sdoc new file mode 100644 index 000000000..b27f68bbc --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/source_node_base.sdoc @@ -0,0 +1,72 @@ +[DOCUMENT] +MID: c2d4542d5f1741c88dfcb4f68ad7dcbd +TITLE: Requirements from Source Nodes +UID: SRC-NODES-BASE + +[GRAMMAR] +ELEMENTS: +- TAG: SECTION + PROPERTIES: + IS_COMPOSITE: True + FIELDS: + - TITLE: UID + TYPE: String + REQUIRED: False + - TITLE: TITLE + TYPE: String + REQUIRED: True +- TAG: REQUIREMENT + PROPERTIES: + VIEW_STYLE: Narrative + FIELDS: + - TITLE: UID + TYPE: String + REQUIRED: False + - TITLE: MID + TYPE: String + REQUIRED: False + - TITLE: TITLE + TYPE: String + REQUIRED: False + - TITLE: FOO + TYPE: String + REQUIRED: False + - TITLE: BAR + TYPE: String + REQUIRED: False + RELATIONS: + - TYPE: Parent + - TYPE: File + +[[SECTION]] +TITLE: Merge example.c into static nodes + +[REQUIREMENT] +UID: REQ-SOURCE-1 +TITLE: TITLE1 from sdoc +FOO: FOO1 text from sdoc +BAR: BAR1 text from sdoc +RELATIONS: +- TYPE: Parent + VALUE: REQ-1 + +[REQUIREMENT] +UID: REQ-SOURCE-2 +MID: 80cd685d-0e18-44b8-9842-c1863a2eb9ec +TITLE: TITLE2 from sdoc +FOO: FOO2 text from sdoc +BAR: BAR2 text from sdoc +RELATIONS: +- TYPE: Parent + VALUE: REQ-2 + +[REQUIREMENT] +UID: REQ-SOURCE-3 +TITLE: TITLE3 from sdoc +FOO: FOO3 text from sdoc +BAR: BAR3 text from sdoc +RELATIONS: +- TYPE: Parent + VALUE: REQ-3 + +[[/SECTION]] diff --git a/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/src/example.c b/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/src/example.c new file mode 100644 index 000000000..fa900f484 --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/src/example.c @@ -0,0 +1,48 @@ +#include + +/** + * Some text. + * + * @relation(REQ-1, scope=function) + * + * UID: REQ-SOURCE-1 + * + * FOO: FOO1 text from example.c + * + * BAR: BAR1 text from example.c + */ +void example_1(void) { + print("hello world\n"); +} + +/** + * Some text. + * + * @relation(REQ-2, scope=function) + * + * UID: REQ-SOURCE-2 + * + * FOO: FOO2 text from example.c + * + * BAR: BAR2 text from example.c + */ +void example_2(void) { + print("hello world\n"); +} + +/** + * Some text. + * + * @relation(REQ-3, scope=function) + * + * UID: REQ-SOURCE-3 + * + * MID: 1973a567-a109-491d-b7f0-6bb22eafa6ab + * + * FOO: FOO3 text from example.c + * + * BAR: BAR3 text from example.c + */ +void example_3(void) { + print("hello world\n"); +} diff --git a/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/strictdoc.toml b/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/strictdoc.toml new file mode 100644 index 000000000..f8688a0cd --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/strictdoc.toml @@ -0,0 +1,14 @@ +[project] + +features = [ + "REQUIREMENT_TO_SOURCE_TRACEABILITY", + "SOURCE_FILE_LANGUAGE_PARSERS", +] + +source_nodes = [ + { "src/" = { uid = "SRC-NODES-BASE", node_type = "REQUIREMENT" } } +] + +exclude_source_paths = [ + "test.itest" +] diff --git a/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/test.itest b/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/test.itest new file mode 100644 index 000000000..6c426ff6b --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/31_merge_with_sdoc_by_uid/test.itest @@ -0,0 +1,58 @@ +# +# This test verifies that a source nodes is merged with a static SDoc node with same explicit UID annotated. +# The following cases shall be handled: +# - If MID is set at neither side, a random (but hidden) automatic MID is assigned by StrictDoc. +# - If MID is set only at the sdoc side, the MID remains effective. +# - If MID is set only at the source side, early random automatic MID is overwritten by source side MID. +# +# Cases tested elsewhere: +# - If MID is set on both sides and is equal, this is considered "merge by mid". +# - If MID is set on both sides but is different, this is considered a validation error. +# +# @relation(SDOC-SRS-141, scope=file) +# + +RUN: %strictdoc --debug export %S --output-dir %T | filecheck %s + +CHECK: Published: Hello world doc + +RUN: %check_exists --file "%T/html/_source_files/src/example.c.html" + +RUN: %cat %T/html/%THIS_TEST_FOLDER/source_node_base.html | filecheck %s --check-prefix CHECK-HTML +CHECK-HTML: Requirements from Source Nodes +CHECK-HTML-NOT: SRC-NODES-BASE/src/example.c/example_1 +CHECK-HTML: REQ-SOURCE-1 +CHECK-HTML: TITLE1 from sdoc +CHECK-HTML: src/example.c, lines: 3-16, function example_1() +CHECK-HTML-NOT: FOO1 text from sdoc +CHECK-HTML: FOO1 text from example.c +CHECK-HTML-NOT: BAR1 text from sdoc +CHECK-HTML: BAR1 text from example.c + +CHECK-HTML-NOT: SRC-NODES-BASE/src/example.c/example_2 +CHECK-HTML: REQ-SOURCE-2 +CHECK-HTML: TITLE2 from sdoc +CHECK-HTML: src/example.c, lines: 18-31, function example_2() +CHECK-HTML-NOT: FOO2 text from sdoc +CHECK-HTML: FOO2 text from example.c +CHECK-HTML-NOT: BAR2 text from sdoc +CHECK-HTML: BAR2 text from example.c + +CHECK-HTML-NOT: SRC-NODES-BASE/src/example.c/example_3 +CHECK-HTML: REQ-SOURCE-3 +CHECK-HTML: TITLE3 from sdoc +CHECK-HTML: 1973a567-a109-491d-b7f0-6bb22eafa6ab +CHECK-HTML: src/example.c, lines: 33-48, function example_3() +CHECK-HTML-NOT: FOO3 text from sdoc +CHECK-HTML: FOO3 text from example.c +CHECK-HTML-NOT: BAR3 text from sdoc +CHECK-HTML: BAR3 text from example.c + +RUN: %cat %T/html/_source_files/src/example.c.html | filecheck %s --check-prefix CHECK-SOURCE-FILE +CHECK-HTML-NOT: SRC-NODES-BASE/src/example.c/example_1 +CHECK-SOURCE-FILE: REQ-SOURCE-1 +CHECK-SOURCE-FILE: REQ-SOURCE-2 +CHECK-SOURCE-FILE: REQ-SOURCE-3 + +RUN: %cat %T/html/source_coverage.html | filecheck %s --check-prefix CHECK-SOURCE-COVERAGE +CHECK-SOURCE-COVERAGE: 100.0 diff --git a/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/parent.sdoc b/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/parent.sdoc new file mode 100644 index 000000000..278b98a77 --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/parent.sdoc @@ -0,0 +1,12 @@ +[DOCUMENT] +TITLE: Hello world doc + +[REQUIREMENT] +UID: REQ-1 +TITLE: Requirement Title +STATEMENT: Requirement Statement + +[REQUIREMENT] +UID: REQ-2 +TITLE: Requirement Title #2 +STATEMENT: Requirement Statement #2 diff --git a/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/source_node_base.sdoc b/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/source_node_base.sdoc new file mode 100644 index 000000000..c6c84f958 --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/source_node_base.sdoc @@ -0,0 +1,54 @@ +[DOCUMENT] +MID: c2d4542d5f1741c88dfcb4f68ad7dcbd +TITLE: Requirements from Source Nodes +UID: SRC-NODES-BASE + +[GRAMMAR] +ELEMENTS: +- TAG: SECTION + PROPERTIES: + IS_COMPOSITE: True + FIELDS: + - TITLE: UID + TYPE: String + REQUIRED: False + - TITLE: TITLE + TYPE: String + REQUIRED: True +- TAG: REQUIREMENT + PROPERTIES: + VIEW_STYLE: Narrative + FIELDS: + - TITLE: UID + TYPE: String + REQUIRED: False + - TITLE: MID + TYPE: String + REQUIRED: False + - TITLE: TITLE + TYPE: String + REQUIRED: False + - TITLE: FOO + TYPE: String + REQUIRED: False + - TITLE: BAR + TYPE: String + REQUIRED: False + RELATIONS: + - TYPE: Parent + - TYPE: File + +[[SECTION]] +TITLE: Merge example.c into static nodes + +[REQUIREMENT] +UID: REQ-SOURCE-1 +MID: 3bd0162d-d6d2-42a1-9324-ab8415190970 +TITLE: TITLE from sdoc +FOO: FOO text from sdoc +BAR: BAR text from sdoc +RELATIONS: +- TYPE: Parent + VALUE: REQ-1 + +[[/SECTION]] diff --git a/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/src/example.c b/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/src/example.c new file mode 100644 index 000000000..0fe5bf23e --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/src/example.c @@ -0,0 +1,16 @@ +#include + +/** + * Some text. + * + * @relation(REQ-1, scope=function) + * + * MID: 3bd0162d-d6d2-42a1-9324-ab8415190970 + * + * FOO: FOO text from example.c + * + * BAR: BAR text from example.c + */ +void example_1(void) { + print("hello world\n"); +} diff --git a/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/strictdoc.toml b/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/strictdoc.toml new file mode 100644 index 000000000..f8688a0cd --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/strictdoc.toml @@ -0,0 +1,14 @@ +[project] + +features = [ + "REQUIREMENT_TO_SOURCE_TRACEABILITY", + "SOURCE_FILE_LANGUAGE_PARSERS", +] + +source_nodes = [ + { "src/" = { uid = "SRC-NODES-BASE", node_type = "REQUIREMENT" } } +] + +exclude_source_paths = [ + "test.itest" +] diff --git a/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/test.itest b/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/test.itest new file mode 100644 index 000000000..aefd72bf4 --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/32_merge_with_sdoc_by_mid/test.itest @@ -0,0 +1,34 @@ +# +# This test verifies that a source nodes is merged with a static SDoc node with same explicit MID annotated. +# The following case shall be handled: +# - If MID is set and equal at both sides and no UID update is required, merge the node. +# +# Cases tested elsewhere: +# - If MID is set and equal on both sides but UID update would be required, this is considered a validation error. +# +# @relation(SDOC-SRS-141, scope=file) +# + +RUN: %strictdoc --debug export %S --output-dir %T | filecheck %s + +CHECK: Published: Hello world doc + +RUN: %check_exists --file "%T/html/_source_files/src/example.c.html" + +RUN: %cat %T/html/%THIS_TEST_FOLDER/source_node_base.html | filecheck %s --check-prefix CHECK-HTML +CHECK-HTML: Requirements from Source Nodes +CHECK-HTML-NOT: SRC-NODES-BASE/src/example.c/example_1 +CHECK-HTML: 3bd0162d-d6d2-42a1-9324-ab8415190970 +CHECK-HTML: TITLE from sdoc +CHECK-HTML: src/example.c, lines: 3-16, function example_1() +CHECK-HTML-NOT: FOO text from sdoc +CHECK-HTML: FOO text from example.c +CHECK-HTML-NOT: BAR text from sdoc +CHECK-HTML: BAR text from example.c + +RUN: %cat %T/html/_source_files/src/example.c.html | filecheck %s --check-prefix CHECK-SOURCE-FILE +CHECK-HTML-NOT: SRC-NODES-BASE/src/example.c/example_1 +CHECK-SOURCE-FILE: 3bd0162d-d6d2-42a1-9324-ab8415190970 + +RUN: %cat %T/html/source_coverage.html | filecheck %s --check-prefix CHECK-SOURCE-COVERAGE +CHECK-SOURCE-COVERAGE: 100.0 diff --git a/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/source_node_base.sdoc b/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/source_node_base.sdoc new file mode 100644 index 000000000..e00351a94 --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/source_node_base.sdoc @@ -0,0 +1,48 @@ +[DOCUMENT] +MID: c2d4542d5f1741c88dfcb4f68ad7dcbd +TITLE: Requirements from Source Nodes +UID: SRC-NODES-BASE + +[GRAMMAR] +ELEMENTS: +- TAG: SECTION + PROPERTIES: + IS_COMPOSITE: True + FIELDS: + - TITLE: UID + TYPE: String + REQUIRED: False + - TITLE: TITLE + TYPE: String + REQUIRED: True +- TAG: REQUIREMENT + PROPERTIES: + VIEW_STYLE: Narrative + FIELDS: + - TITLE: UID + TYPE: String + REQUIRED: False + - TITLE: MID + TYPE: String + REQUIRED: False + - TITLE: TITLE + TYPE: String + REQUIRED: False + - TITLE: FOO + TYPE: String + REQUIRED: False + - TITLE: BAR + TYPE: String + REQUIRED: False + +[[SECTION]] +TITLE: Merge example.c into static nodes + +[REQUIREMENT] +UID: REQ-SOURCE-1 +MID: 3bd0162d-d6d2-42a1-9324-ab8415190970 +TITLE: TITLE from sdoc +FOO: FOO text from sdoc +BAR: BAR text from sdoc + +[[/SECTION]] diff --git a/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/src/example.c b/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/src/example.c new file mode 100644 index 000000000..fcc707090 --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/src/example.c @@ -0,0 +1,14 @@ +#include + +/** + * UID: REQ-SOURCE-1 + * + * MID: e7c09b63-2bed-4046-9511-a5b17978f152 + * + * FOO: FOO text from example.c + * + * BAR: BAR text from example.c + */ +void example_1(void) { + print("hello world\n"); +} diff --git a/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/strictdoc.toml b/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/strictdoc.toml new file mode 100644 index 000000000..f8688a0cd --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/strictdoc.toml @@ -0,0 +1,14 @@ +[project] + +features = [ + "REQUIREMENT_TO_SOURCE_TRACEABILITY", + "SOURCE_FILE_LANGUAGE_PARSERS", +] + +source_nodes = [ + { "src/" = { uid = "SRC-NODES-BASE", node_type = "REQUIREMENT" } } +] + +exclude_source_paths = [ + "test.itest" +] diff --git a/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/test.itest b/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/test.itest new file mode 100644 index 000000000..bc72f832e --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/_validation/03_merge_by_uid_conflicting_mid/test.itest @@ -0,0 +1,9 @@ +# +# This test verifies that if SDoc node and source node have same UID and thus are +# subject to be merged, an error is thrown if both sides set also MID and the MIDs are not equal. +# +# @relation(SDOC-SRS-141, scope=file) +# + +RUN: %expect_exit 1 %strictdoc export %S --output-dir %T | filecheck %s --dump-input=fail +CHECK: error: Can't merge node by UID REQ-SOURCE-1: Conflicting MID: 3bd0162d-d6d2-42a1-9324-ab8415190970 != e7c09b63-2bed-4046-9511-a5b17978f152 diff --git a/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/source_node_base.sdoc b/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/source_node_base.sdoc new file mode 100644 index 000000000..e00351a94 --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/source_node_base.sdoc @@ -0,0 +1,48 @@ +[DOCUMENT] +MID: c2d4542d5f1741c88dfcb4f68ad7dcbd +TITLE: Requirements from Source Nodes +UID: SRC-NODES-BASE + +[GRAMMAR] +ELEMENTS: +- TAG: SECTION + PROPERTIES: + IS_COMPOSITE: True + FIELDS: + - TITLE: UID + TYPE: String + REQUIRED: False + - TITLE: TITLE + TYPE: String + REQUIRED: True +- TAG: REQUIREMENT + PROPERTIES: + VIEW_STYLE: Narrative + FIELDS: + - TITLE: UID + TYPE: String + REQUIRED: False + - TITLE: MID + TYPE: String + REQUIRED: False + - TITLE: TITLE + TYPE: String + REQUIRED: False + - TITLE: FOO + TYPE: String + REQUIRED: False + - TITLE: BAR + TYPE: String + REQUIRED: False + +[[SECTION]] +TITLE: Merge example.c into static nodes + +[REQUIREMENT] +UID: REQ-SOURCE-1 +MID: 3bd0162d-d6d2-42a1-9324-ab8415190970 +TITLE: TITLE from sdoc +FOO: FOO text from sdoc +BAR: BAR text from sdoc + +[[/SECTION]] diff --git a/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/src/example.c b/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/src/example.c new file mode 100644 index 000000000..02a01bd76 --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/src/example.c @@ -0,0 +1,14 @@ +#include + +/** + * UID: REQ-SOURCE-2 + * + * MID: 3bd0162d-d6d2-42a1-9324-ab8415190970 + * + * FOO: FOO text from example.c + * + * BAR: BAR text from example.c + */ +void example_1(void) { + print("hello world\n"); +} diff --git a/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/strictdoc.toml b/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/strictdoc.toml new file mode 100644 index 000000000..f8688a0cd --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/strictdoc.toml @@ -0,0 +1,14 @@ +[project] + +features = [ + "REQUIREMENT_TO_SOURCE_TRACEABILITY", + "SOURCE_FILE_LANGUAGE_PARSERS", +] + +source_nodes = [ + { "src/" = { uid = "SRC-NODES-BASE", node_type = "REQUIREMENT" } } +] + +exclude_source_paths = [ + "test.itest" +] diff --git a/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/test.itest b/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/test.itest new file mode 100644 index 000000000..48cb05075 --- /dev/null +++ b/tests/integration/features/source_code_traceability/_source_nodes/_validation/04_merge_by_mid_conflicting_uid/test.itest @@ -0,0 +1,9 @@ +# +# This test verifies that if SDoc node and source node have same MID and thus are +# subject to be merged, an error is thrown if both sides set also UID and the UID are not equal. +# +# @relation(SDOC-SRS-141, scope=file) +# + +RUN: %expect_exit 1 %strictdoc export %S --output-dir %T | filecheck %s --dump-input=fail +CHECK: error: Can't merge node by MID 3bd0162d-d6d2-42a1-9324-ab8415190970: Conflicting UID: REQ-SOURCE-1 != REQ-SOURCE-2 diff --git a/tests/unit/strictdoc/backend/sdoc_source_code/test_marker_lexer.py b/tests/unit/strictdoc/backend/sdoc_source_code/test_marker_lexer.py index e18784860..a4fecaced 100644 --- a/tests/unit/strictdoc/backend/sdoc_source_code/test_marker_lexer.py +++ b/tests/unit/strictdoc/backend/sdoc_source_code/test_marker_lexer.py @@ -188,7 +188,7 @@ def test_30_relation_and_field(): STATEMENT: When 1, The system 2 shall do 3 -FOOBAR +Additionally, the system 3 shall do 4. """ tree = MarkerLexer.parse(input_string, custom_tags=["STATEMENT"]) @@ -205,43 +205,54 @@ def test_30_relation_and_field(): assert tree.children[1].children[0].data == "node_name" assert tree.children[1].children[0].children[0].value == "STATEMENT" assert tree.children[1].children[1].data == "node_multiline_value" - assert len(tree.children[1].children[1].children) == 2 + assert len(tree.children[1].children[1].children) == 4 assert tree.children[1].children[1].children[0].value == "When C," + assert tree.children[1].children[1].children[1].value == "\n" assert ( - tree.children[1].children[1].children[1].value + tree.children[1].children[1].children[2].value == " The system A shall do B" ) + assert tree.children[1].children[1].children[3].value == "\n\n" assert tree.children[2].data == "node_field" assert tree.children[2].children[0].data == "node_name" assert tree.children[2].children[0].children[0].value == "STATEMENT" assert tree.children[2].children[1].data == "node_multiline_value" - assert len(tree.children[2].children[1].children) == 2 + assert len(tree.children[2].children[1].children) == 4 assert tree.children[2].children[1].children[0].value == "When Z," + assert tree.children[2].children[1].children[1].value == "\n" assert ( - tree.children[2].children[1].children[1].value + tree.children[2].children[1].children[2].value == " The system X shall do Y" ) + assert tree.children[2].children[1].children[3].value == "\n\n" assert tree.children[3].data == "node_field" assert tree.children[3].children[0].data == "node_name" assert tree.children[3].children[0].children[0].value == "STATEMENT" assert tree.children[3].children[1].data == "node_multiline_value" - assert len(tree.children[3].children[1].children) == 1 + assert len(tree.children[3].children[1].children) == 2 assert ( tree.children[3].children[1].children[0].value == "When 1, The system 2 shall do 3" ) + assert tree.children[3].children[1].children[1].value == "\n\n" assert tree.children[4].data == "node_field" assert tree.children[4].children[0].data == "node_name" assert tree.children[4].children[0].children[0].value == "STATEMENT" assert tree.children[4].children[1].data == "node_multiline_value" - assert len(tree.children[4].children[1].children) == 1 + assert len(tree.children[4].children[1].children) == 4 assert ( tree.children[4].children[1].children[0].value == "When 1, The system 2 shall do 3" ) + assert tree.children[4].children[1].children[1].value == "\n\n" + assert ( + tree.children[4].children[1].children[2].value + == "Additionally, the system 3 shall do 4." + ) + assert tree.children[4].children[1].children[3].value == "\n" def test_31_single_node_field(): @@ -397,12 +408,10 @@ def test_33_multiline_and_multiparagraph_fields(): FOOBAR STATEMENT: This - \\n\\n + is - \\n\\n - how we do paragraphs. -FOOBAR + how we do paragraphs. """ tree = MarkerLexer.parse(input_string, custom_tags=["STATEMENT"]) @@ -414,13 +423,14 @@ def test_33_multiline_and_multiparagraph_fields(): assert tree.children[0].children[0].children[0].value == "STATEMENT" assert tree.children[0].children[1].data == "node_multiline_value" assert tree.children[0].children[1].children[0].value == "This" - assert tree.children[0].children[1].children[1].value == " \\n\\n" + assert tree.children[0].children[1].children[1].value == "\n\n" assert tree.children[0].children[1].children[2].value == " is" - assert tree.children[0].children[1].children[3].value == " \\n\\n" + assert tree.children[0].children[1].children[3].value == "\n\n" assert ( tree.children[0].children[1].children[4].value == " how we do paragraphs." ) + assert tree.children[0].children[1].children[5].value == "\n" def test_60_exclude_reserved_keywords():