summaryrefslogtreecommitdiff
path: root/Lib/test/test_functools.py
diff options
context:
space:
mode:
authorRaymond Hettinger <python@rcn.com>2017-02-06 07:15:57 -0800
committerRaymond Hettinger <python@rcn.com>2017-02-06 07:15:57 -0800
commit826745ba953b4ef23462fb0bc2d7b3db23b51d89 (patch)
tree7789cc87df07c2786c40e6888cbb532a94ce6334 /Lib/test/test_functools.py
parent95b272b4e0d5438a12702e51e05d03f5a5a8e505 (diff)
parent515f1cf20f4e9656b1bcda236bad8ed0e33770f0 (diff)
downloadcpython-826745ba953b4ef23462fb0bc2d7b3db23b51d89.tar.gz
merge
Diffstat (limited to 'Lib/test/test_functools.py')
-rw-r--r--Lib/test/test_functools.py45
1 files changed, 45 insertions, 0 deletions
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index 824549b803..63fe83e5db 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -9,6 +9,7 @@ import sys
from test import support
import time
import unittest
+import unittest.mock
from weakref import proxy
import contextlib
try:
@@ -1191,6 +1192,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
+ self.assertEqual(f(mock_int, 1), 16)
+ self.assertEqual(mock_int.__hash__.call_count, 1)
+ self.assertEqual(f.cache_info(), (0, 1, 1, 1))
+
+ # Cache hit: One use as an argument gives one additional call
+ self.assertEqual(f(mock_int, 1), 16)
+ self.assertEqual(mock_int.__hash__.call_count, 2)
+ self.assertEqual(f.cache_info(), (1, 1, 1, 1))
+
+ # Cache eviction: No use as an argument gives no additonal call
+ self.assertEqual(f(6, 2), 20)
+ self.assertEqual(mock_int.__hash__.call_count, 2)
+ self.assertEqual(f.cache_info(), (1, 2, 1, 1))
+
+ # Cache miss: One use as an argument gives one additional call
+ self.assertEqual(f(mock_int, 1), 16)
+ self.assertEqual(mock_int.__hash__.call_count, 3)
+ self.assertEqual(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
@@ -1203,6 +1239,15 @@ class TestLRU:
finally:
builtins.len = old_len
+ def test_lru_star_arg_handling(self):
+ # Test regression that arose in ea064ff3c10f
+ @functools.lru_cache()
+ def f(*args):
+ return args
+
+ self.assertEqual(f(1, 2), (1, 2))
+ self.assertEqual(f((1, 2)), ((1, 2),))
+
def test_lru_type_error(self):
# Regression test for issue #28653.
# lru_cache was leaking when one of the arguments