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