Saturday, November 8, 2008

Checking Type Contract from IronPython

In our system we have an IronPython server, called by a C# client.
The way we expose python to the client is by declaring a C# interface, and implementing it in python.

The layer that implements that interface is actually a facade, so there's very little logic there, and I got away without writing tests for it for a long time... However, I just needed to modify it, so decided to do the right thing :-)

Most of the testing I did until now used fake objects, so the first step was to get a mock library. I downloaded Michael Foord's Mock library which looks really nice and does everything I need at this point.

One thing I wanted to test is that my class implements the C# interface correctly in terms of methods' signatures. Otherwise IronPython throws an exception when it tries to map the arguments to the python method, or when it tries to convert the return value. However, my tests are in python, and calls to the object from python don't go through the type checking/conversion parts.

To overcome this, I call the methods using .NET reflection, which mimics a C# call more accurately and goes through the type checking code. Here's the utility class that helps streamline this process:

class CallThroughCSharpInterface(DynamicProxy):
def __init__(self, obj):
DynamicProxy.__init__(self)
self._obj = obj
self._cs_type = clr.GetClrType(type(obj))

def _dispatch(self,name,*a):
mth = self._cs_type.GetMethod(name)
try:
return mth.Invoke(self._obj,a)
except TargetInvocationException, exc:
raise exc.InnerException

Now I can do something like:

py_server = MyServer()
server = CallThroughCSharpInterface(py_server)
server.foo(x,y,z)

And the call to foo is now checked for type problems.

The code above reuses a DynamicProxy class I already had. Here it is for completeness:

class DynamicProxy(object):
def __getattr__(self,name):
return DynamicProxy.CalledNameHelper(self,name)

class CalledNameHelper(object):
def __init__(self,proxy,method_name):
self.proxy = proxy
self.method_name = method_name
def __call__(self,*a,**kw):
return self.proxy._dispatch(self.method_name,*a,**kw)

This only handles the basic cases - only method calls (no fields / properties) and the interface doesn't contain any overloaded methods.
That's all I needed, so I could keep it simple :-)