From d4f1990c537d761989de342a3dd682e5df46c51c Mon Sep 17 00:00:00 2001 From: Mickie Betz Date: Tue, 14 Apr 2015 16:53:30 -0400 Subject: Including generator yield statements when doing end of code calculations issue 324 --- coverage/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'coverage/parser.py') diff --git a/coverage/parser.py b/coverage/parser.py index f488367..91b685c 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -308,7 +308,7 @@ 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', + 'BREAK_LOOP', 'CONTINUE_LOOP', 'YIELD_VALUE', ) # Opcodes that unconditionally begin a new code chunk. By starting new chunks -- cgit v1.2.1 From c53f2536871ce9a0a96ecb6e7df13eb1866a5bb9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 18 Apr 2015 09:31:59 -0400 Subject: Put all chunk/arc logic in one place. --- coverage/parser.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'coverage/parser.py') diff --git a/coverage/parser.py b/coverage/parser.py index 91b685c..cb0fc1f 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -430,9 +430,10 @@ class ByteParser(object): Returns a list of `Chunk` objects. """ - # The list of chunks so far, and the one we're working on. - chunks = [] - chunk = None + # The list of chunks so far, and the one we're working on. We always + # start with an entrance to the code object. + chunk = Chunk(0, -1, True) + chunks = [chunk] # A dict mapping byte offsets of line starts to the line numbers. bytes_lines_map = dict(self._bytes_lines()) @@ -460,7 +461,7 @@ class ByteParser(object): # Walk the byte codes building chunks. for bc in bytecodes: - # Maybe have to start a new chunk + # Maybe have to start a new chunk. start_new_chunk = False first_chunk = False if bc.offset in bytes_lines_map: @@ -483,7 +484,7 @@ class ByteParser(object): chunk = Chunk(bc.offset, chunk_lineno, first_chunk) chunks.append(chunk) - # Look at the opcode + # Look at the opcode. if bc.jump_to >= 0 and bc.op not in OPS_NO_JUMP: if ignore_branch: # Someone earlier wanted us to ignore this branch. @@ -573,9 +574,6 @@ class ByteParser(object): # A map from byte offsets to chunks jumped into. byte_chunks = dict((c.byte, c) for c in chunks) - # There's always an entrance at the first chunk. - yield (-1, byte_chunks[0].line) - # Traverse from the first chunk in each line, and yield arcs where # the trace function will be invoked. for chunk in chunks: @@ -586,7 +584,7 @@ class ByteParser(object): chunks_to_consider = [chunk] while chunks_to_consider: # Get the chunk we're considering, and make sure we don't - # consider it again + # consider it again. this_chunk = chunks_to_consider.pop() chunks_considered.add(this_chunk) -- cgit v1.2.1 From 30a6a037158eebf062c7da735e0cb905a489d21c Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 20 Apr 2015 12:15:37 -0400 Subject: Fix branch coverage for yield statements. #308 #324 Turns out the "call" and "return" trace events are really "start frame" and "end frame". They happen not only when functions are entered and left, but when generators yield and resume. We aren't interested in arcs into and out of yield statements, so the trace functions look more closely to see what's really happening, and record an arc in human-friendly terms. Thanks for Mickie Betz for pushing on this bug, although her code is no longer here. :( --- coverage/parser.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) (limited to 'coverage/parser.py') diff --git a/coverage/parser.py b/coverage/parser.py index cb0fc1f..fc751eb 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -308,7 +308,7 @@ 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', 'YIELD_VALUE', + 'BREAK_LOOP', 'CONTINUE_LOOP', ) # Opcodes that unconditionally begin a new code chunk. By starting new chunks @@ -430,10 +430,9 @@ class ByteParser(object): Returns a list of `Chunk` objects. """ - # The list of chunks so far, and the one we're working on. We always - # start with an entrance to the code object. - chunk = Chunk(0, -1, True) - chunks = [chunk] + # The list of chunks so far, and the one we're working on. + chunks = [] + chunk = None # A dict mapping byte offsets of line starts to the line numbers. bytes_lines_map = dict(self._bytes_lines()) @@ -482,6 +481,10 @@ class ByteParser(object): if chunk: chunk.exits.add(bc.offset) chunk = Chunk(bc.offset, chunk_lineno, first_chunk) + if not chunks: + # The very first chunk of a code object is always an + # entrance. + chunk.entrance = True chunks.append(chunk) # Look at the opcode. @@ -571,12 +574,15 @@ class ByteParser(object): """ chunks = self._split_into_chunks() - # A map from byte offsets to chunks jumped into. + # A map from byte offsets to the chunk starting at that offset. byte_chunks = dict((c.byte, c) for c in chunks) # Traverse from the first chunk in each line, and yield arcs where # the trace function will be invoked. for chunk in chunks: + if chunk.entrance: + yield (-1, chunk.line) + if not chunk.first: continue @@ -647,6 +653,8 @@ class Chunk(object): .. _basic block: http://en.wikipedia.org/wiki/Basic_block + `byte` is the offset to the bytecode starting this chunk. + `line` is the source line number containing this chunk. `first` is true if this is the first chunk in the source line. @@ -654,19 +662,24 @@ class Chunk(object): An exit < 0 means the chunk can leave the code (return). The exit is the negative of the starting line number of the code block. + The `entrance` attribute is a boolean indicating whether the code object + can be entered at this chunk. + """ def __init__(self, byte, line, first): self.byte = byte self.line = line self.first = first self.length = 0 + self.entrance = False self.exits = set() def __repr__(self): - if self.first: - bang = "!" - else: - bang = "" - return "<%d+%d @%d%s %r>" % ( - self.byte, self.length, self.line, bang, list(self.exits) + return "<%d+%d @%d%s%s %r>" % ( + self.byte, + self.length, + self.line, + "!" if self.first else "", + "v" if self.entrance else "", + list(self.exits), ) -- cgit v1.2.1