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.assertEquals(self.createDictionary(), self.createDictionary())
145        self.assertEquals(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        self.assertIn('a', d)
291        self.assertIn('b', d)
292        self.assertIn('a', k)
293        self.assertIn('b', k)
294
295        self.assertIsNotInstance(k, list)
296
297        self.assertNotIn('c', d)
298        self.assertNotIn('c', k)
299
300        d['c'] = 3
301        self.assertIn('c', d)
302        self.assertIn('c', k)
303
304    def testValuesMutable(self):
305        d = self.createDictionary()
306        self.assertEqual(set(d.values()), set())
307
308        d = self.createDictionary(a=1)
309        v = d.values()
310
311        self.assertEqual(set(v), {1})
312
313        d['b'] = 2
314        self.assertEqual(set(v), {1, 2})
315
316    def testItemsMutable(self):
317        d = self.createDictionary()
318        self.assertEqual(set(d.items()), set())
319
320        d = self.createDictionary(a=1)
321        i = d.items()
322
323        self.assertEqual(set(i), {('a', 1)})
324
325        d['b'] = 2
326
327        self.assertEqual(set(i), {('a', 1), ('b', 2)})
328
329    def testGetItem(self):
330        d = self.createDictionary(a=1, b=2)
331        self.assertEqual(d['a'], 1)
332        self.assertEqual(d['b'], 2)
333
334        d['c'] = 3
335        d['a'] = 4
336        self.assertEqual(d['c'], 3)
337        self.assertEqual(d['a'], 4)
338        del d['b']
339        self.assertEqual(d, {'a': 4, 'c': 3})
340
341        self.assertRaises(TypeError, d.__getitem__)
342
343        class Exc (Exception): pass
344
345        class BadEq(object):
346            def __eq__(self, other):
347                raise Exc()
348            def __hash__(self):
349                return hash(23)
350
351        d = self.createDictionary()
352        d[BadEq()] = 42
353        self.assertRaises(KeyError, d.__getitem__, 23)
354
355        class BadHash(object):
356            fail = False
357            def __hash__(self):
358                if self.fail:
359                    raise Exc()
360                else:
361                    return 42
362
363        d = self.createDictionary()
364        x = BadHash()
365        d[x] = 42
366        x.fail = True
367
368        # FIXME
369        #self.assertRaises(Exc, d.__getitem__, x)
370
371    def testClear(self):
372        d = self.createDictionary(a=1, b=2, c=3)
373        d.clear()
374
375        self.assertEqual(d, {})
376
377        self.assertRaises(TypeError, d.clear, None)
378
379    def testUpdate(self):
380        d = self.createDictionary()
381        d.update({1:100})
382        d.update({2:20})
383        d.update({1:1, 2:2, 3:3})
384        self.assertEqual(d, {1:1, 2:2, 3:3})
385
386        d.update()
387        self.assertEqual(d, {1:1, 2:2, 3:3})
388
389        self.assertRaises((TypeError, AttributeError), d.update, None)
390
391        class SimpleUserDict:
392            def __init__(self):
393                self.d = {1:1, 2:2, 3:3}
394            def keys(self):
395                return self.d.keys()
396            def __getitem__(self, i):
397                return self.d[i]
398
399        d = self.createDictionary()
400        d.update(SimpleUserDict())
401        self.assertEqual(d, {1:1, 2:2, 3:3})
402
403        class Exc(Exception): pass
404
405        d.clear()
406
407        class FailingUserDict:
408            def keys(self):
409                raise Exc()
410
411        self.assertRaises(Exc, d.update, FailingUserDict())
412
413
414        class FailingUserDict:
415            def keys(self):
416                class BogonIter:
417                    def __init__(self):
418                        self.i = 1
419                    def __iter__(self):
420                        return self
421                    def __next__(self):
422                        if self.i:
423                            self.i = 0
424                            return 'a'
425                        raise Exc
426                return BogonIter()
427            def __getitem__(self, key):
428                return key
429
430        self.assertRaises(Exc, d.update, FailingUserDict())
431
432
433        class FailingUserDict:
434            def keys(self):
435                class BogonIter:
436                    def __init__(self):
437                        self.i = ord('a')
438                    def __iter__(self):
439                        return self
440                    def __next__(self):
441                        if self.i <= ord('z'):
442                            rtn = chr(self.i)
443                            self.i += 1
444                            return rtn
445                        raise StopIteration
446                return BogonIter()
447            def __getitem__(self, key):
448                raise Exc
449        self.assertRaises(Exc, d.update, FailingUserDict())
450
451        class badseq(object):
452            def __iter__(self):
453                return self
454            def __next__(self):
455                raise Exc()
456
457        self.assertRaises(Exc, {}.update, badseq())
458        self.assertRaises(ValueError, {}.update, [(1, 2, 3)])
459
460
461    def setDefault(self):
462        d = self.createDictionary()
463        self.assertIs(d.setdefault('key0'), None)
464        d.setdefault('key0', [])
465        self.assertIs(d.setdefault('key0'), None)
466        d.setdefault('key', []).append(3)
467        self.assertEqual(d['key'][0], 3)
468        d.setdefault('key', []).append(4)
469        self.assertEqual(len(d['key']), 2)
470        self.assertRaises(TypeError, d.setdefault)
471
472        class Exc (Exception): pass
473
474        class BadHash(object):
475            fail = False
476            def __hash__(self):
477                if self.fail:
478                    raise Exc()
479                else:
480                    return 42
481
482        x = BadHash()
483        d[x] = 42
484        x.fail = True
485        self.assertRaises(Exc, d.setdefault, x, [])
486
487    def testPopitem(self):
488        for copymode in -1, +1:
489            # -1: b has same structure as a
490            # +1: b is a.copy()
491            for log2size in range(12):
492                size = 2**log2size
493                a = self.createDictionary()
494                b = self.createDictionary()
495                for i in range(size):
496                    a[repr(i)] = i
497                    if copymode < 0:
498                        b[repr(i)] = i
499                if copymode > 0:
500                    b = a.mutableCopy()
501                for i in range(size):
502                    ka, va = ta = a.popitem()
503                    self.assertEqual(va, int(ka))
504                    kb, vb = tb = b.popitem()
505                    self.assertEqual(vb, int(kb))
506                    self.assertFalse(copymode < 0 and ta != tb)
507                self.assertFalse(a)
508                self.assertFalse(b)
509
510        d = self.createDictionary()
511        self.assertRaises(KeyError, d.popitem)
512
513
514    def testPop(self):
515        d = self.createDictionary()
516        k, v = 'abc', 'def'
517        d[k] = v
518        self.assertRaises(KeyError, d.pop, 'ghi')
519
520        self.assertEqual(d.pop(k), v)
521        self.assertEqual(len(d), 0)
522
523        self.assertRaises(KeyError, d.pop, k)
524
525        self.assertEqual(d.pop(k, v), v)
526        d[k] = v
527        self.assertEqual(d.pop(k, 1), v)
528
529        self.assertRaises(TypeError, d.pop)
530
531        class Exc(Exception): pass
532
533        class BadHash(object):
534            fail = False
535            def __hash__(self):
536                if self.fail:
537                    raise Exc()
538                else:
539                    return 42
540
541        #x = BadHash()
542        #d[x] = 42
543        #x.fail = True
544        #self.assertRaises(Exc, d.pop, x)
545
546class DictSetTest (TestCase):
547    testclass = NSDictionary
548
549    def testDictKeys(self):
550        d = self.testclass.dictionaryWithDictionary_({1: 10, "a": "ABC"})
551
552        keys = d.keys()
553
554        self.assertEqual(len(keys), 2)
555        self.assertEqual(set(keys), {1, "a"})
556        self.assertEqual(keys, {1, "a"})
557        self.assertNotEqual(keys, {1, "a", "b"})
558        self.assertNotEqual(keys, {1, "b"})
559        self.assertNotEqual(keys, {1})
560        self.assertNotEqual(keys, 42)
561        self.assertIn(1, keys)
562        self.assertIn("a", keys)
563        self.assertNotIn(10, keys)
564        self.assertNotIn("Z", keys)
565        self.assertEqual(d.keys(), d.keys())
566
567        e = self.testclass.dictionaryWithDictionary_({1: 11, "a": "def"})
568        self.assertEqual(d.keys(), e.keys())
569
570    def testDictItems(self):
571        d = self.testclass.dictionaryWithDictionary_({1: 10, "a": "ABC"})
572        items = d.items()
573
574        self.assertEqual(len(items), 2)
575        self.assertEqual(set(items), {(1, 10), ("a", "ABC")})
576        self.assertEqual(items, {(1, 10), ("a", "ABC")})
577        self.assertNotEqual(items, {(1, 10), ("a", "ABC"), "junk"})
578        self.assertNotEqual(items, {(1, 10), ("a", "def")})
579        self.assertNotEqual(items, {(1, 10)})
580        self.assertNotEqual(items, 42)
581        self.assertIn((1, 10), items)
582        self.assertIn(("a", "ABC"), items)
583        self.assertNotIn((1, 11), items)
584        self.assertNotIn(1, items)
585        self.assertNotIn((), items)
586        self.assertNotIn((1,), items)
587        self.assertNotIn((1, 2, 3), items)
588        self.assertEqual(d.items(), d.items())
589
590        e = d.copy()
591        self.assertEqual(d.items(), e.items())
592
593
594
595class DictSetTest (DictSetTest):
596    testclass = NSMutableDictionary
597
598    def testDictKeysMutable(self):
599        d = self.testclass.dictionaryWithDictionary_({1: 10, "a": "ABC"})
600        e = self.testclass.dictionaryWithDictionary_({1: 11, "a": "def"})
601        self.assertEqual(d.keys(), e.keys())
602
603        del e["a"]
604        self.assertNotEqual(d.keys(), e.keys())
605
606    def testDictItemsMutable(self):
607        d = self.testclass.dictionaryWithDictionary_({1: 10, "a": "ABC"})
608
609        e = d.copy()
610        self.assertEqual(d.items(), e.items())
611        d["a"] = "def"
612        self.assertNotEqual(d.items(), e.items())
613
614    def testDictMixedKeysItems(self):
615        d = self.testclass.dictionaryWithDictionary_(
616                {(1, 1): 11, (2, 2): 22})
617        e = self.testclass.dictionaryWithDictionary_(
618                {1: 1, 2: 2})
619        self.assertEqual(d.keys(), e.items())
620        self.assertNotEqual(d.items(), e.keys())
621
622    def testDictValues(self):
623        d = self.testclass.dictionaryWithDictionary_(
624                {1: 10, "a": "ABC"})
625        values = d.values()
626        self.assertEqual(set(values), {10, "ABC"})
627        self.assertEqual(len(values), 2)
628
629
630
631
632
633class GeneralMappingTestsNSMutableDictionary (
634        mapping_tests.BasicTestMappingProtocol):
635    type2test = NSMutableDictionary
636
637
638import collections
639
640class TestABC (TestCase):
641    def testDictABC(self):
642        self.assertTrue(issubclass(NSDictionary, collections.Mapping))
643        self.assertTrue(issubclass(NSMutableDictionary, collections.Mapping))
644        self.assertTrue(issubclass(NSMutableDictionary, collections.MutableMapping))
645
646    def testViewABC(self):
647        d = NSDictionary.dictionary()
648        self.assertTrue(isinstance(d.keys(), collections.KeysView))
649        self.assertTrue(isinstance(d.values(), collections.ValuesView))
650        self.assertTrue(isinstance(d.items(), collections.ItemsView))
651
652        d = NSMutableDictionary.dictionary()
653        self.assertTrue(isinstance(d.keys(), collections.KeysView))
654        self.assertTrue(isinstance(d.values(), collections.ValuesView))
655        self.assertTrue(isinstance(d.items(), collections.ItemsView))
656
657if __name__ == "__main__":
658    main()
659