Commits

Ronald Oussoren committed f51beec

Actually implement tests for objc.array_property and
fix issues that cropped up.

Comments (0)

Files changed (2)

pyobjc-core/Lib/objc/_properties.py

 __all__ = ('object_property', 'bool_property',
         'array_property', 'set_property', 'dict_property')
 
-from objc import ivar, selector, _C_ID, _C_NSBOOL, _C_BOOL, NULL
+from objc import ivar, selector, _C_ID, _C_NSBOOL, _C_BOOL, NULL, _C_NSUInteger
 from objc import lookUpClass
+import collections
+from copy import copy as copy_func
+import sys
 
 NSSet = lookUpClass('NSSet')
+NSObject = lookUpClass('NSObject')
 
 
 def attrsetter(prop, name, copy):
     if copy:
         def func(self, value):
-            setattr(self, name, value.copy())
+            if isinstance(value, NSObject):
+                setattr(self, name, value.copy())
+            else:
+                setattr(self, name, copy_func(value))
     else:
         def func(self, value):
             setattr(self, name, value)
 NSKeyValueChangeRemoval = 3
 NSKeyValueChangeReplacement = 4
 
-
-class array_proxy (object):
+# FIXME: split into two: array_proxy and mutable_array_proxy
+class array_proxy (collections.MutableSequence):
     # XXX: The implemenation should be complete, but is currently not
     # tested.
     __slots__ = ('_name', '_wrapped', '_parent', '_ro')
 
-    def __init__(cls, name, parent, wrapped, read_only):
-        v = cls.alloc().init()
-        v._name = name
-        v._wrapped = wrapped
-        v._parent = parent
-        v._ro = read_only
+    def __init__(self, name, parent, wrapped, read_only):
+        self._wrapped = wrapped
+        self._name = name
+        self._parent = parent
+        self._ro = read_only
 
 
     def __indexSetForIndex(self, index):
             result = NSMutableIndexSet.alloc().init()
             start, stop, step = index.indices(len(self._wrapped))
             for i in xrange(start, stop, step):
-                result.addIndex(index)
+                result.addIndex_(i)
 
             return result
 
                 return NSIndexSet.alloc().initWithIndex_(v)
 
             else:
-                return NSIndexSet.alloc().initWithIndex_(v)
+                return NSIndexSet.alloc().initWithIndex_(index)
         
 
 
         # Default: just defer to wrapped list
         return getattr(self._wrapped, name)
 
+    def __len__(self):
+        return self._wrapped.__len__()
+
     def __getitem__(self, index):
         return self._wrapped[index]
 
     def __setitem__(self, index, value):
         if self._ro:
-            raise TypeError("Property '%s' is read-only"%(self._name,))
+            raise ValueError("Property '%s' is read-only"%(self._name,))
 
         indexes = self.__indexSetForIndex(index)
         self._parent.willChange_valuesAtIndexes_forKey_(
 
     def __delitem__(self, index):
         if self._ro:
-            raise TypeError("Property '%s' is read-only"%(self._name,))
+            raise ValueError("Property '%s' is read-only"%(self._name,))
 
         indexes = self.__indexSetForIndex(index)
         self._parent.willChange_valuesAtIndexes_forKey_(
                 NSKeyValueChangeRemoval,
                 indexes, self._name)
         try:
-            self._wrapped[index] = value
+            del self._wrapped[index]
         finally:
             self._parent.didChange_valuesAtIndexes_forKey_(
                 NSKeyValueChangeRemoval,
 
     def append(self, value):
         if self._ro:
-            raise TypeError("Property '%s' is read-only"%(self._name,))
+            raise ValueError("Property '%s' is read-only"%(self._name,))
 
-        index = len(self._wrapped)
+        index = len(self)
         indexes = NSIndexSet.alloc().initWithIndex_(index)
         self._parent.willChange_valuesAtIndexes_forKey_(
                 NSKeyValueChangeInsertion,
                 indexes, self._name)
 
     def insert(self, index, value):
+        if self._ro:
+            raise ValueError("Property '%s' is read-only"%(self._name,))
+
         if isinstance(index, slice):
-            raise TypeError("insert argument 1 is a slice")
+            raise ValueError("insert argument 1 is a slice")
 
         indexes = self.__indexSetForIndex(index)
         self._parent.willChange_valuesAtIndexes_forKey_(
 
     def pop(self, index=-1):
         if self._ro:
-            raise TypeError("Property '%s' is read-only"%(self._name,))
+            raise ValueError("Property '%s' is read-only"%(self._name,))
 
         if isinstance(index, slice):
-            raise TypeError("insert argument 1 is a slice")
+            raise ValueError("insert argument 1 is a slice")
 
         indexes = self.__indexSetForIndex(index)
         self._parent.willChange_valuesAtIndexes_forKey_(
                 NSKeyValueChangeRemoval,
                 indexes, self._name)
         try:
-            self._wrapped.insert(index, value)
+            return self._wrapped.pop(index)
         finally:
             self._parent.didChange_valuesAtIndexes_forKey_(
                 NSKeyValueChangeRemoval,
 
     def extend(self, values):
         # XXX: This is suboptimal but correct
-        for item in values:
-            self.append(values)
+        if self._ro:
+            raise ValueError("Property '%s' is read-only"%(self._name,))
 
+        values = list(values)
+
+        indexes = NSIndexSet.alloc().initWithIndexesInRange_((len(self), len(values)))
+
+        self._parent.willChange_valuesAtIndexes_forKey_(
+                NSKeyValueChangeInsertion,
+                indexes, self._name)
+        try:
+            for item in values:
+                self._wrapped.append(item)
+        finally:
+            self._parent.didChange_valuesAtIndexes_forKey_(
+                NSKeyValueChangeInsertion,
+                indexes, self._name)
+
+    def __iadd__(self, value):
+        self._wrapped.extend(value)
+        return self
+
+    def __imul__(self, count):
+        if self._ro:
+            raise ValueError("Property '%s' is read-only"%(self._name,))
+        if not isinstance(count, (int, long)):
+            raise ValueError(count)
+
+        indexes = NSIndexSet.alloc().initWithIndexesInRange_((len(self), len(self)*count))
+        self._parent.willChange_valuesAtIndexes_forKey_(
+                NSKeyValueChangeInsertion,
+                indexes, self._name)
+        try:
+            self._wrapped *= count
+        finally:
+            self._parent.didChange_valuesAtIndexes_forKey_(
+                NSKeyValueChangeInsertion,
+                indexes, self._name)
+
+        return self
+
+    
+    def __eq__(self, other):
+        if isinstance(other, array_proxy):
+            return self._wrapped == other._wrapped
+
+        else:
+            return self._wrapped == other
+
+    def __ne__(self, other):
+        if isinstance(other, array_proxy):
+            return self._wrapped != other._wrapped
+
+        else:
+            return self._wrapped != other
+
+    def __lt__(self, other):
+        if isinstance(other, array_proxy):
+            return self._wrapped < other._wrapped
+
+        else:
+            return self._wrapped < other
+
+    def __le__(self, other):
+        if isinstance(other, array_proxy):
+            return self._wrapped <= other._wrapped
+
+        else:
+            return self._wrapped <= other
+
+    def __gt__(self, other):
+        if isinstance(other, array_proxy):
+            return self._wrapped > other._wrapped
+
+        else:
+            return self._wrapped > other
+
+    def __ge__(self, other):
+        if isinstance(other, array_proxy):
+            return self._wrapped >= other._wrapped
+
+        else:
+            return self._wrapped >= other
+
+
+    if sys.version_info[0] == 2:
+        def __cmp__(self, other):
+            if isinstance(other, array_proxy):
+                return cmp(self._wrapped, other._wrapped)
+
+            else:
+                return cmp(self._wrapped, other)
 
     def sort(self, cmp=None, key=None, reverse=False):
         if self._ro:
-            raise TypeError("Property '%s' is read-only"%(self._name,))
+            raise ValueError("Property '%s' is read-only"%(self._name,))
 
         indexes = NSIndexSet.alloc().initWithIndexesInRange_(
                 (0, len(self._wrapped)))
 
     def reverse(self):
         if self._ro:
-            raise TypeError("Property '%s' is read-only"%(self._name,))
+            raise ValueError("Property '%s' is read-only"%(self._name,))
 
         indexes = NSIndexSet.alloc().initWithIndexesInRange_(
                 (0, len(self._wrapped)))
                 NSKeyValueChangeReplacement,
                 indexes, self._name)
 
+def makeArrayAccessors(name):
+    
+    def countOf(self):
+        return len(getattr(self, name))
+
+    def objectIn(self, idx):
+        return getattr(self, name)[idx]
+
+    def insert(self, value, idx):
+        getattr(self, name).insert(idx, value)
+
+    def replace(self, idx, value):
+        getattr(self, name)[idx] = value
+
+    def remove(self, idx):
+        del getattr(self, name)[idx]
+
+    return countOf, objectIn, insert, remove, replace
 
 class array_property (object_property):
-    def __get__(self):
-        v = object_property.__get__(self)
+    def __init__(self, name=None, 
+            read_only=False, copy=True, dynamic=False, 
+            ivar=None, depends_on=None):
+        super(array_property, self).__init__(name, 
+                read_only=read_only, 
+                copy=copy, dynamic=dynamic,
+                ivar=ivar, depends_on=depends_on)
+
+    def __pyobjc_class_setup__(self, name, class_dict, instance_methods, class_methods):
+        super(array_property, self).__pyobjc_class_setup__(name, class_dict, instance_methods, class_methods)
+
+
+        # Insert (Mutable) Indexed Accessors
+        # FIXME: should only do the mutable bits when we're actually a mutable property
+
+        name = self._name
+        Name = name[0].upper() + name[1:]
+
+        countOf, objectIn, insert, remove, replace = makeArrayAccessors(self._name)
+
+        countOf = selector(countOf, 
+                selector  = 'countOf%s'%(Name,),
+                signature = _C_NSUInteger + '@:',
+        )
+        countOf.isHidden = True
+        instance_methods.add(countOf)
+
+        objectIn = selector(objectIn, 
+                selector  = 'objectIn%sAtIndex:'%(Name,),
+                signature = '@@:' + _C_NSUInteger,
+        )
+        objectIn.isHidden = True
+        instance_methods.add(objectIn)
+
+        insert = selector(insert, 
+                selector  = 'insertObject:in%sAtIndex:'%(Name,),
+                signature = 'v@:@' + _C_NSUInteger,
+        )
+        insert.isHidden = True
+        instance_methods.add(insert)
+
+        remove = selector(remove, 
+                selector  = 'removeObjectFrom%sAtIndex:'%(Name,),
+                signature = 'v@:' + _C_NSUInteger,
+        )
+        remove.isHidden = True
+        instance_methods.add(remove)
+
+        replace = selector(replace, 
+                selector  = 'replaceObjectIn%sAtIndex:withObject:'%(Name,),
+                signature = 'v@:' + _C_NSUInteger + '@',
+        )
+        replace.isHidden = True
+        instance_methods.add(replace)
+
+
+    def __set__(self, object, value):
+        if isinstance(value, array_property):
+            print "set1", object, value
+            value = list(value)
+            print "set2", object, value
+
+        super(array_property, self).__set__(object, value)
+
+    def __get__(self, object, owner):
+        v = object_property.__get__(self, object, owner)
         if v is None:
             v = list()
-            object_property.__set__(self, v)
-        return array_proxy(self._name, self, v, self._ro)
+            object_property.__set__(self, object, v)
+        return array_proxy(self._name, object, v, self._ro)
 
 NSKeyValueUnionSetMutation = 1,
 NSKeyValueMinusSetMutation = 2,

pyobjc-core/PyObjCTest/test_array_property.py

 from PyObjCTools.TestSupport import *
+import objc
+import collections
 
+NSObject = objc.lookUpClass('NSObject')
+NSIndexSet = objc.lookUpClass('NSIndexSet')
+NSMutableIndexSet = objc.lookUpClass('NSMutableIndexSet')
+
+class TestArrayPropertyHelper (NSObject):
+    array = objc.array_property()
+    roArray = objc.array_property(read_only=True)
+
+from test_object_property import OCObserve
 
 class TestArrayProperty (TestCase):
-    def testMissing(self):
+    def _testMissing(self):
         self.fail("Implement tests")
 
     def testGetting(self):
         # Check that default value is an empty value
         # Check that value is a proxy object
-        self.fail("todo")
+        o = TestArrayPropertyHelper.alloc().init()
+
+        v = o.array
+        self.failUnlessIsInstance(v, collections.MutableSequence)
+
+        self.failUnlessEqual(len(v), 0)
+
+        v.append(1)
+        self.failUnlessEqual(len(v), 1)
+
+        self.assertEquals(type(v).__name__, 'array_proxy')
 
     def testSetting(self):
         # Set value, check that 
         # (1) value gets copied
         # (2) accessing the property result in proxy
-        self.fail("todo")
+        observer = OCObserve.alloc().init()
+        l = [1, 2, 3]
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+        try:
+            self.assertEquals(len(observer.values), 0)
+            self.assertEquals(len(o.array), 0)
+            self.assertEquals(len(observer.values), 0)
+            o.array = l
+            self.assertEquals(len(observer.values), 1)
+
+
+            self.assertEquals(len(o.array), 3)
+
+            # This shouldn't affect the property
+            l.append(4)
+            self.assertEquals(len(o.array), 3)
+
+            self.assertEquals(len(l), 4)
+            o.array.append(5)
+            self.assertEquals(len(l), 4)
+
+        finally:
+            observer.unregister(o, 'array')
+
+
 
     def testGetSetItem(self):
         # Use __getitem__, __setitem__ interface and check
         # that the correct KVO events get emitted.
-        self.fail("todo")
+        observer = OCObserve.alloc().init()
+        l = [1, 2, 3]
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+
+        # FIXME: the call to len shouldn't be necessary
+        len(o.array)
+        try:
+            IS = NSIndexSet.alloc().initWithIndex_(0)
+            self.assertEquals(len(observer.values), 0)
+
+            o.array.append(1)
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS)
+            self.assertEquals(observer.values[-1][-1]['new'], [1])
+
+
+            self.assertEquals(o.array[0], 1)
+            o.array[0] = 4
+            self.assertEquals(o.array[0], 4)
+            self.assertEquals(len(observer.values), 2)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS)
+            self.assertEquals(observer.values[-1][-1]['old'], [1])
+            self.assertEquals(observer.values[-1][-1]['new'], [4])
+
+        finally:
+            observer.unregister(o, 'array')
 
     def testGetSetSlice(self):
         # Same as testGetSetItem, but using slice
-        self.fail("todo")
+        observer = OCObserve.alloc().init()
+        l = [1, 2, 3]
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+
+        try:
+            IS = NSIndexSet.alloc().initWithIndexesInRange_((0, 3))
+            IS2 = NSIndexSet.alloc().initWithIndexesInRange_((1, 2))
+            IS3 = NSMutableIndexSet.alloc().init()
+            IS3.addIndex_(0)
+            IS3.addIndex_(2)
+            self.assertEquals(len(observer.values), 0)
+
+            o.array = l
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertNotIn('indexes', observer.values[-1][-1])
+            self.assertEquals(observer.values[-1][-1]['new'], [1, 2, 3])
+
+
+            self.assertEquals(o.array[0], 1)
+            o.array[1:3] = [4, 5]
+            self.assertEquals(o.array[1], 4)
+            self.assertEquals(o.array[2], 5)
+            self.assertEquals(len(observer.values), 2)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS2)
+            self.assertEquals(observer.values[-1][-1]['old'], [2, 3])
+            self.assertEquals(observer.values[-1][-1]['new'], [4, 5])
+
+            self.assertEquals(o.array[0], 1)
+            o.array[0:3:2] = [9, 10]
+            self.assertEquals(o.array[0], 9)
+            self.assertEquals(o.array[1], 4)
+            self.assertEquals(o.array[2], 10)
+            self.assertEquals(len(observer.values), 3)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS3)
+            self.assertEquals(observer.values[-1][-1]['old'], [1, 5])
+            self.assertEquals(observer.values[-1][-1]['new'], [9, 10])
+
+        finally:
+            observer.unregister(o, 'array')
 
     def testInsert(self):
         # Use insert method and check that the correct
         # KVO events get emitted
-        self.fail("todo")
+        # Same as testGetSetItem, but using slice
+        observer = OCObserve.alloc().init()
+        l = [1, 2, 3]
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+
+        try:
+            IS = NSIndexSet.alloc().initWithIndex_(0)
+            IS1 = NSIndexSet.alloc().initWithIndex_(4)
+            self.assertEquals(len(observer.values), 0)
+
+            o.array = l
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertNotIn('indexes', observer.values[-1][-1])
+
+            self.assertEquals(o.array[0], 1)
+
+
+            o.array.insert(0, 'a')
+            self.assertEquals(o.array[0], 'a')
+            self.assertEquals(len(o.array), 4)
+
+            self.assertEquals(len(observer.values), 2)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS)
+            self.assertNotIn('old', observer.values[-1][-1])
+            self.assertEquals(observer.values[-1][-1]['new'], ['a'])
+
+            o.array.insert(4, 'b')
+            self.assertEquals(o.array[4], 'b')
+            self.assertEquals(len(o.array), 5)
+
+            self.assertEquals(len(observer.values), 3)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS1)
+            self.assertNotIn('old', observer.values[-1][-1])
+            self.assertEquals(observer.values[-1][-1]['new'], ['b'])
+
+        finally:
+            observer.unregister(o, 'array')
 
     def testPop(self):
         # Use pop method and check that the correct
         # KVO events get emitted
-        self.fail("todo")
+        observer = OCObserve.alloc().init()
+        l = [1, 2, 3, 4]
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+
+        try:
+            IS = NSIndexSet.alloc().initWithIndex_(0)
+            IS2 = NSIndexSet.alloc().initWithIndex_(2)
+            self.assertEquals(len(observer.values), 0)
+
+            o.array = l
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertNotIn('indexes', observer.values[-1][-1])
+
+            self.assertEquals(o.array[0], 1)
+
+
+            v = o.array.pop(0)
+            self.assertEquals(v, 1)
+            self.assertEquals(o.array[0], 2)
+            self.assertEquals(len(o.array), 3)
+
+            self.assertEquals(len(observer.values), 2)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS)
+            self.assertNotIn('new', observer.values[-1][-1])
+            self.assertEquals(observer.values[-1][-1]['old'], [1])
+
+            v = o.array.pop(2)
+            self.assertEquals(v, 4)
+            self.assertEquals(len(o.array), 2)
+
+            self.assertEquals(len(observer.values), 3)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS2)
+            self.assertNotIn('new', observer.values[-1][-1])
+            self.assertEquals(observer.values[-1][-1]['old'], [4])
+
+        finally:
+            observer.unregister(o, 'array')
 
     def testDelItem(self):
         # Use __delitem__and check that the correct
         # KVO events get emitted
-        self.fail("todo")
+        observer = OCObserve.alloc().init()
+        l = [1, 2, 3, 4]
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+
+        try:
+            IS = NSIndexSet.alloc().initWithIndex_(0)
+            IS2 = NSIndexSet.alloc().initWithIndex_(2)
+            self.assertEquals(len(observer.values), 0)
+
+            o.array = l
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertNotIn('indexes', observer.values[-1][-1])
+
+            self.assertEquals(o.array[0], 1)
+
+
+            del o.array[0]
+            self.assertEquals(o.array[0], 2)
+            self.assertEquals(len(o.array), 3)
+
+            self.assertEquals(len(observer.values), 2)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS)
+            self.assertNotIn('new', observer.values[-1][-1])
+            self.assertEquals(observer.values[-1][-1]['old'], [1])
+
+            del o.array[2]
+            self.assertEquals(len(o.array), 2)
+
+            self.assertEquals(len(observer.values), 3)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS2)
+            self.assertNotIn('new', observer.values[-1][-1])
+            self.assertEquals(observer.values[-1][-1]['old'], [4])
+
+        finally:
+            observer.unregister(o, 'array')
 
     def testDelSlice(self):
         # As testDelItem, but using slices
-        self.fail("todo")
+        observer = OCObserve.alloc().init()
+        l = [1, 2, 3, 4]
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+
+        try:
+            IS = NSMutableIndexSet.alloc().init()
+            IS.addIndex_(0)
+            IS.addIndex_(2)
+            self.assertEquals(len(observer.values), 0)
+
+            o.array = l
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertNotIn('indexes', observer.values[-1][-1])
+
+            self.assertEquals(o.array[0], 1)
+
+
+            del o.array[0:4:2]
+            self.assertEquals(o.array[0], 2)
+            self.assertEquals(o.array[1], 4)
+            self.assertEquals(len(o.array), 2)
+
+            self.assertEquals(len(observer.values), 2)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS)
+            self.assertNotIn('new', observer.values[-1][-1])
+            self.assertEquals(observer.values[-1][-1]['old'], [1, 3])
+
+        finally:
+            observer.unregister(o, 'array')
+
+    def testExtend(self):
+        observer = OCObserve.alloc().init()
+        l = [1, 2, 3, 4]
+        l2 = ['a', 'b', 'c']
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+
+        try:
+            o.array = l
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertEquals(o.array[0], 1)
+
+            o.array.extend(l2)
+
+            self.assertEquals(len(o.array), 7)
+            self.assertEquals(o.array[4], 'a')
+
+            self.assertEquals(len(observer.values), 2)
+            self.assertEquals(observer.values[-1][-1]['indexes'], NSIndexSet.alloc().initWithIndexesInRange_((4, 3)))
+            self.assertNotIn('old', observer.values[-1][-1])
+            self.assertEquals(observer.values[-1][-1]['new'], ['a', 'b', 'c'])
+
+        finally:
+            observer.unregister(o, 'array')
+
+    def testIAdd(self):
+        observer = OCObserve.alloc().init()
+        l = [1, 2, 3, 4]
+        l2 = ['a', 'b', 'c']
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+
+        try:
+            o.array = l
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertEquals(o.array[0], 1)
+
+            o.array += l2
+
+            self.assertEquals(len(o.array), 7)
+            self.assertEquals(o.array[4], 'a')
+
+            self.assertEquals(len(observer.values), 3)
+            self.assertEquals(observer.values[-2][-1]['indexes'], NSIndexSet.alloc().initWithIndexesInRange_((4, 3)))
+            self.assertNotIn('old', observer.values[-2][-1])
+            self.assertEquals(observer.values[-2][-1]['new'], ['a', 'b', 'c'])
+
+            self.assertNotIn('indexes', observer.values[-1][-1])
+
+        finally:
+            observer.unregister(o, 'array')
+
+    def testIMul(self):
+        observer = OCObserve.alloc().init()
+        l = [1, 2]
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+
+        try:
+            o.array = l
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertEquals(o.array[0], 1)
+
+            o.array *= 3
+
+            self.assertEquals(len(o.array), 6)
+            self.assertEquals(o.array[0], 1)
+            self.assertEquals(o.array[1], 2)
+            self.assertEquals(o.array[3], 1)
+            self.assertEquals(o.array[4], 2)
+            self.assertEquals(o.array[5], 1)
+            self.assertEquals(o.array[6], 2)
+
+            self.assertEquals(len(observer.values), 3)
+            self.assertEquals(observer.values[-2][-1]['indexes'], NSIndexSet.alloc().initWithIndexesInRange_((2, 4)))
+            self.assertNotIn('old', observer.values[-2][-1])
+            self.assertEquals(observer.values[-2][-1]['new'], [1, 2, 1, 2])
+
+            self.assertNotIn('indexes', observer.values[-1][-1])
+
+        finally:
+            observer.unregister(o, 'array')
+
 
     def testSort(self):
         # Use sort method and check that the correct
         # KVO events get emitted
-        self.fail("todo")
+        observer = OCObserve.alloc().init()
+        l = [2, 4, 1, 3]
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
+
+        try:
+            IS = NSIndexSet.alloc().initWithIndexesInRange_((0, 4))
+            self.assertEquals(len(observer.values), 0)
+
+            orig_l = l[:]
+            o.array = l
+
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertNotIn('indexes', observer.values[-1][-1])
+
+            self.assertEquals(o.array[0], 2)
+
+            o.array.sort()
+
+            self.assertEquals(o.array[0], 1)
+            self.assertEquals(o.array[1], 2)
+            self.assertEquals(o.array[2], 3)
+            self.assertEquals(o.array[3], 4)
+            self.assertEquals(len(o.array), 4)
+
+            self.assertEquals(len(observer.values), 2)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS)
+            self.assertEquals(observer.values[-1][-1]['old'], l)
+            self.assertEquals(observer.values[-1][-1]['new'], [1,2,3,4])
+            self.assertEquals(orig_l, l)
+
+        finally:
+            observer.unregister(o, 'array')
 
     def testReverse(self):
         # Use reverse method and check that the correct
         # KVO events get emitted
-        self.fail("todo")
+        observer = OCObserve.alloc().init()
+        l = [2, 4, 1, 3]
+        o = TestArrayPropertyHelper.alloc().init()
+        observer.register(o, 'array')
 
-    def testKVOFromObjC(self):
-        # Check that the right interfaces are implemented
-        # to enable accessing and changing the property 
-        # from ObjC, using an NSArrayController
-        # (Which probably means that a number of KVC methods
-        # need to be implemented)
-        self.fail("todo")
+        try:
+            IS = NSIndexSet.alloc().initWithIndexesInRange_((0, 4))
+            self.assertEquals(len(observer.values), 0)
+
+            orig_l = l[:]
+            o.array = l
+
+
+            self.assertEquals(len(observer.values), 1)
+            self.assertNotIn('indexes', observer.values[-1][-1])
+
+            self.assertEquals(o.array[0], 2)
+
+            o.array.reverse()
+
+            self.assertEquals(o.array[0], 3)
+            self.assertEquals(o.array[1], 1)
+            self.assertEquals(o.array[2], 4)
+            self.assertEquals(o.array[3], 2)
+            self.assertEquals(len(o.array), 4)
+
+            self.assertEquals(len(observer.values), 2)
+            self.assertEquals(observer.values[-1][-1]['indexes'], IS)
+            self.assertEquals(observer.values[-1][-1]['old'], l)
+            self.assertEquals(observer.values[-1][-1]['new'], [3, 1, 4, 2])
+            self.assertEquals(orig_l, l)
+
+        finally:
+            observer.unregister(o, 'array')
+
+    def testObjCAccessors(self):
+        # Check that the right ObjC array accessors are defined and work properly
+        self.assertTrue(TestArrayPropertyHelper.instancesRespondToSelector_(b"setArray:"))
+        self.assertTrue(TestArrayPropertyHelper.instancesRespondToSelector_(b"array"))
+        self.assertTrue(TestArrayPropertyHelper.instancesRespondToSelector_(b"countOfArray"))
+        self.assertTrue(TestArrayPropertyHelper.instancesRespondToSelector_(b"objectInArrayAtIndex:"))
+        self.assertTrue(TestArrayPropertyHelper.instancesRespondToSelector_(b"insertObject:inArrayAtIndex:"))
+        self.assertTrue(TestArrayPropertyHelper.instancesRespondToSelector_(b"removeObjectFromArrayAtIndex:"))
+        self.assertTrue(TestArrayPropertyHelper.instancesRespondToSelector_(b"replaceObjectInArrayAtIndex:withObject:"))
+
+        o = TestArrayPropertyHelper.alloc().init()
+        self.assertEquals(0, o.pyobjc_instanceMethods.countOfArray())
+        self.assertRaises(AttributeError, getattr, o, 'countOfArray')
+
+        o.pyobjc_instanceMethods.insertObject_inArrayAtIndex_('a', 0)
+        self.assertEquals(1, o.pyobjc_instanceMethods.countOfArray())
+        self.assertEquals('a', o.array[0])
+        self.assertEquals('a', o.pyobjc_instanceMethods.objectInArrayAtIndex_(0))
+        o.pyobjc_instanceMethods.replaceObjectInArrayAtIndex_withObject_(0, 'b')
+        self.assertEquals('b', o.array[0])
+        o.pyobjc_instanceMethods.removeObjectFromArrayAtIndex_(0)
+        self.assertEquals(0, o.pyobjc_instanceMethods.countOfArray())
+
 
     # Verify docs and/or implementation to check for other
     # mutating methods
 
     def testReadingMethods(self):
         # Check that all read-only methods work as well
-        self.fail("todo")
+
+        o = TestArrayPropertyHelper.alloc().init()
+        o.array = [1, 2, 3, 4]
+
+        self.assertNotIsInstance(o.array, list)
+
+        self.assertEquals(o.array, [1,2,3,4])
+        self.assertNotEquals(o.array, [1,2,3,4, 5])
+        
+
+        self.assertTrue(o.array < [1,2,3,4,5])
+        self.assertTrue(o.array <= [1,2,3,4,5])
+        self.assertTrue(o.array <= [1,2,3,4])
+        self.assertTrue(o.array >= [1,2,3,4])
+        self.assertTrue(o.array > [1,2,3])
+
+        self.assertEquals(o.array.count(1), 1)
+        self.assertEquals(o.array.index(4), 3)
 
     def testMutatingReadonlyProperty(self):
         # Check that trying to mutate a read-only property
         # will raise an exception
-        self.fail("todo")
+        o = TestArrayPropertyHelper.alloc().init()
+
+        o._roArray = [1, 2, 3]
+
+        self.assertEquals(list(o.roArray), [1,2,3])
+
+        self.assertRaises(ValueError, o.roArray.append,1)
+        self.assertRaises(ValueError, o.roArray.extend, [1,2])
+        self.assertRaises(ValueError, o.roArray.sort)
+        self.assertRaises(ValueError, o.roArray.reverse)
+        try:
+            o.roArray[0] = 2
+        except ValueError:
+            pass
+        else:
+            self.fail("ValueError not raised")
+
+        try:
+            del o.roArray[0]
+        except ValueError:
+            pass
+        else:
+            self.fail("ValueError not raised")
+
+        try:
+            o.roArray += [4]
+        except ValueError:
+            pass
+        else:
+            self.fail("ValueError not raised")
+
+        try:
+            o.roArray *= 4
+        except ValueError:
+            pass
+        else:
+            self.fail("TypeError not raised")
+
 
     def testMutatingReadonlyPropertyObjC(self):
         # Check that trying to mutate a read-only property
         # from ObjC will raise an exception
-        self.fail("todo")
+        o = TestArrayPropertyHelper.alloc().init()
+        o._roArray = [1,2,3]
+        self.assertEquals(3, o.pyobjc_instanceMethods.countOfRoArray())
+        self.assertRaises(AttributeError, getattr, o, 'countOfRoArray')
+
+        try:
+            o.pyobjc_instanceMethods.insertObject_inRoArrayAtIndex_('a', 0)
+        except ValueError:
+            pass
+        else:
+            self.fail("ValueError not raised")
+
+        self.assertEquals(3, o.pyobjc_instanceMethods.countOfRoArray())
+        self.assertEquals(1, o.pyobjc_instanceMethods.objectInRoArrayAtIndex_(0))
+        try:
+            o.pyobjc_instanceMethods.replaceObjectInRoArrayAtIndex_withObject_(0, 'b')
+        except ValueError:
+            pass
+        else:
+            self.fail("ValueError not raised")
+
+        try:
+            o.pyobjc_instanceMethods.removeObjectFromRoArrayAtIndex_(0)
+        except ValueError:
+            pass
+        else:
+            self.fail("ValueError not raised")
+
 
 
 if __name__ == "__main__":