diff --git a/tmt/steps/report/junit.py b/tmt/steps/report/junit.py
index f8a4a01254..1ca166e566 100644
--- a/tmt/steps/report/junit.py
+++ b/tmt/steps/report/junit.py
@@ -1,7 +1,7 @@
import dataclasses
import functools
from collections.abc import Iterator
-from typing import TYPE_CHECKING, Any, Optional, TypedDict, overload
+from typing import TYPE_CHECKING, Any, Optional, TypedDict, Union, overload
from jinja2 import FileSystemLoader, select_autoescape
@@ -79,10 +79,29 @@ class PropertyDict(TypedDict):
name: str
value: str
- def __init__(self, wrapped: tmt.Result) -> None:
+ def __init__(
+ self,
+ wrapped: Union[tmt.Result, tmt.result.SubResult],
+ subresults_context_class: 'type[ResultsContext]') -> None:
self._wrapped = wrapped
+ self._subresults_context_class = subresults_context_class
self._properties: dict[str, str] = {}
+ @property
+ def subresult(self) -> 'ResultsContext':
+ """
+ Override the ``tmt.Result.subresult`` and wrap all the ``tmt.result.SubResult`` instances
+ into the ``ResultsContext``.
+ """
+
+ # `tmt.result.SubResult.subresult` is not defined, just raise the AttributeError to silent
+ # the typing errors.
+ if isinstance(self._wrapped, tmt.result.SubResult):
+ raise AttributeError(
+ f"'{self._wrapped.__class__.__name__} object has no attribute 'subresult'")
+
+ return self._subresults_context_class(self._wrapped.subresult)
+
@property
def properties(self) -> list[PropertyDict]:
return [{'name': k, 'value': v} for k, v in self._properties.items()]
@@ -95,9 +114,10 @@ def __getattr__(self, name: str) -> Any:
return getattr(self._wrapped, name)
-def _add_result_properties(result_instance: tmt.Result) -> ResultWrapper:
- """ Dynamically decorate the ``tmt.Result`` instance - add ``properties`` attribute """
- return ResultWrapper(result_instance)
+def _wrap_result(result_instance: Union[tmt.Result, tmt.result.SubResult],
+ subresults_context_class: 'type[ResultsContext]') -> ResultWrapper:
+ """ Dynamically decorate the ``tmt.Result`` instance with more attributes """
+ return ResultWrapper(result_instance, subresults_context_class)
class ResultsContext:
@@ -106,9 +126,9 @@ class ResultsContext:
JUnit template. It wraps all the ``tmt.Result`` instances into the ``ResultWrapper``.
"""
- def __init__(self, results: list[tmt.Result]) -> None:
- # Decorate all the tmt.Results with more attributes
- self._results: list[ResultWrapper] = [_add_result_properties(r) for r in results]
+ def __init__(self, results: Union[list[tmt.Result], list[tmt.result.SubResult]]) -> None:
+ """ Decorate/wrap all the ``tmt.Results`` with more attributes """
+ self._results: list[ResultWrapper] = [_wrap_result(r, self.__class__) for r in results]
def __iter__(self) -> Iterator[ResultWrapper]:
""" Possibility to iterate over results by iterating an instance """
@@ -306,7 +326,7 @@ class ReportJUnitData(tmt.steps.report.ReportStepData):
default=DEFAULT_FLAVOR_NAME,
option='--flavor',
metavar='FLAVOR',
- choices=[DEFAULT_FLAVOR_NAME, CUSTOM_FLAVOR_NAME],
+ choices=[DEFAULT_FLAVOR_NAME, CUSTOM_FLAVOR_NAME, 'subresults'],
help=f"Name of a JUnit flavor to generate. By default, the '{DEFAULT_FLAVOR_NAME}' flavor "
"is used.")
diff --git a/tmt/steps/report/junit/schemas/subresults.xsd b/tmt/steps/report/junit/schemas/subresults.xsd
new file mode 100644
index 0000000000..20146d2d99
--- /dev/null
+++ b/tmt/steps/report/junit/schemas/subresults.xsd
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tmt/steps/report/junit/templates/subresults.xml.j2 b/tmt/steps/report/junit/templates/subresults.xml.j2
new file mode 100644
index 0000000000..55a2919e4c
--- /dev/null
+++ b/tmt/steps/report/junit/templates/subresults.xml.j2
@@ -0,0 +1,40 @@
+{% extends "_base.xml.j2" %}
+
+{% block content %}
+
+
+ {% block testsuites %}
+ {% for result in RESULTS %}
+
+ {# TODO: How to handle main result logs and duration? #}
+ {% set main_log = result.log | first | read_log %}
+ {% set main_log_failures = main_log | failures | e %}
+ {% set main_test_duration = result.duration | duration_to_seconds %}
+
+
+
+ {% for subresult in result.subresult %}
+ {% set subresult_log = subresult.log | first | read_log %}
+ {% set subresult_log_failures = main_log | failures | e %}
+ {% set subresult_test_duration = subresult.duration | duration_to_seconds %}
+
+
+ {% if subresult.result.value == 'error' or subresult.result.value == 'warn' %}
+ {{ subresult_log_failures }}
+ {% elif subresult.result.value == 'fail' %}
+ {{ subresult_log_failures }}
+ {% elif subresult.result.value == 'info' %}
+ {{ subresult_log_failures }}
+ {% endif %}
+
+ {% if INCLUDE_OUTPUT_LOG and subresult_log %}
+ {{ subresult_log | e }}
+ {% endif %}
+
+ {% endfor %}
+
+ {% endfor %}
+ {% endblock %}
+
+
+{% endblock %}