diff options
-rw-r--r-- | coverage/collector.py | 2 | ||||
-rw-r--r-- | coverage/control.py | 5 | ||||
-rw-r--r-- | coverage/parser.py | 21 | ||||
-rw-r--r-- | test/test_arcs.py | 93 |
4 files changed, 108 insertions, 13 deletions
diff --git a/coverage/collector.py b/coverage/collector.py index bc700279..36788438 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -70,6 +70,7 @@ class PyTracer: # Record an executed line. if self.cur_file_data is not None: if self.arcs: + #print "lin", self.last_line, frame.f_lineno self.cur_file_data[(self.last_line, frame.f_lineno)] = None else: self.cur_file_data[frame.f_lineno] = None @@ -80,6 +81,7 @@ class PyTracer: # Leaving this function, pop the filename stack. self.cur_file_data, self.last_line = self.data_stack.pop() elif event == 'exception': + #print "exc", self.last_line, frame.f_lineno self.last_exc_back = frame.f_back return self._trace diff --git a/coverage/control.py b/coverage/control.py index 5e857aa6..da773d3e 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -384,5 +384,8 @@ class Analysis: def arcs_unpredicted(self): possible = self.arc_possibilities() executed = self.arcs_executed() - unpredicted = [e for e in executed if e not in possible] + # Exclude arcs here which connect a line to itself. They can occur + # in executed data in some cases. This is where they can cause trouble, + # and here is where it's the least burden to remove them. + unpredicted = [e for e in executed if e not in possible and e[0] != e[1]] return sorted(unpredicted) diff --git a/coverage/parser.py b/coverage/parser.py index 74490189..7b899d12 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -173,8 +173,13 @@ class CodeParser: ## Opcodes that guide the ByteParser. +def _opcode(name): + """Return the opcode by name from the opcode module.""" + return opcode.opmap[name] + def _opcode_set(*names): - return set([opcode.opmap[name] for name in names]) + """Return a set of opcodes by the names in `names`.""" + return set([_opcode(name) for name in names]) # Opcodes that leave the code object. OPS_CODE_END = _opcode_set('RETURN_VALUE') @@ -191,12 +196,13 @@ 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') +# Individual opcodes we need below. +OP_BREAK_LOOP = _opcode('BREAK_LOOP') +OP_END_FINALLY = _opcode('END_FINALLY') + class ByteParser: """Parse byte codes to understand the structure of code.""" @@ -352,12 +358,15 @@ class ByteParser: block_stack.pop() elif bc.op in OPS_CHUNK_END: # This opcode forces the end of the chunk. - if bc.op in OPS_BREAK: + if bc.op == OP_BREAK_LOOP: # A break is implicit: jump where the top of the # block_stack points. chunk.exits.add(block_stack[-1]) chunk = None - + elif bc.op == OP_END_FINALLY: + if block_stack: + chunk.exits.add(block_stack[-1]) + if chunks: chunks[-1].length = bc.next_offset - chunks[-1].byte for i in range(len(chunks)-1): diff --git a/test/test_arcs.py b/test/test_arcs.py index 0a7850a8..820ce8b1 100644 --- a/test/test_arcs.py +++ b/test/test_arcs.py @@ -72,7 +72,24 @@ class SimpleArcTest(CoverageTest): assert a == 4 """, arcz=".1 12 25 14 45 5.", arcz_missing="12 25") - + + def test_compact_if(self): + self.check_coverage("""\ + a = 1 + if len([]) == 0: a = 2 + assert a == 2 + """, + arcz=".1 12 23 3.", arcz_missing="") + self.check_coverage("""\ + def fn(x): + if x % 2: return True + return False + a = fn(1) + assert a == True + """, + arcz=".1 14 45 5. .2 2. 23 3.", arcz_missing="23 3.") + + class LoopArcTest(CoverageTest): """Arc-measuring tests involving loops.""" @@ -132,9 +149,11 @@ class LoopArcTest(CoverageTest): """, arcz=".1 12 23 34 45 25 56 51 67 17 7.", arcz_missing="17 25") + class ExceptionArcTest(CoverageTest): + """Arc-measuring tests involving exception handling.""" - def test_no_exception(self): + def test_try_except(self): self.check_coverage("""\ a, b = 1, 1 try: @@ -144,8 +163,6 @@ class ExceptionArcTest(CoverageTest): 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: @@ -159,7 +176,71 @@ class ExceptionArcTest(CoverageTest): arcz=".1 12 23 34 58 67 78 8.", arcz_missing="58", arcz_unpredicted="46") - def xest_xx(self): + def test_hidden_raise(self): + self.check_coverage("""\ + a, b = 1, 1 + def oops(x): + if x % 2: raise Exception("odd") + try: + a = 5 + oops(1) + a = 7 + except: + b = 9 + assert a == 5 and b == 9 + """, + arcz=".1 12 .3 3. 24 45 56 67 7A 89 9A A.", + arcz_missing="67 7A", arcz_unpredicted="68") + + def test_try_finally(self): + self.check_coverage("""\ + a, c = 1, 1 + try: + a = 3 + finally: + c = 5 + assert a == 3 and c == 5 + """, + arcz=".1 12 23 35 56 6.", arcz_missing="") self.check_coverage("""\ + a, c, d = 1, 1, 1 + try: + try: + a = 4 + finally: + c = 6 + except: + d = 8 + assert a == 4 and c == 6 and d == 1 # 9 """, - arcz="", arcz_missing="") + arcz=".1 12 23 34 46 67 78 89 69 9.", + arcz_missing="67 78 89", arcz_unpredicted="") + self.check_coverage("""\ + a, c, d = 1, 1, 1 + try: + try: + a = 4 + raise Exception("Yikes!") + a = 6 + finally: + c = 8 + except: + d = 10 # A + assert a == 4 and c == 8 and d == 10 # B + """, + arcz=".1 12 23 34 45 68 89 8B 9A AB B.", + arcz_missing="68 8B", arcz_unpredicted="58") + + if sys.hexversion >= 0x02050000: + def test_except_finally_no_exception(self): + self.check_coverage("""\ + a, b, c = 1, 1, 1 + try: + a = 3 + except: + b = 5 + finally: + c = 7 + assert a == 3 and b == 1 and c == 7 + """, + arcz=".1 12 23 45 37 57 78 8.", arcz_missing="45 57") |