From 47d7c14b45262ac9184d8151b7034add2226094b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=92=D0=B5=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B2?= Date: Mon, 13 Jul 2026 08:43:02 +0300 Subject: [PATCH 1/2] fix: unwrap fixture definitions for doctest locations Co-authored-by: OpenAI Codex --- changelog/14700.bugfix.rst | 1 + src/_pytest/doctest.py | 12 ++++++++++++ testing/test_doctest.py | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 changelog/14700.bugfix.rst diff --git a/changelog/14700.bugfix.rst b/changelog/14700.bugfix.rst new file mode 100644 index 00000000000..46847b59486 --- /dev/null +++ b/changelog/14700.bugfix.rst @@ -0,0 +1 @@ +Fixed an internal error on Python 3.12 when a doctest on a fixture-decorated function was skipped. diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index c5b8d09b09d..ea894b3f441 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -32,6 +32,7 @@ from _pytest.config import Config from _pytest.config.argparsing import Parser from _pytest.fixtures import fixture +from _pytest.fixtures import FixtureFunctionDefinition from _pytest.fixtures import TopRequest from _pytest.nodes import Collector from _pytest.nodes import Item @@ -529,6 +530,17 @@ def _find_lineno(self, obj, source_lines): obj, source_lines, ) + elif py_ver_info_minor == (3, 12): + + def _find_lineno(self, obj, source_lines): + if isinstance(obj, FixtureFunctionDefinition): + obj = inspect.unwrap(obj) + + # Type ignored because this is a private function. + return super()._find_lineno( # type:ignore[misc] + obj, + source_lines, + ) if sys.version_info < (3, 13): diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 9c788d0fc41..cfeedf0566f 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -875,6 +875,26 @@ def foo(x): reportinfo = items[0].reportinfo() assert reportinfo[1] == 1 + def test_fixture_doctest_skip_has_line_number(self, pytester: Pytester): + p = pytester.makepyfile( + test_fixture_doctest_skip=""" + import pytest + + @pytest.fixture + def unavailable(): + ''' + >>> getfixture("unavailable") + ''' + pytest.skip("unavailable") + """ + ) + items, _reprec = pytester.inline_genitems(p, "--doctest-modules") + assert items[0].reportinfo()[1] is not None + + result = pytester.runpytest(p, "--doctest-modules") + assert "INTERNALERROR" not in result.stdout.str() + result.assert_outcomes(skipped=1) + def test_valid_setup_py(self, pytester: Pytester): """ Test to make sure that pytest ignores valid setup.py files when ran From 66d43e13db015fd0ea62e3de3bfdd15e8b52e65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=92=D0=B5=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B2?= Date: Mon, 13 Jul 2026 09:24:01 +0300 Subject: [PATCH 2/2] test: limit doctest fixture regression to Python 3.12 --- testing/test_doctest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index cfeedf0566f..835288a1a30 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -875,6 +875,9 @@ def foo(x): reportinfo = items[0].reportinfo() assert reportinfo[1] == 1 + @pytest.mark.skipif( + sys.version_info < (3, 12), reason="requires Python 3.12 or later" + ) def test_fixture_doctest_skip_has_line_number(self, pytester: Pytester): p = pytester.makepyfile( test_fixture_doctest_skip="""