From 16fe75e4e0bbc6eadb97c199272dd4b7616d8042 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Tue, 22 Feb 2011 01:48:33 +0000 Subject: Have the test filename match the module filename. --- Lib/test/test_cfgparser.py | 1342 ----------------------------------------- Lib/test/test_configparser.py | 1342 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1342 insertions(+), 1342 deletions(-) delete mode 100644 Lib/test/test_cfgparser.py create mode 100644 Lib/test/test_configparser.py diff --git a/Lib/test/test_cfgparser.py b/Lib/test/test_cfgparser.py deleted file mode 100644 index f7d9a26..0000000 --- a/Lib/test/test_cfgparser.py +++ /dev/null @@ -1,1342 +0,0 @@ -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='') - e = cm.exception - self.assertEqual(str(e), "While reading from [line 5]: " - "section 'Foo' already exists") - self.assertEqual(e.args, ("Foo", '', 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 : option 'opt' " - "in section 'Bar' already exists") - self.assertEqual(e.args, ("Bar", "opt", "", 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": ""}) - 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', ''), - ('getdefault', '||'), - ('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', ''), - ('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']), '') - -def test_main(): - support.run_unittest( - ConfigParserTestCase, - ConfigParserTestCaseNonStandardDelimiters, - ConfigParserTestCaseNoValue, - ConfigParserTestCaseExtendedInterpolation, - ConfigParserTestCaseLegacyInterpolation, - ConfigParserTestCaseTrickyFile, - MultilineValuesTestCase, - RawConfigParserTestCase, - RawConfigParserTestCaseNonStandardDelimiters, - RawConfigParserTestSambaConf, - SortedTestCase, - Issue7005TestCase, - StrictTestCase, - CompatibleTestCase, - CopyTestCase, - ConfigParserTestCaseNonStandardDefaultSection, - CoverageOneHundredTestCase, - ) 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='') + e = cm.exception + self.assertEqual(str(e), "While reading from [line 5]: " + "section 'Foo' already exists") + self.assertEqual(e.args, ("Foo", '', 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 : option 'opt' " + "in section 'Bar' already exists") + self.assertEqual(e.args, ("Bar", "opt", "", 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": ""}) + 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', ''), + ('getdefault', '||'), + ('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', ''), + ('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']), '') + +def test_main(): + support.run_unittest( + ConfigParserTestCase, + ConfigParserTestCaseNonStandardDelimiters, + ConfigParserTestCaseNoValue, + ConfigParserTestCaseExtendedInterpolation, + ConfigParserTestCaseLegacyInterpolation, + ConfigParserTestCaseTrickyFile, + MultilineValuesTestCase, + RawConfigParserTestCase, + RawConfigParserTestCaseNonStandardDelimiters, + RawConfigParserTestSambaConf, + SortedTestCase, + Issue7005TestCase, + StrictTestCase, + CompatibleTestCase, + CopyTestCase, + ConfigParserTestCaseNonStandardDefaultSection, + CoverageOneHundredTestCase, + ) -- cgit v0.12