Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement flavors #108

Merged
merged 12 commits into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,39 @@ You have two or more XML files, and you want to merge them into one.
Note that it won't check for duplicate entries. You need to deal with them on
your own.

Schema Support
~~~~~~~~~~~~~~~

By default junitparser supports the schema of windyroad_, which is a relatively
simple schema.

.. _windyroad: https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd

Junitparser also support extra schemas asas flavors:

.. code-block:: python

from junitparser.flavors.xunit2 import JUnitParser, TestCase, TestSuite, \
RerunFailure
# These classes are redefined to support extra properties and attributes
# of the xunit2 schema.
suite = TestSuite("mySuite")
suite.system_err = "System err" # xunit2 specific property
case = TestCase("myCase")
rerun_failure = RerunFailure("Not found", "404") # case property
rerun_failure.stack_trace = "Stack"
rerun_failure.system_err = "E404"
rerun_failure.system_out = "NOT FOUND"
case.add_rerun_result(rerun_failure)

Currently supported schemas including:

- xunit2_, supported by pytest, Erlang/OTP, Maven Surefire, CppTest, etc.

.. _xunit2: https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd

PRs are welcome to support more schemas.

Create XML with custom attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
7 changes: 5 additions & 2 deletions junitparser/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ def _parser(prog_name=None): # pragma: no cover

# command: verify
merge_parser = command_parser.add_parser(
"verify", help="Return a non-zero exit code if one of the testcases failed or errored."
"verify",
help="Return a non-zero exit code if one of the testcases failed or errored.",
)
merge_parser.add_argument(
"--glob",
Expand All @@ -74,7 +75,9 @@ def _parser(prog_name=None): # pragma: no cover
action="store_true",
default=False,
)
merge_parser.add_argument("paths", nargs="+", help="XML path(s) of reports to verify.")
merge_parser.add_argument(
"paths", nargs="+", help="XML path(s) of reports to verify."
)

return parser

Expand Down
1 change: 1 addition & 0 deletions junitparser/flavors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

202 changes: 202 additions & 0 deletions junitparser/flavors/xunit2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
"""
The flavor based on Jenkins xunit plugin:
https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd

According to the internet, The schema is compatible with:

- pytest (as default, though it also supports a "legacy" xunit1 flavor)
- Erlang/OTP
- Maven Surefire
- CppTest

There may be many others that I'm not aware of.
"""

import itertools
from typing import List, TypeVar
from .. import junitparser

T = TypeVar("T")


class JUnitXml(junitparser.JUnitXml):
# Pytest and xunit schema doesn't have skipped in testsuites
skipped = None

def update_statistics(self):
"""Update test count, time, etc."""
time = 0
tests = failures = errors = 0
for suite in self:
suite.update_statistics()
tests += suite.tests
failures += suite.failures
errors += suite.errors
time += suite.time
self.tests = tests
self.failures = failures
self.errors = errors
self.time = round(time, 3)


class TestSuite(junitparser.TestSuite):
"""TestSuit for Pytest, with some different attributes."""

group = junitparser.Attr()
id = junitparser.Attr()
package = junitparser.Attr()
file = junitparser.Attr()
log = junitparser.Attr()
url = junitparser.Attr()
version = junitparser.Attr()

def __iter__(self):
return itertools.chain(
super().iterchildren(TestCase),
(case for suite in super().iterchildren(TestSuite) for case in suite),
)

@property
def system_out(self):
"""<system-out>"""
elem = self.child(junitparser.SystemOut)
if elem is not None:
return elem.text
return None

@system_out.setter
def system_out(self, value: str):
"""<system-out>"""
out = self.child(junitparser.SystemOut)
if out is not None:
out.text = value
else:
out = junitparser.SystemOut(value)
self.append(out)

@property
def system_err(self):
"""<system-err>"""
elem = self.child(junitparser.SystemErr)
if elem is not None:
return elem.text
return None

@system_err.setter
def system_err(self, value: str):
"""<system-err>"""
err = self.child(junitparser.SystemErr)
if err is not None:
err.text = value
else:
err = junitparser.SystemErr(value)
self.append(err)


class StackTrace(junitparser.System):
_tag = "stackTrace"


class RerunType(junitparser.Result):
_tag = "rerunType"

@property
def stack_trace(self):
"""<stackTrace>"""
elem = self.child(StackTrace)
if elem is not None:
return elem.text
return None

@stack_trace.setter
def stack_trace(self, value: str):
"""<stackTrace>"""
trace = self.child(StackTrace)
if trace is not None:
trace.text = value
else:
trace = StackTrace(value)
self.append(trace)

@property
def system_out(self):
"""<system-out>"""
elem = self.child(junitparser.SystemOut)
if elem is not None:
return elem.text
return None

@system_out.setter
def system_out(self, value: str):
"""<system-out>"""
out = self.child(junitparser.SystemOut)
if out is not None:
out.text = value
else:
out = junitparser.SystemOut(value)
self.append(out)

@property
def system_err(self):
"""<system-err>"""
elem = self.child(junitparser.SystemErr)
if elem is not None:
return elem.text
return None

@system_err.setter
def system_err(self, value: str):
"""<system-err>"""
err = self.child(junitparser.SystemErr)
if err is not None:
err.text = value
else:
err = junitparser.SystemErr(value)
self.append(err)


class RerunFailure(RerunType):
_tag = "rerunFailure"


class RerunError(RerunType):
_tag = "rerunError"


class FlakyFailure(RerunType):
_tag = "flakyFailure"


class FlakyError(RerunType):
_tag = "flakyError"


class TestCase(junitparser.TestCase):
group = junitparser.Attr()

def _rerun_results(self, _type: T) -> List[T]:
elems = self.iterchildren(_type)
results = []
for elem in elems:
results.append(_type.fromelem(elem))
return results

def rerun_failures(self):
"""<rerunFailure>"""
return self._rerun_results(RerunFailure)

def rerun_errors(self):
"""<rerunError>"""
return self._rerun_results(RerunError)

def flaky_failures(self):
"""<flakyFailure>"""
return self._rerun_results(FlakyFailure)

def flaky_errors(self):
"""<flakyFailure>"""
return self._rerun_results(FlakyError)

def add_rerun_result(self, result: RerunType):
"""Append a rerun result to the test case. A case can have multiple rerun results"""
self.append(result)
Loading