2
2
import contextlib
3
3
import copy
4
4
import gc
5
+ import operator
5
6
import pickle
7
+ import re
6
8
from random import randrange , shuffle
7
9
import struct
8
10
import sys
@@ -739,11 +741,44 @@ def test_ordered_dict_items_result_gc(self):
739
741
# when it's mutated and returned from __next__:
740
742
self .assertTrue (gc .is_tracked (next (it )))
741
743
744
+
745
+ class _TriggerSideEffectOnEqual :
746
+ count = 0 # number of calls to __eq__
747
+ trigger = 1 # count value when to trigger side effect
748
+
749
+ def __eq__ (self , other ):
750
+ if self .__class__ .count == self .__class__ .trigger :
751
+ self .side_effect ()
752
+ self .__class__ .count += 1
753
+ return True
754
+
755
+ def __hash__ (self ):
756
+ # all instances represent the same key
757
+ return - 1
758
+
759
+ def side_effect (self ):
760
+ raise NotImplementedError
761
+
742
762
class PurePythonOrderedDictTests (OrderedDictTests , unittest .TestCase ):
743
763
744
764
module = py_coll
745
765
OrderedDict = py_coll .OrderedDict
746
766
767
+ def test_issue119004_attribute_error (self ):
768
+ class Key (_TriggerSideEffectOnEqual ):
769
+ def side_effect (self ):
770
+ del dict1 [TODEL ]
771
+
772
+ TODEL = Key ()
773
+ dict1 = self .OrderedDict (dict .fromkeys ((0 , TODEL , 4.2 )))
774
+ dict2 = self .OrderedDict (dict .fromkeys ((0 , Key (), 4.2 )))
775
+ # This causes an AttributeError due to the linked list being changed
776
+ msg = re .escape ("'NoneType' object has no attribute 'key'" )
777
+ self .assertRaisesRegex (AttributeError , msg , operator .eq , dict1 , dict2 )
778
+ self .assertEqual (Key .count , 2 )
779
+ self .assertDictEqual (dict1 , dict .fromkeys ((0 , 4.2 )))
780
+ self .assertDictEqual (dict2 , dict .fromkeys ((0 , Key (), 4.2 )))
781
+
747
782
748
783
class CPythonBuiltinDictTests (unittest .TestCase ):
749
784
"""Builtin dict preserves insertion order.
@@ -764,8 +799,85 @@ class CPythonBuiltinDictTests(unittest.TestCase):
764
799
del method
765
800
766
801
802
+ class CPythonOrderedDictSideEffects :
803
+
804
+ def check_runtime_error_issue119004 (self , dict1 , dict2 ):
805
+ msg = re .escape ("OrderedDict mutated during iteration" )
806
+ self .assertRaisesRegex (RuntimeError , msg , operator .eq , dict1 , dict2 )
807
+
808
+ def test_issue119004_change_size_by_clear (self ):
809
+ class Key (_TriggerSideEffectOnEqual ):
810
+ def side_effect (self ):
811
+ dict1 .clear ()
812
+
813
+ dict1 = self .OrderedDict (dict .fromkeys ((0 , Key (), 4.2 )))
814
+ dict2 = self .OrderedDict (dict .fromkeys ((0 , Key (), 4.2 )))
815
+ self .check_runtime_error_issue119004 (dict1 , dict2 )
816
+ self .assertEqual (Key .count , 2 )
817
+ self .assertDictEqual (dict1 , {})
818
+ self .assertDictEqual (dict2 , dict .fromkeys ((0 , Key (), 4.2 )))
819
+
820
+ def test_issue119004_change_size_by_delete_key (self ):
821
+ class Key (_TriggerSideEffectOnEqual ):
822
+ def side_effect (self ):
823
+ del dict1 [TODEL ]
824
+
825
+ TODEL = Key ()
826
+ dict1 = self .OrderedDict (dict .fromkeys ((0 , TODEL , 4.2 )))
827
+ dict2 = self .OrderedDict (dict .fromkeys ((0 , Key (), 4.2 )))
828
+ self .check_runtime_error_issue119004 (dict1 , dict2 )
829
+ self .assertEqual (Key .count , 2 )
830
+ self .assertDictEqual (dict1 , dict .fromkeys ((0 , 4.2 )))
831
+ self .assertDictEqual (dict2 , dict .fromkeys ((0 , Key (), 4.2 )))
832
+
833
+ def test_issue119004_change_linked_list_by_clear (self ):
834
+ class Key (_TriggerSideEffectOnEqual ):
835
+ def side_effect (self ):
836
+ dict1 .clear ()
837
+ dict1 ['a' ] = dict1 ['b' ] = 'c'
838
+
839
+ dict1 = self .OrderedDict (dict .fromkeys ((0 , Key (), 4.2 )))
840
+ dict2 = self .OrderedDict (dict .fromkeys ((0 , Key (), 4.2 )))
841
+ self .check_runtime_error_issue119004 (dict1 , dict2 )
842
+ self .assertEqual (Key .count , 2 )
843
+ self .assertDictEqual (dict1 , dict .fromkeys (('a' , 'b' ), 'c' ))
844
+ self .assertDictEqual (dict2 , dict .fromkeys ((0 , Key (), 4.2 )))
845
+
846
+ def test_issue119004_change_linked_list_by_delete_key (self ):
847
+ class Key (_TriggerSideEffectOnEqual ):
848
+ def side_effect (self ):
849
+ del dict1 [TODEL ]
850
+ dict1 ['a' ] = 'c'
851
+
852
+ TODEL = Key ()
853
+ dict1 = self .OrderedDict (dict .fromkeys ((0 , TODEL , 4.2 )))
854
+ dict2 = self .OrderedDict (dict .fromkeys ((0 , Key (), 4.2 )))
855
+ self .check_runtime_error_issue119004 (dict1 , dict2 )
856
+ self .assertEqual (Key .count , 2 )
857
+ self .assertDictEqual (dict1 , {0 : None , 'a' : 'c' , 4.2 : None })
858
+ self .assertDictEqual (dict2 , dict .fromkeys ((0 , Key (), 4.2 )))
859
+
860
+ def test_issue119004_change_size_by_delete_key_in_dict_eq (self ):
861
+ class Key (_TriggerSideEffectOnEqual ):
862
+ trigger = 0
863
+ def side_effect (self ):
864
+ del dict1 [TODEL ]
865
+
866
+ TODEL = Key ()
867
+ dict1 = self .OrderedDict (dict .fromkeys ((0 , TODEL , 4.2 )))
868
+ dict2 = self .OrderedDict (dict .fromkeys ((0 , Key (), 4.2 )))
869
+ self .assertEqual (Key .count , 0 )
870
+ # the side effect is in dict.__eq__ and modifies the length
871
+ self .assertNotEqual (dict1 , dict2 )
872
+ self .assertEqual (Key .count , 2 )
873
+ self .assertDictEqual (dict1 , dict .fromkeys ((0 , 4.2 )))
874
+ self .assertDictEqual (dict2 , dict .fromkeys ((0 , Key (), 4.2 )))
875
+
876
+
767
877
@unittest .skipUnless (c_coll , 'requires the C version of the collections module' )
768
- class CPythonOrderedDictTests (OrderedDictTests , unittest .TestCase ):
878
+ class CPythonOrderedDictTests (OrderedDictTests ,
879
+ CPythonOrderedDictSideEffects ,
880
+ unittest .TestCase ):
769
881
770
882
module = c_coll
771
883
OrderedDict = c_coll .OrderedDict
0 commit comments