diff options
-rw-r--r-- | CHANGES.txt | 5 | ||||
-rw-r--r-- | TODO.txt | 2 | ||||
-rw-r--r-- | coverage/parser.py | 28 | ||||
-rw-r--r-- | test/test_parser.py | 36 |
4 files changed, 62 insertions, 9 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index d630b76b..8f934252 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,9 +7,12 @@ Version 3.2b2 - Fixed some problems syntax coloring sources with line continuations and
source with tabs: `issue 30`_ and `issue 31`_.
-
+
+- Classes are no longer incorrectly marked as branches: `issue 32`_.
+
.. _issue 30: http://bitbucket.org/ned/coveragepy/issue/30
.. _issue 31: http://bitbucket.org/ned/coveragepy/issue/31
+.. _issue 32: http://bitbucket.org/ned/coveragepy/issue/32
Version 3.2b1, 10 November 2009
@@ -174,7 +174,7 @@ x Why can't you specify execute (-x) and report (-r) in the same invocation? + Switch to a real test runner, like nose.
+ Test both the C trace function and the Python trace function.
-- parser.py has no direct tests.
++ parser.py has no direct tests.
- Tests about the .coverage file.
+ Tests about the --long-form of arguments.
+ Tests about overriding the .coverage filename.
diff --git a/coverage/parser.py b/coverage/parser.py index 760e4e47..1e8b7792 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -45,6 +45,9 @@ class CodeParser(object): # The line numbers of docstring lines. self.docstrings = set() + # The line numbers of class definitions. + self.classdefs = set() + # A dict mapping line numbers to (lo,hi) for multi-line statements. self.multiline = {} @@ -94,6 +97,11 @@ class CodeParser(object): indent += 1 elif toktype == token.DEDENT: indent -= 1 + elif toktype == token.NAME and ttext == 'class': + # Class definitions look like branches in the byte code, so + # we need to exclude them. The simplest way is to note the + # lines with the 'class' keyword. + self.classdefs.add(slineno) elif toktype == token.OP and ttext == ':': if not excluding and elineno in self.excluded: # Start excluding a suite. We trigger off of the colon @@ -203,7 +211,7 @@ class CodeParser(object): """ excluded_lines = self.first_lines(self.excluded) exit_counts = {} - for l1,l2 in self.arcs(): + for l1, l2 in self.arcs(): if l1 == -1: continue if l1 in excluded_lines: @@ -212,6 +220,10 @@ class CodeParser(object): exit_counts[l1] = 0 exit_counts[l1] += 1 + # Class definitions have one extra exit, so remove one for each: + for l in self.classdefs: + exit_counts[l] -= 1 + return exit_counts exit_counts = expensive(exit_counts) @@ -604,7 +616,7 @@ class AdHocMain(object): else: print("Chunks: %r" % chunks) arcs = bp._all_arcs() - print("Arcs: %r" % arcs) + print("Arcs: %r" % sorted(arcs)) if options.source or options.tokens: cp = CodeParser(filename=filename, exclude=r"no\s*cover") @@ -624,13 +636,15 @@ class AdHocMain(object): m0 = m1 = m2 = m3 = a = ' ' if lineno in cp.statement_starts: m0 = '-' - if lineno in cp.docstrings: - m1 = '"' - if lineno in cp.excluded: - m2 = 'x' exits = exit_counts.get(lineno, 0) if exits > 1: - m3 = str(exits) + m1 = str(exits) + if lineno in cp.docstrings: + m2 = '"' + if lineno in cp.classdefs: + m2 = 'C' + if lineno in cp.excluded: + m3 = 'x' a = arc_chars.get(lineno, '').ljust(arc_width) print("%4d %s%s%s%s%s %s" % (lineno, m0, m1, m2, m3, a, ltext) diff --git a/test/test_parser.py b/test/test_parser.py new file mode 100644 index 00000000..5a66f873 --- /dev/null +++ b/test/test_parser.py @@ -0,0 +1,36 @@ +"""Tests for Coverage.py's code parsing.""" + +import os, sys, textwrap + +sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k +from coveragetest import CoverageTest + +from coverage.parser import CodeParser + + +class ParserTest(CoverageTest): + """Tests for Coverage.py's code parsing.""" + + def parse_source(self, text): + """Parse `text` as source, and return the `CodeParser` used.""" + text = textwrap.dedent(text) + cp = CodeParser(text) + cp.parse_source() + return cp + + def test_exit_counts(self): + cp = self.parse_source("""\ + # check some basic branch counting + class Foo: + def foo(self, a): + if a: + return 5 + else: + return 7 + + class Bar: + pass + """) + self.assertEqual(cp.exit_counts(), { + 2:1, 3:1, 4:2, 5:1, 7:1, 9:1, 10:1 + }) |