diff options
author | Olivier Tilloy <olivier@tilloy.net> | 2009-05-07 09:29:35 +0200 |
---|---|---|
committer | Olivier Tilloy <olivier@tilloy.net> | 2009-05-07 09:29:35 +0200 |
commit | 553c77075a35347ea96c646ef19a9188ae096018 (patch) | |
tree | bda8a3a757beb4647834d9dbfac27bbc3c8cc747 | |
parent | 8189c1ee21be899f090eef7211c12e10a11a9f53 (diff) | |
download | pyexiv2-553c77075a35347ea96c646ef19a9188ae096018.tar.gz |
Reorganized the notifying list unit tests in separate methods.
Support old-style slicing with __setslice__ and __delslice__.
-rw-r--r-- | src/pyexiv2.py | 55 | ||||
-rw-r--r-- | unittest/notifying_list.py | 327 |
2 files changed, 302 insertions, 80 deletions
diff --git a/src/pyexiv2.py b/src/pyexiv2.py index ca0506f..1a8a2c4 100644 --- a/src/pyexiv2.py +++ b/src/pyexiv2.py @@ -281,8 +281,6 @@ class NotifyingList(list): # file:///usr/share/doc/python2.5/html/lib/typesseq-mutable.html # http://docs.python.org/reference/datamodel.html#additional-methods-for-emulation-of-sequence-types - # FIXME: support negatives indexes where relevant - def __init__(self, items=[]): super(NotifyingList, self).__init__(items) self._listeners = set() @@ -297,13 +295,23 @@ class NotifyingList(list): for listener in self._listeners: getattr(listener, method_name)(*args) + def _positive_index(self, index): + # Convert a negative index to its positive representation. + length = len(self) + if index < 0: + return length + index + elif index > length: + return length + else: + return index + def __setitem__(self, index, item): - # FIXME: support slice arguments + # FIXME: support slice arguments for extended slicing super(NotifyingList, self).__setitem__(index, item) self._notify_listeners('items_changed', index, [item]) def __delitem__(self, index): - # FIXME: support slice arguments + # FIXME: support slice arguments for extended slicing super(NotifyingList, self).__delitem__(index) self._notify_listeners('items_deleted', index, index + 1) @@ -318,14 +326,18 @@ class NotifyingList(list): self._notify_listeners('items_inserted', index, items) def insert(self, index, item): + start = self._positive_index(index) super(NotifyingList, self).insert(index, item) - self._notify_listeners('items_inserted', index, [item]) + self._notify_listeners('items_inserted', start, [item]) def pop(self, index=None): if index is None: - index = len(self) - 1 - item = super(NotifyingList, self).pop(index) - self._notify_listeners('items_deleted', index, index + 1) + start = len(self) - 1 + item = super(NotifyingList, self).pop() + else: + start = self._positive_index(index) + item = super(NotifyingList, self).pop(index) + self._notify_listeners('items_deleted', start, start + 1) return item def remove(self, item): @@ -353,11 +365,32 @@ class NotifyingList(list): self._notify_listeners('items_inserted', index, self[index:]) return self - def __setslice__(self, i, j, sequence): - raise NotImplementedError() + def __setslice__(self, i, j, items): + # __setslice__ is deprecated but needs to be overridden for completeness + start, end = self._positive_index(i), self._positive_index(j) + deleted = self[start:end] + super(NotifyingList, self).__setslice__(i, j, items) + old_size = end - start + new_size = len(items) + diff = new_size - old_size + if diff == 0: + self._notify_listeners('items_changed', start, items) + elif diff < 0: + if new_size > 0: + self._notify_listeners('items_changed', start, items) + self._notify_listeners('items_deleted', start + new_size, end) + elif diff > 0: + if old_size > 0: + self._notify_listeners('items_deleted', start, end) + self._notify_listeners('items_inserted', start, items) def __delslice__(self, i, j): - raise NotImplementedError() + # __delslice__ is deprecated but needs to be overridden for completeness + start, end = self._positive_index(i), self._positive_index(j) + deleted = self[start:end] + super(NotifyingList, self).__delslice__(i, j) + if deleted: + self._notify_listeners('items_deleted', start, end) class MetadataTag(object): diff --git a/unittest/notifying_list.py b/unittest/notifying_list.py index 32e5d94..fa7646b 100644 --- a/unittest/notifying_list.py +++ b/unittest/notifying_list.py @@ -31,14 +31,12 @@ import random class SimpleListener(ListenerInterface): - # Total number of notifications - notifications = 0 - # Last notification: (method_name, args) - last = None + def __init__(self): + # Notification stack: list of (method_name, args) + self.stack = [] def _notify(self, method_name, *args): - self.notifications += 1 - self.last = (method_name, args) + self.stack.append((method_name, args)) def items_changed(self, start_index, items): self._notify('items_changed', start_index, items) @@ -75,137 +73,328 @@ class TestNotifyingList(unittest.TestCase): self.failUnlessRaises(NotImplementedError, self.values.remove, 9) # TODO: test all operations (insertion, slicing, ...) - def test_multiple_listeners(self): + def _register_listeners(self): # Register a random number of listeners listeners = [SimpleListener() for i in xrange(random.randint(3, 20))] for listener in listeners: self.values.register_listener(listener) + return listeners + + def test_setitem(self): + listeners = self._register_listeners() self.values[3] = 13 self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 3, 2]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 1) - self.failUnlessEqual(listener.last, ('items_changed', (3, [13]))) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], + ('items_changed', (3, [13]))) self.failUnlessRaises(IndexError, self.values.__setitem__, 9, 27) self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 3, 2]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 1) - self.failUnlessEqual(listener.last, ('items_changed', (3, [13]))) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], + ('items_changed', (3, [13]))) + + def test_delitem(self): + listeners = self._register_listeners() del self.values[5] - self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 2]) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 2]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 2) - self.failUnlessEqual(listener.last, ('items_deleted', (5, 6))) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (5, 6))) self.failUnlessRaises(IndexError, self.values.__delitem__, 9) - self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 2]) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 2]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 2) - self.failUnlessEqual(listener.last, ('items_deleted', (5, 6))) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (5, 6))) + + def test_append(self): + listeners = self._register_listeners() self.values.append(17) - self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 2, 17]) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 17]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 3) - self.failUnlessEqual(listener.last, ('items_inserted', (6, [17]))) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], + ('items_inserted', (7, [17]))) + + def test_extend(self): + listeners = self._register_listeners() self.values.extend([11, 22]) - self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 2, 17, 11, 22]) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 11, 22]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 4) - self.failUnlessEqual(listener.last, + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_inserted', (7, [11, 22]))) self.failUnlessRaises(TypeError, self.values.extend, 26) - self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 2, 17, 11, 22]) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 11, 22]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 4) - self.failUnlessEqual(listener.last, + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_inserted', (7, [11, 22]))) + def test_insert(self): + listeners = self._register_listeners() + self.values.insert(4, 24) - self.failUnlessEqual(self.values, [5, 7, 9, 13, 24, 57, 2, 17, 11, 22]) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 24, 57, 3, 2]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 5) - self.failUnlessEqual(listener.last, ('items_inserted', (4, [24]))) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], + ('items_inserted', (4, [24]))) + + def test_pop(self): + listeners = self._register_listeners() self.values.pop() - self.failUnlessEqual(self.values, [5, 7, 9, 13, 24, 57, 2, 17, 11]) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 6) - self.failUnlessEqual(listener.last, ('items_deleted', (9, 10))) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (6, 7))) self.values.pop(4) - self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 2, 17, 11]) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 3]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 2) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (4, 5))) + + self.values.pop(-2) + self.failUnlessEqual(self.values, [5, 7, 9, 3]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 7) - self.failUnlessEqual(listener.last, ('items_deleted', (4, 5))) + self.failUnlessEqual(len(listener.stack), 3) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (3, 4))) self.failUnlessRaises(IndexError, self.values.pop, 33) - self.failUnlessEqual(self.values, [5, 7, 9, 13, 57, 2, 17, 11]) + self.failUnlessEqual(self.values, [5, 7, 9, 3]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 7) - self.failUnlessEqual(listener.last, ('items_deleted', (4, 5))) + self.failUnlessEqual(len(listener.stack), 3) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (3, 4))) + + def test_remove(self): + listeners = self._register_listeners() self.values.remove(9) - self.failUnlessEqual(self.values, [5, 7, 13, 57, 2, 17, 11]) + self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 8) - self.failUnlessEqual(listener.last, ('items_deleted', (2, 3))) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (2, 3))) self.failUnlessRaises(ValueError, self.values.remove, 33) - self.failUnlessEqual(self.values, [5, 7, 13, 57, 2, 17, 11]) + self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 8) - self.failUnlessEqual(listener.last, ('items_deleted', (2, 3))) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (2, 3))) + + def test_reverse(self): + listeners = self._register_listeners() self.values.reverse() - self.failUnlessEqual(self.values, [11, 17, 2, 57, 13, 7, 5]) + self.failUnlessEqual(self.values, [2, 3, 57, 14, 9, 7, 5]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 9) - self.failUnlessEqual(listener.last, ('reordered', ())) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('reordered', ())) + + def test_sort(self): + listeners = self._register_listeners() self.values.sort() - self.failUnlessEqual(self.values, [2, 5, 7, 11, 13, 17, 57]) + self.failUnlessEqual(self.values, [2, 3, 5, 7, 9, 14, 57]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 10) - self.failUnlessEqual(listener.last, ('reordered', ())) + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('reordered', ())) self.values.sort(cmp=lambda x, y: y - x) - self.failUnlessEqual(self.values, [57, 17, 13, 11, 7, 5, 2]) + self.failUnlessEqual(self.values, [57, 14, 9, 7, 5, 3, 2]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 11) - self.failUnlessEqual(listener.last, ('reordered', ())) + self.failUnlessEqual(len(listener.stack), 2) + self.failUnlessEqual(listener.stack[-1], ('reordered', ())) self.values.sort(key=lambda x: x * x) - self.failUnlessEqual(self.values, [2, 5, 7, 11, 13, 17, 57]) + self.failUnlessEqual(self.values, [2, 3, 5, 7, 9, 14, 57]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 12) - self.failUnlessEqual(listener.last, ('reordered', ())) + self.failUnlessEqual(len(listener.stack), 3) + self.failUnlessEqual(listener.stack[-1], ('reordered', ())) self.values.sort(reverse=True) - self.failUnlessEqual(self.values, [57, 17, 13, 11, 7, 5, 2]) + self.failUnlessEqual(self.values, [57, 14, 9, 7, 5, 3, 2]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 13) - self.failUnlessEqual(listener.last, ('reordered', ())) + self.failUnlessEqual(len(listener.stack), 4) + self.failUnlessEqual(listener.stack[-1], ('reordered', ())) + + def test_iadd(self): + listeners = self._register_listeners() self.values += [44, 31, 19] - self.failUnlessEqual(self.values, [57, 17, 13, 11, 7, 5, 2, 44, 31, 19]) + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2, 44, 31, 19]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 14) - self.failUnlessEqual(listener.last, + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_inserted', (7, [44, 31, 19]))) + def test_imul(self): + listeners = self._register_listeners() + self.values *= 3 self.failUnlessEqual(self.values, - [57, 17, 13, 11, 7, 5, 2, 44, 31, 19, - 57, 17, 13, 11, 7, 5, 2, 44, 31, 19, - 57, 17, 13, 11, 7, 5, 2, 44, 31, 19]) + [5, 7, 9, 14, 57, 3, 2, + 5, 7, 9, 14, 57, 3, 2, + 5, 7, 9, 14, 57, 3, 2]) for listener in listeners: - self.failUnlessEqual(listener.notifications, 15) - self.failUnlessEqual(listener.last, + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_inserted', - (10, [57, 17, 13, 11, 7, 5, 2, 44, 31, 19, - 57, 17, 13, 11, 7, 5, 2, 44, 31, 19]))) + (7, [5, 7, 9, 14, 57, 3, 2, + 5, 7, 9, 14, 57, 3, 2]))) + + def test_setslice(self): + listeners = self._register_listeners() + + # Basic slicing (of the form [i:j]): implemented as __setslice__. + + self.values[2:4] = [3, 4] + self.failUnlessEqual(self.values, [5, 7, 3, 4, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], + ('items_changed', (2, [3, 4]))) + + self.values[3:5] = [77, 8, 12] + self.failUnlessEqual(self.values, [5, 7, 3, 77, 8, 12, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 3) + self.failUnlessEqual(listener.stack[-2], ('items_deleted', (3, 5))) + self.failUnlessEqual(listener.stack[-1], + ('items_inserted', (3, [77, 8, 12]))) + + self.values[2:5] = [1, 0] + self.failUnlessEqual(self.values, [5, 7, 1, 0, 12, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 5) + self.failUnlessEqual(listener.stack[-2], + ('items_changed', (2, [1, 0]))) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (4, 5))) + + self.values[0:2] = [] + self.failUnlessEqual(self.values, [1, 0, 12, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 6) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (0, 2))) + + self.values[2:2] = [7, 5] + self.failUnlessEqual(self.values, [1, 0, 7, 5, 12, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 7) + self.failUnlessEqual(listener.stack[-1], + ('items_inserted', (2, [7, 5]))) + + # With negatives indexes + + self.values[4:-2] = [9] + self.failUnlessEqual(self.values, [1, 0, 7, 5, 9, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 8) + self.failUnlessEqual(listener.stack[-1], + ('items_changed', (4, [9]))) + + self.values[-2:1] = [6, 4] + self.failUnlessEqual(self.values, [1, 0, 7, 5, 9, 6, 4, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 9) + self.failUnlessEqual(listener.stack[-1], + ('items_inserted', (5, [6, 4]))) + + self.values[-5:-2] = [8] + self.failUnlessEqual(self.values, [1, 0, 7, 5, 8, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 11) + self.failUnlessEqual(listener.stack[-2], + ('items_changed', (4, [8]))) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (5, 7))) + + # With missing (implicit) indexes + + self.values[:2] = [4] + self.failUnlessEqual(self.values, [4, 7, 5, 8, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 13) + self.failUnlessEqual(listener.stack[-2], + ('items_changed', (0, [4]))) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (1, 2))) + + self.values[4:] = [1] + self.failUnlessEqual(self.values, [4, 7, 5, 8, 1]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 15) + self.failUnlessEqual(listener.stack[-2], + ('items_changed', (4, [1]))) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (5, 6))) + + self.values[:] = [5, 7, 9, 14, 57, 3, 2] + self.failUnlessEqual(self.values, [5, 7, 9, 14, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 17) + self.failUnlessEqual(listener.stack[-2], ('items_deleted', (0, 5))) + self.failUnlessEqual(listener.stack[-1], + ('items_inserted', + (0, [5, 7, 9, 14, 57, 3, 2]))) + + def test_delslice(self): + listeners = self._register_listeners() + + del self.values[2:3] + self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (2, 3))) + + del self.values[2:2] + self.failUnlessEqual(self.values, [5, 7, 14, 57, 3, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 1) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (2, 3))) + + # With negatives indexes + + del self.values[4:-1] + self.failUnlessEqual(self.values, [5, 7, 14, 57, 2]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 2) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (4, 5))) + + del self.values[-1:5] + self.failUnlessEqual(self.values, [5, 7, 14, 57]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 3) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (4, 5))) + + del self.values[-2:-1] + self.failUnlessEqual(self.values, [5, 7, 57]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 4) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (2, 3))) + + # With missing (implicit) indexes + + del self.values[:1] + self.failUnlessEqual(self.values, [7, 57]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 5) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (0, 1))) + + del self.values[1:] + self.failUnlessEqual(self.values, [7]) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 6) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (1, 2))) + + del self.values[:] + self.failUnlessEqual(self.values, []) + for listener in listeners: + self.failUnlessEqual(len(listener.stack), 7) + self.failUnlessEqual(listener.stack[-1], ('items_deleted', (0, 1))) |