diff options
author | Louie Lu <git@louie.lu> | 2019-03-24 23:33:12 (GMT) |
---|---|---|
committer | Terry Jan Reedy <tjreedy@udel.edu> | 2019-03-24 23:33:12 (GMT) |
commit | 113d735e2091427f9623097d2a222dd99b16b568 (patch) | |
tree | 618029b078e0862ccabf403224c3380be29f2ad8 /Lib/idlelib | |
parent | 13c1f72cd1d91fdc2654f2f57356b2eacb75f164 (diff) | |
download | cpython-113d735e2091427f9623097d2a222dd99b16b568.zip cpython-113d735e2091427f9623097d2a222dd99b16b568.tar.gz cpython-113d735e2091427f9623097d2a222dd99b16b568.tar.bz2 |
bpo-30348: IDLE: Add test_autocomplete unittest (GH-2209)
Diffstat (limited to 'Lib/idlelib')
-rw-r--r-- | Lib/idlelib/autocomplete.py | 17 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_autocomplete.py | 140 |
2 files changed, 130 insertions, 27 deletions
diff --git a/Lib/idlelib/autocomplete.py b/Lib/idlelib/autocomplete.py index d57e9c9..e20b757 100644 --- a/Lib/idlelib/autocomplete.py +++ b/Lib/idlelib/autocomplete.py @@ -104,9 +104,14 @@ class AutoComplete: def open_completions(self, evalfuncs, complete, userWantsWin, mode=None): """Find the completions and create the AutoCompleteWindow. Return True if successful (no syntax error or so found). - if complete is True, then if there's nothing to complete and no + If complete is True, then if there's nothing to complete and no start of completion, won't open completions and return False. If mode is given, will open a completion list only in this mode. + + Action Function Eval Complete WantWin Mode + ^space force_open_completions True, False, True no + . or / try_open_completions False, False, False yes + tab autocomplete False, True, True no """ # Cancel another delayed call, if it exists. if self._delayed_completion_id is not None: @@ -117,11 +122,11 @@ class AutoComplete: curline = self.text.get("insert linestart", "insert") i = j = len(curline) if hp.is_in_string() and (not mode or mode==COMPLETE_FILES): - # Find the beginning of the string - # fetch_completions will look at the file system to determine whether the - # string value constitutes an actual file name - # XXX could consider raw strings here and unescape the string value if it's - # not raw. + # Find the beginning of the string. + # fetch_completions will look at the file system to determine + # whether the string value constitutes an actual file name + # XXX could consider raw strings here and unescape the string + # value if it's not raw. self._remove_autocomplete_window() mode = COMPLETE_FILES # Find last separator or string start diff --git a/Lib/idlelib/idle_test/test_autocomplete.py b/Lib/idlelib/idle_test/test_autocomplete.py index 89a9ed1..398cb35 100644 --- a/Lib/idlelib/idle_test/test_autocomplete.py +++ b/Lib/idlelib/idle_test/test_autocomplete.py @@ -1,8 +1,10 @@ -"Test autocomplete, coverage 57%." +"Test autocomplete, coverage 87%." import unittest +from unittest.mock import Mock, patch from test.support import requires from tkinter import Tk, Text +import os import __main__ import idlelib.autocomplete as ac @@ -26,12 +28,14 @@ class AutoCompleteTest(unittest.TestCase): def setUpClass(cls): requires('gui') cls.root = Tk() + cls.root.withdraw() cls.text = Text(cls.root) cls.editor = DummyEditwin(cls.root, cls.text) @classmethod def tearDownClass(cls): del cls.editor, cls.text + cls.root.update_idletasks() cls.root.destroy() del cls.root @@ -53,7 +57,7 @@ class AutoCompleteTest(unittest.TestCase): self.assertIsNone(self.autocomplete.autocompletewindow) def test_force_open_completions_event(self): - # Test that force_open_completions_event calls _open_completions + # Test that force_open_completions_event calls _open_completions. o_cs = Func() self.autocomplete.open_completions = o_cs self.autocomplete.force_open_completions_event('event') @@ -66,16 +70,16 @@ class AutoCompleteTest(unittest.TestCase): o_c_l = Func() autocomplete._open_completions_later = o_c_l - # _open_completions_later should not be called with no text in editor + # _open_completions_later should not be called with no text in editor. trycompletions('event') Equal(o_c_l.args, None) - # _open_completions_later should be called with COMPLETE_ATTRIBUTES (1) + # _open_completions_later should be called with COMPLETE_ATTRIBUTES (1). self.text.insert('1.0', 're.') trycompletions('event') Equal(o_c_l.args, (False, False, False, 1)) - # _open_completions_later should be called with COMPLETE_FILES (2) + # _open_completions_later should be called with COMPLETE_FILES (2). self.text.delete('1.0', 'end') self.text.insert('1.0', '"./Lib/') trycompletions('event') @@ -86,7 +90,7 @@ class AutoCompleteTest(unittest.TestCase): autocomplete = self.autocomplete # Test that the autocomplete event is ignored if user is pressing a - # modifier key in addition to the tab key + # modifier key in addition to the tab key. ev = Event(mc_state=True) self.assertIsNone(autocomplete.autocomplete_event(ev)) del ev.mc_state @@ -96,15 +100,15 @@ class AutoCompleteTest(unittest.TestCase): self.assertIsNone(autocomplete.autocomplete_event(ev)) self.text.delete('1.0', 'end') - # If autocomplete window is open, complete() method is called + # If autocomplete window is open, complete() method is called. self.text.insert('1.0', 're.') - # This must call autocomplete._make_autocomplete_window() + # This must call autocomplete._make_autocomplete_window(). Equal(self.autocomplete.autocomplete_event(ev), 'break') # If autocomplete window is not active or does not exist, # open_completions is called. Return depends on its return. autocomplete._remove_autocomplete_window() - o_cs = Func() # .result = None + o_cs = Func() # .result = None. autocomplete.open_completions = o_cs Equal(self.autocomplete.autocomplete_event(ev), None) Equal(o_cs.args, (False, True, True)) @@ -113,36 +117,130 @@ class AutoCompleteTest(unittest.TestCase): Equal(o_cs.args, (False, True, True)) def test_open_completions_later(self): - # Test that autocomplete._delayed_completion_id is set - pass + # Test that autocomplete._delayed_completion_id is set. + acp = self.autocomplete + acp._delayed_completion_id = None + acp._open_completions_later(False, False, False, ac.COMPLETE_ATTRIBUTES) + cb1 = acp._delayed_completion_id + self.assertTrue(cb1.startswith('after')) + + # Test that cb1 is cancelled and cb2 is new. + acp._open_completions_later(False, False, False, ac.COMPLETE_FILES) + self.assertNotIn(cb1, self.root.tk.call('after', 'info')) + cb2 = acp._delayed_completion_id + self.assertTrue(cb2.startswith('after') and cb2 != cb1) + self.text.after_cancel(cb2) def test_delayed_open_completions(self): - # Test that autocomplete._delayed_completion_id set to None and that - # open_completions only called if insertion index is the same as - # _delayed_completion_index - pass + # Test that autocomplete._delayed_completion_id set to None + # and that open_completions is not called if the index is not + # equal to _delayed_completion_index. + acp = self.autocomplete + acp.open_completions = Func() + acp._delayed_completion_id = 'after' + acp._delayed_completion_index = self.text.index('insert+1c') + acp._delayed_open_completions(1, 2, 3) + self.assertIsNone(acp._delayed_completion_id) + self.assertEqual(acp.open_completions.called, 0) + + # Test that open_completions is called if indexes match. + acp._delayed_completion_index = self.text.index('insert') + acp._delayed_open_completions(1, 2, 3, ac.COMPLETE_FILES) + self.assertEqual(acp.open_completions.args, (1, 2, 3, 2)) def test_open_completions(self): # Test completions of files and attributes as well as non-completion - # of errors - pass + # of errors. + self.text.insert('1.0', 'pr') + self.assertTrue(self.autocomplete.open_completions(False, True, True)) + self.text.delete('1.0', 'end') + + # Test files. + self.text.insert('1.0', '"t') + #self.assertTrue(self.autocomplete.open_completions(False, True, True)) + self.text.delete('1.0', 'end') + + # Test with blank will fail. + self.assertFalse(self.autocomplete.open_completions(False, True, True)) + + # Test with only string quote will fail. + self.text.insert('1.0', '"') + self.assertFalse(self.autocomplete.open_completions(False, True, True)) + self.text.delete('1.0', 'end') def test_fetch_completions(self): # Test that fetch_completions returns 2 lists: # For attribute completion, a large list containing all variables, and # a small list containing non-private variables. # For file completion, a large list containing all files in the path, - # and a small list containing files that do not start with '.' + # and a small list containing files that do not start with '.'. + autocomplete = self.autocomplete small, large = self.autocomplete.fetch_completions( '', ac.COMPLETE_ATTRIBUTES) - self.assertLess(len(small), len(large)) if __main__.__file__ != ac.__file__: self.assertNotIn('AutoComplete', small) # See issue 36405. + # Test attributes + s, b = autocomplete.fetch_completions('', ac.COMPLETE_ATTRIBUTES) + self.assertLess(len(small), len(large)) + self.assertTrue(all(filter(lambda x: x.startswith('_'), s))) + self.assertTrue(any(filter(lambda x: x.startswith('_'), b))) + + # Test smalll should respect to __all__. + with patch.dict('__main__.__dict__', {'__all__': ['a', 'b']}): + s, b = autocomplete.fetch_completions('', ac.COMPLETE_ATTRIBUTES) + self.assertEqual(s, ['a', 'b']) + self.assertIn('__name__', b) # From __main__.__dict__ + self.assertIn('sum', b) # From __main__.__builtins__.__dict__ + + # Test attributes with name entity. + mock = Mock() + mock._private = Mock() + with patch.dict('__main__.__dict__', {'foo': mock}): + s, b = autocomplete.fetch_completions('foo', ac.COMPLETE_ATTRIBUTES) + self.assertNotIn('_private', s) + self.assertIn('_private', b) + self.assertEqual(s, [i for i in sorted(dir(mock)) if i[:1] != '_']) + self.assertEqual(b, sorted(dir(mock))) + + # Test files + def _listdir(path): + # This will be patch and used in fetch_completions. + if path == '.': + return ['foo', 'bar', '.hidden'] + return ['monty', 'python', '.hidden'] + + with patch.object(os, 'listdir', _listdir): + s, b = autocomplete.fetch_completions('', ac.COMPLETE_FILES) + self.assertEqual(s, ['bar', 'foo']) + self.assertEqual(b, ['.hidden', 'bar', 'foo']) + + s, b = autocomplete.fetch_completions('~', ac.COMPLETE_FILES) + self.assertEqual(s, ['monty', 'python']) + self.assertEqual(b, ['.hidden', 'monty', 'python']) + def test_get_entity(self): # Test that a name is in the namespace of sys.modules and - # __main__.__dict__ - self.assertEqual(self.autocomplete.get_entity('int'), int) + # __main__.__dict__. + autocomplete = self.autocomplete + Equal = self.assertEqual + + Equal(self.autocomplete.get_entity('int'), int) + + # Test name from sys.modules. + mock = Mock() + with patch.dict('sys.modules', {'tempfile': mock}): + Equal(autocomplete.get_entity('tempfile'), mock) + + # Test name from __main__.__dict__. + di = {'foo': 10, 'bar': 20} + with patch.dict('__main__.__dict__', {'d': di}): + Equal(autocomplete.get_entity('d'), di) + + # Test name not in namespace. + with patch.dict('__main__.__dict__', {}): + with self.assertRaises(NameError): + autocomplete.get_entity('not_exist') if __name__ == '__main__': |