From baed905c0b1a8381bd97167a691096e206f1a168 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 17 Jan 2024 15:44:38 +0100
Subject: [PATCH 01/10] Move scheduling of fixture finalization so it isn't
rescheduled if the fixture value is cached.
---
src/_pytest/fixtures.py | 29 ++++++++++------
.../test_scope_fixture_teardown_order.py | 33 +++++++++++++++++++
2 files changed, 52 insertions(+), 10 deletions(-)
create mode 100644 testing/python/test_scope_fixture_teardown_order.py
diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py
index c294ec586b5..5edb7c4a38b 100644
--- a/src/_pytest/fixtures.py
+++ b/src/_pytest/fixtures.py
@@ -538,6 +538,8 @@ def getfixturevalue(self, argname: str) -> Any:
:raises pytest.FixtureLookupError:
If the given fixture could not be found.
"""
+ # Note: This is called during setup for evaluating fixtures defined via
+ # function arguments as well.
fixturedef = self._get_active_fixturedef(argname)
assert fixturedef.cached_result is not None, (
f'The fixture value for "{argname}" is not available. '
@@ -574,9 +576,8 @@ def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:
"""Create a SubRequest based on "self" and call the execute method
of the given FixtureDef object.
- This will force the FixtureDef object to throw away any previous
- results and compute a new fixture value, which will be stored into
- the FixtureDef object itself.
+ If the FixtureDef has cached the result it will do nothing, otherwise it will
+ setup and run the fixture, cache the value, and schedule a finalizer for it.
"""
# prepare a subrequest object before calling fixture function
# (latter managed by fixturedef)
@@ -641,11 +642,8 @@ def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:
# Check if a higher-level scoped fixture accesses a lower level one.
subrequest._check_scope(argname, self._scope, scope)
- try:
- # Call the fixture function.
- fixturedef.execute(request=subrequest)
- finally:
- self._schedule_finalizers(fixturedef, subrequest)
+ # Make sure the fixture value is cached, running it if it isn't
+ fixturedef.execute(request=subrequest)
def _schedule_finalizers(
self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
@@ -1055,7 +1053,9 @@ def finish(self, request: SubRequest) -> None:
self.cached_result = None
self._finalizers.clear()
+ # Note: the return value is entirely unused, no tests depend on it
def execute(self, request: SubRequest) -> FixtureValue:
+ finalizer = functools.partial(self.finish, request=request)
# Get required arguments and register our own finish()
# with their finalization.
for argname in self.argnames:
@@ -1063,7 +1063,7 @@ def execute(self, request: SubRequest) -> FixtureValue:
if argname != "request":
# PseudoFixtureDef is only for "request".
assert isinstance(fixturedef, FixtureDef)
- fixturedef.addfinalizer(functools.partial(self.finish, request=request))
+ fixturedef.addfinalizer(finalizer)
my_cache_key = self.cache_key(request)
if self.cached_result is not None:
@@ -1073,6 +1073,7 @@ def execute(self, request: SubRequest) -> FixtureValue:
if my_cache_key is cache_key:
if self.cached_result[2] is not None:
exc = self.cached_result[2]
+ # this would previously trigger adding a finalizer. Should it?
raise exc
else:
result = self.cached_result[0]
@@ -1083,7 +1084,15 @@ def execute(self, request: SubRequest) -> FixtureValue:
assert self.cached_result is None
ihook = request.node.ihook
- result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
+ try:
+ # Setup the fixture, run the code in it, and cache the value
+ # in self.cached_result
+ result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
+ finally:
+ # schedule our finalizer, even if the setup failed
+ request.node.addfinalizer(finalizer)
+
+ # note: unused
return result
def cache_key(self, request: SubRequest) -> object:
diff --git a/testing/python/test_scope_fixture_teardown_order.py b/testing/python/test_scope_fixture_teardown_order.py
new file mode 100644
index 00000000000..fdff19af2a1
--- /dev/null
+++ b/testing/python/test_scope_fixture_teardown_order.py
@@ -0,0 +1,33 @@
+import pytest
+
+last_executed = ""
+
+
+@pytest.fixture(scope="module")
+def fixture_1():
+ global last_executed
+ assert last_executed == ""
+ last_executed = "autouse_setup"
+ yield
+ assert last_executed == "noautouse_teardown"
+ last_executed = "autouse_teardown"
+
+
+@pytest.fixture(scope="module")
+def fixture_2():
+ global last_executed
+ assert last_executed == "autouse_setup"
+ last_executed = "noautouse_setup"
+ yield
+ assert last_executed == "run_test"
+ last_executed = "noautouse_teardown"
+
+
+def test_autouse_fixture_teardown_order(fixture_1, fixture_2):
+ global last_executed
+ assert last_executed == "noautouse_setup"
+ last_executed = "run_test"
+
+
+def test_2(fixture_1):
+ pass
From 3356fa77226757f75e12223034cd4173d0d108dd Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Wed, 17 Jan 2024 16:31:43 +0100
Subject: [PATCH 02/10] add changelog
---
changelog/1489.bugfix.rst | 1 +
1 file changed, 1 insertion(+)
create mode 100644 changelog/1489.bugfix.rst
diff --git a/changelog/1489.bugfix.rst b/changelog/1489.bugfix.rst
new file mode 100644
index 00000000000..70c5dd1252e
--- /dev/null
+++ b/changelog/1489.bugfix.rst
@@ -0,0 +1 @@
+Fix some instances where teardown of higher-scoped fixtures was not happening in the reverse order they were initialized in.
From f76a77b06831de9732ed045f32fe94edaec5d189 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 13 Feb 2024 13:41:21 +0100
Subject: [PATCH 03/10] Add test for finalizer in a fixture with a cached
exception
---
testing/python/test_scope_fixture_caching.py | 63 ++++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 testing/python/test_scope_fixture_caching.py
diff --git a/testing/python/test_scope_fixture_caching.py b/testing/python/test_scope_fixture_caching.py
new file mode 100644
index 00000000000..21594fb1f30
--- /dev/null
+++ b/testing/python/test_scope_fixture_caching.py
@@ -0,0 +1,63 @@
+from typing import Generator
+
+import pytest
+
+
+# These tests will fail if run out of order, or selectively ... so they should probably be defined in a different way
+
+executed: list[str] = []
+
+
+#########
+# Make sure setup and finalization is only run once when using fixture multiple times
+# I'm pretty sure there's other tests that checks this already though, though idr fully where
+#########
+@pytest.fixture(scope="class")
+def fixture_1() -> Generator[None, None, None]:
+ executed.append("fix setup")
+ yield
+ executed.append("fix teardown")
+
+
+class TestFixtureCaching:
+ def test_1(self, fixture_1: None) -> None:
+ assert executed == ["fix setup"]
+
+ def test_2(self, fixture_1: None) -> None:
+ assert executed == ["fix setup"]
+
+
+def test_expected_setup_and_teardown() -> None:
+ assert executed == ["fix setup", "fix teardown"]
+
+
+######
+# Make sure setup & finalization is only run once, with a cached exception
+######
+executed_crash: list[str] = []
+
+
+@pytest.fixture(scope="class")
+def fixture_crash(request: pytest.FixtureRequest) -> None:
+ executed_crash.append("fix_crash setup")
+
+ def my_finalizer() -> None:
+ executed_crash.append("fix_crash teardown")
+
+ request.addfinalizer(my_finalizer)
+
+ raise Exception("foo")
+
+
+class TestFixtureCachingException:
+ @pytest.mark.xfail
+ def test_crash_1(self, fixture_crash: None) -> None:
+ assert False
+
+ @pytest.mark.xfail
+ def test_crash_2(self, fixture_crash: None) -> None:
+ assert False
+
+
+def test_crash_expected_setup_and_teardown() -> None:
+ assert executed_crash == ["fix_crash setup", "fix_crash teardown"]
From 537a831a4f99f852e99cd2c88ccea50016891d73 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 13 Feb 2024 13:43:12 +0100
Subject: [PATCH 04/10] add typing to teardown_order test
---
testing/python/test_scope_fixture_teardown_order.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/testing/python/test_scope_fixture_teardown_order.py b/testing/python/test_scope_fixture_teardown_order.py
index fdff19af2a1..8ce97cd3262 100644
--- a/testing/python/test_scope_fixture_teardown_order.py
+++ b/testing/python/test_scope_fixture_teardown_order.py
@@ -1,10 +1,13 @@
+from typing import Generator
+
import pytest
+
last_executed = ""
@pytest.fixture(scope="module")
-def fixture_1():
+def fixture_1() -> Generator[None, None, None]:
global last_executed
assert last_executed == ""
last_executed = "autouse_setup"
@@ -14,7 +17,7 @@ def fixture_1():
@pytest.fixture(scope="module")
-def fixture_2():
+def fixture_2() -> Generator[None, None, None]:
global last_executed
assert last_executed == "autouse_setup"
last_executed = "noautouse_setup"
@@ -23,11 +26,11 @@ def fixture_2():
last_executed = "noautouse_teardown"
-def test_autouse_fixture_teardown_order(fixture_1, fixture_2):
+def test_autouse_fixture_teardown_order(fixture_1: None, fixture_2: None) -> None:
global last_executed
assert last_executed == "noautouse_setup"
last_executed = "run_test"
-def test_2(fixture_1):
+def test_2(fixture_1: None) -> None:
pass
From d168ea41f4465208a993982913dc0e58c79146aa Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 13 Feb 2024 13:46:12 +0100
Subject: [PATCH 05/10] fix typing in test to work on runtime in py38
---
testing/python/test_scope_fixture_caching.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/testing/python/test_scope_fixture_caching.py b/testing/python/test_scope_fixture_caching.py
index 21594fb1f30..2a24a2f53a8 100644
--- a/testing/python/test_scope_fixture_caching.py
+++ b/testing/python/test_scope_fixture_caching.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from typing import Generator
import pytest
From 3fc5c5530e9cb95904e578c1c4a9746abe26c964 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Sun, 3 Mar 2024 11:25:52 +0100
Subject: [PATCH 06/10] remove some comments, remove now unused
_schedule_finalizers(), move tests to a pytester test in
testing/python/fixtures.py
---
src/_pytest/fixtures.py | 24 ------
testing/python/fixtures.py | 77 ++++++++++++++++++++
testing/python/test_scope_fixture_caching.py | 65 -----------------
3 files changed, 77 insertions(+), 89 deletions(-)
delete mode 100644 testing/python/test_scope_fixture_caching.py
diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py
index f724af6bae7..532330fa0fa 100644
--- a/src/_pytest/fixtures.py
+++ b/src/_pytest/fixtures.py
@@ -633,13 +633,6 @@ def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:
# Make sure the fixture value is cached, running it if it isn't
fixturedef.execute(request=subrequest)
- def _schedule_finalizers(
- self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
- ) -> None:
- # If fixture function failed it might have registered finalizers.
- finalizer = functools.partial(fixturedef.finish, request=subrequest)
- subrequest.node.addfinalizer(finalizer)
-
@final
class TopRequest(FixtureRequest):
@@ -766,21 +759,6 @@ def _factorytraceback(self) -> List[str]:
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
self._fixturedef.addfinalizer(finalizer)
- def _schedule_finalizers(
- self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
- ) -> None:
- # If the executing fixturedef was not explicitly requested in the argument list (via
- # getfixturevalue inside the fixture call) then ensure this fixture def will be finished
- # first.
- if (
- fixturedef.argname not in self._fixture_defs
- and fixturedef.argname not in self._pyfuncitem.fixturenames
- ):
- fixturedef.addfinalizer(
- functools.partial(self._fixturedef.finish, request=self)
- )
- super()._schedule_finalizers(fixturedef, subrequest)
-
@final
class FixtureLookupError(LookupError):
@@ -1037,7 +1015,6 @@ def finish(self, request: SubRequest) -> None:
self.cached_result = None
self._finalizers.clear()
- # Note: the return value is entirely unused, no tests depend on it
def execute(self, request: SubRequest) -> FixtureValue:
finalizer = functools.partial(self.finish, request=request)
# Get required arguments and register our own finish()
@@ -1076,7 +1053,6 @@ def execute(self, request: SubRequest) -> FixtureValue:
# schedule our finalizer, even if the setup failed
request.node.addfinalizer(finalizer)
- # note: unused
return result
def cache_key(self, request: SubRequest) -> object:
diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py
index 299e411a695..1b7a0e4141a 100644
--- a/testing/python/fixtures.py
+++ b/testing/python/fixtures.py
@@ -4559,3 +4559,80 @@ def test_deduplicate_names() -> None:
assert items == ("a", "b", "c", "d")
items = deduplicate_names((*items, "g", "f", "g", "e", "b"))
assert items == ("a", "b", "c", "d", "g", "f", "e")
+
+
+def test_scope_fixture_caching_1(pytester: Pytester) -> None:
+ """
+ Make sure setup and finalization is only run once when using fixture
+ multiple times. This might be a duplicate of another test."""
+ pytester.makepyfile(
+ """
+ from __future__ import annotations
+
+ from typing import Generator
+
+ import pytest
+ executed: list[str] = []
+ @pytest.fixture(scope="class")
+ def fixture_1() -> Generator[None, None, None]:
+ executed.append("fix setup")
+ yield
+ executed.append("fix teardown")
+
+
+ class TestFixtureCaching:
+ def test_1(self, fixture_1: None) -> None:
+ assert executed == ["fix setup"]
+
+ def test_2(self, fixture_1: None) -> None:
+ assert executed == ["fix setup"]
+
+
+ def test_expected_setup_and_teardown() -> None:
+ assert executed == ["fix setup", "fix teardown"]
+ """
+ )
+ result = pytester.runpytest()
+ assert result.ret == 0
+
+
+def test_scope_fixture_caching_2(pytester: Pytester) -> None:
+ """Make sure setup & finalization is only run once, with a cached exception."""
+ pytester.makepyfile(
+ """
+ from __future__ import annotations
+
+ from typing import Generator
+
+ import pytest
+ executed_crash: list[str] = []
+
+
+ @pytest.fixture(scope="class")
+ def fixture_crash(request: pytest.FixtureRequest) -> None:
+ executed_crash.append("fix_crash setup")
+
+ def my_finalizer() -> None:
+ executed_crash.append("fix_crash teardown")
+
+ request.addfinalizer(my_finalizer)
+
+ raise Exception("foo")
+
+
+ class TestFixtureCachingException:
+ @pytest.mark.xfail
+ def test_crash_1(self, fixture_crash: None) -> None:
+ ...
+
+ @pytest.mark.xfail
+ def test_crash_2(self, fixture_crash: None) -> None:
+ ...
+
+
+ def test_crash_expected_setup_and_teardown() -> None:
+ assert executed_crash == ["fix_crash setup", "fix_crash teardown"]
+ """
+ )
+ result = pytester.runpytest()
+ assert result.ret == 0
diff --git a/testing/python/test_scope_fixture_caching.py b/testing/python/test_scope_fixture_caching.py
deleted file mode 100644
index 2a24a2f53a8..00000000000
--- a/testing/python/test_scope_fixture_caching.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from __future__ import annotations
-
-from typing import Generator
-
-import pytest
-
-
-# These tests will fail if run out of order, or selectively ... so they should probably be defined in a different way
-
-executed: list[str] = []
-
-
-#########
-# Make sure setup and finalization is only run once when using fixture multiple times
-# I'm pretty sure there's other tests that checks this already though, though idr fully where
-#########
-@pytest.fixture(scope="class")
-def fixture_1() -> Generator[None, None, None]:
- executed.append("fix setup")
- yield
- executed.append("fix teardown")
-
-
-class TestFixtureCaching:
- def test_1(self, fixture_1: None) -> None:
- assert executed == ["fix setup"]
-
- def test_2(self, fixture_1: None) -> None:
- assert executed == ["fix setup"]
-
-
-def test_expected_setup_and_teardown() -> None:
- assert executed == ["fix setup", "fix teardown"]
-
-
-######
-# Make sure setup & finalization is only run once, with a cached exception
-######
-executed_crash: list[str] = []
-
-
-@pytest.fixture(scope="class")
-def fixture_crash(request: pytest.FixtureRequest) -> None:
- executed_crash.append("fix_crash setup")
-
- def my_finalizer() -> None:
- executed_crash.append("fix_crash teardown")
-
- request.addfinalizer(my_finalizer)
-
- raise Exception("foo")
-
-
-class TestFixtureCachingException:
- @pytest.mark.xfail
- def test_crash_1(self, fixture_crash: None) -> None:
- assert False
-
- @pytest.mark.xfail
- def test_crash_2(self, fixture_crash: None) -> None:
- assert False
-
-
-def test_crash_expected_setup_and_teardown() -> None:
- assert executed_crash == ["fix_crash setup", "fix_crash teardown"]
From 007d24a6faaa4e3eb8f4a7bc981093c2d7ce631a Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Sun, 3 Mar 2024 13:22:23 +0100
Subject: [PATCH 07/10] improve comment getfixturevalue, move
test_scoped_fixture_teardown_order to testing/python/fixtures.py
---
src/_pytest/fixtures.py | 7 ++-
testing/python/fixtures.py | 54 +++++++++++++++++++
.../test_scope_fixture_teardown_order.py | 36 -------------
3 files changed, 59 insertions(+), 38 deletions(-)
delete mode 100644 testing/python/test_scope_fixture_teardown_order.py
diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py
index 532330fa0fa..fa727513c13 100644
--- a/src/_pytest/fixtures.py
+++ b/src/_pytest/fixtures.py
@@ -531,8 +531,11 @@ def getfixturevalue(self, argname: str) -> Any:
:raises pytest.FixtureLookupError:
If the given fixture could not be found.
"""
- # Note: This is called during setup for evaluating fixtures defined via
- # function arguments as well.
+ # Note that in addition to the use case described in the docstring,
+ # getfixturevalue() is also called by pytest itself during item setup to
+ # evaluate the fixtures that are requested statically
+ # (using function parameters, autouse, etc).
+
fixturedef = self._get_active_fixturedef(argname)
assert fixturedef.cached_result is not None, (
f'The fixture value for "{argname}" is not available. '
diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py
index 1b7a0e4141a..0758b590ca0 100644
--- a/testing/python/fixtures.py
+++ b/testing/python/fixtures.py
@@ -4561,6 +4561,60 @@ def test_deduplicate_names() -> None:
assert items == ("a", "b", "c", "d", "g", "f", "e")
+def test_scoped_fixture_teardown_order(pytester: Pytester) -> None:
+ """
+ Make sure teardowns happen in reverse order of setup with scoped fixtures, when
+ a later test only depends on a subset of scoped fixtures.
+ Regression test for https://github.com/pytest-dev/pytest/issues/1489
+ """
+ pytester.makepyfile(
+ """
+ from typing import Generator
+
+ import pytest
+
+
+ last_executed = ""
+
+
+ @pytest.fixture(scope="module")
+ def fixture_1() -> Generator[None, None, None]:
+ global last_executed
+ assert last_executed == ""
+ last_executed = "autouse_setup"
+ yield
+ assert last_executed == "noautouse_teardown"
+ last_executed = "autouse_teardown"
+
+
+ @pytest.fixture(scope="module")
+ def fixture_2() -> Generator[None, None, None]:
+ global last_executed
+ assert last_executed == "autouse_setup"
+ last_executed = "noautouse_setup"
+ yield
+ assert last_executed == "run_test"
+ last_executed = "noautouse_teardown"
+
+
+ def test_autouse_fixture_teardown_order(fixture_1: None, fixture_2: None) -> None:
+ global last_executed
+ assert last_executed == "noautouse_setup"
+ last_executed = "run_test"
+
+
+ def test_2(fixture_1: None) -> None:
+ # this would previously queue an additional teardown of fixture_1,
+ # despite fixture_1's value being cached, which caused fixture_1 to be
+ # torn down before fixture_2 - violating the rule that teardowns should
+ # happen in reverse order of setup.
+ pass
+ """
+ )
+ result = pytester.runpytest()
+ assert result.ret == 0
+
+
def test_scope_fixture_caching_1(pytester: Pytester) -> None:
"""
Make sure setup and finalization is only run once when using fixture
diff --git a/testing/python/test_scope_fixture_teardown_order.py b/testing/python/test_scope_fixture_teardown_order.py
deleted file mode 100644
index 8ce97cd3262..00000000000
--- a/testing/python/test_scope_fixture_teardown_order.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from typing import Generator
-
-import pytest
-
-
-last_executed = ""
-
-
-@pytest.fixture(scope="module")
-def fixture_1() -> Generator[None, None, None]:
- global last_executed
- assert last_executed == ""
- last_executed = "autouse_setup"
- yield
- assert last_executed == "noautouse_teardown"
- last_executed = "autouse_teardown"
-
-
-@pytest.fixture(scope="module")
-def fixture_2() -> Generator[None, None, None]:
- global last_executed
- assert last_executed == "autouse_setup"
- last_executed = "noautouse_setup"
- yield
- assert last_executed == "run_test"
- last_executed = "noautouse_teardown"
-
-
-def test_autouse_fixture_teardown_order(fixture_1: None, fixture_2: None) -> None:
- global last_executed
- assert last_executed == "noautouse_setup"
- last_executed = "run_test"
-
-
-def test_2(fixture_1: None) -> None:
- pass
From 73ae9649188d312fef29316e0efce58453542b63 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 15 Mar 2024 15:54:17 +0100
Subject: [PATCH 08/10] remove test_scope_fixture_caching1/2, they're now in a
separate PR
---
testing/python/fixtures.py | 77 --------------------------------------
1 file changed, 77 deletions(-)
diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py
index 6f469542b80..d525cae878e 100644
--- a/testing/python/fixtures.py
+++ b/testing/python/fixtures.py
@@ -4676,80 +4676,3 @@ def test_2(fixture_1: None) -> None:
)
result = pytester.runpytest()
assert result.ret == 0
-
-
-def test_scope_fixture_caching_1(pytester: Pytester) -> None:
- """
- Make sure setup and finalization is only run once when using fixture
- multiple times. This might be a duplicate of another test."""
- pytester.makepyfile(
- """
- from __future__ import annotations
-
- from typing import Generator
-
- import pytest
- executed: list[str] = []
- @pytest.fixture(scope="class")
- def fixture_1() -> Generator[None, None, None]:
- executed.append("fix setup")
- yield
- executed.append("fix teardown")
-
-
- class TestFixtureCaching:
- def test_1(self, fixture_1: None) -> None:
- assert executed == ["fix setup"]
-
- def test_2(self, fixture_1: None) -> None:
- assert executed == ["fix setup"]
-
-
- def test_expected_setup_and_teardown() -> None:
- assert executed == ["fix setup", "fix teardown"]
- """
- )
- result = pytester.runpytest()
- assert result.ret == 0
-
-
-def test_scope_fixture_caching_2(pytester: Pytester) -> None:
- """Make sure setup & finalization is only run once, with a cached exception."""
- pytester.makepyfile(
- """
- from __future__ import annotations
-
- from typing import Generator
-
- import pytest
- executed_crash: list[str] = []
-
-
- @pytest.fixture(scope="class")
- def fixture_crash(request: pytest.FixtureRequest) -> None:
- executed_crash.append("fix_crash setup")
-
- def my_finalizer() -> None:
- executed_crash.append("fix_crash teardown")
-
- request.addfinalizer(my_finalizer)
-
- raise Exception("foo")
-
-
- class TestFixtureCachingException:
- @pytest.mark.xfail
- def test_crash_1(self, fixture_crash: None) -> None:
- ...
-
- @pytest.mark.xfail
- def test_crash_2(self, fixture_crash: None) -> None:
- ...
-
-
- def test_crash_expected_setup_and_teardown() -> None:
- assert executed_crash == ["fix_crash setup", "fix_crash teardown"]
- """
- )
- result = pytester.runpytest()
- assert result.ret == 0
From 43f394f128e2acc44db53238093fddb8cf37f935 Mon Sep 17 00:00:00 2001
From: Ran Benita
Date: Sat, 16 Mar 2024 23:18:32 +0200
Subject: [PATCH 09/10] Update src/_pytest/fixtures.py
---
src/_pytest/fixtures.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py
index 55dc71fee82..48429a023ac 100644
--- a/src/_pytest/fixtures.py
+++ b/src/_pytest/fixtures.py
@@ -544,10 +544,9 @@ def getfixturevalue(self, argname: str) -> Any:
If the given fixture could not be found.
"""
# Note that in addition to the use case described in the docstring,
- # getfixturevalue() is also called by pytest itself during item setup to
- # evaluate the fixtures that are requested statically
+ # getfixturevalue() is also called by pytest itself during item and fixture
+ # setup to evaluate the fixtures that are requested statically
# (using function parameters, autouse, etc).
- # As well as called by `pytest_fixture_setup()`
fixturedef = self._get_active_fixturedef(argname)
assert fixturedef.cached_result is not None, (
From 13d104948ae2af443d1d705b22cd567f835a3feb Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Sat, 16 Mar 2024 21:23:34 +0000
Subject: [PATCH 10/10] [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---
testing/python/fixtures.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py
index 7984d45692d..1e22270e51b 100644
--- a/testing/python/fixtures.py
+++ b/testing/python/fixtures.py
@@ -4697,12 +4697,12 @@ def test_crash_expected_setup_and_teardown() -> None:
result = pytester.runpytest()
assert result.ret == 0
-
+
def test_scoped_fixture_teardown_order(pytester: Pytester) -> None:
"""
Make sure teardowns happen in reverse order of setup with scoped fixtures, when
a later test only depends on a subset of scoped fixtures.
-
+
Regression test for https://github.com/pytest-dev/pytest/issues/1489
"""
pytester.makepyfile(
@@ -4750,4 +4750,4 @@ def test_2(fixture_1: None) -> None:
"""
)
result = pytester.runpytest()
- assert result.ret == 0
\ No newline at end of file
+ assert result.ret == 0