Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a62dcac

Browse filesBrowse files
miss-islingtontonybaloneyterryjreedy
authored
[3.12] gh-79871: IDLE - Fix and test debugger module (GH-11451) (#112256)
gh-79871: IDLE - Fix and test debugger module (GH-11451) Add docstrings to the debugger module. Fix two bugs: initialize Idb.botframe (should be in Bdb); In Idb.in_rpc_code, check whether prev_frame is None before trying to use it. Make other code changes. Expand test_debugger coverage from 19% to 66%. --------- (cherry picked from commit adedcfa) Co-authored-by: Anthony Shaw <anthony.p.shaw@gmail.com> Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
1 parent 919be35 commit a62dcac
Copy full SHA for a62dcac

File tree

5 files changed

+400
-77
lines changed
Filter options

5 files changed

+400
-77
lines changed

‎Lib/idlelib/debugger.py

Copy file name to clipboardExpand all lines: Lib/idlelib/debugger.py
+115-64Lines changed: 115 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
"""Debug user code with a GUI interface to a subclass of bdb.Bdb.
2+
3+
The Idb idb and Debugger gui instances each need a reference to each
4+
other or to an rpc proxy for each other.
5+
6+
If IDLE is started with '-n', so that user code and idb both run in the
7+
IDLE process, Debugger is called without an idb. Debugger.__init__
8+
calls Idb with its incomplete self. Idb.__init__ stores gui and gui
9+
then stores idb.
10+
11+
If IDLE is started normally, so that user code executes in a separate
12+
process, debugger_r.start_remote_debugger is called, executing in the
13+
IDLE process. It calls 'start the debugger' in the remote process,
14+
which calls Idb with a gui proxy. Then Debugger is called in the IDLE
15+
for more.
16+
"""
17+
118
import bdb
219
import os
320

@@ -10,66 +27,95 @@
1027

1128

1229
class Idb(bdb.Bdb):
30+
"Supply user_line and user_exception functions for Bdb."
1331

1432
def __init__(self, gui):
15-
self.gui = gui # An instance of Debugger or proxy of remote.
16-
bdb.Bdb.__init__(self)
33+
self.gui = gui # An instance of Debugger or proxy thereof.
34+
super().__init__()
1735

1836
def user_line(self, frame):
19-
if self.in_rpc_code(frame):
37+
"""Handle a user stopping or breaking at a line.
38+
39+
Convert frame to a string and send it to gui.
40+
"""
41+
if _in_rpc_code(frame):
2042
self.set_step()
2143
return
22-
message = self.__frame2message(frame)
44+
message = _frame2message(frame)
2345
try:
2446
self.gui.interaction(message, frame)
2547
except TclError: # When closing debugger window with [x] in 3.x
2648
pass
2749

28-
def user_exception(self, frame, info):
29-
if self.in_rpc_code(frame):
50+
def user_exception(self, frame, exc_info):
51+
"""Handle an the occurrence of an exception."""
52+
if _in_rpc_code(frame):
3053
self.set_step()
3154
return
32-
message = self.__frame2message(frame)
33-
self.gui.interaction(message, frame, info)
34-
35-
def in_rpc_code(self, frame):
36-
if frame.f_code.co_filename.count('rpc.py'):
37-
return True
38-
else:
39-
prev_frame = frame.f_back
40-
prev_name = prev_frame.f_code.co_filename
41-
if 'idlelib' in prev_name and 'debugger' in prev_name:
42-
# catch both idlelib/debugger.py and idlelib/debugger_r.py
43-
# on both Posix and Windows
44-
return False
45-
return self.in_rpc_code(prev_frame)
46-
47-
def __frame2message(self, frame):
48-
code = frame.f_code
49-
filename = code.co_filename
50-
lineno = frame.f_lineno
51-
basename = os.path.basename(filename)
52-
message = f"{basename}:{lineno}"
53-
if code.co_name != "?":
54-
message = f"{message}: {code.co_name}()"
55-
return message
55+
message = _frame2message(frame)
56+
self.gui.interaction(message, frame, exc_info)
57+
58+
def _in_rpc_code(frame):
59+
"Determine if debugger is within RPC code."
60+
if frame.f_code.co_filename.count('rpc.py'):
61+
return True # Skip this frame.
62+
else:
63+
prev_frame = frame.f_back
64+
if prev_frame is None:
65+
return False
66+
prev_name = prev_frame.f_code.co_filename
67+
if 'idlelib' in prev_name and 'debugger' in prev_name:
68+
# catch both idlelib/debugger.py and idlelib/debugger_r.py
69+
# on both Posix and Windows
70+
return False
71+
return _in_rpc_code(prev_frame)
72+
73+
def _frame2message(frame):
74+
"""Return a message string for frame."""
75+
code = frame.f_code
76+
filename = code.co_filename
77+
lineno = frame.f_lineno
78+
basename = os.path.basename(filename)
79+
message = f"{basename}:{lineno}"
80+
if code.co_name != "?":
81+
message = f"{message}: {code.co_name}()"
82+
return message
5683

5784

5885
class Debugger:
59-
60-
vstack = vsource = vlocals = vglobals = None
86+
"""The debugger interface.
87+
88+
This class handles the drawing of the debugger window and
89+
the interactions with the underlying debugger session.
90+
"""
91+
vstack = None
92+
vsource = None
93+
vlocals = None
94+
vglobals = None
95+
stackviewer = None
96+
localsviewer = None
97+
globalsviewer = None
6198

6299
def __init__(self, pyshell, idb=None):
100+
"""Instantiate and draw a debugger window.
101+
102+
:param pyshell: An instance of the PyShell Window
103+
:type pyshell: :class:`idlelib.pyshell.PyShell`
104+
105+
:param idb: An instance of the IDLE debugger (optional)
106+
:type idb: :class:`idlelib.debugger.Idb`
107+
"""
63108
if idb is None:
64109
idb = Idb(self)
65110
self.pyshell = pyshell
66111
self.idb = idb # If passed, a proxy of remote instance.
67112
self.frame = None
68113
self.make_gui()
69-
self.interacting = 0
114+
self.interacting = False
70115
self.nesting_level = 0
71116

72117
def run(self, *args):
118+
"""Run the debugger."""
73119
# Deal with the scenario where we've already got a program running
74120
# in the debugger and we want to start another. If that is the case,
75121
# our second 'run' was invoked from an event dispatched not from
@@ -104,12 +150,13 @@ def run(self, *args):
104150
self.root.after(100, lambda: self.run(*args))
105151
return
106152
try:
107-
self.interacting = 1
153+
self.interacting = True
108154
return self.idb.run(*args)
109155
finally:
110-
self.interacting = 0
156+
self.interacting = False
111157

112158
def close(self, event=None):
159+
"""Close the debugger and window."""
113160
try:
114161
self.quit()
115162
except Exception:
@@ -127,6 +174,7 @@ def close(self, event=None):
127174
self.top.destroy()
128175

129176
def make_gui(self):
177+
"""Draw the debugger gui on the screen."""
130178
pyshell = self.pyshell
131179
self.flist = pyshell.flist
132180
self.root = root = pyshell.root
@@ -135,11 +183,11 @@ def make_gui(self):
135183
self.top.wm_iconname("Debug")
136184
top.wm_protocol("WM_DELETE_WINDOW", self.close)
137185
self.top.bind("<Escape>", self.close)
138-
#
186+
139187
self.bframe = bframe = Frame(top)
140188
self.bframe.pack(anchor="w")
141189
self.buttons = bl = []
142-
#
190+
143191
self.bcont = b = Button(bframe, text="Go", command=self.cont)
144192
bl.append(b)
145193
self.bstep = b = Button(bframe, text="Step", command=self.step)
@@ -150,14 +198,14 @@ def make_gui(self):
150198
bl.append(b)
151199
self.bret = b = Button(bframe, text="Quit", command=self.quit)
152200
bl.append(b)
153-
#
201+
154202
for b in bl:
155203
b.configure(state="disabled")
156204
b.pack(side="left")
157-
#
205+
158206
self.cframe = cframe = Frame(bframe)
159207
self.cframe.pack(side="left")
160-
#
208+
161209
if not self.vstack:
162210
self.__class__.vstack = BooleanVar(top)
163211
self.vstack.set(1)
@@ -180,20 +228,20 @@ def make_gui(self):
180228
self.bglobals = Checkbutton(cframe,
181229
text="Globals", command=self.show_globals, variable=self.vglobals)
182230
self.bglobals.grid(row=1, column=1)
183-
#
231+
184232
self.status = Label(top, anchor="w")
185233
self.status.pack(anchor="w")
186234
self.error = Label(top, anchor="w")
187235
self.error.pack(anchor="w", fill="x")
188236
self.errorbg = self.error.cget("background")
189-
#
237+
190238
self.fstack = Frame(top, height=1)
191239
self.fstack.pack(expand=1, fill="both")
192240
self.flocals = Frame(top)
193241
self.flocals.pack(expand=1, fill="both")
194242
self.fglobals = Frame(top, height=1)
195243
self.fglobals.pack(expand=1, fill="both")
196-
#
244+
197245
if self.vstack.get():
198246
self.show_stack()
199247
if self.vlocals.get():
@@ -204,7 +252,7 @@ def make_gui(self):
204252
def interaction(self, message, frame, info=None):
205253
self.frame = frame
206254
self.status.configure(text=message)
207-
#
255+
208256
if info:
209257
type, value, tb = info
210258
try:
@@ -223,28 +271,28 @@ def interaction(self, message, frame, info=None):
223271
tb = None
224272
bg = self.errorbg
225273
self.error.configure(text=m1, background=bg)
226-
#
274+
227275
sv = self.stackviewer
228276
if sv:
229277
stack, i = self.idb.get_stack(self.frame, tb)
230278
sv.load_stack(stack, i)
231-
#
279+
232280
self.show_variables(1)
233-
#
281+
234282
if self.vsource.get():
235283
self.sync_source_line()
236-
#
284+
237285
for b in self.buttons:
238286
b.configure(state="normal")
239-
#
287+
240288
self.top.wakeup()
241289
# Nested main loop: Tkinter's main loop is not reentrant, so use
242290
# Tcl's vwait facility, which reenters the event loop until an
243-
# event handler sets the variable we're waiting on
291+
# event handler sets the variable we're waiting on.
244292
self.nesting_level += 1
245293
self.root.tk.call('vwait', '::idledebugwait')
246294
self.nesting_level -= 1
247-
#
295+
248296
for b in self.buttons:
249297
b.configure(state="disabled")
250298
self.status.configure(text="")
@@ -288,8 +336,6 @@ def quit(self):
288336
def abort_loop(self):
289337
self.root.tk.call('set', '::idledebugwait', '1')
290338

291-
stackviewer = None
292-
293339
def show_stack(self):
294340
if not self.stackviewer and self.vstack.get():
295341
self.stackviewer = sv = StackViewer(self.fstack, self.flist, self)
@@ -311,9 +357,6 @@ def show_frame(self, stackitem):
311357
self.frame = stackitem[0] # lineno is stackitem[1]
312358
self.show_variables()
313359

314-
localsviewer = None
315-
globalsviewer = None
316-
317360
def show_locals(self):
318361
lv = self.localsviewer
319362
if self.vlocals.get():
@@ -354,26 +397,32 @@ def show_variables(self, force=0):
354397
if gv:
355398
gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)
356399

357-
def set_breakpoint_here(self, filename, lineno):
400+
def set_breakpoint(self, filename, lineno):
401+
"""Set a filename-lineno breakpoint in the debugger.
402+
403+
Called from self.load_breakpoints and EW.setbreakpoint
404+
"""
358405
self.idb.set_break(filename, lineno)
359406

360-
def clear_breakpoint_here(self, filename, lineno):
407+
def clear_breakpoint(self, filename, lineno):
361408
self.idb.clear_break(filename, lineno)
362409

363410
def clear_file_breaks(self, filename):
364411
self.idb.clear_all_file_breaks(filename)
365412

366413
def load_breakpoints(self):
367-
"Load PyShellEditorWindow breakpoints into subprocess debugger"
414+
"""Load PyShellEditorWindow breakpoints into subprocess debugger."""
368415
for editwin in self.pyshell.flist.inversedict:
369416
filename = editwin.io.filename
370417
try:
371418
for lineno in editwin.breakpoints:
372-
self.set_breakpoint_here(filename, lineno)
419+
self.set_breakpoint(filename, lineno)
373420
except AttributeError:
374421
continue
375422

423+
376424
class StackViewer(ScrolledList):
425+
"Code stack viewer for debugger GUI."
377426

378427
def __init__(self, master, flist, gui):
379428
if macosx.isAquaTk():
@@ -414,25 +463,25 @@ def load_stack(self, stack, index=None):
414463
self.select(index)
415464

416465
def popup_event(self, event):
417-
"override base method"
466+
"Override base method."
418467
if self.stack:
419468
return ScrolledList.popup_event(self, event)
420469

421470
def fill_menu(self):
422-
"override base method"
471+
"Override base method."
423472
menu = self.menu
424473
menu.add_command(label="Go to source line",
425474
command=self.goto_source_line)
426475
menu.add_command(label="Show stack frame",
427476
command=self.show_stack_frame)
428477

429478
def on_select(self, index):
430-
"override base method"
479+
"Override base method."
431480
if 0 <= index < len(self.stack):
432481
self.gui.show_frame(self.stack[index])
433482

434483
def on_double(self, index):
435-
"override base method"
484+
"Override base method."
436485
self.show_source(index)
437486

438487
def goto_source_line(self):
@@ -457,6 +506,7 @@ def show_source(self, index):
457506

458507

459508
class NamespaceViewer:
509+
"Global/local namespace viewer for debugger GUI."
460510

461511
def __init__(self, master, title, dict=None):
462512
width = 0
@@ -544,6 +594,7 @@ def load_dict(self, dict, force=0, rpc_client=None):
544594
def close(self):
545595
self.frame.destroy()
546596

597+
547598
if __name__ == "__main__":
548599
from unittest import main
549600
main('idlelib.idle_test.test_debugger', verbosity=2, exit=False)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.