mirror of
https://github.com/python/cpython.git
synced 2025-07-09 20:35:26 +00:00

IDLE: Fix test_debugger bug and buildbot failures (GH-112258)
Missing "requires('gui')" causes Tk() to fail when no gui.
This caused CI Hypothesis test to fail, but I did not understand
the its error message. Then buildbots failed.
IdbTest failed on draft Bdb replacement because so different.
Simplified version works on old and new.
(cherry picked from commit 14fd86a59d
)
Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
297 lines
9.5 KiB
Python
297 lines
9.5 KiB
Python
"""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)
|