Skip to content

Commit c30feee

Browse files
committed
runner: add docstring to SetupState and improve variable naming a bit
1 parent 0d19aff commit c30feee

File tree

3 files changed

+87
-15
lines changed

3 files changed

+87
-15
lines changed

src/_pytest/fixtures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,8 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None:
544544
self._addfinalizer(finalizer, scope=self.scope)
545545

546546
def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None:
547-
item = self._getscopeitem(scope)
548-
item.addfinalizer(finalizer)
547+
node = self._getscopeitem(scope)
548+
node.addfinalizer(finalizer)
549549

550550
def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
551551
"""Apply a marker to a single test function invocation.

src/_pytest/runner.py

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -403,23 +403,86 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport:
403403

404404

405405
class SetupState:
406-
"""Shared state for setting up/tearing down test items or collectors."""
406+
"""Shared state for setting up/tearing down test items or collectors
407+
in a session.
408+
409+
Suppose we have a collection tree as follows:
410+
411+
<Session session>
412+
<Module mod1>
413+
<Function item1>
414+
<Module mod2>
415+
<Function item2>
416+
417+
The SetupState maintains a stack. The stack starts out empty:
418+
419+
[]
420+
421+
During the setup phase of item1, prepare(item1) is called. What it does
422+
is:
423+
424+
push session to stack, run session.setup()
425+
push mod1 to stack, run mod1.setup()
426+
push item1 to stack, run item1.setup()
427+
428+
The stack is:
429+
430+
[session, mod1, item1]
431+
432+
While the stack is in this shape, it is allowed to add finalizers to
433+
each of session, mod1, item1 using addfinalizer().
434+
435+
During the teardown phase of item1, teardown_exact(item2) is called,
436+
where item2 is the next item to item1. What it does is:
437+
438+
pop item1 from stack, run its teardowns
439+
pop mod1 from stack, run its teardowns
440+
441+
mod1 was popped because it ended its purpose with item1. The stack is:
442+
443+
[session]
444+
445+
During the setup phase of item2, prepare(item2) is called. What it does
446+
is:
447+
448+
push mod2 to stack, run mod2.setup()
449+
push item2 to stack, run item2.setup()
450+
451+
Stack:
452+
453+
[session, mod2, item2]
454+
455+
During the teardown phase of item2, teardown_exact(None) is called,
456+
because item2 is the last item. What it does is:
457+
458+
pop item2 from stack, run its teardowns
459+
pop mod2 from stack, run its teardowns
460+
pop session from stack, run its teardowns
461+
462+
Stack:
463+
464+
[]
465+
466+
The end!
467+
"""
407468

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

411474
_prepare_exc_key = StoreKey[Union[OutcomeException, Exception]]()
412475

413-
def prepare(self, colitem: Item) -> None:
414-
"""Setup objects along the collector chain to the test-method."""
415-
416-
# Check if the last collection node has raised an error.
476+
def prepare(self, item: Item) -> None:
477+
"""Setup objects along the collector chain to the item."""
478+
# If a collector fails its setup, fail its entire subtree of items.
479+
# The setup is not retried for each item - the same exception is used.
417480
for col in self.stack:
418481
prepare_exc = col._store.get(self._prepare_exc_key, None)
419482
if prepare_exc:
420483
raise prepare_exc
421484

422-
needed_collectors = colitem.listchain()
485+
needed_collectors = item.listchain()
423486
for col in needed_collectors[len(self.stack) :]:
424487
assert col not in self.stack
425488
self.stack[col] = [col.teardown]
@@ -429,20 +492,29 @@ def prepare(self, colitem: Item) -> None:
429492
col._store[self._prepare_exc_key] = e
430493
raise e
431494

432-
def addfinalizer(self, finalizer: Callable[[], object], colitem: Node) -> None:
433-
"""Attach a finalizer to the given colitem."""
434-
assert colitem and not isinstance(colitem, tuple)
495+
def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None:
496+
"""Attach a finalizer to the given node.
497+
498+
The node must be currently active in the stack.
499+
"""
500+
assert node and not isinstance(node, tuple)
435501
assert callable(finalizer)
436-
assert colitem in self.stack, (colitem, self.stack)
437-
self.stack[colitem].append(finalizer)
502+
assert node in self.stack, (node, self.stack)
503+
self.stack[node].append(finalizer)
438504

439505
def teardown_exact(self, nextitem: Optional[Item]) -> None:
506+
"""Teardown the current stack up until reaching nodes that nextitem
507+
also descends from.
508+
509+
When nextitem is None (meaning we're at the last item), the entire
510+
stack is torn down.
511+
"""
440512
needed_collectors = nextitem and nextitem.listchain() or []
441513
exc = None
442514
while self.stack:
443515
if list(self.stack.keys()) == needed_collectors[: len(self.stack)]:
444516
break
445-
colitem, finalizers = self.stack.popitem()
517+
node, finalizers = self.stack.popitem()
446518
while finalizers:
447519
fin = finalizers.pop()
448520
try:

testing/test_runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def test_setup(self, pytester: Pytester) -> None:
2626
ss = item.session._setupstate
2727
values = [1]
2828
ss.prepare(item)
29-
ss.addfinalizer(values.pop, colitem=item)
29+
ss.addfinalizer(values.pop, item)
3030
assert values
3131
ss.teardown_exact(None)
3232
assert not values

0 commit comments

Comments
 (0)