summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_string.py
blob: 824b89ad517c120b0a665e85ae8ed8de7e8a8fe6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
import unittest
import string
from string import Template


class ModuleTest(unittest.TestCase):

    def test_attrs(self):
        # While the exact order of the items in these attributes is not
        # technically part of the "language spec", in practice there is almost
        # certainly user code that depends on the order, so de-facto it *is*
        # part of the spec.
        self.assertEqual(string.whitespace, ' \t\n\r\x0b\x0c')
        self.assertEqual(string.ascii_lowercase, 'abcdefghijklmnopqrstuvwxyz')
        self.assertEqual(string.ascii_uppercase, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
        self.assertEqual(string.ascii_letters, string.ascii_lowercase + string.ascii_uppercase)
        self.assertEqual(string.digits, '0123456789')
        self.assertEqual(string.hexdigits, string.digits + 'abcdefABCDEF')
        self.assertEqual(string.octdigits, '01234567')
        self.assertEqual(string.punctuation, '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~')
        self.assertEqual(string.printable, string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation + string.whitespace)

    def test_capwords(self):
        self.assertEqual(string.capwords('abc def ghi'), 'Abc Def Ghi')
        self.assertEqual(string.capwords('abc\tdef\nghi'), 'Abc Def Ghi')
        self.assertEqual(string.capwords('abc\t   def  \nghi'), 'Abc Def Ghi')
        self.assertEqual(string.capwords('ABC DEF GHI'), 'Abc Def Ghi')
        self.assertEqual(string.capwords('ABC-DEF-GHI', '-'), 'Abc-Def-Ghi')
        self.assertEqual(string.capwords('ABC-def DEF-ghi GHI'), 'Abc-def Def-ghi Ghi')
        self.assertEqual(string.capwords('   aBc  DeF   '), 'Abc Def')
        self.assertEqual(string.capwords('\taBc\tDeF\t'), 'Abc Def')
        self.assertEqual(string.capwords('\taBc\tDeF\t', '\t'), '\tAbc\tDef\t')

    def test_basic_formatter(self):
        fmt = string.Formatter()
        self.assertEqual(fmt.format("foo"), "foo")
        self.assertEqual(fmt.format("foo{0}", "bar"), "foobar")
        self.assertEqual(fmt.format("foo{1}{0}-{1}", "bar", 6), "foo6bar-6")
        self.assertRaises(TypeError, fmt.format)
        self.assertRaises(TypeError, string.Formatter.format)

    def test_format_keyword_arguments(self):
        fmt = string.Formatter()
        self.assertEqual(fmt.format("-{arg}-", arg='test'), '-test-')
        self.assertRaises(KeyError, fmt.format, "-{arg}-")
        self.assertEqual(fmt.format("-{self}-", self='test'), '-test-')
        self.assertRaises(KeyError, fmt.format, "-{self}-")
        self.assertEqual(fmt.format("-{format_string}-", format_string='test'),
                         '-test-')
        self.assertRaises(KeyError, fmt.format, "-{format_string}-")
        with self.assertRaisesRegex(TypeError, "format_string"):
            fmt.format(format_string="-{arg}-", arg='test')

    def test_auto_numbering(self):
        fmt = string.Formatter()
        self.assertEqual(fmt.format('foo{}{}', 'bar', 6),
                         'foo{}{}'.format('bar', 6))
        self.assertEqual(fmt.format('foo{1}{num}{1}', None, 'bar', num=6),
                         'foo{1}{num}{1}'.format(None, 'bar', num=6))
        self.assertEqual(fmt.format('{:^{}}', 'bar', 6),
                         '{:^{}}'.format('bar', 6))
        self.assertEqual(fmt.format('{:^{}} {}', 'bar', 6, 'X'),
                         '{:^{}} {}'.format('bar', 6, 'X'))
        self.assertEqual(fmt.format('{:^{pad}}{}', 'foo', 'bar', pad=6),
                         '{:^{pad}}{}'.format('foo', 'bar', pad=6))

        with self.assertRaises(ValueError):
            fmt.format('foo{1}{}', 'bar', 6)

        with self.assertRaises(ValueError):
            fmt.format('foo{}{1}', 'bar', 6)

    def test_conversion_specifiers(self):
        fmt = string.Formatter()
        self.assertEqual(fmt.format("-{arg!r}-", arg='test'), "-'test'-")
        self.assertEqual(fmt.format("{0!s}", 'test'), 'test')
        self.assertRaises(ValueError, fmt.format, "{0!h}", 'test')
        # issue13579
        self.assertEqual(fmt.format("{0!a}", 42), '42')
        self.assertEqual(fmt.format("{0!a}",  string.ascii_letters),
            "'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'")
        self.assertEqual(fmt.format("{0!a}",  chr(255)), "'\\xff'")
        self.assertEqual(fmt.format("{0!a}",  chr(256)), "'\\u0100'")

    def test_name_lookup(self):
        fmt = string.Formatter()
        class AnyAttr:
            def __getattr__(self, attr):
                return attr
        x = AnyAttr()
        self.assertEqual(fmt.format("{0.lumber}{0.jack}", x), 'lumberjack')
        with self.assertRaises(AttributeError):
            fmt.format("{0.lumber}{0.jack}", '')

    def test_index_lookup(self):
        fmt = string.Formatter()
        lookup = ["eggs", "and", "spam"]
        self.assertEqual(fmt.format("{0[2]}{0[0]}", lookup), 'spameggs')
        with self.assertRaises(IndexError):
            fmt.format("{0[2]}{0[0]}", [])
        with self.assertRaises(KeyError):
            fmt.format("{0[2]}{0[0]}", {})

    def test_override_get_value(self):
        class NamespaceFormatter(string.Formatter):
            def __init__(self, namespace={}):
                string.Formatter.__init__(self)
                self.namespace = namespace

            def get_value(self, key, args, kwds):
                if isinstance(key, str):
                    try:
                        # Check explicitly passed arguments first
                        return kwds[key]
                    except KeyError:
                        return self.namespace[key]
                else:
                    string.Formatter.get_value(key, args, kwds)

        fmt = NamespaceFormatter({'greeting':'hello'})
        self.assertEqual(fmt.format("{greeting}, world!"), 'hello, world!')


    def test_override_format_field(self):
        class CallFormatter(string.Formatter):
            def format_field(self, value, format_spec):
                return format(value(), format_spec)

        fmt = CallFormatter()
        self.assertEqual(fmt.format('*{0}*', lambda : 'result'), '*result*')


    def test_override_convert_field(self):
        class XFormatter(string.Formatter):
            def convert_field(self, value, conversion):
                if conversion == 'x':
                    return None
                return super().convert_field(value, conversion)

        fmt = XFormatter()
        self.assertEqual(fmt.format("{0!r}:{0!x}", 'foo', 'foo'), "'foo':None")


    def test_override_parse(self):
        class BarFormatter(string.Formatter):
            # returns an iterable that contains tuples of the form:
            # (literal_text, field_name, format_spec, conversion)
            def parse(self, format_string):
                for field in format_string.split('|'):
                    if field[0] == '+':
                        # it's markup
                        field_name, _, format_spec = field[1:].partition(':')
                        yield '', field_name, format_spec, None
                    else:
                        yield field, None, None, None

        fmt = BarFormatter()
        self.assertEqual(fmt.format('*|+0:^10s|*', 'foo'), '*   foo    *')

    def test_check_unused_args(self):
        class CheckAllUsedFormatter(string.Formatter):
            def check_unused_args(self, used_args, args, kwargs):
                # Track which arguments actually got used
                unused_args = set(kwargs.keys())
                unused_args.update(range(0, len(args)))

                for arg in used_args:
                    unused_args.remove(arg)

                if unused_args:
                    raise ValueError("unused arguments")

        fmt = CheckAllUsedFormatter()
        self.assertEqual(fmt.format("{0}", 10), "10")
        self.assertEqual(fmt.format("{0}{i}", 10, i=100), "10100")
        self.assertEqual(fmt.format("{0}{i}{1}", 10, 20, i=100), "1010020")
        self.assertRaises(ValueError, fmt.format, "{0}{i}{1}", 10, 20, i=100, j=0)
        self.assertRaises(ValueError, fmt.format, "{0}", 10, 20)
        self.assertRaises(ValueError, fmt.format, "{0}", 10, 20, i=100)
        self.assertRaises(ValueError, fmt.format, "{i}", 10, 20, i=100)

    def test_vformat_recursion_limit(self):
        fmt = string.Formatter()
        args = ()
        kwargs = dict(i=100)
        with self.assertRaises(ValueError) as err:
            fmt._vformat("{i}", args, kwargs, set(), -1)
        self.assertIn("recursion", str(err.exception))


# Template tests (formerly housed in test_pep292.py)

class Bag:
    pass

class Mapping:
    def __getitem__(self, name):
        obj = self
        for part in name.split('.'):
            try:
                obj = getattr(obj, part)
            except AttributeError:
                raise KeyError(name)
        return obj


class TestTemplate(unittest.TestCase):
    def test_regular_templates(self):
        s = Template('$who likes to eat a bag of $what worth $$100')
        self.assertEqual(s.substitute(dict(who='tim', what='ham')),
                         'tim likes to eat a bag of ham worth $100')
        self.assertRaises(KeyError, s.substitute, dict(who='tim'))
        self.assertRaises(TypeError, Template.substitute)

    def test_regular_templates_with_braces(self):
        s = Template('$who likes ${what} for ${meal}')
        d = dict(who='tim', what='ham', meal='dinner')
        self.assertEqual(s.substitute(d), 'tim likes ham for dinner')
        self.assertRaises(KeyError, s.substitute,
                          dict(who='tim', what='ham'))

    def test_regular_templates_with_upper_case(self):
        s = Template('$WHO likes ${WHAT} for ${MEAL}')
        d = dict(WHO='tim', WHAT='ham', MEAL='dinner')
        self.assertEqual(s.substitute(d), 'tim likes ham for dinner')

    def test_regular_templates_with_non_letters(self):
        s = Template('$_wh0_ likes ${_w_h_a_t_} for ${mea1}')
        d = dict(_wh0_='tim', _w_h_a_t_='ham', mea1='dinner')
        self.assertEqual(s.substitute(d), 'tim likes ham for dinner')

    def test_escapes(self):
        eq = self.assertEqual
        s = Template('$who likes to eat a bag of $$what worth $$100')
        eq(s.substitute(dict(who='tim', what='ham')),
           'tim likes to eat a bag of $what worth $100')
        s = Template('$who likes $$')
        eq(s.substitute(dict(who='tim', what='ham')), 'tim likes $')

    def test_percents(self):
        eq = self.assertEqual
        s = Template('%(foo)s $foo ${foo}')
        d = dict(foo='baz')
        eq(s.substitute(d), '%(foo)s baz baz')
        eq(s.safe_substitute(d), '%(foo)s baz baz')

    def test_stringification(self):
        eq = self.assertEqual
        s = Template('tim has eaten $count bags of ham today')
        d = dict(count=7)
        eq(s.substitute(d), 'tim has eaten 7 bags of ham today')
        eq(s.safe_substitute(d), 'tim has eaten 7 bags of ham today')
        s = Template('tim has eaten ${count} bags of ham today')
        eq(s.substitute(d), 'tim has eaten 7 bags of ham today')

    def test_tupleargs(self):
        eq = self.assertEqual
        s = Template('$who ate ${meal}')
        d = dict(who=('tim', 'fred'), meal=('ham', 'kung pao'))
        eq(s.substitute(d), "('tim', 'fred') ate ('ham', 'kung pao')")
        eq(s.safe_substitute(d), "('tim', 'fred') ate ('ham', 'kung pao')")

    def test_SafeTemplate(self):
        eq = self.assertEqual
        s = Template('$who likes ${what} for ${meal}')
        eq(s.safe_substitute(dict(who='tim')), 'tim likes ${what} for ${meal}')
        eq(s.safe_substitute(dict(what='ham')), '$who likes ham for ${meal}')
        eq(s.safe_substitute(dict(what='ham', meal='dinner')),
           '$who likes ham for dinner')
        eq(s.safe_substitute(dict(who='tim', what='ham')),
           'tim likes ham for ${meal}')
        eq(s.safe_substitute(dict(who='tim', what='ham', meal='dinner')),
           'tim likes ham for dinner')

    def test_invalid_placeholders(self):
        raises = self.assertRaises
        s = Template('$who likes $')
        raises(ValueError, s.substitute, dict(who='tim'))
        s = Template('$who likes ${what)')
        raises(ValueError, s.substitute, dict(who='tim'))
        s = Template('$who likes $100')
        raises(ValueError, s.substitute, dict(who='tim'))
        # Template.idpattern should match to only ASCII characters.
        # https://bugs.python.org/issue31672
        s = Template("$who likes $\u0131")  # (DOTLESS I)
        raises(ValueError, s.substitute, dict(who='tim'))
        s = Template("$who likes $\u0130")  # (LATIN CAPITAL LETTER I WITH DOT ABOVE)
        raises(ValueError, s.substitute, dict(who='tim'))

    def test_idpattern_override(self):
        class PathPattern(Template):
            idpattern = r'[_a-z][._a-z0-9]*'
        m = Mapping()
        m.bag = Bag()
        m.bag.foo = Bag()
        m.bag.foo.who = 'tim'
        m.bag.what = 'ham'
        s = PathPattern('$bag.foo.who likes to eat a bag of $bag.what')
        self.assertEqual(s.substitute(m), 'tim likes to eat a bag of ham')

    def test_flags_override(self):
        class MyPattern(Template):
            flags = 0
        s = MyPattern('$wHO likes ${WHAT} for ${meal}')
        d = dict(wHO='tim', WHAT='ham', meal='dinner', w='fred')
        self.assertRaises(ValueError, s.substitute, d)
        self.assertEqual(s.safe_substitute(d), 'fredHO likes ${WHAT} for dinner')

    def test_idpattern_override_inside_outside(self):
        # bpo-1198569: Allow the regexp inside and outside braces to be
        # different when deriving from Template.
        class MyPattern(Template):
            idpattern = r'[a-z]+'
            braceidpattern = r'[A-Z]+'
            flags = 0
        m = dict(foo='foo', BAR='BAR')
        s = MyPattern('$foo ${BAR}')
        self.assertEqual(s.substitute(m), 'foo BAR')

    def test_idpattern_override_inside_outside_invalid_unbraced(self):
        # bpo-1198569: Allow the regexp inside and outside braces to be
        # different when deriving from Template.
        class MyPattern(Template):
            idpattern = r'[a-z]+'
            braceidpattern = r'[A-Z]+'
            flags = 0
        m = dict(foo='foo', BAR='BAR')
        s = MyPattern('$FOO')
        self.assertRaises(ValueError, s.substitute, m)
        s = MyPattern('${bar}')
        self.assertRaises(ValueError, s.substitute, m)

    def test_pattern_override(self):
        class MyPattern(Template):
            pattern = r"""
            (?P<escaped>@{2})                   |
            @(?P<named>[_a-z][._a-z0-9]*)       |
            @{(?P<braced>[_a-z][._a-z0-9]*)}    |
            (?P<invalid>@)
            """
        m = Mapping()
        m.bag = Bag()
        m.bag.foo = Bag()
        m.bag.foo.who = 'tim'
        m.bag.what = 'ham'
        s = MyPattern('@bag.foo.who likes to eat a bag of @bag.what')
        self.assertEqual(s.substitute(m), 'tim likes to eat a bag of ham')

        class BadPattern(Template):
            pattern = r"""
            (?P<badname>.*)                     |
            (?P<escaped>@{2})                   |
            @(?P<named>[_a-z][._a-z0-9]*)       |
            @{(?P<braced>[_a-z][._a-z0-9]*)}    |
            (?P<invalid>@)                      |
            """
        s = BadPattern('@bag.foo.who likes to eat a bag of @bag.what')
        self.assertRaises(ValueError, s.substitute, {})
        self.assertRaises(ValueError, s.safe_substitute, {})

    def test_braced_override(self):
        class MyTemplate(Template):
            pattern = r"""
            \$(?:
              (?P<escaped>$)                     |
              (?P<named>[_a-z][_a-z0-9]*)        |
              @@(?P<braced>[_a-z][_a-z0-9]*)@@   |
              (?P<invalid>)                      |
           )
           """

        tmpl = 'PyCon in $@@location@@'
        t = MyTemplate(tmpl)
        self.assertRaises(KeyError, t.substitute, {})
        val = t.substitute({'location': 'Cleveland'})
        self.assertEqual(val, 'PyCon in Cleveland')

    def test_braced_override_safe(self):
        class MyTemplate(Template):
            pattern = r"""
            \$(?:
              (?P<escaped>$)                     |
              (?P<named>[_a-z][_a-z0-9]*)        |
              @@(?P<braced>[_a-z][_a-z0-9]*)@@   |
              (?P<invalid>)                      |
           )
           """

        tmpl = 'PyCon in $@@location@@'
        t = MyTemplate(tmpl)
        self.assertEqual(t.safe_substitute(), tmpl)
        val = t.safe_substitute({'location': 'Cleveland'})
        self.assertEqual(val, 'PyCon in Cleveland')

    def test_invalid_with_no_lines(self):
        # The error formatting for invalid templates
        # has a special case for no data that the default
        # pattern can't trigger (always has at least '$')
        # So we craft a pattern that is always invalid
        # with no leading data.
        class MyTemplate(Template):
            pattern = r"""
              (?P<invalid>) |
              unreachable(
                (?P<named>)   |
                (?P<braced>)  |
                (?P<escaped>)
              )
            """
        s = MyTemplate('')
        with self.assertRaises(ValueError) as err:
            s.substitute({})
        self.assertIn('line 1, col 1', str(err.exception))

    def test_unicode_values(self):
        s = Template('$who likes $what')
        d = dict(who='t\xffm', what='f\xfe\fed')
        self.assertEqual(s.substitute(d), 't\xffm likes f\xfe\x0ced')

    def test_keyword_arguments(self):
        eq = self.assertEqual
        s = Template('$who likes $what')
        eq(s.substitute(who='tim', what='ham'), 'tim likes ham')
        eq(s.substitute(dict(who='tim'), what='ham'), 'tim likes ham')
        eq(s.substitute(dict(who='fred', what='kung pao'),
                        who='tim', what='ham'),
           'tim likes ham')
        s = Template('the mapping is $mapping')
        eq(s.substitute(dict(foo='none'), mapping='bozo'),
           'the mapping is bozo')
        eq(s.substitute(dict(mapping='one'), mapping='two'),
           'the mapping is two')

        s = Template('the self is $self')
        eq(s.substitute(self='bozo'), 'the self is bozo')

    def test_keyword_arguments_safe(self):
        eq = self.assertEqual
        raises = self.assertRaises
        s = Template('$who likes $what')
        eq(s.safe_substitute(who='tim', what='ham'), 'tim likes ham')
        eq(s.safe_substitute(dict(who='tim'), what='ham'), 'tim likes ham')
        eq(s.safe_substitute(dict(who='fred', what='kung pao'),
                        who='tim', what='ham'),
           'tim likes ham')
        s = Template('the mapping is $mapping')
        eq(s.safe_substitute(dict(foo='none'), mapping='bozo'),
           'the mapping is bozo')
        eq(s.safe_substitute(dict(mapping='one'), mapping='two'),
           'the mapping is two')
        d = dict(mapping='one')
        raises(TypeError, s.substitute, d, {})
        raises(TypeError, s.safe_substitute, d, {})

        s = Template('the self is $self')
        eq(s.safe_substitute(self='bozo'), 'the self is bozo')

    def test_delimiter_override(self):
        eq = self.assertEqual
        raises = self.assertRaises
        class AmpersandTemplate(Template):
            delimiter = '&'
        s = AmpersandTemplate('this &gift is for &{who} &&')
        eq(s.substitute(gift='bud', who='you'), 'this bud is for you &')
        raises(KeyError, s.substitute)
        eq(s.safe_substitute(gift='bud', who='you'), 'this bud is for you &')
        eq(s.safe_substitute(), 'this &gift is for &{who} &')
        s = AmpersandTemplate('this &gift is for &{who} &')
        raises(ValueError, s.substitute, dict(gift='bud', who='you'))
        eq(s.safe_substitute(), 'this &gift is for &{who} &')

        class PieDelims(Template):
            delimiter = '@'
        s = PieDelims('@who likes to eat a bag of @{what} worth $100')
        self.assertEqual(s.substitute(dict(who='tim', what='ham')),
                         'tim likes to eat a bag of ham worth $100')

    def test_is_valid(self):
        eq = self.assertEqual
        s = Template('$who likes to eat a bag of ${what} worth $$100')
        self.assertTrue(s.is_valid())

        s = Template('$who likes to eat a bag of ${what} worth $100')
        self.assertFalse(s.is_valid())

        # if the pattern has an unrecognized capture group,
        # it should raise ValueError like substitute and safe_substitute do
        class BadPattern(Template):
            pattern = r"""
            (?P<badname>.*)                  |
            (?P<escaped>@{2})                   |
            @(?P<named>[_a-z][._a-z0-9]*)       |
            @{(?P<braced>[_a-z][._a-z0-9]*)}    |
            (?P<invalid>@)                      |
            """
        s = BadPattern('@bag.foo.who likes to eat a bag of @bag.what')
        self.assertRaises(ValueError, s.is_valid)

    def test_get_identifiers(self):
        eq = self.assertEqual
        raises = self.assertRaises
        s = Template('$who likes to eat a bag of ${what} worth $$100')
        ids = s.get_identifiers()
        eq(ids, ['who', 'what'])

        # repeated identifiers only included once
        s = Template('$who likes to eat a bag of ${what} worth $$100; ${who} likes to eat a bag of $what worth $$100')
        ids = s.get_identifiers()
        eq(ids, ['who', 'what'])

        # invalid identifiers are ignored
        s = Template('$who likes to eat a bag of ${what} worth $100')
        ids = s.get_identifiers()
        eq(ids, ['who', 'what'])

        # if the pattern has an unrecognized capture group,
        # it should raise ValueError like substitute and safe_substitute do
        class BadPattern(Template):
            pattern = r"""
            (?P<badname>.*)                  |
            (?P<escaped>@{2})                   |
            @(?P<named>[_a-z][._a-z0-9]*)       |
            @{(?P<braced>[_a-z][._a-z0-9]*)}    |
            (?P<invalid>@)                      |
            """
        s = BadPattern('@bag.foo.who likes to eat a bag of @bag.what')
        self.assertRaises(ValueError, s.get_identifiers)


if __name__ == '__main__':
    unittest.main()
"hl opt">.remove_section('Spaces')) self.assertFalse(cf.remove_section(self.default_section)) self.assertTrue(cf.remove_option('Foo Bar', 'foo'), "remove_option() failed to report existence of option") self.assertFalse(cf.has_option('Foo Bar', 'foo'), "remove_option() failed to remove option") self.assertFalse(cf.remove_option('Foo Bar', 'foo'), "remove_option() failed to report non-existence of option" " that was removed") self.assertTrue(cf.has_option('Foo Bar', 'this_value')) self.assertFalse(cf.remove_option('Foo Bar', 'this_value')) self.assertTrue(cf.remove_option(self.default_section, 'this_value')) self.assertFalse(cf.has_option('Foo Bar', 'this_value')) self.assertFalse(cf.remove_option(self.default_section, 'this_value')) with self.assertRaises(configparser.NoSectionError) as cm: cf.remove_option('No Such Section', 'foo') self.assertEqual(cm.exception.args, ('No Such Section',)) eq(cf.get('Long Line', 'foo'), 'this line is much, much longer than my editor\nlikes it.') # mapping access del cf['Types'] self.assertFalse('Types' in cf) with self.assertRaises(KeyError): del cf['Types'] with self.assertRaises(ValueError): del cf[self.default_section] del cf['Spacey Bar']['foo'] self.assertFalse('foo' in cf['Spacey Bar']) with self.assertRaises(KeyError): del cf['Spacey Bar']['foo'] self.assertTrue('that_value' in cf['Spacey Bar']) with self.assertRaises(KeyError): del cf['Spacey Bar']['that_value'] del cf[self.default_section]['that_value'] self.assertFalse('that_value' in cf['Spacey Bar']) with self.assertRaises(KeyError): del cf[self.default_section]['that_value'] with self.assertRaises(KeyError): del cf['No Such Section']['foo'] # Don't add new asserts below in this method as most of the options # and sections are now removed. def test_basic(self): config_string = """\ [Foo Bar] foo{0[0]}bar1 [Spacey Bar] foo {0[0]} bar2 [Spacey Bar From The Beginning] foo {0[0]} bar3 baz {0[0]} qwe [Commented Bar] foo{0[1]} bar4 {1[1]} comment baz{0[0]}qwe {1[0]}another one [Long Line] foo{0[1]} this line is much, much longer than my editor likes it. [Section\\with$weird%characters[\t] [Internationalized Stuff] foo[bg]{0[1]} Bulgarian foo{0[0]}Default foo[en]{0[0]}English foo[de]{0[0]}Deutsch [Spaces] key with spaces {0[1]} value another with spaces {0[0]} splat! [Types] int {0[1]} 42 float {0[0]} 0.44 boolean {0[0]} NO 123 {0[1]} strange but acceptable """.format(self.delimiters, self.comment_prefixes) if self.allow_no_value: config_string += ( "[NoValue]\n" "option-without-value\n" ) cf = self.fromstring(config_string) self.basic_test(cf) if self.strict: with self.assertRaises(configparser.DuplicateOptionError): cf.read_string(textwrap.dedent("""\ [Duplicate Options Here] option {0[0]} with a value option {0[1]} with another value """.format(self.delimiters))) with self.assertRaises(configparser.DuplicateSectionError): cf.read_string(textwrap.dedent("""\ [And Now For Something] completely different {0[0]} True [And Now For Something] the larch {0[1]} 1 """.format(self.delimiters))) else: cf.read_string(textwrap.dedent("""\ [Duplicate Options Here] option {0[0]} with a value option {0[1]} with another value """.format(self.delimiters))) cf.read_string(textwrap.dedent("""\ [And Now For Something] completely different {0[0]} True [And Now For Something] the larch {0[1]} 1 """.format(self.delimiters))) def test_basic_from_dict(self): config = { "Foo Bar": { "foo": "bar1", }, "Spacey Bar": { "foo": "bar2", }, "Spacey Bar From The Beginning": { "foo": "bar3", "baz": "qwe", }, "Commented Bar": { "foo": "bar4", "baz": "qwe", }, "Long Line": { "foo": "this line is much, much longer than my editor\nlikes " "it.", }, "Section\\with$weird%characters[\t": { }, "Internationalized Stuff": { "foo[bg]": "Bulgarian", "foo": "Default", "foo[en]": "English", "foo[de]": "Deutsch", }, "Spaces": { "key with spaces": "value", "another with spaces": "splat!", }, "Types": { "int": 42, "float": 0.44, "boolean": False, 123: "strange but acceptable", }, } if self.allow_no_value: config.update({ "NoValue": { "option-without-value": None, } }) cf = self.newconfig() cf.read_dict(config) self.basic_test(cf) if self.strict: with self.assertRaises(configparser.DuplicateSectionError): cf.read_dict({ '1': {'key': 'value'}, 1: {'key2': 'value2'}, }) with self.assertRaises(configparser.DuplicateOptionError): cf.read_dict({ "Duplicate Options Here": { 'option': 'with a value', 'OPTION': 'with another value', }, }) else: cf.read_dict({ 'section': {'key': 'value'}, 'SECTION': {'key2': 'value2'}, }) cf.read_dict({ "Duplicate Options Here": { 'option': 'with a value', 'OPTION': 'with another value', }, }) def test_case_sensitivity(self): cf = self.newconfig() cf.add_section("A") cf.add_section("a") cf.add_section("B") L = cf.sections() L.sort() eq = self.assertEqual eq(L, ["A", "B", "a"]) cf.set("a", "B", "value") eq(cf.options("a"), ["b"]) eq(cf.get("a", "b"), "value", "could not locate option, expecting case-insensitive option names") with self.assertRaises(configparser.NoSectionError): # section names are case-sensitive cf.set("b", "A", "value") self.assertTrue(cf.has_option("a", "b")) self.assertFalse(cf.has_option("b", "b")) cf.set("A", "A-B", "A-B value") for opt in ("a-b", "A-b", "a-B", "A-B"): self.assertTrue( cf.has_option("A", opt), "has_option() returned false for option which should exist") eq(cf.options("A"), ["a-b"]) eq(cf.options("a"), ["b"]) cf.remove_option("a", "B") eq(cf.options("a"), []) # SF bug #432369: cf = self.fromstring( "[MySection]\nOption{} first line \n\tsecond line \n".format( self.delimiters[0])) eq(cf.options("MySection"), ["option"]) eq(cf.get("MySection", "Option"), "first line\nsecond line") # SF bug #561822: cf = self.fromstring("[section]\n" "nekey{}nevalue\n".format(self.delimiters[0]), defaults={"key":"value"}) self.assertTrue(cf.has_option("section", "Key")) def test_case_sensitivity_mapping_access(self): cf = self.newconfig() cf["A"] = {} cf["a"] = {"B": "value"} cf["B"] = {} L = [section for section in cf] L.sort() eq = self.assertEqual elem_eq = self.assertCountEqual eq(L, sorted(["A", "B", self.default_section, "a"])) eq(cf["a"].keys(), {"b"}) eq(cf["a"]["b"], "value", "could not locate option, expecting case-insensitive option names") with self.assertRaises(KeyError): # section names are case-sensitive cf["b"]["A"] = "value" self.assertTrue("b" in cf["a"]) cf["A"]["A-B"] = "A-B value" for opt in ("a-b", "A-b", "a-B", "A-B"): self.assertTrue( opt in cf["A"], "has_option() returned false for option which should exist") eq(cf["A"].keys(), {"a-b"}) eq(cf["a"].keys(), {"b"}) del cf["a"]["B"] elem_eq(cf["a"].keys(), {}) # SF bug #432369: cf = self.fromstring( "[MySection]\nOption{} first line \n\tsecond line \n".format( self.delimiters[0])) eq(cf["MySection"].keys(), {"option"}) eq(cf["MySection"]["Option"], "first line\nsecond line") # SF bug #561822: cf = self.fromstring("[section]\n" "nekey{}nevalue\n".format(self.delimiters[0]), defaults={"key":"value"}) self.assertTrue("Key" in cf["section"]) def test_default_case_sensitivity(self): cf = self.newconfig({"foo": "Bar"}) self.assertEqual( cf.get(self.default_section, "Foo"), "Bar", "could not locate option, expecting case-insensitive option names") cf = self.newconfig({"Foo": "Bar"}) self.assertEqual( cf.get(self.default_section, "Foo"), "Bar", "could not locate option, expecting case-insensitive defaults") def test_parse_errors(self): cf = self.newconfig() self.parse_error(cf, configparser.ParsingError, "[Foo]\n" "{}val-without-opt-name\n".format(self.delimiters[0])) self.parse_error(cf, configparser.ParsingError, "[Foo]\n" "{}val-without-opt-name\n".format(self.delimiters[1])) e = self.parse_error(cf, configparser.MissingSectionHeaderError, "No Section!\n") self.assertEqual(e.args, ('<???>', 1, "No Section!\n")) if not self.allow_no_value: e = self.parse_error(cf, configparser.ParsingError, "[Foo]\n wrong-indent\n") self.assertEqual(e.args, ('<???>',)) # read_file on a real file tricky = support.findfile("cfgparser.3") if self.delimiters[0] == '=': error = configparser.ParsingError expected = (tricky,) else: error = configparser.MissingSectionHeaderError expected = (tricky, 1, ' # INI with as many tricky parts as possible\n') with open(tricky, encoding='utf-8') as f: e = self.parse_error(cf, error, f) self.assertEqual(e.args, expected) def parse_error(self, cf, exc, src): if hasattr(src, 'readline'): sio = src else: sio = io.StringIO(src) with self.assertRaises(exc) as cm: cf.read_file(sio) return cm.exception def test_query_errors(self): cf = self.newconfig() self.assertEqual(cf.sections(), [], "new ConfigParser should have no defined sections") self.assertFalse(cf.has_section("Foo"), "new ConfigParser should have no acknowledged " "sections") with self.assertRaises(configparser.NoSectionError): cf.options("Foo") with self.assertRaises(configparser.NoSectionError): cf.set("foo", "bar", "value") e = self.get_error(cf, configparser.NoSectionError, "foo", "bar") self.assertEqual(e.args, ("foo",)) cf.add_section("foo") e = self.get_error(cf, configparser.NoOptionError, "foo", "bar") self.assertEqual(e.args, ("bar", "foo")) def get_error(self, cf, exc, section, option): try: cf.get(section, option) except exc as e: return e else: self.fail("expected exception type %s.%s" % (exc.__module__, exc.__name__)) def test_boolean(self): cf = self.fromstring( "[BOOLTEST]\n" "T1{equals}1\n" "T2{equals}TRUE\n" "T3{equals}True\n" "T4{equals}oN\n" "T5{equals}yes\n" "F1{equals}0\n" "F2{equals}FALSE\n" "F3{equals}False\n" "F4{equals}oFF\n" "F5{equals}nO\n" "E1{equals}2\n" "E2{equals}foo\n" "E3{equals}-1\n" "E4{equals}0.1\n" "E5{equals}FALSE AND MORE".format(equals=self.delimiters[0]) ) for x in range(1, 5): self.assertTrue(cf.getboolean('BOOLTEST', 't%d' % x)) self.assertFalse(cf.getboolean('BOOLTEST', 'f%d' % x)) self.assertRaises(ValueError, cf.getboolean, 'BOOLTEST', 'e%d' % x) def test_weird_errors(self): cf = self.newconfig() cf.add_section("Foo") with self.assertRaises(configparser.DuplicateSectionError) as cm: cf.add_section("Foo") e = cm.exception self.assertEqual(str(e), "Section 'Foo' already exists") self.assertEqual(e.args, ("Foo", None, None)) if self.strict: with self.assertRaises(configparser.DuplicateSectionError) as cm: cf.read_string(textwrap.dedent("""\ [Foo] will this be added{equals}True [Bar] what about this{equals}True [Foo] oops{equals}this won't """.format(equals=self.delimiters[0])), source='<foo-bar>') e = cm.exception self.assertEqual(str(e), "While reading from '<foo-bar>' " "[line 5]: section 'Foo' already exists") self.assertEqual(e.args, ("Foo", '<foo-bar>', 5)) with self.assertRaises(configparser.DuplicateOptionError) as cm: cf.read_dict({'Bar': {'opt': 'val', 'OPT': 'is really `opt`'}}) e = cm.exception self.assertEqual(str(e), "While reading from '<dict>': option " "'opt' in section 'Bar' already exists") self.assertEqual(e.args, ("Bar", "opt", "<dict>", None)) def test_write(self): config_string = ( "[Long Line]\n" "foo{0[0]} this line is much, much longer than my editor\n" " likes it.\n" "[{default_section}]\n" "foo{0[1]} another very\n" " long line\n" "[Long Line - With Comments!]\n" "test {0[1]} we {comment} can\n" " also {comment} place\n" " comments {comment} in\n" " multiline {comment} values" "\n".format(self.delimiters, comment=self.comment_prefixes[0], default_section=self.default_section) ) if self.allow_no_value: config_string += ( "[Valueless]\n" "option-without-value\n" ) cf = self.fromstring(config_string) for space_around_delimiters in (True, False): output = io.StringIO() cf.write(output, space_around_delimiters=space_around_delimiters) delimiter = self.delimiters[0] if space_around_delimiters: delimiter = " {} ".format(delimiter) expect_string = ( "[{default_section}]\n" "foo{equals}another very\n" "\tlong line\n" "\n" "[Long Line]\n" "foo{equals}this line is much, much longer than my editor\n" "\tlikes it.\n" "\n" "[Long Line - With Comments!]\n" "test{equals}we\n" "\talso\n" "\tcomments\n" "\tmultiline\n" "\n".format(equals=delimiter, default_section=self.default_section) ) if self.allow_no_value: expect_string += ( "[Valueless]\n" "option-without-value\n" "\n" ) self.assertEqual(output.getvalue(), expect_string) def test_set_string_types(self): cf = self.fromstring("[sect]\n" "option1{eq}foo\n".format(eq=self.delimiters[0])) # Check that we don't get an exception when setting values in # an existing section using strings: class mystr(str): pass cf.set("sect", "option1", "splat") cf.set("sect", "option1", mystr("splat")) cf.set("sect", "option2", "splat") cf.set("sect", "option2", mystr("splat")) cf.set("sect", "option1", "splat") cf.set("sect", "option2", "splat") def test_read_returns_file_list(self): if self.delimiters[0] != '=': # skip reading the file if we're using an incompatible format return file1 = support.findfile("cfgparser.1") # check when we pass a mix of readable and non-readable files: cf = self.newconfig() parsed_files = cf.read([file1, "nonexistent-file"]) self.assertEqual(parsed_files, [file1]) self.assertEqual(cf.get("Foo Bar", "foo"), "newbar") # check when we pass only a filename: cf = self.newconfig() parsed_files = cf.read(file1) self.assertEqual(parsed_files, [file1]) self.assertEqual(cf.get("Foo Bar", "foo"), "newbar") # check when we pass only missing files: cf = self.newconfig() parsed_files = cf.read(["nonexistent-file"]) self.assertEqual(parsed_files, []) # check when we pass no files: cf = self.newconfig() parsed_files = cf.read([]) self.assertEqual(parsed_files, []) # shared by subclasses def get_interpolation_config(self): return self.fromstring( "[Foo]\n" "bar{equals}something %(with1)s interpolation (1 step)\n" "bar9{equals}something %(with9)s lots of interpolation (9 steps)\n" "bar10{equals}something %(with10)s lots of interpolation (10 steps)\n" "bar11{equals}something %(with11)s lots of interpolation (11 steps)\n" "with11{equals}%(with10)s\n" "with10{equals}%(with9)s\n" "with9{equals}%(with8)s\n" "with8{equals}%(With7)s\n" "with7{equals}%(WITH6)s\n" "with6{equals}%(with5)s\n" "With5{equals}%(with4)s\n" "WITH4{equals}%(with3)s\n" "with3{equals}%(with2)s\n" "with2{equals}%(with1)s\n" "with1{equals}with\n" "\n" "[Mutual Recursion]\n" "foo{equals}%(bar)s\n" "bar{equals}%(foo)s\n" "\n" "[Interpolation Error]\n" # no definition for 'reference' "name{equals}%(reference)s\n".format(equals=self.delimiters[0])) def check_items_config(self, expected): cf = self.fromstring(""" [section] name {0[0]} %(value)s key{0[1]} |%(name)s| getdefault{0[1]} |%(default)s| """.format(self.delimiters), defaults={"default": "<default>"}) L = list(cf.items("section", vars={'value': 'value'})) L.sort() self.assertEqual(L, expected) with self.assertRaises(configparser.NoSectionError): cf.items("no such section") def test_popitem(self): cf = self.fromstring(""" [section1] name1 {0[0]} value1 [section2] name2 {0[0]} value2 [section3] name3 {0[0]} value3 """.format(self.delimiters), defaults={"default": "<default>"}) self.assertEqual(cf.popitem()[0], 'section1') self.assertEqual(cf.popitem()[0], 'section2') self.assertEqual(cf.popitem()[0], 'section3') with self.assertRaises(KeyError): cf.popitem() def test_clear(self): cf = self.newconfig({"foo": "Bar"}) self.assertEqual( cf.get(self.default_section, "Foo"), "Bar", "could not locate option, expecting case-insensitive option names") cf['zing'] = {'option1': 'value1', 'option2': 'value2'} self.assertEqual(cf.sections(), ['zing']) self.assertEqual(set(cf['zing'].keys()), {'option1', 'option2', 'foo'}) cf.clear() self.assertEqual(set(cf.sections()), set()) self.assertEqual(set(cf[self.default_section].keys()), {'foo'}) def test_setitem(self): cf = self.fromstring(""" [section1] name1 {0[0]} value1 [section2] name2 {0[0]} value2 [section3] name3 {0[0]} value3 """.format(self.delimiters), defaults={"nameD": "valueD"}) self.assertEqual(set(cf['section1'].keys()), {'name1', 'named'}) self.assertEqual(set(cf['section2'].keys()), {'name2', 'named'}) self.assertEqual(set(cf['section3'].keys()), {'name3', 'named'}) self.assertEqual(cf['section1']['name1'], 'value1') self.assertEqual(cf['section2']['name2'], 'value2') self.assertEqual(cf['section3']['name3'], 'value3') self.assertEqual(cf.sections(), ['section1', 'section2', 'section3']) cf['section2'] = {'name22': 'value22'} self.assertEqual(set(cf['section2'].keys()), {'name22', 'named'}) self.assertEqual(cf['section2']['name22'], 'value22') self.assertNotIn('name2', cf['section2']) self.assertEqual(cf.sections(), ['section1', 'section2', 'section3']) cf['section3'] = {} self.assertEqual(set(cf['section3'].keys()), {'named'}) self.assertNotIn('name3', cf['section3']) self.assertEqual(cf.sections(), ['section1', 'section2', 'section3']) cf[self.default_section] = {} self.assertEqual(set(cf[self.default_section].keys()), set()) self.assertEqual(set(cf['section1'].keys()), {'name1'}) self.assertEqual(set(cf['section2'].keys()), {'name22'}) self.assertEqual(set(cf['section3'].keys()), set()) self.assertEqual(cf.sections(), ['section1', 'section2', 'section3']) class StrictTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.RawConfigParser strict = True class ConfigParserTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.ConfigParser def test_interpolation(self): cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "bar"), "something with interpolation (1 step)") eq(cf.get("Foo", "bar9"), "something with lots of interpolation (9 steps)") eq(cf.get("Foo", "bar10"), "something with lots of interpolation (10 steps)") e = self.get_error(cf, configparser.InterpolationDepthError, "Foo", "bar11") if self.interpolation == configparser._UNSET: self.assertEqual(e.args, ("bar11", "Foo", "%(with1)s")) elif isinstance(self.interpolation, configparser.LegacyInterpolation): self.assertEqual(e.args, ("bar11", "Foo", "something %(with11)s lots of interpolation (11 steps)")) def test_interpolation_missing_value(self): cf = self.get_interpolation_config() e = self.get_error(cf, configparser.InterpolationMissingOptionError, "Interpolation Error", "name") self.assertEqual(e.reference, "reference") self.assertEqual(e.section, "Interpolation Error") self.assertEqual(e.option, "name") if self.interpolation == configparser._UNSET: self.assertEqual(e.args, ('name', 'Interpolation Error', '', 'reference')) elif isinstance(self.interpolation, configparser.LegacyInterpolation): self.assertEqual(e.args, ('name', 'Interpolation Error', '%(reference)s', 'reference')) def test_items(self): self.check_items_config([('default', '<default>'), ('getdefault', '|<default>|'), ('key', '|value|'), ('name', 'value'), ('value', 'value')]) def test_safe_interpolation(self): # See http://www.python.org/sf/511737 cf = self.fromstring("[section]\n" "option1{eq}xxx\n" "option2{eq}%(option1)s/xxx\n" "ok{eq}%(option1)s/%%s\n" "not_ok{eq}%(option2)s/%%s".format( eq=self.delimiters[0])) self.assertEqual(cf.get("section", "ok"), "xxx/%s") if self.interpolation == configparser._UNSET: self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s") elif isinstance(self.interpolation, configparser.LegacyInterpolation): with self.assertRaises(TypeError): cf.get("section", "not_ok") def test_set_malformatted_interpolation(self): cf = self.fromstring("[sect]\n" "option1{eq}foo\n".format(eq=self.delimiters[0])) self.assertEqual(cf.get('sect', "option1"), "foo") self.assertRaises(ValueError, cf.set, "sect", "option1", "%foo") self.assertRaises(ValueError, cf.set, "sect", "option1", "foo%") self.assertRaises(ValueError, cf.set, "sect", "option1", "f%oo") self.assertEqual(cf.get('sect', "option1"), "foo") # bug #5741: double percents are *not* malformed cf.set("sect", "option2", "foo%%bar") self.assertEqual(cf.get("sect", "option2"), "foo%bar") def test_set_nonstring_types(self): cf = self.fromstring("[sect]\n" "option1{eq}foo\n".format(eq=self.delimiters[0])) # Check that we get a TypeError when setting non-string values # in an existing section: self.assertRaises(TypeError, cf.set, "sect", "option1", 1) self.assertRaises(TypeError, cf.set, "sect", "option1", 1.0) self.assertRaises(TypeError, cf.set, "sect", "option1", object()) self.assertRaises(TypeError, cf.set, "sect", "option2", 1) self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0) self.assertRaises(TypeError, cf.set, "sect", "option2", object()) self.assertRaises(TypeError, cf.set, "sect", 123, "invalid opt name!") self.assertRaises(TypeError, cf.add_section, 123) def test_add_section_default(self): cf = self.newconfig() self.assertRaises(ValueError, cf.add_section, self.default_section) class ConfigParserTestCaseNoInterpolation(BasicTestCase, unittest.TestCase): config_class = configparser.ConfigParser interpolation = None ini = textwrap.dedent(""" [numbers] one = 1 two = %(one)s * 2 three = ${common:one} * 3 [hexen] sixteen = ${numbers:two} * 8 """).strip() def assertMatchesIni(self, cf): self.assertEqual(cf['numbers']['one'], '1') self.assertEqual(cf['numbers']['two'], '%(one)s * 2') self.assertEqual(cf['numbers']['three'], '${common:one} * 3') self.assertEqual(cf['hexen']['sixteen'], '${numbers:two} * 8') def test_no_interpolation(self): cf = self.fromstring(self.ini) self.assertMatchesIni(cf) def test_empty_case(self): cf = self.newconfig() self.assertIsNone(cf.read_string("")) def test_none_as_default_interpolation(self): class CustomConfigParser(configparser.ConfigParser): _DEFAULT_INTERPOLATION = None cf = CustomConfigParser() cf.read_string(self.ini) self.assertMatchesIni(cf) class ConfigParserTestCaseLegacyInterpolation(ConfigParserTestCase): config_class = configparser.ConfigParser interpolation = configparser.LegacyInterpolation() def test_set_malformatted_interpolation(self): cf = self.fromstring("[sect]\n" "option1{eq}foo\n".format(eq=self.delimiters[0])) self.assertEqual(cf.get('sect', "option1"), "foo") cf.set("sect", "option1", "%foo") self.assertEqual(cf.get('sect', "option1"), "%foo") cf.set("sect", "option1", "foo%") self.assertEqual(cf.get('sect', "option1"), "foo%") cf.set("sect", "option1", "f%oo") self.assertEqual(cf.get('sect', "option1"), "f%oo") # bug #5741: double percents are *not* malformed cf.set("sect", "option2", "foo%%bar") self.assertEqual(cf.get("sect", "option2"), "foo%%bar") class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase): delimiters = (':=', '$') comment_prefixes = ('//', '"') inline_comment_prefixes = ('//', '"') class ConfigParserTestCaseNonStandardDefaultSection(ConfigParserTestCase): default_section = 'general' class MultilineValuesTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.ConfigParser wonderful_spam = ("I'm having spam spam spam spam " "spam spam spam beaked beans spam " "spam spam and spam!").replace(' ', '\t\n') def setUp(self): cf = self.newconfig() for i in range(100): s = 'section{}'.format(i) cf.add_section(s) for j in range(10): cf.set(s, 'lovely_spam{}'.format(j), self.wonderful_spam) with open(support.TESTFN, 'w') as f: cf.write(f) def tearDown(self): os.unlink(support.TESTFN) def test_dominating_multiline_values(self): # We're reading from file because this is where the code changed # during performance updates in Python 3.2 cf_from_file = self.newconfig() with open(support.TESTFN) as f: cf_from_file.read_file(f) self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'), self.wonderful_spam.replace('\t\n', '\n')) class RawConfigParserTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.RawConfigParser def test_interpolation(self): cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "bar"), "something %(with1)s interpolation (1 step)") eq(cf.get("Foo", "bar9"), "something %(with9)s lots of interpolation (9 steps)") eq(cf.get("Foo", "bar10"), "something %(with10)s lots of interpolation (10 steps)") eq(cf.get("Foo", "bar11"), "something %(with11)s lots of interpolation (11 steps)") def test_items(self): self.check_items_config([('default', '<default>'), ('getdefault', '|%(default)s|'), ('key', '|%(name)s|'), ('name', '%(value)s'), ('value', 'value')]) def test_set_nonstring_types(self): cf = self.newconfig() cf.add_section('non-string') cf.set('non-string', 'int', 1) cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13]) cf.set('non-string', 'dict', {'pi': 3.14159}) self.assertEqual(cf.get('non-string', 'int'), 1) self.assertEqual(cf.get('non-string', 'list'), [0, 1, 1, 2, 3, 5, 8, 13]) self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159}) cf.add_section(123) cf.set(123, 'this is sick', True) self.assertEqual(cf.get(123, 'this is sick'), True) if cf._dict is configparser._default_dict: # would not work for SortedDict; only checking for the most common # default dictionary (OrderedDict) cf.optionxform = lambda x: x cf.set('non-string', 1, 1) self.assertEqual(cf.get('non-string', 1), 1) class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): delimiters = (':=', '$') comment_prefixes = ('//', '"') inline_comment_prefixes = ('//', '"') class RawConfigParserTestSambaConf(CfgParserTestCaseClass, unittest.TestCase): config_class = configparser.RawConfigParser comment_prefixes = ('#', ';', '----') inline_comment_prefixes = ('//',) empty_lines_in_values = False def test_reading(self): smbconf = support.findfile("cfgparser.2") # check when we pass a mix of readable and non-readable files: cf = self.newconfig() parsed_files = cf.read([smbconf, "nonexistent-file"], encoding='utf-8') self.assertEqual(parsed_files, [smbconf]) sections = ['global', 'homes', 'printers', 'print$', 'pdf-generator', 'tmp', 'Agustin'] self.assertEqual(cf.sections(), sections) self.assertEqual(cf.get("global", "workgroup"), "MDKGROUP") self.assertEqual(cf.getint("global", "max log size"), 50) self.assertEqual(cf.get("global", "hosts allow"), "127.") self.assertEqual(cf.get("tmp", "echo command"), "cat %s; rm %s") class ConfigParserTestCaseExtendedInterpolation(BasicTestCase, unittest.TestCase): config_class = configparser.ConfigParser interpolation = configparser.ExtendedInterpolation() default_section = 'common' strict = True def fromstring(self, string, defaults=None, optionxform=None): cf = self.newconfig(defaults) if optionxform: cf.optionxform = optionxform cf.read_string(string) return cf def test_extended_interpolation(self): cf = self.fromstring(textwrap.dedent(""" [common] favourite Beatle = Paul favourite color = green [tom] favourite band = ${favourite color} day favourite pope = John ${favourite Beatle} II sequel = ${favourite pope}I [ambv] favourite Beatle = George son of Edward VII = ${favourite Beatle} V son of George V = ${son of Edward VII}I [stanley] favourite Beatle = ${ambv:favourite Beatle} favourite pope = ${tom:favourite pope} favourite color = black favourite state of mind = paranoid favourite movie = soylent ${common:favourite color} favourite song = ${favourite color} sabbath - ${favourite state of mind} """).strip()) eq = self.assertEqual eq(cf['common']['favourite Beatle'], 'Paul') eq(cf['common']['favourite color'], 'green') eq(cf['tom']['favourite Beatle'], 'Paul') eq(cf['tom']['favourite color'], 'green') eq(cf['tom']['favourite band'], 'green day') eq(cf['tom']['favourite pope'], 'John Paul II') eq(cf['tom']['sequel'], 'John Paul III') eq(cf['ambv']['favourite Beatle'], 'George') eq(cf['ambv']['favourite color'], 'green') eq(cf['ambv']['son of Edward VII'], 'George V') eq(cf['ambv']['son of George V'], 'George VI') eq(cf['stanley']['favourite Beatle'], 'George') eq(cf['stanley']['favourite color'], 'black') eq(cf['stanley']['favourite state of mind'], 'paranoid') eq(cf['stanley']['favourite movie'], 'soylent green') eq(cf['stanley']['favourite pope'], 'John Paul II') eq(cf['stanley']['favourite song'], 'black sabbath - paranoid') def test_endless_loop(self): cf = self.fromstring(textwrap.dedent(""" [one for you] ping = ${one for me:pong} [one for me] pong = ${one for you:ping} [selfish] me = ${me} """).strip()) with self.assertRaises(configparser.InterpolationDepthError): cf['one for you']['ping'] with self.assertRaises(configparser.InterpolationDepthError): cf['selfish']['me'] def test_strange_options(self): cf = self.fromstring(""" [dollars] $var = $$value $var2 = ${$var} ${sick} = cannot interpolate me [interpolated] $other = ${dollars:$var} $trying = ${dollars:${sick}} """) self.assertEqual(cf['dollars']['$var'], '$value') self.assertEqual(cf['interpolated']['$other'], '$value') self.assertEqual(cf['dollars']['${sick}'], 'cannot interpolate me') exception_class = configparser.InterpolationMissingOptionError with self.assertRaises(exception_class) as cm: cf['interpolated']['$trying'] self.assertEqual(cm.exception.reference, 'dollars:${sick') self.assertEqual(cm.exception.args[2], '}') #rawval def test_case_sensitivity_basic(self): ini = textwrap.dedent(""" [common] optionlower = value OptionUpper = Value [Common] optionlower = a better ${common:optionlower} OptionUpper = A Better ${common:OptionUpper} [random] foolower = ${common:optionlower} redefined FooUpper = ${Common:OptionUpper} Redefined """).strip() cf = self.fromstring(ini) eq = self.assertEqual eq(cf['common']['optionlower'], 'value') eq(cf['common']['OptionUpper'], 'Value') eq(cf['Common']['optionlower'], 'a better value') eq(cf['Common']['OptionUpper'], 'A Better Value') eq(cf['random']['foolower'], 'value redefined') eq(cf['random']['FooUpper'], 'A Better Value Redefined') def test_case_sensitivity_conflicts(self): ini = textwrap.dedent(""" [common] option = value Option = Value [Common] option = a better ${common:option} Option = A Better ${common:Option} [random] foo = ${common:option} redefined Foo = ${Common:Option} Redefined """).strip() with self.assertRaises(configparser.DuplicateOptionError): cf = self.fromstring(ini) # raw options cf = self.fromstring(ini, optionxform=lambda opt: opt) eq = self.assertEqual eq(cf['common']['option'], 'value') eq(cf['common']['Option'], 'Value') eq(cf['Common']['option'], 'a better value') eq(cf['Common']['Option'], 'A Better Value') eq(cf['random']['foo'], 'value redefined') eq(cf['random']['Foo'], 'A Better Value Redefined') def test_other_errors(self): cf = self.fromstring(""" [interpolation fail] case1 = ${where's the brace case2 = ${does_not_exist} case3 = ${wrong_section:wrong_value} case4 = ${i:like:colon:characters} case5 = $100 for Fail No 5! """) with self.assertRaises(configparser.InterpolationSyntaxError): cf['interpolation fail']['case1'] with self.assertRaises(configparser.InterpolationMissingOptionError): cf['interpolation fail']['case2'] with self.assertRaises(configparser.InterpolationMissingOptionError): cf['interpolation fail']['case3'] with self.assertRaises(configparser.InterpolationSyntaxError): cf['interpolation fail']['case4'] with self.assertRaises(configparser.InterpolationSyntaxError): cf['interpolation fail']['case5'] with self.assertRaises(ValueError): cf['interpolation fail']['case6'] = "BLACK $ABBATH" class ConfigParserTestCaseNoValue(ConfigParserTestCase): allow_no_value = True class ConfigParserTestCaseTrickyFile(CfgParserTestCaseClass, unittest.TestCase): config_class = configparser.ConfigParser delimiters = {'='} comment_prefixes = {'#'} allow_no_value = True def test_cfgparser_dot_3(self): tricky = support.findfile("cfgparser.3") cf = self.newconfig() self.assertEqual(len(cf.read(tricky, encoding='utf-8')), 1) self.assertEqual(cf.sections(), ['strange', 'corruption', 'yeah, sections can be ' 'indented as well', 'another one!', 'no values here', 'tricky interpolation', 'more interpolation']) self.assertEqual(cf.getint(self.default_section, 'go', vars={'interpolate': '-1'}), -1) with self.assertRaises(ValueError): # no interpolation will happen cf.getint(self.default_section, 'go', raw=True, vars={'interpolate': '-1'}) self.assertEqual(len(cf.get('strange', 'other').split('\n')), 4) self.assertEqual(len(cf.get('corruption', 'value').split('\n')), 10) longname = 'yeah, sections can be indented as well' self.assertFalse(cf.getboolean(longname, 'are they subsections')) self.assertEqual(cf.get(longname, 'lets use some Unicode'), '片仮名') self.assertEqual(len(cf.items('another one!')), 5) # 4 in section and # `go` from DEFAULT with self.assertRaises(configparser.InterpolationMissingOptionError): cf.items('no values here') self.assertEqual(cf.get('tricky interpolation', 'lets'), 'do this') self.assertEqual(cf.get('tricky interpolation', 'lets'), cf.get('tricky interpolation', 'go')) self.assertEqual(cf.get('more interpolation', 'lets'), 'go shopping') def test_unicode_failure(self): tricky = support.findfile("cfgparser.3") cf = self.newconfig() with self.assertRaises(UnicodeDecodeError): cf.read(tricky, encoding='ascii') class Issue7005TestCase(unittest.TestCase): """Test output when None is set() as a value and allow_no_value == False. http://bugs.python.org/issue7005 """ expected_output = "[section]\noption = None\n\n" def prepare(self, config_class): # This is the default, but that's the point. cp = config_class(allow_no_value=False) cp.add_section("section") cp.set("section", "option", None) sio = io.StringIO() cp.write(sio) return sio.getvalue() def test_none_as_value_stringified(self): cp = configparser.ConfigParser(allow_no_value=False) cp.add_section("section") with self.assertRaises(TypeError): cp.set("section", "option", None) def test_none_as_value_stringified_raw(self): output = self.prepare(configparser.RawConfigParser) self.assertEqual(output, self.expected_output) class SortedTestCase(RawConfigParserTestCase): dict_type = SortedDict def test_sorted(self): cf = self.fromstring("[b]\n" "o4=1\n" "o3=2\n" "o2=3\n" "o1=4\n" "[a]\n" "k=v\n") output = io.StringIO() cf.write(output) self.assertEqual(output.getvalue(), "[a]\n" "k = v\n\n" "[b]\n" "o1 = 4\n" "o2 = 3\n" "o3 = 2\n" "o4 = 1\n\n") class CompatibleTestCase(CfgParserTestCaseClass, unittest.TestCase): config_class = configparser.RawConfigParser comment_prefixes = '#;' inline_comment_prefixes = ';' def test_comment_handling(self): config_string = textwrap.dedent("""\ [Commented Bar] baz=qwe ; a comment foo: bar # not a comment! # but this is a comment ; another comment quirk: this;is not a comment ; a space must precede an inline comment """) cf = self.fromstring(config_string) self.assertEqual(cf.get('Commented Bar', 'foo'), 'bar # not a comment!') self.assertEqual(cf.get('Commented Bar', 'baz'), 'qwe') self.assertEqual(cf.get('Commented Bar', 'quirk'), 'this;is not a comment') class CopyTestCase(BasicTestCase, unittest.TestCase): config_class = configparser.ConfigParser def fromstring(self, string, defaults=None): cf = self.newconfig(defaults) cf.read_string(string) cf_copy = self.newconfig() cf_copy.read_dict(cf) # we have to clean up option duplicates that appeared because of # the magic DEFAULTSECT behaviour. for section in cf_copy.values(): if section.name == self.default_section: continue for default, value in cf[self.default_section].items(): if section[default] == value: del section[default] return cf_copy class FakeFile: def __init__(self): file_path = support.findfile("cfgparser.1") with open(file_path) as f: self.lines = f.readlines() self.lines.reverse() def readline(self): if len(self.lines): return self.lines.pop() return '' def readline_generator(f): """As advised in Doc/library/configparser.rst.""" line = f.readline() while line: yield line line = f.readline() class ReadFileTestCase(unittest.TestCase): def test_file(self): file_paths = [support.findfile("cfgparser.1")] try: file_paths.append(file_paths[0].encode('utf8')) except UnicodeEncodeError: pass # unfortunately we can't test bytes on this path for file_path in file_paths: parser = configparser.ConfigParser() with open(file_path) as f: parser.read_file(f) self.assertIn("Foo Bar", parser) self.assertIn("foo", parser["Foo Bar"]) self.assertEqual(parser["Foo Bar"]["foo"], "newbar") def test_iterable(self): lines = textwrap.dedent(""" [Foo Bar] foo=newbar""").strip().split('\n') parser = configparser.ConfigParser() parser.read_file(lines) self.assertIn("Foo Bar", parser) self.assertIn("foo", parser["Foo Bar"]) self.assertEqual(parser["Foo Bar"]["foo"], "newbar") def test_readline_generator(self): """Issue #11670.""" parser = configparser.ConfigParser() with self.assertRaises(TypeError): parser.read_file(FakeFile()) parser.read_file(readline_generator(FakeFile())) self.assertIn("Foo Bar", parser) self.assertIn("foo", parser["Foo Bar"]) self.assertEqual(parser["Foo Bar"]["foo"], "newbar") def test_source_as_bytes(self): """Issue #18260.""" lines = textwrap.dedent(""" [badbad] [badbad]""").strip().split('\n') parser = configparser.ConfigParser() with self.assertRaises(configparser.DuplicateSectionError) as dse: parser.read_file(lines, source=b"badbad") self.assertEqual( str(dse.exception), "While reading from b'badbad' [line 2]: section 'badbad' " "already exists" ) lines = textwrap.dedent(""" [badbad] bad = bad bad = bad""").strip().split('\n') parser = configparser.ConfigParser() with self.assertRaises(configparser.DuplicateOptionError) as dse: parser.read_file(lines, source=b"badbad") self.assertEqual( str(dse.exception), "While reading from b'badbad' [line 3]: option 'bad' in section " "'badbad' already exists" ) lines = textwrap.dedent(""" [badbad] = bad""").strip().split('\n') parser = configparser.ConfigParser() with self.assertRaises(configparser.ParsingError) as dse: parser.read_file(lines, source=b"badbad") self.assertEqual( str(dse.exception), "Source contains parsing errors: b'badbad'\n\t[line 2]: '= bad'" ) lines = textwrap.dedent(""" [badbad bad = bad""").strip().split('\n') parser = configparser.ConfigParser() with self.assertRaises(configparser.MissingSectionHeaderError) as dse: parser.read_file(lines, source=b"badbad") self.assertEqual( str(dse.exception), "File contains no section headers.\nfile: b'badbad', line: 1\n" "'[badbad'" ) class CoverageOneHundredTestCase(unittest.TestCase): """Covers edge cases in the codebase.""" def test_duplicate_option_error(self): error = configparser.DuplicateOptionError('section', 'option') self.assertEqual(error.section, 'section') self.assertEqual(error.option, 'option') self.assertEqual(error.source, None) self.assertEqual(error.lineno, None) self.assertEqual(error.args, ('section', 'option', None, None)) self.assertEqual(str(error), "Option 'option' in section 'section' " "already exists") def test_interpolation_depth_error(self): error = configparser.InterpolationDepthError('option', 'section', 'rawval') self.assertEqual(error.args, ('option', 'section', 'rawval')) self.assertEqual(error.option, 'option') self.assertEqual(error.section, 'section') def test_parsing_error(self): with self.assertRaises(ValueError) as cm: configparser.ParsingError() self.assertEqual(str(cm.exception), "Required argument `source' not " "given.") with self.assertRaises(ValueError) as cm: configparser.ParsingError(source='source', filename='filename') self.assertEqual(str(cm.exception), "Cannot specify both `filename' " "and `source'. Use `source'.") error = configparser.ParsingError(filename='source') self.assertEqual(error.source, 'source') with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always", DeprecationWarning) self.assertEqual(error.filename, 'source') error.filename = 'filename' self.assertEqual(error.source, 'filename') for warning in w: self.assertTrue(warning.category is DeprecationWarning) def test_interpolation_validation(self): parser = configparser.ConfigParser() parser.read_string(""" [section] invalid_percent = % invalid_reference = %(() invalid_variable = %(does_not_exist)s """) with self.assertRaises(configparser.InterpolationSyntaxError) as cm: parser['section']['invalid_percent'] self.assertEqual(str(cm.exception), "'%' must be followed by '%' or " "'(', found: '%'") with self.assertRaises(configparser.InterpolationSyntaxError) as cm: parser['section']['invalid_reference'] self.assertEqual(str(cm.exception), "bad interpolation variable " "reference '%(()'") def test_readfp_deprecation(self): sio = io.StringIO(""" [section] option = value """) parser = configparser.ConfigParser() with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always", DeprecationWarning) parser.readfp(sio, filename='StringIO') for warning in w: self.assertTrue(warning.category is DeprecationWarning) self.assertEqual(len(parser), 2) self.assertEqual(parser['section']['option'], 'value') def test_safeconfigparser_deprecation(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always", DeprecationWarning) parser = configparser.SafeConfigParser() for warning in w: self.assertTrue(warning.category is DeprecationWarning) def test_sectionproxy_repr(self): parser = configparser.ConfigParser() parser.read_string(""" [section] key = value """) self.assertEqual(repr(parser['section']), '<Section: section>') class ExceptionPicklingTestCase(unittest.TestCase): """Tests for issue #13760: ConfigParser exceptions are not picklable.""" def test_error(self): import pickle e1 = configparser.Error('value') pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(repr(e1), repr(e2)) def test_nosectionerror(self): import pickle e1 = configparser.NoSectionError('section') pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.section, e2.section) self.assertEqual(repr(e1), repr(e2)) def test_nooptionerror(self): import pickle e1 = configparser.NoOptionError('option', 'section') pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.section, e2.section) self.assertEqual(e1.option, e2.option) self.assertEqual(repr(e1), repr(e2)) def test_duplicatesectionerror(self): import pickle e1 = configparser.DuplicateSectionError('section', 'source', 123) pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.section, e2.section) self.assertEqual(e1.source, e2.source) self.assertEqual(e1.lineno, e2.lineno) self.assertEqual(repr(e1), repr(e2)) def test_duplicateoptionerror(self): import pickle e1 = configparser.DuplicateOptionError('section', 'option', 'source', 123) pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.section, e2.section) self.assertEqual(e1.option, e2.option) self.assertEqual(e1.source, e2.source) self.assertEqual(e1.lineno, e2.lineno) self.assertEqual(repr(e1), repr(e2)) def test_interpolationerror(self): import pickle e1 = configparser.InterpolationError('option', 'section', 'msg') pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.section, e2.section) self.assertEqual(e1.option, e2.option) self.assertEqual(repr(e1), repr(e2)) def test_interpolationmissingoptionerror(self): import pickle e1 = configparser.InterpolationMissingOptionError('option', 'section', 'rawval', 'reference') pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.section, e2.section) self.assertEqual(e1.option, e2.option) self.assertEqual(e1.reference, e2.reference) self.assertEqual(repr(e1), repr(e2)) def test_interpolationsyntaxerror(self): import pickle e1 = configparser.InterpolationSyntaxError('option', 'section', 'msg') pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.section, e2.section) self.assertEqual(e1.option, e2.option) self.assertEqual(repr(e1), repr(e2)) def test_interpolationdeptherror(self): import pickle e1 = configparser.InterpolationDepthError('option', 'section', 'rawval') pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.section, e2.section) self.assertEqual(e1.option, e2.option) self.assertEqual(repr(e1), repr(e2)) def test_parsingerror(self): import pickle e1 = configparser.ParsingError('source') e1.append(1, 'line1') e1.append(2, 'line2') e1.append(3, 'line3') pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.source, e2.source) self.assertEqual(e1.errors, e2.errors) self.assertEqual(repr(e1), repr(e2)) e1 = configparser.ParsingError(filename='filename') e1.append(1, 'line1') e1.append(2, 'line2') e1.append(3, 'line3') pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.source, e2.source) self.assertEqual(e1.errors, e2.errors) self.assertEqual(repr(e1), repr(e2)) def test_missingsectionheadererror(self): import pickle e1 = configparser.MissingSectionHeaderError('filename', 123, 'line') pickled = pickle.dumps(e1) e2 = pickle.loads(pickled) self.assertEqual(e1.message, e2.message) self.assertEqual(e1.args, e2.args) self.assertEqual(e1.line, e2.line) self.assertEqual(e1.source, e2.source) self.assertEqual(e1.lineno, e2.lineno) self.assertEqual(repr(e1), repr(e2)) class InlineCommentStrippingTestCase(unittest.TestCase): """Tests for issue #14590: ConfigParser doesn't strip inline comment when delimiter occurs earlier without preceding space..""" def test_stripping(self): cfg = configparser.ConfigParser(inline_comment_prefixes=(';', '#', '//')) cfg.read_string(""" [section] k1 = v1;still v1 k2 = v2 ;a comment k3 = v3 ; also a comment k4 = v4;still v4 ;a comment k5 = v5;still v5 ; also a comment k6 = v6;still v6; and still v6 ;a comment k7 = v7;still v7; and still v7 ; also a comment [multiprefix] k1 = v1;still v1 #a comment ; yeah, pretty much k2 = v2 // this already is a comment ; continued k3 = v3;#//still v3# and still v3 ; a comment """) s = cfg['section'] self.assertEqual(s['k1'], 'v1;still v1') self.assertEqual(s['k2'], 'v2') self.assertEqual(s['k3'], 'v3') self.assertEqual(s['k4'], 'v4;still v4') self.assertEqual(s['k5'], 'v5;still v5') self.assertEqual(s['k6'], 'v6;still v6; and still v6') self.assertEqual(s['k7'], 'v7;still v7; and still v7') s = cfg['multiprefix'] self.assertEqual(s['k1'], 'v1;still v1') self.assertEqual(s['k2'], 'v2') self.assertEqual(s['k3'], 'v3;#//still v3# and still v3') if __name__ == '__main__': unittest.main()