1# -*- coding: utf-8 -*-
2
3#
4# omdict - Ordered Multivalue Dictionary.
5#
6# Ansgar Grunseid
7# grunseid.com
8# grunseid@gmail.com
9#
10# License: Build Amazing Things (Unlicense)
11#
12
13from __future__ import absolute_import
14
15import unittest
16from itertools import product, repeat
17
18import six
19from six.moves import map, zip, zip_longest
20
21from orderedmultidict.orderedmultidict import omdict
22
23try:
24    from collections import OrderedDict as odict  # Python 2.7+.
25except ImportError:
26    from ordereddict import OrderedDict as odict  # Python 2.4-2.6.
27
28_unique = object()
29
30
31def callable_attr(o, attr):
32    return hasattr(o, attr) and callable(getattr(o, attr))
33
34
35def is_iterator(i):
36    return callable_attr(i, 'next') or callable_attr(i, '__next__')
37
38
39# Utility list subclass to expose items() and iteritems() methods on basic
40# lists. This provides a common iteration interface for lists and dictionaries
41# for looping through their items without having to test for and maintain two
42# separate bodies, one for lists and one for dictionaries.
43#
44# So instead of requiring two bodies, one for lists and one for dicts
45#
46#  lists = [[(1,1),(2,2)]]
47#  dicts = [{1:1,2:2}]
48#  for lst in lists:
49#    lst == ...
50#  for dic in dicts:
51#    dic.items() == ...
52#
53# list and dictionary looping bodies can be merged with itemlist
54#
55#  itemlist = [itemlist([(1,1),(2,2)]), {1:1,2:2}]
56#  for ilist in itemlist:
57#    ilist.items() == ...
58#
59
60
61class itemlist(list):
62
63    def items(self):
64        return self
65
66    def iteritems(self):
67        return iter(self)
68
69
70class TestOmdict(unittest.TestCase):
71
72    def setUp(self):
73        self.inits = [
74            {}, {1: 1}, {1: 1, 2: 2, 3: 3}, {None: None}, {
75                None: None, 1: 1, 2: 2}, {False: False},
76        ]
77        self.inits += list(map(itemlist, [
78            [], [(1, 1)], [(1, 1), (2, 2)], [(1, 1), (2, 2), (1, 1)],
79            [(1, 1), (1, 1), (1, 1)], [(None, None), (None, None)],
80            [(False, False)],
81            [(None, 1), (1, None), (None, None), (None, 1), (1, None)],
82        ]))
83
84        # Updates to test update() and updateall().
85        self.updates = [
86            {}, {7: 7}, {7: 7, 8: 8, 9: 9}, {None: None}, {1: 1, 2: 2}]
87        self.updates += list(map(itemlist, [
88            [], [(7, 7)], [(7, 7), (8, 8), (9, 9)], [(None, 'none')],
89            [(9, 9), (1, 2)], [(7, 7), (7, 7), (8, 8), (7, 77)],
90            [(1, 11), (1, 111), (1, 1111), (2, 22),
91                (2, 222), ('a', 'a'), ('a', 'aa')],
92        ]))
93
94        self.keyword_updates = [
95            {}, {'1': 1}, {'1': 1, '2': 2}, {
96                'sup': 'pumps', 'scewps': None}, {'aa': 'aa'},
97        ]
98
99        # Items not initially in any of the multidict inputs self.inits.
100        self.nonitems = [
101            (44, 44), (None, 44), (55, None), ('a', 'b'), (11, 11), (22, 22)]
102
103        # Keys not initially in any of the multidict inputs self.inits or in
104        # self.nonitems.
105        self.nonkeys = [_unique, 'asdfasdosduf', 'oaisfiapsn', 'ioausopdaui']
106
107        self.valuelist = [1, 2, 3, None, 'a', 'b', object()]
108
109    def test_init(self):
110        for init in self.inits:
111            omd = omdict(init)
112            assert omd.allitems() == list(init.items())
113
114            omd1 = omdict(init)
115            omd2 = omdict(omd1)
116            assert omd1.allitems() == list(omd2.allitems())
117
118        # Support both *args and **kwargs dictionary initialization.
119        items = [('sape', 4139), ('guido', 4127), ('jack', 4098)]
120        assert omdict(items).allitems() == items
121        omd_item_set = set(omdict(sape=4139, guido=4127, jack=4098).items())
122        assert omd_item_set == set(items)  # Keyword order isn't preserved.
123
124    def test_load(self):
125        omd = omdict()
126        for init in self.inits:
127            assert omd.load(init) == omd
128            assert omd.allitems() == list(init.items())
129
130    def test_copy(self):
131        for init in self.inits:
132            omd = omdict(init)
133            copy = omd.copy()
134            assert omd is not copy and omd == copy
135
136    def test_clear(self):
137        for init in self.inits:
138            omd = omdict(init)
139            omd.clear()
140            assert omd.items() == []
141
142    def test_fromkeys(self):
143        for init in self.inits:
144            keys = [key for key, value in init.items()]
145            allitems = omdict.fromkeys(keys, _unique).allitems()
146            assert allitems == list(zip(keys, repeat(_unique)))
147
148    def test_has_key(self):
149        for init in self.inits:
150            omd = omdict(init)
151            for key, value in init.items():
152                assert key in omd
153
154    def test_update(self):
155        # Some manual tests.
156        omd = omdict()
157        omd.update([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)])
158        assert omd.allitems() == [(1, 111), (2, 22), (3, 3)]
159
160        omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)])
161        omd.update({1: None, 2: None, 3: None})
162        assert omd.allitems() == [(1, None), (2, None), (3, None)]
163
164        for init in self.inits:
165            zipped = zip(self.updates, self.keyword_updates)
166            for update, keyword_update in zipped:
167                omd1, omd2, omd3 = omdict(init), omdict(init), omdict(init)
168                oldomd = omd1.copy()
169                # Reduce the update to just the final items that will be
170                # present post update(), where repeated keys will be reduced to
171                # their last occurring value. For example, [(7,7),(7,8)] would
172                # be reduced to [(7,8)].
173                reduced = [
174                    i for i in update.items() if i in odict(update).items()]
175
176                # Update with a dictionary.
177                omd1.update(update)
178                # Update with keyword expansion.
179                omd2.update(**keyword_update)
180                # Update with both a dictionary and keyword expansion.
181                omd3.update(update, **keyword_update)
182
183                # Verification.
184                if update or keyword_update:
185                    for key, value in reduced:
186                        assert key in omd1 and key in omd3
187                    for key, value in keyword_update.items():
188                        assert key in omd2 and key in omd3
189                else:
190                    assert omd1 == omd2 == omd3 == oldomd
191
192    def test_updateall(self):
193        # Some manual tests.
194        omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)])
195        omd.updateall({1: None, 2: None, 3: None})
196        assert omd.allitems() == [(1, None), (2, None), (3, None)]
197
198        omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)])
199        omd.updateall([(1, None), (2, None), (3, None), (1, None), (2, None)])
200        assert omd.allitems() == [
201            (1, None), (1, None), (2, None), (3, None), (2, None)]
202
203        omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)])
204        omd.updateall([(1, None), (1, None), (1, None), (2, None)])
205        assert omd.allitems() == [
206            (1, None), (1, None), (2, None), (3, 3), (1, None)]
207
208        for init in self.inits:
209            zipped = zip(self.updates, self.keyword_updates)
210            for update, keyword_update in zipped:
211                omd1, omd2, omd3 = omdict(init), omdict(init), omdict(init)
212                oldomd = omd1.copy()
213
214                # Update with a dictionary.
215                omd1.updateall(update)
216                # Update with keyword expansion.
217                omd2.updateall(**keyword_update)
218                # Update with both a dictionary and keyword expansion.
219                omd3.updateall(update, **keyword_update)
220
221                # Verification.
222                if update or keyword_update:
223                    for key, value in six.iteritems(update):
224                        assert key in omd1 and key in omd3
225                        assert value in omd1.getlist(
226                            key) and value in omd3.getlist(key)
227                    for key, value in keyword_update.items():
228                        assert key in omd2 and key in omd3
229                        assert omd2.getlist(
230                            key) == omd3.getlist(key) == [value]
231                else:
232                    assert omd1 == omd2 == oldomd
233
234    def test_get(self):
235        for init in self.inits:
236            omd = omdict(init)
237            for key in omd.iterkeys():
238                assert omd.get(key) == omd[key]
239            for nonkey in self.nonkeys:
240                assert omd.get(nonkey) is None
241                assert omd.get(nonkey, _unique) == _unique
242
243    def test_getlist(self):
244        for init in self.inits:
245            omd = omdict(init)
246            for key in omd:
247                assert omd.getlist(
248                    key) == [v for k, v in omd.allitems() if k == key]
249            for nonkey in self.nonkeys:
250                assert omd.getlist(nonkey) == []
251                assert omd.getlist(nonkey, _unique) == _unique
252
253    def test_setdefault(self):
254        for init in self.inits:
255            omd = omdict(init)
256            for key in omd.iterkeys():
257                assert omd.setdefault(key, _unique) == omd[key]
258            for nonkey in self.nonkeys:
259                assert omd.setdefault(nonkey) is None
260                assert omd[nonkey] is None
261            omd.load(init)
262            for nonkey in self.nonkeys:
263                assert omd.setdefault(nonkey, 123456) == 123456
264                assert omd[nonkey] == 123456
265
266    def test_setdefaultlist(self):
267        for init in self.inits:
268            omd = omdict(init)
269            for key in omd.iterkeys():
270                assert omd.setdefaultlist(key, _unique) == omd.getlist(key)
271            for nonkey in self.nonkeys:
272                assert omd.setdefaultlist(nonkey) == [None]
273                assert omd.getlist(nonkey) == [None]
274            omd.load(init)
275            for nonkey in self.nonkeys:
276                assert omd.setdefaultlist(nonkey, [1, 2, 3]) == [1, 2, 3]
277                assert omd.getlist(nonkey) == [1, 2, 3]
278
279        # setdefaultlist() with an empty list of values does nothing.
280        for init in self.inits:
281            omd = omdict(init)
282            for key in omd.iterkeys():
283                values = omd.getlist(key)
284                assert key in omd
285                assert omd.setdefaultlist(key, []) == values
286                assert key in omd and omd.getlist(key) == values
287            for nonkey in self.nonkeys:
288                assert nonkey not in omd
289                assert omd.setdefaultlist(nonkey, []) == []
290                assert nonkey not in omd
291
292    def test_add(self):
293        for init in self.inits:
294            omd = omdict(init)
295            for key, value in self.nonitems:
296                assert (key, value) not in omd.allitems()
297                assert omd.add(key, value) == omd
298                assert omd.getlist(key)[-1] == value
299                assert omd.allitems()[-1] == (key, value)
300
301            # Repeat the add() calls with the same items and make sure the old
302            # items aren't replaced.
303            oldomd = omd.copy()
304            for key, value in self.nonitems:
305                assert (key, value) in omd.allitems()
306                assert omd.add(key, value) == omd
307                assert len(omd.getlist(key)) == len(oldomd.getlist(key)) + 1
308                assert omd.getlist(key)[-1] == value
309                assert omd.allitems()[-1] == (key, value)
310
311            # Assert that containers are valid values, too, not just immutables
312            # like integers.
313            assert omd.add(_unique, self.updates) == omd
314            assert omd.getlist(_unique)[-1] == self.updates
315            assert omd.allitems()[-1] == (_unique, self.updates)
316
317            # Add() doesn't require a value, and when one isn't provided it
318            # defaults to None.
319            omd = omdict(init)
320            assert omd.add(_unique) == omd
321            assert _unique in omd and omd[_unique] is None
322
323    def test_addlist(self):
324        for init in self.inits:
325            omd = omdict(init)
326            for nonkey in self.nonkeys:
327                assert (nonkey, self.valuelist) not in omd.allitems()
328                assert omd.addlist(nonkey, self.valuelist) == omd
329                assert omd.getlist(nonkey) == self.valuelist
330                assert (omd.allitems()[-1 * len(self.valuelist):] ==
331                        list(zip(repeat(nonkey), self.valuelist)))
332
333            # Repeat the addlist() calls with the same items and make sure the
334            # old items aren't replaced.
335            oldomd = omd.copy()
336            for nonkey in self.nonkeys:
337                for value in self.valuelist:
338                    assert (nonkey, value) in omd.allitems()
339                assert omd.addlist(nonkey, self.valuelist) == omd
340                assert len(omd.getlist(nonkey)) == (
341                    len(oldomd.getlist(nonkey)) + len(self.valuelist))
342                assert omd.getlist(nonkey) == oldomd.getlist(
343                    nonkey) + self.valuelist
344                assert (omd.allitems()[-1 * len(self.valuelist):] ==
345                        list(zip(repeat(nonkey), self.valuelist)))
346
347            # If an empty list is provided to addlist(), nothing is added.
348            omd = omdict(init)
349            for nonkey in self.nonkeys:
350                assert omd.addlist(nonkey) == omd and nonkey not in omd
351                assert omd.addlist(nonkey, []) == omd and nonkey not in omd
352
353    def test_setlist(self):
354        for init in self.inits:
355            omd = omdict(init)
356            for key in (omd.keys() + self.nonkeys):
357                if key in omd:
358                    assert omd.getlist(key) != self.valuelist
359                assert omd.setlist(key, self.valuelist)
360                assert key in omd and omd.getlist(key) == self.valuelist
361
362        # Setting a key to an empty list is identical to deleting the key.
363        for init in self.inits:
364            omd = omdict(init)
365            for nonkey in self.nonkeys:
366                assert nonkey not in omd
367                omd.setlist(nonkey, [])
368                assert nonkey not in omd
369            for key in omd.keys():
370                assert key in omd
371                omd.setlist(key, [])
372                assert key not in omd
373            assert not omd
374
375    def test_removevalues(self):
376        for init in self.inits:
377            omd = omdict(init)
378            removevals = omd.removevalues  # Shorten to linewrap for PEP 8.
379            for nonkey in self.nonkeys:
380                obj = object()
381                values = [1, 1.1, '1.1', (), [], {}, obj, 5.5, '1.1']
382
383                assert removevals(nonkey, []).getlist(nonkey) == []
384                assert removevals(nonkey, values).getlist(nonkey) == []
385
386                omd.addlist(nonkey, values).removevalues(nonkey, [])
387                assert omd.getlist(nonkey) == values
388                assert removevals(nonkey, values).getlist(nonkey) == []
389
390                omd.addlist(nonkey, values)
391                assert (removevals(nonkey, [1]).getlist(nonkey) ==
392                        [1.1, '1.1', (), [], {}, obj, 5.5, '1.1'])
393                assert (removevals(nonkey, ['1.1', obj]).getlist(nonkey) ==
394                        [1.1, (), [], {}, 5.5])
395                assert (removevals(nonkey, [[], 5.5, ()]).getlist(nonkey) ==
396                        [1.1, {}])
397                assert removevals(nonkey, [{}]).getlist(nonkey) == [1.1]
398                assert removevals(nonkey, [1.1]).getlist(nonkey) == []
399                assert removevals(
400                    nonkey, [9, 9.9, 'nope']).getlist(nonkey) == []
401
402    def test_pop(self):
403        self._test_pop_poplist(lambda omd, key: omd.get(key) == omd.pop(key))
404
405    def test_poplist(self):
406        self._test_pop_poplist(
407            lambda omd, key: omd.getlist(key) == omd.poplist(key))
408
409    def _test_pop_poplist(self, assert_lambda):
410        for init in self.inits:
411            omd = omdict(init)
412            items = omd.items()
413            for key in list(omd.keys()):
414                assert assert_lambda(omd, key)
415                newitems = [item for item in items if item[0] != key]
416                assert omd.items() == newitems
417                items = newitems
418
419            omd.load(init)
420            for nonkey in self.nonkeys:
421                self.assertRaises(KeyError, omd.pop, nonkey)
422                assert omd.pop(nonkey, _unique) == _unique
423                self.assertRaises(KeyError, omd.poplist, nonkey)
424                assert omd.poplist(nonkey, _unique) == _unique
425
426    def test_popvalue(self):
427        # popvalue() with no value provided.
428        for init in self.inits:
429            for last in [True, False]:
430                omd = omdict(init)
431                allitems = omd.allitems()
432                while omd.keys():
433                    for key in omd.keys():
434                        if last:
435                            value = omd.getlist(key)[-1]
436                            _rremove(allitems, (key, value))
437                        else:
438                            value = omd[key]
439                            allitems.remove((key, value))
440
441                        assert value == omd.popvalue(key, last=last)
442                        assert omd.allitems() == allitems
443
444            omd.load(init)
445            for nonkey in self.nonkeys:
446                self.assertRaises(KeyError, omd.popvalue, nonkey)
447                assert omd.popvalue(nonkey, default=_unique) == _unique
448
449        # popvalue() with value provided.
450        #   last = True (default).
451        omd = omdict([(1, 1), (2, 2), (3, 3), (2, 2), (3, 3), (2, 2)])
452        assert omd.popvalue(2, 2) == 2
453        assert omd.allitems() == [(1, 1), (2, 2), (3, 3), (2, 2), (3, 3)]
454        assert omd.popvalue(2, 2) == 2
455        assert omd.allitems() == [(1, 1), (2, 2), (3, 3), (3, 3)]
456        assert omd.popvalue(2, 2) == 2
457        assert omd.allitems() == [(1, 1), (3, 3), (3, 3)]
458        #   last = False.
459        omd = omdict([(3, 3), (2, 2), (3, 3), (2, 2), (3, 3), (2, 2)])
460        assert omd.popvalue(2, 2, last=True) == 2
461        assert omd.allitems() == [(3, 3), (2, 2), (3, 3), (2, 2), (3, 3)]
462        assert omd.popvalue(2, 2, last=True) == 2
463        assert omd.allitems() == [(3, 3), (2, 2), (3, 3), (3, 3)]
464        assert omd.popvalue(2, 2, last=True) == 2
465        assert omd.allitems() == [(3, 3), (3, 3), (3, 3)]
466
467        # Invalid key.
468        self.assertRaises(KeyError, omd.popvalue, _unique, _unique)
469        self.assertRaises(KeyError, omd.popvalue, _unique, 2)
470        self.assertRaises(KeyError, omd.popvalue, _unique, 22)
471        self.assertRaises(KeyError, omd.popvalue, _unique, _unique, last=False)
472        self.assertRaises(KeyError, omd.popvalue, _unique, 2)
473        self.assertRaises(KeyError, omd.popvalue, _unique, 22)
474        assert omd.popvalue(_unique, _unique, 'sup') == 'sup'
475        assert omd.popvalue(_unique, 2, 'sup') == 'sup'
476        assert omd.popvalue(_unique, 22, 'sup') == 'sup'
477
478        # Valid key, invalid value.
479        self.assertRaises(ValueError, omd.popvalue, 3, _unique)
480        self.assertRaises(ValueError, omd.popvalue, 3, _unique, False)
481
482    def test_popitem(self):
483        for init in self.inits:
484            # All permutations of booleans <fromall> and <last>.
485            for fromall, last in product([True, False], repeat=2):
486                omd = omdict(init)
487                allitems = omd.allitems()
488                while omd.allitems():
489                    if fromall:
490                        key, value = omd.allitems()[-1 if last else 0]
491                    else:
492                        key = omd.keys()[-1 if last else 0]
493                        value = omd[key]
494
495                    popkey, popvalue = omd.popitem(fromall=fromall, last=last)
496                    assert popkey == key and popvalue == value
497
498                    if fromall:
499                        if last:
500                            _rremove(allitems, (key, value))
501                        else:
502                            allitems.remove((key, value))
503                    else:
504                        allitems = [(k, v) for k, v in allitems if k != key]
505                    assert omd.allitems() == allitems
506
507            omd = omdict()
508            self.assertRaises(KeyError, omd.popitem)
509
510    def test_poplistitem(self):
511        for init in self.inits:
512            for last in [True, False]:
513                omd, omdcopy = omdict(init), omdict(init)
514                while omd.keys():
515                    key, valuelist = omd.poplistitem(last=last)
516                    assert key == omdcopy.keys()[-1 if last else 0]
517                    assert valuelist == omdcopy.getlist(key)
518                    omdcopy.pop(omdcopy.keys()[-1 if last else 0])
519
520                # poplistitem() on an empty omdict.
521                self.assertRaises(KeyError, omd.poplistitem)
522
523    # Tests every non-'all' items, keys, values, lists method: items(), keys(),
524    # values(), lists(), listitems() and their iterators iteritems(),
525    # iterkeys(), itervalues(), iterlists(), and iterlistitems().
526    def test_nonall_item_key_value_lists(self):
527        for init in self.inits:
528            dic = odict(init.items())
529            omd = omdict(init.items())
530
531            # Testing items(), keys(), values(), lists(), and listitems().
532            assert omd.items() == list(dic.items())
533            assert omd.keys() == list(dic.keys())
534            assert omd.values() == list(dic.values())
535            iterator = zip(omd.keys(), omd.lists(), omd.listitems())
536            for key, valuelist, listitem in iterator:
537                assert omd.values(key) == omd.getlist(key) == valuelist
538                assert omd.items(
539                    key) == [i for i in init.items() if i[0] == key]
540                assert listitem == (key, valuelist)
541
542            # Testing iteritems(), iterkeys(), itervalues(), and iterlists().
543            assert is_iterator(omd.iterkeys())
544            for key1, key2 in zip(omd.iterkeys(), six.iterkeys(dic)):
545                assert key1 == key2
546            assert is_iterator(omd.itervalues())
547            for val1, val2 in zip(omd.itervalues(), six.itervalues(dic)):
548                assert val1 == val2
549            assert is_iterator(omd.iteritems())
550            for item1, item2 in zip(omd.iteritems(), six.iteritems(dic)):
551                assert item1 == item2
552            assert is_iterator(omd.iterlists())
553            for key, values in zip(six.iterkeys(omd), omd.iterlists()):
554                assert omd.getlist(key) == values
555            assert is_iterator(omd.iterlistitems())
556            iterator = zip(
557                omd.iterkeys(), omd.iterlists(), omd.iterlistitems())
558            for key, valuelist, listitem in iterator:
559                assert listitem == (key, valuelist)
560
561            # Test iteritems() and itervalues() with a key.
562            for key in omd.iterkeys():
563                assert is_iterator(omd.iteritems(key))
564                assert list(omd.iteritems(key)) == list(zip(
565                    repeat(key), omd.getlist(key)))
566                assert is_iterator(omd.itervalues(key))
567                assert list(omd.itervalues(key)) == omd.getlist(key)
568            for nonkey in self.nonkeys:
569                self.assertRaises(KeyError, omd.iteritems, nonkey)
570                self.assertRaises(KeyError, omd.itervalues, nonkey)
571
572    # Tests every 'all' items, keys, values method: allitems(), allkeys(),
573    # allvalues() and their iterators iterallitems(), iterallkeys(),
574    # iterallvalues().
575    def test_all_items_keys_values_iterall_items_keys_values(self):
576        for init in self.inits:
577            omd = omdict(init)
578            # map(list, zip(*lst)) - doesn't work if lst is empty, lst == [].
579            keys = [key for key, value in init.items()]
580            values = [value for key, value in init.items()]
581
582            # Test allitems(), allkeys(), allvalues().
583            assert omd.allitems() == list(init.items())
584            assert omd.allkeys() == keys
585            assert omd.allvalues() == values
586
587            # Test iterallitems(), iterallkeys(), iterallvalues().
588            for key1, key2 in zip(omd.iterallkeys(), keys):
589                assert key1 == key2
590            for val1, val2 in zip(omd.iterallvalues(), values):
591                assert val1 == val2
592            for item1, item2 in zip(omd.iterallitems(), init.items()):
593                assert item1 == item2
594
595            # Test allitems(), allvalues(), iterallitems() and iterallvalues()
596            # with a key.
597            for key in omd.iterkeys():
598                assert (omd.allvalues(key) == list(omd.iterallvalues(key)) ==
599                        omd.getlist(key))
600                assert (omd.allitems(key) == list(omd.iterallitems(key)) ==
601                        list(zip(repeat(key), omd.getlist(key))))
602            for nonkey in self.nonkeys:
603                self.assertRaises(KeyError, omd.allvalues, nonkey)
604                self.assertRaises(KeyError, omd.allitems, nonkey)
605                self.assertRaises(KeyError, omd.iterallvalues, nonkey)
606                self.assertRaises(KeyError, omd.iterallitems, nonkey)
607
608    def test_reverse(self):
609        for init in self.inits:
610            reversed = list(init.items())[::-1]
611            assert omdict(init).reverse().allitems() == reversed
612
613    def test_eq(self):
614        for init in self.inits:
615            d, omd = dict(init), omdict(init)
616            assert d == omd
617            assert omd == omd
618            assert omd == omd.copy()
619
620    def test_ne(self):
621        diff = omdict([(_unique, _unique)])
622        for init in self.inits:
623            assert omdict(init) != diff
624            # Compare to basic types.
625            for basic in [1, 1.1, '1.1', (), [], object()]:
626                assert omdict(init) != basic
627
628    def test_len(self):
629        for init in self.inits:
630            assert len(omdict(init)) == len(dict(init))
631
632    def test_size(self):
633        for init in self.inits:
634            assert omdict(init).size() == len(init)
635
636    def test_iter(self):
637        for init in self.inits:
638            omd = omdict(init)
639            for key1, key2 in zip_longest(iter(omd), omd.iterkeys()):
640                assert key1 == key2
641
642    def test_contains(self):
643        for init in self.inits:
644            omd = omdict(init)
645            for key, value in init.items():
646                assert key in omd
647
648    def test_getitem(self):
649        for init in self.inits:
650            dic = dict(init)
651            omd = omdict(init)
652            for key in omd.iterkeys():
653                assert omd[key] == dic[key]
654
655        omd = omdict()
656        self.assertRaises(KeyError, omd.__getitem__, _unique)
657
658    def test_set_setitem(self):
659        for init in self.inits:
660            omd = omdict()
661            omd2 = omdict()
662            for key, value in init.items():
663                omd[key] = value
664                assert omd2.set(key, value) == omd2
665                assert omd == omd2 and omd[key] == value
666
667            # Store containers as values, not just immutables like integers.
668            omd[_unique] = self.valuelist
669            assert omd2.set(_unique, self.valuelist) == omd2
670            assert omd == omd2 and omd[_unique] == self.valuelist
671
672    def test_delitem(self):
673        for init in self.inits:
674            omd = omdict(init)
675            for key in list(omd.keys()):
676                assert key in omd
677                del omd[key]
678                assert key not in omd
679
680    def test_nonzero(self):
681        for init in self.inits:
682            if init:
683                assert omdict(init)
684            else:
685                assert not omdict(init)
686
687    def test_str(self):
688        for init in self.inits:
689            omd = omdict(init)
690            s = '{%s}' % ', '.join(
691                map(lambda p: '%s: %s' % (p[0], p[1]), omd.allitems()))
692            assert s == str(omd)
693
694    def test_odict_omdict_parity(self):
695        for init in self.inits:
696            d = odict(init)
697            omd = omdict(init)
698
699            self._compare_odict_and_omddict(d, omd)
700            self._compare_odict_and_omddict(d.copy(), omd.copy())  # copy().
701            d.clear(), omd.clear()  # clear().
702            self._compare_odict_and_omddict(d, omd)
703
704            assert dict().update(init) == omdict().update(init)  # update().
705            dict_fromkeys = list(d.fromkeys(init).items())
706            omdict_fromkeys = list(omd.fromkeys(init).items())
707            assert dict_fromkeys == omdict_fromkeys  # fromkeys()
708
709    def _compare_odict_and_omddict(self, d, omd):
710        assert len(d) == len(omd)  # __len__().
711
712        # __contains__(), has_key(), get(), and setdefault().
713        for dkey, omdkey in zip(d, omd):
714            assert dkey == omdkey and dkey in d and omdkey in omd
715            assert dkey in d and omdkey in omd
716            assert d.get(dkey) == omd.get(omdkey)
717            d.setdefault(dkey, _unique)
718            omd.setdefault(omdkey, _unique)
719            assert d.get(dkey) == omd.get(omdkey) and d.get(dkey) != _unique
720        for nonkey in self.nonkeys:
721            assert d.get(nonkey) == omd.get(nonkey) is None
722            d.setdefault(nonkey, _unique)
723            omd.setdefault(nonkey, _unique)
724            assert d.get(nonkey) == omd.get(nonkey) == _unique
725
726        # items(), keys, values(), iteritems(), iterkeys, and itervalues().
727        iterators = [
728            zip(d.items(), omd.items(), d.keys(), omd.keys(),
729                d.values(), omd.values()),
730            zip(six.iteritems(d), six.iteritems(omd), six.iterkeys(d),
731                six.iterkeys(omd), six.itervalues(d), six.itervalues(omd))]
732        for iterator in iterators:
733            for ditem, omditem, dkey, omdkey, dvalue, omdvalue in iterator:
734                assert dkey == omdkey
735                assert ditem == omditem
736                assert dvalue == omdvalue
737
738        # pop().
739        dcopy, omdcopy = d.copy(), omd.copy()
740        while dcopy and omdcopy:
741            dpop = dcopy.pop(list(dcopy.keys())[0])
742            omdpop = omdcopy.pop(list(omdcopy.keys())[0])
743            assert dpop == omdpop
744        # popitem().
745        dcopy, omdcopy = d.copy(), omd.copy()
746        while dcopy and omdcopy:
747            assert dcopy.popitem() == omdcopy.popitem()
748
749        # __getitem__().
750        for dkey, omdkey in zip(six.iterkeys(d), six.iterkeys(omd)):
751            assert d[dkey] == omd[omdkey]
752        # __setitem__().
753        for dkey, omdkey in zip(d, omd):
754            d[dkey] = _unique
755            omd[omdkey] = _unique
756            assert dkey == omdkey and d[dkey] == omd[omdkey]
757        # __delitem__().
758        while d and omd:
759            dkey, omdkey = list(d.keys())[0], list(omd.keys())[0]
760            del d[dkey]
761            del omd[omdkey]
762            assert dkey == omdkey and dkey not in d and omdkey not in omd
763
764    def test_fundamentals(self):
765        # Gets, sets, and pops.
766        omd = omdict()
767        omd[1] = 1
768        omd[2] = 2
769        assert omd.allitems() == [(1, 1), (2, 2)]
770        omd[1] = 11
771        assert omd.allitems() == [(1, 11), (2, 2)]
772        omd.add(1, 1.1)
773        assert omd.allitems() == [(1, 11), (2, 2), (1, 1.1)]
774        assert omd.popvalue(1) == 1.1
775        assert omd.allitems() == [(1, 11), (2, 2)]
776        omd.popvalue(2)
777        assert omd.allitems() == [(1, 11)]
778        omd[2] = [2, 2]
779        assert omd.allitems() == [(1, 11), (2, [2, 2])]
780        omd[1] = None
781        assert omd.allitems() == [(1, None), (2, [2, 2])]
782        omd.add(2, None)
783        assert omd.allitems() == [(1, None), (2, [2, 2]), (2, None)]
784        del omd[2]
785        assert omd.allitems() == [(1, None)]
786        omd[3] = 3
787        assert omd.allitems() == [(1, None), (3, 3)]
788        omd.setlist(1, [1, 11, 111])
789        assert omd.allitems() == [(1, 1), (3, 3), (1, 11), (1, 111)]
790        omd.addlist(1, [1111])
791        omd = omdict([(1, 1), (3, 3), (1, 11), (1, 111), (1, 1111)])
792        assert omd.allitems() == [(1, 1), (3, 3), (1, 11), (1, 111), (1, 1111)]
793        omd[1] = None
794        assert omd.allitems() == [(1, None), (3, 3)]
795
796    def test_pops(self):
797        init = [(1, 1), (2, 2), (1, 1), (1, 2), (1, 3)]
798
799        # pop().
800        omd = omdict(init)
801        assert omd.pop(1) == 1
802        assert omd.allitems() == [(2, 2)]
803        assert omd.pop(_unique, 'sup') == 'sup'
804
805        # poplist().
806        omd = omdict(init)
807        assert omd.poplist(1) == [1, 1, 2, 3]
808        assert omd.allitems() == [(2, 2)]
809        self.assertRaises(KeyError, omd.poplist, _unique)
810        assert omd.poplist(_unique, 'sup') == 'sup'
811
812        # popvalue().
813        omd = omdict(init)
814        assert omd.popvalue(1) == 3
815        assert omd.allitems() == [(1, 1), (2, 2), (1, 1), (1, 2)]
816        self.assertRaises(KeyError, omd.popvalue, _unique)
817        assert omd.popvalue(_unique, default='sup') == 'sup'
818        assert omd.popvalue(1, last=False) == 1
819        assert omd.allitems() == [(2, 2), (1, 1), (1, 2)]
820
821        # popitem().
822        omd = omdict(init)
823        assert omd.popitem() == (2, 2)
824        assert omd.allitems() == [(1, 1), (1, 1), (1, 2), (1, 3)]
825        assert omd.popitem() == (1, 1)
826        assert omd.allitems() == []
827        omd = omdict(init)
828        assert omd.popitem(fromall=True) == (1, 3)
829        assert omd.allitems() == [(1, 1), (2, 2), (1, 1), (1, 2)]
830        assert omd.popitem(fromall=True, last=False) == (1, 1)
831        assert omd.allitems() == [(2, 2), (1, 1), (1, 2)]
832
833    def test_splats(self):
834        items = [('1', 1), ('2', 2), ('3', 3)]
835        omd = omdict(items)
836
837        def splat(*args, **kwargs):
838            return args, set(kwargs.items())
839
840        assert splat(*omd, **omd) == (tuple(i[0] for i in items), set(items))
841
842
843class TestBinaryOperators(unittest.TestCase):
844
845    @property
846    def _items(self):
847        original = (1, ['a']), (2, 'b')
848        one_different = (1, ['a']), (3, 'd')
849        all_different = (1, 'c'), (3, 'd')
850        duplicate_key = (1, ['a']), (1, 'e')
851        empty = tuple()
852        return original, one_different, all_different, duplicate_key, empty
853
854    @property
855    def _or_params(self):
856        original, one_diff, all_diff, duplicate_key, empty = self._items
857        return [
858            # self, other, other as dict, other as omdict.
859            (original, original, original + original, original + original),
860            (original, one_diff, original + one_diff, original + one_diff),
861            (original, all_diff, original + all_diff, original + all_diff),
862            (original, duplicate_key, original + ((1, 'e'),), original + duplicate_key),
863            (original, empty, original, original),
864        ]
865
866    def test_or(self):
867        for s, t, d, o in self._or_params:
868            assert omdict(s) | dict(t) == omdict(d)
869            assert omdict(s) | omdict(t) == omdict(o)
870
871    def test_ior(self):
872        for s, t, d, o in self._or_params:
873            # Test with dict.
874            a = omdict(s)
875            a |= dict(t)
876            assert a == omdict(d)
877            # Test with omdict.
878            a = omdict(s)
879            a |= omdict(t)
880            assert a == omdict(o)
881
882
883class TestUtilities(unittest.TestCase):
884
885    def test_rfind(self):
886        tests = [([], 1, -1), ([1], 1, 0), ([1, 2], 2, 1),
887                 ([1, 2, 1, 2], 1, 2), ([1, 2, 3], 4, -1), ([1, 2, 3], 1, 0)]
888        for lst, item, pos in tests:
889            assert _rfind(lst, item) == pos
890
891    def test_rremove(self):
892        tests = [([1, 1], 1, [1]), ([1], 1, []), ([1, 2], 2, [1]),
893                 ([1, 2, 3], 1, [2, 3]), ([1, 2, 1, 2], 1, [1, 2, 2]),
894                 ([1, 2, 1], 1, [1, 2])]
895        for lst, item, result in tests:
896            _rremove(lst, item)
897            assert lst == result
898
899        nonitems = [None, 'asdf', object(), 1000000]
900        for nonitem in nonitems:
901            self.assertRaises(ValueError, _rremove, lst, nonitem)
902
903
904def _rfind(lst, item):
905    """
906    Returns the index of the last occurance of <item> in <lst>. Returns -1 if
907    <item> is not in <l>.
908      ex: _rfind([1,2,1,2], 1) == 2
909    """
910    try:
911        return (len(lst) - 1) - lst[::-1].index(item)
912    except ValueError:
913        return -1
914
915
916def _rremove(lst, item):
917    """
918    Removes the last occurance of <item> in <lst>, or raises a ValueError if
919    <item> is not in <list>.
920      ex: _rremove([1,2,1,2], 1) == [1,2,2]
921    """
922    pos = _rfind(lst, item)
923    if pos >= 0:
924        lst.pop(pos)
925        return lst
926    raise ValueError('_rremove(list, x): x not in list')
927