diff options
Diffstat (limited to 'Lib/test/test_configparser.py')
-rw-r--r-- | Lib/test/test_configparser.py | 1342 |
1 files changed, 1342 insertions, 0 deletions
diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py new file mode 100644 index 0000000..f7d9a26 --- /dev/null +++ b/Lib/test/test_configparser.py @@ -0,0 +1,1342 @@ +import collections +import configparser +import io +import os +import sys +import textwrap +import unittest +import warnings + +from test import support + +class SortedDict(collections.UserDict): + + def items(self): + return sorted(self.data.items()) + + def keys(self): + return sorted(self.data.keys()) + + def values(self): + return [i[1] for i in self.items()] + + def iteritems(self): return iter(self.items()) + def iterkeys(self): return iter(self.keys()) + __iter__ = iterkeys + def itervalues(self): return iter(self.values()) + + +class CfgParserTestCaseClass(unittest.TestCase): + allow_no_value = False + delimiters = ('=', ':') + comment_prefixes = (';', '#') + inline_comment_prefixes = (';', '#') + empty_lines_in_values = True + dict_type = configparser._default_dict + strict = False + default_section = configparser.DEFAULTSECT + interpolation = configparser._UNSET + + def newconfig(self, defaults=None): + arguments = dict( + defaults=defaults, + allow_no_value=self.allow_no_value, + delimiters=self.delimiters, + comment_prefixes=self.comment_prefixes, + inline_comment_prefixes=self.inline_comment_prefixes, + empty_lines_in_values=self.empty_lines_in_values, + dict_type=self.dict_type, + strict=self.strict, + default_section=self.default_section, + interpolation=self.interpolation, + ) + instance = self.config_class(**arguments) + return instance + + def fromstring(self, string, defaults=None): + cf = self.newconfig(defaults) + cf.read_string(string) + return cf + +class BasicTestCase(CfgParserTestCaseClass): + + def basic_test(self, cf): + E = ['Commented Bar', + 'Foo Bar', + 'Internationalized Stuff', + 'Long Line', + 'Section\\with$weird%characters[\t', + 'Spaces', + 'Spacey Bar', + 'Spacey Bar From The Beginning', + 'Types', + ] + + if self.allow_no_value: + E.append('NoValue') + E.sort() + F = [('baz', 'qwe'), ('foo', 'bar3')] + + # API access + L = cf.sections() + L.sort() + eq = self.assertEqual + eq(L, E) + L = cf.items('Spacey Bar From The Beginning') + L.sort() + eq(L, F) + + # mapping access + L = [section for section in cf] + L.sort() + E.append(self.default_section) + E.sort() + eq(L, E) + L = cf['Spacey Bar From The Beginning'].items() + L = sorted(list(L)) + eq(L, F) + L = cf.items() + L = sorted(list(L)) + self.assertEqual(len(L), len(E)) + for name, section in L: + eq(name, section.name) + eq(cf.defaults(), cf[self.default_section]) + + # The use of spaces in the section names serves as a + # regression test for SourceForge bug #583248: + # http://www.python.org/sf/583248 + + # API access + eq(cf.get('Foo Bar', 'foo'), 'bar1') + eq(cf.get('Spacey Bar', 'foo'), 'bar2') + eq(cf.get('Spacey Bar From The Beginning', 'foo'), 'bar3') + eq(cf.get('Spacey Bar From The Beginning', 'baz'), 'qwe') + eq(cf.get('Commented Bar', 'foo'), 'bar4') + eq(cf.get('Commented Bar', 'baz'), 'qwe') + eq(cf.get('Spaces', 'key with spaces'), 'value') + eq(cf.get('Spaces', 'another with spaces'), 'splat!') + eq(cf.getint('Types', 'int'), 42) + eq(cf.get('Types', 'int'), "42") + self.assertAlmostEqual(cf.getfloat('Types', 'float'), 0.44) + eq(cf.get('Types', 'float'), "0.44") + eq(cf.getboolean('Types', 'boolean'), False) + eq(cf.get('Types', '123'), 'strange but acceptable') + if self.allow_no_value: + eq(cf.get('NoValue', 'option-without-value'), None) + + # test vars= and fallback= + eq(cf.get('Foo Bar', 'foo', fallback='baz'), 'bar1') + eq(cf.get('Foo Bar', 'foo', vars={'foo': 'baz'}), 'baz') + with self.assertRaises(configparser.NoSectionError): + cf.get('No Such Foo Bar', 'foo') + with self.assertRaises(configparser.NoOptionError): + cf.get('Foo Bar', 'no-such-foo') + eq(cf.get('No Such Foo Bar', 'foo', fallback='baz'), 'baz') + eq(cf.get('Foo Bar', 'no-such-foo', fallback='baz'), 'baz') + eq(cf.get('Spacey Bar', 'foo', fallback=None), 'bar2') + eq(cf.get('No Such Spacey Bar', 'foo', fallback=None), None) + eq(cf.getint('Types', 'int', fallback=18), 42) + eq(cf.getint('Types', 'no-such-int', fallback=18), 18) + eq(cf.getint('Types', 'no-such-int', fallback="18"), "18") # sic! + with self.assertRaises(configparser.NoOptionError): + cf.getint('Types', 'no-such-int') + self.assertAlmostEqual(cf.getfloat('Types', 'float', + fallback=0.0), 0.44) + self.assertAlmostEqual(cf.getfloat('Types', 'no-such-float', + fallback=0.0), 0.0) + eq(cf.getfloat('Types', 'no-such-float', fallback="0.0"), "0.0") # sic! + with self.assertRaises(configparser.NoOptionError): + cf.getfloat('Types', 'no-such-float') + eq(cf.getboolean('Types', 'boolean', fallback=True), False) + eq(cf.getboolean('Types', 'no-such-boolean', fallback="yes"), + "yes") # sic! + eq(cf.getboolean('Types', 'no-such-boolean', fallback=True), True) + with self.assertRaises(configparser.NoOptionError): + cf.getboolean('Types', 'no-such-boolean') + eq(cf.getboolean('No Such Types', 'boolean', fallback=True), True) + if self.allow_no_value: + eq(cf.get('NoValue', 'option-without-value', fallback=False), None) + eq(cf.get('NoValue', 'no-such-option-without-value', + fallback=False), False) + + # mapping access + eq(cf['Foo Bar']['foo'], 'bar1') + eq(cf['Spacey Bar']['foo'], 'bar2') + section = cf['Spacey Bar From The Beginning'] + eq(section.name, 'Spacey Bar From The Beginning') + self.assertIs(section.parser, cf) + with self.assertRaises(AttributeError): + section.name = 'Name is read-only' + with self.assertRaises(AttributeError): + section.parser = 'Parser is read-only' + eq(section['foo'], 'bar3') + eq(section['baz'], 'qwe') + eq(cf['Commented Bar']['foo'], 'bar4') + eq(cf['Commented Bar']['baz'], 'qwe') + eq(cf['Spaces']['key with spaces'], 'value') + eq(cf['Spaces']['another with spaces'], 'splat!') + eq(cf['Long Line']['foo'], + 'this line is much, much longer than my editor\nlikes it.') + if self.allow_no_value: + eq(cf['NoValue']['option-without-value'], None) + # test vars= and fallback= + eq(cf['Foo Bar'].get('foo', 'baz'), 'bar1') + eq(cf['Foo Bar'].get('foo', fallback='baz'), 'bar1') + eq(cf['Foo Bar'].get('foo', vars={'foo': 'baz'}), 'baz') + with self.assertRaises(KeyError): + cf['No Such Foo Bar']['foo'] + with self.assertRaises(KeyError): + cf['Foo Bar']['no-such-foo'] + with self.assertRaises(KeyError): + cf['No Such Foo Bar'].get('foo', fallback='baz') + eq(cf['Foo Bar'].get('no-such-foo', 'baz'), 'baz') + eq(cf['Foo Bar'].get('no-such-foo', fallback='baz'), 'baz') + eq(cf['Foo Bar'].get('no-such-foo'), None) + eq(cf['Spacey Bar'].get('foo', None), 'bar2') + eq(cf['Spacey Bar'].get('foo', fallback=None), 'bar2') + with self.assertRaises(KeyError): + cf['No Such Spacey Bar'].get('foo', None) + eq(cf['Types'].getint('int', 18), 42) + eq(cf['Types'].getint('int', fallback=18), 42) + eq(cf['Types'].getint('no-such-int', 18), 18) + eq(cf['Types'].getint('no-such-int', fallback=18), 18) + eq(cf['Types'].getint('no-such-int', "18"), "18") # sic! + eq(cf['Types'].getint('no-such-int', fallback="18"), "18") # sic! + eq(cf['Types'].getint('no-such-int'), None) + self.assertAlmostEqual(cf['Types'].getfloat('float', 0.0), 0.44) + self.assertAlmostEqual(cf['Types'].getfloat('float', + fallback=0.0), 0.44) + self.assertAlmostEqual(cf['Types'].getfloat('no-such-float', 0.0), 0.0) + self.assertAlmostEqual(cf['Types'].getfloat('no-such-float', + fallback=0.0), 0.0) + eq(cf['Types'].getfloat('no-such-float', "0.0"), "0.0") # sic! + eq(cf['Types'].getfloat('no-such-float', fallback="0.0"), "0.0") # sic! + eq(cf['Types'].getfloat('no-such-float'), None) + eq(cf['Types'].getboolean('boolean', True), False) + eq(cf['Types'].getboolean('boolean', fallback=True), False) + eq(cf['Types'].getboolean('no-such-boolean', "yes"), "yes") # sic! + eq(cf['Types'].getboolean('no-such-boolean', fallback="yes"), + "yes") # sic! + eq(cf['Types'].getboolean('no-such-boolean', True), True) + eq(cf['Types'].getboolean('no-such-boolean', fallback=True), True) + eq(cf['Types'].getboolean('no-such-boolean'), None) + if self.allow_no_value: + eq(cf['NoValue'].get('option-without-value', False), None) + eq(cf['NoValue'].get('option-without-value', fallback=False), None) + eq(cf['NoValue'].get('no-such-option-without-value', False), False) + eq(cf['NoValue'].get('no-such-option-without-value', + fallback=False), False) + + # Make sure the right things happen for remove_section() and + # remove_option(); added to include check for SourceForge bug #123324. + + cf[self.default_section]['this_value'] = '1' + cf[self.default_section]['that_value'] = '2' + + # API access + self.assertTrue(cf.remove_section('Spaces')) + self.assertFalse(cf.has_option('Spaces', 'key with spaces')) + self.assertFalse(cf.remove_section('Spaces')) + self.assertFalse(cf.remove_section(self.default_section)) + 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.assertTrue(cf.has_option('Foo Bar', 'this_value')) + self.assertFalse(cf.remove_option('Foo Bar', 'this_value')) + self.assertTrue(cf.remove_option(self.default_section, 'this_value')) + self.assertFalse(cf.has_option('Foo Bar', 'this_value')) + self.assertFalse(cf.remove_option(self.default_section, 'this_value')) + + with self.assertRaises(configparser.NoSectionError) as cm: + cf.remove_option('No Such Section', 'foo') + self.assertEqual(cm.exception.args, ('No Such Section',)) + + eq(cf.get('Long Line', 'foo'), + 'this line is much, much longer than my editor\nlikes it.') + + # mapping access + del cf['Types'] + self.assertFalse('Types' in cf) + with self.assertRaises(KeyError): + del cf['Types'] + with self.assertRaises(ValueError): + del cf[self.default_section] + del cf['Spacey Bar']['foo'] + self.assertFalse('foo' in cf['Spacey Bar']) + with self.assertRaises(KeyError): + del cf['Spacey Bar']['foo'] + self.assertTrue('that_value' in cf['Spacey Bar']) + with self.assertRaises(KeyError): + del cf['Spacey Bar']['that_value'] + del cf[self.default_section]['that_value'] + self.assertFalse('that_value' in cf['Spacey Bar']) + with self.assertRaises(KeyError): + del cf[self.default_section]['that_value'] + with self.assertRaises(KeyError): + del cf['No Such Section']['foo'] + + # Don't add new asserts below in this method as most of the options + # and sections are now removed. + + def test_basic(self): + config_string = """\ +[Foo Bar] +foo{0[0]}bar1 +[Spacey Bar] +foo {0[0]} bar2 +[Spacey Bar From The Beginning] + foo {0[0]} bar3 + baz {0[0]} qwe +[Commented Bar] +foo{0[1]} bar4 {1[1]} comment +baz{0[0]}qwe {1[0]}another one +[Long Line] +foo{0[1]} this line is much, much longer than my editor + likes it. +[Section\\with$weird%characters[\t] +[Internationalized Stuff] +foo[bg]{0[1]} Bulgarian +foo{0[0]}Default +foo[en]{0[0]}English +foo[de]{0[0]}Deutsch +[Spaces] +key with spaces {0[1]} value +another with spaces {0[0]} splat! +[Types] +int {0[1]} 42 +float {0[0]} 0.44 +boolean {0[0]} NO +123 {0[1]} strange but acceptable +""".format(self.delimiters, self.comment_prefixes) + if self.allow_no_value: + config_string += ( + "[NoValue]\n" + "option-without-value\n" + ) + cf = self.fromstring(config_string) + self.basic_test(cf) + if self.strict: + with self.assertRaises(configparser.DuplicateOptionError): + cf.read_string(textwrap.dedent("""\ + [Duplicate Options Here] + option {0[0]} with a value + option {0[1]} with another value + """.format(self.delimiters))) + with self.assertRaises(configparser.DuplicateSectionError): + cf.read_string(textwrap.dedent("""\ + [And Now For Something] + completely different {0[0]} True + [And Now For Something] + the larch {0[1]} 1 + """.format(self.delimiters))) + else: + cf.read_string(textwrap.dedent("""\ + [Duplicate Options Here] + option {0[0]} with a value + option {0[1]} with another value + """.format(self.delimiters))) + + cf.read_string(textwrap.dedent("""\ + [And Now For Something] + completely different {0[0]} True + [And Now For Something] + the larch {0[1]} 1 + """.format(self.delimiters))) + + def test_basic_from_dict(self): + config = { + "Foo Bar": { + "foo": "bar1", + }, + "Spacey Bar": { + "foo": "bar2", + }, + "Spacey Bar From The Beginning": { + "foo": "bar3", + "baz": "qwe", + }, + "Commented Bar": { + "foo": "bar4", + "baz": "qwe", + }, + "Long Line": { + "foo": "this line is much, much longer than my editor\nlikes " + "it.", + }, + "Section\\with$weird%characters[\t": { + }, + "Internationalized Stuff": { + "foo[bg]": "Bulgarian", + "foo": "Default", + "foo[en]": "English", + "foo[de]": "Deutsch", + }, + "Spaces": { + "key with spaces": "value", + "another with spaces": "splat!", + }, + "Types": { + "int": 42, + "float": 0.44, + "boolean": False, + 123: "strange but acceptable", + }, + } + if self.allow_no_value: + config.update({ + "NoValue": { + "option-without-value": None, + } + }) + cf = self.newconfig() + cf.read_dict(config) + self.basic_test(cf) + if self.strict: + with self.assertRaises(configparser.DuplicateSectionError): + cf.read_dict({ + '1': {'key': 'value'}, + 1: {'key2': 'value2'}, + }) + with self.assertRaises(configparser.DuplicateOptionError): + cf.read_dict({ + "Duplicate Options Here": { + 'option': 'with a value', + 'OPTION': 'with another value', + }, + }) + else: + cf.read_dict({ + 'section': {'key': 'value'}, + 'SECTION': {'key2': 'value2'}, + }) + cf.read_dict({ + "Duplicate Options Here": { + 'option': 'with a value', + 'OPTION': 'with another value', + }, + }) + + def test_case_sensitivity(self): + cf = self.newconfig() + cf.add_section("A") + cf.add_section("a") + cf.add_section("B") + L = cf.sections() + L.sort() + eq = self.assertEqual + eq(L, ["A", "B", "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") + with self.assertRaises(configparser.NoSectionError): + # section names are case-sensitive + cf.set("b", "A", "value") + self.assertTrue(cf.has_option("a", "b")) + self.assertFalse(cf.has_option("b", "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".format( + self.delimiters[0])) + eq(cf.options("MySection"), ["option"]) + eq(cf.get("MySection", "Option"), "first line\nsecond line") + + # SF bug #561822: + cf = self.fromstring("[section]\n" + "nekey{}nevalue\n".format(self.delimiters[0]), + defaults={"key":"value"}) + self.assertTrue(cf.has_option("section", "Key")) + + + def test_case_sensitivity_mapping_access(self): + cf = self.newconfig() + cf["A"] = {} + cf["a"] = {"B": "value"} + cf["B"] = {} + L = [section for section in cf] + L.sort() + eq = self.assertEqual + elem_eq = self.assertCountEqual + eq(L, sorted(["A", "B", self.default_section, "a"])) + eq(cf["a"].keys(), {"b"}) + eq(cf["a"]["b"], "value", + "could not locate option, expecting case-insensitive option names") + with self.assertRaises(KeyError): + # section names are case-sensitive + cf["b"]["A"] = "value" + self.assertTrue("b" in cf["a"]) + cf["A"]["A-B"] = "A-B value" + for opt in ("a-b", "A-b", "a-B", "A-B"): + self.assertTrue( + opt in cf["A"], + "has_option() returned false for option which should exist") + eq(cf["A"].keys(), {"a-b"}) + eq(cf["a"].keys(), {"b"}) + del cf["a"]["B"] + elem_eq(cf["a"].keys(), {}) + + # SF bug #432369: + cf = self.fromstring( + "[MySection]\nOption{} first line \n\tsecond line \n".format( + self.delimiters[0])) + eq(cf["MySection"].keys(), {"option"}) + eq(cf["MySection"]["Option"], "first line\nsecond line") + + # SF bug #561822: + cf = self.fromstring("[section]\n" + "nekey{}nevalue\n".format(self.delimiters[0]), + defaults={"key":"value"}) + self.assertTrue("Key" in cf["section"]) + + def test_default_case_sensitivity(self): + cf = self.newconfig({"foo": "Bar"}) + self.assertEqual( + cf.get(self.default_section, "Foo"), "Bar", + "could not locate option, expecting case-insensitive option names") + cf = self.newconfig({"Foo": "Bar"}) + self.assertEqual( + cf.get(self.default_section, "Foo"), "Bar", + "could not locate option, expecting case-insensitive defaults") + + def test_parse_errors(self): + cf = self.newconfig() + self.parse_error(cf, configparser.ParsingError, + "[Foo]\n" + "{}val-without-opt-name\n".format(self.delimiters[0])) + self.parse_error(cf, configparser.ParsingError, + "[Foo]\n" + "{}val-without-opt-name\n".format(self.delimiters[1])) + e = self.parse_error(cf, configparser.MissingSectionHeaderError, + "No Section!\n") + self.assertEqual(e.args, ('<???>', 1, "No Section!\n")) + if not self.allow_no_value: + e = self.parse_error(cf, configparser.ParsingError, + "[Foo]\n wrong-indent\n") + self.assertEqual(e.args, ('<???>',)) + # read_file on a real file + tricky = support.findfile("cfgparser.3") + if self.delimiters[0] == '=': + error = configparser.ParsingError + expected = (tricky,) + else: + error = configparser.MissingSectionHeaderError + expected = (tricky, 1, + ' # INI with as many tricky parts as possible\n') + with open(tricky, encoding='utf-8') as f: + e = self.parse_error(cf, error, f) + self.assertEqual(e.args, expected) + + def parse_error(self, cf, exc, src): + if hasattr(src, 'readline'): + sio = src + else: + sio = io.StringIO(src) + with self.assertRaises(exc) as cm: + cf.read_file(sio) + return cm.exception + + 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") + with self.assertRaises(configparser.NoSectionError): + cf.options("Foo") + with self.assertRaises(configparser.NoSectionError): + cf.set("foo", "bar", "value") + e = self.get_error(cf, configparser.NoSectionError, "foo", "bar") + self.assertEqual(e.args, ("foo",)) + cf.add_section("foo") + e = self.get_error(cf, configparser.NoOptionError, "foo", "bar") + self.assertEqual(e.args, ("bar", "foo")) + + def get_error(self, cf, exc, section, option): + try: + cf.get(section, option) + except exc as 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{equals}1\n" + "T2{equals}TRUE\n" + "T3{equals}True\n" + "T4{equals}oN\n" + "T5{equals}yes\n" + "F1{equals}0\n" + "F2{equals}FALSE\n" + "F3{equals}False\n" + "F4{equals}oFF\n" + "F5{equals}nO\n" + "E1{equals}2\n" + "E2{equals}foo\n" + "E3{equals}-1\n" + "E4{equals}0.1\n" + "E5{equals}FALSE AND MORE".format(equals=self.delimiters[0]) + ) + 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") + with self.assertRaises(configparser.DuplicateSectionError) as cm: + cf.add_section("Foo") + e = cm.exception + self.assertEqual(str(e), "Section 'Foo' already exists") + self.assertEqual(e.args, ("Foo", None, None)) + + if self.strict: + with self.assertRaises(configparser.DuplicateSectionError) as cm: + cf.read_string(textwrap.dedent("""\ + [Foo] + will this be added{equals}True + [Bar] + what about this{equals}True + [Foo] + oops{equals}this won't + """.format(equals=self.delimiters[0])), source='<foo-bar>') + e = cm.exception + self.assertEqual(str(e), "While reading from <foo-bar> [line 5]: " + "section 'Foo' already exists") + self.assertEqual(e.args, ("Foo", '<foo-bar>', 5)) + + with self.assertRaises(configparser.DuplicateOptionError) as cm: + cf.read_dict({'Bar': {'opt': 'val', 'OPT': 'is really `opt`'}}) + e = cm.exception + self.assertEqual(str(e), "While reading from <dict>: option 'opt' " + "in section 'Bar' already exists") + self.assertEqual(e.args, ("Bar", "opt", "<dict>", None)) + + def test_write(self): + config_string = ( + "[Long Line]\n" + "foo{0[0]} this line is much, much longer than my editor\n" + " likes it.\n" + "[{default_section}]\n" + "foo{0[1]} another very\n" + " long line\n" + "[Long Line - With Comments!]\n" + "test {0[1]} we {comment} can\n" + " also {comment} place\n" + " comments {comment} in\n" + " multiline {comment} values" + "\n".format(self.delimiters, comment=self.comment_prefixes[0], + default_section=self.default_section) + ) + if self.allow_no_value: + config_string += ( + "[Valueless]\n" + "option-without-value\n" + ) + + cf = self.fromstring(config_string) + for space_around_delimiters in (True, False): + output = io.StringIO() + cf.write(output, space_around_delimiters=space_around_delimiters) + delimiter = self.delimiters[0] + if space_around_delimiters: + delimiter = " {} ".format(delimiter) + expect_string = ( + "[{default_section}]\n" + "foo{equals}another very\n" + "\tlong line\n" + "\n" + "[Long Line]\n" + "foo{equals}this line is much, much longer than my editor\n" + "\tlikes it.\n" + "\n" + "[Long Line - With Comments!]\n" + "test{equals}we\n" + "\talso\n" + "\tcomments\n" + "\tmultiline\n" + "\n".format(equals=delimiter, + default_section=self.default_section) + ) + 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{eq}foo\n".format(eq=self.delimiters[0])) + # 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")) + cf.set("sect", "option1", "splat") + cf.set("sect", "option2", "splat") + + def test_read_returns_file_list(self): + if self.delimiters[0] != '=': + # skip reading the file if we're using an incompatible format + return + file1 = 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{equals}something %(with1)s interpolation (1 step)\n" + "bar9{equals}something %(with9)s lots of interpolation (9 steps)\n" + "bar10{equals}something %(with10)s lots of interpolation (10 steps)\n" + "bar11{equals}something %(with11)s lots of interpolation (11 steps)\n" + "with11{equals}%(with10)s\n" + "with10{equals}%(with9)s\n" + "with9{equals}%(with8)s\n" + "with8{equals}%(With7)s\n" + "with7{equals}%(WITH6)s\n" + "with6{equals}%(with5)s\n" + "With5{equals}%(with4)s\n" + "WITH4{equals}%(with3)s\n" + "with3{equals}%(with2)s\n" + "with2{equals}%(with1)s\n" + "with1{equals}with\n" + "\n" + "[Mutual Recursion]\n" + "foo{equals}%(bar)s\n" + "bar{equals}%(foo)s\n" + "\n" + "[Interpolation Error]\n" + # no definition for 'reference' + "name{equals}%(reference)s\n".format(equals=self.delimiters[0])) + + def check_items_config(self, expected): + cf = self.fromstring(""" + [section] + name {0[0]} %(value)s + key{0[1]} |%(name)s| + getdefault{0[1]} |%(default)s| + """.format(self.delimiters), defaults={"default": "<default>"}) + L = list(cf.items("section", vars={'value': 'value'})) + L.sort() + self.assertEqual(L, expected) + with self.assertRaises(configparser.NoSectionError): + cf.items("no such section") + + +class StrictTestCase(BasicTestCase): + config_class = configparser.RawConfigParser + strict = True + + +class ConfigParserTestCase(BasicTestCase): + config_class = configparser.ConfigParser + + def test_interpolation(self): + cf = self.get_interpolation_config() + eq = self.assertEqual + 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)") + e = self.get_error(cf, configparser.InterpolationDepthError, "Foo", "bar11") + if self.interpolation == configparser._UNSET: + self.assertEqual(e.args, ("bar11", "Foo", "%(with1)s")) + elif isinstance(self.interpolation, configparser.LegacyInterpolation): + self.assertEqual(e.args, ("bar11", "Foo", + "something %(with11)s lots of interpolation (11 steps)")) + + def test_interpolation_missing_value(self): + cf = self.get_interpolation_config() + e = self.get_error(cf, configparser.InterpolationMissingOptionError, + "Interpolation Error", "name") + self.assertEqual(e.reference, "reference") + self.assertEqual(e.section, "Interpolation Error") + self.assertEqual(e.option, "name") + if self.interpolation == configparser._UNSET: + self.assertEqual(e.args, ('name', 'Interpolation Error', + '', 'reference')) + elif isinstance(self.interpolation, configparser.LegacyInterpolation): + self.assertEqual(e.args, ('name', 'Interpolation Error', + '%(reference)s', 'reference')) + + def test_items(self): + self.check_items_config([('default', '<default>'), + ('getdefault', '|<default>|'), + ('key', '|value|'), + ('name', 'value'), + ('value', 'value')]) + + def test_safe_interpolation(self): + # See http://www.python.org/sf/511737 + cf = self.fromstring("[section]\n" + "option1{eq}xxx\n" + "option2{eq}%(option1)s/xxx\n" + "ok{eq}%(option1)s/%%s\n" + "not_ok{eq}%(option2)s/%%s".format( + eq=self.delimiters[0])) + self.assertEqual(cf.get("section", "ok"), "xxx/%s") + if self.interpolation == configparser._UNSET: + self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s") + elif isinstance(self.interpolation, configparser.LegacyInterpolation): + with self.assertRaises(TypeError): + cf.get("section", "not_ok") + + def test_set_malformatted_interpolation(self): + cf = self.fromstring("[sect]\n" + "option1{eq}foo\n".format(eq=self.delimiters[0])) + + 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{eq}foo\n".format(eq=self.delimiters[0])) + # 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()) + self.assertRaises(TypeError, cf.set, "sect", 123, "invalid opt name!") + self.assertRaises(TypeError, cf.add_section, 123) + + def test_add_section_default(self): + cf = self.newconfig() + self.assertRaises(ValueError, cf.add_section, self.default_section) + +class ConfigParserTestCaseLegacyInterpolation(ConfigParserTestCase): + config_class = configparser.ConfigParser + interpolation = configparser.LegacyInterpolation() + + def test_set_malformatted_interpolation(self): + cf = self.fromstring("[sect]\n" + "option1{eq}foo\n".format(eq=self.delimiters[0])) + + self.assertEqual(cf.get('sect', "option1"), "foo") + + cf.set("sect", "option1", "%foo") + self.assertEqual(cf.get('sect', "option1"), "%foo") + cf.set("sect", "option1", "foo%") + self.assertEqual(cf.get('sect', "option1"), "foo%") + cf.set("sect", "option1", "f%oo") + self.assertEqual(cf.get('sect', "option1"), "f%oo") + + # bug #5741: double percents are *not* malformed + cf.set("sect", "option2", "foo%%bar") + self.assertEqual(cf.get("sect", "option2"), "foo%%bar") + +class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase): + delimiters = (':=', '$') + comment_prefixes = ('//', '"') + inline_comment_prefixes = ('//', '"') + +class ConfigParserTestCaseNonStandardDefaultSection(ConfigParserTestCase): + default_section = 'general' + +class MultilineValuesTestCase(BasicTestCase): + 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(support.TESTFN, 'w') as f: + cf.write(f) + + def tearDown(self): + os.unlink(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(support.TESTFN) as f: + cf_from_file.read_file(f) + self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'), + self.wonderful_spam.replace('\t\n', '\n')) + +class RawConfigParserTestCase(BasicTestCase): + config_class = configparser.RawConfigParser + + def test_interpolation(self): + cf = self.get_interpolation_config() + eq = self.assertEqual + 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|'), + ('key', '|%(name)s|'), + ('name', '%(value)s'), + ('value', '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}) + cf.add_section(123) + cf.set(123, 'this is sick', True) + self.assertEqual(cf.get(123, 'this is sick'), True) + if cf._dict.__class__ is configparser._default_dict: + # would not work for SortedDict; only checking for the most common + # default dictionary (OrderedDict) + cf.optionxform = lambda x: x + cf.set('non-string', 1, 1) + self.assertEqual(cf.get('non-string', 1), 1) + +class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): + delimiters = (':=', '$') + comment_prefixes = ('//', '"') + inline_comment_prefixes = ('//', '"') + +class RawConfigParserTestSambaConf(CfgParserTestCaseClass): + config_class = configparser.RawConfigParser + comment_prefixes = ('#', ';', '----') + inline_comment_prefixes = ('//',) + empty_lines_in_values = False + + def test_reading(self): + smbconf = support.findfile("cfgparser.2") + # check when we pass a mix of readable and non-readable files: + cf = self.newconfig() + parsed_files = cf.read([smbconf, "nonexistent-file"], encoding='utf-8') + self.assertEqual(parsed_files, [smbconf]) + sections = ['global', 'homes', 'printers', + 'print$', 'pdf-generator', 'tmp', 'Agustin'] + self.assertEqual(cf.sections(), sections) + self.assertEqual(cf.get("global", "workgroup"), "MDKGROUP") + self.assertEqual(cf.getint("global", "max log size"), 50) + self.assertEqual(cf.get("global", "hosts allow"), "127.") + self.assertEqual(cf.get("tmp", "echo command"), "cat %s; rm %s") + +class ConfigParserTestCaseExtendedInterpolation(BasicTestCase): + config_class = configparser.ConfigParser + interpolation = configparser.ExtendedInterpolation() + default_section = 'common' + + def test_extended_interpolation(self): + cf = self.fromstring(textwrap.dedent(""" + [common] + favourite Beatle = Paul + favourite color = green + + [tom] + favourite band = ${favourite color} day + favourite pope = John ${favourite Beatle} II + sequel = ${favourite pope}I + + [ambv] + favourite Beatle = George + son of Edward VII = ${favourite Beatle} V + son of George V = ${son of Edward VII}I + + [stanley] + favourite Beatle = ${ambv:favourite Beatle} + favourite pope = ${tom:favourite pope} + favourite color = black + favourite state of mind = paranoid + favourite movie = soylent ${common:favourite color} + favourite song = ${favourite color} sabbath - ${favourite state of mind} + """).strip()) + + eq = self.assertEqual + eq(cf['common']['favourite Beatle'], 'Paul') + eq(cf['common']['favourite color'], 'green') + eq(cf['tom']['favourite Beatle'], 'Paul') + eq(cf['tom']['favourite color'], 'green') + eq(cf['tom']['favourite band'], 'green day') + eq(cf['tom']['favourite pope'], 'John Paul II') + eq(cf['tom']['sequel'], 'John Paul III') + eq(cf['ambv']['favourite Beatle'], 'George') + eq(cf['ambv']['favourite color'], 'green') + eq(cf['ambv']['son of Edward VII'], 'George V') + eq(cf['ambv']['son of George V'], 'George VI') + eq(cf['stanley']['favourite Beatle'], 'George') + eq(cf['stanley']['favourite color'], 'black') + eq(cf['stanley']['favourite state of mind'], 'paranoid') + eq(cf['stanley']['favourite movie'], 'soylent green') + eq(cf['stanley']['favourite pope'], 'John Paul II') + eq(cf['stanley']['favourite song'], + 'black sabbath - paranoid') + + def test_endless_loop(self): + cf = self.fromstring(textwrap.dedent(""" + [one for you] + ping = ${one for me:pong} + + [one for me] + pong = ${one for you:ping} + + [selfish] + me = ${me} + """).strip()) + + with self.assertRaises(configparser.InterpolationDepthError): + cf['one for you']['ping'] + with self.assertRaises(configparser.InterpolationDepthError): + cf['selfish']['me'] + + def test_strange_options(self): + cf = self.fromstring(""" + [dollars] + $var = $$value + $var2 = ${$var} + ${sick} = cannot interpolate me + + [interpolated] + $other = ${dollars:$var} + $trying = ${dollars:${sick}} + """) + + self.assertEqual(cf['dollars']['$var'], '$value') + self.assertEqual(cf['interpolated']['$other'], '$value') + self.assertEqual(cf['dollars']['${sick}'], 'cannot interpolate me') + exception_class = configparser.InterpolationMissingOptionError + with self.assertRaises(exception_class) as cm: + cf['interpolated']['$trying'] + self.assertEqual(cm.exception.reference, 'dollars:${sick') + self.assertEqual(cm.exception.args[2], '}') #rawval + + + def test_other_errors(self): + cf = self.fromstring(""" + [interpolation fail] + case1 = ${where's the brace + case2 = ${does_not_exist} + case3 = ${wrong_section:wrong_value} + case4 = ${i:like:colon:characters} + case5 = $100 for Fail No 5! + """) + + with self.assertRaises(configparser.InterpolationSyntaxError): + cf['interpolation fail']['case1'] + with self.assertRaises(configparser.InterpolationMissingOptionError): + cf['interpolation fail']['case2'] + with self.assertRaises(configparser.InterpolationMissingOptionError): + cf['interpolation fail']['case3'] + with self.assertRaises(configparser.InterpolationSyntaxError): + cf['interpolation fail']['case4'] + with self.assertRaises(configparser.InterpolationSyntaxError): + cf['interpolation fail']['case5'] + with self.assertRaises(ValueError): + cf['interpolation fail']['case6'] = "BLACK $ABBATH" + + +class ConfigParserTestCaseNoValue(ConfigParserTestCase): + allow_no_value = True + +class ConfigParserTestCaseTrickyFile(CfgParserTestCaseClass): + config_class = configparser.ConfigParser + delimiters = {'='} + comment_prefixes = {'#'} + allow_no_value = True + + def test_cfgparser_dot_3(self): + tricky = support.findfile("cfgparser.3") + cf = self.newconfig() + self.assertEqual(len(cf.read(tricky, encoding='utf-8')), 1) + self.assertEqual(cf.sections(), ['strange', + 'corruption', + 'yeah, sections can be ' + 'indented as well', + 'another one!', + 'no values here', + 'tricky interpolation', + 'more interpolation']) + self.assertEqual(cf.getint(self.default_section, 'go', + vars={'interpolate': '-1'}), -1) + with self.assertRaises(ValueError): + # no interpolation will happen + cf.getint(self.default_section, 'go', raw=True, + vars={'interpolate': '-1'}) + self.assertEqual(len(cf.get('strange', 'other').split('\n')), 4) + self.assertEqual(len(cf.get('corruption', 'value').split('\n')), 10) + longname = 'yeah, sections can be indented as well' + self.assertFalse(cf.getboolean(longname, 'are they subsections')) + self.assertEqual(cf.get(longname, 'lets use some Unicode'), '片仮名') + self.assertEqual(len(cf.items('another one!')), 5) # 4 in section and + # `go` from DEFAULT + with self.assertRaises(configparser.InterpolationMissingOptionError): + cf.items('no values here') + self.assertEqual(cf.get('tricky interpolation', 'lets'), 'do this') + self.assertEqual(cf.get('tricky interpolation', 'lets'), + cf.get('tricky interpolation', 'go')) + self.assertEqual(cf.get('more interpolation', 'lets'), 'go shopping') + + def test_unicode_failure(self): + tricky = support.findfile("cfgparser.3") + cf = self.newconfig() + with self.assertRaises(UnicodeDecodeError): + cf.read(tricky, encoding='ascii') + + +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 = io.StringIO() + cp.write(sio) + return sio.getvalue() + + def test_none_as_value_stringified(self): + cp = configparser.ConfigParser(allow_no_value=False) + cp.add_section("section") + with self.assertRaises(TypeError): + cp.set("section", "option", None) + + def test_none_as_value_stringified_raw(self): + output = self.prepare(configparser.RawConfigParser) + self.assertEqual(output, self.expected_output) + + +class SortedTestCase(RawConfigParserTestCase): + dict_type = SortedDict + + def test_sorted(self): + cf = self.fromstring("[b]\n" + "o4=1\n" + "o3=2\n" + "o2=3\n" + "o1=4\n" + "[a]\n" + "k=v\n") + output = io.StringIO() + 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") + + +class CompatibleTestCase(CfgParserTestCaseClass): + config_class = configparser.RawConfigParser + comment_prefixes = '#;' + inline_comment_prefixes = ';' + + def test_comment_handling(self): + config_string = textwrap.dedent("""\ + [Commented Bar] + baz=qwe ; a comment + foo: bar # not a comment! + # but this is a comment + ; another comment + quirk: this;is not a comment + ; a space must precede an inline comment + """) + cf = self.fromstring(config_string) + self.assertEqual(cf.get('Commented Bar', 'foo'), + 'bar # not a comment!') + self.assertEqual(cf.get('Commented Bar', 'baz'), 'qwe') + self.assertEqual(cf.get('Commented Bar', 'quirk'), + 'this;is not a comment') + +class CopyTestCase(BasicTestCase): + config_class = configparser.ConfigParser + + def fromstring(self, string, defaults=None): + cf = self.newconfig(defaults) + cf.read_string(string) + cf_copy = self.newconfig() + cf_copy.read_dict(cf) + # we have to clean up option duplicates that appeared because of + # the magic DEFAULTSECT behaviour. + for section in cf_copy.values(): + if section.name == self.default_section: + continue + for default, value in cf[self.default_section].items(): + if section[default] == value: + del section[default] + return cf_copy + +class CoverageOneHundredTestCase(unittest.TestCase): + """Covers edge cases in the codebase.""" + + def test_duplicate_option_error(self): + error = configparser.DuplicateOptionError('section', 'option') + self.assertEqual(error.section, 'section') + self.assertEqual(error.option, 'option') + self.assertEqual(error.source, None) + self.assertEqual(error.lineno, None) + self.assertEqual(error.args, ('section', 'option', None, None)) + self.assertEqual(str(error), "Option 'option' in section 'section' " + "already exists") + + def test_interpolation_depth_error(self): + error = configparser.InterpolationDepthError('option', 'section', + 'rawval') + self.assertEqual(error.args, ('option', 'section', 'rawval')) + self.assertEqual(error.option, 'option') + self.assertEqual(error.section, 'section') + + def test_parsing_error(self): + with self.assertRaises(ValueError) as cm: + configparser.ParsingError() + self.assertEqual(str(cm.exception), "Required argument `source' not " + "given.") + with self.assertRaises(ValueError) as cm: + configparser.ParsingError(source='source', filename='filename') + self.assertEqual(str(cm.exception), "Cannot specify both `filename' " + "and `source'. Use `source'.") + error = configparser.ParsingError(filename='source') + self.assertEqual(error.source, 'source') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", DeprecationWarning) + self.assertEqual(error.filename, 'source') + error.filename = 'filename' + self.assertEqual(error.source, 'filename') + for warning in w: + self.assertTrue(warning.category is DeprecationWarning) + + def test_interpolation_validation(self): + parser = configparser.ConfigParser() + parser.read_string(""" + [section] + invalid_percent = % + invalid_reference = %(() + invalid_variable = %(does_not_exist)s + """) + with self.assertRaises(configparser.InterpolationSyntaxError) as cm: + parser['section']['invalid_percent'] + self.assertEqual(str(cm.exception), "'%' must be followed by '%' or " + "'(', found: '%'") + with self.assertRaises(configparser.InterpolationSyntaxError) as cm: + parser['section']['invalid_reference'] + self.assertEqual(str(cm.exception), "bad interpolation variable " + "reference '%(()'") + + def test_readfp_deprecation(self): + sio = io.StringIO(""" + [section] + option = value + """) + parser = configparser.ConfigParser() + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", DeprecationWarning) + parser.readfp(sio, filename='StringIO') + for warning in w: + self.assertTrue(warning.category is DeprecationWarning) + self.assertEqual(len(parser), 2) + self.assertEqual(parser['section']['option'], 'value') + + def test_safeconfigparser_deprecation(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", DeprecationWarning) + parser = configparser.SafeConfigParser() + for warning in w: + self.assertTrue(warning.category is DeprecationWarning) + + def test_sectionproxy_repr(self): + parser = configparser.ConfigParser() + parser.read_string(""" + [section] + key = value + """) + self.assertEqual(repr(parser['section']), '<Section: section>') + +def test_main(): + support.run_unittest( + ConfigParserTestCase, + ConfigParserTestCaseNonStandardDelimiters, + ConfigParserTestCaseNoValue, + ConfigParserTestCaseExtendedInterpolation, + ConfigParserTestCaseLegacyInterpolation, + ConfigParserTestCaseTrickyFile, + MultilineValuesTestCase, + RawConfigParserTestCase, + RawConfigParserTestCaseNonStandardDelimiters, + RawConfigParserTestSambaConf, + SortedTestCase, + Issue7005TestCase, + StrictTestCase, + CompatibleTestCase, + CopyTestCase, + ConfigParserTestCaseNonStandardDefaultSection, + CoverageOneHundredTestCase, + ) |