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