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