diff options
author | Raymond Hettinger <python@rcn.com> | 2017-01-08 04:44:48 (GMT) |
---|---|---|
committer | Raymond Hettinger <python@rcn.com> | 2017-01-08 04:44:48 (GMT) |
commit | d191ef25c11f2e4d6a8c27f246ca18c6ff05997e (patch) | |
tree | 8e06506ad468c49a2235ddf6b49fde19edd240e3 | |
parent | 4f5c6a27d84ea72d00510e0a9c9f8e69db17a765 (diff) | |
download | cpython-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.py | 36 |
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 |