eric7/DebugClients/Python/coverage/pytracer.py

branch
eric7
changeset 8775
0802ae193343
parent 8527
2bd1325d727e
child 8991
2fc945191992
equal deleted inserted replaced
8774:d728227e8ebb 8775:0802ae193343
9 9
10 from coverage import env 10 from coverage import env
11 11
12 # We need the YIELD_VALUE opcode below, in a comparison-friendly form. 12 # We need the YIELD_VALUE opcode below, in a comparison-friendly form.
13 YIELD_VALUE = dis.opmap['YIELD_VALUE'] 13 YIELD_VALUE = dis.opmap['YIELD_VALUE']
14 if env.PY2:
15 YIELD_VALUE = chr(YIELD_VALUE)
16 14
17 # When running meta-coverage, this file can try to trace itself, which confuses 15 # When running meta-coverage, this file can try to trace itself, which confuses
18 # everything. Don't trace ourselves. 16 # everything. Don't trace ourselves.
19 17
20 THIS_FILE = __file__.rstrip("co") 18 THIS_FILE = __file__.rstrip("co")
21 19
22 20
23 class PyTracer(object): 21 class PyTracer:
24 """Python implementation of the raw data tracer.""" 22 """Python implementation of the raw data tracer."""
25 23
26 # Because of poor implementations of trace-function-manipulating tools, 24 # Because of poor implementations of trace-function-manipulating tools,
27 # the Python trace function must be kept very simple. In particular, there 25 # the Python trace function must be kept very simple. In particular, there
28 # must be only one function ever set as the trace function, both through 26 # must be only one function ever set as the trace function, both through
48 self.should_start_context = None 46 self.should_start_context = None
49 self.warn = None 47 self.warn = None
50 # The threading module to use, if any. 48 # The threading module to use, if any.
51 self.threading = None 49 self.threading = None
52 50
53 self.cur_file_dict = None 51 self.cur_file_data = None
54 self.last_line = 0 # int, but uninitialized. 52 self.last_line = 0 # int, but uninitialized.
55 self.cur_file_name = None 53 self.cur_file_name = None
56 self.context = None 54 self.context = None
57 self.started_context = False 55 self.started_context = False
58 56
59 self.data_stack = [] 57 self.data_stack = []
60 self.last_exc_back = None
61 self.last_exc_firstlineno = 0
62 self.thread = None 58 self.thread = None
63 self.stopped = False 59 self.stopped = False
64 self._activity = False 60 self._activity = False
65 61
66 self.in_atexit = False 62 self.in_atexit = False
83 len(self.data_stack), 79 len(self.data_stack),
84 )) 80 ))
85 if 0: 81 if 0:
86 f.write(".{:x}.{:x}".format( 82 f.write(".{:x}.{:x}".format(
87 self.thread.ident, 83 self.thread.ident,
88 self.threading.currentThread().ident, 84 self.threading.current_thread().ident,
89 )) 85 ))
90 f.write(" {}".format(" ".join(map(str, args)))) 86 f.write(" {}".format(" ".join(map(str, args))))
91 if 0: 87 if 0:
92 f.write(" | ") 88 f.write(" | ")
93 stack = " / ".join( 89 stack = " / ".join(
113 f = frame 109 f = frame
114 while f: 110 while f:
115 self.log(">", f.f_code.co_filename, f.f_lineno, f.f_code.co_name, f.f_trace) 111 self.log(">", f.f_code.co_filename, f.f_lineno, f.f_code.co_name, f.f_trace)
116 f = f.f_back 112 f = f.f_back
117 sys.settrace(None) 113 sys.settrace(None)
118 self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = ( 114 self.cur_file_data, self.cur_file_name, self.last_line, self.started_context = (
119 self.data_stack.pop() 115 self.data_stack.pop()
120 ) 116 )
121 return None 117 return None
122
123 if self.last_exc_back:
124 if frame == self.last_exc_back:
125 # Someone forgot a return event.
126 if self.trace_arcs and self.cur_file_dict:
127 pair = (self.last_line, -self.last_exc_firstlineno)
128 self.cur_file_dict[pair] = None
129 self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = (
130 self.data_stack.pop()
131 )
132 self.last_exc_back = None
133 118
134 # if event != 'call' and frame.f_code.co_filename != self.cur_file_name: 119 # if event != 'call' and frame.f_code.co_filename != self.cur_file_name:
135 # self.log("---\n*", frame.f_code.co_filename, self.cur_file_name, frame.f_lineno) 120 # self.log("---\n*", frame.f_code.co_filename, self.cur_file_name, frame.f_lineno)
136 121
137 if event == 'call': 122 if event == 'call':
150 # Entering a new frame. Decide if we should trace 135 # Entering a new frame. Decide if we should trace
151 # in this file. 136 # in this file.
152 self._activity = True 137 self._activity = True
153 self.data_stack.append( 138 self.data_stack.append(
154 ( 139 (
155 self.cur_file_dict, 140 self.cur_file_data,
156 self.cur_file_name, 141 self.cur_file_name,
157 self.last_line, 142 self.last_line,
158 self.started_context, 143 self.started_context,
159 ) 144 )
160 ) 145 )
163 disp = self.should_trace_cache.get(filename) 148 disp = self.should_trace_cache.get(filename)
164 if disp is None: 149 if disp is None:
165 disp = self.should_trace(filename, frame) 150 disp = self.should_trace(filename, frame)
166 self.should_trace_cache[filename] = disp 151 self.should_trace_cache[filename] = disp
167 152
168 self.cur_file_dict = None 153 self.cur_file_data = None
169 if disp.trace: 154 if disp.trace:
170 tracename = disp.source_filename 155 tracename = disp.source_filename
171 if tracename not in self.data: 156 if tracename not in self.data:
172 self.data[tracename] = {} 157 self.data[tracename] = set()
173 self.cur_file_dict = self.data[tracename] 158 self.cur_file_data = self.data[tracename]
174 # The call event is really a "start frame" event, and happens for 159 # The call event is really a "start frame" event, and happens for
175 # function calls and re-entering generators. The f_lasti field is 160 # function calls and re-entering generators. The f_lasti field is
176 # -1 for calls, and a real offset for generators. Use <0 as the 161 # -1 for calls, and a real offset for generators. Use <0 as the
177 # line number for calls, and the real line number for generators. 162 # line number for calls, and the real line number for generators.
178 if getattr(frame, 'f_lasti', -1) < 0: 163 if getattr(frame, 'f_lasti', -1) < 0:
179 self.last_line = -frame.f_code.co_firstlineno 164 self.last_line = -frame.f_code.co_firstlineno
180 else: 165 else:
181 self.last_line = frame.f_lineno 166 self.last_line = frame.f_lineno
182 elif event == 'line': 167 elif event == 'line':
183 # Record an executed line. 168 # Record an executed line.
184 if self.cur_file_dict is not None: 169 if self.cur_file_data is not None:
185 lineno = frame.f_lineno 170 lineno = frame.f_lineno
186 171
187 if self.trace_arcs: 172 if self.trace_arcs:
188 self.cur_file_dict[(self.last_line, lineno)] = None 173 self.cur_file_data.add((self.last_line, lineno))
189 else: 174 else:
190 self.cur_file_dict[lineno] = None 175 self.cur_file_data.add(lineno)
191 self.last_line = lineno 176 self.last_line = lineno
192 elif event == 'return': 177 elif event == 'return':
193 if self.trace_arcs and self.cur_file_dict: 178 if self.trace_arcs and self.cur_file_data:
194 # Record an arc leaving the function, but beware that a 179 # Record an arc leaving the function, but beware that a
195 # "return" event might just mean yielding from a generator. 180 # "return" event might just mean yielding from a generator.
196 # Jython seems to have an empty co_code, so just assume return. 181 # Jython seems to have an empty co_code, so just assume return.
197 code = frame.f_code.co_code 182 code = frame.f_code.co_code
198 if (not code) or code[frame.f_lasti] != YIELD_VALUE: 183 if (not code) or code[frame.f_lasti] != YIELD_VALUE:
199 first = frame.f_code.co_firstlineno 184 first = frame.f_code.co_firstlineno
200 self.cur_file_dict[(self.last_line, -first)] = None 185 self.cur_file_data.add((self.last_line, -first))
201 # Leaving this function, pop the filename stack. 186 # Leaving this function, pop the filename stack.
202 self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = ( 187 self.cur_file_data, self.cur_file_name, self.last_line, self.started_context = (
203 self.data_stack.pop() 188 self.data_stack.pop()
204 ) 189 )
205 # Leaving a context? 190 # Leaving a context?
206 if self.started_context: 191 if self.started_context:
207 self.context = None 192 self.context = None
208 self.switch_context(None) 193 self.switch_context(None)
209 elif event == 'exception':
210 self.last_exc_back = frame.f_back
211 self.last_exc_firstlineno = frame.f_code.co_firstlineno
212 return self._trace 194 return self._trace
213 195
214 def start(self): 196 def start(self):
215 """Start this Tracer. 197 """Start this Tracer.
216 198
218 200
219 """ 201 """
220 self.stopped = False 202 self.stopped = False
221 if self.threading: 203 if self.threading:
222 if self.thread is None: 204 if self.thread is None:
223 self.thread = self.threading.currentThread() 205 self.thread = self.threading.current_thread()
224 else: 206 else:
225 if self.thread.ident != self.threading.currentThread().ident: 207 if self.thread.ident != self.threading.current_thread().ident:
226 # Re-starting from a different thread!? Don't set the trace 208 # Re-starting from a different thread!? Don't set the trace
227 # function, but we are marked as running again, so maybe it 209 # function, but we are marked as running again, so maybe it
228 # will be ok? 210 # will be ok?
229 #self.log("~", "starting on different threads") 211 #self.log("~", "starting on different threads")
230 return self._trace 212 return self._trace
241 # Set the stop flag. The actual call to sys.settrace(None) will happen 223 # Set the stop flag. The actual call to sys.settrace(None) will happen
242 # in the self._trace callback itself to make sure to call it from the 224 # in the self._trace callback itself to make sure to call it from the
243 # right thread. 225 # right thread.
244 self.stopped = True 226 self.stopped = True
245 227
246 if self.threading and self.thread.ident != self.threading.currentThread().ident: 228 if self.threading and self.thread.ident != self.threading.current_thread().ident:
247 # Called on a different thread than started us: we can't unhook 229 # Called on a different thread than started us: we can't unhook
248 # ourselves, but we've set the flag that we should stop, so we 230 # ourselves, but we've set the flag that we should stop, so we
249 # won't do any more tracing. 231 # won't do any more tracing.
250 #self.log("~", "stopping on different threads") 232 #self.log("~", "stopping on different threads")
251 return 233 return
254 # PyPy clears the trace function before running atexit functions, 236 # PyPy clears the trace function before running atexit functions,
255 # so don't warn if we are in atexit on PyPy and the trace function 237 # so don't warn if we are in atexit on PyPy and the trace function
256 # has changed to None. 238 # has changed to None.
257 dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None) 239 dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None)
258 if (not dont_warn) and tf != self._trace: # pylint: disable=comparison-with-callable 240 if (not dont_warn) and tf != self._trace: # pylint: disable=comparison-with-callable
259 self.warn( 241 msg = f"Trace function changed, measurement is likely wrong: {tf!r}"
260 "Trace function changed, measurement is likely wrong: %r" % (tf,), 242 self.warn(msg, slug="trace-changed")
261 slug="trace-changed",
262 )
263 243
264 def activity(self): 244 def activity(self):
265 """Has there been any activity?""" 245 """Has there been any activity?"""
266 return self._activity 246 return self._activity
267 247

eric ide

mercurial