summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaymond Hettinger <python@rcn.com>2017-01-08 04:44:48 (GMT)
committerRaymond Hettinger <python@rcn.com>2017-01-08 04:44:48 (GMT)
commitd191ef25c11f2e4d6a8c27f246ca18c6ff05997e (patch)
tree8e06506ad468c49a2235ddf6b49fde19edd240e3
parent4f5c6a27d84ea72d00510e0a9c9f8e69db17a765 (diff)
downloadcpython-d191ef25c11f2e4d6a8c27f246ca18c6ff05997e.zip
cpython-d191ef25c11f2e4d6a8c27f246ca18c6ff05997e.tar.gz
cpython-d191ef25c11f2e4d6a8c27f246ca18c6ff05997e.tar.bz2
Issue #29200: Add test for lru cache only calling __hash__ once
-rw-r--r--Lib/test/test_functools.py36
1 files changed, 36 insertions, 0 deletions
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index 3a40861..535b353 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -8,6 +8,7 @@ from random import choice
import sys
from test import support
import unittest
+import unittest.mock
from weakref import proxy
import contextlib
try:
@@ -1190,6 +1191,41 @@ class TestLRU:
self.assertEqual(misses, 4)
self.assertEqual(currsize, 2)
+ def test_lru_hash_only_once(self):
+ # To protect against weird reentrancy bugs and to improve
+ # efficiency when faced with slow __hash__ methods, the
+ # LRU cache guarantees that it will only call __hash__
+ # only once per use as an argument to the cached function.
+
+ @self.module.lru_cache(maxsize=1)
+ def f(x, y):
+ return x * 3 + y
+
+ # Simulate the integer 5
+ mock_int = unittest.mock.Mock()
+ mock_int.__mul__ = unittest.mock.Mock(return_value=15)
+ mock_int.__hash__ = unittest.mock.Mock(return_value=999)
+
+ # Add to cache: One use as an argument gives one call
+ assert f(mock_int, 1) == 16
+ assert mock_int.__hash__.call_count == 1
+ assert f.cache_info() == (0, 1, 1, 1)
+
+ # Cache hit: One use as an argument gives one additional call
+ assert f(mock_int, 1) == 16
+ assert mock_int.__hash__.call_count == 2
+ assert f.cache_info() == (1, 1, 1, 1)
+
+ # Cache eviction: No use as an argument gives no additonal call
+ assert f(6, 2) == 20
+ assert mock_int.__hash__.call_count == 2
+ assert f.cache_info() == (1, 2, 1, 1)
+
+ # Cache miss: One use as an argument gives one additional call
+ assert f(mock_int, 1) == 16
+ assert mock_int.__hash__.call_count == 3
+ assert f.cache_info() == (1, 3, 1, 1)
+
def test_lru_reentrancy_with_len(self):
# Test to make sure the LRU cache code isn't thrown-off by
# caching the built-in len() function. Since len() can be