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        # For bpo-32108, assigning default_section to itself.
854        cf[self.default_section] = cf[self.default_section]
855        self.assertNotEqual(set(cf[self.default_section].keys()), set())
856        cf[self.default_section] = {}
857        self.assertEqual(set(cf[self.default_section].keys()), set())
858        self.assertEqual(set(cf['section1'].keys()), {'name1'})
859        self.assertEqual(set(cf['section2'].keys()), {'name22'})
860        self.assertEqual(set(cf['section3'].keys()), set())
861        self.assertEqual(cf.sections(), ['section1', 'section2', 'section3'])
862        # For bpo-32108, assigning section to itself.
863        cf['section2'] = cf['section2']
864        self.assertEqual(set(cf['section2'].keys()), {'name22'})
865
866    def test_invalid_multiline_value(self):
867        if self.allow_no_value:
868            self.skipTest('if no_value is allowed, ParsingError is not raised')
869
870        invalid = textwrap.dedent("""\
871            [DEFAULT]
872            test {0} test
873            invalid""".format(self.delimiters[0])
874        )
875        cf = self.newconfig()
876        with self.assertRaises(configparser.ParsingError):
877            cf.read_string(invalid)
878        self.assertEqual(cf.get('DEFAULT', 'test'), 'test')
879        self.assertEqual(cf['DEFAULT']['test'], 'test')
880
881
882class StrictTestCase(BasicTestCase, unittest.TestCase):
883    config_class = configparser.RawConfigParser
884    strict = True
885
886
887class ConfigParserTestCase(BasicTestCase, unittest.TestCase):
888    config_class = configparser.ConfigParser
889
890    def test_interpolation(self):
891        cf = self.get_interpolation_config()
892        eq = self.assertEqual
893        eq(cf.get("Foo", "bar"), "something with interpolation (1 step)")
894        eq(cf.get("Foo", "bar9"),
895           "something with lots of interpolation (9 steps)")
896        eq(cf.get("Foo", "bar10"),
897           "something with lots of interpolation (10 steps)")
898        e = self.get_error(cf, configparser.InterpolationDepthError, "Foo", "bar11")
899        if self.interpolation == configparser._UNSET:
900            self.assertEqual(e.args, ("bar11", "Foo",
901                "something %(with11)s lots of interpolation (11 steps)"))
902        elif isinstance(self.interpolation, configparser.LegacyInterpolation):
903            self.assertEqual(e.args, ("bar11", "Foo",
904                "something %(with11)s lots of interpolation (11 steps)"))
905
906    def test_interpolation_missing_value(self):
907        cf = self.get_interpolation_config()
908        e = self.get_error(cf, configparser.InterpolationMissingOptionError,
909                           "Interpolation Error", "name")
910        self.assertEqual(e.reference, "reference")
911        self.assertEqual(e.section, "Interpolation Error")
912        self.assertEqual(e.option, "name")
913        if self.interpolation == configparser._UNSET:
914            self.assertEqual(e.args, ('name', 'Interpolation Error',
915                                    '%(reference)s', 'reference'))
916        elif isinstance(self.interpolation, configparser.LegacyInterpolation):
917            self.assertEqual(e.args, ('name', 'Interpolation Error',
918                                    '%(reference)s', 'reference'))
919
920    def test_items(self):
921        self.check_items_config([('default', '<default>'),
922                                 ('getdefault', '|<default>|'),
923                                 ('key', '|value|'),
924                                 ('name', 'value')])
925
926    def test_safe_interpolation(self):
927        # See http://www.python.org/sf/511737
928        cf = self.fromstring("[section]\n"
929                             "option1{eq}xxx\n"
930                             "option2{eq}%(option1)s/xxx\n"
931                             "ok{eq}%(option1)s/%%s\n"
932                             "not_ok{eq}%(option2)s/%%s".format(
933                                 eq=self.delimiters[0]))
934        self.assertEqual(cf.get("section", "ok"), "xxx/%s")
935        if self.interpolation == configparser._UNSET:
936            self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s")
937        elif isinstance(self.interpolation, configparser.LegacyInterpolation):
938            with self.assertRaises(TypeError):
939                cf.get("section", "not_ok")
940
941    def test_set_malformatted_interpolation(self):
942        cf = self.fromstring("[sect]\n"
943                             "option1{eq}foo\n".format(eq=self.delimiters[0]))
944
945        self.assertEqual(cf.get('sect', "option1"), "foo")
946
947        self.assertRaises(ValueError, cf.set, "sect", "option1", "%foo")
948        self.assertRaises(ValueError, cf.set, "sect", "option1", "foo%")
949        self.assertRaises(ValueError, cf.set, "sect", "option1", "f%oo")
950
951        self.assertEqual(cf.get('sect', "option1"), "foo")
952
953        # bug #5741: double percents are *not* malformed
954        cf.set("sect", "option2", "foo%%bar")
955        self.assertEqual(cf.get("sect", "option2"), "foo%bar")
956
957    def test_set_nonstring_types(self):
958        cf = self.fromstring("[sect]\n"
959                             "option1{eq}foo\n".format(eq=self.delimiters[0]))
960        # Check that we get a TypeError when setting non-string values
961        # in an existing section:
962        self.assertRaises(TypeError, cf.set, "sect", "option1", 1)
963        self.assertRaises(TypeError, cf.set, "sect", "option1", 1.0)
964        self.assertRaises(TypeError, cf.set, "sect", "option1", object())
965        self.assertRaises(TypeError, cf.set, "sect", "option2", 1)
966        self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0)
967        self.assertRaises(TypeError, cf.set, "sect", "option2", object())
968        self.assertRaises(TypeError, cf.set, "sect", 123, "invalid opt name!")
969        self.assertRaises(TypeError, cf.add_section, 123)
970
971    def test_add_section_default(self):
972        cf = self.newconfig()
973        self.assertRaises(ValueError, cf.add_section, self.default_section)
974
975    def test_defaults_keyword(self):
976        """bpo-23835 fix for ConfigParser"""
977        cf = self.newconfig(defaults={1: 2.4})
978        self.assertEqual(cf[self.default_section]['1'], '2.4')
979        self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.4)
980        cf = self.newconfig(defaults={"A": 5.2})
981        self.assertEqual(cf[self.default_section]['a'], '5.2')
982        self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.2)
983
984
985class ConfigParserTestCaseNoInterpolation(BasicTestCase, unittest.TestCase):
986    config_class = configparser.ConfigParser
987    interpolation = None
988    ini = textwrap.dedent("""
989        [numbers]
990        one = 1
991        two = %(one)s * 2
992        three = ${common:one} * 3
993
994        [hexen]
995        sixteen = ${numbers:two} * 8
996    """).strip()
997
998    def assertMatchesIni(self, cf):
999        self.assertEqual(cf['numbers']['one'], '1')
1000        self.assertEqual(cf['numbers']['two'], '%(one)s * 2')
1001        self.assertEqual(cf['numbers']['three'], '${common:one} * 3')
1002        self.assertEqual(cf['hexen']['sixteen'], '${numbers:two} * 8')
1003
1004    def test_no_interpolation(self):
1005        cf = self.fromstring(self.ini)
1006        self.assertMatchesIni(cf)
1007
1008    def test_empty_case(self):
1009        cf = self.newconfig()
1010        self.assertIsNone(cf.read_string(""))
1011
1012    def test_none_as_default_interpolation(self):
1013        class CustomConfigParser(configparser.ConfigParser):
1014            _DEFAULT_INTERPOLATION = None
1015
1016        cf = CustomConfigParser()
1017        cf.read_string(self.ini)
1018        self.assertMatchesIni(cf)
1019
1020
1021class ConfigParserTestCaseLegacyInterpolation(ConfigParserTestCase):
1022    config_class = configparser.ConfigParser
1023    interpolation = configparser.LegacyInterpolation()
1024
1025    def test_set_malformatted_interpolation(self):
1026        cf = self.fromstring("[sect]\n"
1027                             "option1{eq}foo\n".format(eq=self.delimiters[0]))
1028
1029        self.assertEqual(cf.get('sect', "option1"), "foo")
1030
1031        cf.set("sect", "option1", "%foo")
1032        self.assertEqual(cf.get('sect', "option1"), "%foo")
1033        cf.set("sect", "option1", "foo%")
1034        self.assertEqual(cf.get('sect', "option1"), "foo%")
1035        cf.set("sect", "option1", "f%oo")
1036        self.assertEqual(cf.get('sect', "option1"), "f%oo")
1037
1038        # bug #5741: double percents are *not* malformed
1039        cf.set("sect", "option2", "foo%%bar")
1040        self.assertEqual(cf.get("sect", "option2"), "foo%%bar")
1041
1042
1043class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase):
1044    delimiters = (':=', '$')
1045    comment_prefixes = ('//', '"')
1046    inline_comment_prefixes = ('//', '"')
1047
1048
1049class ConfigParserTestCaseNonStandardDefaultSection(ConfigParserTestCase):
1050    default_section = 'general'
1051
1052
1053class MultilineValuesTestCase(BasicTestCase, unittest.TestCase):
1054    config_class = configparser.ConfigParser
1055    wonderful_spam = ("I'm having spam spam spam spam "
1056                      "spam spam spam beaked beans spam "
1057                      "spam spam and spam!").replace(' ', '\t\n')
1058
1059    def setUp(self):
1060        cf = self.newconfig()
1061        for i in range(100):
1062            s = 'section{}'.format(i)
1063            cf.add_section(s)
1064            for j in range(10):
1065                cf.set(s, 'lovely_spam{}'.format(j), self.wonderful_spam)
1066        with open(support.TESTFN, 'w') as f:
1067            cf.write(f)
1068
1069    def tearDown(self):
1070        os.unlink(support.TESTFN)
1071
1072    def test_dominating_multiline_values(self):
1073        # We're reading from file because this is where the code changed
1074        # during performance updates in Python 3.2
1075        cf_from_file = self.newconfig()
1076        with open(support.TESTFN) as f:
1077            cf_from_file.read_file(f)
1078        self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'),
1079                         self.wonderful_spam.replace('\t\n', '\n'))
1080
1081
1082class RawConfigParserTestCase(BasicTestCase, unittest.TestCase):
1083    config_class = configparser.RawConfigParser
1084
1085    def test_interpolation(self):
1086        cf = self.get_interpolation_config()
1087        eq = self.assertEqual
1088        eq(cf.get("Foo", "bar"),
1089           "something %(with1)s interpolation (1 step)")
1090        eq(cf.get("Foo", "bar9"),
1091           "something %(with9)s lots of interpolation (9 steps)")
1092        eq(cf.get("Foo", "bar10"),
1093           "something %(with10)s lots of interpolation (10 steps)")
1094        eq(cf.get("Foo", "bar11"),
1095           "something %(with11)s lots of interpolation (11 steps)")
1096
1097    def test_items(self):
1098        self.check_items_config([('default', '<default>'),
1099                                 ('getdefault', '|%(default)s|'),
1100                                 ('key', '|%(name)s|'),
1101                                 ('name', '%(value)s')])
1102
1103    def test_set_nonstring_types(self):
1104        cf = self.newconfig()
1105        cf.add_section('non-string')
1106        cf.set('non-string', 'int', 1)
1107        cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13])
1108        cf.set('non-string', 'dict', {'pi': 3.14159})
1109        self.assertEqual(cf.get('non-string', 'int'), 1)
1110        self.assertEqual(cf.get('non-string', 'list'),
1111                         [0, 1, 1, 2, 3, 5, 8, 13])
1112        self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159})
1113        cf.add_section(123)
1114        cf.set(123, 'this is sick', True)
1115        self.assertEqual(cf.get(123, 'this is sick'), True)
1116        if cf._dict is configparser._default_dict:
1117            # would not work for SortedDict; only checking for the most common
1118            # default dictionary (dict)
1119            cf.optionxform = lambda x: x
1120            cf.set('non-string', 1, 1)
1121            self.assertEqual(cf.get('non-string', 1), 1)
1122
1123    def test_defaults_keyword(self):
1124        """bpo-23835 legacy behavior for RawConfigParser"""
1125        with self.assertRaises(AttributeError) as ctx:
1126            self.newconfig(defaults={1: 2.4})
1127        err = ctx.exception
1128        self.assertEqual(str(err), "'int' object has no attribute 'lower'")
1129        cf = self.newconfig(defaults={"A": 5.2})
1130        self.assertAlmostEqual(cf[self.default_section]['a'], 5.2)
1131
1132
1133class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase):
1134    delimiters = (':=', '$')
1135    comment_prefixes = ('//', '"')
1136    inline_comment_prefixes = ('//', '"')
1137
1138
1139class RawConfigParserTestSambaConf(CfgParserTestCaseClass, unittest.TestCase):
1140    config_class = configparser.RawConfigParser
1141    comment_prefixes = ('#', ';', '----')
1142    inline_comment_prefixes = ('//',)
1143    empty_lines_in_values = False
1144
1145    def test_reading(self):
1146        smbconf = support.findfile("cfgparser.2")
1147        # check when we pass a mix of readable and non-readable files:
1148        cf = self.newconfig()
1149        parsed_files = cf.read([smbconf, "nonexistent-file"], encoding='utf-8')
1150        self.assertEqual(parsed_files, [smbconf])
1151        sections = ['global', 'homes', 'printers',
1152                    'print$', 'pdf-generator', 'tmp', 'Agustin']
1153        self.assertEqual(cf.sections(), sections)
1154        self.assertEqual(cf.get("global", "workgroup"), "MDKGROUP")
1155        self.assertEqual(cf.getint("global", "max log size"), 50)
1156        self.assertEqual(cf.get("global", "hosts allow"), "127.")
1157        self.assertEqual(cf.get("tmp", "echo command"), "cat %s; rm %s")
1158
1159class ConfigParserTestCaseExtendedInterpolation(BasicTestCase, unittest.TestCase):
1160    config_class = configparser.ConfigParser
1161    interpolation = configparser.ExtendedInterpolation()
1162    default_section = 'common'
1163    strict = True
1164
1165    def fromstring(self, string, defaults=None, optionxform=None):
1166        cf = self.newconfig(defaults)
1167        if optionxform:
1168            cf.optionxform = optionxform
1169        cf.read_string(string)
1170        return cf
1171
1172    def test_extended_interpolation(self):
1173        cf = self.fromstring(textwrap.dedent("""
1174            [common]
1175            favourite Beatle = Paul
1176            favourite color = green
1177
1178            [tom]
1179            favourite band = ${favourite color} day
1180            favourite pope = John ${favourite Beatle} II
1181            sequel = ${favourite pope}I
1182
1183            [ambv]
1184            favourite Beatle = George
1185            son of Edward VII = ${favourite Beatle} V
1186            son of George V = ${son of Edward VII}I
1187
1188            [stanley]
1189            favourite Beatle = ${ambv:favourite Beatle}
1190            favourite pope = ${tom:favourite pope}
1191            favourite color = black
1192            favourite state of mind = paranoid
1193            favourite movie = soylent ${common:favourite color}
1194            favourite song = ${favourite color} sabbath - ${favourite state of mind}
1195        """).strip())
1196
1197        eq = self.assertEqual
1198        eq(cf['common']['favourite Beatle'], 'Paul')
1199        eq(cf['common']['favourite color'], 'green')
1200        eq(cf['tom']['favourite Beatle'], 'Paul')
1201        eq(cf['tom']['favourite color'], 'green')
1202        eq(cf['tom']['favourite band'], 'green day')
1203        eq(cf['tom']['favourite pope'], 'John Paul II')
1204        eq(cf['tom']['sequel'], 'John Paul III')
1205        eq(cf['ambv']['favourite Beatle'], 'George')
1206        eq(cf['ambv']['favourite color'], 'green')
1207        eq(cf['ambv']['son of Edward VII'], 'George V')
1208        eq(cf['ambv']['son of George V'], 'George VI')
1209        eq(cf['stanley']['favourite Beatle'], 'George')
1210        eq(cf['stanley']['favourite color'], 'black')
1211        eq(cf['stanley']['favourite state of mind'], 'paranoid')
1212        eq(cf['stanley']['favourite movie'], 'soylent green')
1213        eq(cf['stanley']['favourite pope'], 'John Paul II')
1214        eq(cf['stanley']['favourite song'],
1215           'black sabbath - paranoid')
1216
1217    def test_endless_loop(self):
1218        cf = self.fromstring(textwrap.dedent("""
1219            [one for you]
1220            ping = ${one for me:pong}
1221
1222            [one for me]
1223            pong = ${one for you:ping}
1224
1225            [selfish]
1226            me = ${me}
1227        """).strip())
1228
1229        with self.assertRaises(configparser.InterpolationDepthError):
1230            cf['one for you']['ping']
1231        with self.assertRaises(configparser.InterpolationDepthError):
1232            cf['selfish']['me']
1233
1234    def test_strange_options(self):
1235        cf = self.fromstring("""
1236            [dollars]
1237            $var = $$value
1238            $var2 = ${$var}
1239            ${sick} = cannot interpolate me
1240
1241            [interpolated]
1242            $other = ${dollars:$var}
1243            $trying = ${dollars:${sick}}
1244        """)
1245
1246        self.assertEqual(cf['dollars']['$var'], '$value')
1247        self.assertEqual(cf['interpolated']['$other'], '$value')
1248        self.assertEqual(cf['dollars']['${sick}'], 'cannot interpolate me')
1249        exception_class = configparser.InterpolationMissingOptionError
1250        with self.assertRaises(exception_class) as cm:
1251            cf['interpolated']['$trying']
1252        self.assertEqual(cm.exception.reference, 'dollars:${sick')
1253        self.assertEqual(cm.exception.args[2], '${dollars:${sick}}') #rawval
1254
1255    def test_case_sensitivity_basic(self):
1256        ini = textwrap.dedent("""
1257            [common]
1258            optionlower = value
1259            OptionUpper = Value
1260
1261            [Common]
1262            optionlower = a better ${common:optionlower}
1263            OptionUpper = A Better ${common:OptionUpper}
1264
1265            [random]
1266            foolower = ${common:optionlower} redefined
1267            FooUpper = ${Common:OptionUpper} Redefined
1268        """).strip()
1269
1270        cf = self.fromstring(ini)
1271        eq = self.assertEqual
1272        eq(cf['common']['optionlower'], 'value')
1273        eq(cf['common']['OptionUpper'], 'Value')
1274        eq(cf['Common']['optionlower'], 'a better value')
1275        eq(cf['Common']['OptionUpper'], 'A Better Value')
1276        eq(cf['random']['foolower'], 'value redefined')
1277        eq(cf['random']['FooUpper'], 'A Better Value Redefined')
1278
1279    def test_case_sensitivity_conflicts(self):
1280        ini = textwrap.dedent("""
1281            [common]
1282            option = value
1283            Option = Value
1284
1285            [Common]
1286            option = a better ${common:option}
1287            Option = A Better ${common:Option}
1288
1289            [random]
1290            foo = ${common:option} redefined
1291            Foo = ${Common:Option} Redefined
1292        """).strip()
1293        with self.assertRaises(configparser.DuplicateOptionError):
1294            cf = self.fromstring(ini)
1295
1296        # raw options
1297        cf = self.fromstring(ini, optionxform=lambda opt: opt)
1298        eq = self.assertEqual
1299        eq(cf['common']['option'], 'value')
1300        eq(cf['common']['Option'], 'Value')
1301        eq(cf['Common']['option'], 'a better value')
1302        eq(cf['Common']['Option'], 'A Better Value')
1303        eq(cf['random']['foo'], 'value redefined')
1304        eq(cf['random']['Foo'], 'A Better Value Redefined')
1305
1306    def test_other_errors(self):
1307        cf = self.fromstring("""
1308            [interpolation fail]
1309            case1 = ${where's the brace
1310            case2 = ${does_not_exist}
1311            case3 = ${wrong_section:wrong_value}
1312            case4 = ${i:like:colon:characters}
1313            case5 = $100 for Fail No 5!
1314        """)
1315
1316        with self.assertRaises(configparser.InterpolationSyntaxError):
1317            cf['interpolation fail']['case1']
1318        with self.assertRaises(configparser.InterpolationMissingOptionError):
1319            cf['interpolation fail']['case2']
1320        with self.assertRaises(configparser.InterpolationMissingOptionError):
1321            cf['interpolation fail']['case3']
1322        with self.assertRaises(configparser.InterpolationSyntaxError):
1323            cf['interpolation fail']['case4']
1324        with self.assertRaises(configparser.InterpolationSyntaxError):
1325            cf['interpolation fail']['case5']
1326        with self.assertRaises(ValueError):
1327            cf['interpolation fail']['case6'] = "BLACK $ABBATH"
1328
1329
1330class ConfigParserTestCaseNoValue(ConfigParserTestCase):
1331    allow_no_value = True
1332
1333
1334class ConfigParserTestCaseTrickyFile(CfgParserTestCaseClass, unittest.TestCase):
1335    config_class = configparser.ConfigParser
1336    delimiters = {'='}
1337    comment_prefixes = {'#'}
1338    allow_no_value = True
1339
1340    def test_cfgparser_dot_3(self):
1341        tricky = support.findfile("cfgparser.3")
1342        cf = self.newconfig()
1343        self.assertEqual(len(cf.read(tricky, encoding='utf-8')), 1)
1344        self.assertEqual(cf.sections(), ['strange',
1345                                         'corruption',
1346                                         'yeah, sections can be '
1347                                         'indented as well',
1348                                         'another one!',
1349                                         'no values here',
1350                                         'tricky interpolation',
1351                                         'more interpolation'])
1352        self.assertEqual(cf.getint(self.default_section, 'go',
1353                                   vars={'interpolate': '-1'}), -1)
1354        with self.assertRaises(ValueError):
1355            # no interpolation will happen
1356            cf.getint(self.default_section, 'go', raw=True,
1357                      vars={'interpolate': '-1'})
1358        self.assertEqual(len(cf.get('strange', 'other').split('\n')), 4)
1359        self.assertEqual(len(cf.get('corruption', 'value').split('\n')), 10)
1360        longname = 'yeah, sections can be indented as well'
1361        self.assertFalse(cf.getboolean(longname, 'are they subsections'))
1362        self.assertEqual(cf.get(longname, 'lets use some Unicode'), '片仮名')
1363        self.assertEqual(len(cf.items('another one!')), 5) # 4 in section and
1364                                                           # `go` from DEFAULT
1365        with self.assertRaises(configparser.InterpolationMissingOptionError):
1366            cf.items('no values here')
1367        self.assertEqual(cf.get('tricky interpolation', 'lets'), 'do this')
1368        self.assertEqual(cf.get('tricky interpolation', 'lets'),
1369                         cf.get('tricky interpolation', 'go'))
1370        self.assertEqual(cf.get('more interpolation', 'lets'), 'go shopping')
1371
1372    def test_unicode_failure(self):
1373        tricky = support.findfile("cfgparser.3")
1374        cf = self.newconfig()
1375        with self.assertRaises(UnicodeDecodeError):
1376            cf.read(tricky, encoding='ascii')
1377
1378
1379class Issue7005TestCase(unittest.TestCase):
1380    """Test output when None is set() as a value and allow_no_value == False.
1381
1382    http://bugs.python.org/issue7005
1383
1384    """
1385
1386    expected_output = "[section]\noption = None\n\n"
1387
1388    def prepare(self, config_class):
1389        # This is the default, but that's the point.
1390        cp = config_class(allow_no_value=False)
1391        cp.add_section("section")
1392        cp.set("section", "option", None)
1393        sio = io.StringIO()
1394        cp.write(sio)
1395        return sio.getvalue()
1396
1397    def test_none_as_value_stringified(self):
1398        cp = configparser.ConfigParser(allow_no_value=False)
1399        cp.add_section("section")
1400        with self.assertRaises(TypeError):
1401            cp.set("section", "option", None)
1402
1403    def test_none_as_value_stringified_raw(self):
1404        output = self.prepare(configparser.RawConfigParser)
1405        self.assertEqual(output, self.expected_output)
1406
1407
1408class SortedTestCase(RawConfigParserTestCase):
1409    dict_type = SortedDict
1410
1411    def test_sorted(self):
1412        cf = self.fromstring("[b]\n"
1413                             "o4=1\n"
1414                             "o3=2\n"
1415                             "o2=3\n"
1416                             "o1=4\n"
1417                             "[a]\n"
1418                             "k=v\n")
1419        output = io.StringIO()
1420        cf.write(output)
1421        self.assertEqual(output.getvalue(),
1422                         "[a]\n"
1423                         "k = v\n\n"
1424                         "[b]\n"
1425                         "o1 = 4\n"
1426                         "o2 = 3\n"
1427                         "o3 = 2\n"
1428                         "o4 = 1\n\n")
1429
1430
1431class CompatibleTestCase(CfgParserTestCaseClass, unittest.TestCase):
1432    config_class = configparser.RawConfigParser
1433    comment_prefixes = '#;'
1434    inline_comment_prefixes = ';'
1435
1436    def test_comment_handling(self):
1437        config_string = textwrap.dedent("""\
1438        [Commented Bar]
1439        baz=qwe ; a comment
1440        foo: bar # not a comment!
1441        # but this is a comment
1442        ; another comment
1443        quirk: this;is not a comment
1444        ; a space must precede an inline comment
1445        """)
1446        cf = self.fromstring(config_string)
1447        self.assertEqual(cf.get('Commented Bar', 'foo'),
1448                         'bar # not a comment!')
1449        self.assertEqual(cf.get('Commented Bar', 'baz'), 'qwe')
1450        self.assertEqual(cf.get('Commented Bar', 'quirk'),
1451                         'this;is not a comment')
1452
1453class CopyTestCase(BasicTestCase, unittest.TestCase):
1454    config_class = configparser.ConfigParser
1455
1456    def fromstring(self, string, defaults=None):
1457        cf = self.newconfig(defaults)
1458        cf.read_string(string)
1459        cf_copy = self.newconfig()
1460        cf_copy.read_dict(cf)
1461        # we have to clean up option duplicates that appeared because of
1462        # the magic DEFAULTSECT behaviour.
1463        for section in cf_copy.values():
1464            if section.name == self.default_section:
1465                continue
1466            for default, value in cf[self.default_section].items():
1467                if section[default] == value:
1468                    del section[default]
1469        return cf_copy
1470
1471
1472class FakeFile:
1473    def __init__(self):
1474        file_path = support.findfile("cfgparser.1")
1475        with open(file_path) as f:
1476            self.lines = f.readlines()
1477            self.lines.reverse()
1478
1479    def readline(self):
1480        if len(self.lines):
1481            return self.lines.pop()
1482        return ''
1483
1484
1485def readline_generator(f):
1486    """As advised in Doc/library/configparser.rst."""
1487    line = f.readline()
1488    while line:
1489        yield line
1490        line = f.readline()
1491
1492
1493class ReadFileTestCase(unittest.TestCase):
1494    def test_file(self):
1495        file_paths = [support.findfile("cfgparser.1")]
1496        try:
1497            file_paths.append(file_paths[0].encode('utf8'))
1498        except UnicodeEncodeError:
1499            pass   # unfortunately we can't test bytes on this path
1500        for file_path in file_paths:
1501            parser = configparser.ConfigParser()
1502            with open(file_path) as f:
1503                parser.read_file(f)
1504            self.assertIn("Foo Bar", parser)
1505            self.assertIn("foo", parser["Foo Bar"])
1506            self.assertEqual(parser["Foo Bar"]["foo"], "newbar")
1507
1508    def test_iterable(self):
1509        lines = textwrap.dedent("""
1510        [Foo Bar]
1511        foo=newbar""").strip().split('\n')
1512        parser = configparser.ConfigParser()
1513        parser.read_file(lines)
1514        self.assertIn("Foo Bar", parser)
1515        self.assertIn("foo", parser["Foo Bar"])
1516        self.assertEqual(parser["Foo Bar"]["foo"], "newbar")
1517
1518    def test_readline_generator(self):
1519        """Issue #11670."""
1520        parser = configparser.ConfigParser()
1521        with self.assertRaises(TypeError):
1522            parser.read_file(FakeFile())
1523        parser.read_file(readline_generator(FakeFile()))
1524        self.assertIn("Foo Bar", parser)
1525        self.assertIn("foo", parser["Foo Bar"])
1526        self.assertEqual(parser["Foo Bar"]["foo"], "newbar")
1527
1528    def test_source_as_bytes(self):
1529        """Issue #18260."""
1530        lines = textwrap.dedent("""
1531        [badbad]
1532        [badbad]""").strip().split('\n')
1533        parser = configparser.ConfigParser()
1534        with self.assertRaises(configparser.DuplicateSectionError) as dse:
1535            parser.read_file(lines, source=b"badbad")
1536        self.assertEqual(
1537            str(dse.exception),
1538            "While reading from b'badbad' [line  2]: section 'badbad' "
1539            "already exists"
1540        )
1541        lines = textwrap.dedent("""
1542        [badbad]
1543        bad = bad
1544        bad = bad""").strip().split('\n')
1545        parser = configparser.ConfigParser()
1546        with self.assertRaises(configparser.DuplicateOptionError) as dse:
1547            parser.read_file(lines, source=b"badbad")
1548        self.assertEqual(
1549            str(dse.exception),
1550            "While reading from b'badbad' [line  3]: option 'bad' in section "
1551            "'badbad' already exists"
1552        )
1553        lines = textwrap.dedent("""
1554        [badbad]
1555        = bad""").strip().split('\n')
1556        parser = configparser.ConfigParser()
1557        with self.assertRaises(configparser.ParsingError) as dse:
1558            parser.read_file(lines, source=b"badbad")
1559        self.assertEqual(
1560            str(dse.exception),
1561            "Source contains parsing errors: b'badbad'\n\t[line  2]: '= bad'"
1562        )
1563        lines = textwrap.dedent("""
1564        [badbad
1565        bad = bad""").strip().split('\n')
1566        parser = configparser.ConfigParser()
1567        with self.assertRaises(configparser.MissingSectionHeaderError) as dse:
1568            parser.read_file(lines, source=b"badbad")
1569        self.assertEqual(
1570            str(dse.exception),
1571            "File contains no section headers.\nfile: b'badbad', line: 1\n"
1572            "'[badbad'"
1573        )
1574
1575
1576class CoverageOneHundredTestCase(unittest.TestCase):
1577    """Covers edge cases in the codebase."""
1578
1579    def test_duplicate_option_error(self):
1580        error = configparser.DuplicateOptionError('section', 'option')
1581        self.assertEqual(error.section, 'section')
1582        self.assertEqual(error.option, 'option')
1583        self.assertEqual(error.source, None)
1584        self.assertEqual(error.lineno, None)
1585        self.assertEqual(error.args, ('section', 'option', None, None))
1586        self.assertEqual(str(error), "Option 'option' in section 'section' "
1587                                     "already exists")
1588
1589    def test_interpolation_depth_error(self):
1590        error = configparser.InterpolationDepthError('option', 'section',
1591                                                     'rawval')
1592        self.assertEqual(error.args, ('option', 'section', 'rawval'))
1593        self.assertEqual(error.option, 'option')
1594        self.assertEqual(error.section, 'section')
1595
1596    def test_parsing_error(self):
1597        with self.assertRaises(ValueError) as cm:
1598            configparser.ParsingError()
1599        self.assertEqual(str(cm.exception), "Required argument `source' not "
1600                                            "given.")
1601        with self.assertRaises(ValueError) as cm:
1602            configparser.ParsingError(source='source', filename='filename')
1603        self.assertEqual(str(cm.exception), "Cannot specify both `filename' "
1604                                            "and `source'. Use `source'.")
1605        error = configparser.ParsingError(filename='source')
1606        self.assertEqual(error.source, 'source')
1607        with warnings.catch_warnings(record=True) as w:
1608            warnings.simplefilter("always", DeprecationWarning)
1609            self.assertEqual(error.filename, 'source')
1610            error.filename = 'filename'
1611            self.assertEqual(error.source, 'filename')
1612        for warning in w:
1613            self.assertTrue(warning.category is DeprecationWarning)
1614
1615    def test_interpolation_validation(self):
1616        parser = configparser.ConfigParser()
1617        parser.read_string("""
1618            [section]
1619            invalid_percent = %
1620            invalid_reference = %(()
1621            invalid_variable = %(does_not_exist)s
1622        """)
1623        with self.assertRaises(configparser.InterpolationSyntaxError) as cm:
1624            parser['section']['invalid_percent']
1625        self.assertEqual(str(cm.exception), "'%' must be followed by '%' or "
1626                                            "'(', found: '%'")
1627        with self.assertRaises(configparser.InterpolationSyntaxError) as cm:
1628            parser['section']['invalid_reference']
1629        self.assertEqual(str(cm.exception), "bad interpolation variable "
1630                                            "reference '%(()'")
1631
1632    def test_readfp_deprecation(self):
1633        sio = io.StringIO("""
1634        [section]
1635        option = value
1636        """)
1637        parser = configparser.ConfigParser()
1638        with warnings.catch_warnings(record=True) as w:
1639            warnings.simplefilter("always", DeprecationWarning)
1640            parser.readfp(sio, filename='StringIO')
1641        for warning in w:
1642            self.assertTrue(warning.category is DeprecationWarning)
1643        self.assertEqual(len(parser), 2)
1644        self.assertEqual(parser['section']['option'], 'value')
1645
1646    def test_safeconfigparser_deprecation(self):
1647        with warnings.catch_warnings(record=True) as w:
1648            warnings.simplefilter("always", DeprecationWarning)
1649            parser = configparser.SafeConfigParser()
1650        for warning in w:
1651            self.assertTrue(warning.category is DeprecationWarning)
1652
1653    def test_sectionproxy_repr(self):
1654        parser = configparser.ConfigParser()
1655        parser.read_string("""
1656            [section]
1657            key = value
1658        """)
1659        self.assertEqual(repr(parser['section']), '<Section: section>')
1660
1661    def test_inconsistent_converters_state(self):
1662        parser = configparser.ConfigParser()
1663        import decimal
1664        parser.converters['decimal'] = decimal.Decimal
1665        parser.read_string("""
1666            [s1]
1667            one = 1
1668            [s2]
1669            two = 2
1670        """)
1671        self.assertIn('decimal', parser.converters)
1672        self.assertEqual(parser.getdecimal('s1', 'one'), 1)
1673        self.assertEqual(parser.getdecimal('s2', 'two'), 2)
1674        self.assertEqual(parser['s1'].getdecimal('one'), 1)
1675        self.assertEqual(parser['s2'].getdecimal('two'), 2)
1676        del parser.getdecimal
1677        with self.assertRaises(AttributeError):
1678            parser.getdecimal('s1', 'one')
1679        self.assertIn('decimal', parser.converters)
1680        del parser.converters['decimal']
1681        self.assertNotIn('decimal', parser.converters)
1682        with self.assertRaises(AttributeError):
1683            parser.getdecimal('s1', 'one')
1684        with self.assertRaises(AttributeError):
1685            parser['s1'].getdecimal('one')
1686        with self.assertRaises(AttributeError):
1687            parser['s2'].getdecimal('two')
1688
1689
1690class ExceptionPicklingTestCase(unittest.TestCase):
1691    """Tests for issue #13760: ConfigParser exceptions are not picklable."""
1692
1693    def test_error(self):
1694        import pickle
1695        e1 = configparser.Error('value')
1696        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1697            pickled = pickle.dumps(e1, proto)
1698            e2 = pickle.loads(pickled)
1699            self.assertEqual(e1.message, e2.message)
1700            self.assertEqual(repr(e1), repr(e2))
1701
1702    def test_nosectionerror(self):
1703        import pickle
1704        e1 = configparser.NoSectionError('section')
1705        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1706            pickled = pickle.dumps(e1, proto)
1707            e2 = pickle.loads(pickled)
1708            self.assertEqual(e1.message, e2.message)
1709            self.assertEqual(e1.args, e2.args)
1710            self.assertEqual(e1.section, e2.section)
1711            self.assertEqual(repr(e1), repr(e2))
1712
1713    def test_nooptionerror(self):
1714        import pickle
1715        e1 = configparser.NoOptionError('option', 'section')
1716        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1717            pickled = pickle.dumps(e1, proto)
1718            e2 = pickle.loads(pickled)
1719            self.assertEqual(e1.message, e2.message)
1720            self.assertEqual(e1.args, e2.args)
1721            self.assertEqual(e1.section, e2.section)
1722            self.assertEqual(e1.option, e2.option)
1723            self.assertEqual(repr(e1), repr(e2))
1724
1725    def test_duplicatesectionerror(self):
1726        import pickle
1727        e1 = configparser.DuplicateSectionError('section', 'source', 123)
1728        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1729            pickled = pickle.dumps(e1, proto)
1730            e2 = pickle.loads(pickled)
1731            self.assertEqual(e1.message, e2.message)
1732            self.assertEqual(e1.args, e2.args)
1733            self.assertEqual(e1.section, e2.section)
1734            self.assertEqual(e1.source, e2.source)
1735            self.assertEqual(e1.lineno, e2.lineno)
1736            self.assertEqual(repr(e1), repr(e2))
1737
1738    def test_duplicateoptionerror(self):
1739        import pickle
1740        e1 = configparser.DuplicateOptionError('section', 'option', 'source',
1741            123)
1742        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1743            pickled = pickle.dumps(e1, proto)
1744            e2 = pickle.loads(pickled)
1745            self.assertEqual(e1.message, e2.message)
1746            self.assertEqual(e1.args, e2.args)
1747            self.assertEqual(e1.section, e2.section)
1748            self.assertEqual(e1.option, e2.option)
1749            self.assertEqual(e1.source, e2.source)
1750            self.assertEqual(e1.lineno, e2.lineno)
1751            self.assertEqual(repr(e1), repr(e2))
1752
1753    def test_interpolationerror(self):
1754        import pickle
1755        e1 = configparser.InterpolationError('option', 'section', 'msg')
1756        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1757            pickled = pickle.dumps(e1, proto)
1758            e2 = pickle.loads(pickled)
1759            self.assertEqual(e1.message, e2.message)
1760            self.assertEqual(e1.args, e2.args)
1761            self.assertEqual(e1.section, e2.section)
1762            self.assertEqual(e1.option, e2.option)
1763            self.assertEqual(repr(e1), repr(e2))
1764
1765    def test_interpolationmissingoptionerror(self):
1766        import pickle
1767        e1 = configparser.InterpolationMissingOptionError('option', 'section',
1768            'rawval', 'reference')
1769        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1770            pickled = pickle.dumps(e1, proto)
1771            e2 = pickle.loads(pickled)
1772            self.assertEqual(e1.message, e2.message)
1773            self.assertEqual(e1.args, e2.args)
1774            self.assertEqual(e1.section, e2.section)
1775            self.assertEqual(e1.option, e2.option)
1776            self.assertEqual(e1.reference, e2.reference)
1777            self.assertEqual(repr(e1), repr(e2))
1778
1779    def test_interpolationsyntaxerror(self):
1780        import pickle
1781        e1 = configparser.InterpolationSyntaxError('option', 'section', 'msg')
1782        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1783            pickled = pickle.dumps(e1, proto)
1784            e2 = pickle.loads(pickled)
1785            self.assertEqual(e1.message, e2.message)
1786            self.assertEqual(e1.args, e2.args)
1787            self.assertEqual(e1.section, e2.section)
1788            self.assertEqual(e1.option, e2.option)
1789            self.assertEqual(repr(e1), repr(e2))
1790
1791    def test_interpolationdeptherror(self):
1792        import pickle
1793        e1 = configparser.InterpolationDepthError('option', 'section',
1794            'rawval')
1795        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1796            pickled = pickle.dumps(e1, proto)
1797            e2 = pickle.loads(pickled)
1798            self.assertEqual(e1.message, e2.message)
1799            self.assertEqual(e1.args, e2.args)
1800            self.assertEqual(e1.section, e2.section)
1801            self.assertEqual(e1.option, e2.option)
1802            self.assertEqual(repr(e1), repr(e2))
1803
1804    def test_parsingerror(self):
1805        import pickle
1806        e1 = configparser.ParsingError('source')
1807        e1.append(1, 'line1')
1808        e1.append(2, 'line2')
1809        e1.append(3, 'line3')
1810        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1811            pickled = pickle.dumps(e1, proto)
1812            e2 = pickle.loads(pickled)
1813            self.assertEqual(e1.message, e2.message)
1814            self.assertEqual(e1.args, e2.args)
1815            self.assertEqual(e1.source, e2.source)
1816            self.assertEqual(e1.errors, e2.errors)
1817            self.assertEqual(repr(e1), repr(e2))
1818        e1 = configparser.ParsingError(filename='filename')
1819        e1.append(1, 'line1')
1820        e1.append(2, 'line2')
1821        e1.append(3, 'line3')
1822        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1823            pickled = pickle.dumps(e1, proto)
1824            e2 = pickle.loads(pickled)
1825            self.assertEqual(e1.message, e2.message)
1826            self.assertEqual(e1.args, e2.args)
1827            self.assertEqual(e1.source, e2.source)
1828            self.assertEqual(e1.errors, e2.errors)
1829            self.assertEqual(repr(e1), repr(e2))
1830
1831    def test_missingsectionheadererror(self):
1832        import pickle
1833        e1 = configparser.MissingSectionHeaderError('filename', 123, 'line')
1834        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1835            pickled = pickle.dumps(e1, proto)
1836            e2 = pickle.loads(pickled)
1837            self.assertEqual(e1.message, e2.message)
1838            self.assertEqual(e1.args, e2.args)
1839            self.assertEqual(e1.line, e2.line)
1840            self.assertEqual(e1.source, e2.source)
1841            self.assertEqual(e1.lineno, e2.lineno)
1842            self.assertEqual(repr(e1), repr(e2))
1843
1844
1845class InlineCommentStrippingTestCase(unittest.TestCase):
1846    """Tests for issue #14590: ConfigParser doesn't strip inline comment when
1847    delimiter occurs earlier without preceding space.."""
1848
1849    def test_stripping(self):
1850        cfg = configparser.ConfigParser(inline_comment_prefixes=(';', '#',
1851                '//'))
1852        cfg.read_string("""
1853        [section]
1854        k1 = v1;still v1
1855        k2 = v2 ;a comment
1856        k3 = v3 ; also a comment
1857        k4 = v4;still v4 ;a comment
1858        k5 = v5;still v5 ; also a comment
1859        k6 = v6;still v6; and still v6 ;a comment
1860        k7 = v7;still v7; and still v7 ; also a comment
1861
1862        [multiprefix]
1863        k1 = v1;still v1 #a comment ; yeah, pretty much
1864        k2 = v2 // this already is a comment ; continued
1865        k3 = v3;#//still v3# and still v3 ; a comment
1866        """)
1867        s = cfg['section']
1868        self.assertEqual(s['k1'], 'v1;still v1')
1869        self.assertEqual(s['k2'], 'v2')
1870        self.assertEqual(s['k3'], 'v3')
1871        self.assertEqual(s['k4'], 'v4;still v4')
1872        self.assertEqual(s['k5'], 'v5;still v5')
1873        self.assertEqual(s['k6'], 'v6;still v6; and still v6')
1874        self.assertEqual(s['k7'], 'v7;still v7; and still v7')
1875        s = cfg['multiprefix']
1876        self.assertEqual(s['k1'], 'v1;still v1')
1877        self.assertEqual(s['k2'], 'v2')
1878        self.assertEqual(s['k3'], 'v3;#//still v3# and still v3')
1879
1880
1881class ExceptionContextTestCase(unittest.TestCase):
1882    """ Test that implementation details doesn't leak
1883    through raising exceptions. """
1884
1885    def test_get_basic_interpolation(self):
1886        parser = configparser.ConfigParser()
1887        parser.read_string("""
1888        [Paths]
1889        home_dir: /Users
1890        my_dir: %(home_dir1)s/lumberjack
1891        my_pictures: %(my_dir)s/Pictures
1892        """)
1893        cm = self.assertRaises(configparser.InterpolationMissingOptionError)
1894        with cm:
1895            parser.get('Paths', 'my_dir')
1896        self.assertIs(cm.exception.__suppress_context__, True)
1897
1898    def test_get_extended_interpolation(self):
1899        parser = configparser.ConfigParser(
1900          interpolation=configparser.ExtendedInterpolation())
1901        parser.read_string("""
1902        [Paths]
1903        home_dir: /Users
1904        my_dir: ${home_dir1}/lumberjack
1905        my_pictures: ${my_dir}/Pictures
1906        """)
1907        cm = self.assertRaises(configparser.InterpolationMissingOptionError)
1908        with cm:
1909            parser.get('Paths', 'my_dir')
1910        self.assertIs(cm.exception.__suppress_context__, True)
1911
1912    def test_missing_options(self):
1913        parser = configparser.ConfigParser()
1914        parser.read_string("""
1915        [Paths]
1916        home_dir: /Users
1917        """)
1918        with self.assertRaises(configparser.NoSectionError) as cm:
1919            parser.options('test')
1920        self.assertIs(cm.exception.__suppress_context__, True)
1921
1922    def test_missing_section(self):
1923        config = configparser.ConfigParser()
1924        with self.assertRaises(configparser.NoSectionError) as cm:
1925            config.set('Section1', 'an_int', '15')
1926        self.assertIs(cm.exception.__suppress_context__, True)
1927
1928    def test_remove_option(self):
1929        config = configparser.ConfigParser()
1930        with self.assertRaises(configparser.NoSectionError) as cm:
1931            config.remove_option('Section1', 'an_int')
1932        self.assertIs(cm.exception.__suppress_context__, True)
1933
1934
1935class ConvertersTestCase(BasicTestCase, unittest.TestCase):
1936    """Introduced in 3.5, issue #18159."""
1937
1938    config_class = configparser.ConfigParser
1939
1940    def newconfig(self, defaults=None):
1941        instance = super().newconfig(defaults=defaults)
1942        instance.converters['list'] = lambda v: [e.strip() for e in v.split()
1943                                                 if e.strip()]
1944        return instance
1945
1946    def test_converters(self):
1947        cfg = self.newconfig()
1948        self.assertIn('boolean', cfg.converters)
1949        self.assertIn('list', cfg.converters)
1950        self.assertIsNone(cfg.converters['int'])
1951        self.assertIsNone(cfg.converters['float'])
1952        self.assertIsNone(cfg.converters['boolean'])
1953        self.assertIsNotNone(cfg.converters['list'])
1954        self.assertEqual(len(cfg.converters), 4)
1955        with self.assertRaises(ValueError):
1956            cfg.converters[''] = lambda v: v
1957        with self.assertRaises(ValueError):
1958            cfg.converters[None] = lambda v: v
1959        cfg.read_string("""
1960        [s]
1961        str = string
1962        int = 1
1963        float = 0.5
1964        list = a b c d e f g
1965        bool = yes
1966        """)
1967        s = cfg['s']
1968        self.assertEqual(s['str'], 'string')
1969        self.assertEqual(s['int'], '1')
1970        self.assertEqual(s['float'], '0.5')
1971        self.assertEqual(s['list'], 'a b c d e f g')
1972        self.assertEqual(s['bool'], 'yes')
1973        self.assertEqual(cfg.get('s', 'str'), 'string')
1974        self.assertEqual(cfg.get('s', 'int'), '1')
1975        self.assertEqual(cfg.get('s', 'float'), '0.5')
1976        self.assertEqual(cfg.get('s', 'list'), 'a b c d e f g')
1977        self.assertEqual(cfg.get('s', 'bool'), 'yes')
1978        self.assertEqual(cfg.get('s', 'str'), 'string')
1979        self.assertEqual(cfg.getint('s', 'int'), 1)
1980        self.assertEqual(cfg.getfloat('s', 'float'), 0.5)
1981        self.assertEqual(cfg.getlist('s', 'list'), ['a', 'b', 'c', 'd',
1982                                                    'e', 'f', 'g'])
1983        self.assertEqual(cfg.getboolean('s', 'bool'), True)
1984        self.assertEqual(s.get('str'), 'string')
1985        self.assertEqual(s.getint('int'), 1)
1986        self.assertEqual(s.getfloat('float'), 0.5)
1987        self.assertEqual(s.getlist('list'), ['a', 'b', 'c', 'd',
1988                                             'e', 'f', 'g'])
1989        self.assertEqual(s.getboolean('bool'), True)
1990        with self.assertRaises(AttributeError):
1991            cfg.getdecimal('s', 'float')
1992        with self.assertRaises(AttributeError):
1993            s.getdecimal('float')
1994        import decimal
1995        cfg.converters['decimal'] = decimal.Decimal
1996        self.assertIn('decimal', cfg.converters)
1997        self.assertIsNotNone(cfg.converters['decimal'])
1998        self.assertEqual(len(cfg.converters), 5)
1999        dec0_5 = decimal.Decimal('0.5')
2000        self.assertEqual(cfg.getdecimal('s', 'float'), dec0_5)
2001        self.assertEqual(s.getdecimal('float'), dec0_5)
2002        del cfg.converters['decimal']
2003        self.assertNotIn('decimal', cfg.converters)
2004        self.assertEqual(len(cfg.converters), 4)
2005        with self.assertRaises(AttributeError):
2006            cfg.getdecimal('s', 'float')
2007        with self.assertRaises(AttributeError):
2008            s.getdecimal('float')
2009        with self.assertRaises(KeyError):
2010            del cfg.converters['decimal']
2011        with self.assertRaises(KeyError):
2012            del cfg.converters['']
2013        with self.assertRaises(KeyError):
2014            del cfg.converters[None]
2015
2016
2017class BlatantOverrideConvertersTestCase(unittest.TestCase):
2018    """What if somebody overrode a getboolean()? We want to make sure that in
2019    this case the automatic converters do not kick in."""
2020
2021    config = """
2022        [one]
2023        one = false
2024        two = false
2025        three = long story short
2026
2027        [two]
2028        one = false
2029        two = false
2030        three = four
2031    """
2032
2033    def test_converters_at_init(self):
2034        cfg = configparser.ConfigParser(converters={'len': len})
2035        cfg.read_string(self.config)
2036        self._test_len(cfg)
2037        self.assertIsNotNone(cfg.converters['len'])
2038
2039    def test_inheritance(self):
2040        class StrangeConfigParser(configparser.ConfigParser):
2041            gettysburg = 'a historic borough in south central Pennsylvania'
2042
2043            def getboolean(self, section, option, *, raw=False, vars=None,
2044                        fallback=configparser._UNSET):
2045                if section == option:
2046                    return True
2047                return super().getboolean(section, option, raw=raw, vars=vars,
2048                                          fallback=fallback)
2049            def getlen(self, section, option, *, raw=False, vars=None,
2050                       fallback=configparser._UNSET):
2051                return self._get_conv(section, option, len, raw=raw, vars=vars,
2052                                      fallback=fallback)
2053
2054        cfg = StrangeConfigParser()
2055        cfg.read_string(self.config)
2056        self._test_len(cfg)
2057        self.assertIsNone(cfg.converters['len'])
2058        self.assertTrue(cfg.getboolean('one', 'one'))
2059        self.assertTrue(cfg.getboolean('two', 'two'))
2060        self.assertFalse(cfg.getboolean('one', 'two'))
2061        self.assertFalse(cfg.getboolean('two', 'one'))
2062        cfg.converters['boolean'] = cfg._convert_to_boolean
2063        self.assertFalse(cfg.getboolean('one', 'one'))
2064        self.assertFalse(cfg.getboolean('two', 'two'))
2065        self.assertFalse(cfg.getboolean('one', 'two'))
2066        self.assertFalse(cfg.getboolean('two', 'one'))
2067
2068    def _test_len(self, cfg):
2069        self.assertEqual(len(cfg.converters), 4)
2070        self.assertIn('boolean', cfg.converters)
2071        self.assertIn('len', cfg.converters)
2072        self.assertNotIn('tysburg', cfg.converters)
2073        self.assertIsNone(cfg.converters['int'])
2074        self.assertIsNone(cfg.converters['float'])
2075        self.assertIsNone(cfg.converters['boolean'])
2076        self.assertEqual(cfg.getlen('one', 'one'), 5)
2077        self.assertEqual(cfg.getlen('one', 'two'), 5)
2078        self.assertEqual(cfg.getlen('one', 'three'), 16)
2079        self.assertEqual(cfg.getlen('two', 'one'), 5)
2080        self.assertEqual(cfg.getlen('two', 'two'), 5)
2081        self.assertEqual(cfg.getlen('two', 'three'), 4)
2082        self.assertEqual(cfg.getlen('two', 'four', fallback=0), 0)
2083        with self.assertRaises(configparser.NoOptionError):
2084            cfg.getlen('two', 'four')
2085        self.assertEqual(cfg['one'].getlen('one'), 5)
2086        self.assertEqual(cfg['one'].getlen('two'), 5)
2087        self.assertEqual(cfg['one'].getlen('three'), 16)
2088        self.assertEqual(cfg['two'].getlen('one'), 5)
2089        self.assertEqual(cfg['two'].getlen('two'), 5)
2090        self.assertEqual(cfg['two'].getlen('three'), 4)
2091        self.assertEqual(cfg['two'].getlen('four', 0), 0)
2092        self.assertEqual(cfg['two'].getlen('four'), None)
2093
2094    def test_instance_assignment(self):
2095        cfg = configparser.ConfigParser()
2096        cfg.getboolean = lambda section, option: True
2097        cfg.getlen = lambda section, option: len(cfg[section][option])
2098        cfg.read_string(self.config)
2099        self.assertEqual(len(cfg.converters), 3)
2100        self.assertIn('boolean', cfg.converters)
2101        self.assertNotIn('len', cfg.converters)
2102        self.assertIsNone(cfg.converters['int'])
2103        self.assertIsNone(cfg.converters['float'])
2104        self.assertIsNone(cfg.converters['boolean'])
2105        self.assertTrue(cfg.getboolean('one', 'one'))
2106        self.assertTrue(cfg.getboolean('two', 'two'))
2107        self.assertTrue(cfg.getboolean('one', 'two'))
2108        self.assertTrue(cfg.getboolean('two', 'one'))
2109        cfg.converters['boolean'] = cfg._convert_to_boolean
2110        self.assertFalse(cfg.getboolean('one', 'one'))
2111        self.assertFalse(cfg.getboolean('two', 'two'))
2112        self.assertFalse(cfg.getboolean('one', 'two'))
2113        self.assertFalse(cfg.getboolean('two', 'one'))
2114        self.assertEqual(cfg.getlen('one', 'one'), 5)
2115        self.assertEqual(cfg.getlen('one', 'two'), 5)
2116        self.assertEqual(cfg.getlen('one', 'three'), 16)
2117        self.assertEqual(cfg.getlen('two', 'one'), 5)
2118        self.assertEqual(cfg.getlen('two', 'two'), 5)
2119        self.assertEqual(cfg.getlen('two', 'three'), 4)
2120        # If a getter impl is assigned straight to the instance, it won't
2121        # be available on the section proxies.
2122        with self.assertRaises(AttributeError):
2123            self.assertEqual(cfg['one'].getlen('one'), 5)
2124        with self.assertRaises(AttributeError):
2125            self.assertEqual(cfg['two'].getlen('one'), 5)
2126
2127
2128class MiscTestCase(unittest.TestCase):
2129    def test__all__(self):
2130        blacklist = {"Error"}
2131        support.check__all__(self, configparser, blacklist=blacklist)
2132
2133
2134if __name__ == '__main__':
2135    unittest.main()
2136