From 9bce91ec7a0b97aafcb3dff19842a29c1ff25287 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 18 Jun 2025 10:14:59 +0300 Subject: [PATCH 01/16] Merge pull request #13524 from bluetech/gh-release ci: replace softprops/action-gh-release with `gh release create` command (cherry picked from commit 0682cbe50641e56a3a4b0a4a2ada938cbcdd668d) --- .github/workflows/deploy.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9e63bc68cfd..ba593c7c3a9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -109,8 +109,8 @@ jobs: tox -e generate-gh-release-notes -- "$VERSION" scripts/latest-release-notes.md - name: Publish GitHub Release - uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 - with: - body_path: scripts/latest-release-notes.md - files: dist/* - tag_name: ${{ github.event.inputs.version }} + env: + VERSION: ${{ github.event.inputs.version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create --notes-file scripts/latest-release-notes.md --verify-tag "$VERSION" dist/* From de35402caed866ce3dd552bcc50a80f5385c8219 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 18 Jun 2025 11:27:51 +0300 Subject: [PATCH 02/16] Merge pull request #13481 from mgorny/pass-werror testing: Explicitly pass `-Werror` to tests needing it (cherry picked from commit 667398f53b7ca9a19a540054b03d505b84379114) --- changelog/13480.bugfix.rst | 1 + testing/test_threadexception.py | 2 +- testing/test_warnings.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelog/13480.bugfix.rst diff --git a/changelog/13480.bugfix.rst b/changelog/13480.bugfix.rst new file mode 100644 index 00000000000..ed649a33516 --- /dev/null +++ b/changelog/13480.bugfix.rst @@ -0,0 +1 @@ +Fixed a few test failures in pytest's own test suite when run with ``-Wdefault`` or a similar override. diff --git a/testing/test_threadexception.py b/testing/test_threadexception.py index 5dad07b8b85..f4595ec435d 100644 --- a/testing/test_threadexception.py +++ b/testing/test_threadexception.py @@ -211,7 +211,7 @@ def test_it(request): """ ) - result = pytester.runpytest() + result = pytester.runpytest("-Werror") # TODO: should be a test failure or error assert result.ret == pytest.ExitCode.INTERNAL_ERROR diff --git a/testing/test_warnings.py b/testing/test_warnings.py index c302e7c6e3c..b1c64dc9332 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -149,6 +149,7 @@ def test_func(fix): ) +@pytest.mark.skip("issue #13485") def test_works_with_filterwarnings(pytester: Pytester) -> None: """Ensure our warnings capture does not mess with pre-installed filters (#2430).""" pytester.makepyfile( From 240454f253b819be994f3d509aa3e78f105b0829 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 06:17:52 -0300 Subject: [PATCH 03/16] prepare-release-pr: open PRs as draft (#13534) (#13539) This way we can trigger the CI jobs just by marking the draft as ready, similar to how the `update-plugin-list` workflow works. (cherry picked from commit d66a761dc91283556c768e59371dc62d6880c296) Co-authored-by: Bruno Oliveira --- scripts/prepare-release-pr.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py index c420a80b3d2..b288e3b3982 100644 --- a/scripts/prepare-release-pr.py +++ b/scripts/prepare-release-pr.py @@ -135,6 +135,7 @@ def prepare_release_pr(base_branch: str, is_major: bool, prerelease: str) -> Non f"--head={release_branch}", f"--title=Release {version}", f"--body={body}", + "--draft", ], check=True, ) From 5bc2f477e6fb39bce4931f26153af173c9aabe99 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Sun, 22 Jun 2025 15:44:16 +0000 Subject: [PATCH 04/16] Self-test: fix test_doctest_unexpected_exception in Python 3.14 (#13548) (#13552) Fixes #13547. (cherry picked from commit 48be3c8bce38bc950dba82747d3ecf451a3f45c8) Co-authored-by: bengartner --- changelog/13547.contrib.rst | 1 + testing/test_doctest.py | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelog/13547.contrib.rst diff --git a/changelog/13547.contrib.rst b/changelog/13547.contrib.rst new file mode 100644 index 00000000000..d8a240c0506 --- /dev/null +++ b/changelog/13547.contrib.rst @@ -0,0 +1 @@ +Self-testing: corrected expected message for ``test_doctest_unexpected_exception`` in Python ``3.14``. diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 1a852ebc82a..f4ed976c839 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -223,9 +223,12 @@ def test_doctest_unexpected_exception(self, pytester: Pytester): "002 >>> 0 / i", "UNEXPECTED EXCEPTION: ZeroDivisionError*", "Traceback (most recent call last):", - ' File "*/doctest.py", line *, in __run', - " *", - *((" *^^^^*", " *", " *") if sys.version_info >= (3, 13) else ()), + *( + (' File "*/doctest.py", line *, in __run', " *") + if sys.version_info <= (3, 14) + else () + ), + *((" *^^^^*", " *", " *") if sys.version_info[:2] == (3, 13) else ()), ' File "", line 1, in ', "ZeroDivisionError: division by zero", "*/test_doctest_unexpected_exception.txt:2: UnexpectedException", From 242522a10f59e4051c1f11a25c001fb2755eb2b1 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Sun, 22 Jun 2025 16:13:14 +0000 Subject: [PATCH 05/16] Defer annotation eval on Python 3.14 (#13550) (#13553) Fixes #13549 (cherry picked from commit 8ca783cf4a3b4007f513a7446b7069d4dac52587) Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- changelog/13549.bugfix.rst | 3 +++ src/_pytest/compat.py | 13 ++++++++++++- src/_pytest/fixtures.py | 5 +++-- src/_pytest/nodes.py | 2 +- testing/test_collection.py | 40 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 changelog/13549.bugfix.rst diff --git a/changelog/13549.bugfix.rst b/changelog/13549.bugfix.rst new file mode 100644 index 00000000000..e69f6a4d6cf --- /dev/null +++ b/changelog/13549.bugfix.rst @@ -0,0 +1,3 @@ +No longer evaluate type annotations in Python ``3.14`` when inspecting function signatures. + +This prevents crashes during module collection when modules do not explicitly use ``from __future__ import annotations`` and import types for annotations within a ``if TYPE_CHECKING:`` block. diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 7d71838be51..bef8c317bb9 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -8,7 +8,7 @@ import functools import inspect from inspect import Parameter -from inspect import signature +from inspect import Signature import os from pathlib import Path import sys @@ -19,6 +19,10 @@ import py +if sys.version_info >= (3, 14): + from annotationlib import Format + + #: constant to prepare valuing pylib path replacements/lazy proxies later on # intended for removal in pytest 8.0 or 9.0 @@ -60,6 +64,13 @@ def is_async_function(func: object) -> bool: return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) +def signature(obj: Callable[..., Any]) -> Signature: + """Return signature without evaluating annotations.""" + if sys.version_info >= (3, 14): + return inspect.signature(obj, annotation_format=Format.STRING) + return inspect.signature(obj) + + def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str: function = get_real_func(function) fn = Path(inspect.getfile(function)) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 92a301e79db..421237e35a0 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -49,6 +49,7 @@ from _pytest.compat import NotSetType from _pytest.compat import safe_getattr from _pytest.compat import safe_isclass +from _pytest.compat import signature from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config import ExitCode @@ -804,8 +805,8 @@ def _format_fixturedef_line(self, fixturedef: FixtureDef[object]) -> str: path, lineno = getfslineno(factory) if isinstance(path, Path): path = bestrelpath(self._pyfuncitem.session.path, path) - signature = inspect.signature(factory) - return f"{path}:{lineno + 1}: def {factory.__name__}{signature}" + sig = signature(factory) + return f"{path}:{lineno + 1}: def {factory.__name__}{sig}" def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._fixturedef.addfinalizer(finalizer) diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 6d39de95f5b..6690f6ab1f8 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -8,7 +8,6 @@ from collections.abc import MutableMapping from functools import cached_property from functools import lru_cache -from inspect import signature import os import pathlib from pathlib import Path @@ -29,6 +28,7 @@ from _pytest._code.code import Traceback from _pytest._code.code import TracebackStyle from _pytest.compat import LEGACY_PATH +from _pytest.compat import signature from _pytest.config import Config from _pytest.config import ConftestImportFailure from _pytest.config.compat import _check_path diff --git a/testing/test_collection.py b/testing/test_collection.py index ccd57eeef43..7cb20734e0d 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1895,3 +1895,43 @@ def test_with_yield(): ) # Assert that no tests were collected result.stdout.fnmatch_lines(["*collected 0 items*"]) + + +def test_annotations_deferred_future(pytester: Pytester): + """Ensure stringified annotations don't raise any errors.""" + pytester.makepyfile( + """ + from __future__ import annotations + import pytest + + @pytest.fixture + def func() -> X: ... # X is undefined + + def test_func(): + assert True + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + result.stdout.fnmatch_lines(["*1 passed*"]) + + +@pytest.mark.skipif( + sys.version_info < (3, 14), reason="Annotations are only skipped on 3.14+" +) +def test_annotations_deferred_314(pytester: Pytester): + """Ensure annotation eval is deferred.""" + pytester.makepyfile( + """ + import pytest + + @pytest.fixture + def func() -> X: ... # X is undefined + + def test_func(): + assert True + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + result.stdout.fnmatch_lines(["*1 passed*"]) From 7b21af10940143b74bb996c928b77bcb2d354c46 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Sun, 22 Jun 2025 14:07:00 -0300 Subject: [PATCH 06/16] Fix `approx` usage with `decimal.FloatOperation` trap set (#13543) (#13555) Fixes #13530 --------- (cherry picked from commit 111685cc506fbaf87c105e67d68fbd881a47a84b) Co-authored-by: Aditi De <92822822+coder-aditi@users.noreply.github.com> Co-authored-by: AD Co-authored-by: Bruno Oliveira --- changelog/13530.bugfix.rst | 1 + src/_pytest/python_api.py | 19 +++++++++++++++++++ testing/python/approx.py | 6 ++++++ 3 files changed, 26 insertions(+) create mode 100644 changelog/13530.bugfix.rst diff --git a/changelog/13530.bugfix.rst b/changelog/13530.bugfix.rst new file mode 100644 index 00000000000..1a5ab365f12 --- /dev/null +++ b/changelog/13530.bugfix.rst @@ -0,0 +1 @@ +Fixed a crash when using :func:`pytest.approx` and :class:`decimal.Decimal` instances with the :class:`decimal.FloatOperation` trap set. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 77b0edc0ac5..31a366251cf 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -531,6 +531,25 @@ class ApproxDecimal(ApproxScalar): DEFAULT_ABSOLUTE_TOLERANCE = Decimal("1e-12") DEFAULT_RELATIVE_TOLERANCE = Decimal("1e-6") + def __repr__(self) -> str: + if isinstance(self.rel, float): + rel = Decimal.from_float(self.rel) + else: + rel = self.rel + + if isinstance(self.abs, float): + abs_ = Decimal.from_float(self.abs) + else: + abs_ = self.abs + + tol_str = "???" + if rel is not None and Decimal("1e-3") <= rel <= Decimal("1e3"): + tol_str = f"{rel:.1e}" + elif abs_ is not None: + tol_str = f"{abs_:.1e}" + + return f"{self.expected} ± {tol_str}" + def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: """Assert that two numbers (or two ordered sequences of numbers) are equal to each other diff --git a/testing/python/approx.py b/testing/python/approx.py index 75b57b6965c..2ca7ee70945 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -2,6 +2,7 @@ from __future__ import annotations from contextlib import contextmanager +import decimal from decimal import Decimal from fractions import Fraction from math import sqrt @@ -1015,6 +1016,11 @@ def __len__(self): expected_repr = "approx([1 ± 1.0e-06, 2 ± 2.0e-06, 3 ± 3.0e-06, 4 ± 4.0e-06])" assert repr(approx(expected)) == expected_repr + def test_decimal_approx_repr(self, monkeypatch) -> None: + monkeypatch.setitem(decimal.getcontext().traps, decimal.FloatOperation, True) + approx_obj = pytest.approx(decimal.Decimal("2.60")) + assert decimal.Decimal("2.600001") == approx_obj + 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"): From 586bef6fb431c93e7fe99abdf281db8ea481e9f3 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 08:16:13 -0300 Subject: [PATCH 07/16] Rename 13480.bugfix to 13480.contrib (#13554) (#13561) Since this is related only to internal testing and does not affect end-users, it is more appropriate to announce it in the "Contributor" section of the changelog. Follow up to #13481. (cherry picked from commit cc580b53beb09ab8385c9fa4c9532aa90a294ded) Co-authored-by: Bruno Oliveira --- changelog/13480.bugfix.rst | 1 - changelog/13480.contrib.rst | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 changelog/13480.bugfix.rst create mode 100644 changelog/13480.contrib.rst diff --git a/changelog/13480.bugfix.rst b/changelog/13480.bugfix.rst deleted file mode 100644 index ed649a33516..00000000000 --- a/changelog/13480.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a few test failures in pytest's own test suite when run with ``-Wdefault`` or a similar override. diff --git a/changelog/13480.contrib.rst b/changelog/13480.contrib.rst new file mode 100644 index 00000000000..9079c6f6b5a --- /dev/null +++ b/changelog/13480.contrib.rst @@ -0,0 +1 @@ +Self-testing: fixed a few test failures when run with ``-Wdefault`` or a similar override. From d879a523e2c14e6b6cee5fed288ce0d46925dcb4 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:05:43 +0000 Subject: [PATCH 08/16] Add `int` and `float` variants to Parser.addini (#13560) (#13562) Fixes #13559. --------- (cherry picked from commit ae7346114d65e3a2c0ccd77af8a0ca00012cd91d) Co-authored-by: NayeemJohn <44828643+NayeemJohnY@users.noreply.github.com> Co-authored-by: Bruno Oliveira --- changelog/13559.bugfix.rst | 1 + src/_pytest/config/argparsing.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changelog/13559.bugfix.rst diff --git a/changelog/13559.bugfix.rst b/changelog/13559.bugfix.rst new file mode 100644 index 00000000000..69036f784ac --- /dev/null +++ b/changelog/13559.bugfix.rst @@ -0,0 +1 @@ +Added missing `int` and `float` variants to the `Literal` type annotation of the `type` parameter in :meth:`pytest.Parser.addini`. diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 948dfe8a510..8d4ed823325 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -174,7 +174,9 @@ def addini( self, name: str, help: str, - type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] + type: Literal[ + "string", "paths", "pathlist", "args", "linelist", "bool", "int", "float" + ] | None = None, default: Any = NOT_SET, ) -> None: From cdf9f6b87fb0f826bfa1f778fbaa74f8838b4a99 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:53:44 +0000 Subject: [PATCH 09/16] pytest.approx: import numpy conditionally (#13563) (#13566) This matches existing conditional-import behavior in `pytest.approx`, and improves runtime for users of `pytest.approx` and not `numpy`. (cherry picked from commit ecc55b435ffd8be19146c75b215886d38421beed) Co-authored-by: Liam DeVoe --- AUTHORS | 1 + changelog/13563.bugfix.rst | 1 + src/_pytest/python_api.py | 7 ++----- 3 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 changelog/13563.bugfix.rst diff --git a/AUTHORS b/AUTHORS index e5b863e71f1..df338aa8c6e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -261,6 +261,7 @@ Leonardus Chen Lev Maximov Levon Saldamli Lewis Cowles +Liam DeVoe Llandy Riveron Del Risco Loic Esteve lovetheguitar diff --git a/changelog/13563.bugfix.rst b/changelog/13563.bugfix.rst new file mode 100644 index 00000000000..543552e20cf --- /dev/null +++ b/changelog/13563.bugfix.rst @@ -0,0 +1 @@ +:func:`pytest.approx` now only imports ``numpy`` if NumPy is already in ``sys.modules``. This fixes unconditional import behavior introduced in `8.4.0`. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 31a366251cf..74346c34976 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -426,12 +426,9 @@ def is_bool(val: Any) -> bool: # Check if `val` is a native bool or numpy bool. if isinstance(val, bool): return True - try: - import numpy as np - + if np := sys.modules.get("numpy"): return isinstance(val, np.bool_) - except ImportError: - return False + return False asarray = _as_numpy_array(actual) if asarray is not None: From f87289c36c8dbe7740e3020f5546b6f8b0861ff0 Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 21:34:44 +0000 Subject: [PATCH 10/16] Fix crash with `times` output style and skipped module (#13573) (#13579) Fixes #13478 --------- (cherry picked from commit c5a75f2498c86850c4ce13bcf10d56efc92394a4) Co-authored-by: Aditi De <92822822+coder-aditi@users.noreply.github.com> Co-authored-by: AD Co-authored-by: Bruno Oliveira --- changelog/13478.bugfix.rst | 1 + src/_pytest/terminal.py | 4 +++- testing/test_terminal.py | 25 +++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 changelog/13478.bugfix.rst diff --git a/changelog/13478.bugfix.rst b/changelog/13478.bugfix.rst new file mode 100644 index 00000000000..1147ee54c9e --- /dev/null +++ b/changelog/13478.bugfix.rst @@ -0,0 +1 @@ +Fixed a crash when using :confval:`console_output_style` with ``times`` and a module is skipped. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 5f27c46b41e..a95f79ba6b6 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -734,7 +734,9 @@ def _get_progress_information_message(self) -> str: last_in_module = tests_completed == tests_in_module if self.showlongtestinfo or last_in_module: self._timing_nodeids_reported.update(r.nodeid for r in not_reported) - return format_node_duration(sum(r.duration for r in not_reported)) + return format_node_duration( + sum(r.duration for r in not_reported if isinstance(r, TestReport)) + ) return "" if collected: return f" [{len(self._progress_nodeids_reported) * 100 // collected:3d}%]" diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 3ea10195c6b..8c50311e9aa 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -112,6 +112,31 @@ def test_func(): [" def test_func():", "> assert 0", "E assert 0"] ) + def test_console_output_style_times_with_skipped_and_passed( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + test_repro=""" + def test_hello(): + pass + """, + test_repro_skip=""" + import pytest + pytest.importorskip("fakepackage_does_not_exist") + """, + ) + result = pytester.runpytest( + "test_repro.py", + "test_repro_skip.py", + "-o", + "console_output_style=times", + ) + + result.stdout.fnmatch_lines("* 1 passed, 1 skipped in *") + + combined = "\n".join(result.stdout.lines + result.stderr.lines) + assert "INTERNALERROR" not in combined + def test_internalerror(self, pytester: Pytester, linecomp) -> None: modcol = pytester.getmodulecol("def test_one(): pass") rep = TerminalReporter(modcol.config, file=linecomp.stringio) From dc6e3be2ddc75a149b6d102d9b7c82ee47a00cfa Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 18 Jul 2025 22:18:13 +0200 Subject: [PATCH 11/16] Merge pull request #13605 from The-Compiler/training-update-2025-07 doc: Update trainings (cherry picked from commit 5f993856350d1cff144e48810b1c0137c8ec45c5) --- doc/en/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index fb5d0482c0d..2b58bebc20f 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -2,7 +2,7 @@ .. sidebar:: **Next Open Trainings and Events** - - `pytest - simple, rapid and fun testing with Python `_, at `EuroPython 2025 `_, **July 14th** (3h), Prague, Czech Republic + - `Testen mit pytest `_ (German), via `Letsboot `_ (3 day in-depth training), **October 29th -- 31st**, Zurich (CH) - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training), **March 3th -- 5th 2026**, Leipzig (DE) / Remote Also see :doc:`previous talks and blogposts ` From 2c94c4a6948ba53440818389298157fa5d5f94cd Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 18:03:20 +0000 Subject: [PATCH 12/16] add missing colon (#13640) (#13641) (cherry picked from commit 03db745771b5304b45512bf41667adfab1fe8f7a) Co-authored-by: John Litborn <11260241+jakkdl@users.noreply.github.com> --- src/_pytest/raises.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/raises.py b/src/_pytest/raises.py index 480ae33647f..5a82f58e656 100644 --- a/src/_pytest/raises.py +++ b/src/_pytest/raises.py @@ -559,7 +559,7 @@ class RaisesExc(AbstractRaises[BaseExcT_co_default]): The type is checked with :func:`isinstance`, and does not need to be an exact match. If that is wanted you can use the ``check`` parameter. - :kwparam str | Pattern[str] match + :kwparam str | Pattern[str] match: A regex to match. :kwparam Callable[[BaseException], bool] check: From b7f05680d1301e0969b30bcb3c4b27433c9ee2b7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 30 Aug 2025 01:03:15 +0200 Subject: [PATCH 13/16] Merge pull request #13685 from CoretexShadow/fix/docs-pytest-generate-tests Docs+Tests: clarify special discovery of pytest_generate_tests (cherry picked from commit 12bde8af6dda3e7104f840209c199d151258b462) --- changelog/13577.doc.rst | 1 + doc/en/how-to/parametrize.rst | 7 +++++++ doc/en/how-to/writing_hook_functions.rst | 6 ++++++ 3 files changed, 14 insertions(+) create mode 100644 changelog/13577.doc.rst diff --git a/changelog/13577.doc.rst b/changelog/13577.doc.rst new file mode 100644 index 00000000000..8d6db9ea983 --- /dev/null +++ b/changelog/13577.doc.rst @@ -0,0 +1 @@ +Clarify that ``pytest_generate_tests`` is discovered in test modules/classes; other hooks must be in ``conftest.py`` or plugins. diff --git a/doc/en/how-to/parametrize.rst b/doc/en/how-to/parametrize.rst index d7c12c1a1f4..fe186146434 100644 --- a/doc/en/how-to/parametrize.rst +++ b/doc/en/how-to/parametrize.rst @@ -240,6 +240,13 @@ command line option and the parametrization of our test function: if "stringinput" in metafunc.fixturenames: metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput")) +.. note:: + + The :hook:`pytest_generate_tests` hook can also be implemented directly in a test + module or inside a test class; unlike other hooks, pytest will discover it there + as well. Other hooks must live in a :ref:`conftest.py ` or a plugin. + See :ref:`writinghooks`. + If we now pass two stringinput values, our test will run twice: .. code-block:: pytest diff --git a/doc/en/how-to/writing_hook_functions.rst b/doc/en/how-to/writing_hook_functions.rst index f4c00d04fda..cd18301ce84 100644 --- a/doc/en/how-to/writing_hook_functions.rst +++ b/doc/en/how-to/writing_hook_functions.rst @@ -235,6 +235,12 @@ Example: """ print(config.hook) +.. note:: + + Unlike other hooks, the :hook:`pytest_generate_tests` hook is also discovered when + defined inside a test module or test class. Other hooks must live in + :ref:`conftest.py plugins ` or external plugins. + See :ref:`parametrize-basics` and the :ref:`hook-reference`. .. _`addoptionhooks`: From 7723ce84b87ab08f86ddafcb342acc28ba5ec99d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 30 Aug 2025 01:03:57 +0200 Subject: [PATCH 14/16] Merge pull request #13683 from even-even/fix_Exeption_to_Exception_in_errorMessage fix: change Exeption to Exception in raises.py (cherry picked from commit e63f6e51c61fbe9ba6fb0acc436838d09fc5bf05) --- src/_pytest/raises.py | 2 +- testing/python/raises_group.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/raises.py b/src/_pytest/raises.py index 5a82f58e656..78fae6ddcde 100644 --- a/src/_pytest/raises.py +++ b/src/_pytest/raises.py @@ -457,7 +457,7 @@ def _parse_exc( return cast(type[BaseExcT_1], origin_exc) else: raise ValueError( - f"Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseExeption]` " + f"Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseException]` " f"are accepted as generic types but got `{exc}`. " f"As `raises` will catch all instances of the specified group regardless of the " f"generic argument specific nested exceptions has to be checked " diff --git a/testing/python/raises_group.py b/testing/python/raises_group.py index 04979c32e98..386e127a13d 100644 --- a/testing/python/raises_group.py +++ b/testing/python/raises_group.py @@ -1304,7 +1304,7 @@ def test_parametrizing_conditional_raisesgroup( def test_annotated_group() -> None: # repr depends on if exceptiongroup backport is being used or not t = repr(ExceptionGroup[ValueError]) - msg = "Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseExeption]` are accepted as generic types but got `{}`. As `raises` will catch all instances of the specified group regardless of the generic argument specific nested exceptions has to be checked with `RaisesGroup`." + msg = "Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseException]` are accepted as generic types but got `{}`. As `raises` will catch all instances of the specified group regardless of the generic argument specific nested exceptions has to be checked with `RaisesGroup`." fail_msg = wrap_escape(msg.format(t)) with pytest.raises(ValueError, match=fail_msg): From 89905381a163be30ae87d62e5f750e902d750c5f Mon Sep 17 00:00:00 2001 From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:03:21 -0300 Subject: [PATCH 15/16] Fix passenv CI in tox ini and make tests insensitive to the presence of the CI env variable (#13684) (#13694) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As discussed in https://github.com/pytest-dev/pytest/pull/13684#:~:text=See%3A%20%23-,13679%20(comment),-ogrisel%20added%204. --------- (cherry picked from commit bf92debb4fd874289f6aed29c6947b611b1de0ae) Co-authored-by: Olivier Grisel Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) --- changelog/13684.contrib.rst | 1 + pyproject.toml | 3 +++ testing/conftest.py | 12 ++++++++++++ testing/python/approx.py | 4 ++-- testing/test_faulthandler.py | 1 + tox.ini | 1 + 6 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 changelog/13684.contrib.rst diff --git a/changelog/13684.contrib.rst b/changelog/13684.contrib.rst new file mode 100644 index 00000000000..bb2f87f1710 --- /dev/null +++ b/changelog/13684.contrib.rst @@ -0,0 +1 @@ +Make pytest's own testsuite insensitive to the presence of the ``CI`` environment variable -- by :user:`ogrisel`. diff --git a/pyproject.toml b/pyproject.toml index 1e9665add02..a3c82db4381 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -426,6 +426,9 @@ markers = [ "slow", # experimental mark for all tests using pexpect "uses_pexpect", + # Disables the `remove_ci_env_var` autouse fixture on a given test that + # actually inspects whether the CI environment variable is set. + "keep_ci_var", ] [tool.towncrier] diff --git a/testing/conftest.py b/testing/conftest.py index 251b430e9cd..25abce913ea 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -237,3 +237,15 @@ def mock_timing(monkeypatch: MonkeyPatch): result = MockTiming() result.patch(monkeypatch) return result + + +@pytest.fixture(autouse=True) +def remove_ci_env_var(monkeypatch: MonkeyPatch, request: pytest.FixtureRequest) -> None: + """Make the test insensitive if it is running in CI or not. + + Use `@pytest.mark.keep_ci_var` in a test to avoid applying this fixture, letting the test + see the real `CI` variable (if present). + """ + has_keep_ci_mark = request.node.get_closest_marker("keep_ci_var") is not None + if not has_keep_ci_mark: + monkeypatch.delenv("CI", raising=False) diff --git a/testing/python/approx.py b/testing/python/approx.py index 2ca7ee70945..06633b544ec 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1085,10 +1085,10 @@ def test_map_over_nested_lists(self): ] def test_map_over_mixed_sequence(self): - assert _recursive_sequence_map(sqrt, [4, (25, 64), [(49)]]) == [ + assert _recursive_sequence_map(sqrt, [4, (25, 64), [49]]) == [ 2, (5, 8), - [(7)], + [7], ] def test_map_over_sequence_like(self): diff --git a/testing/test_faulthandler.py b/testing/test_faulthandler.py index c416e81d2d9..d89cb274b78 100644 --- a/testing/test_faulthandler.py +++ b/testing/test_faulthandler.py @@ -71,6 +71,7 @@ def test_disabled(): assert result.ret == 0 +@pytest.mark.keep_ci_var @pytest.mark.parametrize( "enabled", [ diff --git a/tox.ini b/tox.ini index f1283aa8260..3fe7865a289 100644 --- a/tox.ini +++ b/tox.ini @@ -52,6 +52,7 @@ passenv = PYTEST_ADDOPTS TERM SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST + CI setenv = _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:} {env:_PYTEST_FILES:} From bfae4224fd554d3d7f2c277a4cc092b6ec6af3ae Mon Sep 17 00:00:00 2001 From: pytest bot Date: Wed, 3 Sep 2025 14:00:04 +0000 Subject: [PATCH 16/16] Prepare release version 8.4.2 --- changelog/13478.bugfix.rst | 1 - changelog/13480.contrib.rst | 1 - changelog/13530.bugfix.rst | 1 - changelog/13547.contrib.rst | 1 - changelog/13549.bugfix.rst | 3 -- changelog/13559.bugfix.rst | 1 - changelog/13563.bugfix.rst | 1 - changelog/13577.doc.rst | 1 - changelog/13684.contrib.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-8.4.2.rst | 27 ++++++++++++++++++ doc/en/builtin.rst | 2 +- doc/en/changelog.rst | 43 +++++++++++++++++++++++++++++ doc/en/example/parametrize.rst | 6 ++-- doc/en/example/pythoncollection.rst | 4 +-- doc/en/getting-started.rst | 2 +- doc/en/how-to/fixtures.rst | 2 +- 17 files changed, 79 insertions(+), 19 deletions(-) delete mode 100644 changelog/13478.bugfix.rst delete mode 100644 changelog/13480.contrib.rst delete mode 100644 changelog/13530.bugfix.rst delete mode 100644 changelog/13547.contrib.rst delete mode 100644 changelog/13549.bugfix.rst delete mode 100644 changelog/13559.bugfix.rst delete mode 100644 changelog/13563.bugfix.rst delete mode 100644 changelog/13577.doc.rst delete mode 100644 changelog/13684.contrib.rst create mode 100644 doc/en/announce/release-8.4.2.rst diff --git a/changelog/13478.bugfix.rst b/changelog/13478.bugfix.rst deleted file mode 100644 index 1147ee54c9e..00000000000 --- a/changelog/13478.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a crash when using :confval:`console_output_style` with ``times`` and a module is skipped. diff --git a/changelog/13480.contrib.rst b/changelog/13480.contrib.rst deleted file mode 100644 index 9079c6f6b5a..00000000000 --- a/changelog/13480.contrib.rst +++ /dev/null @@ -1 +0,0 @@ -Self-testing: fixed a few test failures when run with ``-Wdefault`` or a similar override. diff --git a/changelog/13530.bugfix.rst b/changelog/13530.bugfix.rst deleted file mode 100644 index 1a5ab365f12..00000000000 --- a/changelog/13530.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a crash when using :func:`pytest.approx` and :class:`decimal.Decimal` instances with the :class:`decimal.FloatOperation` trap set. diff --git a/changelog/13547.contrib.rst b/changelog/13547.contrib.rst deleted file mode 100644 index d8a240c0506..00000000000 --- a/changelog/13547.contrib.rst +++ /dev/null @@ -1 +0,0 @@ -Self-testing: corrected expected message for ``test_doctest_unexpected_exception`` in Python ``3.14``. diff --git a/changelog/13549.bugfix.rst b/changelog/13549.bugfix.rst deleted file mode 100644 index e69f6a4d6cf..00000000000 --- a/changelog/13549.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -No longer evaluate type annotations in Python ``3.14`` when inspecting function signatures. - -This prevents crashes during module collection when modules do not explicitly use ``from __future__ import annotations`` and import types for annotations within a ``if TYPE_CHECKING:`` block. diff --git a/changelog/13559.bugfix.rst b/changelog/13559.bugfix.rst deleted file mode 100644 index 69036f784ac..00000000000 --- a/changelog/13559.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Added missing `int` and `float` variants to the `Literal` type annotation of the `type` parameter in :meth:`pytest.Parser.addini`. diff --git a/changelog/13563.bugfix.rst b/changelog/13563.bugfix.rst deleted file mode 100644 index 543552e20cf..00000000000 --- a/changelog/13563.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -:func:`pytest.approx` now only imports ``numpy`` if NumPy is already in ``sys.modules``. This fixes unconditional import behavior introduced in `8.4.0`. diff --git a/changelog/13577.doc.rst b/changelog/13577.doc.rst deleted file mode 100644 index 8d6db9ea983..00000000000 --- a/changelog/13577.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Clarify that ``pytest_generate_tests`` is discovered in test modules/classes; other hooks must be in ``conftest.py`` or plugins. diff --git a/changelog/13684.contrib.rst b/changelog/13684.contrib.rst deleted file mode 100644 index bb2f87f1710..00000000000 --- a/changelog/13684.contrib.rst +++ /dev/null @@ -1 +0,0 @@ -Make pytest's own testsuite insensitive to the presence of the ``CI`` environment variable -- by :user:`ogrisel`. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 2ed23eb1fbb..1cc8fbaf7c0 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-8.4.2 release-8.4.1 release-8.4.0 release-8.3.5 diff --git a/doc/en/announce/release-8.4.2.rst b/doc/en/announce/release-8.4.2.rst new file mode 100644 index 00000000000..58a842c4d4b --- /dev/null +++ b/doc/en/announce/release-8.4.2.rst @@ -0,0 +1,27 @@ +pytest-8.4.2 +======================================= + +pytest 8.4.2 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* AD +* Aditi De +* Bruno Oliveira +* Florian Bruhin +* John Litborn +* Liam DeVoe +* Marc Mueller +* NayeemJohn +* Olivier Grisel +* Ran Benita +* bengartner +* 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) + + +Happy testing, +The pytest Development Team diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 31c2fa9df06..ca350c5f3dd 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -142,7 +142,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a For more details: :ref:`doctest_namespace`. - pytestconfig [session scope] -- .../_pytest/fixtures.py:1424 + pytestconfig [session scope] -- .../_pytest/fixtures.py:1425 Session-scoped fixture that returns the session's :class:`pytest.Config` object. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 63623de8aad..89f1c4e61b3 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -31,6 +31,49 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 8.4.2 (2025-09-03) +========================= + +Bug fixes +--------- + +- `#13478 `_: Fixed a crash when using :confval:`console_output_style` with ``times`` and a module is skipped. + + +- `#13530 `_: Fixed a crash when using :func:`pytest.approx` and :class:`decimal.Decimal` instances with the :class:`decimal.FloatOperation` trap set. + + +- `#13549 `_: No longer evaluate type annotations in Python ``3.14`` when inspecting function signatures. + + This prevents crashes during module collection when modules do not explicitly use ``from __future__ import annotations`` and import types for annotations within a ``if TYPE_CHECKING:`` block. + + +- `#13559 `_: Added missing `int` and `float` variants to the `Literal` type annotation of the `type` parameter in :meth:`pytest.Parser.addini`. + + +- `#13563 `_: :func:`pytest.approx` now only imports ``numpy`` if NumPy is already in ``sys.modules``. This fixes unconditional import behavior introduced in `8.4.0`. + + + +Improved documentation +---------------------- + +- `#13577 `_: Clarify that ``pytest_generate_tests`` is discovered in test modules/classes; other hooks must be in ``conftest.py`` or plugins. + + + +Contributor-facing changes +-------------------------- + +- `#13480 `_: Self-testing: fixed a few test failures when run with ``-Wdefault`` or a similar override. + + +- `#13547 `_: Self-testing: corrected expected message for ``test_doctest_unexpected_exception`` in Python ``3.14``. + + +- `#13684 `_: Make pytest's own testsuite insensitive to the presence of the ``CI`` environment variable -- by :user:`ogrisel`. + + pytest 8.4.1 (2025-06-17) ========================= diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 8e6479254bb..6374e0edb3d 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -162,7 +162,7 @@ objects, they are still using the default pytest representation: rootdir: /home/sweet/project collected 8 items - + @@ -239,7 +239,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia rootdir: /home/sweet/project collected 4 items - + @@ -318,7 +318,7 @@ Let's first see how it looks like at collection time: rootdir: /home/sweet/project collected 2 items - + diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 01a8f48fdbf..2487e7b9d19 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -152,7 +152,7 @@ The test collection would look like this: configfile: pytest.ini collected 2 items - + @@ -215,7 +215,7 @@ You can always peek at the collection tree without running tests like this: configfile: pytest.ini collected 3 items - + diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index c5e9f708828..349711faaf4 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 8.4.1 + pytest 8.4.2 .. _`simpletest`: diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 62ace745d43..7f1a7610bb8 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -1423,7 +1423,7 @@ Running the above tests results in the following test IDs being used: rootdir: /home/sweet/project collected 12 items - +