1"""Test config, coverage 93%. 2(100% for IdleConfParser, IdleUserConfParser*, ConfigChanges). 3* Exception is OSError clause in Save method. 4Much of IdleConf is also exercised by ConfigDialog and test_configdialog. 5""" 6from idlelib import config 7import sys 8import os 9import tempfile 10from test.support import captured_stderr, findfile 11import unittest 12from unittest import mock 13import idlelib 14from idlelib.idle_test.mock_idle import Func 15 16# Tests should not depend on fortuitous user configurations. 17# They must not affect actual user .cfg files. 18# Replace user parsers with empty parsers that cannot be saved 19# due to getting '' as the filename when created. 20 21idleConf = config.idleConf 22usercfg = idleConf.userCfg 23testcfg = {} 24usermain = testcfg['main'] = config.IdleUserConfParser('') 25userhigh = testcfg['highlight'] = config.IdleUserConfParser('') 26userkeys = testcfg['keys'] = config.IdleUserConfParser('') 27userextn = testcfg['extensions'] = config.IdleUserConfParser('') 28 29def setUpModule(): 30 idleConf.userCfg = testcfg 31 idlelib.testing = True 32 33def tearDownModule(): 34 idleConf.userCfg = usercfg 35 idlelib.testing = False 36 37 38class IdleConfParserTest(unittest.TestCase): 39 """Test that IdleConfParser works""" 40 41 config = """ 42 [one] 43 one = false 44 two = true 45 three = 10 46 47 [two] 48 one = a string 49 two = true 50 three = false 51 """ 52 53 def test_get(self): 54 parser = config.IdleConfParser('') 55 parser.read_string(self.config) 56 eq = self.assertEqual 57 58 # Test with type argument. 59 self.assertIs(parser.Get('one', 'one', type='bool'), False) 60 self.assertIs(parser.Get('one', 'two', type='bool'), True) 61 eq(parser.Get('one', 'three', type='int'), 10) 62 eq(parser.Get('two', 'one'), 'a string') 63 self.assertIs(parser.Get('two', 'two', type='bool'), True) 64 self.assertIs(parser.Get('two', 'three', type='bool'), False) 65 66 # Test without type should fallback to string. 67 eq(parser.Get('two', 'two'), 'true') 68 eq(parser.Get('two', 'three'), 'false') 69 70 # If option not exist, should return None, or default. 71 self.assertIsNone(parser.Get('not', 'exist')) 72 eq(parser.Get('not', 'exist', default='DEFAULT'), 'DEFAULT') 73 74 def test_get_option_list(self): 75 parser = config.IdleConfParser('') 76 parser.read_string(self.config) 77 get_list = parser.GetOptionList 78 self.assertCountEqual(get_list('one'), ['one', 'two', 'three']) 79 self.assertCountEqual(get_list('two'), ['one', 'two', 'three']) 80 self.assertEqual(get_list('not exist'), []) 81 82 def test_load_nothing(self): 83 parser = config.IdleConfParser('') 84 parser.Load() 85 self.assertEqual(parser.sections(), []) 86 87 def test_load_file(self): 88 # Borrow test/cfgparser.1 from test_configparser. 89 config_path = findfile('cfgparser.1') 90 parser = config.IdleConfParser(config_path) 91 parser.Load() 92 93 self.assertEqual(parser.Get('Foo Bar', 'foo'), 'newbar') 94 self.assertEqual(parser.GetOptionList('Foo Bar'), ['foo']) 95 96 97class IdleUserConfParserTest(unittest.TestCase): 98 """Test that IdleUserConfParser works""" 99 100 def new_parser(self, path=''): 101 return config.IdleUserConfParser(path) 102 103 def test_set_option(self): 104 parser = self.new_parser() 105 parser.add_section('Foo') 106 # Setting new option in existing section should return True. 107 self.assertTrue(parser.SetOption('Foo', 'bar', 'true')) 108 # Setting existing option with same value should return False. 109 self.assertFalse(parser.SetOption('Foo', 'bar', 'true')) 110 # Setting exiting option with new value should return True. 111 self.assertTrue(parser.SetOption('Foo', 'bar', 'false')) 112 self.assertEqual(parser.Get('Foo', 'bar'), 'false') 113 114 # Setting option in new section should create section and return True. 115 self.assertTrue(parser.SetOption('Bar', 'bar', 'true')) 116 self.assertCountEqual(parser.sections(), ['Bar', 'Foo']) 117 self.assertEqual(parser.Get('Bar', 'bar'), 'true') 118 119 def test_remove_option(self): 120 parser = self.new_parser() 121 parser.AddSection('Foo') 122 parser.SetOption('Foo', 'bar', 'true') 123 124 self.assertTrue(parser.RemoveOption('Foo', 'bar')) 125 self.assertFalse(parser.RemoveOption('Foo', 'bar')) 126 self.assertFalse(parser.RemoveOption('Not', 'Exist')) 127 128 def test_add_section(self): 129 parser = self.new_parser() 130 self.assertEqual(parser.sections(), []) 131 132 # Should not add duplicate section. 133 # Configparser raises DuplicateError, IdleParser not. 134 parser.AddSection('Foo') 135 parser.AddSection('Foo') 136 parser.AddSection('Bar') 137 self.assertCountEqual(parser.sections(), ['Bar', 'Foo']) 138 139 def test_remove_empty_sections(self): 140 parser = self.new_parser() 141 142 parser.AddSection('Foo') 143 parser.AddSection('Bar') 144 parser.SetOption('Idle', 'name', 'val') 145 self.assertCountEqual(parser.sections(), ['Bar', 'Foo', 'Idle']) 146 parser.RemoveEmptySections() 147 self.assertEqual(parser.sections(), ['Idle']) 148 149 def test_is_empty(self): 150 parser = self.new_parser() 151 152 parser.AddSection('Foo') 153 parser.AddSection('Bar') 154 self.assertTrue(parser.IsEmpty()) 155 self.assertEqual(parser.sections(), []) 156 157 parser.SetOption('Foo', 'bar', 'false') 158 parser.AddSection('Bar') 159 self.assertFalse(parser.IsEmpty()) 160 self.assertCountEqual(parser.sections(), ['Foo']) 161 162 def test_remove_file(self): 163 with tempfile.TemporaryDirectory() as tdir: 164 path = os.path.join(tdir, 'test.cfg') 165 parser = self.new_parser(path) 166 parser.RemoveFile() # Should not raise exception. 167 168 parser.AddSection('Foo') 169 parser.SetOption('Foo', 'bar', 'true') 170 parser.Save() 171 self.assertTrue(os.path.exists(path)) 172 parser.RemoveFile() 173 self.assertFalse(os.path.exists(path)) 174 175 def test_save(self): 176 with tempfile.TemporaryDirectory() as tdir: 177 path = os.path.join(tdir, 'test.cfg') 178 parser = self.new_parser(path) 179 parser.AddSection('Foo') 180 parser.SetOption('Foo', 'bar', 'true') 181 182 # Should save to path when config is not empty. 183 self.assertFalse(os.path.exists(path)) 184 parser.Save() 185 self.assertTrue(os.path.exists(path)) 186 187 # Should remove the file from disk when config is empty. 188 parser.remove_section('Foo') 189 parser.Save() 190 self.assertFalse(os.path.exists(path)) 191 192 193class IdleConfTest(unittest.TestCase): 194 """Test for idleConf""" 195 196 @classmethod 197 def setUpClass(cls): 198 cls.config_string = {} 199 200 conf = config.IdleConf(_utest=True) 201 if __name__ != '__main__': 202 idle_dir = os.path.dirname(__file__) 203 else: 204 idle_dir = os.path.abspath(sys.path[0]) 205 for ctype in conf.config_types: 206 config_path = os.path.join(idle_dir, '../config-%s.def' % ctype) 207 with open(config_path, 'r') as f: 208 cls.config_string[ctype] = f.read() 209 210 cls.orig_warn = config._warn 211 config._warn = Func() 212 213 @classmethod 214 def tearDownClass(cls): 215 config._warn = cls.orig_warn 216 217 def new_config(self, _utest=False): 218 return config.IdleConf(_utest=_utest) 219 220 def mock_config(self): 221 """Return a mocked idleConf 222 223 Both default and user config used the same config-*.def 224 """ 225 conf = config.IdleConf(_utest=True) 226 for ctype in conf.config_types: 227 conf.defaultCfg[ctype] = config.IdleConfParser('') 228 conf.defaultCfg[ctype].read_string(self.config_string[ctype]) 229 conf.userCfg[ctype] = config.IdleUserConfParser('') 230 conf.userCfg[ctype].read_string(self.config_string[ctype]) 231 232 return conf 233 234 @unittest.skipIf(sys.platform.startswith('win'), 'this is test for unix system') 235 def test_get_user_cfg_dir_unix(self): 236 "Test to get user config directory under unix" 237 conf = self.new_config(_utest=True) 238 239 # Check normal way should success 240 with mock.patch('os.path.expanduser', return_value='/home/foo'): 241 with mock.patch('os.path.exists', return_value=True): 242 self.assertEqual(conf.GetUserCfgDir(), '/home/foo/.idlerc') 243 244 # Check os.getcwd should success 245 with mock.patch('os.path.expanduser', return_value='~'): 246 with mock.patch('os.getcwd', return_value='/home/foo/cpython'): 247 with mock.patch('os.mkdir'): 248 self.assertEqual(conf.GetUserCfgDir(), 249 '/home/foo/cpython/.idlerc') 250 251 # Check user dir not exists and created failed should raise SystemExit 252 with mock.patch('os.path.join', return_value='/path/not/exists'): 253 with self.assertRaises(SystemExit): 254 with self.assertRaises(FileNotFoundError): 255 conf.GetUserCfgDir() 256 257 @unittest.skipIf(not sys.platform.startswith('win'), 'this is test for Windows system') 258 def test_get_user_cfg_dir_windows(self): 259 "Test to get user config directory under Windows" 260 conf = self.new_config(_utest=True) 261 262 # Check normal way should success 263 with mock.patch('os.path.expanduser', return_value='C:\\foo'): 264 with mock.patch('os.path.exists', return_value=True): 265 self.assertEqual(conf.GetUserCfgDir(), 'C:\\foo\\.idlerc') 266 267 # Check os.getcwd should success 268 with mock.patch('os.path.expanduser', return_value='~'): 269 with mock.patch('os.getcwd', return_value='C:\\foo\\cpython'): 270 with mock.patch('os.mkdir'): 271 self.assertEqual(conf.GetUserCfgDir(), 272 'C:\\foo\\cpython\\.idlerc') 273 274 # Check user dir not exists and created failed should raise SystemExit 275 with mock.patch('os.path.join', return_value='/path/not/exists'): 276 with self.assertRaises(SystemExit): 277 with self.assertRaises(FileNotFoundError): 278 conf.GetUserCfgDir() 279 280 def test_create_config_handlers(self): 281 conf = self.new_config(_utest=True) 282 283 # Mock out idle_dir 284 idle_dir = '/home/foo' 285 with mock.patch.dict({'__name__': '__foo__'}): 286 with mock.patch('os.path.dirname', return_value=idle_dir): 287 conf.CreateConfigHandlers() 288 289 # Check keys are equal 290 self.assertCountEqual(conf.defaultCfg.keys(), conf.config_types) 291 self.assertCountEqual(conf.userCfg.keys(), conf.config_types) 292 293 # Check conf parser are correct type 294 for default_parser in conf.defaultCfg.values(): 295 self.assertIsInstance(default_parser, config.IdleConfParser) 296 for user_parser in conf.userCfg.values(): 297 self.assertIsInstance(user_parser, config.IdleUserConfParser) 298 299 # Check config path are correct 300 for config_type, parser in conf.defaultCfg.items(): 301 self.assertEqual(parser.file, 302 os.path.join(idle_dir, 'config-%s.def' % config_type)) 303 for config_type, parser in conf.userCfg.items(): 304 self.assertEqual(parser.file, 305 os.path.join(conf.userdir, 'config-%s.cfg' % config_type)) 306 307 def test_load_cfg_files(self): 308 conf = self.new_config(_utest=True) 309 310 # Borrow test/cfgparser.1 from test_configparser. 311 config_path = findfile('cfgparser.1') 312 conf.defaultCfg['foo'] = config.IdleConfParser(config_path) 313 conf.userCfg['foo'] = config.IdleUserConfParser(config_path) 314 315 # Load all config from path 316 conf.LoadCfgFiles() 317 318 eq = self.assertEqual 319 320 # Check defaultCfg is loaded 321 eq(conf.defaultCfg['foo'].Get('Foo Bar', 'foo'), 'newbar') 322 eq(conf.defaultCfg['foo'].GetOptionList('Foo Bar'), ['foo']) 323 324 # Check userCfg is loaded 325 eq(conf.userCfg['foo'].Get('Foo Bar', 'foo'), 'newbar') 326 eq(conf.userCfg['foo'].GetOptionList('Foo Bar'), ['foo']) 327 328 def test_save_user_cfg_files(self): 329 conf = self.mock_config() 330 331 with mock.patch('idlelib.config.IdleUserConfParser.Save') as m: 332 conf.SaveUserCfgFiles() 333 self.assertEqual(m.call_count, len(conf.userCfg)) 334 335 def test_get_option(self): 336 conf = self.mock_config() 337 338 eq = self.assertEqual 339 eq(conf.GetOption('main', 'EditorWindow', 'width'), '80') 340 eq(conf.GetOption('main', 'EditorWindow', 'width', type='int'), 80) 341 with mock.patch('idlelib.config._warn') as _warn: 342 eq(conf.GetOption('main', 'EditorWindow', 'font', type='int'), None) 343 eq(conf.GetOption('main', 'EditorWindow', 'NotExists'), None) 344 eq(conf.GetOption('main', 'EditorWindow', 'NotExists', default='NE'), 'NE') 345 eq(_warn.call_count, 4) 346 347 def test_set_option(self): 348 conf = self.mock_config() 349 350 conf.SetOption('main', 'Foo', 'bar', 'newbar') 351 self.assertEqual(conf.GetOption('main', 'Foo', 'bar'), 'newbar') 352 353 def test_get_section_list(self): 354 conf = self.mock_config() 355 356 self.assertCountEqual( 357 conf.GetSectionList('default', 'main'), 358 ['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme', 359 'Keys', 'History', 'HelpFiles']) 360 self.assertCountEqual( 361 conf.GetSectionList('user', 'main'), 362 ['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme', 363 'Keys', 'History', 'HelpFiles']) 364 365 with self.assertRaises(config.InvalidConfigSet): 366 conf.GetSectionList('foobar', 'main') 367 with self.assertRaises(config.InvalidConfigType): 368 conf.GetSectionList('default', 'notexists') 369 370 def test_get_highlight(self): 371 conf = self.mock_config() 372 373 eq = self.assertEqual 374 eq(conf.GetHighlight('IDLE Classic', 'normal'), {'foreground': '#000000', 375 'background': '#ffffff'}) 376 eq(conf.GetHighlight('IDLE Classic', 'normal', 'fg'), '#000000') 377 eq(conf.GetHighlight('IDLE Classic', 'normal', 'bg'), '#ffffff') 378 with self.assertRaises(config.InvalidFgBg): 379 conf.GetHighlight('IDLE Classic', 'normal', 'fb') 380 381 # Test cursor (this background should be normal-background) 382 eq(conf.GetHighlight('IDLE Classic', 'cursor'), {'foreground': 'black', 383 'background': '#ffffff'}) 384 385 # Test get user themes 386 conf.SetOption('highlight', 'Foobar', 'normal-foreground', '#747474') 387 conf.SetOption('highlight', 'Foobar', 'normal-background', '#171717') 388 with mock.patch('idlelib.config._warn'): 389 eq(conf.GetHighlight('Foobar', 'normal'), {'foreground': '#747474', 390 'background': '#171717'}) 391 392 def test_get_theme_dict(self): 393 "XXX: NOT YET DONE" 394 conf = self.mock_config() 395 396 # These two should be the same 397 self.assertEqual( 398 conf.GetThemeDict('default', 'IDLE Classic'), 399 conf.GetThemeDict('user', 'IDLE Classic')) 400 401 with self.assertRaises(config.InvalidTheme): 402 conf.GetThemeDict('bad', 'IDLE Classic') 403 404 def test_get_current_theme_and_keys(self): 405 conf = self.mock_config() 406 407 self.assertEqual(conf.CurrentTheme(), conf.current_colors_and_keys('Theme')) 408 self.assertEqual(conf.CurrentKeys(), conf.current_colors_and_keys('Keys')) 409 410 def test_current_colors_and_keys(self): 411 conf = self.mock_config() 412 413 self.assertEqual(conf.current_colors_and_keys('Theme'), 'IDLE Classic') 414 415 def test_default_keys(self): 416 current_platform = sys.platform 417 conf = self.new_config(_utest=True) 418 419 sys.platform = 'win32' 420 self.assertEqual(conf.default_keys(), 'IDLE Classic Windows') 421 422 sys.platform = 'darwin' 423 self.assertEqual(conf.default_keys(), 'IDLE Classic OSX') 424 425 sys.platform = 'some-linux' 426 self.assertEqual(conf.default_keys(), 'IDLE Modern Unix') 427 428 # Restore platform 429 sys.platform = current_platform 430 431 def test_get_extensions(self): 432 userextn.read_string(''' 433 [ZzDummy] 434 enable = True 435 [DISABLE] 436 enable = False 437 ''') 438 eq = self.assertEqual 439 iGE = idleConf.GetExtensions 440 eq(iGE(shell_only=True), []) 441 eq(iGE(), ['ZzDummy']) 442 eq(iGE(editor_only=True), ['ZzDummy']) 443 eq(iGE(active_only=False), ['ZzDummy', 'DISABLE']) 444 eq(iGE(active_only=False, editor_only=True), ['ZzDummy', 'DISABLE']) 445 userextn.remove_section('ZzDummy') 446 userextn.remove_section('DISABLE') 447 448 449 def test_remove_key_bind_names(self): 450 conf = self.mock_config() 451 452 self.assertCountEqual( 453 conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')), 454 ['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'ZzDummy']) 455 456 def test_get_extn_name_for_event(self): 457 userextn.read_string(''' 458 [ZzDummy] 459 enable = True 460 ''') 461 eq = self.assertEqual 462 eq(idleConf.GetExtnNameForEvent('z-in'), 'ZzDummy') 463 eq(idleConf.GetExtnNameForEvent('z-out'), None) 464 userextn.remove_section('ZzDummy') 465 466 def test_get_extension_keys(self): 467 userextn.read_string(''' 468 [ZzDummy] 469 enable = True 470 ''') 471 self.assertEqual(idleConf.GetExtensionKeys('ZzDummy'), 472 {'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>']}) 473 userextn.remove_section('ZzDummy') 474# need option key test 475## key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>'] 476## eq(conf.GetExtensionKeys('ZoomHeight'), {'<<zoom-height>>': key}) 477 478 def test_get_extension_bindings(self): 479 userextn.read_string(''' 480 [ZzDummy] 481 enable = True 482 ''') 483 eq = self.assertEqual 484 iGEB = idleConf.GetExtensionBindings 485 eq(iGEB('NotExists'), {}) 486 expect = {'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>'], 487 '<<z-out>>': ['<Control-Shift-KeyRelease-Delete>']} 488 eq(iGEB('ZzDummy'), expect) 489 userextn.remove_section('ZzDummy') 490 491 def test_get_keybinding(self): 492 conf = self.mock_config() 493 494 eq = self.assertEqual 495 eq(conf.GetKeyBinding('IDLE Modern Unix', '<<copy>>'), 496 ['<Control-Shift-Key-C>', '<Control-Key-Insert>']) 497 eq(conf.GetKeyBinding('IDLE Classic Unix', '<<copy>>'), 498 ['<Alt-Key-w>', '<Meta-Key-w>']) 499 eq(conf.GetKeyBinding('IDLE Classic Windows', '<<copy>>'), 500 ['<Control-Key-c>', '<Control-Key-C>']) 501 eq(conf.GetKeyBinding('IDLE Classic Mac', '<<copy>>'), ['<Command-Key-c>']) 502 eq(conf.GetKeyBinding('IDLE Classic OSX', '<<copy>>'), ['<Command-Key-c>']) 503 504 # Test keybinding not exists 505 eq(conf.GetKeyBinding('NOT EXISTS', '<<copy>>'), []) 506 eq(conf.GetKeyBinding('IDLE Modern Unix', 'NOT EXISTS'), []) 507 508 def test_get_current_keyset(self): 509 current_platform = sys.platform 510 conf = self.mock_config() 511 512 # Ensure that platform isn't darwin 513 sys.platform = 'some-linux' 514 self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys())) 515 516 # This should not be the same, since replace <Alt- to <Option-. 517 # Above depended on config-extensions.def having Alt keys, 518 # which is no longer true. 519 # sys.platform = 'darwin' 520 # self.assertNotEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys())) 521 522 # Restore platform 523 sys.platform = current_platform 524 525 def test_get_keyset(self): 526 conf = self.mock_config() 527 528 # Conflic with key set, should be disable to '' 529 conf.defaultCfg['extensions'].add_section('Foobar') 530 conf.defaultCfg['extensions'].add_section('Foobar_cfgBindings') 531 conf.defaultCfg['extensions'].set('Foobar', 'enable', 'True') 532 conf.defaultCfg['extensions'].set('Foobar_cfgBindings', 'newfoo', '<Key-F3>') 533 self.assertEqual(conf.GetKeySet('IDLE Modern Unix')['<<newfoo>>'], '') 534 535 def test_is_core_binding(self): 536 # XXX: Should move out the core keys to config file or other place 537 conf = self.mock_config() 538 539 self.assertTrue(conf.IsCoreBinding('copy')) 540 self.assertTrue(conf.IsCoreBinding('cut')) 541 self.assertTrue(conf.IsCoreBinding('del-word-right')) 542 self.assertFalse(conf.IsCoreBinding('not-exists')) 543 544 def test_extra_help_source_list(self): 545 # Test GetExtraHelpSourceList and GetAllExtraHelpSourcesList in same 546 # place to prevent prepare input data twice. 547 conf = self.mock_config() 548 549 # Test default with no extra help source 550 self.assertEqual(conf.GetExtraHelpSourceList('default'), []) 551 self.assertEqual(conf.GetExtraHelpSourceList('user'), []) 552 with self.assertRaises(config.InvalidConfigSet): 553 self.assertEqual(conf.GetExtraHelpSourceList('bad'), []) 554 self.assertCountEqual( 555 conf.GetAllExtraHelpSourcesList(), 556 conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user')) 557 558 # Add help source to user config 559 conf.userCfg['main'].SetOption('HelpFiles', '4', 'Python;https://python.org') # This is bad input 560 conf.userCfg['main'].SetOption('HelpFiles', '3', 'Python:https://python.org') # This is bad input 561 conf.userCfg['main'].SetOption('HelpFiles', '2', 'Pillow;https://pillow.readthedocs.io/en/latest/') 562 conf.userCfg['main'].SetOption('HelpFiles', '1', 'IDLE;C:/Programs/Python36/Lib/idlelib/help.html') 563 self.assertEqual(conf.GetExtraHelpSourceList('user'), 564 [('IDLE', 'C:/Programs/Python36/Lib/idlelib/help.html', '1'), 565 ('Pillow', 'https://pillow.readthedocs.io/en/latest/', '2'), 566 ('Python', 'https://python.org', '4')]) 567 self.assertCountEqual( 568 conf.GetAllExtraHelpSourcesList(), 569 conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user')) 570 571 def test_get_font(self): 572 from test.support import requires 573 from tkinter import Tk 574 from tkinter.font import Font 575 conf = self.mock_config() 576 577 requires('gui') 578 root = Tk() 579 root.withdraw() 580 581 f = Font.actual(Font(name='TkFixedFont', exists=True, root=root)) 582 self.assertEqual( 583 conf.GetFont(root, 'main', 'EditorWindow'), 584 (f['family'], 10 if f['size'] <= 0 else f['size'], f['weight'])) 585 586 # Cleanup root 587 root.destroy() 588 del root 589 590 def test_get_core_keys(self): 591 conf = self.mock_config() 592 593 eq = self.assertEqual 594 eq(conf.GetCoreKeys()['<<center-insert>>'], ['<Control-l>']) 595 eq(conf.GetCoreKeys()['<<copy>>'], ['<Control-c>', '<Control-C>']) 596 eq(conf.GetCoreKeys()['<<history-next>>'], ['<Alt-n>']) 597 eq(conf.GetCoreKeys('IDLE Classic Windows')['<<center-insert>>'], 598 ['<Control-Key-l>', '<Control-Key-L>']) 599 eq(conf.GetCoreKeys('IDLE Classic OSX')['<<copy>>'], ['<Command-Key-c>']) 600 eq(conf.GetCoreKeys('IDLE Classic Unix')['<<history-next>>'], 601 ['<Alt-Key-n>', '<Meta-Key-n>']) 602 eq(conf.GetCoreKeys('IDLE Modern Unix')['<<history-next>>'], 603 ['<Alt-Key-n>', '<Meta-Key-n>']) 604 605 606class CurrentColorKeysTest(unittest.TestCase): 607 """ Test colorkeys function with user config [Theme] and [Keys] patterns. 608 609 colorkeys = config.IdleConf.current_colors_and_keys 610 Test all patterns written by IDLE and some errors 611 Item 'default' should really be 'builtin' (versus 'custom). 612 """ 613 colorkeys = idleConf.current_colors_and_keys 614 default_theme = 'IDLE Classic' 615 default_keys = idleConf.default_keys() 616 617 def test_old_builtin_theme(self): 618 # On initial installation, user main is blank. 619 self.assertEqual(self.colorkeys('Theme'), self.default_theme) 620 # For old default, name2 must be blank. 621 usermain.read_string(''' 622 [Theme] 623 default = True 624 ''') 625 # IDLE omits 'name' for default old builtin theme. 626 self.assertEqual(self.colorkeys('Theme'), self.default_theme) 627 # IDLE adds 'name' for non-default old builtin theme. 628 usermain['Theme']['name'] = 'IDLE New' 629 self.assertEqual(self.colorkeys('Theme'), 'IDLE New') 630 # Erroneous non-default old builtin reverts to default. 631 usermain['Theme']['name'] = 'non-existent' 632 self.assertEqual(self.colorkeys('Theme'), self.default_theme) 633 usermain.remove_section('Theme') 634 635 def test_new_builtin_theme(self): 636 # IDLE writes name2 for new builtins. 637 usermain.read_string(''' 638 [Theme] 639 default = True 640 name2 = IDLE Dark 641 ''') 642 self.assertEqual(self.colorkeys('Theme'), 'IDLE Dark') 643 # Leftover 'name', not removed, is ignored. 644 usermain['Theme']['name'] = 'IDLE New' 645 self.assertEqual(self.colorkeys('Theme'), 'IDLE Dark') 646 # Erroneous non-default new builtin reverts to default. 647 usermain['Theme']['name2'] = 'non-existent' 648 self.assertEqual(self.colorkeys('Theme'), self.default_theme) 649 usermain.remove_section('Theme') 650 651 def test_user_override_theme(self): 652 # Erroneous custom name (no definition) reverts to default. 653 usermain.read_string(''' 654 [Theme] 655 default = False 656 name = Custom Dark 657 ''') 658 self.assertEqual(self.colorkeys('Theme'), self.default_theme) 659 # Custom name is valid with matching Section name. 660 userhigh.read_string('[Custom Dark]\na=b') 661 self.assertEqual(self.colorkeys('Theme'), 'Custom Dark') 662 # Name2 is ignored. 663 usermain['Theme']['name2'] = 'non-existent' 664 self.assertEqual(self.colorkeys('Theme'), 'Custom Dark') 665 usermain.remove_section('Theme') 666 userhigh.remove_section('Custom Dark') 667 668 def test_old_builtin_keys(self): 669 # On initial installation, user main is blank. 670 self.assertEqual(self.colorkeys('Keys'), self.default_keys) 671 # For old default, name2 must be blank, name is always used. 672 usermain.read_string(''' 673 [Keys] 674 default = True 675 name = IDLE Classic Unix 676 ''') 677 self.assertEqual(self.colorkeys('Keys'), 'IDLE Classic Unix') 678 # Erroneous non-default old builtin reverts to default. 679 usermain['Keys']['name'] = 'non-existent' 680 self.assertEqual(self.colorkeys('Keys'), self.default_keys) 681 usermain.remove_section('Keys') 682 683 def test_new_builtin_keys(self): 684 # IDLE writes name2 for new builtins. 685 usermain.read_string(''' 686 [Keys] 687 default = True 688 name2 = IDLE Modern Unix 689 ''') 690 self.assertEqual(self.colorkeys('Keys'), 'IDLE Modern Unix') 691 # Leftover 'name', not removed, is ignored. 692 usermain['Keys']['name'] = 'IDLE Classic Unix' 693 self.assertEqual(self.colorkeys('Keys'), 'IDLE Modern Unix') 694 # Erroneous non-default new builtin reverts to default. 695 usermain['Keys']['name2'] = 'non-existent' 696 self.assertEqual(self.colorkeys('Keys'), self.default_keys) 697 usermain.remove_section('Keys') 698 699 def test_user_override_keys(self): 700 # Erroneous custom name (no definition) reverts to default. 701 usermain.read_string(''' 702 [Keys] 703 default = False 704 name = Custom Keys 705 ''') 706 self.assertEqual(self.colorkeys('Keys'), self.default_keys) 707 # Custom name is valid with matching Section name. 708 userkeys.read_string('[Custom Keys]\na=b') 709 self.assertEqual(self.colorkeys('Keys'), 'Custom Keys') 710 # Name2 is ignored. 711 usermain['Keys']['name2'] = 'non-existent' 712 self.assertEqual(self.colorkeys('Keys'), 'Custom Keys') 713 usermain.remove_section('Keys') 714 userkeys.remove_section('Custom Keys') 715 716 717class ChangesTest(unittest.TestCase): 718 719 empty = {'main':{}, 'highlight':{}, 'keys':{}, 'extensions':{}} 720 721 def load(self): # Test_add_option verifies that this works. 722 changes = self.changes 723 changes.add_option('main', 'Msec', 'mitem', 'mval') 724 changes.add_option('highlight', 'Hsec', 'hitem', 'hval') 725 changes.add_option('keys', 'Ksec', 'kitem', 'kval') 726 return changes 727 728 loaded = {'main': {'Msec': {'mitem': 'mval'}}, 729 'highlight': {'Hsec': {'hitem': 'hval'}}, 730 'keys': {'Ksec': {'kitem':'kval'}}, 731 'extensions': {}} 732 733 def setUp(self): 734 self.changes = config.ConfigChanges() 735 736 def test_init(self): 737 self.assertEqual(self.changes, self.empty) 738 739 def test_add_option(self): 740 changes = self.load() 741 self.assertEqual(changes, self.loaded) 742 changes.add_option('main', 'Msec', 'mitem', 'mval') 743 self.assertEqual(changes, self.loaded) 744 745 def test_save_option(self): # Static function does not touch changes. 746 save_option = self.changes.save_option 747 self.assertTrue(save_option('main', 'Indent', 'what', '0')) 748 self.assertFalse(save_option('main', 'Indent', 'what', '0')) 749 self.assertEqual(usermain['Indent']['what'], '0') 750 751 self.assertTrue(save_option('main', 'Indent', 'use-spaces', '0')) 752 self.assertEqual(usermain['Indent']['use-spaces'], '0') 753 self.assertTrue(save_option('main', 'Indent', 'use-spaces', '1')) 754 self.assertFalse(usermain.has_option('Indent', 'use-spaces')) 755 usermain.remove_section('Indent') 756 757 def test_save_added(self): 758 changes = self.load() 759 self.assertTrue(changes.save_all()) 760 self.assertEqual(usermain['Msec']['mitem'], 'mval') 761 self.assertEqual(userhigh['Hsec']['hitem'], 'hval') 762 self.assertEqual(userkeys['Ksec']['kitem'], 'kval') 763 changes.add_option('main', 'Msec', 'mitem', 'mval') 764 self.assertFalse(changes.save_all()) 765 usermain.remove_section('Msec') 766 userhigh.remove_section('Hsec') 767 userkeys.remove_section('Ksec') 768 769 def test_save_help(self): 770 # Any change to HelpFiles overwrites entire section. 771 changes = self.changes 772 changes.save_option('main', 'HelpFiles', 'IDLE', 'idledoc') 773 changes.add_option('main', 'HelpFiles', 'ELDI', 'codeldi') 774 changes.save_all() 775 self.assertFalse(usermain.has_option('HelpFiles', 'IDLE')) 776 self.assertTrue(usermain.has_option('HelpFiles', 'ELDI')) 777 778 def test_save_default(self): # Cover 2nd and 3rd false branches. 779 changes = self.changes 780 changes.add_option('main', 'Indent', 'use-spaces', '1') 781 # save_option returns False; cfg_type_changed remains False. 782 783 # TODO: test that save_all calls usercfg Saves. 784 785 def test_delete_section(self): 786 changes = self.load() 787 changes.delete_section('main', 'fake') # Test no exception. 788 self.assertEqual(changes, self.loaded) # Test nothing deleted. 789 for cfgtype, section in (('main', 'Msec'), ('keys', 'Ksec')): 790 testcfg[cfgtype].SetOption(section, 'name', 'value') 791 changes.delete_section(cfgtype, section) 792 with self.assertRaises(KeyError): 793 changes[cfgtype][section] # Test section gone from changes 794 testcfg[cfgtype][section] # and from mock userCfg. 795 # TODO test for save call. 796 797 def test_clear(self): 798 changes = self.load() 799 changes.clear() 800 self.assertEqual(changes, self.empty) 801 802 803class WarningTest(unittest.TestCase): 804 805 def test_warn(self): 806 Equal = self.assertEqual 807 config._warned = set() 808 with captured_stderr() as stderr: 809 config._warn('warning', 'key') 810 Equal(config._warned, {('warning','key')}) 811 Equal(stderr.getvalue(), 'warning'+'\n') 812 with captured_stderr() as stderr: 813 config._warn('warning', 'key') 814 Equal(stderr.getvalue(), '') 815 with captured_stderr() as stderr: 816 config._warn('warn2', 'yek') 817 Equal(config._warned, {('warning','key'), ('warn2','yek')}) 818 Equal(stderr.getvalue(), 'warn2'+'\n') 819 820 821if __name__ == '__main__': 822 unittest.main(verbosity=2) 823