debugging.py
Overview
The [debugging.py](/projects/286/67457) module integrates Python's interactive debugger (PDB) into the pytest testing framework, enabling automatic or manual entry into a debugging session during test execution. It provides:
Automatic debugging on test failures or interruptions (
--pdboption).Immediate debugging break at the start of each test (
--traceoption).Support for specifying custom debugger classes (
--pdbclsoption).Seamless management of pytest’s standard output capturing to prevent interference with debugging I/O.
Wrapping and extending the pdb debugger class to better integrate with pytest's test lifecycle and capture system.
This module is implemented as a pytest plugin that hooks into pytest’s lifecycle events, command line processing, and test execution to embed interactive debugging capabilities smoothly.
Detailed Explanation of Components
Functions
_validate_usepdb_cls(value: str) -> tuple[str, str]
Purpose: Validates the syntax of the
--pdbclsCLI option, which should be in the format"modulename:classname".Parameters:
value: The string value passed from the CLI.
Returns: A tuple
(modulename, classname).Raises: argparse.ArgumentTypeError if the format is invalid.
Usage Example:
modname, clsname = _validate_usepdb_cls("IPython.terminal.debugger:TerminalPdb")
pytest_addoption(parser: Parser) -> None
Purpose: Registers command line options related to debugging.
Parameters:
parser: pytest’s argument parser.
Functionality:
Adds
--pdbto enable post-failure debugging.Adds
--pdbclsto specify a custom debugger class.Adds
--traceto break into debugger at the start of each test.
Usage: This function is automatically called by pytest during plugin registration.
pytest_configure(config: Config) -> None
Purpose: Configures pytest plugins and overrides
pdb.set_tracebased on CLI options.Parameters:
config: pytest configuration object.
Functionality:
Registers
PdbTraceplugin if--traceis enabled.Registers
PdbInvokeplugin if--pdbis enabled.Saves original
pdb.set_traceand replaces it withpytestPDB.set_traceto integrate with pytest’s capturing.Adds cleanup to restore original
pdb.set_traceafter pytest finishes.
Implementation Detail:
Uses a closurefin()to restore original pdb behavior on pytest cleanup.
Classes
pytestPDB
A singleton-like class that manages integration with pdb, wrapping the debugger class and handling IO capturing during debugging sessions.
Class Attributes:
_pluginmanager: Reference to pytest’s plugin manager._config: Reference to pytest’s Config object._saved: Stack to save originalpdb.set_traceand related state._recursive_debug: Tracks recursive debugger invocations (to avoid nested capture handling)._wrapped_pdb_cls: Cache for wrapped debugger class and its original identifier.
Key Methods:
_is_capturing(capman: CaptureManager | None) -> str | bool
Returns the current capturing state orFalseif no capture manager._import_pdb_cls(capman: CaptureManager | None)
Imports and returns the wrapped pdb class based on--pdbclsoption or defaults topdb.Pdb._get_pdb_wrapper_class(pdb_cls, capman: CaptureManager | None)
Returns a subclass of the given pdb class that integrates with pytest capture management and extends key pdb commands (continue,quit,debug)._init_pdb(method: str, *args, **kwargs)
Initializes a pdb instance, suspends IO capturing, and sets up the terminal writer for output messages. Returns the pdb instance.set_trace(*args, **kwargs) -> None
Replacespdb.set_tracewith a pytest-aware version that suspends capturing and invokes pdb at the caller's frame.
Usage Example:
pytestPDB.set_trace() # Drops into pdb with pytest capture suspended
PdbInvoke
Plugin class that triggers pdb post-mortem debugging on test errors or internal errors.
Hooks Implemented:
pytest_exception_interact(node: Node, call: CallInfo, report: BaseReport) -> None
Suspends capture, prints captured output, and enters pdb unless the exception is aunittest.SkipTest.pytest_internalerror(excinfo: ExceptionInfo) -> None
Enters post-mortem pdb on pytest internal errors.
Interaction:
Registered during pytest configuration if--pdbis enabled.
PdbTrace
Plugin class that wraps test functions to start pdb immediately at test start (for `--trace`).
Hooks Implemented:
pytest_pyfunc_call(pyfuncitem) -> Generator
Wraps the test function with a debugger invocation, yielding to continue test execution.
Usage:
Registered when--traceis enabled.
Functions for Wrapping Test Functions
wrap_pytest_function_for_tracing(pyfuncitem) -> None
Purpose: Replaces a pytest test function with a wrapper that invokes pdb’s
runcall, causing pdb to break at the first statement.Parameters:
pyfuncitem: pytest test function item.
Usage: Called by
PdbTraceplugin to implement--trace.
maybe_wrap_pytest_function_for_tracing(pyfuncitem) -> None
Purpose: Conditionally wraps a test function if
--tracewas enabled.Parameters:
pyfuncitem: pytest test function item.
Internal Functions for Post-Mortem Debugging
_enter_pdb(node: Node, excinfo: ExceptionInfo, rep: BaseReport) -> BaseReport
Purpose: Handles entering pdb post-mortem on test failure.
Functionality:
Suspends capture.
Shows captured stdout, stderr, and logs, depending on test config.
Prints traceback and message.
Invokes
post_mortem()with the traceback or exception object.
Returns: The test report object.
_postmortem_exc_or_tb(excinfo: ExceptionInfo) -> types.TracebackType | BaseException
Purpose: Extracts the appropriate traceback or exception for post-mortem debugging.
Details:
Handles special cases likedoctest.UnexpectedExceptionandConftestImportFailureto return the most useful traceback.
post_mortem(tb_or_exc: types.TracebackType | BaseException) -> None
Purpose: Starts a pdb post-mortem debugging session given a traceback or exception.
Functionality:
Initializes pdb.
Resets pdb state.
Calls pdb’s
interaction()method.If quitting, raises pytest exit outcome.
Important Implementation Details and Algorithms
Dynamic Import of Custom Debugger:
The module imports the debugger class based on the--pdbclsoption at runtime, using__import__andgetattrto resolve nested class paths.Wrapper Class for pdb (
PytestPdbWrapper):
Extends pdb commands for integration:do_continue: Resumes pytest capturing and prints helpful messages.do_quit: Raises pytest’s exit outcome to stop the run cleanly.do_debug: Tracks recursive debugging sessions.Overrides
setup()to suspend IO capturing correctly after continuing.Customizes stack inspection to skip frames marked as hidden (
__tracebackhide__).
Capturing Management:
Before starting pdb (set_traceor post-mortem), pytest suspends IO capturing (capman.suspend()), and after continuing from pdb, capture is resumed. This ensures debugger input/output works interactively.Replacing
pdb.set_trace:
The module patches the globalpdb.set_tracefunction to use pytest’s version, enabling capture management even when user code callspdb.set_trace()directly.
Interaction with Other Modules
_pytest.config:
Uses configuration and plugin manager interfaces to register options and manage plugins._pytest.capture.CaptureManager:
To suspend and resume output capturing around debugger sessions._pytest.runner.CallInfoand_pytest.nodes.Node:
Accesses test call and node information during exception hooks._pytest.reports.BaseReport:
To retrieve and display captured output and tracebacks._pytest.outcomes:
To raise controlled exit signals when quitting pdb.pdbmodule:
Core debugger functionality is wrapped and extended.
Usage Examples
Starting pytest with automatic debugger on failure
pytest --pdb
When a test fails or is interrupted, pytest will suspend output capturing and drop into pdb at the failure point.
Starting pytest with immediate debugging on each test
pytest --trace
Each test function is wrapped to invoke pdb at the very first statement, allowing interactive step-through from the start.
Using a custom debugger class
pytest --pdbcls=IPython.terminal.debugger:TerminalPdb
Uses IPython's debugger instead of the default pdb.
Programmatically invoking debugger in code
import pdb
pdb.set_trace() # Actually invokes pytestPDB.set_trace during pytest runs
Mermaid Class Diagram
classDiagram
class pytestPDB {
+_pluginmanager: PytestPluginManager | None
+_config: Config | None
+_saved: list
+_recursive_debug: int
+_wrapped_pdb_cls: tuple | None
+_is_capturing(capman)
+_import_pdb_cls(capman)
+_get_pdb_wrapper_class(pdb_cls, capman)
+_init_pdb(method, *args, **kwargs)
+set_trace(*args, **kwargs)
}
class PdbInvoke {
+pytest_exception_interact(node, call, report)
+pytest_internalerror(excinfo)
}
class PdbTrace {
+pytest_pyfunc_call(pyfuncitem)
}
pytestPDB <|-- PdbInvoke : uses
pytestPDB <|-- PdbTrace : uses
Mermaid Flowchart: Debugging Invocation Flow
flowchart TD
Start[Test Run Start]
CheckTrace{--trace enabled?}
WrapTest[Wrap test function\nwith debugger]
RunTest[Run test function]
CheckError{Test failed or KeyboardInterrupt?}
CheckPdb{--pdb enabled?}
SuspendCapture[Suspend IO capturing]
ShowCapture[Show captured output]
EnterDebugger[Enter pdb post-mortem]
Continue[Continue test execution]
End[Test Run End]
Start --> CheckTrace
CheckTrace -- Yes --> WrapTest
CheckTrace -- No --> RunTest
WrapTest --> RunTest
RunTest --> CheckError
CheckError -- Yes --> CheckPdb
CheckError -- No --> Continue
CheckPdb -- Yes --> SuspendCapture
CheckPdb -- No --> Continue
SuspendCapture --> ShowCapture --> EnterDebugger --> Continue
Continue --> End
Summary
The [debugging.py](/projects/286/67457) module is a core pytest plugin that enhances developer productivity by integrating Python's interactive debugger into test runs. It supports automatic post-failure debugging, immediate debugging on test start, and custom debugger classes while carefully managing pytest's output capturing system to maintain clear and interactive debugging sessions. The wrapping and extension of the pdb class ensure seamless cooperation between pytest's test lifecycle and the interactive debugging environment.
This module interacts closely with pytest’s core systems such as configuration, test reporting, capture management, and plugin architecture to provide an extensible, robust debugging experience tailored to automated testing workflows.