| import collections | |
| import configparser | |
| import io | |
| import os | |
| import textwrap | |
| import unittest | |
| from test import support | |
| from test.support import os_helper | |
| 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()) | |
| def itervalues(self): | |
| return iter(self.values()) | |
| __iter__ = iterkeys | |
| class CfgParserTestCaseClass: | |
| 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, **kwargs): | |
| 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, | |
| ) | |
| arguments.update(kwargs) | |
| 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', | |
| 'This One Has A ] In It', | |
| ] | |
| 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: | |
| # https://bugs.python.org/issue583248 | |
| # 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') | |
| eq(cf.get('This One Has A ] In It', 'forks'), 'spoons') | |
| 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 | |
| [This One Has A ] In It] | |
| forks {0[0]} spoons | |
| """.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))) | |
| @support.subTests('data', [ | |
| 'foo bar=baz', | |
| 'foo bar=baz', | |
| 'foo=bar=baz', | |
| 'foo = bar=baz', | |
| 'foo\t \t=\t \tbar=baz', | |
| ]) | |
| def test_space_delimiter(self, data): | |
| # gh-156353: Space should be accepted as a delimiter | |
| cf = self.newconfig(delimiters=(' ', '=')) | |
| cf.read_string(f"[all]\n{data}") | |
| self.assertEqual(cf.options('all'), ['foo']) | |
| self.assertEqual(cf.get('all', 'foo'), 'bar=baz') | |
| @support.subTests('delimiter', ' =:;#x\t\0\N{RS}\N{CEDILLA}\N{CAT}') | |
| @support.subTests('space_before', ['', ' ', '\t', ' \t']) | |
| @support.subTests('space_after', ['', ' ', '\t', ' \t']) | |
| def test_any_delimiter(self, delimiter, space_before, space_after): | |
| cf = self.newconfig( | |
| delimiters=(delimiter,), | |
| inline_comment_prefixes=None, | |
| ) | |
| cf.read_string(f"[all]\nfoo{space_before}{delimiter}{space_after}bar=baz") | |
| self.assertEqual(cf.options('all'), ['foo']) | |
| self.assertEqual(cf.get('all', 'foo'), 'bar=baz') | |
| 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", | |
| }, | |
| "This One Has A ] In It": { | |
| "forks": "spoons" | |
| }, | |
| } | |
| 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_crlf_normalization(self): | |
| cf = self.newconfig({"key1": "a\nb","key2": "a\rb", "key3": "a\r\nb", "key4": "a\r\nb"}) | |
| buf = io.StringIO() | |
| cf.write(buf) | |
| cf_str = buf.getvalue() | |
| self.assertNotIn("\r", cf_str) | |
| self.assertNotIn("\r\n", cf_str) | |
| self.assertEqual(cf_str.count("\n"), 10) | |
| self.assertEqual(cf_str.count("\n\t"), 4) | |
| self.assertTrue(cf_str.endswith("\n\n")) | |
| 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", subdir="configdata") | |
| 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.__qualname__)) | |
| def test_boolean(self): | |
| cf = self.fromstring( | |
| "[BOOLTEST]\n" | |
| "T1{equals}1\n" | |
| "T2{equals}TRUE\n" | |
| "T3{equals}True\n" | |
| "T4{equals}oN\n" | |
| "T5{equals}yes\n" | |
| "F1{equals}0\n" | |
| "F2{equals}FALSE\n" | |
| "F3{equals}False\n" | |
| "F4{equals}oFF\n" | |
| "F5{equals}nO\n" | |
| "E1{equals}2\n" | |
| "E2{equals}foo\n" | |
| "E3{equals}-1\n" | |
| "E4{equals}0.1\n" | |
| "E5{equals}FALSE AND MORE".format(equals=self.delimiters[0]) | |
| ) | |
| for x in range(1, 5): | |
| self.assertTrue(cf.getboolean('BOOLTEST', 't%d' % x)) | |
| self.assertFalse(cf.getboolean('BOOLTEST', 'f%d' % x)) | |
| self.assertRaises(ValueError, | |
| cf.getboolean, 'BOOLTEST', 'e%d' % x) | |
| def test_weird_errors(self): | |
| cf = self.newconfig() | |
| cf.add_section("Foo") | |
| with self.assertRaises(configparser.DuplicateSectionError) as cm: | |
| cf.add_section("Foo") | |
| e = cm.exception | |
| self.assertEqual(str(e), "Section 'Foo' already exists") | |
| self.assertEqual(e.args, ("Foo", None, None)) | |
| if self.strict: | |
| with self.assertRaises(configparser.DuplicateSectionError) as cm: | |
| cf.read_string(textwrap.dedent("""\ | |
| [Foo] | |
| will this be added{equals}True | |
| [Bar] | |
| what about this{equals}True | |
| [Foo] | |
| oops{equals}this won't | |
| """.format(equals=self.delimiters[0])), source='<foo-bar>') | |
| e = cm.exception | |
| self.assertEqual(str(e), "While reading from '<foo-bar>' " | |
| "[line 5]: section 'Foo' already exists") | |
| self.assertEqual(e.args, ("Foo", '<foo-bar>', 5)) | |
| with self.assertRaises(configparser.DuplicateOptionError) as cm: | |
| cf.read_dict({'Bar': {'opt': 'val', 'OPT': 'is really `opt`'}}) | |
| e = cm.exception | |
| self.assertEqual(str(e), "While reading from '<dict>': option " | |
| "'opt' in section 'Bar' already exists") | |
| self.assertEqual(e.args, ("Bar", "opt", "<dict>", None)) | |
| def test_get_after_duplicate_option_error(self): | |
| cf = self.newconfig() | |
| ini = textwrap.dedent("""\ | |
| [Foo] | |
| x{equals}1 | |
| y{equals}2 | |
| y{equals}3 | |
| """.format(equals=self.delimiters[0])) | |
| if self.strict: | |
| with self.assertRaises(configparser.DuplicateOptionError): | |
| cf.read_string(ini) | |
| else: | |
| cf.read_string(ini) | |
| self.assertEqual(cf.get('Foo', 'x'), '1') | |
| 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] != '=': | |
| self.skipTest('incompatible format') | |
| file1 = support.findfile("cfgparser.1", subdir="configdata") | |
| # check when we pass a mix of readable and non-readable files: | |
| cf = self.newconfig() | |
| parsed_files = cf.read([file1, "nonexistent-file"], encoding="utf-8") | |
| 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, encoding="utf-8") | |
| self.assertEqual(parsed_files, [file1]) | |
| self.assertEqual(cf.get("Foo Bar", "foo"), "newbar") | |
| # check when we pass only a Path object: | |
| cf = self.newconfig() | |
| parsed_files = cf.read(os_helper.FakePath(file1), encoding="utf-8") | |
| self.assertEqual(parsed_files, [file1]) | |
| self.assertEqual(cf.get("Foo Bar", "foo"), "newbar") | |
| # check when we passed both a filename and a Path object: | |
| cf = self.newconfig() | |
| parsed_files = cf.read([os_helper.FakePath(file1), file1], encoding="utf-8") | |
| self.assertEqual(parsed_files, [file1, 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"], encoding="utf-8") | |
| self.assertEqual(parsed_files, []) | |
| # check when we pass no files: | |
| cf = self.newconfig() | |
| parsed_files = cf.read([], encoding="utf-8") | |
| self.assertEqual(parsed_files, []) | |
| def test_read_returns_file_list_with_bytestring_path(self): | |
| if self.delimiters[0] != '=': | |
| self.skipTest('incompatible format') | |
| file1_bytestring = support.findfile("cfgparser.1", subdir="configdata").encode() | |
| # check when passing an existing bytestring path | |
| cf = self.newconfig() | |
| parsed_files = cf.read(file1_bytestring, encoding="utf-8") | |
| self.assertEqual(parsed_files, [file1_bytestring]) | |
| # check when passing an non-existing bytestring path | |
| cf = self.newconfig() | |
| parsed_files = cf.read(b'nonexistent-file', encoding="utf-8") | |
| self.assertEqual(parsed_files, []) | |
| # check when passing both an existing and non-existing bytestring path | |
| cf = self.newconfig() | |
| parsed_files = cf.read([file1_bytestring, b'nonexistent-file'], encoding="utf-8") | |
| self.assertEqual(parsed_files, [file1_bytestring]) | |
| # shared by subclasses | |
| def get_interpolation_config(self): | |
| return self.fromstring( | |
| "[Foo]\n" | |
| "bar{equals}something %(with1)s interpolation (1 step)\n" | |
| "bar9{equals}something %(with9)s lots of interpolation (9 steps)\n" | |
| "bar10{equals}something %(with10)s lots of interpolation (10 steps)\n" | |
| "bar11{equals}something %(with11)s lots of interpolation (11 steps)\n" | |
| "with11{equals}%(with10)s\n" | |
| "with10{equals}%(with9)s\n" | |
| "with9{equals}%(with8)s\n" | |
| "with8{equals}%(With7)s\n" | |
| "with7{equals}%(WITH6)s\n" | |
| "with6{equals}%(with5)s\n" | |
| "With5{equals}%(with4)s\n" | |
| "WITH4{equals}%(with3)s\n" | |
| "with3{equals}%(with2)s\n" | |
| "with2{equals}%(with1)s\n" | |
| "with1{equals}with\n" | |
| "\n" | |
| "[Mutual Recursion]\n" | |
| "foo{equals}%(bar)s\n" | |
| "bar{equals}%(foo)s\n" | |
| "\n" | |
| "[Interpolation Error]\n" | |
| # no definition for 'reference' | |
| "name{equals}%(reference)s\n".format(equals=self.delimiters[0])) | |
| def check_items_config(self, expected): | |
| cf = self.fromstring(""" | |
| [section] | |
| name {0[0]} %(value)s | |
| key{0[1]} |%(name)s| | |
| getdefault{0[1]} |%(default)s| | |
| """.format(self.delimiters), defaults={"default": "<default>"}) | |
| L = list(cf.items("section", vars={'value': 'value'})) | |
| L.sort() | |
| self.assertEqual(L, expected) | |
| with self.assertRaises(configparser.NoSectionError): | |
| cf.items("no such section") | |
| def test_popitem(self): | |
| cf = self.fromstring(""" | |
| [section1] | |
| name1 {0[0]} value1 | |
| [section2] | |
| name2 {0[0]} value2 | |
| [section3] | |
| name3 {0[0]} value3 | |
| """.format(self.delimiters), defaults={"default": "<default>"}) | |
| self.assertEqual(cf.popitem()[0], 'section1') | |
| self.assertEqual(cf.popitem()[0], 'section2') | |
| self.assertEqual(cf.popitem()[0], 'section3') | |
| with self.assertRaises(KeyError): | |
| cf.popitem() | |
| def test_clear(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['zing'] = {'option1': 'value1', 'option2': 'value2'} | |
| self.assertEqual(cf.sections(), ['zing']) | |
| self.assertEqual(set(cf['zing'].keys()), {'option1', 'option2', 'foo'}) | |
| cf.clear() | |
| self.assertEqual(set(cf.sections()), set()) | |
| self.assertEqual(set(cf[self.default_section].keys()), {'foo'}) | |
| def test_setitem(self): | |
| cf = self.fromstring(""" | |
| [section1] | |
| name1 {0[0]} value1 | |
| [section2] | |
| name2 {0[0]} value2 | |
| [section3] | |
| name3 {0[0]} value3 | |
| """.format(self.delimiters), defaults={"nameD": "valueD"}) | |
| self.assertEqual(set(cf['section1'].keys()), {'name1', 'named'}) | |
| self.assertEqual(set(cf['section2'].keys()), {'name2', 'named'}) | |
| self.assertEqual(set(cf['section3'].keys()), {'name3', 'named'}) | |
| self.assertEqual(cf['section1']['name1'], 'value1') | |
| self.assertEqual(cf['section2']['name2'], 'value2') | |
| self.assertEqual(cf['section3']['name3'], 'value3') | |
| self.assertEqual(cf.sections(), ['section1', 'section2', 'section3']) | |
| cf['section2'] = {'name22': 'value22'} | |
| self.assertEqual(set(cf['section2'].keys()), {'name22', 'named'}) | |
| self.assertEqual(cf['section2']['name22'], 'value22') | |
| self.assertNotIn('name2', cf['section2']) | |
| self.assertEqual(cf.sections(), ['section1', 'section2', 'section3']) | |
| cf['section3'] = {} | |
| self.assertEqual(set(cf['section3'].keys()), {'named'}) | |
| self.assertNotIn('name3', cf['section3']) | |
| self.assertEqual(cf.sections(), ['section1', 'section2', 'section3']) | |
| # For bpo-32108, assigning default_section to itself. | |
| cf[self.default_section] = cf[self.default_section] | |
| self.assertNotEqual(set(cf[self.default_section].keys()), set()) | |
| cf[self.default_section] = {} | |
| self.assertEqual(set(cf[self.default_section].keys()), set()) | |
| self.assertEqual(set(cf['section1'].keys()), {'name1'}) | |
| self.assertEqual(set(cf['section2'].keys()), {'name22'}) | |
| self.assertEqual(set(cf['section3'].keys()), set()) | |
| self.assertEqual(cf.sections(), ['section1', 'section2', 'section3']) | |
| # For bpo-32108, assigning section to itself. | |
| cf['section2'] = cf['section2'] | |
| self.assertEqual(set(cf['section2'].keys()), {'name22'}) | |
| def test_invalid_multiline_value(self): | |
| if self.allow_no_value: | |
| self.skipTest('if no_value is allowed, ParsingError is not raised') | |
| invalid = textwrap.dedent("""\ | |
| [DEFAULT] | |
| test {0} test | |
| invalid""".format(self.delimiters[0]) | |
| ) | |
| cf = self.newconfig() | |
| with self.assertRaises(configparser.ParsingError): | |
| cf.read_string(invalid) | |
| self.assertEqual(cf.get('DEFAULT', 'test'), 'test') | |
| self.assertEqual(cf['DEFAULT']['test'], 'test') | |
| class StrictTestCase(BasicTestCase, unittest.TestCase): | |
| config_class = configparser.RawConfigParser | |
| strict = True | |
| class ConfigParserTestCase(BasicTestCase, unittest.TestCase): | |
| 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", | |
| "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)s', 'reference')) | |
| def test_items(self): | |
| self.check_items_config([('default', '<default>'), | |
| ('getdefault', '|<default>|'), | |
| ('key', '|value|'), | |
| ('name', 'value')]) | |
| def test_safe_interpolation(self): | |
| # See https://bugs.python.org/issue511737 | |
| 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") | |
| 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) | |
| def test_defaults_keyword(self): | |
| """bpo-23835 fix for ConfigParser""" | |
| cf = self.newconfig(defaults={1: 2.5}) | |
| self.assertEqual(cf[self.default_section]['1'], '2.5') | |
| self.assertAlmostEqual(cf[self.default_section].getfloat('1'), 2.5) | |
| cf = self.newconfig(defaults={"A": 5.25}) | |
| self.assertEqual(cf[self.default_section]['a'], '5.25') | |
| self.assertAlmostEqual(cf[self.default_section].getfloat('a'), 5.25) | |
| class ConfigParserTestCaseNoInterpolation(BasicTestCase, unittest.TestCase): | |
| config_class = configparser.ConfigParser | |
| interpolation = None | |
| ini = textwrap.dedent(""" | |
| [numbers] | |
| one = 1 | |
| two = %(one)s * 2 | |
| three = ${common:one} * 3 | |
| [hexen] | |
| sixteen = ${numbers:two} * 8 | |
| """).strip() | |
| def assertMatchesIni(self, cf): | |
| self.assertEqual(cf['numbers']['one'], '1') | |
| self.assertEqual(cf['numbers']['two'], '%(one)s * 2') | |
| self.assertEqual(cf['numbers']['three'], '${common:one} * 3') | |
| self.assertEqual(cf['hexen']['sixteen'], '${numbers:two} * 8') | |
| def test_no_interpolation(self): | |
| cf = self.fromstring(self.ini) | |
| self.assertMatchesIni(cf) | |
| def test_empty_case(self): | |
| cf = self.newconfig() | |
| self.assertIsNone(cf.read_string("")) | |
| def test_none_as_default_interpolation(self): | |
| class CustomConfigParser(configparser.ConfigParser): | |
| _DEFAULT_INTERPOLATION = None | |
| cf = CustomConfigParser() | |
| cf.read_string(self.ini) | |
| self.assertMatchesIni(cf) | |
| class ConfigParserTestCaseInvalidInterpolationType(unittest.TestCase): | |
| def test_error_on_wrong_type_for_interpolation(self): | |
| for value in [configparser.ExtendedInterpolation, 42, "a string"]: | |
| with self.subTest(value=value): | |
| with self.assertRaises(TypeError): | |
| configparser.ConfigParser(interpolation=value) | |
| class ConfigParserTestCaseNonStandardDelimiters(ConfigParserTestCase): | |
| delimiters = (':=', '$') | |
| comment_prefixes = ('//', '"') | |
| inline_comment_prefixes = ('//', '"') | |
| class ConfigParserTestCaseNonStandardDefaultSection(ConfigParserTestCase): | |
| default_section = 'general' | |
| class MultilineValuesTestCase(BasicTestCase, unittest.TestCase): | |
| 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(os_helper.TESTFN, 'w', encoding="utf-8") as f: | |
| cf.write(f) | |
| def tearDown(self): | |
| os.unlink(os_helper.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(os_helper.TESTFN, encoding="utf-8") 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, unittest.TestCase): | |
| config_class = configparser.RawConfigParser | |
| def test_interpolation(self): | |
| cf = self.get_interpolation_config() | |
| eq = self.assertEqual | |
| eq(cf.get("Foo", "bar"), | |
| "something %(with1)s interpolation (1 step)") | |
| eq(cf.get("Foo", "bar9"), | |
| "something %(with9)s lots of interpolation (9 steps)") | |
| eq(cf.get("Foo", "bar10"), | |
| "something %(with10)s lots of interpolation (10 steps)") | |
| eq(cf.get("Foo", "bar11"), | |
| "something %(with11)s lots of interpolation (11 steps)") | |
| def test_items(self): | |
| self.check_items_config([('default', '<default>'), | |
| ('getdefault', '|%(default)s|'), | |
| ('key', '|%(name)s|'), | |
| ('name', '%(value)s')]) | |
| 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 is configparser._default_dict: | |
| # would not work for SortedDict; only checking for the most common | |
| # default dictionary (dict) | |
| cf.optionxform = lambda x: x | |
| cf.set('non-string', 1, 1) | |
| self.assertEqual(cf.get('non-string', 1), 1) | |
| def test_defaults_keyword(self): | |
| """bpo-23835 legacy behavior for RawConfigParser""" | |
| with self.assertRaises(AttributeError) as ctx: | |
| self.newconfig(defaults={1: 2.4}) | |
| err = ctx.exception | |
| self.assertEqual(str(err), "'int' object has no attribute 'lower'") | |
| cf = self.newconfig(defaults={"A": 5.2}) | |
| self.assertAlmostEqual(cf[self.default_section]['a'], 5.2) | |
| class RawConfigParserTestCaseNonStandardDelimiters(RawConfigParserTestCase): | |
| delimiters = (':=', '$') | |
| comment_prefixes = ('//', '"') | |
| inline_comment_prefixes = ('//', '"') | |
| class RawConfigParserTestSambaConf(CfgParserTestCaseClass, unittest.TestCase): | |
| config_class = configparser.RawConfigParser | |
| comment_prefixes = ('#', ';', '----') | |
| inline_comment_prefixes = ('//',) | |
| empty_lines_in_values = False | |
| def test_reading(self): | |
| smbconf = support.findfile("cfgparser.2", subdir="configdata") | |
| # 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, unittest.TestCase): | |
| config_class = configparser.ConfigParser | |
| interpolation = configparser.ExtendedInterpolation() | |
| default_section = 'common' | |
| strict = True | |
| def fromstring(self, string, defaults=None, optionxform=None): | |
| cf = self.newconfig(defaults) | |
| if optionxform: | |
| cf.optionxform = optionxform | |
| cf.read_string(string) | |
| return cf | |
| 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], '${dollars:${sick}}') #rawval | |
| def test_case_sensitivity_basic(self): | |
| ini = textwrap.dedent(""" | |
| [common] | |
| optionlower = value | |
| OptionUpper = Value | |
| [Common] | |
| optionlower = a better ${common:optionlower} | |
| OptionUpper = A Better ${common:OptionUpper} | |
| [random] | |
| foolower = ${common:optionlower} redefined | |
| FooUpper = ${Common:OptionUpper} Redefined | |
| """).strip() | |
| cf = self.fromstring(ini) | |
| eq = self.assertEqual | |
| eq(cf['common']['optionlower'], 'value') | |
| eq(cf['common']['OptionUpper'], 'Value') | |
| eq(cf['Common']['optionlower'], 'a better value') | |
| eq(cf['Common']['OptionUpper'], 'A Better Value') | |
| eq(cf['random']['foolower'], 'value redefined') | |
| eq(cf['random']['FooUpper'], 'A Better Value Redefined') | |
| def test_case_sensitivity_conflicts(self): | |
| ini = textwrap.dedent(""" | |
| [common] | |
| option = value | |
| Option = Value | |
| [Common] | |
| option = a better ${common:option} | |
| Option = A Better ${common:Option} | |
| [random] | |
| foo = ${common:option} redefined | |
| Foo = ${Common:Option} Redefined | |
| """).strip() | |
| with self.assertRaises(configparser.DuplicateOptionError): | |
| cf = self.fromstring(ini) | |
| # raw options | |
| cf = self.fromstring(ini, optionxform=lambda opt: opt) | |
| eq = self.assertEqual | |
| eq(cf['common']['option'], 'value') | |
| eq(cf['common']['Option'], 'Value') | |
| eq(cf['Common']['option'], 'a better value') | |
| eq(cf['Common']['Option'], 'A Better Value') | |
| eq(cf['random']['foo'], 'value redefined') | |
| eq(cf['random']['Foo'], 'A Better Value Redefined') | |
| 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 NoValueAndExtendedInterpolation(CfgParserTestCaseClass): | |
| interpolation = configparser.ExtendedInterpolation() | |
| allow_no_value = True | |
| def test_interpolation_with_allow_no_value(self): | |
| config = textwrap.dedent(""" | |
| [dummy] | |
| a | |
| b = ${a} | |
| """) | |
| cf = self.fromstring(config) | |
| self.assertIs(cf["dummy"]["a"], None) | |
| self.assertEqual(cf["dummy"]["b"], "") | |
| def test_explicit_none(self): | |
| config = textwrap.dedent(""" | |
| [dummy] | |
| a = None | |
| b = ${a} | |
| """) | |
| cf = self.fromstring(config) | |
| self.assertEqual(cf["dummy"]["a"], "None") | |
| self.assertEqual(cf["dummy"]["b"], "None") | |
| class ConfigParserNoValueAndExtendedInterpolationTest( | |
| NoValueAndExtendedInterpolation, | |
| unittest.TestCase, | |
| ): | |
| config_class = configparser.ConfigParser | |
| class RawConfigParserNoValueAndExtendedInterpolationTest( | |
| NoValueAndExtendedInterpolation, | |
| unittest.TestCase, | |
| ): | |
| config_class = configparser.RawConfigParser | |
| class ConfigParserTestCaseTrickyFile(CfgParserTestCaseClass, unittest.TestCase): | |
| config_class = configparser.ConfigParser | |
| delimiters = {'='} | |
| comment_prefixes = {'#'} | |
| allow_no_value = True | |
| def test_cfgparser_dot_3(self): | |
| tricky = support.findfile("cfgparser.3", subdir="configdata") | |
| 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", subdir="configdata") | |
| 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, unittest.TestCase): | |
| 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, unittest.TestCase): | |
| 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 FakeFile: | |
| def __init__(self): | |
| file_path = support.findfile("cfgparser.1", subdir="configdata") | |
| with open(file_path, encoding="utf-8") as f: | |
| self.lines = f.readlines() | |
| self.lines.reverse() | |
| def readline(self): | |
| if len(self.lines): | |
| return self.lines.pop() | |
| return '' | |
| def readline_generator(f): | |
| """As advised in Doc/library/configparser.rst.""" | |
| line = f.readline() | |
| while line: | |
| yield line | |
| line = f.readline() | |
| class ReadFileTestCase(unittest.TestCase): | |
| def test_file(self): | |
| file_paths = [support.findfile("cfgparser.1", subdir="configdata")] | |
| try: | |
| file_paths.append(file_paths[0].encode('utf8')) | |
| except UnicodeEncodeError: | |
| pass # unfortunately we can't test bytes on this path | |
| for file_path in file_paths: | |
| parser = configparser.ConfigParser() | |
| with open(file_path, encoding="utf-8") as f: | |
| parser.read_file(f) | |
| self.assertIn("Foo Bar", parser) | |
| self.assertIn("foo", parser["Foo Bar"]) | |
| self.assertEqual(parser["Foo Bar"]["foo"], "newbar") | |
| def test_iterable(self): | |
| lines = textwrap.dedent(""" | |
| [Foo Bar] | |
| foo=newbar""").strip().split('\n') | |
| parser = configparser.ConfigParser() | |
| parser.read_file(lines) | |
| self.assertIn("Foo Bar", parser) | |
| self.assertIn("foo", parser["Foo Bar"]) | |
| self.assertEqual(parser["Foo Bar"]["foo"], "newbar") | |
| def test_readline_generator(self): | |
| """Issue #11670.""" | |
| parser = configparser.ConfigParser() | |
| with self.assertRaises(TypeError): | |
| parser.read_file(FakeFile()) | |
| parser.read_file(readline_generator(FakeFile())) | |
| self.assertIn("Foo Bar", parser) | |
| self.assertIn("foo", parser["Foo Bar"]) | |
| self.assertEqual(parser["Foo Bar"]["foo"], "newbar") | |
| def test_source_as_bytes(self): | |
| """Issue #18260.""" | |
| lines = textwrap.dedent(""" | |
| [badbad] | |
| [badbad]""").strip().split('\n') | |
| parser = configparser.ConfigParser() | |
| with self.assertRaises(configparser.DuplicateSectionError) as dse: | |
| parser.read_file(lines, source=b"badbad") | |
| self.assertEqual( | |
| str(dse.exception), | |
| "While reading from b'badbad' [line 2]: section 'badbad' " | |
| "already exists" | |
| ) | |
| lines = textwrap.dedent(""" | |
| [badbad] | |
| bad = bad | |
| bad = bad""").strip().split('\n') | |
| parser = configparser.ConfigParser() | |
| with self.assertRaises(configparser.DuplicateOptionError) as dse: | |
| parser.read_file(lines, source=b"badbad") | |
| self.assertEqual( | |
| str(dse.exception), | |
| "While reading from b'badbad' [line 3]: option 'bad' in section " | |
| "'badbad' already exists" | |
| ) | |
| lines = textwrap.dedent(""" | |
| [badbad] | |
| = bad""").strip().split('\n') | |
| parser = configparser.ConfigParser() | |
| with self.assertRaises(configparser.ParsingError) as dse: | |
| parser.read_file(lines, source=b"badbad") | |
| self.assertEqual( | |
| str(dse.exception), | |
| "Source contains parsing errors: b'badbad'\n\t[line 2]: '= bad'" | |
| ) | |
| lines = textwrap.dedent(""" | |
| [badbad | |
| bad = bad""").strip().split('\n') | |
| parser = configparser.ConfigParser() | |
| with self.assertRaises(configparser.MissingSectionHeaderError) as dse: | |
| parser.read_file(lines, source=b"badbad") | |
| self.assertEqual( | |
| str(dse.exception), | |
| "File contains no section headers.\nfile: b'badbad', line: 1\n" | |
| "'[badbad'" | |
| ) | |
| def test_keys_without_value_with_extra_whitespace(self): | |
| lines = [ | |
| '[SECT]\n', | |
| 'KEY1\n', | |
| ' KEY2 = VAL2\n', # note the Space before the key! | |
| ] | |
| parser = configparser.ConfigParser( | |
| comment_prefixes="", | |
| allow_no_value=True, | |
| strict=False, | |
| delimiters=('=',), | |
| interpolation=None, | |
| ) | |
| with self.assertRaises(configparser.MultilineContinuationError) as dse: | |
| parser.read_file(lines) | |
| self.assertEqual( | |
| str(dse.exception), | |
| "Key without value continued with an indented line.\n" | |
| "file: '<???>', line: 3\n" | |
| "' KEY2 = VAL2\\n'" | |
| ) | |
| 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(TypeError) as cm: | |
| configparser.ParsingError() | |
| error = configparser.ParsingError(source='source') | |
| self.assertEqual(error.source, 'source') | |
| error = configparser.ParsingError('source') | |
| self.assertEqual(error.source, 'source') | |
| 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_sectionproxy_repr(self): | |
| parser = configparser.ConfigParser() | |
| parser.read_string(""" | |
| [section] | |
| key = value | |
| """) | |
| self.assertEqual(repr(parser['section']), '<Section: section>') | |
| def test_inconsistent_converters_state(self): | |
| parser = configparser.ConfigParser() | |
| import decimal | |
| parser.converters['decimal'] = decimal.Decimal | |
| parser.read_string(""" | |
| [s1] | |
| one = 1 | |
| [s2] | |
| two = 2 | |
| """) | |
| self.assertIn('decimal', parser.converters) | |
| self.assertEqual(parser.getdecimal('s1', 'one'), 1) | |
| self.assertEqual(parser.getdecimal('s2', 'two'), 2) | |
| self.assertEqual(parser['s1'].getdecimal('one'), 1) | |
| self.assertEqual(parser['s2'].getdecimal('two'), 2) | |
| del parser.getdecimal | |
| with self.assertRaises(AttributeError): | |
| parser.getdecimal('s1', 'one') | |
| self.assertIn('decimal', parser.converters) | |
| del parser.converters['decimal'] | |
| self.assertNotIn('decimal', parser.converters) | |
| with self.assertRaises(AttributeError): | |
| parser.getdecimal('s1', 'one') | |
| with self.assertRaises(AttributeError): | |
| parser['s1'].getdecimal('one') | |
| with self.assertRaises(AttributeError): | |
| parser['s2'].getdecimal('two') | |
| class ExceptionPicklingTestCase(unittest.TestCase): | |
| """Tests for issue #13760: ConfigParser exceptions are not picklable.""" | |
| def test_error(self): | |
| import pickle | |
| e1 = configparser.Error('value') | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| def test_combine_error_linear_complexity(self): | |
| # Ensure that ParsingError.combine() has linear complexity. | |
| # See https://github.com/python/cpython/issues/148370. | |
| n = 50000 | |
| s = '[*]\n' + (err_line := '=\n') * n | |
| p = configparser.ConfigParser(strict=False) | |
| with self.assertRaises(configparser.ParsingError) as cm: | |
| p.read_string(s) | |
| errlines = cm.exception.message.splitlines() | |
| self.assertEqual(len(errlines), n + 1) | |
| self.assertStartsWith(errlines[0], "Source contains parsing errors: ") | |
| self.assertEqual(errlines[42], f"\t[line {43:2d}]: {err_line!r}") | |
| def test_nosectionerror(self): | |
| import pickle | |
| e1 = configparser.NoSectionError('section') | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.section, e2.section) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| def test_nooptionerror(self): | |
| import pickle | |
| e1 = configparser.NoOptionError('option', 'section') | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.section, e2.section) | |
| self.assertEqual(e1.option, e2.option) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| def test_duplicatesectionerror(self): | |
| import pickle | |
| e1 = configparser.DuplicateSectionError('section', 'source', 123) | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.section, e2.section) | |
| self.assertEqual(e1.source, e2.source) | |
| self.assertEqual(e1.lineno, e2.lineno) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| def test_duplicateoptionerror(self): | |
| import pickle | |
| e1 = configparser.DuplicateOptionError('section', 'option', 'source', | |
| 123) | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.section, e2.section) | |
| self.assertEqual(e1.option, e2.option) | |
| self.assertEqual(e1.source, e2.source) | |
| self.assertEqual(e1.lineno, e2.lineno) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| def test_interpolationerror(self): | |
| import pickle | |
| e1 = configparser.InterpolationError('option', 'section', 'msg') | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.section, e2.section) | |
| self.assertEqual(e1.option, e2.option) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| def test_interpolationmissingoptionerror(self): | |
| import pickle | |
| e1 = configparser.InterpolationMissingOptionError('option', 'section', | |
| 'rawval', 'reference') | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.section, e2.section) | |
| self.assertEqual(e1.option, e2.option) | |
| self.assertEqual(e1.reference, e2.reference) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| def test_interpolationsyntaxerror(self): | |
| import pickle | |
| e1 = configparser.InterpolationSyntaxError('option', 'section', 'msg') | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.section, e2.section) | |
| self.assertEqual(e1.option, e2.option) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| def test_interpolationdeptherror(self): | |
| import pickle | |
| e1 = configparser.InterpolationDepthError('option', 'section', | |
| 'rawval') | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.section, e2.section) | |
| self.assertEqual(e1.option, e2.option) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| def test_parsingerror(self): | |
| import pickle | |
| e1 = configparser.ParsingError('source') | |
| e1.append(1, 'line1') | |
| e1.append(2, 'line2') | |
| e1.append(3, 'line3') | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.source, e2.source) | |
| self.assertEqual(e1.errors, e2.errors) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| e1 = configparser.ParsingError('filename') | |
| e1.append(1, 'line1') | |
| e1.append(2, 'line2') | |
| e1.append(3, 'line3') | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.source, e2.source) | |
| self.assertEqual(e1.errors, e2.errors) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| def test_missingsectionheadererror(self): | |
| import pickle | |
| e1 = configparser.MissingSectionHeaderError('filename', 123, 'line') | |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): | |
| pickled = pickle.dumps(e1, proto) | |
| e2 = pickle.loads(pickled) | |
| self.assertEqual(e1.message, e2.message) | |
| self.assertEqual(e1.args, e2.args) | |
| self.assertEqual(e1.line, e2.line) | |
| self.assertEqual(e1.source, e2.source) | |
| self.assertEqual(e1.lineno, e2.lineno) | |
| self.assertEqual(repr(e1), repr(e2)) | |
| class InlineCommentStrippingTestCase(unittest.TestCase): | |
| """Tests for issue #14590: ConfigParser doesn't strip inline comment when | |
| delimiter occurs earlier without preceding space..""" | |
| def test_stripping(self): | |
| cfg = configparser.ConfigParser(inline_comment_prefixes=(';', '#', | |
| '//')) | |
| cfg.read_string(""" | |
| [section] | |
| k1 = v1;still v1 | |
| k2 = v2 ;a comment | |
| k3 = v3 ; also a comment | |
| k4 = v4;still v4 ;a comment | |
| k5 = v5;still v5 ; also a comment | |
| k6 = v6;still v6; and still v6 ;a comment | |
| k7 = v7;still v7; and still v7 ; also a comment | |
| [multiprefix] | |
| k1 = v1;still v1 #a comment ; yeah, pretty much | |
| k2 = v2 // this already is a comment ; continued | |
| k3 = v3;#//still v3# and still v3 ; a comment | |
| """) | |
| s = cfg['section'] | |
| self.assertEqual(s['k1'], 'v1;still v1') | |
| self.assertEqual(s['k2'], 'v2') | |
| self.assertEqual(s['k3'], 'v3') | |
| self.assertEqual(s['k4'], 'v4;still v4') | |
| self.assertEqual(s['k5'], 'v5;still v5') | |
| self.assertEqual(s['k6'], 'v6;still v6; and still v6') | |
| self.assertEqual(s['k7'], 'v7;still v7; and still v7') | |
| s = cfg['multiprefix'] | |
| self.assertEqual(s['k1'], 'v1;still v1') | |
| self.assertEqual(s['k2'], 'v2') | |
| self.assertEqual(s['k3'], 'v3;#//still v3# and still v3') | |
| class ExceptionContextTestCase(unittest.TestCase): | |
| """ Test that implementation details doesn't leak | |
| through raising exceptions. """ | |
| def test_get_basic_interpolation(self): | |
| parser = configparser.ConfigParser() | |
| parser.read_string(""" | |
| [Paths] | |
| home_dir: /Users | |
| my_dir: %(home_dir1)s/lumberjack | |
| my_pictures: %(my_dir)s/Pictures | |
| """) | |
| cm = self.assertRaises(configparser.InterpolationMissingOptionError) | |
| with cm: | |
| parser.get('Paths', 'my_dir') | |
| self.assertIs(cm.exception.__suppress_context__, True) | |
| def test_get_extended_interpolation(self): | |
| parser = configparser.ConfigParser( | |
| interpolation=configparser.ExtendedInterpolation()) | |
| parser.read_string(""" | |
| [Paths] | |
| home_dir: /Users | |
| my_dir: ${home_dir1}/lumberjack | |
| my_pictures: ${my_dir}/Pictures | |
| """) | |
| cm = self.assertRaises(configparser.InterpolationMissingOptionError) | |
| with cm: | |
| parser.get('Paths', 'my_dir') | |
| self.assertIs(cm.exception.__suppress_context__, True) | |
| def test_missing_options(self): | |
| parser = configparser.ConfigParser() | |
| parser.read_string(""" | |
| [Paths] | |
| home_dir: /Users | |
| """) | |
| with self.assertRaises(configparser.NoSectionError) as cm: | |
| parser.options('test') | |
| self.assertIs(cm.exception.__suppress_context__, True) | |
| def test_missing_section(self): | |
| config = configparser.ConfigParser() | |
| with self.assertRaises(configparser.NoSectionError) as cm: | |
| config.set('Section1', 'an_int', '15') | |
| self.assertIs(cm.exception.__suppress_context__, True) | |
| def test_remove_option(self): | |
| config = configparser.ConfigParser() | |
| with self.assertRaises(configparser.NoSectionError) as cm: | |
| config.remove_option('Section1', 'an_int') | |
| self.assertIs(cm.exception.__suppress_context__, True) | |
| class ConvertersTestCase(BasicTestCase, unittest.TestCase): | |
| """Introduced in 3.5, issue #18159.""" | |
| config_class = configparser.ConfigParser | |
| def newconfig(self, defaults=None, **kwargs): | |
| instance = super().newconfig(defaults=defaults, **kwargs) | |
| instance.converters['list'] = lambda v: [e.strip() for e in v.split() | |
| if e.strip()] | |
| return instance | |
| def test_converters(self): | |
| cfg = self.newconfig() | |
| self.assertIn('boolean', cfg.converters) | |
| self.assertIn('list', cfg.converters) | |
| self.assertIsNone(cfg.converters['int']) | |
| self.assertIsNone(cfg.converters['float']) | |
| self.assertIsNone(cfg.converters['boolean']) | |
| self.assertIsNotNone(cfg.converters['list']) | |
| self.assertEqual(len(cfg.converters), 4) | |
| with self.assertRaises(ValueError): | |
| cfg.converters[''] = lambda v: v | |
| with self.assertRaises(ValueError): | |
| cfg.converters[None] = lambda v: v | |
| cfg.read_string(""" | |
| [s] | |
| str = string | |
| int = 1 | |
| float = 0.5 | |
| list = a b c d e f g | |
| bool = yes | |
| """) | |
| s = cfg['s'] | |
| self.assertEqual(s['str'], 'string') | |
| self.assertEqual(s['int'], '1') | |
| self.assertEqual(s['float'], '0.5') | |
| self.assertEqual(s['list'], 'a b c d e f g') | |
| self.assertEqual(s['bool'], 'yes') | |
| self.assertEqual(cfg.get('s', 'str'), 'string') | |
| self.assertEqual(cfg.get('s', 'int'), '1') | |
| self.assertEqual(cfg.get('s', 'float'), '0.5') | |
| self.assertEqual(cfg.get('s', 'list'), 'a b c d e f g') | |
| self.assertEqual(cfg.get('s', 'bool'), 'yes') | |
| self.assertEqual(cfg.get('s', 'str'), 'string') | |
| self.assertEqual(cfg.getint('s', 'int'), 1) | |
| self.assertEqual(cfg.getfloat('s', 'float'), 0.5) | |
| self.assertEqual(cfg.getlist('s', 'list'), ['a', 'b', 'c', 'd', | |
| 'e', 'f', 'g']) | |
| self.assertEqual(cfg.getboolean('s', 'bool'), True) | |
| self.assertEqual(s.get('str'), 'string') | |
| self.assertEqual(s.getint('int'), 1) | |
| self.assertEqual(s.getfloat('float'), 0.5) | |
| self.assertEqual(s.getlist('list'), ['a', 'b', 'c', 'd', | |
| 'e', 'f', 'g']) | |
| self.assertEqual(s.getboolean('bool'), True) | |
| with self.assertRaises(AttributeError): | |
| cfg.getdecimal('s', 'float') | |
| with self.assertRaises(AttributeError): | |
| s.getdecimal('float') | |
| import decimal | |
| cfg.converters['decimal'] = decimal.Decimal | |
| self.assertIn('decimal', cfg.converters) | |
| self.assertIsNotNone(cfg.converters['decimal']) | |
| self.assertEqual(len(cfg.converters), 5) | |
| dec0_5 = decimal.Decimal('0.5') | |
| self.assertEqual(cfg.getdecimal('s', 'float'), dec0_5) | |
| self.assertEqual(s.getdecimal('float'), dec0_5) | |
| del cfg.converters['decimal'] | |
| self.assertNotIn('decimal', cfg.converters) | |
| self.assertEqual(len(cfg.converters), 4) | |
| with self.assertRaises(AttributeError): | |
| cfg.getdecimal('s', 'float') | |
| with self.assertRaises(AttributeError): | |
| s.getdecimal('float') | |
| with self.assertRaises(KeyError): | |
| del cfg.converters['decimal'] | |
| with self.assertRaises(KeyError): | |
| del cfg.converters[''] | |
| with self.assertRaises(KeyError): | |
| del cfg.converters[None] | |
| class BlatantOverrideConvertersTestCase(unittest.TestCase): | |
| """What if somebody overrode a getboolean()? We want to make sure that in | |
| this case the automatic converters do not kick in.""" | |
| config = """ | |
| [one] | |
| one = false | |
| two = false | |
| three = long story short | |
| [two] | |
| one = false | |
| two = false | |
| three = four | |
| """ | |
| def test_converters_at_init(self): | |
| cfg = configparser.ConfigParser(converters={'len': len}) | |
| cfg.read_string(self.config) | |
| self._test_len(cfg) | |
| self.assertIsNotNone(cfg.converters['len']) | |
| def test_inheritance(self): | |
| class StrangeConfigParser(configparser.ConfigParser): | |
| gettysburg = 'a historic borough in south central Pennsylvania' | |
| def getboolean(self, section, option, *, raw=False, vars=None, | |
| fallback=configparser._UNSET): | |
| if section == option: | |
| return True | |
| return super().getboolean(section, option, raw=raw, vars=vars, | |
| fallback=fallback) | |
| def getlen(self, section, option, *, raw=False, vars=None, | |
| fallback=configparser._UNSET): | |
| return self._get_conv(section, option, len, raw=raw, vars=vars, | |
| fallback=fallback) | |
| cfg = StrangeConfigParser() | |
| cfg.read_string(self.config) | |
| self._test_len(cfg) | |
| self.assertIsNone(cfg.converters['len']) | |
| self.assertTrue(cfg.getboolean('one', 'one')) | |
| self.assertTrue(cfg.getboolean('two', 'two')) | |
| self.assertFalse(cfg.getboolean('one', 'two')) | |
| self.assertFalse(cfg.getboolean('two', 'one')) | |
| cfg.converters['boolean'] = cfg._convert_to_boolean | |
| self.assertFalse(cfg.getboolean('one', 'one')) | |
| self.assertFalse(cfg.getboolean('two', 'two')) | |
| self.assertFalse(cfg.getboolean('one', 'two')) | |
| self.assertFalse(cfg.getboolean('two', 'one')) | |
| def _test_len(self, cfg): | |
| self.assertEqual(len(cfg.converters), 4) | |
| self.assertIn('boolean', cfg.converters) | |
| self.assertIn('len', cfg.converters) | |
| self.assertNotIn('tysburg', cfg.converters) | |
| self.assertIsNone(cfg.converters['int']) | |
| self.assertIsNone(cfg.converters['float']) | |
| self.assertIsNone(cfg.converters['boolean']) | |
| self.assertEqual(cfg.getlen('one', 'one'), 5) | |
| self.assertEqual(cfg.getlen('one', 'two'), 5) | |
| self.assertEqual(cfg.getlen('one', 'three'), 16) | |
| self.assertEqual(cfg.getlen('two', 'one'), 5) | |
| self.assertEqual(cfg.getlen('two', 'two'), 5) | |
| self.assertEqual(cfg.getlen('two', 'three'), 4) | |
| self.assertEqual(cfg.getlen('two', 'four', fallback=0), 0) | |
| with self.assertRaises(configparser.NoOptionError): | |
| cfg.getlen('two', 'four') | |
| self.assertEqual(cfg['one'].getlen('one'), 5) | |
| self.assertEqual(cfg['one'].getlen('two'), 5) | |
| self.assertEqual(cfg['one'].getlen('three'), 16) | |
| self.assertEqual(cfg['two'].getlen('one'), 5) | |
| self.assertEqual(cfg['two'].getlen('two'), 5) | |
| self.assertEqual(cfg['two'].getlen('three'), 4) | |
| self.assertEqual(cfg['two'].getlen('four', 0), 0) | |
| self.assertEqual(cfg['two'].getlen('four'), None) | |
| def test_instance_assignment(self): | |
| cfg = configparser.ConfigParser() | |
| cfg.getboolean = lambda section, option: True | |
| cfg.getlen = lambda section, option: len(cfg[section][option]) | |
| cfg.read_string(self.config) | |
| self.assertEqual(len(cfg.converters), 3) | |
| self.assertIn('boolean', cfg.converters) | |
| self.assertNotIn('len', cfg.converters) | |
| self.assertIsNone(cfg.converters['int']) | |
| self.assertIsNone(cfg.converters['float']) | |
| self.assertIsNone(cfg.converters['boolean']) | |
| self.assertTrue(cfg.getboolean('one', 'one')) | |
| self.assertTrue(cfg.getboolean('two', 'two')) | |
| self.assertTrue(cfg.getboolean('one', 'two')) | |
| self.assertTrue(cfg.getboolean('two', 'one')) | |
| cfg.converters['boolean'] = cfg._convert_to_boolean | |
| self.assertFalse(cfg.getboolean('one', 'one')) | |
| self.assertFalse(cfg.getboolean('two', 'two')) | |
| self.assertFalse(cfg.getboolean('one', 'two')) | |
| self.assertFalse(cfg.getboolean('two', 'one')) | |
| self.assertEqual(cfg.getlen('one', 'one'), 5) | |
| self.assertEqual(cfg.getlen('one', 'two'), 5) | |
| self.assertEqual(cfg.getlen('one', 'three'), 16) | |
| self.assertEqual(cfg.getlen('two', 'one'), 5) | |
| self.assertEqual(cfg.getlen('two', 'two'), 5) | |
| self.assertEqual(cfg.getlen('two', 'three'), 4) | |
| # If a getter impl is assigned straight to the instance, it won't | |
| # be available on the section proxies. | |
| with self.assertRaises(AttributeError): | |
| self.assertEqual(cfg['one'].getlen('one'), 5) | |
| with self.assertRaises(AttributeError): | |
| self.assertEqual(cfg['two'].getlen('one'), 5) | |
| class SectionlessTestCase(unittest.TestCase): | |
| def fromstring(self, string): | |
| cfg = configparser.ConfigParser(allow_unnamed_section=True) | |
| cfg.read_string(string) | |
| return cfg | |
| def test_no_first_section(self): | |
| cfg1 = self.fromstring(""" | |
| a = 1 | |
| b = 2 | |
| [sect1] | |
| c = 3 | |
| """) | |
| self.assertEqual(set([configparser.UNNAMED_SECTION, 'sect1']), set(cfg1.sections())) | |
| self.assertEqual('1', cfg1[configparser.UNNAMED_SECTION]['a']) | |
| self.assertEqual('2', cfg1[configparser.UNNAMED_SECTION]['b']) | |
| self.assertEqual('3', cfg1['sect1']['c']) | |
| output = io.StringIO() | |
| cfg1.write(output) | |
| cfg2 = self.fromstring(output.getvalue()) | |
| #self.assertEqual(set([configparser.UNNAMED_SECTION, 'sect1']), set(cfg2.sections())) | |
| self.assertEqual('1', cfg2[configparser.UNNAMED_SECTION]['a']) | |
| self.assertEqual('2', cfg2[configparser.UNNAMED_SECTION]['b']) | |
| self.assertEqual('3', cfg2['sect1']['c']) | |
| def test_no_section(self): | |
| cfg1 = self.fromstring(""" | |
| a = 1 | |
| b = 2 | |
| """) | |
| self.assertEqual([configparser.UNNAMED_SECTION], cfg1.sections()) | |
| self.assertEqual('1', cfg1[configparser.UNNAMED_SECTION]['a']) | |
| self.assertEqual('2', cfg1[configparser.UNNAMED_SECTION]['b']) | |
| output = io.StringIO() | |
| cfg1.write(output) | |
| cfg2 = self.fromstring(output.getvalue()) | |
| self.assertEqual([configparser.UNNAMED_SECTION], cfg2.sections()) | |
| self.assertEqual('1', cfg2[configparser.UNNAMED_SECTION]['a']) | |
| self.assertEqual('2', cfg2[configparser.UNNAMED_SECTION]['b']) | |
| def test_empty_unnamed_section(self): | |
| cfg = configparser.ConfigParser(allow_unnamed_section=True) | |
| cfg.add_section(configparser.UNNAMED_SECTION) | |
| cfg.add_section('section') | |
| output = io.StringIO() | |
| cfg.write(output) | |
| self.assertEqual(output.getvalue(), '[section]\n\n') | |
| def test_add_section(self): | |
| cfg = configparser.ConfigParser(allow_unnamed_section=True) | |
| cfg.add_section(configparser.UNNAMED_SECTION) | |
| cfg.set(configparser.UNNAMED_SECTION, 'a', '1') | |
| self.assertEqual('1', cfg[configparser.UNNAMED_SECTION]['a']) | |
| output = io.StringIO() | |
| cfg.write(output) | |
| self.assertEqual(output.getvalue(), 'a = 1\n\n') | |
| cfg = configparser.ConfigParser(allow_unnamed_section=True) | |
| cfg[configparser.UNNAMED_SECTION] = {'a': '1'} | |
| self.assertEqual('1', cfg[configparser.UNNAMED_SECTION]['a']) | |
| output = io.StringIO() | |
| cfg.write(output) | |
| self.assertEqual(output.getvalue(), 'a = 1\n\n') | |
| def test_disabled_error(self): | |
| with self.assertRaises(configparser.MissingSectionHeaderError): | |
| configparser.ConfigParser().read_string("a = 1") | |
| with self.assertRaises(configparser.UnnamedSectionDisabledError): | |
| configparser.ConfigParser().add_section(configparser.UNNAMED_SECTION) | |
| with self.assertRaises(configparser.UnnamedSectionDisabledError): | |
| configparser.ConfigParser()[configparser.UNNAMED_SECTION] = {'a': '1'} | |
| def test_multiple_configs(self): | |
| cfg = configparser.ConfigParser(allow_unnamed_section=True) | |
| cfg.read_string('a = 1') | |
| cfg.read_string('b = 2') | |
| self.assertEqual([configparser.UNNAMED_SECTION], cfg.sections()) | |
| self.assertEqual('1', cfg[configparser.UNNAMED_SECTION]['a']) | |
| self.assertEqual('2', cfg[configparser.UNNAMED_SECTION]['b']) | |
| class InvalidInputTestCase(unittest.TestCase): | |
| """Tests for issue #65697, where configparser will write configs | |
| it parses back differently. Ex: keys containing delimiters or | |
| matching the section pattern""" | |
| def test_delimiter_in_key(self): | |
| cfg = configparser.ConfigParser(delimiters=('=')) | |
| cfg.add_section('section1') | |
| cfg.set('section1', 'a=b', 'c') | |
| output = io.StringIO() | |
| with self.assertRaises(configparser.InvalidWriteError): | |
| cfg.write(output) | |
| output.close() | |
| def test_section_bracket_in_key(self): | |
| cfg = configparser.ConfigParser() | |
| cfg.add_section('section1') | |
| cfg.set('section1', '[this parses back as a section]', 'foo') | |
| output = io.StringIO() | |
| with self.assertRaises(configparser.InvalidWriteError): | |
| cfg.write(output) | |
| output.close() | |
| class ReDoSTestCase(unittest.TestCase): | |
| """Regression tests for quadratic regex backtracking (gh-146333).""" | |
| def test_option_regex_does_not_backtrack(self): | |
| # A line with many spaces between non-delimiter characters | |
| # should be parsed in linear time, not quadratic. | |
| parser = configparser.RawConfigParser() | |
| content = "[section]\n" + "x" + " " * 40000 + "y" + "\n" | |
| # This should complete almost instantly. Before the fix, | |
| # it would take over a minute due to catastrophic backtracking. | |
| with self.assertRaises(configparser.ParsingError): | |
| parser.read_string(content) | |
| def test_option_regex_no_value_does_not_backtrack(self): | |
| parser = configparser.RawConfigParser(allow_no_value=True) | |
| content = "[section]\n" + "x" + " " * 40000 + "y" + "\n" | |
| parser.read_string(content) | |
| self.assertTrue(parser.has_option("section", "x" + " " * 40000 + "y")) | |
| class MiscTestCase(unittest.TestCase): | |
| def test__all__(self): | |
| support.check__all__(self, configparser, not_exported={"Error"}) | |
| if __name__ == '__main__': | |
| unittest.main() |