1from iniparse import compat as ConfigParser 2from six import StringIO 3try: 4 import UserDict 5except ImportError: 6 import collections as UserDict 7import unittest 8 9import sys 10if sys.version_info[0] < 3: 11 from test import test_support 12else: 13 from test import support as test_support 14 15 16class SortedDict(UserDict.UserDict): 17 def items(self): 18 result = self.data.items() 19 result.sort() 20 return result 21 22 def keys(self): 23 result = self.data.keys() 24 result.sort() 25 return result 26 27 def values(self): 28 result = self.items() 29 return [i[1] for i in result] 30 31 def iteritems(self): 32 return iter(self.items()) 33 34 def iterkeys(self): 35 return iter(self.keys()) 36 37 __iter__ = iterkeys 38 39 def itervalues(self): 40 return iter(self.values()) 41 42 43class TestCaseBase(unittest.TestCase): 44 def newconfig(self, defaults=None): 45 if defaults is None: 46 self.cf = self.config_class() 47 else: 48 self.cf = self.config_class(defaults) 49 return self.cf 50 51 def fromstring(self, string, defaults=None): 52 cf = self.newconfig(defaults) 53 sio = StringIO(string) 54 cf.readfp(sio) 55 return cf 56 57 def test_basic(self): 58 cf = self.fromstring( 59 "[Foo Bar]\n" 60 "foo=bar\n" 61 "[Spacey Bar]\n" 62 "foo = bar\n" 63 "[Commented Bar]\n" 64 "foo: bar ; comment\n" 65 "[Long Line]\n" 66 "foo: this line is much, much longer than my editor\n" 67 " likes it.\n" 68 "[Section\\with$weird%characters[\t]\n" 69 "[Internationalized Stuff]\n" 70 "foo[bg]: Bulgarian\n" 71 "foo=Default\n" 72 "foo[en]=English\n" 73 "foo[de]=Deutsch\n" 74 "[Spaces]\n" 75 "key with spaces : value\n" 76 "another with spaces = splat!\n" 77 ) 78 L = cf.sections() 79 L.sort() 80 eq = self.assertEqual 81 eq(L, [r'Commented Bar', 82 r'Foo Bar', 83 r'Internationalized Stuff', 84 r'Long Line', 85 r'Section\with$weird%characters[' '\t', 86 r'Spaces', 87 r'Spacey Bar', 88 ]) 89 90 # The use of spaces in the section names serves as a 91 # regression test for SourceForge bug #583248: 92 # http://www.python.org/sf/583248 93 eq(cf.get('Foo Bar', 'foo'), 'bar') 94 eq(cf.get('Spacey Bar', 'foo'), 'bar') 95 eq(cf.get('Commented Bar', 'foo'), 'bar') 96 eq(cf.get('Spaces', 'key with spaces'), 'value') 97 eq(cf.get('Spaces', 'another with spaces'), 'splat!') 98 99 self.failIf('__name__' in cf.options("Foo Bar"), 100 '__name__ "option" should not be exposed by the API!') 101 102 # Make sure the right things happen for remove_option(); 103 # added to include check for SourceForge bug #123324: 104 self.failUnless(cf.remove_option('Foo Bar', 'foo'), 105 "remove_option() failed to report existance of option") 106 self.failIf(cf.has_option('Foo Bar', 'foo'), 107 "remove_option() failed to remove option") 108 self.failIf(cf.remove_option('Foo Bar', 'foo'), 109 "remove_option() failed to report non-existance of option" 110 " that was removed") 111 112 self.assertRaises(ConfigParser.NoSectionError, 113 cf.remove_option, 'No Such Section', 'foo') 114 115 eq(cf.get('Long Line', 'foo'), 116 'this line is much, much longer than my editor\nlikes it.') 117 118 def test_case_sensitivity(self): 119 cf = self.newconfig() 120 cf.add_section("A") 121 cf.add_section("a") 122 L = cf.sections() 123 L.sort() 124 eq = self.assertEqual 125 eq(L, ["A", "a"]) 126 cf.set("a", "B", "value") 127 eq(cf.options("a"), ["b"]) 128 eq(cf.get("a", "b"), "value", 129 "could not locate option, expecting case-insensitive option names") 130 self.failUnless(cf.has_option("a", "b")) 131 cf.set("A", "A-B", "A-B value") 132 for opt in ("a-b", "A-b", "a-B", "A-B"): 133 self.failUnless( 134 cf.has_option("A", opt), 135 "has_option() returned false for option which should exist") 136 eq(cf.options("A"), ["a-b"]) 137 eq(cf.options("a"), ["b"]) 138 cf.remove_option("a", "B") 139 eq(cf.options("a"), []) 140 141 # SF bug #432369: 142 cf = self.fromstring( 143 "[MySection]\nOption: first line\n\tsecond line\n") 144 eq(cf.options("MySection"), ["option"]) 145 eq(cf.get("MySection", "Option"), "first line\nsecond line") 146 147 # SF bug #561822: 148 cf = self.fromstring("[section]\nnekey=nevalue\n", 149 defaults={"key":"value"}) 150 self.failUnless(cf.has_option("section", "Key")) 151 152 def test_default_case_sensitivity(self): 153 cf = self.newconfig({"foo": "Bar"}) 154 self.assertEqual( 155 cf.get("DEFAULT", "Foo"), "Bar", 156 "could not locate option, expecting case-insensitive option names") 157 cf = self.newconfig({"Foo": "Bar"}) 158 self.assertEqual( 159 cf.get("DEFAULT", "Foo"), "Bar", 160 "could not locate option, expecting case-insensitive defaults") 161 162 def test_parse_errors(self): 163 self.newconfig() 164 self.parse_error(ConfigParser.ParsingError, 165 "[Foo]\n extra-spaces: splat\n") 166 self.parse_error(ConfigParser.ParsingError, 167 "[Foo]\n extra-spaces= splat\n") 168 self.parse_error(ConfigParser.ParsingError, 169 "[Foo]\noption-without-value\n") 170 self.parse_error(ConfigParser.ParsingError, 171 "[Foo]\n:value-without-option-name\n") 172 self.parse_error(ConfigParser.ParsingError, 173 "[Foo]\n=value-without-option-name\n") 174 self.parse_error(ConfigParser.MissingSectionHeaderError, 175 "No Section!\n") 176 177 def parse_error(self, exc, src): 178 sio = StringIO(src) 179 self.assertRaises(exc, self.cf.readfp, sio) 180 181 def test_query_errors(self): 182 cf = self.newconfig() 183 self.assertEqual(cf.sections(), [], 184 "new ConfigParser should have no defined sections") 185 self.failIf(cf.has_section("Foo"), 186 "new ConfigParser should have no acknowledged sections") 187 self.assertRaises(ConfigParser.NoSectionError, 188 cf.options, "Foo") 189 self.assertRaises(ConfigParser.NoSectionError, 190 cf.set, "foo", "bar", "value") 191 self.get_error(ConfigParser.NoSectionError, "foo", "bar") 192 cf.add_section("foo") 193 self.get_error(ConfigParser.NoOptionError, "foo", "bar") 194 195 def get_error(self, exc, section, option): 196 try: 197 self.cf.get(section, option) 198 except exc as e: 199 return e 200 else: 201 self.fail("expected exception type %s.%s" 202 % (exc.__module__, exc.__name__)) 203 204 def test_boolean(self): 205 cf = self.fromstring( 206 "[BOOLTEST]\n" 207 "T1=1\n" 208 "T2=TRUE\n" 209 "T3=True\n" 210 "T4=oN\n" 211 "T5=yes\n" 212 "F1=0\n" 213 "F2=FALSE\n" 214 "F3=False\n" 215 "F4=oFF\n" 216 "F5=nO\n" 217 "E1=2\n" 218 "E2=foo\n" 219 "E3=-1\n" 220 "E4=0.1\n" 221 "E5=FALSE AND MORE" 222 ) 223 for x in range(1, 5): 224 self.failUnless(cf.getboolean('BOOLTEST', 't%d' % x)) 225 self.failIf(cf.getboolean('BOOLTEST', 'f%d' % x)) 226 self.assertRaises(ValueError, 227 cf.getboolean, 'BOOLTEST', 'e%d' % x) 228 229 def test_weird_errors(self): 230 cf = self.newconfig() 231 cf.add_section("Foo") 232 self.assertRaises(ConfigParser.DuplicateSectionError, 233 cf.add_section, "Foo") 234 235 def test_write(self): 236 cf = self.fromstring( 237 "[Long Line]\n" 238 "foo: this line is much, much longer than my editor\n" 239 " likes it.\n" 240 "[DEFAULT]\n" 241 "foo: another very\n" 242 " long line" 243 ) 244 output = StringIO() 245 cf.write(output) 246 self.assertEqual( 247 output.getvalue(), 248 "[Long Line]\n" 249 "foo: this line is much, much longer than my editor\n" 250 " likes it.\n" 251 "[DEFAULT]\n" 252 "foo: another very\n" 253 " long line" 254 ) 255 256 def test_set_string_types(self): 257 cf = self.fromstring("[sect]\n" 258 "option1=foo\n") 259 # Check that we don't get an exception when setting values in 260 # an existing section using strings: 261 class mystr(str): 262 pass 263 cf.set("sect", "option1", "splat") 264 cf.set("sect", "option1", mystr("splat")) 265 cf.set("sect", "option2", "splat") 266 cf.set("sect", "option2", mystr("splat")) 267 try: 268 unicode 269 except NameError: 270 pass 271 else: 272 cf.set("sect", "option1", unicode("splat")) 273 cf.set("sect", "option2", unicode("splat")) 274 275 def test_read_returns_file_list(self): 276 file1 = test_support.findfile("cfgparser.1") 277 # check when we pass a mix of readable and non-readable files: 278 cf = self.newconfig() 279 parsed_files = cf.read([file1, "nonexistant-file"]) 280 self.assertEqual(parsed_files, [file1]) 281 self.assertEqual(cf.get("Foo Bar", "foo"), "newbar") 282 # check when we pass only a filename: 283 cf = self.newconfig() 284 parsed_files = cf.read(file1) 285 self.assertEqual(parsed_files, [file1]) 286 self.assertEqual(cf.get("Foo Bar", "foo"), "newbar") 287 # check when we pass only missing files: 288 cf = self.newconfig() 289 parsed_files = cf.read(["nonexistant-file"]) 290 self.assertEqual(parsed_files, []) 291 # check when we pass no files: 292 cf = self.newconfig() 293 parsed_files = cf.read([]) 294 self.assertEqual(parsed_files, []) 295 296 # shared by subclasses 297 def get_interpolation_config(self): 298 return self.fromstring( 299 "[Foo]\n" 300 "bar=something %(with1)s interpolation (1 step)\n" 301 "bar9=something %(with9)s lots of interpolation (9 steps)\n" 302 "bar10=something %(with10)s lots of interpolation (10 steps)\n" 303 "bar11=something %(with11)s lots of interpolation (11 steps)\n" 304 "with11=%(with10)s\n" 305 "with10=%(with9)s\n" 306 "with9=%(with8)s\n" 307 "with8=%(With7)s\n" 308 "with7=%(WITH6)s\n" 309 "with6=%(with5)s\n" 310 "With5=%(with4)s\n" 311 "WITH4=%(with3)s\n" 312 "with3=%(with2)s\n" 313 "with2=%(with1)s\n" 314 "with1=with\n" 315 "\n" 316 "[Mutual Recursion]\n" 317 "foo=%(bar)s\n" 318 "bar=%(foo)s\n" 319 "\n" 320 "[Interpolation Error]\n" 321 "name=%(reference)s\n", 322 # no definition for 'reference' 323 defaults={"getname": "%(__name__)s"}) 324 325 def check_items_config(self, expected): 326 cf = self.fromstring( 327 "[section]\n" 328 "name = value\n" 329 "key: |%(name)s| \n" 330 "getdefault: |%(default)s|\n" 331 "getname: |%(__name__)s|", 332 defaults={"default": "<default>"}) 333 L = list(cf.items("section")) 334 L.sort() 335 self.assertEqual(L, expected) 336 337 338class ConfigParserTestCase(TestCaseBase): 339 config_class = ConfigParser.ConfigParser 340 341 def test_interpolation(self): 342 cf = self.get_interpolation_config() 343 eq = self.assertEqual 344 eq(cf.get("Foo", "getname"), "Foo") 345 eq(cf.get("Foo", "bar"), "something with interpolation (1 step)") 346 eq(cf.get("Foo", "bar9"), 347 "something with lots of interpolation (9 steps)") 348 eq(cf.get("Foo", "bar10"), 349 "something with lots of interpolation (10 steps)") 350 self.get_error(ConfigParser.InterpolationDepthError, "Foo", "bar11") 351 352 def test_interpolation_missing_value(self): 353 cf = self.get_interpolation_config() 354 e = self.get_error(ConfigParser.InterpolationError, 355 "Interpolation Error", "name") 356 self.assertEqual(e.reference, "reference") 357 self.assertEqual(e.section, "Interpolation Error") 358 self.assertEqual(e.option, "name") 359 360 def test_items(self): 361 self.check_items_config([('default', '<default>'), 362 ('getdefault', '|<default>|'), 363 ('getname', '|section|'), 364 ('key', '|value|'), 365 ('name', 'value')]) 366 367 def test_set_nonstring_types(self): 368 cf = self.newconfig() 369 cf.add_section('non-string') 370 cf.set('non-string', 'int', 1) 371 cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13, '%(']) 372 cf.set('non-string', 'dict', {'pi': 3.14159, '%(': 1, 373 '%(list)': '%(list)'}) 374 cf.set('non-string', 'string_with_interpolation', '%(list)s') 375 self.assertEqual(cf.get('non-string', 'int', raw=True), 1) 376 self.assertRaises(TypeError, cf.get, 'non-string', 'int') 377 self.assertEqual(cf.get('non-string', 'list', raw=True), 378 [0, 1, 1, 2, 3, 5, 8, 13, '%(']) 379 self.assertRaises(TypeError, cf.get, 'non-string', 'list') 380 self.assertEqual(cf.get('non-string', 'dict', raw=True), 381 {'pi': 3.14159, '%(': 1, '%(list)': '%(list)'}) 382 self.assertRaises(TypeError, cf.get, 'non-string', 'dict') 383 self.assertEqual(cf.get('non-string', 'string_with_interpolation', 384 raw=True), '%(list)s') 385 self.assertRaises(ValueError, cf.get, 'non-string', 386 'string_with_interpolation', raw=False) 387 388 389class RawConfigParserTestCase(TestCaseBase): 390 config_class = ConfigParser.RawConfigParser 391 392 def test_interpolation(self): 393 cf = self.get_interpolation_config() 394 eq = self.assertEqual 395 eq(cf.get("Foo", "getname"), "%(__name__)s") 396 eq(cf.get("Foo", "bar"), 397 "something %(with1)s interpolation (1 step)") 398 eq(cf.get("Foo", "bar9"), 399 "something %(with9)s lots of interpolation (9 steps)") 400 eq(cf.get("Foo", "bar10"), 401 "something %(with10)s lots of interpolation (10 steps)") 402 eq(cf.get("Foo", "bar11"), 403 "something %(with11)s lots of interpolation (11 steps)") 404 405 def test_items(self): 406 self.check_items_config([('default', '<default>'), 407 ('getdefault', '|%(default)s|'), 408 ('getname', '|%(__name__)s|'), 409 ('key', '|%(name)s|'), 410 ('name', 'value')]) 411 412 def test_set_nonstring_types(self): 413 cf = self.newconfig() 414 cf.add_section('non-string') 415 cf.set('non-string', 'int', 1) 416 cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13]) 417 cf.set('non-string', 'dict', {'pi': 3.14159}) 418 self.assertEqual(cf.get('non-string', 'int'), 1) 419 self.assertEqual(cf.get('non-string', 'list'), 420 [0, 1, 1, 2, 3, 5, 8, 13]) 421 self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159}) 422 423 424class SafeConfigParserTestCase(ConfigParserTestCase): 425 config_class = ConfigParser.SafeConfigParser 426 427 def test_safe_interpolation(self): 428 # See http://www.python.org/sf/511737 429 cf = self.fromstring("[section]\n" 430 "option1=xxx\n" 431 "option2=%(option1)s/xxx\n" 432 "ok=%(option1)s/%%s\n" 433 "not_ok=%(option2)s/%%s") 434 self.assertEqual(cf.get("section", "ok"), "xxx/%s") 435 self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s") 436 437 def test_set_malformatted_interpolation(self): 438 cf = self.fromstring("[sect]\n" 439 "option1=foo\n") 440 441 self.assertEqual(cf.get('sect', "option1"), "foo") 442 443 self.assertRaises(ValueError, cf.set, "sect", "option1", "%foo") 444 self.assertRaises(ValueError, cf.set, "sect", "option1", "foo%") 445 self.assertRaises(ValueError, cf.set, "sect", "option1", "f%oo") 446 447 self.assertEqual(cf.get('sect', "option1"), "foo") 448 449 def test_set_nonstring_types(self): 450 cf = self.fromstring("[sect]\n" 451 "option1=foo\n") 452 # Check that we get a TypeError when setting non-string values 453 # in an existing section: 454 self.assertRaises(TypeError, cf.set, "sect", "option1", 1) 455 self.assertRaises(TypeError, cf.set, "sect", "option1", 1.0) 456 self.assertRaises(TypeError, cf.set, "sect", "option1", object()) 457 self.assertRaises(TypeError, cf.set, "sect", "option2", 1) 458 self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0) 459 self.assertRaises(TypeError, cf.set, "sect", "option2", object()) 460 461 def test_add_section_default_1(self): 462 cf = self.newconfig() 463 self.assertRaises(ValueError, cf.add_section, "default") 464 465 def test_add_section_default_2(self): 466 cf = self.newconfig() 467 self.assertRaises(ValueError, cf.add_section, "DEFAULT") 468 469 470class SortedTestCase(RawConfigParserTestCase): 471 def newconfig(self, defaults=None): 472 self.cf = self.config_class(defaults=defaults, dict_type=SortedDict) 473 return self.cf 474 475 def test_sorted(self): 476 self.fromstring("[b]\n" 477 "o4=1\n" 478 "o3=2\n" 479 "o2=3\n" 480 "o1=4\n" 481 "[a]\n" 482 "k=v\n") 483 output = StringIO() 484 self.cf.write(output) 485 self.assertEquals(output.getvalue(), 486 "[a]\n" 487 "k = v\n\n" 488 "[b]\n" 489 "o1 = 4\n" 490 "o2 = 3\n" 491 "o3 = 2\n" 492 "o4 = 1\n\n") 493 494 495def test_main(): 496 test_support.run_unittest( 497 ConfigParserTestCase, 498 RawConfigParserTestCase, 499 SafeConfigParserTestCase, 500 SortedTestCase 501 ) 502 503 504class Suite(unittest.TestSuite): 505 def __init__(self): 506 unittest.TestSuite.__init__(self, [ 507 unittest.makeSuite(RawConfigParserTestCase, 'test'), 508 unittest.makeSuite(ConfigParserTestCase, 'test'), 509 unittest.makeSuite(SafeConfigParserTestCase, 'test'), 510 # unittest.makeSuite(SortedTestCase, 'test') 511 ]) 512 513 514if __name__ == "__main__": 515 test_main() 516