Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion Lib/bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import threading
import os
import weakref
from contextlib import contextmanager
from contextlib import contextmanager, suppress
from inspect import CO_GENERATOR, CO_COROUTINE, CO_ASYNC_GENERATOR

__all__ = ["BdbQuit", "Bdb", "Breakpoint"]
Expand Down Expand Up @@ -177,6 +177,17 @@ def _get_lineno(self, code, offset):
return last_lineno


def _get_executable_linenos(code):
linenos = set()
for _, _, lineno in code.co_lines():
if lineno is not None:
linenos.add(lineno)
for const in code.co_consts:
if hasattr(const, 'co_lines'):
Comment thread
aisk marked this conversation as resolved.
Outdated
linenos |= _get_executable_linenos(const)
return linenos


class Bdb:
"""Generic Python debugger base class.

Expand Down Expand Up @@ -671,6 +682,13 @@ 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)
source = ''.join(linecache.getlines(filename))
Comment thread
aisk marked this conversation as resolved.
Outdated
if source:
with suppress(SyntaxError):
code = compile(source, filename, 'exec')
executable_lines = _get_executable_linenos(code)
if executable_lines and lineno not in executable_lines:
return 'Line %d has no code associated with it' % lineno
self._add_to_breaks(filename, lineno)
bp = Breakpoint(filename, lineno, temporary, cond, funcname)
# After we set a new breakpoint, we need to search through all frames
Expand Down
56 changes: 28 additions & 28 deletions Lib/test/test_bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,43 +976,43 @@ 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]})
db1.set_break(__file__, 51)
Comment thread
aisk marked this conversation as resolved.
self.assertEqual(db1.get_all_breaks(), {fname: [51]})

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]})
db2.set_break(__file__, 52)
db2.set_break(__file__, 53)
db2.set_break(__file__, 54)
self.assertEqual(db1.get_all_breaks(), {fname: [51]})
self.assertEqual(db2.get_all_breaks(), {fname: [51, 52, 53, 54]})
db2.clear_break(__file__, 51)
self.assertEqual(db1.get_all_breaks(), {fname: [51]})
self.assertEqual(db2.get_all_breaks(), {fname: [52, 53, 54]})

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]})
self.assertEqual(db1.get_all_breaks(), {fname: [51]})
self.assertEqual(db2.get_all_breaks(), {fname: [52, 53, 54]})
self.assertEqual(db3.get_all_breaks(), {fname: [52, 53, 54]})
db2.clear_break(__file__, 52)
self.assertEqual(db1.get_all_breaks(), {fname: [51]})
self.assertEqual(db2.get_all_breaks(), {fname: [53, 54]})
self.assertEqual(db3.get_all_breaks(), {fname: [52, 53, 54]})

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]})
db4.set_break(__file__, 55)
self.assertEqual(db1.get_all_breaks(), {fname: [51]})
self.assertEqual(db2.get_all_breaks(), {fname: [53, 54]})
self.assertEqual(db3.get_all_breaks(), {fname: [52, 53, 54]})
self.assertEqual(db4.get_all_breaks(), {fname: [53, 54, 55]})
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]})
db5.set_break(__file__, 56)
self.assertEqual(db1.get_all_breaks(), {fname: [51]})
self.assertEqual(db2.get_all_breaks(), {fname: [53, 54]})
self.assertEqual(db3.get_all_breaks(), {fname: [52, 53, 54]})
self.assertEqual(db4.get_all_breaks(), {fname: [53, 54, 55]})
self.assertEqual(db5.get_all_breaks(), {fname: [56]})


class RunTestCase(BaseTestCase):
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -4188,6 +4188,22 @@ def test_breakpoint(self):
self.assertTrue(any("Breakpoint 1 at" in l for l in stdout.splitlines()), stdout)
self.assertTrue(all("SUCCESS" not in l for l in stdout.splitlines()), stdout)

def test_breakpoint_on_no_bytecode_line(self):
script = """
x = 1
def f():
global x # line 4: no bytecode
x = 2
f()
"""
commands = """
b 4
c
quit
"""
stdout, _ = self.run_pdb_module(script, commands)
self.assertIn('no code', '\n'.join(stdout.splitlines()))

def test_run_pdb_with_pdb(self):
commands = """
c
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:mod:`bdb` will reports an error when set breakpoint on a line with no
Comment thread
aisk marked this conversation as resolved.
Outdated
associated bytecode, such as :keyword:`global` or :keyword:`nonlocal`
statements.
Loading