From 8b49ddfa585e8dee77ba3aeef654598e0052237e Mon Sep 17 00:00:00 2001 From: Carlos Jenkins Date: Wed, 16 Aug 2017 05:23:28 -0600 Subject: [PATCH 1/4] Renamed the fixture record_xml_property to record_property and adapted logic so that the properties are passed to the TestReport object and thus allow compatibility with pytest-xdist. --- AUTHORS | 1 + _pytest/deprecated.py | 6 +++++ _pytest/junitxml.py | 44 ++++++++++++++++++++----------- _pytest/nodes.py | 4 +++ _pytest/runner.py | 8 +++++- changelog/2770.feature | 2 ++ doc/en/builtin.rst | 9 ++++--- doc/en/example/simple.rst | 2 +- doc/en/usage.rst | 54 +++++++++++++++++++++++++++++++-------- testing/test_junitxml.py | 16 ++++++------ 10 files changed, 107 insertions(+), 39 deletions(-) create mode 100644 changelog/2770.feature diff --git a/AUTHORS b/AUTHORS index cda6511a0..a008ba981 100644 --- a/AUTHORS +++ b/AUTHORS @@ -35,6 +35,7 @@ Brianna Laugher Bruno Oliveira Cal Leeming Carl Friedrich Bolz +Carlos Jenkins Ceridwen Charles Cloud Charnjit SiNGH (CCSJ) diff --git a/_pytest/deprecated.py b/_pytest/deprecated.py index 9c0fbeca7..aa1235013 100644 --- a/_pytest/deprecated.py +++ b/_pytest/deprecated.py @@ -41,6 +41,12 @@ MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning( "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" diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index a8cea6fc1..22686313a 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -233,31 +233,41 @@ class _NodeReporter(object): @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', @@ -442,6 +452,10 @@ class LogXML(object): 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) diff --git a/_pytest/nodes.py b/_pytest/nodes.py index e836cd4d6..7d802004f 100644 --- a/_pytest/nodes.py +++ b/_pytest/nodes.py @@ -360,6 +360,10 @@ class Item(Node): 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 diff --git a/_pytest/runner.py b/_pytest/runner.py index b41a3d350..e8aac76fc 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -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) @@ -326,7 +327,8 @@ class TestReport(BaseReport): """ def __init__(self, nodeid, location, keywords, outcome, - longrepr, when, sections=(), duration=0, **extra): + longrepr, when, user_properties, + sections=(), duration=0, **extra): #: normalized collection node id self.nodeid = nodeid @@ -348,6 +350,10 @@ class TestReport(BaseReport): #: 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 diff --git a/changelog/2770.feature b/changelog/2770.feature new file mode 100644 index 000000000..248f2893d --- /dev/null +++ b/changelog/2770.feature @@ -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. diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index a380b9abd..ba033849c 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -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 diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index ffc68b296..d509d56f1 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -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 diff --git a/doc/en/usage.rst b/doc/en/usage.rst index abd8bac2b..2b86420bb 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -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: @@ -245,13 +250,42 @@ This will add an extra property ``example_key="1"`` to the generated +Alternatively, you can integrate this functionality with custom markers: + +.. 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 + + + + + + + .. warning:: - ``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. - - 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. diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 031caeb20..b8bbd888f 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -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") @@ -877,15 +877,15 @@ def test_record_property(testdir): 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") From d838193d2db7f9644c7f7959c3f9cf66033b5dfa Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 21 Feb 2018 17:45:52 -0300 Subject: [PATCH 2/4] Add note about deprecating record_xml_property Also make record_xml_property return record_property directly --- _pytest/junitxml.py | 4 ++-- changelog/2770.removal.rst | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog/2770.removal.rst diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 22686313a..98b2d13cf 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -250,7 +250,7 @@ def record_property(request): @pytest.fixture -def record_xml_property(request): +def record_xml_property(record_property): """(Deprecated) use record_property.""" import warnings from _pytest import deprecated @@ -260,7 +260,7 @@ def record_xml_property(request): stacklevel=2 ) - return record_property(request) + return record_property @pytest.fixture diff --git a/changelog/2770.removal.rst b/changelog/2770.removal.rst new file mode 100644 index 000000000..0e38009ab --- /dev/null +++ b/changelog/2770.removal.rst @@ -0,0 +1 @@ +``record_xml_property`` fixture is now deprecated in favor of the more generic ``record_property``. \ No newline at end of file From 567b1ea7a137757d32d5b187abe744bd4ee27b85 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 21 Feb 2018 17:56:49 -0300 Subject: [PATCH 3/4] Move user_properties to the end of parameter list for backward compatibility --- _pytest/runner.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/_pytest/runner.py b/_pytest/runner.py index e8aac76fc..6792387db 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -317,8 +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) + sections, duration, user_properties=item.user_properties) class TestReport(BaseReport): @@ -327,8 +326,7 @@ class TestReport(BaseReport): """ def __init__(self, nodeid, location, keywords, outcome, - longrepr, when, user_properties, - sections=(), duration=0, **extra): + longrepr, when, sections=(), duration=0, user_properties=(), **extra): #: normalized collection node id self.nodeid = nodeid From 0f58fc881b795478d3f93f8f2bbd6b73a11b4c57 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Thu, 22 Feb 2018 19:26:46 +0100 Subject: [PATCH 4/4] Add pdb test with disabled logging plugin Implement the test from #3210, which was not merged yet, because the PR was abandoned in favor or #3234. --- testing/test_pdb.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index a36ada05b..445cafcc5 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -205,6 +205,24 @@ class TestPDB(object): assert "1 failed" in rest self.flush(child) + def test_pdb_print_captured_logs_nologging(self, testdir): + p1 = testdir.makepyfile(""" + def test_1(): + import logging + logging.warn("get " + "rekt") + assert False + """) + child = testdir.spawn_pytest("--show-capture=all --pdb " + "-p no:logging %s" % p1) + child.expect("get rekt") + output = child.before.decode("utf8") + assert "captured log" not in output + child.expect("(Pdb)") + child.sendeof() + rest = child.read().decode("utf8") + assert "1 failed" in rest + self.flush(child) + def test_pdb_interaction_exception(self, testdir): p1 = testdir.makepyfile(""" import pytest