diff --git a/src/reqstool/model_generators/testdata_model_generator.py b/src/reqstool/model_generators/testdata_model_generator.py index d09139c..86271ca 100644 --- a/src/reqstool/model_generators/testdata_model_generator.py +++ b/src/reqstool/model_generators/testdata_model_generator.py @@ -14,9 +14,15 @@ from reqstool.models.test_data import TEST_RUN_STATUS, TestData, TestsData +def _status_priority(status: TEST_RUN_STATUS) -> int: + return {TEST_RUN_STATUS.PASSED: 0, TEST_RUN_STATUS.SKIPPED: 1, TEST_RUN_STATUS.FAILED: 2}.get(status, 0) + + class TestDataModelGenerator: UNIT_METHOD_IDENTIFIER_REGEX = r"^([a-zA-Z_$][a-zA-Z0-9_$]*).*$" KARATE_METHOD_IDENTIFIER_REGEX = r"\[\d+(?:\.\d+)?:\d+\]\s*(.+)" + # Gradle parameterized: default "[N] args" or custom "{index} text" (N text) — method name absent + DISPLAY_NAME_INDEX_REGEX = r"^\[?\d" def __init__(self, test_result_files: List[Path], urn: str): self.test_result_files = test_result_files @@ -29,8 +35,8 @@ def __generate(self, test_result_files: List[Path], urn: str) -> TestsData: return TestsData(tests=tests) @Requirements("REQ_014", "REQ_015") - def __parse_test_data(self, test_result_files: List[Path], urn: str) -> Dict[str, TestData]: - r_testdata: Dict[str, TestData] = {} + def __parse_test_data(self, test_result_files: List[Path], urn: str) -> Dict[UrnId, TestData]: + r_testdata: Dict[UrnId, TestData] = {} for test_result_file in test_result_files: @@ -50,9 +56,19 @@ def __parse_test_data(self, test_result_files: List[Path], urn: str) -> Dict[str methodname = match_unit.group(1) elif match_karate: methodname = match_karate.group(1) + elif re.match(self.DISPLAY_NAME_INDEX_REGEX, testcase.attrib["name"]): + logging.warning( + f"Skipping parameterized test case with display-name-only format " + f"(method name not recoverable): {testcase.attrib['name']!r} " + f"in {test_result_file}" + ) + continue else: - logging.error(f"{testcase.attrib['name']} is not a valid method name\n") - methodname = "invalid_method_name" + logging.warning( + f"Skipping test case with unrecognized name format: " + f"{testcase.attrib['name']!r} in {test_result_file}" + ) + continue test_run_status: TEST_RUN_STATUS @@ -67,6 +83,8 @@ def __parse_test_data(self, test_result_files: List[Path], urn: str) -> Dict[str test_data = TestData(fully_qualified_name=fqn, status=test_run_status) urn_id = UrnId(urn=urn, id=fqn) - r_testdata[urn_id] = test_data + existing = r_testdata.get(urn_id) + if existing is None or _status_priority(test_run_status) > _status_priority(existing.status): + r_testdata[urn_id] = test_data return r_testdata diff --git a/tests/resources/unit/reqstool/model_generators/test_testdata_model_generator/test_parameterized_testdata_model_generator/TEST-com.example.StatusServiceTest.xml b/tests/resources/unit/reqstool/model_generators/test_testdata_model_generator/test_parameterized_testdata_model_generator/TEST-com.example.StatusServiceTest.xml new file mode 100644 index 0000000..c51c4ff --- /dev/null +++ b/tests/resources/unit/reqstool/model_generators/test_testdata_model_generator/test_parameterized_testdata_model_generator/TEST-com.example.StatusServiceTest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/tests/unit/reqstool/model_generators/test_testdata_model_generator.py b/tests/unit/reqstool/model_generators/test_testdata_model_generator.py index d369986..16176b6 100644 --- a/tests/unit/reqstool/model_generators/test_testdata_model_generator.py +++ b/tests/unit/reqstool/model_generators/test_testdata_model_generator.py @@ -1,11 +1,14 @@ # Copyright © LFV +import logging import re import pytest from reqstool_python_decorators.decorators.decorators import SVCs +from reqstool.common.models.urn_id import UrnId from reqstool.model_generators.testdata_model_generator import TestDataModelGenerator +from reqstool.models.test_data import TEST_RUN_STATUS karate_method_names = [ "[1.4:55] Create a subscripiton with filter and receive messages", @@ -35,6 +38,44 @@ def test_unit_method_identifier_regex(method_name): assert unit_match.group(1) == "testFlightIdAircraftId" +display_name_index_names = [ + "[1] ACTIVE", + "[2] conflictMessage=Entry 10 is AWAITING_MANUAL_EDIT, expected PENDING", + "1 status=ACTIVE", + "2 someDisplayName", +] + + +@pytest.mark.parametrize("method_name", display_name_index_names) +def test_display_name_index_regex(method_name): + assert re.match(TestDataModelGenerator.UNIT_METHOD_IDENTIFIER_REGEX, method_name) is None + assert re.match(TestDataModelGenerator.KARATE_METHOD_IDENTIFIER_REGEX, method_name) is None + assert re.match(TestDataModelGenerator.DISPLAY_NAME_INDEX_REGEX, method_name) is not None + + +def test_parameterized_testdata_model_generator(resource_funcname_rootdir_w_path, caplog): + with caplog.at_level(logging.WARNING): + tdmg = TestDataModelGenerator( + test_result_files=[resource_funcname_rootdir_w_path("TEST-com.example.StatusServiceTest.xml")], + urn="test", + ) + + tests = tdmg.model.tests + + # Only the Maven-style method is in the index; Gradle display-name entries are skipped + assert len(tests) == 1 + + # checkStatusMaven: [1] PASSED, [2] PASSED, [3] FAILED → worst-status-wins → FAILED + fqn = "com.example.StatusServiceTest.checkStatusMaven" + urn_id = UrnId(urn="test", id=fqn) + assert urn_id in tests + assert tests[urn_id].status == TEST_RUN_STATUS.FAILED + + # Gradle display-name entries produced warnings, not errors + assert any("display-name-only" in r.message for r in caplog.records) + assert all(r.levelname != "ERROR" for r in caplog.records) + + def test_testdata_model_generator(local_testdata_resources_rootdir_w_path): # TODO: # * Test the different variants: passed, skipped, failure etc