Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make record_xml_property generic and compatible with xdist and markers. #2770

Merged
merged 3 commits into from
Feb 22, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Brianna Laugher
Bruno Oliveira
Cal Leeming
Carl Friedrich Bolz
Carlos Jenkins
Ceridwen
Charles Cloud
Charnjit SiNGH (CCSJ)
Expand Down
6 changes: 6 additions & 0 deletions _pytest/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ class RemovedInPytest4Warning(DeprecationWarning):
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
)

RECORD_XML_PROPERTY = (
'Fixture renamed from "record_xml_property" to "record_property" as user '
'properties are now available to all reporters.\n'
'"record_xml_property" is now deprecated.'
)

COLLECTOR_MAKEITEM = RemovedInPytest4Warning(
"pycollector makeitem was removed "
"as it is an accidentially leaked internal api"
Expand Down
44 changes: 29 additions & 15 deletions _pytest/junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,31 +233,41 @@ def finalize(self):


@pytest.fixture
def record_xml_property(request):
"""Add extra xml properties to the tag for the calling test.
The fixture is callable with ``(name, value)``, with value being automatically
xml-encoded.
def record_property(request):
"""Add an extra properties the calling test.
User properties become part of the test report and are available to the
configured reporters, like JUnit XML.
The fixture is callable with ``(name, value)``.
"""
request.node.warn(
code='C3',
message='record_xml_property is an experimental feature',
message='record_property is an experimental feature',
)
xml = getattr(request.config, "_xml", None)
if xml is not None:
node_reporter = xml.node_reporter(request.node.nodeid)
return node_reporter.add_property
else:
def add_property_noop(name, value):
pass

return add_property_noop
def append_property(name, value):
request.node.user_properties.append((name, value))
return append_property


@pytest.fixture
def record_xml_property(request):
"""(Deprecated) use record_property."""
import warnings
from _pytest import deprecated
warnings.warn(
deprecated.RECORD_XML_PROPERTY,
DeprecationWarning,
stacklevel=2
)

return record_property(request)


@pytest.fixture
def record_xml_attribute(request):
"""Add extra xml attributes to the tag for the calling test.
The fixture is callable with ``(name, value)``, with value being automatically
xml-encoded
The fixture is callable with ``(name, value)``, with value being
automatically xml-encoded
"""
request.node.warn(
code='C3',
Expand Down Expand Up @@ -442,6 +452,10 @@ def pytest_runtest_logreport(self, report):
if report.when == "teardown":
reporter = self._opentestcase(report)
reporter.write_captured_output(report)

for propname, propvalue in report.user_properties:
reporter.add_property(propname, propvalue)

self.finalize(report)
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)
Expand Down
4 changes: 4 additions & 0 deletions _pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ def __init__(self, name, parent=None, config=None, session=None):
super(Item, self).__init__(name, parent, config, session)
self._report_sections = []

#: user properties is a list of tuples (name, value) that holds user
#: defined properties for this test.
self.user_properties = []

def add_report_section(self, when, key, content):
"""
Adds a new report section, similar to what's done internally to add stdout and
Expand Down
8 changes: 7 additions & 1 deletion _pytest/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ def pytest_runtest_makereport(item, call):
sections.append(("Captured %s %s" % (key, rwhen), content))
return TestReport(item.nodeid, item.location,
keywords, outcome, longrepr, when,
item.user_properties,
sections, duration)


Expand All @@ -326,7 +327,8 @@ class TestReport(BaseReport):
"""

def __init__(self, nodeid, location, keywords, outcome,
longrepr, when, sections=(), duration=0, **extra):
longrepr, when, user_properties,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think user_properties should go after duration to avoid breaking clients needlessly.

sections=(), duration=0, **extra):
#: normalized collection node id
self.nodeid = nodeid

Expand All @@ -348,6 +350,10 @@ def __init__(self, nodeid, location, keywords, outcome,
#: one of 'setup', 'call', 'teardown' to indicate runtest phase.
self.when = when

#: user properties is a list of tuples (name, value) that holds user
#: defined properties of the test
self.user_properties = user_properties

#: list of pairs ``(str, str)`` of extra information which needs to
#: marshallable. Used by pytest to add captured text
#: from ``stdout`` and ``stderr``, but may be used by other plugins
Expand Down
2 changes: 2 additions & 0 deletions changelog/2770.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``record_xml_property`` renamed to ``record_property`` and is now compatible with xdist, markers and any reporter.
``record_xml_property`` name is now deprecated.
9 changes: 5 additions & 4 deletions doc/en/builtin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,11 @@ You can ask for available builtin or project-custom
Inject names into the doctest namespace.
pytestconfig
the pytest config object with access to command line opts.
record_xml_property
Add extra xml properties to the tag for the calling test.
The fixture is callable with ``(name, value)``, with value being automatically
xml-encoded.
record_property
Add an extra properties the calling test.
User properties become part of the test report and are available to the
configured reporters, like JUnit XML.
The fixture is callable with ``(name, value)``.
record_xml_attribute
Add extra xml attributes to the tag for the calling test.
The fixture is callable with ``(name, value)``, with value being automatically
Expand Down
2 changes: 1 addition & 1 deletion doc/en/example/simple.rst
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ We can run this::
file $REGENDOC_TMPDIR/b/test_error.py, line 1
def test_root(db): # no db here, will error out
E fixture 'db' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_xml_attribute, record_xml_property, recwarn, tmpdir, tmpdir_factory
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_xml_attribute, record_property, recwarn, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.

$REGENDOC_TMPDIR/b/test_error.py:1
Expand Down
54 changes: 44 additions & 10 deletions doc/en/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -220,19 +220,24 @@ To set the name of the root test suite xml item, you can configure the ``junit_s
[pytest]
junit_suite_name = my_suite

record_xml_property
record_property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. versionadded:: 2.8
.. versionchanged:: 3.5

Fixture renamed from ``record_xml_property`` to ``record_property`` as user
properties are now available to all reporters.
``record_xml_property`` is now deprecated.

If you want to log additional information for a test, you can use the
``record_xml_property`` fixture:
``record_property`` fixture:

.. code-block:: python

def test_function(record_xml_property):
record_xml_property("example_key", 1)
assert 0
def test_function(record_property):
record_property("example_key", 1)
assert True

This will add an extra property ``example_key="1"`` to the generated
``testcase`` tag:
Expand All @@ -245,13 +250,42 @@ This will add an extra property ``example_key="1"`` to the generated
</properties>
</testcase>

.. warning::
Alternatively, you can integrate this functionality with custom markers:

``record_xml_property`` is an experimental feature, and its interface might be replaced
by something more powerful and general in future versions. The
functionality per-se will be kept, however.
.. code-block:: python

# content of conftest.py

def pytest_collection_modifyitems(session, config, items):
for item in items:
marker = item.get_marker('test_id')
if marker is not None:
test_id = marker.args[0]
item.user_properties.append(('test_id', test_id))

And in your tests:

.. code-block:: python

# content of test_function.py

@pytest.mark.test_id(1501)
def test_function():
assert True

Will result in:

.. code-block:: xml

<testcase classname="test_function" file="test_function.py" line="0" name="test_function" time="0.0009">
<properties>
<property name="test_id" value="1501" />
</properties>
</testcase>

.. warning::

Currently it does not work when used with the ``pytest-xdist`` plugin.
``record_property`` is an experimental feature and may change in the future.

Also please note that using this feature will break any schema verification.
This might be a problem when used with some CI servers.
Expand Down
16 changes: 8 additions & 8 deletions testing/test_junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,10 +863,10 @@ def test_record_property(testdir):
import pytest

@pytest.fixture
def other(record_xml_property):
record_xml_property("bar", 1)
def test_record(record_xml_property, other):
record_xml_property("foo", "<1");
def other(record_property):
record_property("bar", 1)
def test_record(record_property, other):
record_property("foo", "<1");
""")
result, dom = runandparse(testdir, '-rw')
node = dom.find_first_by_tag("testsuite")
Expand All @@ -877,15 +877,15 @@ def test_record(record_xml_property, other):
pnodes[1].assert_attr(name="foo", value="<1")
result.stdout.fnmatch_lines([
'test_record_property.py::test_record',
'*record_xml_property*experimental*',
'*record_property*experimental*',
])


def test_record_property_same_name(testdir):
testdir.makepyfile("""
def test_record_with_same_name(record_xml_property):
record_xml_property("foo", "bar")
record_xml_property("foo", "baz")
def test_record_with_same_name(record_property):
record_property("foo", "bar")
record_property("foo", "baz")
""")
result, dom = runandparse(testdir, '-rw')
node = dom.find_first_by_tag("testsuite")
Expand Down