Repository: SETI/rms-cspyce (language: python)

Issue:
Problem with vectorization and zero-length arrays
Vectorized functions don't handle the case of being passed a zero-length array (they should return a zero-length result). Instead, they assert the minimum length is always 1, and call the underlying scalar function at least once, but with garbage arguments (which can cause CSPICE to throw an error). For example:

```
    void spkez_vector(
        SpiceInt k1,
        ConstSpiceDouble *in11, SpiceInt in11_dim1,
        ConstSpiceChar *str1,
        ConstSpiceChar *str2,
        SpiceInt k2,
        SpiceDouble **out21, SpiceInt *out21_dim1, SpiceInt *out21_dim2,
        SpiceDouble **out11, SpiceInt *out11_dim1) {
        int maxdim = in11_dim1;

        int size = (maxdim == 0 ? 1 : maxdim);     <<<<==== BIG PROBLEM HERE
        in11_dim1 = (in11_dim1 == 0 ? 1 : in11_dim1);

        *out21_dim1 = maxdim; *out21_dim2 = 6;
        *out11_dim1 = maxdim;

        SpiceDouble *out21_buffer = (SpiceDouble *)PyMem_Malloc(size * 6 * sizeof(SpiceDouble));
        SpiceDouble *out11_buffer = out21_buffer ? (SpiceDouble *)PyMem_Malloc(size * sizeof(SpiceDouble)) : NULL;
        *out21 = out21_buffer;
        *out11 = out11_buffer;
        if (!out11_buffer) {
            handle_malloc_failure("spkez" "_vector");
            return;
        }

        for (int i = 0; i < size; i++) {   <<<<==== ALWAYS ITERATES AT LEAST ONCE EVEN WHEN IN11_DIM1 IS ZERO
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wincompatible-pointer-types"
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
            spkez_c(
                k1,
                in11[i],   <<<<==== BAD MEMORY CONTENTS
                str1,
                str2,
                k2,
                out21_buffer + i * 6,
                out11_buffer + i);
#pragma GCC diagnostic pop
        }
    }
```


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 5190d2d..50f0dfc 100644
--- a/README.md
+++ b/README.md
@@ -450,6 +450,10 @@ work as expected.
 You can also write a for loop `for item in spice_cell: ...` to iterate through
 the active elements.
 
+`bool(spice_cell)`
+: If a `spice_cell` is used in a boolean context, its value is `False` if all elements
+are inactive (`spice.cell.card == 0`), and `True` otherwise. 
+
 #### SpiceCell-specific operations
 
 `SpiceCell` also has the following methods and properties specific to a `SpiceCell`.
diff --git a/cspyce/spice_cell.py b/cspyce/spice_cell.py
index e8e0227..0f28e89 100644
--- a/cspyce/spice_cell.py
+++ b/cspyce/spice_cell.py
@@ -142,8 +142,10 @@ def size(self):
 
     @size.setter
     def size(self, size):
-        assert isinstance(size, int) and size > 0
-        self.__grow_array(size)
+        if isinstance(size, int) and size > 0:
+            self.__grow_array(size)
+        else:
+            raise ValueError("size must be a positive integer")
 
     @property
     def card(self):
@@ -151,8 +153,10 @@ def card(self):
 
     @card.setter
     def card(self, value):
-        assert isinstance(value, int) and 0 <= value <= self.size
-        self._header._card = value
+        if isinstance(value, int) and 0 <= value <= self.size:
+            self._header._card = value
+        else:
+            raise ValueError("cardinality must be between 0 and the size of the cell")
 
     def as_array(self):
         return self._user_data[0:self.card]
diff --git a/swig/cspyce_typemaps.i b/swig/cspyce_typemaps.i
index 627e516..abe3043 100644
--- a/swig/cspyce_typemaps.i
+++ b/swig/cspyce_typemaps.i
@@ -29,6 +29,8 @@
 
 #define max(a, b) ((a) > (b) ? (a) : (b))
 
+#define NO_ARRAY_DIMENSION -1
+
 /*******************************************************************************
 *******************************************************************************/
 
@@ -861,7 +863,7 @@ void debug_print_object(PyObject *object, FILE* file) {
 *
 * If a scalar should be shaped into an array of shape (1,):
 *       (type *IN_ARRAY01, SpiceInt DIM1)
-* If a scalar is given, then DIM1 = 0.
+* If a scalar is given, then DIM1 = NO_ARRAY_DIMENSION.
 *******************************************************************************/
 
 
@@ -988,7 +990,7 @@ void debug_print_object(PyObject *object, FILE* file) {
     CONVERT_TO_CONTIGUOUS_ARRAY(Typecode, $input, 0, 1, pyarr)
     $1 = ($1_ltype) PyArray_DATA(pyarr);                        // ARRAY
     if (PyArray_NDIM(pyarr) == 0) {
-        $2 = 0;                                                 // DIM1
+        $2 = NO_ARRAY_DIMENSION;                                // DIM1
     } else {
         $2 = (SpiceInt) PyArray_DIM(pyarr, 0);                  // DIM1
     }
@@ -1078,7 +1080,7 @@ TYPEMAP_IN(ConstSpiceDouble, NPY_DOUBLE)
 *
 * If the first dimension can be missing:
 *       (type *IN_ARRAY12, SpiceInt DIM1, SpiceInt DIM2)
-* If it is missing, DIM1 = 0.
+* If it is missing, DIM1 = NO_ARRAY_DIMENSION.
 *******************************************************************************/
 
 %define TYPEMAP_IN(Type, Typecode) /* Use to fill in numeric types below!
@@ -1265,12 +1267,11 @@ TYPEMAP_IN(ConstSpiceDouble, NPY_DOUBLE)
 
     CONVERT_TO_CONTIGUOUS_ARRAY(Typecode, $input, 1, 2, pyarr)
 
+    $1 = ($1_ltype) PyArray_DATA(pyarr);                        // ARRAY
     if (PyArray_NDIM(pyarr) == 1) {
-        $1 = ($1_ltype) PyArray_DATA(pyarr);                    // ARRAY
-        $2 = 0;                                                 // DIM1
+        $2 = NO_ARRAY_DIMENSION;                                // DIM1
         $3 = (SpiceInt) PyArray_DIM(pyarr, 0);                  // DIM2
     } else {
-        $1 = ($1_ltype) PyArray_DATA(pyarr);                    // ARRAY
         $2 = (SpiceInt) PyArray_DIM(pyarr, 0);                  // DIM1
         $3 = (SpiceInt) PyArray_DIM(pyarr, 1);                  // DIM2
     }
@@ -1288,21 +1289,18 @@ TYPEMAP_IN(ConstSpiceDouble, NPY_DOUBLE)
 //      (Type *IN_ARRAY23, SpiceInt DIM1, SpiceInt DIM2, SpiceInt DIM3)
 
     CONVERT_TO_CONTIGUOUS_ARRAY(Typecode, $input, 2, 3, pyarr)
-
+    $1 = ($1_ltype) PyArray_DATA(pyarr);                        // ARRAY
     if (PyArray_NDIM(pyarr) == 2) {
-        $1 = ($1_ltype) PyArray_DATA(pyarr);                    // ARRAY
-        $2 = 0;                                                 // DIM1
+        $2 = NO_ARRAY_DIMENSION;                                // DIM1
         $3 = (SpiceInt) PyArray_DIM(pyarr, 0);                  // DIM2
         $4 = (SpiceInt) PyArray_DIM(pyarr, 1);                  // DIM3
     } else {
-        $1 = ($1_ltype) PyArray_DATA(pyarr);                    // ARRAY
         $2 = (SpiceInt) PyArray_DIM(pyarr, 0);                  // DIM1
         $3 = (SpiceInt) PyArray_DIM(pyarr, 1);                  // DIM2
         $4 = (SpiceInt) PyArray_DIM(pyarr, 2);                  // DIM3
     }
 }
 
-
 /*******************************************************
 * %typemap(argout)
 * %typemap(freearg)
@@ -1703,7 +1701,7 @@ TYPEMAP_ARGOUT(SpiceDouble,   NPY_DOUBLE)
 *       (type **OUT_ARRAY01, SpiceInt *DIM1)
 *******************************************************************************/
 
-%define TYPEMAP_ARGOUT(Type, Typecode) // To fill in types below!
+%define TYPEMAP_ARGOUT(Type, Typecode, Converter) // To fill in types below!
 
 /***************************************************************
 * (Type **OUT_ARRAY01, SpiceInt *SIZE1)
@@ -1731,17 +1729,17 @@ TYPEMAP_ARGOUT(SpiceDouble,   NPY_DOUBLE)
 //      (Type **OUT_ARRAY01, SpiceInt *SIZE1)
 
     TEST_MALLOC_FAILURE(buffer$argnum);
-    npy_intp dim = max(dimsize$argnum[0], 1);
-    pyarr$argnum = (PyArrayObject *) create_array_with_owned_data(1, &dim, Typecode,  (void **)&buffer$argnum);
-    TEST_MALLOC_FAILURE(pyarr$argnum);
-
-    if (dimsize$argnum[0] == 0) {
-        PyObject* value = PyArray_GETITEM(pyarr$argnum, PyArray_DATA(pyarr$argnum));
+    if (dimsize$argnum[0] == NO_ARRAY_DIMENSION) {
+        // Convert the first element of the buffer to an appropriate Python object
+        PyObject* value = Converter(*(Type *)buffer$argnum);
         TEST_MALLOC_FAILURE(value);
-        // AppendOutput steals the reference to this object, so we don't need DECREF
-        // pyarr$argnum is cleaned up by the freearg
+        // AppendOutput steals the reference to value.  No need to DECREF.
+        // buffer is freed by the freearg code.
         $result = SWIG_Python_AppendOutput($result, value);
     } else {
+        npy_intp dim = dimsize$argnum[0];
+        pyarr$argnum = (PyArrayObject *) create_array_with_owned_data(1, &dim, Typecode, (void **)&buffer$argnum);
+        TEST_MALLOC_FAILURE(pyarr$argnum);
         $result = SWIG_Python_AppendOutput($result, (PyObject *)pyarr$argnum);
         // AppendOutput steals the reference to the argument.
         pyarr$argnum = NULL;
@@ -1761,12 +1759,10 @@ TYPEMAP_ARGOUT(SpiceDouble,   NPY_DOUBLE)
 * Now define these typemaps for every numeric type
 *******************************************************/
 
-TYPEMAP_ARGOUT(SpiceInt,      NPY_INT)
-TYPEMAP_ARGOUT(SpiceInt,      NPY_INT)
-TYPEMAP_ARGOUT(SpiceBoolean,  NPY_INT)
-TYPEMAP_ARGOUT(long,          NPY_LONG)
-TYPEMAP_ARGOUT(double,        NPY_DOUBLE)
-TYPEMAP_ARGOUT(SpiceDouble,   NPY_DOUBLE)
+TYPEMAP_ARGOUT(SpiceInt,      NPY_INT,  PyInt_FromLong)
+TYPEMAP_ARGOUT(SpiceInt,      NPY_INT,  PyInt_FromLong)
+TYPEMAP_ARGOUT(SpiceBoolean,  NPY_INT,  PyInt_FromLong)
+TYPEMAP_ARGOUT(SpiceDouble,   NPY_DOUBLE, PyFloat_FromDouble)
 
 #undef TYPEMAP_ARGOUT
 
@@ -2226,7 +2222,7 @@ TYPEMAP_ARGOUT(SpiceDouble,   NPY_DOUBLE)
 
     TEST_MALLOC_FAILURE(buffer$argnum);
     npy_intp dims[2] = {dimsize$argnum[0], dimsize$argnum[1]};
-    int nd = (dims[0] == 0) ? 1 : 2;
+    int nd = (dims[0] == NO_ARRAY_DIMENSION) ? 1 : 2;
     pyarr$argnum = create_array_with_owned_data(nd, &dims[2 - nd], Typecode,  (void **)&buffer$argnum);
     TEST_MALLOC_FAILURE(pyarr$argnum);
 
@@ -2295,7 +2291,7 @@ TYPEMAP_ARGOUT(SpiceDouble,   NPY_DOUBLE)
 
     TEST_MALLOC_FAILURE(buffer$argnum);
     npy_intp dims[3] = {dimsize$argnum[0], dimsize$argnum[1], dimsize$argnum[2]};
-    int nd = dims[0] == 0 ? 2 : 3;
+    int nd = dims[0] == NO_ARRAY_DIMENSION ? 2 : 3;
     pyarr$argnum = create_array_with_owned_data(nd, &dims[3 - nd], Typecode,  (void **)&buffer$argnum);
     TEST_MALLOC_FAILURE(pyarr$argnum);
 
diff --git a/swig/make_vectorize.py b/swig/make_vectorize.py
index 509fdac..4710d5a 100644
--- a/swig/make_vectorize.py
+++ b/swig/make_vectorize.py
@@ -274,13 +274,20 @@ def write_body(self):
     def get_maxdim_and_size(self):
         out = self.out
         sizers = [arg.dim_names[0] for arg in self.inargs if arg.rank > 0]
-        out(f'int maxdim = {sizers[0]};')
-        for sizer in sizers[1:]:
-            out(f'if (maxdim < {sizer}) maxdim = {sizer};')
-        out()
-        out(f'int size = (maxdim == 0 ? 1 : maxdim);')
-        for sizer in sizers:
-            out(f'{sizer} = ({sizer} == 0 ? 1 : {sizer});')
+        any_zero_test = ' || '.join(f'{sizer} == 0' for sizer in sizers)
+        out(f'int maxdim, size;')
+        out(f'if ({any_zero_test}) {{')
+        with self.indent:
+            out(f'maxdim = size = 0;')
+        out(f'}} else {{')
+        with self.indent:
+            out(f'maxdim = {sizers[0]};')
+            for sizer in sizers[1:]:
+                out(f'if (maxdim < {sizer}) maxdim = {sizer};')
+            out(f'size = (maxdim == NO_ARRAY_DIMENSION) ? 1 : maxdim;')
+            for sizer in sizers:
+                out(f'{sizer} = ({sizer} == NO_ARRAY_DIMENSION ? 1 : {sizer});')
+        out(f'}}')
         out()
 
     def generate_initialize_output_vars(self):
diff --git a/swig/typemap_samples.i b/swig/typemap_samples.i
index 475d5fc..0bafa8d 100644
--- a/swig/typemap_samples.i
+++ b/swig/typemap_samples.i
@@ -62,7 +62,7 @@
     }
 
     PyObject* in_array01_1(SpiceInt *arg, SpiceInt dim) {
-        if (dim == 0) {
+        if (dim == NO_ARRAY_DIMENSION) {
             return Py_BuildValue("i", arg[0]);
         }
         return int_array_to_tuple(arg, dim);
@@ -103,13 +103,23 @@ PyObject* in_array01_1(SpiceInt *arg, SpiceInt dim);
    }
 
    PyObject *in_array12(SpiceInt *arg, SpiceInt dim1, SpiceInt dim2) {
-       PyObject* info = int_array_to_tuple(arg, max(dim1, 1) * dim2);
-       return Py_BuildValue("Nii", info, dim1, dim2);
+       SpiceInt xdim1 = dim1 == NO_ARRAY_DIMENSION ? 1 : dim1;
+       PyObject* info = int_array_to_tuple(arg, xdim1 * dim2);
+       if (dim1 != NO_ARRAY_DIMENSION) {
+           return Py_BuildValue("Nii", info, dim1, dim2);
+       } else {
+           return Py_BuildValue("Ni", info, dim2);
+       }
    }
 
    PyObject *in_array23(SpiceInt *arg, SpiceInt dim1, SpiceInt dim2, SpiceInt dim3) {
-       PyObject* info = int_array_to_tuple(arg, max(dim1, 1) * dim2 * dim3);
-       return Py_BuildValue("Niii", info, dim1, dim2, dim3);
+       SpiceInt xdim1 = dim1 == NO_ARRAY_DIMENSION ? 1 : dim1;
+       PyObject* info = int_array_to_tuple(arg, xdim1 * dim2 * dim3);
+       if (dim1 != NO_ARRAY_DIMENSION) {
+           return Py_BuildValue("Niii", info, dim1, dim2, dim3);
+       } else {
+           return Py_BuildValue("Nii", info, dim2, dim3);
+       }
    }
 %}
 
@@ -159,7 +169,7 @@ PyObject *in_array23(SpiceInt *arg, SpiceInt dim1, SpiceInt dim2, SpiceInt dim3)
     }
 
     void out_array01_malloc(SpiceDouble start, SpiceInt length, SpiceDouble **arrayP, SpiceInt *size) {
-        SpiceInt real_length = max(length, 1);
+        SpiceInt real_length = length == NO_ARRAY_DIMENSION ? 1 : length;
         *size = length;
         if (start >= 0) {
             *arrayP = PyMem_Malloc(real_length * sizeof(SpiceDouble));
@@ -236,7 +246,7 @@ void out_array01_malloc(SpiceDouble start, SpiceInt length, SpiceDouble **arrayP
     void out_array12_1(SpiceInt start, SpiceInt length1, SpiceInt length2, SpiceInt **result, SpiceInt *size1, SpiceInt *size2) {
         SpiceInt *ptr = NULL;
         if (start >= 0) {
-            SpiceInt xlength1 = max(length1, 1);
+            SpiceInt xlength1 = length1 == NO_ARRAY_DIMENSION ? 1 : length1;
             ptr = PyMem_Malloc(xlength1 * length2 * sizeof(SpiceInt));
             for (SpiceInt i = 0; i < xlength1 * length2; i++) {
                 ptr[i] = start + i;
@@ -251,7 +261,7 @@ void out_array01_malloc(SpiceDouble start, SpiceInt length, SpiceDouble **arrayP
                        SpiceDouble **result, SpiceInt *size1, SpiceInt *size2, SpiceInt *size3) {
         SpiceDouble *ptr = NULL;
         if (start >= 0) {
-            SpiceInt xlength1 = max(length1, 1);
+            SpiceInt xlength1 = length1 == NO_ARRAY_DIMENSION ? 1 : length1;
             ptr = PyMem_Malloc(xlength1 * length2 * length3 * sizeof(SpiceDouble));
             for (SpiceInt i = 0; i < xlength1 * length2; i++) {
                 ptr[i] = start + i;


Test patch:
diff --git a/cspyce/typemap_test.py b/cspyce/typemap_test.py
index 7dab035..e6b8caf 100644
--- a/cspyce/typemap_test.py
+++ b/cspyce/typemap_test.py
@@ -10,6 +10,8 @@
 from cspyce import SPICE_CELL_DOUBLE, SPICE_CELL_INT, SpiceCell
 
 
+NO_ARRAY_DIMENSION = -1
+
 def flatten(array):
     return tuple(tuple(array.ravel()))
 
@@ -131,9 +133,7 @@ def test_requires_non_null(self):
 class Test_IN_ARRAY01_GivenDimension:
     # %apply (int *IN_ARRAY01, int DIM1) {(int *arg, int dim)};
     # cs.in_array01_1 received either an int scalar or sequence of integer, and
-    SMALL_INT_ARRAY = np.array((4, 5, 6), dtype="int32")
-    SMALL_FLOAT_ARRAY = np.array((4.0, 5.0, 6.0), dtype="double")
-
+    # returns either that integer or the array as a tuple.
     def test_basic_test_scalar(self):
         assert 1 == ts.in_array01_1(1)
 
@@ -144,6 +144,10 @@ def test_basic_test_int_array(self):
         arg = np.arange(1, 10, dtype='int32')
         assert flatten(arg) == ts.in_array01_1(arg)
 
+    def test_empty_array(self):
+        arg = np.array((), dtype=int)
+        assert () == ts.in_array01_1(arg)
+
     def test_non_contiguous_array(self):
         array = np.arange(12, dtype='int32').reshape((4, 3))[:, 0]
         assert flatten(array) == ts.in_array01_1(array)
@@ -312,25 +316,25 @@ class Test_IN_ARRAY12_VariableDimensions:
     # then dim1 == 0.  As usual, this returns the array elements, and the dimension
     def test_run_1d(self):
         array = np.arange(100, 150, dtype='int32')
-        assert (flatten(array), 0, array.size) == ts.in_array12(array)
+        assert (flatten(array), array.size) == ts.in_array12(array)
 
     def test_run_2d(self):
         array = np.arange(100, 150, dtype='int32').reshape((10, 5))
-        expected_result = (flatten(array),) + tuple(array.shape)
+        expected_result = (flatten(array), *array.shape)
         assert expected_result == ts.in_array12(array)
 
     def test_run_on_tuple(self):
         value = (2, 3, 5, 7, 11)
-        assert (value, 0, len(value)) == ts.in_array12(value)
+        assert (value, len(value)) == ts.in_array12(value)
 
     def test_non_contiguous_array_2d(self):
         array = np.arange(150, dtype='int32').reshape((3, 5, 10))[..., 1]
-        expected_result = (flatten(array),) + tuple(array.shape)
+        expected_result = (flatten(array), *array.shape)
         assert expected_result == ts.in_array12(array)
 
     def test_non_contiguous_array_1d(self):
         array = np.arange(150, dtype='int32').reshape((3, 5, 10))[1, :, 2]
-        assert (flatten(array), 0, array.size) == ts.in_array12(array)
+        assert (flatten(array), array.size) == ts.in_array12(array)
 
     def test_no_other_data_type(self):
         array = np.arange(100, 150, dtype='double')
@@ -352,7 +356,7 @@ class Test_IN_ARRAY23_VariableDimensions:
     # then dim1 == 0.  As usual, this returns the array elements, and the dimension
     def test_run_2d(self):
         array = np.arange(100, 150, dtype='int32').reshape((10, 5))
-        expected_result = (flatten(array), 0) + tuple(array.shape)
+        expected_result = (flatten(array), *array.shape)
         assert expected_result == ts.in_array23(array)
 
     def test_run_3d(self):
@@ -362,7 +366,7 @@ def test_run_3d(self):
 
     def test_non_contiguous_array_2d(self):
         array = np.arange(150, dtype='int32').reshape((3, 5, 10))[..., 1]
-        assert (flatten(array), 0, 3, 5) == ts.in_array23(array)
+        assert (flatten(array), 3, 5) == ts.in_array23(array)
 
     def test_non_contiguous_array_3d(self):
         array = np.empty((3, 4, 5, 8), dtype='int32')[..., 2]
@@ -418,7 +422,7 @@ class Test_OUT_ARRAY01_MallocedArray:
     # Again, a malloced array, but if the function indicates size 0, then we want a scalar
     # Again, start and length, but this time we use floats
     def test_return_scalar(self):
-        result = ts.out_array01_malloc(5.0, 0)
+        result = ts.out_array01_malloc(5.0, NO_ARRAY_DIMENSION)
         assert type(result) == float
         assert 5.0 == result
 
@@ -426,6 +430,10 @@ def test_return_1d(self):
         result = ts.out_array01_malloc(5.0, 2)
         assert (5.0, 6.0) == flatten(result)
 
+    def test_return_empty_1d(self):
+        result = ts.out_array01_malloc(5.0, 0)
+        assert result.size == 0
+
     def test_memory_error(self):
         with pytest.raises(MemoryError):
             ts.out_array01_malloc(-1.0, 10)
@@ -447,7 +455,7 @@ def test_variable_size_array(self):
         assert (10, 2) == value.shape
         assert 30 == value[0, 0]
 
-    # %apply (int **OUT_ARRAY12, int *SIZE1, int *SIZE2) {(int **result, int *size1, int *size2)};
+    # %apply (int **OUT_ARRAY2, int *SIZE1, int *SIZE2) {(int **result, int *size1, int *size2)};
     # Malloced array.  Our function has arguments start value, and dim1, dim2
     # If the start value is < 0, instead, it simulates a malloc failure
     def test_malloced_array(self):
@@ -475,16 +483,20 @@ def test_2dim_fixed_size_array(self):
 
 class Test_OUT_ARRAY12_FixedSize:
     # %apply (int **OUT_ARRAY12, int *SIZE1, int *SIZE2) {(int **result, int *size1, int *size2)};
-    # Same as before, but a dim1=0 indicates to return a 1-dimensional array
+    # Same as before, but a dim1=NO_ARRAY_DIMENSION indicates to return a 1-dimensional array
     def test_2d_array(self):
-        value1 = ts.out_array12_1(25, 40, 41)
-        assert (40, 41) == value1.shape
-        assert 25 == value1[0, 0]
+        value = ts.out_array12_1(25, 40, 41)
+        assert (40, 41) == value.shape
+        assert 25 == value[0, 0]
 
     def test_generating_1d_array(self):
-        value2 = ts.out_array12_1(25, 0, 41)
-        assert (41,) == value2.shape
-        assert 25 == value2[0]
+        value = ts.out_array12_1(25, NO_ARRAY_DIMENSION, 41)
+        assert (41,) == value.shape
+        assert 25 == value[0]
+
+    def test_generating_1d_empty_array(self):
+        value = ts.out_array12_1(25, 0, 41)
+        assert (0, 41) == value.shape
 
     def test_memory_error(self):
         with pytest.raises(MemoryError):
@@ -495,14 +507,18 @@ class Test_OUT_ARRAY23_FixedSize:
     # %apply (double **OUT_ARRAY23, int *SIZE1, int *SIZE2, int *SIZE3) {(double **result, int *size1, int *size2, int *size3)};
     # Same as before, but 2 or 3 dimensions
     def test_yields_3d(self):
-        value1 = ts.out_array23_1(25, 3, 4, 5)
-        assert (3, 4, 5) == value1.shape
-        assert 25 == value1[0, 0, 0]
+        value = ts.out_array23_1(25, 3, 4, 5)
+        assert (3, 4, 5) == value.shape
+        assert 25 == value[0, 0, 0]
 
     def test_yields_2d(self):
-        value2 = ts.out_array23_1(25, 0, 4, 5)
-        assert (4, 5) == value2.shape
-        assert 25 == value2[0, 0]
+        value = ts.out_array23_1(25, NO_ARRAY_DIMENSION, 4, 5)
+        assert (4, 5) == value.shape
+        assert 25 == value[0, 0]
+
+    def test_yields_2d_empty(self):
+        value = ts.out_array23_1(25, 0, 4, 5)
+        assert (0, 4, 5) == value.shape
 
     def test_memory_error(self):
         with pytest.raises(MemoryError):
@@ -775,6 +791,9 @@ def test_creation_from_swig(self):
         t3 = SpiceCell.as_spice_cell(SPICE_CELL_DOUBLE, ())
         assert isinstance(t3[0], float)
 
+        with pytest.raises(ValueError):
+            SpiceCell.as_spice_cell(-1, ())
+
     def test_append(self):
         t1 = SpiceCell(typeno=SPICE_CELL_INT, size=10)
         old_size = t1.size
@@ -810,6 +829,46 @@ def test_indexing(self):
         t1[1], t1[-2] = 20, 30
         assert tuple(t1) == (1, 20, 30, 4)
 
+    def test_size(self):
+        t1 = SpiceCell(typeno=SPICE_CELL_INT, size=10)
+        t1.card = 10
+        assert t1.size == 10
+        # Growing the size doesn't affect the cardinality
+        t1.size = 20
+        assert t1.size == 20 and t1.card == 10
+        # Shrinking the size can change the cardinality
+        t1.card = 15
+        t1.size = 10
+        assert t1.size == t1.card == 10
+
+    def test_size_error(self):
+        t1 = SpiceCell(typeno=SPICE_CELL_INT, size=10)
+        with pytest.raises(ValueError):
+            t1.size = -1
+        with pytest.raises(ValueError):
+            t1.size = 5.7
+        with pytest.raises(ValueError):
+            t1.size = None
+
+    def test_card_error(self):
+        t1 = SpiceCell(typeno=SPICE_CELL_INT, size=10)
+        with pytest.raises(ValueError):
+            t1.card = -1
+        with pytest.raises(ValueError):
+            t1.card = 5.7
+        with pytest.raises(ValueError):
+            t1.card = None
+        with pytest.raises(ValueError):
+            t1.card = t1.size + 1
+
+    def test_boolean(self):
+        t1 = SpiceCell(typeno=SPICE_CELL_INT, size=10)
+        assert not t1
+        t1.append(10)
+        assert t1
+
+
+
 class Test_SpiceCell_Typemap():
     def test_spice_cell_in(self):
         cell = SpiceCell((1, 2, 3, 4))
@@ -828,7 +887,7 @@ def test_string(self):
         assert ts.decode_filename('a/b/string.txt') == 'a/b/string.txt'
 
     def test_byte_array(self):
-        assert ts.decode_filename(b'a/b/bytes.bin') =='a/b/bytes.bin'
+        assert ts.decode_filename(b'a/b/bytes.bin') == 'a/b/bytes.bin'
 
     def test_Path(self):
         expected_value = os.sep.join(["a", "b", "filepath"])
diff --git a/unittests/test_t_x.py b/unittests/test_t_x.py
index 306403f..1921617 100644
--- a/unittests/test_t_x.py
+++ b/unittests/test_t_x.py
@@ -394,12 +394,21 @@ def test_utc2et():
     assert output == 155185515.1831043
     # icy utc2et example gives 1.5518552e+08 as output
 
-
 def test_vadd():
     v1 = [1.0, 2.0, 3.0]
     v2 = [4.0, 5.0, 6.0]
     npt.assert_array_almost_equal(cs.vadd(v1, v2), [5.0, 7.0, 9.0])
 
+# This test is temporary until we have a better structure for dealing with vectors.
+# But we need to test that 0-length vectors are handled properly.
+def test_vadd_vector():
+    v1 = np.array(((1.0, 2.0, 3.0), (4.0, 5.0, 6.0)))
+    v2 = np.zeros((0, 3), dtype=float)
+
+    # Normal add works
+    npt.assert_array_almost_equal(cs.vadd.vector(v1, (100, 200, 300)), v1 + (100, 200, 300))
+    # If there is an empty array, then the result is an empty array.
+    assert cs.vadd.vector(v1, v2).shape == (0, 3)
 
 def test_vaddg():
     v1 = [1.0, 2.0, 3.0]