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 %}