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/pytracer.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'coverage/pytracer.py') diff --git a/coverage/pytracer.py b/coverage/pytracer.py index 0eafbef..3f03aaf 100644 --- a/coverage/pytracer.py +++ b/coverage/pytracer.py @@ -1,7 +1,15 @@ """Raw data collector for Coverage.""" +import dis import sys +from coverage import env + +# We need the YIELD_VALUE opcode below, in a comparison-friendly form. +YIELD_VALUE = dis.opmap['YIELD_VALUE'] +if env.PY2: + YIELD_VALUE = chr(YIELD_VALUE) + class PyTracer(object): """Python implementation of the raw data tracer.""" @@ -79,9 +87,11 @@ class PyTracer(object): if tracename not in self.data: self.data[tracename] = {} self.cur_file_dict = self.data[tracename] - # Set the last_line to -1 because the next arc will be entering a - # code block, indicated by (-1, n). - self.last_line = -1 + # The call event is really a "start frame" event, and happens for + # function calls and re-entering generators. The f_lasti field is + # -1 for calls, and a real offset for generators. Use -1 as the + # line number for calls, and the real line number for generators. + self.last_line = -1 if (frame.f_lasti < 0) else frame.f_lineno elif event == 'line': # Record an executed line. if self.cur_file_dict is not None: @@ -93,8 +103,12 @@ class PyTracer(object): self.last_line = lineno elif event == 'return': if self.arcs and self.cur_file_dict: - first = frame.f_code.co_firstlineno - self.cur_file_dict[(self.last_line, -first)] = None + # Record an arc leaving the function, but beware that a + # "return" event might just mean yielding from a generator. + bytecode = frame.f_code.co_code[frame.f_lasti] + if bytecode != YIELD_VALUE: + first = frame.f_code.co_firstlineno + self.cur_file_dict[(self.last_line, -first)] = None # Leaving this function, pop the filename stack. self.cur_file_dict, self.last_line = self.data_stack.pop() elif event == 'exception': -- cgit v1.2.1 From dc0d0c613de54cd5af74a1d3ac9d86235dc0aee9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 24 Jul 2015 10:43:46 -0400 Subject: Add license mention to the top of all files. #313. --- coverage/pytracer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'coverage/pytracer.py') diff --git a/coverage/pytracer.py b/coverage/pytracer.py index 3f03aaf..c657ad0 100644 --- a/coverage/pytracer.py +++ b/coverage/pytracer.py @@ -1,4 +1,7 @@ -"""Raw data collector for Coverage.""" +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt + +"""Raw data collector for coverage.py.""" import dis import sys -- cgit v1.2.1