[Python-checkins] bpo-24160: Fix breakpoints persistence across multiple pdb sessions (GH-21989)

gvanrossum webhook-mailer at python.org
Fri Apr 2 12:15:37 EDT 2021


https://github.com/python/cpython/commit/ad442a674ca443feec43a88a2d3671784712e550
commit: ad442a674ca443feec43a88a2d3671784712e550
branch: master
author: Irit Katriel <iritkatriel at yahoo.com>
committer: gvanrossum <gvanrossum at gmail.com>
date: 2021-04-02T09:15:21-07:00
summary:

bpo-24160: Fix breakpoints persistence across multiple pdb sessions (GH-21989)

files:
A Misc/NEWS.d/next/Library/2020-08-28-23-07-53.bpo-24160.MSGnKr.rst
M Lib/bdb.py
M Lib/test/test_bdb.py
M Lib/test/test_pdb.py

diff --git a/Lib/bdb.py b/Lib/bdb.py
index b18a0612d8c78..abb50c092e69b 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -34,6 +34,8 @@ def __init__(self, skip=None):
         self.fncache = {}
         self.frame_returning = None
 
+        self._load_breaks()
+
     def canonic(self, filename):
         """Return canonical form of filename.
 
@@ -365,6 +367,12 @@ def set_quit(self):
     # 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 @@ def set_break(self, filename, lineno, temporary=False, cond=None,
         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 @@ def __init__(self, file, line, temporary=False, cond=None, funcname=None):
         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.
 
diff --git a/Lib/test/test_bdb.py b/Lib/test/test_bdb.py
index 71be069d2412c..398698c79c16d 100644
--- a/Lib/test/test_bdb.py
+++ b/Lib/test/test_bdb.py
@@ -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 @@ def test_clear_at_no_bp(self):
         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."""
 
diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py
index d5abc3f95daca..98e2b937d1339 100644
--- a/Lib/test/test_pdb.py
+++ b/Lib/test/test_pdb.py
@@ -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()
diff --git a/Misc/NEWS.d/next/Library/2020-08-28-23-07-53.bpo-24160.MSGnKr.rst b/Misc/NEWS.d/next/Library/2020-08-28-23-07-53.bpo-24160.MSGnKr.rst
new file mode 100644
index 0000000000000..c0cfd87b878af
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-08-28-23-07-53.bpo-24160.MSGnKr.rst
@@ -0,0 +1 @@
+Fixed bug where breakpoints did not persist across multiple debugger sessions in :mod:`pdb`'s interactive mode.



More information about the Python-checkins mailing list