summaryrefslogtreecommitdiff
path: root/coverage/pytracer.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/pytracer.py')
-rw-r--r--coverage/pytracer.py51
1 files changed, 41 insertions, 10 deletions
diff --git a/coverage/pytracer.py b/coverage/pytracer.py
index 23f4946c..b41f4059 100644
--- a/coverage/pytracer.py
+++ b/coverage/pytracer.py
@@ -3,6 +3,7 @@
"""Raw data collector for coverage.py."""
+import atexit
import dis
import sys
@@ -44,16 +45,21 @@ class PyTracer(object):
self.threading = None
self.cur_file_dict = []
- self.last_line = [0]
+ self.last_line = 0 # int, but uninitialized.
self.data_stack = []
self.last_exc_back = None
self.last_exc_firstlineno = 0
self.thread = None
self.stopped = False
+ self._activity = False
+
+ self.in_atexit = False
+ # On exit, self.in_atexit = True
+ atexit.register(setattr, self, 'in_atexit', True)
def __repr__(self):
- return "<PyTracer at 0x{0:0x}: {1} lines in {2} files>".format(
+ return "<PyTracer at {0}: {1} lines in {2} files>".format(
id(self),
sum(len(v) for v in self.data.values()),
len(self.data),
@@ -77,6 +83,7 @@ class PyTracer(object):
if event == 'call':
# Entering a new function context. Decide if we should trace
# in this file.
+ self._activity = True
self.data_stack.append((self.cur_file_dict, self.last_line))
filename = frame.f_code.co_filename
disp = self.should_trace_cache.get(filename)
@@ -94,7 +101,7 @@ class PyTracer(object):
# function calls and re-entering generators. The f_lasti field is
# -1 for calls, and a real offset for generators. Use <0 as the
# line number for calls, and the real line number for generators.
- if frame.f_lasti < 0:
+ if getattr(frame, 'f_lasti', -1) < 0:
self.last_line = -frame.f_code.co_firstlineno
else:
self.last_line = frame.f_lineno
@@ -111,8 +118,9 @@ class PyTracer(object):
if self.trace_arcs and self.cur_file_dict:
# 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:
+ # Jython seems to have an empty co_code, so just assume return.
+ code = frame.f_code.co_code
+ if (not code) or code[frame.f_lasti] != YIELD_VALUE:
first = frame.f_code.co_firstlineno
self.cur_file_dict[(self.last_line, -first)] = None
# Leaving this function, pop the filename stack.
@@ -128,10 +136,18 @@ class PyTracer(object):
Return a Python function suitable for use with sys.settrace().
"""
+ self.stopped = False
if self.threading:
- self.thread = self.threading.currentThread()
+ if self.thread is None:
+ self.thread = self.threading.currentThread()
+ else:
+ if self.thread.ident != self.threading.currentThread().ident:
+ # Re-starting from a different thread!? Don't set the trace
+ # function, but we are marked as running again, so maybe it
+ # will be ok?
+ return self._trace
+
sys.settrace(self._trace)
- self.stopped = False
return self._trace
def stop(self):
@@ -144,12 +160,27 @@ class PyTracer(object):
return
if self.warn:
- if sys.gettrace() != self._trace:
- msg = "Trace function changed, measurement is likely wrong: %r"
- self.warn(msg % (sys.gettrace(),))
+ # PyPy clears the trace function before running atexit functions,
+ # so don't warn if we are in atexit on PyPy and the trace function
+ # has changed to None.
+ tf = sys.gettrace()
+ dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None)
+ if (not dont_warn) and tf != self._trace:
+ self.warn(
+ "Trace function changed, measurement is likely wrong: %r" % (tf,),
+ slug="trace-changed",
+ )
sys.settrace(None)
+ def activity(self):
+ """Has there been any activity?"""
+ return self._activity
+
+ def reset_activity(self):
+ """Reset the activity() flag."""
+ self._activity = False
+
def get_stats(self):
"""Return a dictionary of statistics, or None."""
return None