Testing the numpy.i typemaps#
Introduction#
Writing tests for thenumpy.iSWIGinterface file is a combinatorial headache. At present, 12 differentdata types are supported, each with 74 different argument signatures,for a total of 888 typemaps supported “out of the box”. Each of thesetypemaps, in turn, might require several unit tests in order to verifyexpected behavior for both proper and improper inputs. Currently,this results in more than 1,000 individual unit tests executed whenmaketest is run in thenumpy/tools/swig subdirectory.
To facilitate this many similar unit tests, some high-levelprogramming techniques are employed, including C andSWIG macros,as well as Python inheritance. The purpose of this document is to describethe testing infrastructure employed to verify that thenumpy.itypemaps are working as expected.
Testing organization#
There are three independent testing frameworks supported, for one-,two-, and three-dimensional arrays respectively. For one-dimensionalarrays, there are two C++ files, a header and a source, named:
Vector.hVector.cxx
that contain prototypes and code for a variety of functions that haveone-dimensional arrays as function arguments. The file:
Vector.i
is aSWIG interface file that defines a python moduleVectorthat wraps the functions inVector.h while utilizing the typemapsinnumpy.i to correctly handle the C arrays.
TheMakefile callsswig to generateVector.py andVector_wrap.cxx, and also executes thesetup.py script thatcompilesVector_wrap.cxx and links together the extension module_Vector.so or_Vector.dylib, depending on the platform. Thisextension module and the proxy fileVector.py are both placed in asubdirectory under thebuild directory.
The actual testing takes place with a Python script named:
testVector.py
that uses the standard Python library moduleunittest, whichperforms several tests of each function defined inVector.h foreach data type supported.
Two-dimensional arrays are tested in exactly the same manner. Theabove description applies, but withMatrix substituted forVector. For three-dimensional tests, substituteTensor forVector. For four-dimensional tests, substituteSuperTensorforVector. For flat in-place array tests, substituteFlatforVector.For the descriptions that follow, we will reference theVector tests, but the same information applies toMatrix,Tensor andSuperTensor tests.
The commandmaketest will ensure that all of the test software isbuilt and then run all three test scripts.
Testing header files#
Vector.h is a C++ header file that defines a C macro calledTEST_FUNC_PROTOS that takes two arguments:TYPE, which is adata type name such asunsignedint; andSNAME, which is ashort name for the same data type with no spaces, e.g.uint. Thismacro defines several function prototypes that have the prefixSNAME and have at least one argument that is an array of typeTYPE. Those functions that have return arguments return aTYPE value.
TEST_FUNC_PROTOS is then implemented for all of the data typessupported bynumpy.i:
signedcharunsignedcharshortunsignedshortintunsignedintlongunsignedlonglonglongunsignedlonglongfloatdouble
Testing source files#
Vector.cxx is a C++ source file that implements compilable codefor each of the function prototypes specified inVector.h. Itdefines a C macroTEST_FUNCS that has the same arguments and worksin the same way asTEST_FUNC_PROTOS does inVector.h.TEST_FUNCS is implemented for each of the 12 data types as above.
Testing SWIG interface files#
Vector.i is aSWIG interface file that defines python moduleVector. It follows the conventions for usingnumpy.i asdescribed in this chapter. It defines aSWIG macro%apply_numpy_typemaps that has a single argumentTYPE.It uses theSWIG directive%apply to apply the providedtypemaps to the argument signatures found inVector.h. This macrois then implemented for all of the data types supported bynumpy.i. It then does a%include"Vector.h" to wrap all ofthe function prototypes inVector.h using the typemaps innumpy.i.
Testing Python scripts#
Aftermake is used to build the testing extension modules,testVector.py can be run to execute the tests. As with otherscripts that useunittest to facilitate unit testing,testVector.py defines a class that inherits fromunittest.TestCase:
classVectorTestCase(unittest.TestCase):
However, this class is not run directly. Rather, it serves as a baseclass to several other python classes, each one specific to aparticular data type. TheVectorTestCase class stores two stringsfor typing information:
- self.typeStr
A string that matches one of the
SNAMEprefixes used inVector.handVector.cxx. For example,"double".- self.typeCode
A short (typically single-character) string that represents adata type in numpy and corresponds to
self.typeStr. Forexample, ifself.typeStris"double", thenself.typeCodeshould be"d".
Each test defined by theVectorTestCase class extracts the pythonfunction it is trying to test by accessing theVector module’sdictionary:
length=Vector.__dict__[self.typeStr+"Length"]
In the case of double precision tests, this will return the pythonfunctionVector.doubleLength.
We then define a new test case class for each supported data type witha short definition such as:
classdoubleTestCase(VectorTestCase):def__init__(self,methodName="runTest"):VectorTestCase.__init__(self,methodName)self.typeStr="double"self.typeCode="d"
Each of these 12 classes is collected into aunittest.TestSuite,which is then executed. Errors and failures are summed together andreturned as the exit argument. Any non-zero result indicates that atleast one test did not pass.