Skip to content

custom set_trace function with coverage enabled stops at the wrong location on 3.14 #2095

@bretello

Description

@bretello

Describe the bug

Running a script with a custom pdb.set_trace() wrapper on 3.14 results in the debugger stopping at the wrong location (one line over)

To Reproduce

Click to show complete reproduction script
#!/bin/bash

# create venvs and install coverage for 3.14 and 3.5
for pyv in 3.14 3.14; do
    venv="venv-${pyv}"
    uv venv "$venv" --python $pyv

    source ${venv}/bin/activate

    uv pip install coverage==7.12.0
done

# setup the pdb commands to run
pdb_cmds=$(
    cat <<EOF
continue
continue
EOF
)

# reproduction python script: a custom set_trace function plus a set_trace call
repro_pyscript="wrong_break_location_repro.py"
cat >${repro_pyscript} <<EOF
import pdb
import sys
import inspect

def set_trace(frame=None):
    if frame is None:
        frame = inspect.currentframe().f_back

    pdb.Pdb().set_trace(frame)


def f():
    print(f"{sys.version=}")
    print("coverage=", "coverage" in sys.modules)
    a = 1
    set_trace()
    a = 2
    set_trace()


if __name__ == "__main__":
    f()
EOF

# run a 4-way diff: python 3.14 vs 3.13 with coverage enabled and disabled
vimdiff \
    <(echo -e "$pdb_cmds" | ./venv-3.13/bin/coverage run ${repro_pyscript} 2>&1) \
    <(echo -e "$pdb_cmds" | ./venv-3.14/bin/coverage run ${repro_pyscript} 2>&1) \
    <(echo -e "$pdb_cmds" | ./venv-3.13/bin/python ${repro_pyscript} 2>&1) \
    <(echo -e "$pdb_cmds" | ./venv-3.14/bin/python ${repro_pyscript} 2>&1)

Additional information:

  1. Using python 3.14.0 and 3.13.1
  2. Problem happens on latest release (7.12.0) and latest main (de192da). First commit showing the issue is 7795441
  3. Running the above script will create virtualenvs for 3.13 and 3.14, write a testfile and show a 4-way diff against python 3.14/3.15 with coverage enabled and disabled. See below for an explanation.

Expected behavior
Running this script through coverage run should break on set_trace():

def set_trace(frame=None):
    if frame is None:
        frame = inspect.currentframe().f_back

    pdb.Pdb().set_trace(frame)

def f():
    print(f"{sys.version=}")
    print("coverage={"coverage" in in sys.modules}")
    a = 1
    set_trace()
    a = 2
    set_trace()

This is the output on 3.14 when running through coverage run : Instead of stopping on set_trace(), the we stop on the line following the call, the second one stops on set_trace() as expected:

sys.version='3.14.0 (main, Oct 14 2025, 21:27:55) [Clang 20.1.4 ]'
coverage=True
> ./wrong_break_location_repro.py(19)f()
-> a = 2
continue
(Pdb) > ./wrong_break_location_repro.py(20)f()
-> set_trace()
(Pdb) 
continue

This is the output on 3.14 when running as a script: set_trace breaks on the set_trace calls both times (same behaviour as 3.13 with and without coverage enabled)

sys.version='3.14.0 (main, Oct 14 2025, 21:27:55) [Clang 20.1.4 ]'
coverage=False
> ./wrong_break_location_repro.py(18)f()
-> set_trace()
continue
(Pdb) > ./wrong_break_location_repro.py(20)f()
-> set_trace()
(Pdb)
continue

Additional context

Switching over to ctrace solves the issue, e.g. by setting in pyproject.toml:

[tool.coverage.run]
core = "ctrace"

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions