1 # -*- coding: utf-8 -*-
5 from threading
import Lock
, Thread
6 from freezegun
import freeze_time
11 def CheckFactory(cached_property_decorator
, threadsafe
=False):
13 Create dynamically a Check class whose add_cached method is decorated by
14 the cached_property_decorator.
19 self
.control_total
= 0
24 def add_control(self
):
25 self
.control_total
+= 1
26 return self
.control_total
28 @cached_property_decorator
32 # Need to guard this since += isn't atomic.
34 self
.cached_total
+= 1
36 self
.cached_total
+= 1
38 return self
.cached_total
40 def run_threads(self
, num_threads
):
42 for _
in range(num_threads
):
43 thread
= Thread(target
=lambda: self
.add_cached
)
45 threads
.append(thread
)
46 for thread
in threads
:
52 class TestCachedProperty(unittest
.TestCase
):
53 """Tests for cached_property"""
55 cached_property_factory
= cached_property
.cached_property
57 def assert_control(self
, check
, expected
):
59 Assert that both `add_control` and 'control_total` equal `expected`
61 self
.assertEqual(check
.add_control
, expected
)
62 self
.assertEqual(check
.control_total
, expected
)
64 def assert_cached(self
, check
, expected
):
66 Assert that both `add_cached` and 'cached_total` equal `expected`
68 self
.assertEqual(check
.add_cached
, expected
)
69 self
.assertEqual(check
.cached_total
, expected
)
71 def test_cached_property(self
):
72 Check
= CheckFactory(self
.cached_property_factory
)
75 # The control shows that we can continue to add 1
76 self
.assert_control(check
, 1)
77 self
.assert_control(check
, 2)
79 # The cached version demonstrates how nothing is added after the first
80 self
.assert_cached(check
, 1)
81 self
.assert_cached(check
, 1)
83 # The cache does not expire
84 with
freeze_time("9999-01-01"):
85 self
.assert_cached(check
, 1)
87 # Typically descriptors return themselves if accessed though the class
88 # rather than through an instance.
89 self
.assertTrue(isinstance(Check
.add_cached
, self
.cached_property_factory
))
91 def test_reset_cached_property(self
):
92 Check
= CheckFactory(self
.cached_property_factory
)
95 # Run standard cache assertion
96 self
.assert_cached(check
, 1)
97 self
.assert_cached(check
, 1)
102 # Value is cached again after the next access
103 self
.assert_cached(check
, 2)
104 self
.assert_cached(check
, 2)
106 def test_none_cached_property(self
):
109 self
.cached_total
= None
111 @self.cached_property_factory
112 def add_cached(self
):
113 return self
.cached_total
115 self
.assert_cached(Check(), None)
117 def test_set_cached_property(self
):
118 Check
= CheckFactory(self
.cached_property_factory
)
120 check
.add_cached
= "foo"
121 self
.assertEqual(check
.add_cached
, "foo")
122 self
.assertEqual(check
.cached_total
, 0)
124 def test_threads(self
):
125 Check
= CheckFactory(self
.cached_property_factory
, threadsafe
=True)
129 # cached_property_with_ttl is *not* thread-safe!
130 check
.run_threads(num_threads
)
131 # This assertion hinges on the fact the system executing the test can
132 # spawn and start running num_threads threads within the sleep period
133 # (defined in the Check class as 1 second). If num_threads were to be
134 # massively increased (try 10000), the actual value returned would be
135 # between 1 and num_threads, depending on thread scheduling and
137 self
.assert_cached(check
, num_threads
)
138 self
.assert_cached(check
, num_threads
)
140 # The cache does not expire
141 with
freeze_time("9999-01-01"):
142 check
.run_threads(num_threads
)
143 self
.assert_cached(check
, num_threads
)
144 self
.assert_cached(check
, num_threads
)
147 class TestThreadedCachedProperty(TestCachedProperty
):
148 """Tests for threaded_cached_property"""
150 cached_property_factory
= cached_property
.threaded_cached_property
152 def test_threads(self
):
153 Check
= CheckFactory(self
.cached_property_factory
, threadsafe
=True)
157 # threaded_cached_property_with_ttl is thread-safe
158 check
.run_threads(num_threads
)
159 self
.assert_cached(check
, 1)
160 self
.assert_cached(check
, 1)
162 # The cache does not expire
163 with
freeze_time("9999-01-01"):
164 check
.run_threads(num_threads
)
165 self
.assert_cached(check
, 1)
166 self
.assert_cached(check
, 1)
169 class TestCachedPropertyWithTTL(TestCachedProperty
):
170 """Tests for cached_property_with_ttl"""
172 cached_property_factory
= cached_property
.cached_property_with_ttl
174 def test_ttl_expiry(self
):
175 Check
= CheckFactory(self
.cached_property_factory(ttl
=100000))
178 # Run standard cache assertion
179 self
.assert_cached(check
, 1)
180 self
.assert_cached(check
, 1)
182 # The cache expires in the future
183 with
freeze_time("9999-01-01"):
184 self
.assert_cached(check
, 2)
185 self
.assert_cached(check
, 2)
187 # Things are not reverted when we are back to the present
188 self
.assert_cached(check
, 2)
189 self
.assert_cached(check
, 2)
191 def test_threads_ttl_expiry(self
):
192 Check
= CheckFactory(self
.cached_property_factory(ttl
=100000), threadsafe
=True)
196 # Same as in test_threads
197 check
.run_threads(num_threads
)
198 self
.assert_cached(check
, num_threads
)
199 self
.assert_cached(check
, num_threads
)
201 # The cache expires in the future
202 with
freeze_time("9999-01-01"):
203 check
.run_threads(num_threads
)
204 self
.assert_cached(check
, 2 * num_threads
)
205 self
.assert_cached(check
, 2 * num_threads
)
207 # Things are not reverted when we are back to the present
208 self
.assert_cached(check
, 2 * num_threads
)
209 self
.assert_cached(check
, 2 * num_threads
)
212 class TestThreadedCachedPropertyWithTTL(
213 TestThreadedCachedProperty
, TestCachedPropertyWithTTL
215 """Tests for threaded_cached_property_with_ttl"""
217 cached_property_factory
= cached_property
.threaded_cached_property_with_ttl
219 def test_threads_ttl_expiry(self
):
220 Check
= CheckFactory(self
.cached_property_factory(ttl
=100000), threadsafe
=True)
224 # Same as in test_threads
225 check
.run_threads(num_threads
)
226 self
.assert_cached(check
, 1)
227 self
.assert_cached(check
, 1)
229 # The cache expires in the future
230 with
freeze_time("9999-01-01"):
231 check
.run_threads(num_threads
)
232 self
.assert_cached(check
, 2)
233 self
.assert_cached(check
, 2)
235 # Things are not reverted when we are back to the present
236 self
.assert_cached(check
, 2)
237 self
.assert_cached(check
, 2)