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