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