Skip to content

Commit 4d90afc

Browse files
committed
bpo-30917: IDLE: Add config.IdleConf unittest
1 parent ab025e3 commit 4d90afc

File tree

2 files changed

+289
-13
lines changed

2 files changed

+289
-13
lines changed

Lib/idlelib/config.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,15 @@ class IdleConf:
159159
for config_type in self.config_types:
160160
(user home dir)/.idlerc/config-{config-type}.cfg
161161
"""
162-
def __init__(self):
162+
def __init__(self, _utest=False):
163163
self.config_types = ('main', 'highlight', 'keys', 'extensions')
164164
self.defaultCfg = {}
165165
self.userCfg = {}
166166
self.cfg = {} # TODO use to select userCfg vs defaultCfg
167-
self.CreateConfigHandlers()
168-
self.LoadCfgFiles()
169167

168+
if not _utest:
169+
self.CreateConfigHandlers()
170+
self.LoadCfgFiles()
170171

171172
def CreateConfigHandlers(self):
172173
"Populate default and user config parser dictionaries."
@@ -463,16 +464,8 @@ def GetExtensions(self, active_only=True,
463464

464465
def RemoveKeyBindNames(self, extnNameList):
465466
"Return extnNameList with keybinding section names removed."
466-
# TODO Easier to return filtered copy with list comp
467-
names = extnNameList
468-
kbNameIndicies = []
469-
for name in names:
470-
if name.endswith(('_bindings', '_cfgBindings')):
471-
kbNameIndicies.append(names.index(name))
472-
kbNameIndicies.sort(reverse=True)
473-
for index in kbNameIndicies: #delete each keybinding section name
474-
del(names[index])
475-
return names
467+
return sorted(
468+
[n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))])
476469

477470
def GetExtnNameForEvent(self, virtualEvent):
478471
"""Return the name of the extension binding virtualEvent, or None.

Lib/idlelib/idle_test/test_config.py

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import tempfile
1010
from test.support import captured_stderr, findfile
1111
import unittest
12+
from unittest import mock
1213
from idlelib import config
1314

1415
# Tests should not depend on fortuitous user configurations.
@@ -185,6 +186,288 @@ def test_save(self):
185186
self.assertFalse(os.path.exists(path))
186187

187188

189+
class IdleConfTest(unittest.TestCase):
190+
"""Test for idleConf"""
191+
192+
def new_config(self, _utest=False):
193+
return config.IdleConf(_utest=_utest)
194+
195+
def mock_config(self):
196+
"""Return a mocked idleConf
197+
198+
Both default and user config used the same config-*.def
199+
"""
200+
import sys
201+
conf = config.IdleConf(_utest=True)
202+
if __name__ != '__main__':
203+
idle_dir = os.path.dirname(__file__)
204+
else:
205+
idle_dir = os.path.abspath(sys.path[0])
206+
for ctype in conf.config_types:
207+
config_path = os.path.join(idle_dir, '../config-%s.def' % ctype)
208+
conf.defaultCfg[ctype] = config.IdleConfParser(config_path)
209+
conf.userCfg[ctype] = config.IdleUserConfParser(config_path)
210+
conf.LoadCfgFiles()
211+
212+
return conf
213+
214+
def test_get_user_cfg_dir(self):
215+
"Test to get user config directory"
216+
conf = self.new_config(_utest=True)
217+
218+
# Check normal way should success
219+
with mock.patch('os.path.expanduser', return_value='/home/foo'):
220+
with mock.patch('os.path.exists', return_value=True):
221+
self.assertEqual(conf.GetUserCfgDir(), '/home/foo/.idlerc')
222+
223+
# Check os.getcwd should success
224+
with mock.patch('os.path.expanduser', return_value='~'):
225+
with mock.patch('os.getcwd', return_value='/home/foo/cpython'):
226+
with mock.patch('os.mkdir'):
227+
self.assertEqual(conf.GetUserCfgDir(),
228+
'/home/foo/cpython/.idlerc')
229+
230+
# Check user dir not exists and created failed should raise SystemExit
231+
with mock.patch('os.path.join', return_value='/path/not/exists'):
232+
with self.assertRaises(SystemExit):
233+
with self.assertRaises(FileNotFoundError):
234+
conf.GetUserCfgDir()
235+
236+
def test_create_config_handlers(self):
237+
conf = self.new_config(_utest=True)
238+
239+
# Mock out idle_dir
240+
idle_dir = '/home/foo'
241+
with mock.patch.dict({'__name__': '__foo__'}):
242+
with mock.patch('os.path.dirname', return_value=idle_dir):
243+
conf.CreateConfigHandlers()
244+
245+
# Check keys are equal
246+
self.assertCountEqual(conf.defaultCfg.keys(), conf.config_types)
247+
self.assertCountEqual(conf.userCfg.keys(), conf.config_types)
248+
249+
# Check conf parser are correct type
250+
for default_parser in conf.defaultCfg.values():
251+
self.assertIsInstance(default_parser, config.IdleConfParser)
252+
for user_parser in conf.userCfg.values():
253+
self.assertIsInstance(user_parser, config.IdleUserConfParser)
254+
255+
# Check config path are correct
256+
for config_type, parser in conf.defaultCfg.items():
257+
self.assertEqual(parser.file,
258+
os.path.join(idle_dir, 'config-%s.def' % config_type))
259+
for config_type, parser in conf.userCfg.items():
260+
self.assertEqual(parser.file,
261+
os.path.join(conf.userdir, 'config-%s.cfg' % config_type))
262+
263+
def test_load_cfg_files(self):
264+
conf = self.new_config(_utest=True)
265+
266+
# Borrow test/cfgparser.1 from test_configparser.
267+
config_path = findfile('cfgparser.1')
268+
conf.defaultCfg['foo'] = config.IdleConfParser(config_path)
269+
conf.userCfg['foo'] = config.IdleUserConfParser(config_path)
270+
271+
# Load all config from path
272+
conf.LoadCfgFiles()
273+
274+
eq = self.assertEqual
275+
276+
# Check defaultCfg is loaded
277+
eq(conf.defaultCfg['foo'].Get('Foo Bar', 'foo'), 'newbar')
278+
eq(conf.defaultCfg['foo'].GetOptionList('Foo Bar'), ['foo'])
279+
280+
# Check userCfg is loaded
281+
eq(conf.userCfg['foo'].Get('Foo Bar', 'foo'), 'newbar')
282+
eq(conf.userCfg['foo'].GetOptionList('Foo Bar'), ['foo'])
283+
284+
def test_get_section_list(self):
285+
conf = self.mock_config()
286+
287+
self.assertCountEqual(
288+
conf.GetSectionList('default', 'main'),
289+
['General', 'EditorWindow', 'Indent', 'Theme',
290+
'Keys', 'History', 'HelpFiles'])
291+
self.assertCountEqual(
292+
conf.GetSectionList('user', 'main'),
293+
['General', 'EditorWindow', 'Indent', 'Theme',
294+
'Keys', 'History', 'HelpFiles'])
295+
296+
def test_get_highlight(self):
297+
conf = self.mock_config()
298+
299+
eq = self.assertEqual
300+
eq(conf.GetHighlight('IDLE Classic', 'normal'), {'foreground': '#000000',
301+
'background': '#ffffff'})
302+
eq(conf.GetHighlight('IDLE Classic', 'normal', 'fg'), '#000000')
303+
eq(conf.GetHighlight('IDLE Classic', 'normal', 'bg'), '#ffffff')
304+
with self.assertRaises(config.InvalidFgBg):
305+
conf.GetHighlight('IDLE Classic', 'normal', 'fb')
306+
307+
# Test cursor (this background should be normal-background)
308+
eq(conf.GetHighlight('IDLE Classic', 'cursor'), {'foreground': 'black',
309+
'background': '#ffffff'})
310+
311+
def test_get_theme_dict(self):
312+
"XXX: NOT YET DONE"
313+
conf = self.mock_config()
314+
315+
# These two should be the same
316+
self.assertEqual(
317+
conf.GetThemeDict('default', 'IDLE Classic'),
318+
conf.GetThemeDict('user', 'IDLE Classic'))
319+
320+
with self.assertRaises(config.InvalidTheme):
321+
conf.GetThemeDict('bad', 'IDLE Classic')
322+
323+
def test_current_colors_and_keys(self):
324+
conf = self.mock_config()
325+
326+
self.assertEqual(conf.current_colors_and_keys('Theme'), 'IDLE Classic')
327+
328+
def test_default_keys(self):
329+
import sys
330+
current_platform = sys.platform
331+
conf = self.new_config(_utest=True)
332+
333+
sys.platform = 'win'
334+
self.assertEqual(conf.default_keys(), 'IDLE Classic Windows')
335+
336+
sys.platform = 'darwin'
337+
self.assertEqual(conf.default_keys(), 'IDLE Classic OSX')
338+
339+
sys.platform = 'linux'
340+
self.assertEqual(conf.default_keys(), 'IDLE Modern Unix')
341+
342+
# Restore platform
343+
sys.platform = current_platform
344+
345+
def test_get_extensions(self):
346+
conf = self.mock_config()
347+
348+
eq = self.assertEqual
349+
eq(conf.GetExtensions(),
350+
['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
351+
'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding',
352+
'ZoomHeight'])
353+
eq(conf.GetExtensions(active_only=False),
354+
['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
355+
'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding',
356+
'ZoomHeight'])
357+
eq(conf.GetExtensions(editor_only=True),
358+
['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
359+
'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding',
360+
'ZoomHeight'])
361+
eq(conf.GetExtensions(shell_only=True),
362+
['AutoComplete', 'AutoExpand', 'CallTips', 'FormatParagraph',
363+
'ParenMatch', 'ZoomHeight'])
364+
365+
def test_remove_key_bind_names(self):
366+
conf = self.mock_config()
367+
368+
self.assertEqual(
369+
conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')),
370+
['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext',
371+
'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding',
372+
'ZoomHeight'])
373+
374+
def test_get_extn_name_for_event(self):
375+
conf = self.mock_config()
376+
377+
eq = self.assertEqual
378+
eq(conf.GetExtnNameForEvent('force-open-completions'), 'AutoComplete')
379+
eq(conf.GetExtnNameForEvent('expand-word'), 'AutoExpand')
380+
eq(conf.GetExtnNameForEvent('force-open-calltip'), 'CallTips')
381+
eq(conf.GetExtnNameForEvent('zoom-height'), 'ZoomHeight')
382+
383+
def test_get_extension_keys(self):
384+
conf = self.mock_config()
385+
386+
eq = self.assertEqual
387+
eq(conf.GetExtensionKeys('AutoComplete'),
388+
{'<<force-open-completions>>': ['<Control-Key-space>']})
389+
eq(conf.GetExtensionKeys('ParenMatch'),
390+
{'<<flash-paren>>': ['<Control-Key-0>']})
391+
eq(conf.GetExtensionKeys('ZoomHeight'),
392+
{'<<zoom-height>>': ['<Alt-Key-2>']})
393+
394+
def test_get_extension_bindings(self):
395+
conf = self.mock_config()
396+
397+
self.assertEqual(conf.GetExtensionBindings('NotExists'), {})
398+
self.assertEqual(
399+
conf.GetExtensionBindings('ZoomHeight'),
400+
{'<<zoom-height>>': ['<Alt-Key-2>']})
401+
402+
def test_get_current_keyset(self):
403+
import sys
404+
current_platform = sys.platform
405+
conf = self.mock_config()
406+
407+
# Ensure that platform isn't darwin
408+
sys.platform = 'linux'
409+
self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
410+
411+
# This should not be the same, sicne replace <Alt- to <Option-
412+
sys.platform = 'darwin'
413+
self.assertNotEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
414+
415+
# Restore platform
416+
sys.platform = current_platform
417+
418+
def test_is_core_binding(self):
419+
# XXX: Should move out the core keys to config file or other place
420+
conf = self.mock_config()
421+
422+
self.assertTrue(conf.IsCoreBinding('copy'))
423+
self.assertTrue(conf.IsCoreBinding('cut'))
424+
self.assertTrue(conf.IsCoreBinding('del-word-right'))
425+
self.assertFalse(conf.IsCoreBinding('not-exists'))
426+
427+
def test_get_extra_help_source_list(self):
428+
conf = self.mock_config()
429+
430+
self.assertEqual(conf.GetExtraHelpSourceList('default'), [])
431+
self.assertEqual(conf.GetExtraHelpSourceList('user'), [])
432+
with self.assertRaises(config.InvalidConfigSet):
433+
self.assertEqual(conf.GetExtraHelpSourceList('bad'), [])
434+
435+
def test_get_all_extra_help_source_list(self):
436+
conf = self.mock_config()
437+
438+
self.assertCountEqual(
439+
conf.GetAllExtraHelpSourcesList(),
440+
conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user'))
441+
442+
def test_get_font(self):
443+
from tkinter import Tk
444+
from tkinter.font import Font
445+
conf = self.mock_config()
446+
447+
root = Tk()
448+
root.withdraw()
449+
f = Font.actual(Font(name='TkFixedFont', exists=True, root=root))
450+
451+
self.assertEqual(
452+
conf.GetFont(root, 'main', 'EditorWindow'),
453+
(f['family'], 10 if f['size'] < 10 else f['size'], f['weight']))
454+
455+
def test_get_core_keys(self):
456+
conf = self.mock_config()
457+
458+
eq = self.assertEqual
459+
eq(conf.GetCoreKeys()['<<center-insert>>'], ['<Control-l>'])
460+
eq(conf.GetCoreKeys()['<<copy>>'], ['<Control-c>', '<Control-C>'])
461+
eq(conf.GetCoreKeys()['<<history-next>>'], ['<Alt-n>'])
462+
eq(conf.GetCoreKeys('IDLE Classic Windows')['<<center-insert>>'],
463+
['<Control-Key-l>', '<Control-Key-L>'])
464+
eq(conf.GetCoreKeys('IDLE Classic OSX')['<<copy>>'], ['<Command-Key-c>'])
465+
eq(conf.GetCoreKeys('IDLE Classic Unix')['<<history-next>>'],
466+
['<Alt-Key-n>', '<Meta-Key-n>'])
467+
eq(conf.GetCoreKeys('IDLE Modern Unix')['<<history-next>>'],
468+
['<Alt-Key-n>', '<Meta-Key-n>'])
469+
470+
188471
class CurrentColorKeysTest(unittest.TestCase):
189472
""" Test colorkeys function with user config [Theme] and [Keys] patterns.
190473

0 commit comments

Comments
 (0)