From 120794a0f0e42ff4555380a585dd4ee9f2304b37 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 7 Jan 2017 20:44:48 -0800 Subject: Issue #29200: Add test for lru cache only calling __hash__ once --- Lib/test/test_functools.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'Lib/test/test_functools.py') diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 3a40861594..535b35380b 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 -- cgit v1.2.1 From b31d572ce9205ad0f4e9794fe5178b417bc4e717 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Sat, 7 Jan 2017 20:53:09 -0800 Subject: Issue #29200: Fix test to use self.assertEqual instead of py.test style tests --- Lib/test/test_functools.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'Lib/test/test_functools.py') diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 535b35380b..ae0d9f70ee 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -1207,24 +1207,24 @@ class TestLRU: 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) + 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 - assert f(mock_int, 1) == 16 - assert mock_int.__hash__.call_count == 2 - assert f.cache_info() == (1, 1, 1, 1) + 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 - assert f(6, 2) == 20 - assert mock_int.__hash__.call_count == 2 - assert f.cache_info() == (1, 2, 1, 1) + 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 - assert f(mock_int, 1) == 16 - assert mock_int.__hash__.call_count == 3 - assert f.cache_info() == (1, 3, 1, 1) + 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 -- cgit v1.2.1 From 409c6f3fb41e050d8e103f0e3362428a10b7027f Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Mon, 9 Jan 2017 07:50:19 -0800 Subject: Add test for ea064ff3c10f --- Lib/test/test_functools.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'Lib/test/test_functools.py') diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 9f4899ee73..eeb3a227a2 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -1238,6 +1238,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 -- cgit v1.2.1