Add everything and nothing config validation helpers

This commit is contained in:
Jean-Paul Calderone 2020-11-18 12:42:31 -05:00
parent aedac9d570
commit 34714d5f6b
2 changed files with 126 additions and 1 deletions

View File

@ -14,6 +14,17 @@ if PY2:
from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, list, object, range, str, max, min # noqa: F401
import os.path
from configparser import (
ConfigParser,
)
from hypothesis import (
given,
)
from hypothesis.strategies import (
dictionaries,
text,
)
from twisted.python.filepath import (
FilePath,
@ -23,6 +34,51 @@ from twisted.trial import unittest
from allmydata.util import configutil
def arbitrary_config_dicts(
min_sections=0,
max_sections=3,
max_items_per_section=3,
max_item_length=8,
max_value_length=8,
):
"""
Build ``dict[str, dict[str, str]]`` instances populated with arbitrary
configurations.
"""
return dictionaries(
text(),
dictionaries(
text(max_size=max_item_length),
text(max_size=max_value_length),
max_size=max_items_per_section,
),
min_size=min_sections,
max_size=max_sections,
)
def to_configparser(dictconfig):
"""
Take a ``dict[str, dict[str, str]]`` and turn it into the corresponding
populated ``ConfigParser`` instance.
"""
cp = ConfigParser()
for section, items in dictconfig.items():
cp.add_section(section)
for k, v in items.items():
cp.set(
section,
k,
# ConfigParser has a feature that everyone knows and loves
# where it will use %-style interpolation to substitute
# values from one part of the config into another part of
# the config. Escape all our `%`s to avoid hitting this
# and complicating things.
v.replace("%", "%%"),
)
return cp
class ConfigUtilTests(unittest.TestCase):
def setUp(self):
super(ConfigUtilTests, self).setUp()
@ -166,3 +222,48 @@ enabled = false
config = configutil.get_config(fname)
self.assertEqual(config.get("node", "a"), "foo")
self.assertEqual(config.get("node", "b"), "bar")
@given(arbitrary_config_dicts())
def test_everything_valid(self, cfgdict):
"""
``validate_config`` returns ``None`` when the validator is
``ValidConfiguration.everything()``.
"""
cfg = to_configparser(cfgdict)
self.assertIs(
configutil.validate_config(
"<test_everything_valid>",
cfg,
configutil.ValidConfiguration.everything(),
),
None,
)
@given(arbitrary_config_dicts(min_sections=1))
def test_nothing_valid(self, cfgdict):
"""
``validate_config`` raises ``UnknownConfigError`` when the validator is
``ValidConfiguration.nothing()`` for all non-empty configurations.
"""
cfg = to_configparser(cfgdict)
with self.assertRaises(configutil.UnknownConfigError):
configutil.validate_config(
"<test_everything_valid>",
cfg,
configutil.ValidConfiguration.nothing(),
)
def test_nothing_empty_valid(self):
"""
``validate_config`` returns ``None`` when the validator is
``ValidConfiguration.nothing()`` if the configuration is empty.
"""
cfg = ConfigParser()
self.assertIs(
configutil.validate_config(
"<test_everything_valid>",
cfg,
configutil.ValidConfiguration.nothing(),
),
None,
)

View File

@ -115,10 +115,34 @@ class ValidConfiguration(object):
an item name as bytes and returns True if that section, item pair is
valid, False otherwise.
"""
_static_valid_sections = attr.ib()
_static_valid_sections = attr.ib(
validator=attr.validators.instance_of(dict)
)
_is_valid_section = attr.ib(default=lambda section_name: False)
_is_valid_item = attr.ib(default=lambda section_name, item_name: False)
@classmethod
def everything(cls):
"""
Create a validator which considers everything valid.
"""
return cls(
{},
lambda section_name: True,
lambda section_name, item_name: True,
)
@classmethod
def nothing(cls):
"""
Create a validator which considers nothing valid.
"""
return cls(
{},
lambda section_name: False,
lambda section_name, item_name: False,
)
def is_valid_section(self, section_name):
"""
:return: True if the given section name is valid, False otherwise.