1import collections
2import configparser
3import io
4import os
5import pathlib
6import textwrap
7import unittest
8import warnings
9
10from test import support
11
12
13class SortedDict(collections.UserDict):
14
15    def items(self):
16        return sorted(self.data.items())
17
18    def keys(self):
19        return sorted(self.data.keys())
20
21    def values(self):
22        return [i[1] for i in self.items()]
23
24    def iteritems(self):
25        return iter(self.items())
26
27    def iterkeys(self):
28        return iter(self.keys())
29
30    def itervalues(self):
31        return iter(self.values())
32
33    __iter__ = iterkeys
34
35
36class CfgParserTestCaseClass:
37    allow_no_value = False
38    delimiters = ('=', ':')
39    comment_prefixes = (';', '#')
40    inline_comment_prefixes = (';', '#')
41    empty_lines_in_values = True
42    dict_type = configparser._default_dict
43    strict = False
44    default_section = configparser.DEFAULTSECT
45    interpolation = configparser._UNSET
46
47    def newconfig(self, defaults=None):
48        arguments = dict(
49            defaults=defaults,
50            allow_no_value=self.allow_no_value,
51            delimiters=self.delimiters,
52            comment_prefixes=self.comment_prefixes,
53            inline_comment_prefixes=self.inline_comment_prefixes,
54            empty_lines_in_values=self.empty_lines_in_values,
55            dict_type=self.dict_type,
56            strict=self.strict,
57            default_section=self.default_section,
58            interpolation=self.interpolation,
59        )
60        instance = self.config_class(**arguments)
61        return instance
62
63    def fromstring(self, string, defaults=None):
64        cf = self.newconfig(defaults)
65        cf.read_string(string)
66        return cf
67
68
69class BasicTestCase(CfgParserTestCaseClass):
70
71    def basic_test(self, cf):
72        E = ['Commented Bar',
73             'Foo Bar',
74             'Internationalized Stuff',
75             'Long Line',
76             'Section\\with$weird%characters[\t',
77             'Spaces',
78             'Spacey Bar',
79             'Spacey Bar From The Beginning',
80             'Types',
81             ]
82
83        if self.allow_no_value:
84            E.append('NoValue')
85        E.sort()
86        F = [('baz', 'qwe'), ('foo', 'bar3')]
87
88        # API access
89        L = cf.sections()
90        L.sort()
91        eq = self.assertEqual
92        eq(L, E)
93        L = cf.items('Spacey Bar From The Beginning')
94        L.sort()
95        eq(L, F)
96
97        # mapping access
98        L = [section for section in cf]
99        L.sort()
100        E.append(self.default_section)
101        E.sort()
102        eq(L, E)
103        L = cf['Spacey Bar From The Beginning'].items()
104        L = sorted(list(L))
105        eq(L, F)
106        L = cf.items()
107        L = sorted(list(L))
108        self.assertEqual(len(L), len(E))
109        for name, section in L:
110            eq(name, section.name)
111        eq(cf.defaults(), cf[self.default_section])
112
113        # The use of spaces in the section names serves as a
114        # regression test for SourceForge bug #583248:
115        # http://www.python.org/sf/583248
116
117        # API access
118        eq(cf.get('Foo Bar', 'foo'), 'bar1')
119        eq(cf.get('Spacey Bar', 'foo'), 'bar2')
120        eq(cf.get('Spacey Bar From The Beginning', 'foo'), 'bar3')
121        eq(cf.get('Spacey Bar From The Beginning', 'baz'), 'qwe')
122        eq(cf.get('Commented Bar', 'foo'), 'bar4')
123        eq(cf.get('Commented Bar', 'baz'), 'qwe')
124        eq(cf.get('Spaces', 'key with spaces'), 'value')
125        eq(cf.get('Spaces', 'another with spaces'), 'splat!')
126        eq(cf.getint('Types', 'int'), 42)
127        eq(cf.get('Types', 'int'), "42")
128        self.assertAlmostEqual(cf.getfloat('Types', 'float'), 0.44)
129        eq(cf.get('Types', 'float'), "0.44")
130        eq(cf.getboolean('Types', 'boolean'), False)
131        eq(cf.get('Types', '123'), 'strange but acceptable')
132        if self.allow_no_value:
133            eq(cf.get('NoValue', 'option-without-value'), None)
134
135        # test vars= and fallback=
136        eq(cf.get('Foo Bar', 'foo', fallback='baz'), 'bar1')
137        eq(cf.get('Foo Bar', 'foo', vars={'foo': 'baz'}), 'baz')
138        with self.assertRaises(configparser.NoSectionError):
139            cf.get('No Such Foo Bar', 'foo')
140        with self.assertRaises(configparser.NoOptionError):
141            cf.get('Foo Bar', 'no-such-foo')
142        eq(cf.get('No Such Foo Bar', 'foo', fallback='baz'), 'baz')
143        eq(cf.get('Foo Bar', 'no-such-foo', fallback='baz'), 'baz')
144        eq(cf.get('Spacey Bar', 'foo', fallback=None), 'bar2')
145        eq(cf.get('No Such Spacey Bar', 'foo', fallback=None), None)
146        eq(cf.getint('Types', 'int', fallback=18), 42)
147        eq(cf.getint('Types', 'no-such-int', fallback=18), 18)
148        eq(cf.getint('Types', 'no-such-int', fallback="18"), "18") # sic!
149        with self.assertRaises(configparser.NoOptionError):
150            cf.getint('Types', 'no-such-int')
151        self.assertAlmostEqual(cf.getfloat('Types', 'float',
152                                           fallback=0.0), 0.44)
153        self.assertAlmostEqual(cf.getfloat('Types', 'no-such-float',
154                                           fallback=0.0), 0.0)
155        eq(cf.getfloat('Types', 'no-such-float', fallback="0.0"), "0.0") # sic!
156        with self.assertRaises(configparser.NoOptionError):
157            cf.getfloat('Types', 'no-such-float')
158        eq(cf.getboolean('Types', 'boolean', fallback=True), False)
159        eq(cf.getboolean('Types', 'no-such-boolean', fallback="yes"),
160           "yes") # sic!
161        eq(cf.getboolean('Types', 'no-such-boolean', fallback=True), True)
162        with self.assertRaises(configparser.NoOptionError):
163            cf.getboolean('Types', 'no-such-boolean')
164        eq(cf.getboolean('No Such Types', 'boolean', fallback=True), True)
165        if self.allow_no_value:
166            eq(cf.get('NoValue', 'option-without-value', fallback=False), None)
167            eq(cf.get('NoValue', 'no-such-option-without-value',
168                      fallback=False), False)
169
170        # mapping access
171        eq(cf['Foo Bar']['foo'], 'bar1')
172        eq(cf['Spacey Bar']['foo'], 'bar2')
173        section = cf['Spacey Bar From The Beginning']
174        eq(section.name, 'Spacey Bar From The Beginning')
175        self.assertIs(section.parser, cf)
176        with self.assertRaises(AttributeError):
177            section.name = 'Name is read-only'
178        with self.assertRaises(AttributeError):
179            section.parser = 'Parser is read-only'
180        eq(section['foo'], 'bar3')
181        eq(section['baz'], 'qwe')
182        eq(cf['Commented Bar']['foo'], 'bar4')
183        eq(cf['Commented Bar']['baz'], 'qwe')
184        eq(cf['Spaces']['key with spaces'], 'value')
185        eq(cf['Spaces']['another with spaces'], 'splat!')
186        eq(cf['Long Line']['foo'],
187           'this line is much, much longer than my editor\nlikes it.')
188        if self.allow_no_value:
189            eq(cf['NoValue']['option-without-value'], None)
190        # test vars= and fallback=
191        eq(cf['Foo Bar'].get('foo', 'baz'), 'bar1')
192        eq(cf['Foo Bar'].get('foo', fallback='baz'), 'bar1')
193        eq(cf['Foo Bar'].get('foo', vars={'foo': 'baz'}), 'baz')
194        with self.assertRaises(KeyError):
195            cf['No Such Foo Bar']['foo']
196        with self.assertRaises(KeyError):
197            cf['Foo Bar']['no-such-foo']
198        with self.assertRaises(KeyError):
199            cf['No Such Foo Bar'].get('foo', fallback='baz')
200        eq(cf['Foo Bar'].get('no-such-foo', 'baz'), 'baz')
201        eq(cf['Foo Bar'].get('no-such-foo', fallback='baz'), 'baz')
202        eq(cf['Foo Bar'].get('no-such-foo'), None)
203        eq(cf['Spacey Bar'].get('foo', None), 'bar2')
204        eq(cf['Spacey Bar'].get('foo', fallback=None), 'bar2')
205        with self.assertRaises(KeyError):
206            cf['No Such Spacey Bar'].get('foo', None)
207        eq(cf['Types'].getint('int', 18), 42)
208        eq(cf['Types'].getint('int', fallback=18), 42)
209        eq(cf['Types'].getint('no-such-int', 18), 18)
210        eq(cf['Types'].getint('no-such-int', fallback=18), 18)
211        eq(cf['Types'].getint('no-such-int', "18"), "18") # sic!
212        eq(cf['Types'].getint('no-such-int', fallback="18"), "18") # sic!
213        eq(cf['Types'].getint('no-such-int'), None)
214        self.assertAlmostEqual(cf['Types'].getfloat('float', 0.0), 0.44)
215        self.assertAlmostEqual(cf['Types'].getfloat('float',
216                                                    fallback=0.0), 0.44)
217        self.assertAlmostEqual(cf['Types'].getfloat('no-such-float', 0.0), 0.0)
218        self.assertAlmostEqual(cf['Types'].getfloat('no-such-float',
219                                                    fallback=0.0), 0.0)
220        eq(cf['Types'].getfloat('no-such-float', "0.0"), "0.0") # sic!
221        eq(cf['Types'].getfloat('no-such-float', fallback="0.0"), "0.0") # sic!
222        eq(cf['Types'].getfloat('no-such-float'), None)
223        eq(cf['Types'].getboolean('boolean', True), False)
224        eq(cf['Types'].getboolean('boolean', fallback=True), False)
225        eq(cf['Types'].getboolean('no-such-boolean', "yes"), "yes") # sic!
226        eq(cf['Types'].getboolean('no-such-boolean', fallback="yes"),
227           "yes") # sic!
228        eq(cf['Types'].getboolean('no-such-boolean', True), True)
229        eq(cf['Types'].getboolean('no-such-boolean', fallback=True), True)
230        eq(cf['Types'].getboolean('no-such-boolean'), None)
231        if self.allow_no_value:
232            eq(cf['NoValue'].get('option-without-value', False), None)
233            eq(cf['NoValue'].get('option-without-value', fallback=False), None)
234            eq(cf['NoValue'].get('no-such-option-without-value', False), False)
235            eq(cf['NoValue'].get('no-such-option-without-value',
236                      fallback=False), False)
237
238        # Make sure the right things happen for remove_section() and
239        # remove_option(); added to include check for SourceForge bug #123324.
240
241        cf[self.default_section]['this_value'] = '1'
242        cf[self.default_section]['that_value'] = '2'
243
244        # API access
245        self.assertTrue(cf.remove_section('Spaces'))
246        self.assertFalse(cf.has_option('Spaces', 'key with spaces'))
247        self.assertFalse(cf.remove_section('Spaces'))
248        self.assertFalse(cf.remove_section(self.default_section))
249        self.assertTrue(cf.remove_option('Foo Bar', 'foo'),
250                        "remove_option() failed to report existence of option")
251        self.assertFalse(cf.has_option('Foo Bar', 'foo'),
252                    "remove_option() failed to remove option")
253        self.assertFalse(cf.remove_option('Foo Bar', 'foo'),
254                    "remove_option() failed to report non-existence of option"
255                    " that was removed")
256        self.assertTrue(cf.has_option('Foo Bar', 'this_value'))
257        self.assertFalse(cf.remove_option('Foo Bar', 'this_value'))
258        self.assertTrue(cf.remove_option(self.default_section, 'this_value'))
259        self.assertFalse(cf.has_option('Foo Bar', 'this_value'))
260        self.assertFalse(cf.remove_option(self.default_section, 'this_value'))
261
262        with self.assertRaises(configparser.NoSectionError) as cm:
263            cf.remove_option('No Such Section', 'foo')
264        self.assertEqual(cm.exception.args, ('No Such Section',))
265
266        eq(cf.get('Long Line', 'foo'),
267           'this line is much, much longer than my editor\nlikes it.')
268
269        # mapping access
270        del cf['Types']
271        self.assertFalse('Types' in cf)
272        with self.assertRaises(KeyError):
273            del cf['Types']
274        with self.assertRaises(ValueError):
275            del cf[self.default_section]
276        del cf['Spacey Bar']['foo']
277        self.assertFalse('foo' in cf['Spacey Bar'])
278        with self.assertRaises(KeyError):
279            del cf['Spacey Bar']['foo']
280        self.assertTrue('that_value' in cf['Spacey Bar'])
281        with self.assertRaises(KeyError):
282            del cf['Spacey Bar']['that_value']
283        del cf[self.default_section]['that_value']
284        self.assertFalse('that_value' in cf['Spacey Bar'])
285        with self.assertRaises(KeyError):
286            del cf[self.default_section]['that_value']
287        with self.assertRaises(KeyError):
288            del cf['No Such Section']['foo']
289
290        # Don't add new asserts below in this method as most of the options
291        # and sections are now removed.
292
293    def test_basic(self):
294        config_string = """\
295[Foo Bar]
296foo{0[0]}bar1
297[Spacey Bar]
298foo {0[0]} bar2
299[Spacey Bar From The Beginning]
300  foo {0[0]} bar3
301  baz {0[0]} qwe
302[Commented Bar]
303foo{0[1]} bar4 {1[1]} comment
304baz{0[0]}qwe {1[0]}another one
305[Long Line]
306foo{0[1]} this line is much, much longer than my editor
307   likes it.
308[Section\\with$weird%characters[\t]
309[Internationalized Stuff]
310foo[bg]{0[1]} Bulgarian
311foo{0[0]}Default
312foo[en]{0[0]}English
313foo[de]{0[0]}Deutsch
314[Spaces]
315key with spaces {0[1]} value
316another with spaces {0[0]} splat!
317[Types]
318int {0[1]} 42
319float {0[0]} 0.44
320boolean {0[0]} NO
321123 {0[1]} strange but acceptable
322""".format(self.delimiters, self.comment_prefixes)
323        if self.allow_no_value:
324            config_string += (
325                "[NoValue]\n"
326                "option-without-value\n"
327                )
328        cf = self.fromstring(config_string)
329        self.basic_test(cf)
330        if self.strict:
331            with self.assertRaises(configparser.DuplicateOptionError):
332                cf.read_string(textwrap.dedent("""\
333                    [Duplicate Options Here]
334                    option {0[0]} with a value
335                    option {0[1]} with another value
336                """.format(self.delimiters)))
337            with self.assertRaises(configparser.DuplicateSectionError):
338                cf.read_string(textwrap.dedent("""\
339                    [And Now For Something]
340                    completely different {0[0]} True
341                    [And Now For Something]
342                    the larch {0[1]} 1
343                """.format(self.delimiters)))
344        else:
345            cf.read_string(textwrap.dedent("""\
346                [Duplicate Options Here]
347                option {0[0]} with a value
348                option {0[1]} with another value
349            """.format(self.delimiters)))
350
351            cf.read_string(textwrap.dedent("""\
352                [And Now For Something]
353                completely different {0[0]} True
354                [And Now For Something]
355                the larch {0[1]} 1
356            """.format(self.delimiters)))
357
358    def test_basic_from_dict(self):
359        config = {
360            "Foo Bar": {
361                "foo": "bar1",
362            },
363            "Spacey Bar": {
364                "foo": "bar2",
365            },
366            "Spacey Bar From The Beginning": {
367                "foo": "bar3",
368                "baz": "qwe",
369            },
370            "Commented Bar": {
371                "foo": "bar4",
372                "baz": "qwe",
373            },
374            "Long Line": {
375                "foo": "this line is much, much longer than my editor\nlikes "
376                       "it.",
377            },
378            "Section\\with$weird%characters[\t": {
379            },
380            "Internationalized Stuff": {
381                "foo[bg]": "Bulgarian",
382                "foo": "Default",
383                "foo[en]": "English",
384                "foo[de]": "Deutsch",
385            },
386            "Spaces": {
387                "key with spaces": "value",
388                "another with spaces": "splat!",
389            },
390            "Types": {
391                "int": 42,
392                "float": 0.44,
393                "boolean": False,
394                123: "strange but acceptable",
395            },
396        }
397        if self.allow_no_value:
398            config.update({
399                "NoValue": {
400                    "option-without-value": None,
401                }
402            })
403        cf = self.newconfig()
404        cf.read_dict(config)
405        self.basic_test(cf)
406        if self.strict:
407            with self.assertRaises(configparser.DuplicateSectionError):
408                cf.read_dict({
409                    '1': {'key': 'value'},
410                    1: {'key2': 'value2'},
411                })
412            with self.assertRaises(configparser.DuplicateOptionError):
413                cf.read_dict({
414                    "Duplicate Options Here": {
415                        'option': 'with a value',
416                        'OPTION': 'with another value',
417                    },
418                })
419        else:
420            cf.read_dict({
421                'section': {'key': 'value'},
422                'SECTION': {'key2': 'value2'},
423            })
424            cf.read_dict({
425                "Duplicate Options Here": {
426                    'option': 'with a value',
427                    'OPTION': 'with another value',
428                },
429            })
430
431    def test_case_sensitivity(self):
432        cf = self.newconfig()
433        cf.add_section("A")
434        cf.add_section("a")
435        cf.add_section("B")
436        L = cf.sections()
437        L.sort()
438        eq = self.assertEqual
439        eq(L, ["A", "B", "a"])
440        cf.set("a", "B", "value")
441        eq(cf.options("a"), ["b"])
442        eq(cf.get("a", "b"), "value",
443           "could not locate option, expecting case-insensitive option names")
444        with self.assertRaises(configparser.NoSectionError):
445            # section names are case-sensitive
446            cf.set("b", "A", "value")
447        self.assertTrue(cf.has_option("a", "b"))
448        self.assertFalse(cf.has_option("b", "b"))
449        cf.set("A", "A-B", "A-B value")
450        for opt in ("a-b", "A-b", "a-B", "A-B"):
451            self.assertTrue(
452                cf.has_option("A", opt),
453                "has_option() returned false for option which should exist")
454        eq(cf.options("A"), ["a-b"])
455        eq(cf.options("a"), ["b"])
456        cf.remove_option("a", "B")
457        eq(cf.options("a"), [])
458
459        # SF bug #432369:
460        cf = self.fromstring(
461            "[MySection]\nOption{} first line   \n\tsecond line   \n".format(
462                self.delimiters[0]))
463        eq(cf.options("MySection"), ["option"])
464        eq(cf.get("MySection", "Option"), "first line\nsecond line")
465
466        # SF bug #561822:
467        cf = self.fromstring("[section]\n"
468                             "nekey{}nevalue\n".format(self.delimiters[0]),
469                             defaults={"key":"value"})
470        self.assertTrue(cf.has_option("section", "Key"))
471
472
473    def test_case_sensitivity_mapping_access(self):
474        cf = self.newconfig()
475        cf["A"] = {}
476        cf["a"] = {"B": "value"}
477        cf["B"] = {}
478        L = [section for section in cf]
479        L.sort()
480        eq = self.assertEqual
481        elem_eq = self.assertCountEqual
482        eq(L, sorted(["A", "B", self.default_section, "a"]))
483        eq(cf["a"].keys(), {"b"})
484        eq(cf["a"]["b"], "value",
485           "could not locate option, expecting case-insensitive option names")
486        with self.assertRaises(KeyError):
487            # section names are case-sensitive
488            cf["b"]["A"] = "value"
489        self.assertTrue("b" in cf["a"])
490        cf["A"]["A-B"] = "A-B value"
491        for opt in ("a-b", "A-b", "a-B", "A-B"):
492            self.assertTrue(
493                opt in cf["A"],
494                "has_option() returned false for option which should exist")
495        eq(cf["A"].keys(), {"a-b"})
496        eq(cf["a"].keys(), {"b"})
497        del cf["a"]["B"]
498        elem_eq(cf["a"].keys(), {})
499
500        # SF bug #432369:
501        cf = self.fromstring(
502            "[MySection]\nOption{} first line   \n\tsecond line   \n".format(
503                self.delimiters[0]))
504        eq(cf["MySection"].keys(), {"option"})
505        eq(cf["MySection"]["Option"], "first line\nsecond line")
506
507        # SF bug #561822:
508        cf = self.fromstring("[section]\n"
509                             "nekey{}nevalue\n".format(self.delimiters[0]),
510                             defaults={"key":"value"})
511        self.assertTrue("Key" in cf["section"])
512
513    def test_default_case_sensitivity(self):
514        cf = self.newconfig({"foo": "Bar"})
515        self.assertEqual(
516            cf.get(self.default_section, "Foo"), "Bar",
517            "could not locate option, expecting case-insensitive option names")
518        cf = self.newconfig({"Foo": "Bar"})
519        self.assertEqual(
520            cf.get(self.default_section, "Foo"), "Bar",
521            "could not locate option, expecting case-insensitive defaults")
522
523    def test_parse_errors(self):
524        cf = self.newconfig()
525        self.parse_error(cf, configparser.ParsingError,
526                         "[Foo]\n"
527                         "{}val-without-opt-name\n".format(self.delimiters[0]))
528        self.parse_error(cf, configparser.ParsingError,
529                         "[Foo]\n"
530                         "{}val-without-opt-name\n".format(self.delimiters[1]))
531        e = self.parse_error(cf, configparser.MissingSectionHeaderError,
532                             "No Section!\n")
533        self.assertEqual(e.args, ('<???>', 1, "No Section!\n"))
534        if not self.allow_no_value:
535            e = self.parse_error(cf, configparser.ParsingError,
536                                "[Foo]\n  wrong-indent\n")
537            self.assertEqual(e.args, ('<???>',))
538            # read_file on a real file
539            tricky = support.findfile("cfgparser.3")
540            if self.delimiters[0] == '=':
541                error = configparser.ParsingError
542                expected = (tricky,)
543            else:
544                error = configparser.MissingSectionHeaderError
545                expected = (tricky, 1,
546                            '  # INI with as many tricky parts as possible\n')
547            with open(tricky, encoding='utf-8') as f:
548                e = self.parse_error(cf, error, f)
549            self.assertEqual(e.args, expected)
550
551    def parse_error(self, cf, exc, src):
552        if hasattr(src, 'readline'):
553            sio = src
554        else:
555            sio = io.StringIO(src)
556        with self.assertRaises(exc) as cm:
557            cf.read_file(sio)
558        return cm.exception
559
560    def test_query_errors(self):
561        cf = self.newconfig()
562        self.assertEqual(cf.sections(), [],
563                         "new ConfigParser should have no defined sections")
564        self.assertFalse(cf.has_section("Foo"),
565                         "new ConfigParser should have no acknowledged "
566                         "sections")
567        with self.assertRaises(configparser.NoSectionError):
568            cf.options("Foo")
569        with self.assertRaises(configparser.NoSectionError):
570            cf.set("foo", "bar", "value")
571        e = self.get_error(cf, configparser.NoSectionError, "foo", "bar")
572        self.assertEqual(e.args, ("foo",))
573        cf.add_section("foo")
574        e = self.get_error(cf, configparser.NoOptionError, "foo", "bar")
575        self.assertEqual(e.args, ("bar", "foo"))
576
577    def get_error(self, cf, exc, section, option):
578        try:
579            cf.get(section, option)
580        except exc as e:
581            return e
582        else:
583            self.fail("expected exception type %s.%s"
584                      % (exc.__module__, exc.__qualname__))
585
586    def test_boolean(self):
587        cf = self.fromstring(
588            "[BOOLTEST]\n"
589            "T1{equals}1\n"
590            "T2{equals}TRUE\n"
591            "T3{equals}True\n"
592            "T4{equals}oN\n"
593            "T5{equals}yes\n"
594            "F1{equals}0\n"
595            "F2{equals}FALSE\n"
596            "F3{equals}False\n"
597            "F4{equals}oFF\n"
598            "F5{equals}nO\n"
599            "E1{equals}2\n"
600            "E2{equals}foo\n"
601            "E3{equals}-1\n"
602            "E4{equals}0.1\n"
603            "E5{equals}FALSE AND MORE".format(equals=self.delimiters[0])
604            )
605        for x in range(1, 5):
606            self.assertTrue(cf.getboolean('BOOLTEST', 't%d' % x))
607            self.assertFalse(cf.getboolean('BOOLTEST', 'f%d' % x))
608            self.assertRaises(ValueError,
609                              cf.getboolean, 'BOOLTEST', 'e%d' % x)
610
611    def test_weird_errors(self):
612        cf = self.newconfig()
613        cf.add_section("Foo")
614        with self.assertRaises(configparser.DuplicateSectionError) as cm:
615            cf.add_section("Foo")
616        e = cm.exception
617        self.assertEqual(str(e), "Section 'Foo' already exists")
618        self.assertEqual(e.args, ("Foo", None, None))
619
620        if self.strict:
621            with self.assertRaises(configparser.DuplicateSectionError) as cm:
622                cf.read_string(textwrap.dedent("""\
623                    [Foo]
624                    will this be added{equals}True
625                    [Bar]
626                    what about this{equals}True
627                    [Foo]
628                    oops{equals}this won't
629                """.format(equals=self.delimiters[0])), source='<foo-bar>')
630            e = cm.exception
631            self.assertEqual(str(e), "While reading from '<foo-bar>' "
632                                     "[line  5]: section 'Foo' already exists")
633            self.assertEqual(e.args, ("Foo", '<foo-bar>', 5))
634
635            with self.assertRaises(configparser.DuplicateOptionError) as cm:
636                cf.read_dict({'Bar': {'opt': 'val', 'OPT': 'is really `opt`'}})
637            e = cm.exception
638            self.assertEqual(str(e), "While reading from '<dict>': option "
639                                     "'opt' in section 'Bar' already exists")
640            self.assertEqual(e.args, ("Bar", "opt", "<dict>", None))
641
642    def test_write(self):
643        config_string = (
644            "[Long Line]\n"
645            "foo{0[0]} this line is much, much longer than my editor\n"
646            "   likes it.\n"
647            "[{default_section}]\n"
648            "foo{0[1]} another very\n"
649            " long line\n"
650            "[Long Line - With Comments!]\n"
651            "test {0[1]} we        {comment} can\n"
652            "            also      {comment} place\n"
653            "            comments  {comment} in\n"
654            "            multiline {comment} values"
655            "\n".format(self.delimiters, comment=self.comment_prefixes[0],
656                        default_section=self.default_section)
657            )
658        if self.allow_no_value:
659            config_string += (
660            "[Valueless]\n"
661            "option-without-value\n"
662            )
663
664        cf = self.fromstring(config_string)
665        for space_around_delimiters in (True, False):
666            output = io.StringIO()
667            cf.write(output, space_around_delimiters=space_around_delimiters)
668            delimiter = self.delimiters[0]
669            if space_around_delimiters:
670                delimiter = " {} ".format(delimiter)
671            expect_string = (
672                "[{default_section}]\n"
673                "foo{equals}another very\n"
674                "\tlong line\n"
675                "\n"
676                "[Long Line]\n"
677                "foo{equals}this line is much, much longer than my editor\n"
678                "\tlikes it.\n"
679                "\n"
680                "[Long Line - With Comments!]\n"
681                "test{equals}we\n"
682                "\talso\n"
683                "\tcomments\n"
684                "\tmultiline\n"
685                "\n".format(equals=delimiter,
686                            default_section=self.default_section)
687                )
688            if self.allow_no_value:
689                expect_string += (
690                    "[Valueless]\n"
691                    "option-without-value\n"
692                    "\n"
693                    )
694            self.assertEqual(output.getvalue(), expect_string)
695
696    def test_set_string_types(self):
697        cf = self.fromstring("[sect]\n"
698                             "option1{eq}foo\n".format(eq=self.delimiters[0]))
699        # Check that we don't get an exception when setting values in
700        # an existing section using strings:
701        class mystr(str):
702            pass
703        cf.set("sect", "option1", "splat")
704        cf.set("sect", "option1", mystr("splat"))
705        cf.set("sect", "option2", "splat")
706        cf.set("sect", "option2", mystr("splat"))
707        cf.set("sect", "option1", "splat")
708        cf.set("sect", "option2", "splat")
709
710    def test_read_returns_file_list(self):
711        if self.delimiters[0] != '=':
712            self.skipTest('incompatible format')
713        file1 = support.findfile("cfgparser.1")
714        # check when we pass a mix of readable and non-readable files:
715        cf = self.newconfig()
716        parsed_files = cf.read([file1, "nonexistent-file"])
717        self.assertEqual(parsed_files, [file1])
718        self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
719        # check when we pass only a filename:
720        cf = self.newconfig()
721        parsed_files = cf.read(file1)
722        self.assertEqual(parsed_files, [file1])
723        self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
724        # check when we pass only a Path object:
725        cf = self.newconfig()
726        parsed_files = cf.read(pathlib.Path(file1))
727        self.assertEqual(parsed_files, [file1])
728        self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
729        # check when we passed both a filename and a Path object:
730        cf = self.newconfig()
731        parsed_files = cf.read([pathlib.Path(file1), file1])
732        self.assertEqual(parsed_files, [file1, file1])
733        self.assertEqual(cf.get("Foo Bar", "foo"), "newbar")
734        # check when we pass only missing files:
735        cf = self.newconfig()
736        parsed_files = cf.read(["nonexistent-file"])
737        self.assertEqual(parsed_files, [])
738        # check when we pass no files:
739        cf = self.newconfig()
740        parsed_files = cf.read([])
741        self.assertEqual(parsed_files, [])
742
743    def test_read_returns_file_list_with_bytestring_path(self):
744        if self.delimiters[0] != '=':
745            self.skipTest('incompatible format')
746        file1_bytestring = support.findfile("cfgparser.1").encode()
747        # check when passing an existing bytestring path
748        cf = self.newconfig()
749        parsed_files = cf.read(file1_bytestring)
750        self.assertEqual(parsed_files, [file1_bytestring])
751        # check when passing an non-existing bytestring path
752        cf = self.newconfig()
753        parsed_files = cf.read(b'nonexistent-file')
754        self.assertEqual(parsed_files, [])
755        # check when passing both an existing and non-existing bytestring path
756        cf = self.newconfig()
757        parsed_files = cf.read([file1_bytestring, b'nonexistent-file'])
758        self.assertEqual(parsed_files, [file1_bytestring])
759
760    # shared by subclasses
761    def get_interpolation_config(self):
762        return self.fromstring(
763            "[Foo]\n"
764            "bar{equals}something %(with1)s interpolation (1 step)\n"
765            "bar9{equals}something %(with9)s lots of interpolation (9 steps)\n"
766            "bar10{equals}something %(with10)s lots of interpolation (10 steps)\n"
767            "bar11{equals}something %(with11)s lots of interpolation (11 steps)\n"
768            "with11{equals}%(with10)s\n"
769            "with10{equals}%(with9)s\n"
770            "with9{equals}%(with8)s\n"
771            "with8{equals}%(With7)s\n"
772            "with7{equals}%(WITH6)s\n"
773            "with6{equals}%(with5)s\n"
774            "With5{equals}%(with4)s\n"
775            "WITH4{equals}%(with3)s\n"
776            "with3{equals}%(with2)s\n"
777            "with2{equals}%(with1)s\n"
778            "with1{equals}with\n"
779            "\n"
780            "[Mutual Recursion]\n"
781            "foo{equals}%(bar)s\n"
782            "bar{equals}%(foo)s\n"
783            "\n"
784            "[Interpolation Error]\n"
785            # no definition for 'reference'
786            "name{equals}%(reference)s\n".format(equals=self.delimiters[0]))
787
788    def check_items_config(self, expected):
789        cf = self.fromstring("""
790            [section]
791            name {0[0]} %(value)s
792            key{0[1]} |%(name)s|
793            getdefault{0[1]} |%(default)s|
794        """.format(self.delimiters), defaults={"default": "<default>"})
795        L = list(cf.items("section", vars={'value': 'value'}))
796        L.sort()
797        self.assertEqual(L, expected)
798        with self.assertRaises(configparser.NoSectionError):
799            cf.items("no such section")
800
801    def test_popitem(self):
802        cf = self.fromstring("""
803            [section1]
804            name1 {0[0]} value1
805            [section2]
806            name2 {0[0]} value2
807            [section3]
808            name3 {0[0]} value3
809        """.format(self.delimiters), defaults={"default": "<default>"})
810        self.assertEqual(cf.popitem()[0], 'section1')
811        self.assertEqual(cf.popitem()[0], 'section2')
812        self.assertEqual(cf.popitem()[0], 'section3')
813        with self.assertRaises(KeyError):
814            cf.popitem()
815
816    def test_clear(self):
817        cf = self.newconfig({"foo": "Bar"})
818        self.assertEqual(
819            cf.get(self.default_section, "Foo"), "Bar",
820            "could not locate option, expecting case-insensitive option names")
821        cf['zing'] = {'option1': 'value1', 'option2': 'value2'}
822        self.assertEqual(cf.sections(), ['zing'])
823        self.assertEqual(set(cf['zing'].keys()), {'option1', 'option2', 'foo'})
824        cf.clear()
825        self.assertEqual(set(cf.sections()), set())
826        self.assertEqual(set(cf[self.default_section].keys()), {'foo'})
827
828    def test_setitem(self):
829        cf = self.fromstring("""
830            [section1]
831            name1 {0[0]} value1
832            [section2]
833            name2 {0[0]} value2
834            [section3]
835            name3 {0[0]} value3
836        """.format(self.delimiters), defaults={"nameD": "valueD"})
837        self.assertEqual(set(cf['section1'].keys()), {'name1', 'named'})
838        self.assertEqual(set(cf['section2'].keys()), {'name2', 'named'})
839        self.assertEqual(set(cf['section3'].keys()), {'name3', 'named'})
840        self.assertEqual(cf['section1']['name1'], 'value1')
841        self.assertEqual(cf['section2']['name2'], 'value2')
842        self.assertEqual(cf['section3']['name3'], 'value3')
843        self.assertEqual(cf.sections(), ['section1', 'section2', 'section3'])
844        cf['section2'] = {'name22': 'value22'}
845        self.assertEqual(set(cf['section2'].keys()), {'name22', 'named'})
846        self.assertEqual(cf['section2']['name22'], 'value22')
847        self.assertNotIn('name2', cf['section2'])
848        self.assertEqual(cf.sections(), ['section1', 'section2', 'section3'])
849        cf['section3'] = {}
850        self.assertEqual(set(cf['section3'].keys()), {'named'})
851        self.assertNotIn('name3', cf['section3'])
852        self.assertEqual(cf.sections(), ['section1', 'section2', 'section3'])
853        cf[self.default_section] = {}
854        self.assertEqual(set(cf[self.default_section].keys()), set())
855        self.assertEqual(set(cf['section1'].keys()), {'name1'})
856        self.assertEqual(set(cf['section2'].keys()), {'name22'})
857        self.assertEqual(set(cf['section3'].keys()), set())
858        self.assertEqual(cf.sections(), ['section1', 'section2', 'section3'])
859
860    def test_invalid_multiline_value(self):
861        if self.allow_no_value:
862            self.skipTest('if no_value is allowed, ParsingError is not raised')
863
864        invalid = textwrap.dedent("""\
865            [DEFAULT]
866            test {0} test
867            invalid""".format(self.delimiters[0])
868        )
869        cf = self.newconfig()
870        with self.assertRaises(configparser.ParsingError):
871            cf.read_string(invalid)
872        self.assertEqual(cf.get('DEFAULT', 'test'), 'test')
873        self.assertEqual(cf['DEFAULT']['test'], 'test')
874
875
876class StrictTestCase(BasicTestCase, unittest.TestCase):
877    config_class = configparser.RawConfigParser
878    strict = True
879
880
881class ConfigParserTestCase(BasicTestCase, unittest.TestCase):
882    config_class = configparser.ConfigParser
883
884    def test_interpolation(self):
885        cf = self.get_interpolation_config()
886        eq = self.assertEqual
887        eq(cf.get("Foo", "bar"), "something with interpolation (1 step)")
888        eq(cf.get("Foo", "bar9"),
889           "something with lots of interpolation (9 steps)")
890        eq(cf.get("Foo", "bar10"),
891           "something with lots of interpolation (10 steps)")
892        e = self.get_error(cf, configparser.InterpolationDepthError, "Foo", "bar11")
893        if self.interpolation == configparser._UNSET:
894            self.assertEqual(e.args, ("bar11", "Foo",
895                "something %(with11)s lots of interpolation (11 steps)"))
896        elif isinstance(self.interpolation, configparser.LegacyInterpolation):
897            self.assertEqual(e.args, ("bar11", "Foo",
898                "something %(with11)s lots of interpolation (11 steps)"))
899
900    def test_interpolation_missing_value(self):
901        cf = self.get_interpolation_config()
902        e = self.get_error(cf, configparser.InterpolationMissingOptionError,
903                           "Interpolation Error", "name")
904        self.assertEqual(e.reference, "reference")
905        self.assertEqual(e.section, "Interpolation Error")
906        self.assertEqual(e.option, "name")
907        if self.interpolation == configparser._UNSET:
908            self.assertEqual(e.args, ('name', 'Interpolation Error',
909                                    '%(reference)s', 'reference'))
910        elif isinstance(self.interpolation, configparser.LegacyInterpolation):
911            self.assertEqual(e.args, ('name', 'Interpolation Error',
912                                    '%(reference)s', 'reference'))
913
914    def test_items(self):
915        self.check_items_config([('default', '<default>'),
916                                 ('getdefault', '|<default>|'),
917                                 ('key', '|value|'),
918                                 ('name', 'value'),
919                                 ('value', 'value')])
920
921    def test_safe_interpolation(self):
922        # See http://www.python.org/sf/511737
923        cf = self.fromstring("[section]\n"
924                             "option1{eq}xxx\n"
925                             "option2{eq}%(option1)s/xxx\n"
926                             "ok{eq}%(option1)s/%%s\n"
927                             "not_ok{eq}%(option2)s/%%s".format(
928                                 eq=self.delimiters[0]))
929        self.assertEqual(cf.get("section", "ok"), "xxx/%s")
930        if self.interpolation == configparser._UNSET:
931            self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s")
932        elif isinstance(self.interpolation, configparser.LegacyInterpolation):
933            with self.assertRaises(TypeError):
934                cf.get("section", "not_ok")
935
936    def test_set_malformatted_interpolation(self):
937        cf = self.fromstring("[sect]\n"
938                             "option1{eq}foo\n".format(eq=self.delimiters[0]))
939
940        self.assertEqual(cf.get('sect', "option1"), "foo")
941
942        self.assertRaises(ValueError, cf.set, "sect", "option1", "%foo")
943        self.assertRaises(ValueError, cf.set, "sect", "option1", "foo%")
944        self.assertRaises(ValueError, cf.set, "sect", "option1", "f%oo")
945
946        self.assertEqual(cf.get('sect', "option1"), "foo")
947
948        # bug #5741: double percents are *not* malformed
949        cf.set("sect", "option2", "foo%%bar")
950        self.assertEqual(cf.get("sect", "option2"), "foo%bar")
951
952    def test_set_nonstring_types(self):
953        cf = self.fromstring("[sect]\n"
954                             "option1{eq}foo\n".format(eq=self.delimiters[0]))
955        # Check that we get a TypeError when setting non-string values
956        # in an existing section:
957        self.assertRaises(TypeError, cf.set, "sect", "option1", 1)
958        self.assertRaises(TypeError, cf.set, "sect", "option1", 1.0)
959        self.assertRaises(TypeError, cf.set, "sect", "option1", object())
960        self.assertRaises(TypeError, cf.set, "sect", "option2", 1)
961        self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0)
962        self.assertRaises(TypeError, cf.set, "sect", "option2", object())
963        self.assertRaises(TypeError, cf.set, "sect", 123, "invalid opt name!")
964        self.assertRaises(TypeError, cf.add_section, 123)
965
966    def test_add_section_default(self):
967        cf = self.newconfig()
968        self.assertRaises(ValueError, cf.add_section, self.default_section)
969
970    def test_defaults_keyword(self):
971        """bpo-23835 fix for ConfigParser"""
972        cf = self.newconfig(defaults={1: 2.4})
973        self.assertEqual(cf[self.default_section]['1'], '2.4')
974        self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.4)
975        cf = self.newconfig(defaults={"A": 5.2})
976        self.assertEqual(cf[self.default_section]['a'], '5.2')
977        self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.2)
978
979
980class ConfigParserTestCaseNoInterpolation(BasicTestCase, unittest.TestCase):
981    config_class = configparser.ConfigParser
982    interpolation = None
983    ini = textwrap.dedent("""
984        [numbers]
985        one = 1
986        two = %(one)s * 2
987        three = ${common:one} * 3
988
989        [hexen]
990        sixteen = ${numbers:two} * 8
991    """).strip()
992
993    def assertMatchesIni(self, cf):
994        self.assertEqual(cf['numbers']['one'], '1')
995        self.assertEqual(cf['numbers']['two'], '%(one)s * 2')
996        self.assertEqual(cf['numbers']['three'], '${common:one} * 3')
997        self.assertEqual(cf['hexen']['sixteen'], '${numbers:two} * 8')
998
999    def test_no_interpolation(self):
1000        cf = self.fromstring(self.ini)
1001        self.assertMatchesIni(cf)
1002
1003    def test_empty_case(self):
1004        cf = self.newconfig()
1005        self.assertIsNone(cf.read_string(""))
1006
1007    def test_none_as_default_interpolation(self):
1008        class CustomConfigParser(configparser.ConfigParser):
1009            _DEFAULT_INTERPOLATION = None
1010
1011        cf = CustomConfigParser()
1012        cf.read_string(self.ini)
1013        self.assertMatchesIni(cf)
1014
1015
1016class ConfigParserTestCaseLegacyInterpolation(ConfigParserTestCase):
1017    config_class = configparser.ConfigParser
1018    interpolation = configparser.LegacyInterpolation()
1019
1020    def test_set_malformatted_interpolation(self):
1021        cf = self.fromstring("[sect]\n"
1022                             "option1{eq}foo\n".format(eq=self.delimiters[0]))
1023
1024        self.assertEqual(cf.get('sect', "option1"), "foo")
1025
1026        cf.set("sect", "option1", "%foo")
1027        self.assertEqual(cf.get('sect', "option1"), "%foo")
1028        cf.set("sect", "option1", "foo%")
1029        self.assertEqual(cf.get('sect', "option1"), "foo%")
1030        cf.set("sect", "option1", "f%oo")
1031        self.assertEqual(cf.get('sect', "option1"), "f%oo")
1032
1033        # bug #5741: double percents are *not* malformed
1034        cf.set("sect", "option2", "foo%%bar")
1035        self.assertEqual(cf.get("sect", "option2"), "foo%%bar")
1036
1037
1038class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase):
1039    delimiters = (':=', '$')
1040    comment_prefixes = ('//', '"')
1041    inline_comment_prefixes = ('//', '"')
1042
1043
1044class ConfigParserTestCaseNonStandardDefaultSection(ConfigParserTestCase):
1045    default_section = 'general'
1046
1047
1048class MultilineValuesTestCase(BasicTestCase, unittest.TestCase):
1049    config_class = configparser.ConfigParser
1050    wonderful_spam = ("I'm having spam spam spam spam "
1051                      "spam spam spam beaked beans spam "
1052                      "spam spam and spam!").replace(' ', '\t\n')
1053
1054    def setUp(self):
1055        cf = self.newconfig()
1056        for i in range(100):
1057            s = 'section{}'.format(i)
1058            cf.add_section(s)
1059            for j in range(10):
1060                cf.set(s, 'lovely_spam{}'.format(j), self.wonderful_spam)
1061        with open(support.TESTFN, 'w') as f:
1062            cf.write(f)
1063
1064    def tearDown(self):
1065        os.unlink(support.TESTFN)
1066
1067    def test_dominating_multiline_values(self):
1068        # We're reading from file because this is where the code changed
1069        # during performance updates in Python 3.2
1070        cf_from_file = self.newconfig()
1071        with open(support.TESTFN) as f:
1072            cf_from_file.read_file(f)
1073        self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'),
1074                         self.wonderful_spam.replace('\t\n', '\n'))
1075
1076
1077class RawConfigParserTestCase(BasicTestCase, unittest.TestCase):
1078    config_class = configparser.RawConfigParser
1079
1080    def test_interpolation(self):
1081        cf = self.get_interpolation_config()
1082        eq = self.assertEqual
1083        eq(cf.get("Foo", "bar"),
1084           "something %(with1)s interpolation (1 step)")
1085        eq(cf.get("Foo", "bar9"),
1086           "something %(with9)s lots of interpolation (9 steps)")
1087        eq(cf.get("Foo", "bar10"),
1088           "something %(with10)s lots of interpolation (10 steps)")
1089        eq(cf.get("Foo", "bar11"),
1090           "something %(with11)s lots of interpolation (11 steps)")
1091
1092    def test_items(self):
1093        self.check_items_config([('default', '<default>'),
1094                                 ('getdefault', '|%(default)s|'),
1095                                 ('key', '|%(name)s|'),
1096                                 ('name', '%(value)s'),
1097                                 ('value', 'value')])
1098
1099    def test_set_nonstring_types(self):
1100        cf = self.newconfig()
1101        cf.add_section('non-string')
1102        cf.set('non-string', 'int', 1)
1103        cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13])
1104        cf.set('non-string', 'dict', {'pi': 3.14159})
1105        self.assertEqual(cf.get('non-string', 'int'), 1)
1106        self.assertEqual(cf.get('non-string', 'list'),
1107                         [0, 1, 1, 2, 3, 5, 8, 13])
1108        self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159})
1109        cf.add_section(123)
1110        cf.set(123, 'this is sick', True)
1111        self.assertEqual(cf.get(123, 'this is sick'), True)
1112        if cf._dict is configparser._default_dict:
1113            # would not work for SortedDict; only checking for the most common
1114            # default dictionary (OrderedDict)
1115            cf.optionxform = lambda x: x
1116            cf.set('non-string', 1, 1)
1117            self.assertEqual(cf.get('non-string', 1), 1)
1118
1119    def test_defaults_keyword(self):
1120        """bpo-23835 legacy behavior for RawConfigParser"""
1121        with self.assertRaises(AttributeError) as ctx:
1122            self.newconfig(defaults={1: 2.4})
1123        err = ctx.exception
1124        self.assertEqual(str(err), "'int' object has no attribute 'lower'")
1125        cf = self.newconfig(defaults={"A": 5.2})
1126        self.assertAlmostEqual(cf[self.default_section]['a'], 5.2)
1127
1128
1129class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase):
1130    delimiters = (':=', '$')
1131    comment_prefixes = ('//', '"')
1132    inline_comment_prefixes = ('//', '"')
1133
1134
1135class RawConfigParserTestSambaConf(CfgParserTestCaseClass, unittest.TestCase):
1136    config_class = configparser.RawConfigParser
1137    comment_prefixes = ('#', ';', '----')
1138    inline_comment_prefixes = ('//',)
1139    empty_lines_in_values = False
1140
1141    def test_reading(self):
1142        smbconf = support.findfile("cfgparser.2")
1143        # check when we pass a mix of readable and non-readable files:
1144        cf = self.newconfig()
1145        parsed_files = cf.read([smbconf, "nonexistent-file"], encoding='utf-8')
1146        self.assertEqual(parsed_files, [smbconf])
1147        sections = ['global', 'homes', 'printers',
1148                    'print$', 'pdf-generator', 'tmp', 'Agustin']
1149        self.assertEqual(cf.sections(), sections)
1150        self.assertEqual(cf.get("global", "workgroup"), "MDKGROUP")
1151        self.assertEqual(cf.getint("global", "max log size"), 50)
1152        self.assertEqual(cf.get("global", "hosts allow"), "127.")
1153        self.assertEqual(cf.get("tmp", "echo command"), "cat %s; rm %s")
1154
1155class ConfigParserTestCaseExtendedInterpolation(BasicTestCase, unittest.TestCase):
1156    config_class = configparser.ConfigParser
1157    interpolation = configparser.ExtendedInterpolation()
1158    default_section = 'common'
1159    strict = True
1160
1161    def fromstring(self, string, defaults=None, optionxform=None):
1162        cf = self.newconfig(defaults)
1163        if optionxform:
1164            cf.optionxform = optionxform
1165        cf.read_string(string)
1166        return cf
1167
1168    def test_extended_interpolation(self):
1169        cf = self.fromstring(textwrap.dedent("""
1170            [common]
1171            favourite Beatle = Paul
1172            favourite color = green
1173
1174            [tom]
1175            favourite band = ${favourite color} day
1176            favourite pope = John ${favourite Beatle} II
1177            sequel = ${favourite pope}I
1178
1179            [ambv]
1180            favourite Beatle = George
1181            son of Edward VII = ${favourite Beatle} V
1182            son of George V = ${son of Edward VII}I
1183
1184            [stanley]
1185            favourite Beatle = ${ambv:favourite Beatle}
1186            favourite pope = ${tom:favourite pope}
1187            favourite color = black
1188            favourite state of mind = paranoid
1189            favourite movie = soylent ${common:favourite color}
1190            favourite song = ${favourite color} sabbath - ${favourite state of mind}
1191        """).strip())
1192
1193        eq = self.assertEqual
1194        eq(cf['common']['favourite Beatle'], 'Paul')
1195        eq(cf['common']['favourite color'], 'green')
1196        eq(cf['tom']['favourite Beatle'], 'Paul')
1197        eq(cf['tom']['favourite color'], 'green')
1198        eq(cf['tom']['favourite band'], 'green day')
1199        eq(cf['tom']['favourite pope'], 'John Paul II')
1200        eq(cf['tom']['sequel'], 'John Paul III')
1201        eq(cf['ambv']['favourite Beatle'], 'George')
1202        eq(cf['ambv']['favourite color'], 'green')
1203        eq(cf['ambv']['son of Edward VII'], 'George V')
1204        eq(cf['ambv']['son of George V'], 'George VI')
1205        eq(cf['stanley']['favourite Beatle'], 'George')
1206        eq(cf['stanley']['favourite color'], 'black')
1207        eq(cf['stanley']['favourite state of mind'], 'paranoid')
1208        eq(cf['stanley']['favourite movie'], 'soylent green')
1209        eq(cf['stanley']['favourite pope'], 'John Paul II')
1210        eq(cf['stanley']['favourite song'],
1211           'black sabbath - paranoid')
1212
1213    def test_endless_loop(self):
1214        cf = self.fromstring(textwrap.dedent("""
1215            [one for you]
1216            ping = ${one for me:pong}
1217
1218            [one for me]
1219            pong = ${one for you:ping}
1220
1221            [selfish]
1222            me = ${me}
1223        """).strip())
1224
1225        with self.assertRaises(configparser.InterpolationDepthError):
1226            cf['one for you']['ping']
1227        with self.assertRaises(configparser.InterpolationDepthError):
1228            cf['selfish']['me']
1229
1230    def test_strange_options(self):
1231        cf = self.fromstring("""
1232            [dollars]
1233            $var = $$value
1234            $var2 = ${$var}
1235            ${sick} = cannot interpolate me
1236
1237            [interpolated]
1238            $other = ${dollars:$var}
1239            $trying = ${dollars:${sick}}
1240        """)
1241
1242        self.assertEqual(cf['dollars']['$var'], '$value')
1243        self.assertEqual(cf['interpolated']['$other'], '$value')
1244        self.assertEqual(cf['dollars']['${sick}'], 'cannot interpolate me')
1245        exception_class = configparser.InterpolationMissingOptionError
1246        with self.assertRaises(exception_class) as cm:
1247            cf['interpolated']['$trying']
1248        self.assertEqual(cm.exception.reference, 'dollars:${sick')
1249        self.assertEqual(cm.exception.args[2], '${dollars:${sick}}') #rawval
1250
1251    def test_case_sensitivity_basic(self):
1252        ini = textwrap.dedent("""
1253            [common]
1254            optionlower = value
1255            OptionUpper = Value
1256
1257            [Common]
1258            optionlower = a better ${common:optionlower}
1259            OptionUpper = A Better ${common:OptionUpper}
1260
1261            [random]
1262            foolower = ${common:optionlower} redefined
1263            FooUpper = ${Common:OptionUpper} Redefined
1264        """).strip()
1265
1266        cf = self.fromstring(ini)
1267        eq = self.assertEqual
1268        eq(cf['common']['optionlower'], 'value')
1269        eq(cf['common']['OptionUpper'], 'Value')
1270        eq(cf['Common']['optionlower'], 'a better value')
1271        eq(cf['Common']['OptionUpper'], 'A Better Value')
1272        eq(cf['random']['foolower'], 'value redefined')
1273        eq(cf['random']['FooUpper'], 'A Better Value Redefined')
1274
1275    def test_case_sensitivity_conflicts(self):
1276        ini = textwrap.dedent("""
1277            [common]
1278            option = value
1279            Option = Value
1280
1281            [Common]
1282            option = a better ${common:option}
1283            Option = A Better ${common:Option}
1284
1285            [random]
1286            foo = ${common:option} redefined
1287            Foo = ${Common:Option} Redefined
1288        """).strip()
1289        with self.assertRaises(configparser.DuplicateOptionError):
1290            cf = self.fromstring(ini)
1291
1292        # raw options
1293        cf = self.fromstring(ini, optionxform=lambda opt: opt)
1294        eq = self.assertEqual
1295        eq(cf['common']['option'], 'value')
1296        eq(cf['common']['Option'], 'Value')
1297        eq(cf['Common']['option'], 'a better value')
1298        eq(cf['Common']['Option'], 'A Better Value')
1299        eq(cf['random']['foo'], 'value redefined')
1300        eq(cf['random']['Foo'], 'A Better Value Redefined')
1301
1302    def test_other_errors(self):
1303        cf = self.fromstring("""
1304            [interpolation fail]
1305            case1 = ${where's the brace
1306            case2 = ${does_not_exist}
1307            case3 = ${wrong_section:wrong_value}
1308            case4 = ${i:like:colon:characters}
1309            case5 = $100 for Fail No 5!
1310        """)
1311
1312        with self.assertRaises(configparser.InterpolationSyntaxError):
1313            cf['interpolation fail']['case1']
1314        with self.assertRaises(configparser.InterpolationMissingOptionError):
1315            cf['interpolation fail']['case2']
1316        with self.assertRaises(configparser.InterpolationMissingOptionError):
1317            cf['interpolation fail']['case3']
1318        with self.assertRaises(configparser.InterpolationSyntaxError):
1319            cf['interpolation fail']['case4']
1320        with self.assertRaises(configparser.InterpolationSyntaxError):
1321            cf['interpolation fail']['case5']
1322        with self.assertRaises(ValueError):
1323            cf['interpolation fail']['case6'] = "BLACK $ABBATH"
1324
1325
1326class ConfigParserTestCaseNoValue(ConfigParserTestCase):
1327    allow_no_value = True
1328
1329
1330class ConfigParserTestCaseTrickyFile(CfgParserTestCaseClass, unittest.TestCase):
1331    config_class = configparser.ConfigParser
1332    delimiters = {'='}
1333    comment_prefixes = {'#'}
1334    allow_no_value = True
1335
1336    def test_cfgparser_dot_3(self):
1337        tricky = support.findfile("cfgparser.3")
1338        cf = self.newconfig()
1339        self.assertEqual(len(cf.read(tricky, encoding='utf-8')), 1)
1340        self.assertEqual(cf.sections(), ['strange',
1341                                         'corruption',
1342                                         'yeah, sections can be '
1343                                         'indented as well',
1344                                         'another one!',
1345                                         'no values here',
1346                                         'tricky interpolation',
1347                                         'more interpolation'])
1348        self.assertEqual(cf.getint(self.default_section, 'go',
1349                                   vars={'interpolate': '-1'}), -1)
1350        with self.assertRaises(ValueError):
1351            # no interpolation will happen
1352            cf.getint(self.default_section, 'go', raw=True,
1353                      vars={'interpolate': '-1'})
1354        self.assertEqual(len(cf.get('strange', 'other').split('\n')), 4)
1355        self.assertEqual(len(cf.get('corruption', 'value').split('\n')), 10)
1356        longname = 'yeah, sections can be indented as well'
1357        self.assertFalse(cf.getboolean(longname, 'are they subsections'))
1358        self.assertEqual(cf.get(longname, 'lets use some Unicode'), '片仮名')
1359        self.assertEqual(len(cf.items('another one!')), 5) # 4 in section and
1360                                                           # `go` from DEFAULT
1361        with self.assertRaises(configparser.InterpolationMissingOptionError):
1362            cf.items('no values here')
1363        self.assertEqual(cf.get('tricky interpolation', 'lets'), 'do this')
1364        self.assertEqual(cf.get('tricky interpolation', 'lets'),
1365                         cf.get('tricky interpolation', 'go'))
1366        self.assertEqual(cf.get('more interpolation', 'lets'), 'go shopping')
1367
1368    def test_unicode_failure(self):
1369        tricky = support.findfile("cfgparser.3")
1370        cf = self.newconfig()
1371        with self.assertRaises(UnicodeDecodeError):
1372            cf.read(tricky, encoding='ascii')
1373
1374
1375class Issue7005TestCase(unittest.TestCase):
1376    """Test output when None is set() as a value and allow_no_value == False.
1377
1378    http://bugs.python.org/issue7005
1379
1380    """
1381
1382    expected_output = "[section]\noption = None\n\n"
1383
1384    def prepare(self, config_class):
1385        # This is the default, but that's the point.
1386        cp = config_class(allow_no_value=False)
1387        cp.add_section("section")
1388        cp.set("section", "option", None)
1389        sio = io.StringIO()
1390        cp.write(sio)
1391        return sio.getvalue()
1392
1393    def test_none_as_value_stringified(self):
1394        cp = configparser.ConfigParser(allow_no_value=False)
1395        cp.add_section("section")
1396        with self.assertRaises(TypeError):
1397            cp.set("section", "option", None)
1398
1399    def test_none_as_value_stringified_raw(self):
1400        output = self.prepare(configparser.RawConfigParser)
1401        self.assertEqual(output, self.expected_output)
1402
1403
1404class SortedTestCase(RawConfigParserTestCase):
1405    dict_type = SortedDict
1406
1407    def test_sorted(self):
1408        cf = self.fromstring("[b]\n"
1409                             "o4=1\n"
1410                             "o3=2\n"
1411                             "o2=3\n"
1412                             "o1=4\n"
1413                             "[a]\n"
1414                             "k=v\n")
1415        output = io.StringIO()
1416        cf.write(output)
1417        self.assertEqual(output.getvalue(),
1418                         "[a]\n"
1419                         "k = v\n\n"
1420                         "[b]\n"
1421                         "o1 = 4\n"
1422                         "o2 = 3\n"
1423                         "o3 = 2\n"
1424                         "o4 = 1\n\n")
1425
1426
1427class CompatibleTestCase(CfgParserTestCaseClass, unittest.TestCase):
1428    config_class = configparser.RawConfigParser
1429    comment_prefixes = '#;'
1430    inline_comment_prefixes = ';'
1431
1432    def test_comment_handling(self):
1433        config_string = textwrap.dedent("""\
1434        [Commented Bar]
1435        baz=qwe ; a comment
1436        foo: bar # not a comment!
1437        # but this is a comment
1438        ; another comment
1439        quirk: this;is not a comment
1440        ; a space must precede an inline comment
1441        """)
1442        cf = self.fromstring(config_string)
1443        self.assertEqual(cf.get('Commented Bar', 'foo'),
1444                         'bar # not a comment!')
1445        self.assertEqual(cf.get('Commented Bar', 'baz'), 'qwe')
1446        self.assertEqual(cf.get('Commented Bar', 'quirk'),
1447                         'this;is not a comment')
1448
1449class CopyTestCase(BasicTestCase, unittest.TestCase):
1450    config_class = configparser.ConfigParser
1451
1452    def fromstring(self, string, defaults=None):
1453        cf = self.newconfig(defaults)
1454        cf.read_string(string)
1455        cf_copy = self.newconfig()
1456        cf_copy.read_dict(cf)
1457        # we have to clean up option duplicates that appeared because of
1458        # the magic DEFAULTSECT behaviour.
1459        for section in cf_copy.values():
1460            if section.name == self.default_section:
1461                continue
1462            for default, value in cf[self.default_section].items():
1463                if section[default] == value:
1464                    del section[default]
1465        return cf_copy
1466
1467
1468class FakeFile:
1469    def __init__(self):
1470        file_path = support.findfile("cfgparser.1")
1471        with open(file_path) as f:
1472            self.lines = f.readlines()
1473            self.lines.reverse()
1474
1475    def readline(self):
1476        if len(self.lines):
1477            return self.lines.pop()
1478        return ''
1479
1480
1481def readline_generator(f):
1482    """As advised in Doc/library/configparser.rst."""
1483    line = f.readline()
1484    while line:
1485        yield line
1486        line = f.readline()
1487
1488
1489class ReadFileTestCase(unittest.TestCase):
1490    def test_file(self):
1491        file_paths = [support.findfile("cfgparser.1")]
1492        try:
1493            file_paths.append(file_paths[0].encode('utf8'))
1494        except UnicodeEncodeError:
1495            pass   # unfortunately we can't test bytes on this path
1496        for file_path in file_paths:
1497            parser = configparser.ConfigParser()
1498            with open(file_path) as f:
1499                parser.read_file(f)
1500            self.assertIn("Foo Bar", parser)
1501            self.assertIn("foo", parser["Foo Bar"])
1502            self.assertEqual(parser["Foo Bar"]["foo"], "newbar")
1503
1504    def test_iterable(self):
1505        lines = textwrap.dedent("""
1506        [Foo Bar]
1507        foo=newbar""").strip().split('\n')
1508        parser = configparser.ConfigParser()
1509        parser.read_file(lines)
1510        self.assertIn("Foo Bar", parser)
1511        self.assertIn("foo", parser["Foo Bar"])
1512        self.assertEqual(parser["Foo Bar"]["foo"], "newbar")
1513
1514    def test_readline_generator(self):
1515        """Issue #11670."""
1516        parser = configparser.ConfigParser()
1517        with self.assertRaises(TypeError):
1518            parser.read_file(FakeFile())
1519        parser.read_file(readline_generator(FakeFile()))
1520        self.assertIn("Foo Bar", parser)
1521        self.assertIn("foo", parser["Foo Bar"])
1522        self.assertEqual(parser["Foo Bar"]["foo"], "newbar")
1523
1524    def test_source_as_bytes(self):
1525        """Issue #18260."""
1526        lines = textwrap.dedent("""
1527        [badbad]
1528        [badbad]""").strip().split('\n')
1529        parser = configparser.ConfigParser()
1530        with self.assertRaises(configparser.DuplicateSectionError) as dse:
1531            parser.read_file(lines, source=b"badbad")
1532        self.assertEqual(
1533            str(dse.exception),
1534            "While reading from b'badbad' [line  2]: section 'badbad' "
1535            "already exists"
1536        )
1537        lines = textwrap.dedent("""
1538        [badbad]
1539        bad = bad
1540        bad = bad""").strip().split('\n')
1541        parser = configparser.ConfigParser()
1542        with self.assertRaises(configparser.DuplicateOptionError) as dse:
1543            parser.read_file(lines, source=b"badbad")
1544        self.assertEqual(
1545            str(dse.exception),
1546            "While reading from b'badbad' [line  3]: option 'bad' in section "
1547            "'badbad' already exists"
1548        )
1549        lines = textwrap.dedent("""
1550        [badbad]
1551        = bad""").strip().split('\n')
1552        parser = configparser.ConfigParser()
1553        with self.assertRaises(configparser.ParsingError) as dse:
1554            parser.read_file(lines, source=b"badbad")
1555        self.assertEqual(
1556            str(dse.exception),
1557            "Source contains parsing errors: b'badbad'\n\t[line  2]: '= bad'"
1558        )
1559        lines = textwrap.dedent("""
1560        [badbad
1561        bad = bad""").strip().split('\n')
1562        parser = configparser.ConfigParser()
1563        with self.assertRaises(configparser.MissingSectionHeaderError) as dse:
1564            parser.read_file(lines, source=b"badbad")
1565        self.assertEqual(
1566            str(dse.exception),
1567            "File contains no section headers.\nfile: b'badbad', line: 1\n"
1568            "'[badbad'"
1569        )
1570
1571
1572class CoverageOneHundredTestCase(unittest.TestCase):
1573    """Covers edge cases in the codebase."""
1574
1575    def test_duplicate_option_error(self):
1576        error = configparser.DuplicateOptionError('section', 'option')
1577        self.assertEqual(error.section, 'section')
1578        self.assertEqual(error.option, 'option')
1579        self.assertEqual(error.source, None)
1580        self.assertEqual(error.lineno, None)
1581        self.assertEqual(error.args, ('section', 'option', None, None))
1582        self.assertEqual(str(error), "Option 'option' in section 'section' "
1583                                     "already exists")
1584
1585    def test_interpolation_depth_error(self):
1586        error = configparser.InterpolationDepthError('option', 'section',
1587                                                     'rawval')
1588        self.assertEqual(error.args, ('option', 'section', 'rawval'))
1589        self.assertEqual(error.option, 'option')
1590        self.assertEqual(error.section, 'section')
1591
1592    def test_parsing_error(self):
1593        with self.assertRaises(ValueError) as cm:
1594            configparser.ParsingError()
1595        self.assertEqual(str(cm.exception), "Required argument `source' not "
1596                                            "given.")
1597        with self.assertRaises(ValueError) as cm:
1598            configparser.ParsingError(source='source', filename='filename')
1599        self.assertEqual(str(cm.exception), "Cannot specify both `filename' "
1600                                            "and `source'. Use `source'.")
1601        error = configparser.ParsingError(filename='source')
1602        self.assertEqual(error.source, 'source')
1603        with warnings.catch_warnings(record=True) as w:
1604            warnings.simplefilter("always", DeprecationWarning)
1605            self.assertEqual(error.filename, 'source')
1606            error.filename = 'filename'
1607            self.assertEqual(error.source, 'filename')
1608        for warning in w:
1609            self.assertTrue(warning.category is DeprecationWarning)
1610
1611    def test_interpolation_validation(self):
1612        parser = configparser.ConfigParser()
1613        parser.read_string("""
1614            [section]
1615            invalid_percent = %
1616            invalid_reference = %(()
1617            invalid_variable = %(does_not_exist)s
1618        """)
1619        with self.assertRaises(configparser.InterpolationSyntaxError) as cm:
1620            parser['section']['invalid_percent']
1621        self.assertEqual(str(cm.exception), "'%' must be followed by '%' or "
1622                                            "'(', found: '%'")
1623        with self.assertRaises(configparser.InterpolationSyntaxError) as cm:
1624            parser['section']['invalid_reference']
1625        self.assertEqual(str(cm.exception), "bad interpolation variable "
1626                                            "reference '%(()'")
1627
1628    def test_readfp_deprecation(self):
1629        sio = io.StringIO("""
1630        [section]
1631        option = value
1632        """)
1633        parser = configparser.ConfigParser()
1634        with warnings.catch_warnings(record=True) as w:
1635            warnings.simplefilter("always", DeprecationWarning)
1636            parser.readfp(sio, filename='StringIO')
1637        for warning in w:
1638            self.assertTrue(warning.category is DeprecationWarning)
1639        self.assertEqual(len(parser), 2)
1640        self.assertEqual(parser['section']['option'], 'value')
1641
1642    def test_safeconfigparser_deprecation(self):
1643        with warnings.catch_warnings(record=True) as w:
1644            warnings.simplefilter("always", DeprecationWarning)
1645            parser = configparser.SafeConfigParser()
1646        for warning in w:
1647            self.assertTrue(warning.category is DeprecationWarning)
1648
1649    def test_sectionproxy_repr(self):
1650        parser = configparser.ConfigParser()
1651        parser.read_string("""
1652            [section]
1653            key = value
1654        """)
1655        self.assertEqual(repr(parser['section']), '<Section: section>')
1656
1657    def test_inconsistent_converters_state(self):
1658        parser = configparser.ConfigParser()
1659        import decimal
1660        parser.converters['decimal'] = decimal.Decimal
1661        parser.read_string("""
1662            [s1]
1663            one = 1
1664            [s2]
1665            two = 2
1666        """)
1667        self.assertIn('decimal', parser.converters)
1668        self.assertEqual(parser.getdecimal('s1', 'one'), 1)
1669        self.assertEqual(parser.getdecimal('s2', 'two'), 2)
1670        self.assertEqual(parser['s1'].getdecimal('one'), 1)
1671        self.assertEqual(parser['s2'].getdecimal('two'), 2)
1672        del parser.getdecimal
1673        with self.assertRaises(AttributeError):
1674            parser.getdecimal('s1', 'one')
1675        self.assertIn('decimal', parser.converters)
1676        del parser.converters['decimal']
1677        self.assertNotIn('decimal', parser.converters)
1678        with self.assertRaises(AttributeError):
1679            parser.getdecimal('s1', 'one')
1680        with self.assertRaises(AttributeError):
1681            parser['s1'].getdecimal('one')
1682        with self.assertRaises(AttributeError):
1683            parser['s2'].getdecimal('two')
1684
1685
1686class ExceptionPicklingTestCase(unittest.TestCase):
1687    """Tests for issue #13760: ConfigParser exceptions are not picklable."""
1688
1689    def test_error(self):
1690        import pickle
1691        e1 = configparser.Error('value')
1692        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1693            pickled = pickle.dumps(e1, proto)
1694            e2 = pickle.loads(pickled)
1695            self.assertEqual(e1.message, e2.message)
1696            self.assertEqual(repr(e1), repr(e2))
1697
1698    def test_nosectionerror(self):
1699        import pickle
1700        e1 = configparser.NoSectionError('section')
1701        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1702            pickled = pickle.dumps(e1, proto)
1703            e2 = pickle.loads(pickled)
1704            self.assertEqual(e1.message, e2.message)
1705            self.assertEqual(e1.args, e2.args)
1706            self.assertEqual(e1.section, e2.section)
1707            self.assertEqual(repr(e1), repr(e2))
1708
1709    def test_nooptionerror(self):
1710        import pickle
1711        e1 = configparser.NoOptionError('option', 'section')
1712        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1713            pickled = pickle.dumps(e1, proto)
1714            e2 = pickle.loads(pickled)
1715            self.assertEqual(e1.message, e2.message)
1716            self.assertEqual(e1.args, e2.args)
1717            self.assertEqual(e1.section, e2.section)
1718            self.assertEqual(e1.option, e2.option)
1719            self.assertEqual(repr(e1), repr(e2))
1720
1721    def test_duplicatesectionerror(self):
1722        import pickle
1723        e1 = configparser.DuplicateSectionError('section', 'source', 123)
1724        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1725            pickled = pickle.dumps(e1, proto)
1726            e2 = pickle.loads(pickled)
1727            self.assertEqual(e1.message, e2.message)
1728            self.assertEqual(e1.args, e2.args)
1729            self.assertEqual(e1.section, e2.section)
1730            self.assertEqual(e1.source, e2.source)
1731            self.assertEqual(e1.lineno, e2.lineno)
1732            self.assertEqual(repr(e1), repr(e2))
1733
1734    def test_duplicateoptionerror(self):
1735        import pickle
1736        e1 = configparser.DuplicateOptionError('section', 'option', 'source',
1737            123)
1738        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1739            pickled = pickle.dumps(e1, proto)
1740            e2 = pickle.loads(pickled)
1741            self.assertEqual(e1.message, e2.message)
1742            self.assertEqual(e1.args, e2.args)
1743            self.assertEqual(e1.section, e2.section)
1744            self.assertEqual(e1.option, e2.option)
1745            self.assertEqual(e1.source, e2.source)
1746            self.assertEqual(e1.lineno, e2.lineno)
1747            self.assertEqual(repr(e1), repr(e2))
1748
1749    def test_interpolationerror(self):
1750        import pickle
1751        e1 = configparser.InterpolationError('option', 'section', 'msg')
1752        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1753            pickled = pickle.dumps(e1, proto)
1754            e2 = pickle.loads(pickled)
1755            self.assertEqual(e1.message, e2.message)
1756            self.assertEqual(e1.args, e2.args)
1757            self.assertEqual(e1.section, e2.section)
1758            self.assertEqual(e1.option, e2.option)
1759            self.assertEqual(repr(e1), repr(e2))
1760
1761    def test_interpolationmissingoptionerror(self):
1762        import pickle
1763        e1 = configparser.InterpolationMissingOptionError('option', 'section',
1764            'rawval', 'reference')
1765        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1766            pickled = pickle.dumps(e1, proto)
1767            e2 = pickle.loads(pickled)
1768            self.assertEqual(e1.message, e2.message)
1769            self.assertEqual(e1.args, e2.args)
1770            self.assertEqual(e1.section, e2.section)
1771            self.assertEqual(e1.option, e2.option)
1772            self.assertEqual(e1.reference, e2.reference)
1773            self.assertEqual(repr(e1), repr(e2))
1774
1775    def test_interpolationsyntaxerror(self):
1776        import pickle
1777        e1 = configparser.InterpolationSyntaxError('option', 'section', 'msg')
1778        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1779            pickled = pickle.dumps(e1, proto)
1780            e2 = pickle.loads(pickled)
1781            self.assertEqual(e1.message, e2.message)
1782            self.assertEqual(e1.args, e2.args)
1783            self.assertEqual(e1.section, e2.section)
1784            self.assertEqual(e1.option, e2.option)
1785            self.assertEqual(repr(e1), repr(e2))
1786
1787    def test_interpolationdeptherror(self):
1788        import pickle
1789        e1 = configparser.InterpolationDepthError('option', 'section',
1790            'rawval')
1791        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1792            pickled = pickle.dumps(e1, proto)
1793            e2 = pickle.loads(pickled)
1794            self.assertEqual(e1.message, e2.message)
1795            self.assertEqual(e1.args, e2.args)
1796            self.assertEqual(e1.section, e2.section)
1797            self.assertEqual(e1.option, e2.option)
1798            self.assertEqual(repr(e1), repr(e2))
1799
1800    def test_parsingerror(self):
1801        import pickle
1802        e1 = configparser.ParsingError('source')
1803        e1.append(1, 'line1')
1804        e1.append(2, 'line2')
1805        e1.append(3, 'line3')
1806        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1807            pickled = pickle.dumps(e1, proto)
1808            e2 = pickle.loads(pickled)
1809            self.assertEqual(e1.message, e2.message)
1810            self.assertEqual(e1.args, e2.args)
1811            self.assertEqual(e1.source, e2.source)
1812            self.assertEqual(e1.errors, e2.errors)
1813            self.assertEqual(repr(e1), repr(e2))
1814        e1 = configparser.ParsingError(filename='filename')
1815        e1.append(1, 'line1')
1816        e1.append(2, 'line2')
1817        e1.append(3, 'line3')
1818        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1819            pickled = pickle.dumps(e1, proto)
1820            e2 = pickle.loads(pickled)
1821            self.assertEqual(e1.message, e2.message)
1822            self.assertEqual(e1.args, e2.args)
1823            self.assertEqual(e1.source, e2.source)
1824            self.assertEqual(e1.errors, e2.errors)
1825            self.assertEqual(repr(e1), repr(e2))
1826
1827    def test_missingsectionheadererror(self):
1828        import pickle
1829        e1 = configparser.MissingSectionHeaderError('filename', 123, 'line')
1830        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1831            pickled = pickle.dumps(e1, proto)
1832            e2 = pickle.loads(pickled)
1833            self.assertEqual(e1.message, e2.message)
1834            self.assertEqual(e1.args, e2.args)
1835            self.assertEqual(e1.line, e2.line)
1836            self.assertEqual(e1.source, e2.source)
1837            self.assertEqual(e1.lineno, e2.lineno)
1838            self.assertEqual(repr(e1), repr(e2))
1839
1840
1841class InlineCommentStrippingTestCase(unittest.TestCase):
1842    """Tests for issue #14590: ConfigParser doesn't strip inline comment when
1843    delimiter occurs earlier without preceding space.."""
1844
1845    def test_stripping(self):
1846        cfg = configparser.ConfigParser(inline_comment_prefixes=(';', '#',
1847                '//'))
1848        cfg.read_string("""
1849        [section]
1850        k1 = v1;still v1
1851        k2 = v2 ;a comment
1852        k3 = v3 ; also a comment
1853        k4 = v4;still v4 ;a comment
1854        k5 = v5;still v5 ; also a comment
1855        k6 = v6;still v6; and still v6 ;a comment
1856        k7 = v7;still v7; and still v7 ; also a comment
1857
1858        [multiprefix]
1859        k1 = v1;still v1 #a comment ; yeah, pretty much
1860        k2 = v2 // this already is a comment ; continued
1861        k3 = v3;#//still v3# and still v3 ; a comment
1862        """)
1863        s = cfg['section']
1864        self.assertEqual(s['k1'], 'v1;still v1')
1865        self.assertEqual(s['k2'], 'v2')
1866        self.assertEqual(s['k3'], 'v3')
1867        self.assertEqual(s['k4'], 'v4;still v4')
1868        self.assertEqual(s['k5'], 'v5;still v5')
1869        self.assertEqual(s['k6'], 'v6;still v6; and still v6')
1870        self.assertEqual(s['k7'], 'v7;still v7; and still v7')
1871        s = cfg['multiprefix']
1872        self.assertEqual(s['k1'], 'v1;still v1')
1873        self.assertEqual(s['k2'], 'v2')
1874        self.assertEqual(s['k3'], 'v3;#//still v3# and still v3')
1875
1876
1877class ExceptionContextTestCase(unittest.TestCase):
1878    """ Test that implementation details doesn't leak
1879    through raising exceptions. """
1880
1881    def test_get_basic_interpolation(self):
1882        parser = configparser.ConfigParser()
1883        parser.read_string("""
1884        [Paths]
1885        home_dir: /Users
1886        my_dir: %(home_dir1)s/lumberjack
1887        my_pictures: %(my_dir)s/Pictures
1888        """)
1889        cm = self.assertRaises(configparser.InterpolationMissingOptionError)
1890        with cm:
1891            parser.get('Paths', 'my_dir')
1892        self.assertIs(cm.exception.__suppress_context__, True)
1893
1894    def test_get_extended_interpolation(self):
1895        parser = configparser.ConfigParser(
1896          interpolation=configparser.ExtendedInterpolation())
1897        parser.read_string("""
1898        [Paths]
1899        home_dir: /Users
1900        my_dir: ${home_dir1}/lumberjack
1901        my_pictures: ${my_dir}/Pictures
1902        """)
1903        cm = self.assertRaises(configparser.InterpolationMissingOptionError)
1904        with cm:
1905            parser.get('Paths', 'my_dir')
1906        self.assertIs(cm.exception.__suppress_context__, True)
1907
1908    def test_missing_options(self):
1909        parser = configparser.ConfigParser()
1910        parser.read_string("""
1911        [Paths]
1912        home_dir: /Users
1913        """)
1914        with self.assertRaises(configparser.NoSectionError) as cm:
1915            parser.options('test')
1916        self.assertIs(cm.exception.__suppress_context__, True)
1917
1918    def test_missing_section(self):
1919        config = configparser.ConfigParser()
1920        with self.assertRaises(configparser.NoSectionError) as cm:
1921            config.set('Section1', 'an_int', '15')
1922        self.assertIs(cm.exception.__suppress_context__, True)
1923
1924    def test_remove_option(self):
1925        config = configparser.ConfigParser()
1926        with self.assertRaises(configparser.NoSectionError) as cm:
1927            config.remove_option('Section1', 'an_int')
1928        self.assertIs(cm.exception.__suppress_context__, True)
1929
1930
1931class ConvertersTestCase(BasicTestCase, unittest.TestCase):
1932    """Introduced in 3.5, issue #18159."""
1933
1934    config_class = configparser.ConfigParser
1935
1936    def newconfig(self, defaults=None):
1937        instance = super().newconfig(defaults=defaults)
1938        instance.converters['list'] = lambda v: [e.strip() for e in v.split()
1939                                                 if e.strip()]
1940        return instance
1941
1942    def test_converters(self):
1943        cfg = self.newconfig()
1944        self.assertIn('boolean', cfg.converters)
1945        self.assertIn('list', cfg.converters)
1946        self.assertIsNone(cfg.converters['int'])
1947        self.assertIsNone(cfg.converters['float'])
1948        self.assertIsNone(cfg.converters['boolean'])
1949        self.assertIsNotNone(cfg.converters['list'])
1950        self.assertEqual(len(cfg.converters), 4)
1951        with self.assertRaises(ValueError):
1952            cfg.converters[''] = lambda v: v
1953        with self.assertRaises(ValueError):
1954            cfg.converters[None] = lambda v: v
1955        cfg.read_string("""
1956        [s]
1957        str = string
1958        int = 1
1959        float = 0.5
1960        list = a b c d e f g
1961        bool = yes
1962        """)
1963        s = cfg['s']
1964        self.assertEqual(s['str'], 'string')
1965        self.assertEqual(s['int'], '1')
1966        self.assertEqual(s['float'], '0.5')
1967        self.assertEqual(s['list'], 'a b c d e f g')
1968        self.assertEqual(s['bool'], 'yes')
1969        self.assertEqual(cfg.get('s', 'str'), 'string')
1970        self.assertEqual(cfg.get('s', 'int'), '1')
1971        self.assertEqual(cfg.get('s', 'float'), '0.5')
1972        self.assertEqual(cfg.get('s', 'list'), 'a b c d e f g')
1973        self.assertEqual(cfg.get('s', 'bool'), 'yes')
1974        self.assertEqual(cfg.get('s', 'str'), 'string')
1975        self.assertEqual(cfg.getint('s', 'int'), 1)
1976        self.assertEqual(cfg.getfloat('s', 'float'), 0.5)
1977        self.assertEqual(cfg.getlist('s', 'list'), ['a', 'b', 'c', 'd',
1978                                                    'e', 'f', 'g'])
1979        self.assertEqual(cfg.getboolean('s', 'bool'), True)
1980        self.assertEqual(s.get('str'), 'string')
1981        self.assertEqual(s.getint('int'), 1)
1982        self.assertEqual(s.getfloat('float'), 0.5)
1983        self.assertEqual(s.getlist('list'), ['a', 'b', 'c', 'd',
1984                                             'e', 'f', 'g'])
1985        self.assertEqual(s.getboolean('bool'), True)
1986        with self.assertRaises(AttributeError):
1987            cfg.getdecimal('s', 'float')
1988        with self.assertRaises(AttributeError):
1989            s.getdecimal('float')
1990        import decimal
1991        cfg.converters['decimal'] = decimal.Decimal
1992        self.assertIn('decimal', cfg.converters)
1993        self.assertIsNotNone(cfg.converters['decimal'])
1994        self.assertEqual(len(cfg.converters), 5)
1995        dec0_5 = decimal.Decimal('0.5')
1996        self.assertEqual(cfg.getdecimal('s', 'float'), dec0_5)
1997        self.assertEqual(s.getdecimal('float'), dec0_5)
1998        del cfg.converters['decimal']
1999        self.assertNotIn('decimal', cfg.converters)
2000        self.assertEqual(len(cfg.converters), 4)
2001        with self.assertRaises(AttributeError):
2002            cfg.getdecimal('s', 'float')
2003        with self.assertRaises(AttributeError):
2004            s.getdecimal('float')
2005        with self.assertRaises(KeyError):
2006            del cfg.converters['decimal']
2007        with self.assertRaises(KeyError):
2008            del cfg.converters['']
2009        with self.assertRaises(KeyError):
2010            del cfg.converters[None]
2011
2012
2013class BlatantOverrideConvertersTestCase(unittest.TestCase):
2014    """What if somebody overrode a getboolean()? We want to make sure that in
2015    this case the automatic converters do not kick in."""
2016
2017    config = """
2018        [one]
2019        one = false
2020        two = false
2021        three = long story short
2022
2023        [two]
2024        one = false
2025        two = false
2026        three = four
2027    """
2028
2029    def test_converters_at_init(self):
2030        cfg = configparser.ConfigParser(converters={'len': len})
2031        cfg.read_string(self.config)
2032        self._test_len(cfg)
2033        self.assertIsNotNone(cfg.converters['len'])
2034
2035    def test_inheritance(self):
2036        class StrangeConfigParser(configparser.ConfigParser):
2037            gettysburg = 'a historic borough in south central Pennsylvania'
2038
2039            def getboolean(self, section, option, *, raw=False, vars=None,
2040                        fallback=configparser._UNSET):
2041                if section == option:
2042                    return True
2043                return super().getboolean(section, option, raw=raw, vars=vars,
2044                                          fallback=fallback)
2045            def getlen(self, section, option, *, raw=False, vars=None,
2046                       fallback=configparser._UNSET):
2047                return self._get_conv(section, option, len, raw=raw, vars=vars,
2048                                      fallback=fallback)
2049
2050        cfg = StrangeConfigParser()
2051        cfg.read_string(self.config)
2052        self._test_len(cfg)
2053        self.assertIsNone(cfg.converters['len'])
2054        self.assertTrue(cfg.getboolean('one', 'one'))
2055        self.assertTrue(cfg.getboolean('two', 'two'))
2056        self.assertFalse(cfg.getboolean('one', 'two'))
2057        self.assertFalse(cfg.getboolean('two', 'one'))
2058        cfg.converters['boolean'] = cfg._convert_to_boolean
2059        self.assertFalse(cfg.getboolean('one', 'one'))
2060        self.assertFalse(cfg.getboolean('two', 'two'))
2061        self.assertFalse(cfg.getboolean('one', 'two'))
2062        self.assertFalse(cfg.getboolean('two', 'one'))
2063
2064    def _test_len(self, cfg):
2065        self.assertEqual(len(cfg.converters), 4)
2066        self.assertIn('boolean', cfg.converters)
2067        self.assertIn('len', cfg.converters)
2068        self.assertNotIn('tysburg', cfg.converters)
2069        self.assertIsNone(cfg.converters['int'])
2070        self.assertIsNone(cfg.converters['float'])
2071        self.assertIsNone(cfg.converters['boolean'])
2072        self.assertEqual(cfg.getlen('one', 'one'), 5)
2073        self.assertEqual(cfg.getlen('one', 'two'), 5)
2074        self.assertEqual(cfg.getlen('one', 'three'), 16)
2075        self.assertEqual(cfg.getlen('two', 'one'), 5)
2076        self.assertEqual(cfg.getlen('two', 'two'), 5)
2077        self.assertEqual(cfg.getlen('two', 'three'), 4)
2078        self.assertEqual(cfg.getlen('two', 'four', fallback=0), 0)
2079        with self.assertRaises(configparser.NoOptionError):
2080            cfg.getlen('two', 'four')
2081        self.assertEqual(cfg['one'].getlen('one'), 5)
2082        self.assertEqual(cfg['one'].getlen('two'), 5)
2083        self.assertEqual(cfg['one'].getlen('three'), 16)
2084        self.assertEqual(cfg['two'].getlen('one'), 5)
2085        self.assertEqual(cfg['two'].getlen('two'), 5)
2086        self.assertEqual(cfg['two'].getlen('three'), 4)
2087        self.assertEqual(cfg['two'].getlen('four', 0), 0)
2088        self.assertEqual(cfg['two'].getlen('four'), None)
2089
2090    def test_instance_assignment(self):
2091        cfg = configparser.ConfigParser()
2092        cfg.getboolean = lambda section, option: True
2093        cfg.getlen = lambda section, option: len(cfg[section][option])
2094        cfg.read_string(self.config)
2095        self.assertEqual(len(cfg.converters), 3)
2096        self.assertIn('boolean', cfg.converters)
2097        self.assertNotIn('len', cfg.converters)
2098        self.assertIsNone(cfg.converters['int'])
2099        self.assertIsNone(cfg.converters['float'])
2100        self.assertIsNone(cfg.converters['boolean'])
2101        self.assertTrue(cfg.getboolean('one', 'one'))
2102        self.assertTrue(cfg.getboolean('two', 'two'))
2103        self.assertTrue(cfg.getboolean('one', 'two'))
2104        self.assertTrue(cfg.getboolean('two', 'one'))
2105        cfg.converters['boolean'] = cfg._convert_to_boolean
2106        self.assertFalse(cfg.getboolean('one', 'one'))
2107        self.assertFalse(cfg.getboolean('two', 'two'))
2108        self.assertFalse(cfg.getboolean('one', 'two'))
2109        self.assertFalse(cfg.getboolean('two', 'one'))
2110        self.assertEqual(cfg.getlen('one', 'one'), 5)
2111        self.assertEqual(cfg.getlen('one', 'two'), 5)
2112        self.assertEqual(cfg.getlen('one', 'three'), 16)
2113        self.assertEqual(cfg.getlen('two', 'one'), 5)
2114        self.assertEqual(cfg.getlen('two', 'two'), 5)
2115        self.assertEqual(cfg.getlen('two', 'three'), 4)
2116        # If a getter impl is assigned straight to the instance, it won't
2117        # be available on the section proxies.
2118        with self.assertRaises(AttributeError):
2119            self.assertEqual(cfg['one'].getlen('one'), 5)
2120        with self.assertRaises(AttributeError):
2121            self.assertEqual(cfg['two'].getlen('one'), 5)
2122
2123
2124class MiscTestCase(unittest.TestCase):
2125    def test__all__(self):
2126        blacklist = {"Error"}
2127        support.check__all__(self, configparser, blacklist=blacklist)
2128
2129
2130if __name__ == '__main__':
2131    unittest.main()
2132