aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOlivier Tilloy <olivier@tilloy.net>2009-05-07 09:29:35 +0200
committerOlivier Tilloy <olivier@tilloy.net>2009-05-07 09:29:35 +0200
commit553c77075a35347ea96c646ef19a9188ae096018 (patch)
treebda8a3a757beb4647834d9dbfac27bbc3c8cc747
parent8189c1ee21be899f090eef7211c12e10a11a9f53 (diff)
downloadpyexiv2-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.py55
-rw-r--r--unittest/notifying_list.py327
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)))