From d191ef25c11f2e4d6a8c27f246ca18c6ff05997e Mon Sep 17 00:00:00 2001
From: Raymond Hettinger <python@rcn.com>
Date: Sat, 7 Jan 2017 20:44:48 -0800
Subject: [PATCH] Issue #29200: Add test for lru cache only calling __hash__
 once

---
 Lib/test/test_functools.py | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

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
-- 
2.40.0