• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import collections
2import collections.abc
3import gc
4import pickle
5import random
6import string
7import sys
8import unittest
9import weakref
10from test import support
11
12
13class DictTest(unittest.TestCase):
14
15    def test_invalid_keyword_arguments(self):
16        class Custom(dict):
17            pass
18        for invalid in {1 : 2}, Custom({1 : 2}):
19            with self.assertRaises(TypeError):
20                dict(**invalid)
21            with self.assertRaises(TypeError):
22                {}.update(**invalid)
23
24    def test_constructor(self):
25        # calling built-in types without argument must return empty
26        self.assertEqual(dict(), {})
27        self.assertIsNot(dict(), {})
28
29    def test_literal_constructor(self):
30        # check literal constructor for different sized dicts
31        # (to exercise the BUILD_MAP oparg).
32        for n in (0, 1, 6, 256, 400):
33            items = [(''.join(random.sample(string.ascii_letters, 8)), i)
34                     for i in range(n)]
35            random.shuffle(items)
36            formatted_items = ('{!r}: {:d}'.format(k, v) for k, v in items)
37            dictliteral = '{' + ', '.join(formatted_items) + '}'
38            self.assertEqual(eval(dictliteral), dict(items))
39
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
72    def test_bool(self):
73        self.assertIs(not {}, True)
74        self.assertTrue({1: 2})
75        self.assertIs(bool({}), False)
76        self.assertIs(bool({1: 2}), True)
77
78    def test_keys(self):
79        d = {}
80        self.assertEqual(set(d.keys()), set())
81        d = {'a': 1, 'b': 2}
82        k = d.keys()
83        self.assertEqual(set(k), {'a', 'b'})
84        self.assertIn('a', k)
85        self.assertIn('b', k)
86        self.assertIn('a', d)
87        self.assertIn('b', d)
88        self.assertRaises(TypeError, d.keys, None)
89        self.assertEqual(repr(dict(a=1).keys()), "dict_keys(['a'])")
90
91    def test_values(self):
92        d = {}
93        self.assertEqual(set(d.values()), set())
94        d = {1:2}
95        self.assertEqual(set(d.values()), {2})
96        self.assertRaises(TypeError, d.values, None)
97        self.assertEqual(repr(dict(a=1).values()), "dict_values([1])")
98
99    def test_items(self):
100        d = {}
101        self.assertEqual(set(d.items()), set())
102
103        d = {1:2}
104        self.assertEqual(set(d.items()), {(1, 2)})
105        self.assertRaises(TypeError, d.items, None)
106        self.assertEqual(repr(dict(a=1).items()), "dict_items([('a', 1)])")
107
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
128    def test_contains(self):
129        d = {}
130        self.assertNotIn('a', d)
131        self.assertFalse('a' in d)
132        self.assertTrue('a' not in d)
133        d = {'a': 1, 'b': 2}
134        self.assertIn('a', d)
135        self.assertIn('b', d)
136        self.assertNotIn('c', d)
137
138        self.assertRaises(TypeError, d.__contains__)
139
140    def test_len(self):
141        d = {}
142        self.assertEqual(len(d), 0)
143        d = {'a': 1, 'b': 2}
144        self.assertEqual(len(d), 2)
145
146    def test_getitem(self):
147        d = {'a': 1, 'b': 2}
148        self.assertEqual(d['a'], 1)
149        self.assertEqual(d['b'], 2)
150        d['c'] = 3
151        d['a'] = 4
152        self.assertEqual(d['c'], 3)
153        self.assertEqual(d['a'], 4)
154        del d['b']
155        self.assertEqual(d, {'a': 4, 'c': 3})
156
157        self.assertRaises(TypeError, d.__getitem__)
158
159        class BadEq(object):
160            def __eq__(self, other):
161                raise Exc()
162            def __hash__(self):
163                return 24
164
165        d = {}
166        d[BadEq()] = 42
167        self.assertRaises(KeyError, d.__getitem__, 23)
168
169        class Exc(Exception): pass
170
171        class BadHash(object):
172            fail = False
173            def __hash__(self):
174                if self.fail:
175                    raise Exc()
176                else:
177                    return 42
178
179        x = BadHash()
180        d[x] = 42
181        x.fail = True
182        self.assertRaises(Exc, d.__getitem__, x)
183
184    def test_clear(self):
185        d = {1:1, 2:2, 3:3}
186        d.clear()
187        self.assertEqual(d, {})
188
189        self.assertRaises(TypeError, d.clear, None)
190
191    def test_update(self):
192        d = {}
193        d.update({1:100})
194        d.update({2:20})
195        d.update({1:1, 2:2, 3:3})
196        self.assertEqual(d, {1:1, 2:2, 3:3})
197
198        d.update()
199        self.assertEqual(d, {1:1, 2:2, 3:3})
200
201        self.assertRaises((TypeError, AttributeError), d.update, None)
202
203        class SimpleUserDict:
204            def __init__(self):
205                self.d = {1:1, 2:2, 3:3}
206            def keys(self):
207                return self.d.keys()
208            def __getitem__(self, i):
209                return self.d[i]
210        d.clear()
211        d.update(SimpleUserDict())
212        self.assertEqual(d, {1:1, 2:2, 3:3})
213
214        class Exc(Exception): pass
215
216        d.clear()
217        class FailingUserDict:
218            def keys(self):
219                raise Exc
220        self.assertRaises(Exc, d.update, FailingUserDict())
221
222        class FailingUserDict:
223            def keys(self):
224                class BogonIter:
225                    def __init__(self):
226                        self.i = 1
227                    def __iter__(self):
228                        return self
229                    def __next__(self):
230                        if self.i:
231                            self.i = 0
232                            return 'a'
233                        raise Exc
234                return BogonIter()
235            def __getitem__(self, key):
236                return key
237        self.assertRaises(Exc, d.update, FailingUserDict())
238
239        class FailingUserDict:
240            def keys(self):
241                class BogonIter:
242                    def __init__(self):
243                        self.i = ord('a')
244                    def __iter__(self):
245                        return self
246                    def __next__(self):
247                        if self.i <= ord('z'):
248                            rtn = chr(self.i)
249                            self.i += 1
250                            return rtn
251                        raise StopIteration
252                return BogonIter()
253            def __getitem__(self, key):
254                raise Exc
255        self.assertRaises(Exc, d.update, FailingUserDict())
256
257        class badseq(object):
258            def __iter__(self):
259                return self
260            def __next__(self):
261                raise Exc()
262
263        self.assertRaises(Exc, {}.update, badseq())
264
265        self.assertRaises(ValueError, {}.update, [(1, 2, 3)])
266
267    def test_fromkeys(self):
268        self.assertEqual(dict.fromkeys('abc'), {'a':None, 'b':None, 'c':None})
269        d = {}
270        self.assertIsNot(d.fromkeys('abc'), d)
271        self.assertEqual(d.fromkeys('abc'), {'a':None, 'b':None, 'c':None})
272        self.assertEqual(d.fromkeys((4,5),0), {4:0, 5:0})
273        self.assertEqual(d.fromkeys([]), {})
274        def g():
275            yield 1
276        self.assertEqual(d.fromkeys(g()), {1:None})
277        self.assertRaises(TypeError, {}.fromkeys, 3)
278        class dictlike(dict): pass
279        self.assertEqual(dictlike.fromkeys('a'), {'a':None})
280        self.assertEqual(dictlike().fromkeys('a'), {'a':None})
281        self.assertIsInstance(dictlike.fromkeys('a'), dictlike)
282        self.assertIsInstance(dictlike().fromkeys('a'), dictlike)
283        class mydict(dict):
284            def __new__(cls):
285                return collections.UserDict()
286        ud = mydict.fromkeys('ab')
287        self.assertEqual(ud, {'a':None, 'b':None})
288        self.assertIsInstance(ud, collections.UserDict)
289        self.assertRaises(TypeError, dict.fromkeys)
290
291        class Exc(Exception): pass
292
293        class baddict1(dict):
294            def __init__(self):
295                raise Exc()
296
297        self.assertRaises(Exc, baddict1.fromkeys, [1])
298
299        class BadSeq(object):
300            def __iter__(self):
301                return self
302            def __next__(self):
303                raise Exc()
304
305        self.assertRaises(Exc, dict.fromkeys, BadSeq())
306
307        class baddict2(dict):
308            def __setitem__(self, key, value):
309                raise Exc()
310
311        self.assertRaises(Exc, baddict2.fromkeys, [1])
312
313        # test fast path for dictionary inputs
314        d = dict(zip(range(6), range(6)))
315        self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6)))
316
317        class baddict3(dict):
318            def __new__(cls):
319                return d
320        d = {i : i for i in range(10)}
321        res = d.copy()
322        res.update(a=None, b=None, c=None)
323        self.assertEqual(baddict3.fromkeys({"a", "b", "c"}), res)
324
325    def test_copy(self):
326        d = {1: 1, 2: 2, 3: 3}
327        self.assertIsNot(d.copy(), d)
328        self.assertEqual(d.copy(), d)
329        self.assertEqual(d.copy(), {1: 1, 2: 2, 3: 3})
330
331        copy = d.copy()
332        d[4] = 4
333        self.assertNotEqual(copy, d)
334
335        self.assertEqual({}.copy(), {})
336        self.assertRaises(TypeError, d.copy, None)
337
338    def test_copy_fuzz(self):
339        for dict_size in [10, 100, 1000, 10000, 100000]:
340            dict_size = random.randrange(
341                dict_size // 2, dict_size + dict_size // 2)
342            with self.subTest(dict_size=dict_size):
343                d = {}
344                for i in range(dict_size):
345                    d[i] = i
346
347                d2 = d.copy()
348                self.assertIsNot(d2, d)
349                self.assertEqual(d, d2)
350                d2['key'] = 'value'
351                self.assertNotEqual(d, d2)
352                self.assertEqual(len(d2), len(d) + 1)
353
354    def test_copy_maintains_tracking(self):
355        class A:
356            pass
357
358        key = A()
359
360        for d in ({}, {'a': 1}, {key: 'val'}):
361            d2 = d.copy()
362            self.assertEqual(gc.is_tracked(d), gc.is_tracked(d2))
363
364    def test_copy_noncompact(self):
365        # Dicts don't compact themselves on del/pop operations.
366        # Copy will use a slow merging strategy that produces
367        # a compacted copy when roughly 33% of dict is a non-used
368        # keys-space (to optimize memory footprint).
369        # In this test we want to hit the slow/compacting
370        # branch of dict.copy() and make sure it works OK.
371        d = {k: k for k in range(1000)}
372        for k in range(950):
373            del d[k]
374        d2 = d.copy()
375        self.assertEqual(d2, d)
376
377    def test_get(self):
378        d = {}
379        self.assertIs(d.get('c'), None)
380        self.assertEqual(d.get('c', 3), 3)
381        d = {'a': 1, 'b': 2}
382        self.assertIs(d.get('c'), None)
383        self.assertEqual(d.get('c', 3), 3)
384        self.assertEqual(d.get('a'), 1)
385        self.assertEqual(d.get('a', 3), 1)
386        self.assertRaises(TypeError, d.get)
387        self.assertRaises(TypeError, d.get, None, None, None)
388
389    def test_setdefault(self):
390        # dict.setdefault()
391        d = {}
392        self.assertIs(d.setdefault('key0'), None)
393        d.setdefault('key0', [])
394        self.assertIs(d.setdefault('key0'), None)
395        d.setdefault('key', []).append(3)
396        self.assertEqual(d['key'][0], 3)
397        d.setdefault('key', []).append(4)
398        self.assertEqual(len(d['key']), 2)
399        self.assertRaises(TypeError, d.setdefault)
400
401        class Exc(Exception): pass
402
403        class BadHash(object):
404            fail = False
405            def __hash__(self):
406                if self.fail:
407                    raise Exc()
408                else:
409                    return 42
410
411        x = BadHash()
412        d[x] = 42
413        x.fail = True
414        self.assertRaises(Exc, d.setdefault, x, [])
415
416    def test_setdefault_atomic(self):
417        # Issue #13521: setdefault() calls __hash__ and __eq__ only once.
418        class Hashed(object):
419            def __init__(self):
420                self.hash_count = 0
421                self.eq_count = 0
422            def __hash__(self):
423                self.hash_count += 1
424                return 42
425            def __eq__(self, other):
426                self.eq_count += 1
427                return id(self) == id(other)
428        hashed1 = Hashed()
429        y = {hashed1: 5}
430        hashed2 = Hashed()
431        y.setdefault(hashed2, [])
432        self.assertEqual(hashed1.hash_count, 1)
433        self.assertEqual(hashed2.hash_count, 1)
434        self.assertEqual(hashed1.eq_count + hashed2.eq_count, 1)
435
436    def test_setitem_atomic_at_resize(self):
437        class Hashed(object):
438            def __init__(self):
439                self.hash_count = 0
440                self.eq_count = 0
441            def __hash__(self):
442                self.hash_count += 1
443                return 42
444            def __eq__(self, other):
445                self.eq_count += 1
446                return id(self) == id(other)
447        hashed1 = Hashed()
448        # 5 items
449        y = {hashed1: 5, 0: 0, 1: 1, 2: 2, 3: 3}
450        hashed2 = Hashed()
451        # 6th item forces a resize
452        y[hashed2] = []
453        self.assertEqual(hashed1.hash_count, 1)
454        self.assertEqual(hashed2.hash_count, 1)
455        self.assertEqual(hashed1.eq_count + hashed2.eq_count, 1)
456
457    def test_popitem(self):
458        # dict.popitem()
459        for copymode in -1, +1:
460            # -1: b has same structure as a
461            # +1: b is a.copy()
462            for log2size in range(12):
463                size = 2**log2size
464                a = {}
465                b = {}
466                for i in range(size):
467                    a[repr(i)] = i
468                    if copymode < 0:
469                        b[repr(i)] = i
470                if copymode > 0:
471                    b = a.copy()
472                for i in range(size):
473                    ka, va = ta = a.popitem()
474                    self.assertEqual(va, int(ka))
475                    kb, vb = tb = b.popitem()
476                    self.assertEqual(vb, int(kb))
477                    self.assertFalse(copymode < 0 and ta != tb)
478                self.assertFalse(a)
479                self.assertFalse(b)
480
481        d = {}
482        self.assertRaises(KeyError, d.popitem)
483
484    def test_pop(self):
485        # Tests for pop with specified key
486        d = {}
487        k, v = 'abc', 'def'
488        d[k] = v
489        self.assertRaises(KeyError, d.pop, 'ghi')
490
491        self.assertEqual(d.pop(k), v)
492        self.assertEqual(len(d), 0)
493
494        self.assertRaises(KeyError, d.pop, k)
495
496        self.assertEqual(d.pop(k, v), v)
497        d[k] = v
498        self.assertEqual(d.pop(k, 1), v)
499
500        self.assertRaises(TypeError, d.pop)
501
502        class Exc(Exception): pass
503
504        class BadHash(object):
505            fail = False
506            def __hash__(self):
507                if self.fail:
508                    raise Exc()
509                else:
510                    return 42
511
512        x = BadHash()
513        d[x] = 42
514        x.fail = True
515        self.assertRaises(Exc, d.pop, x)
516
517    def test_mutating_iteration(self):
518        # changing dict size during iteration
519        d = {}
520        d[1] = 1
521        with self.assertRaises(RuntimeError):
522            for i in d:
523                d[i+1] = 1
524
525    def test_mutating_iteration_delete(self):
526        # change dict content during iteration
527        d = {}
528        d[0] = 0
529        with self.assertRaises(RuntimeError):
530            for i in d:
531                del d[0]
532                d[0] = 0
533
534    def test_mutating_iteration_delete_over_values(self):
535        # change dict content during iteration
536        d = {}
537        d[0] = 0
538        with self.assertRaises(RuntimeError):
539            for i in d.values():
540                del d[0]
541                d[0] = 0
542
543    def test_mutating_iteration_delete_over_items(self):
544        # change dict content during iteration
545        d = {}
546        d[0] = 0
547        with self.assertRaises(RuntimeError):
548            for i in d.items():
549                del d[0]
550                d[0] = 0
551
552    def test_mutating_lookup(self):
553        # changing dict during a lookup (issue #14417)
554        class NastyKey:
555            mutate_dict = None
556
557            def __init__(self, value):
558                self.value = value
559
560            def __hash__(self):
561                # hash collision!
562                return 1
563
564            def __eq__(self, other):
565                if NastyKey.mutate_dict:
566                    mydict, key = NastyKey.mutate_dict
567                    NastyKey.mutate_dict = None
568                    del mydict[key]
569                return self.value == other.value
570
571        key1 = NastyKey(1)
572        key2 = NastyKey(2)
573        d = {key1: 1}
574        NastyKey.mutate_dict = (d, key1)
575        d[key2] = 2
576        self.assertEqual(d, {key2: 2})
577
578    def test_repr(self):
579        d = {}
580        self.assertEqual(repr(d), '{}')
581        d[1] = 2
582        self.assertEqual(repr(d), '{1: 2}')
583        d = {}
584        d[1] = d
585        self.assertEqual(repr(d), '{1: {...}}')
586
587        class Exc(Exception): pass
588
589        class BadRepr(object):
590            def __repr__(self):
591                raise Exc()
592
593        d = {1: BadRepr()}
594        self.assertRaises(Exc, repr, d)
595
596    def test_repr_deep(self):
597        d = {}
598        for i in range(sys.getrecursionlimit() + 100):
599            d = {1: d}
600        self.assertRaises(RecursionError, repr, d)
601
602    def test_eq(self):
603        self.assertEqual({}, {})
604        self.assertEqual({1: 2}, {1: 2})
605
606        class Exc(Exception): pass
607
608        class BadCmp(object):
609            def __eq__(self, other):
610                raise Exc()
611            def __hash__(self):
612                return 1
613
614        d1 = {BadCmp(): 1}
615        d2 = {1: 1}
616
617        with self.assertRaises(Exc):
618            d1 == d2
619
620    def test_keys_contained(self):
621        self.helper_keys_contained(lambda x: x.keys())
622        self.helper_keys_contained(lambda x: x.items())
623
624    def helper_keys_contained(self, fn):
625        # Test rich comparisons against dict key views, which should behave the
626        # same as sets.
627        empty = fn(dict())
628        empty2 = fn(dict())
629        smaller = fn({1:1, 2:2})
630        larger = fn({1:1, 2:2, 3:3})
631        larger2 = fn({1:1, 2:2, 3:3})
632        larger3 = fn({4:1, 2:2, 3:3})
633
634        self.assertTrue(smaller <  larger)
635        self.assertTrue(smaller <= larger)
636        self.assertTrue(larger >  smaller)
637        self.assertTrue(larger >= smaller)
638
639        self.assertFalse(smaller >= larger)
640        self.assertFalse(smaller >  larger)
641        self.assertFalse(larger  <= smaller)
642        self.assertFalse(larger  <  smaller)
643
644        self.assertFalse(smaller <  larger3)
645        self.assertFalse(smaller <= larger3)
646        self.assertFalse(larger3 >  smaller)
647        self.assertFalse(larger3 >= smaller)
648
649        # Inequality strictness
650        self.assertTrue(larger2 >= larger)
651        self.assertTrue(larger2 <= larger)
652        self.assertFalse(larger2 > larger)
653        self.assertFalse(larger2 < larger)
654
655        self.assertTrue(larger == larger2)
656        self.assertTrue(smaller != larger)
657
658        # There is an optimization on the zero-element case.
659        self.assertTrue(empty == empty2)
660        self.assertFalse(empty != empty2)
661        self.assertFalse(empty == smaller)
662        self.assertTrue(empty != smaller)
663
664        # With the same size, an elementwise compare happens
665        self.assertTrue(larger != larger3)
666        self.assertFalse(larger == larger3)
667
668    def test_errors_in_view_containment_check(self):
669        class C:
670            def __eq__(self, other):
671                raise RuntimeError
672
673        d1 = {1: C()}
674        d2 = {1: C()}
675        with self.assertRaises(RuntimeError):
676            d1.items() == d2.items()
677        with self.assertRaises(RuntimeError):
678            d1.items() != d2.items()
679        with self.assertRaises(RuntimeError):
680            d1.items() <= d2.items()
681        with self.assertRaises(RuntimeError):
682            d1.items() >= d2.items()
683
684        d3 = {1: C(), 2: C()}
685        with self.assertRaises(RuntimeError):
686            d2.items() < d3.items()
687        with self.assertRaises(RuntimeError):
688            d3.items() > d2.items()
689
690    def test_dictview_set_operations_on_keys(self):
691        k1 = {1:1, 2:2}.keys()
692        k2 = {1:1, 2:2, 3:3}.keys()
693        k3 = {4:4}.keys()
694
695        self.assertEqual(k1 - k2, set())
696        self.assertEqual(k1 - k3, {1,2})
697        self.assertEqual(k2 - k1, {3})
698        self.assertEqual(k3 - k1, {4})
699        self.assertEqual(k1 & k2, {1,2})
700        self.assertEqual(k1 & k3, set())
701        self.assertEqual(k1 | k2, {1,2,3})
702        self.assertEqual(k1 ^ k2, {3})
703        self.assertEqual(k1 ^ k3, {1,2,4})
704
705    def test_dictview_set_operations_on_items(self):
706        k1 = {1:1, 2:2}.items()
707        k2 = {1:1, 2:2, 3:3}.items()
708        k3 = {4:4}.items()
709
710        self.assertEqual(k1 - k2, set())
711        self.assertEqual(k1 - k3, {(1,1), (2,2)})
712        self.assertEqual(k2 - k1, {(3,3)})
713        self.assertEqual(k3 - k1, {(4,4)})
714        self.assertEqual(k1 & k2, {(1,1), (2,2)})
715        self.assertEqual(k1 & k3, set())
716        self.assertEqual(k1 | k2, {(1,1), (2,2), (3,3)})
717        self.assertEqual(k1 ^ k2, {(3,3)})
718        self.assertEqual(k1 ^ k3, {(1,1), (2,2), (4,4)})
719
720    def test_items_symmetric_difference(self):
721        rr = random.randrange
722        for _ in range(100):
723            left = {x:rr(3) for x in range(20) if rr(2)}
724            right = {x:rr(3) for x in range(20) if rr(2)}
725            with self.subTest(left=left, right=right):
726                expected = set(left.items()) ^ set(right.items())
727                actual = left.items() ^ right.items()
728                self.assertEqual(actual, expected)
729
730    def test_dictview_mixed_set_operations(self):
731        # Just a few for .keys()
732        self.assertTrue({1:1}.keys() == {1})
733        self.assertTrue({1} == {1:1}.keys())
734        self.assertEqual({1:1}.keys() | {2}, {1, 2})
735        self.assertEqual({2} | {1:1}.keys(), {1, 2})
736        # And a few for .items()
737        self.assertTrue({1:1}.items() == {(1,1)})
738        self.assertTrue({(1,1)} == {1:1}.items())
739        self.assertEqual({1:1}.items() | {2}, {(1,1), 2})
740        self.assertEqual({2} | {1:1}.items(), {(1,1), 2})
741
742    def test_missing(self):
743        # Make sure dict doesn't have a __missing__ method
744        self.assertFalse(hasattr(dict, "__missing__"))
745        self.assertFalse(hasattr({}, "__missing__"))
746        # Test several cases:
747        # (D) subclass defines __missing__ method returning a value
748        # (E) subclass defines __missing__ method raising RuntimeError
749        # (F) subclass sets __missing__ instance variable (no effect)
750        # (G) subclass doesn't define __missing__ at all
751        class D(dict):
752            def __missing__(self, key):
753                return 42
754        d = D({1: 2, 3: 4})
755        self.assertEqual(d[1], 2)
756        self.assertEqual(d[3], 4)
757        self.assertNotIn(2, d)
758        self.assertNotIn(2, d.keys())
759        self.assertEqual(d[2], 42)
760
761        class E(dict):
762            def __missing__(self, key):
763                raise RuntimeError(key)
764        e = E()
765        with self.assertRaises(RuntimeError) as c:
766            e[42]
767        self.assertEqual(c.exception.args, (42,))
768
769        class F(dict):
770            def __init__(self):
771                # An instance variable __missing__ should have no effect
772                self.__missing__ = lambda key: None
773        f = F()
774        with self.assertRaises(KeyError) as c:
775            f[42]
776        self.assertEqual(c.exception.args, (42,))
777
778        class G(dict):
779            pass
780        g = G()
781        with self.assertRaises(KeyError) as c:
782            g[42]
783        self.assertEqual(c.exception.args, (42,))
784
785    def test_tuple_keyerror(self):
786        # SF #1576657
787        d = {}
788        with self.assertRaises(KeyError) as c:
789            d[(1,)]
790        self.assertEqual(c.exception.args, ((1,),))
791
792    def test_bad_key(self):
793        # Dictionary lookups should fail if __eq__() raises an exception.
794        class CustomException(Exception):
795            pass
796
797        class BadDictKey:
798            def __hash__(self):
799                return hash(self.__class__)
800
801            def __eq__(self, other):
802                if isinstance(other, self.__class__):
803                    raise CustomException
804                return other
805
806        d = {}
807        x1 = BadDictKey()
808        x2 = BadDictKey()
809        d[x1] = 1
810        for stmt in ['d[x2] = 2',
811                     'z = d[x2]',
812                     'x2 in d',
813                     'd.get(x2)',
814                     'd.setdefault(x2, 42)',
815                     'd.pop(x2)',
816                     'd.update({x2: 2})']:
817            with self.assertRaises(CustomException):
818                exec(stmt, locals())
819
820    def test_resize1(self):
821        # Dict resizing bug, found by Jack Jansen in 2.2 CVS development.
822        # This version got an assert failure in debug build, infinite loop in
823        # release build.  Unfortunately, provoking this kind of stuff requires
824        # a mix of inserts and deletes hitting exactly the right hash codes in
825        # exactly the right order, and I can't think of a randomized approach
826        # that would be *likely* to hit a failing case in reasonable time.
827
828        d = {}
829        for i in range(5):
830            d[i] = i
831        for i in range(5):
832            del d[i]
833        for i in range(5, 9):  # i==8 was the problem
834            d[i] = i
835
836    def test_resize2(self):
837        # Another dict resizing bug (SF bug #1456209).
838        # This caused Segmentation faults or Illegal instructions.
839
840        class X(object):
841            def __hash__(self):
842                return 5
843            def __eq__(self, other):
844                if resizing:
845                    d.clear()
846                return False
847        d = {}
848        resizing = False
849        d[X()] = 1
850        d[X()] = 2
851        d[X()] = 3
852        d[X()] = 4
853        d[X()] = 5
854        # now trigger a resize
855        resizing = True
856        d[9] = 6
857
858    def test_empty_presized_dict_in_freelist(self):
859        # Bug #3537: if an empty but presized dict with a size larger
860        # than 7 was in the freelist, it triggered an assertion failure
861        with self.assertRaises(ZeroDivisionError):
862            d = {'a': 1 // 0, 'b': None, 'c': None, 'd': None, 'e': None,
863                 'f': None, 'g': None, 'h': None}
864        d = {}
865
866    def test_container_iterator(self):
867        # Bug #3680: tp_traverse was not implemented for dictiter and
868        # dictview objects.
869        class C(object):
870            pass
871        views = (dict.items, dict.values, dict.keys)
872        for v in views:
873            obj = C()
874            ref = weakref.ref(obj)
875            container = {obj: 1}
876            obj.v = v(container)
877            obj.x = iter(obj.v)
878            del obj, container
879            gc.collect()
880            self.assertIs(ref(), None, "Cycle was not collected")
881
882    def _not_tracked(self, t):
883        # Nested containers can take several collections to untrack
884        gc.collect()
885        gc.collect()
886        self.assertFalse(gc.is_tracked(t), t)
887
888    def _tracked(self, t):
889        self.assertTrue(gc.is_tracked(t), t)
890        gc.collect()
891        gc.collect()
892        self.assertTrue(gc.is_tracked(t), t)
893
894    @support.cpython_only
895    def test_track_literals(self):
896        # Test GC-optimization of dict literals
897        x, y, z, w = 1.5, "a", (1, None), []
898
899        self._not_tracked({})
900        self._not_tracked({x:(), y:x, z:1})
901        self._not_tracked({1: "a", "b": 2})
902        self._not_tracked({1: 2, (None, True, False, ()): int})
903        self._not_tracked({1: object()})
904
905        # Dicts with mutable elements are always tracked, even if those
906        # elements are not tracked right now.
907        self._tracked({1: []})
908        self._tracked({1: ([],)})
909        self._tracked({1: {}})
910        self._tracked({1: set()})
911
912    @support.cpython_only
913    def test_track_dynamic(self):
914        # Test GC-optimization of dynamically-created dicts
915        class MyObject(object):
916            pass
917        x, y, z, w, o = 1.5, "a", (1, object()), [], MyObject()
918
919        d = dict()
920        self._not_tracked(d)
921        d[1] = "a"
922        self._not_tracked(d)
923        d[y] = 2
924        self._not_tracked(d)
925        d[z] = 3
926        self._not_tracked(d)
927        self._not_tracked(d.copy())
928        d[4] = w
929        self._tracked(d)
930        self._tracked(d.copy())
931        d[4] = None
932        self._not_tracked(d)
933        self._not_tracked(d.copy())
934
935        # dd isn't tracked right now, but it may mutate and therefore d
936        # which contains it must be tracked.
937        d = dict()
938        dd = dict()
939        d[1] = dd
940        self._not_tracked(dd)
941        self._tracked(d)
942        dd[1] = d
943        self._tracked(dd)
944
945        d = dict.fromkeys([x, y, z])
946        self._not_tracked(d)
947        dd = dict()
948        dd.update(d)
949        self._not_tracked(dd)
950        d = dict.fromkeys([x, y, z, o])
951        self._tracked(d)
952        dd = dict()
953        dd.update(d)
954        self._tracked(dd)
955
956        d = dict(x=x, y=y, z=z)
957        self._not_tracked(d)
958        d = dict(x=x, y=y, z=z, w=w)
959        self._tracked(d)
960        d = dict()
961        d.update(x=x, y=y, z=z)
962        self._not_tracked(d)
963        d.update(w=w)
964        self._tracked(d)
965
966        d = dict([(x, y), (z, 1)])
967        self._not_tracked(d)
968        d = dict([(x, y), (z, w)])
969        self._tracked(d)
970        d = dict()
971        d.update([(x, y), (z, 1)])
972        self._not_tracked(d)
973        d.update([(x, y), (z, w)])
974        self._tracked(d)
975
976    @support.cpython_only
977    def test_track_subtypes(self):
978        # Dict subtypes are always tracked
979        class MyDict(dict):
980            pass
981        self._tracked(MyDict())
982
983    def make_shared_key_dict(self, n):
984        class C:
985            pass
986
987        dicts = []
988        for i in range(n):
989            a = C()
990            a.x, a.y, a.z = 1, 2, 3
991            dicts.append(a.__dict__)
992
993        return dicts
994
995    @support.cpython_only
996    def test_splittable_setdefault(self):
997        """split table must be combined when setdefault()
998        breaks insertion order"""
999        a, b = self.make_shared_key_dict(2)
1000
1001        a['a'] = 1
1002        size_a = sys.getsizeof(a)
1003        a['b'] = 2
1004        b.setdefault('b', 2)
1005        size_b = sys.getsizeof(b)
1006        b['a'] = 1
1007
1008        self.assertGreater(size_b, size_a)
1009        self.assertEqual(list(a), ['x', 'y', 'z', 'a', 'b'])
1010        self.assertEqual(list(b), ['x', 'y', 'z', 'b', 'a'])
1011
1012    @support.cpython_only
1013    def test_splittable_del(self):
1014        """split table must be combined when del d[k]"""
1015        a, b = self.make_shared_key_dict(2)
1016
1017        orig_size = sys.getsizeof(a)
1018
1019        del a['y']  # split table is combined
1020        with self.assertRaises(KeyError):
1021            del a['y']
1022
1023        self.assertGreater(sys.getsizeof(a), orig_size)
1024        self.assertEqual(list(a), ['x', 'z'])
1025        self.assertEqual(list(b), ['x', 'y', 'z'])
1026
1027        # Two dicts have different insertion order.
1028        a['y'] = 42
1029        self.assertEqual(list(a), ['x', 'z', 'y'])
1030        self.assertEqual(list(b), ['x', 'y', 'z'])
1031
1032    @support.cpython_only
1033    def test_splittable_pop(self):
1034        """split table must be combined when d.pop(k)"""
1035        a, b = self.make_shared_key_dict(2)
1036
1037        orig_size = sys.getsizeof(a)
1038
1039        a.pop('y')  # split table is combined
1040        with self.assertRaises(KeyError):
1041            a.pop('y')
1042
1043        self.assertGreater(sys.getsizeof(a), orig_size)
1044        self.assertEqual(list(a), ['x', 'z'])
1045        self.assertEqual(list(b), ['x', 'y', 'z'])
1046
1047        # Two dicts have different insertion order.
1048        a['y'] = 42
1049        self.assertEqual(list(a), ['x', 'z', 'y'])
1050        self.assertEqual(list(b), ['x', 'y', 'z'])
1051
1052    @support.cpython_only
1053    def test_splittable_pop_pending(self):
1054        """pop a pending key in a split table should not crash"""
1055        a, b = self.make_shared_key_dict(2)
1056
1057        a['a'] = 4
1058        with self.assertRaises(KeyError):
1059            b.pop('a')
1060
1061    @support.cpython_only
1062    def test_splittable_popitem(self):
1063        """split table must be combined when d.popitem()"""
1064        a, b = self.make_shared_key_dict(2)
1065
1066        orig_size = sys.getsizeof(a)
1067
1068        item = a.popitem()  # split table is combined
1069        self.assertEqual(item, ('z', 3))
1070        with self.assertRaises(KeyError):
1071            del a['z']
1072
1073        self.assertGreater(sys.getsizeof(a), orig_size)
1074        self.assertEqual(list(a), ['x', 'y'])
1075        self.assertEqual(list(b), ['x', 'y', 'z'])
1076
1077    @support.cpython_only
1078    def test_splittable_setattr_after_pop(self):
1079        """setattr() must not convert combined table into split table."""
1080        # Issue 28147
1081        import _testcapi
1082
1083        class C:
1084            pass
1085        a = C()
1086
1087        a.a = 1
1088        self.assertTrue(_testcapi.dict_hassplittable(a.__dict__))
1089
1090        # dict.pop() convert it to combined table
1091        a.__dict__.pop('a')
1092        self.assertFalse(_testcapi.dict_hassplittable(a.__dict__))
1093
1094        # But C should not convert a.__dict__ to split table again.
1095        a.a = 1
1096        self.assertFalse(_testcapi.dict_hassplittable(a.__dict__))
1097
1098        # Same for popitem()
1099        a = C()
1100        a.a = 2
1101        self.assertTrue(_testcapi.dict_hassplittable(a.__dict__))
1102        a.__dict__.popitem()
1103        self.assertFalse(_testcapi.dict_hassplittable(a.__dict__))
1104        a.a = 3
1105        self.assertFalse(_testcapi.dict_hassplittable(a.__dict__))
1106
1107    def test_iterator_pickling(self):
1108        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1109            data = {1:"a", 2:"b", 3:"c"}
1110            it = iter(data)
1111            d = pickle.dumps(it, proto)
1112            it = pickle.loads(d)
1113            self.assertEqual(list(it), list(data))
1114
1115            it = pickle.loads(d)
1116            try:
1117                drop = next(it)
1118            except StopIteration:
1119                continue
1120            d = pickle.dumps(it, proto)
1121            it = pickle.loads(d)
1122            del data[drop]
1123            self.assertEqual(list(it), list(data))
1124
1125    def test_itemiterator_pickling(self):
1126        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1127            data = {1:"a", 2:"b", 3:"c"}
1128            # dictviews aren't picklable, only their iterators
1129            itorg = iter(data.items())
1130            d = pickle.dumps(itorg, proto)
1131            it = pickle.loads(d)
1132            # note that the type of the unpickled iterator
1133            # is not necessarily the same as the original.  It is
1134            # merely an object supporting the iterator protocol, yielding
1135            # the same objects as the original one.
1136            # self.assertEqual(type(itorg), type(it))
1137            self.assertIsInstance(it, collections.abc.Iterator)
1138            self.assertEqual(dict(it), data)
1139
1140            it = pickle.loads(d)
1141            drop = next(it)
1142            d = pickle.dumps(it, proto)
1143            it = pickle.loads(d)
1144            del data[drop[0]]
1145            self.assertEqual(dict(it), data)
1146
1147    def test_valuesiterator_pickling(self):
1148        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1149            data = {1:"a", 2:"b", 3:"c"}
1150            # data.values() isn't picklable, only its iterator
1151            it = iter(data.values())
1152            d = pickle.dumps(it, proto)
1153            it = pickle.loads(d)
1154            self.assertEqual(list(it), list(data.values()))
1155
1156            it = pickle.loads(d)
1157            drop = next(it)
1158            d = pickle.dumps(it, proto)
1159            it = pickle.loads(d)
1160            values = list(it) + [drop]
1161            self.assertEqual(sorted(values), sorted(list(data.values())))
1162
1163    def test_reverseiterator_pickling(self):
1164        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1165            data = {1:"a", 2:"b", 3:"c"}
1166            it = reversed(data)
1167            d = pickle.dumps(it, proto)
1168            it = pickle.loads(d)
1169            self.assertEqual(list(it), list(reversed(data)))
1170
1171            it = pickle.loads(d)
1172            try:
1173                drop = next(it)
1174            except StopIteration:
1175                continue
1176            d = pickle.dumps(it, proto)
1177            it = pickle.loads(d)
1178            del data[drop]
1179            self.assertEqual(list(it), list(reversed(data)))
1180
1181    def test_reverseitemiterator_pickling(self):
1182        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1183            data = {1:"a", 2:"b", 3:"c"}
1184            # dictviews aren't picklable, only their iterators
1185            itorg = reversed(data.items())
1186            d = pickle.dumps(itorg, proto)
1187            it = pickle.loads(d)
1188            # note that the type of the unpickled iterator
1189            # is not necessarily the same as the original.  It is
1190            # merely an object supporting the iterator protocol, yielding
1191            # the same objects as the original one.
1192            # self.assertEqual(type(itorg), type(it))
1193            self.assertIsInstance(it, collections.abc.Iterator)
1194            self.assertEqual(dict(it), data)
1195
1196            it = pickle.loads(d)
1197            drop = next(it)
1198            d = pickle.dumps(it, proto)
1199            it = pickle.loads(d)
1200            del data[drop[0]]
1201            self.assertEqual(dict(it), data)
1202
1203    def test_reversevaluesiterator_pickling(self):
1204        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1205            data = {1:"a", 2:"b", 3:"c"}
1206            # data.values() isn't picklable, only its iterator
1207            it = reversed(data.values())
1208            d = pickle.dumps(it, proto)
1209            it = pickle.loads(d)
1210            self.assertEqual(list(it), list(reversed(data.values())))
1211
1212            it = pickle.loads(d)
1213            drop = next(it)
1214            d = pickle.dumps(it, proto)
1215            it = pickle.loads(d)
1216            values = list(it) + [drop]
1217            self.assertEqual(sorted(values), sorted(data.values()))
1218
1219    def test_instance_dict_getattr_str_subclass(self):
1220        class Foo:
1221            def __init__(self, msg):
1222                self.msg = msg
1223        f = Foo('123')
1224        class _str(str):
1225            pass
1226        self.assertEqual(f.msg, getattr(f, _str('msg')))
1227        self.assertEqual(f.msg, f.__dict__[_str('msg')])
1228
1229    def test_object_set_item_single_instance_non_str_key(self):
1230        class Foo: pass
1231        f = Foo()
1232        f.__dict__[1] = 1
1233        f.a = 'a'
1234        self.assertEqual(f.__dict__, {1:1, 'a':'a'})
1235
1236    def check_reentrant_insertion(self, mutate):
1237        # This object will trigger mutation of the dict when replaced
1238        # by another value.  Note this relies on refcounting: the test
1239        # won't achieve its purpose on fully-GCed Python implementations.
1240        class Mutating:
1241            def __del__(self):
1242                mutate(d)
1243
1244        d = {k: Mutating() for k in 'abcdefghijklmnopqr'}
1245        for k in list(d):
1246            d[k] = k
1247
1248    def test_reentrant_insertion(self):
1249        # Reentrant insertion shouldn't crash (see issue #22653)
1250        def mutate(d):
1251            d['b'] = 5
1252        self.check_reentrant_insertion(mutate)
1253
1254        def mutate(d):
1255            d.update(self.__dict__)
1256            d.clear()
1257        self.check_reentrant_insertion(mutate)
1258
1259        def mutate(d):
1260            while d:
1261                d.popitem()
1262        self.check_reentrant_insertion(mutate)
1263
1264    def test_merge_and_mutate(self):
1265        class X:
1266            def __hash__(self):
1267                return 0
1268
1269            def __eq__(self, o):
1270                other.clear()
1271                return False
1272
1273        l = [(i,0) for i in range(1, 1337)]
1274        other = dict(l)
1275        other[X()] = 0
1276        d = {X(): 0, 1: 1}
1277        self.assertRaises(RuntimeError, d.update, other)
1278
1279    def test_free_after_iterating(self):
1280        support.check_free_after_iterating(self, iter, dict)
1281        support.check_free_after_iterating(self, lambda d: iter(d.keys()), dict)
1282        support.check_free_after_iterating(self, lambda d: iter(d.values()), dict)
1283        support.check_free_after_iterating(self, lambda d: iter(d.items()), dict)
1284
1285    def test_equal_operator_modifying_operand(self):
1286        # test fix for seg fault reported in bpo-27945 part 3.
1287        class X():
1288            def __del__(self):
1289                dict_b.clear()
1290
1291            def __eq__(self, other):
1292                dict_a.clear()
1293                return True
1294
1295            def __hash__(self):
1296                return 13
1297
1298        dict_a = {X(): 0}
1299        dict_b = {X(): X()}
1300        self.assertTrue(dict_a == dict_b)
1301
1302        # test fix for seg fault reported in bpo-38588 part 1.
1303        class Y:
1304            def __eq__(self, other):
1305                dict_d.clear()
1306                return True
1307
1308        dict_c = {0: Y()}
1309        dict_d = {0: set()}
1310        self.assertTrue(dict_c == dict_d)
1311
1312    def test_fromkeys_operator_modifying_dict_operand(self):
1313        # test fix for seg fault reported in issue 27945 part 4a.
1314        class X(int):
1315            def __hash__(self):
1316                return 13
1317
1318            def __eq__(self, other):
1319                if len(d) > 1:
1320                    d.clear()
1321                return False
1322
1323        d = {}  # this is required to exist so that d can be constructed!
1324        d = {X(1): 1, X(2): 2}
1325        try:
1326            dict.fromkeys(d)  # shouldn't crash
1327        except RuntimeError:  # implementation defined
1328            pass
1329
1330    def test_fromkeys_operator_modifying_set_operand(self):
1331        # test fix for seg fault reported in issue 27945 part 4b.
1332        class X(int):
1333            def __hash__(self):
1334                return 13
1335
1336            def __eq__(self, other):
1337                if len(d) > 1:
1338                    d.clear()
1339                return False
1340
1341        d = {}  # this is required to exist so that d can be constructed!
1342        d = {X(1), X(2)}
1343        try:
1344            dict.fromkeys(d)  # shouldn't crash
1345        except RuntimeError:  # implementation defined
1346            pass
1347
1348    def test_dictitems_contains_use_after_free(self):
1349        class X:
1350            def __eq__(self, other):
1351                d.clear()
1352                return NotImplemented
1353
1354        d = {0: set()}
1355        (0, X()) in d.items()
1356
1357    def test_dict_contain_use_after_free(self):
1358        # bpo-40489
1359        class S(str):
1360            def __eq__(self, other):
1361                d.clear()
1362                return NotImplemented
1363
1364            def __hash__(self):
1365                return hash('test')
1366
1367        d = {S(): 'value'}
1368        self.assertFalse('test' in d)
1369
1370    def test_init_use_after_free(self):
1371        class X:
1372            def __hash__(self):
1373                pair[:] = []
1374                return 13
1375
1376        pair = [X(), 123]
1377        dict([pair])
1378
1379    def test_oob_indexing_dictiter_iternextitem(self):
1380        class X(int):
1381            def __del__(self):
1382                d.clear()
1383
1384        d = {i: X(i) for i in range(8)}
1385
1386        def iter_and_mutate():
1387            for result in d.items():
1388                if result[0] == 2:
1389                    d[2] = None # free d[2] --> X(2).__del__ was called
1390
1391        self.assertRaises(RuntimeError, iter_and_mutate)
1392
1393    def test_reversed(self):
1394        d = {"a": 1, "b": 2, "foo": 0, "c": 3, "d": 4}
1395        del d["foo"]
1396        r = reversed(d)
1397        self.assertEqual(list(r), list('dcba'))
1398        self.assertRaises(StopIteration, next, r)
1399
1400    def test_reverse_iterator_for_empty_dict(self):
1401        # bpo-38525: reversed iterator should work properly
1402
1403        # empty dict is directly used for reference count test
1404        self.assertEqual(list(reversed({})), [])
1405        self.assertEqual(list(reversed({}.items())), [])
1406        self.assertEqual(list(reversed({}.values())), [])
1407        self.assertEqual(list(reversed({}.keys())), [])
1408
1409        # dict() and {} don't trigger the same code path
1410        self.assertEqual(list(reversed(dict())), [])
1411        self.assertEqual(list(reversed(dict().items())), [])
1412        self.assertEqual(list(reversed(dict().values())), [])
1413        self.assertEqual(list(reversed(dict().keys())), [])
1414
1415    def test_reverse_iterator_for_shared_shared_dicts(self):
1416        class A:
1417            def __init__(self, x, y):
1418                if x: self.x = x
1419                if y: self.y = y
1420
1421        self.assertEqual(list(reversed(A(1, 2).__dict__)), ['y', 'x'])
1422        self.assertEqual(list(reversed(A(1, 0).__dict__)), ['x'])
1423        self.assertEqual(list(reversed(A(0, 1).__dict__)), ['y'])
1424
1425    def test_dict_copy_order(self):
1426        # bpo-34320
1427        od = collections.OrderedDict([('a', 1), ('b', 2)])
1428        od.move_to_end('a')
1429        expected = list(od.items())
1430
1431        copy = dict(od)
1432        self.assertEqual(list(copy.items()), expected)
1433
1434        # dict subclass doesn't override __iter__
1435        class CustomDict(dict):
1436            pass
1437
1438        pairs = [('a', 1), ('b', 2), ('c', 3)]
1439
1440        d = CustomDict(pairs)
1441        self.assertEqual(pairs, list(dict(d).items()))
1442
1443        class CustomReversedDict(dict):
1444            def keys(self):
1445                return reversed(list(dict.keys(self)))
1446
1447            __iter__ = keys
1448
1449            def items(self):
1450                return reversed(dict.items(self))
1451
1452        d = CustomReversedDict(pairs)
1453        self.assertEqual(pairs[::-1], list(dict(d).items()))
1454
1455    @support.cpython_only
1456    def test_dict_items_result_gc(self):
1457        # bpo-42536: dict.items's tuple-reuse speed trick breaks the GC's
1458        # assumptions about what can be untracked. Make sure we re-track result
1459        # tuples whenever we reuse them.
1460        it = iter({None: []}.items())
1461        gc.collect()
1462        # That GC collection probably untracked the recycled internal result
1463        # tuple, which is initialized to (None, None). Make sure it's re-tracked
1464        # when it's mutated and returned from __next__:
1465        self.assertTrue(gc.is_tracked(next(it)))
1466
1467    @support.cpython_only
1468    def test_dict_items_result_gc(self):
1469        # Same as test_dict_items_result_gc above, but reversed.
1470        it = reversed({None: []}.items())
1471        gc.collect()
1472        self.assertTrue(gc.is_tracked(next(it)))
1473
1474    def test_str_nonstr(self):
1475        # cpython uses a different lookup function if the dict only contains
1476        # `str` keys. Make sure the unoptimized path is used when a non-`str`
1477        # key appears.
1478
1479        class StrSub(str):
1480            pass
1481
1482        eq_count = 0
1483        # This class compares equal to the string 'key3'
1484        class Key3:
1485            def __hash__(self):
1486                return hash('key3')
1487
1488            def __eq__(self, other):
1489                nonlocal eq_count
1490                if isinstance(other, Key3) or isinstance(other, str) and other == 'key3':
1491                    eq_count += 1
1492                    return True
1493                return False
1494
1495        key3_1 = StrSub('key3')
1496        key3_2 = Key3()
1497        key3_3 = Key3()
1498
1499        dicts = []
1500
1501        # Create dicts of the form `{'key1': 42, 'key2': 43, key3: 44}` in a
1502        # bunch of different ways. In all cases, `key3` is not of type `str`.
1503        # `key3_1` is a `str` subclass and `key3_2` is a completely unrelated
1504        # type.
1505        for key3 in (key3_1, key3_2):
1506            # A literal
1507            dicts.append({'key1': 42, 'key2': 43, key3: 44})
1508
1509            # key3 inserted via `dict.__setitem__`
1510            d = {'key1': 42, 'key2': 43}
1511            d[key3] = 44
1512            dicts.append(d)
1513
1514            # key3 inserted via `dict.setdefault`
1515            d = {'key1': 42, 'key2': 43}
1516            self.assertEqual(d.setdefault(key3, 44), 44)
1517            dicts.append(d)
1518
1519            # key3 inserted via `dict.update`
1520            d = {'key1': 42, 'key2': 43}
1521            d.update({key3: 44})
1522            dicts.append(d)
1523
1524            # key3 inserted via `dict.__ior__`
1525            d = {'key1': 42, 'key2': 43}
1526            d |= {key3: 44}
1527            dicts.append(d)
1528
1529            # `dict(iterable)`
1530            def make_pairs():
1531                yield ('key1', 42)
1532                yield ('key2', 43)
1533                yield (key3, 44)
1534            d = dict(make_pairs())
1535            dicts.append(d)
1536
1537            # `dict.copy`
1538            d = d.copy()
1539            dicts.append(d)
1540
1541            # dict comprehension
1542            d = {key: 42 + i for i,key in enumerate(['key1', 'key2', key3])}
1543            dicts.append(d)
1544
1545        for d in dicts:
1546            with self.subTest(d=d):
1547                self.assertEqual(d.get('key1'), 42)
1548
1549                # Try to make an object that is of type `str` and is equal to
1550                # `'key1'`, but (at least on cpython) is a different object.
1551                noninterned_key1 = 'ke'
1552                noninterned_key1 += 'y1'
1553                if support.check_impl_detail(cpython=True):
1554                    # suppress a SyntaxWarning
1555                    interned_key1 = 'key1'
1556                    self.assertFalse(noninterned_key1 is interned_key1)
1557                self.assertEqual(d.get(noninterned_key1), 42)
1558
1559                self.assertEqual(d.get('key3'), 44)
1560                self.assertEqual(d.get(key3_1), 44)
1561                self.assertEqual(d.get(key3_2), 44)
1562
1563                # `key3_3` itself is definitely not a dict key, so make sure
1564                # that `__eq__` gets called.
1565                #
1566                # Note that this might not hold for `key3_1` and `key3_2`
1567                # because they might be the same object as one of the dict keys,
1568                # in which case implementations are allowed to skip the call to
1569                # `__eq__`.
1570                eq_count = 0
1571                self.assertEqual(d.get(key3_3), 44)
1572                self.assertGreaterEqual(eq_count, 1)
1573
1574
1575class CAPITest(unittest.TestCase):
1576
1577    # Test _PyDict_GetItem_KnownHash()
1578    @support.cpython_only
1579    def test_getitem_knownhash(self):
1580        from _testcapi import dict_getitem_knownhash
1581
1582        d = {'x': 1, 'y': 2, 'z': 3}
1583        self.assertEqual(dict_getitem_knownhash(d, 'x', hash('x')), 1)
1584        self.assertEqual(dict_getitem_knownhash(d, 'y', hash('y')), 2)
1585        self.assertEqual(dict_getitem_knownhash(d, 'z', hash('z')), 3)
1586
1587        # not a dict
1588        self.assertRaises(SystemError, dict_getitem_knownhash, [], 1, hash(1))
1589        # key does not exist
1590        self.assertRaises(KeyError, dict_getitem_knownhash, {}, 1, hash(1))
1591
1592        class Exc(Exception): pass
1593        class BadEq:
1594            def __eq__(self, other):
1595                raise Exc
1596            def __hash__(self):
1597                return 7
1598
1599        k1, k2 = BadEq(), BadEq()
1600        d = {k1: 1}
1601        self.assertEqual(dict_getitem_knownhash(d, k1, hash(k1)), 1)
1602        self.assertRaises(Exc, dict_getitem_knownhash, d, k2, hash(k2))
1603
1604
1605from test import mapping_tests
1606
1607class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol):
1608    type2test = dict
1609
1610class Dict(dict):
1611    pass
1612
1613class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol):
1614    type2test = Dict
1615
1616
1617if __name__ == "__main__":
1618    unittest.main()
1619