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