move sections
[python/dscho.git] / Lib / test / test_cfgparser.py
blob6c502878025551985c2fbbebe51c3c94e898764c
1 import ConfigParser
2 import StringIO
3 import unittest
4 import UserDict
6 from test import test_support
9 class SortedDict(UserDict.UserDict):
10 def items(self):
11 result = self.data.items()
12 result.sort()
13 return result
15 def keys(self):
16 result = self.data.keys()
17 result.sort()
18 return result
20 def values(self):
21 # XXX never used?
22 result = self.items()
23 return [i[1] for i in result]
25 def iteritems(self): return iter(self.items())
26 def iterkeys(self): return iter(self.keys())
27 __iter__ = iterkeys
28 def itervalues(self): return iter(self.values())
31 class TestCaseBase(unittest.TestCase):
32 allow_no_value = False
34 def newconfig(self, defaults=None):
35 if defaults is None:
36 self.cf = self.config_class(allow_no_value=self.allow_no_value)
37 else:
38 self.cf = self.config_class(defaults,
39 allow_no_value=self.allow_no_value)
40 return self.cf
42 def fromstring(self, string, defaults=None):
43 cf = self.newconfig(defaults)
44 sio = StringIO.StringIO(string)
45 cf.readfp(sio)
46 return cf
48 def test_basic(self):
49 config_string = (
50 "[Foo Bar]\n"
51 "foo=bar\n"
52 "[Spacey Bar]\n"
53 "foo = bar\n"
54 "[Commented Bar]\n"
55 "foo: bar ; comment\n"
56 "[Long Line]\n"
57 "foo: this line is much, much longer than my editor\n"
58 " likes it.\n"
59 "[Section\\with$weird%characters[\t]\n"
60 "[Internationalized Stuff]\n"
61 "foo[bg]: Bulgarian\n"
62 "foo=Default\n"
63 "foo[en]=English\n"
64 "foo[de]=Deutsch\n"
65 "[Spaces]\n"
66 "key with spaces : value\n"
67 "another with spaces = splat!\n"
69 if self.allow_no_value:
70 config_string += (
71 "[NoValue]\n"
72 "option-without-value\n"
75 cf = self.fromstring(config_string)
76 L = cf.sections()
77 L.sort()
78 E = [r'Commented Bar',
79 r'Foo Bar',
80 r'Internationalized Stuff',
81 r'Long Line',
82 r'Section\with$weird%characters[' '\t',
83 r'Spaces',
84 r'Spacey Bar',
86 if self.allow_no_value:
87 E.append(r'NoValue')
88 E.sort()
89 eq = self.assertEqual
90 eq(L, E)
92 # The use of spaces in the section names serves as a
93 # regression test for SourceForge bug #583248:
94 # http://www.python.org/sf/583248
95 eq(cf.get('Foo Bar', 'foo'), 'bar')
96 eq(cf.get('Spacey Bar', 'foo'), 'bar')
97 eq(cf.get('Commented Bar', 'foo'), 'bar')
98 eq(cf.get('Spaces', 'key with spaces'), 'value')
99 eq(cf.get('Spaces', 'another with spaces'), 'splat!')
100 if self.allow_no_value:
101 eq(cf.get('NoValue', 'option-without-value'), None)
103 self.assertNotIn('__name__', cf.options("Foo Bar"),
104 '__name__ "option" should not be exposed by the API!')
106 # Make sure the right things happen for remove_option();
107 # added to include check for SourceForge bug #123324:
108 self.assertTrue(cf.remove_option('Foo Bar', 'foo'),
109 "remove_option() failed to report existence of option")
110 self.assertFalse(cf.has_option('Foo Bar', 'foo'),
111 "remove_option() failed to remove option")
112 self.assertFalse(cf.remove_option('Foo Bar', 'foo'),
113 "remove_option() failed to report non-existence of option"
114 " that was removed")
116 self.assertRaises(ConfigParser.NoSectionError,
117 cf.remove_option, 'No Such Section', 'foo')
119 eq(cf.get('Long Line', 'foo'),
120 'this line is much, much longer than my editor\nlikes it.')
122 def test_case_sensitivity(self):
123 cf = self.newconfig()
124 cf.add_section("A")
125 cf.add_section("a")
126 L = cf.sections()
127 L.sort()
128 eq = self.assertEqual
129 eq(L, ["A", "a"])
130 cf.set("a", "B", "value")
131 eq(cf.options("a"), ["b"])
132 eq(cf.get("a", "b"), "value",
133 "could not locate option, expecting case-insensitive option names")
134 self.assertTrue(cf.has_option("a", "b"))
135 cf.set("A", "A-B", "A-B value")
136 for opt in ("a-b", "A-b", "a-B", "A-B"):
137 self.assertTrue(
138 cf.has_option("A", opt),
139 "has_option() returned false for option which should exist")
140 eq(cf.options("A"), ["a-b"])
141 eq(cf.options("a"), ["b"])
142 cf.remove_option("a", "B")
143 eq(cf.options("a"), [])
145 # SF bug #432369:
146 cf = self.fromstring(
147 "[MySection]\nOption: first line\n\tsecond line\n")
148 eq(cf.options("MySection"), ["option"])
149 eq(cf.get("MySection", "Option"), "first line\nsecond line")
151 # SF bug #561822:
152 cf = self.fromstring("[section]\nnekey=nevalue\n",
153 defaults={"key":"value"})
154 self.assertTrue(cf.has_option("section", "Key"))
157 def test_default_case_sensitivity(self):
158 cf = self.newconfig({"foo": "Bar"})
159 self.assertEqual(
160 cf.get("DEFAULT", "Foo"), "Bar",
161 "could not locate option, expecting case-insensitive option names")
162 cf = self.newconfig({"Foo": "Bar"})
163 self.assertEqual(
164 cf.get("DEFAULT", "Foo"), "Bar",
165 "could not locate option, expecting case-insensitive defaults")
167 def test_parse_errors(self):
168 self.newconfig()
169 self.parse_error(ConfigParser.ParsingError,
170 "[Foo]\n extra-spaces: splat\n")
171 self.parse_error(ConfigParser.ParsingError,
172 "[Foo]\n extra-spaces= splat\n")
173 self.parse_error(ConfigParser.ParsingError,
174 "[Foo]\n:value-without-option-name\n")
175 self.parse_error(ConfigParser.ParsingError,
176 "[Foo]\n=value-without-option-name\n")
177 self.parse_error(ConfigParser.MissingSectionHeaderError,
178 "No Section!\n")
180 def parse_error(self, exc, src):
181 sio = StringIO.StringIO(src)
182 self.assertRaises(exc, self.cf.readfp, sio)
184 def test_query_errors(self):
185 cf = self.newconfig()
186 self.assertEqual(cf.sections(), [],
187 "new ConfigParser should have no defined sections")
188 self.assertFalse(cf.has_section("Foo"),
189 "new ConfigParser should have no acknowledged sections")
190 self.assertRaises(ConfigParser.NoSectionError,
191 cf.options, "Foo")
192 self.assertRaises(ConfigParser.NoSectionError,
193 cf.set, "foo", "bar", "value")
194 self.get_error(ConfigParser.NoSectionError, "foo", "bar")
195 cf.add_section("foo")
196 self.get_error(ConfigParser.NoOptionError, "foo", "bar")
198 def get_error(self, exc, section, option):
199 try:
200 self.cf.get(section, option)
201 except exc, e:
202 return e
203 else:
204 self.fail("expected exception type %s.%s"
205 % (exc.__module__, exc.__name__))
207 def test_boolean(self):
208 cf = self.fromstring(
209 "[BOOLTEST]\n"
210 "T1=1\n"
211 "T2=TRUE\n"
212 "T3=True\n"
213 "T4=oN\n"
214 "T5=yes\n"
215 "F1=0\n"
216 "F2=FALSE\n"
217 "F3=False\n"
218 "F4=oFF\n"
219 "F5=nO\n"
220 "E1=2\n"
221 "E2=foo\n"
222 "E3=-1\n"
223 "E4=0.1\n"
224 "E5=FALSE AND MORE"
226 for x in range(1, 5):
227 self.assertTrue(cf.getboolean('BOOLTEST', 't%d' % x))
228 self.assertFalse(cf.getboolean('BOOLTEST', 'f%d' % x))
229 self.assertRaises(ValueError,
230 cf.getboolean, 'BOOLTEST', 'e%d' % x)
232 def test_weird_errors(self):
233 cf = self.newconfig()
234 cf.add_section("Foo")
235 self.assertRaises(ConfigParser.DuplicateSectionError,
236 cf.add_section, "Foo")
238 def test_write(self):
239 config_string = (
240 "[Long Line]\n"
241 "foo: this line is much, much longer than my editor\n"
242 " likes it.\n"
243 "[DEFAULT]\n"
244 "foo: another very\n"
245 " long line\n"
247 if self.allow_no_value:
248 config_string += (
249 "[Valueless]\n"
250 "option-without-value\n"
253 cf = self.fromstring(config_string)
254 output = StringIO.StringIO()
255 cf.write(output)
256 expect_string = (
257 "[DEFAULT]\n"
258 "foo = another very\n"
259 "\tlong line\n"
260 "\n"
261 "[Long Line]\n"
262 "foo = this line is much, much longer than my editor\n"
263 "\tlikes it.\n"
264 "\n"
266 if self.allow_no_value:
267 expect_string += (
268 "[Valueless]\n"
269 "option-without-value\n"
270 "\n"
272 self.assertEqual(output.getvalue(), expect_string)
274 def test_set_string_types(self):
275 cf = self.fromstring("[sect]\n"
276 "option1=foo\n")
277 # Check that we don't get an exception when setting values in
278 # an existing section using strings:
279 class mystr(str):
280 pass
281 cf.set("sect", "option1", "splat")
282 cf.set("sect", "option1", mystr("splat"))
283 cf.set("sect", "option2", "splat")
284 cf.set("sect", "option2", mystr("splat"))
285 try:
286 unicode
287 except NameError:
288 pass
289 else:
290 cf.set("sect", "option1", unicode("splat"))
291 cf.set("sect", "option2", unicode("splat"))
293 def test_read_returns_file_list(self):
294 file1 = test_support.findfile("cfgparser.1")
295 # check when we pass a mix of readable and non-readable files:
296 cf = self.newconfig()
297 parsed_files = cf.read([file1, "nonexistent-file"])
298 self.assertEqual(parsed_files, [file1])
299 self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
300 # check when we pass only a filename:
301 cf = self.newconfig()
302 parsed_files = cf.read(file1)
303 self.assertEqual(parsed_files, [file1])
304 self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
305 # check when we pass only missing files:
306 cf = self.newconfig()
307 parsed_files = cf.read(["nonexistent-file"])
308 self.assertEqual(parsed_files, [])
309 # check when we pass no files:
310 cf = self.newconfig()
311 parsed_files = cf.read([])
312 self.assertEqual(parsed_files, [])
314 # shared by subclasses
315 def get_interpolation_config(self):
316 return self.fromstring(
317 "[Foo]\n"
318 "bar=something %(with1)s interpolation (1 step)\n"
319 "bar9=something %(with9)s lots of interpolation (9 steps)\n"
320 "bar10=something %(with10)s lots of interpolation (10 steps)\n"
321 "bar11=something %(with11)s lots of interpolation (11 steps)\n"
322 "with11=%(with10)s\n"
323 "with10=%(with9)s\n"
324 "with9=%(with8)s\n"
325 "with8=%(With7)s\n"
326 "with7=%(WITH6)s\n"
327 "with6=%(with5)s\n"
328 "With5=%(with4)s\n"
329 "WITH4=%(with3)s\n"
330 "with3=%(with2)s\n"
331 "with2=%(with1)s\n"
332 "with1=with\n"
333 "\n"
334 "[Mutual Recursion]\n"
335 "foo=%(bar)s\n"
336 "bar=%(foo)s\n"
337 "\n"
338 "[Interpolation Error]\n"
339 "name=%(reference)s\n",
340 # no definition for 'reference'
341 defaults={"getname": "%(__name__)s"})
343 def check_items_config(self, expected):
344 cf = self.fromstring(
345 "[section]\n"
346 "name = value\n"
347 "key: |%(name)s| \n"
348 "getdefault: |%(default)s|\n"
349 "getname: |%(__name__)s|",
350 defaults={"default": "<default>"})
351 L = list(cf.items("section"))
352 L.sort()
353 self.assertEqual(L, expected)
356 class ConfigParserTestCase(TestCaseBase):
357 config_class = ConfigParser.ConfigParser
359 def test_interpolation(self):
360 cf = self.get_interpolation_config()
361 eq = self.assertEqual
362 eq(cf.get("Foo", "getname"), "Foo")
363 eq(cf.get("Foo", "bar"), "something with interpolation (1 step)")
364 eq(cf.get("Foo", "bar9"),
365 "something with lots of interpolation (9 steps)")
366 eq(cf.get("Foo", "bar10"),
367 "something with lots of interpolation (10 steps)")
368 self.get_error(ConfigParser.InterpolationDepthError, "Foo", "bar11")
370 def test_interpolation_missing_value(self):
371 self.get_interpolation_config()
372 e = self.get_error(ConfigParser.InterpolationError,
373 "Interpolation Error", "name")
374 self.assertEqual(e.reference, "reference")
375 self.assertEqual(e.section, "Interpolation Error")
376 self.assertEqual(e.option, "name")
378 def test_items(self):
379 self.check_items_config([('default', '<default>'),
380 ('getdefault', '|<default>|'),
381 ('getname', '|section|'),
382 ('key', '|value|'),
383 ('name', 'value')])
385 def test_set_nonstring_types(self):
386 cf = self.newconfig()
387 cf.add_section('non-string')
388 cf.set('non-string', 'int', 1)
389 cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13, '%('])
390 cf.set('non-string', 'dict', {'pi': 3.14159, '%(': 1,
391 '%(list)': '%(list)'})
392 cf.set('non-string', 'string_with_interpolation', '%(list)s')
393 self.assertEqual(cf.get('non-string', 'int', raw=True), 1)
394 self.assertRaises(TypeError, cf.get, 'non-string', 'int')
395 self.assertEqual(cf.get('non-string', 'list', raw=True),
396 [0, 1, 1, 2, 3, 5, 8, 13, '%('])
397 self.assertRaises(TypeError, cf.get, 'non-string', 'list')
398 self.assertEqual(cf.get('non-string', 'dict', raw=True),
399 {'pi': 3.14159, '%(': 1, '%(list)': '%(list)'})
400 self.assertRaises(TypeError, cf.get, 'non-string', 'dict')
401 self.assertEqual(cf.get('non-string', 'string_with_interpolation',
402 raw=True), '%(list)s')
403 self.assertRaises(ValueError, cf.get, 'non-string',
404 'string_with_interpolation', raw=False)
407 class RawConfigParserTestCase(TestCaseBase):
408 config_class = ConfigParser.RawConfigParser
410 def test_interpolation(self):
411 cf = self.get_interpolation_config()
412 eq = self.assertEqual
413 eq(cf.get("Foo", "getname"), "%(__name__)s")
414 eq(cf.get("Foo", "bar"),
415 "something %(with1)s interpolation (1 step)")
416 eq(cf.get("Foo", "bar9"),
417 "something %(with9)s lots of interpolation (9 steps)")
418 eq(cf.get("Foo", "bar10"),
419 "something %(with10)s lots of interpolation (10 steps)")
420 eq(cf.get("Foo", "bar11"),
421 "something %(with11)s lots of interpolation (11 steps)")
423 def test_items(self):
424 self.check_items_config([('default', '<default>'),
425 ('getdefault', '|%(default)s|'),
426 ('getname', '|%(__name__)s|'),
427 ('key', '|%(name)s|'),
428 ('name', 'value')])
430 def test_set_nonstring_types(self):
431 cf = self.newconfig()
432 cf.add_section('non-string')
433 cf.set('non-string', 'int', 1)
434 cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13])
435 cf.set('non-string', 'dict', {'pi': 3.14159})
436 self.assertEqual(cf.get('non-string', 'int'), 1)
437 self.assertEqual(cf.get('non-string', 'list'),
438 [0, 1, 1, 2, 3, 5, 8, 13])
439 self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159})
442 class SafeConfigParserTestCase(ConfigParserTestCase):
443 config_class = ConfigParser.SafeConfigParser
445 def test_safe_interpolation(self):
446 # See http://www.python.org/sf/511737
447 cf = self.fromstring("[section]\n"
448 "option1=xxx\n"
449 "option2=%(option1)s/xxx\n"
450 "ok=%(option1)s/%%s\n"
451 "not_ok=%(option2)s/%%s")
452 self.assertEqual(cf.get("section", "ok"), "xxx/%s")
453 self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s")
455 def test_set_malformatted_interpolation(self):
456 cf = self.fromstring("[sect]\n"
457 "option1=foo\n")
459 self.assertEqual(cf.get('sect', "option1"), "foo")
461 self.assertRaises(ValueError, cf.set, "sect", "option1", "%foo")
462 self.assertRaises(ValueError, cf.set, "sect", "option1", "foo%")
463 self.assertRaises(ValueError, cf.set, "sect", "option1", "f%oo")
465 self.assertEqual(cf.get('sect', "option1"), "foo")
467 # bug #5741: double percents are *not* malformed
468 cf.set("sect", "option2", "foo%%bar")
469 self.assertEqual(cf.get("sect", "option2"), "foo%bar")
471 def test_set_nonstring_types(self):
472 cf = self.fromstring("[sect]\n"
473 "option1=foo\n")
474 # Check that we get a TypeError when setting non-string values
475 # in an existing section:
476 self.assertRaises(TypeError, cf.set, "sect", "option1", 1)
477 self.assertRaises(TypeError, cf.set, "sect", "option1", 1.0)
478 self.assertRaises(TypeError, cf.set, "sect", "option1", object())
479 self.assertRaises(TypeError, cf.set, "sect", "option2", 1)
480 self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0)
481 self.assertRaises(TypeError, cf.set, "sect", "option2", object())
483 def test_add_section_default_1(self):
484 cf = self.newconfig()
485 self.assertRaises(ValueError, cf.add_section, "default")
487 def test_add_section_default_2(self):
488 cf = self.newconfig()
489 self.assertRaises(ValueError, cf.add_section, "DEFAULT")
492 class SafeConfigParserTestCaseNoValue(SafeConfigParserTestCase):
493 allow_no_value = True
496 class SortedTestCase(RawConfigParserTestCase):
497 def newconfig(self, defaults=None):
498 self.cf = self.config_class(defaults=defaults, dict_type=SortedDict)
499 return self.cf
501 def test_sorted(self):
502 self.fromstring("[b]\n"
503 "o4=1\n"
504 "o3=2\n"
505 "o2=3\n"
506 "o1=4\n"
507 "[a]\n"
508 "k=v\n")
509 output = StringIO.StringIO()
510 self.cf.write(output)
511 self.assertEquals(output.getvalue(),
512 "[a]\n"
513 "k = v\n\n"
514 "[b]\n"
515 "o1 = 4\n"
516 "o2 = 3\n"
517 "o3 = 2\n"
518 "o4 = 1\n\n")
521 def test_main():
522 test_support.run_unittest(
523 ConfigParserTestCase,
524 RawConfigParserTestCase,
525 SafeConfigParserTestCase,
526 SortedTestCase,
527 SafeConfigParserTestCaseNoValue,
531 if __name__ == "__main__":
532 test_main()