1  """Test debugger, coverage 66%
       2  
       3  Try to make tests pass with draft bdbx, which may replace bdb in 3.13+.
       4  """
       5  
       6  from idlelib import debugger
       7  from collections import namedtuple
       8  from textwrap import dedent
       9  from tkinter import Tk
      10  
      11  from test.support import requires
      12  import unittest
      13  from unittest import mock
      14  from unittest.mock import Mock, patch
      15  
      16  """A test python script for the debug tests."""
      17  TEST_CODE = dedent("""
      18      i = 1
      19      i += 2
      20      if i == 3:
      21         print(i)
      22      """)
      23  
      24  
      25  class ESC[4;38;5;81mMockFrame:
      26      "Minimal mock frame."
      27  
      28      def __init__(self, code, lineno):
      29          self.f_code = code
      30          self.f_lineno = lineno
      31  
      32  
      33  class ESC[4;38;5;81mIdbTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      34  
      35      @classmethod
      36      def setUpClass(cls):
      37          cls.gui = Mock()
      38          cls.idb = debugger.Idb(cls.gui)
      39  
      40          # Create test and code objects to simulate a debug session.
      41          code_obj = compile(TEST_CODE, 'idlelib/file.py', mode='exec')
      42          frame1 = MockFrame(code_obj, 1)
      43          frame1.f_back = None
      44          frame2 = MockFrame(code_obj, 2)
      45          frame2.f_back = frame1
      46          cls.frame = frame2
      47          cls.msg = 'file.py:2: <module>()'
      48  
      49      def test_init(self):
      50          self.assertIs(self.idb.gui, self.gui)
      51          # Won't test super call since two Bdbs are very different.
      52  
      53      def test_user_line(self):
      54          # Test that .user_line() creates a string message for a frame.
      55          self.gui.interaction = Mock()
      56          self.idb.user_line(self.frame)
      57          self.gui.interaction.assert_called_once_with(self.msg, self.frame)
      58  
      59      def test_user_exception(self):
      60          # Test that .user_exception() creates a string message for a frame.
      61          exc_info = (type(ValueError), ValueError(), None)
      62          self.gui.interaction = Mock()
      63          self.idb.user_exception(self.frame, exc_info)
      64          self.gui.interaction.assert_called_once_with(
      65                  self.msg, self.frame, exc_info)
      66  
      67  
      68  class ESC[4;38;5;81mFunctionTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      69      # Test module functions together.
      70  
      71      def test_functions(self):
      72          rpc_obj = compile(TEST_CODE,'rpc.py', mode='exec')
      73          rpc_frame = MockFrame(rpc_obj, 2)
      74          rpc_frame.f_back = rpc_frame
      75          self.assertTrue(debugger._in_rpc_code(rpc_frame))
      76          self.assertEqual(debugger._frame2message(rpc_frame),
      77                           'rpc.py:2: <module>()')
      78  
      79          code_obj = compile(TEST_CODE, 'idlelib/debugger.py', mode='exec')
      80          code_frame = MockFrame(code_obj, 1)
      81          code_frame.f_back = None
      82          self.assertFalse(debugger._in_rpc_code(code_frame))
      83          self.assertEqual(debugger._frame2message(code_frame),
      84                           'debugger.py:1: <module>()')
      85  
      86          code_frame.f_back = code_frame
      87          self.assertFalse(debugger._in_rpc_code(code_frame))
      88          code_frame.f_back = rpc_frame
      89          self.assertTrue(debugger._in_rpc_code(code_frame))
      90  
      91  
      92  class ESC[4;38;5;81mDebuggerTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      93      "Tests for Debugger that do not need a real root."
      94  
      95      @classmethod
      96      def setUpClass(cls):
      97          cls.pyshell = Mock()
      98          cls.pyshell.root = Mock()
      99          cls.idb = Mock()
     100          with patch.object(debugger.Debugger, 'make_gui'):
     101              cls.debugger = debugger.Debugger(cls.pyshell, cls.idb)
     102          cls.debugger.root = Mock()
     103  
     104      def test_cont(self):
     105          self.debugger.cont()
     106          self.idb.set_continue.assert_called_once()
     107  
     108      def test_step(self):
     109          self.debugger.step()
     110          self.idb.set_step.assert_called_once()
     111  
     112      def test_quit(self):
     113          self.debugger.quit()
     114          self.idb.set_quit.assert_called_once()
     115  
     116      def test_next(self):
     117          with patch.object(self.debugger, 'frame') as frame:
     118              self.debugger.next()
     119              self.idb.set_next.assert_called_once_with(frame)
     120  
     121      def test_ret(self):
     122          with patch.object(self.debugger, 'frame') as frame:
     123              self.debugger.ret()
     124              self.idb.set_return.assert_called_once_with(frame)
     125  
     126      def test_clear_breakpoint(self):
     127          self.debugger.clear_breakpoint('test.py', 4)
     128          self.idb.clear_break.assert_called_once_with('test.py', 4)
     129  
     130      def test_clear_file_breaks(self):
     131          self.debugger.clear_file_breaks('test.py')
     132          self.idb.clear_all_file_breaks.assert_called_once_with('test.py')
     133  
     134      def test_set_load_breakpoints(self):
     135          # Test the .load_breakpoints() method calls idb.
     136          FileIO = namedtuple('FileIO', 'filename')
     137  
     138          class ESC[4;38;5;81mMockEditWindow(ESC[4;38;5;149mobject):
     139              def __init__(self, fn, breakpoints):
     140                  self.io = FileIO(fn)
     141                  self.breakpoints = breakpoints
     142  
     143          self.pyshell.flist = Mock()
     144          self.pyshell.flist.inversedict = (
     145              MockEditWindow('test1.py', [4, 4]),
     146              MockEditWindow('test2.py', [13, 44, 45]),
     147          )
     148          self.debugger.set_breakpoint('test0.py', 1)
     149          self.idb.set_break.assert_called_once_with('test0.py', 1)
     150          self.debugger.load_breakpoints()  # Call set_breakpoint 5 times.
     151          self.idb.set_break.assert_has_calls(
     152              [mock.call('test0.py', 1),
     153               mock.call('test1.py', 4),
     154               mock.call('test1.py', 4),
     155               mock.call('test2.py', 13),
     156               mock.call('test2.py', 44),
     157               mock.call('test2.py', 45)])
     158  
     159      def test_sync_source_line(self):
     160          # Test that .sync_source_line() will set the flist.gotofileline with fixed frame.
     161          test_code = compile(TEST_CODE, 'test_sync.py', 'exec')
     162          test_frame = MockFrame(test_code, 1)
     163          self.debugger.frame = test_frame
     164  
     165          self.debugger.flist = Mock()
     166          with patch('idlelib.debugger.os.path.exists', return_value=True):
     167              self.debugger.sync_source_line()
     168          self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1)
     169  
     170  
     171  class ESC[4;38;5;81mDebuggerGuiTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     172      """Tests for debugger.Debugger that need tk root.
     173  
     174      close needs debugger.top set in make_gui.
     175      """
     176  
     177      @classmethod
     178      def setUpClass(cls):
     179          requires('gui')
     180          cls.root = root = Tk()
     181          root.withdraw()
     182          cls.pyshell = Mock()
     183          cls.pyshell.root = root
     184          cls.idb = Mock()
     185  # stack tests fail with debugger here.
     186  ##        cls.debugger = debugger.Debugger(cls.pyshell, cls.idb)
     187  ##        cls.debugger.root = root
     188  ##        # real root needed for real make_gui
     189  ##        # run, interacting, abort_loop
     190  
     191      @classmethod
     192      def tearDownClass(cls):
     193          cls.root.destroy()
     194          del cls.root
     195  
     196      def setUp(self):
     197          self.debugger = debugger.Debugger(self.pyshell, self.idb)
     198          self.debugger.root = self.root
     199          # real root needed for real make_gui
     200          # run, interacting, abort_loop
     201  
     202      def test_run_debugger(self):
     203          self.debugger.run(1, 'two')
     204          self.idb.run.assert_called_once_with(1, 'two')
     205          self.assertEqual(self.debugger.interacting, 0)
     206  
     207      def test_close(self):
     208          # Test closing the window in an idle state.
     209          self.debugger.close()
     210          self.pyshell.close_debugger.assert_called_once()
     211  
     212      def test_show_stack(self):
     213          self.debugger.show_stack()
     214          self.assertEqual(self.debugger.stackviewer.gui, self.debugger)
     215  
     216      def test_show_stack_with_frame(self):
     217          test_frame = MockFrame(None, None)
     218          self.debugger.frame = test_frame
     219  
     220          # Reset the stackviewer to force it to be recreated.
     221          self.debugger.stackviewer = None
     222          self.idb.get_stack.return_value = ([], 0)
     223          self.debugger.show_stack()
     224  
     225          # Check that the newly created stackviewer has the test gui as a field.
     226          self.assertEqual(self.debugger.stackviewer.gui, self.debugger)
     227          self.idb.get_stack.assert_called_once_with(test_frame, None)
     228  
     229  
     230  class ESC[4;38;5;81mStackViewerTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     231  
     232      @classmethod
     233      def setUpClass(cls):
     234          requires('gui')
     235          cls.root = Tk()
     236          cls.root.withdraw()
     237  
     238      @classmethod
     239      def tearDownClass(cls):
     240          cls.root.destroy()
     241          del cls.root
     242  
     243      def setUp(self):
     244          self.code = compile(TEST_CODE, 'test_stackviewer.py', 'exec')
     245          self.stack = [
     246              (MockFrame(self.code, 1), 1),
     247              (MockFrame(self.code, 2), 2)
     248          ]
     249          # Create a stackviewer and load the test stack.
     250          self.sv = debugger.StackViewer(self.root, None, None)
     251          self.sv.load_stack(self.stack)
     252  
     253      def test_init(self):
     254          # Test creation of StackViewer.
     255          gui = None
     256          flist = None
     257          master_window = self.root
     258          sv = debugger.StackViewer(master_window, flist, gui)
     259          self.assertTrue(hasattr(sv, 'stack'))
     260  
     261      def test_load_stack(self):
     262          # Test the .load_stack() method against a fixed test stack.
     263          # Check the test stack is assigned and the list contains the repr of them.
     264          self.assertEqual(self.sv.stack, self.stack)
     265          self.assertTrue('?.<module>(), line 1:' in self.sv.get(0))
     266          self.assertEqual(self.sv.get(1), '?.<module>(), line 2: ')
     267  
     268      def test_show_source(self):
     269          # Test the .show_source() method against a fixed test stack.
     270          # Patch out the file list to monitor it
     271          self.sv.flist = Mock()
     272          # Patch out isfile to pretend file exists.
     273          with patch('idlelib.debugger.os.path.isfile', return_value=True) as isfile:
     274              self.sv.show_source(1)
     275              isfile.assert_called_once_with('test_stackviewer.py')
     276              self.sv.flist.open.assert_called_once_with('test_stackviewer.py')
     277  
     278  
     279  class ESC[4;38;5;81mNameSpaceTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     280  
     281      @classmethod
     282      def setUpClass(cls):
     283          requires('gui')
     284          cls.root = Tk()
     285          cls.root.withdraw()
     286  
     287      @classmethod
     288      def tearDownClass(cls):
     289          cls.root.destroy()
     290          del cls.root
     291  
     292      def test_init(self):
     293          debugger.NamespaceViewer(self.root, 'Test')
     294  
     295  
     296  if __name__ == '__main__':
     297      unittest.main(verbosity=2)