commit d3e1025bacd796eabe5285206762a858bf6d867a Author: Matt Traudt sirmatt@ksu.edu Date: Fri Jul 13 12:07:03 2018 -0400
Add State class and 100% test coverage for it
GH: ref #166 --- sbws/util/state.py | 62 ++++++++++++++++++++++ tests/unit/util/test_state.py | 120 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+)
diff --git a/sbws/util/state.py b/sbws/util/state.py new file mode 100644 index 0000000..58aef38 --- /dev/null +++ b/sbws/util/state.py @@ -0,0 +1,62 @@ +from sbws.util.filelock import FileLock +import os +import json + + +class State: + _ALLOWED_TYPES = (int, float, str, bool, type(None)) + + def __init__(self, fname): + self._fname = fname + self._state = self._read() + + def _read(self): + with FileLock(self._fname): + if not os.path.exists(self._fname): + return {} + with open(self._fname, 'rt') as fd: + return json.load(fd) + + def _write(self): + with FileLock(self._fname): + with open(self._fname, 'wt') as fd: + return json.dump(self._state, fd) + + def __len__(self): + self._state = self._read() + return self._state.__len__() + + def __getitem__(self, key): + if not isinstance(key, str): + raise TypeError( + 'Keys must be strings. %s is a %s' % (key, type(key))) + self._state = self._read() + return self._state.__getitem__(key) + + def __delitem__(self, key): + if not isinstance(key, str): + raise TypeError( + 'Keys must be strings. %s is a %s' % (key, type(key))) + self._state = self._read() + self._state.__delitem__(key) + self._write() + + def __setitem__(self, key, value): + if not isinstance(key, str): + raise TypeError( + 'Keys must be strings. %s is a %s' % (key, type(key))) + if type(value) not in State._ALLOWED_TYPES: + raise TypeError( + 'May only store value with type in %s, not %s' % + (State._ALLOWED_TYPES, type(value))) + self._state = self._read() + self._state.__setitem__(key, value) + self._write() + + def __iter__(self): + self._state = self._read() + return self._state.__iter__() + + def __contains__(self, item): + self._state = self._read() + return self._state.__contains__(item) diff --git a/tests/unit/util/test_state.py b/tests/unit/util/test_state.py new file mode 100644 index 0000000..42a6a8e --- /dev/null +++ b/tests/unit/util/test_state.py @@ -0,0 +1,120 @@ +from sbws.util.state import State +import os +# from tempfile import NamedTemporaryFile as NTF + + +def test_state_set_allowed_key_types(tmpdir): + state = State(os.path.join(tmpdir, 'statefoo')) + attempt_keys = ('k') + for key in attempt_keys: + state[key] = 4 + assert state[key] == 4 + + +def test_state_set_bad_key_types(tmpdir): + state = State(os.path.join(tmpdir, 'statefoo')) + attempt_keys = (15983, None, True, -1.2, [], {}, set()) + for key in attempt_keys: + try: + state[key] = 4 + except TypeError: + pass + else: + assert None, 'Should not have been able to use %s %s as a key' %\ + (key, type(key)) + try: + state[key] + except TypeError: + pass + else: + assert None, '%s %s is not a valid key type, so should have got '\ + 'TypeError when giving it' % (key, type(key)) + + +def test_state_set_allowed_value_types(tmpdir): + state = State(os.path.join(tmpdir, 'statefoo')) + attempt_vals = (15983, None, True, -1.2, 'loooooool') + for val in attempt_vals: + state['foo'] = val + assert state['foo'] == val + + +def test_state_set_bad_value_types(tmpdir): + state = State(os.path.join(tmpdir, 'statefoo')) + attempt_vals = ([], {}, set()) + for val in attempt_vals: + try: + state['foo'] = val + except TypeError: + pass + else: + assert None, 'Should not have been able to use %s %s as a value' %\ + (val, type(val)) + + +def test_state_del(tmpdir): + state = State(os.path.join(tmpdir, 'statefoo')) + d = {'a': 1, 'b': 2, 'c': 3, 'd': 4} + for key in d: + state[key] = d[key] + assert len(state) == len(d) + + del d['a'] + del state['a'] + assert len(state) == len(d) + for key in d: + assert d[key] == state[key] + + attempt_keys = (15983, None, True, -1.2, [], {}, set()) + for key in attempt_keys: + try: + del state[False] + except TypeError: + pass + else: + assert None, 'Should not have been allowed to delete %s %s '\ + 'because it is not a valid key type' % (key, type(key)) + + d['e'] = 5 + state['e'] = 5 + d['e'] = 5.5 + state['e'] = 5.5 + assert len(state) == len(d) + + +def test_state_get_len(tmpdir): + state = State(os.path.join(tmpdir, 'statefoo')) + d = {'a': 1, 'b': 2, 'c': 3, 'd': 4} + for key in d: + state[key] = d[key] + assert len(state) == len(d) + + del d['a'] + del state['a'] + assert len(state) == len(d) + + d['e'] = 5 + state['e'] = 5 + d['e'] = 5.5 + state['e'] = 5.5 + assert len(state) == len(d) + + +def test_state_contains(tmpdir): + state = State(os.path.join(tmpdir, 'statefoo')) + d = {'a': 1, 'b': 2, 'c': 3, 'd': 4} + for key in d: + state[key] = d[key] + assert 'a' in state + assert 'e' not in state + + +def test_state_iter(tmpdir): + state = State(os.path.join(tmpdir, 'statefoo')) + for key in state: + pass + d = {'a': 1, 'b': 2, 'c': 3, 'd': 4} + for key in d: + state[key] = d[key] + for key in state: + pass
tor-commits@lists.torproject.org