| """Test debugger, coverage 66% |
| |
| Try to make tests pass with draft bdbx, which may replace bdb in 3.13+. |
| """ |
| |
| 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 unittest.mock import Mock, patch |
| |
| """A test python script for the debug tests.""" |
| TEST_CODE = dedent(""" |
| i = 1 |
| i += 2 |
| if i == 3: |
| print(i) |
| """) |
| |
| |
| class MockFrame: |
| "Minimal mock frame." |
| |
| def __init__(self, code, lineno): |
| self.f_code = code |
| self.f_lineno = lineno |
| |
| |
| class IdbTest(unittest.TestCase): |
| |
| @classmethod |
| def setUpClass(cls): |
| cls.gui = Mock() |
| cls.idb = debugger.Idb(cls.gui) |
| |
| # 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: <module>()' |
| |
| def test_init(self): |
| self.assertIs(self.idb.gui, self.gui) |
| # Won't test super call since two Bdbs are very different. |
| |
| def test_user_line(self): |
| # Test that .user_line() creates a string message for a frame. |
| 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() |
| 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): |
| # Test module functions together. |
| |
| 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: <module>()') |
| |
| 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: <module>()') |
| |
| 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): |
| "Tests for Debugger that do not need a real root." |
| |
| @classmethod |
| def setUpClass(cls): |
| 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 test_cont(self): |
| self.debugger.cont() |
| self.idb.set_continue.assert_called_once() |
| |
| def test_step(self): |
| self.debugger.step() |
| self.idb.set_step.assert_called_once() |
| |
| def test_quit(self): |
| self.debugger.quit() |
| self.idb.set_quit.assert_called_once() |
| |
| 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): |
| with patch.object(self.debugger, 'frame') as frame: |
| self.debugger.ret() |
| self.idb.set_return.assert_called_once_with(frame) |
| |
| 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') |
| |
| class MockEditWindow(object): |
| def __init__(self, fn, breakpoints): |
| self.io = FileIO(fn) |
| self.breakpoints = breakpoints |
| |
| self.pyshell.flist = Mock() |
| self.pyshell.flist.inversedict = ( |
| MockEditWindow('test1.py', [4, 4]), |
| MockEditWindow('test2.py', [13, 44, 45]), |
| ) |
| 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('test0.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)]) |
| |
| 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 tk root. |
| |
| close needs debugger.top set in make_gui. |
| """ |
| |
| @classmethod |
| def setUpClass(cls): |
| requires('gui') |
| cls.root = root = Tk() |
| root.withdraw() |
| 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): |
| 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_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 |
| |
| # 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): |
| |
| @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.code = compile(TEST_CODE, 'test_stackviewer.py', 'exec') |
| self.stack = [ |
| (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) |
| |
| def test_init(self): |
| # Test creation of StackViewer. |
| gui = None |
| flist = 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. |
| # Check the test stack is assigned and the list contains the repr of them. |
| self.assertEqual(self.sv.stack, self.stack) |
| self.assertTrue('?.<module>(), line 1:' in self.sv.get(0)) |
| self.assertEqual(self.sv.get(1), '?.<module>(), line 2: ') |
| |
| 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() |
| # Patch out isfile to pretend file exists. |
| 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') |
| |
| |
| class NameSpaceTest(unittest.TestCase): |
| |
| @classmethod |
| def setUpClass(cls): |
| requires('gui') |
| 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) |