From 9cd14b4ffb1d96342a2e9d1f5cb92eab53bb5079 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 6 Feb 2024 23:09:00 +0200 Subject: [PATCH] doctest: fix autouse fixtures possibly not getting picked up Fix #11929. Figured out what's going on. We have the following collection tree: ``` ``` And the `test_main.py` contains an autouse fixture (`fake_game_ui`) that `doctest_main` needs in order to run properly. The fixture doesn't run! It doesn't run because nothing collects the fixtures from (calls `parsefactories()` on) the `test_main.py` `DoctestModule`. How come it only started happening with commit ab63ebb3dc07b89670b96ae97044f48406c44fa0? Turns out it mostly only worked accidentally. Each `DoctestModule` is also collected as a normal `Module`, with the `Module` collected after the `DoctestModule`. For example, if we add a non-doctest test to `test_main.py`, the collection tree looks like this: ``` ``` Now, `Module` *does* collect fixtures. When autouse fixtures are collected, they are added to the `_nodeid_autousenames` dict. Before ab63ebb3dc07b89670b96ae97044f48406c44fa0, `DoctestItem` consults `_nodeid_autousenames` at *setup* time. At this point, the `Module` has collected and so it ended up picking the autouse fixture (this relies on another "accident", that the `DoctestModule` and `Module` have the same node ID). After ab63ebb3dc07b89670b96ae97044f48406c44fa0, `DoctestItem` consults `_nodeid_autousenames` at *collection* time (= when it's created). At this point, the `Module` hasn't collected yet, so the autouse fixture is not picked out. The fix is simple -- have `DoctestModule.collect()` call `parsefactories`. From some testing I've done it shouldn't have negative consequences (I hope). --- changelog/11929.bugfix.rst | 1 + src/_pytest/doctest.py | 4 ++++ testing/test_doctest.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 changelog/11929.bugfix.rst diff --git a/changelog/11929.bugfix.rst b/changelog/11929.bugfix.rst new file mode 100644 index 000000000..8ab50e6f4 --- /dev/null +++ b/changelog/11929.bugfix.rst @@ -0,0 +1 @@ +Fix a regression in pytest 8.0.0 whereby autouse fixtures defined in a module get ignored by the doctests in the module. diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index e0fb7c161..8fe992b6a 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -567,6 +567,10 @@ class DoctestModule(Module): else: raise + # While doctests currently don't support fixtures directly, we still + # need to pick up autouse fixtures. + self.session._fixturemanager.parsefactories(self) + # Uses internal doctest module parsing mechanism. finder = MockAwareDocTestFinder() optionflags = get_optionflags(self.config) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 372528490..c91ec31cd 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1376,6 +1376,38 @@ class TestDoctestAutoUseFixtures: str(result.stdout.no_fnmatch_line("*FAILURES*")) result.stdout.fnmatch_lines(["*=== 1 passed in *"]) + @pytest.mark.parametrize("scope", [*SCOPES, "package"]) + def test_auto_use_defined_in_same_module( + self, pytester: Pytester, scope: str + ) -> None: + """Autouse fixtures defined in the same module as the doctest get picked + up properly. + + Regression test for #11929. + """ + pytester.makepyfile( + f""" + import pytest + + AUTO = "the fixture did not run" + + @pytest.fixture(autouse=True, scope="{scope}") + def auto(request): + global AUTO + AUTO = "the fixture ran" + + def my_doctest(): + '''My doctest. + + >>> my_doctest() + 'the fixture ran' + ''' + return AUTO + """ + ) + result = pytester.runpytest("--doctest-modules") + result.assert_outcomes(passed=1) + class TestDoctestNamespaceFixture: SCOPES = ["module", "session", "class", "function"]