Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions src/reqstool/model_generators/testdata_model_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:

Expand All @@ -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

Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="com.example.StatusServiceTest" tests="7" skipped="0" failures="1" errors="0"
timestamp="2026-06-01T12:00:00.000Z" hostname="ci" time="0.1">
<properties/>
<!-- Maven-style: UNIT regex matches, worst-status-wins applies across all three instances -->
<testcase name="checkStatusMaven(StatusType)[1]" classname="com.example.StatusServiceTest" time="0.001"/>
<testcase name="checkStatusMaven(StatusType)[2]" classname="com.example.StatusServiceTest" time="0.001"/>
<testcase name="checkStatusMaven(StatusType)[3]" classname="com.example.StatusServiceTest" time="0.001">
<failure message="expected not equal" type="org.opentest4j.AssertionFailedError"><![CDATA[failure details]]></failure>
</testcase>
<!-- Gradle default [N] format: no method name → WARNING + skip -->
<testcase name="[1] ACTIVE" classname="com.example.StatusServiceTest" time="0.001"/>
<testcase name="[2] PENDING" classname="com.example.StatusServiceTest" time="0.001"/>
<!-- Gradle custom {index} format (N text): no method name → WARNING + skip -->
<testcase name="1 status=ACTIVE" classname="com.example.StatusServiceTest" time="0.001"/>
<testcase name="2 status=PENDING" classname="com.example.StatusServiceTest" time="0.001"/>
</testsuite>
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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
Expand Down