diff --git a/AUTHORS b/AUTHORS index 04b87359d6c..42bdf81b16a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -75,6 +75,7 @@ Ronny Pfannschmidt Ross Lawley Ryan Wooden Samuele Pedroni +Tareq Alayan Tom Viner Trevor Bekolay Wouter van Ackooy diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0d61505331a..c1f573f18aa 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,10 @@ tests. Thanks `@kalekundert`_ for the complete PR (`#1441`_). +* New Add ability to add global properties in the final xunit output file. + Thanks `@tareqalayan`_ for the complete PR `#1454`_). + + * **Changes** @@ -28,10 +32,13 @@ .. _@milliams: https://github.com/milliams .. _@novas0x2a: https://github.com/novas0x2a .. _@kalekundert: https://github.com/kalekundert +.. _@tareqalayan: https://github.com/tareqalayan .. _#1428: https://github.com/pytest-dev/pytest/pull/1428 .. _#1444: https://github.com/pytest-dev/pytest/pull/1444 .. _#1441: https://github.com/pytest-dev/pytest/pull/1441 +.. _#1454: https://github.com/pytest-dev/pytest/pull/1454 + 2.9.1.dev1 ========== diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 6c090f9c0c0..168faaf1367 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -254,6 +254,7 @@ def __init__(self, logfile, prefix): ], 0) self.node_reporters = {} # nodeid -> _NodeReporter self.node_reporters_ordered = [] + self.global_properties = [] def finalize(self, report): nodeid = getattr(report, 'nodeid', report) @@ -273,9 +274,12 @@ def node_reporter(self, report): if key in self.node_reporters: # TODO: breasks for --dist=each return self.node_reporters[key] + reporter = _NodeReporter(nodeid, self) + self.node_reporters[key] = reporter self.node_reporters_ordered.append(reporter) + return reporter def add_stats(self, key): @@ -361,7 +365,9 @@ def pytest_sessionfinish(self): numtests = self.stats['passed'] + self.stats['failure'] logfile.write('') + logfile.write(Junit.testsuite( + self._get_global_properties_node(), [x.to_xml() for x in self.node_reporters_ordered], name="pytest", errors=self.stats['error'], @@ -374,3 +380,18 @@ def pytest_sessionfinish(self): def pytest_terminal_summary(self, terminalreporter): terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile)) + + def add_global_property(self, name, value): + self.global_properties.append((str(name), bin_xml_escape(value))) + + def _get_global_properties_node(self): + """Return a Junit node containing custom properties, if any. + """ + if self.global_properties: + return Junit.properties( + [ + Junit.property(name=name, value=value) + for name, value in self.global_properties + ] + ) + return '' diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 4b92fd1e150..adf97c067fc 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -182,6 +182,47 @@ This will add an extra property ``example_key="1"`` to the generated +LogXML: add_global_property +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 2.10 + +If you want to add a properties node in the testsuite level, which may contains properties that are relevant +to all testcases you can use ``LogXML.add_global_properties`` +Note that this feature will be available on v2.10.1. + +.. code-block:: python + + import pytest + + @pytest.fixture(scope="session") + def log_global_env_facts(f): + + if pytest.config.pluginmanager.hasplugin('junitxml'): + my_junit = getattr(pytest.config, '_xml', None) + + my_junit.add_global_property('ARCH', 'PPC') + my_junit.add_global_property('STORAGE_TYPE', 'CEPH') + + @pytest.mark.usefixtures(log_global_env_facts) + def start_and_prepare_env(): + pass + + class TestMe: + def test_foo(self): + assert True + +This will add a property node below the testsuite node to the generated xml: +.. code-block:: xml + + + + + + + + + .. warning:: This is an experimental feature, and its interface might be replaced diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 99c59cb7a0b..8ff1028df98 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -783,3 +783,38 @@ def test_pass(): u'test_fancy_items_regression test_pass' u' test_fancy_items_regression.py', ] + + +def test_global_properties(testdir): + path = testdir.tmpdir.join("test_global_properties.xml") + log = LogXML(str(path), None) + from _pytest.runner import BaseReport + + class Report(BaseReport): + sections = [] + nodeid = "test_node_id" + + log.pytest_sessionstart() + log.add_global_property('foo', 1) + log.add_global_property('bar', 2) + log.pytest_sessionfinish() + + dom = minidom.parse(str(path)) + + properties = dom.getElementsByTagName('properties') + + assert (properties.length == 1), "There must be one node" + + property_list = dom.getElementsByTagName('property') + + assert (property_list.length == 2), "There most be only 2 property nodes" + + expected = {'foo': '1', 'bar': '2'} + actual = {} + + for p in property_list: + k = str(p.getAttribute('name')) + v = str(p.getAttribute('value')) + actual[k] = v + + assert actual == expected