From 98530184a5d8f6b84da9cd1a72907f6ea28a65be Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 17:11:16 -0300 Subject: [PATCH 01/11] Remove funcargnames compatibility property --- changelog/5585.breaking.rst | 4 ++++ doc/en/deprecations.rst | 27 +++++++++++++-------------- src/_pytest/deprecated.py | 4 ---- src/_pytest/fixtures.py | 7 ------- src/_pytest/python.py | 13 ------------- testing/python/fixtures.py | 22 ---------------------- 6 files changed, 17 insertions(+), 60 deletions(-) create mode 100644 changelog/5585.breaking.rst diff --git a/changelog/5585.breaking.rst b/changelog/5585.breaking.rst new file mode 100644 index 000000000..12b9a52dd --- /dev/null +++ b/changelog/5585.breaking.rst @@ -0,0 +1,4 @@ +As per our policy, the following features have been deprecated in the 5.X series and are now +removed: + +* The ``funcargnames`` read-only property of ``FixtureRequest``, ``Metafunc``, and ``Function`` classes. Use ``fixturenames`` attribute. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 3334b5d5f..d5c7540ed 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -127,20 +127,6 @@ Services known to support the ``xunit2`` format: * `Azure Pipelines `__. -``funcargnames`` alias for ``fixturenames`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 5.0 - -The ``FixtureRequest``, ``Metafunc``, and ``Function`` classes track the names of -their associated fixtures, with the aptly-named ``fixturenames`` attribute. - -Prior to pytest 2.3, this attribute was named ``funcargnames``, and we have kept -that as an alias since. It is finally due for removal, as it is often confusing -in places where we or plugin authors must distinguish between fixture names and -names supplied by non-fixture things such as ``pytest.mark.parametrize``. - - Result log (``--result-log``) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -176,6 +162,19 @@ Removed Features As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after an appropriate period of deprecation has passed. +``funcargnames`` alias for ``fixturenames`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 6.0 + +The ``FixtureRequest``, ``Metafunc``, and ``Function`` classes track the names of +their associated fixtures, with the aptly-named ``fixturenames`` attribute. + +Prior to pytest 2.3, this attribute was named ``funcargnames``, and we have kept +that as an alias since. It is finally due for removal, as it is often confusing +in places where we or plugin authors must distinguish between fixture names and +names supplied by non-fixture things such as ``pytest.mark.parametrize``. + ``pytest.config`` global ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 7481473fd..d76144d60 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -19,10 +19,6 @@ DEPRECATED_EXTERNAL_PLUGINS = { "pytest_faulthandler", } -FUNCARGNAMES = PytestDeprecationWarning( - "The `funcargnames` attribute was an alias for `fixturenames`, " - "since pytest 2.3 - use the newer attribute instead." -) FILLFUNCARGS = PytestDeprecationWarning( "The `_fillfuncargs` function is deprecated, use " diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index feb145da0..c0fdc587b 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -47,7 +47,6 @@ from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config.argparsing import Parser from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS -from _pytest.deprecated import FUNCARGNAMES from _pytest.mark import ParameterSet from _pytest.outcomes import fail from _pytest.outcomes import TEST_OUTCOME @@ -457,12 +456,6 @@ class FixtureRequest: result.extend(set(self._fixture_defs).difference(result)) return result - @property - def funcargnames(self) -> List[str]: - """Alias attribute for ``fixturenames`` for pre-2.3 compatibility.""" - warnings.warn(FUNCARGNAMES, stacklevel=2) - return self.fixturenames - @property def node(self): """Underlying collection node (depends on current request scope).""" diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 820b7e86c..ae5108e76 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -53,7 +53,6 @@ from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config.argparsing import Parser from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH -from _pytest.deprecated import FUNCARGNAMES from _pytest.fixtures import FuncFixtureInfo from _pytest.main import Session from _pytest.mark import MARK_GEN @@ -906,12 +905,6 @@ class Metafunc: self._calls = [] # type: List[CallSpec2] self._arg2fixturedefs = fixtureinfo.name2fixturedefs - @property - def funcargnames(self) -> List[str]: - """Alias attribute for ``fixturenames`` for pre-2.3 compatibility.""" - warnings.warn(FUNCARGNAMES, stacklevel=2) - return self.fixturenames - def parametrize( self, argnames: Union[str, List[str], Tuple[str, ...]], @@ -1568,12 +1561,6 @@ class Function(PyobjMixin, nodes.Item): """(compatonly) for code expecting pytest-2.2 style request objects.""" return self - @property - def funcargnames(self) -> List[str]: - """Alias attribute for ``fixturenames`` for pre-2.3 compatibility.""" - warnings.warn(FUNCARGNAMES, stacklevel=2) - return self.fixturenames - def runtest(self) -> None: """Execute the underlying test function.""" self.ihook.pytest_pyfunc_call(pyfuncitem=self) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 8ea1a75ff..f00741772 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -815,28 +815,6 @@ class TestRequestBasic: result = testdir.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - def test_funcargnames_compatattr(self, testdir): - testdir.makepyfile( - """ - import pytest - def pytest_generate_tests(metafunc): - with pytest.warns(pytest.PytestDeprecationWarning): - assert metafunc.funcargnames == metafunc.fixturenames - @pytest.fixture - def fn(request): - with pytest.warns(pytest.PytestDeprecationWarning): - assert request._pyfuncitem.funcargnames == \ - request._pyfuncitem.fixturenames - with pytest.warns(pytest.PytestDeprecationWarning): - return request.funcargnames, request.fixturenames - - def test_hello(fn): - assert fn[0] == fn[1] - """ - ) - reprec = testdir.inline_run() - reprec.assertoutcome(passed=1) - def test_setupdecorator_and_xunit(self, testdir): testdir.makepyfile( """ From c747dc524878bb476e44d8ee17abd507da0a2c01 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 17:26:06 -0300 Subject: [PATCH 02/11] Drop support for positional arguments in @pytest.fixture --- changelog/5585.breaking.rst | 2 ++ doc/en/deprecations.rst | 7 ++++ src/_pytest/deprecated.py | 5 --- src/_pytest/fixtures.py | 51 +--------------------------- testing/python/fixtures.py | 67 ------------------------------------- 5 files changed, 10 insertions(+), 122 deletions(-) diff --git a/changelog/5585.breaking.rst b/changelog/5585.breaking.rst index 12b9a52dd..655c4a42e 100644 --- a/changelog/5585.breaking.rst +++ b/changelog/5585.breaking.rst @@ -2,3 +2,5 @@ As per our policy, the following features have been deprecated in the 5.X series removed: * The ``funcargnames`` read-only property of ``FixtureRequest``, ``Metafunc``, and ``Function`` classes. Use ``fixturenames`` attribute. + +* ``@pytest.fixture`` no longer supports positional arguments, pass all arguments by keyword instead. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index d5c7540ed..886bfd7fc 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -162,6 +162,13 @@ Removed Features As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after an appropriate period of deprecation has passed. +``pytest.fixture`` arguments are keyword only +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 6.0 + +Passing arguments to pytest.fixture() as positional arguments has been removed - pass them by keyword instead. + ``funcargnames`` alias for ``fixturenames`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index d76144d60..2f3c80251 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -30,11 +30,6 @@ RESULT_LOG = PytestDeprecationWarning( "See https://docs.pytest.org/en/stable/deprecations.html#result-log-result-log for more information." ) -FIXTURE_POSITIONAL_ARGUMENTS = PytestDeprecationWarning( - "Passing arguments to pytest.fixture() as positional arguments is deprecated - pass them " - "as a keyword argument instead." -) - NODE_USE_FROM_PARENT = UnformattedWarning( PytestDeprecationWarning, "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n" diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index c0fdc587b..03d8f5394 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -2,7 +2,6 @@ import functools import inspect import os import sys -import warnings from collections import defaultdict from collections import deque from types import TracebackType @@ -46,7 +45,6 @@ from _pytest.compat import TYPE_CHECKING from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config.argparsing import Parser -from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS from _pytest.mark import ParameterSet from _pytest.outcomes import fail from _pytest.outcomes import TEST_OUTCOME @@ -1246,7 +1244,7 @@ def fixture( # noqa: F811 def fixture( # noqa: F811 fixture_function: Optional[_FixtureFunction] = None, - *args: Any, + *, scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = "function", params: Optional[Iterable[object]] = None, autouse: bool = False, @@ -1308,53 +1306,6 @@ def fixture( # noqa: F811 name the decorated function ``fixture_`` and then use ``@pytest.fixture(name='')``. """ - # Positional arguments backward compatibility. - # If a kwarg is equal to its default, assume it was not explicitly - # passed, i.e. not duplicated. The more correct way is to use a - # **kwargs and check `in`, but that obfuscates the function signature. - if isinstance(fixture_function, str): - # It's actually the first positional argument, scope. - args = (fixture_function, *args) # type: ignore[unreachable] - fixture_function = None - duplicated_args = [] - if len(args) > 0: - if scope == "function": - scope = args[0] - else: - duplicated_args.append("scope") - if len(args) > 1: - if params is None: - params = args[1] - else: - duplicated_args.append("params") - if len(args) > 2: - if autouse is False: - autouse = args[2] - else: - duplicated_args.append("autouse") - if len(args) > 3: - if ids is None: - ids = args[3] - else: - duplicated_args.append("ids") - if len(args) > 4: - if name is None: - name = args[4] - else: - duplicated_args.append("name") - if len(args) > 5: - raise TypeError( - "fixture() takes 5 positional arguments but {} were given".format(len(args)) - ) - if duplicated_args: - raise TypeError( - "The fixture arguments are defined as positional and keyword: {}. " - "Use only keyword arguments.".format(", ".join(duplicated_args)) - ) - if args: - warnings.warn(FIXTURE_POSITIONAL_ARGUMENTS, stacklevel=2) - # End backward compatiblity. - fixture_marker = FixtureFunctionMarker( scope=scope, params=params, autouse=autouse, ids=ids, name=name, ) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index f00741772..d54583858 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -4130,73 +4130,6 @@ def test_fixture_named_request(testdir): ) -def test_fixture_duplicated_arguments() -> None: - """Raise error if there are positional and keyword arguments for the same parameter (#1682).""" - with pytest.raises(TypeError) as excinfo: - - @pytest.fixture("session", scope="session") # type: ignore[call-overload] - def arg(arg): - pass - - assert ( - str(excinfo.value) - == "The fixture arguments are defined as positional and keyword: scope. " - "Use only keyword arguments." - ) - - with pytest.raises(TypeError) as excinfo: - - @pytest.fixture( # type: ignore[call-overload] - "function", - ["p1"], - True, - ["id1"], - "name", - scope="session", - params=["p1"], - autouse=True, - ids=["id1"], - name="name", - ) - def arg2(request): - pass - - assert ( - str(excinfo.value) - == "The fixture arguments are defined as positional and keyword: scope, params, autouse, ids, name. " - "Use only keyword arguments." - ) - - -def test_fixture_with_positionals() -> None: - """Raise warning, but the positionals should still works (#1682).""" - from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS - - with pytest.warns(pytest.PytestDeprecationWarning) as warnings: - - @pytest.fixture("function", [0], True) # type: ignore[call-overload] - def fixture_with_positionals(): - pass - - assert str(warnings[0].message) == str(FIXTURE_POSITIONAL_ARGUMENTS) - - assert fixture_with_positionals._pytestfixturefunction.scope == "function" - assert fixture_with_positionals._pytestfixturefunction.params == (0,) - assert fixture_with_positionals._pytestfixturefunction.autouse - - -def test_fixture_with_too_many_positionals() -> None: - with pytest.raises(TypeError) as excinfo: - - @pytest.fixture("function", [0], True, ["id"], "name", "extra") # type: ignore[call-overload] - def fixture_with_positionals(): - pass - - assert ( - str(excinfo.value) == "fixture() takes 5 positional arguments but 6 were given" - ) - - def test_indirect_fixture_does_not_break_scope(testdir): """Ensure that fixture scope is respected when using indirect fixtures (#570)""" testdir.makepyfile( From 73e06373dc67055bf83d9a1f6cf3242bc90a68c3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 17:34:28 -0300 Subject: [PATCH 03/11] Hard failure when constructing Node subclasses --- changelog/5585.breaking.rst | 2 ++ doc/en/deprecations.rst | 57 ++++++++++++++++++------------------- src/_pytest/deprecated.py | 7 ----- src/_pytest/nodes.py | 10 +++++-- testing/deprecated_test.py | 16 ----------- 5 files changed, 36 insertions(+), 56 deletions(-) diff --git a/changelog/5585.breaking.rst b/changelog/5585.breaking.rst index 655c4a42e..bbf133a38 100644 --- a/changelog/5585.breaking.rst +++ b/changelog/5585.breaking.rst @@ -4,3 +4,5 @@ removed: * The ``funcargnames`` read-only property of ``FixtureRequest``, ``Metafunc``, and ``Function`` classes. Use ``fixturenames`` attribute. * ``@pytest.fixture`` no longer supports positional arguments, pass all arguments by keyword instead. + +* Direct construction of ``Node`` subclasses now raise an error, use ``from_parent`` instead. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 886bfd7fc..72a0e5b93 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -43,7 +43,6 @@ it, use `function._request._fillfixtures()` instead, though note this is not a public API and may break in the future. - ``--no-print-logs`` command-line option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -58,35 +57,6 @@ A ``--show-capture`` command-line option was added in ``pytest 3.5.0`` which all display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default). - -Node Construction changed to ``Node.from_parent`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 5.4 - -The construction of nodes now should use the named constructor ``from_parent``. -This limitation in api surface intends to enable better/simpler refactoring of the collection tree. - -This means that instead of :code:`MyItem(name="foo", parent=collector, obj=42)` -one now has to invoke :code:`MyItem.from_parent(collector, name="foo")`. - -Plugins that wish to support older versions of pytest and suppress the warning can use -`hasattr` to check if `from_parent` exists in that version: - -.. code-block:: python - - def pytest_pycollect_makeitem(collector, name, obj): - if hasattr(MyItem, "from_parent"): - item = MyItem.from_parent(collector, name="foo") - item.obj = 42 - return item - else: - return MyItem(name="foo", parent=collector, obj=42) - -Note that ``from_parent`` should only be called with keyword arguments for the parameters. - - - ``junit_family`` default value change to "xunit2" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -162,6 +132,33 @@ Removed Features As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after an appropriate period of deprecation has passed. +Node Construction changed to ``Node.from_parent`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0 + +The construction of nodes now should use the named constructor ``from_parent``. +This limitation in api surface intends to enable better/simpler refactoring of the collection tree. + +This means that instead of :code:`MyItem(name="foo", parent=collector, obj=42)` +one now has to invoke :code:`MyItem.from_parent(collector, name="foo")`. + +Plugins that wish to support older versions of pytest and suppress the warning can use +`hasattr` to check if `from_parent` exists in that version: + +.. code-block:: python + + def pytest_pycollect_makeitem(collector, name, obj): + if hasattr(MyItem, "from_parent"): + item = MyItem.from_parent(collector, name="foo") + item.obj = 42 + return item + else: + return MyItem(name="foo", parent=collector, obj=42) + +Note that ``from_parent`` should only be called with keyword arguments for the parameters. + + ``pytest.fixture`` arguments are keyword only ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 2f3c80251..401cce80d 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -30,13 +30,6 @@ RESULT_LOG = PytestDeprecationWarning( "See https://docs.pytest.org/en/stable/deprecations.html#result-log-result-log for more information." ) -NODE_USE_FROM_PARENT = UnformattedWarning( - PytestDeprecationWarning, - "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n" - "See " - "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent" - " for more details.", -) JUNIT_XML_DEFAULT_FAMILY = PytestDeprecationWarning( "The 'junit_family' default value will change to 'xunit2' in pytest 6.0. See:\n" diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 79e091443..8e3e86a50 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -26,7 +26,6 @@ from _pytest.compat import TYPE_CHECKING from _pytest.config import Config from _pytest.config import ConftestImportFailure from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH -from _pytest.deprecated import NODE_USE_FROM_PARENT from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureLookupError from _pytest.mark.structures import Mark @@ -97,8 +96,13 @@ _NodeType = TypeVar("_NodeType", bound="Node") class NodeMeta(type): def __call__(self, *k, **kw): - warnings.warn(NODE_USE_FROM_PARENT.format(name=self.__name__), stacklevel=2) - return super().__call__(*k, **kw) + msg = ( + "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n" + "See " + "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent" + " for more details." + ).format(name=self.__name__) + fail(msg, pytrace=False) def _create(self, *k, **kw): return super().__call__(*k, **kw) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 9507d9902..aab7b7b36 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,11 +1,9 @@ import copy -import inspect import warnings from unittest import mock import pytest from _pytest import deprecated -from _pytest import nodes from _pytest.config import Config from _pytest.pytester import Testdir @@ -106,20 +104,6 @@ def test_warn_about_imminent_junit_family_default_change(testdir, junit_family): result.stdout.fnmatch_lines([warning_msg]) -def test_node_direct_ctor_warning() -> None: - class MockConfig: - pass - - ms = MockConfig() - with pytest.warns( - DeprecationWarning, - match="Direct construction of .* has been deprecated, please use .*.from_parent.*", - ) as w: - nodes.Node(name="test", config=ms, session=ms, nodeid="None") # type: ignore - assert w[0].lineno == inspect.currentframe().f_lineno - 1 # type: ignore - assert w[0].filename == __file__ - - @pytest.mark.skip(reason="should be reintroduced in 6.1: #7361") def test_fillfuncargs_is_deprecated() -> None: with pytest.warns( From 6ecbd008c417795a8a11563428d1706c4b4230f2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 17:39:13 -0300 Subject: [PATCH 04/11] Change junit_family default to xunit2 --- changelog/5585.breaking.rst | 2 + doc/en/deprecations.rst | 80 ++++++++++++++++++------------------- src/_pytest/deprecated.py | 7 ---- src/_pytest/junitxml.py | 9 ++--- testing/deprecated_test.py | 29 -------------- 5 files changed, 45 insertions(+), 82 deletions(-) diff --git a/changelog/5585.breaking.rst b/changelog/5585.breaking.rst index bbf133a38..c429324d2 100644 --- a/changelog/5585.breaking.rst +++ b/changelog/5585.breaking.rst @@ -6,3 +6,5 @@ removed: * ``@pytest.fixture`` no longer supports positional arguments, pass all arguments by keyword instead. * Direct construction of ``Node`` subclasses now raise an error, use ``from_parent`` instead. + +* The default value for ``junit_family`` has changed to ``xunit2``. If you require the old format, add ``junit_family=xunit1`` to your configuration file. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 72a0e5b93..df0704d61 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -57,45 +57,6 @@ A ``--show-capture`` command-line option was added in ``pytest 3.5.0`` which all display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default). -``junit_family`` default value change to "xunit2" -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 5.2 - -The default value of ``junit_family`` option will change to ``xunit2`` in pytest 6.0, which -is an update of the old ``xunit1`` format and is supported by default in modern tools -that manipulate this type of file (for example, Jenkins, Azure Pipelines, etc.). - -Users are recommended to try the new ``xunit2`` format and see if their tooling that consumes the JUnit -XML file supports it. - -To use the new format, update your ``pytest.ini``: - -.. code-block:: ini - - [pytest] - junit_family=xunit2 - -If you discover that your tooling does not support the new format, and want to keep using the -legacy version, set the option to ``legacy`` instead: - -.. code-block:: ini - - [pytest] - junit_family=legacy - -By using ``legacy`` you will keep using the legacy/xunit1 format when upgrading to -pytest 6.0, where the default format will be ``xunit2``. - -In order to let users know about the transition, pytest will issue a warning in case -the ``--junitxml`` option is given in the command line but ``junit_family`` is not explicitly -configured in ``pytest.ini``. - -Services known to support the ``xunit2`` format: - -* `Jenkins `__ with the `JUnit `__ plugin. -* `Azure Pipelines `__. - Result log (``--result-log``) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -132,10 +93,49 @@ Removed Features As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after an appropriate period of deprecation has passed. +``junit_family`` default value change to "xunit2" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionchanged:: 6.0 + +The default value of ``junit_family`` option will change to ``xunit2`` in pytest 6.0, which +is an update of the old ``xunit1`` format and is supported by default in modern tools +that manipulate this type of file (for example, Jenkins, Azure Pipelines, etc.). + +Users are recommended to try the new ``xunit2`` format and see if their tooling that consumes the JUnit +XML file supports it. + +To use the new format, update your ``pytest.ini``: + +.. code-block:: ini + + [pytest] + junit_family=xunit2 + +If you discover that your tooling does not support the new format, and want to keep using the +legacy version, set the option to ``legacy`` instead: + +.. code-block:: ini + + [pytest] + junit_family=legacy + +By using ``legacy`` you will keep using the legacy/xunit1 format when upgrading to +pytest 6.0, where the default format will be ``xunit2``. + +In order to let users know about the transition, pytest will issue a warning in case +the ``--junitxml`` option is given in the command line but ``junit_family`` is not explicitly +configured in ``pytest.ini``. + +Services known to support the ``xunit2`` format: + +* `Jenkins `__ with the `JUnit `__ plugin. +* `Azure Pipelines `__. + Node Construction changed to ``Node.from_parent`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. deprecated:: 6.0 +.. versionchanged:: 6.0 The construction of nodes now should use the named constructor ``from_parent``. This limitation in api surface intends to enable better/simpler refactoring of the collection tree. diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 401cce80d..285d72265 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -30,13 +30,6 @@ RESULT_LOG = PytestDeprecationWarning( "See https://docs.pytest.org/en/stable/deprecations.html#result-log-result-log for more information." ) - -JUNIT_XML_DEFAULT_FAMILY = PytestDeprecationWarning( - "The 'junit_family' default value will change to 'xunit2' in pytest 6.0. See:\n" - " https://docs.pytest.org/en/stable/deprecations.html#junit-family-default-value-change-to-xunit2\n" - "for more information." -) - COLLECT_DIRECTORY_HOOK = PytestDeprecationWarning( "The pytest_collect_directory hook is not working.\n" "Please use collect_ignore in conftests or pytest_collection_modifyitems." diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 1e563eb8d..0acfb49bc 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -21,7 +21,6 @@ from typing import Tuple from typing import Union import pytest -from _pytest import deprecated from _pytest import nodes from _pytest import timing from _pytest._code.code import ExceptionRepr @@ -33,7 +32,6 @@ from _pytest.fixtures import FixtureRequest from _pytest.reports import TestReport from _pytest.store import StoreKey from _pytest.terminal import TerminalReporter -from _pytest.warnings import _issue_warning_captured xml_key = StoreKey["LogXML"]() @@ -413,7 +411,9 @@ def pytest_addoption(parser: Parser) -> None: default="total", ) # choices=['total', 'call']) parser.addini( - "junit_family", "Emit XML for schema: one of legacy|xunit1|xunit2", default=None + "junit_family", + "Emit XML for schema: one of legacy|xunit1|xunit2", + default="xunit2", ) @@ -422,9 +422,6 @@ def pytest_configure(config: Config) -> None: # Prevent opening xmllog on worker nodes (xdist). if xmlpath and not hasattr(config, "workerinput"): junit_family = config.getini("junit_family") - if not junit_family: - _issue_warning_captured(deprecated.JUNIT_XML_DEFAULT_FAMILY, config.hook, 2) - junit_family = "xunit1" config._store[xml_key] = LogXML( xmlpath, config.option.junitprefix, diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index aab7b7b36..fb591d7d9 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -75,35 +75,6 @@ def test_external_plugins_integrated(testdir, plugin): testdir.parseconfig("-p", plugin) -@pytest.mark.parametrize("junit_family", [None, "legacy", "xunit2"]) -def test_warn_about_imminent_junit_family_default_change(testdir, junit_family): - """Show a warning if junit_family is not defined and --junitxml is used (#6179)""" - testdir.makepyfile( - """ - def test_foo(): - pass - """ - ) - if junit_family: - testdir.makeini( - """ - [pytest] - junit_family={junit_family} - """.format( - junit_family=junit_family - ) - ) - - result = testdir.runpytest("--junit-xml=foo.xml") - warning_msg = ( - "*PytestDeprecationWarning: The 'junit_family' default value will change*" - ) - if junit_family: - result.stdout.no_fnmatch_line(warning_msg) - else: - result.stdout.fnmatch_lines([warning_msg]) - - @pytest.mark.skip(reason="should be reintroduced in 6.1: #7361") def test_fillfuncargs_is_deprecated() -> None: with pytest.warns( From 345a59dd53cc07488d2b3939da3240d978e1394e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 17:41:56 -0300 Subject: [PATCH 05/11] Add note about pytest.collect deprecation --- doc/en/deprecations.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index df0704d61..d5416b938 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -30,11 +30,19 @@ This hook has an `item` parameter which cannot be serialized by ``pytest-xdist`` Use the ``pytest_warning_recored`` hook instead, which replaces the ``item`` parameter by a ``nodeid`` parameter. +The ``pytest.collect`` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0 + +The ``pytest.collect`` module is no longer part of the public API, all its names +should now be imported from ``pytest`` directly instead. + The ``pytest._fillfuncargs`` function ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. deprecated:: 5.5 +.. deprecated:: 6.0 This function was kept for backward compatibility with an older plugin. From 457d351941279cd4d93d9fe9a622aa001b9e322e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 17:46:47 -0300 Subject: [PATCH 06/11] Remove deprecated TerminalReporter.writer property --- changelog/5585.breaking.rst | 2 ++ doc/en/deprecations.rst | 16 ++++++++-------- src/_pytest/deprecated.py | 6 ------ src/_pytest/terminal.py | 12 ------------ testing/deprecated_test.py | 31 ------------------------------- 5 files changed, 10 insertions(+), 57 deletions(-) diff --git a/changelog/5585.breaking.rst b/changelog/5585.breaking.rst index c429324d2..3f71d3ece 100644 --- a/changelog/5585.breaking.rst +++ b/changelog/5585.breaking.rst @@ -8,3 +8,5 @@ removed: * Direct construction of ``Node`` subclasses now raise an error, use ``from_parent`` instead. * The default value for ``junit_family`` has changed to ``xunit2``. If you require the old format, add ``junit_family=xunit1`` to your configuration file. + +* The ``TerminalReporter`` no longer has a ``writer`` attribute. Plugin authors may use the public functions of the ``TerminalReporter`` instead of accessing the ``TerminalWriter`` object directly. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index d5416b938..fb84647c5 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -82,10 +82,17 @@ The plan is remove the ``--result-log`` option in pytest 6.0 if ``pytest-reportl to all users and is deemed stable. The ``pytest-reportlog`` plugin might even be merged into the core at some point, depending on the plans for the plugins and number of users using it. + +Removed Features +---------------- + +As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after +an appropriate period of deprecation has passed. + TerminalReporter.writer ~~~~~~~~~~~~~~~~~~~~~~~ -.. deprecated:: 5.4 +.. versionremoved:: 6.0 The ``TerminalReporter.writer`` attribute has been deprecated and should no longer be used. This was inadvertently exposed as part of the public API of that plugin and ties it too much @@ -94,13 +101,6 @@ with ``py.io.TerminalWriter``. Plugins that used ``TerminalReporter.writer`` directly should instead use ``TerminalReporter`` methods that provide the same functionality. - -Removed Features ----------------- - -As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after -an appropriate period of deprecation has passed. - ``junit_family`` default value change to "xunit2" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 285d72265..839348282 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -42,12 +42,6 @@ PYTEST_COLLECT_MODULE = UnformattedWarning( ) -TERMINALWRITER_WRITER = PytestDeprecationWarning( - "The TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.\n" - "See https://docs.pytest.org/en/stable/deprecations.html#terminalreporter-writer for more information." -) - - MINUS_K_DASH = PytestDeprecationWarning( "The `-k '-expr'` syntax to -k is deprecated.\nUse `-k 'not expr'` instead." ) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 1d49df4cf..af6843000 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -31,7 +31,6 @@ from _pytest import nodes from _pytest import timing from _pytest._code import ExceptionInfo from _pytest._code.code import ExceptionRepr -from _pytest._io import TerminalWriter from _pytest._io.wcwidth import wcswidth from _pytest.compat import order_preserving_dict from _pytest.compat import TYPE_CHECKING @@ -39,7 +38,6 @@ from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config.argparsing import Parser -from _pytest.deprecated import TERMINALWRITER_WRITER from _pytest.nodes import Item from _pytest.nodes import Node from _pytest.reports import BaseReport @@ -335,16 +333,6 @@ class TerminalReporter: self._already_displayed_warnings = None # type: Optional[int] self._keyboardinterrupt_memo = None # type: Optional[ExceptionRepr] - @property - def writer(self) -> TerminalWriter: - warnings.warn(TERMINALWRITER_WRITER, stacklevel=2) - return self._tw - - @writer.setter - def writer(self, value: TerminalWriter) -> None: - warnings.warn(TERMINALWRITER_WRITER, stacklevel=2) - self._tw = value - def _determine_show_progress_info(self) -> "Literal['progress', 'count', False]": """Return whether we should display progress information based on the current config.""" # do not show progress if we are not capturing output (#3038) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index fb591d7d9..5660b312a 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,10 +1,8 @@ -import copy import warnings from unittest import mock import pytest from _pytest import deprecated -from _pytest.config import Config from _pytest.pytester import Testdir @@ -36,35 +34,6 @@ def test_pytest_collect_module_deprecated(attribute): getattr(pytest.collect, attribute) -def test_terminal_reporter_writer_attr(pytestconfig: Config) -> None: - """Check that TerminalReporter._tw is also available as 'writer' (#2984) - This attribute has been deprecated in 5.4. - """ - try: - import xdist # noqa - - pytest.skip("xdist workers disable the terminal reporter plugin") - except ImportError: - pass - terminal_reporter = pytestconfig.pluginmanager.get_plugin("terminalreporter") - original_tw = terminal_reporter._tw - - with pytest.warns(pytest.PytestDeprecationWarning) as cw: - assert terminal_reporter.writer is original_tw - assert len(cw) == 1 - assert cw[0].filename == __file__ - - new_tw = copy.copy(original_tw) - with pytest.warns(pytest.PytestDeprecationWarning) as cw: - terminal_reporter.writer = new_tw - try: - assert terminal_reporter._tw is new_tw - finally: - terminal_reporter.writer = original_tw - assert len(cw) == 2 - assert cw[0].filename == cw[1].filename == __file__ - - @pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS)) @pytest.mark.filterwarnings("default") def test_external_plugins_integrated(testdir, plugin): From 52b0cc4f193debc8e16fd386b1644c80d9d329ec Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 17:55:37 -0300 Subject: [PATCH 07/11] Remove broken pytest_collect_directory hook --- doc/en/deprecations.rst | 9 +++++++++ doc/en/reference.rst | 1 - src/_pytest/deprecated.py | 4 ---- src/_pytest/hookspec.py | 11 ----------- src/_pytest/nodes.py | 2 -- testing/acceptance_test.py | 3 +-- testing/test_collection.py | 14 -------------- 7 files changed, 10 insertions(+), 34 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index fb84647c5..12844265d 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -89,6 +89,15 @@ Removed Features As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after an appropriate period of deprecation has passed. + +``pytest_collect_directory`` hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 6.0 + +The ``pytest_collect_directory`` has not worked properly for years (it was called +but the results were ignored). Users may consider using :func:`pytest_collection_modifyitems <_pytest.hookspec.pytest_collection_modifyitems>` instead. + TerminalReporter.writer ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/reference.rst b/doc/en/reference.rst index 3bc7161aa..eb2370ae4 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -656,7 +656,6 @@ Collection hooks .. autofunction:: pytest_collection .. autofunction:: pytest_ignore_collect -.. autofunction:: pytest_collect_directory .. autofunction:: pytest_collect_file .. autofunction:: pytest_pycollect_makemodule diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 839348282..500fbe2f8 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -30,10 +30,6 @@ RESULT_LOG = PytestDeprecationWarning( "See https://docs.pytest.org/en/stable/deprecations.html#result-log-result-log for more information." ) -COLLECT_DIRECTORY_HOOK = PytestDeprecationWarning( - "The pytest_collect_directory hook is not working.\n" - "Please use collect_ignore in conftests or pytest_collection_modifyitems." -) PYTEST_COLLECT_MODULE = UnformattedWarning( PytestDeprecationWarning, diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index ce435901c..e60bfe9f9 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -12,7 +12,6 @@ from typing import Union import py.path from pluggy import HookspecMarker -from .deprecated import COLLECT_DIRECTORY_HOOK from _pytest.compat import TYPE_CHECKING if TYPE_CHECKING: @@ -262,16 +261,6 @@ def pytest_ignore_collect(path: py.path.local, config: "Config") -> Optional[boo """ -@hookspec(firstresult=True, warn_on_impl=COLLECT_DIRECTORY_HOOK) -def pytest_collect_directory(path: py.path.local, parent) -> Optional[object]: - """Called before traversing a directory for collection files. - - Stops at first non-None result, see :ref:`firstresult`. - - :param py.path.local path: The path to analyze. - """ - - def pytest_collect_file(path: py.path.local, parent) -> "Optional[Collector]": """Return collection Node or None for the given path. diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 8e3e86a50..addeaf1c2 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -553,8 +553,6 @@ class FSCollector(Collector): for pat in self._norecursepatterns: if path.check(fnmatch=pat): return False - ihook = self.session.gethookproxy(path) - ihook.pytest_collect_directory(path=path, parent=self) return True def _collectfile( diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index d58b4fd03..039d8dad9 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -223,13 +223,12 @@ class TestGeneralUsage: "E {}: No module named 'qwerty'".format(exc_name), ] - @pytest.mark.filterwarnings("ignore::pytest.PytestDeprecationWarning") def test_early_skip(self, testdir): testdir.mkdir("xyz") testdir.makeconftest( """ import pytest - def pytest_collect_directory(): + def pytest_collect_file(): pytest.skip("early") """ ) diff --git a/testing/test_collection.py b/testing/test_collection.py index 01dbcc731..12030e56e 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -257,20 +257,6 @@ class TestCollectPluginHookRelay: assert len(wascalled) == 1 assert wascalled[0].ext == ".abc" - @pytest.mark.filterwarnings("ignore:.*pytest_collect_directory.*") - def test_pytest_collect_directory(self, testdir): - wascalled = [] - - class Plugin: - def pytest_collect_directory(self, path): - wascalled.append(path.basename) - - testdir.mkdir("hello") - testdir.mkdir("world") - pytest.main(testdir.tmpdir, plugins=[Plugin()]) - assert "hello" in wascalled - assert "world" in wascalled - class TestPrunetraceback: def test_custom_repr_failure(self, testdir): From b32c48ee0519f7469077ed9878bbc1d550660d78 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 17:56:16 -0300 Subject: [PATCH 08/11] Add bottom changelog deprecation notice --- changelog/5585.breaking.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog/5585.breaking.rst b/changelog/5585.breaking.rst index 3f71d3ece..34e264d0b 100644 --- a/changelog/5585.breaking.rst +++ b/changelog/5585.breaking.rst @@ -10,3 +10,7 @@ removed: * The default value for ``junit_family`` has changed to ``xunit2``. If you require the old format, add ``junit_family=xunit1`` to your configuration file. * The ``TerminalReporter`` no longer has a ``writer`` attribute. Plugin authors may use the public functions of the ``TerminalReporter`` instead of accessing the ``TerminalWriter`` object directly. + + +For more information consult +`Deprecations and Removals `__ in the docs. From ef946d557cc7b7f030805b94c1dbae51f39fcce4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 18:10:27 -0300 Subject: [PATCH 09/11] Remove resultlog plugin --- changelog/5585.breaking.rst | 2 + doc/en/deprecations.rst | 18 ++- src/_pytest/config/__init__.py | 1 - src/_pytest/deprecated.py | 6 - src/_pytest/resultlog.py | 108 -------------- testing/deprecated_test.py | 20 --- testing/test_conftest.py | 15 -- testing/test_resultlog.py | 256 --------------------------------- testing/test_warnings.py | 25 ---- 9 files changed, 10 insertions(+), 441 deletions(-) delete mode 100644 src/_pytest/resultlog.py delete mode 100644 testing/test_resultlog.py diff --git a/changelog/5585.breaking.rst b/changelog/5585.breaking.rst index 34e264d0b..0ecba32df 100644 --- a/changelog/5585.breaking.rst +++ b/changelog/5585.breaking.rst @@ -11,6 +11,8 @@ removed: * The ``TerminalReporter`` no longer has a ``writer`` attribute. Plugin authors may use the public functions of the ``TerminalReporter`` instead of accessing the ``TerminalWriter`` object directly. +* The ``--result-log`` option has been removed. Users are recommended to use the `pytest-reportlog `__ plugin instead. + For more information consult `Deprecations and Removals `__ in the docs. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 12844265d..bec321e69 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -66,10 +66,17 @@ display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` +Removed Features +---------------- + +As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after +an appropriate period of deprecation has passed. + Result log (``--result-log``) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 4.0 +.. versionremoved:: 6.0 The ``--result-log`` option produces a stream of test reports which can be analysed at runtime, but it uses a custom format which requires users to implement their own @@ -78,18 +85,9 @@ parser. The `pytest-reportlog `__ plugin provides a ``--report-log`` option, a more standard and extensible alternative, producing one JSON object per-line, and should cover the same use cases. Please try it out and provide feedback. -The plan is remove the ``--result-log`` option in pytest 6.0 if ``pytest-reportlog`` proves satisfactory -to all users and is deemed stable. The ``pytest-reportlog`` plugin might even be merged into the core +The ``pytest-reportlog`` plugin might even be merged into the core at some point, depending on the plans for the plugins and number of users using it. - -Removed Features ----------------- - -As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after -an appropriate period of deprecation has passed. - - ``pytest_collect_directory`` hook ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 453dd8345..1c6ad3288 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -239,7 +239,6 @@ default_plugins = essential_plugins + ( "nose", "assertion", "junitxml", - "resultlog", "doctest", "cacheprovider", "freeze_support", diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 500fbe2f8..ecdb60d37 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -25,12 +25,6 @@ FILLFUNCARGS = PytestDeprecationWarning( "function._request._fillfixtures() instead if you cannot avoid reaching into internals." ) -RESULT_LOG = PytestDeprecationWarning( - "--result-log is deprecated, please try the new pytest-reportlog plugin.\n" - "See https://docs.pytest.org/en/stable/deprecations.html#result-log-result-log for more information." -) - - PYTEST_COLLECT_MODULE = UnformattedWarning( PytestDeprecationWarning, "pytest.collect.{name} was moved to pytest.{name}\n" diff --git a/src/_pytest/resultlog.py b/src/_pytest/resultlog.py deleted file mode 100644 index 686f7f3b0..000000000 --- a/src/_pytest/resultlog.py +++ /dev/null @@ -1,108 +0,0 @@ -"""log machine-parseable test session result information to a plain text file.""" -import os -from typing import IO -from typing import Union - -from _pytest._code.code import ExceptionRepr -from _pytest.config import Config -from _pytest.config.argparsing import Parser -from _pytest.reports import CollectReport -from _pytest.reports import TestReport -from _pytest.store import StoreKey - - -resultlog_key = StoreKey["ResultLog"]() - - -def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("terminal reporting", "resultlog plugin options") - group.addoption( - "--resultlog", - "--result-log", - action="store", - metavar="path", - default=None, - help="DEPRECATED path for machine-readable result log.", - ) - - -def pytest_configure(config: Config) -> None: - resultlog = config.option.resultlog - # Prevent opening resultlog on worker nodes (xdist). - if resultlog and not hasattr(config, "workerinput"): - dirname = os.path.dirname(os.path.abspath(resultlog)) - if not os.path.isdir(dirname): - os.makedirs(dirname) - logfile = open(resultlog, "w", 1) # line buffered - config._store[resultlog_key] = ResultLog(config, logfile) - config.pluginmanager.register(config._store[resultlog_key]) - - from _pytest.deprecated import RESULT_LOG - from _pytest.warnings import _issue_warning_captured - - _issue_warning_captured(RESULT_LOG, config.hook, stacklevel=2) - - -def pytest_unconfigure(config: Config) -> None: - resultlog = config._store.get(resultlog_key, None) - if resultlog: - resultlog.logfile.close() - del config._store[resultlog_key] - config.pluginmanager.unregister(resultlog) - - -class ResultLog: - def __init__(self, config: Config, logfile: IO[str]) -> None: - self.config = config - self.logfile = logfile # preferably line buffered - - def write_log_entry(self, testpath: str, lettercode: str, longrepr: str) -> None: - print("{} {}".format(lettercode, testpath), file=self.logfile) - for line in longrepr.splitlines(): - print(" %s" % line, file=self.logfile) - - def log_outcome( - self, report: Union[TestReport, CollectReport], lettercode: str, longrepr: str - ) -> None: - testpath = getattr(report, "nodeid", None) - if testpath is None: - testpath = report.fspath - self.write_log_entry(testpath, lettercode, longrepr) - - def pytest_runtest_logreport(self, report: TestReport) -> None: - if report.when != "call" and report.passed: - return - res = self.config.hook.pytest_report_teststatus( - report=report, config=self.config - ) - code = res[1] # type: str - if code == "x": - longrepr = str(report.longrepr) - elif code == "X": - longrepr = "" - elif report.passed: - longrepr = "" - elif report.skipped: - assert isinstance(report.longrepr, tuple) - longrepr = str(report.longrepr[2]) - else: - longrepr = str(report.longrepr) - self.log_outcome(report, code, longrepr) - - def pytest_collectreport(self, report: CollectReport) -> None: - if not report.passed: - if report.failed: - code = "F" - longrepr = str(report.longrepr) - else: - assert report.skipped - code = "S" - longrepr = "%s:%d: %s" % report.longrepr # type: ignore - self.log_outcome(report, code, longrepr) - - def pytest_internalerror(self, excrepr: ExceptionRepr) -> None: - if excrepr.reprcrash is not None: - path = excrepr.reprcrash.path - else: - path = "cwd:%s" % os.getcwd() - self.write_log_entry(path, "!", str(excrepr)) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 5660b312a..d3db792ab 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -6,26 +6,6 @@ from _pytest import deprecated from _pytest.pytester import Testdir -@pytest.mark.filterwarnings("default") -def test_resultlog_is_deprecated(testdir): - result = testdir.runpytest("--help") - result.stdout.fnmatch_lines(["*DEPRECATED path for machine-readable result log*"]) - - testdir.makepyfile( - """ - def test(): - pass - """ - ) - result = testdir.runpytest("--result-log=%s" % testdir.tmpdir.join("result.log")) - result.stdout.fnmatch_lines( - [ - "*--result-log is deprecated, please try the new pytest-reportlog plugin.", - "*See https://docs.pytest.org/en/stable/deprecations.html#result-log-result-log for more information*", - ] - ) - - @pytest.mark.skip(reason="should be reintroduced in 6.1: #7361") @pytest.mark.parametrize("attribute", pytest.collect.__all__) # type: ignore # false positive due to dynamic attribute diff --git a/testing/test_conftest.py b/testing/test_conftest.py index d1a69f4ba..5a4764080 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -306,21 +306,6 @@ def test_no_conftest(testdir): assert result.ret == ExitCode.USAGE_ERROR -def test_conftest_existing_resultlog(testdir): - x = testdir.mkdir("tests") - x.join("conftest.py").write( - textwrap.dedent( - """\ - def pytest_addoption(parser): - parser.addoption("--xyz", action="store_true") - """ - ) - ) - testdir.makefile(ext=".log", result="") # Writes result.log - result = testdir.runpytest("-h", "--resultlog", "result.log") - result.stdout.fnmatch_lines(["*--xyz*"]) - - def test_conftest_existing_junitxml(testdir): x = testdir.mkdir("tests") x.join("conftest.py").write( diff --git a/testing/test_resultlog.py b/testing/test_resultlog.py deleted file mode 100644 index f2eb612c1..000000000 --- a/testing/test_resultlog.py +++ /dev/null @@ -1,256 +0,0 @@ -import os -from io import StringIO -from typing import List - -import _pytest._code -import pytest -from _pytest.pytester import Testdir -from _pytest.resultlog import pytest_configure -from _pytest.resultlog import pytest_unconfigure -from _pytest.resultlog import ResultLog -from _pytest.resultlog import resultlog_key - - -pytestmark = pytest.mark.filterwarnings("ignore:--result-log is deprecated") - - -def test_write_log_entry() -> None: - reslog = ResultLog(None, None) # type: ignore[arg-type] - reslog.logfile = StringIO() - reslog.write_log_entry("name", ".", "") - entry = reslog.logfile.getvalue() - assert entry[-1] == "\n" - entry_lines = entry.splitlines() - assert len(entry_lines) == 1 - assert entry_lines[0] == ". name" - - reslog.logfile = StringIO() - reslog.write_log_entry("name", "s", "Skipped") - entry = reslog.logfile.getvalue() - assert entry[-1] == "\n" - entry_lines = entry.splitlines() - assert len(entry_lines) == 2 - assert entry_lines[0] == "s name" - assert entry_lines[1] == " Skipped" - - reslog.logfile = StringIO() - reslog.write_log_entry("name", "s", "Skipped\n") - entry = reslog.logfile.getvalue() - assert entry[-1] == "\n" - entry_lines = entry.splitlines() - assert len(entry_lines) == 2 - assert entry_lines[0] == "s name" - assert entry_lines[1] == " Skipped" - - reslog.logfile = StringIO() - longrepr = " tb1\n tb 2\nE tb3\nSome Error" - reslog.write_log_entry("name", "F", longrepr) - entry = reslog.logfile.getvalue() - assert entry[-1] == "\n" - entry_lines = entry.splitlines() - assert len(entry_lines) == 5 - assert entry_lines[0] == "F name" - assert entry_lines[1:] == [" " + line for line in longrepr.splitlines()] - - -class TestWithFunctionIntegration: - # XXX (hpk) i think that the resultlog plugin should - # provide a Parser object so that one can remain - # ignorant regarding formatting details. - def getresultlog(self, testdir: Testdir, arg: str) -> List[str]: - resultlog = testdir.tmpdir.join("resultlog") - testdir.plugins.append("resultlog") - args = ["--resultlog=%s" % resultlog] + [arg] - testdir.runpytest(*args) - return [x for x in resultlog.readlines(cr=0) if x] - - def test_collection_report(self, testdir: Testdir) -> None: - ok = testdir.makepyfile(test_collection_ok="") - fail = testdir.makepyfile(test_collection_fail="XXX") - lines = self.getresultlog(testdir, ok) - assert not lines - - lines = self.getresultlog(testdir, fail) - assert lines - assert lines[0].startswith("F ") - assert lines[0].endswith("test_collection_fail.py"), lines[0] - for x in lines[1:]: - assert x.startswith(" ") - assert "XXX" in "".join(lines[1:]) - - def test_log_test_outcomes(self, testdir: Testdir) -> None: - mod = testdir.makepyfile( - test_mod=""" - import pytest - def test_pass(): pass - def test_skip(): pytest.skip("hello") - def test_fail(): raise ValueError("FAIL") - - @pytest.mark.xfail - def test_xfail(): raise ValueError("XFAIL") - @pytest.mark.xfail - def test_xpass(): pass - - """ - ) - lines = self.getresultlog(testdir, mod) - assert len(lines) >= 3 - assert lines[0].startswith(". ") - assert lines[0].endswith("test_pass") - assert lines[1].startswith("s "), lines[1] - assert lines[1].endswith("test_skip") - assert lines[2].find("hello") != -1 - - assert lines[3].startswith("F ") - assert lines[3].endswith("test_fail") - tb = "".join(lines[4:8]) - assert tb.find('raise ValueError("FAIL")') != -1 - - assert lines[8].startswith("x ") - tb = "".join(lines[8:14]) - assert tb.find('raise ValueError("XFAIL")') != -1 - - assert lines[14].startswith("X ") - assert len(lines) == 15 - - @pytest.mark.parametrize("style", ("native", "long", "short")) - def test_internal_exception(self, style) -> None: - # they are produced for example by a teardown failing - # at the end of the run or a failing hook invocation - try: - raise ValueError - except ValueError: - excinfo = _pytest._code.ExceptionInfo.from_current() - file = StringIO() - reslog = ResultLog(None, file) # type: ignore[arg-type] - reslog.pytest_internalerror(excinfo.getrepr(style=style)) - entry = file.getvalue() - entry_lines = entry.splitlines() - - assert entry_lines[0].startswith("! ") - if style != "native": - assert os.path.basename(__file__)[:-9] in entry_lines[0] # .pyc/class - assert entry_lines[-1][0] == " " - assert "ValueError" in entry - - -def test_generic(testdir: Testdir, LineMatcher) -> None: - testdir.plugins.append("resultlog") - testdir.makepyfile( - """ - import pytest - def test_pass(): - pass - def test_fail(): - assert 0 - def test_skip(): - pytest.skip("") - @pytest.mark.xfail - def test_xfail(): - assert 0 - @pytest.mark.xfail(run=False) - def test_xfail_norun(): - assert 0 - """ - ) - testdir.runpytest("--resultlog=result.log") - lines = testdir.tmpdir.join("result.log").readlines(cr=0) - LineMatcher(lines).fnmatch_lines( - [ - ". *:test_pass", - "F *:test_fail", - "s *:test_skip", - "x *:test_xfail", - "x *:test_xfail_norun", - ] - ) - - -def test_makedir_for_resultlog(testdir: Testdir, LineMatcher) -> None: - """--resultlog should automatically create directories for the log file""" - testdir.plugins.append("resultlog") - testdir.makepyfile( - """ - import pytest - def test_pass(): - pass - """ - ) - testdir.runpytest("--resultlog=path/to/result.log") - lines = testdir.tmpdir.join("path/to/result.log").readlines(cr=0) - LineMatcher(lines).fnmatch_lines([". *:test_pass"]) - - -def test_no_resultlog_on_workers(testdir: Testdir) -> None: - config = testdir.parseconfig("-p", "resultlog", "--resultlog=resultlog") - - assert resultlog_key not in config._store - pytest_configure(config) - assert resultlog_key in config._store - pytest_unconfigure(config) - assert resultlog_key not in config._store - - config.workerinput = {} # type: ignore[attr-defined] - pytest_configure(config) - assert resultlog_key not in config._store - pytest_unconfigure(config) - assert resultlog_key not in config._store - - -def test_unknown_teststatus(testdir: Testdir) -> None: - """Ensure resultlog correctly handles unknown status from pytest_report_teststatus - - Inspired on pytest-rerunfailures. - """ - testdir.makepyfile( - """ - def test(): - assert 0 - """ - ) - testdir.makeconftest( - """ - import pytest - - def pytest_report_teststatus(report): - if report.outcome == 'rerun': - return "rerun", "r", "RERUN" - - @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_makereport(): - res = yield - report = res.get_result() - if report.when == "call": - report.outcome = 'rerun' - """ - ) - result = testdir.runpytest("--resultlog=result.log") - result.stdout.fnmatch_lines( - ["test_unknown_teststatus.py r *[[]100%[]]", "* 1 rerun *"] - ) - - lines = testdir.tmpdir.join("result.log").readlines(cr=0) - assert lines[0] == "r test_unknown_teststatus.py::test" - - -def test_failure_issue380(testdir: Testdir) -> None: - testdir.makeconftest( - """ - import pytest - class MyCollector(pytest.File): - def collect(self): - raise ValueError() - def repr_failure(self, excinfo): - return "somestring" - def pytest_collect_file(path, parent): - return MyCollector(parent=parent, fspath=path) - """ - ) - testdir.makepyfile( - """ - def test_func(): - pass - """ - ) - result = testdir.runpytest("--resultlog=log") - assert result.ret == 2 diff --git a/testing/test_warnings.py b/testing/test_warnings.py index d26c71ca3..685e1365d 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -737,31 +737,6 @@ class TestStackLevel: assert "config{sep}__init__.py".format(sep=os.sep) in file assert func == "import_plugin" - def test_issue4445_resultlog(self, testdir, capwarn): - """#4445: Make sure the warning points to a reasonable location - See origin of _issue_warning_captured at: _pytest.resultlog.py:35 - """ - testdir.makepyfile( - """ - def test_dummy(): - pass - """ - ) - # Use parseconfigure() because the warning in resultlog.py is triggered in - # the pytest_configure hook - testdir.parseconfigure( - "--result-log={dir}".format(dir=testdir.tmpdir.join("result.log")) - ) - - # with stacklevel=2 the warning originates from resultlog.pytest_configure - # and is thrown when --result-log is used - warning, location = capwarn.captured.pop() - file, _, func = location - - assert "--result-log is deprecated" in str(warning.message) - assert "resultlog.py" in file - assert func == "pytest_configure" - def test_issue4445_issue5928_mark_generator(self, testdir): """#4445 and #5928: Make sure the warning from an unknown mark points to the test file where this mark is used. From 7605150eaab0b6befa1875b9adf6d4e6b168edd2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 18:15:01 -0300 Subject: [PATCH 10/11] Move --no-print-logs removal notice to 'Removed Features' --- doc/en/deprecations.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index bec321e69..9f15553c7 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -51,6 +51,12 @@ it, use `function._request._fillfixtures()` instead, though note this is not a public API and may break in the future. +Removed Features +---------------- + +As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after +an appropriate period of deprecation has passed. + ``--no-print-logs`` command-line option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -66,12 +72,6 @@ display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` -Removed Features ----------------- - -As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after -an appropriate period of deprecation has passed. - Result log (``--result-log``) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 372a0940053c9fd14280c7feb924fc90f49020d3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 17 Aug 2020 18:21:21 -0300 Subject: [PATCH 11/11] PytestDeprecationWarning no longer a hard error --- src/_pytest/warnings.py | 2 -- testing/test_warnings.py | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 0604aa60b..4478d8723 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -104,8 +104,6 @@ def catch_warnings_for_item( warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning) - warnings.filterwarnings("error", category=pytest.PytestDeprecationWarning) - # Filters should have this precedence: mark, cmdline options, ini. # Filters should be applied in the inverse order of precedence. for arg in inifilters: diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 685e1365d..550ebb4b8 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -513,6 +513,9 @@ class TestDeprecationWarningsByDefault: @pytest.mark.parametrize("change_default", [None, "ini", "cmdline"]) +@pytest.mark.skip( + reason="This test should be enabled again before pytest 7.0 is released" +) def test_deprecation_warning_as_error(testdir, change_default): """This ensures that PytestDeprecationWarnings raised by pytest are turned into errors.