Source code for pykiso.test_result.xml_result

##########################################################################
# Copyright (c) 2010-2022 Robert Bosch GmbH
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
##########################################################################

"""
XML test result for JUnit support
*********************************

:module: xml_result

:synopsis: overwrite xmlrunner.result to add the test IDs
    into the xml report.

.. currentmodule:: xml_result
"""

from __future__ import annotations

import copy
import json
import sys
import unittest
from io import TextIOWrapper
from types import TracebackType
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Type

if TYPE_CHECKING:
    from unittest.case import TestCase
    from xml.dom.minidom import Document, Element
    from ..test_coordinator.test_case import BasicTest

import xmlrunner.result
import xmlrunner.runner

from pykiso.logging_initializer import get_logging_options, initialize_logging


[docs]class TestInfo(xmlrunner.result._TestInfo): """This class keeps useful information about the execution of a test method. Used by XmlTestResult """ def __init__( self, test_result: xmlrunner.runner._XMLTestResult, test_case: BasicTest, outcome: int = 0, err: Optional[Tuple[Type[BaseException], BaseException, TracebackType]] = None, subTest: TestCase = None, filename: Optional[str] = None, lineno: Optional[bool] = None, doc: Optional[str] = None, ): """Initialize the TestInfo class and append additional tag that have to be stored for each test :param test_result: test result class :param test_method: test method (dynamically created eg: test_case.MyTest2-1-2) :param outcome: result of the test (SUCCESS, FAILURE, ERROR, SKIP) :param err: exception information of an error that was raised during the test :param subTest: optional subTest that was run :param filename: name of the file :param lineno: store the test line number :param doc: additional documentation to store """ super().__init__( test_result, test_case, outcome, err, subTest, filename, lineno, doc ) # reinitialize logging inside XmlTestRunner's run # otherwise stdout is never caught in the JUnit report log_options = get_logging_options() initialize_logging( log_options.log_path, log_options.log_level, log_options.verbose, log_options.report_type, ) # handle class setup error if isinstance(test_case, unittest.suite._ErrorHolder): test_case._testMethodDoc = "" test_case.test_ids = {} self._test = test_case # add attribute that will be used to format the errors self._testMethodDoc = test_case._testMethodDoc # store extra tag self.test_ids = json.dumps(test_case.test_ids) def __repr__(self) -> str: """Return the same representation as the one of unittest.TestCase. :return: the wrapped test case's representation. """ *modules, test_case, test_method = self.test_id.split(".") return f"{test_method} ({'.'.join(modules)}.{test_case})"
[docs]class XmlTestResult(xmlrunner.runner._XMLTestResult): """ Test result class that can express test results in a XML report. Used by XMLTestRunner. """ def __init__( self, stream: TextIOWrapper = sys.stderr, descriptions: bool = True, verbosity: int = 0, elapsed_times: bool = True, properties: Optional[Dict[str, Any]] = None, infoclass: Type[TestInfo] = TestInfo, ): """Initialize both base classes with the appropriate parameters. :param stream: buffered text interface to a buffered raw stream :param descriptions: include description of the test :param verbosity: print output into the console :param elapsed_times: include the time spend on the test :param properties: junit testsuite properties :param infoclass: class containing the test information """ xmlrunner.runner._XMLTestResult.__init__( self, stream=stream, descriptions=descriptions, verbosity=verbosity, elapsed_times=elapsed_times, properties=properties, infoclass=infoclass, )
[docs] def addSuccess(self, test: TestCase) -> None: """Calls only _XMLTestResult's addSuccess as BannerTestResult's appends TestCase instances to the successes list, but _XMLTestResult wraps them in TestInfo instances with additional attributes. :param test: current succeeded TestCase. """ return xmlrunner.runner._XMLTestResult.addSuccess(self, test)
# save the original staticmethod that will be overwritten in order to call it report_testcase = copy.deepcopy(xmlrunner.runner._XMLTestResult._report_testcase) @staticmethod def _report_testcase( test_result: TestInfo, xml_testsuite: Element, xml_document: Document ): """Callback function that create the test case section into the XML document. :param test_result: result of a test run :param xml_testsuite: xml test suite to be written :param xml_document: xml document base """ # call the original method XmlTestResult.report_testcase(test_result, xml_testsuite, xml_document) # add user-defined properties to the test cases if any xml_testcases = xml_testsuite.getElementsByTagName("testcase") for xml_testcase in xml_testcases: testcase_properties = getattr(test_result._test, "properties", None) if not isinstance(testcase_properties, dict): break # avoid adding the same properties to the same testcase multiple times if xml_testcase.getElementsByTagName("properties"): continue # add the properties to the testcase XmlTestResult._report_testsuite_properties( xml_testcase, xml_document, testcase_properties ) # here can be added additional tags that have to be stored into the xml test report xml_testsuite.setAttribute("test_ids", str(test_result.test_ids)) # Overwrite the original staticmethod xmlrunner.runner._XMLTestResult._report_testcase with ours xmlrunner.runner._XMLTestResult._report_testcase = _report_testcase