From a7d7676f7b0db4e587b416f20d53804c1e1bb0eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 May 2022 23:50:10 +0000 Subject: [PATCH 01/41] [7.1.x] Clarify precision when using NUMBER option in --doctest-modules (#9923) Co-authored-by: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> --- doc/en/how-to/doctest.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/en/how-to/doctest.rst b/doc/en/how-to/doctest.rst index ce0b5a5f649..09de800c703 100644 --- a/doc/en/how-to/doctest.rst +++ b/doc/en/how-to/doctest.rst @@ -126,14 +126,17 @@ pytest also introduces new options: in expected doctest output. * ``NUMBER``: when enabled, floating-point numbers only need to match as far as - the precision you have written in the expected doctest output. For example, - the following output would only need to match to 2 decimal places:: + the precision you have written in the expected doctest output. The numbers are + compared using :func:`pytest.approx` with relative tolerance equal to the + precision. For example, the following output would only need to match to 2 + decimal places when comparing ``3.14`` to + ``pytest.approx(math.pi, rel=10**-2)``:: >>> math.pi 3.14 - If you wrote ``3.1416`` then the actual output would need to match to 4 - decimal places; and so on. + If you wrote ``3.1416`` then the actual output would need to match to + approximately 4 decimal places; and so on. This avoids false positives caused by limited floating-point precision, like this:: From 2f8ae29c173ea8335a1e0cc7027a76032429e8f9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 13:38:22 +0000 Subject: [PATCH 02/41] [7.1.x] testing: fix Path.rglob("") failures in Python 3.11b1 (#9934) Co-authored-by: Ran Benita --- testing/test_conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 68048204581..d2bf860c6fe 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -553,7 +553,7 @@ def test_no_conftest(fxtr): ) ) print("created directory structure:") - for x in pytester.path.rglob(""): + for x in pytester.path.glob("**/"): print(" " + str(x.relative_to(pytester.path))) return {"runner": runner, "package": package, "swc": swc, "snc": snc} From 03793466756913dd96d6c2f550b1acd30476024b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 10 May 2022 11:37:33 -0300 Subject: [PATCH 03/41] [7.1.x] Consistently add **Tutorial**: in front of how-to links in reference --- doc/en/reference/reference.rst | 54 +++++++++++++++++----------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 167c8fed9a3..38ee93e044f 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -92,7 +92,7 @@ pytest.param pytest.raises ~~~~~~~~~~~~~ -**Tutorial**: :ref:`assertraises`. +**Tutorial**: :ref:`assertraises` .. autofunction:: pytest.raises(expected_exception: Exception [, *, match]) :with: excinfo @@ -100,7 +100,7 @@ pytest.raises pytest.deprecated_call ~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`ensuring_function_triggers`. +**Tutorial**: :ref:`ensuring_function_triggers` .. autofunction:: pytest.deprecated_call() :with: @@ -108,7 +108,7 @@ pytest.deprecated_call pytest.register_assert_rewrite ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`assertion-rewriting`. +**Tutorial**: :ref:`assertion-rewriting` .. autofunction:: pytest.register_assert_rewrite @@ -123,7 +123,7 @@ pytest.warns pytest.freeze_includes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`freezing-pytest`. +**Tutorial**: :ref:`freezing-pytest` .. autofunction:: pytest.freeze_includes @@ -143,7 +143,7 @@ fixtures or plugins. pytest.mark.filterwarnings ~~~~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`filterwarnings`. +**Tutorial**: :ref:`filterwarnings` Add warning filters to marked test items. @@ -169,7 +169,7 @@ Add warning filters to marked test items. pytest.mark.parametrize ~~~~~~~~~~~~~~~~~~~~~~~ -:ref:`parametrize`. +**Tutorial**: :ref:`parametrize` This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see there. @@ -179,7 +179,7 @@ This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see pytest.mark.skip ~~~~~~~~~~~~~~~~ -:ref:`skip`. +**Tutorial**: :ref:`skip` Unconditionally skip a test function. @@ -193,7 +193,7 @@ Unconditionally skip a test function. pytest.mark.skipif ~~~~~~~~~~~~~~~~~~ -:ref:`skipif`. +**Tutorial**: :ref:`skipif` Skip a test function if a condition is ``True``. @@ -209,7 +209,7 @@ Skip a test function if a condition is ``True``. pytest.mark.usefixtures ~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`usefixtures`. +**Tutorial**: :ref:`usefixtures` Mark a test function as using the given fixture names. @@ -231,7 +231,7 @@ Mark a test function as using the given fixture names. pytest.mark.xfail ~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`xfail`. +**Tutorial**: :ref:`xfail` Marks a test function as *expected to fail*. @@ -297,7 +297,7 @@ When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node. Fixtures -------- -**Tutorial**: :ref:`fixture`. +**Tutorial**: :ref:`fixture` Fixtures are requested by test functions or other fixtures by declaring them as argument names. @@ -338,7 +338,7 @@ For more details, consult the full :ref:`fixtures docs `. config.cache ~~~~~~~~~~~~ -**Tutorial**: :ref:`cache`. +**Tutorial**: :ref:`cache` The ``config.cache`` object allows other plugins and fixtures to store and retrieve values across test runs. To access it from fixtures @@ -358,7 +358,7 @@ Under the hood, the cache plugin uses the simple capsys ~~~~~~ -:ref:`captures`. +**Tutorial**: :ref:`captures` .. autofunction:: _pytest.capture.capsys() :no-auto-options: @@ -383,7 +383,7 @@ capsys capsysbinary ~~~~~~~~~~~~ -:ref:`captures`. +**Tutorial**: :ref:`captures` .. autofunction:: _pytest.capture.capsysbinary() :no-auto-options: @@ -405,7 +405,7 @@ capsysbinary capfd ~~~~~~ -:ref:`captures`. +**Tutorial**: :ref:`captures` .. autofunction:: _pytest.capture.capfd() :no-auto-options: @@ -427,7 +427,7 @@ capfd capfdbinary ~~~~~~~~~~~~ -:ref:`captures`. +**Tutorial**: :ref:`captures` .. autofunction:: _pytest.capture.capfdbinary() :no-auto-options: @@ -449,7 +449,7 @@ capfdbinary doctest_namespace ~~~~~~~~~~~~~~~~~ -:ref:`doctest`. +**Tutorial**: :ref:`doctest` .. autofunction:: _pytest.doctest.doctest_namespace() @@ -469,7 +469,7 @@ doctest_namespace request ~~~~~~~ -:ref:`request example`. +**Example**: :ref:`request example` The ``request`` fixture is a special fixture providing information of the requesting test function. @@ -490,7 +490,7 @@ pytestconfig record_property ~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`record_property example`. +**Tutorial**: :ref:`record_property example` .. autofunction:: _pytest.junitxml.record_property() @@ -500,7 +500,7 @@ record_property record_testsuite_property ~~~~~~~~~~~~~~~~~~~~~~~~~ -**Tutorial**: :ref:`record_testsuite_property example`. +**Tutorial**: :ref:`record_testsuite_property example` .. autofunction:: _pytest.junitxml.record_testsuite_property() @@ -510,7 +510,7 @@ record_testsuite_property caplog ~~~~~~ -:ref:`logging`. +**Tutorial**: :ref:`logging` .. autofunction:: _pytest.logging.caplog() :no-auto-options: @@ -526,7 +526,7 @@ caplog monkeypatch ~~~~~~~~~~~ -:ref:`monkeypatching`. +**Tutorial**: :ref:`monkeypatching` .. autofunction:: _pytest.monkeypatch.monkeypatch() :no-auto-options: @@ -612,7 +612,7 @@ Each recorded warning is an instance of :class:`warnings.WarningMessage`. tmp_path ~~~~~~~~ -:ref:`tmp_path` +**Tutorial**: :ref:`tmp_path` .. autofunction:: _pytest.tmpdir.tmp_path() :no-auto-options: @@ -623,7 +623,7 @@ tmp_path tmp_path_factory ~~~~~~~~~~~~~~~~ -:ref:`tmp_path_factory example` +**Tutorial**: :ref:`tmp_path_factory example` .. _`tmp_path_factory factory api`: @@ -638,7 +638,7 @@ tmp_path_factory tmpdir ~~~~~~ -:ref:`tmpdir and tmpdir_factory` +**Tutorial**: :ref:`tmpdir and tmpdir_factory` .. autofunction:: _pytest.legacypath.LegacyTmpdirPlugin.tmpdir() :no-auto-options: @@ -649,7 +649,7 @@ tmpdir tmpdir_factory ~~~~~~~~~~~~~~ -:ref:`tmpdir and tmpdir_factory` +**Tutorial**: :ref:`tmpdir and tmpdir_factory` ``tmpdir_factory`` is an instance of :class:`~pytest.TempdirFactory`: @@ -662,7 +662,7 @@ tmpdir_factory Hooks ----- -:ref:`writing-plugins`. +**Tutorial**: :ref:`writing-plugins` .. currentmodule:: _pytest.hookspec From 91d84e925606feb5ebe5ebad2d56a44d1f1fd0be Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 10 May 2022 17:21:32 -0300 Subject: [PATCH 04/41] [7.1.x] Move documentation contents from reference.rst to docstrings --- doc/en/reference/reference.rst | 62 ---------------------------------- src/_pytest/capture.py | 53 ++++++++++++++++++++++++++--- src/_pytest/doctest.py | 13 ++++++- src/_pytest/recwarn.py | 7 ++++ 4 files changed, 68 insertions(+), 67 deletions(-) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 38ee93e044f..d082697258c 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -363,17 +363,6 @@ capsys .. autofunction:: _pytest.capture.capsys() :no-auto-options: - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_output(capsys): - print("hello") - captured = capsys.readouterr() - assert captured.out == "hello\n" - .. autoclass:: pytest.CaptureFixture() :members: @@ -388,18 +377,6 @@ capsysbinary .. autofunction:: _pytest.capture.capsysbinary() :no-auto-options: - Returns an instance of :class:`CaptureFixture[bytes] `. - - Example: - - .. code-block:: python - - def test_output(capsysbinary): - print("hello") - captured = capsysbinary.readouterr() - assert captured.out == b"hello\n" - - .. fixture:: capfd capfd @@ -410,18 +387,6 @@ capfd .. autofunction:: _pytest.capture.capfd() :no-auto-options: - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_system_echo(capfd): - os.system('echo "hello"') - captured = capfd.readouterr() - assert captured.out == "hello\n" - - .. fixture:: capfdbinary capfdbinary @@ -432,17 +397,6 @@ capfdbinary .. autofunction:: _pytest.capture.capfdbinary() :no-auto-options: - Returns an instance of :class:`CaptureFixture[bytes] `. - - Example: - - .. code-block:: python - - def test_system_echo(capfdbinary): - os.system('echo "hello"') - captured = capfdbinary.readouterr() - assert captured.out == b"hello\n" - .. fixture:: doctest_namespace @@ -453,16 +407,6 @@ doctest_namespace .. autofunction:: _pytest.doctest.doctest_namespace() - Usually this fixture is used in conjunction with another ``autouse`` fixture: - - .. code-block:: python - - @pytest.fixture(autouse=True) - def add_np(doctest_namespace): - doctest_namespace["np"] = numpy - - For more details: :ref:`doctest_namespace`. - .. fixture:: request @@ -600,12 +544,6 @@ recwarn .. autoclass:: pytest.WarningsRecorder() :members: -Each recorded warning is an instance of :class:`warnings.WarningMessage`. - -.. note:: - ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated - differently; see :ref:`ensuring_function_triggers`. - .. fixture:: tmp_path diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index ee9de373325..2a3c4143be4 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -876,11 +876,22 @@ def disabled(self) -> Generator[None, None, None]: @fixture def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: - """Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. + r"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsys.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_output(capsys): + print("hello") + captured = capsys.readouterr() + assert captured.out == "hello\n" """ capman = request.config.pluginmanager.getplugin("capturemanager") capture_fixture = CaptureFixture[str](SysCapture, request, _ispytest=True) @@ -893,11 +904,22 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: @fixture def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: - """Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. + r"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``bytes`` objects. + + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + + .. code-block:: python + + def test_output(capsysbinary): + print("hello") + captured = capsysbinary.readouterr() + assert captured.out == b"hello\n" """ capman = request.config.pluginmanager.getplugin("capturemanager") capture_fixture = CaptureFixture[bytes](SysCaptureBinary, request, _ispytest=True) @@ -910,11 +932,22 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, @fixture def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: - """Enable text capturing of writes to file descriptors ``1`` and ``2``. + r"""Enable text capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_system_echo(capfd): + os.system('echo "hello"') + captured = capfd.readouterr() + assert captured.out == "hello\n" """ capman = request.config.pluginmanager.getplugin("capturemanager") capture_fixture = CaptureFixture[str](FDCapture, request, _ispytest=True) @@ -927,11 +960,23 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: @fixture def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: - """Enable bytes capturing of writes to file descriptors ``1`` and ``2``. + r"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``byte`` objects. + + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + + .. code-block:: python + + def test_system_echo(capfdbinary): + os.system('echo "hello"') + captured = capfdbinary.readouterr() + assert captured.out == b"hello\n" + """ capman = request.config.pluginmanager.getplugin("capturemanager") capture_fixture = CaptureFixture[bytes](FDCaptureBinary, request, _ispytest=True) diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 7d37be2acc0..aa071cde883 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -730,5 +730,16 @@ def _get_report_choice(key: str) -> int: @pytest.fixture(scope="session") def doctest_namespace() -> Dict[str, Any]: """Fixture that returns a :py:class:`dict` that will be injected into the - namespace of doctests.""" + namespace of doctests. + + Usually this fixture is used in conjunction with another ``autouse`` fixture: + + .. code-block:: python + + @pytest.fixture(autouse=True) + def add_np(doctest_namespace): + doctest_namespace["np"] = numpy + + For more details: :ref:`doctest_namespace`. + """ return dict() diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 175b571a80c..410569c1693 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -158,7 +158,14 @@ def warns( class WarningsRecorder(warnings.catch_warnings): """A context manager to record raised warnings. + Each recorded warning is an instance of :class:`warnings.WarningMessage`. + Adapted from `warnings.catch_warnings`. + + .. note:: + ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated + differently; see :ref:`ensuring_function_triggers`. + """ def __init__(self, *, _ispytest: bool = False) -> None: From dc36836dd2cda93a70ca41e59c56558fbbb4ef97 Mon Sep 17 00:00:00 2001 From: Pax <13646646+paxcodes@users.noreply.github.com> Date: Wed, 11 May 2022 01:02:36 -0700 Subject: [PATCH 05/41] [7.1.x] Add link to python docs on logging levels --- doc/en/how-to/logging.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/en/how-to/logging.rst b/doc/en/how-to/logging.rst index 5a089c2fba0..c9fe62b492c 100644 --- a/doc/en/how-to/logging.rst +++ b/doc/en/how-to/logging.rst @@ -180,8 +180,8 @@ logging records as they are emitted directly into the console. You can specify the logging level for which log records with equal or higher level are printed to the console by passing ``--log-cli-level``. This setting -accepts the logging level names as seen in python's documentation or an integer -as the logging level num. +accepts the logging level names or numeric values as seen in +:ref:`logging's documentation `. Additionally, you can also specify ``--log-cli-format`` and ``--log-cli-date-format`` which mirror and default to ``--log-format`` and @@ -202,9 +202,8 @@ Note that relative paths for the log-file location, whether passed on the CLI or config file, are always resolved relative to the current working directory. You can also specify the logging level for the log file by passing -``--log-file-level``. This setting accepts the logging level names as seen in -python's documentation(ie, uppercased level names) or an integer as the logging -level num. +``--log-file-level``. This setting accepts the logging level names or numeric +values as seen in :ref:`logging's documentation `. Additionally, you can also specify ``--log-file-format`` and ``--log-file-date-format`` which are equal to ``--log-format`` and From cb5fd75beabb5350967ca696cc0b28b529ef275e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 12 May 2022 09:35:39 -0300 Subject: [PATCH 06/41] [7.1.x] doc: link to pytest-subtests --- doc/en/how-to/unittest.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/en/how-to/unittest.rst b/doc/en/how-to/unittest.rst index bff75110778..0d9e9f90542 100644 --- a/doc/en/how-to/unittest.rst +++ b/doc/en/how-to/unittest.rst @@ -27,12 +27,15 @@ Almost all ``unittest`` features are supported: * ``setUpClass/tearDownClass``; * ``setUpModule/tearDownModule``; +.. _`pytest-subtests`: https://github.com/pytest-dev/pytest-subtests .. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol +Additionally, :ref:`subtests ` are supported by the +`pytest-subtests`_ plugin. + Up to this point pytest does not have support for the following features: * `load_tests protocol`_; -* :ref:`subtests `; Benefits out of the box ----------------------- From f7e57ae8396ea30e04745adde51bdf5aaf0ff744 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Mon, 16 May 2022 10:17:18 +0200 Subject: [PATCH 07/41] [7.1.x] Remove docs on Python 2.7 and 3.4 Support --- doc/en/backwards-compatibility.rst | 15 +++++ doc/en/changelog.rst | 12 +++- doc/en/contents.rst | 1 - doc/en/py27-py34-deprecation.rst | 99 ------------------------------ 4 files changed, 24 insertions(+), 103 deletions(-) delete mode 100644 doc/en/py27-py34-deprecation.rst diff --git a/doc/en/backwards-compatibility.rst b/doc/en/backwards-compatibility.rst index 3a0ff126164..64bcbf5bd49 100644 --- a/doc/en/backwards-compatibility.rst +++ b/doc/en/backwards-compatibility.rst @@ -77,3 +77,18 @@ Deprecation Roadmap Features currently deprecated and removed in previous releases can be found in :ref:`deprecations`. We track future deprecation and removal of features using milestones and the `deprecation `_ and `removal `_ labels on GitHub. + + +Python version support +====================== + +Released pytest versions support all Python versions that are actively maintained at the time of the release: + +============== =================== +pytest version min. Python version +============== =================== +7.1+ 3.7+ +6.2 - 7.0 3.6+ +5.0 - 6.1 3.5+ +3.3 - 4.6 2.7, 3.4+ +============== =================== diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 46e18d32255..01c6d6983a2 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -2618,7 +2618,8 @@ Important This release is a Python3.5+ only release. -For more details, see our :std:doc:`Python 2.7 and 3.4 support plan `. +For more details, see our `Python 2.7 and 3.4 support plan +`_. Removals -------- @@ -2842,7 +2843,11 @@ Features - :issue:`6870`: New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``. - Remark: while this is technically a new feature and according to our :ref:`policy ` it should not have been backported, we have opened an exception in this particular case because it fixes a serious interaction with ``pytest-xdist``, so it can also be considered a bugfix. + Remark: while this is technically a new feature and according to our + `policy `_ + it should not have been backported, we have opened an exception in this + particular case because it fixes a serious interaction with ``pytest-xdist``, + so it can also be considered a bugfix. Trivial/Internal Changes ------------------------ @@ -3014,7 +3019,8 @@ Important The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**. -For more details, see our :std:doc:`Python 2.7 and 3.4 support plan `. +For more details, see our `Python 2.7 and 3.4 support plan +`_. Features diff --git a/doc/en/contents.rst b/doc/en/contents.rst index 049d44ba9d8..ae42884f658 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -85,7 +85,6 @@ Further topics backwards-compatibility deprecations - py27-py34-deprecation contributing development_guide diff --git a/doc/en/py27-py34-deprecation.rst b/doc/en/py27-py34-deprecation.rst deleted file mode 100644 index 660b078e30e..00000000000 --- a/doc/en/py27-py34-deprecation.rst +++ /dev/null @@ -1,99 +0,0 @@ -Python 2.7 and 3.4 support -========================== - -It is demanding on the maintainers of an open source project to support many Python versions, as -there's extra cost of keeping code compatible between all versions, while holding back on -features only made possible on newer Python versions. - -In case of Python 2 and 3, the difference between the languages makes it even more prominent, -because many new Python 3 features cannot be used in a Python 2/3 compatible code base. - -Python 2.7 EOL has been reached :pep:`in 2020 <0373#maintenance-releases>`, with -the last release made in April, 2020. - -Python 3.4 EOL has been reached :pep:`in 2019 <0429#release-schedule>`, with the last release made in March, 2019. - -For those reasons, in Jun 2019 it was decided that **pytest 4.6** series will be the last to support Python 2.7 and 3.4. - -What this means for general users ---------------------------------- - -Thanks to the `python_requires`_ setuptools option, -Python 2.7 and Python 3.4 users using a modern pip version -will install the last pytest 4.6.X version automatically even if 5.0 or later versions -are available on PyPI. - -Users should ensure they are using the latest pip and setuptools versions for this to work. - -Maintenance of 4.6.X versions ------------------------------ - -Until January 2020, the pytest core team ported many bug-fixes from the main release into the -``4.6.x`` branch, with several 4.6.X releases being made along the year. - -From now on, the core team will **no longer actively backport patches**, but the ``4.6.x`` -branch will continue to exist so the community itself can contribute patches. - -The core team will be happy to accept those patches, and make new 4.6.X releases **until mid-2020** -(but consider that date as a ballpark, after that date the team might still decide to make new releases -for critical bugs). - -.. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires - -Technical aspects -~~~~~~~~~~~~~~~~~ - -(This section is a transcript from :issue:`5275`). - -In this section we describe the technical aspects of the Python 2.7 and 3.4 support plan. - -.. _what goes into 4.6.x releases: - -What goes into 4.6.X releases -+++++++++++++++++++++++++++++ - -New 4.6.X releases will contain bug fixes only. - -When will 4.6.X releases happen -+++++++++++++++++++++++++++++++ - -New 4.6.X releases will happen after we have a few bugs in place to release, or if a few weeks have -passed (say a single bug has been fixed a month after the latest 4.6.X release). - -No hard rules here, just ballpark. - -Who will handle applying bug fixes -++++++++++++++++++++++++++++++++++ - -We core maintainers expect that people still using Python 2.7/3.4 and being affected by -bugs to step up and provide patches and/or port bug fixes from the active branches. - -We will be happy to guide users interested in doing so, so please don't hesitate to ask. - -**Backporting changes into 4.6** - -Please follow these instructions: - -#. ``git fetch --all --prune`` - -#. ``git checkout origin/4.6.x -b backport-XXXX`` # use the PR number here - -#. Locate the merge commit on the PR, in the *merged* message, for example: - - nicoddemus merged commit 0f8b462 into pytest-dev:features - -#. ``git cherry-pick -m1 REVISION`` # use the revision you found above (``0f8b462``). - -#. Open a PR targeting ``4.6.x``: - - * Prefix the message with ``[4.6]`` so it is an obvious backport - * Delete the PR body, it usually contains a duplicate commit message. - -**Providing new PRs to 4.6** - -Fresh pull requests to ``4.6.x`` will be accepted provided that -the equivalent code in the active branches does not contain that bug (for example, a bug is specific -to Python 2 only). - -Bug fixes that also happen in the mainstream version should be first fixed -there, and then backported as per instructions above. From 643cc79759840e81bd11cba082f106eca5944975 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 17 May 2022 07:53:53 -0300 Subject: [PATCH 08/41] [7.1.x] Remove "Python 2.7 and 3.4 Support" from globaltoc --- doc/en/_templates/globaltoc.html | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/en/_templates/globaltoc.html b/doc/en/_templates/globaltoc.html index 7c595e7ebf2..9bd144903b1 100644 --- a/doc/en/_templates/globaltoc.html +++ b/doc/en/_templates/globaltoc.html @@ -17,7 +17,6 @@

About the project

  • Changelog
  • Contributing
  • Backwards Compatibility
  • -
  • Python 2.7 and 3.4 Support
  • Sponsor
  • pytest for Enterprise
  • License
  • From 5f18e9d1ab835bea2093e1337976f0edaf1679b7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 17 May 2022 07:55:06 -0300 Subject: [PATCH 09/41] [7.1.x] Reorder the reference guides in the docs --- doc/en/reference/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/reference/index.rst b/doc/en/reference/index.rst index d9648400317..ee1b2e6214d 100644 --- a/doc/en/reference/index.rst +++ b/doc/en/reference/index.rst @@ -8,8 +8,8 @@ Reference guides .. toctree:: :maxdepth: 1 + reference fixtures - plugin_list customize - reference exit-codes + plugin_list From 796574f3b8fcf444e0bd7033d2c386c8ba6745e3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 17 May 2022 08:13:11 -0300 Subject: [PATCH 10/41] [7.1.x] Fix rst markup in TempdirFactory's docstring. --- .pre-commit-config.yaml | 2 +- src/_pytest/legacypath.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33b292c2b2b..8b382fc85eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -101,7 +101,7 @@ repos: types: [python] - id: py-path-deprecated name: py.path usage is deprecated - exclude: docs|src/_pytest/deprecated.py|testing/deprecated_test.py + exclude: docs|src/_pytest/deprecated.py|testing/deprecated_test.py|src/_pytest/legacypath.py language: pygrep entry: \bpy\.path\.local types: [python] diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index 37e8c24220e..49149311a7d 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -270,8 +270,8 @@ def testdir(pytester: Pytester) -> Testdir: @final @attr.s(init=False, auto_attribs=True) class TempdirFactory: - """Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH`` - for :class:``TempPathFactory``.""" + """Backward compatibility wrapper that implements :class:`py.path.local` + for :class:`TempPathFactory`.""" _tmppath_factory: TempPathFactory @@ -282,11 +282,11 @@ def __init__( self._tmppath_factory = tmppath_factory def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object.""" + """Same as :meth:`TempPathFactory.mktemp`, but returns a :class:`py.path.local` object.""" return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) def getbasetemp(self) -> LEGACY_PATH: - """Backward compat wrapper for ``_tmppath_factory.getbasetemp``.""" + """Same as :meth:`TempPathFactory.getbasetemp`, but returns a :class:`py.path.local` object.""" return legacy_path(self._tmppath_factory.getbasetemp().resolve()) From 835de758195399c2a81303adad2fb8b81b192524 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 24 May 2022 10:42:44 -0300 Subject: [PATCH 11/41] [7.1.x] Declutter doc entry page (#9991) Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- doc/en/_templates/globaltoc.html | 2 -- doc/en/conf.py | 2 +- doc/en/index.rst | 17 ----------------- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/doc/en/_templates/globaltoc.html b/doc/en/_templates/globaltoc.html index 9bd144903b1..09d970b64ed 100644 --- a/doc/en/_templates/globaltoc.html +++ b/doc/en/_templates/globaltoc.html @@ -29,5 +29,3 @@

    About the project

    {%- endif %}
    -Index -
    diff --git a/doc/en/conf.py b/doc/en/conf.py index 1adc3493ad5..5aedd2d322d 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -247,7 +247,7 @@ html_domain_indices = True # If false, no index is generated. -html_use_index = True +html_use_index = False # If true, the index is split into individual pages for each letter. # html_split_index = False diff --git a/doc/en/index.rst b/doc/en/index.rst index 03a39aaaae8..8b4528ddf2d 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -27,8 +27,6 @@ scale to support complex functional testing for applications and libraries. **PyPI package name**: :pypi:`pytest` -**Documentation as PDF**: `download latest `_ - A quick example --------------- @@ -104,11 +102,6 @@ Bugs/Requests Please use the `GitHub issue tracker `_ to submit bugs or request features. -Changelog ---------- - -Consult the :ref:`Changelog ` page for fixes and enhancements of each version. - Support pytest -------------- @@ -141,13 +134,3 @@ Security pytest has never been associated with a security vulnerability, but in any case, to report a security vulnerability please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. - - -License -------- - -Copyright Holger Krekel and others, 2004. - -Distributed under the terms of the `MIT`_ license, pytest is free and open source software. - -.. _`MIT`: https://github.com/pytest-dev/pytest/blob/main/LICENSE From f70e370e3525ef59e223fdfa1e92aca9edeb3d79 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 31 May 2022 16:24:55 -0300 Subject: [PATCH 12/41] [7.1.x] Do not advertise that importlib will be default import mode --- doc/en/explanation/goodpractices.rst | 5 ++--- doc/en/explanation/pythonpath.rst | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/doc/en/explanation/goodpractices.rst b/doc/en/explanation/goodpractices.rst index 32a14991ae2..9ea7d226f5c 100644 --- a/doc/en/explanation/goodpractices.rst +++ b/doc/en/explanation/goodpractices.rst @@ -173,10 +173,9 @@ This layout prevents a lot of common pitfalls and has many benefits, which are b `blog post by Ionel Cristian Mărieș `_. .. note:: - The new ``--import-mode=importlib`` (see :ref:`import-modes`) doesn't have + The ``--import-mode=importlib`` option (see :ref:`import-modes`) does not have any of the drawbacks above because ``sys.path`` is not changed when importing - test modules, so users that run - into this issue are strongly encouraged to try it and report if the new option works well for them. + test modules, so users that run into this issue are strongly encouraged to try it. The ``src`` directory layout is still strongly recommended however. diff --git a/doc/en/explanation/pythonpath.rst b/doc/en/explanation/pythonpath.rst index 2330356b863..e1d240b498a 100644 --- a/doc/en/explanation/pythonpath.rst +++ b/doc/en/explanation/pythonpath.rst @@ -45,10 +45,19 @@ these values: * ``importlib``: new in pytest-6.0, this mode uses :mod:`importlib` to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`. - For this reason this doesn't require test module names to be unique, but also makes test - modules non-importable by each other. + For this reason this doesn't require test module names to be unique. + + One drawback however is that test modules are non-importable by each other. Also, utility + modules in the tests directories are not automatically importable because the tests directory is no longer + added to :py:data:`sys.path`. + + Initially we intended to make ``importlib`` the default in future releases, however it is clear now that + it has its own set of drawbacks so the default will remain ``prepend`` for the foreseeable future. + +.. seealso:: + + The :confval:`pythonpath` configuration variable. - We intend to make ``importlib`` the default in future releases, depending on feedback. ``prepend`` and ``append`` import modes scenarios ------------------------------------------------- From eb55d4c94564816962f7a120a28784856ba2daf7 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Thu, 2 Jun 2022 08:16:59 -0500 Subject: [PATCH 13/41] [7.1.x] docs(monkeypatch): Fix autodoc reference links --- AUTHORS | 1 + doc/en/how-to/monkeypatch.rst | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index c0d1236b8b1..aacf351c8b4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -329,6 +329,7 @@ Tom Dalton Tom Viner Tomáš Gavenčiak Tomer Keren +Tony Narlock Tor Colvin Trevor Bekolay Tyler Goodlet diff --git a/doc/en/how-to/monkeypatch.rst b/doc/en/how-to/monkeypatch.rst index 9c61233f7e5..0ae87824030 100644 --- a/doc/en/how-to/monkeypatch.rst +++ b/doc/en/how-to/monkeypatch.rst @@ -3,7 +3,7 @@ How to monkeypatch/mock modules and environments ================================================================ -.. currentmodule:: _pytest.monkeypatch +.. currentmodule:: pytest Sometimes tests need to invoke functionality which depends on global settings or which invokes code which cannot be easily @@ -436,7 +436,7 @@ separate fixtures for each potential mock and reference them in the needed tests _ = app.create_connection_string() -.. currentmodule:: _pytest.monkeypatch +.. currentmodule:: pytest API Reference ------------- From 55e7a71446866a5134e49b9b6599261c16ecc1dd Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 7 Jun 2022 18:49:37 -0300 Subject: [PATCH 14/41] [7.1.x] Update location of `usage.rst` to fix manpage compilation --- doc/en/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index 5aedd2d322d..0022363b27e 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -320,7 +320,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)] +man_pages = [("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)] # -- Options for Epub output --------------------------------------------------- From dc0812610d0bf485063aa5cb0bee3838ee933b96 Mon Sep 17 00:00:00 2001 From: Zach OBrien Date: Tue, 14 Jun 2022 05:54:32 -0400 Subject: [PATCH 15/41] [7.1.x] Fix representation of tuples in approx --- AUTHORS | 1 + changelog/9917.bugfix.rst | 1 + src/_pytest/python_api.py | 20 +++++++++++-------- testing/python/approx.py | 42 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 changelog/9917.bugfix.rst diff --git a/AUTHORS b/AUTHORS index aacf351c8b4..6f623555c37 100644 --- a/AUTHORS +++ b/AUTHORS @@ -356,5 +356,6 @@ Yoav Caspi Yuval Shimon Zac Hatfield-Dodds Zachary Kneupper +Zachary OBrien Zoltán Máté Zsolt Cserna diff --git a/changelog/9917.bugfix.rst b/changelog/9917.bugfix.rst new file mode 100644 index 00000000000..ac0616d2ee7 --- /dev/null +++ b/changelog/9917.bugfix.rst @@ -0,0 +1 @@ +Fixed string representation for :func:`pytest.approx` when used to compare tuples. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 5fa21961924..724e71aa5c2 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -133,9 +133,11 @@ def _check_type(self) -> None: # raise if there are any non-numeric elements in the sequence. -def _recursive_list_map(f, x): - if isinstance(x, list): - return [_recursive_list_map(f, xi) for xi in x] +def _recursive_sequence_map(f, x): + """Recursively map a function over a sequence of arbitary depth""" + if isinstance(x, (list, tuple)): + seq_type = type(x) + return seq_type(_recursive_sequence_map(f, xi) for xi in x) else: return f(x) @@ -144,7 +146,9 @@ class ApproxNumpy(ApproxBase): """Perform approximate comparisons where the expected value is numpy array.""" def __repr__(self) -> str: - list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist()) + list_scalars = _recursive_sequence_map( + self._approx_scalar, self.expected.tolist() + ) return f"approx({list_scalars!r})" def _repr_compare(self, other_side: "ndarray") -> List[str]: @@ -164,7 +168,7 @@ def get_value_from_nested_list( return value np_array_shape = self.expected.shape - approx_side_as_list = _recursive_list_map( + approx_side_as_seq = _recursive_sequence_map( self._approx_scalar, self.expected.tolist() ) @@ -179,7 +183,7 @@ def get_value_from_nested_list( max_rel_diff = -math.inf different_ids = [] for index in itertools.product(*(range(i) for i in np_array_shape)): - approx_value = get_value_from_nested_list(approx_side_as_list, index) + approx_value = get_value_from_nested_list(approx_side_as_seq, index) other_value = get_value_from_nested_list(other_side, index) if approx_value != other_value: abs_diff = abs(approx_value.expected - other_value) @@ -194,7 +198,7 @@ def get_value_from_nested_list( ( str(index), str(get_value_from_nested_list(other_side, index)), - str(get_value_from_nested_list(approx_side_as_list, index)), + str(get_value_from_nested_list(approx_side_as_seq, index)), ) for index in different_ids ] @@ -326,7 +330,7 @@ def _repr_compare(self, other_side: Sequence[float]) -> List[str]: f"Lengths: {len(self.expected)} and {len(other_side)}", ] - approx_side_as_map = _recursive_list_map(self._approx_scalar, self.expected) + approx_side_as_map = _recursive_sequence_map(self._approx_scalar, self.expected) number_of_elements = len(approx_side_as_map) max_abs_diff = -math.inf diff --git a/testing/python/approx.py b/testing/python/approx.py index 7b4fbad156e..6acb466ffb1 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -2,12 +2,14 @@ from contextlib import contextmanager from decimal import Decimal from fractions import Fraction +from math import sqrt from operator import eq from operator import ne from typing import Optional import pytest from _pytest.pytester import Pytester +from _pytest.python_api import _recursive_sequence_map from pytest import approx inf, nan = float("inf"), float("nan") @@ -133,6 +135,18 @@ def test_error_messages_native_dtypes(self, assert_approx_raises_regex): ], ) + assert_approx_raises_regex( + (1, 2.2, 4), + (1, 3.2, 4), + [ + r" comparison failed. Mismatched elements: 1 / 3:", + rf" Max absolute difference: {SOME_FLOAT}", + rf" Max relative difference: {SOME_FLOAT}", + r" Index \| Obtained\s+\| Expected ", + rf" 1 \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", + ], + ) + # Specific test for comparison with 0.0 (relative diff will be 'inf') assert_approx_raises_regex( [0.0], @@ -878,3 +892,31 @@ def test_allow_ordered_sequences_only(self) -> None: """pytest.approx() should raise an error on unordered sequences (#9692).""" with pytest.raises(TypeError, match="only supports ordered sequences"): assert {1, 2, 3} == approx({1, 2, 3}) + + +class TestRecursiveSequenceMap: + def test_map_over_scalar(self): + assert _recursive_sequence_map(sqrt, 16) == 4 + + def test_map_over_empty_list(self): + assert _recursive_sequence_map(sqrt, []) == [] + + def test_map_over_list(self): + assert _recursive_sequence_map(sqrt, [4, 16, 25, 676]) == [2, 4, 5, 26] + + def test_map_over_tuple(self): + assert _recursive_sequence_map(sqrt, (4, 16, 25, 676)) == (2, 4, 5, 26) + + def test_map_over_nested_lists(self): + assert _recursive_sequence_map(sqrt, [4, [25, 64], [[49]]]) == [ + 2, + [5, 8], + [[7]], + ] + + def test_map_over_mixed_sequence(self): + assert _recursive_sequence_map(sqrt, [4, (25, 64), [(49)]]) == [ + 2, + (5, 8), + [(7)], + ] From be9f8b1d057ac9363f1edb71d67ef9f199d0fff4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 14 Jun 2022 07:29:19 -0300 Subject: [PATCH 16/41] [7.1.x] Mark pdb+expect tests as xfail for now --- testing/test_debugging.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testing/test_debugging.py b/testing/test_debugging.py index a95b542adec..9298726a561 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -353,6 +353,7 @@ def test_pdb_prevent_ConftestImportFailure_hiding_exception( result = pytester.runpytest_subprocess("--pdb", ".") result.stdout.fnmatch_lines(["-> import unknown"]) + @pytest.mark.xfail(reason="#10042") def test_pdb_interaction_capturing_simple(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -521,6 +522,7 @@ def function_1(): assert "BdbQuit" not in rest assert "UNEXPECTED EXCEPTION" not in rest + @pytest.mark.xfail(reason="#10042") def test_pdb_interaction_capturing_twice(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -556,6 +558,7 @@ def test_1(): assert "1 failed" in rest self.flush(child) + @pytest.mark.xfail(reason="#10042") def test_pdb_with_injected_do_debug(self, pytester: Pytester) -> None: """Simulates pdbpp, which injects Pdb into do_debug, and uses self.__class__ in do_continue. @@ -1000,6 +1003,7 @@ def test_1(): assert "reading from stdin while output" not in rest TestPDB.flush(child) + @pytest.mark.xfail(reason="#10042") def test_pdb_not_altered(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -1159,6 +1163,7 @@ def test_2(): @pytest.mark.parametrize("fixture", ("capfd", "capsys")) +@pytest.mark.xfail(reason="#10042") def test_pdb_suspends_fixture_capturing(pytester: Pytester, fixture: str) -> None: """Using "-s" with pytest should suspend/resume fixture capturing.""" p1 = pytester.makepyfile( From d89c13d670a4ee87994f48a208625fb57f0e1782 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 14 Jun 2022 07:31:06 -0300 Subject: [PATCH 17/41] [7.1.x] Update training list --- doc/en/index.rst | 8 ++------ doc/en/talks.rst | 2 ++ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 8b4528ddf2d..501ec913388 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -2,16 +2,12 @@ .. sidebar:: Next Open Trainings - - `PyConDE `__, April 11th 2022 (3h), Berlin, Germany - - `PyConIT `__, June 3rd 2022 (4h), Florence, Italy + - `Europython `__, July 11th 2022 (3h), Dublin, Ireland + - `CH Open Workshop-Tage `__ (German), September 8th 2022, Bern, Switzerland - `Professional Testing with Python `_, via `Python Academy `_, March 7th to 9th 2023 (3 day in-depth training), Remote and Leipzig, Germany Also see :doc:`previous talks and blogposts `. -.. - - `Europython `__, July 11th to 17th (3h), Dublin, Ireland - - `CH Open Workshoptage `__ (German), September 6th to 8th (1 day), Bern, Switzerland - .. _features: pytest: helps you write better programs diff --git a/doc/en/talks.rst b/doc/en/talks.rst index 160345614c8..b9b153a792e 100644 --- a/doc/en/talks.rst +++ b/doc/en/talks.rst @@ -17,6 +17,8 @@ Books Talks and blog postings --------------------------------------------- +- Training: `pytest - simple, rapid and fun testing with Python `_, Florian Bruhin, PyConDE 2022 + - `pytest: Simple, rapid and fun testing with Python, `_ (@ 4:22:32), Florian Bruhin, WeAreDevelopers World Congress 2021 - Webinar: `pytest: Test Driven Development für Python (German) `_, Florian Bruhin, via mylearning.ch, 2020 From 9c3bc106dd522acbc70d13d2d31866f5d969320f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 25 Jun 2022 20:55:23 -0300 Subject: [PATCH 18/41] [7.1.x] Added Docstring description for the Path property of FixtureRequest #9975 --- AUTHORS | 1 + src/_pytest/fixtures.py | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index 6f623555c37..8e2afc3d5f3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -257,6 +257,7 @@ Oscar Benjamin Parth Patel Patrick Hayes Paul Müller +Paul Reece Pauli Virtanen Pavel Karateev Paweł Adamczak diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index ee3e93f1908..e11481a766b 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -491,6 +491,7 @@ def module(self): @property def path(self) -> Path: + """Path where the test function was collected.""" if self.scope not in ("function", "class", "module", "package"): raise AttributeError(f"path not available in {self.scope}-scoped context") # TODO: Remove ignore once _pyfuncitem is properly typed. From a1027af1689be3779298d305af6ea51f616527b8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 26 Jun 2022 01:31:08 +0000 Subject: [PATCH 19/41] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/en/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index 0022363b27e..3c26b8ce0b7 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -320,7 +320,9 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)] +man_pages = [ + ("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1) +] # -- Options for Epub output --------------------------------------------------- From 315695f53fa51d4ca842813e61484ee2b21f30dd Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 26 Jun 2022 09:54:18 -0300 Subject: [PATCH 20/41] [7.1.x] Indicate support for a tuple of exceptions in xfail raises= --- doc/en/reference/reference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index d082697258c..b113deb3215 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -245,7 +245,7 @@ Marks a test function as *expected to fail*. :keyword str reason: Reason why the test function is marked as xfail. :keyword Type[Exception] raises: - Exception subclass expected to be raised by the test function; other exceptions will fail the test. + Exception subclass (or tuple of subclasses) expected to be raised by the test function; other exceptions will fail the test. :keyword bool run: If the test function should actually be executed. If ``False``, the function will always xfail and will not be executed (useful if a function is segfaulting). From b8ce513e95a74aad490cd2cbc8b1f4ed9eed1a83 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 26 Jun 2022 12:56:41 +0000 Subject: [PATCH 21/41] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/en/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index 0022363b27e..3c26b8ce0b7 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -320,7 +320,9 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)] +man_pages = [ + ("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1) +] # -- Options for Epub output --------------------------------------------------- From 0e7a6a769e32ec9e9adda8c9c87d57492b55c787 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 27 Jun 2022 09:58:38 -0300 Subject: [PATCH 22/41] [7.1.x] Use PurePath directly instead of os.path.sep in rewrite.py --- changelog/9791.bugfix.rst | 1 + src/_pytest/assertion/rewrite.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog/9791.bugfix.rst diff --git a/changelog/9791.bugfix.rst b/changelog/9791.bugfix.rst new file mode 100644 index 00000000000..f81c870165b --- /dev/null +++ b/changelog/9791.bugfix.rst @@ -0,0 +1 @@ +Fixed a path handling code in ``rewrite.py`` that seems to work fine, but was incorrect and fails in some systems. diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 81096764e04..9d0b431b4a7 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -190,7 +190,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: return False # For matching the name it must be as if it was a filename. - path = PurePath(os.path.sep.join(parts) + ".py") + path = PurePath(*parts).with_suffix(".py") for pat in self.fnpats: # if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based From 9a16e6983544da37770448c7bbac37bfb4d977b9 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 28 Jun 2022 12:49:50 -0300 Subject: [PATCH 23/41] [7.1.x] Do not call tearDown for skipped unittest.TestCases with --pdb --- changelog/10060.bugfix.rst | 1 + src/_pytest/unittest.py | 5 +++- testing/test_unittest.py | 50 ++++++++++++++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 changelog/10060.bugfix.rst diff --git a/changelog/10060.bugfix.rst b/changelog/10060.bugfix.rst new file mode 100644 index 00000000000..c8941820eca --- /dev/null +++ b/changelog/10060.bugfix.rst @@ -0,0 +1 @@ +When running with ``--pdb``, ``TestCase.tearDown`` is no longer called for tests when the *class* has been skipped via ``unittest.skip`` or ``pytest.mark.skip``. diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 851e4943b23..c2df986530c 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -316,7 +316,10 @@ def runtest(self) -> None: # Arguably we could always postpone tearDown(), but this changes the moment where the # TestCase instance interacts with the results object, so better to only do it # when absolutely needed. - if self.config.getoption("usepdb") and not _is_skipped(self.obj): + # We need to consider if the test itself is skipped, or the whole class. + assert isinstance(self.parent, UnitTestCase) + skipped = _is_skipped(self.obj) or _is_skipped(self.parent.obj) + if self.config.getoption("usepdb") and not skipped: self._explicit_tearDown = self._testcase.tearDown setattr(self._testcase, "tearDown", lambda *args: None) diff --git a/testing/test_unittest.py b/testing/test_unittest.py index fb312814542..d917d331a03 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1241,12 +1241,15 @@ def test_2(self): @pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"]) -def test_pdb_teardown_skipped( +def test_pdb_teardown_skipped_for_functions( pytester: Pytester, monkeypatch: MonkeyPatch, mark: str ) -> None: - """With --pdb, setUp and tearDown should not be called for skipped tests.""" + """ + With --pdb, setUp and tearDown should not be called for tests skipped + via a decorator (#7215). + """ tracked: List[str] = [] - monkeypatch.setattr(pytest, "test_pdb_teardown_skipped", tracked, raising=False) + monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) pytester.makepyfile( """ @@ -1256,10 +1259,10 @@ def test_pdb_teardown_skipped( class MyTestCase(unittest.TestCase): def setUp(self): - pytest.test_pdb_teardown_skipped.append("setUp:" + self.id()) + pytest.track_pdb_teardown_skipped.append("setUp:" + self.id()) def tearDown(self): - pytest.test_pdb_teardown_skipped.append("tearDown:" + self.id()) + pytest.track_pdb_teardown_skipped.append("tearDown:" + self.id()) {mark}("skipped for reasons") def test_1(self): @@ -1274,6 +1277,43 @@ def test_1(self): assert tracked == [] +@pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"]) +def test_pdb_teardown_skipped_for_classes( + pytester: Pytester, monkeypatch: MonkeyPatch, mark: str +) -> None: + """ + With --pdb, setUp and tearDown should not be called for tests skipped + via a decorator on the class (#10060). + """ + tracked: List[str] = [] + monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) + + pytester.makepyfile( + """ + import unittest + import pytest + + {mark}("skipped for reasons") + class MyTestCase(unittest.TestCase): + + def setUp(self): + pytest.track_pdb_teardown_skipped.append("setUp:" + self.id()) + + def tearDown(self): + pytest.track_pdb_teardown_skipped.append("tearDown:" + self.id()) + + def test_1(self): + pass + + """.format( + mark=mark + ) + ) + result = pytester.runpytest_inprocess("--pdb") + result.stdout.fnmatch_lines("* 1 skipped in *") + assert tracked == [] + + def test_async_support(pytester: Pytester) -> None: pytest.importorskip("unittest.async_case") From 550a42d71727522bcbd88657aa3ea9e035948cc4 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 29 Jun 2022 14:00:03 -0400 Subject: [PATCH 24/41] [7.1.x] update does_not_raise docs now that pytest is 3.7+ only (cherry picked from commit 2941da0f2bdc58cabfc6977f45bea3c9404493ea) --- doc/en/example/parametrize.rst | 32 +++++--------------------------- testing/python/raises.py | 12 ++---------- 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 66d72f3cc0d..f8147683975 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -657,18 +657,15 @@ Use :func:`pytest.raises` with the :ref:`pytest.mark.parametrize ref` decorator to write parametrized tests in which some tests raise exceptions and others do not. -It is helpful to define a no-op context manager ``does_not_raise`` to serve -as a complement to ``raises``. For example: +It may be helpful to use ``nullcontext`` as a complement to ``raises``. -.. code-block:: python +For example: - from contextlib import contextmanager - import pytest +.. code-block:: python + from contextlib import nullcontext as does_not_raise - @contextmanager - def does_not_raise(): - yield + import pytest @pytest.mark.parametrize( @@ -687,22 +684,3 @@ as a complement to ``raises``. For example: In the example above, the first three test cases should run unexceptionally, while the fourth should raise ``ZeroDivisionError``. - -If you're only supporting Python 3.7+, you can simply use ``nullcontext`` -to define ``does_not_raise``: - -.. code-block:: python - - from contextlib import nullcontext as does_not_raise - -Or, if you're supporting Python 3.3+ you can use: - -.. code-block:: python - - from contextlib import ExitStack as does_not_raise - -Or, if desired, you can ``pip install contextlib2`` and use: - -.. code-block:: python - - from contextlib2 import nullcontext as does_not_raise diff --git a/testing/python/raises.py b/testing/python/raises.py index 2d62e91091b..45e22870ebc 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -82,13 +82,9 @@ def test_raise_wrong_exception_passes_by(): def test_does_not_raise(self, pytester: Pytester) -> None: pytester.makepyfile( """ - from contextlib import contextmanager + from contextlib import nullcontext as does_not_raise import pytest - @contextmanager - def does_not_raise(): - yield - @pytest.mark.parametrize('example_input,expectation', [ (3, does_not_raise()), (2, does_not_raise()), @@ -107,13 +103,9 @@ def test_division(example_input, expectation): def test_does_not_raise_does_raise(self, pytester: Pytester) -> None: pytester.makepyfile( """ - from contextlib import contextmanager + from contextlib import nullcontext as does_not_raise import pytest - @contextmanager - def does_not_raise(): - yield - @pytest.mark.parametrize('example_input,expectation', [ (0, does_not_raise()), (1, pytest.raises(ZeroDivisionError)), From 549e3136cc6af6ba8044e778e4dbd80a5a3d91e0 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 29 Jun 2022 17:57:37 -0400 Subject: [PATCH 25/41] [7.1.x] Pass importmode to import_path in DoctestModule --- AUTHORS | 1 + changelog/3396.bugfix.rst | 1 + src/_pytest/doctest.py | 6 +++++- testing/test_doctest.py | 22 ++++++++++++++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 changelog/3396.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 8e2afc3d5f3..18c5782e156 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,6 +15,7 @@ Alan Velasco Alexander Johnson Alexander King Alexei Kozlenok +Alice Purcell Allan Feldman Aly Sivji Amir Elkess diff --git a/changelog/3396.bugfix.rst b/changelog/3396.bugfix.rst new file mode 100644 index 00000000000..aa3f278c2a6 --- /dev/null +++ b/changelog/3396.bugfix.rst @@ -0,0 +1 @@ +Doctests now respect the ``--import-mode`` flag. diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index aa071cde883..98796aaafea 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -542,7 +542,11 @@ def _find( ) else: try: - module = import_path(self.path, root=self.config.rootpath) + module = import_path( + self.path, + root=self.config.rootpath, + mode=self.config.getoption("importmode"), + ) except ImportError: if self.config.getvalue("doctest_ignore_import_errors"): pytest.skip("unable to import module %r" % self.path) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 1b4809240c0..828253d3223 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -113,6 +113,28 @@ def test_simple_doctestfile(self, pytester: Pytester): reprec = pytester.inline_run(p) reprec.assertoutcome(failed=1) + def test_importmode(self, pytester: Pytester): + p = pytester.makepyfile( + **{ + "namespacepkg/innerpkg/__init__.py": "", + "namespacepkg/innerpkg/a.py": """ + def some_func(): + return 42 + """, + "namespacepkg/innerpkg/b.py": """ + from namespacepkg.innerpkg.a import some_func + def my_func(): + ''' + >>> my_func() + 42 + ''' + return some_func() + """, + } + ) + reprec = pytester.inline_run(p, "--doctest-modules", "--import-mode=importlib") + reprec.assertoutcome(passed=1) + def test_new_pattern(self, pytester: Pytester): p = pytester.maketxtfile( xdoc=""" From 1eb843452921d4490c372882f2ca7d6a2e7f202e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 6 Jul 2022 11:27:46 +0000 Subject: [PATCH 26/41] [7.1.x] Remove europython training (#10108) Co-authored-by: Florian Bruhin --- doc/en/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 501ec913388..870fd5a2b15 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -2,7 +2,6 @@ .. sidebar:: Next Open Trainings - - `Europython `__, July 11th 2022 (3h), Dublin, Ireland - `CH Open Workshop-Tage `__ (German), September 8th 2022, Bern, Switzerland - `Professional Testing with Python `_, via `Python Academy `_, March 7th to 9th 2023 (3 day in-depth training), Remote and Leipzig, Germany From 199367168ba8432c984b4a60fe03e9aee09e13cc Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Fri, 8 Jul 2022 22:06:56 -0400 Subject: [PATCH 27/41] [7.1.x] replace atomicwrites with os.replace (cherry picked from commit 7dc540f258cf898b7ffe540f5d460490bd825425) --- .pre-commit-config.yaml | 1 - changelog/10114.trivial.rst | 1 + setup.cfg | 1 - src/_pytest/assertion/rewrite.py | 68 +++++++++++--------------------- testing/test_assertrewrite.py | 25 ++---------- 5 files changed, 26 insertions(+), 70 deletions(-) create mode 100644 changelog/10114.trivial.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8b382fc85eb..0eeafd468cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -67,7 +67,6 @@ repos: - attrs>=19.2.0 - packaging - tomli - - types-atomicwrites - types-pkg_resources - repo: local hooks: diff --git a/changelog/10114.trivial.rst b/changelog/10114.trivial.rst new file mode 100644 index 00000000000..42c43a1d0d6 --- /dev/null +++ b/changelog/10114.trivial.rst @@ -0,0 +1 @@ +Replace `atomicwrites `__ dependency on windows with `os.replace`. diff --git a/setup.cfg b/setup.cfg index fe6ea4095bc..c922ce434cd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,7 +47,6 @@ install_requires = pluggy>=0.12,<2.0 py>=1.8.2 tomli>=1.0.0 - atomicwrites>=1.0;sys_platform=="win32" colorama;sys_platform=="win32" importlib-metadata>=0.12;python_version<"3.8" python_requires = >=3.7 diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 9d0b431b4a7..a1bbb171b4b 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -302,53 +302,29 @@ def _write_pyc_fp( fp.write(marshal.dumps(co)) -if sys.platform == "win32": - from atomicwrites import atomic_write - - def _write_pyc( - state: "AssertionState", - co: types.CodeType, - source_stat: os.stat_result, - pyc: Path, - ) -> bool: - try: - with atomic_write(os.fspath(pyc), mode="wb", overwrite=True) as fp: - _write_pyc_fp(fp, source_stat, co) - except OSError as e: - state.trace(f"error writing pyc file at {pyc}: {e}") - # we ignore any failure to write the cache file - # there are many reasons, permission-denied, pycache dir being a - # file etc. - return False - return True - -else: - - def _write_pyc( - state: "AssertionState", - co: types.CodeType, - source_stat: os.stat_result, - pyc: Path, - ) -> bool: - proc_pyc = f"{pyc}.{os.getpid()}" - try: - fp = open(proc_pyc, "wb") - except OSError as e: - state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}") - return False - - try: +def _write_pyc( + state: "AssertionState", + co: types.CodeType, + source_stat: os.stat_result, + pyc: Path, +) -> bool: + proc_pyc = f"{pyc}.{os.getpid()}" + try: + with open(proc_pyc, "wb") as fp: _write_pyc_fp(fp, source_stat, co) - os.rename(proc_pyc, pyc) - except OSError as e: - state.trace(f"error writing pyc file at {pyc}: {e}") - # we ignore any failure to write the cache file - # there are many reasons, permission-denied, pycache dir being a - # file etc. - return False - finally: - fp.close() - return True + except OSError as e: + state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}") + return False + + try: + os.replace(proc_pyc, pyc) + except OSError as e: + state.trace(f"error writing pyc file at {pyc}: {e}") + # we ignore any failure to write the cache file + # there are many reasons, permission-denied, pycache dir being a + # file etc. + return False + return True def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]: diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index a515bbae96b..3c98392ed98 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1009,7 +1009,7 @@ def test_meta_path(): ) assert pytester.runpytest().ret == 0 - def test_write_pyc(self, pytester: Pytester, tmp_path, monkeypatch) -> None: + def test_write_pyc(self, pytester: Pytester, tmp_path) -> None: from _pytest.assertion.rewrite import _write_pyc from _pytest.assertion import AssertionState @@ -1021,27 +1021,8 @@ def test_write_pyc(self, pytester: Pytester, tmp_path, monkeypatch) -> None: co = compile("1", "f.py", "single") assert _write_pyc(state, co, os.stat(source_path), pycpath) - if sys.platform == "win32": - from contextlib import contextmanager - - @contextmanager - def atomic_write_failed(fn, mode="r", overwrite=False): - e = OSError() - e.errno = 10 - raise e - yield - - monkeypatch.setattr( - _pytest.assertion.rewrite, "atomic_write", atomic_write_failed - ) - else: - - def raise_oserror(*args): - raise OSError() - - monkeypatch.setattr("os.rename", raise_oserror) - - assert not _write_pyc(state, co, os.stat(source_path), pycpath) + with mock.patch.object(os, "replace", side_effect=OSError): + assert not _write_pyc(state, co, os.stat(source_path), pycpath) def test_resources_provider_for_loader(self, pytester: Pytester) -> None: """ From 55189ebda6bfa654af91e413e0277dac497a6f88 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 11 Jul 2022 08:47:45 -0400 Subject: [PATCH 28/41] [7.1.x] Fix mypy pre-commit run --- src/_pytest/assertion/rewrite.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index a1bbb171b4b..63f9dd8f27b 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -281,7 +281,9 @@ def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources: else: from importlib.resources.readers import FileReader - return FileReader(types.SimpleNamespace(path=self._rewritten_names[name])) + return FileReader( # type:ignore[no-any-return] + types.SimpleNamespace(path=self._rewritten_names[name]) + ) def _write_pyc_fp( From 9b3affc006c88220f05d5efa4bf1063811036f65 Mon Sep 17 00:00:00 2001 From: Nipunn Koorapati Date: Thu, 14 Jul 2022 16:36:05 -0700 Subject: [PATCH 29/41] [7.1.x] Add typing for FixtureRequest.param --- AUTHORS | 1 + changelog/9514.bugfix.rst | 1 + src/_pytest/fixtures.py | 11 ++++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 changelog/9514.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 18c5782e156..6eaf26f8437 100644 --- a/AUTHORS +++ b/AUTHORS @@ -247,6 +247,7 @@ Nicholas Murphy Niclas Olofsson Nicolas Delaby Nikolay Kondratyev +Nipunn Koorapati Olga Matoula Oleg Pidsadnyi Oleg Sushchenko diff --git a/changelog/9514.bugfix.rst b/changelog/9514.bugfix.rst new file mode 100644 index 00000000000..a940b5c72d5 --- /dev/null +++ b/changelog/9514.bugfix.rst @@ -0,0 +1 @@ +Type-annotate ``FixtureRequest.param`` as ``Any`` as a stop gap measure until :issue:`8073` is fixed. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index e11481a766b..7d4bd1433a0 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -350,7 +350,7 @@ def reorder_items_atscope( return items_done -def get_direct_param_fixture_func(request): +def get_direct_param_fixture_func(request: "FixtureRequest") -> Any: return request.param @@ -412,6 +412,15 @@ def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None: self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() self._arg2index: Dict[str, int] = {} self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager + # Notes on the type of `param`: + # -`request.param` is only defined in parametrized fixtures, and will raise + # AttributeError otherwise. Python typing has no notion of "undefined", so + # this cannot be reflected in the type. + # - Technically `param` is only (possibly) defined on SubRequest, not + # FixtureRequest, but the typing of that is still in flux so this cheats. + # - In the future we might consider using a generic for the param type, but + # for now just using Any. + self.param: Any @property def scope(self) -> "_ScopeName": From da7daaac9266ad5c08cc2bbf44c61159f1da3727 Mon Sep 17 00:00:00 2001 From: Wolfremium <64548160+Wolfremium13@users.noreply.github.com> Date: Fri, 15 Jul 2022 12:40:18 +0100 Subject: [PATCH 30/41] [7.1.x] Explicit note that tmpdir fixture is discouraged in favour of tmp_path #9937 --- AUTHORS | 4 ++++ changelog/9937.doc.rst | 1 + doc/en/how-to/tmp_path.rst | 6 ++++-- src/_pytest/legacypath.py | 14 +++++++++++++- 4 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 changelog/9937.doc.rst diff --git a/AUTHORS b/AUTHORS index 6eaf26f8437..e4e1f2f12af 100644 --- a/AUTHORS +++ b/AUTHORS @@ -84,6 +84,7 @@ Damian Skrzypczak Daniel Grana Daniel Hahler Daniel Nuri +Daniel Sánchez Castelló Daniel Wandschneider Daniele Procida Danielle Jenkins @@ -185,6 +186,7 @@ Katarzyna Król Katerina Koukiou Keri Volans Kevin Cox +Kevin Hierro Carrasco Kevin J. Foley Kian Eliasi Kian-Meng Ang @@ -322,6 +324,7 @@ Taneli Hukkinen Tanvi Mehta Tarcisio Fischer Tareq Alayan +Tatiana Ovary Ted Xiao Terje Runde Thomas Grainger @@ -339,6 +342,7 @@ Tyler Goodlet Tzu-ping Chung Vasily Kuznetsov Victor Maryama +Victor Rodriguez Victor Uriarte Vidar T. Fauske Virgil Dupras diff --git a/changelog/9937.doc.rst b/changelog/9937.doc.rst new file mode 100644 index 00000000000..326d20ec8f6 --- /dev/null +++ b/changelog/9937.doc.rst @@ -0,0 +1 @@ +Explicit note that :fixture:`tmpdir` fixture is discouraged in favour of :fixture:`tmp_path`. diff --git a/doc/en/how-to/tmp_path.rst b/doc/en/how-to/tmp_path.rst index b261a55637e..99e9e55367a 100644 --- a/doc/en/how-to/tmp_path.rst +++ b/doc/en/how-to/tmp_path.rst @@ -104,8 +104,10 @@ The ``tmpdir`` and ``tmpdir_factory`` fixtures The ``tmpdir`` and ``tmpdir_factory`` fixtures are similar to ``tmp_path`` and ``tmp_path_factory``, but use/return legacy `py.path.local`_ objects -rather than standard :class:`pathlib.Path` objects. These days, prefer to -use ``tmp_path`` and ``tmp_path_factory``. +rather than standard :class:`pathlib.Path` objects. + +.. note:: + These days, it is preferred to use ``tmp_path`` and ``tmp_path_factory``. See :fixture:`tmpdir ` :fixture:`tmpdir_factory ` API for details. diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index 49149311a7d..f71e7e96ead 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -271,7 +271,14 @@ def testdir(pytester: Pytester) -> Testdir: @attr.s(init=False, auto_attribs=True) class TempdirFactory: """Backward compatibility wrapper that implements :class:`py.path.local` - for :class:`TempPathFactory`.""" + for :class:`TempPathFactory`. + + .. note:: + These days, it is preferred to use ``tmp_path_factory``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + + """ _tmppath_factory: TempPathFactory @@ -312,6 +319,11 @@ def tmpdir(tmp_path: Path) -> LEGACY_PATH: The returned object is a `legacy_path`_ object. + .. note:: + These days, it is preferred to use ``tmp_path``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + .. _legacy_path: https://py.readthedocs.io/en/latest/path.html """ return legacy_path(tmp_path) From b9d00fba134957885a12dd1bd4bffb68b00f6fbd Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 14 Jul 2022 08:37:08 -0300 Subject: [PATCH 31/41] Merge pull request #10132 from hroncok/python3.11.0b4 --- testing/test_doctest.py | 6 +++++- testing/test_main.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 828253d3223..2f73feb8c4b 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -223,7 +223,11 @@ def test_doctest_unexpected_exception(self, pytester: Pytester): "Traceback (most recent call last):", ' File "*/doctest.py", line *, in __run', " *", - *((" *^^^^*",) if sys.version_info >= (3, 11) else ()), + *( + (" *^^^^*",) + if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) + else () + ), ' File "", line 1, in ', "ZeroDivisionError: division by zero", "*/test_doctest_unexpected_exception.txt:2: UnexpectedException", diff --git a/testing/test_main.py b/testing/test_main.py index 2df51bb7bb9..71597626790 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -47,7 +47,7 @@ def pytest_internalerror(excrepr, excinfo): end_lines = ( result.stdout.lines[-4:] - if sys.version_info >= (3, 11) + if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) else result.stdout.lines[-3:] ) @@ -57,7 +57,7 @@ def pytest_internalerror(excrepr, excinfo): 'INTERNALERROR> raise SystemExit("boom")', *( ("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",) - if sys.version_info >= (3, 11) + if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) else () ), "INTERNALERROR> SystemExit: boom", @@ -68,7 +68,7 @@ def pytest_internalerror(excrepr, excinfo): 'INTERNALERROR> raise ValueError("boom")', *( ("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",) - if sys.version_info >= (3, 11) + if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) else () ), "INTERNALERROR> ValueError: boom", From 6f8c4fa742e6fdcd0c6e085625d504e874190653 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 10:06:32 +0000 Subject: [PATCH 32/41] [7.1.x] JUnit XML: Escape error messages in setup/teardown (#10207) Co-authored-by: holesch <8659229+holesch@users.noreply.github.com> --- AUTHORS | 1 + changelog/10190.bugfix.rst | 1 + src/_pytest/junitxml.py | 2 +- testing/test_junitxml.py | 22 ++++++++++++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 changelog/10190.bugfix.rst diff --git a/AUTHORS b/AUTHORS index e4e1f2f12af..914e036a44a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -307,6 +307,7 @@ Seth Junot Shantanu Jain Shubham Adep Simon Gomizelj +Simon Holesch Simon Kerr Skylar Downes Srinivas Reddy Thatiparthy diff --git a/changelog/10190.bugfix.rst b/changelog/10190.bugfix.rst new file mode 100644 index 00000000000..731d0efad9a --- /dev/null +++ b/changelog/10190.bugfix.rst @@ -0,0 +1 @@ +Invalid XML characters in setup or teardown error messages are now properly escaped for JUnit XML reports. diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 1b9e3bfecac..7c06a9a8a47 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -231,7 +231,7 @@ def append_error(self, report: TestReport) -> None: msg = f'failed on teardown with "{reason}"' else: msg = f'failed on setup with "{reason}"' - self._add_simple("error", msg, str(report.longrepr)) + self._add_simple("error", bin_xml_escape(msg), str(report.longrepr)) def append_skipped(self, report: TestReport) -> None: if hasattr(report, "wasxfail"): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 02531e81435..b266c76d922 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1625,6 +1625,28 @@ def test_skip(): snode.assert_attr(message="1 <> 2") +def test_escaped_setup_teardown_error( + pytester: Pytester, run_and_parse: RunAndParse +) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.fixture() + def my_setup(): + raise Exception("error: \033[31mred\033[m") + + def test_esc(my_setup): + pass + """ + ) + _, dom = run_and_parse() + node = dom.find_first_by_tag("testcase") + snode = node.find_first_by_tag("error") + assert "#x1B[31mred#x1B[m" in snode["message"] + assert "#x1B[31mred#x1B[m" in snode.text + + @parametrize_families def test_logging_passing_tests_disabled_does_not_log_test_output( pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str From 7133c546f8d74c7d3c002351097c2a39f05e20cb Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 15 Jul 2022 23:08:21 -0300 Subject: [PATCH 33/41] [7.1.x] Add monkeypatch.context() to how-to doc intro --- doc/en/how-to/monkeypatch.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/en/how-to/monkeypatch.rst b/doc/en/how-to/monkeypatch.rst index 0ae87824030..9214de77edb 100644 --- a/doc/en/how-to/monkeypatch.rst +++ b/doc/en/how-to/monkeypatch.rst @@ -25,6 +25,7 @@ functionality in tests: monkeypatch.delenv(name, raising=True) monkeypatch.syspath_prepend(path) monkeypatch.chdir(path) + monkeypatch.context() All modifications will be undone after the requesting test function or fixture has finished. The ``raising`` @@ -55,6 +56,9 @@ during a test. 5. Use :py:meth:`monkeypatch.syspath_prepend ` to modify ``sys.path`` which will also call ``pkg_resources.fixup_namespace_packages`` and :py:func:`importlib.invalidate_caches`. +6. Use :py:meth:`monkeypatch.context ` to apply patches only in a specific scope, which can help +control teardown of complex fixtures or patches to the stdlib. + See the `monkeypatch blog post`_ for some introduction material and a discussion of its motivation. From f5cdd18b6a22ea66842c4295610ea4ef0899d3f8 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 13 Aug 2022 21:07:27 +0200 Subject: [PATCH 34/41] [7.1.x] Mention `monkeypatch.context()` in the docs --- src/_pytest/monkeypatch.py | 43 ++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 91d590fb3df..f29c4f2ef55 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -29,21 +29,25 @@ def monkeypatch() -> Generator["MonkeyPatch", None, None]: """A convenient fixture for monkey-patching. - The fixture provides these methods to modify objects, dictionaries or - os.environ:: - - monkeypatch.setattr(obj, name, value, raising=True) - monkeypatch.delattr(obj, name, raising=True) - monkeypatch.setitem(mapping, name, value) - monkeypatch.delitem(obj, name, raising=True) - monkeypatch.setenv(name, value, prepend=None) - monkeypatch.delenv(name, raising=True) - monkeypatch.syspath_prepend(path) - monkeypatch.chdir(path) + The fixture provides these methods to modify objects, dictionaries, or + :data:`os.environ`: + + * :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` + * :meth:`monkeypatch.delattr(obj, name, raising=True) ` + * :meth:`monkeypatch.setitem(mapping, name, value) ` + * :meth:`monkeypatch.delitem(obj, name, raising=True) ` + * :meth:`monkeypatch.setenv(name, value, prepend=None) ` + * :meth:`monkeypatch.delenv(name, raising=True) ` + * :meth:`monkeypatch.syspath_prepend(path) ` + * :meth:`monkeypatch.chdir(path) ` All modifications will be undone after the requesting test function or - fixture has finished. The ``raising`` parameter determines if a KeyError - or AttributeError will be raised if the set/deletion operation has no target. + fixture has finished. The ``raising`` parameter determines if a :class:`KeyError` + or :class:`AttributeError` will be raised if the set/deletion operation does not have the + specified target. + + To undo modifications done by the fixture in a contained scope, + use :meth:`context() `. """ mpatch = MonkeyPatch() yield mpatch @@ -353,11 +357,14 @@ def undo(self) -> None: There is generally no need to call `undo()`, since it is called automatically during tear-down. - Note that the same `monkeypatch` fixture is used across a - single test function invocation. If `monkeypatch` is used both by - the test function itself and one of the test fixtures, - calling `undo()` will undo all of the changes made in - both functions. + .. note:: + The same `monkeypatch` fixture is used across a + single test function invocation. If `monkeypatch` is used both by + the test function itself and one of the test fixtures, + calling `undo()` will undo all of the changes made in + both functions. + + Prefer to use :meth:`context() ` instead. """ for obj, name, value in reversed(self._setattr): if value is not notset: From 9b2c6cedc73cbd3bd3e314a45a243bfff889bd6b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 15 Aug 2022 13:55:19 -0300 Subject: [PATCH 35/41] [7.1.x] Add reference to the Where to patch docs in monkeypatch.setattr --- doc/en/how-to/monkeypatch.rst | 24 +++++++++++------------ src/_pytest/monkeypatch.py | 37 +++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/doc/en/how-to/monkeypatch.rst b/doc/en/how-to/monkeypatch.rst index 9214de77edb..81edd00bda5 100644 --- a/doc/en/how-to/monkeypatch.rst +++ b/doc/en/how-to/monkeypatch.rst @@ -14,18 +14,16 @@ environment variable, or to modify ``sys.path`` for importing. The ``monkeypatch`` fixture provides these helper methods for safely patching and mocking functionality in tests: -.. code-block:: python +* :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` +* :meth:`monkeypatch.delattr(obj, name, raising=True) ` +* :meth:`monkeypatch.setitem(mapping, name, value) ` +* :meth:`monkeypatch.delitem(obj, name, raising=True) ` +* :meth:`monkeypatch.setenv(name, value, prepend=None) ` +* :meth:`monkeypatch.delenv(name, raising=True) ` +* :meth:`monkeypatch.syspath_prepend(path) ` +* :meth:`monkeypatch.chdir(path) ` +* :meth:`monkeypatch.context() ` - monkeypatch.setattr(obj, name, value, raising=True) - monkeypatch.setattr("somemodule.obj.name", value, raising=True) - monkeypatch.delattr(obj, name, raising=True) - monkeypatch.setitem(mapping, name, value) - monkeypatch.delitem(obj, name, raising=True) - monkeypatch.setenv(name, value, prepend=None) - monkeypatch.delenv(name, raising=True) - monkeypatch.syspath_prepend(path) - monkeypatch.chdir(path) - monkeypatch.context() All modifications will be undone after the requesting test function or fixture has finished. The ``raising`` @@ -64,8 +62,8 @@ and a discussion of its motivation. .. _`monkeypatch blog post`: https://tetamap.wordpress.com//2009/03/03/monkeypatching-in-unit-tests-done-right/ -Simple example: monkeypatching functions ----------------------------------------- +Monkeypatching functions +------------------------ Consider a scenario where you are working with user directories. In the context of testing, you do not want your test to depend on the running user. ``monkeypatch`` diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index f29c4f2ef55..6c400ac9bdf 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -40,6 +40,7 @@ def monkeypatch() -> Generator["MonkeyPatch", None, None]: * :meth:`monkeypatch.delenv(name, raising=True) ` * :meth:`monkeypatch.syspath_prepend(path) ` * :meth:`monkeypatch.chdir(path) ` + * :meth:`monkeypatch.context() ` All modifications will be undone after the requesting test function or fixture has finished. The ``raising`` parameter determines if a :class:`KeyError` @@ -186,16 +187,40 @@ def setattr( value: object = notset, raising: bool = True, ) -> None: - """Set attribute value on target, memorizing the old value. + """ + Set attribute value on target, memorizing the old value. + + For example: + + .. code-block:: python + + import os + + monkeypatch.setattr(os, "getcwd", lambda: "/") + + The code above replaces the :func:`os.getcwd` function by a ``lambda`` which + always returns ``"/"``. - For convenience you can specify a string as ``target`` which + For convenience, you can specify a string as ``target`` which will be interpreted as a dotted import path, with the last part - being the attribute name. For example, - ``monkeypatch.setattr("os.getcwd", lambda: "/")`` - would set the ``getcwd`` function of the ``os`` module. + being the attribute name: - Raises AttributeError if the attribute does not exist, unless + .. code-block:: python + + monkeypatch.setattr("os.getcwd", lambda: "/") + + Raises :class:`AttributeError` if the attribute does not exist, unless ``raising`` is set to False. + + **Where to patch** + + ``monkeypatch.setattr`` works by (temporarily) changing the object that a name points to with another one. + There can be many names pointing to any individual object, so for patching to work you must ensure + that you patch the name used by the system under test. + + See the section :ref:`Where to patch ` in the :mod:`unittest.mock` + docs for a complete explanation, which is meant for :func:`unittest.mock.patch` but + applies to ``monkeypatch.setattr`` as well. """ __tracebackhide__ = True import inspect From 88fc45bd57d217a9dc6f53e419c94d28e2d5392f Mon Sep 17 00:00:00 2001 From: aizpurua23a <57321880+aizpurua23a@users.noreply.github.com> Date: Mon, 15 Aug 2022 19:00:03 +0200 Subject: [PATCH 36/41] [7.1.x] Update fixtures.rst w/ finalizer order --- doc/en/how-to/fixtures.rst | 77 +++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 08013877455..f5217cc5560 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -733,8 +733,81 @@ does offer some nuances for when you're in a pinch. .. code-block:: pytest $ pytest -q test_emaillib.py - . [100%] - 1 passed in 0.12s + . [100%] + 1 passed in 0.12s + +Note on finalizer order +"""""""""""""""""""""""" + +Finalizers are executed in a first-in-last-out order. +For yield fixtures, the first teardown code to run is from the right-most fixture, i.e. the last test parameter. + +.. regendoc:wipe + +.. code-block:: python + + import pytest + + + def test_bar(fix_w_yield1, fix_w_yield2): + print("test_bar") + + + @pytest.fixture + def fix_w_yield1(): + yield + print("after_yield_1") + + + @pytest.fixture + def fix_w_yield2(): + yield + print("after_yield_2") + + +.. code-block:: pytest + + $ pytest test_module.py + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + collected 1 item + + test_module.py test_bar + .after_yield_2 + after_yield_1 + + + +For finalizers, the first fixture to run is last call to `request.addfinalizer`. + +.. code-block:: python + + import pytest + + + @pytest.fixture + def fix_w_finalizers(request): + request.addfinalizer(partial(print, "finalizer_2")) + request.addfinalizer(partial(print, "finalizer_1")) + + + def test_bar(fix_w_finalizers): + print("test_bar") + + +.. code-block:: pytest + + $ pytest test_module.py + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + collected 1 item + + test_module.py test_bar + .finalizer_1 + finalizer_2 + +This is so because yield fixtures use `addfinalizer` behind the scenes: when the fixture executes, `addfinalizer` registers a function that resumes the generator, which in turn calls the teardown code. + .. _`safe teardowns`: From 71b79fcda5313624544ae501a2045186c1c72244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20Kalm=C3=A1r?= Date: Fri, 26 Aug 2022 14:46:47 +0200 Subject: [PATCH 37/41] [7.1.x] Ignore editable installation modules --- changelog/10230.bugfix.rst | 1 + src/_pytest/config/__init__.py | 3 ++- testing/test_config.py | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelog/10230.bugfix.rst diff --git a/changelog/10230.bugfix.rst b/changelog/10230.bugfix.rst new file mode 100644 index 00000000000..6ca2b00bf89 --- /dev/null +++ b/changelog/10230.bugfix.rst @@ -0,0 +1 @@ +Ignore ``.py`` files created by ``pyproject.toml``-based editable builds introduced in `pip 21.3 `__. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 91ad3f094ff..53f40c24571 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -833,7 +833,8 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: if is_simple_module: module_name, _ = os.path.splitext(fn) # we ignore "setup.py" at the root of the distribution - if module_name != "setup": + # as well as editable installation finder modules made by setuptools + if module_name != "setup" and not module_name.startswith("__editable__"): seen_some = True yield module_name elif is_package: diff --git a/testing/test_config.py b/testing/test_config.py index 6784809e097..3653ce47fa3 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -837,6 +837,9 @@ def test_confcutdir_check_isdir(self, pytester: Pytester) -> None: (["src/bar/__init__.py"], ["bar"]), (["src/bar/__init__.py", "setup.py"], ["bar"]), (["source/python/bar/__init__.py", "setup.py"], ["bar"]), + # editable installation finder modules + (["__editable___xyz_finder.py"], []), + (["bar/__init__.py", "__editable___xyz_finder.py"], ["bar"]), ], ) def test_iter_rewritable_modules(self, names, expected) -> None: From aae93d6127c43a7f9036556ba7482019d389e21d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 26 Aug 2022 09:57:18 -0300 Subject: [PATCH 38/41] Ignore type-errors related to attr.asdict --- src/_pytest/reports.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 725fdf61739..3c58321e706 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -455,7 +455,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: def serialize_repr_entry( entry: Union[ReprEntry, ReprEntryNative] ) -> Dict[str, Any]: - data = attr.asdict(entry) + data = attr.asdict(entry) # type:ignore[arg-type] for key, value in data.items(): if hasattr(value, "__dict__"): data[key] = attr.asdict(value) @@ -463,7 +463,7 @@ def serialize_repr_entry( return entry_data def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: - result = attr.asdict(reprtraceback) + result = attr.asdict(reprtraceback) # type:ignore[arg-type] result["reprentries"] = [ serialize_repr_entry(x) for x in reprtraceback.reprentries ] @@ -473,7 +473,7 @@ def serialize_repr_crash( reprcrash: Optional[ReprFileLocation], ) -> Optional[Dict[str, Any]]: if reprcrash is not None: - return attr.asdict(reprcrash) + return attr.asdict(reprcrash) # type:ignore[arg-type] else: return None From fc0e024b118fa63e84637bd5c9242b2b382e58fd Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 31 Aug 2022 14:32:36 -0300 Subject: [PATCH 39/41] [7.1.x] Fix regendoc --- doc/en/how-to/fixtures.rst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index f5217cc5560..6fab0b942d0 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -742,10 +742,10 @@ Note on finalizer order Finalizers are executed in a first-in-last-out order. For yield fixtures, the first teardown code to run is from the right-most fixture, i.e. the last test parameter. -.. regendoc:wipe .. code-block:: python + # content of test_finalizers.py import pytest @@ -767,12 +767,12 @@ For yield fixtures, the first teardown code to run is from the right-most fixtur .. code-block:: pytest - $ pytest test_module.py + $ pytest -s test_finalizers.py =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y collected 1 item - test_module.py test_bar + test_finalizers.py test_bar .after_yield_2 after_yield_1 @@ -782,6 +782,8 @@ For finalizers, the first fixture to run is last call to `request.addfinalizer`. .. code-block:: python + # content of test_finalizers.py + from functools import partial import pytest @@ -797,12 +799,12 @@ For finalizers, the first fixture to run is last call to `request.addfinalizer`. .. code-block:: pytest - $ pytest test_module.py + $ pytest -s test_finalizers.py =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y collected 1 item - test_module.py test_bar + test_finalizers.py test_bar .finalizer_1 finalizer_2 @@ -1412,6 +1414,8 @@ Running the above tests results in the following test IDs being used: + + From fadfb4f3463bc828535f86682300907a30f240e9 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Wed, 31 Aug 2022 17:49:55 +0000 Subject: [PATCH 40/41] Prepare release version 7.1.3 --- changelog/10060.bugfix.rst | 1 - changelog/10114.trivial.rst | 1 - changelog/10190.bugfix.rst | 1 - changelog/10230.bugfix.rst | 1 - changelog/3396.bugfix.rst | 1 - changelog/9514.bugfix.rst | 1 - changelog/9791.bugfix.rst | 1 - changelog/9917.bugfix.rst | 1 - changelog/9937.doc.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-7.1.3.rst | 28 ++++++++ doc/en/builtin.rst | 102 ++++++++++++++++++++++++------ doc/en/changelog.rst | 41 ++++++++++++ doc/en/getting-started.rst | 2 +- doc/en/how-to/fixtures.rst | 12 +++- 15 files changed, 164 insertions(+), 31 deletions(-) delete mode 100644 changelog/10060.bugfix.rst delete mode 100644 changelog/10114.trivial.rst delete mode 100644 changelog/10190.bugfix.rst delete mode 100644 changelog/10230.bugfix.rst delete mode 100644 changelog/3396.bugfix.rst delete mode 100644 changelog/9514.bugfix.rst delete mode 100644 changelog/9791.bugfix.rst delete mode 100644 changelog/9917.bugfix.rst delete mode 100644 changelog/9937.doc.rst create mode 100644 doc/en/announce/release-7.1.3.rst diff --git a/changelog/10060.bugfix.rst b/changelog/10060.bugfix.rst deleted file mode 100644 index c8941820eca..00000000000 --- a/changelog/10060.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -When running with ``--pdb``, ``TestCase.tearDown`` is no longer called for tests when the *class* has been skipped via ``unittest.skip`` or ``pytest.mark.skip``. diff --git a/changelog/10114.trivial.rst b/changelog/10114.trivial.rst deleted file mode 100644 index 42c43a1d0d6..00000000000 --- a/changelog/10114.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Replace `atomicwrites `__ dependency on windows with `os.replace`. diff --git a/changelog/10190.bugfix.rst b/changelog/10190.bugfix.rst deleted file mode 100644 index 731d0efad9a..00000000000 --- a/changelog/10190.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Invalid XML characters in setup or teardown error messages are now properly escaped for JUnit XML reports. diff --git a/changelog/10230.bugfix.rst b/changelog/10230.bugfix.rst deleted file mode 100644 index 6ca2b00bf89..00000000000 --- a/changelog/10230.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Ignore ``.py`` files created by ``pyproject.toml``-based editable builds introduced in `pip 21.3 `__. diff --git a/changelog/3396.bugfix.rst b/changelog/3396.bugfix.rst deleted file mode 100644 index aa3f278c2a6..00000000000 --- a/changelog/3396.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Doctests now respect the ``--import-mode`` flag. diff --git a/changelog/9514.bugfix.rst b/changelog/9514.bugfix.rst deleted file mode 100644 index a940b5c72d5..00000000000 --- a/changelog/9514.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Type-annotate ``FixtureRequest.param`` as ``Any`` as a stop gap measure until :issue:`8073` is fixed. diff --git a/changelog/9791.bugfix.rst b/changelog/9791.bugfix.rst deleted file mode 100644 index f81c870165b..00000000000 --- a/changelog/9791.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a path handling code in ``rewrite.py`` that seems to work fine, but was incorrect and fails in some systems. diff --git a/changelog/9917.bugfix.rst b/changelog/9917.bugfix.rst deleted file mode 100644 index ac0616d2ee7..00000000000 --- a/changelog/9917.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed string representation for :func:`pytest.approx` when used to compare tuples. diff --git a/changelog/9937.doc.rst b/changelog/9937.doc.rst deleted file mode 100644 index 326d20ec8f6..00000000000 --- a/changelog/9937.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Explicit note that :fixture:`tmpdir` fixture is discouraged in favour of :fixture:`tmp_path`. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index bcc0b45664b..142425cdee7 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-7.1.3 release-7.1.2 release-7.1.1 release-7.1.0 diff --git a/doc/en/announce/release-7.1.3.rst b/doc/en/announce/release-7.1.3.rst new file mode 100644 index 00000000000..4cb1b271704 --- /dev/null +++ b/doc/en/announce/release-7.1.3.rst @@ -0,0 +1,28 @@ +pytest-7.1.3 +======================================= + +pytest 7.1.3 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Anthony Sottile +* Bruno Oliveira +* Gergely Kalmár +* Nipunn Koorapati +* Pax +* Sviatoslav Sydorenko +* Tim Hoffmann +* Tony Narlock +* Wolfremium +* Zach OBrien +* aizpurua23a + + +Happy testing, +The pytest Development Team diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 77ae5a13028..7e22002245d 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -40,32 +40,86 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. - capsysbinary -- .../_pytest/capture.py:895 + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_output(capsys): + print("hello") + captured = capsys.readouterr() + assert captured.out == "hello\n" + + capsysbinary -- .../_pytest/capture.py:906 Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``bytes`` objects. - capfd -- .../_pytest/capture.py:912 + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + + .. code-block:: python + + def test_output(capsysbinary): + print("hello") + captured = capsysbinary.readouterr() + assert captured.out == b"hello\n" + + capfd -- .../_pytest/capture.py:934 Enable text capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text`` objects. - capfdbinary -- .../_pytest/capture.py:929 + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_system_echo(capfd): + os.system('echo "hello"') + captured = capfd.readouterr() + assert captured.out == "hello\n" + + capfdbinary -- .../_pytest/capture.py:962 Enable bytes capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method calls, which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``byte`` objects. - doctest_namespace [session scope] -- .../_pytest/doctest.py:731 + Returns an instance of :class:`CaptureFixture[bytes] `. + + Example: + + .. code-block:: python + + def test_system_echo(capfdbinary): + os.system('echo "hello"') + captured = capfdbinary.readouterr() + assert captured.out == b"hello\n" + + doctest_namespace [session scope] -- .../_pytest/doctest.py:735 Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. - pytestconfig [session scope] -- .../_pytest/fixtures.py:1334 + Usually this fixture is used in conjunction with another ``autouse`` fixture: + + .. code-block:: python + + @pytest.fixture(autouse=True) + def add_np(doctest_namespace): + doctest_namespace["np"] = numpy + + For more details: :ref:`doctest_namespace`. + + pytestconfig [session scope] -- .../_pytest/fixtures.py:1344 Session-scoped fixture that returns the session's :class:`pytest.Config` object. @@ -117,10 +171,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a `pytest-xdist `__ plugin. See :issue:`7767` for details. - tmpdir_factory [session scope] -- .../_pytest/legacypath.py:295 + tmpdir_factory [session scope] -- .../_pytest/legacypath.py:302 Return a :class:`pytest.TempdirFactory` instance for the test session. - tmpdir -- .../_pytest/legacypath.py:302 + tmpdir -- .../_pytest/legacypath.py:309 Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. @@ -132,6 +186,11 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a The returned object is a `legacy_path`_ object. + .. note:: + These days, it is preferred to use ``tmp_path``. + + :ref:`About the tmpdir and tmpdir_factory fixtures`. + .. _legacy_path: https://py.readthedocs.io/en/latest/path.html caplog -- .../_pytest/logging.py:487 @@ -148,21 +207,26 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a monkeypatch -- .../_pytest/monkeypatch.py:29 A convenient fixture for monkey-patching. - The fixture provides these methods to modify objects, dictionaries or - os.environ:: + The fixture provides these methods to modify objects, dictionaries, or + :data:`os.environ`: - monkeypatch.setattr(obj, name, value, raising=True) - monkeypatch.delattr(obj, name, raising=True) - monkeypatch.setitem(mapping, name, value) - monkeypatch.delitem(obj, name, raising=True) - monkeypatch.setenv(name, value, prepend=None) - monkeypatch.delenv(name, raising=True) - monkeypatch.syspath_prepend(path) - monkeypatch.chdir(path) + * :meth:`monkeypatch.setattr(obj, name, value, raising=True) ` + * :meth:`monkeypatch.delattr(obj, name, raising=True) ` + * :meth:`monkeypatch.setitem(mapping, name, value) ` + * :meth:`monkeypatch.delitem(obj, name, raising=True) ` + * :meth:`monkeypatch.setenv(name, value, prepend=None) ` + * :meth:`monkeypatch.delenv(name, raising=True) ` + * :meth:`monkeypatch.syspath_prepend(path) ` + * :meth:`monkeypatch.chdir(path) ` + * :meth:`monkeypatch.context() ` All modifications will be undone after the requesting test function or - fixture has finished. The ``raising`` parameter determines if a KeyError - or AttributeError will be raised if the set/deletion operation has no target. + fixture has finished. The ``raising`` parameter determines if a :class:`KeyError` + or :class:`AttributeError` will be raised if the set/deletion operation does not have the + specified target. + + To undo modifications done by the fixture in a contained scope, + use :meth:`context() `. recwarn -- .../_pytest/recwarn.py:29 Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 01c6d6983a2..e60b08c555e 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,47 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 7.1.3 (2022-08-31) +========================= + +Bug Fixes +--------- + +- `#10060 `_: When running with ``--pdb``, ``TestCase.tearDown`` is no longer called for tests when the *class* has been skipped via ``unittest.skip`` or ``pytest.mark.skip``. + + +- `#10190 `_: Invalid XML characters in setup or teardown error messages are now properly escaped for JUnit XML reports. + + +- `#10230 `_: Ignore ``.py`` files created by ``pyproject.toml``-based editable builds introduced in `pip 21.3 `__. + + +- `#3396 `_: Doctests now respect the ``--import-mode`` flag. + + +- `#9514 `_: Type-annotate ``FixtureRequest.param`` as ``Any`` as a stop gap measure until :issue:`8073` is fixed. + + +- `#9791 `_: Fixed a path handling code in ``rewrite.py`` that seems to work fine, but was incorrect and fails in some systems. + + +- `#9917 `_: Fixed string representation for :func:`pytest.approx` when used to compare tuples. + + + +Improved Documentation +---------------------- + +- `#9937 `_: Explicit note that :fixture:`tmpdir` fixture is discouraged in favour of :fixture:`tmp_path`. + + + +Trivial/Internal Changes +------------------------ + +- `#10114 `_: Replace `atomicwrites `__ dependency on windows with `os.replace`. + + pytest 7.1.2 (2022-04-23) ========================= diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index f7e03cc8b67..ce8ce815260 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -22,7 +22,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 7.1.2 + pytest 7.1.3 .. _`simpletest`: diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 6fab0b942d0..7c0087ae3cc 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -733,6 +733,8 @@ does offer some nuances for when you're in a pinch. .. code-block:: pytest $ pytest -q test_emaillib.py + . [100%] + 1 passed in 0.12s . [100%] 1 passed in 0.12s @@ -770,6 +772,7 @@ For yield fixtures, the first teardown code to run is from the right-most fixtur $ pytest -s test_finalizers.py =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + rootdir: /home/sweet/project collected 1 item test_finalizers.py test_bar @@ -777,6 +780,7 @@ For yield fixtures, the first teardown code to run is from the right-most fixtur after_yield_1 + ============================ 1 passed in 0.12s ============================= For finalizers, the first fixture to run is last call to `request.addfinalizer`. @@ -802,12 +806,16 @@ For finalizers, the first fixture to run is last call to `request.addfinalizer`. $ pytest -s test_finalizers.py =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + rootdir: /home/sweet/project collected 1 item test_finalizers.py test_bar .finalizer_1 finalizer_2 + + ============================ 1 passed in 0.12s ============================= + This is so because yield fixtures use `addfinalizer` behind the scenes: when the fixture executes, `addfinalizer` registers a function that resumes the generator, which in turn calls the teardown code. @@ -1407,7 +1415,7 @@ Running the above tests results in the following test IDs being used: =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y rootdir: /home/sweet/project - collected 11 items + collected 12 items @@ -1427,7 +1435,7 @@ Running the above tests results in the following test IDs being used: - ======================= 11 tests collected in 0.12s ======================== + ======================= 12 tests collected in 0.12s ======================== .. _`fixture-parametrize-marks`: From 4645bcd44915c2fd6043b101626e5bf1a983ac07 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 31 Aug 2022 14:53:40 -0300 Subject: [PATCH 41/41] Remove incorrect output in how-to/fixtures.rst --- doc/en/how-to/fixtures.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 7c0087ae3cc..368dc04469f 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -735,8 +735,6 @@ does offer some nuances for when you're in a pinch. $ pytest -q test_emaillib.py . [100%] 1 passed in 0.12s - . [100%] - 1 passed in 0.12s Note on finalizer order """"""""""""""""""""""""