diff options
author | Matthias Baumgartner <dev@igsor.net> | 2023-01-06 12:20:22 +0100 |
---|---|---|
committer | Matthias Baumgartner <dev@igsor.net> | 2023-01-06 12:20:22 +0100 |
commit | 079b4da93ea336b5bcc801cfd64c310aa7f8ddee (patch) | |
tree | 9c9a1cf7cbb9d71ba8dcce395996a1af3db790e2 /test | |
parent | 0ba7a15c124d3a738a45247e78381dd56f7f1fa9 (diff) | |
download | tagit-079b4da93ea336b5bcc801cfd64c310aa7f8ddee.tar.gz tagit-079b4da93ea336b5bcc801cfd64c310aa7f8ddee.tar.bz2 tagit-079b4da93ea336b5bcc801cfd64c310aa7f8ddee.zip |
config early port (test still fails)
Diffstat (limited to 'test')
-rw-r--r-- | test/__init__.py | 0 | ||||
-rw-r--r-- | test/config/__init__.py | 0 | ||||
-rw-r--r-- | test/config/test_schema.py | 284 | ||||
-rw-r--r-- | test/config/test_settings.py | 903 | ||||
-rw-r--r-- | test/config/test_types.py | 251 |
5 files changed, 1438 insertions, 0 deletions
diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/__init__.py diff --git a/test/config/__init__.py b/test/config/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/config/__init__.py diff --git a/test/config/test_schema.py b/test/config/test_schema.py new file mode 100644 index 0000000..9e3d3b7 --- /dev/null +++ b/test/config/test_schema.py @@ -0,0 +1,284 @@ +""" + +Part of the tagit test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import unittest + +# tagit imports +from tagit.config import types +from tagit.utils import errors + +# objects to test +from tagit.config.schema import ConfigSchema, ConfigKey, ConfigTitle, IncompatibleTypes + + +## code ## + +def _config_title_check(self, key0, key1): + self.assertEqual(key0, key1) + self.assertEqual(key0._title, key1._title) + self.assertEqual(key0._description, key1._description) + self.assertSetEqual(key0._modules, key1._modules) + +def _config_key_check(self, key0, key1): + self.assertEqual(key0, key1) + self.assertEqual(key0._title, key1._title) + self.assertEqual(key0._description, key1._description) + self.assertSetEqual(key0._modules, key1._modules) + self.assertSetEqual(key0._examples, key1._examples) + +class TestConfigSchema(unittest.TestCase): + def test_collection(self): + # getitem, call + # contains + # iter + # len + # keys + pass + + def test_title(self): + schema = ConfigSchema() + # recognize title + schema.declare_title(('some', 'key'), 'module', 'title', 'description') + schema.declare(('other', 'key'), types.Int(), 0, + 'module', 'title', 'description', 'example') + self.assertTrue(schema.is_title(('some', 'key'))) + self.assertFalse(schema.is_title(('other', 'key'))) + _config_title_check(self, schema.get_title(('some', 'key')), + ConfigTitle(('some', 'key'), 'title', 'module', 'description')) + self.assertRaises(KeyError, schema.get_title, ('other', 'key')) + # config takes precedence + schema.declare(('some', 'key'), types.Int(), 0, + 'module', 'title', 'description', 'example') + self.assertFalse(schema.is_title(('some', 'key'))) + # can still retrieve the title + _config_title_check(self, schema.get_title(('some', 'key')), + ConfigTitle(('some', 'key'), 'title', 'module', 'description')) + + def test_declare(self): + schema = ConfigSchema() + # keys can be declared + schema.declare(('some', 'key'), types.Int(), 0, + 'module', 'title', 'description', 'example') + self.assertSetEqual(set(schema.config.keys()), {('some', 'key')}) + _config_key_check(self, schema.config[('some', 'key')], + ConfigKey(('some', 'key'), types.Int(), 0, + 'module', 'title', 'description', 'example')) + # double insert with identical signature is accepted + schema.declare(('some', 'key'), types.Int(), 0, + 'module', 'title', 'description', 'example') + self.assertSetEqual(set(schema.config.keys()), {('some', 'key')}) + _config_key_check(self, schema.config[('some', 'key')], + ConfigKey(('some', 'key'), types.Int(), 0, + 'module', 'title', 'description', 'example')) + # additional info is accepted + schema.declare(('some', 'key'), types.Int(), 0, + 'other_module', 'other_title', 'other_description', 'other_example') + self.assertSetEqual(set(schema.config.keys()), {('some', 'key')}) + ck = ConfigKey(('some', 'key'), types.Int(), 0, + 'module', 'other_title', 'other_description', 'example') + ck._examples.add('other_example') + ck._modules.add('other_module') + _config_key_check(self, schema.config[('some', 'key')], ck) + + # empty key is rejected + self.assertRaises(errors.ProgrammingError, schema.declare, [], types.Int(), 0) + # empty type is rejected + self.assertRaises(AttributeError, schema.declare, ('foo', ), None, None) + # invalid defaults are rejected + self.assertRaises(errors.ProgrammingError, schema.declare, ('foo', ), types.Unsigned(), -1) + self.assertRaises(errors.ProgrammingError, schema.declare, ('foo', ), types.Int(), 'abc') + self.assertRaises(errors.ProgrammingError, schema.declare, ('foo', ), types.String(), 123) + self.assertRaises(errors.ProgrammingError, schema.declare, ('foo', ), types.Enum('foo'), 'bar') + # double insert with different signature is rejected + self.assertRaises(IncompatibleTypes, schema.declare, ('some', 'key'), types.Unsigned(), 0) + self.assertRaises(IncompatibleTypes, schema.declare, ('some', 'key'), types.Int(), 2) + + def test_declare_title(self): + schema = ConfigSchema() + # titles can be declared + schema.declare_title(('some', 'key'), 'module', 'title', 'description') + self.assertSetEqual(set(schema.titles.keys()), {('some', 'key')}) + _config_title_check(self, schema.titles[('some', 'key')], + ConfigTitle(('some', 'key'), + 'title', 'module', 'description')) + # double insert is accepted + schema.declare_title(('some', 'key'), 'module', 'title', 'description') + self.assertSetEqual(set(schema.titles.keys()), {('some', 'key')}) + _config_title_check(self, schema.titles[('some', 'key')], ConfigTitle(('some', 'key'), + 'title', 'module', 'description')) + # additional info is accepted + schema.declare_title(('some', 'key'), 'other_module', 'other_title', 'other_description') + self.assertSetEqual(set(schema.titles.keys()), {('some', 'key')}) + ck = ConfigTitle(('some', 'key'), 'other_title', 'module', 'other_description') + ck._modules.add('other_module') + _config_title_check(self, schema.titles[('some', 'key')], ck) + # empty key is rejected + self.assertRaises(errors.ProgrammingError, schema.declare_title, [], types.Int(), 0) + # title and config key can exist in parallel + schema.declare(('other', 'key'), types.Int(), 0) + self.assertSetEqual(set(schema.config.keys()), {('other', 'key')}) + _config_key_check(self, schema.config[('other', 'key')], + ConfigKey(('other', 'key'), types.Int(), 0)) + schema.declare_title(('other', 'key'), 'module', 'title', 'description') + self.assertIn(('other', 'key'), set(schema.titles.keys())) + _config_title_check(self, schema.titles[('other', 'key')], ConfigTitle(('other', 'key'), + 'title', 'module', 'description')) + + +class TestConfigTitle(unittest.TestCase): + def test_magicks(self): + ck = ConfigTitle(('some', 'key'), 'title', 'module', 'description') + # representation + self.assertEqual(repr(ck), "ConfigTitle(('some', 'key'), title)") + # comparison + self.assertEqual(ck, ConfigTitle(('some', 'key'))) + self.assertEqual(hash(ck), hash(ConfigTitle(('some', 'key')))) + self.assertNotEqual(ck, ConfigTitle(('other', 'key'))) + + def test_properties(self): + ck = ConfigTitle(('some', 'key'), 'title', 'module', 'some_description') + # key can't be overwritten + self.assertRaises(AttributeError, ck.__setattr__, 'key', ('other', 'key')) + # modules + ck.modules = 'other_module' + self.assertSetEqual(ck.modules, {'module', 'other_module'}) + ck.modules = None + self.assertSetEqual(ck.modules, {'module', 'other_module'}) + ck.modules = '' + self.assertSetEqual(ck.modules, {'module', 'other_module'}) + # title + ck.title = 'other_title' + self.assertEqual(ck.title, 'other_title') + ck.title = None + self.assertEqual(ck.title, 'other_title') + ck.title = '' + self.assertEqual(ck.title, 'other_title') + # description + ck.description = 'other_description' + self.assertEqual(ck.description, 'other_description') + ck.description = None + self.assertEqual(ck.description, 'other_description') + ck.description = '' + self.assertEqual(ck.description, 'other_description') + + def test_tree(self): + self.assertEqual(ConfigTitle(('some', 'path', 'to', 'a', 'key')).branch, + ('some', 'path', 'to', 'a')) + self.assertEqual(ConfigTitle(('some', 'path', 'to', 'a', 'key')).leaf, + 'key') + self.assertEqual(ConfigTitle(('somekey', )).branch, + tuple()) + self.assertEqual(ConfigTitle(('somekey', )).leaf, + 'somekey') + + +class TestConfigKey(unittest.TestCase): + def test_magicks(self): + ck = ConfigKey(('some', 'key'), types.Int(), 0, + 'module', 'title', 'some_description', 'some_example') + ck.example = 'other_example' + ck.modules = 'other_module' + # representation + self.assertEqual(repr(ck), "ConfigKey(('some', 'key'), Int, 0)") + # comparison + self.assertEqual(ck, ConfigKey(('some', 'key'), types.Int(), 0)) + self.assertEqual(hash(ck), hash(ConfigKey(('some', 'key'), types.Int(), 0))) + self.assertNotEqual(ck, ConfigKey(('some', 'key'), types.Int(), 1)) + self.assertNotEqual(ck, ConfigKey(('some', 'key'), types.Unsigned(), 0)) + self.assertNotEqual(ck, ConfigKey(('other', 'key'), types.Int(), 0)) + + def test_properties(self): + ck = ConfigKey(('some', 'key'), types.Int(), 0, + 'module', 'title', 'some_description', 'some_example') + self.assertEqual(ck.key, ('some', 'key')) + self.assertEqual(ck.type, types.Int()) + self.assertEqual(ck.default, 0) + self.assertSetEqual(ck.modules, {'module'}) + self.assertEqual(ck.title, 'title') + self.assertEqual(ck.description, 'some_description') + self.assertEqual(ck.example, 'some_example') + + # key, type, default can't be overwritten + self.assertRaises(AttributeError, ck.__setattr__, 'key', ('other', 'key')) + self.assertRaises(AttributeError, ck.__setattr__, 'type', types.Unsigned()) + self.assertRaises(AttributeError, ck.__setattr__, 'default', 123) + # modules + ck.modules = 'other_module' + self.assertSetEqual(ck.modules, {'module', 'other_module'}) + ck.modules = None + self.assertSetEqual(ck.modules, {'module', 'other_module'}) + ck.modules = '' + self.assertSetEqual(ck.modules, {'module', 'other_module'}) + # title + ck.title = 'other_title' + self.assertEqual(ck.title, 'other_title') + ck.title = None + self.assertEqual(ck.title, 'other_title') + ck.title = '' + self.assertEqual(ck.title, 'other_title') + # description + ck.description = 'other_description' + self.assertEqual(ck.description, 'other_description') + ck.description = None + self.assertEqual(ck.description, 'other_description') + ck.description = '' + self.assertEqual(ck.description, 'other_description') + # example + ck.example = 'other_example' + ex = ck.example + self.assertTrue('other_example' in ex) + self.assertTrue('some_example' in ex) + ck.example = None + self.assertEqual(ck.example, ex) + ck.example = '' + self.assertEqual(ck.example, ex) + # type example if unspecified + self.assertEqual(ConfigKey(('some', 'key'), types.Int(), 0).example, types.Int().example) + + def test_checks(self): + # check + self.assertTrue(ConfigKey(('some', 'key'), types.Int(), 0).check(0)) + self.assertFalse(ConfigKey(('some', 'key'), types.Int(), 0).check(1.23)) + self.assertFalse(ConfigKey(('some', 'key'), types.Int(), 0).check('foobar')) + self.assertTrue(ConfigKey(('some', 'key'), types.Unsigned(), 0).check(0)) + self.assertFalse(ConfigKey(('some', 'key'), types.Unsigned(), 0).check(-1)) + self.assertTrue(ConfigKey(('some', 'key'), types.String(), 0).check('foobar')) + self.assertFalse(ConfigKey(('some', 'key'), types.String(), 0).check(['foo', 'bar'])) + + # backtrack + self.assertTrue(ConfigKey(['somekey'], types.Int(), 0).backtrack(0)) + self.assertTrue(ConfigKey(['somekey'], types.Int(), 0).backtrack(-1)) + self.assertTrue(ConfigKey(['somekey'], types.Int(), 0).backtrack(123)) + self.assertRaises(types.ConfigTypeError, + ConfigKey(['somekey'], types.Int(), 0).backtrack, 1.23) + self.assertRaises(types.ConfigTypeError, + ConfigKey(['somekey'], types.Int(), 0).backtrack, 'foobar') + self.assertTrue(ConfigKey(['somekey'], types.Unsigned(), 0).backtrack(0)) + self.assertRaises(types.ConfigTypeError, + ConfigKey(['somekey'], types.Unsigned(), 0).backtrack, -1) + self.assertTrue(ConfigKey(['somekey'], types.String(), 0).backtrack('foobar')) + self.assertRaises(types.ConfigTypeError, + ConfigKey(['somekey'], types.String(), 0).backtrack, ['foo', 'bar']) + + def test_tree(self): + self.assertEqual(ConfigKey(('some', 'path', 'to', 'a', 'key'), types.Int(), 0).branch, + ('some', 'path', 'to', 'a')) + self.assertEqual(ConfigKey(('some', 'path', 'to', 'a', 'key'), types.Int(), 0).leaf, + 'key') + self.assertEqual(ConfigKey(('somekey', ), types.Int(), 0).branch, + tuple()) + self.assertEqual(ConfigKey(('somekey', ), types.Int(), 0).leaf, + 'somekey') + + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## diff --git a/test/config/test_settings.py b/test/config/test_settings.py new file mode 100644 index 0000000..d7ce7f8 --- /dev/null +++ b/test/config/test_settings.py @@ -0,0 +1,903 @@ +"""Test settings loading and access. + +Part of the tagit test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import io +import json +import os +import tempfile +import unittest + +# tagit imports +from tagit.config import Settings, ConfigError, types + +# objects to test +from tagit.config.schema import ConfigSchema + + +## code ## + +class TestSettings(unittest.TestCase): + def setUp(self): + # example schema + self.schema = ConfigSchema() + self.schema.declare(('ui', 'standalone', 'browser', 'cols'), types.Unsigned(), 3) + self.schema.declare(('session', 'paths', 'library'), types.Path(), '') + self.schema.declare(('session', 'paths', 'config'), types.Path(), '') + self.schema.declare(('extraction', 'constant', 'enabled'), types.Bool(), False) + + # example config + self.cfg = Settings(schema=self.schema, data={ + ('session', 'paths', 'library'): '/path/to/lib', + ('session', 'messaging', 'verbose'): 8, + }) + + def test_retrieval(self): + # get configured + self.assertEqual('/path/to/lib', self.cfg('session', 'paths', 'library')) + # get default + self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols')) + # get invalid + self.assertRaises(KeyError, self.cfg, 'ui', 'standalone', 'browser', 'rows') + # get unknown + self.assertEqual(8, self.cfg('session', 'messaging', 'verbose')) + self.assertRaises(KeyError, self.cfg, 'session', 'messaging', 'debug') + # get with default + self.assertEqual('/path/to/lib', self.cfg('session', 'paths', 'library', default='foo')) + self.assertEqual(8, self.cfg('session', 'messaging', 'verbose', default=12)) + self.assertEqual(5, self.cfg('ui', 'standalone', 'browser', 'cols', default=5)) + self.assertEqual(3.14, self.cfg('ui', 'standalone', 'browser', 'rows', default=3.14)) + # get if invalid was configured + cfg = Settings(schema=self.schema, data={ + ('session', 'paths', 'library'): 123, + ('session', 'messaging', 'verbose'): 8, + }) + self.assertEqual(8, cfg('session', 'messaging', 'verbose')) + self.assertEqual('foobar', cfg('session', 'paths', 'library', default='foobar')) + self.assertEqual('', cfg('session', 'paths', 'library')) + # test aliases + self.assertEqual('/path/to/lib', self.cfg['session', 'paths', 'library']) + self.assertEqual('/path/to/lib', self.cfg[('session', 'paths', 'library')]) + + def test_view(self): + # retrieve view + sub = self.cfg('ui', 'standalone', 'browser') + self.assertListEqual(list(sub), [ + ('cols', )]) + # set in original affects view + self.assertEqual(3, sub('cols')) + self.cfg.set(('ui', 'standalone', 'browser', 'cols'), 4) + self.assertEqual(4, sub('cols')) + # unset in original affects view + self.cfg.unset('ui', 'standalone', 'browser', 'cols') + self.assertEqual(3, sub('cols')) + # set in view affects original + sub.set(('cols', ), 5) + self.assertEqual(5, sub('cols')) + self.assertEqual(5, self.cfg('ui', 'standalone', 'browser', 'cols')) + # unset in view affects original + sub.unset('cols') + self.assertEqual(3, sub('cols')) + self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols')) + # has + self.assertIn(('cols', ), sub) + self.assertNotIn(('rows', ), sub) + self.assertNotIn(('ui', 'standalone', 'browser'), sub) + # unspecified view + sub = self.cfg('session', 'messaging') + self.assertListEqual(list(sub), [ + ('verbose', )]) + # upper branch view + sub = self.cfg('session') + self.assertCountEqual(list(sub), [ + ('paths', 'library'), + ('paths', 'config'), + ('messaging', 'verbose')]) + self.assertEqual('/path/to/lib', sub('paths', 'library')) + self.assertEqual(8, sub('messaging', 'verbose')) + self.assertRaises(KeyError, sub, 'session', 'paths', 'library') + # defaults + sub = self.cfg('ui', 'standalone') + self.assertCountEqual(list(sub), [ + ('browser', 'cols')]) + # aliases + sub = self.cfg['session'] + self.assertCountEqual(list(sub), [ + ('paths', 'library'), + ('paths', 'config'), + ('messaging', 'verbose') + ]) + + def test_modify_value(self): + # knowns + # overwrite default + self.cfg.set(('ui', 'standalone', 'browser', 'cols'), 4) + self.assertEqual(4, self.cfg('ui', 'standalone', 'browser', 'cols')) + # overwrite configured + self.cfg.set(('ui', 'standalone', 'browser', 'cols'), 5) + self.assertEqual(5, self.cfg('ui', 'standalone', 'browser', 'cols')) + # remove configured + self.cfg.unset('ui', 'standalone', 'browser', 'cols') + self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols')) + # remove default + self.cfg.unset('ui', 'standalone', 'browser', 'cols') + self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols')) + # overwrite with invalid + self.assertRaises(TypeError, self.cfg.set, ('ui', 'standalone', 'browser', 'cols'), 'hello') + # set to default + self.cfg.set(('session', 'paths', 'library'), '') + self.assertEqual('', self.cfg('session', 'paths', 'library')) + self.assertNotIn(('session', 'paths', 'library'), self.cfg.config) + + # unknowns + # overwrite unknown + self.cfg.set(('session', 'messaging', 'verbose'), 1) + self.assertEqual(1, self.cfg('session', 'messaging', 'verbose')) + # overwrite unknonw with different type + self.cfg.set(('session', 'messaging', 'verbose'), 'hello') + self.assertEqual('hello', self.cfg('session', 'messaging', 'verbose')) + # remove unknown + self.cfg.unset('session', 'messaging', 'verbose') + self.assertRaises(KeyError, self.cfg, 'session', 'messaging', 'verbose') + + # define new unknown + self.cfg.set(('storage', 'library', 'autosave'), 15) + self.assertEqual(15, self.cfg('storage', 'library', 'autosave')) + # overwrite new unknown + self.cfg.set(('storage', 'library', 'autosave'), 5) + self.assertEqual(5, self.cfg('storage', 'library', 'autosave')) + # overwrite new unknown with different type + self.cfg.set(('storage', 'library', 'autosave'), 'hello') + self.assertEqual('hello', self.cfg('storage', 'library', 'autosave')) + # remove unknown + self.cfg.unset('storage', 'library', 'autosave') + self.assertRaises(KeyError, self.cfg, 'storage', 'library', 'autosave') + # remove invalid + self.cfg.unset('storage', 'library', 'autosave') + self.assertRaises(KeyError, self.cfg, 'storage', 'library', 'autosave') + + # alias + self.cfg['storage', 'library', 'autosave'] = 12 + self.assertEqual(12, self.cfg('storage', 'library', 'autosave')) + del self.cfg['storage', 'library', 'autosave'] + self.assertRaises(KeyError, self.cfg, 'storage', 'library', 'autosave') + + def test_set_branch(self): + # knowns + cfg = Settings(schema=self.schema) + cfg.set(('session', ), { + 'paths': {'library': '/path/to/other'}, + 'messaging': {'debug': True}}) + self.assertDictEqual(cfg.config, { + ('session', 'paths', 'library'): '/path/to/other', + ('session', 'messaging', 'debug'): True, + }) + cfg.set(('session', 'paths'), { + 'library': '', + 'config': '/path/to/config', + 'numerical': '/path/to/num', + }) + self.assertDictEqual(cfg.config, { + # library is omitted because it's the default + ('session', 'paths', 'config'): '/path/to/config', + ('session', 'paths', 'numerical'): '/path/to/num', + ('session', 'messaging', 'debug'): True, # keey rest + }) + # error: value + self.assertRaises(TypeError, cfg.set, ('session', ), { + 'paths': {'library': 1234}, + 'messaging': {'debug': True} + }) + # error: subkey + self.assertRaises(TypeError, cfg.set, ('session', ), { + 'paths': 123, + 'messaging': {'debug': True} + }) + # error: superkey + self.assertRaises(TypeError, cfg.set, ('session', ), { + 'paths': {'library': {'path': '/path/to/lib'}}, + 'messaging': {'debug': True} + }) + + # unknowns + cfg = Settings(schema=self.schema) + cfg.set(('storage', ), { + 'library': {'autosave': 10, 'write_through': True}, + 'index': {'preview_size': [10, 20, 30]}}) + self.assertDictEqual(cfg.config, { + ('storage', 'library', 'autosave'): 10, + ('storage', 'library', 'write_through'): True, + ('storage', 'index', 'preview_size'): [10, 20, 30], + }) + # unknowns with previous keys + cfg.set(('storage', ), { + 'library': {'autoindex': 20, 'autosave': 20}, + 'numerical': {'write_through': False}}) + self.assertDictEqual(cfg.config, { + ('storage', 'library', 'autosave'): 20, # change value + ('storage', 'library', 'autoindex'): 20, # add key + ('storage', 'library', 'write_through'): True, # keep key + ('storage', 'numerical', 'write_through'): False, # add key + ('storage', 'index', 'preview_size'): [10, 20, 30], # keep key + }) + # error: superkey + self.assertRaises(TypeError, cfg.set, ('storage', ), { + 'library': {'autoindex': {'autosave': 20}}, + 'numerical': {'write_through': True}}) + # error: subkey + self.assertRaises(TypeError, cfg.set, ('storage', ), { + 'library': 123, + 'numerical': {'write_through': True}}) + + # alias + cfg['session'] = { + 'paths': {'library': '', 'numerical': 'otherpath'}, + 'messaging': {'verbose': 3, 'debug': False} + } + self.assertDictEqual(cfg.config, { + ('session', 'paths', 'numerical'): 'otherpath', + ('session', 'messaging', 'verbose'): 3, + ('session', 'messaging', 'debug'): False, + # keep the rest as-is + ('storage', 'library', 'autosave'): 20, # change value + ('storage', 'library', 'autoindex'): 20, # add key + ('storage', 'library', 'write_through'): True, # keep key + ('storage', 'numerical', 'write_through'): False, # add key + ('storage', 'index', 'preview_size'): [10, 20, 30], # keep key + }) + cfg[('session', )] = { + 'paths': {'library': '/path/to/somewhere', 'numerical': 'mypath'}, + 'messaging': {'debug': True} + } + self.assertDictEqual(cfg.config, { + ('session', 'paths', 'library'): '/path/to/somewhere', + ('session', 'paths', 'numerical'): 'mypath', + ('session', 'messaging', 'debug'): True, + # keep the rest as-is + ('session', 'messaging', 'verbose'): 3, + ('storage', 'library', 'autosave'): 20, # change value + ('storage', 'library', 'autoindex'): 20, # add key + ('storage', 'library', 'write_through'): True, # keep key + ('storage', 'numerical', 'write_through'): False, # add key + ('storage', 'index', 'preview_size'): [10, 20, 30], # keep key + }) + + # test nesting + cfg['session']['paths'] = {'library': '/path/to/elsewhere'} + self.assertEqual('/path/to/elsewhere', cfg('session', 'paths', 'library')) + cfg['session', 'paths'] = {'library': '/path/to/elsewhere2'} + self.assertEqual('/path/to/elsewhere2', cfg('session', 'paths', 'library')) + cfg['session']['paths']['library'] = '/path/to/elsewhere3' + self.assertEqual('/path/to/elsewhere3', cfg('session', 'paths', 'library')) + + def test_set_branch_dict(self): + # unknowns + cfg = Settings(schema=ConfigSchema(), data={ + ('session', 'paths', 'preview', 'files'): 'thumbs' + }) + # adding siblings is allowed + cfg.set(('session', 'paths', 'preview', 'original'), '') + self.assertDictEqual(cfg.config, { + ('session', 'paths', 'preview', 'files'): 'thumbs', + ('session', 'paths', 'preview', 'original'): '', + }) + # clearing sub-items is allowed + cfg.set(('session', 'paths', 'preview'), {}) + self.assertDictEqual(cfg.config, { + ('session', 'paths', 'preview'): {}, + }) + # adding sub-items is allowed + cfg.set(('session', 'paths', 'preview', 'sqlite'), 'thumbs.db') + self.assertDictEqual(cfg.config, { + ('session', 'paths', 'preview', 'sqlite'): 'thumbs.db', + }) + # adding siblings dict-style is allowed + cfg.set(('session', 'paths', 'preview'), {'exif': 'none', 'rest': {}}) + self.assertDictEqual(cfg.config, { + ('session', 'paths', 'preview', 'sqlite'): 'thumbs.db', + ('session', 'paths', 'preview', 'exif'): 'none', + ('session', 'paths', 'preview', 'rest'): {}, + }) + # adding sub-items to non-dict items is not allowed + self.assertRaises(TypeError, cfg.set, ('session', 'paths', 'preview', 'exif', 'sub'), 123) + # adding super-items of non-dict type is not allowed + self.assertRaises(TypeError, cfg.set, ('session', 'paths', 'preview'), 123) + + def test_unset_branch(self): + # mixed knowns/unknowns + self.cfg.unset('session') + self.assertNotIn(('session', 'messaging', 'verbose'), self.cfg) + self.assertNotIn(('session', 'messaging'), self.cfg) + self.assertIn(('session', ), self.cfg) + self.assertEqual('', self.cfg('session', 'paths', 'library')) + + # already empty + self.cfg.unset('ui', 'standalone') + self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols')) + + def test_unset_branch_alias(self): + # mixed knowns/unknowns + del self.cfg['session'] + self.assertNotIn(('session', 'messaging', 'verbose'), self.cfg) + self.assertNotIn(('session', 'messaging'), self.cfg) + self.assertIn(('session', ), self.cfg) + self.assertEqual('', self.cfg('session', 'paths', 'library')) + + # already empty + del self.cfg['ui', 'standalone'] + self.assertEqual(3, self.cfg('ui', 'standalone', 'browser', 'cols')) + + def test_has(self): + # check default + self.assertIn(('ui', 'standalone', 'browser', 'cols'), self.cfg) + # check configured + self.assertIn(('session', 'paths', 'library'), self.cfg) + # check newly configured + self.cfg.set(('ui', 'standalone', 'browser', 'cols'), 4) + self.assertIn(('ui', 'standalone', 'browser', 'cols'), self.cfg) + # check invalid + self.assertNotIn(('ui', 'standalone', 'browser', 'rows'), self.cfg) + # check branch + self.assertIn(('ui', 'standalone', 'browser'), self.cfg) + # check branch + self.assertNotIn(('ui', 'standalone', 'filter'), self.cfg) + + # check unknown + self.assertIn(('session', 'messaging', 'verbose'), self.cfg) + self.assertNotIn(('session', 'messaging', 'debug'), self.cfg) + # check unknown branch + self.assertIn(('session', 'messaging'), self.cfg) + self.assertNotIn(('storage', 'library'), self.cfg) + self.assertNotIn(('storage', ), self.cfg) + + def test_iteration(self): + # length + self.assertEqual(5, len(self.cfg)) + # iterate keys, explicit/implicit + self.assertCountEqual(list(self.cfg), [ + ('ui', 'standalone', 'browser', 'cols'), + ('extraction', 'constant', 'enabled'), + ('session', 'paths', 'library'), + ('session', 'paths', 'config'), + ('session', 'messaging', 'verbose')]) + self.assertCountEqual(list(self.cfg.keys()), [ + ('ui', 'standalone', 'browser', 'cols'), + ('extraction', 'constant', 'enabled'), + ('session', 'paths', 'library'), + ('session', 'paths', 'config'), + ('session', 'messaging', 'verbose')]) + # iterate items + self.assertCountEqual(list(self.cfg.items()), [ + (('ui', 'standalone', 'browser', 'cols'), 3), + (('extraction', 'constant', 'enabled'), False), + (('session', 'paths', 'library'), '/path/to/lib'), + (('session', 'paths', 'config'), ''), + (('session', 'messaging', 'verbose'), 8)]) + + def test_magicks(self): + # comparison + self.assertEqual(self.cfg, self.cfg) + self.assertEqual(self.cfg, self.cfg()) + self.assertNotEqual(self.cfg, self.cfg('ui')) + self.assertEqual(self.cfg, Settings(self.schema, data=self.cfg.config)) + self.assertNotEqual(self.cfg, Settings(ConfigSchema(), data=self.cfg.config)) + self.assertNotEqual(self.cfg, Settings(self.schema)) + self.assertEqual(hash(self.cfg), hash(self.cfg)) + self.assertEqual(hash(self.cfg), hash(self.cfg())) + self.assertEqual(hash(self.cfg), hash(Settings(self.schema, data=self.cfg.config))) + # representation + self.assertEqual(str(self.cfg), str(self.cfg.config)) + self.assertEqual(repr(self.cfg), 'Settings(prefix=(), keys=2, len=5)') + + def test_diff(self): + """ + # diff + >>> cfg.unset('ui', 'standalone', 'browser', 'cols') + >>> cfg.diff(Settings()) + Settings({'session': {'paths': {'library': '/path/to/lib'}}}) + >>> cfg.set(('ui', 'standalone', 'browser', 'cols'), 4) + >>> cfg.diff(Settings()) + Settings({'session': {'paths': {'library': '/path/to/lib'}}, + 'ui': {'standalone': {'browser': {'cols': 4}}}}) + """ + # contains all configured + self.assertDictEqual(self.cfg.diff(Settings()).config, { + ('session', 'paths', 'library'): '/path/to/lib', + ('session', 'messaging', 'verbose'): 8, + }) + # still no cols (because it won't be stored in self.cfg) + self.cfg.set(('ui', 'standalone', 'browser', 'cols'), 3) + self.assertDictEqual(self.cfg.diff(Settings()).config, { + ('session', 'paths', 'library'): '/path/to/lib', + ('session', 'messaging', 'verbose'): 8, + }) + # still no cols (because it's the default) + self.assertDictEqual(self.cfg.diff(Settings(data= + {('ui', 'standalone', 'browser', 'cols'): 3} + )).config, { + ('session', 'paths', 'library'): '/path/to/lib', + ('session', 'messaging', 'verbose'): 8, + }) + # now with cols (because it deviates) + self.assertDictEqual(self.cfg.diff(Settings(data= + {('ui', 'standalone', 'browser', 'cols'): 5} + )).config, { + ('ui', 'standalone', 'browser', 'cols'): 3, + ('session', 'paths', 'library'): '/path/to/lib', + ('session', 'messaging', 'verbose'): 8, + }) + # non-differing known + self.assertDictEqual(self.cfg.diff(Settings(data= + {('session', 'paths', 'library'): '/path/to/lib'} + )).config, { + ('session', 'messaging', 'verbose'): 8, + }) + # non-differing unknown + self.assertDictEqual(self.cfg.diff(Settings(data= + {('session', 'messaging', 'verbose'): 8} + )).config, { + ('session', 'paths', 'library'): '/path/to/lib', + }) + + def test_flatten_tree(self): + schema = ConfigSchema() + schema.declare(('ui', 'standalone', 'tiledocks'), + types.Dict(types.String(), types.Dict(types.String(), types.Int())), {}) + schema.declare(('session', 'debug'), types.Bool(), True) + schema.declare(('storage', 'library', 'autosave'), types.Unsigned(), 0) + + # ordinary scenario + flat = Settings.flatten_tree({ + 'session': { + 'debug': True, + 'paths': { + 'library': '/path/to/lib', + 'config': {'path': '/path/to/conf' }, + } + }}, schema, on_error='raise') + self.assertDictEqual(flat, { + ('session', 'debug'): True, # defaults are kept + ('session', 'paths', 'library'): '/path/to/lib', + ('session', 'paths', 'config', 'path'): '/path/to/conf', + }) + + # dict types + flat = Settings.flatten_tree({ + 'ui': { + 'standalone': { + 'tiledocks': { + 'dashboard': { + 'Buttons': 123 + }}}}}, schema, on_error='raise') + self.assertDictEqual(flat, { + ('ui', 'standalone', 'tiledocks'): {'dashboard': {'Buttons': 123}}}) + + flat = Settings.flatten_tree({ + 'ui': { + 'standalone': { + 'tiledocks': { + 'dashboard': {} + }}}}, schema) + self.assertDictEqual(flat, { + ('ui', 'standalone', 'tiledocks'): {'dashboard': {}} + }) + + # dict types w/o schema + flat = Settings.flatten_tree({ + 'ui': { + 'standalone': { + 'tiledocks': { + 'dashboard': { + 'Buttons': 123 + }}}}}, ConfigSchema(), on_error='raise') + self.assertDictEqual(flat, { + ('ui', 'standalone', 'tiledocks', 'dashboard', 'Buttons'): 123}) + + # dict types w/o schema + flat = Settings.flatten_tree({ + 'ui': { + 'standalone': { + 'tiledocks': { + 'dashboard': { + 'Buttons': {} + }}}}}, ConfigSchema(), on_error='raise') + self.assertDictEqual(flat, { + ('ui', 'standalone', 'tiledocks', 'dashboard', 'Buttons'): {}}) + + # error case: invalid value + config = { + 'session': { + 'debug': 123, + 'paths': { + 'library': '/path/to/lib', + 'config': {'path': '/path/to/conf' }, + } + }} + self.assertRaises(TypeError, Settings.flatten_tree, config, schema, on_error='raise') + flat = Settings.flatten_tree(config, schema, on_error='ignore') + self.assertDictEqual(flat, { + # debug is omitted because it's invalid + ('session', 'paths', 'library'): '/path/to/lib', + ('session', 'paths', 'config', 'path'): '/path/to/conf', + }) + # error case: invalid subkey + config = { + 'session': { + 'debug': { + 'verbose': True + }, + 'paths': { + 'library': '/path/to/lib', + 'config': {'path': '/path/to/conf' }, + } + }} + self.assertRaises(TypeError, Settings.flatten_tree, config, schema, on_error='raise') + flat = Settings.flatten_tree(config, schema, on_error='ignore') + self.assertDictEqual(flat, { + # debug is omitted because it's invalid + ('session', 'paths', 'library'): '/path/to/lib', + ('session', 'paths', 'config', 'path'): '/path/to/conf', + }) + # error case: invalid superkey + config = { + 'session': 123, + 'storage': { + 'library': { + 'autosave': -4, + 'write_through': True + }}} + self.assertRaises(TypeError, Settings.flatten_tree, config, schema, on_error='raise') + flat = Settings.flatten_tree(config, schema, on_error='ignore') + self.assertDictEqual(flat, {('storage', 'library', 'write_through'): True}) + + def test_clone(self): + cfg = self.cfg.clone() + self.assertEqual(self.cfg, cfg) + cfg.set(('session', 'paths', 'library'), '/path/to/elsewhere') + self.assertEqual('/path/to/elsewhere', cfg('session', 'paths', 'library')) + self.assertEqual('/path/to/lib', self.cfg('session', 'paths', 'library')) + self.assertNotEqual(self.cfg, cfg) + + def test_file_connected(self): + self.assertFalse(self.cfg.file_connected()) + self.cfg.set(('session', 'paths', 'config'), '/path/to/somewhere') + self.assertTrue(self.cfg.file_connected()) + self.cfg.unset('session', 'paths', 'config') + self.assertFalse(self.cfg.file_connected()) + + def test_schema_changes(self): + schema = ConfigSchema() + cfg = Settings.Open({ + 'ui': {'standalone': {'tiledocks': {'Buttons': {'buttons': [1,2,3]}, + 'Info': {}, + }}}, + 'session': {'paths': {'preview': {'files': 'thumbs'}}, + 'debug': False, + 'size': 'will_become_invalid', + }, + }, schema=schema) + self.assertDictEqual(cfg.config, { + ('ui', 'standalone', 'tiledocks', 'Buttons', 'buttons'): [1,2,3], + ('ui', 'standalone', 'tiledocks', 'Info'): {}, + ('session', 'paths', 'preview', 'files'): 'thumbs', + ('session', 'debug'): False, + ('session', 'size'): 'will_become_invalid', + }) + + schema.declare(('ui', 'standalone', 'tiledocks'), + types.Dict(types.String(), types.Dict(types.String(), types.Any())), {}) + schema.declare(('session', 'paths', 'preview'), + types.Dict(types.String(), types.String()), {}) + schema.declare(('session', 'debug'), types.Bool(), False) + + cfg.rebase(clear_defaults=False) + self.assertDictEqual(cfg.config, { + ('ui', 'standalone', 'tiledocks'): {'Buttons': {'buttons': [1,2,3]}, 'Info': {}}, + ('session', 'paths', 'preview'): {'files': 'thumbs'}, + ('session', 'debug'): False, # is still in here + ('session', 'size'): 'will_become_invalid', + }) + + cfg.rebase() + self.assertDictEqual(cfg.config, { + ('ui', 'standalone', 'tiledocks'): {'Buttons': {'buttons': [1,2,3]}, 'Info': {}}, + ('session', 'paths', 'preview'): {'files': 'thumbs'}, + # now session.debug is gone + ('session', 'size'): 'will_become_invalid', + }) + + # create a schema violation + schema.declare(('session', 'size'), types.Int(), 0) + # raises an error + self.assertRaises(TypeError, cfg.rebase) + # ignore the error, size will be removed + cfg.rebase(on_error='ignore') + self.assertDictEqual(cfg.config, { + ('ui', 'standalone', 'tiledocks'): {'Buttons': {'buttons': [1,2,3]}, 'Info': {}}, + ('session', 'paths', 'preview'): {'files': 'thumbs'}, + }) + + def test_to_tree(self): + self.assertDictEqual(self.cfg.to_tree(), { + 'session': {'paths': {'library': '/path/to/lib'}, + 'messaging': {'verbose': 8}}, + }) + self.assertDictEqual(self.cfg.to_tree(defaults=True), { + 'session': {'paths': {'library': '/path/to/lib', + 'config': ''}, + 'messaging': {'verbose': 8}}, + 'ui': {'standalone': {'browser': {'cols': 3}}}, + 'extraction': {'constant': {'enabled': False}} + }) + # corner cases + self.assertDictEqual(Settings(schema=self.schema, data={}).to_tree(), {}) + self.assertDictEqual(Settings(schema=self.schema, data={('session', ): True}).to_tree(), + {'session': True}) + # tree from subkey + self.assertDictEqual(self.cfg('session', 'paths').to_tree(), { + 'library': '/path/to/lib'}) + self.assertDictEqual(self.cfg('session', 'paths').to_tree(defaults=True), { + 'library': '/path/to/lib', + 'config': ''}) + + def test_Open_formats(self): + config = { + 'session': { + 'paths': { + 'library': '/path/to/somewhere', + 'numerical': '/path/to/numerical', + }, + 'messaging': { + 'debug': True, + 'verbose': False, + }, + }, + 'extraction': { + 'constant': { + 'enabled': False + }, + }} + + # store to file + path = tempfile.mkstemp(prefix='tagit_')[1] + with open(path, 'w') as ofile: + json.dump(config, ofile) + + # load from json string + cfg = Settings.Open(json.dumps(config), schema=self.schema) + # default + self.assertEqual(3, cfg('ui', 'standalone', 'browser', 'cols')) + self.assertEqual(False, cfg('extraction', 'constant', 'enabled')) + # invalid + self.assertNotIn(('ui', 'standalone', 'browser', 'rows'), cfg) + # known + self.assertEqual('/path/to/somewhere', cfg('session', 'paths', 'library')) + # unknown + self.assertEqual('/path/to/numerical', cfg('session', 'paths', 'numerical')) + self.assertTrue(cfg('session', 'messaging', 'debug')) + self.assertFalse(cfg('session', 'messaging', 'verbose')) + # config dict + self.assertDictEqual(cfg.config, { + ('session', 'paths', 'library'): '/path/to/somewhere', + ('session', 'paths', 'numerical'): '/path/to/numerical', + ('session', 'messaging', 'debug'): True, + ('session', 'messaging', 'verbose'): False, + }) + + # load from dict + cfg2 = Settings.Open(config, schema=self.schema) + self.assertDictEqual(cfg.config, cfg2.config) + + # load from file + cfg2 = Settings.Open(path, schema=self.schema) + self.assertDictEqual(cfg.config, cfg2.config) + + # load from opened file + with open(path) as ifile: + cfg2 = Settings.Open(ifile, schema=self.schema) + self.assertDictEqual(cfg.config, cfg2.config) + + # invalid type + self.assertRaises(TypeError, Settings.Open, 15, schema=self.schema) + + os.unlink(path) + + def test_Open_errors(self): + # invalid value + config = {'session': {'paths': {'library': 15}}} + self.assertRaises(types.ConfigTypeError, Settings.Open, config, schema=self.schema) + # too deep + config = {'session': {'paths': {'library': {'plain': '/path/to/somewhere'}}}} + self.assertRaises(types.ConfigTypeError, Settings.Open, config, schema=self.schema) + # too shallow + config = {'session': {'paths': 123}} + self.assertRaises(ConfigError, Settings.Open, config, schema=self.schema) + # empty + cfg = Settings.Open({}, schema=self.schema) + self.assertDictEqual(cfg.config, {}) + + def test_Open_dict_keys(self): + schema = ConfigSchema() + schema.declare(('ui', 'standalone', 'tiledocks'), + types.Dict(types.String(), types.Dict(types.String(), types.Any())), {}) + # correct settings + config = { + 'ui': { + 'standalone': { + 'tiledocks': { + 'dashboard': { + 'Buttons': { + 'buttons': ['ShowBrowsing', 'ShowDashboard'] + }, + 'Info': {}, + }, + 'sidebar': { + 'LibSummary': {'size': 200}, + }, + }}}} + cfg = Settings.Open(config, schema=schema) + self.assertDictEqual(cfg('ui', 'standalone', 'tiledocks'), + config['ui']['standalone']['tiledocks']) + self.assertNotIn(('ui', 'standalone', 'tiledocks', 'dashboard'), cfg) + + # incorrect settings + config = { + 'ui': { + 'standalone': { + 'tiledocks': { + 'dashboard': { + 'Buttons': { + 'buttons': ['ShowBrowsing', 'ShowDashboard'] + }, + 'Info': {}, + }, + 'sidebar': [ 'LibSummary' ], + }}}} + self.assertRaises(types.ConfigTypeError, Settings.Open, config, schema=schema) + + def test_Open_sequence(self): + # start with empty config + schema = ConfigSchema() + schema.declare(('session', 'paths', 'library'), types.Path(), '') + schema.declare(('session', 'verbose'), types.Int(), 0) + schema.declare(('session', 'debug'), types.Bool(), False) + + cfg = Settings(schema=schema) + # update from first source + cfg.update(Settings.Open({ + 'session': {'verbose': 1} + }, schema=schema, clear_defaults=False)) + # update from second source + cfg.update(Settings.Open({ + 'session': {'paths': {'library': 'foobar'}, + 'debug': True} + }, schema=schema, clear_defaults=False)) + # update from third source + cfg.update(Settings.Open({ + 'session': {'paths': {'library': 'barfoo'}, + 'debug': False} + }, schema=schema, clear_defaults=False)) + # check: the default value was cleared + self.assertDictEqual(cfg.config, { + ('session', 'paths', 'library'): 'barfoo', + ('session', 'verbose'): 1, + }) + # update from fourth source + cfg.update(Settings.Open({ + 'session': {'paths': {'library': '/path/to/lib'}, + 'verbose': 0} + }, schema=schema, clear_defaults=False)) + # check: the default value is again cleared + self.assertDictEqual(cfg.config, { + ('session', 'paths', 'library'): '/path/to/lib', + }) + + def test_update(self): + # plain test + self.cfg.update(Settings(schema=self.schema, data={ + ('session', 'paths', 'library'): '/path/to/elsewhere', + ('session', 'paths', 'config'): '/path/to/somewhere', + ('ui', 'standalone', 'browser', 'cols'): 3, + ('ui', 'standalone', 'browser', 'rows'): 5, + })) + self.assertDictEqual(self.cfg.config, { + ('session', 'paths', 'library'): '/path/to/elsewhere', # overwrite knowns + ('session', 'paths', 'config'): '/path/to/somewhere', # add new knowns + # don't add defaults + ('ui', 'standalone', 'browser', 'rows'): 5, # add new unknowns + ('session', 'messaging', 'verbose'): 8, # keep old values + }) + # reset to default + self.cfg.update(Settings(schema=self.schema, data={ + ('session', 'paths', 'library'): '', + ('session', 'messaging', 'verbose'): 5 + })) + self.assertDictEqual(self.cfg.config, { + # removed due to becoming default + ('session', 'paths', 'config'): '/path/to/somewhere', + ('ui', 'standalone', 'browser', 'rows'): 5, + ('session', 'messaging', 'verbose'): 5, # overwrite unknowns + }) + + # error: invalid value + self.assertRaises(TypeError, self.cfg.update, + Settings(schema=self.schema, data={ + ('session', 'paths', 'library'): 15, + ('ui', 'standalone', 'browser', 'rows'): 5, + })) + + # error: known superkey + self.assertRaises(TypeError, self.cfg.update, + Settings(schema=self.schema, data={ + ('session', 'paths'): 15, + })) + # error: known subkey + self.assertRaises(TypeError, self.cfg.update, + Settings(schema=self.schema, data={ + ('session', 'paths', 'config', 'write_through'): False, + })) + + # error: unknown superkey + self.assertRaises(TypeError, self.cfg.update, + Settings(schema=self.schema, data={ + ('session', 'messaging'): 15, + ('ui', 'standalone', 'browser', 'rows'): 5, + })) + # error: unknown subkey + self.assertRaises(TypeError, self.cfg.update, + Settings(schema=self.schema, data={ + ('session', 'messaging', 'verbose', 'debug'): True, + ('ui', 'standalone', 'browser', 'rows'): 5, + })) + + # error: unconfigured known superkey + self.assertRaises(TypeError, Settings(schema=self.schema, data={ + }).update, Settings(schema=ConfigSchema(), data={ + ('ui', 'standalone', 'browser'): 15 + })) + # error: unconfigured known subkey + self.assertRaises(TypeError, Settings(schema=self.schema, data={ + }).update, Settings(schema=ConfigSchema(), data={ + ('ui', 'standalone', 'browser', 'cols', 'foo'): 15 + })) + + # corner case: overwrite unknown subkey with dict + cfg = Settings(schema=ConfigSchema(), data={ + ('view', 'toolbag', 'right', 'Info'): 3 + }).update(Settings(schema=ConfigSchema(), data={ + ('view', 'toolbag', 'right'): {} + })) + self.assertDictEqual(cfg.config, { + ('view', 'toolbag', 'right'): {} + }) + + def test_save(self): + # needs uri + self.assertRaises(ValueError, self.cfg.save) + # can save if uri is known + path = tempfile.mkstemp(prefix='tagit_')[1] + self.assertEqual(os.stat(path).st_size, 0) + self.cfg.save(path) + # file has changed + self.assertGreater(os.stat(path).st_size, 0) + # can restore from file + cfg = Settings.Open(path, schema=self.schema) + self.assertEqual(cfg, self.cfg) + self.assertDictEqual(cfg.config, self.cfg.config) + # can save to buffer + buf = io.StringIO() + self.cfg.save(buf) + with open(path) as ifile: + self.assertEqual(buf.getvalue(), ifile.read()) + os.unlink(path) + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## diff --git a/test/config/test_types.py b/test/config/test_types.py new file mode 100644 index 0000000..31537e6 --- /dev/null +++ b/test/config/test_types.py @@ -0,0 +1,251 @@ +""" + +Part of the tagit test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import unittest + +# tagit imports +from tagit.config.types import ConfigTypeError + +# objects to test +from tagit.config import types + + +## code ## + +class TestTypes(unittest.TestCase): + def test_any(self): + inst = types.Any() + # representation (str, repr) + self.assertEqual(str(inst), 'Any') + self.assertEqual(repr(inst), 'Any()') + # comparison (eq, hash) + self.assertEqual(types.Any(), types.Any()) + self.assertEqual(hash(types.Any()), hash(types.Any())) + # backtrack + self.assertIsNone(inst.backtrack(None, '')) + self.assertIsNone(inst.backtrack('foobar', '')) + self.assertIsNone(inst.backtrack(123, '')) + self.assertIsNone(inst.backtrack([], '')) + self.assertIsNone(inst.backtrack(inst, '')) + + def test_bool(self): + inst = types.Bool() + # representation (str, repr) + self.assertEqual(str(inst), 'Bool') + self.assertEqual(repr(inst), 'Bool()') + # comparison (eq, hash) + self.assertEqual(types.Bool(), types.Bool()) + self.assertEqual(hash(types.Bool()), hash(types.Bool())) + # backtrack + self.assertIsNone(inst.backtrack(True, '')) + self.assertIsNone(inst.backtrack(False, '')) + self.assertRaises(ConfigTypeError, inst.backtrack, None, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 123, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 'foo', '') + self.assertRaises(ConfigTypeError, inst.backtrack, [], '') + self.assertRaises(ConfigTypeError, inst.backtrack, inst, '') + + def test_keybind(self): + inst = types.Keybind() + # representation (str, repr) + self.assertEqual(str(inst), 'Keybind') + self.assertEqual(repr(inst), 'Keybind()') + # comparison (eq, hash) + self.assertEqual(types.Keybind(), types.Keybind()) + self.assertEqual(hash(types.Keybind()), hash(types.Keybind())) + # backtrack + self.assertIsNone(inst.backtrack([], '')) + self.assertIsNone(inst.backtrack([(5, ('ctrl', ), ('all', ))], '')) + self.assertIsNone(inst.backtrack([('abc', ('rest', ), ('all', ))], '')) + self.assertRaises(ConfigTypeError, inst.backtrack, None, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 123, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 'foo', '') + self.assertRaises(ConfigTypeError, inst.backtrack, inst, '') + self.assertRaises(ConfigTypeError, inst.backtrack, [1,2,3], '') + self.assertRaises(ConfigTypeError, inst.backtrack, [(1,2,3)], '') + self.assertRaises(ConfigTypeError, inst.backtrack, [(1,(3,),3)], '') + self.assertRaises(ConfigTypeError, inst.backtrack, [(1,3,(3,))], '') + self.assertRaises(ConfigTypeError, inst.backtrack, [(3,(3,),('all',))], '') + self.assertRaises(ConfigTypeError, inst.backtrack, [(3,('foobar',),('all',))], '') + self.assertRaises(ConfigTypeError, inst.backtrack, [(3,('all',),(3,))], '') + self.assertRaises(ConfigTypeError, inst.backtrack, [(3,('all',),('foobar',))], '') + + def test_int(self): + inst = types.Int() + # representation (str, repr) + self.assertEqual(str(inst), 'Int') + self.assertEqual(repr(inst), 'Int()') + # comparison (eq, hash) + self.assertEqual(types.Int(), types.Int()) + self.assertEqual(hash(types.Int()), hash(types.Int())) + # backtrack + self.assertIsNone(inst.backtrack(0, '')) + self.assertIsNone(inst.backtrack(1, '')) + self.assertIsNone(inst.backtrack(-1, '')) + self.assertRaises(ConfigTypeError, inst.backtrack, None, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 'foo', '') + self.assertRaises(ConfigTypeError, inst.backtrack, [], '') + self.assertRaises(ConfigTypeError, inst.backtrack, inst, '') + + def test_unsigned(self): + inst = types.Unsigned() + # representation (str, repr) + self.assertEqual(str(inst), 'Unsigned int') + self.assertEqual(repr(inst), 'Unsigned()') + # comparison (eq, hash) + self.assertEqual(types.Unsigned(), types.Unsigned()) + self.assertEqual(hash(types.Unsigned()), hash(types.Unsigned())) + # backtrack + self.assertIsNone(inst.backtrack(0, '')) + self.assertIsNone(inst.backtrack(1, '')) + self.assertIsNone(inst.backtrack(123, '')) + self.assertRaises(ConfigTypeError, inst.backtrack, None, '') + self.assertRaises(ConfigTypeError, inst.backtrack, -1, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 'foo', '') + self.assertRaises(ConfigTypeError, inst.backtrack, [], '') + self.assertRaises(ConfigTypeError, inst.backtrack, inst, '') + + def test_float(self): + inst = types.Float() + # representation (str, repr) + self.assertEqual(str(inst), 'Float') + self.assertEqual(repr(inst), 'Float()') + # comparison (eq, hash) + self.assertEqual(types.Float(), types.Float()) + self.assertEqual(hash(types.Float()), hash(types.Float())) + # backtrack + self.assertIsNone(inst.backtrack(1.23, '')) + self.assertIsNone(inst.backtrack(-1.23, '')) + self.assertIsNone(inst.backtrack(1, '')) + self.assertIsNone(inst.backtrack(-1, '')) + self.assertRaises(ConfigTypeError, inst.backtrack, None, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 'foo', '') + self.assertRaises(ConfigTypeError, inst.backtrack, [], '') + self.assertRaises(ConfigTypeError, inst.backtrack, inst, '') + + def test_string(self): + inst = types.String() + # representation (str, repr) + self.assertEqual(str(inst), 'String') + self.assertEqual(repr(inst), 'String()') + # comparison (eq, hash) + self.assertEqual(types.String(), types.String()) + self.assertEqual(hash(types.String()), hash(types.String())) + # backtrack + self.assertIsNone(inst.backtrack('', '')) + self.assertIsNone(inst.backtrack('foobar', '')) + self.assertRaises(ConfigTypeError, inst.backtrack, None, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 123, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '') + self.assertRaises(ConfigTypeError, inst.backtrack, [], '') + self.assertRaises(ConfigTypeError, inst.backtrack, inst, '') + + def test_path(self): + inst = types.Path() + # representation (str, repr) + self.assertEqual(str(inst), 'Path') + self.assertEqual(repr(inst), 'Path()') + # comparison (eq, hash) + self.assertEqual(types.Path(), types.Path()) + self.assertEqual(hash(types.Path()), hash(types.Path())) + # backtrack + self.assertIsNone(inst.backtrack('', '')) + self.assertIsNone(inst.backtrack('foobar', '')) + self.assertRaises(ConfigTypeError, inst.backtrack, None, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 123, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '') + self.assertRaises(ConfigTypeError, inst.backtrack, [], '') + self.assertRaises(ConfigTypeError, inst.backtrack, inst, '') + + def test_enum(self): + inst = types.Enum('foo', 'bar', 123) + # representation (str, repr) + self.assertEqual(str(inst), 'One out of ({})'.format( + ', '.join(str(itm) for itm in inst.options))) + self.assertEqual(repr(inst), f'Enum([{inst.options}])') + # comparison (eq, hash) + self.assertEqual(types.Enum(), types.Enum()) + self.assertEqual(types.Enum('foo', 'bar'), types.Enum(['foo', 'bar'])) + self.assertEqual(types.Enum(123, 'bar'), types.Enum(['bar', 123])) + self.assertEqual(hash(types.Enum('foo')), hash(types.Enum(['foo']))) + # backtrack + self.assertIsNone(inst.backtrack('foo', '')) + self.assertIsNone(inst.backtrack('bar', '')) + self.assertIsNone(inst.backtrack(123, '')) + self.assertRaises(ConfigTypeError, inst.backtrack, None, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 'foobar', '') + self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '') + self.assertRaises(ConfigTypeError, inst.backtrack, [], '') + self.assertRaises(ConfigTypeError, inst.backtrack, inst, '') + + def test_list(self): + inst = types.List(types.Int()) + # representation (str, repr) + self.assertEqual(str(inst), 'List of Int') + self.assertEqual(repr(inst), 'List(Int)') + # comparison (eq, hash) + self.assertEqual(types.List(types.Int()), types.List(types.Int())) + self.assertNotEqual(types.List(types.Int()), types.List(types.Unsigned())) + self.assertNotEqual(types.List(types.Unsigned()), types.List(types.Int())) + self.assertNotEqual(types.List(types.Unsigned()), types.List(types.String())) + self.assertNotEqual(types.List(types.Bool()), types.List(types.String())) + self.assertEqual(hash(types.List(types.Int())), hash(types.List(types.Int()))) + self.assertEqual(hash(types.List(types.Unsigned())), hash(types.List(types.Unsigned()))) + self.assertEqual(hash(types.List(types.String())), hash(types.List(types.String()))) + # backtrack + self.assertIsNone(inst.backtrack([], '')) + self.assertIsNone(inst.backtrack([1,2,3], '')) + self.assertIsNone(inst.backtrack((1,2,3), '')) + self.assertRaises(ConfigTypeError, inst.backtrack, None, '') + self.assertRaises(ConfigTypeError, inst.backtrack, {1,2,3}, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 'foobar', '') + self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '') + self.assertRaises(ConfigTypeError, inst.backtrack, ['a', 'b', 'c'], '') + self.assertRaises(ConfigTypeError, inst.backtrack, [123, 'b', 'c'], '') + + def test_dict(self): + inst = types.Dict(types.String(), types.Int()) + # representation (str, repr) + self.assertEqual(str(inst), 'Dict from String to Int') + self.assertEqual(repr(inst), 'Dict(String, Int)') + # comparison (eq, hash) + self.assertEqual(types.Dict(types.Int(), types.String()), + types.Dict(types.Int(), types.String())) + self.assertEqual(types.Dict(types.List(types.Int()), types.String()), + types.Dict(types.List(types.Int()), types.String())) + self.assertNotEqual(types.Dict(types.Int(), types.Unsigned()), + types.Dict(types.Unsigned(), types.Int())) + self.assertNotEqual(types.Dict(types.Unsigned(), types.String()), + types.Dict(types.Int(), types.String())) + self.assertNotEqual(types.Dict(types.Unsigned(), types.String()), + types.Dict(types.Int(), types.String())) + self.assertEqual(hash(types.Dict(types.Int(), types.String())), + hash(types.Dict(types.Int(), types.String()))) + self.assertEqual(hash(types.Dict(types.List(types.Int()), types.String())), + hash(types.Dict(types.List(types.Int()), types.String()))) + # backtrack + self.assertIsNone(inst.backtrack({'foo': 2}, '')) + self.assertIsNone(inst.backtrack({'foo': 2, 'bar': 3}, '')) + self.assertIsNone(inst.backtrack({}, '')) + self.assertRaises(ConfigTypeError, inst.backtrack, None, '') + self.assertRaises(ConfigTypeError, inst.backtrack, {1,2,3}, '') + self.assertRaises(ConfigTypeError, inst.backtrack, 'foobar', '') + self.assertRaises(ConfigTypeError, inst.backtrack, 1.23, '') + self.assertRaises(ConfigTypeError, inst.backtrack, ['a', 'b', 'c'], '') + self.assertRaises(ConfigTypeError, inst.backtrack, [123, 'b', 'c'], '') + self.assertRaises(ConfigTypeError, inst.backtrack, {2: 'foo', 3: 'bar'}, '') + self.assertRaises(ConfigTypeError, inst.backtrack, {'foo': 2, 3: 'bar'}, '') + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## |