Mocking GEOS

My use of mocks isn't as sophisticated as Dave's, perhaps, but I stumbled onto a simple testing pattern that might be useful to other Python geospatial/GIS developers who are wrapping C libs using ctypes.

Consider Shapely: it wraps the GEOS library, the quality and accuracy of which we take as a given (though not blindly, because I do contribute fixes and enhancements to GEOS). The predicates and topological functions of GEOS are called from within Python descriptors, classes that perform argument validation and handle GEOS errors. For Shapely, I'm testing these descriptors, the GEOS wrappers, not GEOS itself. What pair of geometries would I have to pass to GEOSDisjoint (for example) in order to get the return value of 2 that signifies an error? Even if known, they might be subject to issues of numerical precision, or be sensitive to changes in GEOS. I'd rather not fuss with this. Instead, I want some function to stand in for GEOSDisjoint and friends, one that takes 2 arguments and has very predictable return values in the range (0, 1, 2). A function like libc's strcmp():

>>> import ctypes
>>> libc = ctypes.CDLL('libc.dylib') # this is OS X
>>> libc.strcmp('\0', '\0')
0
>>> libc.strcmp('\1', '\0')
1
>>> libc.strcmp('\2', '\0')
2

Meaningless, but handy, isomorphism between strcmp() and GEOS binary operations in hand, a generic wrapper for GEOS can be fully tested like this:

import ctypes
import unittest

from shapely import predicates

BN = libc.strcmp

class CompMockGeom(object):
    # Values chosen with libc.strcmp in mind
    vals = {'0': '\0', '1': '\1', '2': '\2'}
    def __init__(self, cat):
        self._geom = ctypes.c_char_p(self.vals[cat])
    comp = predicates.BinaryPredicate(BN)

class BinaryPredicateAttributeTestCase(unittest.TestCase):

    def test_bin_false(self):
        g1 = CompMockGeom('0')
        g2 = CompMockGeom('0')
        self.assertEquals(g1.comp(g2), False)

    def test_bin_true(self):
        g1 = CompMockGeom('1')
        g2 = CompMockGeom('0')
        self.assertEquals(g1.comp(g2), True)

    def test_bin_error(self):
        g1 = CompMockGeom('2')
        g2 = CompMockGeom('0')
        self.assertRaises(predicates.PredicateError, g1.comp, g2)

Comments

Re: Mocking GEOS

Author: ajfowler

Hi, Unrelated to this post, but I came across one of your old blog posts about web-mapping accessibility. Is this topic off of your radar now? aj

Re: Mocking GEOS

Author: Sean

Yes, but it looks like it's on yours. What's up with web map accessibility?

Re: Mocking GEOS

Author: ajfowler

Well I'm looking into creating a text description of a map. There aren't a lot of resources out there, but I'm avidly searching.