Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(82203)

Side by Side Diff: Lib/test/test_functools.py

Issue 14373: C implementation of functools.lru_cache
Patch Set: Created 5 years, 9 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 import abc 1 import abc
2 import collections 2 import collections
3 from itertools import permutations 3 from itertools import permutations
4 import pickle 4 import pickle
5 from random import choice 5 from random import choice
6 import sys 6 import sys
7 from test import support 7 from test import support
8 import unittest 8 import unittest
9 from weakref import proxy 9 from weakref import proxy
10 try:
11 import threading
12 except ImportError:
13 threading = None
10 14
11 import functools 15 import functools
12 16
13 py_functools = support.import_fresh_module('functools', blocked=['_functools']) 17 py_functools = support.import_fresh_module('functools', blocked=['_functools'])
14 c_functools = support.import_fresh_module('functools', fresh=['_functools']) 18 c_functools = support.import_fresh_module('functools', fresh=['_functools'])
15 19
16 decimal = support.import_fresh_module('decimal', fresh=['_decimal']) 20 decimal = support.import_fresh_module('decimal', fresh=['_decimal'])
17 21
18 22
19 def capture(*args, **kw): 23 def capture(*args, **kw):
(...skipping 850 matching lines...) Expand 10 before | Expand all | Expand 10 after
870 with self.assertRaises(TypeError): 874 with self.assertRaises(TypeError):
871 a >= b 875 a >= b
872 876
873 with self.subTest("LE when equal"): 877 with self.subTest("LE when equal"):
874 a = ComparatorNotImplemented(9) 878 a = ComparatorNotImplemented(9)
875 b = ComparatorNotImplemented(9) 879 b = ComparatorNotImplemented(9)
876 self.assertEqual(a, b) 880 self.assertEqual(a, b)
877 with self.assertRaises(TypeError): 881 with self.assertRaises(TypeError):
878 a <= b 882 a <= b
879 883
880 class TestLRU(unittest.TestCase): 884 class TestLRU:
881 885
882 def test_lru(self): 886 def test_lru(self):
883 def orig(x, y): 887 def orig(x, y):
884 return 3 * x + y 888 return 3 * x + y
885 f = functools.lru_cache(maxsize=20)(orig) 889 f = self.module.lru_cache(maxsize=20)(orig)
886 hits, misses, maxsize, currsize = f.cache_info() 890 hits, misses, maxsize, currsize = f.cache_info()
887 self.assertEqual(maxsize, 20) 891 self.assertEqual(maxsize, 20)
888 self.assertEqual(currsize, 0) 892 self.assertEqual(currsize, 0)
889 self.assertEqual(hits, 0) 893 self.assertEqual(hits, 0)
890 self.assertEqual(misses, 0) 894 self.assertEqual(misses, 0)
891 895
892 domain = range(5) 896 domain = range(5)
893 for i in range(1000): 897 for i in range(1000):
894 x, y = choice(domain), choice(domain) 898 x, y = choice(domain), choice(domain)
895 actual = f(x, y) 899 actual = f(x, y)
(...skipping 17 matching lines...) Expand all
913 917
914 # Test bypassing the cache 918 # Test bypassing the cache
915 self.assertIs(f.__wrapped__, orig) 919 self.assertIs(f.__wrapped__, orig)
916 f.__wrapped__(x, y) 920 f.__wrapped__(x, y)
917 hits, misses, maxsize, currsize = f.cache_info() 921 hits, misses, maxsize, currsize = f.cache_info()
918 self.assertEqual(hits, 0) 922 self.assertEqual(hits, 0)
919 self.assertEqual(misses, 1) 923 self.assertEqual(misses, 1)
920 self.assertEqual(currsize, 1) 924 self.assertEqual(currsize, 1)
921 925
922 # test size zero (which means "never-cache") 926 # test size zero (which means "never-cache")
923 @functools.lru_cache(0) 927 @self.module.lru_cache(0)
924 def f(): 928 def f():
925 nonlocal f_cnt 929 nonlocal f_cnt
926 f_cnt += 1 930 f_cnt += 1
927 return 20 931 return 20
928 self.assertEqual(f.cache_info().maxsize, 0) 932 self.assertEqual(f.cache_info().maxsize, 0)
929 f_cnt = 0 933 f_cnt = 0
930 for i in range(5): 934 for i in range(5):
931 self.assertEqual(f(), 20) 935 self.assertEqual(f(), 20)
932 self.assertEqual(f_cnt, 5) 936 self.assertEqual(f_cnt, 5)
933 hits, misses, maxsize, currsize = f.cache_info() 937 hits, misses, maxsize, currsize = f.cache_info()
934 self.assertEqual(hits, 0) 938 self.assertEqual(hits, 0)
935 self.assertEqual(misses, 5) 939 self.assertEqual(misses, 5)
936 self.assertEqual(currsize, 0) 940 self.assertEqual(currsize, 0)
937 941
938 # test size one 942 # test size one
939 @functools.lru_cache(1) 943 @self.module.lru_cache(1)
940 def f(): 944 def f():
941 nonlocal f_cnt 945 nonlocal f_cnt
942 f_cnt += 1 946 f_cnt += 1
943 return 20 947 return 20
944 self.assertEqual(f.cache_info().maxsize, 1) 948 self.assertEqual(f.cache_info().maxsize, 1)
945 f_cnt = 0 949 f_cnt = 0
946 for i in range(5): 950 for i in range(5):
947 self.assertEqual(f(), 20) 951 self.assertEqual(f(), 20)
948 self.assertEqual(f_cnt, 1) 952 self.assertEqual(f_cnt, 1)
949 hits, misses, maxsize, currsize = f.cache_info() 953 hits, misses, maxsize, currsize = f.cache_info()
950 self.assertEqual(hits, 4) 954 self.assertEqual(hits, 4)
951 self.assertEqual(misses, 1) 955 self.assertEqual(misses, 1)
952 self.assertEqual(currsize, 1) 956 self.assertEqual(currsize, 1)
953 957
954 # test size two 958 # test size two
955 @functools.lru_cache(2) 959 @self.module.lru_cache(2)
956 def f(x): 960 def f(x):
957 nonlocal f_cnt 961 nonlocal f_cnt
958 f_cnt += 1 962 f_cnt += 1
959 return x*10 963 return x*10
960 self.assertEqual(f.cache_info().maxsize, 2) 964 self.assertEqual(f.cache_info().maxsize, 2)
961 f_cnt = 0 965 f_cnt = 0
962 for x in 7, 9, 7, 9, 7, 9, 8, 8, 8, 9, 9, 9, 8, 8, 8, 7: 966 for x in 7, 9, 7, 9, 7, 9, 8, 8, 8, 9, 9, 9, 8, 8, 8, 7:
963 # * * * * 967 # * * * *
964 self.assertEqual(f(x), x*10) 968 self.assertEqual(f(x), x*10)
965 self.assertEqual(f_cnt, 4) 969 self.assertEqual(f_cnt, 4)
966 hits, misses, maxsize, currsize = f.cache_info() 970 hits, misses, maxsize, currsize = f.cache_info()
967 self.assertEqual(hits, 12) 971 self.assertEqual(hits, 12)
968 self.assertEqual(misses, 4) 972 self.assertEqual(misses, 4)
969 self.assertEqual(currsize, 2) 973 self.assertEqual(currsize, 2)
970 974
971 def test_lru_with_maxsize_none(self): 975 def test_lru_with_maxsize_none(self):
972 @functools.lru_cache(maxsize=None) 976 @self.module.lru_cache(maxsize=None)
973 def fib(n): 977 def fib(n):
974 if n < 2: 978 if n < 2:
975 return n 979 return n
976 return fib(n-1) + fib(n-2) 980 return fib(n-1) + fib(n-2)
977 self.assertEqual([fib(n) for n in range(16)], 981 self.assertEqual([fib(n) for n in range(16)],
978 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]) 982 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610])
979 self.assertEqual(fib.cache_info(), 983 self.assertEqual(fib.cache_info(),
980 functools._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)) 984 self.module._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16 ))
981 fib.cache_clear() 985 fib.cache_clear()
982 self.assertEqual(fib.cache_info(), 986 self.assertEqual(fib.cache_info(),
983 functools._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0)) 987 self.module._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))
988
989 def test_lru_with_maxsize_negative(self):
990 @self.module.lru_cache(maxsize=-10)
AntoinePitrou 2013/11/22 20:36:58 Is a negative size documented? Shouldn't it raise
storchaka 2013/11/22 21:54:56 This test tests that the behavior is match a behav
991 def eq(n):
992 return n
993 for i in (0, 1):
994 self.assertEqual([eq(n) for n in range(150)], list(range(150)))
995 self.assertEqual(eq.cache_info(),
996 self.module._CacheInfo(hits=0, misses=300, maxsize=-10, currsize=1))
984 997
985 def test_lru_with_exceptions(self): 998 def test_lru_with_exceptions(self):
986 # Verify that user_function exceptions get passed through without 999 # Verify that user_function exceptions get passed through without
987 # creating a hard-to-read chained exception. 1000 # creating a hard-to-read chained exception.
988 # http://bugs.python.org/issue13177 1001 # http://bugs.python.org/issue13177
989 for maxsize in (None, 128): 1002 for maxsize in (None, 128):
990 @functools.lru_cache(maxsize) 1003 @self.module.lru_cache(maxsize)
991 def func(i): 1004 def func(i):
992 return 'abc'[i] 1005 return 'abc'[i]
993 self.assertEqual(func(0), 'a') 1006 self.assertEqual(func(0), 'a')
994 with self.assertRaises(IndexError) as cm: 1007 with self.assertRaises(IndexError) as cm:
995 func(15) 1008 func(15)
996 self.assertIsNone(cm.exception.__context__) 1009 self.assertIsNone(cm.exception.__context__)
997 # Verify that the previous exception did not result in a cached entr y 1010 # Verify that the previous exception did not result in a cached entr y
998 with self.assertRaises(IndexError): 1011 with self.assertRaises(IndexError):
999 func(15) 1012 func(15)
1000 1013
1001 def test_lru_with_types(self): 1014 def test_lru_with_types(self):
1002 for maxsize in (None, 128): 1015 for maxsize in (None, 128):
1003 @functools.lru_cache(maxsize=maxsize, typed=True) 1016 @self.module.lru_cache(maxsize=maxsize, typed=True)
1004 def square(x): 1017 def square(x):
1005 return x * x 1018 return x * x
1006 self.assertEqual(square(3), 9) 1019 self.assertEqual(square(3), 9)
1007 self.assertEqual(type(square(3)), type(9)) 1020 self.assertEqual(type(square(3)), type(9))
1008 self.assertEqual(square(3.0), 9.0) 1021 self.assertEqual(square(3.0), 9.0)
1009 self.assertEqual(type(square(3.0)), type(9.0)) 1022 self.assertEqual(type(square(3.0)), type(9.0))
1010 self.assertEqual(square(x=3), 9) 1023 self.assertEqual(square(x=3), 9)
1011 self.assertEqual(type(square(x=3)), type(9)) 1024 self.assertEqual(type(square(x=3)), type(9))
1012 self.assertEqual(square(x=3.0), 9.0) 1025 self.assertEqual(square(x=3.0), 9.0)
1013 self.assertEqual(type(square(x=3.0)), type(9.0)) 1026 self.assertEqual(type(square(x=3.0)), type(9.0))
1014 self.assertEqual(square.cache_info().hits, 4) 1027 self.assertEqual(square.cache_info().hits, 4)
1015 self.assertEqual(square.cache_info().misses, 4) 1028 self.assertEqual(square.cache_info().misses, 4)
1016 1029
1017 def test_lru_with_keyword_args(self): 1030 def test_lru_with_keyword_args(self):
1018 @functools.lru_cache() 1031 @self.module.lru_cache()
1019 def fib(n): 1032 def fib(n):
1020 if n < 2: 1033 if n < 2:
1021 return n 1034 return n
1022 return fib(n=n-1) + fib(n=n-2) 1035 return fib(n=n-1) + fib(n=n-2)
1023 self.assertEqual( 1036 self.assertEqual(
1024 [fib(n=number) for number in range(16)], 1037 [fib(n=number) for number in range(16)],
1025 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610] 1038 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
1026 ) 1039 )
1027 self.assertEqual(fib.cache_info(), 1040 self.assertEqual(fib.cache_info(),
1028 functools._CacheInfo(hits=28, misses=16, maxsize=128, currsize=16)) 1041 self.module._CacheInfo(hits=28, misses=16, maxsize=128, currsize=16) )
1029 fib.cache_clear() 1042 fib.cache_clear()
1030 self.assertEqual(fib.cache_info(), 1043 self.assertEqual(fib.cache_info(),
1031 functools._CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)) 1044 self.module._CacheInfo(hits=0, misses=0, maxsize=128, currsize=0))
1032 1045
1033 def test_lru_with_keyword_args_maxsize_none(self): 1046 def test_lru_with_keyword_args_maxsize_none(self):
1034 @functools.lru_cache(maxsize=None) 1047 @self.module.lru_cache(maxsize=None)
1035 def fib(n): 1048 def fib(n):
1036 if n < 2: 1049 if n < 2:
1037 return n 1050 return n
1038 return fib(n=n-1) + fib(n=n-2) 1051 return fib(n=n-1) + fib(n=n-2)
1039 self.assertEqual([fib(n=number) for number in range(16)], 1052 self.assertEqual([fib(n=number) for number in range(16)],
1040 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]) 1053 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610])
1041 self.assertEqual(fib.cache_info(), 1054 self.assertEqual(fib.cache_info(),
1042 functools._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)) 1055 self.module._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16 ))
1043 fib.cache_clear() 1056 fib.cache_clear()
1044 self.assertEqual(fib.cache_info(), 1057 self.assertEqual(fib.cache_info(),
1045 functools._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0)) 1058 self.module._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0))
1059
1060 def test_lru_cache_decoration(self):
1061 def f(zomg: 'zomg_annotation'):
1062 """f doc string"""
1063 return 42
1064 g = self.module.lru_cache()(f)
1065 for attr in self.module.WRAPPER_ASSIGNMENTS:
1066 self.assertEqual(getattr(g, attr), getattr(f, attr))
1067
1068 @unittest.skipUnless(threading, 'This test requires threading.')
1069 def test_lru_cache_threaded(self):
1070 def orig(x, y):
1071 return 3 * x + y
1072 f = self.module.lru_cache(maxsize=20)(orig)
1073 hits, misses, maxsize, currsize = f.cache_info()
1074 self.assertEqual(currsize, 0)
1075
1076 def full(f, *args):
1077 for _ in range(10):
1078 f(*args)
1079
1080 def clear(f):
1081 for _ in range(10):
1082 f.cache_clear()
1083
1084 # create 5 threads in order to fill cache
AntoinePitrou 2013/11/22 20:36:58 I'm afraid the threads will be too short to really
storchaka 2013/11/22 22:37:06 Done.
1085 threads = []
1086 for k in range(5):
1087 t = threading.Thread(target=full, args=[f, k, k])
1088 t.start()
1089 threads.append(t)
1090
1091 for t in threads:
1092 t.join()
1093
1094 hits, misses, maxsize, currsize = f.cache_info()
1095 self.assertEqual(hits, 45)
1096 self.assertEqual(misses, 5)
1097 self.assertEqual(currsize, 5)
1098
1099 # create 5 threads in order to fill cache and 1 to clear it
1100 cleaner = threading.Thread(target=clear, args=[f])
1101 cleaner.start()
1102 threads = [cleaner]
1103 for k in range(5):
1104 t = threading.Thread(target=full, args=[f, k, k])
1105 t.start()
1106 threads.append(t)
1107
1108 for t in threads:
1109 t.join()
1046 1110
AntoinePitrou 2013/11/22 20:36:58 I think I would also like to see additional tests
storchaka 2013/11/22 22:37:06 I have no ideas right now. Could you propose a cod
AntoinePitrou 2013/11/22 22:38:30 I'm afraid I'm already busy right now :-(
1047 def test_need_for_rlock(self): 1111 def test_need_for_rlock(self):
1048 # This will deadlock on an LRU cache that uses a regular lock 1112 # This will deadlock on an LRU cache that uses a regular lock
1049 1113
1050 @functools.lru_cache(maxsize=10) 1114 @self.module.lru_cache(maxsize=10)
1051 def test_func(x): 1115 def test_func(x):
1052 'Used to demonstrate a reentrant lru_cache call within a single thre ad' 1116 'Used to demonstrate a reentrant lru_cache call within a single thre ad'
1053 return x 1117 return x
1054 1118
1055 class DoubleEq: 1119 class DoubleEq:
1056 'Demonstrate a reentrant lru_cache call within a single thread' 1120 'Demonstrate a reentrant lru_cache call within a single thread'
1057 def __init__(self, x): 1121 def __init__(self, x):
1058 self.x = x 1122 self.x = x
1059 def __hash__(self): 1123 def __hash__(self):
1060 return self.x 1124 return self.x
1061 def __eq__(self, other): 1125 def __eq__(self, other):
1062 if self.x == 2: 1126 if self.x == 2:
1063 test_func(DoubleEq(1)) 1127 test_func(DoubleEq(1))
1064 return self.x == other.x 1128 return self.x == other.x
1065 1129
1066 test_func(DoubleEq(1)) # Load the cache 1130 test_func(DoubleEq(1)) # Load the cache
1067 test_func(DoubleEq(2)) # Load the cache 1131 test_func(DoubleEq(2)) # Load the cache
1068 self.assertEqual(test_func(DoubleEq(2)), # Trigger a re-entrant __eq_ _ call 1132 self.assertEqual(test_func(DoubleEq(2)), # Trigger a re-entrant __eq_ _ call
1069 DoubleEq(2)) # Verify the correct return value 1133 DoubleEq(2)) # Verify the correct return value
1134
1135 class TestLRUC(TestLRU, unittest.TestCase):
1136 module = c_functools
1137
1138 class TestLRUPy(TestLRU, unittest.TestCase):
1139 module = py_functools
1070 1140
1071 1141
1072 class TestSingleDispatch(unittest.TestCase): 1142 class TestSingleDispatch(unittest.TestCase):
1073 def test_simple_overloads(self): 1143 def test_simple_overloads(self):
1074 @functools.singledispatch 1144 @functools.singledispatch
1075 def g(obj): 1145 def g(obj):
1076 return "base" 1146 return "base"
1077 def g_int(i): 1147 def g_int(i):
1078 return "integer" 1148 return "integer"
1079 g.register(int, g_int) 1149 g.register(int, g_int)
(...skipping 468 matching lines...) Expand 10 before | Expand all | Expand 10 after
1548 TestPartialC, 1618 TestPartialC,
1549 TestPartialPy, 1619 TestPartialPy,
1550 TestPartialCSubclass, 1620 TestPartialCSubclass,
1551 TestPartialMethod, 1621 TestPartialMethod,
1552 TestUpdateWrapper, 1622 TestUpdateWrapper,
1553 TestTotalOrdering, 1623 TestTotalOrdering,
1554 TestCmpToKeyC, 1624 TestCmpToKeyC,
1555 TestCmpToKeyPy, 1625 TestCmpToKeyPy,
1556 TestWraps, 1626 TestWraps,
1557 TestReduce, 1627 TestReduce,
1558 TestLRU, 1628 TestLRUC,
1629 TestLRUPy,
1559 TestSingleDispatch, 1630 TestSingleDispatch,
1560 ) 1631 )
1561 support.run_unittest(*test_classes) 1632 support.run_unittest(*test_classes)
1562 1633
1563 # verify reference counting 1634 # verify reference counting
1564 if verbose and hasattr(sys, "gettotalrefcount"): 1635 if verbose and hasattr(sys, "gettotalrefcount"):
1565 import gc 1636 import gc
1566 counts = [None] * 5 1637 counts = [None] * 5
1567 for i in range(len(counts)): 1638 for i in range(len(counts)):
1568 support.run_unittest(*test_classes) 1639 support.run_unittest(*test_classes)
1569 gc.collect() 1640 gc.collect()
1570 counts[i] = sys.gettotalrefcount() 1641 counts[i] = sys.gettotalrefcount()
1571 print(counts) 1642 print(counts)
1572 1643
1573 if __name__ == '__main__': 1644 if __name__ == '__main__':
1574 test_main(verbose=True) 1645 test_main(verbose=True)
OLDNEW
« no previous file with comments | « Lib/functools.py ('k') | Modules/_functoolsmodule.c » ('j') | Modules/_functoolsmodule.c » ('J')

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+