Skip to content

Commit

Permalink
runner: avoid using node's store in SetupState
Browse files Browse the repository at this point in the history
SetupState maintains its own state, so it can store the exception
itself, instead of using the node's store, which is better avoided when
possible.

This also reduces the lifetime of the reference-cycle-inducing exception
objects which is never a bad thing.
  • Loading branch information
bluetech committed Jan 24, 2021
1 parent d5df8f9 commit 48fb989
Showing 1 changed file with 14 additions and 11 deletions.
25 changes: 14 additions & 11 deletions src/_pytest/runner.py
Expand Up @@ -36,7 +36,6 @@
from _pytest.outcomes import OutcomeException
from _pytest.outcomes import Skipped
from _pytest.outcomes import TEST_OUTCOME
from _pytest.store import StoreKey

if TYPE_CHECKING:
from typing_extensions import Literal
Expand Down Expand Up @@ -467,29 +466,33 @@ class SetupState:
"""

def __init__(self) -> None:
# Maps node -> the node's finalizers.
# The stack is in the dict insertion order.
self.stack: Dict[Node, List[Callable[[], object]]] = {}

_prepare_exc_key = StoreKey[Union[OutcomeException, Exception]]()
self.stack: Dict[
Node,
Tuple[
# Node's finalizers.
List[Callable[[], object]],
# Node's exception, if its setup raised.
Optional[Union[OutcomeException, Exception]],
],
] = {}

def prepare(self, item: Item) -> None:
"""Setup objects along the collector chain to the item."""
# If a collector fails its setup, fail its entire subtree of items.
# The setup is not retried for each item - the same exception is used.
for col in self.stack:
prepare_exc = col._store.get(self._prepare_exc_key, None)
for col, (finalizers, prepare_exc) in self.stack.items():
if prepare_exc:
raise prepare_exc

needed_collectors = item.listchain()
for col in needed_collectors[len(self.stack) :]:
assert col not in self.stack
self.stack[col] = [col.teardown]
self.stack[col] = ([col.teardown], None)
try:
col.setup()
except TEST_OUTCOME as e:
col._store[self._prepare_exc_key] = e
self.stack[col] = (self.stack[col][0], e)
raise e

def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None:
Expand All @@ -500,7 +503,7 @@ def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None:
assert node and not isinstance(node, tuple)
assert callable(finalizer)
assert node in self.stack, (node, self.stack)
self.stack[node].append(finalizer)
self.stack[node][0].append(finalizer)

def teardown_exact(self, nextitem: Optional[Item]) -> None:
"""Teardown the current stack up until reaching nodes that nextitem
Expand All @@ -514,7 +517,7 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None:
while self.stack:
if list(self.stack.keys()) == needed_collectors[: len(self.stack)]:
break
node, finalizers = self.stack.popitem()
node, (finalizers, prepare_exc) = self.stack.popitem()
while finalizers:
fin = finalizers.pop()
try:
Expand Down

0 comments on commit 48fb989

Please sign in to comment.