diff options
Diffstat (limited to 'coverage/pytracer.py')
-rw-r--r-- | coverage/pytracer.py | 51 |
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 |