Skip to content

Commit

Permalink
feat: adds an option that allows prefixing the class name in an xunit…
Browse files Browse the repository at this point in the history
… report
  • Loading branch information
joscha committed Nov 28, 2015
1 parent fb4a919 commit 062b73e
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 10 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ Brendan McCollam
Erik Rose
Sascha Peilicke
Andre Caron
Joscha Feth
8 changes: 8 additions & 0 deletions README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,14 @@ Options
Name of the testsuite in the xunit xml, generated by plugin.
Default test suite name is nosetests.

--xunit-prefix-with-testsuite-name

Enables prefixing of the test class name in the xunit xml.
This can be used in a matrixed build to distinguish between failures
in different environments.
If enabled, the testsuite name is used as a prefix.
[NOSE_XUNIT_PREFIX_WITH_TESTSUITE_NAME]

--all-modules

Enable plugin AllModules: Collect tests from all python modules.
Expand Down
26 changes: 23 additions & 3 deletions nose/plugins/xunit.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
If you need to change the name of the test suite, you can set the
``--xunit-testsuite-name`` option.
If you need to prefix the name of the tested classes, you can set the
``--xunit-prefix-with-testsuite-name`` option.
This can be used in a matrixed build to distinguish between failures
in different environments.
The testsuite name is used as a prefix.
Here is an abbreviated version of what an XML test report might look like::
<?xml version="1.0" encoding="UTF-8"?>
Expand Down Expand Up @@ -169,6 +175,12 @@ def _quoteattr(self, attr):
attr = xml_safe(attr)
return saxutils.quoteattr(attr)

def _getCls(self, id):
if self.xunit_prefix_class:
return self._quoteattr('%s.%s' % (self.xunit_testsuite_name, id_split(id)[0]))
else:
return self._quoteattr(id_split(id)[0])

def options(self, parser, env):
"""Sets additional command line options."""
Plugin.options(self, parser, env)
Expand All @@ -187,6 +199,13 @@ def options(self, parser, env):
help=("Name of the testsuite in the xunit xml, generated by plugin. "
"Default test suite name is nosetests."))

parser.add_option(
'--xunit-prefix-with-testsuite-name', action='store_true',
dest='xunit_prefix_class',
default=bool(env.get('NOSE_XUNIT_PREFIX_WITH_TESTSUITE_NAME', False)),
help=("Whether to prefix the class name under test with testsuite name. "
"Defaults to false."))

def configure(self, options, config):
"""Configures the xunit plugin."""
Plugin.configure(self, options, config)
Expand All @@ -200,6 +219,7 @@ def configure(self, options, config):
self.errorlist = []
self.error_report_file_name = os.path.realpath(options.xunit_file)
self.xunit_testsuite_name = options.xunit_testsuite_name
self.xunit_prefix_class = options.xunit_prefix_class

def report(self, stream):
"""Writes an Xunit-formatted XML file
Expand Down Expand Up @@ -292,7 +312,7 @@ def addError(self, test, err, capt=None):
u'<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">'
u'<%(type)s type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>'
u'</%(type)s>%(systemout)s%(systemerr)s</testcase>' %
{'cls': self._quoteattr(id_split(id)[0]),
{'cls': self._getCls(id),
'name': self._quoteattr(id_split(id)[-1]),
'taken': taken,
'type': type,
Expand All @@ -315,7 +335,7 @@ def addFailure(self, test, err, capt=None, tb_info=None):
u'<testcase classname=%(cls)s name=%(name)s time="%(taken).3f">'
u'<failure type=%(errtype)s message=%(message)s><![CDATA[%(tb)s]]>'
u'</failure>%(systemout)s%(systemerr)s</testcase>' %
{'cls': self._quoteattr(id_split(id)[0]),
{'cls': self._getCls(id),
'name': self._quoteattr(id_split(id)[-1]),
'taken': taken,
'errtype': self._quoteattr(nice_classname(err[0])),
Expand All @@ -334,7 +354,7 @@ def addSuccess(self, test, capt=None):
self.errorlist.append(
'<testcase classname=%(cls)s name=%(name)s '
'time="%(taken).3f">%(systemout)s%(systemerr)s</testcase>' %
{'cls': self._quoteattr(id_split(id)[0]),
{'cls': self._getCls(id),
'name': self._quoteattr(id_split(id)[-1]),
'taken': taken,
'systemout': self._getCapturedStdout(),
Expand Down
60 changes: 53 additions & 7 deletions unit_tests/test_xunit.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,32 @@ def test_tee_works_with_distutils_log(self):


class TestXMLOutputWithXML(unittest.TestCase):
def test_prefix_from_environ(self):
parser = optparse.OptionParser()
x = Xunit()
x.add_options(parser, env={'NOSE_XUNIT_PREFIX_WITH_TESTSUITE_NAME': 'true'})
(options, args) = parser.parse_args([])
eq_(options.xunit_prefix_class, True)

def test_prefix_from_opt(self):
parser = optparse.OptionParser()
x = Xunit()
x.add_options(parser, env={})
(options, args) = parser.parse_args(["--xunit-prefix-with-testsuite-name"])
eq_(options.xunit_prefix_class, True)

class BaseTestXMLOutputWithXML(unittest.TestCase):
def configure(self, args):
parser = optparse.OptionParser()
self.x.add_options(parser, env={})
(options, args) = parser.parse_args(args)
self.x.configure(options, Config())

def setUp(self):
self.xmlfile = os.path.abspath(
os.path.join(os.path.dirname(__file__),
'support', 'xunit.xml'))
parser = optparse.OptionParser()
self.x = Xunit()
self.x.add_options(parser, env={})
(options, args) = parser.parse_args([
"--with-xunit",
"--xunit-file=%s" % self.xmlfile
])
self.x.configure(options, Config())

try:
import xml.etree.ElementTree
Expand All @@ -128,6 +141,39 @@ class DummyStream:
f.close()
return data

class TestXMLOutputWithXMLAndPrefix(BaseTestXMLOutputWithXML):
def setUp(self):
super(TestXMLOutputWithXMLAndPrefix, self).setUp()
self.configure([
"--with-xunit",
"--xunit-file=%s" % self.xmlfile,
"--xunit-prefix-with-testsuite-name"
])

def test_addSuccess(self):
test = mktest()
self.x.beforeTest(test)
self.x.addSuccess(test, (None,None,None))

result = self.get_xml_report()
print result

if self.ET:
tree = self.ET.fromstring(result)
tc = tree.find("testcase")
eq_(tc.attrib['classname'], "nosetests.test_xunit.TC")
else:
# this is a dumb test for 2.4-
assert '<testcase classname="nosetests.test_xunit.TC" name="runTest"' in result

class TestXMLOutputWithXML(BaseTestXMLOutputWithXML):
def setUp(self):
super(TestXMLOutputWithXML, self).setUp()
self.configure([
"--with-xunit",
"--xunit-file=%s" % self.xmlfile
])

def test_addFailure(self):
test = mktest()
self.x.beforeTest(test)
Expand Down

0 comments on commit 062b73e

Please sign in to comment.