1from collections import defaultdict
2import datetime
3import itertools
4import time
5from unittest import TestCase
6
7import mock
8
9from dogpile.cache import CacheRegion
10from dogpile.cache import exception
11from dogpile.cache import make_region
12from dogpile.cache import util
13from dogpile.cache.api import CacheBackend
14from dogpile.cache.api import CachedValue
15from dogpile.cache.api import NO_VALUE
16from dogpile.cache.proxy import ProxyBackend
17from dogpile.cache.region import _backend_loader
18from dogpile.cache.region import RegionInvalidationStrategy
19from dogpile.cache.region import value_version
20from dogpile.util import compat
21from . import assert_raises_message
22from . import configparser
23from . import eq_
24from . import io
25from . import is_
26from ._fixtures import MockBackend
27
28
29def key_mangler(key):
30    return "HI!" + key
31
32
33class APITest(TestCase):
34    def test_no_value_str(self):
35        eq_(str(NO_VALUE), "<dogpile.cache.api.NoValue object>")
36
37
38class RegionTest(TestCase):
39    def _region(self, init_args={}, config_args={}, backend="mock"):
40        reg = CacheRegion(**init_args)
41        reg.configure(backend, **config_args)
42        return reg
43
44    def test_set_name(self):
45        my_region = make_region(name="my-name")
46        eq_(my_region.name, "my-name")
47
48    def test_instance_from_dict(self):
49        my_conf = {
50            "cache.example.backend": "mock",
51            "cache.example.expiration_time": 600,
52            "cache.example.arguments.url": "127.0.0.1",
53        }
54        my_region = make_region()
55        my_region.configure_from_config(my_conf, "cache.example.")
56        eq_(my_region.expiration_time, 600)
57        assert isinstance(my_region.backend, MockBackend) is True
58        eq_(my_region.backend.arguments, {"url": "127.0.0.1"})
59
60    def test_instance_from_config_string(self):
61        my_conf = (
62            "[xyz]\n"
63            "cache.example.backend=mock\n"
64            "cache.example.expiration_time=600\n"
65            "cache.example.arguments.url=127.0.0.1\n"
66            "cache.example.arguments.dogpile_lockfile=false\n"
67            "cache.example.arguments.xyz=None\n"
68        )
69
70        my_region = make_region()
71        config = configparser.ConfigParser()
72        compat.read_config_file(config, io.StringIO(my_conf))
73
74        my_region.configure_from_config(
75            dict(config.items("xyz")), "cache.example."
76        )
77        eq_(my_region.expiration_time, 600)
78        assert isinstance(my_region.backend, MockBackend) is True
79        eq_(
80            my_region.backend.arguments,
81            {"url": "127.0.0.1", "dogpile_lockfile": False, "xyz": None},
82        )
83
84    def test_datetime_expiration_time(self):
85        my_region = make_region()
86        my_region.configure(
87            backend="mock", expiration_time=datetime.timedelta(days=1, hours=8)
88        )
89        eq_(my_region.expiration_time, 32 * 60 * 60)
90
91    def test_reject_invalid_expiration_time(self):
92        my_region = make_region()
93
94        assert_raises_message(
95            exception.ValidationError,
96            "expiration_time is not a number or timedelta.",
97            my_region.configure,
98            "mock",
99            "one hour",
100        )
101
102    def test_key_mangler_argument(self):
103        reg = self._region(init_args={"key_mangler": key_mangler})
104        assert reg.key_mangler is key_mangler
105
106        reg = self._region()
107        assert reg.key_mangler is None
108
109        MockBackend.key_mangler = lambda self, k: "foo"
110        reg = self._region()
111        eq_(reg.key_mangler("bar"), "foo")
112        MockBackend.key_mangler = None
113
114    def test_key_mangler_impl(self):
115        reg = self._region(init_args={"key_mangler": key_mangler})
116
117        reg.set("some key", "some value")
118        eq_(list(reg.backend._cache), ["HI!some key"])
119        eq_(reg.get("some key"), "some value")
120        eq_(
121            reg.get_or_create("some key", lambda: "some new value"),
122            "some value",
123        )
124        reg.delete("some key")
125        eq_(reg.get("some key"), NO_VALUE)
126
127    def test_dupe_config(self):
128        reg = CacheRegion()
129        reg.configure("mock")
130        assert_raises_message(
131            exception.RegionAlreadyConfigured,
132            "This region is already configured",
133            reg.configure,
134            "mock",
135        )
136        eq_(reg.is_configured, True)
137
138    def test_replace_backend_config(self):
139        reg = CacheRegion()
140
141        reg.configure("dogpile.cache.null")
142        eq_(reg.is_configured, True)
143
144        null_backend = _backend_loader.load("dogpile.cache.null")
145        assert reg.key_mangler is null_backend.key_mangler
146
147        reg.configure("mock", replace_existing_backend=True)
148        eq_(reg.is_configured, True)
149
150        assert isinstance(reg.backend, MockBackend)
151        assert reg.key_mangler is MockBackend.key_mangler
152
153    def test_replace_backend_config_with_custom_key_mangler(self):
154        reg = CacheRegion(key_mangler=key_mangler)
155
156        reg.configure("dogpile.cache.null")
157        eq_(reg.is_configured, True)
158        assert reg.key_mangler is key_mangler
159
160        reg.configure("mock", replace_existing_backend=True)
161        eq_(reg.is_configured, True)
162        assert reg.key_mangler is key_mangler
163
164    def test_no_config(self):
165        reg = CacheRegion()
166        assert_raises_message(
167            exception.RegionNotConfigured,
168            "No backend is configured on this region.",
169            getattr,
170            reg,
171            "backend",
172        )
173        eq_(reg.is_configured, False)
174
175    def test_invalid_backend(self):
176        reg = CacheRegion()
177        assert_raises_message(
178            exception.PluginNotFound,
179            "Couldn't find cache plugin to load: unknown",
180            reg.configure,
181            "unknown",
182        )
183        eq_(reg.is_configured, False)
184
185    def test_set_get_value(self):
186        reg = self._region()
187        reg.set("some key", "some value")
188        eq_(reg.get("some key"), "some value")
189
190    def test_set_get_nothing(self):
191        reg = self._region()
192        eq_(reg.get("some key"), NO_VALUE)
193        eq_(reg.get("some key", expiration_time=10), NO_VALUE)
194        reg.invalidate()
195        eq_(reg.get("some key"), NO_VALUE)
196
197    def test_creator(self):
198        reg = self._region()
199
200        def creator():
201            return "some value"
202
203        eq_(reg.get_or_create("some key", creator), "some value")
204
205    def test_multi_creator(self):
206        reg = self._region()
207
208        def creator(*keys):
209            return ["some value %s" % key for key in keys]
210
211        eq_(
212            reg.get_or_create_multi(["k3", "k2", "k5"], creator),
213            ["some value k3", "some value k2", "some value k5"],
214        )
215
216    def test_remove(self):
217        reg = self._region()
218        reg.set("some key", "some value")
219        reg.delete("some key")
220        reg.delete("some key")
221        eq_(reg.get("some key"), NO_VALUE)
222
223    def test_expire(self):
224        reg = self._region(config_args={"expiration_time": 1})
225        counter = itertools.count(1)
226
227        def creator():
228            return "some value %d" % next(counter)
229
230        eq_(reg.get_or_create("some key", creator), "some value 1")
231        time.sleep(2)
232        is_(reg.get("some key"), NO_VALUE)
233        eq_(reg.get("some key", ignore_expiration=True), "some value 1")
234        eq_(
235            reg.get_or_create("some key", creator, expiration_time=-1),
236            "some value 1",
237        )
238        eq_(reg.get_or_create("some key", creator), "some value 2")
239        eq_(reg.get("some key"), "some value 2")
240
241    def test_expire_multi(self):
242        reg = self._region(config_args={"expiration_time": 1})
243        counter = itertools.count(1)
244
245        def creator(*keys):
246            return ["some value %s %d" % (key, next(counter)) for key in keys]
247
248        eq_(
249            reg.get_or_create_multi(["k3", "k2", "k5"], creator),
250            ["some value k3 2", "some value k2 1", "some value k5 3"],
251        )
252        time.sleep(2)
253        is_(reg.get("k2"), NO_VALUE)
254        eq_(reg.get("k2", ignore_expiration=True), "some value k2 1")
255        eq_(
256            reg.get_or_create_multi(["k3", "k2"], creator, expiration_time=-1),
257            ["some value k3 2", "some value k2 1"],
258        )
259        eq_(
260            reg.get_or_create_multi(["k3", "k2"], creator),
261            ["some value k3 5", "some value k2 4"],
262        )
263        eq_(reg.get("k2"), "some value k2 4")
264
265    def test_expire_on_get(self):
266        reg = self._region(config_args={"expiration_time": 0.5})
267        reg.set("some key", "some value")
268        eq_(reg.get("some key"), "some value")
269        time.sleep(1)
270        is_(reg.get("some key"), NO_VALUE)
271
272    def test_ignore_expire_on_get(self):
273        reg = self._region(config_args={"expiration_time": 0.5})
274        reg.set("some key", "some value")
275        eq_(reg.get("some key"), "some value")
276        time.sleep(1)
277        eq_(reg.get("some key", ignore_expiration=True), "some value")
278
279    def test_override_expire_on_get(self):
280        reg = self._region(config_args={"expiration_time": 0.5})
281        reg.set("some key", "some value")
282        eq_(reg.get("some key"), "some value")
283        time.sleep(1)
284        eq_(reg.get("some key", expiration_time=5), "some value")
285        is_(reg.get("some key"), NO_VALUE)
286
287    def test_expire_override(self):
288        reg = self._region(config_args={"expiration_time": 5})
289        counter = itertools.count(1)
290
291        def creator():
292            return "some value %d" % next(counter)
293
294        eq_(
295            reg.get_or_create("some key", creator, expiration_time=1),
296            "some value 1",
297        )
298        time.sleep(2)
299        eq_(reg.get("some key"), "some value 1")
300        eq_(
301            reg.get_or_create("some key", creator, expiration_time=1),
302            "some value 2",
303        )
304        eq_(reg.get("some key"), "some value 2")
305
306    def test_hard_invalidate_get(self):
307        reg = self._region()
308        reg.set("some key", "some value")
309        time.sleep(0.1)
310        reg.invalidate()
311        is_(reg.get("some key"), NO_VALUE)
312
313    def test_hard_invalidate_get_or_create(self):
314        reg = self._region()
315        counter = itertools.count(1)
316
317        def creator():
318            return "some value %d" % next(counter)
319
320        eq_(reg.get_or_create("some key", creator), "some value 1")
321
322        time.sleep(0.1)
323        reg.invalidate()
324        eq_(reg.get_or_create("some key", creator), "some value 2")
325
326        eq_(reg.get_or_create("some key", creator), "some value 2")
327
328        reg.invalidate()
329        eq_(reg.get_or_create("some key", creator), "some value 3")
330
331        eq_(reg.get_or_create("some key", creator), "some value 3")
332
333    def test_hard_invalidate_get_or_create_multi(self):
334        reg = self._region()
335        counter = itertools.count(1)
336
337        def creator(*keys):
338            return ["some value %s %d" % (k, next(counter)) for k in keys]
339
340        eq_(
341            reg.get_or_create_multi(["k1", "k2"], creator),
342            ["some value k1 1", "some value k2 2"],
343        )
344
345        time.sleep(0.1)
346        reg.invalidate()
347        eq_(
348            reg.get_or_create_multi(["k1", "k2"], creator),
349            ["some value k1 3", "some value k2 4"],
350        )
351
352        eq_(
353            reg.get_or_create_multi(["k1", "k2"], creator),
354            ["some value k1 3", "some value k2 4"],
355        )
356
357        reg.invalidate()
358        eq_(
359            reg.get_or_create_multi(["k1", "k2"], creator),
360            ["some value k1 5", "some value k2 6"],
361        )
362
363        eq_(
364            reg.get_or_create_multi(["k1", "k2"], creator),
365            ["some value k1 5", "some value k2 6"],
366        )
367
368    def test_soft_invalidate_get(self):
369        reg = self._region(config_args={"expiration_time": 1})
370        reg.set("some key", "some value")
371        time.sleep(0.1)
372        reg.invalidate(hard=False)
373        is_(reg.get("some key"), NO_VALUE)
374
375    def test_soft_invalidate_get_or_create(self):
376        reg = self._region(config_args={"expiration_time": 1})
377        counter = itertools.count(1)
378
379        def creator():
380            return "some value %d" % next(counter)
381
382        eq_(reg.get_or_create("some key", creator), "some value 1")
383
384        time.sleep(0.1)
385        reg.invalidate(hard=False)
386        eq_(reg.get_or_create("some key", creator), "some value 2")
387
388    def test_soft_invalidate_get_or_create_multi(self):
389        reg = self._region(config_args={"expiration_time": 5})
390        values = [1, 2, 3]
391
392        def creator(*keys):
393            v = values.pop(0)
394            return [v for k in keys]
395
396        ret = reg.get_or_create_multi([1, 2], creator)
397        eq_(ret, [1, 1])
398        time.sleep(0.1)
399        reg.invalidate(hard=False)
400        ret = reg.get_or_create_multi([1, 2], creator)
401        eq_(ret, [2, 2])
402
403    def test_soft_invalidate_requires_expire_time_get(self):
404        reg = self._region()
405        reg.invalidate(hard=False)
406        assert_raises_message(
407            exception.DogpileCacheException,
408            "Non-None expiration time required for soft invalidation",
409            reg.get_or_create,
410            "some key",
411            lambda: "x",
412        )
413
414    def test_soft_invalidate_requires_expire_time_get_multi(self):
415        reg = self._region()
416        reg.invalidate(hard=False)
417        assert_raises_message(
418            exception.DogpileCacheException,
419            "Non-None expiration time required for soft invalidation",
420            reg.get_or_create_multi,
421            ["k1", "k2"],
422            lambda k: "x",
423        )
424
425    def test_should_cache_fn(self):
426        reg = self._region()
427        values = [1, 2, 3]
428
429        def creator():
430            return values.pop(0)
431
432        should_cache_fn = lambda val: val in (1, 3)  # noqa
433        ret = reg.get_or_create(
434            "some key", creator, should_cache_fn=should_cache_fn
435        )
436        eq_(ret, 1)
437        eq_(reg.backend._cache["some key"][0], 1)
438        time.sleep(0.1)
439        reg.invalidate()
440        ret = reg.get_or_create(
441            "some key", creator, should_cache_fn=should_cache_fn
442        )
443        eq_(ret, 2)
444        eq_(reg.backend._cache["some key"][0], 1)
445        reg.invalidate()
446        ret = reg.get_or_create(
447            "some key", creator, should_cache_fn=should_cache_fn
448        )
449        eq_(ret, 3)
450        eq_(reg.backend._cache["some key"][0], 3)
451
452    def test_should_cache_fn_multi(self):
453        reg = self._region()
454        values = [1, 2, 3]
455
456        def creator(*keys):
457            v = values.pop(0)
458            return [v for k in keys]
459
460        should_cache_fn = lambda val: val in (1, 3)  # noqa
461        ret = reg.get_or_create_multi(
462            [1, 2], creator, should_cache_fn=should_cache_fn
463        )
464        eq_(ret, [1, 1])
465        eq_(reg.backend._cache[1][0], 1)
466        time.sleep(0.1)
467        reg.invalidate()
468        ret = reg.get_or_create_multi(
469            [1, 2], creator, should_cache_fn=should_cache_fn
470        )
471        eq_(ret, [2, 2])
472        eq_(reg.backend._cache[1][0], 1)
473        time.sleep(0.1)
474        reg.invalidate()
475        ret = reg.get_or_create_multi(
476            [1, 2], creator, should_cache_fn=should_cache_fn
477        )
478        eq_(ret, [3, 3])
479        eq_(reg.backend._cache[1][0], 3)
480
481    def test_should_set_multiple_values(self):
482        reg = self._region()
483        values = {"key1": "value1", "key2": "value2", "key3": "value3"}
484        reg.set_multi(values)
485        eq_(values["key1"], reg.get("key1"))
486        eq_(values["key2"], reg.get("key2"))
487        eq_(values["key3"], reg.get("key3"))
488
489    def test_should_get_multiple_values(self):
490        reg = self._region()
491        values = {"key1": "value1", "key2": "value2", "key3": "value3"}
492        reg.set_multi(values)
493        reg_values = reg.get_multi(["key1", "key2", "key3"])
494        eq_(reg_values, ["value1", "value2", "value3"])
495
496    def test_should_delete_multiple_values(self):
497        reg = self._region()
498        values = {"key1": "value1", "key2": "value2", "key3": "value3"}
499        reg.set_multi(values)
500        reg.delete_multi(["key2", "key1000"])
501        eq_(values["key1"], reg.get("key1"))
502        eq_(NO_VALUE, reg.get("key2"))
503        eq_(values["key3"], reg.get("key3"))
504
505
506class ProxyRegionTest(RegionTest):
507
508    """ This is exactly the same as the region test above, but it goes through
509    a dummy proxy.  The purpose of this is to make sure the tests  still run
510    successfully even when there is a proxy """
511
512    class MockProxy(ProxyBackend):
513        @property
514        def _cache(self):
515            return self.proxied._cache
516
517    def _region(self, init_args={}, config_args={}, backend="mock"):
518        reg = CacheRegion(**init_args)
519        config_args["wrap"] = [ProxyRegionTest.MockProxy]
520        reg.configure(backend, **config_args)
521        return reg
522
523
524class CustomInvalidationStrategyTest(RegionTest):
525
526    """Try region tests with custom invalidation strategy.
527
528    This is exactly the same as the region test above, but it uses custom
529    invalidation strategy. The purpose of this is to make sure the tests
530    still run successfully even when there is a proxy.
531
532    """
533
534    class CustomInvalidationStrategy(RegionInvalidationStrategy):
535        def __init__(self):
536            self._soft_invalidated = None
537            self._hard_invalidated = None
538
539        def invalidate(self, hard=None):
540            if hard:
541                self._soft_invalidated = None
542                self._hard_invalidated = time.time()
543            else:
544                self._soft_invalidated = time.time()
545                self._hard_invalidated = None
546
547        def is_invalidated(self, timestamp):
548            return (
549                self._soft_invalidated and timestamp < self._soft_invalidated
550            ) or (
551                self._hard_invalidated and timestamp < self._hard_invalidated
552            )
553
554        def was_hard_invalidated(self):
555            return bool(self._hard_invalidated)
556
557        def is_hard_invalidated(self, timestamp):
558            return (
559                self._hard_invalidated and timestamp < self._hard_invalidated
560            )
561
562        def was_soft_invalidated(self):
563            return bool(self._soft_invalidated)
564
565        def is_soft_invalidated(self, timestamp):
566            return (
567                self._soft_invalidated and timestamp < self._soft_invalidated
568            )
569
570    def _region(self, init_args={}, config_args={}, backend="mock"):
571        reg = CacheRegion(**init_args)
572        invalidator = self.CustomInvalidationStrategy()
573        reg.configure(backend, region_invalidator=invalidator, **config_args)
574        return reg
575
576
577class TestProxyValue(object):
578    def __init__(self, value):
579        self.value = value
580
581
582class AsyncCreatorTest(TestCase):
583    def _fixture(self):
584        def async_creation_runner(cache, somekey, creator, mutex):
585            try:
586                value = creator()
587                cache.set(somekey, value)
588            finally:
589                mutex.release()
590
591        return mock.Mock(side_effect=async_creation_runner)
592
593    def test_get_or_create(self):
594        acr = self._fixture()
595        reg = CacheRegion(async_creation_runner=acr)
596        reg.configure("mock", expiration_time=0.2)
597
598        def some_value():
599            return "some value"
600
601        def some_new_value():
602            return "some new value"
603
604        eq_(reg.get_or_create("some key", some_value), "some value")
605        time.sleep(0.5)
606        eq_(reg.get_or_create("some key", some_new_value), "some value")
607        eq_(reg.get_or_create("some key", some_new_value), "some new value")
608        eq_(
609            acr.mock_calls,
610            [
611                mock.call(
612                    reg, "some key", some_new_value, reg._mutex("some key")
613                )
614            ],
615        )
616
617    def test_fn_decorator(self):
618        acr = self._fixture()
619        reg = CacheRegion(async_creation_runner=acr)
620        reg.configure("mock", expiration_time=5)
621
622        canary = mock.Mock()
623
624        @reg.cache_on_arguments()
625        def go(x, y):
626            canary(x, y)
627            return x + y
628
629        eq_(go(1, 2), 3)
630        eq_(go(1, 2), 3)
631
632        eq_(canary.mock_calls, [mock.call(1, 2)])
633
634        eq_(go(3, 4), 7)
635
636        eq_(canary.mock_calls, [mock.call(1, 2), mock.call(3, 4)])
637
638        reg.invalidate(hard=False)
639
640        eq_(go(1, 2), 3)
641
642        eq_(
643            canary.mock_calls,
644            [mock.call(1, 2), mock.call(3, 4), mock.call(1, 2)],
645        )
646
647        eq_(
648            acr.mock_calls,
649            [
650                mock.call(
651                    reg,
652                    "tests.cache.test_region:go|1 2",
653                    mock.ANY,
654                    reg._mutex("tests.cache.test_region:go|1 2"),
655                )
656            ],
657        )
658
659    def test_fn_decorator_with_kw(self):
660        acr = self._fixture()
661        reg = CacheRegion(async_creation_runner=acr)
662        reg.configure("mock", expiration_time=5)
663
664        @reg.cache_on_arguments()
665        def go(x, **kw):
666            return x
667
668        test_value = TestProxyValue("Decorator Test")
669        self.assertRaises(ValueError, go, x=1, foo=test_value)
670
671        @reg.cache_on_arguments()
672        def go2(x):
673            return x
674
675        # keywords that match positional names can be passed
676        result = go2(x=test_value)
677        self.assertTrue(isinstance(result, TestProxyValue))
678
679
680class ProxyBackendTest(TestCase):
681    class GetCounterProxy(ProxyBackend):
682        counter = 0
683
684        def get(self, key):
685            ProxyBackendTest.GetCounterProxy.counter += 1
686            return self.proxied.get(key)
687
688    class SetCounterProxy(ProxyBackend):
689        counter = 0
690
691        def set(self, key, value):
692            ProxyBackendTest.SetCounterProxy.counter += 1
693            return self.proxied.set(key, value)
694
695    class UsedKeysProxy(ProxyBackend):
696
697        """ Keep a counter of hose often we set a particular key"""
698
699        def __init__(self, *args, **kwargs):
700            super(ProxyBackendTest.UsedKeysProxy, self).__init__(
701                *args, **kwargs
702            )
703            self._key_count = defaultdict(lambda: 0)
704
705        def setcount(self, key):
706            return self._key_count[key]
707
708        def set(self, key, value):
709            self._key_count[key] += 1
710            self.proxied.set(key, value)
711
712    class NeverSetProxy(ProxyBackend):
713
714        """ A totally contrived example of a Proxy that we pass arguments to.
715        Never set a key that matches never_set """
716
717        def __init__(self, never_set, *args, **kwargs):
718            super(ProxyBackendTest.NeverSetProxy, self).__init__(
719                *args, **kwargs
720            )
721            self.never_set = never_set
722            self._key_count = defaultdict(lambda: 0)
723
724        def set(self, key, value):
725            if key != self.never_set:
726                self.proxied.set(key, value)
727
728    class CanModifyCachedValueProxy(ProxyBackend):
729        def get(self, key):
730            value = ProxyBackend.get(self, key)
731            assert isinstance(value, CachedValue)
732            return value
733
734        def set(self, key, value):
735            assert isinstance(value, CachedValue)
736            ProxyBackend.set(self, key, value)
737
738    def _region(self, init_args={}, config_args={}, backend="mock"):
739        reg = CacheRegion(**init_args)
740        reg.configure(backend, **config_args)
741        return reg
742
743    def test_cachedvalue_passed(self):
744        reg = self._region(
745            config_args={"wrap": [ProxyBackendTest.CanModifyCachedValueProxy]}
746        )
747
748        reg.set("some key", "some value")
749        eq_(reg.get("some key"), "some value")
750
751    def test_counter_proxies(self):
752        # count up the gets and sets and make sure they are passed through
753        # to the backend properly.  Test that methods not overridden
754        # continue to work
755
756        reg = self._region(
757            config_args={
758                "wrap": [
759                    ProxyBackendTest.GetCounterProxy,
760                    ProxyBackendTest.SetCounterProxy,
761                ]
762            }
763        )
764        ProxyBackendTest.GetCounterProxy.counter = 0
765        ProxyBackendTest.SetCounterProxy.counter = 0
766
767        # set a range of values in the cache
768        for i in range(10):
769            reg.set(i, i)
770        eq_(ProxyBackendTest.GetCounterProxy.counter, 0)
771        eq_(ProxyBackendTest.SetCounterProxy.counter, 10)
772
773        # check that the range of values is still there
774        for i in range(10):
775            v = reg.get(i)
776            eq_(v, i)
777        eq_(ProxyBackendTest.GetCounterProxy.counter, 10)
778        eq_(ProxyBackendTest.SetCounterProxy.counter, 10)
779
780        # make sure the delete function(not overridden) still
781        # executes properly
782        for i in range(10):
783            reg.delete(i)
784            v = reg.get(i)
785            is_(v, NO_VALUE)
786
787    def test_instance_proxies(self):
788        # Test that we can create an instance of a new proxy and
789        # pass that to make_region instead of the class.  The two instances
790        # should not interfere with each other
791        proxy_num = ProxyBackendTest.UsedKeysProxy(5)
792        proxy_abc = ProxyBackendTest.UsedKeysProxy(5)
793        reg_num = self._region(config_args={"wrap": [proxy_num]})
794        reg_abc = self._region(config_args={"wrap": [proxy_abc]})
795        for i in range(10):
796            reg_num.set(i, True)
797            reg_abc.set(chr(ord("a") + i), True)
798
799        for i in range(5):
800            reg_num.set(i, True)
801            reg_abc.set(chr(ord("a") + i), True)
802
803        # make sure proxy_num has the right counts per key
804        eq_(proxy_num.setcount(1), 2)
805        eq_(proxy_num.setcount(9), 1)
806        eq_(proxy_num.setcount("a"), 0)
807
808        # make sure proxy_abc has the right counts per key
809        eq_(proxy_abc.setcount("a"), 2)
810        eq_(proxy_abc.setcount("g"), 1)
811        eq_(proxy_abc.setcount("9"), 0)
812
813    def test_argument_proxies(self):
814        # Test that we can pass an argument to Proxy on creation
815        proxy = ProxyBackendTest.NeverSetProxy(5)
816        reg = self._region(config_args={"wrap": [proxy]})
817        for i in range(10):
818            reg.set(i, True)
819
820        # make sure 1 was set, but 5 was not
821        eq_(reg.get(5), NO_VALUE)
822        eq_(reg.get(1), True)
823
824    def test_actual_backend_proxied(self):
825        # ensure that `reg.actual_backend` is the actual backend
826        # also ensure that `reg.backend` is a proxied backend
827        reg = self._region(
828            config_args={
829                "wrap": [
830                    ProxyBackendTest.GetCounterProxy,
831                    ProxyBackendTest.SetCounterProxy,
832                ]
833            }
834        )
835        assert isinstance(reg.backend, ProxyBackend)
836        assert isinstance(reg.actual_backend, CacheBackend)
837
838    def test_actual_backend_noproxy(self):
839        # ensure that `reg.actual_backend` is the actual backend
840        # also ensure that `reg.backend` is NOT a proxied backend
841        reg = self._region()
842        assert isinstance(reg.backend, CacheBackend)
843        assert isinstance(reg.actual_backend, CacheBackend)
844
845
846class LoggingTest(TestCase):
847    def _region(self, init_args={}, config_args={}, backend="mock"):
848        reg = CacheRegion(**init_args)
849        reg.configure(backend, **config_args)
850        return reg
851
852    def test_log_time(self):
853        reg = self._region()
854
855        times = [50, 55, 60]
856
857        def mock_time():
858            return times.pop(0)
859
860        with mock.patch("dogpile.cache.region.log") as mock_log, mock.patch(
861            "dogpile.cache.region.time", mock.Mock(time=mock_time)
862        ):
863            with reg._log_time(["foo", "bar", "bat"]):
864                pass
865
866        eq_(
867            mock_log.mock_calls,
868            [
869                mock.call.debug(
870                    "Cache value generated in %(seconds).3f "
871                    "seconds for key(s): %(keys)r",
872                    {
873                        "seconds": 5,
874                        "keys": util.repr_obj(["foo", "bar", "bat"]),
875                    },
876                )
877            ],
878        )
879
880    def test_repr_obj_truncated(self):
881
882        eq_(
883            repr(util.repr_obj(["some_big_long_name" for i in range(200)])),
884            "['some_big_long_name', 'some_big_long_name', "
885            "'some_big_long_name', 'some_big_long_name', 'some_big_long_name',"
886            " 'some_big_long_name', 'some_big_long_na ... "
887            "(4100 characters truncated) ... me_big_long_name', "
888            "'some_big_long_name', 'some_big_long_name', 'some_big_long_"
889            "name', 'some_big_long_name', 'some_big_long_name', "
890            "'some_big_long_name']",
891        )
892
893    def test_log_is_cache_miss(self):
894        reg = self._region()
895
896        with mock.patch("dogpile.cache.region.log") as mock_log:
897            is_(reg._is_cache_miss(NO_VALUE, "some key"), True)
898        eq_(
899            mock_log.mock_calls,
900            [mock.call.debug("No value present for key: %r", "some key")],
901        )
902
903    def test_log_is_value_version_miss(self):
904
905        reg = self._region()
906        inv = mock.Mock(is_hard_invalidated=lambda val: True)
907        with mock.patch(
908            "dogpile.cache.region.log"
909        ) as mock_log, mock.patch.object(reg, "region_invalidator", inv):
910            is_(
911                reg._is_cache_miss(
912                    CachedValue(
913                        "some value", {"v": value_version - 5, "ct": 500}
914                    ),
915                    "some key",
916                ),
917                True,
918            )
919        eq_(
920            mock_log.mock_calls,
921            [
922                mock.call.debug(
923                    "Dogpile version update for key: %r", "some key"
924                )
925            ],
926        )
927
928    def test_log_is_hard_invalidated(self):
929
930        reg = self._region()
931        inv = mock.Mock(is_hard_invalidated=lambda val: True)
932        with mock.patch(
933            "dogpile.cache.region.log"
934        ) as mock_log, mock.patch.object(reg, "region_invalidator", inv):
935            is_(
936                reg._is_cache_miss(
937                    CachedValue("some value", {"v": value_version, "ct": 500}),
938                    "some key",
939                ),
940                True,
941            )
942        eq_(
943            mock_log.mock_calls,
944            [
945                mock.call.debug(
946                    "Hard invalidation detected for key: %r", "some key"
947                )
948            ],
949        )
950