Skip to content

Commit fa6acdc

Browse files
committed
junitxml: add properties node in testsuite level
The commit allow users to add a properties node in testsuite level see example below: <testsuite errors="0" failures="0" name="pytest" skips="1" tests="1" time="11.824"> <properties> <property name="ARCH" value="PPC"/> <property name="OS" value="RHEL 7.2"/> <property name="TestPlanURL" value="https://url.."/> <property name="Automated" value="True"/> </properties> <testcase classname="git.....> </testcase> </testsuite> The current situation is that properties node can be added to every testcase node. However, sometimes we need some global properties that applies to all testcases and give better description for the testsuite itself.
1 parent 5fd8207 commit fa6acdc

File tree

5 files changed

+111
-0
lines changed

5 files changed

+111
-0
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Ronny Pfannschmidt
7575
Ross Lawley
7676
Ryan Wooden
7777
Samuele Pedroni
78+
Tareq Alayan
7879
Tom Viner
7980
Trevor Bekolay
8081
Wouter van Ackooy

CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
tests.
1616
Thanks `@kalekundert`_ for the complete PR (`#1441`_).
1717

18+
* New Add ability to add global properties in the final xunit output file.
19+
Thanks `@tareqalayan`_ for the complete PR `#1454`_).
20+
21+
1822
*
1923

2024
**Changes**
@@ -28,10 +32,13 @@
2832
.. _@milliams: https://github.com/milliams
2933
.. _@novas0x2a: https://github.com/novas0x2a
3034
.. _@kalekundert: https://github.com/kalekundert
35+
.. _@tareqalayan: https://github.com/tareqalayan
3136

3237
.. _#1428: https://github.com/pytest-dev/pytest/pull/1428
3338
.. _#1444: https://github.com/pytest-dev/pytest/pull/1444
3439
.. _#1441: https://github.com/pytest-dev/pytest/pull/1441
40+
.. _#1454: https://github.com/pytest-dev/pytest/pull/1454
41+
3542

3643
2.9.1.dev1
3744
==========

_pytest/junitxml.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ def __init__(self, logfile, prefix):
254254
], 0)
255255
self.node_reporters = {} # nodeid -> _NodeReporter
256256
self.node_reporters_ordered = []
257+
self.global_properties = []
257258

258259
def finalize(self, report):
259260
nodeid = getattr(report, 'nodeid', report)
@@ -273,9 +274,12 @@ def node_reporter(self, report):
273274
if key in self.node_reporters:
274275
# TODO: breasks for --dist=each
275276
return self.node_reporters[key]
277+
276278
reporter = _NodeReporter(nodeid, self)
279+
277280
self.node_reporters[key] = reporter
278281
self.node_reporters_ordered.append(reporter)
282+
279283
return reporter
280284

281285
def add_stats(self, key):
@@ -361,7 +365,9 @@ def pytest_sessionfinish(self):
361365
numtests = self.stats['passed'] + self.stats['failure']
362366

363367
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
368+
364369
logfile.write(Junit.testsuite(
370+
self._get_global_properties_node(),
365371
[x.to_xml() for x in self.node_reporters_ordered],
366372
name="pytest",
367373
errors=self.stats['error'],
@@ -374,3 +380,18 @@ def pytest_sessionfinish(self):
374380
def pytest_terminal_summary(self, terminalreporter):
375381
terminalreporter.write_sep("-",
376382
"generated xml file: %s" % (self.logfile))
383+
384+
def add_global_property(self, name, value):
385+
self.global_properties.append((str(name), bin_xml_escape(value)))
386+
387+
def _get_global_properties_node(self):
388+
"""Return a Junit node containing custom properties, if any.
389+
"""
390+
if self.global_properties:
391+
return Junit.properties(
392+
[
393+
Junit.property(name=name, value=value)
394+
for name, value in self.global_properties
395+
]
396+
)
397+
return ''

doc/en/usage.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,53 @@ This will add an extra property ``example_key="1"`` to the generated
193193
Also please note that using this feature will break any schema verification.
194194
This might be a problem when used with some CI servers.
195195

196+
LogXML: add_global_property
197+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
198+
199+
.. versionadded:: 2.10
200+
201+
If you want to add a properties node in the testsuite level, which may contains properties that are relevant
202+
to all testcases you can use ``LogXML.add_global_properties``
203+
204+
.. code-block:: python
205+
206+
import pytest
207+
208+
@pytest.fixture(scope="session")
209+
def log_global_env_facts(f):
210+
211+
if pytest.config.pluginmanager.hasplugin('junitxml'):
212+
my_junit = getattr(pytest.config, '_xml', None)
213+
214+
my_junit.add_global_property('ARCH', 'PPC')
215+
my_junit.add_global_property('STORAGE_TYPE', 'CEPH')
216+
217+
@pytest.mark.usefixtures(log_global_env_facts)
218+
def start_and_prepare_env():
219+
pass
220+
221+
class TestMe:
222+
def test_foo(self):
223+
assert True
224+
225+
This will add a property node below the testsuite node to the generated xml:
226+
227+
.. code-block:: xml
228+
229+
<testsuite errors="0" failures="0" name="pytest" skips="0" tests="1" time="0.006">
230+
<properties>
231+
<property name="ARCH" value="PPC"/>
232+
<property name="STORAGE_TYPE" value="CEPH"/>
233+
</properties>
234+
<testcase classname="test_me.TestMe" file="test_me.py" line="16" name="test_foo" time="0.000243663787842"/>
235+
</testsuite>
236+
237+
.. warning::
238+
239+
This is an experimental feature, and its interface might be replaced
240+
by something more powerful and general in future versions. The
241+
functionality per-se will be kept.
242+
196243
Creating resultlog format files
197244
----------------------------------------------------
198245

testing/test_junitxml.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,3 +783,38 @@ def test_pass():
783783
u'test_fancy_items_regression test_pass'
784784
u' test_fancy_items_regression.py',
785785
]
786+
787+
788+
def test_global_properties(testdir):
789+
path = testdir.tmpdir.join("test_global_properties.xml")
790+
log = LogXML(str(path), None)
791+
from _pytest.runner import BaseReport
792+
793+
class Report(BaseReport):
794+
sections = []
795+
nodeid = "test_node_id"
796+
797+
log.pytest_sessionstart()
798+
log.add_global_property('foo', 1)
799+
log.add_global_property('bar', 2)
800+
log.pytest_sessionfinish()
801+
802+
dom = minidom.parse(str(path))
803+
804+
properties = dom.getElementsByTagName('properties')
805+
806+
assert (properties.length == 1), "There must be one <properties> node"
807+
808+
property_list = dom.getElementsByTagName('property')
809+
810+
assert (property_list.length == 2), "There most be only 2 property nodes"
811+
812+
expected = {'foo': '1', 'bar': '2'}
813+
actual = {}
814+
815+
for p in property_list:
816+
k = str(p.getAttribute('name'))
817+
v = str(p.getAttribute('value'))
818+
actual[k] = v
819+
820+
assert actual == expected

0 commit comments

Comments
 (0)