def test_parse_error_from_xml_parse_error(self):
     error = XmlParseError('xml parse error')
     error.code = 123
     error.position = (1, 2)
     actual = ParseError.from_exception('file', error)
     expected = ParseError('file', 'xml parse error', 1, 2)
     self.assertEqual(expected, actual)
Example #2
0
def parse_junit_xml_files(files: Iterable[str]) -> ParsedUnitTestResults:
    """Parses junit xml files and returns aggregated statistics as a ParsedUnitTestResults."""
    def parse(path: str) -> Union[str, Any]:
        if not os.path.exists(path):
            return FileNotFoundError(f'File does not exist.')
        if os.stat(path).st_size == 0:
            return Exception(f'File is empty.')

        try:
            return JUnitXml.fromfile(path)
        except BaseException as e:
            return e

    parsed_files = [(result_file, parse(result_file)) for result_file in files]
    junits = [(result_file, junit) for result_file, junit in parsed_files
              if not isinstance(junit, BaseException)]
    errors = [
        ParseError.from_exception(result_file, exception)
        for result_file, exception in parsed_files
        if isinstance(exception, BaseException)
    ]

    suites = [(result_file, suite) for result_file, junit in junits
              for suite in (junit if junit._tag == "testsuites" else [junit])]
    suite_tests = sum([suite.tests for result_file, suite in suites])
    suite_skipped = sum(
        [suite.skipped + suite.disabled for result_file, suite in suites])
    suite_failures = sum([suite.failures for result_file, suite in suites])
    suite_errors = sum([suite.errors for result_file, suite in suites])
    suite_time = int(sum([suite.time for result_file, suite in suites]))

    def int_opt(string: Optional[str]) -> Optional[int]:
        try:
            return int(string) if string else None
        except ValueError:
            return None

    def get_cases(suite: TestSuite) -> List[TestCase]:
        """
        JUnit seems to allow for testsuite tags inside testsuite tags, potentially at any depth.
        https://llg.cubic.org/docs/junit/

        This skips all inner testsuite tags and returns a list of all contained testcase tags.
        """
        suites = list(suite.iterchildren(TestSuite))
        cases = list(suite.iterchildren(TestCase))
        return [case for suite in suites for case in get_cases(suite)] + cases

    cases = [
        UnitTestCase(result_file=result_file,
                     test_file=case._elem.get('file'),
                     line=int_opt(case._elem.get('line')),
                     class_name=case.classname,
                     test_name=case.name,
                     result=get_result(results),
                     message=get_message(results),
                     content=get_content(results),
                     time=case.time) for result_file, suite in suites
        for case in get_cases(suite)
        if case.classname is not None or case.name is not None
        for results in [get_results(case.result, case.status)]
    ]

    return ParsedUnitTestResults(
        files=len(parsed_files),
        errors=errors,
        # test state counts from suites
        suites=len(suites),
        suite_tests=suite_tests,
        suite_skipped=suite_skipped,
        suite_failures=suite_failures,
        suite_errors=suite_errors,
        suite_time=suite_time,
        # test cases
        cases=cases)
 def test_parse_error_from_error(self):
     actual = ParseError.from_exception('file', ValueError('error'))
     expected = ParseError('file', 'error', None, None)
     self.assertEqual(expected, actual)
 def test_parse_error_from_file_not_found(self):
     error = FileNotFoundError(2, 'No such file or directory')
     error.filename = 'some file path'
     actual = ParseError.from_exception('file', error)
     expected = ParseError('file', "[Errno 2] No such file or directory: 'some file path'", None, None)
     self.assertEqual(expected, actual)