10000 bpo-30348: IDLE: Add test_autocomplete unittest (GH-2209) · python/cpython@0e05d8a · GitHub
[go: up one dir, main page]

Skip to content

Commit 0e05d8a

Browse files
bpo-30348: IDLE: Add test_autocomplete unittest (GH-2209)
(cherry picked from commit 113d735) Co-authored-by: Louie Lu <git@louie.lu>
1 parent fc00102 commit 0e05d8a

File tree

3 files changed

+131
-27
lines changed

3 files changed

+131
-27
lines changed

Lib/idlelib/autocomplete.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,14 @@ def _delayed_open_completions(self, *args):
104104
def open_completions(self, evalfuncs, complete, userWantsWin, mode=None):
105105
"""Find the completions and create the AutoCompleteWindow.
106106
Return True if successful (no syntax error or so found).
107-
if complete is True, then if there's nothing to complete and no
107+
If complete is True, then if there's nothing to complete and no
108108
start of completion, won't open completions and return False.
109109
If mode is given, will open a completion list only in this mode.
110+
111+
Action Function Eval Complete WantWin Mode
112+
^space force_open_completions True, False, True no
113+
. or / try_open_completions False, False, False yes
114+
tab autocomplete False, True, True no
110115
"""
111116
# Cancel another delayed call, if it exists.
112117
if self._delayed_completion_id is not None:
@@ -117,11 +122,11 @@ def open_completions(self, evalfuncs, complete, userWantsWin, mode=None):
117122
curline = self.text.get("insert linestart", "insert")
118123
i = j = len(curline)
119124
if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):
120-
# Find the beginning of the string
121-
# fetch_completions will look at the file system to determine whether the
122-
# string value constitutes an actual file name
123-
# XXX could consider raw strings here and unescape the string value if it's
124-
# not raw.
125+
# Find the beginning of the string.
126+
# fetch_completions will look at the file system to determine
127+
# whether the string value constitutes an actual file name
128+
# XXX could consider raw strings here and unescape the string
129+
# value if it's not raw.
125130
self._remove_autocomplete_window()
126131
mode = COMPLETE_FILES
127132
# Find last separator or string start

Lib/idlelib/idle_test/test_autocomplete.py

Lines changed: 119 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
"Test autocomplete, coverage 57%."
1+
"Test autocomplete, coverage 87%."
22

33
import unittest
4+
from unittest.mock import Mock, patch
45
from test.support import requires
56
from tkinter import Tk, Text
7+
import os
68
import __main__
79

810
import idlelib.autocomplete as ac
@@ -26,12 +28,14 @@ class AutoCompleteTest(unittest.TestCase):
2628
def setUpClass(cls):
2729
requires('gui')
2830
cls.root = Tk()
31+
cls.root.withdraw()
2932
cls.text = Text(cls.root)
3033
cls.editor = DummyEditwin(cls.root, cls.text)
3134

3235
@classmethod
3336
def tearDownClass(cls):
3437
del cls.editor, cls.text
38+
cls.root.update_idletasks()
3539
cls.root.destroy()
3640
del cls.root
3741

@@ -53,7 +57,7 @@ def test_remove_autocomplete_window(self):
5357
self.assertIsNone(self.autocomplete.autocompletewindow)
5458

5559
def test_force_open_completions_event(self):
56-
# Test that force_open_completions_event calls _open_completions
60+
# Test that force_open_completions_event calls _open_completions.
5761
o_cs = Func()
5862
self.autocomplete.open_completions = o_cs
5963
self.autocomplete.force_open_completions_event('event')
@@ -66,16 +70,16 @@ def test_try_open_completions_event(self):
6670
o_c_l = Func()
6771
autocomplete._open_completions_later = o_c_l
6872

69-
# _open_completions_later should not be called with no text in editor
73+
# _open_completions_later should not be called with no text in editor.
7074
trycompletions('event')
7175
Equal(o_c_l.args, None)
7276

73-
# _open_completions_later should be called with COMPLETE_ATTRIBUTES (1)
77+
# _open_completions_later should be called with COMPLETE_ATTRIBUTES (1).
7478
self.text.insert('1.0', 're.')
7579
trycompletions('event')
7680
Equal(o_c_l.args, (False, False, False, 1))
7781

78-
# _open_completions_later should be called with COMPLETE_FILES (2)
82+
# _open_completions_later should be called with COMPLETE_FILES (2).
7983
self.text.delete('1.0', 'end')
8084
self.text.insert('1.0', '"./Lib/')
8185
trycompletions('event')
@@ -86,7 +90,7 @@ def test_autocomplete_event(self):
8690
autocomplete = self.autocomplete
8791

8892
# Test that the autocomplete event is ignored if user is pressing a
89-
# modifier key in addition to the tab key
93+
# modifier key in addition to the tab key.
9094
ev = Event(mc_state=True)
9195
self.assertIsNone(autocomplete.autocomplete_event(ev))
9296
del ev.mc_state
@@ -96,15 +100,15 @@ def test_autocomplete_event(self):
96100
self.assertIsNone(autocomplete.autocomplete_event(ev))
97101
self.text.delete('1.0', 'end')
98102

99-
# If autocomplete window is open, complete() method is called
103+
# If autocomplete window is open, complete() method is called.
100104
self.text.insert('1.0', 're.')
101-
# This must call autocomplete._make_autocomplete_window()
105+
# This must call autocomplete._make_autocomplete_window().
102106
Equal(self.autocomplete.autocomplete_event(ev), 'break')
103107

104108
# If autocomplete window is not active or does not exist,
105109
# open_completions is called. Return depends on its return.
106110
autocomplete._remove_autocomplete_window()
107-
o_cs = Func() # .result = None
111+
o_cs = Func() # .result = None.
108112
autocomplete.open_completions = o_cs
109113
Equal(self.autocomplete.autocomplete_event(ev), None)
110114
Equal(o_cs.args, (False, True, True))
@@ -113,36 +117,130 @@ def test_autocomplete_event(self):
113117
Equal(o_cs.args, (False, True, True))
114118

115119
def test_open_completions_later(self):
116-
# Test that autocomplete._delayed_completion_id is set
117-
pass
120+
# Test that autocomplete._delayed_completion_id is set.
121+
acp = self.autocomplete
122+
acp._delayed_completion_id = None
123+
acp._open_completions_later(False, False, False, ac.COMPLETE_ATTRIBUTES)
124+
cb1 = acp._delayed_completion_id
125+
self.assertTrue(cb1.startswith('after'))
126+
127+
# Test that cb1 is cancelled and cb2 is new.
128+
acp._open_completions_later(False, False, False, ac.COMPLETE_FILES)
129+
self.assertNotIn(cb1, self.root.tk.call('after', 'info'))
130+
cb2 = acp._delayed_completion_id
131+
self.assertTrue(cb2.startswith('after') and cb2 != cb1)
132+
self.text.after_cancel(cb2)
118133

119134
def test_delayed_open_completions(self):
120-
# Test that autocomplete._delayed_completion_id set to None and that
121-
# open_completions only called if insertion index is the same as
122-
# _delayed_completion_index
123-
pass
135+
# Test that autocomplete._delayed_completion_id set to None
136+
# and that open_completions is not called if the index is not
137+
# equal to _delayed_completion_index.
138+
acp = self.autocomplete
139+
acp.open_completions = Func()
140+
acp._delayed_completion_id = 'after'
141+
acp._delayed_completion_index = self.text.index('insert+1c')
142+
acp._delayed_open_completions(1, 2, 3)
143+
self.assertIsNone(acp._delayed_completion_id)
144+
self.assertEqual(acp.open_completions.called, 0)
145+
146+
# Test that open_completions is called if indexes match.
147+
acp._delayed_completion_index = self.text.index('insert')
148+
acp._delayed_open_completions(1, 2, 3, ac.COMPLETE_FILES)
149+
self.assertEqual(acp.open_completions.args, (1, 2, 3, 2))
124150

125151
def test_open_completions(self):
126152
# Test completions of files and attributes as well as non-completion
127-
# of errors
128-
pass
153+
# of errors.
154+
self.text.insert('1.0', 'pr')
155+
self.assertTrue(self.autocomplete.open_completions(False, True, True))
156+
self.text.delete('1.0', 'end')
157+
158+
# Test files.
159+
self.text.insert('1.0', '"t')
160+
#self.assertTrue(self.autocomplete.open_completions(False, True, True))
161+
self.text.delete('1.0', 'end')
162+
163+
# Test with blank will fail.
164+
self.assertFalse(self.autocomplete.open_completions(False, True, True))
165+
166+
# Test with only string quote will fail.
167+
self.text.insert('1.0', '"')
168+
self.assertFalse(self.autocomplete.open_completions(False, True, True))
169+
self.text.delete('1.0', 'end')
129170

130171
def test_fetch_completions(self):
131172
# Test that fetch_completions returns 2 lists:
132173
# For attribute completion, a large list containing all variables, and
133174
# a small list containing non-private variables.
134175
# For file completion, a large list containing all files in the path,
135-
# and a small list containing files that do not start with '.'
176+
# and a small list containing files that do not start with '.'.
177+
autocomplete = self.autocomplete
136178
small, large = self.autocomplete.fetch_completions(
137179
'', ac.COMPLETE_ATTRIBUTES)
138-
self.assertLess(len(small), len(large))
139180
if __main__.__file__ != ac.__file__:
140181
self.assertNotIn('AutoComplete', small) # See issue 36405.
141182

183+
# Test attributes
184+
s, b = autocomplete.fetch_completions('', ac.COMPLETE_ATTRIBUTES)
185+
self.assertLess(len(small), len(large))
186+
self.assertTrue(all(filter(lambda x: x.startswith('_'), s)))
187+
self.assertTrue(any(filter(lambda x: x.startswith('_'), b)))
188+
189+
# Test smalll should respect to __all__.
190+
with patch.dict('__main__.__dict__', {'__all__': ['a', 'b']}):
191+
s, b = autocomplete.fetch_completions('', ac.COMPLETE_ATTRIBUTES)
192+
self.assertEqual(s, ['a', 'b'])
193+
self.assertIn('__name__', b) # From __main__.__dict__
194+
self.assertIn('sum', b) # From __main__.__builtins__.__dict__
195+
196+
# Test attributes with name entity.
197+
mock = Mock()
198+
mock._private = Mock()
199+
with patch.dict('__main__.__dict__', {'foo': mock}):
200+
s, b = autocomplete.fetch_completions('foo', ac.COMPLETE_ATTRIBUTES)
201+
self.assertNotIn('_private', s)
202+
self.assertIn('_private', b)
203+
self.assertEqual(s, [i for i in sorted(dir(mock)) if i[:1] != '_'])
204+
self.assertEqual(b, sorted(dir(mock)))
205+
206+
# Test files
207+
def _listdir(path):
208+
# This will be patch and used in fetch_completions.
209+
if path == '.':
210+
return ['foo', 'bar', '.hidden']
211+
return ['monty', 'python', '.hidden']
212+
213+
with patch.object(os, 'listdir', _listdir):
214+
s, b = autocomplete.fetch_completions('', ac.COMPLETE_FILES)
215+
self.assertEqual(s, ['bar', 'foo'])
216+
self.assertEqual(b, ['.hidden', 'bar', 'foo'])
217+
218+
s, b = autocomplete.fetch_completions('~', ac.COMPLETE_FILES)
219+
self.assertEqual(s, ['monty', 'python'])
220+
self.assertEqual(b, ['.hidden', 'monty', 'python'])
221+
142222
def test_get_entity(self):
143223
# Test that a name is in the namespace of sys.modules and
144-
# __main__.__dict__
145-
self.assertEqual(self.autocomplete.get_entity('int'), int)
224+
# __main__.__dict__.
225+
autocomplete = self.autocomplete
226+
Equal = self.assertEqual
227+
228+
Equal(self.autocomplete.get_entity('int'), int)
229+
230+
# Test name from sys.modules.
231+
mock = Mock()
232+
with patch.dict('sys.modules', {'tempfile': mock}):
233+
Equal(autocomplete.get_entity('tempfile'), mock)
234+
235+
# Test name from __main__.__dict__.
236+
di = {'foo': 10, 'bar': 20}
237+
with patch.dict('__main__.__dict__', {'d': di}):
238+
Equal(autocomplete.get_entity('d'), di)
239+
240+
# Test name not in namespace.
241+
with patch.dict('__main__.__dict__', {}):
242+
with self.assertRaises(NameError):
243+
autocomplete.get_entity('not_exist')
146244

147245

148246
if __name__ == '__main__':
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Increase test coverage of idlelib.autocomplete by 30%.

0 commit comments

Comments
 (0)
0