yapf junitxml

This commit is contained in:
Ronny Pfannschmidt 2015-09-29 20:46:40 +02:00
parent efb5332023
commit 02f5defd89
1 changed files with 54 additions and 35 deletions

View File

@ -1,9 +1,13 @@
""" report test results in JUnit-XML format, for use with Hudson and build integration servers. """
report test results in JUnit-XML format,
for use with Jenkins and build integration servers.
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
Based on initial code from Ross Lawley. Based on initial code from Ross Lawley.
""" """
# Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
# src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
import py import py
import os import os
import re import re
@ -19,10 +23,10 @@ else:
unicode = str unicode = str
long = int long = int
class Junit(py.xml.Namespace): class Junit(py.xml.Namespace):
pass pass
# We need to get the subset of the invalid unicode ranges according to # We need to get the subset of the invalid unicode ranges according to
# XML 1.0 which are valid in this python build. Hence we calculate # XML 1.0 which are valid in this python build. Hence we calculate
# this dynamically instead of hardcoding it. The spec range of valid # this dynamically instead of hardcoding it. The spec range of valid
@ -30,21 +34,19 @@ class Junit(py.xml.Namespace):
# | [#x10000-#x10FFFF] # | [#x10000-#x10FFFF]
_legal_chars = (0x09, 0x0A, 0x0d) _legal_chars = (0x09, 0x0A, 0x0d)
_legal_ranges = ( _legal_ranges = (
(0x20, 0x7E), (0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF),
(0x80, 0xD7FF),
(0xE000, 0xFFFD),
(0x10000, 0x10FFFF),
) )
_legal_xml_re = [unicode("%s-%s") % (unichr(low), unichr(high)) _legal_xml_re = [
for (low, high) in _legal_ranges unicode("%s-%s") % (unichr(low), unichr(high))
if low < sys.maxunicode] for (low, high) in _legal_ranges if low < sys.maxunicode
]
_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re _legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re
illegal_xml_re = re.compile(unicode('[^%s]') % illegal_xml_re = re.compile(unicode('[^%s]') % unicode('').join(_legal_xml_re))
unicode('').join(_legal_xml_re))
del _legal_chars del _legal_chars
del _legal_ranges del _legal_ranges
del _legal_xml_re del _legal_xml_re
def bin_xml_escape(arg): def bin_xml_escape(arg):
def repl(matchobj): def repl(matchobj):
i = ord(matchobj.group()) i = ord(matchobj.group())
@ -52,30 +54,44 @@ def bin_xml_escape(arg):
return unicode('#x%02X') % i return unicode('#x%02X') % i
else: else:
return unicode('#x%04X') % i return unicode('#x%04X') % i
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg))) return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
@pytest.fixture @pytest.fixture
def record_xml_property(request): def record_xml_property(request):
"""Fixture that adds extra xml properties to the tag for the calling test. """Fixture that adds extra xml properties to the tag for the calling test.
The fixture is callable with (name, value), with value being automatically The fixture is callable with (name, value), with value being automatically
xml-encoded. xml-encoded.
""" """
def inner(name, value): def inner(name, value):
if hasattr(request.config, "_xml"): if hasattr(request.config, "_xml"):
request.config._xml.add_custom_property(name, value) request.config._xml.add_custom_property(name, value)
msg = 'record_xml_property is an experimental feature' msg = 'record_xml_property is an experimental feature'
request.config.warn(code='C3', message=msg, request.config.warn(code='C3',
message=msg,
fslocation=request.node.location[:2]) fslocation=request.node.location[:2])
return inner return inner
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("terminal reporting") group = parser.getgroup("terminal reporting")
group.addoption('--junitxml', '--junit-xml', action="store", group.addoption(
dest="xmlpath", metavar="path", default=None, '--junitxml', '--junit-xml',
help="create junit-xml style report file at given path.") action="store",
group.addoption('--junitprefix', '--junit-prefix', action="store", dest="xmlpath",
metavar="str", default=None, metavar="path",
help="prepend prefix to classnames in junit-xml output") default=None,
help="create junit-xml style report file at given path.")
group.addoption(
'--junitprefix', '--junit-prefix',
action="store",
metavar="str",
default=None,
help="prepend prefix to classnames in junit-xml output")
def pytest_configure(config): def pytest_configure(config):
xmlpath = config.option.xmlpath xmlpath = config.option.xmlpath
@ -84,17 +100,20 @@ def pytest_configure(config):
config._xml = LogXML(xmlpath, config.option.junitprefix) config._xml = LogXML(xmlpath, config.option.junitprefix)
config.pluginmanager.register(config._xml) config.pluginmanager.register(config._xml)
def pytest_unconfigure(config): def pytest_unconfigure(config):
xml = getattr(config, '_xml', None) xml = getattr(config, '_xml', None)
if xml: if xml:
del config._xml del config._xml
config.pluginmanager.unregister(xml) config.pluginmanager.unregister(xml)
def mangle_testnames(names): def mangle_testnames(names):
names = [x.replace(".py", "") for x in names if x != '()'] names = [x.replace(".py", "") for x in names if x != '()']
names[0] = names[0].replace("/", '.') names[0] = names[0].replace("/", '.')
return names return names
class LogXML(object): class LogXML(object):
def __init__(self, logfile, prefix): def __init__(self, logfile, prefix):
logfile = os.path.expanduser(os.path.expandvars(logfile)) logfile = os.path.expanduser(os.path.expandvars(logfile))
@ -134,10 +153,10 @@ class LogXML(object):
for capname in ('out', 'err'): for capname in ('out', 'err'):
allcontent = "" allcontent = ""
for name, content in report.get_sections("Captured std%s" % for name, content in report.get_sections("Captured std%s" %
capname): capname):
allcontent += content allcontent += content
if allcontent: if allcontent:
tag = getattr(Junit, 'system-'+capname) tag = getattr(Junit, 'system-' + capname)
self.append(tag(bin_xml_escape(allcontent))) self.append(tag(bin_xml_escape(allcontent)))
def append(self, obj): def append(self, obj):
@ -150,10 +169,10 @@ class LogXML(object):
if self.custom_properties: if self.custom_properties:
result = Junit.properties( result = Junit.properties(
[ [
Junit.property(name=name, value=value) Junit.property(name=name,
value=value)
for name, value in self.custom_properties.items() for name, value in self.custom_properties.items()
] ])
)
self.custom_properties.clear() self.custom_properties.clear()
return result return result
return None return None
@ -163,7 +182,7 @@ class LogXML(object):
self._write_captured_output(report) self._write_captured_output(report)
def append_failure(self, report): def append_failure(self, report):
#msg = str(report.longrepr.reprtraceback.extraline) # msg = str(report.longrepr.reprtraceback.extraline)
if hasattr(report, "wasxfail"): if hasattr(report, "wasxfail"):
self.append( self.append(
Junit.skipped(message="xfail-marked test passes unexpectedly")) Junit.skipped(message="xfail-marked test passes unexpectedly"))
@ -183,13 +202,13 @@ class LogXML(object):
self._write_captured_output(report) self._write_captured_output(report)
def append_collect_error(self, report): def append_collect_error(self, report):
#msg = str(report.longrepr.reprtraceback.extraline) # msg = str(report.longrepr.reprtraceback.extraline)
self.append(Junit.error(bin_xml_escape(report.longrepr), self.append(Junit.error(bin_xml_escape(report.longrepr),
message="collection failure")) message="collection failure"))
self.errors += 1 self.errors += 1
def append_collect_skipped(self, report): def append_collect_skipped(self, report):
#msg = str(report.longrepr.reprtraceback.extraline) # msg = str(report.longrepr.reprtraceback.extraline)
self.append(Junit.skipped(bin_xml_escape(report.longrepr), self.append(Junit.skipped(bin_xml_escape(report.longrepr),
message="collection skipped")) message="collection skipped"))
self.skipped += 1 self.skipped += 1
@ -210,8 +229,7 @@ class LogXML(object):
self.append( self.append(
Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason), Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason),
type="pytest.skip", type="pytest.skip",
message=skipreason message=skipreason))
))
self.skipped += 1 self.skipped += 1
self._write_captured_output(report) self._write_captured_output(report)
@ -278,9 +296,10 @@ class LogXML(object):
data = bin_xml_escape(excrepr) data = bin_xml_escape(excrepr)
self.tests.append( self.tests.append(
Junit.testcase( Junit.testcase(
Junit.error(data, message="internal error"), Junit.error(data,
classname="pytest", message="internal error"),
name="internal")) classname="pytest",
name="internal"))
def pytest_sessionstart(self): def pytest_sessionstart(self):
self.suite_start_time = time.time() self.suite_start_time = time.time()
@ -302,9 +321,9 @@ class LogXML(object):
failures=self.failed, failures=self.failed,
skips=self.skipped, skips=self.skipped,
tests=numtests, tests=numtests,
time="%.3f" % suite_time_delta, time="%.3f" % suite_time_delta, ).unicode(indent=0))
).unicode(indent=0))
logfile.close() logfile.close()
def pytest_terminal_summary(self, terminalreporter): def pytest_terminal_summary(self, terminalreporter):
terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile)) terminalreporter.write_sep("-",
"generated xml file: %s" % (self.logfile))