views:

83

answers:

1

I'm playing with contract.py, Terrence Way's reference implementation of design-by-contract for Python. The implementation throws an exception when a contract (precondition/postcondition/invariant) is violated, but it doesn't provide you a quick way of identifying which specific contract has failed if there are multiple ones associated with a method.

For example, if I take the circbuf.py example, and violate the precondition by passing in a negative argument, like so:

circbuf(-5)

Then I get a traceback that looks like this:

Traceback (most recent call last):
  File "circbuf.py", line 115, in <module>
    circbuf(-5)
  File "<string>", line 3, in __assert_circbuf___init___chk
  File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1204, in call_constructor_all
  File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1293, in _method_call_all
  File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1332, in _call_all
  File "build/bdist.macosx-10.5-i386/egg/contract.py", line 1371, in _check_preconditions
contract.PreconditionViolationError: ('__main__.circbuf.__init__', 4)

My hunch is that the second argument in the PreconditionViolationError (4) refers to the line number in the circbuf.init docstring that contains the assertion:

def __init__(self, leng):
    """Construct an empty circular buffer.

    pre::
        leng > 0
    post[self]::
        self.is_empty() and len(self.buf) == leng
    """

However, it's a pain to have to open the file and count the docstring line numbers. Does anybody have a quicker solution for identifying which contract has failed?

(Note that in this example, there's a single precondition, so it's obvious, but multiple preconditions are possible).

+1  A: 

Without modifying his code, I don't think you can, but since this is python...

If you look for where he raises the exception to the user, it I think is possible to push the info you're looking for into it... I wouldn't expect you to be able to get the trace-back to be any better though because the code is actually contained in a comment block and then processed.

The code is pretty complicated, but this might be a block to look at - maybe if you dump out some of the args you can figure out whats going on...

def _check_preconditions(a, func, va, ka):
    # ttw006: correctly weaken pre-conditions...
    # ab002: Avoid generating AttributeError exceptions...
    if hasattr(func, '__assert_pre'):
        try:
            func.__assert_pre(*va, **ka)
        except PreconditionViolationError, args:
            # if the pre-conditions fail, *all* super-preconditions
            # must fail too, otherwise
            for f in a:
                if f is not func and hasattr(f, '__assert_pre'):
                    f.__assert_pre(*va, **ka)
                    raise InvalidPreconditionError(args)
            # rr001: raise original PreconditionViolationError, not
            # inner AttributeError...
            # raise
            raise args
            # ...rr001
    # ...ab002
    # ...ttw006
Petriborg