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