Commits

Ronald Oussoren committed 2900fac

Initial implementation of tests of the list/tuple
API for NSArray's.

All tuple-related tests pass, I'm still working on
the list-related ones.

Known issues:
* Running the tests causes a crash in one test,
I haven't investigated this yet.
* NSArray.count is different from list.count,
due to the existence of 'NSArray -count' in
ObjC. I haven't though about a solution to this
yet.
* Need to write documentation, especially about
differences between native sequences and the Cocoa
ones.

Comments (0)

Files changed (3)

pyobjc-core/Lib/objc/_convenience.py

     ('extend', extend_addObjectsFromArray_),
 )
 
-def index_indexOfObject_(self, item):
+_index_sentinel=object()
+def index_indexOfObject_inRange_(self, item, start=0, stop=_index_sentinel):
     from Foundation import NSNotFound
-    res = self.indexOfObject_(container_wrap(item))
-    if res == NSNotFound:
-        raise ValueError, "%s.index(x): x not in list" % (type(self).__name__,)
+    if start == 0 and stop is _index_sentinel:
+        res = self.indexOfObject_(container_wrap(item))
+        if res == NSNotFound:
+            raise ValueError("%s.index(x): x not in list" % (type(self).__name__,))
+    else:
+        l = len(self)
+        if start < 0:
+            start = l + start
+            if start < 0:
+                start = 0
+
+        if stop is not _index_sentinel:
+            if stop < 0:
+                stop = l + stop
+                if stop < 0:
+                    stop = 0
+        else:
+            stop = l
+
+        if stop <= start:
+            ln = 0 
+        else:
+            ln = stop - start
+
+
+        if ln == 0:
+            raise ValueError("%s.index(x): x not in list" % (type(self).__name__,))
+
+        res = self.indexOfObject_inRange_(item, (start, ln))
+        if res == NSNotFound:
+            raise ValueError("%s.index(x): x not in list" % (type(self).__name__,))
     return res
 
-CONVENIENCE_METHODS[b'indexOfObject:'] = (
-    ('index', index_indexOfObject_),
+CONVENIENCE_METHODS[b'indexOfObject:inRange:'] = (
+    ('index', index_indexOfObject_inRange_),
 )
 
 def insert_insertObject_atIndex_(self, idx, item):
     collections.Mapping.register(lookUpClass('NSDictionary'))
     collections.MutableMapping.register(lookUpClass('NSMutableDictionary'))
 
+    def nsarray_new(cls, sequence=None):
+        if not sequence:
+            return cls.array()
+
+        elif isinstance(sequence, (str, unicode)):
+            return cls.arrayWithArray_(list(sequence))
+
+        else:
+            if not isinstance(sequence, (list, tuple)):
+                # FIXME: teach bridge to treat range and other list-lik
+                # types correctly
+                return cls.arrayWithArray_(list(sequence))
+
+            return cls.arrayWithArray_(sequence)
+
+    NSMutableArray = lookUpClass('NSMutableArray')
+    def nsarray_add(self, other):
+        result = NSMutableArray.arrayWithArray_(self)
+        result.extend(other)
+        return result
+
+    def nsarray_radd(self, other):
+        result = NSMutableArray.arrayWithArray_(other)
+        result.extend(self)
+        return result
+
+    def nsarray_mul(self, other):
+        """
+        This tries to implement anNSArray * N
+        somewhat efficently (and definitely more
+        efficient that repeated appending).
+        """
+        result = NSMutableArray.array()
+
+        if other <= 0:
+            return result
+
+        n = 1
+        tmp = self
+        while other:
+            if other & n != 0:
+                result.extend(tmp)
+                other -= n
+
+            if other:
+                n <<= 1
+                tmp = tmp.arrayByAddingObjectsFromArray_(tmp)
+
+        #for n in xrange(other):
+            #result.extend(self)
+        return result
+
 
     def nsdict_new(cls, *args, **kwds):
         if len(args) == 0:
             ('fromkeys', classmethod(nsmutabledict_fromkeys)),
         )
 
+        CLASS_METHODS['NSArray'] = (
+            ('__new__', nsarray_new),
+            ('__add__', nsarray_add),
+            ('__radd__', nsarray_radd),
+            ('__mul__', nsarray_mul),
+            ('__rmul__', nsarray_mul),
+        )
+
     else:
         CLASS_METHODS['NSDictionary'] = (
             ('__new__', nsdict_new),
         CLASS_METHODS['NSMutableDictionary'] = (
             ('__new__', nsdict_new),
         )
-
+        CLASS_METHODS['NSArray'] = (
+            ('__new__', nsarray_new),
+            ('__add__', nsarray_add),
+            ('__radd__', nsarray_radd),
+            ('__mul__', nsarray_mul),
+            ('__rmul__', nsarray_mul),
+        )

pyobjc-core/Modules/objc/OC_PythonObject.m

  * This is needed to be able to add a python object to a
  * NSArray and then use array.description()
  */
+-(BOOL)isNSArray__
+{
+	        return NO;
+}
+-(BOOL)isNSDictionary__
+{
+	        return NO;
+}
+-(BOOL)isNSSet__
+{
+	        return NO;
+}
+-(BOOL)isNSNumber__
+{
+	        return NO;
+}
+-(BOOL)isNSData__
+{
+	        return NO;
+}
+-(BOOL)isNSDate__
+{
+	        return NO;
+}
 -(BOOL)isNSString__
 {
-	return NO;
+	        return NO;
 }
+-(BOOL)isNSValue__
+{
+	        return NO;
+}
+
 
 +classFallbacksForKeyedArchiver
 {

pyobjc-core/PyObjCTest/test3_array_interface.py

+from PyObjCTools.TestSupport import *
+from test import list_tests, seq_tests
+import objc
+
+# Import some of the stdlib tests
+from test import mapping_tests
+
+NSArray = objc.lookUpClass('NSArray')
+NSMutableArray = objc.lookUpClass('NSMutableArray')
+
+# FIXME: Need to create a dictionary to activate the __new__ method.
+NSArray.array()
+
+class ArrayTests (seq_tests.CommonTest):
+    type2test = NSArray
+
+    def test_constructors(self):
+
+        self.assertEqual(NSArray(), ())
+        t0_3 = (0, 1, 2, 3)
+        t0_3_bis = NSArray(t0_3)
+        self.assertEqual(t0_3, t0_3_bis)
+
+        self.assertEqual(NSArray("hello"), NSArray(["h", "e", "l", "l", "o"]))
+
+    def test_truth(self):
+        super(ArrayTests, self).test_truth()
+        self.assertTrue(not NSArray())
+        self.assertTrue(NSArray([1, 2]))
+
+    def test_len(self):
+        super(ArrayTests, self).test_len()
+
+    def test_count(self):
+        # Disable test_count because NSArray.count
+        # does not conform the right interface
+        pass
+
+    def test_index(self):
+        # This duplicates the tests in seq_tests, but
+        # disables the 'count' test because NSArray.count
+        # is not the regular python one.
+
+        u = self.type2test([0, 1])
+        self.assertEqual(u.index(0), 0)
+        self.assertEqual(u.index(1), 1)
+        self.assertRaises(ValueError, u.index, 2)
+
+        u = self.type2test([-2, -1, 0, 0, 1, 2])
+        #self.assertEqual(u.count(0), 2)
+        self.assertEqual(u.index(0), 2)
+        self.assertEqual(u.index(0, 2), 2)
+        self.assertEqual(u.index(-2, -10), 0)
+        self.assertEqual(u.index(0, 3), 3)
+        self.assertEqual(u.index(0, 3, 4), 3)
+        self.assertRaises(ValueError, u.index, 2, 0, -10)
+
+        self.assertRaises(TypeError, u.index)
+
+
+    def test_addmul(self):
+        # Same as the one in our superclass, but disables subclassing tests
+        u1 = self.type2test([0])
+        u2 = self.type2test([0, 1])
+        self.assertEqual(u1, u1 + self.type2test())
+        self.assertEqual(u1, self.type2test() + u1)
+        self.assertEqual(u1 + self.type2test([1]), u2)
+        self.assertEqual(self.type2test([-1]) + u1, self.type2test([-1, 0]))
+        self.assertEqual(self.type2test(), u2*0)
+        self.assertEqual(self.type2test(), 0*u2)
+        self.assertEqual(self.type2test(), u2*0)
+        self.assertEqual(self.type2test(), 0*u2)
+        self.assertEqual(u2, u2*1)
+        self.assertEqual(u2, 1*u2)
+        self.assertEqual(u2, u2*1)
+        self.assertEqual(u2, 1*u2)
+        self.assertEqual(u2+u2, u2*2)
+        self.assertEqual(u2+u2, 2*u2)
+        self.assertEqual(u2+u2, u2*2)
+        self.assertEqual(u2+u2, 2*u2)
+        self.assertEqual(u2+u2+u2, u2*3)
+        self.assertEqual(u2+u2+u2, 3*u2)
+
+
+    # Disable a couple of tests that are not relevant for us.
+
+    def test_bigrepeat(self): pass
+    def test_getitemoverwriteiter(self): pass
+    def test_contains_fake(self): 
+        # Disabled because the test seems to make use of an
+        # implementation detail of sequences, it assumes 
+        # that "X in SEQ" is implemented as:
+        #
+        #   for value in SEQ:
+        #       if X == value:   # (1)
+        #           return True
+        #   return False
+        #
+        # NSArray seems to have the test on (1) in a 
+        # different order: 'if value == X', which causes
+        # this test to fail.
+        pass
+    def test_contains_order(self): 
+        # See test_contains_fake
+        pass
+
+class MutableArrayTest (list_tests.CommonTest):
+    type2test = NSMutableArray
+
+    # Disable a couple of tests that are not relevant for us.
+    def test_bigrepeat(self): pass
+    def test_repr(self): pass
+    def test_contains_fake(self): pass
+    def test_print(self): pass
+
+    # Disable inplace operation tests ( += and *= ) because
+    # we cannot support true inplace operations: most NSArray
+    # and NSMutable array instances are actually instances of
+    # NSCFArray and we cannot (and shouldn't detect whether or
+    # not a value is mutable)
+    def test_imul(self): pass
+    def test_iadd(self): pass
+
+if __name__ == "__main__":
+    main()