mirror of
https://github.com/python/cpython.git
synced 2025-10-10 00:43:41 +00:00
bpo-24160: Fix breakpoints persistence across multiple pdb sessions (GH-21989)
This commit is contained in:
parent
afd1265058
commit
ad442a674c
4 changed files with 144 additions and 13 deletions
29
Lib/bdb.py
29
Lib/bdb.py
|
@ -34,6 +34,8 @@ class Bdb:
|
|||
self.fncache = {}
|
||||
self.frame_returning = None
|
||||
|
||||
self._load_breaks()
|
||||
|
||||
def canonic(self, filename):
|
||||
"""Return canonical form of filename.
|
||||
|
||||
|
@ -365,6 +367,12 @@ class Bdb:
|
|||
# Call self.get_*break*() to see the breakpoints or better
|
||||
# for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().
|
||||
|
||||
def _add_to_breaks(self, filename, lineno):
|
||||
"""Add breakpoint to breaks, if not already there."""
|
||||
bp_linenos = self.breaks.setdefault(filename, [])
|
||||
if lineno not in bp_linenos:
|
||||
bp_linenos.append(lineno)
|
||||
|
||||
def set_break(self, filename, lineno, temporary=False, cond=None,
|
||||
funcname=None):
|
||||
"""Set a new breakpoint for filename:lineno.
|
||||
|
@ -377,12 +385,21 @@ class Bdb:
|
|||
line = linecache.getline(filename, lineno)
|
||||
if not line:
|
||||
return 'Line %s:%d does not exist' % (filename, lineno)
|
||||
list = self.breaks.setdefault(filename, [])
|
||||
if lineno not in list:
|
||||
list.append(lineno)
|
||||
self._add_to_breaks(filename, lineno)
|
||||
bp = Breakpoint(filename, lineno, temporary, cond, funcname)
|
||||
return None
|
||||
|
||||
def _load_breaks(self):
|
||||
"""Apply all breakpoints (set in other instances) to this one.
|
||||
|
||||
Populates this instance's breaks list from the Breakpoint class's
|
||||
list, which can have breakpoints set by another Bdb instance. This
|
||||
is necessary for interactive sessions to keep the breakpoints
|
||||
active across multiple calls to run().
|
||||
"""
|
||||
for (filename, lineno) in Breakpoint.bplist.keys():
|
||||
self._add_to_breaks(filename, lineno)
|
||||
|
||||
def _prune_breaks(self, filename, lineno):
|
||||
"""Prune breakpoints for filename:lineno.
|
||||
|
||||
|
@ -681,6 +698,12 @@ class Breakpoint:
|
|||
else:
|
||||
self.bplist[file, line] = [self]
|
||||
|
||||
@staticmethod
|
||||
def clearBreakpoints():
|
||||
Breakpoint.next = 1
|
||||
Breakpoint.bplist = {}
|
||||
Breakpoint.bpbynumber = [None]
|
||||
|
||||
def deleteMe(self):
|
||||
"""Delete the breakpoint from the list associated to a file:line.
|
||||
|
||||
|
|
|
@ -74,9 +74,7 @@ class BdbNotExpectedError(BdbException): """Unexpected result."""
|
|||
dry_run = 0
|
||||
|
||||
def reset_Breakpoint():
|
||||
_bdb.Breakpoint.next = 1
|
||||
_bdb.Breakpoint.bplist = {}
|
||||
_bdb.Breakpoint.bpbynumber = [None]
|
||||
_bdb.Breakpoint.clearBreakpoints()
|
||||
|
||||
def info_breakpoints():
|
||||
bp_list = [bp for bp in _bdb.Breakpoint.bpbynumber if bp]
|
||||
|
@ -951,6 +949,49 @@ class BreakpointTestCase(BaseTestCase):
|
|||
with TracerRun(self) as tracer:
|
||||
self.assertRaises(BdbError, tracer.runcall, tfunc_import)
|
||||
|
||||
def test_load_bps_from_previous_Bdb_instance(self):
|
||||
reset_Breakpoint()
|
||||
db1 = Bdb()
|
||||
fname = db1.canonic(__file__)
|
||||
db1.set_break(__file__, 1)
|
||||
self.assertEqual(db1.get_all_breaks(), {fname: [1]})
|
||||
|
||||
db2 = Bdb()
|
||||
db2.set_break(__file__, 2)
|
||||
db2.set_break(__file__, 3)
|
||||
db2.set_break(__file__, 4)
|
||||
self.assertEqual(db1.get_all_breaks(), {fname: [1]})
|
||||
self.assertEqual(db2.get_all_breaks(), {fname: [1, 2, 3, 4]})
|
||||
db2.clear_break(__file__, 1)
|
||||
self.assertEqual(db1.get_all_breaks(), {fname: [1]})
|
||||
self.assertEqual(db2.get_all_breaks(), {fname: [2, 3, 4]})
|
||||
|
||||
db3 = Bdb()
|
||||
self.assertEqual(db1.get_all_breaks(), {fname: [1]})
|
||||
self.assertEqual(db2.get_all_breaks(), {fname: [2, 3, 4]})
|
||||
self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]})
|
||||
db2.clear_break(__file__, 2)
|
||||
self.assertEqual(db1.get_all_breaks(), {fname: [1]})
|
||||
self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]})
|
||||
self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]})
|
||||
|
||||
db4 = Bdb()
|
||||
db4.set_break(__file__, 5)
|
||||
self.assertEqual(db1.get_all_breaks(), {fname: [1]})
|
||||
self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]})
|
||||
self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]})
|
||||
self.assertEqual(db4.get_all_breaks(), {fname: [3, 4, 5]})
|
||||
reset_Breakpoint()
|
||||
|
||||
db5 = Bdb()
|
||||
db5.set_break(__file__, 6)
|
||||
self.assertEqual(db1.get_all_breaks(), {fname: [1]})
|
||||
self.assertEqual(db2.get_all_breaks(), {fname: [3, 4]})
|
||||
self.assertEqual(db3.get_all_breaks(), {fname: [2, 3, 4]})
|
||||
self.assertEqual(db4.get_all_breaks(), {fname: [3, 4, 5]})
|
||||
self.assertEqual(db5.get_all_breaks(), {fname: [6]})
|
||||
|
||||
|
||||
class RunTestCase(BaseTestCase):
|
||||
"""Test run, runeval and set_trace."""
|
||||
|
||||
|
|
|
@ -213,6 +213,9 @@ def test_pdb_basic_commands():
|
|||
BAZ
|
||||
"""
|
||||
|
||||
def reset_Breakpoint():
|
||||
import bdb
|
||||
bdb.Breakpoint.clearBreakpoints()
|
||||
|
||||
def test_pdb_breakpoint_commands():
|
||||
"""Test basic commands related to breakpoints.
|
||||
|
@ -227,10 +230,7 @@ def test_pdb_breakpoint_commands():
|
|||
First, need to clear bdb state that might be left over from previous tests.
|
||||
Otherwise, the new breakpoints might get assigned different numbers.
|
||||
|
||||
>>> from bdb import Breakpoint
|
||||
>>> Breakpoint.next = 1
|
||||
>>> Breakpoint.bplist = {}
|
||||
>>> Breakpoint.bpbynumber = [None]
|
||||
>>> reset_Breakpoint()
|
||||
|
||||
Now test the breakpoint commands. NORMALIZE_WHITESPACE is needed because
|
||||
the breakpoint list outputs a tab for the "stop only" and "ignore next"
|
||||
|
@ -323,6 +323,72 @@ def test_pdb_breakpoint_commands():
|
|||
4
|
||||
"""
|
||||
|
||||
def test_pdb_breakpoints_preserved_across_interactive_sessions():
|
||||
"""Breakpoints are remembered between interactive sessions
|
||||
|
||||
>>> reset_Breakpoint()
|
||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
... 'import test.test_pdb',
|
||||
... 'break test.test_pdb.do_something',
|
||||
... 'break test.test_pdb.do_nothing',
|
||||
... 'break',
|
||||
... 'continue',
|
||||
... ]):
|
||||
... pdb.run('print()')
|
||||
> <string>(1)<module>()
|
||||
(Pdb) import test.test_pdb
|
||||
(Pdb) break test.test_pdb.do_something
|
||||
Breakpoint 1 at ...test_pdb.py:...
|
||||
(Pdb) break test.test_pdb.do_nothing
|
||||
Breakpoint 2 at ...test_pdb.py:...
|
||||
(Pdb) break
|
||||
Num Type Disp Enb Where
|
||||
1 breakpoint keep yes at ...test_pdb.py:...
|
||||
2 breakpoint keep yes at ...test_pdb.py:...
|
||||
(Pdb) continue
|
||||
|
||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
... 'break',
|
||||
... 'break pdb.find_function',
|
||||
... 'break',
|
||||
... 'clear 1',
|
||||
... 'continue',
|
||||
... ]):
|
||||
... pdb.run('print()')
|
||||
> <string>(1)<module>()
|
||||
(Pdb) break
|
||||
Num Type Disp Enb Where
|
||||
1 breakpoint keep yes at ...test_pdb.py:...
|
||||
2 breakpoint keep yes at ...test_pdb.py:...
|
||||
(Pdb) break pdb.find_function
|
||||
Breakpoint 3 at ...pdb.py:94
|
||||
(Pdb) break
|
||||
Num Type Disp Enb Where
|
||||
1 breakpoint keep yes at ...test_pdb.py:...
|
||||
2 breakpoint keep yes at ...test_pdb.py:...
|
||||
3 breakpoint keep yes at ...pdb.py:...
|
||||
(Pdb) clear 1
|
||||
Deleted breakpoint 1 at ...test_pdb.py:...
|
||||
(Pdb) continue
|
||||
|
||||
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
... 'break',
|
||||
... 'clear 2',
|
||||
... 'clear 3',
|
||||
... 'continue',
|
||||
... ]):
|
||||
... pdb.run('print()')
|
||||
> <string>(1)<module>()
|
||||
(Pdb) break
|
||||
Num Type Disp Enb Where
|
||||
2 breakpoint keep yes at ...test_pdb.py:...
|
||||
3 breakpoint keep yes at ...pdb.py:...
|
||||
(Pdb) clear 2
|
||||
Deleted breakpoint 2 at ...test_pdb.py:...
|
||||
(Pdb) clear 3
|
||||
Deleted breakpoint 3 at ...pdb.py:...
|
||||
(Pdb) continue
|
||||
"""
|
||||
|
||||
def do_nothing():
|
||||
pass
|
||||
|
@ -699,8 +765,7 @@ def test_next_until_return_at_return_event():
|
|||
... test_function_2()
|
||||
... end = 1
|
||||
|
||||
>>> from bdb import Breakpoint
|
||||
>>> Breakpoint.next = 1
|
||||
>>> reset_Breakpoint()
|
||||
>>> with PdbTestInput(['break test_function_2',
|
||||
... 'continue',
|
||||
... 'return',
|
||||
|
@ -1137,7 +1202,7 @@ def test_pdb_next_command_in_generator_for_loop():
|
|||
> <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[1]>(3)test_function()
|
||||
-> for i in test_gen():
|
||||
(Pdb) break test_gen
|
||||
Breakpoint 6 at <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[0]>:1
|
||||
Breakpoint 1 at <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[0]>:1
|
||||
(Pdb) continue
|
||||
> <doctest test.test_pdb.test_pdb_next_command_in_generator_for_loop[0]>(2)test_gen()
|
||||
-> yield 0
|
||||
|
@ -1213,6 +1278,7 @@ def test_pdb_issue_20766():
|
|||
... print('pdb %d: %s' % (i, sess._previous_sigint_handler))
|
||||
... i += 1
|
||||
|
||||
>>> reset_Breakpoint()
|
||||
>>> with PdbTestInput(['continue',
|
||||
... 'continue']):
|
||||
... test_function()
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fixed bug where breakpoints did not persist across multiple debugger sessions in :mod:`pdb`'s interactive mode.
|
Loading…
Add table
Add a link
Reference in a new issue