[TIP] branch coverage

Andrew Dalke dalke at dalkescientific.com
Mon Jan 28 16:57:25 PST 2008


I have a preliminary PLY parser for Python.  I'm working on the code  
instrumentation part.  I'm not sure about what to trace, and am  
looking for feedback.

Take this as an example

1 + (a or b) + stop() + 1/0; print "Done."

def stop(): raise SystemExit()


The code ' + 1/0; print "Done."' never gets executed because the stop 
() raises an exception

There seem to be a few things I can trace.  These are:

  - statement is reached (but not necessarily fully executed)

reached("expr-statement line 1 (0:27)")
1 + (a or b) + stop() + 1/0
reached("print-statement line 1 (29:40)")
print "Done."

This is comparable to what's done now, with the advantage that I can  
instrument the .pyc file and do full coverage of Python standard  
library components like 'os.py'.


   - track the start of all normal code execution branches (if,while,  
short-circuit, etc.)

1 + ($(a:or-test 5:6) or $(b:or-test 10:11) + stop() + 1/0
print "Done."

where the $() is a syntax I just made up, but I hope it's  
understandable.  If you want it expressed in Python code, it's  
something like:

def _trace_expr1():
     stack = []
     stack.append(1)
     stack.append(a)
     if not stack[-1]:
       was_false("or-test 5:6")
       stack[-1] = b
       if not stack[-1]:
         was_false("or-test 10:11")
         stack[-1] = False
       else:
         was_true("or-test 10:11")
     else:
       was_true("or-test 5:6")

     stack[-2:] = [stack[-2]+stack[-1]]
     stack[-1] = stack[-1] + stop() + 1/0
     return stack[-1]
_trace_expr1()


   - Every operation is completed


    reached("expr-statement line 1 (0:27)")
    stack = []
    stack.append(1)
    stack.append(a)
    success("name line 1 (5:6)")
    if not stack[-1]:
      stack[-1] = b
      success("name line 1 (10:11)")
      if not stack[-1]:
        stack[-1] = False

    stack[-2:] = [stack[-2] + stack[-1]]
    success("add line 1 (0:12)")

    stack.append(stop())
    success("call line 1 (15:21)")
    stack.append(1)
    stack.append(0)
    stack[-2:] = [stack[-2] / stack[-1]]
    success("div line 1 (24:26)")
    stack[-2:] = [stack[-2] + stack[-1]]
    success("add line 1 (0:26)")
    stack.pop()
    finished("expr-statement line 1 (0:27)")


This is the only one that can report that stop() was called, but that  
+ 1/0 never occurred.

This much instrumentation will cause a huge performance hit because  
each of the logging calls corresponds to a function call.

Another problem with the last is that sometime you *want* functions  
to raise an exception, as with:

opt_parser.error("cannot mix --oil and --water command-line parameters")

or in this somewhat unusual case (and IMO poor style)

    a = x or y or die("either 'x' or 'y' must be true")

Exceptions will likely cause a lot of false positives, and I don't  
like that.  It means people won't use this sort of tool.



The short version of this might be

Given the Python program

   a = 0
   try:
     a or b()
   except NameError:
     pass

how important is it to notice that the () in b() is never called?


Hmm... I could have something which reports that the *last*  
instruction in a statement is executed, and just not care if it  
throws an exception.  Nahh, I need to do that on every branch, so  
that the end of all branches of

   1 + (a or b()) + (c or d())

is checked.

Does anyone have experience and comments about this?


				Andrew
				dalke at dalkescientific.com





More information about the testing-in-python mailing list