570 lines
18 KiB
Python
570 lines
18 KiB
Python
|
"""Unit tests for bison.scheme"""
|
||
|
|
||
|
import pytest
|
||
|
|
||
|
from bison import errors, scheme
|
||
|
|
||
|
|
||
|
def test_base_opt():
|
||
|
"""Validate the base option, which should fail."""
|
||
|
opt = scheme._BaseOpt()
|
||
|
with pytest.raises(NotImplementedError):
|
||
|
opt.validate('test-data')
|
||
|
|
||
|
|
||
|
class TestOption:
|
||
|
"""Tests for the `Option` class."""
|
||
|
|
||
|
def test_init_simple(self):
|
||
|
"""Initialize an Option."""
|
||
|
opt = scheme.Option('test-opt')
|
||
|
|
||
|
assert opt.name == 'test-opt'
|
||
|
assert type(opt.default) == scheme.NoDefault
|
||
|
assert opt.type is None
|
||
|
assert opt.choices is None
|
||
|
assert opt.bind_env is None
|
||
|
|
||
|
def test_init_full(self):
|
||
|
"""Initialize an Option."""
|
||
|
opt = scheme.Option(
|
||
|
name='test-opt',
|
||
|
default='foo',
|
||
|
field_type=str,
|
||
|
choices=['foo', 'bar'],
|
||
|
bind_env=True
|
||
|
)
|
||
|
|
||
|
assert opt.name == 'test-opt'
|
||
|
assert type(opt.default) != scheme.NoDefault
|
||
|
assert opt.default == 'foo'
|
||
|
assert opt.type == str
|
||
|
assert opt.choices == ['foo', 'bar']
|
||
|
assert opt.bind_env is True
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'field_type,value', [
|
||
|
(str, 'test-value'),
|
||
|
(str, ''),
|
||
|
(int, 0),
|
||
|
(int, 1000),
|
||
|
(int, -1),
|
||
|
(float, 0.0),
|
||
|
(float, 1000.999),
|
||
|
(float, -1.0),
|
||
|
(bool, True),
|
||
|
(bool, False),
|
||
|
(list, []),
|
||
|
(list, [1, 2, 3]),
|
||
|
(list, ['a', 'b', 'c']),
|
||
|
(tuple, tuple()),
|
||
|
(tuple, (1,)),
|
||
|
(tuple, ('a', 'b')),
|
||
|
(dict, {}),
|
||
|
(dict, {'a': 'b'}),
|
||
|
(dict, {1: 2})
|
||
|
]
|
||
|
)
|
||
|
def test_validate_type_ok(self, field_type, value):
|
||
|
"""Validate an Option, where type validation succeeds"""
|
||
|
opt = scheme.Option('test-option', field_type=field_type)
|
||
|
opt.validate(value)
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'field_type,value', [
|
||
|
(str, None),
|
||
|
(str, 1),
|
||
|
(str, 1.8),
|
||
|
(str, True),
|
||
|
(str, ['a', 'b', 'c']),
|
||
|
|
||
|
(int, None),
|
||
|
(int, 'value'),
|
||
|
(int, 1.8),
|
||
|
(int, True),
|
||
|
(int, ['a', 'b', 'c']),
|
||
|
|
||
|
(float, None),
|
||
|
(float, 'value'),
|
||
|
(float, 1),
|
||
|
(float, True),
|
||
|
(float, ['a', 'b', 'c']),
|
||
|
|
||
|
(bool, None),
|
||
|
(bool, 'value'),
|
||
|
(bool, 1),
|
||
|
(bool, 1.8),
|
||
|
(bool, ['a', 'b', 'c']),
|
||
|
|
||
|
(list, None),
|
||
|
(list, 'value'),
|
||
|
(list, 1),
|
||
|
(list, 1.8),
|
||
|
(list, True),
|
||
|
]
|
||
|
)
|
||
|
def test_validate_type_failure(self, field_type, value):
|
||
|
"""Validate an Option, where type validation fails"""
|
||
|
opt = scheme.Option('test-option', field_type=field_type)
|
||
|
with pytest.raises(errors.SchemeValidationError):
|
||
|
opt.validate(value)
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'choices,value', [
|
||
|
(['one', 'two'], 'one'), # choices can be lists
|
||
|
(('one', 'two'), 'one'), # choices can be tuples
|
||
|
([1, 2, 3], 1),
|
||
|
((1, 2, 3), 1),
|
||
|
([None], None),
|
||
|
((None,), None),
|
||
|
([1.21, 1.22, 1.23], 1.23),
|
||
|
((1.21, 1.22, 1.23), 1.23),
|
||
|
([True, False], False),
|
||
|
((True, False), False)
|
||
|
]
|
||
|
)
|
||
|
def test_validate_choices_ok(self, choices, value):
|
||
|
"""Validate an Option, where choice validation succeeds"""
|
||
|
opt = scheme.Option('test-option', choices=choices)
|
||
|
opt.validate(value)
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'choices,value', [
|
||
|
(['one', 'two'], 'three'),
|
||
|
([1, 2, 3], 0),
|
||
|
([], None),
|
||
|
([0.2, 0.3, 0.4], 0.1),
|
||
|
([False], True)
|
||
|
]
|
||
|
)
|
||
|
def test_validate_choices_failure(self, choices, value):
|
||
|
"""Validate an Option, where choice validation succeeds"""
|
||
|
opt = scheme.Option('test-option', choices=choices)
|
||
|
with pytest.raises(errors.SchemeValidationError):
|
||
|
opt.validate(value)
|
||
|
|
||
|
|
||
|
class TestDictOption:
|
||
|
"""Tests for the `DictOption` class."""
|
||
|
|
||
|
def test_init_simple(self):
|
||
|
"""Initialize a DictOption."""
|
||
|
opt = scheme.DictOption('test-opt', None)
|
||
|
|
||
|
assert opt.name == 'test-opt'
|
||
|
assert type(opt.default) == scheme.NoDefault
|
||
|
assert opt.scheme is None
|
||
|
assert opt.bind_env is False
|
||
|
|
||
|
def test_init_full(self):
|
||
|
"""Initialize a DictOption."""
|
||
|
opt = scheme.DictOption(
|
||
|
name='test-opt',
|
||
|
scheme=scheme.Scheme(),
|
||
|
default='foo',
|
||
|
bind_env=True
|
||
|
)
|
||
|
|
||
|
assert opt.name == 'test-opt'
|
||
|
assert type(opt.default) != scheme.NoDefault
|
||
|
assert opt.default == 'foo'
|
||
|
assert isinstance(opt.scheme, scheme.Scheme)
|
||
|
assert opt.bind_env is True
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'value', [
|
||
|
'foo',
|
||
|
1,
|
||
|
1.234,
|
||
|
False,
|
||
|
True,
|
||
|
None,
|
||
|
[1, 2, 3],
|
||
|
['a', 'b', 'c'],
|
||
|
[{'a': 1}, {'b': 2}],
|
||
|
('foo', 'bar'),
|
||
|
{1, 2, 3}
|
||
|
]
|
||
|
)
|
||
|
def test_validate_bad_data(self, value):
|
||
|
"""Validate a DictOption where the given value is not a dict"""
|
||
|
opt = scheme.DictOption('test-opt', scheme.Scheme())
|
||
|
with pytest.raises(errors.SchemeValidationError):
|
||
|
opt.validate(value)
|
||
|
|
||
|
def test_validate_no_scheme(self):
|
||
|
"""Validate a DictOption with no scheme"""
|
||
|
opt = scheme.DictOption('test-opt', None)
|
||
|
opt.validate({'foo': 'bar'})
|
||
|
|
||
|
def test_validate_with_scheme(self):
|
||
|
"""Validate a DictOption with a scheme"""
|
||
|
opt = scheme.DictOption('test-opt', scheme.Scheme(
|
||
|
scheme.Option('foo', field_type=str)
|
||
|
))
|
||
|
opt.validate({'foo': 'bar'})
|
||
|
|
||
|
|
||
|
class TestListOption:
|
||
|
"""Tests for the `ListOption` class."""
|
||
|
|
||
|
def test_init_simple(self):
|
||
|
"""Initialize a ListOption."""
|
||
|
opt = scheme.ListOption('test-opt')
|
||
|
|
||
|
assert opt.name == 'test-opt'
|
||
|
assert type(opt.default) == scheme.NoDefault
|
||
|
assert opt.member_type is None
|
||
|
assert opt.member_scheme is None
|
||
|
assert opt.bind_env is False
|
||
|
|
||
|
def test_init_full(self):
|
||
|
"""Initialize a ListOption."""
|
||
|
opt = scheme.ListOption(
|
||
|
name='test-opt',
|
||
|
default='foo',
|
||
|
member_type=dict,
|
||
|
member_scheme=scheme.Scheme(),
|
||
|
bind_env=True
|
||
|
)
|
||
|
|
||
|
assert opt.name == 'test-opt'
|
||
|
assert type(opt.default) != scheme.NoDefault
|
||
|
assert opt.default == 'foo'
|
||
|
assert opt.member_type == dict
|
||
|
assert isinstance(opt.member_scheme, scheme.Scheme)
|
||
|
assert opt.bind_env is True
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'value', [
|
||
|
'foo',
|
||
|
1,
|
||
|
1.234,
|
||
|
False,
|
||
|
True,
|
||
|
None,
|
||
|
{'a': 1, 'b': 2},
|
||
|
('foo', 'bar'),
|
||
|
{1, 2, 3}
|
||
|
]
|
||
|
)
|
||
|
def test_validate_bad_data(self, value):
|
||
|
"""Validate when the value is not a list"""
|
||
|
opt = scheme.ListOption('test-opt')
|
||
|
with pytest.raises(errors.SchemeValidationError):
|
||
|
opt.validate(value)
|
||
|
|
||
|
def test_validate_member_type_scheme_conflict(self):
|
||
|
"""Validate the ListOption when both member_type and member_scheme are defined."""
|
||
|
opt = scheme.ListOption(
|
||
|
name='test-opt',
|
||
|
member_type=int,
|
||
|
member_scheme=scheme.Scheme()
|
||
|
)
|
||
|
with pytest.raises(errors.SchemeValidationError):
|
||
|
opt.validate([1, 2, 3])
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'member_type,value', [
|
||
|
(str, ['a', 'b', 'c']),
|
||
|
(int, [1, 2, 3]),
|
||
|
(float, [1.0, 2.0, 3.0]),
|
||
|
(bool, [False, False, True]),
|
||
|
(tuple, [(1,), (2,), (3,)]),
|
||
|
(list, [[1], [2], [3]]),
|
||
|
(dict, [{'a': 1, 'b': 2}])
|
||
|
]
|
||
|
)
|
||
|
def test_validate_member_type_ok(self, member_type, value):
|
||
|
"""Validate the ListOption, where member_type validation succeeds."""
|
||
|
opt = scheme.ListOption('test-opt', member_type=member_type)
|
||
|
opt.validate(value)
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'member_type,value', [
|
||
|
(str, ['a', 1]),
|
||
|
(str, [1, 2]),
|
||
|
(int, [1, 2, '3']),
|
||
|
(int, ['foo', 'bar']),
|
||
|
(float, [1.0, '2.0', 3.0]),
|
||
|
(float, ['foo', 'bar']),
|
||
|
(bool, ['False', False, True]),
|
||
|
(bool, ['foo', 'bar']),
|
||
|
(tuple, [(1,), '(2,)', (3,)]),
|
||
|
(tuple, ['']),
|
||
|
(list, [[1], (2,), [3]]),
|
||
|
(list, ['foo', 'bar']),
|
||
|
(dict, [{'a': 1}, {1, 2, 3}]),
|
||
|
(dict, ['foo', 'bar'])
|
||
|
]
|
||
|
)
|
||
|
def test_validate_member_type_failure(self, member_type, value):
|
||
|
"""Validate the ListOption, where member_type validation fails."""
|
||
|
opt = scheme.ListOption('test-opt', member_type=member_type)
|
||
|
with pytest.raises(errors.SchemeValidationError):
|
||
|
opt.validate(value)
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'member_scheme,value', [
|
||
|
# an empty scheme will validate every dict as correct
|
||
|
(scheme.Scheme(), [{'foo': 'bar'}]),
|
||
|
(scheme.Scheme(), [{1: 3}]),
|
||
|
(scheme.Scheme(), [{1.23: 2.31}]),
|
||
|
(scheme.Scheme(), [{False: True}]),
|
||
|
(scheme.Scheme(), [{None: None}]),
|
||
|
(scheme.Scheme(), [{(1, 2): (2, 1)}]),
|
||
|
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': 'bar'}]),
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': 'baz'}]),
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': 'baz'}, {'foo': 'bar'}])
|
||
|
]
|
||
|
)
|
||
|
def test_validate_member_scheme_ok(self, member_scheme, value):
|
||
|
"""Validate the ListOption, where member_scheme validation succeeds."""
|
||
|
opt = scheme.ListOption('test-opt', member_scheme=member_scheme)
|
||
|
opt.validate(value)
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'member_scheme,value', [
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': 1}]),
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': 1.23}]),
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': False}]),
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': True}]),
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': None}]),
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': (1, 2)}]),
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': ['a', 'b']}]),
|
||
|
(scheme.Scheme(scheme.Option('foo', field_type=str)), [{'foo': {'a', 'b'}}]),
|
||
|
|
||
|
(scheme.Scheme(scheme.Option('bar', field_type=int)), [{'bar': 'foo'}]),
|
||
|
(scheme.Scheme(scheme.Option('bar', field_type=int)), [{'bar': 1.23}]),
|
||
|
(scheme.Scheme(scheme.Option('bar', field_type=int)), [{'bar': False}]),
|
||
|
(scheme.Scheme(scheme.Option('bar', field_type=int)), [{'bar': True}]),
|
||
|
(scheme.Scheme(scheme.Option('bar', field_type=int)), [{'bar': None}]),
|
||
|
(scheme.Scheme(scheme.Option('bar', field_type=int)), [{'bar': (1, 2)}]),
|
||
|
(scheme.Scheme(scheme.Option('bar', field_type=int)), [{'bar': ['a', 'b']}]),
|
||
|
(scheme.Scheme(scheme.Option('bar', field_type=int)), [{'bar': {'a', 'b'}}])
|
||
|
]
|
||
|
)
|
||
|
def test_validate_member_scheme_fail(self, member_scheme, value):
|
||
|
"""Validate the ListOption, where member_scheme validation fails."""
|
||
|
opt = scheme.ListOption('test-opt', member_scheme=member_scheme)
|
||
|
with pytest.raises(errors.SchemeValidationError):
|
||
|
opt.validate(value)
|
||
|
|
||
|
def test_validate_member_scheme_not_a_scheme(self):
|
||
|
"""Validate the ListOption, where the member_scheme is not a Scheme."""
|
||
|
opt = scheme.ListOption('test-opt', member_scheme='not-none-or-scheme')
|
||
|
with pytest.raises(errors.SchemeValidationError):
|
||
|
opt.validate(['a', 'b', 'c'])
|
||
|
|
||
|
|
||
|
class TestScheme:
|
||
|
"""Tests for the `Scheme` class."""
|
||
|
|
||
|
def test_empty_init(self):
|
||
|
"""Initialize a Scheme with no arguments."""
|
||
|
sch = scheme.Scheme()
|
||
|
|
||
|
assert len(sch.args) == 0
|
||
|
assert sch._flat is None
|
||
|
|
||
|
def test_single_arg_init(self):
|
||
|
"""Initialize a Scheme with one argument."""
|
||
|
sch = scheme.Scheme(
|
||
|
'item'
|
||
|
)
|
||
|
|
||
|
assert len(sch.args) == 1
|
||
|
assert sch._flat is None
|
||
|
|
||
|
def test_multi_arg_init(self):
|
||
|
"""Initialize a Scheme with multiple arguments."""
|
||
|
sch = scheme.Scheme(
|
||
|
'item-1',
|
||
|
'item-2',
|
||
|
'item-3'
|
||
|
)
|
||
|
|
||
|
assert len(sch.args) == 3
|
||
|
assert sch._flat is None
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'args,expected', [
|
||
|
(
|
||
|
# args
|
||
|
(scheme.Option('foo', default='bar'),),
|
||
|
# expected
|
||
|
{'foo': 'bar'}
|
||
|
),
|
||
|
(
|
||
|
# args
|
||
|
(
|
||
|
scheme.Option('foo'),
|
||
|
scheme.Option('bar', default='baz'),
|
||
|
scheme.ListOption('list', default=['a', 'b'])
|
||
|
),
|
||
|
# expected
|
||
|
{
|
||
|
'bar': 'baz',
|
||
|
'list': ['a', 'b']
|
||
|
}
|
||
|
),
|
||
|
(
|
||
|
# args
|
||
|
(
|
||
|
scheme.DictOption('foo', scheme=scheme.Scheme(), default={}),
|
||
|
scheme.DictOption('bar', scheme=scheme.Scheme(
|
||
|
scheme.Option('test', default=True),
|
||
|
scheme.Option('data', default=None),
|
||
|
scheme.Option('value', default=20),
|
||
|
scheme.Option('float', default=10.1010),
|
||
|
scheme.Option('no_default'),
|
||
|
scheme.DictOption('dct', scheme=scheme.Scheme(
|
||
|
scheme.Option('nested', default='here')
|
||
|
))
|
||
|
))
|
||
|
),
|
||
|
# expected
|
||
|
{
|
||
|
'foo': {},
|
||
|
'bar': {
|
||
|
'test': True,
|
||
|
'data': None,
|
||
|
'value': 20,
|
||
|
'float': 10.1010,
|
||
|
'dct': {
|
||
|
'nested': 'here'
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
),
|
||
|
]
|
||
|
)
|
||
|
def test_build_defaults(self, args, expected):
|
||
|
"""Build a defaults dict from a Scheme."""
|
||
|
sch = scheme.Scheme(*args)
|
||
|
defaults = sch.build_defaults()
|
||
|
|
||
|
assert defaults == expected
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'args', [
|
||
|
('a', 'b'), # not an instance of _BaseOpt
|
||
|
]
|
||
|
)
|
||
|
def test_build_defaults_failure(self, args):
|
||
|
"""Build a defaults dict from a Scheme with bad data."""
|
||
|
sch = scheme.Scheme(*args)
|
||
|
with pytest.raises(errors.InvalidSchemeError):
|
||
|
sch.build_defaults()
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'args,expected', [
|
||
|
(
|
||
|
(scheme.Option('foo'),),
|
||
|
['foo']
|
||
|
),
|
||
|
(
|
||
|
(scheme.Option('foo'), scheme.Option('bar')),
|
||
|
['foo', 'bar']
|
||
|
),
|
||
|
(
|
||
|
(
|
||
|
scheme.Option('foo'),
|
||
|
scheme.DictOption('bar', scheme=scheme.Scheme(
|
||
|
scheme.Option('test'),
|
||
|
scheme.DictOption('dct', scheme=scheme.Scheme(
|
||
|
scheme.Option('nested')
|
||
|
)),
|
||
|
scheme.ListOption('list')
|
||
|
))
|
||
|
),
|
||
|
['foo', 'bar', 'bar.test', 'bar.dct', 'bar.list', 'bar.dct.nested']
|
||
|
)
|
||
|
]
|
||
|
)
|
||
|
def test_flatten(self, args, expected):
|
||
|
"""Flatten a Scheme."""
|
||
|
sch = scheme.Scheme(*args)
|
||
|
flattened = sch.flatten()
|
||
|
|
||
|
assert len(flattened) == len(expected)
|
||
|
for key in expected:
|
||
|
assert key in flattened
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'args,value', [
|
||
|
(
|
||
|
# option exists in config
|
||
|
(scheme.Option('foo', default='bar', field_type=str),),
|
||
|
{'foo': 'baz'}
|
||
|
),
|
||
|
(
|
||
|
# option does not exist in config, but has default
|
||
|
(scheme.Option('foo', default='bar', field_type=str),),
|
||
|
{}
|
||
|
),
|
||
|
(
|
||
|
# multiple args
|
||
|
(
|
||
|
scheme.Option('foo', field_type=str),
|
||
|
scheme.Option('bar', field_type=int),
|
||
|
scheme.Option('baz', choices=['test'])
|
||
|
),
|
||
|
{'foo': 'a', 'bar': 1, 'baz': 'test'}
|
||
|
)
|
||
|
]
|
||
|
)
|
||
|
def test_validate_ok(self, args, value):
|
||
|
"""Validate a Scheme successfully."""
|
||
|
sch = scheme.Scheme(*args)
|
||
|
sch.validate(value)
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'args,value', [
|
||
|
(
|
||
|
# option does not exist in config, no default
|
||
|
(scheme.Option('foo', field_type=str),),
|
||
|
{}
|
||
|
),
|
||
|
(
|
||
|
# option exists in config, fails validation
|
||
|
(scheme.Option('foo', default='bar', field_type=str),),
|
||
|
{'foo': 1}
|
||
|
),
|
||
|
(
|
||
|
# multiple args, one fails validation
|
||
|
(
|
||
|
scheme.Option('foo', field_type=str),
|
||
|
scheme.Option('bar', field_type=int),
|
||
|
scheme.Option('baz', choices=['test'])
|
||
|
),
|
||
|
{'foo': 'a', 'bar': 1, 'baz': 'something'}
|
||
|
)
|
||
|
]
|
||
|
)
|
||
|
def test_validate_failure(self, args, value):
|
||
|
"""Validate a Scheme unsuccessfully."""
|
||
|
sch = scheme.Scheme(*args)
|
||
|
with pytest.raises(errors.SchemeValidationError):
|
||
|
sch.validate(value)
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'value', [
|
||
|
'foo',
|
||
|
1,
|
||
|
1.23,
|
||
|
['a', 'b', 'c'],
|
||
|
{'a', 'b', 'c'},
|
||
|
('a', 'b', 'c'),
|
||
|
None,
|
||
|
False,
|
||
|
True
|
||
|
]
|
||
|
)
|
||
|
def test_validate_failure_bad_config(self, value):
|
||
|
"""Validate a Scheme where the given config is not a dict."""
|
||
|
sch = scheme.Scheme()
|
||
|
with pytest.raises(errors.SchemeValidationError):
|
||
|
sch.validate(value)
|