Debugger Integration

This module provides the integration of Python's interactive debugger (PDB) within the pytest testing framework. Its core purpose is to enable developers to enter an interactive debugging session automatically when tests fail, raise exceptions, or when explicitly requested via command line options. This integration facilitates immediate investigation of test failures by dropping into PDB, improving developer productivity and debugging efficiency.


Core Concepts and Purpose


How the Debugger Integration Works

Command Line Options and Plugin Registration

The integration registers three key CLI options via the `pytest_addoption` hook:

Based on these options, the integration registers two plugin classes during `pytest_configure`:

The `pytest_configure` function also overrides the global [pdb.set_trace](/projects/286/67332) function with a pytest-aware wrapper to integrate with pytest's capturing and reporting systems.

Wrapping Test Functions for Immediate Debugging

When the `--trace` option is enabled, `PdbTrace` wraps each test function. The wrapper runs the test function inside the debugger's `runcall` method, causing pytest to enter the debugger prompt at the very first statement of the test function.

This wrapping is done by replacing the `obj` attribute (the test function) on the pytest test item with a debugger-invoking wrapper function.

Handling Debugging on Test Failures

When a test fails, the `pytest_exception_interact` hook in `PdbInvoke` is triggered. This hook:

The `_enter_pdb` function uses the test report to show captured output and the traceback before invoking `post_mortem` to start the debugger at the exception traceback or exception object.

Custom Debugger Class Support

Users can specify a debugger class using the `--pdbcls` option with the syntax `module:classname`. The integration dynamically imports and wraps this debugger class to integrate with pytest's capture management and reporting.

The wrapping adds features such as:

This wrapper class is cached to avoid repeated imports and wrapping during a test run.

Overriding pdb.set_trace

The module replaces the global [pdb.set_trace](/projects/286/67332) function with `pytestPDB.set_trace`. This replacement ensures that when [set_trace()](/projects/286/67332) is called programmatically within tests or fixtures, pytest suspends IO capturing and initializes the debugger properly with pytest's enhancements.


Interaction with Other System Components


Key Functionalities and Workflows

Debugging on Test Failure Workflow

  1. Test execution raises an exception.

  2. Pytest's hook pytest_exception_interact is invoked.

  3. The capture manager suspends output capturing.

  4. Captured output and tracebacks are displayed.

  5. The debugger is initialized at the exact failure point (post-mortem).

  6. User interacts with the debugger to inspect state.

  7. Debugger continue resumes test execution or quit stops the session.

Immediate Debugging on Test Start (--trace)

  1. Pytest wraps the test function with a debugger call before execution.

  2. The debugger prompt appears immediately on test start.

  3. User can step through the test interactively from the first statement.

Custom Debugger Class Loading

  1. Parses the --pdbcls option.

  2. Dynamically imports the specified module and retrieves the class.

  3. Wraps the debugger class with pytest-specific functionality.

  4. Uses this custom debugger class for all debugging sessions.


Important Design Patterns and Approaches


Code Snippets Illustrating Key Concepts

Registering CLI Options and Plugins

def pytest_addoption(parser: Parser) -> None:
    group = parser.getgroup("general")
    group.addoption("--pdb", dest="usepdb", action="store_true", help="Start debugger on errors")
    group.addoption("--pdbcls", dest="usepdb_cls", metavar="modulename:classname", type=_validate_usepdb_cls, help="Custom debugger class")
    group.addoption("--trace", dest="trace", action="store_true", help="Break immediately when running each test")

def pytest_configure(config: Config) -> None:
    if config.getvalue("trace"):
        config.pluginmanager.register(PdbTrace(), "pdbtrace")
    if config.getvalue("usepdb"):
        config.pluginmanager.register(PdbInvoke(), "pdbinvoke")

Wrapping a Test Function to Enter Debugger Immediately (--trace)

def wrap_pytest_function_for_tracing(pyfuncitem) -> None:
    _pdb = pytestPDB._init_pdb("runcall")
    testfunction = pyfuncitem.obj

    @functools.wraps(testfunction)
    def wrapper(*args, **kwargs) -> None:
        func = functools.partial(testfunction, *args, **kwargs)
        _pdb.runcall(func)

    pyfuncitem.obj = wrapper

Entering Post-Mortem Debugging on Test Failure

def pytest_exception_interact(self, node, call, report) -> None:
    capman = node.config.pluginmanager.getplugin("capturemanager")
    if capman:
        capman.suspend_global_capture(in_=True)
    _enter_pdb(node, call.excinfo, report)

def _enter_pdb(node, excinfo, rep):
    tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
    tw.sep(">", "traceback")
    rep.toterminal(tw)
    tw.sep(">", "entering PDB")
    tb_or_exc = _postmortem_exc_or_tb(excinfo)
    post_mortem(tb_or_exc)

Mermaid Sequence Diagram: Debugging on Test Failure Flow

sequenceDiagram
    participant TestRunner
    participant CaptureManager
    participant PdbInvoke
    participant TerminalReporter
    participant Debugger

    TestRunner->>PdbInvoke: pytest_exception_interact(node, call, report)
    PdbInvoke->>CaptureManager: suspend_global_capture(in_=True)
    CaptureManager-->>PdbInvoke: capturing suspended
    PdbInvoke->>TerminalReporter: display captured output & traceback
    TerminalReporter-->>PdbInvoke: output displayed
    PdbInvoke->>Debugger: post_mortem(traceback)
    Debugger-->>PdbInvoke: interactive debugging started
    Debugger->>TestRunner: on quit exit testing

This documentation explains how pytest integrates the Python debugger (PDB) to provide an interactive debugging experience on test failures or via command line options. The integration manages output capturing intricately and supports wrapping tests for immediate debugging, making it a powerful tool for developers diagnosing test problems.