@@ -1046,3 +1046,127 @@ class subclass(str):
1046
1046
s = "\x96 \0 \x13 \x1d \x18 \x03 "
1047
1047
assert {42 : 4 , s : 1 }[subclass (s )] == 1
1048
1048
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