diff mbox series

[3/4] resulttool: junit: improve ptest status handling and log

Message ID 20260108113647.56663-4-miroslav.cernak@siemens.com
State Under Review
Headers show
Series resulttool: JUnit ptest support and robustness; add selftests | expand

Commit Message

Miroslav Cernak Jan. 8, 2026, 11:36 a.m. UTC
While using resulttool's JUnit export, several issues surfaced
and are addressed here:
- Avoid KeyError when ptest results are missing by guarding access to
  ptest_summary e.g., KeyError: 'bzip2'
- Report actual failures as FAILED instead of SKIPPED and list the failing
  testcase names (e.g., DataSQLite-testrunner, Foundation-testrunner).
- Include testcase names in failure output rather than only the ptest name,
  improving first-read diagnosability.
- Make multiline failure details readable: put full traces in JUnit output;
  for attribute-only messages that cannot contain newlines, collapse ā€œ\nā€ to
  spaces to avoid ā€œ ā€ artifacts in GitLab.

This produces a more accurate and readable JUnit report, prevents crashes
when ptestresult.* lacks entries, and makes CI output actionable on first read.

Signed-off-by: Miroslav Cernak <miroslav.cernak@siemens.com>
---
 scripts/lib/resulttool/junit.py | 78 +++++++++++++++++++++++----------
 1 file changed, 54 insertions(+), 24 deletions(-)
diff mbox series

Patch

diff --git a/scripts/lib/resulttool/junit.py b/scripts/lib/resulttool/junit.py
index c4476f1e59..0f541dd80b 100644
--- a/scripts/lib/resulttool/junit.py
+++ b/scripts/lib/resulttool/junit.py
@@ -26,14 +26,14 @@  class PtestSummary:
         self.FAILED = []
         self.SKIPPED = []
 
-    def add_status(self, ptest_name, status):
+    def add_status(self, ptest_testcase, status):
         self.tests += 1
         if status == "FAILED":
-            self.FAILED.append(ptest_name)
+            self.FAILED.append(ptest_testcase)
         elif status == "ERROR":
-            self.ERROR.append(ptest_name)
+            self.ERROR.append(ptest_testcase)
         elif status == "SKIPPED":
-            self.SKIPPED.append(ptest_name)
+            self.SKIPPED.append(ptest_testcase)
 
     @property
     def status(self):
@@ -49,32 +49,38 @@  class PtestSummary:
     @property
     def log_summary(self):
         """Return a summary of the ptest suite"""
-        summary_str = "ERROR:" + os.linesep
-        summary_str += os.linesep.join([s + "- " for s in self.ERROR]) + os.linesep
-        summary_str = "FAILED:" + os.linesep
-        summary_str += os.linesep.join([s + "- " for s in self.FAILED]) + os.linesep
-        summary_str = "SKIPPED:" + os.linesep
-        summary_str += os.linesep.join([s + "- " for s in self.SKIPPED]) + os.linesep
-        return summary_str
+        summary_parts = []
+
+        if self.ERROR:
+            summary_parts.append("ERROR:" + os.linesep)
+            summary_parts.append(os.linesep.join(["- " + s for s in self.ERROR]) + os.linesep)
+
+        if self.FAILED:
+            summary_parts.append("FAILED:" + os.linesep)
+            summary_parts.append(os.linesep.join(["- " + s for s in self.FAILED]) + os.linesep)
+
+        if self.SKIPPED:
+            summary_parts.append("SKIPPED:" + os.linesep)
+            summary_parts.append(os.linesep.join(["- " + s for s in self.SKIPPED]) + os.linesep)
+
+        return "".join(summary_parts) if summary_parts else "No failures or errors"
 
 
 def create_testcase(testsuite, testcase_dict, status, status_message, status_text=None, system_out=None):
     """Create a junit testcase node"""
     testcase_node = ET.SubElement(testsuite, "testcase", testcase_dict)
 
+    print("%s -> %s status: %s" % (testcase_dict["classname"], testcase_dict["name"], status))
+
     se = None
     if status == "SKIPPED":
-        se = ET.SubElement(testcase_node, "skipped", message=status_message)
+        se = ET.SubElement(testcase_node, "skipped", message = status_message.replace('\n', ' ') if status_message else None)
     elif status == "FAILED":
-        se = ET.SubElement(testcase_node, "failure", message=status_message)
+        se = ET.SubElement(testcase_node, "failure")
+        se = ET.SubElement(testcase_node, "system-out").text = (status_message or "") + os.linesep + (system_out or "")
     elif status == "ERROR":
-        se = ET.SubElement(testcase_node, "error", message=status_message)
-    if se and status_text:
-        se.text = status_text
-
-    if system_out:
-        ET.SubElement(testcase_node, "system-out").text = system_out
-
+        se = ET.SubElement(testcase_node, "error")
+        se = ET.SubElement(testcase_node, "system-out").text = (status_message or "") + os.linesep + (system_out or "")
 
 def junit_tree(testresults, test_log_dir=None):
     """Create a JUnit XML tree from testresults
@@ -102,9 +108,10 @@  def junit_tree(testresults, test_log_dir=None):
 
             if result_id.startswith("ptestresult."):
                 ptest_name = result_id.split(".", 3)[1]
+                test_case = result_id.split(".", 3)[2]
                 if ptest_name not in ptest_summarys:
                     ptest_summarys[ptest_name] = PtestSummary()
-                ptest_summarys[ptest_name].add_status(ptest_name, result["status"])
+                ptest_summarys[ptest_name].add_status(test_case, result["status"])
             else:
                 image_total_time += int(result["duration"])
                 image_tests += 1
@@ -145,6 +152,7 @@  def junit_tree(testresults, test_log_dir=None):
                     "time": str(result["duration"]),
                 }
 
+                exitcode = result.get("exitcode")
                 log = result.get("log")
                 system_out = None
                 if log:
@@ -155,15 +163,24 @@  def junit_tree(testresults, test_log_dir=None):
                     else:
                         system_out = log
 
+                # Determine status and log summary
+                if ptest_name in ptest_summarys:
+                    status = ptest_summarys[ptest_name].status
+                    log_summary = ptest_summarys[ptest_name].log_summary
+                else:
+                    # When there is no detailed result for the ptest, we assume it was skipped or errored
+                    status = "SKIPPED" if exitcode in (None, "0") else "ERROR"
+                    print("Warning: ptest %s has no detailed results, marking as %s" % (ptest_name, status))
+                    log_summary = log if log else "No log available."
+
                 create_testcase(ptest_testsuite,
                                 testcase_dict,
-                                ptest_summarys[ptest_name].status,
-                                ptest_summarys[ptest_name].log_summary,
+                                status,
+                                log_summary,
                                 system_out=system_out)
 
                 ptest_total_time += int(result["duration"])
                 ptest_tests += 1
-                status = ptest_summarys[ptest_name].status
                 if status == "FAILED":
                     ptest_failures += 1
                 elif status == "ERROR":
@@ -188,6 +205,19 @@  def junit_tree(testresults, test_log_dir=None):
     testsuites_node.set("skipped", str(total_skipped))
     testsuites_node.set("tests", str(total_tests))
     testsuites_node.set("time", str(total_time))
+
+    ptest_success = ptest_tests - ptest_errors - ptest_failures - ptest_skipped
+    image_success = image_tests - image_errors - image_failures - image_skipped
+    total_success = total_tests - total_errors - total_failures - total_skipped
+
+    print("ptest     -> tests: %d, success: %d, error: %d, failures: %d, skipped: %d, time: %d" %
+              (ptest_tests, ptest_success, ptest_errors, ptest_failures, ptest_skipped, ptest_total_time))
+
+    print("testimage -> tests: %d, success: %d, error: %d, failures: %d, skipped: %d, time: %d" %
+            (image_tests, image_success, image_errors, image_failures, image_skipped, image_total_time))
+
+    print("total     -> tests: %d, success: %d, error: %d, failures: %d, skipped: %d, time: %d" %
+            (total_tests, total_success, total_errors, total_failures, total_skipped, total_time))
     tree = ET.ElementTree(testsuites_node)
     return tree, test_logfiles