source: trunk/src/allmydata/test/test_configutil.py

Last change on this file was 1cfe843d, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-22T23:40:25Z

more python2 removal

  • Property mode set to 100644
File size: 9.2 KB
Line 
1"""
2Tests for allmydata.util.configutil.
3
4Ported to Python 3.
5"""
6
7import os.path
8from configparser import (
9    ConfigParser,
10)
11from functools import (
12    partial,
13)
14
15from hypothesis import (
16    given,
17)
18from hypothesis.strategies import (
19    dictionaries,
20    text,
21    characters,
22)
23
24from twisted.python.filepath import (
25    FilePath,
26)
27from twisted.trial import unittest
28
29from allmydata.util import configutil
30
31
32def arbitrary_config_dicts(
33        min_sections=0,
34        max_sections=3,
35        max_section_name_size=8,
36        max_items_per_section=3,
37        max_item_length=8,
38        max_value_length=8,
39):
40    """
41    Build ``dict[str, dict[str, str]]`` instances populated with arbitrary
42    configurations.
43    """
44    identifier_text = partial(
45        text,
46        # Don't allow most control characters or spaces
47        alphabet=characters(
48            blacklist_categories=('Cc', 'Cs', 'Zs'),
49        ),
50    )
51    return dictionaries(
52        identifier_text(
53            min_size=1,
54            max_size=max_section_name_size,
55        ),
56        dictionaries(
57            identifier_text(
58                min_size=1,
59                max_size=max_item_length,
60            ),
61            text(max_size=max_value_length),
62            max_size=max_items_per_section,
63        ),
64        min_size=min_sections,
65        max_size=max_sections,
66    )
67
68
69def to_configparser(dictconfig):
70    """
71    Take a ``dict[str, dict[str, str]]`` and turn it into the corresponding
72    populated ``ConfigParser`` instance.
73    """
74    cp = ConfigParser()
75    for section, items in dictconfig.items():
76        cp.add_section(section)
77        for k, v in items.items():
78            cp.set(
79                section,
80                k,
81                # ConfigParser has a feature that everyone knows and loves
82                # where it will use %-style interpolation to substitute
83                # values from one part of the config into another part of
84                # the config.  Escape all our `%`s to avoid hitting this
85                # and complicating things.
86                v.replace("%", "%%"),
87            )
88    return cp
89
90
91class ConfigUtilTests(unittest.TestCase):
92    def setUp(self):
93        super(ConfigUtilTests, self).setUp()
94        self.static_valid_config = configutil.ValidConfiguration(
95            dict(node=['valid']),
96        )
97        self.dynamic_valid_config = configutil.ValidConfiguration(
98            dict(),
99            lambda section_name: section_name == "node",
100            lambda section_name, item_name: (section_name, item_name) == ("node", "valid"),
101        )
102
103    def create_tahoe_cfg(self, cfg):
104        d = self.mktemp()
105        os.mkdir(d)
106        fname = os.path.join(d, 'tahoe.cfg')
107        with open(fname, "w") as f:
108            f.write(cfg)
109        return fname
110
111    def test_config_utils(self):
112        tahoe_cfg = self.create_tahoe_cfg("""\
113[node]
114nickname = client-0
115web.port = adopt-socket:fd=5
116[storage]
117enabled = false
118""")
119
120        # test that at least one option was read correctly
121        config = configutil.get_config(tahoe_cfg)
122        self.failUnlessEqual(config.get("node", "nickname"), "client-0")
123
124        # test that set_config can mutate an existing option
125        configutil.set_config(config, "node", "nickname", "Alice!")
126        configutil.write_config(FilePath(tahoe_cfg), config)
127
128        config = configutil.get_config(tahoe_cfg)
129        self.failUnlessEqual(config.get("node", "nickname"), "Alice!")
130
131        # test that set_config can set a new option
132        descriptor = "Twas brillig, and the slithy toves Did gyre and gimble in the wabe"
133        configutil.set_config(config, "node", "descriptor", descriptor)
134        configutil.write_config(FilePath(tahoe_cfg), config)
135
136        config = configutil.get_config(tahoe_cfg)
137        self.failUnlessEqual(config.get("node", "descriptor"), descriptor)
138
139    def test_config_validation_success(self):
140        """
141        ``configutil.validate_config`` returns ``None`` when the configuration it
142        is given has nothing more than the static sections and items defined
143        by the validator.
144        """
145        # should succeed, no exceptions
146        configutil.validate_config(
147            "<test_config_validation_success>",
148            to_configparser({"node": {"valid": "foo"}}),
149            self.static_valid_config,
150        )
151
152    def test_config_dynamic_validation_success(self):
153        """
154        A configuration with sections and items that are not matched by the static
155        validation but are matched by the dynamic validation is considered
156        valid.
157        """
158        # should succeed, no exceptions
159        configutil.validate_config(
160            "<test_config_dynamic_validation_success>",
161            to_configparser({"node": {"valid": "foo"}}),
162            self.dynamic_valid_config,
163        )
164
165    def test_config_validation_invalid_item(self):
166        config = to_configparser({"node": {"valid": "foo", "invalid": "foo"}})
167        e = self.assertRaises(
168            configutil.UnknownConfigError,
169            configutil.validate_config,
170            "<test_config_validation_invalid_item>",
171            config,
172            self.static_valid_config,
173        )
174        self.assertIn("section [node] contains unknown option 'invalid'", str(e))
175
176    def test_config_validation_invalid_section(self):
177        """
178        A configuration with a section that is matched by neither the static nor
179        dynamic validators is rejected.
180        """
181        config = to_configparser({"node": {"valid": "foo"}, "invalid": {}})
182        e = self.assertRaises(
183            configutil.UnknownConfigError,
184            configutil.validate_config,
185            "<test_config_validation_invalid_section>",
186            config,
187            self.static_valid_config,
188        )
189        self.assertIn("contains unknown section [invalid]", str(e))
190
191    def test_config_dynamic_validation_invalid_section(self):
192        """
193        A configuration with a section that is matched by neither the static nor
194        dynamic validators is rejected.
195        """
196        config = to_configparser({"node": {"valid": "foo"}, "invalid": {}})
197        e = self.assertRaises(
198            configutil.UnknownConfigError,
199            configutil.validate_config,
200            "<test_config_dynamic_validation_invalid_section>",
201            config,
202            self.dynamic_valid_config,
203        )
204        self.assertIn("contains unknown section [invalid]", str(e))
205
206    def test_config_dynamic_validation_invalid_item(self):
207        """
208        A configuration with a section, item pair that is matched by neither the
209        static nor dynamic validators is rejected.
210        """
211        config = to_configparser({"node": {"valid": "foo", "invalid": "foo"}})
212        e = self.assertRaises(
213            configutil.UnknownConfigError,
214            configutil.validate_config,
215            "<test_config_dynamic_validation_invalid_item>",
216            config,
217            self.dynamic_valid_config,
218        )
219        self.assertIn("section [node] contains unknown option 'invalid'", str(e))
220
221    def test_duplicate_sections(self):
222        """
223        Duplicate section names are merged.
224        """
225        fname = self.create_tahoe_cfg('[node]\na = foo\n[node]\n b = bar\n')
226        config = configutil.get_config(fname)
227        self.assertEqual(config.get("node", "a"), "foo")
228        self.assertEqual(config.get("node", "b"), "bar")
229
230    @given(arbitrary_config_dicts())
231    def test_everything_valid(self, cfgdict):
232        """
233        ``validate_config`` returns ``None`` when the validator is
234        ``ValidConfiguration.everything()``.
235        """
236        cfg = to_configparser(cfgdict)
237        self.assertIs(
238            configutil.validate_config(
239                "<test_everything_valid>",
240                cfg,
241                configutil.ValidConfiguration.everything(),
242            ),
243            None,
244        )
245
246    @given(arbitrary_config_dicts(min_sections=1))
247    def test_nothing_valid(self, cfgdict):
248        """
249        ``validate_config`` raises ``UnknownConfigError`` when the validator is
250        ``ValidConfiguration.nothing()`` for all non-empty configurations.
251        """
252        cfg = to_configparser(cfgdict)
253        with self.assertRaises(configutil.UnknownConfigError):
254            configutil.validate_config(
255                "<test_everything_valid>",
256                cfg,
257                configutil.ValidConfiguration.nothing(),
258            )
259
260    def test_nothing_empty_valid(self):
261        """
262        ``validate_config`` returns ``None`` when the validator is
263        ``ValidConfiguration.nothing()`` if the configuration is empty.
264        """
265        cfg = ConfigParser()
266        self.assertIs(
267            configutil.validate_config(
268                "<test_everything_valid>",
269                cfg,
270                configutil.ValidConfiguration.nothing(),
271            ),
272            None,
273        )
274
275    @given(arbitrary_config_dicts())
276    def test_copy_config(self, cfgdict):
277        """
278        ``copy_config`` creates a new ``ConfigParser`` object containing the same
279        values as its input.
280        """
281        cfg = to_configparser(cfgdict)
282        copied = configutil.copy_config(cfg)
283        # Should be equal
284        self.assertEqual(cfg, copied)
285        # But not because they're the same object.
286        self.assertIsNot(cfg, copied)
Note: See TracBrowser for help on using the repository browser.