PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/venv/Lib/site-packages/coverage/pytracer.py

https://bitbucket.org/g-muiru/server-python
Python | 215 lines | 119 code | 28 blank | 68 comment | 37 complexity | 873dc15d915ae334c2b02cea5a1bfa8d MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause, MPL-2.0-no-copyleft-exception
  1. # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
  2. # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
  3. """Raw data collector for coverage.py."""
  4. import atexit
  5. import dis
  6. import sys
  7. from coverage import env
  8. # We need the YIELD_VALUE opcode below, in a comparison-friendly form.
  9. YIELD_VALUE = dis.opmap['YIELD_VALUE']
  10. if env.PY2:
  11. YIELD_VALUE = chr(YIELD_VALUE)
  12. class PyTracer(object):
  13. """Python implementation of the raw data tracer."""
  14. # Because of poor implementations of trace-function-manipulating tools,
  15. # the Python trace function must be kept very simple. In particular, there
  16. # must be only one function ever set as the trace function, both through
  17. # sys.settrace, and as the return value from the trace function. Put
  18. # another way, the trace function must always return itself. It cannot
  19. # swap in other functions, or return None to avoid tracing a particular
  20. # frame.
  21. #
  22. # The trace manipulator that introduced this restriction is DecoratorTools,
  23. # which sets a trace function, and then later restores the pre-existing one
  24. # by calling sys.settrace with a function it found in the current frame.
  25. #
  26. # Systems that use DecoratorTools (or similar trace manipulations) must use
  27. # PyTracer to get accurate results. The command-line --timid argument is
  28. # used to force the use of this tracer.
  29. def __init__(self):
  30. # Attributes set from the collector:
  31. self.data = None
  32. self.trace_arcs = False
  33. self.should_trace = None
  34. self.should_trace_cache = None
  35. self.warn = None
  36. # The threading module to use, if any.
  37. self.threading = None
  38. self.cur_file_dict = None
  39. self.last_line = 0 # int, but uninitialized.
  40. self.cur_file_name = None
  41. self.data_stack = []
  42. self.last_exc_back = None
  43. self.last_exc_firstlineno = 0
  44. self.thread = None
  45. self.stopped = False
  46. self._activity = False
  47. self.in_atexit = False
  48. # On exit, self.in_atexit = True
  49. atexit.register(setattr, self, 'in_atexit', True)
  50. def __repr__(self):
  51. return "<PyTracer at {0}: {1} lines in {2} files>".format(
  52. id(self),
  53. sum(len(v) for v in self.data.values()),
  54. len(self.data),
  55. )
  56. def log(self, marker, *args):
  57. """For hard-core logging of what this tracer is doing."""
  58. with open("/tmp/debug_trace.txt", "a") as f:
  59. f.write("{} {:x}.{:x}[{}] {:x} {}\n".format(
  60. marker,
  61. id(self),
  62. self.thread.ident,
  63. len(self.data_stack),
  64. self.threading.currentThread().ident,
  65. " ".join(map(str, args))
  66. ))
  67. def _trace(self, frame, event, arg_unused):
  68. """The trace function passed to sys.settrace."""
  69. #self.log(":", frame.f_code.co_filename, frame.f_lineno, event)
  70. if (self.stopped and sys.gettrace() == self._trace):
  71. # The PyTrace.stop() method has been called, possibly by another
  72. # thread, let's deactivate ourselves now.
  73. #self.log("X", frame.f_code.co_filename, frame.f_lineno)
  74. sys.settrace(None)
  75. return None
  76. if self.last_exc_back:
  77. if frame == self.last_exc_back:
  78. # Someone forgot a return event.
  79. if self.trace_arcs and self.cur_file_dict:
  80. pair = (self.last_line, -self.last_exc_firstlineno)
  81. self.cur_file_dict[pair] = None
  82. self.cur_file_dict, self.cur_file_name, self.last_line = self.data_stack.pop()
  83. self.last_exc_back = None
  84. if event == 'call':
  85. # Entering a new function context. Decide if we should trace
  86. # in this file.
  87. self._activity = True
  88. self.data_stack.append((self.cur_file_dict, self.cur_file_name, self.last_line))
  89. filename = frame.f_code.co_filename
  90. self.cur_file_name = filename
  91. disp = self.should_trace_cache.get(filename)
  92. if disp is None:
  93. disp = self.should_trace(filename, frame)
  94. self.should_trace_cache[filename] = disp
  95. self.cur_file_dict = None
  96. if disp.trace:
  97. tracename = disp.source_filename
  98. if tracename not in self.data:
  99. self.data[tracename] = {}
  100. self.cur_file_dict = self.data[tracename]
  101. # The call event is really a "start frame" event, and happens for
  102. # function calls and re-entering generators. The f_lasti field is
  103. # -1 for calls, and a real offset for generators. Use <0 as the
  104. # line number for calls, and the real line number for generators.
  105. if getattr(frame, 'f_lasti', -1) < 0:
  106. self.last_line = -frame.f_code.co_firstlineno
  107. else:
  108. self.last_line = frame.f_lineno
  109. elif event == 'line':
  110. # Record an executed line.
  111. if self.cur_file_dict is not None:
  112. lineno = frame.f_lineno
  113. #if frame.f_code.co_filename != self.cur_file_name:
  114. # self.log("*", frame.f_code.co_filename, self.cur_file_name, lineno)
  115. if self.trace_arcs:
  116. self.cur_file_dict[(self.last_line, lineno)] = None
  117. else:
  118. self.cur_file_dict[lineno] = None
  119. self.last_line = lineno
  120. elif event == 'return':
  121. if self.trace_arcs and self.cur_file_dict:
  122. # Record an arc leaving the function, but beware that a
  123. # "return" event might just mean yielding from a generator.
  124. # Jython seems to have an empty co_code, so just assume return.
  125. code = frame.f_code.co_code
  126. if (not code) or code[frame.f_lasti] != YIELD_VALUE:
  127. first = frame.f_code.co_firstlineno
  128. self.cur_file_dict[(self.last_line, -first)] = None
  129. # Leaving this function, pop the filename stack.
  130. self.cur_file_dict, self.cur_file_name, self.last_line = self.data_stack.pop()
  131. elif event == 'exception':
  132. self.last_exc_back = frame.f_back
  133. self.last_exc_firstlineno = frame.f_code.co_firstlineno
  134. return self._trace
  135. def start(self):
  136. """Start this Tracer.
  137. Return a Python function suitable for use with sys.settrace().
  138. """
  139. self.stopped = False
  140. if self.threading:
  141. if self.thread is None:
  142. self.thread = self.threading.currentThread()
  143. else:
  144. if self.thread.ident != self.threading.currentThread().ident:
  145. # Re-starting from a different thread!? Don't set the trace
  146. # function, but we are marked as running again, so maybe it
  147. # will be ok?
  148. #self.log("~", "starting on different threads")
  149. return self._trace
  150. sys.settrace(self._trace)
  151. return self._trace
  152. def stop(self):
  153. """Stop this Tracer."""
  154. # Get the activate tracer callback before setting the stop flag to be
  155. # able to detect if the tracer was changed prior to stopping it.
  156. tf = sys.gettrace()
  157. # Set the stop flag. The actual call to sys.settrace(None) will happen
  158. # in the self._trace callback itself to make sure to call it from the
  159. # right thread.
  160. self.stopped = True
  161. if self.threading and self.thread.ident != self.threading.currentThread().ident:
  162. # Called on a different thread than started us: we can't unhook
  163. # ourselves, but we've set the flag that we should stop, so we
  164. # won't do any more tracing.
  165. #self.log("~", "stopping on different threads")
  166. return
  167. if self.warn:
  168. # PyPy clears the trace function before running atexit functions,
  169. # so don't warn if we are in atexit on PyPy and the trace function
  170. # has changed to None.
  171. dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None)
  172. if (not dont_warn) and tf != self._trace:
  173. self.warn(
  174. "Trace function changed, measurement is likely wrong: %r" % (tf,),
  175. slug="trace-changed",
  176. )
  177. def activity(self):
  178. """Has there been any activity?"""
  179. return self._activity
  180. def reset_activity(self):
  181. """Reset the activity() flag."""
  182. self._activity = False
  183. def get_stats(self):
  184. """Return a dictionary of statistics, or None."""
  185. return None