Skip to content

Commit a9bf62c

Browse files
authored
[3.6] bpo-30899: Add unittests, 100% coverage, for IDLE's two ConfigParser subclasses. (GH-2662) (#2685)
Patch by Louie Lu. (cherry picked from commit 50c9435)
1 parent c017948 commit a9bf62c

File tree

4 files changed

+198
-30
lines changed

4 files changed

+198
-30
lines changed

Lib/idlelib/config.py

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -81,31 +81,6 @@ class IdleUserConfParser(IdleConfParser):
8181
IdleConfigParser specialised for user configuration handling.
8282
"""
8383

84-
def AddSection(self, section):
85-
"If section doesn't exist, add it."
86-
if not self.has_section(section):
87-
self.add_section(section)
88-
89-
def RemoveEmptySections(self):
90-
"Remove any sections that have no options."
91-
for section in self.sections():
92-
if not self.GetOptionList(section):
93-
self.remove_section(section)
94-
95-
def IsEmpty(self):
96-
"Return True if no sections after removing empty sections."
97-
self.RemoveEmptySections()
98-
return not self.sections()
99-
100-
def RemoveOption(self, section, option):
101-
"""Return True if option is removed from section, else False.
102-
103-
False if either section does not exist or did not have option.
104-
"""
105-
if self.has_section(section):
106-
return self.remove_option(section, option)
107-
return False
108-
10984
def SetOption(self, section, option, value):
11085
"""Return True if option is added or changed to value, else False.
11186
@@ -123,6 +98,31 @@ def SetOption(self, section, option, value):
12398
self.set(section, option, value)
12499
return True
125100

101+
def RemoveOption(self, section, option):
102+
"""Return True if option is removed from section, else False.
103+
104+
False if either section does not exist or did not have option.
105+
"""
106+
if self.has_section(section):
107+
return self.remove_option(section, option)
108+
return False
109+
110+
def AddSection(self, section):
111+
"If section doesn't exist, add it."
112+
if not self.has_section(section):
113+
self.add_section(section)
114+
115+
def RemoveEmptySections(self):
116+
"Remove any sections that have no options."
117+
for section in self.sections():
118+
if not self.GetOptionList(section):
119+
self.remove_section(section)
120+
121+
def IsEmpty(self):
122+
"Return True if no sections after removing empty sections."
123+
self.RemoveEmptySections()
124+
return not self.sections()
125+
126126
def RemoveFile(self):
127127
"Remove user config file self.file from disk if it exists."
128128
if os.path.exists(self.file):
@@ -829,9 +829,12 @@ def save_option(config_type, section, item, value):
829829
def save_all(self):
830830
"""Save configuration changes to the user config file.
831831
832-
Then clear self in preparation for additional changes.
832+
Clear self in preparation for additional changes.
833+
Return changed for testing.
833834
"""
834835
idleConf.userCfg['main'].Save()
836+
837+
changed = False
835838
for config_type in self:
836839
cfg_type_changed = False
837840
page = self[config_type]
@@ -844,12 +847,14 @@ def save_all(self):
844847
cfg_type_changed = True
845848
if cfg_type_changed:
846849
idleConf.userCfg[config_type].Save()
850+
changed = True
847851
for config_type in ['keys', 'highlight']:
848852
# Save these even if unchanged!
849853
idleConf.userCfg[config_type].Save()
850854
self.clear()
851855
# ConfigDialog caller must add the following call
852856
# self.save_all_changed_extensions() # Uses a different mechanism.
857+
return changed
853858

854859
def delete_section(self, config_type, section):
855860
"""Delete a section from self, userCfg, and file.

Lib/idlelib/idle_test/test_config.py

Lines changed: 165 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
'''Test idlelib.config.
22
3-
Much is tested by opening config dialog live or in test_configdialog.
4-
Coverage: 27%
3+
Coverage: 46% (100% for IdleConfParser, IdleUserConfParser*, ConfigChanges).
4+
* Except is OSError clause in Save method.
5+
Much of IdleConf is exercised by ConfigDialog and test_configdialog,
6+
but it should be tested here.
57
'''
6-
from test.support import captured_stderr
8+
import os
9+
import tempfile
10+
from test.support import captured_stderr, findfile
711
import unittest
812
from idlelib import config
913

@@ -26,6 +30,161 @@ def tearDownModule():
2630
idleConf.userCfg = usercfg
2731

2832

33+
class IdleConfParserTest(unittest.TestCase):
34+
"""Test that IdleConfParser works"""
35+
36+
config = """
37+
[one]
38+
one = false
39+
two = true
40+
three = 10
41+
42+
[two]
43+
one = a string
44+
two = true
45+
three = false
46+
"""
47+
48+
def test_get(self):
49+
parser = config.IdleConfParser('')
50+
parser.read_string(self.config)
51+
eq = self.assertEqual
52+
53+
# Test with type argument.
54+
self.assertIs(parser.Get('one', 'one', type='bool'), False)
55+
self.assertIs(parser.Get('one', 'two', type='bool'), True)
56+
eq(parser.Get('one', 'three', type='int'), 10)
57+
eq(parser.Get('two', 'one'), 'a string')
58+
self.assertIs(parser.Get('two', 'two', type='bool'), True)
59+
self.assertIs(parser.Get('two', 'three', type='bool'), False)
60+
61+
# Test without type should fallback to string.
62+
eq(parser.Get('two', 'two'), 'true')
63+
eq(parser.Get('two', 'three'), 'false')
64+
65+
# If option not exist, should return None, or default.
66+
self.assertIsNone(parser.Get('not', 'exist'))
67+
eq(parser.Get('not', 'exist', default='DEFAULT'), 'DEFAULT')
68+
69+
def test_get_option_list(self):
70+
parser = config.IdleConfParser('')
71+
parser.read_string(self.config)
72+
get_list = parser.GetOptionList
73+
self.assertCountEqual(get_list('one'), ['one', 'two', 'three'])
74+
self.assertCountEqual(get_list('two'), ['one', 'two', 'three'])
75+
self.assertEqual(get_list('not exist'), [])
76+
77+
def test_load_nothing(self):
78+
parser = config.IdleConfParser('')
79+
parser.Load()
80+
self.assertEqual(parser.sections(), [])
81+
82+
def test_load_file(self):
83+
# Borrow test/cfgparser.1 from test_configparser.
84+
config_path = findfile('cfgparser.1')
85+
parser = config.IdleConfParser(config_path)
86+
parser.Load()
87+
88+
self.assertEqual(parser.Get('Foo Bar', 'foo'), 'newbar')
89+
self.assertEqual(parser.GetOptionList('Foo Bar'), ['foo'])
90+
91+
92+
class IdleUserConfParserTest(unittest.TestCase):
93+
"""Test that IdleUserConfParser works"""
94+
95+
def new_parser(self, path=''):
96+
return config.IdleUserConfParser(path)
97+
98+
def test_set_option(self):
99+
parser = self.new_parser()
100+
parser.add_section('Foo')
101+
# Setting new option in existing section should return True.
102+
self.assertTrue(parser.SetOption('Foo', 'bar', 'true'))
103+
# Setting existing option with same value should return False.
104+
self.assertFalse(parser.SetOption('Foo', 'bar', 'true'))
105+
# Setting exiting option with new value should return True.
106+
self.assertTrue(parser.SetOption('Foo', 'bar', 'false'))
107+
self.assertEqual(parser.Get('Foo', 'bar'), 'false')
108+
109+
# Setting option in new section should create section and return True.
110+
self.assertTrue(parser.SetOption('Bar', 'bar', 'true'))
111+
self.assertCountEqual(parser.sections(), ['Bar', 'Foo'])
112+
self.assertEqual(parser.Get('Bar', 'bar'), 'true')
113+
114+
def test_remove_option(self):
115+
parser = self.new_parser()
116+
parser.AddSection('Foo')
117+
parser.SetOption('Foo', 'bar', 'true')
118+
119+
self.assertTrue(parser.RemoveOption('Foo', 'bar'))
120+
self.assertFalse(parser.RemoveOption('Foo', 'bar'))
121+
self.assertFalse(parser.RemoveOption('Not', 'Exist'))
122+
123+
def test_add_section(self):
124+
parser = self.new_parser()
125+
self.assertEqual(parser.sections(), [])
126+
127+
# Should not add duplicate section.
128+
# Configparser raises DuplicateError, IdleParser not.
129+
parser.AddSection('Foo')
130+
parser.AddSection('Foo')
131+
parser.AddSection('Bar')
132+
self.assertCountEqual(parser.sections(), ['Bar', 'Foo'])
133+
134+
def test_remove_empty_sections(self):
135+
parser = self.new_parser()
136+
137+
parser.AddSection('Foo')
138+
parser.AddSection('Bar')
139+
parser.SetOption('Idle', 'name', 'val')
140+
self.assertCountEqual(parser.sections(), ['Bar', 'Foo', 'Idle'])
141+
parser.RemoveEmptySections()
142+
self.assertEqual(parser.sections(), ['Idle'])
143+
144+
def test_is_empty(self):
145+
parser = self.new_parser()
146+
147+
parser.AddSection('Foo')
148+
parser.AddSection('Bar')
149+
self.assertTrue(parser.IsEmpty())
150+
self.assertEqual(parser.sections(), [])
151+
152+
parser.SetOption('Foo', 'bar', 'false')
153+
parser.AddSection('Bar')
154+
self.assertFalse(parser.IsEmpty())
155+
self.assertCountEqual(parser.sections(), ['Foo'])
156+
157+
def test_remove_file(self):
158+
with tempfile.TemporaryDirectory() as tdir:
159+
path = os.path.join(tdir, 'test.cfg')
160+
parser = self.new_parser(path)
161+
parser.RemoveFile() # Should not raise exception.
162+
163+
parser.AddSection('Foo')
164+
parser.SetOption('Foo', 'bar', 'true')
165+
parser.Save()
166+
self.assertTrue(os.path.exists(path))
167+
parser.RemoveFile()
168+
self.assertFalse(os.path.exists(path))
169+
170+
def test_save(self):
171+
with tempfile.TemporaryDirectory() as tdir:
172+
path = os.path.join(tdir, 'test.cfg')
173+
parser = self.new_parser(path)
174+
parser.AddSection('Foo')
175+
parser.SetOption('Foo', 'bar', 'true')
176+
177+
# Should save to path when config is not empty.
178+
self.assertFalse(os.path.exists(path))
179+
parser.Save()
180+
self.assertTrue(os.path.exists(path))
181+
182+
# Should remove the file from disk when config is empty.
183+
parser.remove_section('Foo')
184+
parser.Save()
185+
self.assertFalse(os.path.exists(path))
186+
187+
29188
class CurrentColorKeysTest(unittest.TestCase):
30189
""" Test colorkeys function with user config [Theme] and [Keys] patterns.
31190
@@ -179,10 +338,12 @@ def test_save_option(self): # Static function does not touch changes.
179338

180339
def test_save_added(self):
181340
changes = self.load()
182-
changes.save_all()
341+
self.assertTrue(changes.save_all())
183342
self.assertEqual(usermain['Msec']['mitem'], 'mval')
184343
self.assertEqual(userhigh['Hsec']['hitem'], 'hval')
185344
self.assertEqual(userkeys['Ksec']['kitem'], 'kval')
345+
changes.add_option('main', 'Msec', 'mitem', 'mval')
346+
self.assertFalse(changes.save_all())
186347
usermain.remove_section('Msec')
187348
userhigh.remove_section('Hsec')
188349
userkeys.remove_section('Ksec')

Lib/test/cfgparser.1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
# Also used by idlelib.test_idle.test_config.
12
[Foo Bar]
23
foo=newbar
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
IDLE: Add tests for ConfigParser subclasses in config. Patch by Louie Lu.

0 commit comments

Comments
 (0)