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