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