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