Skip to content

Commit 0fabb0a

Browse files
authored
Revise breakpoint handling (#73)
* Track down code in breakpoint command WARNING -- needs a new filecache API for this. * Fix up list command range parsing * Use github for pyficache in CI
1 parent 2eeb516 commit 0fabb0a

File tree

12 files changed

+97
-68
lines changed

12 files changed

+97
-68
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
# - run: pip install --local -e git+https://github.com/rocky/pytracer.git#egg=tracer
2121
# Until the next pyficache release
2222
# - run: pip install pyficache
23-
# - run: pip install --local -e git+https://github.com/rocky/python-filecache.git#egg=pyficache
23+
- run: pip install --local -e git+https://github.com/rocky/python-filecache.git#egg=pyficache
2424
- run: pip install uncompyle6
2525
# - run: pip install --local -e git+https://github.com/rocky/python-decompile3.git#egg=decompyle3
2626
- run: pip install --local -e .

.github/workflows/ubuntu-prompt-toolkit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
# Until the next pytracer release
2727
# pip install git+https://github.com/rocky/pytracer#egg=tracer
2828
# Until the next pyficache release
29-
# pip install git+https://github.com/rocky/python-filecache#egg=pyficache
29+
pip install git+https://github.com/rocky/python-filecache#egg=pyficache
3030
pip install -e .[dev,full]
3131
- name: Test trepan3k
3232
run: |

.github/workflows/ubuntu.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
# Until the next pytracer release
2727
# pip install git+https://github.com/rocky/pytracer#egg=tracer
2828
# Until the next pyficache release
29-
# pip install git+https://github.com/rocky/python-filecache#egg=pyficache
29+
pip install git+https://github.com/rocky/python-filecache#egg=pyficache
3030
pip install -e .[dev]
3131
- name: Test trepan3k
3232
run: |

test/unit/lib/test_lib_brkpt.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def bar():
2020
bpmgr = BreakpointManager()
2121
assert 0 == bpmgr.last()
2222
line_number = foo.__code__.co_firstlineno
23-
bp = bpmgr.add_breakpoint(__file__, line_number, 0, func=foo)
23+
bp = bpmgr.add_breakpoint(__file__, line_number, 0, func_or_code=foo)
2424

2525
assert re.search(r"1\s+breakpoint\s+keep\s+yes .*0 at.*%d" % line_number, str(bp)), str(bp)
2626
assert "B" == bp.icon_char()
@@ -41,14 +41,14 @@ def bar():
4141
"Breakpoint 1 previously deleted.",
4242
)
4343
line_number = test_breakpoint.__code__.co_firstlineno
44-
bp2 = bpmgr.add_breakpoint(__file__, line_number, 0, func=test_breakpoint, temporary=True)
44+
bp2 = bpmgr.add_breakpoint(__file__, line_number, 0, func_or_code=test_breakpoint, temporary=True)
4545
assert "t" == bp2.icon_char()
4646
assert ["2"] == bpmgr.bpnumbers(), "Extracting breakpoint-numbers"
4747

4848
count = 3
4949
line_number = bar.__code__.co_firstlineno
5050
for _ in range(count):
51-
bp = bpmgr.add_breakpoint(__file__, line_number, 0, func=bar)
51+
bp = bpmgr.add_breakpoint(__file__, line_number, 0, func_or_code=bar)
5252
filename = bp.filename
5353
assert count == len(
5454
bpmgr.delete_breakpoints_by_lineno(os.path.realpath(filename), line_number)
@@ -64,7 +64,7 @@ def test_checkfuncname():
6464

6565
bpmgr = BreakpointManager()
6666
frame = inspect.currentframe()
67-
bp = bpmgr.add_breakpoint(__file__, frame.f_lineno + 1, -1, func=test_checkfuncname)
67+
bp = bpmgr.add_breakpoint(__file__, frame.f_lineno + 1, -1, func_or_code=test_checkfuncname)
6868
assert checkfuncname(bp, frame)
6969

7070
def foo(brkpt, bpmgr):
@@ -73,12 +73,12 @@ def foo(brkpt, bpmgr):
7373
# current_frame.f_lineno is constantly updated. So adjust for line
7474
# the difference between the add_breakpoint and the check.
7575
bp3 = bpmgr.add_breakpoint(
76-
os.path.realpath(__file__), current_frame.f_lineno + 2, -1, False, func=foo
76+
os.path.realpath(__file__), current_frame.f_lineno + 2, -1, False, func_or_code=foo
7777
)
7878
assert checkfuncname(bp3, current_frame), str(bp3)
7979
assert not checkfuncname(bp3, current_frame)
8080
return
8181

82-
bp2 = bpmgr.add_breakpoint(__file__, None, -1, False, None, func=foo)
82+
bp2 = bpmgr.add_breakpoint(__file__, None, -1, False, None, func_or_code=foo)
8383
foo(bp2, bpmgr)
8484
return

test/unit/lib/test_lib_complete.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def test_next_token():
6161
def test_complete_brkpts():
6262
bpmgr = BreakpointManager()
6363
frame = inspect.currentframe()
64-
bp = bpmgr.add_breakpoint(__file__, frame.f_lineno, 10, func=test_complete_brkpts)
64+
bp = bpmgr.add_breakpoint(__file__, frame.f_lineno, 10, func_or_code=test_complete_brkpts)
6565
assert bp
6666
for find in "1":
6767
assert complete_brkpts(bpmgr, find) == [

test/unit/processor/command/test_cmd_break.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"""Unit test for trepan.processor.command.break"""
22

33
import os
4-
import platform
54
from test.unit.cmdhelper import errmsg, msg, reset_output, setup_unit_test_debugger
65

7-
from trepan.processor.cmdbreak import parse_break_cmd
6+
from trepan.processor.cmdbreak import INVALID_PARSE_BREAK, parse_break_cmd
87

98
# We have to use this subterfuge because "break" is Python reserved word,
109
# so it can't be used as a module-name component.
@@ -37,17 +36,13 @@ def test_parse_break_cmd():
3736
assert (expected_code, None, True, True) == (code, cond, li > 1, offset > 0)
3837
assert fi.endswith(__file__)
3938

40-
code, fi, li, cond, offset = parse_break_cmd_wrapper(proc, "break 11")
41-
assert isinstance(fi, str)
42-
assert (expected_code, None, 11, None) == (code, cond, li, offset)
39+
result = parse_break_cmd_wrapper(proc, "break 2")
40+
assert result == INVALID_PARSE_BREAK
4341

44-
if platform.system() == "Windows":
45-
brk_cmd = f'b """{__file__}""":8'
46-
else:
47-
brk_cmd = f"b {__file__}:8"
42+
brk_cmd = f'b """{__file__}""":1'
4843

4944
code, fi, li, cond, offset = parse_break_cmd_wrapper(proc, brk_cmd)
50-
assert (True, 8) == (isinstance(fi, str), li)
45+
assert (__file__, 1) == (fi, li)
5146
# FIXME: This varies. Why?
5247
# assert "<module>" == code
5348

trepan/lib/breakpoint.py

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
__all__ = ["BreakpointManager", "Breakpoint"]
88

99
import os.path as osp
10+
from collections import defaultdict
1011
from types import CodeType, ModuleType
1112
from typing import Optional
1213
from xdis import load_module
@@ -138,8 +139,8 @@ def __init__(self):
138139
# class unless it is put inside __init__
139140

140141
self.bpbynumber: list = [None]
141-
self.bplist = {}
142-
self.code_list = {}
142+
self.bplist = defaultdict(list)
143+
self.code_list = defaultdict(list)
143144
return
144145

145146
def bpnumbers(self):
@@ -174,7 +175,7 @@ def add_breakpoint(
174175
offset: int = -1,
175176
temporary: bool = False,
176177
condition: Optional[str] = None,
177-
func = None,
178+
func_or_code = None,
178179
):
179180
"""
180181
Add a breakpoint in ``filename`` at line number ``lineno``.
@@ -188,41 +189,33 @@ def add_breakpoint(
188189
filename = osp.realpath(filename)
189190

190191
assert (
191-
isinstance(filename, str) or func is not None
192+
isinstance(filename, str) or func_or_code is not None
192193
), "You must either supply a filename or give a line number"
193194

194-
if isinstance(func, CodeType):
195-
code = func
196-
elif isinstance(func, ModuleType):
197-
if hasattr(func, "__cached__"):
195+
if isinstance(func_or_code, CodeType):
196+
code = func_or_code
197+
elif isinstance(func_or_code, ModuleType):
198+
if hasattr(func_or_code, "__cached__"):
198199
# FIXME: we can probably do better hooking into importlib
199200
# or something lower-level
200-
_, _, _, code, _, _, _ = load_module(func.__cached__, fast_load=True, get_code=True)
201+
_, _, _, code, _, _, _ = load_module(func_or_code.__cached__, fast_load=True, get_code=True)
201202
else:
202-
print(f"Don't know what to do with frozen module {func}")
203+
print(f"Don't know what to do with frozen module {func_or_code}")
203204
return
204-
elif hasattr(func, "__code__"):
205-
code = func.__code__
206-
elif hasattr(func, "f_code"):
207-
code = func.f_code
205+
elif hasattr(func_or_code, "__code__"):
206+
code = func_or_code.__code__
207+
elif hasattr(func_or_code, "f_code"):
208+
code = func_or_code.f_code
208209
else:
209-
print(f"Don't know what to do with {func}, {type(func)}")
210+
print(f"Don't know what to do with {func_or_code}, {type(func_or_code)}")
210211
return
211212
brkpt = Breakpoint(bpnum, filename, lineno, temporary, condition, code, offset)
212213

213214
# Build the internal lists of breakpoints
214215
self.bpbynumber.append(brkpt)
215-
if (filename, lineno) in self.bplist:
216-
self.bplist[filename, lineno].append(brkpt)
217-
else:
218-
self.bplist[filename, lineno] = [brkpt]
219-
pass
220-
if func and offset in [None, -1]:
221-
if code in self.code_list:
222-
self.code_list[code].append(brkpt)
223-
else:
224-
self.code_list[code] = [brkpt]
225-
pass
216+
self.bplist[filename, lineno].append(brkpt)
217+
if func_or_code and offset in [None, -1]:
218+
self.code_list[code].append(brkpt)
226219
return brkpt
227220

228221
def delete_all_breakpoints(self) -> str:

trepan/misc.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
# You should have received a copy of the GNU General Public License
1616
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717

18+
import inspect
1819
import os
1920
from glob import glob
21+
from types import CodeType
2022
from typing import Any
2123

2224

@@ -55,7 +57,11 @@ def wrapped_lines(msg_part1: str, msg_part2: str, width: int) -> str:
5557

5658

5759
def pretty_modfunc_name(s) -> str:
58-
if s == "<module>":
60+
if isinstance(s, CodeType):
61+
return f"{s.co_name}()"
62+
elif inspect.isfunction(s):
63+
return f"{s.__name__}()"
64+
elif str(s).startswith("<"):
5965
# FIXME:
6066
# Pick replace with something more custom to modname?
6167
return str(s)
@@ -91,4 +97,6 @@ def get_option(key):
9197
print(wrapped_lines("hi", "there", 80))
9298
print(wrapped_lines("hi", "there", 5))
9399
print(pyfiles(__file__))
100+
print(pretty_modfunc_name(pretty_modfunc_name.__code__))
101+
print(pretty_modfunc_name(pyfiles))
94102
pass

trepan/processor/cmdbreak.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
def set_break(
2828
cmd_obj,
29-
func,
29+
func_or_code,
3030
filename,
3131
lineno,
3232
condition,
@@ -48,7 +48,9 @@ def set_break(
4848
pass
4949

5050
if lineno:
51-
line_info = code_line_info(filename, lineno)
51+
code_map, line_info = code_line_info(filename, lineno)
52+
if isinstance(func_or_code, str):
53+
func_or_code = code_map.get(func_or_code, func_or_code)
5254
if not line_info:
5355
linestarts = dict(findlinestarts(cmd_obj.proc.curframe.f_code))
5456
if lineno not in linestarts.values():
@@ -87,23 +89,30 @@ def set_break(
8789
offset=offset,
8890
temporary=temporary,
8991
condition=condition,
90-
func=func,
92+
func_or_code=func_or_code,
9193
)
92-
if func and inspect.isfunction(func):
93-
cmd_obj.msg(f"Breakpoint {bp.number} set on calling function {func.__name__}()")
94+
if func_or_code and inspect.isfunction(func_or_code):
95+
cmd_obj.msg(f"Breakpoint {bp.number} set on calling function {func_or_code.__name__}()")
9496
part1 = f"Currently this is line {lineno} of file"
9597
msg = wrapped_lines(
9698
part1, cmd_obj.core.filename(filename), cmd_obj.settings["width"]
9799
)
98100
cmd_obj.msg(msg)
99101
else:
100-
part1 = f"Breakpoint {bp.number} set at line {lineno} of file"
102+
if hasattr(func_or_code, "co_name"):
103+
code_name = func_or_code.co_name
104+
if not code_name.startswith("<"):
105+
code_name += "()"
106+
func_str = f" in {code_name}"
107+
else:
108+
func_str = ""
109+
part1 = f"Breakpoint {bp.number} set at line {lineno}{func_str} of file"
101110
msg = wrapped_lines(
102111
part1, cmd_obj.core.filename(filename), cmd_obj.settings["width"]
103112
)
104113
cmd_obj.msg(msg)
105-
if func:
106-
func_str = f" of {pretty_modfunc_name(func)}"
114+
if func_or_code:
115+
func_str = f" of {pretty_modfunc_name(func_or_code)}"
107116
else:
108117
func_str = ""
109118
if offset is not None and offset >= 0:
@@ -175,18 +184,27 @@ def parse_break_cmd(proc, args):
175184
# print '-' * 10
176185
cmdproc.frame = sys._getframe()
177186
cmdproc.setup()
187+
# FIXME: we should not need ot set setting
188+
cmdproc.settings = d.settings
189+
set_break(cmdproc, "set_break", __file__, 50, True, False, [])
178190
for cmd in (
179191
"break '''c:\\tmp\\foo.bat''':1",
180192
'break """/Users/My Documents/foo.py""":2',
181193
"break",
182194
"break 10",
183195
"break if True",
184-
"break cmdproc.py:5",
196+
f"break {__file__}:5",
197+
f"break {__file__}:{cmdproc.frame.f_lineno}",
185198
"break set_break()",
186199
"break 4 if i==5",
187-
# "break cmdproc.setup()",
200+
"break cmdproc.setup()",
188201
):
189202
args = cmd.split(" ")
190203
cmdproc.current_command = cmd
191-
print(parse_break_cmd(cmdproc, args))
204+
print(f"cmd: {cmd}")
205+
break_info = parse_break_cmd(cmdproc, args)
206+
print(break_info)
207+
if break_info != INVALID_PARSE_BREAK:
208+
code, filename, line_number, condition, offset = break_info
209+
set_break(cmdproc, code, filename, line_number, condition, False, [], offset=offset)
192210
pass

trepan/processor/cmdlist.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# You should have received a copy of the GNU General Public License
1515
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

17+
from trepan.lib.stack import frame2file
1718
from trepan.processor.location import resolve_location
1819
from trepan.processor.parse.parser import LocationError
1920
from trepan.processor.parse.scanner import ScannerError
@@ -90,10 +91,20 @@ def parse_list_cmd(proc, args, listsize=10):
9091
else:
9192
# First is location. Last may be empty or a number
9293
assert isinstance(list_range.first, Location)
93-
location = resolve_location(proc, list_range.first)
94-
if not location:
95-
return INVALID_PARSE_LIST
96-
first = location.line_number
94+
first = list_range.first.line_number
95+
if list_range.first.path is None:
96+
if list_range.first.method is None:
97+
filename = frame2file(proc.core, proc.curframe, canonic=False)
98+
else:
99+
location = resolve_location(proc, list_range.first)
100+
if location is None or location == INVALID_PARSE_LOCATION:
101+
return INVALID_PARSE_LIST
102+
filename = location.path
103+
if first is None:
104+
first = location.line_number
105+
else:
106+
filename = list_range.first.path
107+
97108
last = list_range.last
98109
# if location.method:
99110
# first -= listsize // 2
@@ -109,7 +120,7 @@ def parse_list_cmd(proc, args, listsize=10):
109120
# Treat as a count rather than an absolute location
110121
last = first + last
111122

112-
return location.path, first, last
123+
return filename, first, last
113124
pass
114125
return
115126

0 commit comments

Comments
 (0)