diff options
Diffstat (limited to 'tests/__init__.py')
-rw-r--r-- | tests/__init__.py | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..35ac81a --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,304 @@ +import sys +import unittest + + +class CacheTestMixin: + + Cache = None + + def test_defaults(self): + cache = self.Cache(maxsize=1) + self.assertEqual(0, len(cache)) + self.assertEqual(1, cache.maxsize) + self.assertEqual(0, cache.currsize) + self.assertEqual(1, cache.getsizeof(None)) + self.assertEqual(1, cache.getsizeof("")) + self.assertEqual(1, cache.getsizeof(0)) + self.assertTrue(repr(cache).startswith(cache.__class__.__name__)) + + def test_insert(self): + cache = self.Cache(maxsize=2) + + cache.update({1: 1, 2: 2}) + self.assertEqual(2, len(cache)) + self.assertEqual(1, cache[1]) + self.assertEqual(2, cache[2]) + + cache[3] = 3 + self.assertEqual(2, len(cache)) + self.assertEqual(3, cache[3]) + self.assertTrue(1 in cache or 2 in cache) + + cache[4] = 4 + self.assertEqual(2, len(cache)) + self.assertEqual(4, cache[4]) + self.assertTrue(1 in cache or 2 in cache or 3 in cache) + + def test_update(self): + cache = self.Cache(maxsize=2) + + cache.update({1: 1, 2: 2}) + self.assertEqual(2, len(cache)) + self.assertEqual(1, cache[1]) + self.assertEqual(2, cache[2]) + + cache.update({1: 1, 2: 2}) + self.assertEqual(2, len(cache)) + self.assertEqual(1, cache[1]) + self.assertEqual(2, cache[2]) + + cache.update({1: "a", 2: "b"}) + self.assertEqual(2, len(cache)) + self.assertEqual("a", cache[1]) + self.assertEqual("b", cache[2]) + + def test_delete(self): + cache = self.Cache(maxsize=2) + + cache.update({1: 1, 2: 2}) + self.assertEqual(2, len(cache)) + self.assertEqual(1, cache[1]) + self.assertEqual(2, cache[2]) + + del cache[2] + self.assertEqual(1, len(cache)) + self.assertEqual(1, cache[1]) + self.assertNotIn(2, cache) + + del cache[1] + self.assertEqual(0, len(cache)) + self.assertNotIn(1, cache) + self.assertNotIn(2, cache) + + with self.assertRaises(KeyError): + del cache[1] + self.assertEqual(0, len(cache)) + self.assertNotIn(1, cache) + self.assertNotIn(2, cache) + + def test_pop(self): + cache = self.Cache(maxsize=2) + + cache.update({1: 1, 2: 2}) + self.assertEqual(2, cache.pop(2)) + self.assertEqual(1, len(cache)) + self.assertEqual(1, cache.pop(1)) + self.assertEqual(0, len(cache)) + + with self.assertRaises(KeyError): + cache.pop(2) + with self.assertRaises(KeyError): + cache.pop(1) + with self.assertRaises(KeyError): + cache.pop(0) + + self.assertEqual(None, cache.pop(2, None)) + self.assertEqual(None, cache.pop(1, None)) + self.assertEqual(None, cache.pop(0, None)) + + def test_popitem(self): + cache = self.Cache(maxsize=2) + + cache.update({1: 1, 2: 2}) + self.assertIn(cache.pop(1), {1: 1, 2: 2}) + self.assertEqual(1, len(cache)) + self.assertIn(cache.pop(2), {1: 1, 2: 2}) + self.assertEqual(0, len(cache)) + + with self.assertRaises(KeyError): + cache.popitem() + + @unittest.skipUnless(sys.version_info >= (3, 7), "requires Python 3.7") + def test_popitem_exception_context(self): + # since Python 3.7, MutableMapping.popitem() suppresses + # exception context as implementation detail + exception = None + try: + self.Cache(maxsize=2).popitem() + except Exception as e: + exception = e + self.assertIsNone(exception.__cause__) + self.assertTrue(exception.__suppress_context__) + + def test_missing(self): + class DefaultCache(self.Cache): + def __missing__(self, key): + self[key] = key + return key + + cache = DefaultCache(maxsize=2) + + self.assertEqual(0, cache.currsize) + self.assertEqual(2, cache.maxsize) + self.assertEqual(0, len(cache)) + self.assertEqual(1, cache[1]) + self.assertEqual(2, cache[2]) + self.assertEqual(2, len(cache)) + self.assertTrue(1 in cache and 2 in cache) + + self.assertEqual(3, cache[3]) + self.assertEqual(2, len(cache)) + self.assertTrue(3 in cache) + self.assertTrue(1 in cache or 2 in cache) + self.assertTrue(1 not in cache or 2 not in cache) + + self.assertEqual(4, cache[4]) + self.assertEqual(2, len(cache)) + self.assertTrue(4 in cache) + self.assertTrue(1 in cache or 2 in cache or 3 in cache) + + # verify __missing__() is *not* called for any operations + # besides __getitem__() + + self.assertEqual(4, cache.get(4)) + self.assertEqual(None, cache.get(5)) + self.assertEqual(5 * 5, cache.get(5, 5 * 5)) + self.assertEqual(2, len(cache)) + + self.assertEqual(4, cache.pop(4)) + with self.assertRaises(KeyError): + cache.pop(5) + self.assertEqual(None, cache.pop(5, None)) + self.assertEqual(5 * 5, cache.pop(5, 5 * 5)) + self.assertEqual(1, len(cache)) + + cache.clear() + cache[1] = 1 + 1 + self.assertEqual(1 + 1, cache.setdefault(1)) + self.assertEqual(1 + 1, cache.setdefault(1, 1)) + self.assertEqual(1 + 1, cache[1]) + self.assertEqual(2 + 2, cache.setdefault(2, 2 + 2)) + self.assertEqual(2 + 2, cache.setdefault(2, None)) + self.assertEqual(2 + 2, cache.setdefault(2)) + self.assertEqual(2 + 2, cache[2]) + self.assertEqual(2, len(cache)) + self.assertTrue(1 in cache and 2 in cache) + self.assertEqual(None, cache.setdefault(3)) + self.assertEqual(2, len(cache)) + self.assertTrue(3 in cache) + self.assertTrue(1 in cache or 2 in cache) + self.assertTrue(1 not in cache or 2 not in cache) + + def test_missing_getsizeof(self): + class DefaultCache(self.Cache): + def __missing__(self, key): + try: + self[key] = key + except ValueError: + pass # not stored + return key + + cache = DefaultCache(maxsize=2, getsizeof=lambda x: x) + + self.assertEqual(0, cache.currsize) + self.assertEqual(2, cache.maxsize) + + self.assertEqual(1, cache[1]) + self.assertEqual(1, len(cache)) + self.assertEqual(1, cache.currsize) + self.assertIn(1, cache) + + self.assertEqual(2, cache[2]) + self.assertEqual(1, len(cache)) + self.assertEqual(2, cache.currsize) + self.assertNotIn(1, cache) + self.assertIn(2, cache) + + self.assertEqual(3, cache[3]) # not stored + self.assertEqual(1, len(cache)) + self.assertEqual(2, cache.currsize) + self.assertEqual(1, cache[1]) + self.assertEqual(1, len(cache)) + self.assertEqual(1, cache.currsize) + self.assertEqual((1, 1), cache.popitem()) + + def _test_getsizeof(self, cache): + self.assertEqual(0, cache.currsize) + self.assertEqual(3, cache.maxsize) + self.assertEqual(1, cache.getsizeof(1)) + self.assertEqual(2, cache.getsizeof(2)) + self.assertEqual(3, cache.getsizeof(3)) + + cache.update({1: 1, 2: 2}) + self.assertEqual(2, len(cache)) + self.assertEqual(3, cache.currsize) + self.assertEqual(1, cache[1]) + self.assertEqual(2, cache[2]) + + cache[1] = 2 + self.assertEqual(1, len(cache)) + self.assertEqual(2, cache.currsize) + self.assertEqual(2, cache[1]) + self.assertNotIn(2, cache) + + cache.update({1: 1, 2: 2}) + self.assertEqual(2, len(cache)) + self.assertEqual(3, cache.currsize) + self.assertEqual(1, cache[1]) + self.assertEqual(2, cache[2]) + + cache[3] = 3 + self.assertEqual(1, len(cache)) + self.assertEqual(3, cache.currsize) + self.assertEqual(3, cache[3]) + self.assertNotIn(1, cache) + self.assertNotIn(2, cache) + + with self.assertRaises(ValueError): + cache[3] = 4 + self.assertEqual(1, len(cache)) + self.assertEqual(3, cache.currsize) + self.assertEqual(3, cache[3]) + + with self.assertRaises(ValueError): + cache[4] = 4 + self.assertEqual(1, len(cache)) + self.assertEqual(3, cache.currsize) + self.assertEqual(3, cache[3]) + + def test_getsizeof_param(self): + self._test_getsizeof(self.Cache(maxsize=3, getsizeof=lambda x: x)) + + def test_getsizeof_subclass(self): + class Cache(self.Cache): + def getsizeof(self, value): + return value + + self._test_getsizeof(Cache(maxsize=3)) + + def test_pickle(self): + import pickle + + source = self.Cache(maxsize=2) + source.update({1: 1, 2: 2}) + + cache = pickle.loads(pickle.dumps(source)) + self.assertEqual(source, cache) + + self.assertEqual(2, len(cache)) + self.assertEqual(1, cache[1]) + self.assertEqual(2, cache[2]) + + cache[3] = 3 + self.assertEqual(2, len(cache)) + self.assertEqual(3, cache[3]) + self.assertTrue(1 in cache or 2 in cache) + + cache[4] = 4 + self.assertEqual(2, len(cache)) + self.assertEqual(4, cache[4]) + self.assertTrue(1 in cache or 2 in cache or 3 in cache) + + self.assertEqual(cache, pickle.loads(pickle.dumps(cache))) + + def test_pickle_maxsize(self): + import pickle + import sys + + # test empty cache, single element, large cache (recursion limit) + for n in [0, 1, sys.getrecursionlimit() * 2]: + source = self.Cache(maxsize=n) + source.update((i, i) for i in range(n)) + cache = pickle.loads(pickle.dumps(source)) + self.assertEqual(n, len(cache)) + self.assertEqual(source, cache) |