runner.py
Overview
`runner.py` is a core module responsible for managing the execution lifecycle of test items within the pytest testing framework. It implements the protocols to **collect**, **setup**, **run**, and **teardown** tests, while handling exceptions, timing, and reporting outcomes. This file orchestrates the phases of test execution—setup, call (test function execution), and teardown—ensuring resources are allocated and cleaned up properly, and detailed reports are generated for consumption by pytest’s reporting subsystems.
Key responsibilities include:
Managing the test lifecycle protocol for each test item.
Wrapping test phase invocations with timing and exception capturing (
CallInfo).Maintaining shared state during setup and teardown via
SetupState.Interacting with pytest’s hook system to trigger and respond to test execution events.
Providing terminal reporting support for slow test durations.
Producing collect and test reports (
CollectReport,TestReport) from execution outcomes.Updating environment variables to reflect the current test state for external tools/debuggers.
Detailed Explanation of Classes and Functions
Public Hook Implementations
pytest_addoption(parser: Parser) -> NoneRegisters command line options for reporting slow test durations:
--durations=N to show the N slowest setup/test durations.
--durations-min=N to filter durations by a minimal threshold.
pytest_terminal_summary(terminalreporter: TerminalReporter) -> NoneAt the end of the test session, displays a summary of the slowest test durations on the terminal based on the configured options.
pytest_sessionstart(session: Session) -> NoneInitializes the session's setup state stack.
pytest_sessionfinish(session: Session) -> NoneEnsures all remaining teardown actions are performed at session end.
pytest_runtest_protocol(item: Item, nextitem: Item | None) -> boolImplements the test execution protocol for a single test item:
Logs test start.
Runs the full test lifecycle via
runtestprotocol.Logs test finish.
pytest_runtest_setup(item: Item) -> NonePrepares the test item environment by invoking the setup stack.
pytest_runtest_call(item: Item) -> NoneExecutes the test function, managing system-level exception info for postmortem debugging.
pytest_runtest_teardown(item: Item, nextitem: Item | None) -> NoneRuns teardown for the current item and updates the setup stack accordingly.
pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | NoneDetermines concise test status codes (category, letter, word) for setup and teardown phases.
Core Functions
runtestprotocol(item: Item, log: bool = True, nextitem: Item | None = None) -> list[TestReport]Runs the full test lifecycle phases for a test item:
Setup phase via
call_and_report.If setup passed and not in setup-only mode, run the call phase.
Teardown phase (always runs).
Cleans up item references to free memory.
**Parameters:**
item: The test item to run.log: Whether to log the results.nextitem: The next test item (used for optimized teardown).
**Returns:** List of `TestReport` objects for each phase.
**Usage example:**
reports = runtestprotocol(my_test_item) for rep in reports: print(f"{rep.when} phase: {rep.outcome}")call_and_report(item: Item, when: Literal["setup", "call", "teardown"], log: bool = True, **kwds) -> TestReportInvokes the appropriate pytest hook method for the test phase (
setup,call,teardown), wrapping the call in aCallInfoto capture timing and exceptions, then generates aTestReport.Handles exceptions that should propagate immediately (e.g.,
Exit,KeyboardInterruptif no debugger).Parameters:
item: Test item.when: Phase name ("setup","call", or"teardown").log: Whether to invoke pytest's log report hook.**kwds: Additional keyword arguments passed to the hook.
**Returns:** A `TestReport` instance.
check_interactive_exception(call: CallInfo[object], report: BaseReport) -> boolDetermines if an exception raised during a test phase should trigger pytest's interactive exception handling (e.g., entering a debugger).
show_test_item(item: Item) -> NonePrints the test function name, parameters, and used fixtures to the terminal.
_update_current_test_var(item: Item, when: Literal["setup", "call", "teardown"] | None) -> NoneUpdates or clears the environment variable
PYTEST_CURRENT_TESTto reflect the current test node and phase, aiding external debugging tools.pytest_make_collect_report(collector: Collector) -> CollectReportWraps the collection of test items from a collector node in a
CallInfo, handling collection exceptions and producing aCollectReport.collect_one_node(collector: Collector) -> CollectReportFacilitates collection of a single node, triggering collection start hooks and generating a collection report.
Classes
CallInfo(Generic[TResult])
A data class capturing the result or exception info of a function call within the test lifecycle.
**Attributes:**
_result: The return value of the call (if no exception).excinfo: Exception info if the call raised, elseNone.start: Start time in epoch seconds.stop: Stop time in epoch seconds.duration: Duration in seconds.when: Phase context ("collect","setup","call","teardown").
**Key Methods:**
resultproperty: Returns the call result if no exception was raised; raisesAttributeErrorotherwise.from_call(func: Callable[[], TResult], when: ..., reraise: ...) -> CallInfo[TResult]: Class method to invoke a function, capture timing, exceptions, and wrap in aCallInfo.
**Usage example:**
def some_setup():
# setup logic here
return "done"
call_info = CallInfo.from_call(some_setup, when="setup")
if call_info.excinfo is None:
print(f"Setup succeeded with result: {call_info.result}")
else:
print("Setup failed with exception")
SetupState
Manages the shared setup/teardown stack for test items and collectors within a test session.
**Purpose:**
Maintains a stack of active nodes (session, modules, items).
Ensures setup is performed in order (top-down chain).
Ensures teardown is performed in reverse order (bottom-up), only popping nodes no longer needed.
Supports adding finalizers to nodes while active.
Handles exceptions during setup and teardown, propagating them properly.
**Key Methods:**
setup(item: Item) -> NoneSets up all collectors and the test item along the collector chain. Raises stored setup exceptions if any.
addfinalizer(finalizer: Callable[[], object], node: Node) -> NoneAdds a teardown finalizer callable to a currently active node.
teardown_exact(nextitem: Item | None) -> NoneTears down nodes that are not needed by the
nextitemcollector chain. Ifnextitemis None, tears down the entire stack.
**Behavior:**
Uses a dictionary as a stack with insertion order.
On setup failure, the exception is stored and re-raised on dependent items.
On teardown, runs all finalizers in LIFO order.
Collects and aggregates multiple exceptions into
BaseExceptionGroupif supported.
**Example usage:**
setup_state = SetupState()
setup_state.setup(test_item)
# ... run test ...
setup_state.teardown_exact(next_test_item)
Algorithm and Implementation Details
Test Lifecycle Phases: The protocol strictly separates setup, call, and teardown phases, each wrapped by
call_and_reportthat captures results and exceptions uniformly.Setup Stack:
SetupStateenforces correct nesting of setup/teardown calls, preventing resource leaks or repeated setup.Exception Groups: If multiple exceptions occur during teardown, they are grouped to avoid losing information.
Environment Variable Management: The module updates
PYTEST_CURRENT_TESTto reflect the current running test and phase, sanitized to avoid null bytes.Interactive Exception Handling: Certain exceptions trigger hooks to enter interactive debugging.
Timing: Uses
_pytest.timing.Instantto capture high-resolution timing of test phases.Reporting Integration: Produces
TestReportandCollectReportobjects consumed by pytest’s reporting plugins and terminal output.
Interaction with Other System Components
Test Nodes (
Item,Collector,Node): The core entities representing tests and collection points, passed extensively through runner calls.Hooks and Plugins: Interacts with pytest hooks (
pytest_runtest_setup,pytest_runtest_call,pytest_runtest_teardown, etc.) to delegate actual test execution and to report results.Reports (
TestReport,CollectReport): Constructs these objects from execution results for downstream use in terminal and XML reporting.Terminal Reporting: Uses terminalwriter from item config to show additional information during test execution.
Exception Handling Utilities: Uses
ExceptionInfoandExceptionChainReprfor rich exception capture and presentation.Configuration: Reads pytest options like
--durationsandsetupshowto control reporting behavior.Session State: Stores
SetupStateinstance on the test session to maintain lifecycle state across tests.
Mermaid Class Diagram
classDiagram
class CallInfo~TResult~ {
- _result: TResult | None
- excinfo: ExceptionInfo[BaseException] | None
- start: float
- stop: float
- duration: float
- when: Literal["collect", "setup", "call", "teardown"]
+ result: TResult
+ from_call(func: Callable[[], TResult], when, reraise) CallInfo~TResult~
+ __repr__()
}
class SetupState {
- stack: dict~Node, tuple[list[Callable], tuple[Exception, traceback] | None]~
+ setup(item: Item)
+ addfinalizer(finalizer: Callable, node: Node)
+ teardown_exact(nextitem: Item | None)
}
CallInfo ..> ExceptionInfo
SetupState ..> Callable
SetupState ..> Node
Summary
`runner.py` is the backbone module for pytest's test execution lifecycle. It carefully manages the phases of running tests, ensures resource setup and cleanup across nested collector hierarchies, captures execution results with detailed timing and exception info, and integrates tightly with pytest's hook and reporting systems. Its design supports robustness, extensibility, and detailed user feedback during test runs.
This module directly interacts with test collection nodes, test items, session state, and reporting plugins, forming a critical bridge between test discovery and result presentation.