summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/collector.py6
-rw-r--r--coverage/control.py35
-rw-r--r--coverage/data.py4
-rw-r--r--coverage/parser.py26
-rw-r--r--test/coveragetest.py60
-rw-r--r--test/test_arcs.py96
6 files changed, 185 insertions, 42 deletions
diff --git a/coverage/collector.py b/coverage/collector.py
index aadbab77..bc700279 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -49,7 +49,7 @@ class PyTracer:
if frame == self.last_exc_back:
# Someone forgot a return event.
if self.arcs and self.cur_file_data:
- self.cur_file_data[(self.last_line, 0)] = None
+ self.cur_file_data[(self.last_line, -1)] = None
self.cur_file_data, self.last_line = self.data_stack.pop()
self.last_exc_back = None
@@ -65,7 +65,7 @@ class PyTracer:
self.cur_file_data = self.data[tracename]
else:
self.cur_file_data = None
- self.last_line = 0
+ self.last_line = -1
elif event == 'line':
# Record an executed line.
if self.cur_file_data is not None:
@@ -76,7 +76,7 @@ class PyTracer:
self.last_line = frame.f_lineno
elif event == 'return':
if self.arcs and self.cur_file_data:
- self.cur_file_data[(self.last_line, 0)] = None
+ self.cur_file_data[(self.last_line, -1)] = None
# Leaving this function, pop the filename stack.
self.cur_file_data, self.last_line = self.data_stack.pop()
elif event == 'exception':
diff --git a/coverage/control.py b/coverage/control.py
index ce79cd30..952897dc 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -3,7 +3,7 @@
import os, socket
from coverage.annotate import AnnotateReporter
-from coverage.backward import string_class
+from coverage.backward import string_class, sorted # pylint: disable-msg=W0622
from coverage.codeunit import code_unit_factory, CodeUnit
from coverage.collector import Collector
from coverage.data import CoverageData
@@ -330,45 +330,54 @@ class Analysis:
"""The results of analyzing a code unit."""
def __init__(self, cov, code_unit):
+ self.coverage = cov
self.code_unit = code_unit
from coverage.parser import CodeParser
- filename = code_unit.filename
- ext = os.path.splitext(filename)[1]
+ self.filename = self.code_unit.filename
+ ext = os.path.splitext(self.filename)[1]
source = None
if ext == '.py':
- if not os.path.exists(filename):
- source = cov.file_locator.get_zip_data(filename)
+ if not os.path.exists(self.filename):
+ source = self.coverage.file_locator.get_zip_data(self.filename)
if not source:
raise CoverageException(
- "No source for code '%s'." % code_unit.filename
+ "No source for code '%s'." % self.filename
)
self.parser = CodeParser(
- text=source, filename=filename, exclude=cov.exclude_re
+ text=source, filename=self.filename, exclude=self.coverage.exclude_re
)
self.statements, self.excluded, line_map = self.parser.parse_source()
# Identify missing statements.
self.missing = []
- execed = cov.data.executed_lines(filename)
+ self.executed = self.coverage.data.executed_lines(self.filename)
for line in self.statements:
lines = line_map.get(line)
if lines:
for l in range(lines[0], lines[1]+1):
- if l in execed:
+ if l in self.executed:
break
else:
self.missing.append(line)
else:
- if line not in execed:
+ if line not in self.executed:
self.missing.append(line)
- self.filename = self.code_unit.filename
-
def missing_formatted(self):
return format_lines(self.statements, self.missing)
- def arc_info(self):
+ def arc_possibilities(self):
return self.parser.arc_info()
+
+ def arcs_executed(self):
+ return self.coverage.data.executed_arcs(self.filename)
+
+ def arcs_missing(self):
+ possible = self.arc_possibilities()
+ executed = self.arcs_executed()
+ missing = [p for p in possible if p not in executed]
+ return sorted(missing)
+
diff --git a/coverage/data.py b/coverage/data.py
index 26dc5aa1..0c1fc3aa 100644
--- a/coverage/data.py
+++ b/coverage/data.py
@@ -198,6 +198,10 @@ class CoverageData:
"""
return self.lines.get(filename) or {}
+ def executed_arcs(self, filename):
+ """A map containing all the arcs executed in `filename`."""
+ return self.arcs.get(filename) or {}
+
def summary(self, fullpath=False):
"""Return a dict summarizing the coverage data.
diff --git a/coverage/parser.py b/coverage/parser.py
index ba281024..9460f083 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -2,7 +2,7 @@
import glob, opcode, os, re, sys, token, tokenize
-from coverage.backward import set, StringIO # pylint: disable-msg=W0622
+from coverage.backward import set, sorted, StringIO # pylint: disable-msg=W0622
from coverage.bytecode import ByteCodes, CodeObjects
from coverage.misc import nice_pair, CoverageException
@@ -148,8 +148,7 @@ class CodeParser:
if new_l not in ignore:
lset.add(new_l)
lines = list(lset)
- lines.sort()
- return lines
+ return sorted(lines)
def parse_source(self):
"""Parse source text to find executable lines, excluded lines, etc.
@@ -169,7 +168,8 @@ class CodeParser:
def arc_info(self):
"""Get information about the arcs available in the code."""
- return self.byte_parser._all_arcs()
+ arcs = self.byte_parser._all_arcs()
+ return sorted(arcs)
class ByteParser:
@@ -281,7 +281,7 @@ class ByteParser:
class Chunk(object):
"""A sequence of bytecodes with exits to other bytecodes.
- An exit of None means the chunk can leave the code block (return).
+ An exit of -1 means the chunk can leave the code block (return).
"""
def __init__(self, byte, line=0):
@@ -313,7 +313,7 @@ class ByteParser:
chunk.exits.add(bc.jump_to)
if bc.op in self._code_enders:
- chunk.exits.add(None)
+ chunk.exits.add(-1)
if bc.op in self._chunk_enders:
chunk = None
@@ -332,12 +332,12 @@ class ByteParser:
byte_chunks = dict([(c.byte, c) for c in chunks])
# Build a map from byte offsets to actual lines reached.
- byte_lines = {None:[None]}
+ byte_lines = {-1:[-1]}
bytes_to_add = set([c.byte for c in chunks])
while bytes_to_add:
byte_to_add = bytes_to_add.pop()
- if byte_to_add in byte_lines or byte_to_add is None:
+ if byte_to_add in byte_lines or byte_to_add == -1:
continue
# Which lines does this chunk lead to?
@@ -365,8 +365,8 @@ class ByteParser:
lines.add(ch.line)
else:
for ex in ch.exits:
- if ex is None:
- lines.add(None)
+ if ex == -1:
+ lines.add(-1)
elif ex not in bytes_considered:
bytes_to_consider.append(ex)
@@ -382,7 +382,7 @@ class ByteParser:
for exit_line in byte_lines[ex]:
arcs.add((chunk.line, exit_line))
for line in byte_lines[0]:
- arcs.add((None, line))
+ arcs.add((-1, line))
return arcs
@@ -472,9 +472,9 @@ class AdHocMain(object):
arc_chars = {}
if options.chunks:
for lfrom, lto in sorted(arcs):
- if lfrom is None:
+ if lfrom == -1:
arc_chars[lto] = arc_chars.get(lto, '') + 'v'
- elif lto is None:
+ elif lto == -1:
arc_chars[lfrom] = arc_chars.get(lfrom, '') + '^'
else:
if lfrom == lto-1:
diff --git a/test/coveragetest.py b/test/coveragetest.py
index 36fe078a..e51723ec 100644
--- a/test/coveragetest.py
+++ b/test/coveragetest.py
@@ -3,7 +3,7 @@
import imp, os, random, shutil, sys, tempfile, textwrap, unittest
import coverage
-from coverage.backward import set, StringIO # pylint: disable-msg=W0622
+from coverage.backward import set, sorted, StringIO # pylint: disable-msg=W0622
from backtest import run_command
@@ -102,7 +102,29 @@ class CoverageTest(unittest.TestCase):
self.n += 1
return modname
- def check_coverage(self, text, lines=None, missing="", excludes=None, report=""):
+ # Map chars to numbers for arcz_to_arcs
+ _arcz_map = {'.': -1}
+ _arcz_map.update(dict([(c, ord(c)-ord('0')) for c in '123456789']))
+ _arcz_map.update(dict([(c, 10+ord(c)-ord('A')) for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ']))
+
+ def arcz_to_arcs(self, arcz):
+ """Convert a compact textual representation of arcs to a list of pairs.
+
+ The text has space-separated pairs of letters. Period is -1, 1-9 are
+ 1-9, A-Z are 10 through 36. The resulting list is sorted regardless of
+ the order of the input pairs.
+
+ ".1 12 2." --> [(-1,1), (1,2), (2,-1)]
+
+ """
+ arcs = []
+ for a,b in arcz.split():
+ arcs.append((self._arcz_map[a], self._arcz_map[b]))
+ return sorted(arcs)
+
+ def check_coverage(self, text, lines=None, missing="", excludes=None,
+ report="", arcs=None, arcs_missing=None,
+ arcz=None, arcz_missing=None):
"""Check the coverage measurement of `text`.
The source `text` is run and measured. `lines` are the line numbers
@@ -117,8 +139,13 @@ class CoverageTest(unittest.TestCase):
self.make_file(modname+".py", text)
+ if arcz is not None:
+ arcs = self.arcz_to_arcs(arcz)
+ if arcz_missing is not None:
+ arcs_missing = self.arcz_to_arcs(arcz_missing)
+
# Start up Coverage.
- cov = coverage.coverage()
+ cov = coverage.coverage(branch=(arcs_missing is not None))
cov.erase()
for exc in excludes or []:
cov.exclude(exc)
@@ -144,17 +171,24 @@ class CoverageTest(unittest.TestCase):
break
else:
self.fail("None of the lines choices matched %r" % clines)
- if missing is not None:
- if type(missing) == type(""):
- self.assertEqual(analysis.missing_formatted(), missing)
- else:
- for missing_list in missing:
- if analysis.missing == missing_list:
- break
+
+ if missing is not None:
+ if type(missing) == type(""):
+ self.assertEqual(analysis.missing_formatted(), missing)
else:
- self.fail(
- "None of the missing choices matched %r" % analysis.missing_formatted()
- )
+ for missing_list in missing:
+ if analysis.missing == missing_list:
+ break
+ else:
+ self.fail(
+ "None of the missing choices matched %r" % analysis.missing_formatted()
+ )
+
+ if arcs is not None:
+ self.assertEqual(analysis.arc_possibilities(), arcs)
+
+ if arcs_missing is not None:
+ self.assertEqual(analysis.arcs_missing(), arcs_missing)
if report:
frep = StringIO()
diff --git a/test/test_arcs.py b/test/test_arcs.py
new file mode 100644
index 00000000..6c68e397
--- /dev/null
+++ b/test/test_arcs.py
@@ -0,0 +1,96 @@
+"""Tests for Coverage.py's arc measurement."""
+
+import os, re, sys, textwrap
+
+import coverage
+from coverage.backward import StringIO
+
+sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k
+from coveragetest import CoverageTest
+
+
+class ArcTest(CoverageTest):
+ """Tests for Coverage.py's arc measurement."""
+
+ def test_simple_sequence(self):
+ self.check_coverage("""\
+ a = 1
+ b = 2
+ """,
+ arcs=[(-1,1), (1,2), (2,-1)], arcs_missing=[])
+ self.check_coverage("""\
+ a = 1
+
+ b = 3
+ """,
+ arcs=[(-1,1), (1,3), (3,-1)], arcs_missing=[])
+ self.check_coverage("""\
+
+ a = 2
+ b = 3
+
+ c = 5
+ """,
+ arcs=[(-1,2), (2,3), (3,5),(5,-1)], arcs_missing=[])
+
+ def test_function_def(self):
+ self.check_coverage("""\
+ def foo():
+ a = 2
+
+ foo()
+ """,
+ arcs=[(-1,1), (-1,2),(1,4),(2,-1), (4,-1)], arcs_missing=[])
+
+ def test_if(self):
+ self.check_coverage("""\
+ a = 1
+ if len([]) == 0:
+ a = 3
+ assert a == 3
+ """,
+ arcz=".1 12 23 24 34 4.", arcz_missing="24")
+ self.check_coverage("""\
+ a = 1
+ if len([]) == 1:
+ a = 3
+ assert a == 1
+ """,
+ arcz=".1 12 23 24 34 4.", arcz_missing="23 34")
+
+ def test_if_else(self):
+ self.check_coverage("""\
+ if len([]) == 0:
+ a = 2
+ else:
+ b = 4
+ c = 5
+ """,
+ arcz=".1 12 25 14 45 5.", arcz_missing="14 45")
+ self.check_coverage("""\
+ if len([]) == 1:
+ a = 2
+ else:
+ b = 4
+ c = 5
+ """,
+ arcz=".1 12 25 14 45 5.", arcz_missing="12 25")
+
+ def test_loop(self):
+ self.check_coverage("""\
+ for i in range(10):
+ a = 2
+ b = 3
+ """,
+ arcz=".1 12 21 13 3.", arcz_missing="")
+ self.check_coverage("""\
+ for i in range(0):
+ a = 2
+ b = 3
+ """,
+ arcz=".1 12 21 13 3.", arcz_missing="12 21")
+
+ def xest_xx(self):
+ self.check_coverage("""\
+ """,
+ arcz="", arcz_missing="")