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