Sunday 5 December 2004 — This is over 18 years old. Be careful.
I’ve been hacking on code this weekend, and I’ve managed to get myself stuck. I started using Gareth Rees’ coverage.py to measure the code coverage of some unit tests. It works well, but counts docstrings as missed lines of code. I have a lot of docstrings, so the results were too noisy to be useful. I wanted to fix it so that it understood docstrings for what they were. I also wanted to add some other features, for example, the ability to mark lines of code as not expected to be run, so that they would not be counted as missed lines.
So I dug into the code. It works by using the debugging trace hook to record which lines of code are executed, and parsing the source of the modules to understand which lines are executable. I decided to switch it from the parser module to the compiler module for the parsing half of the job. The parser module returns a low-level, grammar-centric representation of the source text, making it difficult to distinguish a docstring from any other expression statement. The compiler module is higher-level, returning a tree of nodes that corresponds more to the semantics of the program. It seemed like a no-brainer, and it all went very well.
Then I wanted to add another feature, so that an entire suite of statements (the Python equivalent of a block in other languages) could be marked as “not expected to be run”. For example, if a module has a chunk of code at the end to allow it to be run from the command line, it would be nice if the entire suite could be marked without having to put a marker comment on every line. So a single marker could do it like this:
if __name__ == '__main__': #-notrun-
And if we’re going to do that, then it should work uniformly for all suites:
# this code
# will be run
# this code
# won't be run
And there’s the problem: the compiler module completely discards any trace of the else. It has both suites of code, but the actual line with the “else:” on it isn’t represented in the parse tree at all. So it’s impossible to match up line-oriented regular expression results (finding the markers) with the parse tree results (what code does it apply to?).
So I’m pondering my options. Go back to the old parse technique, and hack up something to exclude docstrings? Disallow excluding “else” suites? Do something else completely? It had all been going so well. Sigh.
I've used trace.py from the Python standard library for checking unit test coverage. It knows about docstrings. It marks lines containing 'finally:' as not executed, even when the finally block itself was executed, but that bug was easy to fix. I still haven't gotten around to submitting this bug to Python's bug tracker, bad me.
The SchoolTool test runner has an option (--coverage) to produce code coverage reports while running unit tests. You can find it at http://source.schooltool.org/, however it is GPLed. IIRC the Zope 3 test runner also supports coverage analysis with trace.py.
Oh, and before you ask -- I do not think trace.py allows you to mark code blocks as expected to not be executed. It lets you mark individual lines with #pragma: NO COVER, though.
I'm sick of the whole thing today, but I'll probably investigate the options later this week. There's also sancho,
which is a unit test framework including coverage analysis.
I would love to see a decent test runner with code coverage reports (hopefully using hotshot to avoid the time penalties).
If you do make progress on this let me know and Ill pass it on to a few folks on my team for testing or co-development opportunities.
$ python -h
-OO : remove doc-strings in addition to the -O optimizations
Levi: interesting idea. As it happens, I've managed to properly ignore the docstrings, and am trying to chase down excluding entire suites of code from the coverage.
There is a slide set from one of the Zope guys on hotshot, I cant seem to find the link for it at the moment but it does mention the coverage capabilities.
(I am currently trying to use hotshot for coverage analysis, but hotshot's coverage support is very undocumented and low level.)
Add a comment: