1"""
2Tests to ensure that NSDictionary and NSMutableDictionary conform
3to the python dict interface
4
5These tests are basicly a port of the Python 3.2 tests for dictionaries
6and dictionary views.
7
8TODO:
9- Port all tests
10- Do the same for python 2.x
11- Do the same for sets (NSSet) and lists (NSArray)
12"""
13from PyObjCTools.TestSupport import *
14import objc
15
16# Import some of the stdlib tests
17from test import mapping_tests
18
19NSDictionary = objc.lookUpClass('NSDictionary')
20NSMutableDictionary = objc.lookUpClass('NSMutableDictionary')
21
22class TestNSDictionaryInterface (TestCase):
23    def dictClass(self):
24        return NSDictionary
25
26    def createDictionary(self, **kwds):
27        return NSDictionary.dictionaryWithDictionary_(kwds)
28
29    def testConstructor(self):
30        value = self.dictClass()()
31        self.assertEqual(value, {})
32
33    def testBool(self):
34        self.assertIs(not self.createDictionary(), True)
35        self.assertTrue(self.createDictionary(foo='1'))
36        self.assertIs(bool(self.createDictionary()), False)
37        self.assertIs(bool(self.createDictionary(foo='1')), True)
38
39    def testKeys(self):
40        d = self.createDictionary()
41        self.assertEqual(set(d.keys()), set())
42
43        d = self.createDictionary(a=1, b=2)
44        k = d.keys()
45
46        self.assertIn('a', d)
47        self.assertIn('b', d)
48        self.assertIn('a', k)
49        self.assertIn('b', k)
50
51        self.assertIsNotInstance(k, list)
52
53        self.assertEqual(repr(self.createDictionary(a=1).keys()),
54                "<nsdict_keys(['a'])>")
55
56    def testValues(self):
57        d = self.createDictionary()
58        self.assertEqual(set(d.values()), set())
59
60        d = self.createDictionary(a=1)
61
62        self.assertEqual(set(d.values()), {1})
63        self.assertRaises(TypeError, d.values, None)
64        self.assertIsNotInstance(d.values(), list)
65
66        self.assertEqual(repr(self.createDictionary(a=1).values()),
67                "<nsdict_values([1])>")
68
69    def testItems(self):
70        d = self.createDictionary()
71        self.assertEqual(set(d.items()), set())
72
73        d = self.createDictionary(a=1)
74        self.assertEqual(set(d.items()), {('a', 1)})
75        self.assertRaises(TypeError, d.items, None)
76        self.assertEqual(repr(self.createDictionary(a=1).items()),
77                "<nsdict_items([('a', 1)])>")
78
79    def testContains(self):
80        d = self.createDictionary()
81        self.assertNotIn('a', d)
82        self.assertFalse('a' in d)
83        self.assertTrue('a' not in d)
84
85        d = self.createDictionary(a=1, b=2)
86        self.assertIn('a', d)
87        self.assertIn('b', d)
88        self.assertNotIn('c', d)
89
90        self.assertRaises(TypeError, d.__contains__)
91
92    def testLen(self):
93        d = self.createDictionary()
94        self.assertEqual(len(d), 0)
95
96        d = self.createDictionary(a=1, b=2)
97        self.assertEqual(len(d), 2)
98
99    def testGetItem(self):
100        d = self.createDictionary(a=1, b=2)
101        self.assertEqual(d['a'], 1)
102        self.assertEqual(d['b'], 2)
103
104        self.assertRaises(TypeError, d.__getitem__)
105
106    def testFromKeys(self):
107        d = self.dictClass().fromkeys('abc')
108        self.assertEqual(d, {'a':None, 'b':None, 'c':None})
109
110        d = self.createDictionary()
111        self.assertIsNot(d.fromkeys('abc'), d)
112        self.assertEqual(d.fromkeys('abc'), {'a':None, 'b':None, 'c':None})
113        self.assertEqual(d.fromkeys((4,5),0), {4:0, 5:0})
114        self.assertEqual(d.fromkeys([]), {})
115
116        def g():
117            yield 1
118
119        self.assertEqual(d.fromkeys(g()), {1:None})
120        self.assertRaises(TypeError, {}.fromkeys, 3)
121
122        d = self.dictClass()(zip(range(6), range(6)))
123        self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6)))
124
125    def _testCopy(self):
126        self.fail("Decide what to do w.r.t. -copy and -mutableCopy")
127
128    def testGet(self):
129        d = self.createDictionary()
130        self.assertIs(d.get('c'), None)
131        self.assertEqual(d.get('c', 3), 3)
132
133
134        d = self.createDictionary(a=1, b=2)
135        self.assertIs(d.get('c'), None)
136        self.assertEqual(d.get('c', 3), 3)
137        self.assertEqual(d.get('a'), 1)
138        self.assertEqual(d.get('a', 3), 1)
139        self.assertRaises(TypeError, d.get)
140        self.assertRaises(TypeError, d.get, None, None, None)
141
142
143    def testEq(self):
144        self.assertEqual(self.createDictionary(), self.createDictionary())
145        self.assertEqual(self.createDictionary(a=1), self.createDictionary(a=1))
146
147        class Exc(Exception): pass
148
149        class BadCmp(object):
150            def __eq__(self, other):
151                raise Exc()
152            def __hash__(self):
153                return 1
154
155        d1 = {BadCmp(): 1}
156        d2 = {BadCmp(): 1}
157
158        with self.assertRaises(Exc):
159            d1 == d2
160
161
162    def testKeysContained(self):
163        self.helper_keys_contained(lambda x: x.keys())
164        self.helper_keys_contained(lambda x: x.items())
165
166    def helper_keys_contained(self, fn):
167        # Test rich comparisons against dict key views, which should behave the
168        # same as sets.
169
170        empty = fn(self.createDictionary())
171        empty2 = fn(self.createDictionary())
172        smaller = fn(self.createDictionary(a=1, b=2))
173        larger = fn(self.createDictionary(a=1, b=2, c=3))
174        larger2 = fn(self.createDictionary(a=1, b=2, c=3))
175        larger3 = fn(self.createDictionary(d=1, b=2, c=3))
176
177        self.assertTrue(smaller <  larger)
178        self.assertTrue(smaller <= larger)
179        self.assertTrue(larger >  smaller)
180        self.assertTrue(larger >= smaller)
181
182        self.assertFalse(smaller >= larger)
183        self.assertFalse(smaller >  larger)
184        self.assertFalse(larger  <= smaller)
185        self.assertFalse(larger  <  smaller)
186
187        self.assertFalse(smaller <  larger3)
188        self.assertFalse(smaller <= larger3)
189        self.assertFalse(larger3 >  smaller)
190        self.assertFalse(larger3 >= smaller)
191
192        # Inequality strictness
193        self.assertTrue(larger2 >= larger)
194        self.assertTrue(larger2 <= larger)
195        self.assertFalse(larger2 > larger)
196        self.assertFalse(larger2 < larger)
197
198        self.assertTrue(larger == larger2)
199        self.assertTrue(smaller != larger)
200
201        # There is an optimization on the zero-element case.
202        self.assertTrue(empty == empty2)
203        self.assertFalse(empty != empty2)
204        self.assertFalse(empty == smaller)
205        self.assertTrue(empty != smaller)
206
207        # With the same size, an elementwise compare happens
208        self.assertTrue(larger != larger3)
209        self.assertFalse(larger == larger3)
210
211    def testErrorsInViewContainmentCheck(self):
212        class C:
213            def __eq__(self, other):
214                raise RuntimeError()
215
216        d1 = self.dictClass().dictionaryWithDictionary_({1: C()})
217        d2 = self.dictClass().dictionaryWithDictionary_({1: C()})
218
219        with self.assertRaises(RuntimeError):
220            d1.items() == d2.items()
221        with self.assertRaises(RuntimeError):
222            d1.items() != d2.items()
223        with self.assertRaises(RuntimeError):
224            d1.items() <= d2.items()
225        with self.assertRaises(RuntimeError):
226            d1.items() >= d2.items()
227
228        d3 = self.dictClass().dictionaryWithDictionary_({1: C(), 2: C()})
229        with self.assertRaises(RuntimeError):
230            d2.items() < d3.items()
231        with self.assertRaises(RuntimeError):
232            d3.items() > d2.items()
233
234        k1 = self.dictClass().dictionaryWithDictionary_({1:1, 2:2}).keys()
235        k2 = self.dictClass().dictionaryWithDictionary_({1:1, 2:2, 3:3}).keys()
236        k3 = self.dictClass().dictionaryWithDictionary_({4:4}).keys()
237
238        self.assertEqual(k1 - k2, set())
239        self.assertEqual(k1 - k3, {1,2})
240        self.assertEqual(k2 - k1, {3})
241        self.assertEqual(k3 - k1, {4})
242        self.assertEqual(k1 & k2, {1,2})
243        self.assertEqual(k1 & k3, set())
244        self.assertEqual(k1 | k2, {1,2,3})
245        self.assertEqual(k1 ^ k2, {3})
246        self.assertEqual(k1 ^ k3, {1,2,4})
247
248    def testDictviewSetOperationsOnItems(self):
249        k1 = self.dictClass().dictionaryWithDictionary_({1:1, 2:2}).items()
250        k2 = self.dictClass().dictionaryWithDictionary_({1:1, 2:2, 3:3}).items()
251        k3 = self.dictClass().dictionaryWithDictionary_({4:4}).items()
252
253        self.assertEqual(k1 - k2, set())
254        self.assertEqual(k1 - k3, {(1,1), (2,2)})
255        self.assertEqual(k2 - k1, {(3,3)})
256        self.assertEqual(k3 - k1, {(4,4)})
257        self.assertEqual(k1 & k2, {(1,1), (2,2)})
258        self.assertEqual(k1 & k3, set())
259        self.assertEqual(k1 | k2, {(1,1), (2,2), (3,3)})
260        self.assertEqual(k1 ^ k2, {(3,3)})
261        self.assertEqual(k1 ^ k3, {(1,1), (2,2), (4,4)})
262
263    def testDictviewMixedSetOperations(self):
264        # Just a few for .keys()
265        self.assertTrue(self.dictClass().dictionaryWithDictionary_({1:1}).keys() == {1})
266        self.assertTrue({1} == self.dictClass().dictionaryWithDictionary_({1:1}).keys())
267        self.assertEqual(self.dictClass().dictionaryWithDictionary_({1:1}).keys() | {2}, {1, 2})
268        self.assertEqual({2} | self.dictClass().dictionaryWithDictionary_({1:1}).keys(), {1, 2})
269        # And a few for .items()
270        self.assertTrue(self.dictClass().dictionaryWithDictionary_({1:1}).items() == {(1,1)})
271        self.assertTrue({(1,1)} == self.dictClass().dictionaryWithDictionary_({1:1}).items())
272        self.assertEqual(self.dictClass().dictionaryWithDictionary_({1:1}).items() | {2}, {(1,1), 2})
273        self.assertEqual({2} | self.dictClass().dictionaryWithDictionary_({1:1}).items(), {(1,1), 2})
274
275
276class TestNSMutableDictionaryInterface (TestNSDictionaryInterface):
277    def dictClass(self):
278        return NSMutableDictionary
279
280    def createDictionary(self, **kwds):
281        return NSMutableDictionary.dictionaryWithDictionary_(kwds)
282
283    def testKeysMutable(self):
284        d = self.createDictionary()
285        self.assertEqual(set(d.keys()), set())
286
287        d = self.createDictionary(a=1, b=2)
288        k = d.keys()
289
290
291        self.assertIn('a', d)
292        self.assertIn('b', d)
293        self.assertIn('a', k)
294        self.assertIn('b', k)
295
296        self.assertIsNotInstance(k, list)
297
298        self.assertNotIn('c', d)
299        self.assertNotIn('c', k)
300
301        d['c'] = 3
302        self.assertIn('c', d)
303        self.assertIn('c', k)
304
305    def testValuesMutable(self):
306        d = self.createDictionary()
307        self.assertEqual(set(d.values()), set())
308
309        d = self.createDictionary(a=1)
310        v = d.values()
311
312        self.assertEqual(set(v), {1})
313
314        d['b'] = 2
315        self.assertEqual(set(v), {1, 2})
316
317    def testItemsMutable(self):
318        d = self.createDictionary()
319        self.assertEqual(set(d.items()), set())
320
321        d = self.createDictionary(a=1)
322        i = d.items()
323
324        self.assertEqual(set(i), {('a', 1)})
325
326        d['b'] = 2
327
328        self.assertEqual(set(i), {('a', 1), ('b', 2)})
329
330    def testGetItem(self):
331        d = self.createDictionary(a=1, b=2)
332        self.assertEqual(d['a'], 1)
333        self.assertEqual(d['b'], 2)
334
335        d['c'] = 3
336        d['a'] = 4
337        self.assertEqual(d['c'], 3)
338        self.assertEqual(d['a'], 4)
339        del d['b']
340        self.assertEqual(d, {'a': 4, 'c': 3})
341
342        self.assertRaises(TypeError, d.__getitem__)
343
344        class Exc (Exception): pass
345
346        class BadEq(object):
347            def __eq__(self, other):
348                raise Exc()
349            def __hash__(self):
350                return hash(23)
351
352        d = self.createDictionary()
353        d[BadEq()] = 42
354        self.assertRaises(KeyError, d.__getitem__, 23)
355
356        class BadHash(object):
357            fail = False
358            def __hash__(self):
359                if self.fail:
360                    raise Exc()
361                else:
362                    return 42
363
364        d = self.createDictionary()
365        x = BadHash()
366        d[x] = 42
367        x.fail = True
368
369        # FIXME
370        #self.assertRaises(Exc, d.__getitem__, x)
371
372    def testClear(self):
373        d = self.createDictionary(a=1, b=2, c=3)
374        d.clear()
375
376        self.assertEqual(d, {})
377
378        self.assertRaises(TypeError, d.clear, None)
379
380    def testUpdate(self):
381        d = self.createDictionary()
382        d.update({1:100})
383        d.update({2:20})
384        d.update({1:1, 2:2, 3:3})
385        self.assertEqual(d, {1:1, 2:2, 3:3})
386
387        d.update()
388        self.assertEqual(d, {1:1, 2:2, 3:3})
389
390        self.assertRaises((TypeError, AttributeError), d.update, None)
391
392        class SimpleUserDict:
393            def __init__(self):
394                self.d = {1:1, 2:2, 3:3}
395            def keys(self):
396                return self.d.keys()
397            def __getitem__(self, i):
398                return self.d[i]
399
400        d = self.createDictionary()
401        d.update(SimpleUserDict())
402        self.assertEqual(d, {1:1, 2:2, 3:3})
403
404        class Exc(Exception): pass
405
406        d.clear()
407
408        class FailingUserDict:
409            def keys(self):
410                raise Exc()
411
412        self.assertRaises(Exc, d.update, FailingUserDict())
413
414
415        class FailingUserDict:
416            def keys(self):
417                class BogonIter:
418                    def __init__(self):
419                        self.i = 1
420                    def __iter__(self):
421                        return self
422                    def __next__(self):
423                        if self.i:
424                            self.i = 0
425                            return 'a'
426                        raise Exc
427                return BogonIter()
428            def __getitem__(self, key):
429                return key
430
431        self.assertRaises(Exc, d.update, FailingUserDict())
432
433
434        class FailingUserDict:
435            def keys(self):
436                class BogonIter:
437                    def __init__(self):
438                        self.i = ord('a')
439                    def __iter__(self):
440                        return self
441                    def __next__(self):
442                        if self.i <= ord('z'):
443                            rtn = chr(self.i)
444                            self.i += 1
445                            return rtn
446                        raise StopIteration
447                return BogonIter()
448            def __getitem__(self, key):
449                raise Exc
450        self.assertRaises(Exc, d.update, FailingUserDict())
451
452        class badseq(object):
453            def __iter__(self):
454                return self
455            def __next__(self):
456                raise Exc()
457
458        self.assertRaises(Exc, {}.update, badseq())
459        self.assertRaises(ValueError, {}.update, [(1, 2, 3)])
460
461
462    def setDefault(self):
463        d = self.createDictionary()
464        self.assertIs(d.setdefault('key0'), None)
465        d.setdefault('key0', [])
466        self.assertIs(d.setdefault('key0'), None)
467        d.setdefault('key', []).append(3)
468        self.assertEqual(d['key'][0], 3)
469        d.setdefault('key', []).append(4)
470        self.assertEqual(len(d['key']), 2)
471        self.assertRaises(TypeError, d.setdefault)
472
473        class Exc (Exception): pass
474
475        class BadHash(object):
476            fail = False
477            def __hash__(self):
478                if self.fail:
479                    raise Exc()
480                else:
481                    return 42
482
483        x = BadHash()
484        d[x] = 42
485        x.fail = True
486        self.assertRaises(Exc, d.setdefault, x, [])
487
488    def testPopitem(self):
489        for copymode in -1, +1:
490            # -1: b has same structure as a
491            # +1: b is a.copy()
492            for log2size in range(12):
493                size = 2**log2size
494                a = self.createDictionary()
495                b = self.createDictionary()
496                for i in range(size):
497                    a[repr(i)] = i
498                    if copymode < 0:
499                        b[repr(i)] = i
500                if copymode > 0:
501                    b = a.mutableCopy()
502                for i in range(size):
503                    ka, va = ta = a.popitem()
504                    self.assertEqual(va, int(ka))
505                    kb, vb = tb = b.popitem()
506                    self.assertEqual(vb, int(kb))
507                    self.assertFalse(copymode < 0 and ta != tb)
508                self.assertFalse(a)
509                self.assertFalse(b)
510
511        d = self.createDictionary()
512        self.assertRaises(KeyError, d.popitem)
513
514
515    def testPop(self):
516        d = self.createDictionary()
517        k, v = 'abc', 'def'
518        d[k] = v
519        self.assertRaises(KeyError, d.pop, 'ghi')
520
521        self.assertEqual(d.pop(k), v)
522        self.assertEqual(len(d), 0)
523
524        self.assertRaises(KeyError, d.pop, k)
525
526        self.assertEqual(d.pop(k, v), v)
527        d[k] = v
528        self.assertEqual(d.pop(k, 1), v)
529
530        self.assertRaises(TypeError, d.pop)
531
532        class Exc(Exception): pass
533
534        class BadHash(object):
535            fail = False
536            def __hash__(self):
537                if self.fail:
538                    raise Exc()
539                else:
540                    return 42
541
542        #x = BadHash()
543        #d[x] = 42
544        #x.fail = True
545        #self.assertRaises(Exc, d.pop, x)
546
547class DictSetTest (TestCase):
548    testclass = NSDictionary
549
550    def testDictKeys(self):
551        d = self.testclass.dictionaryWithDictionary_({1: 10, "a": "ABC"})
552
553        keys = d.keys()
554
555        self.assertEqual(len(keys), 2)
556        self.assertEqual(set(keys), {1, "a"})
557        self.assertEqual(keys, {1, "a"})
558        self.assertNotEqual(keys, {1, "a", "b"})
559        self.assertNotEqual(keys, {1, "b"})
560        self.assertNotEqual(keys, {1})
561        self.assertNotEqual(keys, 42)
562        self.assertIn(1, keys)
563        self.assertIn("a", keys)
564        self.assertNotIn(10, keys)
565        self.assertNotIn("Z", keys)
566        self.assertEqual(d.keys(), d.keys())
567
568        e = self.testclass.dictionaryWithDictionary_({1: 11, "a": "def"})
569        self.assertEqual(d.keys(), e.keys())
570
571    def testDictItems(self):
572        d = self.testclass.dictionaryWithDictionary_({1: 10, "a": "ABC"})
573        items = d.items()
574
575        self.assertEqual(len(items), 2)
576        self.assertEqual(set(items), {(1, 10), ("a", "ABC")})
577        self.assertEqual(items, {(1, 10), ("a", "ABC")})
578        self.assertNotEqual(items, {(1, 10), ("a", "ABC"), "junk"})
579        self.assertNotEqual(items, {(1, 10), ("a", "def")})
580        self.assertNotEqual(items, {(1, 10)})
581        self.assertNotEqual(items, 42)
582        self.assertIn((1, 10), items)
583        self.assertIn(("a", "ABC"), items)
584        self.assertNotIn((1, 11), items)
585        self.assertNotIn(1, items)
586        self.assertNotIn((), items)
587        self.assertNotIn((1,), items)
588        self.assertNotIn((1, 2, 3), items)
589        self.assertEqual(d.items(), d.items())
590
591        e = d.copy()
592        self.assertEqual(d.items(), e.items())
593
594
595
596class DictSetTest (DictSetTest):
597    testclass = NSMutableDictionary
598
599    def testDictKeysMutable(self):
600        d = self.testclass.dictionaryWithDictionary_({1: 10, "a": "ABC"})
601        e = self.testclass.dictionaryWithDictionary_({1: 11, "a": "def"})
602        self.assertEqual(d.keys(), e.keys())
603
604        del e["a"]
605        self.assertNotEqual(d.keys(), e.keys())
606
607    def testDictItemsMutable(self):
608        d = self.testclass.dictionaryWithDictionary_({1: 10, "a": "ABC"})
609
610        e = d.copy()
611        self.assertEqual(d.items(), e.items())
612        d["a"] = "def"
613        self.assertNotEqual(d.items(), e.items())
614
615    def testDictMixedKeysItems(self):
616        d = self.testclass.dictionaryWithDictionary_(
617                {(1, 1): 11, (2, 2): 22})
618        e = self.testclass.dictionaryWithDictionary_(
619                {1: 1, 2: 2})
620        self.assertEqual(d.keys(), e.items())
621        self.assertNotEqual(d.items(), e.keys())
622
623    def testDictValues(self):
624        d = self.testclass.dictionaryWithDictionary_(
625                {1: 10, "a": "ABC"})
626        values = d.values()
627        self.assertEqual(set(values), {10, "ABC"})
628        self.assertEqual(len(values), 2)
629
630
631
632
633
634class GeneralMappingTestsNSMutableDictionary (
635        mapping_tests.BasicTestMappingProtocol):
636    type2test = NSMutableDictionary
637
638
639import collections
640
641class TestABC (TestCase):
642    def testDictABC(self):
643        self.assertTrue(issubclass(NSDictionary, collections.Mapping))
644        self.assertTrue(issubclass(NSMutableDictionary, collections.Mapping))
645        self.assertTrue(issubclass(NSMutableDictionary, collections.MutableMapping))
646
647    def testViewABC(self):
648        d = NSDictionary.dictionary()
649        self.assertTrue(isinstance(d.keys(), collections.KeysView))
650        self.assertTrue(isinstance(d.values(), collections.ValuesView))
651        self.assertTrue(isinstance(d.items(), collections.ItemsView))
652
653        d = NSMutableDictionary.dictionary()
654        self.assertTrue(isinstance(d.keys(), collections.KeysView))
655        self.assertTrue(isinstance(d.values(), collections.ValuesView))
656        self.assertTrue(isinstance(d.items(), collections.ItemsView))
657
658if __name__ == "__main__":
659    main()
660