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 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 import os.path
from configparser import (
ConfigParser,
)
from hypothesis import (
given,
)
from hypothesis.strategies import (
dictionaries,
text,
)
from twisted.python.filepath import ( from twisted.python.filepath import (
FilePath, FilePath,
@ -23,6 +34,51 @@ from twisted.trial import unittest
from allmydata.util import configutil 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): class ConfigUtilTests(unittest.TestCase):
def setUp(self): def setUp(self):
super(ConfigUtilTests, self).setUp() super(ConfigUtilTests, self).setUp()
@ -166,3 +222,48 @@ enabled = false
config = configutil.get_config(fname) config = configutil.get_config(fname)
self.assertEqual(config.get("node", "a"), "foo") self.assertEqual(config.get("node", "a"), "foo")
self.assertEqual(config.get("node", "b"), "bar") 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 an item name as bytes and returns True if that section, item pair is
valid, False otherwise. 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_section = attr.ib(default=lambda section_name: False)
_is_valid_item = attr.ib(default=lambda section_name, item_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): def is_valid_section(self, section_name):
""" """
:return: True if the given section name is valid, False otherwise. :return: True if the given section name is valid, False otherwise.