diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2009-10-16 07:25:56 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2009-10-16 07:25:56 -0400 |
commit | 84c9cc21909050a008ac2964ac387feaa2ad0f74 (patch) | |
tree | 4ceba3042dbc4b1e3d97aab54d75d83ca73109dc | |
parent | fd10b36d21e578508a564e0c6828723ba30de7ec (diff) | |
download | python-coveragepy-git-84c9cc21909050a008ac2964ac387feaa2ad0f74.tar.gz |
Start unit testing the arc measurement. In arcs, -1 means enter or exit.
-rw-r--r-- | coverage/collector.py | 6 | ||||
-rw-r--r-- | coverage/control.py | 35 | ||||
-rw-r--r-- | coverage/data.py | 4 | ||||
-rw-r--r-- | coverage/parser.py | 26 | ||||
-rw-r--r-- | test/coveragetest.py | 60 | ||||
-rw-r--r-- | test/test_arcs.py | 96 |
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="") |