@@ -1046,3 +1046,127 @@ class subclass(str):
10461046 s = "\x96 \0 \x13 \x1d \x18 \x03 "
10471047 assert {42 : 4 , s : 1 }[subclass (s )] == 1
10481048 assert {42 : 4 , subclass (s ): 1 }[s ] == 1
1049+
1050+ def test_append_in_eq_during_lookup ():
1051+ class Key :
1052+ def __init__ (self , d , hash ):
1053+ self .d = d
1054+ self .done = False
1055+ self .hash = hash
1056+ def __hash__ (self ):
1057+ return self .hash
1058+ def __eq__ (self , other ):
1059+ if not self .done :
1060+ self .done = True
1061+ d [self .hash ] = 'expected value ' + str (self .hash )
1062+ return other is self
1063+
1064+ d = dict ()
1065+ for i in range (256 ):
1066+ k = Key (d , i )
1067+ d [k ] = 'other value ' + str (i )
1068+ # 1 should have the same hash as Key, the __eq__ should insert actual 1.
1069+ # What may happen:
1070+ # 1. the insertion does not cause rehashing, no side effect is detected and lookup
1071+ # is not restarted, but it still finds the item, because now it is in a collision chain
1072+ # 2. the insertion causes rehashing, the indices array is relocated, the side effect
1073+ # is detected and we restart the lookup
1074+ assert d [i ] == 'expected value ' + str (i )
1075+
1076+ def test_delete_in_eq_during_lookup ():
1077+ class Key :
1078+ def __init__ (self , d ):
1079+ self .d = d
1080+ self .done = False
1081+ def __hash__ (self ):
1082+ return 1
1083+ def __eq__ (self , other ):
1084+ if not self .done :
1085+ self .done = True
1086+ del d [self ]
1087+ return isinstance (other , Key )
1088+
1089+ d = dict ()
1090+ # repeat few times to trigger re-hashing
1091+ for i in range (256 ):
1092+ d [Key (d )] = 'some value'
1093+ # Here CPython detects the side effect and restarts the lookup
1094+ assert d .get (Key (d ), None ) is None
1095+
1096+ def test_delete_in_eq_during_insert ():
1097+ class Key :
1098+ def __init__ (self , d ):
1099+ self .d = d
1100+ self .done = False
1101+ def __hash__ (self ):
1102+ return 1
1103+ def __eq__ (self , other ):
1104+ if not self .done :
1105+ self .done = True
1106+ del d [self ]
1107+ return isinstance (other , Key )
1108+
1109+ d = dict ()
1110+ # repeat few times to trigger compaction in delete
1111+ for i in range (256 ):
1112+ d [Key (d )] = 'some value'
1113+ # Here CPython detects the side effects and restarts the insertion
1114+ d [1 ] = 'other value'
1115+ assert d == {1 : 'other value' }
1116+ del d [1 ]
1117+
1118+ def test_override_inserted_value_in_eq ():
1119+ class Key :
1120+ def __init__ (self , d ):
1121+ self .d = d
1122+ self .done = False
1123+ def __hash__ (self ):
1124+ return 1
1125+ def __eq__ (self , other ):
1126+ if not self .done :
1127+ self .done = True
1128+ d [self ] = 'override value'
1129+ return isinstance (other , Key )
1130+
1131+ d = dict ()
1132+ # repeat few times to trigger compaction and rehashing
1133+ for i in range (256 ):
1134+ d [Key (d )] = 'some value'
1135+ # Here CPython detects the side effect and restarts the lookup
1136+ val = d [Key (d )]
1137+ assert val == 'override value' , val
1138+ del d [Key (d )]
1139+
1140+ def test_check_ref_identity_before_eq ():
1141+ eq_calls = 0
1142+ class Key :
1143+ def __hash__ (self ):
1144+ return 1
1145+ def __eq__ (self , other ):
1146+ nonlocal eq_calls
1147+ eq_calls += 1
1148+ return self is other
1149+
1150+ # check that our __eq__ works
1151+ k = Key ()
1152+ assert k == k
1153+ assert eq_calls == 1
1154+
1155+ d = dict ()
1156+ d [k ] = 'some value'
1157+ assert d [k ] == 'some value'
1158+ assert eq_calls == 1
1159+
1160+ # TODO: GR-40680
1161+ # def test_iteration_and_del():
1162+ # def test_iter(get_iterable):
1163+ # try:
1164+ # d = {'a': 1, 'b': 2}
1165+ # for k in get_iterable(d):
1166+ # d['b'] = 42
1167+ # except RuntimeError as e:
1168+ # return
1169+ # assert False
1170+ # test_iter(lambda d: d.keys())
1171+ # test_iter(lambda d: d.values())
1172+ # test_iter(lambda d: d.items())
0 commit comments