diff options
author | Georg Brandl <georg@python.org> | 2010-07-28 13:13:46 (GMT) |
---|---|---|
committer | Georg Brandl <georg@python.org> | 2010-07-28 13:13:46 (GMT) |
commit | 96a60ae90c291d94c058c80351fa38b6d73eda92 (patch) | |
tree | 68b74d560f5ff935e337ad620636624b203e00ac /Lib/test/test_cfgparser.py | |
parent | cbb0ae4a428bae3f5c29a1f8974121bb195299ef (diff) | |
download | cpython-96a60ae90c291d94c058c80351fa38b6d73eda92.zip cpython-96a60ae90c291d94c058c80351fa38b6d73eda92.tar.gz cpython-96a60ae90c291d94c058c80351fa38b6d73eda92.tar.bz2 |
#1682942: add some ConfigParser features: alternate delimiters, alternate comments, empty lines in values. Also enhance the docs with more examples and mention SafeConfigParser before ConfigParser. Patch by Lukas Langa, review by myself, Eric and Ezio.
Diffstat (limited to 'Lib/test/test_cfgparser.py')
-rw-r--r-- | Lib/test/test_cfgparser.py | 280 |
1 files changed, 185 insertions, 95 deletions
diff --git a/Lib/test/test_cfgparser.py b/Lib/test/test_cfgparser.py index c5a2595..e00a51a 100644 --- a/Lib/test/test_cfgparser.py +++ b/Lib/test/test_cfgparser.py @@ -3,6 +3,7 @@ import configparser import io import os import unittest +import textwrap from test import support @@ -23,15 +24,26 @@ class SortedDict(collections.UserDict): def itervalues(self): return iter(self.values()) -class TestCaseBase(unittest.TestCase): +class CfgParserTestCaseClass(unittest.TestCase): allow_no_value = False + delimiters = ('=', ':') + comment_prefixes = (';', '#') + empty_lines_in_values = True + dict_type = configparser._default_dict def newconfig(self, defaults=None): + arguments = dict( + allow_no_value=self.allow_no_value, + delimiters=self.delimiters, + comment_prefixes=self.comment_prefixes, + empty_lines_in_values=self.empty_lines_in_values, + dict_type=self.dict_type, + ) if defaults is None: - self.cf = self.config_class(allow_no_value=self.allow_no_value) + self.cf = self.config_class(**arguments) else: self.cf = self.config_class(defaults, - allow_no_value=self.allow_no_value) + **arguments) return self.cf def fromstring(self, string, defaults=None): @@ -40,27 +52,33 @@ class TestCaseBase(unittest.TestCase): cf.readfp(sio) return cf +class BasicTestCase(CfgParserTestCaseClass): + 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" - ) + config_string = """\ +[Foo Bar] +foo{0[0]}bar +[Spacey Bar] +foo {0[0]} bar +[Spacey Bar From The Beginning] + foo {0[0]} bar + baz {0[0]} qwe +[Commented Bar] +foo{0[1]} bar {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! +""".format(self.delimiters, self.comment_prefixes) if self.allow_no_value: config_string += ( "[NoValue]\n" @@ -70,13 +88,14 @@ class TestCaseBase(unittest.TestCase): 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', + E = ['Commented Bar', + 'Foo Bar', + 'Internationalized Stuff', + 'Long Line', + 'Section\\with$weird%characters[\t', + 'Spaces', + 'Spacey Bar', + 'Spacey Bar From The Beginning', ] if self.allow_no_value: E.append(r'NoValue') @@ -89,7 +108,10 @@ class TestCaseBase(unittest.TestCase): # http://www.python.org/sf/583248 eq(cf.get('Foo Bar', 'foo'), 'bar') eq(cf.get('Spacey Bar', 'foo'), 'bar') + eq(cf.get('Spacey Bar From The Beginning', 'foo'), 'bar') + eq(cf.get('Spacey Bar From The Beginning', 'baz'), 'qwe') eq(cf.get('Commented Bar', 'foo'), 'bar') + eq(cf.get('Commented Bar', 'baz'), 'qwe') eq(cf.get('Spaces', 'key with spaces'), 'value') eq(cf.get('Spaces', 'another with spaces'), 'splat!') if self.allow_no_value: @@ -140,12 +162,14 @@ class TestCaseBase(unittest.TestCase): # SF bug #432369: cf = self.fromstring( - "[MySection]\nOption: first line\n\tsecond line\n") + "[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]\nnekey=nevalue\n", + cf = self.fromstring("[section]\n" + "nekey{}nevalue\n".format(self.delimiters[0]), defaults={"key":"value"}) self.assertTrue(cf.has_option("section", "Key")) @@ -162,18 +186,19 @@ class TestCaseBase(unittest.TestCase): def test_parse_errors(self): self.newconfig() - e = self.parse_error(configparser.ParsingError, - "[Foo]\n extra-spaces: splat\n") - self.assertEqual(e.args, ('<???>',)) - self.parse_error(configparser.ParsingError, - "[Foo]\n extra-spaces= splat\n") self.parse_error(configparser.ParsingError, - "[Foo]\n:value-without-option-name\n") + "[Foo]\n" + "{}val-without-opt-name\n".format(self.delimiters[0])) self.parse_error(configparser.ParsingError, - "[Foo]\n=value-without-option-name\n") + "[Foo]\n" + "{}val-without-opt-name\n".format(self.delimiters[1])) e = self.parse_error(configparser.MissingSectionHeaderError, "No Section!\n") self.assertEqual(e.args, ('<???>', 1, "No Section!\n")) + if not self.allow_no_value: + e = self.parse_error(configparser.ParsingError, + "[Foo]\n wrong-indent\n") + self.assertEqual(e.args, ('<???>',)) def parse_error(self, exc, src): sio = io.StringIO(src) @@ -188,9 +213,9 @@ class TestCaseBase(unittest.TestCase): self.assertFalse(cf.has_section("Foo"), "new ConfigParser should have no acknowledged " "sections") - with self.assertRaises(configparser.NoSectionError) as cm: + with self.assertRaises(configparser.NoSectionError): cf.options("Foo") - with self.assertRaises(configparser.NoSectionError) as cm: + with self.assertRaises(configparser.NoSectionError): cf.set("foo", "bar", "value") e = self.get_error(configparser.NoSectionError, "foo", "bar") self.assertEqual(e.args, ("foo",)) @@ -210,21 +235,21 @@ class TestCaseBase(unittest.TestCase): 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" + "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)) @@ -242,11 +267,17 @@ class TestCaseBase(unittest.TestCase): def test_write(self): config_string = ( "[Long Line]\n" - "foo: this line is much, much longer than my editor\n" + "foo{0[0]} this line is much, much longer than my editor\n" " likes it.\n" "[DEFAULT]\n" - "foo: another very\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]) ) if self.allow_no_value: config_string += ( @@ -259,13 +290,19 @@ class TestCaseBase(unittest.TestCase): cf.write(output) expect_string = ( "[DEFAULT]\n" - "foo = another very\n" + "foo {equals} another very\n" "\tlong line\n" "\n" "[Long Line]\n" - "foo = this line is much, much longer than my editor\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=self.delimiters[0]) ) if self.allow_no_value: expect_string += ( @@ -277,7 +314,7 @@ class TestCaseBase(unittest.TestCase): def test_set_string_types(self): cf = self.fromstring("[sect]\n" - "option1=foo\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): @@ -290,6 +327,9 @@ class TestCaseBase(unittest.TestCase): 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() @@ -314,45 +354,45 @@ class TestCaseBase(unittest.TestCase): 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" + "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=%(bar)s\n" - "bar=%(foo)s\n" + "foo{equals}%(bar)s\n" + "bar{equals}%(foo)s\n" "\n" "[Interpolation Error]\n" - "name=%(reference)s\n", + "name{equals}%(reference)s\n".format(equals=self.delimiters[0]), # 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|", + "name {0[0]} value\n" + "key{0[1]} |%(name)s| \n" + "getdefault{0[1]} |%(default)s|\n" + "getname{0[1]} |%(__name__)s|".format(self.delimiters), defaults={"default": "<default>"}) L = list(cf.items("section")) L.sort() self.assertEqual(L, expected) -class ConfigParserTestCase(TestCaseBase): +class ConfigParserTestCase(BasicTestCase): config_class = configparser.ConfigParser def test_interpolation(self): @@ -414,7 +454,11 @@ class ConfigParserTestCase(TestCaseBase): self.assertRaises(ValueError, cf.get, 'non-string', 'string_with_interpolation', raw=False) -class MultilineValuesTestCase(TestCaseBase): +class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase): + delimiters = (':=', '$') + comment_prefixes = ('//', '"') + +class MultilineValuesTestCase(BasicTestCase): config_class = configparser.ConfigParser wonderful_spam = ("I'm having spam spam spam spam " "spam spam spam beaked beans spam " @@ -442,7 +486,7 @@ class MultilineValuesTestCase(TestCaseBase): self.assertEqual(cf_from_file.get('section8', 'lovely_spam4'), self.wonderful_spam.replace('\t\n', '\n')) -class RawConfigParserTestCase(TestCaseBase): +class RawConfigParserTestCase(BasicTestCase): config_class = configparser.RawConfigParser def test_interpolation(self): @@ -476,6 +520,28 @@ class RawConfigParserTestCase(TestCaseBase): [0, 1, 1, 2, 3, 5, 8, 13]) self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159}) +class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): + delimiters = (':=', '$') + comment_prefixes = ('//', '"') + +class RawConfigParserTestSambaConf(BasicTestCase): + config_class = configparser.RawConfigParser + 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"]) + 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 SafeConfigParserTestCase(ConfigParserTestCase): config_class = configparser.SafeConfigParser @@ -483,16 +549,17 @@ class SafeConfigParserTestCase(ConfigParserTestCase): 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") + "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") self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s") def test_set_malformatted_interpolation(self): cf = self.fromstring("[sect]\n" - "option1=foo\n") + "option1{eq}foo\n".format(eq=self.delimiters[0])) self.assertEqual(cf.get('sect', "option1"), "foo") @@ -508,7 +575,7 @@ class SafeConfigParserTestCase(ConfigParserTestCase): def test_set_nonstring_types(self): cf = self.fromstring("[sect]\n" - "option1=foo\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) @@ -526,15 +593,16 @@ class SafeConfigParserTestCase(ConfigParserTestCase): cf = self.newconfig() self.assertRaises(ValueError, cf.add_section, "DEFAULT") +class SafeConfigParserTestCaseNonStandardDelimiters(SafeConfigParserTestCase): + delimiters = (':=', '$') + comment_prefixes = ('//', '"') class SafeConfigParserTestCaseNoValue(SafeConfigParserTestCase): allow_no_value = True class SortedTestCase(RawConfigParserTestCase): - def newconfig(self, defaults=None): - self.cf = self.config_class(defaults=defaults, dict_type=SortedDict) - return self.cf + dict_type = SortedDict def test_sorted(self): self.fromstring("[b]\n" @@ -556,14 +624,36 @@ class SortedTestCase(RawConfigParserTestCase): "o4 = 1\n\n") +class CompatibleTestCase(CfgParserTestCaseClass): + config_class = configparser.RawConfigParser + comment_prefixes = configparser.RawConfigParser._COMPATIBLE + + 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 + """) + cf = self.fromstring(config_string) + self.assertEqual(cf.get('Commented Bar', 'foo'), 'bar # not a comment!') + self.assertEqual(cf.get('Commented Bar', 'baz'), 'qwe') + + def test_main(): support.run_unittest( ConfigParserTestCase, + ConfigParserTestCaseNonStandardDelimiters, MultilineValuesTestCase, RawConfigParserTestCase, + RawConfigParserTestCaseNonStandardDelimiters, + RawConfigParserTestSambaConf, SafeConfigParserTestCase, + SafeConfigParserTestCaseNonStandardDelimiters, SafeConfigParserTestCaseNoValue, SortedTestCase, + CompatibleTestCase, ) |