From 20764c0e76017cddfb65ea17474d03af9c682384 Mon Sep 17 00:00:00 2001 From: Patrick Dahlke Date: Thu, 9 Apr 2026 22:21:17 +0200 Subject: [PATCH] Handle JUnit result state in parse_testcase() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, test cases with an element were silently classified as "passed" because the if/elif/else chain only checked for and . This is a false-positive verdict — errors indicate unexpected exceptions or infrastructure crashes, not passing tests. - Add explicit handling for the element in parse_testcase() - Add CSS styling for the tr_error result type (orange) - Also fix potential None in failure text (use `or ""` guard) - Add test fixture and test case for error state parsing Closes #132 --- sphinxcontrib/test_reports/css/common.css | 9 +++++++ sphinxcontrib/test_reports/junitparser.py | 10 +++++-- tests/doc_test/utils/xml_data_error.xml | 12 +++++++++ tests/test_junit_parser.py | 33 +++++++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/doc_test/utils/xml_data_error.xml diff --git a/sphinxcontrib/test_reports/css/common.css b/sphinxcontrib/test_reports/css/common.css index 14b9cca..a88f5b9 100644 --- a/sphinxcontrib/test_reports/css/common.css +++ b/sphinxcontrib/test_reports/css/common.css @@ -10,6 +10,10 @@ tr.tr_skipped{ background-color: rgba(0,0,0,0.1) !important; } +tr.tr_error, div.tr_error { + background-color: rgba(255,165,0,0.2) !important; +} + td.status p { padding: 3px 5px; text-align: center; @@ -30,3 +34,8 @@ td.status p.tr_skipped{ border: 1px solid #555; color: #555; } + +td.status p.tr_error{ + border: 1px solid #cc6600; + color: #cc6600; +} diff --git a/sphinxcontrib/test_reports/junitparser.py b/sphinxcontrib/test_reports/junitparser.py index 7430f6a..5461f0c 100644 --- a/sphinxcontrib/test_reports/junitparser.py +++ b/sphinxcontrib/test_reports/junitparser.py @@ -70,8 +70,14 @@ def parse_testcase(xml_object): tc_dict["result"] = "failure" tc_dict["type"] = result.attrib.get("type", "unknown") # tc_dict["text"] = re.sub(r"[\n\t]*", "", result.text) # Removes newlines and tabs - tc_dict["text"] = result.text - tc_dict["message"] = "" + tc_dict["text"] = result.text or "" + tc_dict["message"] = result.attrib.get("message", "") + elif hasattr(testcase, "error"): + result = testcase.error + tc_dict["result"] = "error" + tc_dict["type"] = result.attrib.get("type", "unknown") + tc_dict["text"] = result.text or "" + tc_dict["message"] = result.attrib.get("message", "unknown") else: tc_dict["result"] = "passed" tc_dict["type"] = "" diff --git a/tests/doc_test/utils/xml_data_error.xml b/tests/doc_test/utils/xml_data_error.xml new file mode 100644 index 0000000..1475938 --- /dev/null +++ b/tests/doc_test/utils/xml_data_error.xml @@ -0,0 +1,12 @@ + + + + some failure details + + + stack trace here + + + + + diff --git a/tests/test_junit_parser.py b/tests/test_junit_parser.py index 57d8195..9a6a324 100644 --- a/tests/test_junit_parser.py +++ b/tests/test_junit_parser.py @@ -16,6 +16,9 @@ ) xml_ctest_path = os.path.join(os.path.dirname(__file__), "doc_test/utils", "ctest.xml") +xml_error_path = os.path.join( + os.path.dirname(__file__), "doc_test/utils", "xml_data_error.xml" +) def test_init_parser(): @@ -148,3 +151,33 @@ def test_parse_ctest_xml(): assert test_suite["testcases"][3]["result"] == "failure" assert test_suite["testcases"][4]["name"] == "skipped_test" assert test_suite["testcases"][4]["result"] == "skipped" + + +def test_parse_error_xml(): + from sphinxcontrib.test_reports.junitparser import JUnitParser + + parser = JUnitParser(xml_error_path) + test_suites = parser.parse() + + assert len(test_suites) == 1 + test_suite = test_suites[0] + + assert test_suite["name"] == "error_suite" + assert test_suite["tests"] == 4 + assert test_suite["errors"] == 1 + assert test_suite["failures"] == 1 + + assert test_suite["testcases"][0]["name"] == "ASuccessfulTest" + assert test_suite["testcases"][0]["result"] == "passed" + + assert test_suite["testcases"][1]["name"] == "AFailingTest" + assert test_suite["testcases"][1]["result"] == "failure" + + assert test_suite["testcases"][2]["name"] == "AnErrorTest" + assert test_suite["testcases"][2]["result"] == "error" + assert test_suite["testcases"][2]["type"] == "RuntimeError" + assert test_suite["testcases"][2]["message"] == "unexpected crash" + assert test_suite["testcases"][2]["text"] == "stack trace here" + + assert test_suite["testcases"][3]["name"] == "ASkippedTest" + assert test_suite["testcases"][3]["result"] == "skipped"