| import ConfigParser | |
| import StringIO | |
| import os | |
| import unittest | |
| import UserDict | |
| from test import test_support | |
| class SortedDict(UserDict.UserDict): | |
| def items(self): | |
| result = self.data.items() | |
| result.sort() | |
| return result | |
| def keys(self): | |
| result = self.data.keys() | |
| result.sort() | |
| return result | |
| def values(self): | |
| # XXX never used? | |
| result = self.items() | |
| return [i[1] for i in result] | |
| def iteritems(self): return iter(self.items()) | |
| def iterkeys(self): return iter(self.keys()) | |
| __iter__ = iterkeys | |
| def itervalues(self): return iter(self.values()) | |
| class TestCaseBase(unittest.TestCase): | |
| allow_no_value = False | |
| def newconfig(self, defaults=None): | |
| if defaults is None: | |
| self.cf = self.config_class(allow_no_value=self.allow_no_value) | |
| else: | |
| self.cf = self.config_class(defaults, | |
| allow_no_value=self.allow_no_value) | |
| return self.cf | |
| def fromstring(self, string, defaults=None): | |
| cf = self.newconfig(defaults) | |
| sio = StringIO.StringIO(string) | |
| cf.readfp(sio) | |
| return cf | |
| def test_basic(self): | |
| config_string = ( | |
| "[Foo Bar]\n" | |
| "foo=bar\n" | |
| "[Spacey Bar]\n" | |
| "foo = bar\n" | |
| "[Commented Bar]\n" | |
| "foo: bar ; comment\n" | |
| "[Long Line]\n" | |
| "foo: this line is much, much longer than my editor\n" | |
| " likes it.\n" | |
| "[Section\\with$weird%characters[\t]\n" | |
| "[Internationalized Stuff]\n" | |
| "foo[bg]: Bulgarian\n" | |
| "foo=Default\n" | |
| "foo[en]=English\n" | |
| "foo[de]=Deutsch\n" | |
| "[Spaces]\n" | |
| "key with spaces : value\n" | |
| "another with spaces = splat!\n" | |
| ) | |
| if self.allow_no_value: | |
| config_string += ( | |
| "[NoValue]\n" | |
| "option-without-value\n" | |
| ) | |
| cf = self.fromstring(config_string) | |
| L = cf.sections() | |
| L.sort() | |
| E = [r'Commented Bar', | |
| r'Foo Bar', | |
| r'Internationalized Stuff', | |
| r'Long Line', | |
| r'Section\with$weird%characters[' '\t', | |
| r'Spaces', | |
| r'Spacey Bar', | |
| ] | |
| if self.allow_no_value: | |
| E.append(r'NoValue') | |
| E.sort() | |
| eq = self.assertEqual | |
| eq(L, E) | |
| # The use of spaces in the section names serves as a | |
| # regression test for SourceForge bug #583248: | |
| # http://www.python.org/sf/583248 | |
| eq(cf.get('Foo Bar', 'foo'), 'bar') | |
| eq(cf.get('Spacey Bar', 'foo'), 'bar') | |
| eq(cf.get('Commented Bar', 'foo'), 'bar') | |
| eq(cf.get('Spaces', 'key with spaces'), 'value') | |
| eq(cf.get('Spaces', 'another with spaces'), 'splat!') | |
| if self.allow_no_value: | |
| eq(cf.get('NoValue', 'option-without-value'), None) | |
| self.assertNotIn('__name__', cf.options("Foo Bar"), | |
| '__name__ "option" should not be exposed by the API!') | |
| # Make sure the right things happen for remove_option(); | |
| # added to include check for SourceForge bug #123324: | |
| self.assertTrue(cf.remove_option('Foo Bar', 'foo'), | |
| "remove_option() failed to report existence of option") | |
| self.assertFalse(cf.has_option('Foo Bar', 'foo'), | |
| "remove_option() failed to remove option") | |
| self.assertFalse(cf.remove_option('Foo Bar', 'foo'), | |
| "remove_option() failed to report non-existence of option" | |
| " that was removed") | |
| self.assertRaises(ConfigParser.NoSectionError, | |
| cf.remove_option, 'No Such Section', 'foo') | |
| eq(cf.get('Long Line', 'foo'), | |
| 'this line is much, much longer than my editor\nlikes it.') | |
| def test_case_sensitivity(self): | |
| cf = self.newconfig() | |
| cf.add_section("A") | |
| cf.add_section("a") | |
| L = cf.sections() | |
| L.sort() | |
| eq = self.assertEqual | |
| eq(L, ["A", "a"]) | |
| cf.set("a", "B", "value") | |
| eq(cf.options("a"), ["b"]) | |
| eq(cf.get("a", "b"), "value", | |
| "could not locate option, expecting case-insensitive option names") | |
| self.assertTrue(cf.has_option("a", "b")) | |
| cf.set("A", "A-B", "A-B value") | |
| for opt in ("a-b", "A-b", "a-B", "A-B"): | |
| self.assertTrue( | |
| cf.has_option("A", opt), | |
| "has_option() returned false for option which should exist") | |
| eq(cf.options("A"), ["a-b"]) | |
| eq(cf.options("a"), ["b"]) | |
| cf.remove_option("a", "B") | |
| eq(cf.options("a"), []) | |
| # SF bug #432369: | |
| cf = self.fromstring( | |
| "[MySection]\nOption: first line\n\tsecond line\n") | |
| eq(cf.options("MySection"), ["option"]) | |
| eq(cf.get("MySection", "Option"), "first line\nsecond line") | |
| # SF bug #561822: | |
| cf = self.fromstring("[section]\nnekey=nevalue\n", | |
| defaults={"key":"value"}) | |
| self.assertTrue(cf.has_option("section", "Key")) | |
| def test_default_case_sensitivity(self): | |
| cf = self.newconfig({"foo": "Bar"}) | |
| self.assertEqual( | |
| cf.get("DEFAULT", "Foo"), "Bar", | |
| "could not locate option, expecting case-insensitive option names") | |
| cf = self.newconfig({"Foo": "Bar"}) | |
| self.assertEqual( | |
| cf.get("DEFAULT", "Foo"), "Bar", | |
| "could not locate option, expecting case-insensitive defaults") | |
| def test_parse_errors(self): | |
| self.newconfig() | |
| self.parse_error(ConfigParser.ParsingError, | |
| "[Foo]\n extra-spaces: splat\n") | |
| self.parse_error(ConfigParser.ParsingError, | |
| "[Foo]\n extra-spaces= splat\n") | |
| self.parse_error(ConfigParser.ParsingError, | |
| "[Foo]\n:value-without-option-name\n") | |
| self.parse_error(ConfigParser.ParsingError, | |
| "[Foo]\n=value-without-option-name\n") | |
| self.parse_error(ConfigParser.MissingSectionHeaderError, | |
| "No Section!\n") | |
| def parse_error(self, exc, src): | |
| sio = StringIO.StringIO(src) | |
| self.assertRaises(exc, self.cf.readfp, sio) | |
| def test_query_errors(self): | |
| cf = self.newconfig() | |
| self.assertEqual(cf.sections(), [], | |
| "new ConfigParser should have no defined sections") | |
| self.assertFalse(cf.has_section("Foo"), | |
| "new ConfigParser should have no acknowledged " | |
| "sections") | |
| self.assertRaises(ConfigParser.NoSectionError, | |
| cf.options, "Foo") | |
| self.assertRaises(ConfigParser.NoSectionError, | |
| cf.set, "foo", "bar", "value") | |
| self.get_error(ConfigParser.NoSectionError, "foo", "bar") | |
| cf.add_section("foo") | |
| self.get_error(ConfigParser.NoOptionError, "foo", "bar") | |
| def get_error(self, exc, section, option): | |
| try: | |
| self.cf.get(section, option) | |
| except exc, e: | |
| return e | |
| else: | |
| self.fail("expected exception type %s.%s" | |
| % (exc.__module__, exc.__name__)) | |
| def test_boolean(self): | |
| cf = self.fromstring( | |
| "[BOOLTEST]\n" | |
| "T1=1\n" | |
| "T2=TRUE\n" | |
| "T3=True\n" | |
| "T4=oN\n" | |
| "T5=yes\n" | |
| "F1=0\n" | |
| "F2=FALSE\n" | |
| "F3=False\n" | |
| "F4=oFF\n" | |
| "F5=nO\n" | |
| "E1=2\n" | |
| "E2=foo\n" | |
| "E3=-1\n" | |
| "E4=0.1\n" | |
| "E5=FALSE AND MORE" | |
| ) | |
| for x in range(1, 5): | |
| self.assertTrue(cf.getboolean('BOOLTEST', 't%d' % x)) | |
| self.assertFalse(cf.getboolean('BOOLTEST', 'f%d' % x)) | |
| self.assertRaises(ValueError, | |
| cf.getboolean, 'BOOLTEST', 'e%d' % x) | |
| def test_weird_errors(self): | |
| cf = self.newconfig() | |
| cf.add_section("Foo") | |
| self.assertRaises(ConfigParser.DuplicateSectionError, | |
| cf.add_section, "Foo") | |
| def test_write(self): | |
| config_string = ( | |
| "[Long Line]\n" | |
| "foo: this line is much, much longer than my editor\n" | |
| " likes it.\n" | |
| "[DEFAULT]\n" | |
| "foo: another very\n" | |
| " long line\n" | |
| ) | |
| if self.allow_no_value: | |
| config_string += ( | |
| "[Valueless]\n" | |
| "option-without-value\n" | |
| ) | |
| cf = self.fromstring(config_string) | |
| output = StringIO.StringIO() | |
| cf.write(output) | |
| expect_string = ( | |
| "[DEFAULT]\n" | |
| "foo = another very\n" | |
| "\tlong line\n" | |
| "\n" | |
| "[Long Line]\n" | |
| "foo = this line is much, much longer than my editor\n" | |
| "\tlikes it.\n" | |
| "\n" | |
| ) | |
| if self.allow_no_value: | |
| expect_string += ( | |
| "[Valueless]\n" | |
| "option-without-value\n" | |
| "\n" | |
| ) | |
| self.assertEqual(output.getvalue(), expect_string) | |
| def test_set_string_types(self): | |
| cf = self.fromstring("[sect]\n" | |
| "option1=foo\n") | |
| # Check that we don't get an exception when setting values in | |
| # an existing section using strings: | |
| class mystr(str): | |
| pass | |
| cf.set("sect", "option1", "splat") | |
| cf.set("sect", "option1", mystr("splat")) | |
| cf.set("sect", "option2", "splat") | |
| cf.set("sect", "option2", mystr("splat")) | |
| try: | |
| unicode | |
| except NameError: | |
| pass | |
| else: | |
| cf.set("sect", "option1", unicode("splat")) | |
| cf.set("sect", "option2", unicode("splat")) | |
| def test_read_returns_file_list(self): | |
| file1 = test_support.findfile("cfgparser.1") | |
| # check when we pass a mix of readable and non-readable files: | |
| cf = self.newconfig() | |
| parsed_files = cf.read([file1, "nonexistent-file"]) | |
| self.assertEqual(parsed_files, [file1]) | |
| self.assertEqual(cf.get("Foo Bar", "foo"), "newbar") | |
| # check when we pass only a filename: | |
| cf = self.newconfig() | |
| parsed_files = cf.read(file1) | |
| self.assertEqual(parsed_files, [file1]) | |
| self.assertEqual(cf.get("Foo Bar", "foo"), "newbar") | |
| # check when we pass only missing files: | |
| cf = self.newconfig() | |
| parsed_files = cf.read(["nonexistent-file"]) | |
| self.assertEqual(parsed_files, []) | |
| # check when we pass no files: | |
| cf = self.newconfig() | |
| parsed_files = cf.read([]) | |
| self.assertEqual(parsed_files, []) | |
| # shared by subclasses | |
| def get_interpolation_config(self): | |
| return self.fromstring( | |
| "[Foo]\n" | |
| "bar=something %(with1)s interpolation (1 step)\n" | |
| "bar9=something %(with9)s lots of interpolation (9 steps)\n" | |
| "bar10=something %(with10)s lots of interpolation (10 steps)\n" | |
| "bar11=something %(with11)s lots of interpolation (11 steps)\n" | |
| "with11=%(with10)s\n" | |
| "with10=%(with9)s\n" | |
| "with9=%(with8)s\n" | |
| "with8=%(With7)s\n" | |
| "with7=%(WITH6)s\n" | |
| "with6=%(with5)s\n" | |
| "With5=%(with4)s\n" | |
| "WITH4=%(with3)s\n" | |
| "with3=%(with2)s\n" | |
| "with2=%(with1)s\n" | |
| "with1=with\n" | |
| "\n" | |
| "[Mutual Recursion]\n" | |
| "foo=%(bar)s\n" | |
| "bar=%(foo)s\n" | |
| "\n" | |
| "[Interpolation Error]\n" | |
| "name=%(reference)s\n", | |
| # no definition for 'reference' | |
| defaults={"getname": "%(__name__)s"}) | |
| def check_items_config(self, expected): | |
| cf = self.fromstring( | |
| "[section]\n" | |
| "name = value\n" | |
| "key: |%(name)s| \n" | |
| "getdefault: |%(default)s|\n" | |
| "getname: |%(__name__)s|", | |
| defaults={"default": "<default>"}) | |
| L = list(cf.items("section")) | |
| L.sort() | |
| self.assertEqual(L, expected) | |
| class ConfigParserTestCase(TestCaseBase): | |
| config_class = ConfigParser.ConfigParser | |
| allow_no_value = True | |
| def test_interpolation(self): | |
| rawval = { | |
| ConfigParser.ConfigParser: ("something %(with11)s " | |
| "lots of interpolation (11 steps)"), | |
| ConfigParser.SafeConfigParser: "%(with1)s", | |
| } | |
| cf = self.get_interpolation_config() | |
| eq = self.assertEqual | |
| eq(cf.get("Foo", "getname"), "Foo") | |
| eq(cf.get("Foo", "bar"), "something with interpolation (1 step)") | |
| eq(cf.get("Foo", "bar9"), | |
| "something with lots of interpolation (9 steps)") | |
| eq(cf.get("Foo", "bar10"), | |
| "something with lots of interpolation (10 steps)") | |
| self.get_error(ConfigParser.InterpolationDepthError, "Foo", "bar11") | |
| def test_interpolation_missing_value(self): | |
| self.get_interpolation_config() | |
| e = self.get_error(ConfigParser.InterpolationError, | |
| "Interpolation Error", "name") | |
| self.assertEqual(e.reference, "reference") | |
| self.assertEqual(e.section, "Interpolation Error") | |
| self.assertEqual(e.option, "name") | |
| def test_items(self): | |
| self.check_items_config([('default', '<default>'), | |
| ('getdefault', '|<default>|'), | |
| ('getname', '|section|'), | |
| ('key', '|value|'), | |
| ('name', 'value')]) | |
| def test_set_nonstring_types(self): | |
| cf = self.newconfig() | |
| cf.add_section('non-string') | |
| cf.set('non-string', 'int', 1) | |
| cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13, '%(']) | |
| cf.set('non-string', 'dict', {'pi': 3.14159, '%(': 1, | |
| '%(list)': '%(list)'}) | |
| cf.set('non-string', 'string_with_interpolation', '%(list)s') | |
| cf.set('non-string', 'no-value') | |
| self.assertEqual(cf.get('non-string', 'int', raw=True), 1) | |
| self.assertRaises(TypeError, cf.get, 'non-string', 'int') | |
| self.assertEqual(cf.get('non-string', 'list', raw=True), | |
| [0, 1, 1, 2, 3, 5, 8, 13, '%(']) | |
| self.assertRaises(TypeError, cf.get, 'non-string', 'list') | |
| self.assertEqual(cf.get('non-string', 'dict', raw=True), | |
| {'pi': 3.14159, '%(': 1, '%(list)': '%(list)'}) | |
| self.assertRaises(TypeError, cf.get, 'non-string', 'dict') | |
| self.assertEqual(cf.get('non-string', 'string_with_interpolation', | |
| raw=True), '%(list)s') | |
| self.assertRaises(ValueError, cf.get, 'non-string', | |
| 'string_with_interpolation', raw=False) | |
| self.assertEqual(cf.get('non-string', 'no-value'), None) | |
| class MultilineValuesTestCase(TestCaseBase): | |
| config_class = ConfigParser.ConfigParser | |
| wonderful_spam = ("I'm having spam spam spam spam " | |
| "spam spam spam beaked beans spam " | |
| "spam spam and spam!").replace(' ', '\t\n') | |
| def setUp(self): | |
| cf = self.newconfig() | |
| for i in range(100): | |
| s = 'section{}'.format(i) | |
| cf.add_section(s) | |
| for j in range(10): | |
| cf.set(s, 'lovely_spam{}'.format(j), self.wonderful_spam) | |
| with open(test_support.TESTFN, 'w') as f: | |
| cf.write(f) | |
| def tearDown(self): | |
| os.unlink(test_support.TESTFN) | |
| def test_dominating_multiline_values(self): | |
| # we're reading from file because this is where the code changed | |
| # during performance updates in Python 3.2 | |
| cf_from_file = self.newconfig() | |
| with open(test_support.TESTFN) as f: | |
| cf_from_file.readfp(f) | |
| self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'), | |
| self.wonderful_spam.replace('\t\n', '\n')) | |
| class RawConfigParserTestCase(TestCaseBase): | |
| config_class = ConfigParser.RawConfigParser | |
| def test_interpolation(self): | |
| cf = self.get_interpolation_config() | |
| eq = self.assertEqual | |
| eq(cf.get("Foo", "getname"), "%(__name__)s") | |
| eq(cf.get("Foo", "bar"), | |
| "something %(with1)s interpolation (1 step)") | |
| eq(cf.get("Foo", "bar9"), | |
| "something %(with9)s lots of interpolation (9 steps)") | |
| eq(cf.get("Foo", "bar10"), | |
| "something %(with10)s lots of interpolation (10 steps)") | |
| eq(cf.get("Foo", "bar11"), | |
| "something %(with11)s lots of interpolation (11 steps)") | |
| def test_items(self): | |
| self.check_items_config([('default', '<default>'), | |
| ('getdefault', '|%(default)s|'), | |
| ('getname', '|%(__name__)s|'), | |
| ('key', '|%(name)s|'), | |
| ('name', 'value')]) | |
| def test_set_nonstring_types(self): | |
| cf = self.newconfig() | |
| cf.add_section('non-string') | |
| cf.set('non-string', 'int', 1) | |
| cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13]) | |
| cf.set('non-string', 'dict', {'pi': 3.14159}) | |
| self.assertEqual(cf.get('non-string', 'int'), 1) | |
| self.assertEqual(cf.get('non-string', 'list'), | |
| [0, 1, 1, 2, 3, 5, 8, 13]) | |
| self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159}) | |
| class SafeConfigParserTestCase(ConfigParserTestCase): | |
| config_class = ConfigParser.SafeConfigParser | |
| def test_safe_interpolation(self): | |
| # See http://www.python.org/sf/511737 | |
| cf = self.fromstring("[section]\n" | |
| "option1=xxx\n" | |
| "option2=%(option1)s/xxx\n" | |
| "ok=%(option1)s/%%s\n" | |
| "not_ok=%(option2)s/%%s") | |
| self.assertEqual(cf.get("section", "ok"), "xxx/%s") | |
| self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s") | |
| def test_set_malformatted_interpolation(self): | |
| cf = self.fromstring("[sect]\n" | |
| "option1=foo\n") | |
| self.assertEqual(cf.get('sect', "option1"), "foo") | |
| self.assertRaises(ValueError, cf.set, "sect", "option1", "%foo") | |
| self.assertRaises(ValueError, cf.set, "sect", "option1", "foo%") | |
| self.assertRaises(ValueError, cf.set, "sect", "option1", "f%oo") | |
| self.assertEqual(cf.get('sect', "option1"), "foo") | |
| # bug #5741: double percents are *not* malformed | |
| cf.set("sect", "option2", "foo%%bar") | |
| self.assertEqual(cf.get("sect", "option2"), "foo%bar") | |
| def test_set_nonstring_types(self): | |
| cf = self.fromstring("[sect]\n" | |
| "option1=foo\n") | |
| # Check that we get a TypeError when setting non-string values | |
| # in an existing section: | |
| self.assertRaises(TypeError, cf.set, "sect", "option1", 1) | |
| self.assertRaises(TypeError, cf.set, "sect", "option1", 1.0) | |
| self.assertRaises(TypeError, cf.set, "sect", "option1", object()) | |
| self.assertRaises(TypeError, cf.set, "sect", "option2", 1) | |
| self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0) | |
| self.assertRaises(TypeError, cf.set, "sect", "option2", object()) | |
| def test_add_section_default_1(self): | |
| cf = self.newconfig() | |
| self.assertRaises(ValueError, cf.add_section, "default") | |
| def test_add_section_default_2(self): | |
| cf = self.newconfig() | |
| self.assertRaises(ValueError, cf.add_section, "DEFAULT") | |
| class SafeConfigParserTestCaseNoValue(SafeConfigParserTestCase): | |
| allow_no_value = True | |
| class Issue7005TestCase(unittest.TestCase): | |
| """Test output when None is set() as a value and allow_no_value == False. | |
| http://bugs.python.org/issue7005 | |
| """ | |
| expected_output = "[section]\noption = None\n\n" | |
| def prepare(self, config_class): | |
| # This is the default, but that's the point. | |
| cp = config_class(allow_no_value=False) | |
| cp.add_section("section") | |
| cp.set("section", "option", None) | |
| sio = StringIO.StringIO() | |
| cp.write(sio) | |
| return sio.getvalue() | |
| def test_none_as_value_stringified(self): | |
| output = self.prepare(ConfigParser.ConfigParser) | |
| self.assertEqual(output, self.expected_output) | |
| def test_none_as_value_stringified_raw(self): | |
| output = self.prepare(ConfigParser.RawConfigParser) | |
| self.assertEqual(output, self.expected_output) | |
| class SortedTestCase(RawConfigParserTestCase): | |
| def newconfig(self, defaults=None): | |
| self.cf = self.config_class(defaults=defaults, dict_type=SortedDict) | |
| return self.cf | |
| def test_sorted(self): | |
| self.fromstring("[b]\n" | |
| "o4=1\n" | |
| "o3=2\n" | |
| "o2=3\n" | |
| "o1=4\n" | |
| "[a]\n" | |
| "k=v\n") | |
| output = StringIO.StringIO() | |
| self.cf.write(output) | |
| self.assertEqual(output.getvalue(), | |
| "[a]\n" | |
| "k = v\n\n" | |
| "[b]\n" | |
| "o1 = 4\n" | |
| "o2 = 3\n" | |
| "o3 = 2\n" | |
| "o4 = 1\n\n") | |
| def test_main(): | |
| test_support.run_unittest( | |
| ConfigParserTestCase, | |
| MultilineValuesTestCase, | |
| RawConfigParserTestCase, | |
| SafeConfigParserTestCase, | |
| SafeConfigParserTestCaseNoValue, | |
| SortedTestCase, | |
| Issue7005TestCase, | |
| ) | |
| if __name__ == "__main__": | |
| test_main() |