diff -r ca8e477c590c -r 2fc945191992 eric7/DebugClients/Python/coverage/pytracer.py --- a/eric7/DebugClients/Python/coverage/pytracer.py Sun Mar 20 17:26:35 2022 +0100 +++ b/eric7/DebugClients/Python/coverage/pytracer.py Sun Mar 20 17:49:44 2022 +0100 @@ -10,14 +10,18 @@ from coverage import env # We need the YIELD_VALUE opcode below, in a comparison-friendly form. -YIELD_VALUE = dis.opmap['YIELD_VALUE'] +RESUME = dis.opmap.get('RESUME') +RETURN_VALUE = dis.opmap['RETURN_VALUE'] +if RESUME is None: + YIELD_VALUE = dis.opmap['YIELD_VALUE'] + YIELD_FROM = dis.opmap['YIELD_FROM'] + YIELD_FROM_OFFSET = 0 if env.PYPY else 2 # When running meta-coverage, this file can try to trace itself, which confuses # everything. Don't trace ourselves. THIS_FILE = __file__.rstrip("co") - class PyTracer: """Python implementation of the raw data tracer.""" @@ -64,7 +68,7 @@ atexit.register(setattr, self, 'in_atexit', True) def __repr__(self): - return "<PyTracer at {}: {} lines in {} files>".format( + return "<PyTracer at 0x{:x}: {} lines in {} files>".format( id(self), sum(len(v) for v in self.data.values()), len(self.data), @@ -78,13 +82,13 @@ id(self), len(self.data_stack), )) - if 0: + if 0: # if you want thread ids.. f.write(".{:x}.{:x}".format( self.thread.ident, self.threading.current_thread().ident, )) f.write(" {}".format(" ".join(map(str, args)))) - if 0: + if 0: # if you want callers.. f.write(" | ") stack = " / ".join( (fname or "???").rpartition("/")[-1] @@ -132,8 +136,7 @@ else: self.started_context = False - # Entering a new frame. Decide if we should trace - # in this file. + # Entering a new frame. Decide if we should trace in this file. self._activity = True self.data_stack.append( ( @@ -160,7 +163,14 @@ # 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 getattr(frame, 'f_lasti', -1) < 0: + if RESUME is not None: + # The current opcode is guaranteed to be RESUME. The argument + # determines what kind of resume it is. + oparg = frame.f_code.co_code[frame.f_lasti + 1] + real_call = (oparg == 0) + else: + real_call = (getattr(frame, 'f_lasti', -1) < 0) + if real_call: self.last_line = -frame.f_code.co_firstlineno else: self.last_line = frame.f_lineno @@ -178,9 +188,27 @@ if self.trace_arcs and self.cur_file_data: # Record an arc leaving the function, but beware that a # "return" event might just mean yielding from a generator. - # 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: + lasti = frame.f_lasti + if RESUME is not None: + if len(code) == lasti + 2: + # A return from the end of a code object is a real return. + real_return = True + else: + # it's a real return. + real_return = (code[lasti + 2] != RESUME) + else: + if code[lasti] == RETURN_VALUE: + real_return = True + elif code[lasti] == YIELD_VALUE: + real_return = False + elif len(code) <= lasti + YIELD_FROM_OFFSET: + real_return = True + elif code[lasti + YIELD_FROM_OFFSET] == YIELD_FROM: + real_return = False + else: + real_return = True + if real_return: first = frame.f_code.co_firstlineno self.cur_file_data.add((self.last_line, -first)) # Leaving this function, pop the filename stack. @@ -238,8 +266,10 @@ # has changed to None. 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: # pylint: disable=comparison-with-callable - msg = f"Trace function changed, measurement is likely wrong: {tf!r}" - self.warn(msg, slug="trace-changed") + self.warn( + f"Trace function changed, data is likely wrong: {tf!r} != {self._trace!r}", + slug="trace-changed", + ) def activity(self): """Has there been any activity?"""