From 4d90afcd2f95e16160f9d54d378a3c94c3228f4c Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Thu, 13 Jul 2017 12:45:01 +0800 Subject: [PATCH 01/21] bpo-30917: IDLE: Add config.IdleConf unittest --- Lib/idlelib/config.py | 19 +- Lib/idlelib/idle_test/test_config.py | 283 +++++++++++++++++++++++++++ 2 files changed, 289 insertions(+), 13 deletions(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index 4cf44271f3a750..b6e0ba9c9cf22f 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -159,14 +159,15 @@ class IdleConf: for config_type in self.config_types: (user home dir)/.idlerc/config-{config-type}.cfg """ - def __init__(self): + def __init__(self, _utest=False): self.config_types = ('main', 'highlight', 'keys', 'extensions') self.defaultCfg = {} self.userCfg = {} self.cfg = {} # TODO use to select userCfg vs defaultCfg - self.CreateConfigHandlers() - self.LoadCfgFiles() + if not _utest: + self.CreateConfigHandlers() + self.LoadCfgFiles() def CreateConfigHandlers(self): "Populate default and user config parser dictionaries." @@ -463,16 +464,8 @@ def GetExtensions(self, active_only=True, def RemoveKeyBindNames(self, extnNameList): "Return extnNameList with keybinding section names removed." - # TODO Easier to return filtered copy with list comp - names = extnNameList - kbNameIndicies = [] - for name in names: - if name.endswith(('_bindings', '_cfgBindings')): - kbNameIndicies.append(names.index(name)) - kbNameIndicies.sort(reverse=True) - for index in kbNameIndicies: #delete each keybinding section name - del(names[index]) - return names + return sorted( + [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]) def GetExtnNameForEvent(self, virtualEvent): """Return the name of the extension binding virtualEvent, or None. diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index f5a609f6d1600b..945360ed77270c 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -9,6 +9,7 @@ import tempfile from test.support import captured_stderr, findfile import unittest +from unittest import mock from idlelib import config # Tests should not depend on fortuitous user configurations. @@ -185,6 +186,288 @@ def test_save(self): self.assertFalse(os.path.exists(path)) +class IdleConfTest(unittest.TestCase): + """Test for idleConf""" + + def new_config(self, _utest=False): + return config.IdleConf(_utest=_utest) + + def mock_config(self): + """Return a mocked idleConf + + Both default and user config used the same config-*.def + """ + import sys + conf = config.IdleConf(_utest=True) + if __name__ != '__main__': + idle_dir = os.path.dirname(__file__) + else: + idle_dir = os.path.abspath(sys.path[0]) + for ctype in conf.config_types: + config_path = os.path.join(idle_dir, '../config-%s.def' % ctype) + conf.defaultCfg[ctype] = config.IdleConfParser(config_path) + conf.userCfg[ctype] = config.IdleUserConfParser(config_path) + conf.LoadCfgFiles() + + return conf + + def test_get_user_cfg_dir(self): + "Test to get user config directory" + conf = self.new_config(_utest=True) + + # Check normal way should success + with mock.patch('os.path.expanduser', return_value='/home/foo'): + with mock.patch('os.path.exists', return_value=True): + self.assertEqual(conf.GetUserCfgDir(), '/home/foo/.idlerc') + + # Check os.getcwd should success + with mock.patch('os.path.expanduser', return_value='~'): + with mock.patch('os.getcwd', return_value='/home/foo/cpython'): + with mock.patch('os.mkdir'): + self.assertEqual(conf.GetUserCfgDir(), + '/home/foo/cpython/.idlerc') + + # Check user dir not exists and created failed should raise SystemExit + with mock.patch('os.path.join', return_value='/path/not/exists'): + with self.assertRaises(SystemExit): + with self.assertRaises(FileNotFoundError): + conf.GetUserCfgDir() + + def test_create_config_handlers(self): + conf = self.new_config(_utest=True) + + # Mock out idle_dir + idle_dir = '/home/foo' + with mock.patch.dict({'__name__': '__foo__'}): + with mock.patch('os.path.dirname', return_value=idle_dir): + conf.CreateConfigHandlers() + + # Check keys are equal + self.assertCountEqual(conf.defaultCfg.keys(), conf.config_types) + self.assertCountEqual(conf.userCfg.keys(), conf.config_types) + + # Check conf parser are correct type + for default_parser in conf.defaultCfg.values(): + self.assertIsInstance(default_parser, config.IdleConfParser) + for user_parser in conf.userCfg.values(): + self.assertIsInstance(user_parser, config.IdleUserConfParser) + + # Check config path are correct + for config_type, parser in conf.defaultCfg.items(): + self.assertEqual(parser.file, + os.path.join(idle_dir, 'config-%s.def' % config_type)) + for config_type, parser in conf.userCfg.items(): + self.assertEqual(parser.file, + os.path.join(conf.userdir, 'config-%s.cfg' % config_type)) + + def test_load_cfg_files(self): + conf = self.new_config(_utest=True) + + # Borrow test/cfgparser.1 from test_configparser. + config_path = findfile('cfgparser.1') + conf.defaultCfg['foo'] = config.IdleConfParser(config_path) + conf.userCfg['foo'] = config.IdleUserConfParser(config_path) + + # Load all config from path + conf.LoadCfgFiles() + + eq = self.assertEqual + + # Check defaultCfg is loaded + eq(conf.defaultCfg['foo'].Get('Foo Bar', 'foo'), 'newbar') + eq(conf.defaultCfg['foo'].GetOptionList('Foo Bar'), ['foo']) + + # Check userCfg is loaded + eq(conf.userCfg['foo'].Get('Foo Bar', 'foo'), 'newbar') + eq(conf.userCfg['foo'].GetOptionList('Foo Bar'), ['foo']) + + def test_get_section_list(self): + conf = self.mock_config() + + self.assertCountEqual( + conf.GetSectionList('default', 'main'), + ['General', 'EditorWindow', 'Indent', 'Theme', + 'Keys', 'History', 'HelpFiles']) + self.assertCountEqual( + conf.GetSectionList('user', 'main'), + ['General', 'EditorWindow', 'Indent', 'Theme', + 'Keys', 'History', 'HelpFiles']) + + def test_get_highlight(self): + conf = self.mock_config() + + eq = self.assertEqual + eq(conf.GetHighlight('IDLE Classic', 'normal'), {'foreground': '#000000', + 'background': '#ffffff'}) + eq(conf.GetHighlight('IDLE Classic', 'normal', 'fg'), '#000000') + eq(conf.GetHighlight('IDLE Classic', 'normal', 'bg'), '#ffffff') + with self.assertRaises(config.InvalidFgBg): + conf.GetHighlight('IDLE Classic', 'normal', 'fb') + + # Test cursor (this background should be normal-background) + eq(conf.GetHighlight('IDLE Classic', 'cursor'), {'foreground': 'black', + 'background': '#ffffff'}) + + def test_get_theme_dict(self): + "XXX: NOT YET DONE" + conf = self.mock_config() + + # These two should be the same + self.assertEqual( + conf.GetThemeDict('default', 'IDLE Classic'), + conf.GetThemeDict('user', 'IDLE Classic')) + + with self.assertRaises(config.InvalidTheme): + conf.GetThemeDict('bad', 'IDLE Classic') + + def test_current_colors_and_keys(self): + conf = self.mock_config() + + self.assertEqual(conf.current_colors_and_keys('Theme'), 'IDLE Classic') + + def test_default_keys(self): + import sys + current_platform = sys.platform + conf = self.new_config(_utest=True) + + sys.platform = 'win' + self.assertEqual(conf.default_keys(), 'IDLE Classic Windows') + + sys.platform = 'darwin' + self.assertEqual(conf.default_keys(), 'IDLE Classic OSX') + + sys.platform = 'linux' + self.assertEqual(conf.default_keys(), 'IDLE Modern Unix') + + # Restore platform + sys.platform = current_platform + + def test_get_extensions(self): + conf = self.mock_config() + + eq = self.assertEqual + eq(conf.GetExtensions(), + ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', + 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', + 'ZoomHeight']) + eq(conf.GetExtensions(active_only=False), + ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', + 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', + 'ZoomHeight']) + eq(conf.GetExtensions(editor_only=True), + ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', + 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', + 'ZoomHeight']) + eq(conf.GetExtensions(shell_only=True), + ['AutoComplete', 'AutoExpand', 'CallTips', 'FormatParagraph', + 'ParenMatch', 'ZoomHeight']) + + def test_remove_key_bind_names(self): + conf = self.mock_config() + + self.assertEqual( + conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')), + ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', + 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', + 'ZoomHeight']) + + def test_get_extn_name_for_event(self): + conf = self.mock_config() + + eq = self.assertEqual + eq(conf.GetExtnNameForEvent('force-open-completions'), 'AutoComplete') + eq(conf.GetExtnNameForEvent('expand-word'), 'AutoExpand') + eq(conf.GetExtnNameForEvent('force-open-calltip'), 'CallTips') + eq(conf.GetExtnNameForEvent('zoom-height'), 'ZoomHeight') + + def test_get_extension_keys(self): + conf = self.mock_config() + + eq = self.assertEqual + eq(conf.GetExtensionKeys('AutoComplete'), + {'<>': ['']}) + eq(conf.GetExtensionKeys('ParenMatch'), + {'<>': ['']}) + eq(conf.GetExtensionKeys('ZoomHeight'), + {'<>': ['']}) + + def test_get_extension_bindings(self): + conf = self.mock_config() + + self.assertEqual(conf.GetExtensionBindings('NotExists'), {}) + self.assertEqual( + conf.GetExtensionBindings('ZoomHeight'), + {'<>': ['']}) + + def test_get_current_keyset(self): + import sys + current_platform = sys.platform + conf = self.mock_config() + + # Ensure that platform isn't darwin + sys.platform = 'linux' + self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys())) + + # This should not be the same, sicne replace >'], ['']) + eq(conf.GetCoreKeys()['<>'], ['', '']) + eq(conf.GetCoreKeys()['<>'], ['']) + eq(conf.GetCoreKeys('IDLE Classic Windows')['<>'], + ['', '']) + eq(conf.GetCoreKeys('IDLE Classic OSX')['<>'], ['']) + eq(conf.GetCoreKeys('IDLE Classic Unix')['<>'], + ['', '']) + eq(conf.GetCoreKeys('IDLE Modern Unix')['<>'], + ['', '']) + + class CurrentColorKeysTest(unittest.TestCase): """ Test colorkeys function with user config [Theme] and [Keys] patterns. From 9fc76705eca01f37b52c2081d0eb39ed70bacfeb Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Thu, 13 Jul 2017 15:57:09 +0800 Subject: [PATCH 02/21] Add pragma to exclude coverage count --- Lib/idlelib/config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index b6e0ba9c9cf22f..dfe17fbdcc300d 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -871,7 +871,8 @@ def clear(self): # TODO Revise test output, write expanded unittest -def _dump(): # htest # (not really, but ignore in coverage) +def _dump(): # pragma: no cover + # htest # (not really, but ignore in coverage) from zlib import crc32 line, crc = 0, 0 @@ -900,7 +901,8 @@ def dumpCfg(cfg): dumpCfg(idleConf.userCfg) print('\nlines = ', line, ', crc = ', crc, sep='') -if __name__ == '__main__': + +if __name__ == '__main__': # pragma: no cover import unittest unittest.main('idlelib.idle_test.test_config', verbosity=2, exit=False) From d4dac808d136139e6918c92e9217347cd9de08a0 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Thu, 13 Jul 2017 15:57:19 +0800 Subject: [PATCH 03/21] Add unittest --- Lib/idlelib/idle_test/test_config.py | 76 ++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 945360ed77270c..53bee7a3e123e2 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -281,6 +281,24 @@ def test_load_cfg_files(self): eq(conf.userCfg['foo'].Get('Foo Bar', 'foo'), 'newbar') eq(conf.userCfg['foo'].GetOptionList('Foo Bar'), ['foo']) + def test_get_option(self): + conf = self.mock_config() + + eq = self.assertEqual + eq(conf.GetOption('main', 'EditorWindow', 'width'), '80') + eq(conf.GetOption('main', 'EditorWindow', 'width', type='int'), 80) + with mock.patch('idlelib.config._warn') as _warn: + eq(conf.GetOption('main', 'EditorWindow', 'font', type='int'), None) + eq(conf.GetOption('main', 'EditorWindow', 'NotExists'), None) + eq(conf.GetOption('main', 'EditorWindow', 'NotExists', default='NE'), 'NE') + eq(_warn.call_count, 4) + + def test_set_option(self): + conf = self.mock_config() + + conf.SetOption('main', 'Foo', 'bar', 'newbar') + self.assertEqual(conf.GetOption('main', 'Foo', 'bar'), 'newbar') + def test_get_section_list(self): conf = self.mock_config() @@ -293,6 +311,11 @@ def test_get_section_list(self): ['General', 'EditorWindow', 'Indent', 'Theme', 'Keys', 'History', 'HelpFiles']) + with self.assertRaises(config.InvalidConfigSet): + conf.GetSectionList('foobar', 'main') + with self.assertRaises(config.InvalidConfigType): + conf.GetSectionList('default', 'notexists') + def test_get_highlight(self): conf = self.mock_config() @@ -308,6 +331,13 @@ def test_get_highlight(self): eq(conf.GetHighlight('IDLE Classic', 'cursor'), {'foreground': 'black', 'background': '#ffffff'}) + # Test get user themes + conf.SetOption('highlight', 'Foobar', 'normal-foreground', '#747474') + conf.SetOption('highlight', 'Foobar', 'normal-background', '#171717') + with mock.patch('idlelib.config._warn'): + eq(conf.GetHighlight('Foobar', 'normal'), {'foreground': '#747474', + 'background': '#171717'}) + def test_get_theme_dict(self): "XXX: NOT YET DONE" conf = self.mock_config() @@ -320,6 +350,12 @@ def test_get_theme_dict(self): with self.assertRaises(config.InvalidTheme): conf.GetThemeDict('bad', 'IDLE Classic') + def test_get_current_theme_and_keys(self): + conf = self.mock_config() + + self.assertEqual(conf.CurrentTheme(), conf.current_colors_and_keys('Theme')) + self.assertEqual(conf.CurrentKeys(), conf.current_colors_and_keys('Keys')) + def test_current_colors_and_keys(self): conf = self.mock_config() @@ -362,6 +398,13 @@ def test_get_extensions(self): ['AutoComplete', 'AutoExpand', 'CallTips', 'FormatParagraph', 'ParenMatch', 'ZoomHeight']) + # Add user extensions + conf.SetOption('extensions', 'Foobar', 'enable', 'True') + eq(conf.GetExtensions(), + ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', + 'FormatParagraph', 'ParenMatch', 'RstripExtension', + 'ScriptBinding', 'ZoomHeight', 'Foobar']) # User extensions didn't sort + def test_remove_key_bind_names(self): conf = self.mock_config() @@ -399,6 +442,13 @@ def test_get_extension_bindings(self): conf.GetExtensionBindings('ZoomHeight'), {'<>': ['']}) + # Add non-configuarable bindings + conf.defaultCfg['extensions'].add_section('Foobar') + conf.defaultCfg['extensions'].add_section('Foobar_bindings') + conf.defaultCfg['extensions'].set('Foobar', 'enable', 'True') + conf.defaultCfg['extensions'].set('Foobar_bindings', 'foobar', '') + self.assertEqual(conf.GetExtensionBindings('Foobar'), {'<>': ['']}) + def test_get_current_keyset(self): import sys current_platform = sys.platform @@ -415,6 +465,16 @@ def test_get_current_keyset(self): # Restore platform sys.platform = current_platform + def test_get_keyset(self): + conf = self.mock_config() + + # Conflic with key set, should be disable to '' + conf.defaultCfg['extensions'].add_section('Foobar') + conf.defaultCfg['extensions'].add_section('Foobar_cfgBindings') + conf.defaultCfg['extensions'].set('Foobar', 'enable', 'True') + conf.defaultCfg['extensions'].set('Foobar_cfgBindings', 'newfoo', '') + self.assertEqual(conf.GetKeySet('IDLE Modern Unix')['<>'], '') + def test_is_core_binding(self): # XXX: Should move out the core keys to config file or other place conf = self.mock_config() @@ -424,17 +484,25 @@ def test_is_core_binding(self): self.assertTrue(conf.IsCoreBinding('del-word-right')) self.assertFalse(conf.IsCoreBinding('not-exists')) - def test_get_extra_help_source_list(self): + def test_extra_help_source_list(self): conf = self.mock_config() + # Test default with no extra help source self.assertEqual(conf.GetExtraHelpSourceList('default'), []) self.assertEqual(conf.GetExtraHelpSourceList('user'), []) with self.assertRaises(config.InvalidConfigSet): self.assertEqual(conf.GetExtraHelpSourceList('bad'), []) + self.assertCountEqual( + conf.GetAllExtraHelpSourcesList(), + conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user')) - def test_get_all_extra_help_source_list(self): - conf = self.mock_config() - + # Add help source to user config + conf.userCfg['main'].SetOption('HelpFiles', '1', 'IDLE;C:/Programs/Python36/Lib/idlelib/help.html') + conf.userCfg['main'].SetOption('HelpFiles', '2', 'Pillow;https://pillow.readthedocs.io/en/latest/') + conf.userCfg['main'].SetOption('HelpFiles', '3', 'Python:https://python.org') + self.assertEqual(conf.GetExtraHelpSourceList('user'), + [('IDLE', 'C:/Programs/Python36/Lib/idlelib/help.html', '1'), + ('Pillow', 'https://pillow.readthedocs.io/en/latest/', '2')]) self.assertCountEqual( conf.GetAllExtraHelpSourcesList(), conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user')) From eb32d913829d0ba9d1d4801b505f0e20a60202fd Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Thu, 13 Jul 2017 15:59:48 +0800 Subject: [PATCH 04/21] Add gui requires --- Lib/idlelib/idle_test/test_config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 53bee7a3e123e2..599c32583651f3 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -508,10 +508,12 @@ def test_extra_help_source_list(self): conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user')) def test_get_font(self): + from test.support import requires from tkinter import Tk from tkinter.font import Font conf = self.mock_config() + requires('gui') root = Tk() root.withdraw() f = Font.actual(Font(name='TkFixedFont', exists=True, root=root)) From ddedbc73032e43ec7537d42795a34acd5cf3c0f1 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Sat, 15 Jul 2017 12:40:34 +0800 Subject: [PATCH 05/21] Revert "Add pragma to exclude coverage count" This reverts commit 9fc76705eca01f37b52c2081d0eb39ed70bacfeb. --- Lib/idlelib/config.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index dfe17fbdcc300d..b6e0ba9c9cf22f 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -871,8 +871,7 @@ def clear(self): # TODO Revise test output, write expanded unittest -def _dump(): # pragma: no cover - # htest # (not really, but ignore in coverage) +def _dump(): # htest # (not really, but ignore in coverage) from zlib import crc32 line, crc = 0, 0 @@ -901,8 +900,7 @@ def dumpCfg(cfg): dumpCfg(idleConf.userCfg) print('\nlines = ', line, ', crc = ', crc, sep='') - -if __name__ == '__main__': # pragma: no cover +if __name__ == '__main__': import unittest unittest.main('idlelib.idle_test.test_config', verbosity=2, exit=False) From 06d8329205d4239f5c99591d76ff3ae579cf3c9a Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Sat, 15 Jul 2017 12:50:46 +0800 Subject: [PATCH 06/21] Add MacOS specific test correct --- Lib/idlelib/idle_test/test_config.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 599c32583651f3..9fc21bcbc643c2 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -5,6 +5,7 @@ Much of IdleConf is exercised by ConfigDialog and test_configdialog, but it should be tested here. ''' +import sys import os import tempfile from test.support import captured_stderr, findfile @@ -431,16 +432,18 @@ def test_get_extension_keys(self): {'<>': ['']}) eq(conf.GetExtensionKeys('ParenMatch'), {'<>': ['']}) - eq(conf.GetExtensionKeys('ZoomHeight'), - {'<>': ['']}) + + key = [''] if sys.platform == 'darwin' else [''] + eq(conf.GetExtensionKeys('ZoomHeight'), {'<>': key}) def test_get_extension_bindings(self): conf = self.mock_config() self.assertEqual(conf.GetExtensionBindings('NotExists'), {}) + + key = [''] if sys.platform == 'darwin' else [''] self.assertEqual( - conf.GetExtensionBindings('ZoomHeight'), - {'<>': ['']}) + conf.GetExtensionBindings('ZoomHeight'), {'<>': key}) # Add non-configuarable bindings conf.defaultCfg['extensions'].add_section('Foobar') From f29e0da02037326f50cf1b68647af8d978f49c27 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Sat, 15 Jul 2017 12:52:26 +0800 Subject: [PATCH 07/21] Split get_user_config_dir to unix and windows --- Lib/idlelib/idle_test/test_config.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 9fc21bcbc643c2..2e85915fba5235 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -212,8 +212,8 @@ def mock_config(self): return conf - def test_get_user_cfg_dir(self): - "Test to get user config directory" + def test_get_user_cfg_dir_unix(self): + "Test to get user config directory under unix" conf = self.new_config(_utest=True) # Check normal way should success @@ -234,6 +234,28 @@ def test_get_user_cfg_dir(self): with self.assertRaises(FileNotFoundError): conf.GetUserCfgDir() + def test_get_user_cfg_dir_windows(self): + "Test to get user config directory under windows" + conf = self.new_config(_utest=True) + + # Check normal way should success + with mock.patch('os.path.expanduser', return_value='C:\\foo'): + with mock.patch('os.path.exists', return_value=True): + self.assertEqual(conf.GetUserCfgDir(), 'C:\\foo\\.idlerc') + + # Check os.getcwd should success + with mock.patch('os.path.expanduser', return_value='~'): + with mock.patch('os.getcwd', return_value='C:\\foo\\cpython'): + with mock.patch('os.mkdir'): + self.assertEqual(conf.GetUserCfgDir(), + 'C:\\foo\\cpython\\.idlerc') + + # Check user dir not exists and created failed should raise SystemExit + with mock.patch('os.path.join', return_value='/path/not/exists'): + with self.assertRaises(SystemExit): + with self.assertRaises(FileNotFoundError): + conf.GetUserCfgDir() + def test_create_config_handlers(self): conf = self.new_config(_utest=True) From 3f2fdcce4e7b20cbca28a82de72c62d861dc4215 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Sat, 15 Jul 2017 15:51:05 +0800 Subject: [PATCH 08/21] Remove unused import --- Lib/idlelib/idle_test/test_config.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 2e85915fba5235..5c0d9bf9f09ab1 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -198,7 +198,6 @@ def mock_config(self): Both default and user config used the same config-*.def """ - import sys conf = config.IdleConf(_utest=True) if __name__ != '__main__': idle_dir = os.path.dirname(__file__) @@ -385,7 +384,6 @@ def test_current_colors_and_keys(self): self.assertEqual(conf.current_colors_and_keys('Theme'), 'IDLE Classic') def test_default_keys(self): - import sys current_platform = sys.platform conf = self.new_config(_utest=True) @@ -475,7 +473,6 @@ def test_get_extension_bindings(self): self.assertEqual(conf.GetExtensionBindings('Foobar'), {'<>': ['']}) def test_get_current_keyset(self): - import sys current_platform = sys.platform conf = self.mock_config() From e445a0e51a042e9ba0f2f2ac450b01d0d9757629 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Sat, 15 Jul 2017 15:52:22 +0800 Subject: [PATCH 09/21] Destroy tk.root after test cases end --- Lib/idlelib/idle_test/test_config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 5c0d9bf9f09ab1..1ba74ef6822d8b 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -538,12 +538,16 @@ def test_get_font(self): requires('gui') root = Tk() root.withdraw() - f = Font.actual(Font(name='TkFixedFont', exists=True, root=root)) + f = Font.actual(Font(name='TkFixedFont', exists=True, root=root)) self.assertEqual( conf.GetFont(root, 'main', 'EditorWindow'), (f['family'], 10 if f['size'] < 10 else f['size'], f['weight'])) + # Cleanup root + root.destroy() + del root + def test_get_core_keys(self): conf = self.mock_config() From 0f1607d3542ca9441ac301b3dd5904a2195d6e24 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Mon, 17 Jul 2017 15:33:59 +0800 Subject: [PATCH 10/21] Add skipIf at get_user_cfg_dir test --- Lib/idlelib/idle_test/test_config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 1ba74ef6822d8b..cb480bc036b3ba 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -211,6 +211,7 @@ def mock_config(self): return conf + @unittest.skipIf(sys.platform.startswith('win'), 'this is test for unix system') def test_get_user_cfg_dir_unix(self): "Test to get user config directory under unix" conf = self.new_config(_utest=True) @@ -233,6 +234,7 @@ def test_get_user_cfg_dir_unix(self): with self.assertRaises(FileNotFoundError): conf.GetUserCfgDir() + @unittest.skipIf(not sys.platform.startswith('win'), 'this is test for windows system') def test_get_user_cfg_dir_windows(self): "Test to get user config directory under windows" conf = self.new_config(_utest=True) From 980a801f1bac749e6c89865b7898108cacac9705 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Mon, 17 Jul 2017 15:36:43 +0800 Subject: [PATCH 11/21] Update mock_config to deepcopy make it faster --- Lib/idlelib/idle_test/test_config.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index cb480bc036b3ba..d0a96fef5d2cab 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -5,6 +5,7 @@ Much of IdleConf is exercised by ConfigDialog and test_configdialog, but it should be tested here. ''' +import copy import sys import os import tempfile @@ -190,14 +191,8 @@ def test_save(self): class IdleConfTest(unittest.TestCase): """Test for idleConf""" - def new_config(self, _utest=False): - return config.IdleConf(_utest=_utest) - - def mock_config(self): - """Return a mocked idleConf - - Both default and user config used the same config-*.def - """ + @classmethod + def setUpClass(cls): conf = config.IdleConf(_utest=True) if __name__ != '__main__': idle_dir = os.path.dirname(__file__) @@ -208,6 +203,17 @@ def mock_config(self): conf.defaultCfg[ctype] = config.IdleConfParser(config_path) conf.userCfg[ctype] = config.IdleUserConfParser(config_path) conf.LoadCfgFiles() + cls.conf = conf + + def new_config(self, _utest=False): + return config.IdleConf(_utest=_utest) + + def mock_config(self): + """Return a mocked idleConf + + Both default and user config used the same config-*.def + """ + conf = copy.deepcopy(self.conf) return conf From 4ecfb1a07c14a1eb9d373eb904cc0730a238544f Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Tue, 18 Jul 2017 10:36:54 +0800 Subject: [PATCH 12/21] Add comment explain why put extra_help_source_list in same test --- Lib/idlelib/idle_test/test_config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index d0a96fef5d2cab..d8d8806569145d 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -515,6 +515,8 @@ def test_is_core_binding(self): self.assertFalse(conf.IsCoreBinding('not-exists')) def test_extra_help_source_list(self): + # Test GetExtraHelpSourceList and GetAllExtraHelpSourcesList in same + # place to prevent prepare input data twice. conf = self.mock_config() # Test default with no extra help source From d9b3702f45956fd4b81a4e0818e51dc514af2739 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Tue, 18 Jul 2017 10:42:18 +0800 Subject: [PATCH 13/21] Add disable extension to unittest --- Lib/idlelib/idle_test/test_config.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index d8d8806569145d..6fd52198e76ba9 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -410,6 +410,9 @@ def test_default_keys(self): def test_get_extensions(self): conf = self.mock_config() + # Add disable extensions + conf.SetOption('extensions', 'DISABLE', 'enable', 'False') + eq = self.assertEqual eq(conf.GetExtensions(), ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', @@ -418,7 +421,7 @@ def test_get_extensions(self): eq(conf.GetExtensions(active_only=False), ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', - 'ZoomHeight']) + 'ZoomHeight', 'DISABLE']) eq(conf.GetExtensions(editor_only=True), ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', @@ -426,6 +429,14 @@ def test_get_extensions(self): eq(conf.GetExtensions(shell_only=True), ['AutoComplete', 'AutoExpand', 'CallTips', 'FormatParagraph', 'ParenMatch', 'ZoomHeight']) + eq(conf.GetExtensions(active_only=False, editor_only=True), + ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', + 'FormatParagraph', 'ParenMatch', 'RstripExtension', + 'ScriptBinding', 'ZoomHeight', 'DISABLE']) + eq(conf.GetExtensions(active_only=False, shell_only=True), + ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', + 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', + 'ZoomHeight', 'DISABLE']) # Add user extensions conf.SetOption('extensions', 'Foobar', 'enable', 'True') @@ -433,6 +444,10 @@ def test_get_extensions(self): ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', 'ZoomHeight', 'Foobar']) # User extensions didn't sort + eq(conf.GetExtensions(active_only=False), + ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', + 'FormatParagraph', 'ParenMatch', 'RstripExtension', + 'ScriptBinding', 'ZoomHeight', 'DISABLE', 'Foobar']) def test_remove_key_bind_names(self): conf = self.mock_config() From 511b7265356a56792f37db6eec241fc99eb8ff32 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Tue, 18 Jul 2017 10:50:54 +0800 Subject: [PATCH 14/21] Add get_keybinding unittest --- Lib/idlelib/idle_test/test_config.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 6fd52198e76ba9..1e3e95d2225bc2 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -452,7 +452,7 @@ def test_get_extensions(self): def test_remove_key_bind_names(self): conf = self.mock_config() - self.assertEqual( + self.assertCountEqual( conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')), ['AutoComplete', 'AutoExpand', 'CallTips', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'RstripExtension', 'ScriptBinding', @@ -495,6 +495,23 @@ def test_get_extension_bindings(self): conf.defaultCfg['extensions'].set('Foobar_bindings', 'foobar', '') self.assertEqual(conf.GetExtensionBindings('Foobar'), {'<>': ['']}) + def test_get_keybinding(self): + conf = self.mock_config() + + eq = self.assertEqual + eq(conf.GetKeyBinding('IDLE Modern Unix', '<>'), + ['', '']) + eq(conf.GetKeyBinding('IDLE Classic Unix', '<>'), + ['', '']) + eq(conf.GetKeyBinding('IDLE Classic Windows', '<>'), + ['', '']) + eq(conf.GetKeyBinding('IDLE Classic Mac', '<>'), ['']) + eq(conf.GetKeyBinding('IDLE Classic OSX', '<>'), ['']) + + # Test keybinding not exists + eq(conf.GetKeyBinding('NOT EXISTS', '<>'), []) + eq(conf.GetKeyBinding('IDLE Modern Unix', 'NOT EXISTS'), []) + def test_get_current_keyset(self): current_platform = sys.platform conf = self.mock_config() From 49e05349bacc1bec1fc2fdceb9e4dc1be151cc78 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Tue, 18 Jul 2017 10:56:24 +0800 Subject: [PATCH 15/21] Add save_user_cfg_files unittest --- Lib/idlelib/idle_test/test_config.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 1e3e95d2225bc2..70e937ed6f2842 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -311,6 +311,13 @@ def test_load_cfg_files(self): eq(conf.userCfg['foo'].Get('Foo Bar', 'foo'), 'newbar') eq(conf.userCfg['foo'].GetOptionList('Foo Bar'), ['foo']) + def test_save_user_cfg_files(self): + conf = self.mock_config() + + with mock.patch('idlelib.config.IdleUserConfParser.Save') as m: + conf.SaveUserCfgFiles() + self.assertEqual(m.call_count, len(conf.userCfg)) + def test_get_option(self): conf = self.mock_config() From 7fba576e72e4c58ac318972fc2441b2c8a23e4e4 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Mon, 17 Jul 2017 23:36:11 -0400 Subject: [PATCH 16/21] News blurb --- Misc/NEWS.d/next/IDLE/2017-07-17-23-35-57.bpo-30917.hSiuuO.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/IDLE/2017-07-17-23-35-57.bpo-30917.hSiuuO.rst diff --git a/Misc/NEWS.d/next/IDLE/2017-07-17-23-35-57.bpo-30917.hSiuuO.rst b/Misc/NEWS.d/next/IDLE/2017-07-17-23-35-57.bpo-30917.hSiuuO.rst new file mode 100644 index 00000000000000..af25a10e96c25e --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2017-07-17-23-35-57.bpo-30917.hSiuuO.rst @@ -0,0 +1 @@ +Add tests for idlelib.config.IdleConf. Patch by Louie Lu. From 206ae1e7a223797572a67042a64e95a8b6282696 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Tue, 18 Jul 2017 01:18:55 -0400 Subject: [PATCH 17/21] supress warnintg, update coverage --- Lib/idlelib/config.py | 4 +++- Lib/idlelib/idle_test/test_config.py | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index b6e0ba9c9cf22f..34913faa702ad0 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -30,6 +30,7 @@ import sys from tkinter.font import Font +import idlelib class InvalidConfigType(Exception): pass class InvalidConfigSet(Exception): pass @@ -216,7 +217,8 @@ def GetUserCfgDir(self): except OSError: warn = ('\n Warning: unable to create user config directory\n' + userDir + '\n Check path and permissions.\n Exiting!\n') - print(warn, file=sys.stderr) + if not idlelib.testing: + print(warn, file=sys.stderr) raise SystemExit # TODO continue without userDIr instead of exit return userDir diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 70e937ed6f2842..f7559cfc33ba7b 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -1,9 +1,8 @@ '''Test idlelib.config. -Coverage: 46% (100% for IdleConfParser, IdleUserConfParser*, ConfigChanges). -* Except is OSError clause in Save method. -Much of IdleConf is exercised by ConfigDialog and test_configdialog, -but it should be tested here. +Coverage: 96% (100% for IdleConfParser, IdleUserConfParser*, ConfigChanges). +* Exception is OSError clause in Save method. +Much of IdleConf is also exercised by ConfigDialog and test_configdialog. ''' import copy import sys @@ -12,7 +11,9 @@ from test.support import captured_stderr, findfile import unittest from unittest import mock +import idlelib from idlelib import config +from idlelib.idle_test.mock_idle import Func # Tests should not depend on fortuitous user configurations. # They must not affect actual user .cfg files. @@ -28,9 +29,11 @@ def setUpModule(): idleConf.userCfg = testcfg + idlelib.testing = True def tearDownModule(): idleConf.userCfg = usercfg + idlelib.testing = False class IdleConfParserTest(unittest.TestCase): @@ -204,6 +207,12 @@ def setUpClass(cls): conf.userCfg[ctype] = config.IdleUserConfParser(config_path) conf.LoadCfgFiles() cls.conf = conf + cls.orig_warn = config._warn + config._warn = Func() + + @classmethod + def tearDownClass(cls): + config._warn = cls.orig_warn def new_config(self, _utest=False): return config.IdleConf(_utest=_utest) From c850b7f8d1708d4835a45c2ca1b769a31f6dcd7f Mon Sep 17 00:00:00 2001 From: terryjreedy Date: Tue, 18 Jul 2017 01:21:07 -0400 Subject: [PATCH 18/21] Update 2017-07-17-23-35-57.bpo-30917.hSiuuO.rst --- .../NEWS.d/next/IDLE/2017-07-17-23-35-57.bpo-30917.hSiuuO.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/IDLE/2017-07-17-23-35-57.bpo-30917.hSiuuO.rst b/Misc/NEWS.d/next/IDLE/2017-07-17-23-35-57.bpo-30917.hSiuuO.rst index af25a10e96c25e..c2cc9ccdd117a8 100644 --- a/Misc/NEWS.d/next/IDLE/2017-07-17-23-35-57.bpo-30917.hSiuuO.rst +++ b/Misc/NEWS.d/next/IDLE/2017-07-17-23-35-57.bpo-30917.hSiuuO.rst @@ -1 +1,3 @@ -Add tests for idlelib.config.IdleConf. Patch by Louie Lu. +Add tests for idlelib.config.IdleConf. +Increase coverage from 46% to 96%. +Patch by Louie Lu. From ef45060c6c5f9249ee41a81bb4b519f6dd381d8f Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Tue, 18 Jul 2017 15:09:42 +0800 Subject: [PATCH 19/21] Addressed terry's comments --- Lib/idlelib/idle_test/test_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index 70e937ed6f2842..c8b053b841c200 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -402,13 +402,13 @@ def test_default_keys(self): current_platform = sys.platform conf = self.new_config(_utest=True) - sys.platform = 'win' + sys.platform = 'win32' self.assertEqual(conf.default_keys(), 'IDLE Classic Windows') sys.platform = 'darwin' self.assertEqual(conf.default_keys(), 'IDLE Classic OSX') - sys.platform = 'linux' + sys.platform = 'some-linux' self.assertEqual(conf.default_keys(), 'IDLE Modern Unix') # Restore platform @@ -524,7 +524,7 @@ def test_get_current_keyset(self): conf = self.mock_config() # Ensure that platform isn't darwin - sys.platform = 'linux' + sys.platform = 'some-linux' self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys())) # This should not be the same, sicne replace Date: Tue, 18 Jul 2017 15:11:17 +0800 Subject: [PATCH 20/21] Put extra help source list input data reverse to check it is sorted --- Lib/idlelib/idle_test/test_config.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py index c8b053b841c200..a0da33bad2752a 100644 --- a/Lib/idlelib/idle_test/test_config.py +++ b/Lib/idlelib/idle_test/test_config.py @@ -568,12 +568,14 @@ def test_extra_help_source_list(self): conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user')) # Add help source to user config - conf.userCfg['main'].SetOption('HelpFiles', '1', 'IDLE;C:/Programs/Python36/Lib/idlelib/help.html') + conf.userCfg['main'].SetOption('HelpFiles', '4', 'Python;https://python.org') # This is bad input + conf.userCfg['main'].SetOption('HelpFiles', '3', 'Python:https://python.org') # This is bad input conf.userCfg['main'].SetOption('HelpFiles', '2', 'Pillow;https://pillow.readthedocs.io/en/latest/') - conf.userCfg['main'].SetOption('HelpFiles', '3', 'Python:https://python.org') + conf.userCfg['main'].SetOption('HelpFiles', '1', 'IDLE;C:/Programs/Python36/Lib/idlelib/help.html') self.assertEqual(conf.GetExtraHelpSourceList('user'), [('IDLE', 'C:/Programs/Python36/Lib/idlelib/help.html', '1'), - ('Pillow', 'https://pillow.readthedocs.io/en/latest/', '2')]) + ('Pillow', 'https://pillow.readthedocs.io/en/latest/', '2'), + ('Python', 'https://python.org', '4')]) self.assertCountEqual( conf.GetAllExtraHelpSourcesList(), conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user')) From c94d4b50ab6d7529a4d0059c0c05dfed12134352 Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Tue, 18 Jul 2017 15:13:20 +0800 Subject: [PATCH 21/21] Remove sorted in RemoveKeyBindNames --- Lib/idlelib/config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/idlelib/config.py b/Lib/idlelib/config.py index 34913faa702ad0..63d9a44234560b 100644 --- a/Lib/idlelib/config.py +++ b/Lib/idlelib/config.py @@ -466,8 +466,7 @@ def GetExtensions(self, active_only=True, def RemoveKeyBindNames(self, extnNameList): "Return extnNameList with keybinding section names removed." - return sorted( - [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]) + return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))] def GetExtnNameForEvent(self, virtualEvent): """Return the name of the extension binding virtualEvent, or None.