summaryrefslogtreecommitdiff
path: root/Lib/idlelib/idle_test
diff options
context:
space:
mode:
authorCheryl Sabella <cheryl.sabella@gmail.com>2019-02-19 00:11:18 -0500
committerTerry Jan Reedy <tjreedy@udel.edu>2019-02-19 00:11:18 -0500
commitee0f927bd8dba805a04963dbec1ad49fe830b842 (patch)
treebb1cdae28dcceb1a6bf322ee8446ae1d73412c01 /Lib/idlelib/idle_test
parent4371c0a9c0848f7a0947d43f26f234842b41efdf (diff)
downloadcpython-git-ee0f927bd8dba805a04963dbec1ad49fe830b842.tar.gz
bpo-35689: IDLE: Add docstrings and unittests for colorizer.py (GH-11472)
Diffstat (limited to 'Lib/idlelib/idle_test')
-rw-r--r--Lib/idlelib/idle_test/test_colorizer.py358
1 files changed, 348 insertions, 10 deletions
diff --git a/Lib/idlelib/idle_test/test_colorizer.py b/Lib/idlelib/idle_test/test_colorizer.py
index 1e74bed1f0..4ade5a149b 100644
--- a/Lib/idlelib/idle_test/test_colorizer.py
+++ b/Lib/idlelib/idle_test/test_colorizer.py
@@ -1,36 +1,103 @@
-"Test colorizer, coverage 25%."
+"Test colorizer, coverage 93%."
from idlelib import colorizer
from test.support import requires
-from tkinter import Tk, Text
import unittest
+from unittest import mock
+
+from functools import partial
+from tkinter import Tk, Text
+from idlelib import config
+from idlelib.percolator import Percolator
+
+
+usercfg = colorizer.idleConf.userCfg
+testcfg = {
+ 'main': config.IdleUserConfParser(''),
+ 'highlight': config.IdleUserConfParser(''),
+ 'keys': config.IdleUserConfParser(''),
+ 'extensions': config.IdleUserConfParser(''),
+}
+
+source = (
+ "if True: int ('1') # keyword, builtin, string, comment\n"
+ "elif False: print(0) # 'string' in comment\n"
+ "else: float(None) # if in comment\n"
+ "if iF + If + IF: 'keyword matching must respect case'\n"
+ "if'': x or'' # valid string-keyword no-space combinations\n"
+ "async def f(): await g()\n"
+ "'x', '''x''', \"x\", \"\"\"x\"\"\"\n"
+ )
+
+
+def setUpModule():
+ colorizer.idleConf.userCfg = testcfg
+
+
+def tearDownModule():
+ colorizer.idleConf.userCfg = usercfg
class FunctionTest(unittest.TestCase):
def test_any(self):
- self.assertTrue(colorizer.any('test', ('a', 'b')))
+ self.assertEqual(colorizer.any('test', ('a', 'b', 'cd')),
+ '(?P<test>a|b|cd)')
def test_make_pat(self):
+ # Tested in more detail by testing prog.
self.assertTrue(colorizer.make_pat())
+ def test_prog(self):
+ prog = colorizer.prog
+ eq = self.assertEqual
+ line = 'def f():\n print("hello")\n'
+ m = prog.search(line)
+ eq(m.groupdict()['KEYWORD'], 'def')
+ m = prog.search(line, m.end())
+ eq(m.groupdict()['SYNC'], '\n')
+ m = prog.search(line, m.end())
+ eq(m.groupdict()['BUILTIN'], 'print')
+ m = prog.search(line, m.end())
+ eq(m.groupdict()['STRING'], '"hello"')
+ m = prog.search(line, m.end())
+ eq(m.groupdict()['SYNC'], '\n')
+
+ def test_idprog(self):
+ idprog = colorizer.idprog
+ m = idprog.match('nospace')
+ self.assertIsNone(m)
+ m = idprog.match(' space')
+ self.assertEqual(m.group(0), ' space')
+
class ColorConfigTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
- cls.root = Tk()
- cls.text = Text(cls.root)
+ root = cls.root = Tk()
+ root.withdraw()
+ cls.text = Text(root)
@classmethod
def tearDownClass(cls):
del cls.text
+ cls.root.update_idletasks()
cls.root.destroy()
del cls.root
- def test_colorizer(self):
- colorizer.color_config(self.text)
+ def test_color_config(self):
+ text = self.text
+ eq = self.assertEqual
+ colorizer.color_config(text)
+ # Uses IDLE Classic theme as default.
+ eq(text['background'], '#ffffff')
+ eq(text['foreground'], '#000000')
+ eq(text['selectbackground'], 'gray')
+ eq(text['selectforeground'], '#000000')
+ eq(text['insertbackground'], 'black')
+ eq(text['inactiveselectbackground'], 'gray')
class ColorDelegatorTest(unittest.TestCase):
@@ -38,15 +105,286 @@ class ColorDelegatorTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
- cls.root = Tk()
+ root = cls.root = Tk()
+ root.withdraw()
+ text = cls.text = Text(root)
+ cls.percolator = Percolator(text)
+ # Delegator stack = [Delagator(text)]
@classmethod
def tearDownClass(cls):
+ cls.percolator.redir.close()
+ del cls.percolator, cls.text
+ cls.root.update_idletasks()
cls.root.destroy()
del cls.root
- def test_colorizer(self):
- colorizer.ColorDelegator()
+ def setUp(self):
+ self.color = colorizer.ColorDelegator()
+ self.percolator.insertfilter(self.color)
+ # Calls color.setdelagate(Delagator(text)).
+
+ def tearDown(self):
+ self.color.close()
+ self.percolator.removefilter(self.color)
+ self.text.delete('1.0', 'end')
+ self.color.resetcache()
+ del self.color
+
+ def test_init(self):
+ color = self.color
+ self.assertIsInstance(color, colorizer.ColorDelegator)
+ # The following are class variables.
+ self.assertTrue(color.allow_colorizing)
+ self.assertFalse(color.colorizing)
+
+ def test_setdelegate(self):
+ # Called in setUp.
+ color = self.color
+ self.assertIsInstance(color.delegate, colorizer.Delegator)
+ # It is too late to mock notify_range, so test side effect.
+ self.assertEqual(self.root.tk.call(
+ 'after', 'info', color.after_id)[1], 'timer')
+
+ def test_LoadTagDefs(self):
+ highlight = partial(config.idleConf.GetHighlight, theme='IDLE Classic')
+ for tag, colors in self.color.tagdefs.items():
+ with self.subTest(tag=tag):
+ self.assertIn('background', colors)
+ self.assertIn('foreground', colors)
+ if tag not in ('SYNC', 'TODO'):
+ self.assertEqual(colors, highlight(element=tag.lower()))
+
+ def test_config_colors(self):
+ text = self.text
+ highlight = partial(config.idleConf.GetHighlight, theme='IDLE Classic')
+ for tag in self.color.tagdefs:
+ for plane in ('background', 'foreground'):
+ with self.subTest(tag=tag, plane=plane):
+ if tag in ('SYNC', 'TODO'):
+ self.assertEqual(text.tag_cget(tag, plane), '')
+ else:
+ self.assertEqual(text.tag_cget(tag, plane),
+ highlight(element=tag.lower())[plane])
+ # 'sel' is marked as the highest priority.
+ self.assertEqual(text.tag_names()[-1], 'sel')
+
+ @mock.patch.object(colorizer.ColorDelegator, 'notify_range')
+ def test_insert(self, mock_notify):
+ text = self.text
+ # Initial text.
+ text.insert('insert', 'foo')
+ self.assertEqual(text.get('1.0', 'end'), 'foo\n')
+ mock_notify.assert_called_with('1.0', '1.0+3c')
+ # Additional text.
+ text.insert('insert', 'barbaz')
+ self.assertEqual(text.get('1.0', 'end'), 'foobarbaz\n')
+ mock_notify.assert_called_with('1.3', '1.3+6c')
+
+ @mock.patch.object(colorizer.ColorDelegator, 'notify_range')
+ def test_delete(self, mock_notify):
+ text = self.text
+ # Initialize text.
+ text.insert('insert', 'abcdefghi')
+ self.assertEqual(text.get('1.0', 'end'), 'abcdefghi\n')
+ # Delete single character.
+ text.delete('1.7')
+ self.assertEqual(text.get('1.0', 'end'), 'abcdefgi\n')
+ mock_notify.assert_called_with('1.7')
+ # Delete multiple characters.
+ text.delete('1.3', '1.6')
+ self.assertEqual(text.get('1.0', 'end'), 'abcgi\n')
+ mock_notify.assert_called_with('1.3')
+
+ def test_notify_range(self):
+ text = self.text
+ color = self.color
+ eq = self.assertEqual
+
+ # Colorizing already scheduled.
+ save_id = color.after_id
+ eq(self.root.tk.call('after', 'info', save_id)[1], 'timer')
+ self.assertFalse(color.colorizing)
+ self.assertFalse(color.stop_colorizing)
+ self.assertTrue(color.allow_colorizing)
+
+ # Coloring scheduled and colorizing in progress.
+ color.colorizing = True
+ color.notify_range('1.0', 'end')
+ self.assertFalse(color.stop_colorizing)
+ eq(color.after_id, save_id)
+
+ # No colorizing scheduled and colorizing in progress.
+ text.after_cancel(save_id)
+ color.after_id = None
+ color.notify_range('1.0', '1.0+3c')
+ self.assertTrue(color.stop_colorizing)
+ self.assertIsNotNone(color.after_id)
+ eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
+ # New event scheduled.
+ self.assertNotEqual(color.after_id, save_id)
+
+ # No colorizing scheduled and colorizing off.
+ text.after_cancel(color.after_id)
+ color.after_id = None
+ color.allow_colorizing = False
+ color.notify_range('1.4', '1.4+10c')
+ # Nothing scheduled when colorizing is off.
+ self.assertIsNone(color.after_id)
+
+ def test_toggle_colorize_event(self):
+ color = self.color
+ eq = self.assertEqual
+
+ # Starts with colorizing allowed and scheduled.
+ self.assertFalse(color.colorizing)
+ self.assertFalse(color.stop_colorizing)
+ self.assertTrue(color.allow_colorizing)
+ eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
+
+ # Toggle colorizing off.
+ color.toggle_colorize_event()
+ self.assertIsNone(color.after_id)
+ self.assertFalse(color.colorizing)
+ self.assertFalse(color.stop_colorizing)
+ self.assertFalse(color.allow_colorizing)
+
+ # Toggle on while colorizing in progress (doesn't add timer).
+ color.colorizing = True
+ color.toggle_colorize_event()
+ self.assertIsNone(color.after_id)
+ self.assertTrue(color.colorizing)
+ self.assertFalse(color.stop_colorizing)
+ self.assertTrue(color.allow_colorizing)
+
+ # Toggle off while colorizing in progress.
+ color.toggle_colorize_event()
+ self.assertIsNone(color.after_id)
+ self.assertTrue(color.colorizing)
+ self.assertTrue(color.stop_colorizing)
+ self.assertFalse(color.allow_colorizing)
+
+ # Toggle on while colorizing not in progress.
+ color.colorizing = False
+ color.toggle_colorize_event()
+ eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
+ self.assertFalse(color.colorizing)
+ self.assertTrue(color.stop_colorizing)
+ self.assertTrue(color.allow_colorizing)
+
+ @mock.patch.object(colorizer.ColorDelegator, 'recolorize_main')
+ def test_recolorize(self, mock_recmain):
+ text = self.text
+ color = self.color
+ eq = self.assertEqual
+ # Call recolorize manually and not scheduled.
+ text.after_cancel(color.after_id)
+
+ # No delegate.
+ save_delegate = color.delegate
+ color.delegate = None
+ color.recolorize()
+ mock_recmain.assert_not_called()
+ color.delegate = save_delegate
+
+ # Toggle off colorizing.
+ color.allow_colorizing = False
+ color.recolorize()
+ mock_recmain.assert_not_called()
+ color.allow_colorizing = True
+
+ # Colorizing in progress.
+ color.colorizing = True
+ color.recolorize()
+ mock_recmain.assert_not_called()
+ color.colorizing = False
+
+ # Colorizing is done, but not completed, so rescheduled.
+ color.recolorize()
+ self.assertFalse(color.stop_colorizing)
+ self.assertFalse(color.colorizing)
+ mock_recmain.assert_called()
+ eq(mock_recmain.call_count, 1)
+ # Rescheduled when TODO tag still exists.
+ eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
+
+ # No changes to text, so no scheduling added.
+ text.tag_remove('TODO', '1.0', 'end')
+ color.recolorize()
+ self.assertFalse(color.stop_colorizing)
+ self.assertFalse(color.colorizing)
+ mock_recmain.assert_called()
+ eq(mock_recmain.call_count, 2)
+ self.assertIsNone(color.after_id)
+
+ @mock.patch.object(colorizer.ColorDelegator, 'notify_range')
+ def test_recolorize_main(self, mock_notify):
+ text = self.text
+ color = self.color
+ eq = self.assertEqual
+
+ text.insert('insert', source)
+ expected = (('1.0', ('KEYWORD',)), ('1.2', ()), ('1.3', ('KEYWORD',)),
+ ('1.7', ()), ('1.9', ('BUILTIN',)), ('1.14', ('STRING',)),
+ ('1.19', ('COMMENT',)),
+ ('2.1', ('KEYWORD',)), ('2.18', ()), ('2.25', ('COMMENT',)),
+ ('3.6', ('BUILTIN',)), ('3.12', ('KEYWORD',)), ('3.21', ('COMMENT',)),
+ ('4.0', ('KEYWORD',)), ('4.3', ()), ('4.6', ()),
+ ('5.2', ('STRING',)), ('5.8', ('KEYWORD',)), ('5.10', ('STRING',)),
+ ('6.0', ('KEYWORD',)), ('6.10', ('DEFINITION',)), ('6.11', ()),
+ ('7.0', ('STRING',)), ('7.4', ()), ('7.5', ('STRING',)),
+ ('7.12', ()), ('7.14', ('STRING',)),
+ # SYNC at the end of every line.
+ ('1.55', ('SYNC',)), ('2.50', ('SYNC',)), ('3.34', ('SYNC',)),
+ )
+
+ # Nothing marked to do therefore no tags in text.
+ text.tag_remove('TODO', '1.0', 'end')
+ color.recolorize_main()
+ for tag in text.tag_names():
+ with self.subTest(tag=tag):
+ eq(text.tag_ranges(tag), ())
+
+ # Source marked for processing.
+ text.tag_add('TODO', '1.0', 'end')
+ # Check some indexes.
+ color.recolorize_main()
+ for index, expected_tags in expected:
+ with self.subTest(index=index):
+ eq(text.tag_names(index), expected_tags)
+
+ # Check for some tags for ranges.
+ eq(text.tag_nextrange('TODO', '1.0'), ())
+ eq(text.tag_nextrange('KEYWORD', '1.0'), ('1.0', '1.2'))
+ eq(text.tag_nextrange('COMMENT', '2.0'), ('2.22', '2.43'))
+ eq(text.tag_nextrange('SYNC', '2.0'), ('2.43', '3.0'))
+ eq(text.tag_nextrange('STRING', '2.0'), ('4.17', '4.53'))
+ eq(text.tag_nextrange('STRING', '7.0'), ('7.0', '7.3'))
+ eq(text.tag_nextrange('STRING', '7.3'), ('7.5', '7.12'))
+ eq(text.tag_nextrange('STRING', '7.12'), ('7.14', '7.17'))
+ eq(text.tag_nextrange('STRING', '7.17'), ('7.19', '7.26'))
+ eq(text.tag_nextrange('SYNC', '7.0'), ('7.26', '9.0'))
+
+ @mock.patch.object(colorizer.ColorDelegator, 'recolorize')
+ @mock.patch.object(colorizer.ColorDelegator, 'notify_range')
+ def test_removecolors(self, mock_notify, mock_recolorize):
+ text = self.text
+ color = self.color
+ text.insert('insert', source)
+
+ color.recolorize_main()
+ # recolorize_main doesn't add these tags.
+ text.tag_add("ERROR", "1.0")
+ text.tag_add("TODO", "1.0")
+ text.tag_add("hit", "1.0")
+ for tag in color.tagdefs:
+ with self.subTest(tag=tag):
+ self.assertNotEqual(text.tag_ranges(tag), ())
+
+ color.removecolors()
+ for tag in color.tagdefs:
+ with self.subTest(tag=tag):
+ self.assertEqual(text.tag_ranges(tag), ())
if __name__ == '__main__':