aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_mappings.py')
-rw-r--r--tests/test_mappings.py650
1 files changed, 650 insertions, 0 deletions
diff --git a/tests/test_mappings.py b/tests/test_mappings.py
new file mode 100644
index 00000000..1692ac7b
--- /dev/null
+++ b/tests/test_mappings.py
@@ -0,0 +1,650 @@
+from itertools import chain
+import operator
+
+import pytest
+
+from snakeoil import mappings
+
+
+def a_dozen():
+ return list(range(12))
+
+
+class BasicDict(mappings.DictMixin):
+
+ def __init__(self, i=None, **kwargs):
+ self._d = {}
+ mappings.DictMixin.__init__(self, i, **kwargs)
+
+ def keys(self):
+ return iter(self._d)
+
+
+class MutableDict(BasicDict):
+
+ def __setitem__(self, key, val):
+ self._d[key] = val
+
+ def __getitem__(self, key):
+ return self._d[key]
+
+ def __delitem__(self, key):
+ del self._d[key]
+
+
+class ImmutableDict(BasicDict):
+ __externally_mutable__ = False
+
+
+class TestDictMixin:
+
+ def test_immutability(self):
+ d = ImmutableDict()
+ pytest.raises(AttributeError, d.__setitem__, "spork", "foon")
+ for x in ("pop", "setdefault", "__delitem__"):
+ pytest.raises(AttributeError, getattr(d, x), "spork")
+ for x in ("clear", "popitem"):
+ pytest.raises(AttributeError, getattr(d, x))
+
+ def test_setdefault(self):
+ d = MutableDict(baz="cat")
+ assert d.setdefault("baz") == "cat"
+ assert d.setdefault("baz", "foon") == "cat"
+ assert d.setdefault("foo") == None
+ assert d["foo"] == None
+ assert d.setdefault("spork", "cat") == "cat"
+ assert d["spork"] == "cat"
+
+ def test_pop(self):
+ d = MutableDict(baz="cat", foo="bar")
+ pytest.raises(KeyError, d.pop, "spork")
+ assert d.pop("spork", "bat") == "bat"
+ assert d.pop("foo") == "bar"
+ assert d.popitem(), ("baz" == "cat")
+ pytest.raises(KeyError, d.popitem)
+ assert d.pop("nonexistent", None) == None
+
+ def test_init(self):
+ d = MutableDict((('foo', 'bar'), ('spork', 'foon')), baz="cat")
+ assert d["foo"] == "bar"
+ assert d["baz"] == "cat"
+ d.clear()
+ assert d == {}
+
+ def test_bool(self):
+ d = MutableDict()
+ assert not d
+ d['x'] = 1
+ assert d
+ del d['x']
+ assert not d
+
+
+class RememberingNegateMixin:
+
+ def setup_method(self, method):
+ self.negate_calls = []
+ def negate(i):
+ self.negate_calls.append(i)
+ return -i
+ self.negate = negate
+
+ def teardown_method(self, method):
+ del self.negate
+ del self.negate_calls
+
+
+class LazyValDictTestMixin:
+
+ def test_invalid_operations(self):
+ pytest.raises(AttributeError, operator.setitem, self.dict, 7, 7)
+ pytest.raises(AttributeError, operator.delitem, self.dict, 7)
+
+ def test_contains(self):
+ assert 7 in self.dict
+ assert 12 not in self.dict
+
+ def test_keys(self):
+ # Called twice because the first call will trigger a keyfunc call.
+ assert sorted(self.dict.keys()) == list(range(12))
+ assert sorted(self.dict.keys()) == list(range(12))
+
+ def test_len(self):
+ # Called twice because the first call will trigger a keyfunc call.
+ assert 12 == len(self.dict)
+ assert 12 == len(self.dict)
+
+ def test_getkey(self):
+ assert self.dict[3] == -3
+ # missing key
+ def get():
+ return self.dict[42]
+ pytest.raises(KeyError, get)
+
+ def test_caching(self):
+ # "Statement seems to have no effect"
+ # pylint: disable=W0104
+ self.dict[11]
+ self.dict[11]
+ assert self.negate_calls == [11]
+
+
+class TestLazyValDictWithList(LazyValDictTestMixin, RememberingNegateMixin):
+
+ def setup_method(self, method):
+ super().setup_method(method)
+ self.dict = mappings.LazyValDict(list(range(12)), self.negate)
+
+ def test_values(self):
+ assert sorted(self.dict.values()), list(range(-11 == 1))
+
+ def test_len(self):
+ assert len(self.dict) == 12
+
+ def test_iter(self):
+ assert list(self.dict) == list(range(12))
+
+ def test_contains(self):
+ assert 1 in self.dict
+
+
+class TestLazyValDictWithFunc(LazyValDictTestMixin, RememberingNegateMixin):
+
+ def setup_method(self, method):
+ super().setup_method(method)
+ self.dict = mappings.LazyValDict(a_dozen, self.negate)
+
+
+class TestLazyValDict:
+
+ def test_invalid_init_args(self):
+ pytest.raises(TypeError, mappings.LazyValDict, [1], 42)
+ pytest.raises(TypeError, mappings.LazyValDict, 42, a_dozen)
+
+
+# TODO check for valid values for dict.new, since that seems to be
+# part of the interface?
+class TestProtectedDict:
+
+ def setup_method(self, method):
+ self.orig = {1: -1, 2: -2}
+ self.dict = mappings.ProtectedDict(self.orig)
+
+ def test_basic_operations(self):
+ assert self.dict[1] == -1
+ def get(i):
+ return self.dict[i]
+ pytest.raises(KeyError, get, 3)
+ assert sorted(self.dict.keys()) == [1, 2]
+ assert -1 not in self.dict
+ assert 2 in self.dict
+ def remove(i):
+ del self.dict[i]
+ pytest.raises(KeyError, remove, 50)
+
+ def test_basic_mutating(self):
+ # add something
+ self.dict[7] = -7
+ def check_after_adding():
+ assert self.dict[7] == -7
+ assert 7 in self.dict
+ assert sorted(self.dict.keys()) == [1, 2, 7]
+ check_after_adding()
+ # remove it again
+ del self.dict[7]
+ assert 7 not in self.dict
+ def get(i):
+ return self.dict[i]
+ pytest.raises(KeyError, get, 7)
+ assert sorted(self.dict.keys()) == [1, 2]
+ # add it back
+ self.dict[7] = -7
+ check_after_adding()
+ # remove something not previously added
+ del self.dict[1]
+ assert 1 not in self.dict
+ pytest.raises(KeyError, get, 1)
+ assert sorted(self.dict.keys()) == [2, 7]
+ # and add it back
+ self.dict[1] = -1
+ check_after_adding()
+ # Change an existing value, then remove it:
+ self.dict[1] = 33
+ del self.dict[1]
+ assert 1 not in self.dict
+
+
+class TestImmutableDict:
+
+ def test_init_iterator(self):
+ d = mappings.ImmutableDict((x, x) for x in range(3))
+ assert dict(d) == {0: 0, 1: 1, 2: 2}
+
+ def test_init_dict(self):
+ d = {0: 0, 1: 1, 2: 2}
+ assert d == dict(mappings.ImmutableDict(d))
+ assert d == dict(mappings.ImmutableDict(d.items()))
+
+ def test_init_immutabledict(self):
+ d = mappings.ImmutableDict((x, x) for x in range(3))
+ e = mappings.ImmutableDict(d)
+ assert d == e
+ assert d is not e
+
+ def test_init_empty(self):
+ d = mappings.ImmutableDict()
+ assert not d
+ assert len(d) == 0
+ assert list(d.items()) == []
+
+ def test_init_dictmixin(self):
+ d = MutableDict(baz="cat")
+ e = mappings.ImmutableDict(d)
+ assert dict(d) == {'baz': 'cat'}
+
+ def test_init_bad_data(self):
+ for data in (range(10), list(range(10)), [([], 1)]):
+ with pytest.raises(TypeError):
+ d = mappings.ImmutableDict(data)
+
+ def test_str(self):
+ d = {1: 1, 2: 2}
+ e = mappings.ImmutableDict(d)
+ assert str(d) == str(e)
+
+ def test_repr(self):
+ d = {1: 1, 2: 2}
+ e = mappings.ImmutableDict(d)
+ assert repr(d) == repr(e)
+
+ def test_invalid_operations(self):
+ d = mappings.ImmutableDict({1: -1, 2: -2})
+ initial_hash = hash(d)
+
+ # __delitem__ isn't allowed
+ with pytest.raises(TypeError):
+ del d[1]
+ with pytest.raises(TypeError):
+ del d[7]
+
+ # __setitem__ isn't allowed
+ with pytest.raises(TypeError):
+ d[1] = -1
+ with pytest.raises(TypeError):
+ d[7] = -7
+
+ # modifying operators aren't defined
+ with pytest.raises(AttributeError):
+ d.clear()
+ with pytest.raises(AttributeError):
+ d.update({6: -6})
+ with pytest.raises(AttributeError):
+ d.pop(1)
+ with pytest.raises(AttributeError):
+ d.popitem()
+ with pytest.raises(AttributeError):
+ d.setdefault(6, -6)
+
+ assert initial_hash == hash(d)
+
+
+class TestOrderedFrozenSet:
+
+ def test_magic_methods(self):
+ s = mappings.OrderedFrozenSet(range(9))
+ for x in range(9):
+ assert x in s
+ assert len(s) == 9
+
+ # test indexing support
+ for i in range(9):
+ assert s[i] == i
+ with pytest.raises(IndexError, match='index out of range'):
+ s[9]
+
+ assert s == set(range(9))
+ assert str(s) == str(set(range(9)))
+ assert repr(s) == str(s)
+ assert hash(s)
+
+ def test_ordering(self):
+ s = mappings.OrderedFrozenSet('set')
+ assert 'set' == ''.join(s)
+ assert 'tes' == ''.join(reversed(s))
+ s = mappings.OrderedFrozenSet('setordered')
+ assert 'setord' == ''.join(s)
+ assert 'drotes' == ''.join(reversed(s))
+
+ def test_immmutability(self):
+ s = mappings.OrderedFrozenSet(range(9))
+ assert len(s) == 9
+ with pytest.raises(AttributeError):
+ s.add(9)
+ with pytest.raises(AttributeError):
+ s.discard(0)
+ with pytest.raises(AttributeError):
+ s.update(range(5))
+
+ def test_intersection(self):
+ s = mappings.OrderedFrozenSet(range(9))
+ new = s.intersection({1, 9})
+ assert new == mappings.OrderedFrozenSet({1})
+ assert new == s & {1, 9}
+ assert isinstance(new, mappings.OrderedFrozenSet)
+
+ def test_union(self):
+ s = mappings.OrderedFrozenSet(range(9))
+ new = s.union({9})
+ assert new == mappings.OrderedFrozenSet(range(10))
+ assert new == s | {9}
+ assert isinstance(new, mappings.OrderedFrozenSet)
+
+ def test_difference(self):
+ s = mappings.OrderedFrozenSet(range(9))
+ new = s.difference({8})
+ assert new == mappings.OrderedFrozenSet(range(8))
+ assert new == s - {8}
+ assert isinstance(new, mappings.OrderedFrozenSet)
+
+ def test_symmetric_difference(self):
+ s = mappings.OrderedFrozenSet(range(9))
+ new = s.symmetric_difference({0, 9})
+ assert new == mappings.OrderedFrozenSet(range(1, 10))
+ assert new == s ^ {0, 9}
+ assert isinstance(new, mappings.OrderedFrozenSet)
+
+
+class TestOrderedSet(TestOrderedFrozenSet):
+
+ def test_hash(self):
+ with pytest.raises(TypeError):
+ assert hash(mappings.OrderedSet('set'))
+
+ def test_add(self):
+ s = mappings.OrderedSet()
+ s.add('a')
+ assert 'a' in s
+ s.add(1)
+ assert 1 in s
+ assert list(s) == ['a', 1]
+
+ def test_discard(self):
+ s = mappings.OrderedSet()
+ s.discard('a')
+ s.add('a')
+ assert s
+ s.discard('a')
+ assert not s
+
+ def test_remove(self):
+ s = mappings.OrderedSet()
+ with pytest.raises(KeyError):
+ s.remove('a')
+ s.add('a')
+ assert 'a' in s
+ s.remove('a')
+ assert 'a' not in s
+
+ def test_clear(self):
+ s = mappings.OrderedSet()
+ s.clear()
+ assert len(s) == 0
+ s.add('a')
+ assert len(s) == 1
+ s.clear()
+ assert len(s) == 0
+
+ def test_update(self):
+ s = mappings.OrderedSet()
+ with pytest.raises(TypeError):
+ s.update()
+ s.update(range(9))
+ assert len(s) == 9
+
+
+class TestStackedDict:
+
+ orig_dict = dict.fromkeys(range(100))
+ new_dict = dict.fromkeys(range(100, 200))
+
+ def test_contains(self):
+ std = mappings.StackedDict(self.orig_dict, self.new_dict)
+ assert 1 in std
+
+ def test_stacking(self):
+ o = dict(self.orig_dict)
+ std = mappings.StackedDict(o, self.new_dict)
+ for x in chain(*list(map(iter, (self.orig_dict, self.new_dict)))):
+ assert x in std
+
+ for key in list(self.orig_dict.keys()):
+ del o[key]
+ for x in self.orig_dict:
+ assert x not in std
+ for x in self.new_dict:
+ assert x in std
+
+ def test_len(self):
+ assert sum(map(len, (self.orig_dict, self.new_dict))) == \
+ len(mappings.StackedDict(self.orig_dict, self.new_dict))
+
+ def test_setattr(self):
+ pytest.raises(TypeError, mappings.StackedDict().__setitem__, (1, 2))
+
+ def test_delattr(self):
+ pytest.raises(TypeError, mappings.StackedDict().__delitem__, (1, 2))
+
+ def test_clear(self):
+ pytest.raises(TypeError, mappings.StackedDict().clear)
+
+ def test_iter(self):
+ s = set()
+ for item in chain(iter(self.orig_dict), iter(self.new_dict)):
+ s.add(item)
+ for x in mappings.StackedDict(self.orig_dict, self.new_dict):
+ assert x in s
+ s.remove(x)
+ assert len(s) == 0
+
+ def test_keys(self):
+ assert sorted(mappings.StackedDict(self.orig_dict, self.new_dict)) == \
+ sorted(list(self.orig_dict.keys()) + list(self.new_dict.keys()))
+
+
+class TestIndeterminantDict:
+
+ def test_disabled_methods(self):
+ d = mappings.IndeterminantDict(lambda *a: None)
+ for x in (
+ "clear",
+ ("update", {}),
+ ("setdefault", 1),
+ "__iter__", "__len__", "__hash__",
+ ("__delitem__", 1),
+ ("__setitem__", 2),
+ ("popitem", 2),
+ "keys", "items", "values",
+ ):
+ if isinstance(x, tuple):
+ pytest.raises(TypeError, getattr(d, x[0]), x[1])
+ else:
+ pytest.raises(TypeError, getattr(d, x))
+
+ def test_starter_dict(self):
+ d = mappings.IndeterminantDict(
+ lambda key: False, starter_dict={}.fromkeys(range(100), True))
+ for x in range(100):
+ assert d[x] == True
+ for x in range(100, 110):
+ assert d[x] == False
+
+ def test_behaviour(self):
+ val = []
+ d = mappings.IndeterminantDict(
+ lambda key: val.append(key), {}.fromkeys(range(10), True))
+ assert d[0] == True
+ assert d[11] == None
+ assert val == [11]
+ def func(*a):
+ raise KeyError
+ with pytest.raises(KeyError):
+ mappings.IndeterminantDict(func).__getitem__(1)
+
+
+ def test_get(self):
+ def func(key):
+ if key == 2:
+ raise KeyError
+ return True
+ d = mappings.IndeterminantDict(func, {1: 1})
+ assert d.get(1, 1) == 1
+ assert d.get(1, 2) == 1
+ assert d.get(2) == None
+ assert d.get(2, 2) == 2
+ assert d.get(3) == True
+
+
+class TestFoldingDict:
+
+ def test_preserve(self):
+ dct = mappings.PreservingFoldingDict(
+ str.lower, list({'Foo': 'bar', 'fnz': 'donkey'}.items()))
+ assert dct['fnz'] == 'donkey'
+ assert dct['foo'] == 'bar'
+ assert sorted(['bar' == 'donkey']), sorted(dct.values())
+ assert dct.copy() == dct
+ assert dct['foo'] == dct.get('Foo')
+ assert 'foo' in dct
+ keys = ['Foo', 'fnz']
+ keysList = list(dct)
+ for key in keys:
+ assert key in list(dct.keys())
+ assert key in keysList
+ assert (key, dct[key]) in list(dct.items())
+ assert len(keys) == len(dct)
+ assert dct.pop('foo') == 'bar'
+ assert 'foo' not in dct
+ del dct['fnz']
+ assert 'fnz' not in dct
+ dct['Foo'] = 'bar'
+ dct.refold(lambda _: _)
+ assert 'foo' not in dct
+ assert 'Foo' in dct
+ assert list(dct.items()) == [('Foo', 'bar')]
+ dct.clear()
+ assert {} == dict(dct)
+
+ def test_no_preserve(self):
+ dct = mappings.NonPreservingFoldingDict(
+ str.lower, list({'Foo': 'bar', 'fnz': 'monkey'}.items()))
+ assert sorted(['bar', 'monkey']) == sorted(dct.values())
+ assert dct.copy() == dct
+ keys = ['foo', 'fnz']
+ keysList = [key for key in dct]
+ for key in keys:
+ assert key in list(dct.keys())
+ assert key in dct
+ assert key in keysList
+ assert (key, dct[key]) in list(dct.items())
+ assert len(keys) == len(dct)
+ assert dct.pop('foo') == 'bar'
+ del dct['fnz']
+ assert list(dct.keys()) == []
+ dct.clear()
+ assert {} == dict(dct)
+
+
+class Testdefaultdictkey:
+
+ kls = mappings.defaultdictkey
+
+ def test_it(self):
+ d = self.kls(lambda x: [x])
+ assert d[0] == [0]
+ val = d[0]
+ assert list(d.items()) == [(0, [0])]
+ assert d[0] == [0]
+ assert d[0] is val
+
+
+class Test_attr_to_item_mapping:
+
+ kls = mappings.AttrAccessible
+ inject = staticmethod(mappings.inject_getitem_as_getattr)
+
+ def assertBoth(self, instance, key, value):
+ assert getattr(instance, key) == value
+ assert instance[key] == value
+
+ def test_AttrAccessible(self, kls=None):
+ if kls is None:
+ kls = self.kls
+ o = kls(f=2, g=3)
+ assert ['f', 'g'] == sorted(o)
+ self.assertBoth(o, 'g', 3)
+ o.g = 4
+ self.assertBoth(o, 'g', 4)
+ del o.g
+ with pytest.raises(KeyError):
+ operator.__getitem__(o, 'g')
+ with pytest.raises(AttributeError):
+ getattr(o, 'g')
+ del o['f']
+ with pytest.raises(KeyError):
+ operator.__getitem__(o, 'f')
+ with pytest.raises(AttributeError):
+ getattr(o, 'f')
+
+ def test_inject(self):
+ class foon(dict):
+ self.inject(locals())
+
+ self.test_AttrAccessible(foon)
+
+
+class Test_ProxiedAttrs:
+
+ kls = mappings.ProxiedAttrs
+
+ def test_it(self):
+ class foo:
+ def __init__(self, **kwargs):
+ for attr, val in kwargs.items():
+ setattr(self, attr, val)
+ obj = foo()
+ d = self.kls(obj)
+ with pytest.raises(KeyError):
+ operator.__getitem__(d, 'x')
+ with pytest.raises(KeyError):
+ operator.__delitem__(d, 'x')
+ assert 'x' not in d
+ d['x'] = 1
+ assert d['x'] == 1
+ assert 'x' in d
+ assert ['x'] == list(x for x in d if not x.startswith("__"))
+ del d['x']
+ assert 'x' not in d
+ with pytest.raises(KeyError):
+ operator.__delitem__(d, 'x')
+ with pytest.raises(KeyError):
+ operator.__getitem__(d, 'x')
+
+ # Finally, verify that immutable attribute errors are handled correctly.
+ d = self.kls(object())
+ with pytest.raises(KeyError):
+ operator.__setitem__(d, 'x', 1)
+ with pytest.raises(KeyError):
+ operator.__delitem__(d, 'x')
+
+
+class TestSlottedDict:
+
+ kls = staticmethod(mappings.make_SlottedDict_kls)
+
+ def test_exceptions(self):
+ d = self.kls(['spork'])()
+ for op in (operator.getitem, operator.delitem):
+ with pytest.raises(KeyError):
+ op(d, 'spork')
+ with pytest.raises(KeyError):
+ op(d, 'foon')