From 789e4eaa1641937c709d15c03d3f4d772c1072b6 Mon Sep 17 00:00:00 2001 From: Otto Sabart Date: Mon, 26 Aug 2024 09:00:00 +0200 Subject: [PATCH] Add support for subresults in junit report plugin flavor Define an experimental junit flavor for subresults. --- docs/releases.rst | 6 ++ tmt/steps/report/junit.py | 32 ++++++-- tmt/steps/report/junit/schemas/subresults.xsd | 81 +++++++++++++++++++ .../report/junit/templates/subresults.xml.j2 | 70 ++++++++++++++++ 4 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 tmt/steps/report/junit/schemas/subresults.xsd create mode 100644 tmt/steps/report/junit/templates/subresults.xml.j2 diff --git a/docs/releases.rst b/docs/releases.rst index 8dd848805a..f47f51d6bf 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -7,6 +7,12 @@ tmt-1.37 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The :ref:`/plugins/report/junit` report plugin now supports a new +``subresults`` JUnit flavor. This flavor adds support for tmt subresults and +changes the level of ```` and ```` tags. The +``tmt.Result`` tags become ```` (instead of ``testcase``) and +```` tags become ``tmt.SubResult``. + The :ref:`/plugins/report/junit` report plugin now validates all the XML flavors against their respective XSD schemas and tries to prettify the final XML output. These functionalities are always disabled for ``custom`` flavors. diff --git a/tmt/steps/report/junit.py b/tmt/steps/report/junit.py index 234f66f16a..665677ef43 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, cast, overload +from typing import TYPE_CHECKING, Any, Optional, TypedDict, Union, cast, overload from jinja2 import FileSystemLoader, select_autoescape @@ -83,10 +83,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()] @@ -108,9 +127,10 @@ class ResultsContext: wraps all the :py:class:`tmt.Result` instances into the :py:class:`ResultWrapper`. """ - def __init__(self, results: list[tmt.Result]) -> None: - # Decorate all the tmt.Results with more attributes - self._results: list[ResultWrapper] = [ResultWrapper(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] = [ + ResultWrapper(r, subresults_context_class=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): flavor: str = field( default=DEFAULT_FLAVOR_NAME, option='--flavor', - choices=[DEFAULT_FLAVOR_NAME, CUSTOM_FLAVOR_NAME], + choices=[DEFAULT_FLAVOR_NAME, CUSTOM_FLAVOR_NAME, 'subresults'], help='Name of a JUnit flavor to generate.') template_path: Optional[Path] = field( diff --git a/tmt/steps/report/junit/schemas/subresults.xsd b/tmt/steps/report/junit/schemas/subresults.xsd new file mode 100644 index 0000000000..8345211ff2 --- /dev/null +++ b/tmt/steps/report/junit/schemas/subresults.xsd @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..d2d4391f59 --- /dev/null +++ b/tmt/steps/report/junit/templates/subresults.xml.j2 @@ -0,0 +1,70 @@ +{% extends "_base.xml.j2" %} + +{# + This flavor changes the level of `` and `` tags. The + `tmt.Result` becomes ```` instead of ``testcase`` and + ```` tags become ``tmt.SubResult``. +#} + +{% block content %} + + + {% block testsuites %} + {% for result in RESULTS %} + {% 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 %} + + + + {# + TODO: + The `error`, `failure` and `skipped` tags are probably not allowed within the testsuite + {% if result.result.value == 'error' or result.result.value == 'warn' %} + {{ main_log_failures }} + {% elif result.result.value == 'fail' %} + {{ main_log_failures }} + {% elif result.result.value == 'info' %} + {{ main_log_failures }} + {% endif %} + #} + + {% if INCLUDE_OUTPUT_LOG and result_log %} + {{ main_log | e }} + {% endif %} + + {% 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 %} + + {# + TODO: + Add properties from testcase into testsuite and test with Polarion? + #} + + {% endfor %} + {% endblock %} + + {# + TODO: + Add properties + #} + + +{% endblock %}