"""Unit tests for bison.bison""" import os import pytest import bison from bison import errors from bison.bison import YAML class TestBison: """Test for the `Bison` class.""" def test_simple_init(self): """Initialize a Bison object.""" b = bison.Bison() assert b.scheme is None assert b.config_name == 'config' assert b.config_paths == [] assert b.config_file is None assert b.env_prefix is None assert b.auto_env is False assert b._full_config is None assert isinstance(b._default, bison.DotDict) assert isinstance(b._config, bison.DotDict) assert isinstance(b._environment, bison.DotDict) assert isinstance(b._override, bison.DotDict) assert len(b._default) == 0 assert len(b._config) == 0 assert len(b._environment) == 0 assert len(b._override) == 0 def test_config_property_empty(self): """Get the full configuration when nothing is set.""" b = bison.Bison() assert b._full_config is None c = b.config assert isinstance(c, bison.DotDict) assert len(c) == 0 assert b._full_config == c @pytest.mark.parametrize( 'key,expected,config', [ ('foo', None, None), ('foo', None, bison.DotDict()), ('foo', None, bison.DotDict({'foo': None})), ('foo', 'bar', bison.DotDict({'foo': 'bar'})), ('foo.bar', 'baz', bison.DotDict({'foo': {'bar': 'baz'}})), ('foo.bar.baz', 1, bison.DotDict({'foo': {'bar': {'baz': 1}}})), ] ) def test_get(self, key, expected, config): """Get configuration values from Bison.""" b = bison.Bison() b._full_config = config # for the test, set the config manually value = b.get(key) assert value == expected @pytest.mark.parametrize( 'key,default,expected,config', [ ('foo', 7, 7, None), ('foo', None, None, bison.DotDict()), ('foo', 'bar', 'bar', bison.DotDict()), ('foo', 'bar', None, bison.DotDict({'foo': None})), ('foo', 'bar', 'test', bison.DotDict({'foo': 'test'})), ('foo.bar', 7, 'baz', bison.DotDict({'foo': {'bar': 'baz'}})), ('foo.bar', 7, 7, bison.DotDict({'foo': {'something': 'else'}})), ] ) def test_get_with_defaults(self, key, default, expected, config): """Get config values from Bison, using defaults if the key is not set.""" b = bison.Bison() b._full_config = config # for the test, set the config manually value = b.get(key, default=default) assert value == expected @pytest.mark.parametrize( 'key,expected,config', [ ('foo', None, None), ('foo', None, bison.DotDict()), ('foo', None, bison.DotDict({'foo': None})), ('foo', 'bar', bison.DotDict({'foo': 'bar'})), ('foo.bar', 'baz', bison.DotDict({'foo': {'bar': 'baz'}})), ('foo.bar.baz', 1, bison.DotDict({'foo': {'bar': {'baz': 1}}})), ] ) def test_get_subscriptable(self, key, expected, config): """Get config values from Bison via subscripting.""" b = bison.Bison() b._full_config = config # for the test, set the config manually value = b[key] assert value == expected @pytest.mark.parametrize( 'key,value', [ ('foo', 'bar'), ('foo', 1), ('foo', False), ('foo', True), ('foo', None), ('foo.bar', 'baz'), ('foo.bar.baz', 1), ] ) def test_set(self, key, value): """Set configuration overrides for a Bison instance.""" b = bison.Bison() assert len(b._override) == 0 assert b.get(key) is None b.set(key, value) assert len(b._override) == 1 assert b.get(key) == value def test_set_multiple_nested(self): """Set overrides for multiple nested values""" b = bison.Bison() assert len(b._override) == 0 assert len(b.config) == 0 b.set('foo.bar.a', 'test') assert b.config == { 'foo': { 'bar': { 'a': 'test' } } } b.set('foo.bar.b', 'test') assert b.config == { 'foo': { 'bar': { 'a': 'test', 'b': 'test' } } } def test_set_multiple_nested_2(self): """Set overrides for multiple nested values when some already exist.""" b = bison.Bison() assert len(b._override) == 0 assert len(b.config) == 0 # set the override config config to something to begin b._override = bison.DotDict({ 'foo': 'bar', 'bar': { 'bat': { 'a': 'test' }, 'bird': { 'b': 'test' } } }) b.set('foo', 'baz') assert b.config == { 'foo': 'baz', 'bar': { 'bat': { 'a': 'test' }, 'bird': { 'b': 'test' } } } b.set('bar.bat.a', None) assert b.config == { 'foo': 'baz', 'bar': { 'bat': { 'a': None }, 'bird': { 'b': 'test' } } } b.set('bar.bird', 'warbler') assert b.config == { 'foo': 'baz', 'bar': { 'bat': { 'a': None }, 'bird': 'warbler' } } def test_set_multiple_nested_3(self): """Set overrides for multiple nested values when some already exist.""" b = bison.Bison() assert len(b._override) == 0 assert len(b.config) == 0 # set a non override config config to something to begin b._config = bison.DotDict({ 'foo': 'bar', 'bar': { 'bat': { 'a': 'test' }, 'bird': { 'b': 'test' } } }) b.set('foo', 'baz') assert b.config == { 'foo': 'baz', 'bar': { 'bat': { 'a': 'test' }, 'bird': { 'b': 'test' } } } b.set('bar.bat.a', None) assert b.config == { 'foo': 'baz', 'bar': { 'bat': { 'a': None }, 'bird': { 'b': 'test' } } } b.set('bar.bird', 'warbler') assert b.config == { 'foo': 'baz', 'bar': { 'bat': { 'a': None }, 'bird': 'warbler' } } @pytest.mark.parametrize( 'paths', [ (), ('path1',), ('path1', 'path2'), ('path1', 'path2', 'path3') ] ) def test_add_config_paths(self, paths): """Add configuration paths to the Bison instance.""" b = bison.Bison() assert len(b.config_paths) == 0 b.add_config_paths(*paths) assert len(b.config_paths) == len(paths) def test_validate_no_scheme(self): """Validate the Bison configuration when there is no Scheme to validate against.""" b = bison.Bison() b.set('foo', 'bar') b.validate() def test_validate_ok(self): """Validate the Bison configuration successfully.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', field_type=str) )) # add 'foo' to the config b.set('foo', 'bar') # validation should succeed -- the value of 'foo' is a string b.validate() def test_validate_fail(self): """Validate the Bison configuration unsuccessfully.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', field_type=str) )) # add 'foo' to the config b.set('foo', 1) # validation should fail -- the value of 'foo' is not a string with pytest.raises(errors.SchemeValidationError): b.validate() def test_find_config(self, yaml_config): """Find a config file when it does exist""" b = bison.Bison() b.config_name = 'config' b.config_format = YAML b.config_paths = ['.', yaml_config.dirname] assert b.config_file is None b._find_config() assert b.config_file == os.path.join(yaml_config.dirname, yaml_config.basename) def test_find_config_nonexistent(self): """Find a config file when it does not exist""" b = bison.Bison() b.config_name = 'config' b.config_format = YAML b.config_paths = ['.'] assert b.config_file is None with pytest.raises(errors.BisonError): b._find_config() def test_parse_no_sources(self): """Parse when there are no sources to parse from.""" b = bison.Bison() b.parse() assert len(b.config) == 0 def test_parse_config_no_paths(self): """Parse the file config when no paths are specified""" b = bison.Bison() assert b.config_file is None assert len(b._config) == 0 b._parse_config() assert b.config_file is None assert len(b._config) == 0 def test_parse_config_ok(self, yaml_config): """Parse the file config successfully.""" b = bison.Bison() b.add_config_paths(yaml_config.dirname) assert b.config_file is None assert len(b._config) == 0 b._parse_config() assert b.config_file == os.path.join(yaml_config.dirname, yaml_config.basename) assert len(b._config) == 2 assert b.config == { 'foo': True, 'bar': { 'baz': 1, 'test': 'value' } } def test_parse_config_fail(self, bad_yaml_config): """Parse the file config unsuccessfully.""" b = bison.Bison() b.add_config_paths(bad_yaml_config.dirname) assert b.config_file is None assert len(b._config) == 0 with pytest.raises(errors.BisonError): b._parse_config() assert b.config_file == os.path.join(bad_yaml_config.dirname, bad_yaml_config.basename) assert len(b._config) == 0 def test_parse_config_not_required_found(self, yaml_config): """Parse the file config when it isn't required.""" b = bison.Bison() b.add_config_paths(yaml_config.dirname) assert b.config_file is None assert len(b._config) == 0 b._parse_config(requires_cfg=False) assert b.config_file == os.path.join(yaml_config.dirname, yaml_config.basename) assert len(b._config) == 2 assert b.config == { 'foo': True, 'bar': { 'baz': 1, 'test': 'value' } } def test_parse_config_not_required_not_found(self): """Parse the file config when it isn't required.""" b = bison.Bison() b.add_config_paths('.') assert b.config_file is None assert len(b._config) == 0 b._parse_config(requires_cfg=False) assert b.config_file is None assert len(b._config) == 0 def test_parse_defaults_no_scheme(self): """Parse the defaults when there is no Scheme.""" b = bison.Bison() assert len(b._default) == 0 assert b._full_config is None b._parse_default() assert len(b._default) == 0 assert b._full_config is None def test_parse_defaults_ok(self): """Parse the defaults successfully.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', default='bar') )) assert len(b._default) == 0 assert len(b.config) == 0 b._parse_default() assert len(b._default) == 1 assert b.config == {'foo': 'bar'} def test_parse_env_env_prefix(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison() b.env_prefix = 'TEST_ENV_' b.auto_env = True assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() # no scheme means nothing parsed. assert len(b._environment) == 0 assert len(b.config) == 0 def test_parse_env_env_prefix2(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison() # here, do not include the trailing '_', this should be added # on automatically if not there. b.env_prefix = 'TEST_ENV' b.auto_env = True assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() # no scheme means nothing parsed. assert len(b._environment) == 0 assert len(b.config) == 0 def test_parse_env_bind_env(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env='FOO') )) b.env_prefix = 'TEST_ENV' assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() # we should get nothing back here -- env parsing will NOT # use the env_prefix when bind_env is specified manually. assert len(b._environment) == 0 assert len(b.config) == 0 def test_parse_env_bind_env_no_prefix(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env='TEST_ENV_FOO') )) assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() assert len(b._environment) == 1 assert b.config == { 'foo': 'bar' } def test_parse_env_bind_env_auto(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( # this will autoenv to TEST_ENV_FOO, so we set to something # different here to test that it a.) works, b.) overrides autoenv bison.Option('foo', bind_env='TEST_OTHER_ENV_BAR'), bison.DictOption('nested', scheme=bison.Scheme( bison.DictOption('env', scheme=bison.Scheme( bison.Option('key') )) )) )) b.env_prefix = 'TEST_ENV' b.auto_env = True assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() assert len(b._environment) == 2 assert b.config == { 'foo': 'baz', 'nested': { 'env': { 'key': 'test' } } } def test_parse_env_bind_env_false(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=False) )) b.env_prefix = 'TEST_ENV' assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() # we should get nothing back here -- nothing configured for # env parsing assert len(b._environment) == 0 assert len(b.config) == 0 def test_parse_env_bind_env_false_no_prefix(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=False) )) assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() # we should get nothing back here -- nothing configured for # env parsing assert len(b._environment) == 0 assert len(b.config) == 0 def test_parse_env_bind_env_false_auto(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=False) )) b.env_prefix = 'TEST_ENV' b.auto_env = True assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() # we should not get back the 'foo' key since its disabled, # even with auto-env assert len(b._environment) == 0 assert len(b.config) == 0 def test_parse_env_bind_env_true(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=True) )) b.env_prefix = 'TEST_ENV' assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() assert len(b._environment) == 1 assert b.config == { 'foo': 'bar' } def test_parse_env_bind_env_true_no_prefix(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=True) )) assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() # we should not get anything back since we don't have a 'FOO' env variable assert len(b._environment) == 0 assert len(b.config) == 0 def test_parse_env_bind_env_true_auto(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=True), bison.DictOption('nested', scheme=bison.Scheme( bison.DictOption('env', scheme=bison.Scheme( bison.Option('key') )) )) )) b.env_prefix = 'TEST_ENV' b.auto_env = True assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() assert len(b._environment) == 2 assert b.config == { 'foo': 'bar', 'nested': { 'env': { 'key': 'test' } } } def test_parse_env_int_value(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env='FOO_INT', field_type=int) )) assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() assert len(b._environment) == 1 assert b.config == { 'foo': 1, } def test_parse_env_bool_value(self, with_env): """Parse the environment variable configuration.""" b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env='FOO_BOOL', field_type=bool) )) assert len(b._environment) == 0 assert len(b.config) == 0 b._parse_env() assert len(b._environment) == 1 assert b.config == { 'foo': False, } def test_parse_validate_nested_optional(self, yaml_optional_nested): """Parse a config for a scheme with a non-required option with required nested options where the non-required option is not set. """ b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=True), bison.DictOption('nested2', required=False, bind_env=True, scheme=bison.Scheme( bison.Option('x', field_type=str), bison.Option('y', field_type=str) )) )) b.add_config_paths(yaml_optional_nested.dirname) assert b.config_file is None assert len(b._config) == 0 b.parse(requires_cfg=False) assert b.config_file == os.path.join(yaml_optional_nested.dirname, yaml_optional_nested.basename) assert len(b._config) == 2 assert b.config == { 'foo': True, 'nested1': { 'x': 'abc', 'y': 'def' } } b.validate() def test_parse_validate_nested_optional2(self, yaml_optional_nested): """Parse a config for a scheme with a non-required option with required nested options where the non-required option is set. """ b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=True), bison.DictOption('nested1', required=False, bind_env=True, scheme=bison.Scheme( bison.Option('x', field_type=str), bison.Option('y', field_type=str) )) )) b.add_config_paths(yaml_optional_nested.dirname) assert b.config_file is None assert len(b._config) == 0 b.parse(requires_cfg=False) assert b.config_file == os.path.join(yaml_optional_nested.dirname, yaml_optional_nested.basename) assert len(b._config) == 2 assert b.config == { 'foo': True, 'nested1': { 'x': 'abc', 'y': 'def' } } b.validate() def test_parse_validate_nested_optional3(self, yaml_optional_nested): """Parse a config for a scheme with a non-required option with required nested options where the non-required option is not set, and the required options have defaults. """ b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=True), bison.DictOption('nested2', required=False, bind_env=True, scheme=bison.Scheme( bison.Option('x', default="abc", field_type=str), bison.Option('y', default="def", field_type=str) )) )) b.add_config_paths(yaml_optional_nested.dirname) assert b.config_file is None assert len(b._config) == 0 b.parse(requires_cfg=False) assert b.config_file == os.path.join(yaml_optional_nested.dirname, yaml_optional_nested.basename) assert len(b._config) == 2 assert b.config == { 'foo': True, 'nested1': { 'x': 'abc', 'y': 'def' }, 'nested2': { # defaults get picked up 'x': 'abc', 'y': 'def' } } b.validate() def test_parse_validate_nested_optional4(self, yaml_optional_nested): """Parse a config for a scheme with a non-required option with required nested options where the non-required option is not set and it has a default value. """ b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=True), bison.DictOption('nested2', required=False, default={}, bind_env=True, scheme=bison.Scheme( bison.Option('x', field_type=str), bison.Option('y', field_type=str) )) )) b.add_config_paths(yaml_optional_nested.dirname) assert b.config_file is None assert len(b._config) == 0 b.parse(requires_cfg=False) assert b.config_file == os.path.join(yaml_optional_nested.dirname, yaml_optional_nested.basename) assert len(b._config) == 2 assert b.config == { 'foo': True, 'nested1': { 'x': 'abc', 'y': 'def' }, 'nested2': {} # default gets picked up } # validation should fail -- the default value gets added, but the required # options are not set in the default with pytest.raises(errors.SchemeValidationError): b.validate() def test_parse_validate_nested_optional5(self, yaml_optional_nested): """Parse a config for a scheme with a non-required option with required nested options where the non-required option is not set and it has a default value. """ b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=True), bison.DictOption('nested2', required=False, default={'x': 'ghi', 'y': 'jkl'}, bind_env=True, scheme=bison.Scheme( bison.Option('x', field_type=str), bison.Option('y', field_type=str) )) )) b.add_config_paths(yaml_optional_nested.dirname) assert b.config_file is None assert len(b._config) == 0 b.parse(requires_cfg=False) assert b.config_file == os.path.join(yaml_optional_nested.dirname, yaml_optional_nested.basename) assert len(b._config) == 2 assert b.config == { 'foo': True, 'nested1': { 'x': 'abc', 'y': 'def' }, 'nested2': { # default gets picked up 'x': 'ghi', 'y': 'jkl' } } b.validate() def test_parse_validate_nested_optional6(self, yaml_optional_nested): """Parse a config for a scheme with a non-required option with required nested options where the non-required option is not set and it has a default value. This test is the same as the one above, but the defaults are specified differently. """ b = bison.Bison(scheme=bison.Scheme( bison.Option('foo', bind_env=True), bison.DictOption('nested2', required=False, default={}, bind_env=True, scheme=bison.Scheme( bison.Option('x', default='ghi', field_type=str), bison.Option('y', default='jkl', field_type=str) )) )) b.add_config_paths(yaml_optional_nested.dirname) assert b.config_file is None assert len(b._config) == 0 b.parse(requires_cfg=False) assert b.config_file == os.path.join(yaml_optional_nested.dirname, yaml_optional_nested.basename) assert len(b._config) == 2 assert b.config == { 'foo': True, 'nested1': { 'x': 'abc', 'y': 'def' }, 'nested2': { # default gets picked up 'x': 'ghi', 'y': 'jkl' } } b.validate()