diff --git a/strictdoc/core/project_config.py b/strictdoc/core/project_config.py
index 80e6d3a91..e2df7a1d3 100644
--- a/strictdoc/core/project_config.py
+++ b/strictdoc/core/project_config.py
@@ -10,7 +10,7 @@
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Dict, List, Optional, Tuple, Pattern
import toml
@@ -123,6 +123,7 @@ def __init__(
exclude_source_paths: Optional[List[str]] = None,
grammars: Optional[Dict[str, str]] = None,
test_report_root_dict: Optional[Dict[str, str]] = None,
+ tree_map_test_path_pattern: Optional[str] = None,
source_nodes: Optional[List[SourceNodesEntry]] = None,
html2pdf_strict: bool = False,
html2pdf_template: Optional[str] = None,
@@ -293,6 +294,12 @@ def __init__(
self.test_report_root_dict: Dict[str, str] = (
test_report_root_dict if test_report_root_dict is not None else {}
)
+ self.tree_map_test_path_pattern: Pattern[str] = re.compile(
+ tree_map_test_path_pattern
+ if tree_map_test_path_pattern is not None
+ else r"tests/"
+ )
+
self.source_nodes: List[SourceNodesEntry] = (
source_nodes if source_nodes is not None else []
)
@@ -896,6 +903,7 @@ def _load_from_dictionary(
include_source_paths: List[str] = []
exclude_source_paths: List[str] = []
test_report_root_dict: Dict[str, str] = {}
+ tree_map_test_path_pattern: Optional[str] = None
source_nodes: List[SourceNodesEntry] = []
html2pdf_strict: bool = False
html2pdf_template: Optional[str] = None
@@ -1003,6 +1011,10 @@ def _load_from_dictionary(
assert isinstance(test_report_root_entry_, dict)
test_report_root_dict.update(test_report_root_entry_)
+ tree_map_test_path_pattern = project_content.get(
+ "tree_map_test_path_pattern", tree_map_test_path_pattern
+ )
+
section_behavior = project_content.get(
"section_behavior", section_behavior
)
@@ -1051,6 +1063,7 @@ def _load_from_dictionary(
include_source_paths=include_source_paths,
exclude_source_paths=exclude_source_paths,
test_report_root_dict=test_report_root_dict,
+ tree_map_test_path_pattern=tree_map_test_path_pattern,
source_nodes=source_nodes,
html2pdf_strict=html2pdf_strict,
html2pdf_template=html2pdf_template,
diff --git a/strictdoc/features/tree_map/generator.py b/strictdoc/features/tree_map/generator.py
index 0991a24ae..2471026c0 100644
--- a/strictdoc/features/tree_map/generator.py
+++ b/strictdoc/features/tree_map/generator.py
@@ -180,7 +180,9 @@ def get_node_stats(
node_
)
for source_file_tuple_ in source_files:
- if "tests/" in source_file_tuple_[0]:
+ if project_config.tree_map_test_path_pattern.search(
+ source_file_tuple_[0]
+ ):
node_stats.child_nodes_with_links_to_test_files = 1
else:
node_stats.child_nodes_with_links_to_source_files = 1
@@ -547,11 +549,11 @@ def hover_(row_: Any) -> Any:
parts.append(
GraphSection(
title="Requirements coverage with test",
- description="""\
+ description=f"""\
This graph shows which requirements are covered by at least one test.
A requirement is also considered covered if it has child requirements that are
themselves covered by tests. A source file is considered a test file if its path
-contains "tests/".
+matches "{project_config.tree_map_test_path_pattern.pattern}".
-
diff --git a/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/.gitignore b/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/.gitignore
new file mode 100644
index 000000000..2ca1152b2
--- /dev/null
+++ b/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/.gitignore
@@ -0,0 +1 @@
+!reports/
diff --git a/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/reports/tests_unit.ctest.junit.xml b/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/reports/tests_unit.ctest.junit.xml
new file mode 100644
index 000000000..05ee12a49
--- /dev/null
+++ b/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/reports/tests_unit.ctest.junit.xml
@@ -0,0 +1,91 @@
+
+
+
+
+ Running main() from /home/johan/Development/projects/line/build/_deps/googletest-src/googletest/src/gtest_main.cc
+Note: Google Test filter = MyTestSuiteName.TestName
+[==========] Running 1 test from 1 test suite.
+[----------] Global test environment set-up.
+[----------] 1 test from MyTestSuiteName
+[ RUN ] MyTestSuiteName.TestName
+[ OK ] MyTestSuiteName.TestName (0 ms)
+[----------] 1 test from MyTestSuiteName (0 ms total)
+
+[----------] Global test environment tear-down
+[==========] 1 test from 1 test suite ran. (0 ms total)
+[ PASSED ] 1 test.
+
+
+
+
+ Running main() from /home/johan/Development/projects/line/build/_deps/googletest-src/googletest/src/gtest_main.cc
+Note: Google Test filter = MyTestHelper.TestName2
+[==========] Running 1 test from 1 test suite.
+[----------] Global test environment set-up.
+[----------] 1 test from MyTestHelper
+[ RUN ] MyTestHelper.TestName2
+[ OK ] MyTestHelper.TestName2 (0 ms)
+[----------] 1 test from MyTestHelper (0 ms total)
+
+[----------] Global test environment tear-down
+[==========] 1 test from 1 test suite ran. (0 ms total)
+[ PASSED ] 1 test.
+
+
+
+
+ Running main() from /home/johan/Development/projects/line/build/_deps/googletest-src/googletest/src/gtest_main.cc
+Note: Google Test filter = MyTestPattern/MyTestHelperPattern.TestName3/0
+[==========] Running 1 test from 1 test suite.
+[----------] Global test environment set-up.
+[----------] 1 test from MyTestPattern/MyTestHelperPattern
+[ RUN ] MyTestPattern/MyTestHelperPattern.TestName3/0
+[ OK ] MyTestPattern/MyTestHelperPattern.TestName3/0 (0 ms)
+[----------] 1 test from MyTestPattern/MyTestHelperPattern (0 ms total)
+
+[----------] Global test environment tear-down
+[==========] 1 test from 1 test suite ran. (0 ms total)
+[ PASSED ] 1 test.
+
+
+
+
+ Running main() from /home/johan/Development/projects/line/build/_deps/googletest-src/googletest/src/gtest_main.cc
+Note: Google Test filter = MyTestPattern/MyTestHelperPattern.TestName3/1
+[==========] Running 1 test from 1 test suite.
+[----------] Global test environment set-up.
+[----------] 1 test from MyTestPattern/MyTestHelperPattern
+[ RUN ] MyTestPattern/MyTestHelperPattern.TestName3/1
+[ OK ] MyTestPattern/MyTestHelperPattern.TestName3/1 (0 ms)
+[----------] 1 test from MyTestPattern/MyTestHelperPattern (0 ms total)
+
+[----------] Global test environment tear-down
+[==========] 1 test from 1 test suite ran. (0 ms total)
+[ PASSED ] 1 test.
+
+
+
+
+ Running main() from /home/johan/Development/projects/line/build/_deps/googletest-src/googletest/src/gtest_main.cc
+Note: Google Test filter = MyTestPattern/MyTestHelperPattern.TestName3/2
+[==========] Running 1 test from 1 test suite.
+[----------] Global test environment set-up.
+[----------] 1 test from MyTestPattern/MyTestHelperPattern
+[ RUN ] MyTestPattern/MyTestHelperPattern.TestName3/2
+[ OK ] MyTestPattern/MyTestHelperPattern.TestName3/2 (0 ms)
+[----------] 1 test from MyTestPattern/MyTestHelperPattern (0 ms total)
+
+[----------] Global test environment tear-down
+[==========] 1 test from 1 test suite ran. (0 ms total)
+[ PASSED ] 1 test.
+
+
+
diff --git a/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/strictdoc.toml b/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/strictdoc.toml
new file mode 100644
index 000000000..c601c98ad
--- /dev/null
+++ b/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/strictdoc.toml
@@ -0,0 +1,9 @@
+[project]
+
+features = [
+ "REQUIREMENT_TO_SOURCE_TRACEABILITY",
+ "SOURCE_FILE_LANGUAGE_PARSERS",
+ "TREE_MAP_SCREEN",
+]
+
+tree_map_test_path_pattern = "verification/"
diff --git a/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/test.itest b/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/test.itest
new file mode 100644
index 000000000..1f1566582
--- /dev/null
+++ b/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/test.itest
@@ -0,0 +1,19 @@
+# This test mainly verifies that the test results are resolved for multiple test
+# cases and multiple files.
+
+RUN: %strictdoc export %S --output-dir %T | filecheck %s --dump-input=fail
+
+# Ensure that the test report document is generated.
+CHECK: Published: Test report: (empty)
+RUN: %check_exists --file "%T/html/%THIS_TEST_FOLDER/reports/tests_unit.ctest.junit.html"
+
+# Ensure that the source and test files are generated.
+RUN: %check_exists --file "%T/html/_source_files/verification/test1.cpp.html"
+
+# Ensure that the test report document has the right content.
+RUN: %cat "%T/html/%THIS_TEST_FOLDER/reports/tests_unit.ctest.junit.html" | filecheck %s --check-prefix CHECK-TEST-REPORT
+CHECK-TEST-REPORT:href="../../_source_files/verification/test1.cpp.html#MyTestSuiteName.TestName#17#21">
+CHECK-TEST-REPORT: verification/test1.cpp, lines: 17-21, function TEST(MyTestSuiteName, TestName)
+
+RUN: %cat "%T/html/tree_map.html" | filecheck %s --check-prefix CHECK-TREE-MAP
+CHECK-TREE-MAP: matches "verification/"
diff --git a/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/verification/test1.cpp b/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/verification/test1.cpp
new file mode 100644
index 000000000..93bd73e7c
--- /dev/null
+++ b/tests/integration/features/test_reports/junit_xml/ctest/06_configurable_tests_pattern/verification/test1.cpp
@@ -0,0 +1,48 @@
+#include
+
+//
+// Code that we want to test
+//
+
+int Multiplication(int value1, int value2)
+{
+ // Multiply like a boss
+ return value1 * value2;
+}
+
+//
+// GTest code below
+//
+
+TEST(MyTestSuiteName, TestName)
+{
+ // Test it
+ EXPECT_EQ(Multiplication(2, 2), 4);
+}
+
+class MyTestHelper : public testing::Test
+{
+ protected:
+ MyTestHelper()
+ {
+ // Setup MyTestHelper
+ }
+};
+
+TEST_F(MyTestHelper, TestName2)
+{
+ // Test it
+ EXPECT_EQ(Multiplication(1, 4), 4);
+}
+
+class MyTestHelperPattern : public testing::TestWithParam
+{
+};
+
+INSTANTIATE_TEST_SUITE_P(MyTestPattern, MyTestHelperPattern, testing::Values(1, 2, 3));
+
+TEST_P(MyTestHelperPattern, TestName3)
+{
+ // Test it
+ EXPECT_NE(Multiplication(GetParam(), GetParam()), 0);
+}
diff --git a/tests/unit/strictdoc/core/test_project_config.py b/tests/unit/strictdoc/core/test_project_config.py
index 77da1c34d..9c32bacf6 100644
--- a/tests/unit/strictdoc/core/test_project_config.py
+++ b/tests/unit/strictdoc/core/test_project_config.py
@@ -1,4 +1,5 @@
import pytest
+import re
from strictdoc.core.project_config import ProjectConfig, ProjectFeature
@@ -61,3 +62,28 @@ def test_61_validate_invalid_host():
def test_62_validate_invalid_port():
with pytest.raises(AssertionError):
_ = ProjectConfig(server_port=1000000)
+
+
+def test_100_tree_map_test_path_pattern_default():
+ default_config = ProjectConfig()
+ assert default_config.tree_map_test_path_pattern.pattern == "tests/"
+
+
+def test_101_tree_map_test_path_pattern_custom():
+ explicitly_provided = ProjectConfig(tree_map_test_path_pattern="custom/")
+ assert explicitly_provided.tree_map_test_path_pattern.pattern == "custom/"
+
+
+def test_102_tree_map_test_path_pattern_bad_regex():
+ with pytest.raises(re.error):
+ _ = ProjectConfig(tree_map_test_path_pattern="(unclosed parenthesis")
+
+
+def test_103_tree_map_test_path_pattern_enables_multiple_filename_styles():
+ explicitly_provided = ProjectConfig(
+ tree_map_test_path_pattern=r"(^test/)|_test\.go$"
+ )
+ pat = explicitly_provided.tree_map_test_path_pattern
+ assert pat.search("test/some_test.py")
+ assert pat.search("main_test.go")
+ assert not pat.search("main.go")