From bbb58e8cc684238b2597758b3cb5217ea83cd315 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Sun, 6 Jan 2019 14:38:34 +1100 Subject: [PATCH 01/47] start setting up tests for the Idb module --- Lib/idlelib/idle_test/test_debugger.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 35efb3411c73b5..ffa810e2a70fd6 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -2,8 +2,9 @@ from idlelib import debugger import unittest +from unittest import mock from test.support import requires -requires('gui') +#requires('gui') from tkinter import Tk @@ -23,6 +24,12 @@ def test_init(self): debugger.NamespaceViewer(self.root, 'Test') +@mock.patch('bdb.Bdb') +class IdbTest(unittest.TestCase): + def test_init(self, bdb): + import pdb; pdb.set_trace() + bdb.__init__.assertCalled() + # Other classes are Idb, Debugger, and StackViewer. if __name__ == '__main__': From 525efbc181ce4f20d337a9e318e0a97d11d9768f Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 10:14:37 +1100 Subject: [PATCH 02/47] handle NoneType in frame.f_back (first frame in session) and call bdb using the modern super function --- Lib/idlelib/debugger.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index ccd03e46e16147..8e9b27cc9bbca6 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -10,12 +10,19 @@ class Idb(bdb.Bdb): + """Idle debugger, based on the bdb debugger.""" def __init__(self, gui): self.gui = gui # An instance of Debugger or proxy of remote. - bdb.Bdb.__init__(self) + super(Idb, self).__init__() def user_line(self, frame): + """ + Handle a user stopping or breaking at a line. + + Implements Bdb.user_line() to convert frame to string + and send message to GUI. + """ if self.in_rpc_code(frame): self.set_step() return @@ -25,18 +32,22 @@ def user_line(self, frame): except TclError: # When closing debugger window with [x] in 3.x pass - def user_exception(self, frame, info): + def user_exception(self, frame, exc_info): + """Handle an the occurrence of an exception.""" if self.in_rpc_code(frame): self.set_step() return message = self.__frame2message(frame) - self.gui.interaction(message, frame, info) + self.gui.interaction(message, frame, exc_info) def in_rpc_code(self, frame): + """Determine if debugger is within RPC code.""" if frame.f_code.co_filename.count('rpc.py'): return True else: prev_frame = frame.f_back + if prev_frame is None: + return False prev_name = prev_frame.f_code.co_filename if 'idlelib' in prev_name and 'debugger' in prev_name: # catch both idlelib/debugger.py and idlelib/debugger_r.py @@ -45,6 +56,7 @@ def in_rpc_code(self, frame): return self.in_rpc_code(prev_frame) def __frame2message(self, frame): + """Convert a frame to a message string.""" code = frame.f_code filename = code.co_filename lineno = frame.f_lineno From 83aeefa0426f618c3236c36684d2cb2e2bbb2cb9 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 10:16:10 +1100 Subject: [PATCH 03/47] add a test for Idb to ensure calling super. Add a test to check the handling of frames in user_line --- Lib/idlelib/idle_test/test_debugger.py | 63 +++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index ffa810e2a70fd6..7fed2142610527 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -4,8 +4,9 @@ import unittest from unittest import mock from test.support import requires -#requires('gui') +requires('gui') from tkinter import Tk +from textwrap import dedent class NameSpaceTest(unittest.TestCase): @@ -24,13 +25,63 @@ def test_init(self): debugger.NamespaceViewer(self.root, 'Test') -@mock.patch('bdb.Bdb') +class MockFrameType(object): + """Reflection of the types.FrameType.""" + f_back = None + f_builtins = None + f_code = None + f_globals = None + f_lasti = None + f_lineno = None + f_locals = None + f_restricted = 0 + f_trace = None + + def __init__(self): + pass + + class IdbTest(unittest.TestCase): - def test_init(self, bdb): - import pdb; pdb.set_trace() - bdb.__init__.assertCalled() -# Other classes are Idb, Debugger, and StackViewer. + def test_init_runs_bdb_init(self): + """Test that Idb calls the base Bdb __init__.""" + idb = debugger.Idb(None) + assert idb.gui is None + assert hasattr(idb, 'breaks') + + def test_user_line_basic_frame(self): + """Test that .user_line() creates a string message for a frame.""" + + # Create a test code object to simuate a debug session. + test_code = dedent(""" + i = 1 + i += 2 + if i == 3: + print(i) + """) + code_obj = compile(test_code, + 'test_user_line_basic_frame.py', + mode='exec') + + # Create 2 test frames for lines 1 and 2 of the test code. + test_frame1 = MockFrameType() + test_frame1.f_code = code_obj + test_frame1.f_lineno = 1 + + test_frame2 = MockFrameType() + test_frame2.f_code = code_obj + test_frame2.f_lineno = 2 + test_frame2.f_back = test_frame1 + + gui = mock.Mock() + gui.interaction = mock.Mock() + + idb = debugger.Idb(gui) + idb.user_line(test_frame2) + + gui.interaction.assert_called_once() + gui.interaction.assert_called_with('test_user_line_basic_frame.py:2: ()', test_frame2) + if __name__ == '__main__': unittest.main(verbosity=2) From d01dcdb84967dc5553650e745698875da9950c4f Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 10:33:44 +1100 Subject: [PATCH 04/47] add test for user_exception to validate passing of exc_info to gui interaction --- Lib/idlelib/idle_test/test_debugger.py | 35 +++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 7fed2142610527..8af04436d86f3a 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -52,7 +52,7 @@ def test_init_runs_bdb_init(self): def test_user_line_basic_frame(self): """Test that .user_line() creates a string message for a frame.""" - # Create a test code object to simuate a debug session. + # Create a test code object to simulate a debug session. test_code = dedent(""" i = 1 i += 2 @@ -79,9 +79,42 @@ def test_user_line_basic_frame(self): idb = debugger.Idb(gui) idb.user_line(test_frame2) + assert not idb.in_rpc_code(test_frame2) gui.interaction.assert_called_once() gui.interaction.assert_called_with('test_user_line_basic_frame.py:2: ()', test_frame2) + def test_user_exception(self): + """Test that .user_exception() creates a string message for a frame.""" + + # Create a test code object to simulate a debug session. + test_code = dedent(""" + i = 1 + i += 2 + if i == 3: + print(i) + """) + code_obj = compile(test_code, + 'test_user_exception.py', + mode='exec') + + # Create 1 test frame + test_frame1 = MockFrameType() + test_frame1.f_code = code_obj + test_frame1.f_lineno = 1 + + # Example from sys.exc_info() + test_exc_info = (type(ValueError), ValueError(), None) + + gui = mock.Mock() + gui.interaction = mock.Mock() + + idb = debugger.Idb(gui) + idb.user_exception(test_frame1, test_exc_info) + + assert not idb.in_rpc_code(test_frame1) + gui.interaction.assert_called_once() + gui.interaction.assert_called_with('test_user_exception.py:1: ()', test_frame1, test_exc_info) + if __name__ == '__main__': unittest.main(verbosity=2) From 6a123ef743d386847e34dbb8d4fd9e9d53768c98 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 10:41:31 +1100 Subject: [PATCH 05/47] add tests for in_rpc_code to catch various frame file names --- Lib/idlelib/idle_test/test_debugger.py | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 8af04436d86f3a..622b89b2fe65e3 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -115,6 +115,64 @@ def test_user_exception(self): gui.interaction.assert_called_once() gui.interaction.assert_called_with('test_user_exception.py:1: ()', test_frame1, test_exc_info) + def test_in_rpc_code_rpc_py(self): + """Test that .in_rpc_code detects position of rpc.py.""" + + # Create a test code object to simulate a debug session. + test_code = dedent(""" + i = 1 + i += 2 + if i == 3: + print(i) + """) + code_obj = compile(test_code, + 'rpc.py', + mode='exec') + + # Create 1 test frame + test_frame = MockFrameType() + test_frame.f_code = code_obj + test_frame.f_lineno = 1 + + gui = mock.Mock() + gui.interaction = mock.Mock() + + idb = debugger.Idb(gui) + + assert idb.in_rpc_code(test_frame) + + def test_in_rpc_code_debugger_star_dot_py(self): + """Test that .in_rpc_code detects position of idlelib/debugger*.py.""" + + # Create a test code object to simulate a debug session. + for filename in ('idlelib/debugger.py', 'idlelib/debugger_r.py'): + test_code = dedent(""" + i = 1 + i += 2 + if i == 3: + print(i) + """) + code_obj = compile(test_code, + filename, + mode='exec') + + # Create 2 test frames + test_frame = MockFrameType() + test_frame.f_code = code_obj + test_frame.f_lineno = 1 + + test_frame2 = MockFrameType() + test_frame2.f_code = code_obj + test_frame2.f_lineno = 2 + test_frame2.f_back = test_frame + + gui = mock.Mock() + gui.interaction = mock.Mock() + + idb = debugger.Idb(gui) + + assert not idb.in_rpc_code(test_frame2) + if __name__ == '__main__': unittest.main(verbosity=2) From e5e882c92ab65a98a5a3677d01ab143380ffc846 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 10:43:49 +1100 Subject: [PATCH 06/47] add some PEP8 compliant line breaks --- Lib/idlelib/debugger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 8e9b27cc9bbca6..b4087094f81623 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -384,6 +384,7 @@ def load_breakpoints(self): except AttributeError: continue + class StackViewer(ScrolledList): def __init__(self, master, flist, gui): @@ -555,6 +556,7 @@ def load_dict(self, dict, force=0, rpc_client=None): def close(self): self.frame.destroy() + if __name__ == "__main__": from unittest import main main('idlelib.idle_test.test_debugger', verbosity=2, exit=False) From 35915dafeab0df98dd48e5cd8c1b218d3df718aa Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 14:27:14 +1100 Subject: [PATCH 07/47] setup tests for the debugger.Debugger class --- Lib/idlelib/debugger.py | 31 +++++++++++++++++++------- Lib/idlelib/idle_test/test_debugger.py | 29 +++++++++++++++++++++++- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index b4087094f81623..1cdd08d44cb2f2 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -68,10 +68,30 @@ def __frame2message(self, frame): class Debugger: - - vstack = vsource = vlocals = vglobals = None + """ + The debugger interface. + + This class handles the drawing of the debugger window and + the interactions with the underlying debugger session. + """ + vstack = None + vsource = None + vlocals = None + vglobals = None + stackviewer = None + localsviewer = None + globalsviewer = None def __init__(self, pyshell, idb=None): + """ + Instantiate and draw a debugger window. + + :param pyshell: An instance of the PyShell Window + :type pyshell: :class:`idlelib.pyshell.PyShell` + + :param idb: An instance of the IDLE debugger (optional) + :type idb: :class:`idlelib.debugger.Idb` + """ if idb is None: idb = Idb(self) self.pyshell = pyshell @@ -299,8 +319,6 @@ def quit(self): def abort_loop(self): self.root.tk.call('set', '::idledebugwait', '1') - stackviewer = None - def show_stack(self): if not self.stackviewer and self.vstack.get(): self.stackviewer = sv = StackViewer(self.fstack, self.flist, self) @@ -322,9 +340,6 @@ def show_frame(self, stackitem): self.frame = stackitem[0] # lineno is stackitem[1] self.show_variables() - localsviewer = None - globalsviewer = None - def show_locals(self): lv = self.localsviewer if self.vlocals.get(): @@ -375,7 +390,7 @@ def clear_file_breaks(self, filename): self.idb.clear_all_file_breaks(filename) def load_breakpoints(self): - "Load PyShellEditorWindow breakpoints into subprocess debugger" + """Load PyShellEditorWindow breakpoints into subprocess debugger.""" for editwin in self.pyshell.flist.inversedict: filename = editwin.io.filename try: diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 622b89b2fe65e3..9e08908b3b45fe 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -4,7 +4,7 @@ import unittest from unittest import mock from test.support import requires -requires('gui') +# requires('gui') from tkinter import Tk from textwrap import dedent @@ -174,5 +174,32 @@ def test_in_rpc_code_debugger_star_dot_py(self): assert not idb.in_rpc_code(test_frame2) +def make_pyshell_mock(): + """Factory for generating test fixtures of PyShell.""" + pyshell = mock.Mock() + pyshell.root = None + return pyshell + + +class DebuggerTest(unittest.TestCase): + """Tests for the idlelib.debugger.Debugger class.""" + + def test_setup_debugger(self): + """Test that Debugger can be instantiated with a mock PyShell.""" + pyshell = make_pyshell_mock() + test_debugger = debugger.Debugger(pyshell) + + assert test_debugger.pyshell == pyshell + assert test_debugger.frame is None + + def test_run_debugger(self): + """Test Debugger.run() with an Idb instance.""" + mock_idb = mock.Mock() # Mocked debugger.Idb + test_debugger = debugger.Debugger(make_pyshell_mock(), idb=mock_idb) + test_debugger.run() + mock_idb.run.assert_called_once() + assert test_debugger.interacting == 0 + + if __name__ == '__main__': unittest.main(verbosity=2) From dbb5c7e42fca956c1724e8a685f0f2f0caa362a8 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 14:38:57 +1100 Subject: [PATCH 08/47] add tests for run with and without an idb instance set on init --- Lib/idlelib/idle_test/test_debugger.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 9e08908b3b45fe..5d74913a528301 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -192,12 +192,23 @@ def test_setup_debugger(self): assert test_debugger.pyshell == pyshell assert test_debugger.frame is None - def test_run_debugger(self): + def test_run_debugger_with_idb(self): """Test Debugger.run() with an Idb instance.""" mock_idb = mock.Mock() # Mocked debugger.Idb test_debugger = debugger.Debugger(make_pyshell_mock(), idb=mock_idb) - test_debugger.run() + test_debugger.run(1, 'two') mock_idb.run.assert_called_once() + mock_idb.run.called_with(1, 'two') + assert test_debugger.interacting == 0 + + def test_run_debugger_no_idb(self): + """Test Debugger.run() with no Idb instance.""" + test_debugger = debugger.Debugger(make_pyshell_mock(), idb=None) + assert test_debugger.idb is not None + test_debugger.idb.run = mock.Mock() + test_debugger.run(1, 'two') + test_debugger.idb.run.assert_called_once() + test_debugger.idb.run.called_with(1, 'two') assert test_debugger.interacting == 0 From 29290197ca3d27a9aec6f1c00c4ed9153aff0a58 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 14:49:03 +1100 Subject: [PATCH 09/47] add tests for interaction, which are currently demonstrating a bug in Idb --- Lib/idlelib/idle_test/test_debugger.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 5d74913a528301..ea673b86e44879 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -211,6 +211,32 @@ def test_run_debugger_no_idb(self): test_debugger.idb.run.called_with(1, 'two') assert test_debugger.interacting == 0 + def test_close(self): + """Test closing the window in an idle state.""" + pyshell = make_pyshell_mock() + test_debugger = debugger.Debugger(pyshell) + test_debugger.close() + pyshell.close_debugger.assert_called_once() + + def test_close_whilst_interacting(self): + """Test closing the window in an interactive state.""" + pyshell = make_pyshell_mock() + test_debugger = debugger.Debugger(pyshell) + test_debugger.interacting = 1 + test_debugger.close() + pyshell.close_debugger.assert_not_called() + + def test_interaction_with_message_and_frame(self): + test_message = "testing 1234.." + test_frame = MockFrameType() + + pyshell = make_pyshell_mock() + test_debugger = debugger.Debugger(pyshell) + + # Patch out the status label so we can check messages + test_debugger.status = mock.Mock() + test_debugger.interaction(test_message, test_frame) + if __name__ == '__main__': unittest.main(verbosity=2) From bbe430039323ac7594a4238fee88941b9e55ca6a Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 14:56:05 +1100 Subject: [PATCH 10/47] Bdb expects an instance attribute called 'botframe', which is set by the '.reset()' method. This method is never called, so the attribute does not exist and under certain situations, could crash the debugger window --- Lib/idlelib/debugger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 1cdd08d44cb2f2..c46bbeb0796031 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -14,6 +14,7 @@ class Idb(bdb.Bdb): def __init__(self, gui): self.gui = gui # An instance of Debugger or proxy of remote. + self.botframe = None super(Idb, self).__init__() def user_line(self, frame): @@ -102,6 +103,7 @@ def __init__(self, pyshell, idb=None): self.nesting_level = 0 def run(self, *args): + """Run the debugger.""" # Deal with the scenario where we've already got a program running # in the debugger and we want to start another. If that is the case, # our second 'run' was invoked from an event dispatched not from @@ -142,6 +144,7 @@ def run(self, *args): self.interacting = 0 def close(self, event=None): + """Close the debugger and window.""" try: self.quit() except Exception: @@ -159,6 +162,7 @@ def close(self, event=None): self.top.destroy() def make_gui(self): + """Draw the debugger gui on the screen.""" pyshell = self.pyshell self.flist = pyshell.flist self.root = root = pyshell.root From 4957496ebd688f52870fd8db93cc0717e05f451f Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 15:29:40 +1100 Subject: [PATCH 11/47] remove empty single line comments --- Lib/idlelib/debugger.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index c46bbeb0796031..c7bab7e9f0a671 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -171,11 +171,11 @@ def make_gui(self): self.top.wm_iconname("Debug") top.wm_protocol("WM_DELETE_WINDOW", self.close) self.top.bind("", self.close) - # + self.bframe = bframe = Frame(top) self.bframe.pack(anchor="w") self.buttons = bl = [] - # + self.bcont = b = Button(bframe, text="Go", command=self.cont) bl.append(b) self.bstep = b = Button(bframe, text="Step", command=self.step) @@ -186,14 +186,14 @@ def make_gui(self): bl.append(b) self.bret = b = Button(bframe, text="Quit", command=self.quit) bl.append(b) - # + for b in bl: b.configure(state="disabled") b.pack(side="left") - # + self.cframe = cframe = Frame(bframe) self.cframe.pack(side="left") - # + if not self.vstack: self.__class__.vstack = BooleanVar(top) self.vstack.set(1) @@ -216,20 +216,20 @@ def make_gui(self): self.bglobals = Checkbutton(cframe, text="Globals", command=self.show_globals, variable=self.vglobals) self.bglobals.grid(row=1, column=1) - # + self.status = Label(top, anchor="w") self.status.pack(anchor="w") self.error = Label(top, anchor="w") self.error.pack(anchor="w", fill="x") self.errorbg = self.error.cget("background") - # + self.fstack = Frame(top, height=1) self.fstack.pack(expand=1, fill="both") self.flocals = Frame(top) self.flocals.pack(expand=1, fill="both") self.fglobals = Frame(top, height=1) self.fglobals.pack(expand=1, fill="both") - # + if self.vstack.get(): self.show_stack() if self.vlocals.get(): @@ -240,7 +240,7 @@ def make_gui(self): def interaction(self, message, frame, info=None): self.frame = frame self.status.configure(text=message) - # + if info: type, value, tb = info try: @@ -258,28 +258,28 @@ def interaction(self, message, frame, info=None): tb = None bg = self.errorbg self.error.configure(text=m1, background=bg) - # + sv = self.stackviewer if sv: stack, i = self.idb.get_stack(self.frame, tb) sv.load_stack(stack, i) - # + self.show_variables(1) - # + if self.vsource.get(): self.sync_source_line() - # + for b in self.buttons: b.configure(state="normal") - # + self.top.wakeup() # Nested main loop: Tkinter's main loop is not reentrant, so use # Tcl's vwait facility, which reenters the event loop until an - # event handler sets the variable we're waiting on + # event handler sets the variable we're waiting on. self.nesting_level += 1 self.root.tk.call('vwait', '::idledebugwait') self.nesting_level -= 1 - # + for b in self.buttons: b.configure(state="disabled") self.status.configure(text="") From fbf1cacd53e48c4736a5829b1bd17820cacb6867 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 15:29:56 +1100 Subject: [PATCH 12/47] add tests for the basic operations --- Lib/idlelib/idle_test/test_debugger.py | 199 ++++++++++++++++++++----- 1 file changed, 162 insertions(+), 37 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index ea673b86e44879..f7ce6c482c832c 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -8,6 +8,14 @@ from tkinter import Tk from textwrap import dedent +"""A test python script for the debug tests.""" +TEST_CODE = dedent(""" + i = 1 + i += 2 + if i == 3: + print(i) + """) + class NameSpaceTest(unittest.TestCase): @@ -53,13 +61,7 @@ def test_user_line_basic_frame(self): """Test that .user_line() creates a string message for a frame.""" # Create a test code object to simulate a debug session. - test_code = dedent(""" - i = 1 - i += 2 - if i == 3: - print(i) - """) - code_obj = compile(test_code, + code_obj = compile(TEST_CODE, 'test_user_line_basic_frame.py', mode='exec') @@ -79,7 +81,7 @@ def test_user_line_basic_frame(self): idb = debugger.Idb(gui) idb.user_line(test_frame2) - assert not idb.in_rpc_code(test_frame2) + self.assertFalse(idb.in_rpc_code(test_frame2)) gui.interaction.assert_called_once() gui.interaction.assert_called_with('test_user_line_basic_frame.py:2: ()', test_frame2) @@ -87,13 +89,7 @@ def test_user_exception(self): """Test that .user_exception() creates a string message for a frame.""" # Create a test code object to simulate a debug session. - test_code = dedent(""" - i = 1 - i += 2 - if i == 3: - print(i) - """) - code_obj = compile(test_code, + code_obj = compile(TEST_CODE, 'test_user_exception.py', mode='exec') @@ -111,7 +107,7 @@ def test_user_exception(self): idb = debugger.Idb(gui) idb.user_exception(test_frame1, test_exc_info) - assert not idb.in_rpc_code(test_frame1) + self.assertFalse(idb.in_rpc_code(test_frame1)) gui.interaction.assert_called_once() gui.interaction.assert_called_with('test_user_exception.py:1: ()', test_frame1, test_exc_info) @@ -119,13 +115,7 @@ def test_in_rpc_code_rpc_py(self): """Test that .in_rpc_code detects position of rpc.py.""" # Create a test code object to simulate a debug session. - test_code = dedent(""" - i = 1 - i += 2 - if i == 3: - print(i) - """) - code_obj = compile(test_code, + code_obj = compile(TEST_CODE, 'rpc.py', mode='exec') @@ -139,20 +129,15 @@ def test_in_rpc_code_rpc_py(self): idb = debugger.Idb(gui) - assert idb.in_rpc_code(test_frame) + self.assertTrue(idb.in_rpc_code(test_frame)) def test_in_rpc_code_debugger_star_dot_py(self): """Test that .in_rpc_code detects position of idlelib/debugger*.py.""" # Create a test code object to simulate a debug session. for filename in ('idlelib/debugger.py', 'idlelib/debugger_r.py'): - test_code = dedent(""" - i = 1 - i += 2 - if i == 3: - print(i) - """) - code_obj = compile(test_code, + + code_obj = compile(TEST_CODE, filename, mode='exec') @@ -171,7 +156,7 @@ def test_in_rpc_code_debugger_star_dot_py(self): idb = debugger.Idb(gui) - assert not idb.in_rpc_code(test_frame2) + self.assertFalse(idb.in_rpc_code(test_frame2)) def make_pyshell_mock(): @@ -189,8 +174,8 @@ def test_setup_debugger(self): pyshell = make_pyshell_mock() test_debugger = debugger.Debugger(pyshell) - assert test_debugger.pyshell == pyshell - assert test_debugger.frame is None + self.assertEquals(test_debugger.pyshell, pyshell) + self.assertIsNone(test_debugger.frame) def test_run_debugger_with_idb(self): """Test Debugger.run() with an Idb instance.""" @@ -199,17 +184,17 @@ def test_run_debugger_with_idb(self): test_debugger.run(1, 'two') mock_idb.run.assert_called_once() mock_idb.run.called_with(1, 'two') - assert test_debugger.interacting == 0 + self.assertEquals(test_debugger.interacting, 0) def test_run_debugger_no_idb(self): """Test Debugger.run() with no Idb instance.""" test_debugger = debugger.Debugger(make_pyshell_mock(), idb=None) - assert test_debugger.idb is not None + self.assertIsNotNone(test_debugger.idb) test_debugger.idb.run = mock.Mock() test_debugger.run(1, 'two') test_debugger.idb.run.assert_called_once() test_debugger.idb.run.called_with(1, 'two') - assert test_debugger.interacting == 0 + self.assertEquals(test_debugger.interacting, 0) def test_close(self): """Test closing the window in an idle state.""" @@ -227,16 +212,156 @@ def test_close_whilst_interacting(self): pyshell.close_debugger.assert_not_called() def test_interaction_with_message_and_frame(self): + """Test the interaction sets the window message.""" test_message = "testing 1234.." test_frame = MockFrameType() + test_code = compile(TEST_CODE, 'test_interaction.py', 'exec') + + test_frame.f_code = test_code + test_frame.f_lineno = 1 pyshell = make_pyshell_mock() + test_debugger = debugger.Debugger(pyshell) + # Patch out the root window and tk session so we can test them + test_debugger.root = mock.Mock() + test_debugger.root.tk = mock.Mock() + # Patch out the status label so we can check messages test_debugger.status = mock.Mock() test_debugger.interaction(test_message, test_frame) + # Check the test message was displayed and cleared + test_debugger.status.configure.assert_has_calls([mock.call(text='testing 1234..'), mock.call(text='')]) + + def test_interaction_with_message_and_frame_and_exc_info(self): + """Test the interaction sets the window message with exception info.""" + test_message = "testing 1234.." + test_frame = MockFrameType() + test_exc_info = (type(ValueError), ValueError(), None) + test_code = compile(TEST_CODE, 'test_interaction.py', 'exec') + + test_frame.f_code = test_code + test_frame.f_lineno = 1 + + pyshell = make_pyshell_mock() + + test_debugger = debugger.Debugger(pyshell) + + # Patch out the root window and tk session so we can test them + test_debugger.root = mock.Mock() + test_debugger.root.tk = mock.Mock() + + # Patch out the status label so we can check messages + test_debugger.status = mock.Mock() + test_debugger.interaction(test_message, test_frame, test_exc_info) + + # Check the test message was displayed and cleared + test_debugger.status.configure.assert_has_calls([mock.call(text='testing 1234..'), mock.call(text='')]) + + def test_sync_source_line(self): + """Test that .sync_source_line() will set the flist.gotofileline with fixed frame.""" + pyshell = make_pyshell_mock() + + test_debugger = debugger.Debugger(pyshell) + + test_frame = MockFrameType() + test_code = compile(TEST_CODE, 'test_sync.py', 'exec') + + test_frame.f_code = test_code + test_frame.f_lineno = 1 + + test_debugger.frame = test_frame + + # Patch out the file list + test_debugger.flist = mock.Mock() + + # Pretend file exists + with mock.patch('idlelib.debugger.os.path.exists', return_value=True): + test_debugger.sync_source_line() + + test_debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) + + def test_cont(self): + """Test the .cont() method calls idb.set_continue().""" + pyshell = make_pyshell_mock() + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + + # Patch out the root window and tk session so we can test them + test_debugger.root = mock.Mock() + test_debugger.root.tk = mock.Mock() + + test_debugger.cont() + + # Check set_continue was called on the idb instance. + idb.set_continue.assert_called_once() + + def test_step(self): + """Test the .step() method calls idb.set_step().""" + pyshell = make_pyshell_mock() + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + + # Patch out the root window and tk session so we can test them + test_debugger.root = mock.Mock() + test_debugger.root.tk = mock.Mock() + + test_debugger.step() + + # Check set_step was called on the idb instance. + idb.set_step.assert_called_once() + + def test_next(self): + """Test the .next() method calls idb.set_next().""" + pyshell = make_pyshell_mock() + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + + # Patch out the root window and tk session so we can test them + test_debugger.root = mock.Mock() + test_debugger.root.tk = mock.Mock() + test_frame = MockFrameType() + + test_debugger.frame = test_frame + test_debugger.next() + + # Check set_next was called on the idb instance. + idb.set_next.assert_called_once_with(test_frame) + + def test_ret(self): + """Test the .ret() method calls idb.set_return().""" + pyshell = make_pyshell_mock() + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + + # Patch out the root window and tk session so we can test them + test_debugger.root = mock.Mock() + test_debugger.root.tk = mock.Mock() + test_frame = MockFrameType() + + test_debugger.frame = test_frame + test_debugger.ret() + + # Check set_return was called on the idb instance. + idb.set_return.assert_called_once_with(test_frame) + + def test_quit(self): + """Test the .quit() method calls idb.set_quit().""" + pyshell = make_pyshell_mock() + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + + # Patch out the root window and tk session so we can test them + test_debugger.root = mock.Mock() + test_debugger.root.tk = mock.Mock() + + test_debugger.quit() + + # Check set_quit was called on the idb instance. + idb.set_quit.assert_called_once_with() + if __name__ == '__main__': unittest.main(verbosity=2) From 2b42337278a46a50db29cbf5c921378350ec8c18 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 15:53:37 +1100 Subject: [PATCH 13/47] finished tests for Debugger --- Lib/idlelib/idle_test/test_debugger.py | 92 +++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index f7ce6c482c832c..3faacc07ba51dd 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -1,10 +1,11 @@ "Test debugger, coverage 19%" +from collections import namedtuple from idlelib import debugger import unittest from unittest import mock from test.support import requires -# requires('gui') +requires('gui') from tkinter import Tk from textwrap import dedent @@ -362,6 +363,95 @@ def test_quit(self): # Check set_quit was called on the idb instance. idb.set_quit.assert_called_once_with() + def test_show_stack(self): + """Test the .show_stack() method calls with stackview""" + pyshell = make_pyshell_mock() + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + test_debugger.show_stack() + + # Check that the newly created stackviewer has the test gui as a field. + self.assertEquals(test_debugger.stackviewer.gui, test_debugger) + + def test_show_stack_with_frame(self): + """Test the .show_stack() method calls with stackview and frame""" + pyshell = make_pyshell_mock() + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + + # Set a frame on the GUI before showing stack + test_frame = MockFrameType() + test_debugger.frame = test_frame + + # Reset the stackviewer to force it to be recreated. + test_debugger.stackviewer = None + + idb.get_stack.return_value = ([], 0) + test_debugger.show_stack() + + # Check that the newly created stackviewer has the test gui as a field. + self.assertEquals(test_debugger.stackviewer.gui, test_debugger) + + idb.get_stack.assert_called_once_with(test_frame, None) + + def test_set_breakpoint_here(self): + """Test the .set_breakpoint_here() method calls idb.""" + pyshell = make_pyshell_mock() + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + + test_debugger.set_breakpoint_here('test.py', 4) + + idb.set_break.assert_called_once_with('test.py', 4) + + def test_clear_breakpoint_here(self): + """Test the .clear_breakpoint_here() method calls idb.""" + pyshell = make_pyshell_mock() + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + + test_debugger.clear_breakpoint_here('test.py', 4) + + idb.clear_break.assert_called_once_with('test.py', 4) + + def test_clear_file_breaks(self): + """Test the .clear_file_breaks() method calls idb.""" + pyshell = make_pyshell_mock() + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + + test_debugger.clear_file_breaks('test.py') + + idb.clear_all_file_breaks.assert_called_once_with('test.py') + + def test_load_breakpoints(self): + """Test the .load_breakpoints() method calls idb.""" + FileIO = namedtuple('FileIO', 'filename') + + class MockEditWindow(object): + def __init__(self, fn, breakpoints): + self.io = FileIO(fn) + self.breakpoints = breakpoints + + pyshell = make_pyshell_mock() + pyshell.flist = mock.Mock() + pyshell.flist.inversedict = ( + MockEditWindow('test1.py', [1, 4, 4]), + MockEditWindow('test2.py', [13, 44, 45]), + ) + idb = mock.Mock() + test_debugger = debugger.Debugger(pyshell, idb) + + test_debugger.load_breakpoints() + + idb.set_break.assert_has_calls( + [mock.call('test1.py', 1), + mock.call('test1.py', 4), + mock.call('test1.py', 4), + mock.call('test2.py', 13), + mock.call('test2.py', 44), + mock.call('test2.py', 45)]) + if __name__ == '__main__': unittest.main(verbosity=2) From 56741f1d1019ee2f5795f3b77d3709b284cf40e7 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 16:52:43 +1100 Subject: [PATCH 14/47] add a constructor for MockFrameType to condense the code --- Lib/idlelib/idle_test/test_debugger.py | 73 ++++++++++++++------------ 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 3faacc07ba51dd..bf1c3f724f31b0 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -5,7 +5,7 @@ import unittest from unittest import mock from test.support import requires -requires('gui') +#requires('gui') from tkinter import Tk from textwrap import dedent @@ -46,8 +46,9 @@ class MockFrameType(object): f_restricted = 0 f_trace = None - def __init__(self): - pass + def __init__(self, code, lineno): + self.f_code = code + self.f_lineno = lineno class IdbTest(unittest.TestCase): @@ -67,13 +68,9 @@ def test_user_line_basic_frame(self): mode='exec') # Create 2 test frames for lines 1 and 2 of the test code. - test_frame1 = MockFrameType() - test_frame1.f_code = code_obj - test_frame1.f_lineno = 1 + test_frame1 = MockFrameType(code_obj, 1) - test_frame2 = MockFrameType() - test_frame2.f_code = code_obj - test_frame2.f_lineno = 2 + test_frame2 = MockFrameType(code_obj, 2) test_frame2.f_back = test_frame1 gui = mock.Mock() @@ -95,9 +92,7 @@ def test_user_exception(self): mode='exec') # Create 1 test frame - test_frame1 = MockFrameType() - test_frame1.f_code = code_obj - test_frame1.f_lineno = 1 + test_frame1 = MockFrameType(code_obj, 1) # Example from sys.exc_info() test_exc_info = (type(ValueError), ValueError(), None) @@ -121,9 +116,7 @@ def test_in_rpc_code_rpc_py(self): mode='exec') # Create 1 test frame - test_frame = MockFrameType() - test_frame.f_code = code_obj - test_frame.f_lineno = 1 + test_frame = MockFrameType(code_obj, 1) gui = mock.Mock() gui.interaction = mock.Mock() @@ -143,13 +136,9 @@ def test_in_rpc_code_debugger_star_dot_py(self): mode='exec') # Create 2 test frames - test_frame = MockFrameType() - test_frame.f_code = code_obj - test_frame.f_lineno = 1 + test_frame = MockFrameType(code_obj, 1) - test_frame2 = MockFrameType() - test_frame2.f_code = code_obj - test_frame2.f_lineno = 2 + test_frame2 = MockFrameType(code_obj, 2) test_frame2.f_back = test_frame gui = mock.Mock() @@ -215,11 +204,9 @@ def test_close_whilst_interacting(self): def test_interaction_with_message_and_frame(self): """Test the interaction sets the window message.""" test_message = "testing 1234.." - test_frame = MockFrameType() test_code = compile(TEST_CODE, 'test_interaction.py', 'exec') - test_frame.f_code = test_code - test_frame.f_lineno = 1 + test_frame = MockFrameType(test_code, 1) pyshell = make_pyshell_mock() @@ -239,12 +226,9 @@ def test_interaction_with_message_and_frame(self): def test_interaction_with_message_and_frame_and_exc_info(self): """Test the interaction sets the window message with exception info.""" test_message = "testing 1234.." - test_frame = MockFrameType() test_exc_info = (type(ValueError), ValueError(), None) test_code = compile(TEST_CODE, 'test_interaction.py', 'exec') - - test_frame.f_code = test_code - test_frame.f_lineno = 1 + test_frame = MockFrameType(test_code, 1) pyshell = make_pyshell_mock() @@ -267,11 +251,8 @@ def test_sync_source_line(self): test_debugger = debugger.Debugger(pyshell) - test_frame = MockFrameType() test_code = compile(TEST_CODE, 'test_sync.py', 'exec') - - test_frame.f_code = test_code - test_frame.f_lineno = 1 + test_frame = MockFrameType(test_code, 1) test_debugger.frame = test_frame @@ -323,7 +304,7 @@ def test_next(self): # Patch out the root window and tk session so we can test them test_debugger.root = mock.Mock() test_debugger.root.tk = mock.Mock() - test_frame = MockFrameType() + test_frame = MockFrameType(None, None) test_debugger.frame = test_frame test_debugger.next() @@ -340,7 +321,7 @@ def test_ret(self): # Patch out the root window and tk session so we can test them test_debugger.root = mock.Mock() test_debugger.root.tk = mock.Mock() - test_frame = MockFrameType() + test_frame = MockFrameType(None, None) test_debugger.frame = test_frame test_debugger.ret() @@ -380,7 +361,7 @@ def test_show_stack_with_frame(self): test_debugger = debugger.Debugger(pyshell, idb) # Set a frame on the GUI before showing stack - test_frame = MockFrameType() + test_frame = MockFrameType(None, None) test_debugger.frame = test_frame # Reset the stackviewer to force it to be recreated. @@ -453,5 +434,27 @@ def __init__(self, fn, breakpoints): mock.call('test2.py', 45)]) +class StackViewerTest(unittest.TestCase): + + def test_init(self): + """Test creation of StackViewer.""" + gui = None + flist = None + master_window = None + sv = debugger.StackViewer(master_window, flist, gui) + self.assertTrue(hasattr(sv, 'stack')) + + def test_load_stack(self): + """Test the .load_stack method against a fixed list.""" + # Arrange a list of test stacks + test_code = compile(TEST_CODE, 'test_load_stack.py', 'exec') + test_stacks = [ + (MockFrameType(test_code, 1), 1), + (MockFrameType(test_code, 2), 2) + ] + test_sv = debugger.StackViewer(None, None, None) + + test_sv.load_stack(test_stacks) + if __name__ == '__main__': unittest.main(verbosity=2) From 8d015ce70cd289a8d7510c4d6a40a61c68753c2b Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 17:06:06 +1100 Subject: [PATCH 15/47] add tests for show_source and load_stack for the StackViewer class --- Lib/idlelib/idle_test/test_debugger.py | 37 +++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index bf1c3f724f31b0..8d2d679d30e0d5 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -445,16 +445,45 @@ def test_init(self): self.assertTrue(hasattr(sv, 'stack')) def test_load_stack(self): - """Test the .load_stack method against a fixed list.""" - # Arrange a list of test stacks + """Test the .load_stack() method against a fixed test stack.""" + # Arrange a test stack. test_code = compile(TEST_CODE, 'test_load_stack.py', 'exec') - test_stacks = [ + test_stack = [ (MockFrameType(test_code, 1), 1), (MockFrameType(test_code, 2), 2) ] + + # Create a stackviewer and load the test stack. + test_sv = debugger.StackViewer(None, None, None) + test_sv.load_stack(test_stack) + + # Check the test stack is assigned and the list contains the repr of them. + self.assertEquals(test_sv.stack, test_stack) + self.assertEquals(test_sv.get(0), '?.(), line 1: ') + self.assertEquals(test_sv.get(1), '?.(), line 2: ') + + def test_show_source(self): + """Test the .show_source() method against a fixed test stack.""" + # Arrange a test stack. + test_code = compile(TEST_CODE, 'test_show_source.py', 'exec') + test_stack = [ + (MockFrameType(test_code, 1), 1), + (MockFrameType(test_code, 2), 2) + ] + + # Create a stackviewer and load the test stack. test_sv = debugger.StackViewer(None, None, None) + test_sv.load_stack(test_stack) + + # Patch out the file list to monitor it + test_sv.flist = mock.Mock() + + # Patch out isfile to pretend file exists. + with mock.patch('idlelib.debugger.os.path.isfile', return_value=True) as isfile: + test_sv.show_source(1) + isfile.assert_called_once_with('test_show_source.py') + test_sv.flist.open.assert_called_once_with('test_show_source.py') - test_sv.load_stack(test_stacks) if __name__ == '__main__': unittest.main(verbosity=2) From 48075560a5a51888b275a3f653e173e9da7389f0 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Mon, 7 Jan 2019 17:07:15 +1100 Subject: [PATCH 16/47] remove line comment --- Lib/idlelib/idle_test/test_debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 8d2d679d30e0d5..e3425ddc6a37f2 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -5,7 +5,7 @@ import unittest from unittest import mock from test.support import requires -#requires('gui') +requires('gui') from tkinter import Tk from textwrap import dedent From 88e5f91c3b94758506f29d3c30b165279d403841 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" Date: Mon, 7 Jan 2019 06:18:25 +0000 Subject: [PATCH 17/47] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst diff --git a/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst b/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst new file mode 100644 index 00000000000000..377dee6ee15f79 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst @@ -0,0 +1,3 @@ +Add tests for the idlelib.debugger module to validate the existing behaviors of the debugger window, the stack frame viewer and the namespace viewer. +Fixed an issue where debug sessions could crash where the previous frame was None (ie. the first frame in a callstack). +Fixed an issue where the Idb class would not set the "botframe" attribute, causing the debug session to crash. \ No newline at end of file From 0df251212d68f04e32bcb1ecf91c905b9e6e54cf Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Thu, 10 Jan 2019 17:07:12 +1100 Subject: [PATCH 18/47] fixed references to .assertEquals, moved code into setup and teardown, fixed assert statements --- Lib/idlelib/debugger.py | 2 +- Lib/idlelib/idle_test/test_debugger.py | 274 +++++++++---------------- 2 files changed, 96 insertions(+), 180 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index c7bab7e9f0a671..d3a5e01c84c032 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -15,7 +15,7 @@ class Idb(bdb.Bdb): def __init__(self, gui): self.gui = gui # An instance of Debugger or proxy of remote. self.botframe = None - super(Idb, self).__init__() + super().__init__() def user_line(self, frame): """ diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index e3425ddc6a37f2..7d461094c927b4 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -5,7 +5,7 @@ import unittest from unittest import mock from test.support import requires -requires('gui') +#requires('gui') from tkinter import Tk from textwrap import dedent @@ -34,7 +34,7 @@ def test_init(self): debugger.NamespaceViewer(self.root, 'Test') -class MockFrameType(object): +class MockFrameType: """Reflection of the types.FrameType.""" f_back = None f_builtins = None @@ -53,11 +53,16 @@ def __init__(self, code, lineno): class IdbTest(unittest.TestCase): + def setUp(self): + self.gui = mock.Mock() + self.gui.interaction = mock.Mock() + self.idb = debugger.Idb(self.gui) + def test_init_runs_bdb_init(self): """Test that Idb calls the base Bdb __init__.""" idb = debugger.Idb(None) - assert idb.gui is None - assert hasattr(idb, 'breaks') + self.assertIsNone(idb.gui) + self.assertTrue(hasattr(idb, 'breaks')) def test_user_line_basic_frame(self): """Test that .user_line() creates a string message for a frame.""" @@ -73,15 +78,11 @@ def test_user_line_basic_frame(self): test_frame2 = MockFrameType(code_obj, 2) test_frame2.f_back = test_frame1 - gui = mock.Mock() - gui.interaction = mock.Mock() - - idb = debugger.Idb(gui) - idb.user_line(test_frame2) + self.idb.user_line(test_frame2) - self.assertFalse(idb.in_rpc_code(test_frame2)) - gui.interaction.assert_called_once() - gui.interaction.assert_called_with('test_user_line_basic_frame.py:2: ()', test_frame2) + self.assertFalse(self.idb.in_rpc_code(test_frame2)) + self.gui.interaction.assert_called_once() + self.gui.interaction.assert_called_with('test_user_line_basic_frame.py:2: ()', test_frame2) def test_user_exception(self): """Test that .user_exception() creates a string message for a frame.""" @@ -97,15 +98,11 @@ def test_user_exception(self): # Example from sys.exc_info() test_exc_info = (type(ValueError), ValueError(), None) - gui = mock.Mock() - gui.interaction = mock.Mock() - - idb = debugger.Idb(gui) - idb.user_exception(test_frame1, test_exc_info) + self.idb.user_exception(test_frame1, test_exc_info) - self.assertFalse(idb.in_rpc_code(test_frame1)) - gui.interaction.assert_called_once() - gui.interaction.assert_called_with('test_user_exception.py:1: ()', test_frame1, test_exc_info) + self.assertFalse(self.idb.in_rpc_code(test_frame1)) + self.gui.interaction.assert_called_once() + self.gui.interaction.assert_called_with('test_user_exception.py:1: ()', test_frame1, test_exc_info) def test_in_rpc_code_rpc_py(self): """Test that .in_rpc_code detects position of rpc.py.""" @@ -118,12 +115,7 @@ def test_in_rpc_code_rpc_py(self): # Create 1 test frame test_frame = MockFrameType(code_obj, 1) - gui = mock.Mock() - gui.interaction = mock.Mock() - - idb = debugger.Idb(gui) - - self.assertTrue(idb.in_rpc_code(test_frame)) + self.assertTrue(self.idb.in_rpc_code(test_frame)) def test_in_rpc_code_debugger_star_dot_py(self): """Test that .in_rpc_code detects position of idlelib/debugger*.py.""" @@ -141,65 +133,64 @@ def test_in_rpc_code_debugger_star_dot_py(self): test_frame2 = MockFrameType(code_obj, 2) test_frame2.f_back = test_frame - gui = mock.Mock() - gui.interaction = mock.Mock() + self.assertFalse(self.idb.in_rpc_code(test_frame2)) - idb = debugger.Idb(gui) - - self.assertFalse(idb.in_rpc_code(test_frame2)) +class DebuggerTest(unittest.TestCase): + """Tests for the idlelib.debugger.Debugger class.""" -def make_pyshell_mock(): - """Factory for generating test fixtures of PyShell.""" - pyshell = mock.Mock() - pyshell.root = None - return pyshell + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.root.withdraw() + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root -class DebuggerTest(unittest.TestCase): - """Tests for the idlelib.debugger.Debugger class.""" + def setUp(self): + self.pyshell = mock.Mock() + self.pyshell.root = self.root + self.idb = mock.Mock() + self.debugger = debugger.Debugger(self.pyshell, self.idb) + self.debugger.root = self.root def test_setup_debugger(self): """Test that Debugger can be instantiated with a mock PyShell.""" - pyshell = make_pyshell_mock() - test_debugger = debugger.Debugger(pyshell) + test_debugger = debugger.Debugger(self.pyshell) - self.assertEquals(test_debugger.pyshell, pyshell) + self.assertEqual(test_debugger.pyshell, self.pyshell) self.assertIsNone(test_debugger.frame) def test_run_debugger_with_idb(self): """Test Debugger.run() with an Idb instance.""" - mock_idb = mock.Mock() # Mocked debugger.Idb - test_debugger = debugger.Debugger(make_pyshell_mock(), idb=mock_idb) + test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) test_debugger.run(1, 'two') - mock_idb.run.assert_called_once() - mock_idb.run.called_with(1, 'two') - self.assertEquals(test_debugger.interacting, 0) + self.idb.run.assert_called_once() + self.idb.run.called_with(1, 'two') + self.assertEqual(test_debugger.interacting, 0) def test_run_debugger_no_idb(self): """Test Debugger.run() with no Idb instance.""" - test_debugger = debugger.Debugger(make_pyshell_mock(), idb=None) + test_debugger = debugger.Debugger(self.pyshell, idb=None) self.assertIsNotNone(test_debugger.idb) test_debugger.idb.run = mock.Mock() test_debugger.run(1, 'two') test_debugger.idb.run.assert_called_once() test_debugger.idb.run.called_with(1, 'two') - self.assertEquals(test_debugger.interacting, 0) + self.assertEqual(test_debugger.interacting, 0) def test_close(self): """Test closing the window in an idle state.""" - pyshell = make_pyshell_mock() - test_debugger = debugger.Debugger(pyshell) - test_debugger.close() - pyshell.close_debugger.assert_called_once() + self.debugger.close() + self.pyshell.close_debugger.assert_called_once() def test_close_whilst_interacting(self): """Test closing the window in an interactive state.""" - pyshell = make_pyshell_mock() - test_debugger = debugger.Debugger(pyshell) - test_debugger.interacting = 1 - test_debugger.close() - pyshell.close_debugger.assert_not_called() + self.debugger.interacting = 1 + self.debugger.close() + self.pyshell.close_debugger.assert_not_called() def test_interaction_with_message_and_frame(self): """Test the interaction sets the window message.""" @@ -208,20 +199,15 @@ def test_interaction_with_message_and_frame(self): test_frame = MockFrameType(test_code, 1) - pyshell = make_pyshell_mock() - - test_debugger = debugger.Debugger(pyshell) - - # Patch out the root window and tk session so we can test them - test_debugger.root = mock.Mock() - test_debugger.root.tk = mock.Mock() + # Set the response of Idb.get_stack(), required for the stackviewer + self.idb.get_stack.return_value = ([], 0) # Patch out the status label so we can check messages - test_debugger.status = mock.Mock() - test_debugger.interaction(test_message, test_frame) + self.debugger.status = mock.Mock() + self.debugger.interaction(test_message, test_frame) # Check the test message was displayed and cleared - test_debugger.status.configure.assert_has_calls([mock.call(text='testing 1234..'), mock.call(text='')]) + self.debugger.status.configure.assert_has_calls([mock.call(text='testing 1234..'), mock.call(text='')]) def test_interaction_with_message_and_frame_and_exc_info(self): """Test the interaction sets the window message with exception info.""" @@ -230,180 +216,114 @@ def test_interaction_with_message_and_frame_and_exc_info(self): test_code = compile(TEST_CODE, 'test_interaction.py', 'exec') test_frame = MockFrameType(test_code, 1) - pyshell = make_pyshell_mock() - - test_debugger = debugger.Debugger(pyshell) - - # Patch out the root window and tk session so we can test them - test_debugger.root = mock.Mock() - test_debugger.root.tk = mock.Mock() + # Set the response of Idb.get_stack(), required for the stackviewer + self.idb.get_stack.return_value = ([], 0) # Patch out the status label so we can check messages - test_debugger.status = mock.Mock() - test_debugger.interaction(test_message, test_frame, test_exc_info) + self.debugger.status = mock.Mock() + self.debugger.interaction(test_message, test_frame, test_exc_info) # Check the test message was displayed and cleared - test_debugger.status.configure.assert_has_calls([mock.call(text='testing 1234..'), mock.call(text='')]) + self.debugger.status.configure.assert_has_calls([mock.call(text='testing 1234..'), mock.call(text='')]) def test_sync_source_line(self): """Test that .sync_source_line() will set the flist.gotofileline with fixed frame.""" - pyshell = make_pyshell_mock() - - test_debugger = debugger.Debugger(pyshell) - test_code = compile(TEST_CODE, 'test_sync.py', 'exec') test_frame = MockFrameType(test_code, 1) - test_debugger.frame = test_frame + self.debugger.frame = test_frame # Patch out the file list - test_debugger.flist = mock.Mock() + self.debugger.flist = mock.Mock() # Pretend file exists with mock.patch('idlelib.debugger.os.path.exists', return_value=True): - test_debugger.sync_source_line() + self.debugger.sync_source_line() - test_debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) + self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) def test_cont(self): """Test the .cont() method calls idb.set_continue().""" - pyshell = make_pyshell_mock() - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) - - # Patch out the root window and tk session so we can test them - test_debugger.root = mock.Mock() - test_debugger.root.tk = mock.Mock() - - test_debugger.cont() + self.debugger.cont() # Check set_continue was called on the idb instance. - idb.set_continue.assert_called_once() + self.idb.set_continue.assert_called_once() def test_step(self): """Test the .step() method calls idb.set_step().""" - pyshell = make_pyshell_mock() - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) - - # Patch out the root window and tk session so we can test them - test_debugger.root = mock.Mock() - test_debugger.root.tk = mock.Mock() - - test_debugger.step() + self.debugger.step() # Check set_step was called on the idb instance. - idb.set_step.assert_called_once() + self.idb.set_step.assert_called_once() def test_next(self): """Test the .next() method calls idb.set_next().""" - pyshell = make_pyshell_mock() - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) - - # Patch out the root window and tk session so we can test them - test_debugger.root = mock.Mock() - test_debugger.root.tk = mock.Mock() test_frame = MockFrameType(None, None) - test_debugger.frame = test_frame - test_debugger.next() + self.debugger.frame = test_frame + self.debugger.next() # Check set_next was called on the idb instance. - idb.set_next.assert_called_once_with(test_frame) + self.idb.set_next.assert_called_once_with(test_frame) def test_ret(self): """Test the .ret() method calls idb.set_return().""" - pyshell = make_pyshell_mock() - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) - - # Patch out the root window and tk session so we can test them - test_debugger.root = mock.Mock() - test_debugger.root.tk = mock.Mock() test_frame = MockFrameType(None, None) - test_debugger.frame = test_frame - test_debugger.ret() + self.debugger.frame = test_frame + self.debugger.ret() # Check set_return was called on the idb instance. - idb.set_return.assert_called_once_with(test_frame) + self.idb.set_return.assert_called_once_with(test_frame) def test_quit(self): """Test the .quit() method calls idb.set_quit().""" - pyshell = make_pyshell_mock() - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) - - # Patch out the root window and tk session so we can test them - test_debugger.root = mock.Mock() - test_debugger.root.tk = mock.Mock() - - test_debugger.quit() + self.debugger.quit() # Check set_quit was called on the idb instance. - idb.set_quit.assert_called_once_with() + self.idb.set_quit.assert_called_once_with() def test_show_stack(self): """Test the .show_stack() method calls with stackview""" - pyshell = make_pyshell_mock() - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) - test_debugger.show_stack() + self.debugger.show_stack() # Check that the newly created stackviewer has the test gui as a field. - self.assertEquals(test_debugger.stackviewer.gui, test_debugger) + self.assertEqual(self.debugger.stackviewer.gui, self.debugger) def test_show_stack_with_frame(self): """Test the .show_stack() method calls with stackview and frame""" - pyshell = make_pyshell_mock() - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) - # Set a frame on the GUI before showing stack test_frame = MockFrameType(None, None) - test_debugger.frame = test_frame + self.debugger.frame = test_frame # Reset the stackviewer to force it to be recreated. - test_debugger.stackviewer = None + self.debugger.stackviewer = None - idb.get_stack.return_value = ([], 0) - test_debugger.show_stack() + self.idb.get_stack.return_value = ([], 0) + self.debugger.show_stack() # Check that the newly created stackviewer has the test gui as a field. - self.assertEquals(test_debugger.stackviewer.gui, test_debugger) + self.assertEqual(self.debugger.stackviewer.gui, self.debugger) - idb.get_stack.assert_called_once_with(test_frame, None) + self.idb.get_stack.assert_called_once_with(test_frame, None) def test_set_breakpoint_here(self): """Test the .set_breakpoint_here() method calls idb.""" - pyshell = make_pyshell_mock() - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) + self.debugger.set_breakpoint_here('test.py', 4) - test_debugger.set_breakpoint_here('test.py', 4) - - idb.set_break.assert_called_once_with('test.py', 4) + self.idb.set_break.assert_called_once_with('test.py', 4) def test_clear_breakpoint_here(self): """Test the .clear_breakpoint_here() method calls idb.""" - pyshell = make_pyshell_mock() - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) - - test_debugger.clear_breakpoint_here('test.py', 4) + self.debugger.clear_breakpoint_here('test.py', 4) - idb.clear_break.assert_called_once_with('test.py', 4) + self.idb.clear_break.assert_called_once_with('test.py', 4) def test_clear_file_breaks(self): """Test the .clear_file_breaks() method calls idb.""" - pyshell = make_pyshell_mock() - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) + self.debugger.clear_file_breaks('test.py') - test_debugger.clear_file_breaks('test.py') - - idb.clear_all_file_breaks.assert_called_once_with('test.py') + self.idb.clear_all_file_breaks.assert_called_once_with('test.py') def test_load_breakpoints(self): """Test the .load_breakpoints() method calls idb.""" @@ -414,18 +334,14 @@ def __init__(self, fn, breakpoints): self.io = FileIO(fn) self.breakpoints = breakpoints - pyshell = make_pyshell_mock() - pyshell.flist = mock.Mock() - pyshell.flist.inversedict = ( + self.pyshell.flist = mock.Mock() + self.pyshell.flist.inversedict = ( MockEditWindow('test1.py', [1, 4, 4]), MockEditWindow('test2.py', [13, 44, 45]), ) - idb = mock.Mock() - test_debugger = debugger.Debugger(pyshell, idb) - - test_debugger.load_breakpoints() + self.debugger.load_breakpoints() - idb.set_break.assert_has_calls( + self.idb.set_break.assert_has_calls( [mock.call('test1.py', 1), mock.call('test1.py', 4), mock.call('test1.py', 4), @@ -458,9 +374,9 @@ def test_load_stack(self): test_sv.load_stack(test_stack) # Check the test stack is assigned and the list contains the repr of them. - self.assertEquals(test_sv.stack, test_stack) - self.assertEquals(test_sv.get(0), '?.(), line 1: ') - self.assertEquals(test_sv.get(1), '?.(), line 2: ') + self.assertEqual(test_sv.stack, test_stack) + self.assertEqual(test_sv.get(0), '?.(), line 1: ') + self.assertEqual(test_sv.get(1), '?.(), line 2: ') def test_show_source(self): """Test the .show_source() method against a fixed test stack.""" From dfe6db9c238cb6f152338aa118e198a5530e21b7 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Thu, 10 Jan 2019 17:13:38 +1100 Subject: [PATCH 19/47] cleanupm stackviewer tests and use Tk instance --- Lib/idlelib/idle_test/test_debugger.py | 59 +++++++++++++------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 7d461094c927b4..588baa5f73bdd9 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -352,53 +352,52 @@ def __init__(self, fn, breakpoints): class StackViewerTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + def setUp(self): + self.code = compile(TEST_CODE, 'test_stackviewer.py', 'exec') + self.stack = [ + (MockFrameType(self.code, 1), 1), + (MockFrameType(self.code, 2), 2) + ] + + # Create a stackviewer and load the test stack. + self.sv = debugger.StackViewer(self.root, None, None) + self.sv.load_stack(self.stack) + def test_init(self): """Test creation of StackViewer.""" gui = None flist = None - master_window = None + master_window = self.root sv = debugger.StackViewer(master_window, flist, gui) self.assertTrue(hasattr(sv, 'stack')) def test_load_stack(self): """Test the .load_stack() method against a fixed test stack.""" - # Arrange a test stack. - test_code = compile(TEST_CODE, 'test_load_stack.py', 'exec') - test_stack = [ - (MockFrameType(test_code, 1), 1), - (MockFrameType(test_code, 2), 2) - ] - - # Create a stackviewer and load the test stack. - test_sv = debugger.StackViewer(None, None, None) - test_sv.load_stack(test_stack) - # Check the test stack is assigned and the list contains the repr of them. - self.assertEqual(test_sv.stack, test_stack) - self.assertEqual(test_sv.get(0), '?.(), line 1: ') - self.assertEqual(test_sv.get(1), '?.(), line 2: ') + self.assertEqual(self.sv.stack, self.stack) + self.assertEqual(self.sv.get(0), '?.(), line 1: ') + self.assertEqual(self.sv.get(1), '?.(), line 2: ') def test_show_source(self): """Test the .show_source() method against a fixed test stack.""" - # Arrange a test stack. - test_code = compile(TEST_CODE, 'test_show_source.py', 'exec') - test_stack = [ - (MockFrameType(test_code, 1), 1), - (MockFrameType(test_code, 2), 2) - ] - - # Create a stackviewer and load the test stack. - test_sv = debugger.StackViewer(None, None, None) - test_sv.load_stack(test_stack) - # Patch out the file list to monitor it - test_sv.flist = mock.Mock() + self.sv.flist = mock.Mock() # Patch out isfile to pretend file exists. with mock.patch('idlelib.debugger.os.path.isfile', return_value=True) as isfile: - test_sv.show_source(1) - isfile.assert_called_once_with('test_show_source.py') - test_sv.flist.open.assert_called_once_with('test_show_source.py') + self.sv.show_source(1) + isfile.assert_called_once_with('test_stackviewer.py') + self.sv.flist.open.assert_called_once_with('test_stackviewer.py') if __name__ == '__main__': From 2b9a43a49cceef2ce8a27c405783248f7e723d1f Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Thu, 10 Jan 2019 20:16:19 +1100 Subject: [PATCH 20/47] untoggle the gui requirement --- Lib/idlelib/idle_test/test_debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 588baa5f73bdd9..df0014e3ed220b 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -5,7 +5,7 @@ import unittest from unittest import mock from test.support import requires -#requires('gui') +requires('gui') from tkinter import Tk from textwrap import dedent From 5ae4060c99ce2a81dd1ef58189e0a6a6f8d5cf7d Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 5 Apr 2019 10:26:56 +1100 Subject: [PATCH 21/47] Change the single-line docstrings to comments --- Lib/idlelib/idle_test/test_debugger.py | 61 ++++++++++++++------------ 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index df0014e3ed220b..a60a6c28829232 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -58,14 +58,18 @@ def setUp(self): self.gui.interaction = mock.Mock() self.idb = debugger.Idb(self.gui) + def tearDown(self): + self.gui.interaction.destroy() + del self.idb + def test_init_runs_bdb_init(self): - """Test that Idb calls the base Bdb __init__.""" + # Test that Idb calls the base Bdb __init__. idb = debugger.Idb(None) self.assertIsNone(idb.gui) self.assertTrue(hasattr(idb, 'breaks')) def test_user_line_basic_frame(self): - """Test that .user_line() creates a string message for a frame.""" + # Test that .user_line() creates a string message for a frame. # Create a test code object to simulate a debug session. code_obj = compile(TEST_CODE, @@ -85,7 +89,7 @@ def test_user_line_basic_frame(self): self.gui.interaction.assert_called_with('test_user_line_basic_frame.py:2: ()', test_frame2) def test_user_exception(self): - """Test that .user_exception() creates a string message for a frame.""" + # Test that .user_exception() creates a string message for a frame. # Create a test code object to simulate a debug session. code_obj = compile(TEST_CODE, @@ -105,7 +109,7 @@ def test_user_exception(self): self.gui.interaction.assert_called_with('test_user_exception.py:1: ()', test_frame1, test_exc_info) def test_in_rpc_code_rpc_py(self): - """Test that .in_rpc_code detects position of rpc.py.""" + # Test that .in_rpc_code detects position of rpc.py. # Create a test code object to simulate a debug session. code_obj = compile(TEST_CODE, @@ -118,7 +122,7 @@ def test_in_rpc_code_rpc_py(self): self.assertTrue(self.idb.in_rpc_code(test_frame)) def test_in_rpc_code_debugger_star_dot_py(self): - """Test that .in_rpc_code detects position of idlelib/debugger*.py.""" + # Test that .in_rpc_code detects position of idlelib/debugger*.py. # Create a test code object to simulate a debug session. for filename in ('idlelib/debugger.py', 'idlelib/debugger_r.py'): @@ -157,14 +161,14 @@ def setUp(self): self.debugger.root = self.root def test_setup_debugger(self): - """Test that Debugger can be instantiated with a mock PyShell.""" + # Test that Debugger can be instantiated with a mock PyShell. test_debugger = debugger.Debugger(self.pyshell) self.assertEqual(test_debugger.pyshell, self.pyshell) self.assertIsNone(test_debugger.frame) def test_run_debugger_with_idb(self): - """Test Debugger.run() with an Idb instance.""" + # Test Debugger.run() with an Idb instance. test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) test_debugger.run(1, 'two') self.idb.run.assert_called_once() @@ -172,7 +176,7 @@ def test_run_debugger_with_idb(self): self.assertEqual(test_debugger.interacting, 0) def test_run_debugger_no_idb(self): - """Test Debugger.run() with no Idb instance.""" + # Test Debugger.run() with no Idb instance. test_debugger = debugger.Debugger(self.pyshell, idb=None) self.assertIsNotNone(test_debugger.idb) test_debugger.idb.run = mock.Mock() @@ -182,18 +186,18 @@ def test_run_debugger_no_idb(self): self.assertEqual(test_debugger.interacting, 0) def test_close(self): - """Test closing the window in an idle state.""" + # Test closing the window in an idle state. self.debugger.close() self.pyshell.close_debugger.assert_called_once() def test_close_whilst_interacting(self): - """Test closing the window in an interactive state.""" + # Test closing the window in an interactive state. self.debugger.interacting = 1 self.debugger.close() self.pyshell.close_debugger.assert_not_called() def test_interaction_with_message_and_frame(self): - """Test the interaction sets the window message.""" + # Test the interaction sets the window message. test_message = "testing 1234.." test_code = compile(TEST_CODE, 'test_interaction.py', 'exec') @@ -210,7 +214,7 @@ def test_interaction_with_message_and_frame(self): self.debugger.status.configure.assert_has_calls([mock.call(text='testing 1234..'), mock.call(text='')]) def test_interaction_with_message_and_frame_and_exc_info(self): - """Test the interaction sets the window message with exception info.""" + # Test the interaction sets the window message with exception info. test_message = "testing 1234.." test_exc_info = (type(ValueError), ValueError(), None) test_code = compile(TEST_CODE, 'test_interaction.py', 'exec') @@ -227,7 +231,7 @@ def test_interaction_with_message_and_frame_and_exc_info(self): self.debugger.status.configure.assert_has_calls([mock.call(text='testing 1234..'), mock.call(text='')]) def test_sync_source_line(self): - """Test that .sync_source_line() will set the flist.gotofileline with fixed frame.""" + # Test that .sync_source_line() will set the flist.gotofileline with fixed frame. test_code = compile(TEST_CODE, 'test_sync.py', 'exec') test_frame = MockFrameType(test_code, 1) @@ -243,21 +247,21 @@ def test_sync_source_line(self): self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) def test_cont(self): - """Test the .cont() method calls idb.set_continue().""" + # Test the .cont() method calls idb.set_continue(). self.debugger.cont() # Check set_continue was called on the idb instance. self.idb.set_continue.assert_called_once() def test_step(self): - """Test the .step() method calls idb.set_step().""" + # Test the .step() method calls idb.set_step(). self.debugger.step() # Check set_step was called on the idb instance. self.idb.set_step.assert_called_once() def test_next(self): - """Test the .next() method calls idb.set_next().""" + # Test the .next() method calls idb.set_next(). test_frame = MockFrameType(None, None) self.debugger.frame = test_frame @@ -267,7 +271,7 @@ def test_next(self): self.idb.set_next.assert_called_once_with(test_frame) def test_ret(self): - """Test the .ret() method calls idb.set_return().""" + # Test the .ret() method calls idb.set_return(). test_frame = MockFrameType(None, None) self.debugger.frame = test_frame @@ -277,21 +281,22 @@ def test_ret(self): self.idb.set_return.assert_called_once_with(test_frame) def test_quit(self): - """Test the .quit() method calls idb.set_quit().""" + # Test the .quit() method calls idb.set_quit(). self.debugger.quit() # Check set_quit was called on the idb instance. self.idb.set_quit.assert_called_once_with() def test_show_stack(self): - """Test the .show_stack() method calls with stackview""" + # Test the .show_stack() method calls with stackview. self.debugger.show_stack() # Check that the newly created stackviewer has the test gui as a field. self.assertEqual(self.debugger.stackviewer.gui, self.debugger) def test_show_stack_with_frame(self): - """Test the .show_stack() method calls with stackview and frame""" + # Test the .show_stack() method calls with stackview and frame. + # Set a frame on the GUI before showing stack test_frame = MockFrameType(None, None) self.debugger.frame = test_frame @@ -308,25 +313,25 @@ def test_show_stack_with_frame(self): self.idb.get_stack.assert_called_once_with(test_frame, None) def test_set_breakpoint_here(self): - """Test the .set_breakpoint_here() method calls idb.""" + # Test the .set_breakpoint_here() method calls idb. self.debugger.set_breakpoint_here('test.py', 4) self.idb.set_break.assert_called_once_with('test.py', 4) def test_clear_breakpoint_here(self): - """Test the .clear_breakpoint_here() method calls idb.""" + # Test the .clear_breakpoint_here() method calls idb. self.debugger.clear_breakpoint_here('test.py', 4) self.idb.clear_break.assert_called_once_with('test.py', 4) def test_clear_file_breaks(self): - """Test the .clear_file_breaks() method calls idb.""" + # Test the .clear_file_breaks() method calls idb. self.debugger.clear_file_breaks('test.py') self.idb.clear_all_file_breaks.assert_called_once_with('test.py') def test_load_breakpoints(self): - """Test the .load_breakpoints() method calls idb.""" + # Test the .load_breakpoints() method calls idb. FileIO = namedtuple('FileIO', 'filename') class MockEditWindow(object): @@ -374,7 +379,7 @@ def setUp(self): self.sv.load_stack(self.stack) def test_init(self): - """Test creation of StackViewer.""" + # Test creation of StackViewer. gui = None flist = None master_window = self.root @@ -382,14 +387,16 @@ def test_init(self): self.assertTrue(hasattr(sv, 'stack')) def test_load_stack(self): - """Test the .load_stack() method against a fixed test stack.""" + # Test the .load_stack() method against a fixed test stack. + # Check the test stack is assigned and the list contains the repr of them. self.assertEqual(self.sv.stack, self.stack) self.assertEqual(self.sv.get(0), '?.(), line 1: ') self.assertEqual(self.sv.get(1), '?.(), line 2: ') def test_show_source(self): - """Test the .show_source() method against a fixed test stack.""" + # Test the .show_source() method against a fixed test stack. + # Patch out the file list to monitor it self.sv.flist = mock.Mock() From 14b908ff54b2b3d7ad91916ababdcec5eb480290 Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 5 Apr 2019 11:51:53 +1100 Subject: [PATCH 22/47] Removed the 2 interaction tests because they cause the UI to wait for a user interaction, which makes the tests hand --- Lib/idlelib/debugger.py | 2 +- Lib/idlelib/idle_test/test_debugger.py | 34 -------------------------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index d3a5e01c84c032..f2a26798801202 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -277,7 +277,7 @@ def interaction(self, message, frame, info=None): # Tcl's vwait facility, which reenters the event loop until an # event handler sets the variable we're waiting on. self.nesting_level += 1 - self.root.tk.call('vwait', '::idledebugwait') + self.root.tk.call('vwait', '::idledebugwait', timeout=0.1) self.nesting_level -= 1 for b in self.buttons: diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index a60a6c28829232..5e291d6d7c38d6 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -196,40 +196,6 @@ def test_close_whilst_interacting(self): self.debugger.close() self.pyshell.close_debugger.assert_not_called() - def test_interaction_with_message_and_frame(self): - # Test the interaction sets the window message. - test_message = "testing 1234.." - test_code = compile(TEST_CODE, 'test_interaction.py', 'exec') - - test_frame = MockFrameType(test_code, 1) - - # Set the response of Idb.get_stack(), required for the stackviewer - self.idb.get_stack.return_value = ([], 0) - - # Patch out the status label so we can check messages - self.debugger.status = mock.Mock() - self.debugger.interaction(test_message, test_frame) - - # Check the test message was displayed and cleared - self.debugger.status.configure.assert_has_calls([mock.call(text='testing 1234..'), mock.call(text='')]) - - def test_interaction_with_message_and_frame_and_exc_info(self): - # Test the interaction sets the window message with exception info. - test_message = "testing 1234.." - test_exc_info = (type(ValueError), ValueError(), None) - test_code = compile(TEST_CODE, 'test_interaction.py', 'exec') - test_frame = MockFrameType(test_code, 1) - - # Set the response of Idb.get_stack(), required for the stackviewer - self.idb.get_stack.return_value = ([], 0) - - # Patch out the status label so we can check messages - self.debugger.status = mock.Mock() - self.debugger.interaction(test_message, test_frame, test_exc_info) - - # Check the test message was displayed and cleared - self.debugger.status.configure.assert_has_calls([mock.call(text='testing 1234..'), mock.call(text='')]) - def test_sync_source_line(self): # Test that .sync_source_line() will set the flist.gotofileline with fixed frame. test_code = compile(TEST_CODE, 'test_sync.py', 'exec') From ae491f4ab93fbf9a79597aae1e4d384d34ba4efa Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 5 Apr 2019 12:06:51 +1100 Subject: [PATCH 23/47] Fix the test assertions and use stacked frames when testing debugging (because a single frame would never exist) --- Lib/idlelib/debugger.py | 2 +- Lib/idlelib/idle_test/test_debugger.py | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index f2a26798801202..d3a5e01c84c032 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -277,7 +277,7 @@ def interaction(self, message, frame, info=None): # Tcl's vwait facility, which reenters the event loop until an # event handler sets the variable we're waiting on. self.nesting_level += 1 - self.root.tk.call('vwait', '::idledebugwait', timeout=0.1) + self.root.tk.call('vwait', '::idledebugwait') self.nesting_level -= 1 for b in self.buttons: diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 5e291d6d7c38d6..a22fba7f616636 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -73,7 +73,7 @@ def test_user_line_basic_frame(self): # Create a test code object to simulate a debug session. code_obj = compile(TEST_CODE, - 'test_user_line_basic_frame.py', + 'idlelib/debugger.py', mode='exec') # Create 2 test frames for lines 1 and 2 of the test code. @@ -86,27 +86,30 @@ def test_user_line_basic_frame(self): self.assertFalse(self.idb.in_rpc_code(test_frame2)) self.gui.interaction.assert_called_once() - self.gui.interaction.assert_called_with('test_user_line_basic_frame.py:2: ()', test_frame2) + self.gui.interaction.assert_called_with('debugger.py:2: ()', test_frame2) def test_user_exception(self): # Test that .user_exception() creates a string message for a frame. # Create a test code object to simulate a debug session. code_obj = compile(TEST_CODE, - 'test_user_exception.py', + 'idlelib/debugger.py', mode='exec') - # Create 1 test frame + # Create 2 test frames for lines 1 and 2 of the test code. test_frame1 = MockFrameType(code_obj, 1) + test_frame2 = MockFrameType(code_obj, 2) + test_frame2.f_back = test_frame1 + # Example from sys.exc_info() test_exc_info = (type(ValueError), ValueError(), None) - self.idb.user_exception(test_frame1, test_exc_info) + self.idb.user_exception(test_frame2, test_exc_info) - self.assertFalse(self.idb.in_rpc_code(test_frame1)) + self.assertFalse(self.idb.in_rpc_code(test_frame2)) self.gui.interaction.assert_called_once() - self.gui.interaction.assert_called_with('test_user_exception.py:1: ()', test_frame1, test_exc_info) + self.gui.interaction.assert_called_with('debugger.py:2: ()', test_frame2, test_exc_info) def test_in_rpc_code_rpc_py(self): # Test that .in_rpc_code detects position of rpc.py. @@ -357,7 +360,7 @@ def test_load_stack(self): # Check the test stack is assigned and the list contains the repr of them. self.assertEqual(self.sv.stack, self.stack) - self.assertEqual(self.sv.get(0), '?.(), line 1: ') + self.assertEqual(self.sv.get(0), '?.(), line 1: "Test stackviewer, coverage 63%."') self.assertEqual(self.sv.get(1), '?.(), line 2: ') def test_show_source(self): From bc28ced24e2cc028a8079352c22cc8605fe9b99b Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 5 Apr 2019 12:37:37 +1100 Subject: [PATCH 24/47] Make the test vaguer so that if the coverage checks pass it doesn't impact the exact coverage number --- Lib/idlelib/idle_test/test_debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index a22fba7f616636..feba7bc730de7f 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -360,7 +360,7 @@ def test_load_stack(self): # Check the test stack is assigned and the list contains the repr of them. self.assertEqual(self.sv.stack, self.stack) - self.assertEqual(self.sv.get(0), '?.(), line 1: "Test stackviewer, coverage 63%."') + self.assertTrue('?.(), line 1:' in self.sv.get(0)) self.assertEqual(self.sv.get(1), '?.(), line 2: ') def test_show_source(self): From 9333fcf5424fe59ee292ff842885230b61c097be Mon Sep 17 00:00:00 2001 From: Anthony Shaw Date: Fri, 5 Apr 2019 12:50:12 +1100 Subject: [PATCH 25/47] Clean up attributes in memory and split tests between those which need an Idb instance and those which don't --- Lib/idlelib/idle_test/test_debugger.py | 69 +++++++++++++++++++------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index feba7bc730de7f..551cf91bf09e5f 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -59,7 +59,8 @@ def setUp(self): self.idb = debugger.Idb(self.gui) def tearDown(self): - self.gui.interaction.destroy() + del self.gui.interaction + del self.gui del self.idb def test_init_runs_bdb_init(self): @@ -159,10 +160,15 @@ def tearDownClass(cls): def setUp(self): self.pyshell = mock.Mock() self.pyshell.root = self.root - self.idb = mock.Mock() - self.debugger = debugger.Debugger(self.pyshell, self.idb) + self.debugger = debugger.Debugger(self.pyshell, None) self.debugger.root = self.root + def tearDown(self): + del self.pyshell.root + del self.pyshell + del self.debugger.root + del self.debugger + def test_setup_debugger(self): # Test that Debugger can be instantiated with a mock PyShell. test_debugger = debugger.Debugger(self.pyshell) @@ -170,13 +176,6 @@ def test_setup_debugger(self): self.assertEqual(test_debugger.pyshell, self.pyshell) self.assertIsNone(test_debugger.frame) - def test_run_debugger_with_idb(self): - # Test Debugger.run() with an Idb instance. - test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) - test_debugger.run(1, 'two') - self.idb.run.assert_called_once() - self.idb.run.called_with(1, 'two') - self.assertEqual(test_debugger.interacting, 0) def test_run_debugger_no_idb(self): # Test Debugger.run() with no Idb instance. @@ -215,6 +214,49 @@ def test_sync_source_line(self): self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) + def test_show_stack(self): + # Test the .show_stack() method calls with stackview. + self.debugger.show_stack() + + # Check that the newly created stackviewer has the test gui as a field. + self.assertEqual(self.debugger.stackviewer.gui, self.debugger) + + +class DebuggerIdbTest(unittest.TestCase): + """Tests for the idlelib.debugger.Debugger class with an Idb.""" + + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + def setUp(self): + self.pyshell = mock.Mock() + self.pyshell.root = self.root + self.idb = mock.Mock() + self.debugger = debugger.Debugger(self.pyshell, self.idb) + self.debugger.root = self.root + + def tearDown(self): + del self.pyshell.root + del self.pyshell + del self.idb + del self.debugger.root + del self.debugger + + def test_run_debugger(self): + # Test Debugger.run() with an Idb instance. + test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) + test_debugger.run(1, 'two') + self.idb.run.assert_called_once() + self.idb.run.called_with(1, 'two') + self.assertEqual(test_debugger.interacting, 0) + def test_cont(self): # Test the .cont() method calls idb.set_continue(). self.debugger.cont() @@ -256,13 +298,6 @@ def test_quit(self): # Check set_quit was called on the idb instance. self.idb.set_quit.assert_called_once_with() - def test_show_stack(self): - # Test the .show_stack() method calls with stackview. - self.debugger.show_stack() - - # Check that the newly created stackviewer has the test gui as a field. - self.assertEqual(self.debugger.stackviewer.gui, self.debugger) - def test_show_stack_with_frame(self): # Test the .show_stack() method calls with stackview and frame. From daa2015bf14f370bfe541cd789ea690884266fb7 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Tue, 14 Nov 2023 02:34:39 -0500 Subject: [PATCH 26/47] Update Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst Condense as is current practice. --- .../NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst b/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst index 377dee6ee15f79..f0f128eb3928f6 100644 --- a/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst +++ b/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst @@ -1,3 +1 @@ -Add tests for the idlelib.debugger module to validate the existing behaviors of the debugger window, the stack frame viewer and the namespace viewer. -Fixed an issue where debug sessions could crash where the previous frame was None (ie. the first frame in a callstack). -Fixed an issue where the Idb class would not set the "botframe" attribute, causing the debug session to crash. \ No newline at end of file +Add docstrings to the IDLE 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. Greatly expand test_debugger. \ No newline at end of file From 37afb53b5b4bb264f68f7a7bdff83dc05551fbc9 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Tue, 14 Nov 2023 02:37:19 -0500 Subject: [PATCH 27/47] Update 2019-01-07-06-18-25.bpo-35668.JimxP5.rst trailing newline --- .../next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst b/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst index f0f128eb3928f6..8bb5420517d55f 100644 --- a/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst +++ b/Misc/NEWS.d/next/IDLE/2019-01-07-06-18-25.bpo-35668.JimxP5.rst @@ -1 +1,4 @@ -Add docstrings to the IDLE 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. Greatly expand test_debugger. \ No newline at end of file +Add docstrings to the IDLE 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. +Greatly expand test_debugger. From e9d0e4eb891b5199b42a61c25c2b73eaa6d5fb43 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 01:32:00 -0500 Subject: [PATCH 28/47] Add comment to stackviewer. --- Lib/idlelib/stackviewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/idlelib/stackviewer.py b/Lib/idlelib/stackviewer.py index 4858cc682a4f45..f8e60fd9b6d818 100644 --- a/Lib/idlelib/stackviewer.py +++ b/Lib/idlelib/stackviewer.py @@ -1,3 +1,5 @@ +# Rename to stackbrowser or possibly consolidate with browser. + import linecache import os From 3b97b55b475e30dcdcb8073b71b55118165e23ff Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 01:33:24 -0500 Subject: [PATCH 29/47] Add debugger module docstrings and revise those for Idb. --- Lib/idlelib/debugger.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 40ab9dade9da95..50a83008699e19 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -1,3 +1,20 @@ +"""Debug user code with a GUI interface to a subclass of bdb.Bdb. + +The Idb idb and Debugger gui instances each need a reference to each +other or to an rpc proxy for each other. + +If IDLE is started with '-n', so that user code and idb both run in the +IDLE process, Debugger is called without an idb. Debugger.__init__ +calls Idb with its incomplete self. Idb.__init__ stores gui and gui +then stores idb. + +If IDLE is started normally, so that user code executes in a separate +process, debugger_r.start_remote_debugger is called, executing in the +IDLE process. It calls 'start the debugger' in the remote process, +which calls Idb with a gui proxy. Then Debugger is called in the IDLE +for more. +""" + import bdb import os @@ -10,24 +27,22 @@ class Idb(bdb.Bdb): - """Idle debugger, based on the bdb debugger.""" + "Supply user_line and user_exception functions for Bdb." def __init__(self, gui): - self.gui = gui # An instance of Debugger or proxy of remote. + self.gui = gui # An instance of Debugger or proxy thereof. self.botframe = None super().__init__() def user_line(self, frame): - """ - Handle a user stopping or breaking at a line. + """Handle a user stopping or breaking at a line. - Implements Bdb.user_line() to convert frame to string - and send message to GUI. + Convert frame to a string and send it to gui. """ if self.in_rpc_code(frame): self.set_step() return - message = self.__frame2message(frame) + message = self.frame2message(frame) try: self.gui.interaction(message, frame) except TclError: # When closing debugger window with [x] in 3.x @@ -38,13 +53,13 @@ def user_exception(self, frame, exc_info): if self.in_rpc_code(frame): self.set_step() return - message = self.__frame2message(frame) + message = self.frame2message(frame) self.gui.interaction(message, frame, exc_info) def in_rpc_code(self, frame): - """Determine if debugger is within RPC code.""" + "Determine if debugger is within RPC code." if frame.f_code.co_filename.count('rpc.py'): - return True + return True # Skip this frame. else: prev_frame = frame.f_back if prev_frame is None: @@ -56,7 +71,7 @@ def in_rpc_code(self, frame): return False return self.in_rpc_code(prev_frame) - def __frame2message(self, frame): + def frame2message(self, frame): """Convert a frame to a message string.""" code = frame.f_code filename = code.co_filename @@ -406,6 +421,7 @@ def load_breakpoints(self): class StackViewer(ScrolledList): + "Code stack viewer for debugger GUI." def __init__(self, master, flist, gui): if macosx.isAquaTk(): @@ -489,6 +505,7 @@ def show_source(self, index): class NamespaceViewer: + "Global/local namespace viewer for debugger GUI." def __init__(self, master, title, dict=None): width = 0 From d989bfae029db2353b0feb409472e2ef03527ea8 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 01:38:14 -0500 Subject: [PATCH 30/47] Fix test failures by using 'assert_called_once_with'. + some style changes. --- Lib/idlelib/idle_test/test_debugger.py | 82 ++++++++++++-------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 551cf91bf09e5f..89f959f09a49f7 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -1,13 +1,14 @@ "Test debugger, coverage 19%" -from collections import namedtuple from idlelib import debugger +from collections import namedtuple +from textwrap import dedent +from tkinter import Tk + +from test.support import requires import unittest from unittest import mock -from test.support import requires requires('gui') -from tkinter import Tk -from textwrap import dedent """A test python script for the debug tests.""" TEST_CODE = dedent(""" @@ -18,33 +19,8 @@ """) -class NameSpaceTest(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.root = Tk() - cls.root.withdraw() - - @classmethod - def tearDownClass(cls): - cls.root.destroy() - del cls.root - - def test_init(self): - debugger.NamespaceViewer(self.root, 'Test') - - -class MockFrameType: - """Reflection of the types.FrameType.""" - f_back = None - f_builtins = None - f_code = None - f_globals = None - f_lasti = None - f_lineno = None - f_locals = None - f_restricted = 0 - f_trace = None +class MockFrame: + "Minimal mock" def __init__(self, code, lineno): self.f_code = code @@ -78,9 +54,9 @@ def test_user_line_basic_frame(self): mode='exec') # Create 2 test frames for lines 1 and 2 of the test code. - test_frame1 = MockFrameType(code_obj, 1) + test_frame1 = MockFrame(code_obj, 1) - test_frame2 = MockFrameType(code_obj, 2) + test_frame2 = MockFrame(code_obj, 2) test_frame2.f_back = test_frame1 self.idb.user_line(test_frame2) @@ -98,9 +74,9 @@ def test_user_exception(self): mode='exec') # Create 2 test frames for lines 1 and 2 of the test code. - test_frame1 = MockFrameType(code_obj, 1) + test_frame1 = MockFrame(code_obj, 1) - test_frame2 = MockFrameType(code_obj, 2) + test_frame2 = MockFrame(code_obj, 2) test_frame2.f_back = test_frame1 # Example from sys.exc_info() @@ -121,7 +97,7 @@ def test_in_rpc_code_rpc_py(self): mode='exec') # Create 1 test frame - test_frame = MockFrameType(code_obj, 1) + test_frame = MockFrame(code_obj, 1) self.assertTrue(self.idb.in_rpc_code(test_frame)) @@ -136,9 +112,9 @@ def test_in_rpc_code_debugger_star_dot_py(self): mode='exec') # Create 2 test frames - test_frame = MockFrameType(code_obj, 1) + test_frame = MockFrame(code_obj, 1) - test_frame2 = MockFrameType(code_obj, 2) + test_frame2 = MockFrame(code_obj, 2) test_frame2.f_back = test_frame self.assertFalse(self.idb.in_rpc_code(test_frame2)) @@ -184,7 +160,7 @@ def test_run_debugger_no_idb(self): test_debugger.idb.run = mock.Mock() test_debugger.run(1, 'two') test_debugger.idb.run.assert_called_once() - test_debugger.idb.run.called_with(1, 'two') + test_debugger.idb.run.assert_called_once_with(1, 'two') self.assertEqual(test_debugger.interacting, 0) def test_close(self): @@ -201,7 +177,7 @@ def test_close_whilst_interacting(self): def test_sync_source_line(self): # Test that .sync_source_line() will set the flist.gotofileline with fixed frame. test_code = compile(TEST_CODE, 'test_sync.py', 'exec') - test_frame = MockFrameType(test_code, 1) + test_frame = MockFrame(test_code, 1) self.debugger.frame = test_frame @@ -273,7 +249,7 @@ def test_step(self): def test_next(self): # Test the .next() method calls idb.set_next(). - test_frame = MockFrameType(None, None) + test_frame = MockFrame(None, None) self.debugger.frame = test_frame self.debugger.next() @@ -283,7 +259,7 @@ def test_next(self): def test_ret(self): # Test the .ret() method calls idb.set_return(). - test_frame = MockFrameType(None, None) + test_frame = MockFrame(None, None) self.debugger.frame = test_frame self.debugger.ret() @@ -302,7 +278,7 @@ def test_show_stack_with_frame(self): # Test the .show_stack() method calls with stackview and frame. # Set a frame on the GUI before showing stack - test_frame = MockFrameType(None, None) + test_frame = MockFrame(None, None) self.debugger.frame = test_frame # Reset the stackviewer to force it to be recreated. @@ -374,8 +350,8 @@ def tearDownClass(cls): def setUp(self): self.code = compile(TEST_CODE, 'test_stackviewer.py', 'exec') self.stack = [ - (MockFrameType(self.code, 1), 1), - (MockFrameType(self.code, 2), 2) + (MockFrame(self.code, 1), 1), + (MockFrame(self.code, 2), 2) ] # Create a stackviewer and load the test stack. @@ -411,5 +387,21 @@ def test_show_source(self): self.sv.flist.open.assert_called_once_with('test_stackviewer.py') +class NameSpaceTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + def test_init(self): + debugger.NamespaceViewer(self.root, 'Test') + + if __name__ == '__main__': unittest.main(verbosity=2) From 22e82491cce8e312a822d3bf6cf80cbc020e9954 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 01:59:32 -0500 Subject: [PATCH 31/47] Remove duplicate test. --- Lib/idlelib/idle_test/test_debugger.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 89f959f09a49f7..728a424147cb8c 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -20,7 +20,7 @@ class MockFrame: - "Minimal mock" + "Minimal mock frame." def __init__(self, code, lineno): self.f_code = code @@ -152,17 +152,6 @@ def test_setup_debugger(self): self.assertEqual(test_debugger.pyshell, self.pyshell) self.assertIsNone(test_debugger.frame) - - def test_run_debugger_no_idb(self): - # Test Debugger.run() with no Idb instance. - test_debugger = debugger.Debugger(self.pyshell, idb=None) - self.assertIsNotNone(test_debugger.idb) - test_debugger.idb.run = mock.Mock() - test_debugger.run(1, 'two') - test_debugger.idb.run.assert_called_once() - test_debugger.idb.run.assert_called_once_with(1, 'two') - self.assertEqual(test_debugger.interacting, 0) - def test_close(self): # Test closing the window in an idle state. self.debugger.close() @@ -230,7 +219,7 @@ def test_run_debugger(self): test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) test_debugger.run(1, 'two') self.idb.run.assert_called_once() - self.idb.run.called_with(1, 'two') + self.idb.run.assert_called_once_with(1, 'two') self.assertEqual(test_debugger.interacting, 0) def test_cont(self): From f9f1a22c638a6643c3afa68d650d95a3ca6b24e4 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 20:24:24 -0500 Subject: [PATCH 32/47] Remove added 'botframe' initialization as very likely not needed. --- Lib/idlelib/debugger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 50a83008699e19..b06a1147f065a2 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -31,7 +31,6 @@ class Idb(bdb.Bdb): def __init__(self, gui): self.gui = gui # An instance of Debugger or proxy thereof. - self.botframe = None super().__init__() def user_line(self, frame): From a202f4f6cbb96903b60dbf57a52519bee2f23af0 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 20:55:01 -0500 Subject: [PATCH 33/47] Move function frame2message to module level. The `self` argument is not used. --- Lib/idlelib/debugger.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index b06a1147f065a2..e6ec4f0d8ce63d 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -41,7 +41,7 @@ def user_line(self, frame): if self.in_rpc_code(frame): self.set_step() return - message = self.frame2message(frame) + message = _frame2message(frame) try: self.gui.interaction(message, frame) except TclError: # When closing debugger window with [x] in 3.x @@ -52,7 +52,7 @@ def user_exception(self, frame, exc_info): if self.in_rpc_code(frame): self.set_step() return - message = self.frame2message(frame) + message = _frame2message(frame) self.gui.interaction(message, frame, exc_info) def in_rpc_code(self, frame): @@ -70,16 +70,16 @@ def in_rpc_code(self, frame): return False return self.in_rpc_code(prev_frame) - def frame2message(self, frame): - """Convert a frame to a message string.""" - code = frame.f_code - filename = code.co_filename - lineno = frame.f_lineno - basename = os.path.basename(filename) - message = f"{basename}:{lineno}" - if code.co_name != "?": - message = f"{message}: {code.co_name}()" - return message +def _frame2message(frame): + """Return a message string for frame.""" + code = frame.f_code + filename = code.co_filename + lineno = frame.f_lineno + basename = os.path.basename(filename) + message = f"{basename}:{lineno}" + if code.co_name != "?": + message = f"{message}: {code.co_name}()" + return message class Debugger: From 597cbbaaa4d926c65a573e7cae83d47836e4b691 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 20:58:00 -0500 Subject: [PATCH 34/47] IDLE style is no newline to start docstring. --- Lib/idlelib/debugger.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index e6ec4f0d8ce63d..717a6d9e634383 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -83,8 +83,7 @@ def _frame2message(frame): class Debugger: - """ - The debugger interface. + """The debugger interface. This class handles the drawing of the debugger window and the interactions with the underlying debugger session. @@ -98,8 +97,7 @@ class Debugger: globalsviewer = None def __init__(self, pyshell, idb=None): - """ - Instantiate and draw a debugger window. + """Instantiate and draw a debugger window. :param pyshell: An instance of the PyShell Window :type pyshell: :class:`idlelib.pyshell.PyShell` From 0a53cd148174788386ec09125907d28aa32096d3 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 21:00:05 -0500 Subject: [PATCH 35/47] Change 'doc string' to 'Doc string.' --- Lib/idlelib/debugger.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 717a6d9e634383..77430651e40ee5 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -459,12 +459,12 @@ def load_stack(self, stack, index=None): self.select(index) def popup_event(self, event): - "override base method" + "Override base method." if self.stack: return ScrolledList.popup_event(self, event) def fill_menu(self): - "override base method" + "Override base method." menu = self.menu menu.add_command(label="Go to source line", command=self.goto_source_line) @@ -472,12 +472,12 @@ def fill_menu(self): command=self.show_stack_frame) def on_select(self, index): - "override base method" + "Override base method." if 0 <= index < len(self.stack): self.gui.show_frame(self.stack[index]) def on_double(self, index): - "override base method" + "Override base method." self.show_source(index) def goto_source_line(self): From 114d68687aba2e4d9755db0ecdbe71d7318991af Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 21:11:18 -0500 Subject: [PATCH 36/47] Remove unneeded Ibd setups and teardowns. Ran leak tests to be sure teardowns not needed. --- Lib/idlelib/idle_test/test_debugger.py | 36 +++++++++----------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 728a424147cb8c..d39baa1298c637 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -29,15 +29,13 @@ def __init__(self, code, lineno): class IdbTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.gui = mock.Mock() + cls.idb = debugger.Idb(cls.gui) + def setUp(self): - self.gui = mock.Mock() self.gui.interaction = mock.Mock() - self.idb = debugger.Idb(self.gui) - - def tearDown(self): - del self.gui.interaction - del self.gui - del self.idb def test_init_runs_bdb_init(self): # Test that Idb calls the base Bdb __init__. @@ -49,21 +47,14 @@ def test_user_line_basic_frame(self): # Test that .user_line() creates a string message for a frame. # Create a test code object to simulate a debug session. - code_obj = compile(TEST_CODE, - 'idlelib/debugger.py', - mode='exec') - - # Create 2 test frames for lines 1 and 2 of the test code. + code_obj = compile(TEST_CODE, 'idlelib/debugger.py', mode='exec') test_frame1 = MockFrame(code_obj, 1) - test_frame2 = MockFrame(code_obj, 2) test_frame2.f_back = test_frame1 self.idb.user_line(test_frame2) - self.assertFalse(self.idb.in_rpc_code(test_frame2)) - self.gui.interaction.assert_called_once() - self.gui.interaction.assert_called_with('debugger.py:2: ()', test_frame2) + self.gui.interaction.assert_called_once_with('debugger.py:2: ()', test_frame2) def test_user_exception(self): # Test that .user_exception() creates a string message for a frame. @@ -85,10 +76,9 @@ def test_user_exception(self): self.idb.user_exception(test_frame2, test_exc_info) self.assertFalse(self.idb.in_rpc_code(test_frame2)) - self.gui.interaction.assert_called_once() - self.gui.interaction.assert_called_with('debugger.py:2: ()', test_frame2, test_exc_info) + self.gui.interaction.assert_called_once_with('debugger.py:2: ()', test_frame2, test_exc_info) - def test_in_rpc_code_rpc_py(self): + def test_in_rpc_code(self): # Test that .in_rpc_code detects position of rpc.py. # Create a test code object to simulate a debug session. @@ -101,7 +91,7 @@ def test_in_rpc_code_rpc_py(self): self.assertTrue(self.idb.in_rpc_code(test_frame)) - def test_in_rpc_code_debugger_star_dot_py(self): + def test_not_in_rpc_code(self): # Test that .in_rpc_code detects position of idlelib/debugger*.py. # Create a test code object to simulate a debug session. @@ -215,12 +205,10 @@ def tearDown(self): del self.debugger def test_run_debugger(self): - # Test Debugger.run() with an Idb instance. test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) - test_debugger.run(1, 'two') - self.idb.run.assert_called_once() + self.debugger.run(1, 'two') self.idb.run.assert_called_once_with(1, 'two') - self.assertEqual(test_debugger.interacting, 0) + self.assertEqual(self.debugger.interacting, 0) def test_cont(self): # Test the .cont() method calls idb.set_continue(). From 1c1107d2f28fdc26524fa50cee4d921f347a1c9c Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 21:42:07 -0500 Subject: [PATCH 37/47] Polish IdbTest. --- Lib/idlelib/idle_test/test_debugger.py | 60 ++++++-------------------- 1 file changed, 12 insertions(+), 48 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index d39baa1298c637..596915cd29aa61 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -29,6 +29,9 @@ def __init__(self, code, lineno): class IdbTest(unittest.TestCase): + code_obj = compile(TEST_CODE, 'idlelib/debugger.py', mode='exec') + rpc_obj = compile(TEST_CODE,'rpc.py', mode='exec') + @classmethod def setUpClass(cls): cls.gui = mock.Mock() @@ -37,19 +40,17 @@ def setUpClass(cls): def setUp(self): self.gui.interaction = mock.Mock() - def test_init_runs_bdb_init(self): - # Test that Idb calls the base Bdb __init__. + def test_init(self): + # Test that Idb.__init_ calls Bdb.__init__. idb = debugger.Idb(None) self.assertIsNone(idb.gui) self.assertTrue(hasattr(idb, 'breaks')) - def test_user_line_basic_frame(self): + def test_user_line(self): # Test that .user_line() creates a string message for a frame. - - # Create a test code object to simulate a debug session. - code_obj = compile(TEST_CODE, 'idlelib/debugger.py', mode='exec') - test_frame1 = MockFrame(code_obj, 1) - test_frame2 = MockFrame(code_obj, 2) + # Create test and code objects to simulate a debug session. + test_frame1 = MockFrame(self.code_obj, 1) + test_frame2 = MockFrame(self.code_obj, 2) test_frame2.f_back = test_frame1 self.idb.user_line(test_frame2) @@ -59,56 +60,19 @@ def test_user_line_basic_frame(self): def test_user_exception(self): # Test that .user_exception() creates a string message for a frame. - # Create a test code object to simulate a debug session. - code_obj = compile(TEST_CODE, - 'idlelib/debugger.py', - mode='exec') - - # Create 2 test frames for lines 1 and 2 of the test code. - test_frame1 = MockFrame(code_obj, 1) - - test_frame2 = MockFrame(code_obj, 2) + test_frame1 = MockFrame(self.code_obj, 1) + test_frame2 = MockFrame(self.code_obj, 2) test_frame2.f_back = test_frame1 - - # Example from sys.exc_info() test_exc_info = (type(ValueError), ValueError(), None) self.idb.user_exception(test_frame2, test_exc_info) - self.assertFalse(self.idb.in_rpc_code(test_frame2)) self.gui.interaction.assert_called_once_with('debugger.py:2: ()', test_frame2, test_exc_info) def test_in_rpc_code(self): - # Test that .in_rpc_code detects position of rpc.py. - - # Create a test code object to simulate a debug session. - code_obj = compile(TEST_CODE, - 'rpc.py', - mode='exec') - - # Create 1 test frame - test_frame = MockFrame(code_obj, 1) - + test_frame = MockFrame(self.rpc_obj, 1) self.assertTrue(self.idb.in_rpc_code(test_frame)) - def test_not_in_rpc_code(self): - # Test that .in_rpc_code detects position of idlelib/debugger*.py. - - # Create a test code object to simulate a debug session. - for filename in ('idlelib/debugger.py', 'idlelib/debugger_r.py'): - - code_obj = compile(TEST_CODE, - filename, - mode='exec') - - # Create 2 test frames - test_frame = MockFrame(code_obj, 1) - - test_frame2 = MockFrame(code_obj, 2) - test_frame2.f_back = test_frame - - self.assertFalse(self.idb.in_rpc_code(test_frame2)) - class DebuggerTest(unittest.TestCase): """Tests for the idlelib.debugger.Debugger class.""" From ed58072c398380b6037e683f13c734eff76cca33 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 21:47:03 -0500 Subject: [PATCH 38/47] Make in_rpc_code a module function, like frame2message. --- Lib/idlelib/debugger.py | 32 +++++++++++++------------- Lib/idlelib/idle_test/test_debugger.py | 6 ++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 77430651e40ee5..9dd1476f1d8847 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -38,7 +38,7 @@ def user_line(self, frame): Convert frame to a string and send it to gui. """ - if self.in_rpc_code(frame): + if _in_rpc_code(frame): self.set_step() return message = _frame2message(frame) @@ -49,26 +49,26 @@ def user_line(self, frame): def user_exception(self, frame, exc_info): """Handle an the occurrence of an exception.""" - if self.in_rpc_code(frame): + if _in_rpc_code(frame): self.set_step() return message = _frame2message(frame) self.gui.interaction(message, frame, exc_info) - def in_rpc_code(self, frame): - "Determine if debugger is within RPC code." - if frame.f_code.co_filename.count('rpc.py'): - return True # Skip this frame. - else: - prev_frame = frame.f_back - if prev_frame is None: - return False - prev_name = prev_frame.f_code.co_filename - if 'idlelib' in prev_name and 'debugger' in prev_name: - # catch both idlelib/debugger.py and idlelib/debugger_r.py - # on both Posix and Windows - return False - return self.in_rpc_code(prev_frame) +def _in_rpc_code(frame): + "Determine if debugger is within RPC code." + if frame.f_code.co_filename.count('rpc.py'): + return True # Skip this frame. + else: + prev_frame = frame.f_back + if prev_frame is None: + return False + prev_name = prev_frame.f_code.co_filename + if 'idlelib' in prev_name and 'debugger' in prev_name: + # catch both idlelib/debugger.py and idlelib/debugger_r.py + # on both Posix and Windows + return False + return in_rpc_code(prev_frame) def _frame2message(frame): """Return a message string for frame.""" diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 596915cd29aa61..a1a07ffcea6725 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -54,7 +54,7 @@ def test_user_line(self): test_frame2.f_back = test_frame1 self.idb.user_line(test_frame2) - self.assertFalse(self.idb.in_rpc_code(test_frame2)) + self.assertFalse(debugger._in_rpc_code(test_frame2)) self.gui.interaction.assert_called_once_with('debugger.py:2: ()', test_frame2) def test_user_exception(self): @@ -66,12 +66,12 @@ def test_user_exception(self): test_exc_info = (type(ValueError), ValueError(), None) self.idb.user_exception(test_frame2, test_exc_info) - self.assertFalse(self.idb.in_rpc_code(test_frame2)) + self.assertFalse(debugger._in_rpc_code(test_frame2)) self.gui.interaction.assert_called_once_with('debugger.py:2: ()', test_frame2, test_exc_info) def test_in_rpc_code(self): test_frame = MockFrame(self.rpc_obj, 1) - self.assertTrue(self.idb.in_rpc_code(test_frame)) + self.assertTrue(debugger._in_rpc_code(test_frame)) class DebuggerTest(unittest.TestCase): From d541302bfd57aae50be4ccdca7ed815b52740d36 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 22:14:25 -0500 Subject: [PATCH 39/47] Add FunctionTest --- Lib/idlelib/debugger.py | 2 +- Lib/idlelib/idle_test/test_debugger.py | 28 +++++++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 9dd1476f1d8847..79f2a2828daf2b 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -68,7 +68,7 @@ def _in_rpc_code(frame): # catch both idlelib/debugger.py and idlelib/debugger_r.py # on both Posix and Windows return False - return in_rpc_code(prev_frame) + return _in_rpc_code(prev_frame) def _frame2message(frame): """Return a message string for frame.""" diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index a1a07ffcea6725..61062be5855bfb 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -54,7 +54,6 @@ def test_user_line(self): test_frame2.f_back = test_frame1 self.idb.user_line(test_frame2) - self.assertFalse(debugger._in_rpc_code(test_frame2)) self.gui.interaction.assert_called_once_with('debugger.py:2: ()', test_frame2) def test_user_exception(self): @@ -66,12 +65,31 @@ def test_user_exception(self): test_exc_info = (type(ValueError), ValueError(), None) self.idb.user_exception(test_frame2, test_exc_info) - self.assertFalse(debugger._in_rpc_code(test_frame2)) self.gui.interaction.assert_called_once_with('debugger.py:2: ()', test_frame2, test_exc_info) - def test_in_rpc_code(self): - test_frame = MockFrame(self.rpc_obj, 1) - self.assertTrue(debugger._in_rpc_code(test_frame)) + +class FunctionTest(unittest.TestCase): + # Test module functions. + + def test_functions(self): + rpc_obj = compile(TEST_CODE,'rpc.py', mode='exec') + rpc_frame = MockFrame(rpc_obj, 2) + rpc_frame.f_back = rpc_frame + self.assertTrue(debugger._in_rpc_code(rpc_frame)) + self.assertEqual(debugger._frame2message(rpc_frame), + 'rpc.py:2: ()') + + code_obj = compile(TEST_CODE, 'idlelib/debugger.py', mode='exec') + code_frame = MockFrame(code_obj, 1) + code_frame.f_back = None + self.assertFalse(debugger._in_rpc_code(code_frame)) + self.assertEqual(debugger._frame2message(code_frame), + 'debugger.py:1: ()') + + code_frame.f_back = code_frame + self.assertFalse(debugger._in_rpc_code(code_frame)) + code_frame.f_back = rpc_frame + self.assertTrue(debugger._in_rpc_code(code_frame)) class DebuggerTest(unittest.TestCase): From df1936f90f0a907ddac6bf78f4924ea519f117b8 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 22:36:14 -0500 Subject: [PATCH 40/47] Refactor IdbTest. --- Lib/idlelib/idle_test/test_debugger.py | 36 ++++++++++++-------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 61062be5855bfb..40a75b7cb1e2f0 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -29,16 +29,19 @@ def __init__(self, code, lineno): class IdbTest(unittest.TestCase): - code_obj = compile(TEST_CODE, 'idlelib/debugger.py', mode='exec') - rpc_obj = compile(TEST_CODE,'rpc.py', mode='exec') - @classmethod def setUpClass(cls): cls.gui = mock.Mock() cls.idb = debugger.Idb(cls.gui) - def setUp(self): - self.gui.interaction = mock.Mock() + # Create test and code objects to simulate a debug session. + code_obj = compile(TEST_CODE, 'idlelib/file.py', mode='exec') + frame1 = MockFrame(code_obj, 1) + frame1.f_back = None + frame2 = MockFrame(code_obj, 2) + frame2.f_back = frame1 + cls.frame = frame2 + cls.msg = 'file.py:2: ()' def test_init(self): # Test that Idb.__init_ calls Bdb.__init__. @@ -48,24 +51,17 @@ def test_init(self): def test_user_line(self): # Test that .user_line() creates a string message for a frame. - # Create test and code objects to simulate a debug session. - test_frame1 = MockFrame(self.code_obj, 1) - test_frame2 = MockFrame(self.code_obj, 2) - test_frame2.f_back = test_frame1 - - self.idb.user_line(test_frame2) - self.gui.interaction.assert_called_once_with('debugger.py:2: ()', test_frame2) + self.gui.interaction = mock.Mock() + self.idb.user_line(self.frame) + self.gui.interaction.assert_called_once_with(self.msg, self.frame) def test_user_exception(self): # Test that .user_exception() creates a string message for a frame. - - test_frame1 = MockFrame(self.code_obj, 1) - test_frame2 = MockFrame(self.code_obj, 2) - test_frame2.f_back = test_frame1 - test_exc_info = (type(ValueError), ValueError(), None) - - self.idb.user_exception(test_frame2, test_exc_info) - self.gui.interaction.assert_called_once_with('debugger.py:2: ()', test_frame2, test_exc_info) + exc_info = (type(ValueError), ValueError(), None) + self.gui.interaction = mock.Mock() + self.idb.user_exception(self.frame, exc_info) + self.gui.interaction.assert_called_once_with( + self.msg, self.frame, exc_info) class FunctionTest(unittest.TestCase): From fedfbc0b17be1c56f35ea698d60f15a04ff3438e Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 22:53:01 -0500 Subject: [PATCH 41/47] FunctionTest comment --- Lib/idlelib/idle_test/test_debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 40a75b7cb1e2f0..69cfaaac4cf800 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -65,7 +65,7 @@ def test_user_exception(self): class FunctionTest(unittest.TestCase): - # Test module functions. + # Test module functions together. def test_functions(self): rpc_obj = compile(TEST_CODE,'rpc.py', mode='exec') From a731e635e346dbfa189b1501db2dbcbc9811f055 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 22:55:53 -0500 Subject: [PATCH 42/47] Use False, True for Debugger().interacting. --- Lib/idlelib/debugger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 79f2a2828daf2b..9fa93f6c673950 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -111,7 +111,7 @@ def __init__(self, pyshell, idb=None): self.idb = idb # If passed, a proxy of remote instance. self.frame = None self.make_gui() - self.interacting = 0 + self.interacting = False self.nesting_level = 0 def run(self, *args): @@ -150,10 +150,10 @@ def run(self, *args): self.root.after(100, lambda: self.run(*args)) return try: - self.interacting = 1 + self.interacting = True return self.idb.run(*args) finally: - self.interacting = 0 + self.interacting = False def close(self, event=None): """Close the debugger and window.""" From 9eb65481c5b900b865fa71705bab8a08d3614c52 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Wed, 15 Nov 2023 23:07:03 -0500 Subject: [PATCH 43/47] Modify Debugger tests --- Lib/idlelib/idle_test/test_debugger.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 69cfaaac4cf800..d08dd35cb4d3fe 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -88,7 +88,7 @@ def test_functions(self): self.assertTrue(debugger._in_rpc_code(code_frame)) -class DebuggerTest(unittest.TestCase): +class DebuggerGuiTest(unittest.TestCase): """Tests for the idlelib.debugger.Debugger class.""" @classmethod @@ -106,6 +106,8 @@ def setUp(self): self.pyshell.root = self.root self.debugger = debugger.Debugger(self.pyshell, None) self.debugger.root = self.root + # real root needed for real make_gui + # run, interacting, abort_loop def tearDown(self): del self.pyshell.root @@ -120,6 +122,12 @@ def test_setup_debugger(self): self.assertEqual(test_debugger.pyshell, self.pyshell) self.assertIsNone(test_debugger.frame) + def test_run_debugger(self): + test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) + self.debugger.run(1, 'two') + self.idb.run.assert_called_once_with(1, 'two') + self.assertEqual(self.debugger.interacting, 0) + def test_close(self): # Test closing the window in an idle state. self.debugger.close() @@ -155,26 +163,22 @@ def test_show_stack(self): self.assertEqual(self.debugger.stackviewer.gui, self.debugger) -class DebuggerIdbTest(unittest.TestCase): +class DebuggerTest(unittest.TestCase): """Tests for the idlelib.debugger.Debugger class with an Idb.""" @classmethod def setUpClass(cls): - cls.root = Tk() - cls.root.withdraw() - - @classmethod - def tearDownClass(cls): - cls.root.destroy() - del cls.root - - def setUp(self): self.pyshell = mock.Mock() self.pyshell.root = self.root self.idb = mock.Mock() self.debugger = debugger.Debugger(self.pyshell, self.idb) self.debugger.root = self.root + @classmethod + def tearDownClass(cls): pass + + def setUp(self): pass + def tearDown(self): del self.pyshell.root del self.pyshell From 0454ea5e8e2c1a69da675674f1cc7625bbd23220 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 16 Nov 2023 00:48:47 -0500 Subject: [PATCH 44/47] Import Mock and patch, refactor to run many tests without gui. --- Lib/idlelib/idle_test/test_debugger.py | 135 +++++++++---------------- 1 file changed, 49 insertions(+), 86 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index d08dd35cb4d3fe..8fc8e321ea2c95 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -8,7 +8,7 @@ from test.support import requires import unittest from unittest import mock -requires('gui') +from unittest.mock import Mock, patch """A test python script for the debug tests.""" TEST_CODE = dedent(""" @@ -31,7 +31,7 @@ class IdbTest(unittest.TestCase): @classmethod def setUpClass(cls): - cls.gui = mock.Mock() + cls.gui = Mock() cls.idb = debugger.Idb(cls.gui) # Create test and code objects to simulate a debug session. @@ -51,14 +51,14 @@ def test_init(self): def test_user_line(self): # Test that .user_line() creates a string message for a frame. - self.gui.interaction = mock.Mock() + self.gui.interaction = Mock() self.idb.user_line(self.frame) self.gui.interaction.assert_called_once_with(self.msg, self.frame) def test_user_exception(self): # Test that .user_exception() creates a string message for a frame. exc_info = (type(ValueError), ValueError(), None) - self.gui.interaction = mock.Mock() + self.gui.interaction = Mock() self.idb.user_exception(self.frame, exc_info) self.gui.interaction.assert_called_once_with( self.msg, self.frame, exc_info) @@ -93,6 +93,7 @@ class DebuggerGuiTest(unittest.TestCase): @classmethod def setUpClass(cls): + requires('gui') cls.root = Tk() cls.root.withdraw() @@ -102,7 +103,7 @@ def tearDownClass(cls): del cls.root def setUp(self): - self.pyshell = mock.Mock() + self.pyshell = Mock() self.pyshell.root = self.root self.debugger = debugger.Debugger(self.pyshell, None) self.debugger.root = self.root @@ -122,11 +123,11 @@ def test_setup_debugger(self): self.assertEqual(test_debugger.pyshell, self.pyshell) self.assertIsNone(test_debugger.frame) - def test_run_debugger(self): - test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) - self.debugger.run(1, 'two') - self.idb.run.assert_called_once_with(1, 'two') - self.assertEqual(self.debugger.interacting, 0) +## def test_run_debugger(self): +## test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) +## self.debugger.run(1, 'two') +## self.idb.run.assert_called_once_with(1, 'two') +## self.assertEqual(self.debugger.interacting, 0) def test_close(self): # Test closing the window in an idle state. @@ -147,10 +148,10 @@ def test_sync_source_line(self): self.debugger.frame = test_frame # Patch out the file list - self.debugger.flist = mock.Mock() + self.debugger.flist = Mock() # Pretend file exists - with mock.patch('idlelib.debugger.os.path.exists', return_value=True): + with patch('idlelib.debugger.os.path.exists', return_value=True): self.debugger.sync_source_line() self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) @@ -168,75 +169,58 @@ class DebuggerTest(unittest.TestCase): @classmethod def setUpClass(cls): - self.pyshell = mock.Mock() - self.pyshell.root = self.root - self.idb = mock.Mock() - self.debugger = debugger.Debugger(self.pyshell, self.idb) - self.debugger.root = self.root - - @classmethod - def tearDownClass(cls): pass + cls.pyshell = Mock() + cls.pyshell.root = Mock() + cls.idb = Mock() + with patch.object(debugger.Debugger, 'make_gui'): + cls.debugger = debugger.Debugger(cls.pyshell, cls.idb) + cls.debugger.root = Mock() def setUp(self): pass - def tearDown(self): - del self.pyshell.root - del self.pyshell - del self.idb - del self.debugger.root - del self.debugger - - def test_run_debugger(self): - test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) - self.debugger.run(1, 'two') - self.idb.run.assert_called_once_with(1, 'two') - self.assertEqual(self.debugger.interacting, 0) +## def test_run_debugger(self): +## test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) +## self.debugger.run(1, 'two') +## self.idb.run.assert_called_once_with(1, 'two') +## self.assertEqual(self.debugger.interacting, 0) def test_cont(self): - # Test the .cont() method calls idb.set_continue(). self.debugger.cont() - - # Check set_continue was called on the idb instance. self.idb.set_continue.assert_called_once() def test_step(self): - # Test the .step() method calls idb.set_step(). self.debugger.step() - - # Check set_step was called on the idb instance. self.idb.set_step.assert_called_once() - def test_next(self): - # Test the .next() method calls idb.set_next(). - test_frame = MockFrame(None, None) - - self.debugger.frame = test_frame - self.debugger.next() + def test_quit(self): + self.debugger.quit() + self.idb.set_quit.assert_called_once() - # Check set_next was called on the idb instance. - self.idb.set_next.assert_called_once_with(test_frame) + def test_next(self): + with patch.object(self.debugger, 'frame') as frame: + self.debugger.next() + self.idb.set_next.assert_called_once_with(frame) def test_ret(self): - # Test the .ret() method calls idb.set_return(). - test_frame = MockFrame(None, None) + with patch.object(self.debugger, 'frame') as frame: + self.debugger.ret() + self.idb.set_return.assert_called_once_with(frame) - self.debugger.frame = test_frame - self.debugger.ret() - - # Check set_return was called on the idb instance. - self.idb.set_return.assert_called_once_with(test_frame) + @unittest.skip('') + def test_set_breakpoint_here(self): + self.debugger.set_breakpoint_here('test.py', 4) + self.idb.set_break.assert_called_once_with('test.py', 4) - def test_quit(self): - # Test the .quit() method calls idb.set_quit(). - self.debugger.quit() + def test_clear_breakpoint_here(self): + self.debugger.clear_breakpoint_here('test.py', 4) + self.idb.clear_break.assert_called_once_with('test.py', 4) - # Check set_quit was called on the idb instance. - self.idb.set_quit.assert_called_once_with() + def test_clear_file_breaks(self): + self.debugger.clear_file_breaks('test.py') + self.idb.clear_all_file_breaks.assert_called_once_with('test.py') + @unittest.skip('') def test_show_stack_with_frame(self): - # Test the .show_stack() method calls with stackview and frame. - - # Set a frame on the GUI before showing stack test_frame = MockFrame(None, None) self.debugger.frame = test_frame @@ -251,24 +235,6 @@ def test_show_stack_with_frame(self): self.idb.get_stack.assert_called_once_with(test_frame, None) - def test_set_breakpoint_here(self): - # Test the .set_breakpoint_here() method calls idb. - self.debugger.set_breakpoint_here('test.py', 4) - - self.idb.set_break.assert_called_once_with('test.py', 4) - - def test_clear_breakpoint_here(self): - # Test the .clear_breakpoint_here() method calls idb. - self.debugger.clear_breakpoint_here('test.py', 4) - - self.idb.clear_break.assert_called_once_with('test.py', 4) - - def test_clear_file_breaks(self): - # Test the .clear_file_breaks() method calls idb. - self.debugger.clear_file_breaks('test.py') - - self.idb.clear_all_file_breaks.assert_called_once_with('test.py') - def test_load_breakpoints(self): # Test the .load_breakpoints() method calls idb. FileIO = namedtuple('FileIO', 'filename') @@ -278,7 +244,7 @@ def __init__(self, fn, breakpoints): self.io = FileIO(fn) self.breakpoints = breakpoints - self.pyshell.flist = mock.Mock() + self.pyshell.flist = Mock() self.pyshell.flist.inversedict = ( MockEditWindow('test1.py', [1, 4, 4]), MockEditWindow('test2.py', [13, 44, 45]), @@ -298,6 +264,7 @@ class StackViewerTest(unittest.TestCase): @classmethod def setUpClass(cls): + requires('gui') cls.root = Tk() cls.root.withdraw() @@ -312,7 +279,6 @@ def setUp(self): (MockFrame(self.code, 1), 1), (MockFrame(self.code, 2), 2) ] - # Create a stackviewer and load the test stack. self.sv = debugger.StackViewer(self.root, None, None) self.sv.load_stack(self.stack) @@ -327,7 +293,6 @@ def test_init(self): def test_load_stack(self): # Test the .load_stack() method against a fixed test stack. - # Check the test stack is assigned and the list contains the repr of them. self.assertEqual(self.sv.stack, self.stack) self.assertTrue('?.(), line 1:' in self.sv.get(0)) @@ -335,12 +300,10 @@ def test_load_stack(self): def test_show_source(self): # Test the .show_source() method against a fixed test stack. - # Patch out the file list to monitor it - self.sv.flist = mock.Mock() - + self.sv.flist = Mock() # Patch out isfile to pretend file exists. - with mock.patch('idlelib.debugger.os.path.isfile', return_value=True) as isfile: + with patch('idlelib.debugger.os.path.isfile', return_value=True) as isfile: self.sv.show_source(1) isfile.assert_called_once_with('test_stackviewer.py') self.sv.flist.open.assert_called_once_with('test_stackviewer.py') From 63008a58a877f250e05aa2c94b74c0ebb3c72a4d Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 18 Nov 2023 22:13:00 -0500 Subject: [PATCH 45/47] Put no-gui tests first, fix breakpoints, work on gui tests. --- Lib/idlelib/debugger.py | 10 +- Lib/idlelib/idle_test/test_debugger.py | 185 +++++++++++-------------- Lib/idlelib/pyshell.py | 16 +-- 3 files changed, 98 insertions(+), 113 deletions(-) diff --git a/Lib/idlelib/debugger.py b/Lib/idlelib/debugger.py index 9fa93f6c673950..f487b4c4b16a60 100644 --- a/Lib/idlelib/debugger.py +++ b/Lib/idlelib/debugger.py @@ -397,10 +397,14 @@ def show_variables(self, force=0): if gv: gv.load_dict(gdict, force, self.pyshell.interp.rpcclt) - def set_breakpoint_here(self, filename, lineno): + def set_breakpoint(self, filename, lineno): + """Set a filename-lineno breakpoint in the debugger. + + Called from self.load_breakpoints and EW.setbreakpoint + """ self.idb.set_break(filename, lineno) - def clear_breakpoint_here(self, filename, lineno): + def clear_breakpoint(self, filename, lineno): self.idb.clear_break(filename, lineno) def clear_file_breaks(self, filename): @@ -412,7 +416,7 @@ def load_breakpoints(self): filename = editwin.io.filename try: for lineno in editwin.breakpoints: - self.set_breakpoint_here(filename, lineno) + self.set_breakpoint(filename, lineno) except AttributeError: continue diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 8fc8e321ea2c95..766a86180f6637 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -88,84 +88,8 @@ def test_functions(self): self.assertTrue(debugger._in_rpc_code(code_frame)) -class DebuggerGuiTest(unittest.TestCase): - """Tests for the idlelib.debugger.Debugger class.""" - - @classmethod - def setUpClass(cls): - requires('gui') - cls.root = Tk() - cls.root.withdraw() - - @classmethod - def tearDownClass(cls): - cls.root.destroy() - del cls.root - - def setUp(self): - self.pyshell = Mock() - self.pyshell.root = self.root - self.debugger = debugger.Debugger(self.pyshell, None) - self.debugger.root = self.root - # real root needed for real make_gui - # run, interacting, abort_loop - - def tearDown(self): - del self.pyshell.root - del self.pyshell - del self.debugger.root - del self.debugger - - def test_setup_debugger(self): - # Test that Debugger can be instantiated with a mock PyShell. - test_debugger = debugger.Debugger(self.pyshell) - - self.assertEqual(test_debugger.pyshell, self.pyshell) - self.assertIsNone(test_debugger.frame) - -## def test_run_debugger(self): -## test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) -## self.debugger.run(1, 'two') -## self.idb.run.assert_called_once_with(1, 'two') -## self.assertEqual(self.debugger.interacting, 0) - - def test_close(self): - # Test closing the window in an idle state. - self.debugger.close() - self.pyshell.close_debugger.assert_called_once() - - def test_close_whilst_interacting(self): - # Test closing the window in an interactive state. - self.debugger.interacting = 1 - self.debugger.close() - self.pyshell.close_debugger.assert_not_called() - - def test_sync_source_line(self): - # Test that .sync_source_line() will set the flist.gotofileline with fixed frame. - test_code = compile(TEST_CODE, 'test_sync.py', 'exec') - test_frame = MockFrame(test_code, 1) - - self.debugger.frame = test_frame - - # Patch out the file list - self.debugger.flist = Mock() - - # Pretend file exists - with patch('idlelib.debugger.os.path.exists', return_value=True): - self.debugger.sync_source_line() - - self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) - - def test_show_stack(self): - # Test the .show_stack() method calls with stackview. - self.debugger.show_stack() - - # Check that the newly created stackviewer has the test gui as a field. - self.assertEqual(self.debugger.stackviewer.gui, self.debugger) - - class DebuggerTest(unittest.TestCase): - """Tests for the idlelib.debugger.Debugger class with an Idb.""" + "Tests for Debugger that do not need a real root." @classmethod def setUpClass(cls): @@ -176,13 +100,11 @@ def setUpClass(cls): cls.debugger = debugger.Debugger(cls.pyshell, cls.idb) cls.debugger.root = Mock() - def setUp(self): pass - -## def test_run_debugger(self): -## test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) -## self.debugger.run(1, 'two') -## self.idb.run.assert_called_once_with(1, 'two') -## self.assertEqual(self.debugger.interacting, 0) + def test_run_debugger(self): + test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) + self.debugger.run(1, 'two') + self.idb.run.assert_called_once_with(1, 'two') + self.assertEqual(self.debugger.interacting, 0) def test_cont(self): self.debugger.cont() @@ -206,19 +128,6 @@ def test_ret(self): self.debugger.ret() self.idb.set_return.assert_called_once_with(frame) - @unittest.skip('') - def test_set_breakpoint_here(self): - self.debugger.set_breakpoint_here('test.py', 4) - self.idb.set_break.assert_called_once_with('test.py', 4) - - def test_clear_breakpoint_here(self): - self.debugger.clear_breakpoint_here('test.py', 4) - self.idb.clear_break.assert_called_once_with('test.py', 4) - - def test_clear_file_breaks(self): - self.debugger.clear_file_breaks('test.py') - self.idb.clear_all_file_breaks.assert_called_once_with('test.py') - @unittest.skip('') def test_show_stack_with_frame(self): test_frame = MockFrame(None, None) @@ -235,7 +144,15 @@ def test_show_stack_with_frame(self): self.idb.get_stack.assert_called_once_with(test_frame, None) - def test_load_breakpoints(self): + def test_clear_breakpoint(self): + self.debugger.clear_breakpoint('test.py', 4) + self.idb.clear_break.assert_called_once_with('test.py', 4) + + def test_clear_file_breaks(self): + self.debugger.clear_file_breaks('test.py') + self.idb.clear_all_file_breaks.assert_called_once_with('test.py') + + def test_set_load_breakpoints(self): # Test the .load_breakpoints() method calls idb. FileIO = namedtuple('FileIO', 'filename') @@ -246,13 +163,14 @@ def __init__(self, fn, breakpoints): self.pyshell.flist = Mock() self.pyshell.flist.inversedict = ( - MockEditWindow('test1.py', [1, 4, 4]), + MockEditWindow('test1.py', [4, 4]), MockEditWindow('test2.py', [13, 44, 45]), ) - self.debugger.load_breakpoints() - + self.debugger.set_breakpoint('test0.py', 1) + self.idb.set_break.assert_called_once_with('test0.py', 1) + self.debugger.load_breakpoints() # Call set_breakpoint 5 times. self.idb.set_break.assert_has_calls( - [mock.call('test1.py', 1), + [mock.call('test0.py', 1), mock.call('test1.py', 4), mock.call('test1.py', 4), mock.call('test2.py', 13), @@ -260,6 +178,69 @@ def __init__(self, fn, breakpoints): mock.call('test2.py', 45)]) +class DebuggerGuiTest(unittest.TestCase): + "Tests for debugger.Debugger that need GUI." + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = root = Tk() + root.withdraw() + cls.pyshell = pyshell = Mock() + pyshell.root = root + cls.idb = Mock() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + def setUp(self): + self.debugger = debugger.Debugger(self.pyshell, self.idb) + self.debugger.root = self.root + # real root needed for real make_gui + # run, interacting, abort_loop + + def test_run_debugger(self): + self.debugger.run(1, 'two') + self.idb.run.assert_called_once_with(1, 'two') + self.assertEqual(self.debugger.interacting, 0) + + def test_close(self): + # Test closing the window in an idle state. + self.debugger.close() + self.pyshell.close_debugger.assert_called_once() + + def test_close_whilst_interacting(self): + # Test closing the window in an interactive state. + self.debugger.interacting = 1 + self.debugger.close() + self.pyshell.close_debugger.assert_not_called() + + def test_sync_source_line(self): + # Test that .sync_source_line() will set the flist.gotofileline with fixed frame. + test_code = compile(TEST_CODE, 'test_sync.py', 'exec') + test_frame = MockFrame(test_code, 1) + + self.debugger.frame = test_frame + + # Patch out the file list + self.debugger.flist = Mock() + + # Pretend file exists + with patch('idlelib.debugger.os.path.exists', return_value=True): + self.debugger.sync_source_line() + + self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) + + def test_show_stack(self): + # Test the .show_stack() method calls with stackview. + self.debugger.show_stack() + + # Check that the newly created stackviewer has the test gui as a field. + self.assertEqual(self.debugger.stackviewer.gui, self.debugger) + + class StackViewerTest(unittest.TestCase): @classmethod diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 7a2707935b60c9..00b3732a7bc4eb 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -133,8 +133,8 @@ class PyShellEditorWindow(EditorWindow): def __init__(self, *args): self.breakpoints = [] EditorWindow.__init__(self, *args) - self.text.bind("<>", self.set_breakpoint_here) - self.text.bind("<>", self.clear_breakpoint_here) + self.text.bind("<>", self.set_breakpoint_event) + self.text.bind("<>", self.clear_breakpoint_event) self.text.bind("<>", self.flist.open_shell) #TODO: don't read/write this from/to .idlerc when testing @@ -155,8 +155,8 @@ def filename_changed_hook(old_hook=self.io.filename_change_hook, ("Copy", "<>", "rmenu_check_copy"), ("Paste", "<>", "rmenu_check_paste"), (None, None, None), - ("Set Breakpoint", "<>", None), - ("Clear Breakpoint", "<>", None) + ("Set Breakpoint", "<>", None), + ("Clear Breakpoint", "<>", None) ] def color_breakpoint_text(self, color=True): @@ -181,11 +181,11 @@ def set_breakpoint(self, lineno): self.breakpoints.append(lineno) try: # update the subprocess debugger debug = self.flist.pyshell.interp.debugger - debug.set_breakpoint_here(filename, lineno) + debug.set_breakpoint(filename, lineno) except: # but debugger may not be active right now.... pass - def set_breakpoint_here(self, event=None): + def set_breakpoint_event(self, event=None): text = self.text filename = self.io.filename if not filename: @@ -194,7 +194,7 @@ def set_breakpoint_here(self, event=None): lineno = int(float(text.index("insert"))) self.set_breakpoint(lineno) - def clear_breakpoint_here(self, event=None): + def clear_breakpoint_event(self, event=None): text = self.text filename = self.io.filename if not filename: @@ -209,7 +209,7 @@ def clear_breakpoint_here(self, event=None): "insert lineend +1char") try: debug = self.flist.pyshell.interp.debugger - debug.clear_breakpoint_here(filename, lineno) + debug.clear_breakpoint(filename, lineno) except: pass From fe14bb8fef3ba735030e7a9140d56219246f53b2 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 18 Nov 2023 22:37:53 -0500 Subject: [PATCH 46/47] Tests pass. --- Lib/idlelib/idle_test/test_debugger.py | 77 +++++++++++--------------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index 766a86180f6637..a2d2f9710886ff 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -100,12 +100,6 @@ def setUpClass(cls): cls.debugger = debugger.Debugger(cls.pyshell, cls.idb) cls.debugger.root = Mock() - def test_run_debugger(self): - test_debugger = debugger.Debugger(self.pyshell, idb=self.idb) - self.debugger.run(1, 'two') - self.idb.run.assert_called_once_with(1, 'two') - self.assertEqual(self.debugger.interacting, 0) - def test_cont(self): self.debugger.cont() self.idb.set_continue.assert_called_once() @@ -128,22 +122,6 @@ def test_ret(self): self.debugger.ret() self.idb.set_return.assert_called_once_with(frame) - @unittest.skip('') - def test_show_stack_with_frame(self): - test_frame = MockFrame(None, None) - self.debugger.frame = test_frame - - # Reset the stackviewer to force it to be recreated. - self.debugger.stackviewer = None - - self.idb.get_stack.return_value = ([], 0) - self.debugger.show_stack() - - # Check that the newly created stackviewer has the test gui as a field. - self.assertEqual(self.debugger.stackviewer.gui, self.debugger) - - self.idb.get_stack.assert_called_once_with(test_frame, None) - def test_clear_breakpoint(self): self.debugger.clear_breakpoint('test.py', 4) self.idb.clear_break.assert_called_once_with('test.py', 4) @@ -177,18 +155,37 @@ def __init__(self, fn, breakpoints): mock.call('test2.py', 44), mock.call('test2.py', 45)]) + def test_sync_source_line(self): + # Test that .sync_source_line() will set the flist.gotofileline with fixed frame. + test_code = compile(TEST_CODE, 'test_sync.py', 'exec') + test_frame = MockFrame(test_code, 1) + self.debugger.frame = test_frame + self.debugger.flist = Mock() + with patch('idlelib.debugger.os.path.exists', return_value=True): + self.debugger.sync_source_line() + self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) + + class DebuggerGuiTest(unittest.TestCase): - "Tests for debugger.Debugger that need GUI." + """Tests for debugger.Debugger that need tk root. + + close needs debugger.top set in make_gui. + """ @classmethod def setUpClass(cls): requires('gui') cls.root = root = Tk() root.withdraw() - cls.pyshell = pyshell = Mock() - pyshell.root = root + cls.pyshell = Mock() + cls.pyshell.root = root cls.idb = Mock() +# stack tests fail with debugger here. +## cls.debugger = debugger.Debugger(cls.pyshell, cls.idb) +## cls.debugger.root = root +## # real root needed for real make_gui +## # run, interacting, abort_loop @classmethod def tearDownClass(cls): @@ -211,34 +208,22 @@ def test_close(self): self.debugger.close() self.pyshell.close_debugger.assert_called_once() - def test_close_whilst_interacting(self): - # Test closing the window in an interactive state. - self.debugger.interacting = 1 - self.debugger.close() - self.pyshell.close_debugger.assert_not_called() - - def test_sync_source_line(self): - # Test that .sync_source_line() will set the flist.gotofileline with fixed frame. - test_code = compile(TEST_CODE, 'test_sync.py', 'exec') - test_frame = MockFrame(test_code, 1) + def test_show_stack(self): + self.debugger.show_stack() + self.assertEqual(self.debugger.stackviewer.gui, self.debugger) + def test_show_stack_with_frame(self): + test_frame = MockFrame(None, None) self.debugger.frame = test_frame - # Patch out the file list - self.debugger.flist = Mock() - - # Pretend file exists - with patch('idlelib.debugger.os.path.exists', return_value=True): - self.debugger.sync_source_line() - - self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) - - def test_show_stack(self): - # Test the .show_stack() method calls with stackview. + # Reset the stackviewer to force it to be recreated. + self.debugger.stackviewer = None + self.idb.get_stack.return_value = ([], 0) self.debugger.show_stack() # Check that the newly created stackviewer has the test gui as a field. self.assertEqual(self.debugger.stackviewer.gui, self.debugger) + self.idb.get_stack.assert_called_once_with(test_frame, None) class StackViewerTest(unittest.TestCase): From 5fe98689012ab4dacc36e76719d0639e78af87e8 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 18 Nov 2023 22:53:11 -0500 Subject: [PATCH 47/47] ws --- Lib/idlelib/idle_test/test_debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py index a2d2f9710886ff..db01a893cb1980 100644 --- a/Lib/idlelib/idle_test/test_debugger.py +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -166,7 +166,7 @@ def test_sync_source_line(self): self.debugger.sync_source_line() self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1) - + class DebuggerGuiTest(unittest.TestCase): """Tests for debugger.Debugger that need tk root.