[TIP] branch coverage

Michael Foord fuzzyman at voidspace.org.uk
Thu Jan 24 13:56:07 PST 2008


Andrew Dalke wrote:
> Hi all,
>
> m h sesquile at gmail.com:
>   
>>  I really don't want to write a python compiler, but
>> am assuming tracing at that level won't be supported in cpython, but
>> perhaps might in pypy....
>>     
>
>
> On Jan 24, 2008, at 12:32 PM, Laura Creighton wrote:
>   
>> I think that Andrew Dalke, who is cc'd to this note, has already done
>> a bunch of work which would be very useful to you.
>>     
>
> I was talking with Laura a few days ago about a lightning talk I  
> wanted to present at PyCon - branch coverage.  There's no easy way to  
> do it with Python.  The closest is to use the compiler module to  
> generate the AST then instrument the AST.  The problem is, the  
> compiler module is a bear to work with and doesn't record everything  
> I want.  For example, in the coverage report I want to see which  
> branches weren't covered, pinpointed to the character range of the  
> expression.  Python's AST doesn't have byte positions.
>
> What I've been doing over the last couple of days is getting a PLY  
> grammar for Python.  As of last night it parses (and builds the  
> trivial concrete syntax tree) of the entire standard library.  After  
> the AST works I plan to convert code like this (from line 548 of  
> subprocess.py)
>
>   

A PLY grammar for Python is *extremely* interested - for entirely 
unrelated reasons. :-)

Michael

>      if close_fds and (stdin is not None or stdout is not None or
>                        stderr is not None):
>          raise ValueError("close_fds is not supported on Windows "
>                           "platforms if you redirect stdin/stdout/ 
> stderr")
>
> into something equivalent to this mess
>
>    __reached_statement(100)  # assuming this is statement number 100
>
>    if close_fds:
>      __branch_is_true(1)  # each branch also gets a unique id, with
>                           # some table mapping that to source file  
> and byte range
>
>      if stdin is not None:
>        __branch_is_true(2)
>        __result_bool = True
>        __result_obj = stdin
>      else:
>        __branch_is_false(2)
>
>        if stdout is not None:
>          __branch_is_true(3)
>          __result_bool = True
>        __result_obj = stdout
>        else:
>          __branch_is_false(3)
>
>          if stderr is not None:
>            __branch_is_true(4)
>            __result_bool = True
>            __result_obj = stderr
>          else:
>            __branch_is_false(4)
>            __result_bool = False
>            __result_obj = stderr
>    else:
>      __branch_is_false(1)
>      __result_bool = False
>      __result_obj = close_fds
>
>   if __result_bool:
>          __reached_statement(101)
>          raise ValueError("close_fds is not supported on Windows "
>                           "platforms if you redirect stdin/stdout/ 
> stderr")
>
> where __branch_is_true and __branch_is_false and __reached_statement  
> keep track of which branch points and lines were executed.  (Along  
> with a filename, module name, and md5 checksum to prevent version skew.)
>
>
> This horrible if statement expansion mess is needed because it's the  
> only way to keep Python guarantees:
>
>    -- short circuiting
>
>    -- the bool check is only done once per term
>
> Otherwise something like
>
>     if _bool_check(1, close_fds) and (_bool_check(2, stdin is not  
> None) ... )
>
> would work, where
>
>    def _bool_check(branch_number, obj):
>      if obj:
>        __branch_is_true(branch_number)
>      else:
>        __branch_is_false(branch_number)
>      return obj
>
> This is simple, but it calls bool(obj) twice.
>
>
>    -- the correct object is returned
>
> I had another hack which looked like
>
>      if (_about_to_call(1) and close_fds or is_false(1)) and  (...:
>
> along with some state tracking.  This handles the booleanness  
> correctly, but does not return the correct object during assignment
>
>    x = a and (b or c or d)
>
>
> Once I have the modified AST, what should I do with it?
>
> I could generate raw Python code, which would be ugly, have the  
> comments stripped out, and line numbers changed.  Or I could generate  
> byte code.
>
> If the latter, I was thinking to write a .py -> .pyc compiler, but do  
> I use it like compileall?  Or do I generate the .pyc files in another  
> directory, which is used for the coverage testing.  Where do I keep  
> the coverage results?  Probably all in a single directly, named after  
> the Python module name.
>
> Do people only care about if the branch was true/false or are the  
> number of tests also important?  What about the number of times a  
> line was executed, vs. a flag saying that it was covered?
>
>
>
>
> 				Andrew
> 				dalke at dalkescientific.com
>
>
>
> _______________________________________________
> testing-in-python mailing list
> testing-in-python at lists.idyll.org
> http://lists.idyll.org/listinfo/testing-in-python
>
>   




More information about the testing-in-python mailing list