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