| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- "Test codecontext, coverage 100%"
- from idlelib import codecontext
- import unittest
- import unittest.mock
- from test.support import requires
- from tkinter import NSEW, Tk, Frame, Text, TclError
- from unittest import mock
- import re
- from idlelib import config
- usercfg = codecontext.idleConf.userCfg
- testcfg = {
- 'main': config.IdleUserConfParser(''),
- 'highlight': config.IdleUserConfParser(''),
- 'keys': config.IdleUserConfParser(''),
- 'extensions': config.IdleUserConfParser(''),
- }
- code_sample = """\
- class C1():
- # Class comment.
- def __init__(self, a, b):
- self.a = a
- self.b = b
- def compare(self):
- if a > b:
- return a
- elif a < b:
- return b
- else:
- return None
- """
- class DummyEditwin:
- def __init__(self, root, frame, text):
- self.root = root
- self.top = root
- self.text_frame = frame
- self.text = text
- self.label = ''
- def getlineno(self, index):
- return int(float(self.text.index(index)))
- def update_menu_label(self, **kwargs):
- self.label = kwargs['label']
- class CodeContextTest(unittest.TestCase):
- @classmethod
- def setUpClass(cls):
- requires('gui')
- root = cls.root = Tk()
- root.withdraw()
- frame = cls.frame = Frame(root)
- text = cls.text = Text(frame)
- text.insert('1.0', code_sample)
- # Need to pack for creation of code context text widget.
- frame.pack(side='left', fill='both', expand=1)
- text.grid(row=1, column=1, sticky=NSEW)
- cls.editor = DummyEditwin(root, frame, text)
- codecontext.idleConf.userCfg = testcfg
- @classmethod
- def tearDownClass(cls):
- codecontext.idleConf.userCfg = usercfg
- cls.editor.text.delete('1.0', 'end')
- del cls.editor, cls.frame, cls.text
- cls.root.update_idletasks()
- cls.root.destroy()
- del cls.root
- def setUp(self):
- self.text.yview(0)
- self.text['font'] = 'TkFixedFont'
- self.cc = codecontext.CodeContext(self.editor)
- self.highlight_cfg = {"background": '#abcdef',
- "foreground": '#123456'}
- orig_idleConf_GetHighlight = codecontext.idleConf.GetHighlight
- def mock_idleconf_GetHighlight(theme, element):
- if element == 'context':
- return self.highlight_cfg
- return orig_idleConf_GetHighlight(theme, element)
- GetHighlight_patcher = unittest.mock.patch.object(
- codecontext.idleConf, 'GetHighlight', mock_idleconf_GetHighlight)
- GetHighlight_patcher.start()
- self.addCleanup(GetHighlight_patcher.stop)
- self.font_override = 'TkFixedFont'
- def mock_idleconf_GetFont(root, configType, section):
- return self.font_override
- GetFont_patcher = unittest.mock.patch.object(
- codecontext.idleConf, 'GetFont', mock_idleconf_GetFont)
- GetFont_patcher.start()
- self.addCleanup(GetFont_patcher.stop)
- def tearDown(self):
- if self.cc.context:
- self.cc.context.destroy()
- # Explicitly call __del__ to remove scheduled scripts.
- self.cc.__del__()
- del self.cc.context, self.cc
- def test_init(self):
- eq = self.assertEqual
- ed = self.editor
- cc = self.cc
- eq(cc.editwin, ed)
- eq(cc.text, ed.text)
- eq(cc.text['font'], ed.text['font'])
- self.assertIsNone(cc.context)
- eq(cc.info, [(0, -1, '', False)])
- eq(cc.topvisible, 1)
- self.assertIsNone(self.cc.t1)
- def test_del(self):
- self.cc.__del__()
- def test_del_with_timer(self):
- timer = self.cc.t1 = self.text.after(10000, lambda: None)
- self.cc.__del__()
- with self.assertRaises(TclError) as cm:
- self.root.tk.call('after', 'info', timer)
- self.assertIn("doesn't exist", str(cm.exception))
- def test_reload(self):
- codecontext.CodeContext.reload()
- self.assertEqual(self.cc.context_depth, 15)
- def test_toggle_code_context_event(self):
- eq = self.assertEqual
- cc = self.cc
- toggle = cc.toggle_code_context_event
- # Make sure code context is off.
- if cc.context:
- toggle()
- # Toggle on.
- toggle()
- self.assertIsNotNone(cc.context)
- eq(cc.context['font'], self.text['font'])
- eq(cc.context['fg'], self.highlight_cfg['foreground'])
- eq(cc.context['bg'], self.highlight_cfg['background'])
- eq(cc.context.get('1.0', 'end-1c'), '')
- eq(cc.editwin.label, 'Hide Code Context')
- eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer')
- # Toggle off.
- toggle()
- self.assertIsNone(cc.context)
- eq(cc.editwin.label, 'Show Code Context')
- self.assertIsNone(self.cc.t1)
- # Scroll down and toggle back on.
- line11_context = '\n'.join(x[2] for x in cc.get_context(11)[0])
- cc.text.yview(11)
- toggle()
- eq(cc.context.get('1.0', 'end-1c'), line11_context)
- # Toggle off and on again.
- toggle()
- toggle()
- eq(cc.context.get('1.0', 'end-1c'), line11_context)
- def test_get_context(self):
- eq = self.assertEqual
- gc = self.cc.get_context
- # stopline must be greater than 0.
- with self.assertRaises(AssertionError):
- gc(1, stopline=0)
- eq(gc(3), ([(2, 0, 'class C1():', 'class')], 0))
- # Don't return comment.
- eq(gc(4), ([(2, 0, 'class C1():', 'class')], 0))
- # Two indentation levels and no comment.
- eq(gc(5), ([(2, 0, 'class C1():', 'class'),
- (4, 4, ' def __init__(self, a, b):', 'def')], 0))
- # Only one 'def' is returned, not both at the same indent level.
- eq(gc(10), ([(2, 0, 'class C1():', 'class'),
- (7, 4, ' def compare(self):', 'def'),
- (8, 8, ' if a > b:', 'if')], 0))
- # With 'elif', also show the 'if' even though it's at the same level.
- eq(gc(11), ([(2, 0, 'class C1():', 'class'),
- (7, 4, ' def compare(self):', 'def'),
- (8, 8, ' if a > b:', 'if'),
- (10, 8, ' elif a < b:', 'elif')], 0))
- # Set stop_line to not go back to first line in source code.
- # Return includes stop_line.
- eq(gc(11, stopline=2), ([(2, 0, 'class C1():', 'class'),
- (7, 4, ' def compare(self):', 'def'),
- (8, 8, ' if a > b:', 'if'),
- (10, 8, ' elif a < b:', 'elif')], 0))
- eq(gc(11, stopline=3), ([(7, 4, ' def compare(self):', 'def'),
- (8, 8, ' if a > b:', 'if'),
- (10, 8, ' elif a < b:', 'elif')], 4))
- eq(gc(11, stopline=8), ([(8, 8, ' if a > b:', 'if'),
- (10, 8, ' elif a < b:', 'elif')], 8))
- # Set stop_indent to test indent level to stop at.
- eq(gc(11, stopindent=4), ([(7, 4, ' def compare(self):', 'def'),
- (8, 8, ' if a > b:', 'if'),
- (10, 8, ' elif a < b:', 'elif')], 4))
- # Check that the 'if' is included.
- eq(gc(11, stopindent=8), ([(8, 8, ' if a > b:', 'if'),
- (10, 8, ' elif a < b:', 'elif')], 8))
- def test_update_code_context(self):
- eq = self.assertEqual
- cc = self.cc
- # Ensure code context is active.
- if not cc.context:
- cc.toggle_code_context_event()
- # Invoke update_code_context without scrolling - nothing happens.
- self.assertIsNone(cc.update_code_context())
- eq(cc.info, [(0, -1, '', False)])
- eq(cc.topvisible, 1)
- # Scroll down to line 1.
- cc.text.yview(1)
- cc.update_code_context()
- eq(cc.info, [(0, -1, '', False)])
- eq(cc.topvisible, 2)
- eq(cc.context.get('1.0', 'end-1c'), '')
- # Scroll down to line 2.
- cc.text.yview(2)
- cc.update_code_context()
- eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
- eq(cc.topvisible, 3)
- eq(cc.context.get('1.0', 'end-1c'), 'class C1():')
- # Scroll down to line 3. Since it's a comment, nothing changes.
- cc.text.yview(3)
- cc.update_code_context()
- eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
- eq(cc.topvisible, 4)
- eq(cc.context.get('1.0', 'end-1c'), 'class C1():')
- # Scroll down to line 4.
- cc.text.yview(4)
- cc.update_code_context()
- eq(cc.info, [(0, -1, '', False),
- (2, 0, 'class C1():', 'class'),
- (4, 4, ' def __init__(self, a, b):', 'def')])
- eq(cc.topvisible, 5)
- eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
- ' def __init__(self, a, b):')
- # Scroll down to line 11. Last 'def' is removed.
- cc.text.yview(11)
- cc.update_code_context()
- eq(cc.info, [(0, -1, '', False),
- (2, 0, 'class C1():', 'class'),
- (7, 4, ' def compare(self):', 'def'),
- (8, 8, ' if a > b:', 'if'),
- (10, 8, ' elif a < b:', 'elif')])
- eq(cc.topvisible, 12)
- eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
- ' def compare(self):\n'
- ' if a > b:\n'
- ' elif a < b:')
- # No scroll. No update, even though context_depth changed.
- cc.update_code_context()
- cc.context_depth = 1
- eq(cc.info, [(0, -1, '', False),
- (2, 0, 'class C1():', 'class'),
- (7, 4, ' def compare(self):', 'def'),
- (8, 8, ' if a > b:', 'if'),
- (10, 8, ' elif a < b:', 'elif')])
- eq(cc.topvisible, 12)
- eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
- ' def compare(self):\n'
- ' if a > b:\n'
- ' elif a < b:')
- # Scroll up.
- cc.text.yview(5)
- cc.update_code_context()
- eq(cc.info, [(0, -1, '', False),
- (2, 0, 'class C1():', 'class'),
- (4, 4, ' def __init__(self, a, b):', 'def')])
- eq(cc.topvisible, 6)
- # context_depth is 1.
- eq(cc.context.get('1.0', 'end-1c'), ' def __init__(self, a, b):')
- def test_jumptoline(self):
- eq = self.assertEqual
- cc = self.cc
- jump = cc.jumptoline
- if not cc.context:
- cc.toggle_code_context_event()
- # Empty context.
- cc.text.yview('2.0')
- cc.update_code_context()
- eq(cc.topvisible, 2)
- cc.context.mark_set('insert', '1.5')
- jump()
- eq(cc.topvisible, 1)
- # 4 lines of context showing.
- cc.text.yview('12.0')
- cc.update_code_context()
- eq(cc.topvisible, 12)
- cc.context.mark_set('insert', '3.0')
- jump()
- eq(cc.topvisible, 8)
- # More context lines than limit.
- cc.context_depth = 2
- cc.text.yview('12.0')
- cc.update_code_context()
- eq(cc.topvisible, 12)
- cc.context.mark_set('insert', '1.0')
- jump()
- eq(cc.topvisible, 8)
- # Context selection stops jump.
- cc.text.yview('5.0')
- cc.update_code_context()
- cc.context.tag_add('sel', '1.0', '2.0')
- cc.context.mark_set('insert', '1.0')
- jump() # Without selection, to line 2.
- eq(cc.topvisible, 5)
- @mock.patch.object(codecontext.CodeContext, 'update_code_context')
- def test_timer_event(self, mock_update):
- # Ensure code context is not active.
- if self.cc.context:
- self.cc.toggle_code_context_event()
- self.cc.timer_event()
- mock_update.assert_not_called()
- # Activate code context.
- self.cc.toggle_code_context_event()
- self.cc.timer_event()
- mock_update.assert_called()
- def test_font(self):
- eq = self.assertEqual
- cc = self.cc
- orig_font = cc.text['font']
- test_font = 'TkTextFont'
- self.assertNotEqual(orig_font, test_font)
- # Ensure code context is not active.
- if cc.context is not None:
- cc.toggle_code_context_event()
- self.font_override = test_font
- # Nothing breaks or changes with inactive code context.
- cc.update_font()
- # Activate code context, previous font change is immediately effective.
- cc.toggle_code_context_event()
- eq(cc.context['font'], test_font)
- # Call the font update, change is picked up.
- self.font_override = orig_font
- cc.update_font()
- eq(cc.context['font'], orig_font)
- def test_highlight_colors(self):
- eq = self.assertEqual
- cc = self.cc
- orig_colors = dict(self.highlight_cfg)
- test_colors = {'background': '#222222', 'foreground': '#ffff00'}
- def assert_colors_are_equal(colors):
- eq(cc.context['background'], colors['background'])
- eq(cc.context['foreground'], colors['foreground'])
- # Ensure code context is not active.
- if cc.context:
- cc.toggle_code_context_event()
- self.highlight_cfg = test_colors
- # Nothing breaks with inactive code context.
- cc.update_highlight_colors()
- # Activate code context, previous colors change is immediately effective.
- cc.toggle_code_context_event()
- assert_colors_are_equal(test_colors)
- # Call colors update with no change to the configured colors.
- cc.update_highlight_colors()
- assert_colors_are_equal(test_colors)
- # Call the colors update with code context active, change is picked up.
- self.highlight_cfg = orig_colors
- cc.update_highlight_colors()
- assert_colors_are_equal(orig_colors)
- class HelperFunctionText(unittest.TestCase):
- def test_get_spaces_firstword(self):
- get = codecontext.get_spaces_firstword
- test_lines = (
- (' first word', (' ', 'first')),
- ('\tfirst word', ('\t', 'first')),
- (' \u19D4\u19D2: ', (' ', '\u19D4\u19D2')),
- ('no spaces', ('', 'no')),
- ('', ('', '')),
- ('# TEST COMMENT', ('', '')),
- (' (continuation)', (' ', ''))
- )
- for line, expected_output in test_lines:
- self.assertEqual(get(line), expected_output)
- # Send the pattern in the call.
- self.assertEqual(get(' (continuation)',
- c=re.compile(r'^(\s*)([^\s]*)')),
- (' ', '(continuation)'))
- def test_get_line_info(self):
- eq = self.assertEqual
- gli = codecontext.get_line_info
- lines = code_sample.splitlines()
- # Line 1 is not a BLOCKOPENER.
- eq(gli(lines[0]), (codecontext.INFINITY, '', False))
- # Line 2 is a BLOCKOPENER without an indent.
- eq(gli(lines[1]), (0, 'class C1():', 'class'))
- # Line 3 is not a BLOCKOPENER and does not return the indent level.
- eq(gli(lines[2]), (codecontext.INFINITY, ' # Class comment.', False))
- # Line 4 is a BLOCKOPENER and is indented.
- eq(gli(lines[3]), (4, ' def __init__(self, a, b):', 'def'))
- # Line 8 is a different BLOCKOPENER and is indented.
- eq(gli(lines[7]), (8, ' if a > b:', 'if'))
- # Test tab.
- eq(gli('\tif a == b:'), (1, '\tif a == b:', 'if'))
- if __name__ == '__main__':
- unittest.main(verbosity=2)
|