runner: add docstring to SetupState and improve variable naming a bit
This commit is contained in:
parent
0d19aff562
commit
c30feeef8b
|
@ -544,8 +544,8 @@ class FixtureRequest:
|
||||||
self._addfinalizer(finalizer, scope=self.scope)
|
self._addfinalizer(finalizer, scope=self.scope)
|
||||||
|
|
||||||
def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None:
|
def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None:
|
||||||
item = self._getscopeitem(scope)
|
node = self._getscopeitem(scope)
|
||||||
item.addfinalizer(finalizer)
|
node.addfinalizer(finalizer)
|
||||||
|
|
||||||
def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
|
def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
|
||||||
"""Apply a marker to a single test function invocation.
|
"""Apply a marker to a single test function invocation.
|
||||||
|
|
|
@ -403,23 +403,86 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport:
|
||||||
|
|
||||||
|
|
||||||
class SetupState:
|
class SetupState:
|
||||||
"""Shared state for setting up/tearing down test items or collectors."""
|
"""Shared state for setting up/tearing down test items or collectors
|
||||||
|
in a session.
|
||||||
|
|
||||||
|
Suppose we have a collection tree as follows:
|
||||||
|
|
||||||
|
<Session session>
|
||||||
|
<Module mod1>
|
||||||
|
<Function item1>
|
||||||
|
<Module mod2>
|
||||||
|
<Function item2>
|
||||||
|
|
||||||
|
The SetupState maintains a stack. The stack starts out empty:
|
||||||
|
|
||||||
|
[]
|
||||||
|
|
||||||
|
During the setup phase of item1, prepare(item1) is called. What it does
|
||||||
|
is:
|
||||||
|
|
||||||
|
push session to stack, run session.setup()
|
||||||
|
push mod1 to stack, run mod1.setup()
|
||||||
|
push item1 to stack, run item1.setup()
|
||||||
|
|
||||||
|
The stack is:
|
||||||
|
|
||||||
|
[session, mod1, item1]
|
||||||
|
|
||||||
|
While the stack is in this shape, it is allowed to add finalizers to
|
||||||
|
each of session, mod1, item1 using addfinalizer().
|
||||||
|
|
||||||
|
During the teardown phase of item1, teardown_exact(item2) is called,
|
||||||
|
where item2 is the next item to item1. What it does is:
|
||||||
|
|
||||||
|
pop item1 from stack, run its teardowns
|
||||||
|
pop mod1 from stack, run its teardowns
|
||||||
|
|
||||||
|
mod1 was popped because it ended its purpose with item1. The stack is:
|
||||||
|
|
||||||
|
[session]
|
||||||
|
|
||||||
|
During the setup phase of item2, prepare(item2) is called. What it does
|
||||||
|
is:
|
||||||
|
|
||||||
|
push mod2 to stack, run mod2.setup()
|
||||||
|
push item2 to stack, run item2.setup()
|
||||||
|
|
||||||
|
Stack:
|
||||||
|
|
||||||
|
[session, mod2, item2]
|
||||||
|
|
||||||
|
During the teardown phase of item2, teardown_exact(None) is called,
|
||||||
|
because item2 is the last item. What it does is:
|
||||||
|
|
||||||
|
pop item2 from stack, run its teardowns
|
||||||
|
pop mod2 from stack, run its teardowns
|
||||||
|
pop session from stack, run its teardowns
|
||||||
|
|
||||||
|
Stack:
|
||||||
|
|
||||||
|
[]
|
||||||
|
|
||||||
|
The end!
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
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]]] = {}
|
self.stack: Dict[Node, List[Callable[[], object]]] = {}
|
||||||
|
|
||||||
_prepare_exc_key = StoreKey[Union[OutcomeException, Exception]]()
|
_prepare_exc_key = StoreKey[Union[OutcomeException, Exception]]()
|
||||||
|
|
||||||
def prepare(self, colitem: Item) -> None:
|
def prepare(self, item: Item) -> None:
|
||||||
"""Setup objects along the collector chain to the test-method."""
|
"""Setup objects along the collector chain to the item."""
|
||||||
|
# If a collector fails its setup, fail its entire subtree of items.
|
||||||
# Check if the last collection node has raised an error.
|
# The setup is not retried for each item - the same exception is used.
|
||||||
for col in self.stack:
|
for col in self.stack:
|
||||||
prepare_exc = col._store.get(self._prepare_exc_key, None)
|
prepare_exc = col._store.get(self._prepare_exc_key, None)
|
||||||
if prepare_exc:
|
if prepare_exc:
|
||||||
raise prepare_exc
|
raise prepare_exc
|
||||||
|
|
||||||
needed_collectors = colitem.listchain()
|
needed_collectors = item.listchain()
|
||||||
for col in needed_collectors[len(self.stack) :]:
|
for col in needed_collectors[len(self.stack) :]:
|
||||||
assert col not in self.stack
|
assert col not in self.stack
|
||||||
self.stack[col] = [col.teardown]
|
self.stack[col] = [col.teardown]
|
||||||
|
@ -429,20 +492,29 @@ class SetupState:
|
||||||
col._store[self._prepare_exc_key] = e
|
col._store[self._prepare_exc_key] = e
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def addfinalizer(self, finalizer: Callable[[], object], colitem: Node) -> None:
|
def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None:
|
||||||
"""Attach a finalizer to the given colitem."""
|
"""Attach a finalizer to the given node.
|
||||||
assert colitem and not isinstance(colitem, tuple)
|
|
||||||
|
The node must be currently active in the stack.
|
||||||
|
"""
|
||||||
|
assert node and not isinstance(node, tuple)
|
||||||
assert callable(finalizer)
|
assert callable(finalizer)
|
||||||
assert colitem in self.stack, (colitem, self.stack)
|
assert node in self.stack, (node, self.stack)
|
||||||
self.stack[colitem].append(finalizer)
|
self.stack[node].append(finalizer)
|
||||||
|
|
||||||
def teardown_exact(self, nextitem: Optional[Item]) -> None:
|
def teardown_exact(self, nextitem: Optional[Item]) -> None:
|
||||||
|
"""Teardown the current stack up until reaching nodes that nextitem
|
||||||
|
also descends from.
|
||||||
|
|
||||||
|
When nextitem is None (meaning we're at the last item), the entire
|
||||||
|
stack is torn down.
|
||||||
|
"""
|
||||||
needed_collectors = nextitem and nextitem.listchain() or []
|
needed_collectors = nextitem and nextitem.listchain() or []
|
||||||
exc = None
|
exc = None
|
||||||
while self.stack:
|
while self.stack:
|
||||||
if list(self.stack.keys()) == needed_collectors[: len(self.stack)]:
|
if list(self.stack.keys()) == needed_collectors[: len(self.stack)]:
|
||||||
break
|
break
|
||||||
colitem, finalizers = self.stack.popitem()
|
node, finalizers = self.stack.popitem()
|
||||||
while finalizers:
|
while finalizers:
|
||||||
fin = finalizers.pop()
|
fin = finalizers.pop()
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -26,7 +26,7 @@ class TestSetupState:
|
||||||
ss = item.session._setupstate
|
ss = item.session._setupstate
|
||||||
values = [1]
|
values = [1]
|
||||||
ss.prepare(item)
|
ss.prepare(item)
|
||||||
ss.addfinalizer(values.pop, colitem=item)
|
ss.addfinalizer(values.pop, item)
|
||||||
assert values
|
assert values
|
||||||
ss.teardown_exact(None)
|
ss.teardown_exact(None)
|
||||||
assert not values
|
assert not values
|
||||||
|
|
Loading…
Reference in New Issue