1import unittest
2
3import cachetools.func
4
5
6class DecoratorTestMixin:
7    def decorator(self, maxsize, **kwargs):
8        return self.DECORATOR(maxsize, **kwargs)
9
10    def test_decorator(self):
11        cached = self.decorator(maxsize=2)(lambda n: n)
12        self.assertEqual(cached.cache_parameters(), {"maxsize": 2, "typed": False})
13        self.assertEqual(cached.cache_info(), (0, 0, 2, 0))
14        self.assertEqual(cached(1), 1)
15        self.assertEqual(cached.cache_info(), (0, 1, 2, 1))
16        self.assertEqual(cached(1), 1)
17        self.assertEqual(cached.cache_info(), (1, 1, 2, 1))
18        self.assertEqual(cached(1.0), 1.0)
19        self.assertEqual(cached.cache_info(), (2, 1, 2, 1))
20
21    def test_decorator_clear(self):
22        cached = self.decorator(maxsize=2)(lambda n: n)
23        self.assertEqual(cached.cache_parameters(), {"maxsize": 2, "typed": False})
24        self.assertEqual(cached.cache_info(), (0, 0, 2, 0))
25        self.assertEqual(cached(1), 1)
26        self.assertEqual(cached.cache_info(), (0, 1, 2, 1))
27        cached.cache_clear()
28        self.assertEqual(cached.cache_info(), (0, 0, 2, 0))
29        self.assertEqual(cached(1), 1)
30        self.assertEqual(cached.cache_info(), (0, 1, 2, 1))
31
32    def test_decorator_nocache(self):
33        cached = self.decorator(maxsize=0)(lambda n: n)
34        self.assertEqual(cached.cache_parameters(), {"maxsize": 0, "typed": False})
35        self.assertEqual(cached.cache_info(), (0, 0, 0, 0))
36        self.assertEqual(cached(1), 1)
37        self.assertEqual(cached.cache_info(), (0, 1, 0, 0))
38        self.assertEqual(cached(1), 1)
39        self.assertEqual(cached.cache_info(), (0, 2, 0, 0))
40        self.assertEqual(cached(1.0), 1.0)
41        self.assertEqual(cached.cache_info(), (0, 3, 0, 0))
42
43    def test_decorator_unbound(self):
44        cached = self.decorator(maxsize=None)(lambda n: n)
45        self.assertEqual(cached.cache_parameters(), {"maxsize": None, "typed": False})
46        self.assertEqual(cached.cache_info(), (0, 0, None, 0))
47        self.assertEqual(cached(1), 1)
48        self.assertEqual(cached.cache_info(), (0, 1, None, 1))
49        self.assertEqual(cached(1), 1)
50        self.assertEqual(cached.cache_info(), (1, 1, None, 1))
51        self.assertEqual(cached(1.0), 1.0)
52        self.assertEqual(cached.cache_info(), (2, 1, None, 1))
53
54    def test_decorator_typed(self):
55        cached = self.decorator(maxsize=2, typed=True)(lambda n: n)
56        self.assertEqual(cached.cache_parameters(), {"maxsize": 2, "typed": True})
57        self.assertEqual(cached.cache_info(), (0, 0, 2, 0))
58        self.assertEqual(cached(1), 1)
59        self.assertEqual(cached.cache_info(), (0, 1, 2, 1))
60        self.assertEqual(cached(1), 1)
61        self.assertEqual(cached.cache_info(), (1, 1, 2, 1))
62        self.assertEqual(cached(1.0), 1.0)
63        self.assertEqual(cached.cache_info(), (1, 2, 2, 2))
64        self.assertEqual(cached(1.0), 1.0)
65        self.assertEqual(cached.cache_info(), (2, 2, 2, 2))
66
67    def test_decorator_user_function(self):
68        cached = self.decorator(lambda n: n)
69        self.assertEqual(cached.cache_parameters(), {"maxsize": 128, "typed": False})
70        self.assertEqual(cached.cache_info(), (0, 0, 128, 0))
71        self.assertEqual(cached(1), 1)
72        self.assertEqual(cached.cache_info(), (0, 1, 128, 1))
73        self.assertEqual(cached(1), 1)
74        self.assertEqual(cached.cache_info(), (1, 1, 128, 1))
75        self.assertEqual(cached(1.0), 1.0)
76        self.assertEqual(cached.cache_info(), (2, 1, 128, 1))
77
78    def test_decorator_needs_rlock(self):
79        cached = self.decorator(lambda n: n)
80
81        class RecursiveEquals:
82            def __init__(self, use_cache):
83                self._use_cache = use_cache
84
85            def __hash__(self):
86                return hash(self._use_cache)
87
88            def __eq__(self, other):
89                if self._use_cache:
90                    # This call will happen while the cache-lock is held,
91                    # requiring a reentrant lock to avoid deadlock.
92                    cached(self)
93                return self._use_cache == other._use_cache
94
95        # Prime the cache.
96        cached(RecursiveEquals(False))
97        cached(RecursiveEquals(True))
98        # Then do a call which will cause a deadlock with a non-reentrant lock.
99        self.assertEqual(cached(RecursiveEquals(True)), RecursiveEquals(True))
100
101
102class FIFODecoratorTest(unittest.TestCase, DecoratorTestMixin):
103
104    DECORATOR = staticmethod(cachetools.func.fifo_cache)
105
106
107class LFUDecoratorTest(unittest.TestCase, DecoratorTestMixin):
108
109    DECORATOR = staticmethod(cachetools.func.lfu_cache)
110
111
112class LRUDecoratorTest(unittest.TestCase, DecoratorTestMixin):
113
114    DECORATOR = staticmethod(cachetools.func.lru_cache)
115
116
117class MRUDecoratorTest(unittest.TestCase, DecoratorTestMixin):
118
119    DECORATOR = staticmethod(cachetools.func.mru_cache)
120
121
122class RRDecoratorTest(unittest.TestCase, DecoratorTestMixin):
123
124    DECORATOR = staticmethod(cachetools.func.rr_cache)
125
126
127class TTLDecoratorTest(unittest.TestCase, DecoratorTestMixin):
128
129    DECORATOR = staticmethod(cachetools.func.ttl_cache)
130