diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2009-10-19 06:55:03 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2009-10-19 06:55:03 -0400 |
commit | 832ee468e711e6d33fb49fb718e60a85c970a8ef (patch) | |
tree | 048ed03bbec8096b58f6123a5b81cba1ef684f5a | |
parent | 352324b4df27881776ceeb9584574081691a477e (diff) | |
download | python-coveragepy-git-832ee468e711e6d33fb49fb718e60a85c970a8ef.tar.gz |
Start testing exceptions with arc measurements.
-rw-r--r-- | coverage/control.py | 5 | ||||
-rw-r--r-- | coverage/parser.py | 23 | ||||
-rw-r--r-- | test/coveragetest.py | 17 | ||||
-rw-r--r-- | test/test_arcs.py | 37 |
4 files changed, 71 insertions, 11 deletions
diff --git a/coverage/control.py b/coverage/control.py index 952897dc..5e857aa6 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -381,3 +381,8 @@ class Analysis: missing = [p for p in possible if p not in executed] return sorted(missing) + def arcs_unpredicted(self): + possible = self.arc_possibilities() + executed = self.arcs_executed() + unpredicted = [e for e in executed if e not in possible] + return sorted(unpredicted) diff --git a/coverage/parser.py b/coverage/parser.py index 99656f17..74490189 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -171,20 +171,32 @@ class CodeParser: arcs = self.byte_parser._all_arcs() return sorted(arcs) -# Opcodes that guide the ByteParser. +## Opcodes that guide the ByteParser. def _opcode_set(*names): return set([opcode.opmap[name] for name in names]) +# Opcodes that leave the code object. OPS_CODE_END = _opcode_set('RETURN_VALUE') + +# Opcodes that unconditionally end the code chunk. OPS_CHUNK_END = _opcode_set( 'JUMP_ABSOLUTE', 'JUMP_FORWARD', 'RETURN_VALUE', 'RAISE_VARARGS', 'BREAK_LOOP', 'CONTINUE_LOOP', ) + +# Opcodes that push a block on the block stack. OPS_PUSH_BLOCK = _opcode_set('SETUP_LOOP', 'SETUP_EXCEPT', 'SETUP_FINALLY') + +# Opcodes that pop a block from the block stack. OPS_POP_BLOCK = _opcode_set('POP_BLOCK') + +# Opcodes that break a loop. OPS_BREAK = _opcode_set('BREAK_LOOP') +# Opcodes that have a jump destination, but aren't really a jump. +OPS_NO_JUMP = _opcode_set('SETUP_EXCEPT', 'SETUP_FINALLY') + class ByteParser: """Parse byte codes to understand the structure of code.""" @@ -325,17 +337,24 @@ class ByteParser: chunks.append(chunk) # Look at the opcode - if bc.jump_to >= 0: + if bc.jump_to >= 0 and bc.op not in OPS_NO_JUMP: + # The opcode has a jump, it's an exit for this chunk. chunk.exits.add(bc.jump_to) if bc.op in OPS_CODE_END: + # The opcode can exit the code object. chunk.exits.add(-1) elif bc.op in OPS_PUSH_BLOCK: + # The opcode adds a block to the block_stack. block_stack.append(bc.jump_to) elif bc.op in OPS_POP_BLOCK: + # The opcode pops a block from the block stack. block_stack.pop() elif bc.op in OPS_CHUNK_END: + # This opcode forces the end of the chunk. if bc.op in OPS_BREAK: + # A break is implicit: jump where the top of the + # block_stack points. chunk.exits.add(block_stack[-1]) chunk = None diff --git a/test/coveragetest.py b/test/coveragetest.py index 635b05b2..b1f4644c 100644 --- a/test/coveragetest.py +++ b/test/coveragetest.py @@ -125,8 +125,7 @@ class CoverageTest(unittest.TestCase): return sorted(arcs) def check_coverage(self, text, lines=None, missing="", excludes=None, - report="", arcs=None, arcs_missing=None, - arcz=None, arcz_missing=None): + report="", arcz=None, arcz_missing="", arcz_unpredicted=""): """Check the coverage measurement of `text`. The source `text` is run and measured. `lines` are the line numbers @@ -134,6 +133,12 @@ class CoverageTest(unittest.TestCase): are regexes to match against for excluding lines, and `report` is the text of the measurement report. + For arc measurement, `arcz` is a string that can be decoded into arcs + in the code (see `arcz_to_arcs` for the encoding scheme), + `arcz_missing` are the arcs that are not executed, and + `arcs_unpredicted` are the arcs executed in the code, but not deducible + from the code. + """ # We write the code into a file so that we can import it. # Coverage wants to deal with things as modules with file names. @@ -141,10 +146,11 @@ class CoverageTest(unittest.TestCase): self.make_file(modname+".py", text) + arcs = arcs_missing = arcs_unpredicted = None 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) + arcs_missing = self.arcz_to_arcs(arcz_missing or "") + arcs_unpredicted = self.arcz_to_arcs(arcz_unpredicted or "") # Start up Coverage. cov = coverage.coverage(branch=(arcs_missing is not None)) @@ -194,6 +200,9 @@ class CoverageTest(unittest.TestCase): if arcs_missing is not None: self.assertEqual(analysis.arcs_missing(), arcs_missing) + if arcs_unpredicted is not None: + self.assertEqual(analysis.arcs_unpredicted(), arcs_unpredicted) + if report: frep = StringIO() cov.report(mod, file=frep) diff --git a/test/test_arcs.py b/test/test_arcs.py index 89b8921a..0a7850a8 100644 --- a/test/test_arcs.py +++ b/test/test_arcs.py @@ -14,13 +14,13 @@ class SimpleArcTest(CoverageTest): a = 1 b = 2 """, - arcs=[(-1,1), (1,2), (2,-1)], arcs_missing=[]) + arcz=".1 12 2.") self.check_coverage("""\ a = 1 b = 3 """, - arcs=[(-1,1), (1,3), (3,-1)], arcs_missing=[]) + arcz=".1 13 3.") self.check_coverage("""\ a = 2 @@ -28,7 +28,7 @@ class SimpleArcTest(CoverageTest): c = 5 """, - arcs=[(-1,2), (2,3), (3,5),(5,-1)], arcs_missing=[]) + arcz=".2 23 35 5.") def test_function_def(self): self.check_coverage("""\ @@ -37,7 +37,7 @@ class SimpleArcTest(CoverageTest): foo() """, - arcs=[(-1,1), (-1,2),(1,4),(2,-1), (4,-1)], arcs_missing=[]) + arcz=".1 .2 14 2. 4.") def test_if(self): self.check_coverage("""\ @@ -131,7 +131,34 @@ class LoopArcTest(CoverageTest): assert a == 2 and i == 2 # 7 """, arcz=".1 12 23 34 45 25 56 51 67 17 7.", arcz_missing="17 25") - + +class ExceptionArcTest(CoverageTest): + + def test_no_exception(self): + self.check_coverage("""\ + a, b = 1, 1 + try: + a = 3 + except: + b = 5 + assert a == 3 and b == 1 + """, + arcz=".1 12 23 36 45 56 6.", arcz_missing="45 56") + + def test_raise(self): + self.check_coverage("""\ + a, b = 1, 1 + try: + a = 3 + raise Exception("Yikes!") + a = 5 + except: + b = 7 + assert a == 3 and b == 7 + """, + arcz=".1 12 23 34 58 67 78 8.", + arcz_missing="58", arcz_unpredicted="46") + def xest_xx(self): self.check_coverage("""\ """, |