11import collections
22import collections .abc
3- # import gc // XXX RustPython
3+ import gc
44import pickle
55import random
66import string
@@ -37,6 +37,38 @@ def test_literal_constructor(self):
3737 dictliteral = '{' + ', ' .join (formatted_items ) + '}'
3838 self .assertEqual (eval (dictliteral ), dict (items ))
3939
40+ def test_merge_operator (self ):
41+
42+ a = {0 : 0 , 1 : 1 , 2 : 1 }
43+ b = {1 : 1 , 2 : 2 , 3 : 3 }
44+
45+ c = a .copy ()
46+ c |= b
47+
48+ self .assertEqual (a | b , {0 : 0 , 1 : 1 , 2 : 2 , 3 : 3 })
49+ self .assertEqual (c , {0 : 0 , 1 : 1 , 2 : 2 , 3 : 3 })
50+
51+ c = b .copy ()
52+ c |= a
53+
54+ self .assertEqual (b | a , {1 : 1 , 2 : 1 , 3 : 3 , 0 : 0 })
55+ self .assertEqual (c , {1 : 1 , 2 : 1 , 3 : 3 , 0 : 0 })
56+
57+ c = a .copy ()
58+ c |= [(1 , 1 ), (2 , 2 ), (3 , 3 )]
59+
60+ self .assertEqual (c , {0 : 0 , 1 : 1 , 2 : 2 , 3 : 3 })
61+
62+ self .assertIs (a .__or__ (None ), NotImplemented )
63+ self .assertIs (a .__or__ (()), NotImplemented )
64+ self .assertIs (a .__or__ ("BAD" ), NotImplemented )
65+ self .assertIs (a .__or__ ("" ), NotImplemented )
66+
67+ self .assertRaises (TypeError , a .__ior__ , None )
68+ self .assertEqual (a .__ior__ (()), {0 : 0 , 1 : 1 , 2 : 1 })
69+ self .assertRaises (ValueError , a .__ior__ , "BAD" )
70+ self .assertEqual (a .__ior__ ("" ), {0 : 0 , 1 : 1 , 2 : 1 })
71+
4072 def test_bool (self ):
4173 self .assertIs (not {}, True )
4274 self .assertTrue ({1 : 2 })
@@ -73,6 +105,26 @@ def test_items(self):
73105 self .assertRaises (TypeError , d .items , None )
74106 self .assertEqual (repr (dict (a = 1 ).items ()), "dict_items([('a', 1)])" )
75107
108+ def test_views_mapping (self ):
109+ mappingproxy = type (type .__dict__ )
110+ class Dict (dict ):
111+ pass
112+ for cls in [dict , Dict ]:
113+ d = cls ()
114+ m1 = d .keys ().mapping
115+ m2 = d .values ().mapping
116+ m3 = d .items ().mapping
117+
118+ for m in [m1 , m2 , m3 ]:
119+ self .assertIsInstance (m , mappingproxy )
120+ self .assertEqual (m , d )
121+
122+ d ["foo" ] = "bar"
123+
124+ for m in [m1 , m2 , m3 ]:
125+ self .assertIsInstance (m , mappingproxy )
126+ self .assertEqual (m , d )
127+
76128 def test_contains (self ):
77129 d = {}
78130 self .assertNotIn ('a' , d )
@@ -668,6 +720,16 @@ def test_dictview_set_operations_on_items(self):
668720 self .assertEqual (k1 ^ k2 , {(3 ,3 )})
669721 self .assertEqual (k1 ^ k3 , {(1 ,1 ), (2 ,2 ), (4 ,4 )})
670722
723+ def test_items_symmetric_difference (self ):
724+ rr = random .randrange
725+ for _ in range (100 ):
726+ left = {x :rr (3 ) for x in range (20 ) if rr (2 )}
727+ right = {x :rr (3 ) for x in range (20 ) if rr (2 )}
728+ with self .subTest (left = left , right = right ):
729+ expected = set (left .items ()) ^ set (right .items ())
730+ actual = left .items () ^ right .items ()
731+ self .assertEqual (actual , expected )
732+
671733 def test_dictview_mixed_set_operations (self ):
672734 # Just a few for .keys()
673735 self .assertTrue ({1 :1 }.keys () == {1 })
@@ -994,7 +1056,7 @@ def test_splittable_pop(self):
9941056
9951057 @support .cpython_only
9961058 def test_splittable_pop_pending (self ):
997- """pop a pending key in a splitted table should not crash"""
1059+ """pop a pending key in a split table should not crash"""
9981060 a , b = self .make_shared_key_dict (2 )
9991061
10001062 a ['a' ] = 4
@@ -1230,7 +1292,7 @@ def test_free_after_iterating(self):
12301292 # TODO: RUSTPYTHON
12311293 @unittest .expectedFailure
12321294 def test_equal_operator_modifying_operand (self ):
1233- # test fix for seg fault reported in issue 27945 part 3.
1295+ # test fix for seg fault reported in bpo- 27945 part 3.
12341296 class X ():
12351297 def __del__ (self ):
12361298 dict_b .clear ()
@@ -1246,6 +1308,16 @@ def __hash__(self):
12461308 dict_b = {X (): X ()}
12471309 self .assertTrue (dict_a == dict_b )
12481310
1311+ # test fix for seg fault reported in bpo-38588 part 1.
1312+ class Y :
1313+ def __eq__ (self , other ):
1314+ dict_d .clear ()
1315+ return True
1316+
1317+ dict_c = {0 : Y ()}
1318+ dict_d = {0 : set ()}
1319+ self .assertTrue (dict_c == dict_d )
1320+
12491321 def test_fromkeys_operator_modifying_dict_operand (self ):
12501322 # test fix for seg fault reported in issue 27945 part 4a.
12511323 class X (int ):
@@ -1291,6 +1363,19 @@ def __eq__(self, other):
12911363 d = {0 : set ()}
12921364 (0 , X ()) in d .items ()
12931365
1366+ def test_dict_contain_use_after_free (self ):
1367+ # bpo-40489
1368+ class S (str ):
1369+ def __eq__ (self , other ):
1370+ d .clear ()
1371+ return NotImplemented
1372+
1373+ def __hash__ (self ):
1374+ return hash ('test' )
1375+
1376+ d = {S (): 'value' }
1377+ self .assertFalse ('test' in d )
1378+
12941379 def test_init_use_after_free (self ):
12951380 class X :
12961381 def __hash__ (self ):
@@ -1321,6 +1406,31 @@ def test_reversed(self):
13211406 self .assertEqual (list (r ), list ('dcba' ))
13221407 self .assertRaises (StopIteration , next , r )
13231408
1409+ def test_reverse_iterator_for_empty_dict (self ):
1410+ # bpo-38525: reversed iterator should work properly
1411+
1412+ # empty dict is directly used for reference count test
1413+ self .assertEqual (list (reversed ({})), [])
1414+ self .assertEqual (list (reversed ({}.items ())), [])
1415+ self .assertEqual (list (reversed ({}.values ())), [])
1416+ self .assertEqual (list (reversed ({}.keys ())), [])
1417+
1418+ # dict() and {} don't trigger the same code path
1419+ self .assertEqual (list (reversed (dict ())), [])
1420+ self .assertEqual (list (reversed (dict ().items ())), [])
1421+ self .assertEqual (list (reversed (dict ().values ())), [])
1422+ self .assertEqual (list (reversed (dict ().keys ())), [])
1423+
1424+ def test_reverse_iterator_for_shared_shared_dicts (self ):
1425+ class A :
1426+ def __init__ (self , x , y ):
1427+ if x : self .x = x
1428+ if y : self .y = y
1429+
1430+ self .assertEqual (list (reversed (A (1 , 2 ).__dict__ )), ['y' , 'x' ])
1431+ self .assertEqual (list (reversed (A (1 , 0 ).__dict__ )), ['x' ])
1432+ self .assertEqual (list (reversed (A (0 , 1 ).__dict__ )), ['y' ])
1433+
13241434 def test_dict_copy_order (self ):
13251435 # bpo-34320
13261436 od = collections .OrderedDict ([('a' , 1 ), ('b' , 2 )])
@@ -1351,6 +1461,125 @@ def items(self):
13511461 d = CustomReversedDict (pairs )
13521462 self .assertEqual (pairs [::- 1 ], list (dict (d ).items ()))
13531463
1464+ @support .cpython_only
1465+ def test_dict_items_result_gc (self ):
1466+ # bpo-42536: dict.items's tuple-reuse speed trick breaks the GC's
1467+ # assumptions about what can be untracked. Make sure we re-track result
1468+ # tuples whenever we reuse them.
1469+ it = iter ({None : []}.items ())
1470+ gc .collect ()
1471+ # That GC collection probably untracked the recycled internal result
1472+ # tuple, which is initialized to (None, None). Make sure it's re-tracked
1473+ # when it's mutated and returned from __next__:
1474+ self .assertTrue (gc .is_tracked (next (it )))
1475+
1476+ @support .cpython_only
1477+ def test_dict_items_result_gc_reversed (self ):
1478+ # Same as test_dict_items_result_gc above, but reversed.
1479+ it = reversed ({None : []}.items ())
1480+ gc .collect ()
1481+ self .assertTrue (gc .is_tracked (next (it )))
1482+
1483+ def test_str_nonstr (self ):
1484+ # cpython uses a different lookup function if the dict only contains
1485+ # `str` keys. Make sure the unoptimized path is used when a non-`str`
1486+ # key appears.
1487+
1488+ class StrSub (str ):
1489+ pass
1490+
1491+ eq_count = 0
1492+ # This class compares equal to the string 'key3'
1493+ class Key3 :
1494+ def __hash__ (self ):
1495+ return hash ('key3' )
1496+
1497+ def __eq__ (self , other ):
1498+ nonlocal eq_count
1499+ if isinstance (other , Key3 ) or isinstance (other , str ) and other == 'key3' :
1500+ eq_count += 1
1501+ return True
1502+ return False
1503+
1504+ key3_1 = StrSub ('key3' )
1505+ key3_2 = Key3 ()
1506+ key3_3 = Key3 ()
1507+
1508+ dicts = []
1509+
1510+ # Create dicts of the form `{'key1': 42, 'key2': 43, key3: 44}` in a
1511+ # bunch of different ways. In all cases, `key3` is not of type `str`.
1512+ # `key3_1` is a `str` subclass and `key3_2` is a completely unrelated
1513+ # type.
1514+ for key3 in (key3_1 , key3_2 ):
1515+ # A literal
1516+ dicts .append ({'key1' : 42 , 'key2' : 43 , key3 : 44 })
1517+
1518+ # key3 inserted via `dict.__setitem__`
1519+ d = {'key1' : 42 , 'key2' : 43 }
1520+ d [key3 ] = 44
1521+ dicts .append (d )
1522+
1523+ # key3 inserted via `dict.setdefault`
1524+ d = {'key1' : 42 , 'key2' : 43 }
1525+ self .assertEqual (d .setdefault (key3 , 44 ), 44 )
1526+ dicts .append (d )
1527+
1528+ # key3 inserted via `dict.update`
1529+ d = {'key1' : 42 , 'key2' : 43 }
1530+ d .update ({key3 : 44 })
1531+ dicts .append (d )
1532+
1533+ # key3 inserted via `dict.__ior__`
1534+ d = {'key1' : 42 , 'key2' : 43 }
1535+ d |= {key3 : 44 }
1536+ dicts .append (d )
1537+
1538+ # `dict(iterable)`
1539+ def make_pairs ():
1540+ yield ('key1' , 42 )
1541+ yield ('key2' , 43 )
1542+ yield (key3 , 44 )
1543+ d = dict (make_pairs ())
1544+ dicts .append (d )
1545+
1546+ # `dict.copy`
1547+ d = d .copy ()
1548+ dicts .append (d )
1549+
1550+ # dict comprehension
1551+ d = {key : 42 + i for i ,key in enumerate (['key1' , 'key2' , key3 ])}
1552+ dicts .append (d )
1553+
1554+ for d in dicts :
1555+ with self .subTest (d = d ):
1556+ self .assertEqual (d .get ('key1' ), 42 )
1557+
1558+ # Try to make an object that is of type `str` and is equal to
1559+ # `'key1'`, but (at least on cpython) is a different object.
1560+ noninterned_key1 = 'ke'
1561+ noninterned_key1 += 'y1'
1562+ if support .check_impl_detail (cpython = True ):
1563+ # suppress a SyntaxWarning
1564+ interned_key1 = 'key1'
1565+ self .assertFalse (noninterned_key1 is interned_key1 )
1566+ self .assertEqual (d .get (noninterned_key1 ), 42 )
1567+
1568+ self .assertEqual (d .get ('key3' ), 44 )
1569+ self .assertEqual (d .get (key3_1 ), 44 )
1570+ self .assertEqual (d .get (key3_2 ), 44 )
1571+
1572+ # `key3_3` itself is definitely not a dict key, so make sure
1573+ # that `__eq__` gets called.
1574+ #
1575+ # Note that this might not hold for `key3_1` and `key3_2`
1576+ # because they might be the same object as one of the dict keys,
1577+ # in which case implementations are allowed to skip the call to
1578+ # `__eq__`.
1579+ eq_count = 0
1580+ self .assertEqual (d .get (key3_3 ), 44 )
1581+ self .assertGreaterEqual (eq_count , 1 )
1582+
13541583
13551584class CAPITest (unittest .TestCase ):
13561585
0 commit comments