src/eric7/DebugClients/Python/coverage/pytracer.py

branch
eric7
changeset 9252
32dd11232e06
parent 9209
b99e7fd55fd3
equal deleted inserted replaced
9251:e0f98cc25bf5 9252:32dd11232e06
64 self._activity = False 64 self._activity = False
65 65
66 self.in_atexit = False 66 self.in_atexit = False
67 # On exit, self.in_atexit = True 67 # On exit, self.in_atexit = True
68 atexit.register(setattr, self, 'in_atexit', True) 68 atexit.register(setattr, self, 'in_atexit', True)
69
70 # Cache a bound method on the instance, so that we don't have to
71 # re-create a bound method object all the time.
72 self._cached_bound_method_trace = self._trace
69 73
70 def __repr__(self): 74 def __repr__(self):
71 return "<PyTracer at 0x{:x}: {} lines in {} files>".format( 75 return "<PyTracer at 0x{:x}: {} lines in {} files>".format(
72 id(self), 76 id(self),
73 sum(len(v) for v in self.data.values()), 77 sum(len(v) for v in self.data.values()),
103 if THIS_FILE in frame.f_code.co_filename: 107 if THIS_FILE in frame.f_code.co_filename:
104 return None 108 return None
105 109
106 #self.log(":", frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + "()", event) 110 #self.log(":", frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + "()", event)
107 111
108 if (self.stopped and sys.gettrace() == self._trace): # pylint: disable=comparison-with-callable 112 if (self.stopped and sys.gettrace() == self._cached_bound_method_trace): # pylint: disable=comparison-with-callable
109 # The PyTrace.stop() method has been called, possibly by another 113 # The PyTrace.stop() method has been called, possibly by another
110 # thread, let's deactivate ourselves now. 114 # thread, let's deactivate ourselves now.
111 if 0: 115 if 0:
112 self.log("---\nX", frame.f_code.co_filename, frame.f_lineno) 116 self.log("---\nX", frame.f_code.co_filename, frame.f_lineno)
113 f = frame 117 f = frame
127 # Should we start a new context? 131 # Should we start a new context?
128 if self.should_start_context and self.context is None: 132 if self.should_start_context and self.context is None:
129 context_maybe = self.should_start_context(frame) 133 context_maybe = self.should_start_context(frame)
130 if context_maybe is not None: 134 if context_maybe is not None:
131 self.context = context_maybe 135 self.context = context_maybe
132 self.started_context = True 136 started_context = True
133 self.switch_context(self.context) 137 self.switch_context(self.context)
134 else: 138 else:
135 self.started_context = False 139 started_context = False
136 else: 140 else:
137 self.started_context = False 141 started_context = False
142 self.started_context = started_context
138 143
139 # Entering a new frame. Decide if we should trace in this file. 144 # Entering a new frame. Decide if we should trace in this file.
140 self._activity = True 145 self._activity = True
141 self.data_stack.append( 146 self.data_stack.append(
142 ( 147 (
143 self.cur_file_data, 148 self.cur_file_data,
144 self.cur_file_name, 149 self.cur_file_name,
145 self.last_line, 150 self.last_line,
146 self.started_context, 151 started_context,
147 ) 152 )
148 ) 153 )
154
155 # Improve tracing performance: when calling a function, both caller
156 # and callee are often within the same file. if that's the case, we
157 # don't have to re-check whether to trace the corresponding
158 # function (which is a little bit espensive since it involves
159 # dictionary lookups). This optimization is only correct if we
160 # didn't start a context.
149 filename = frame.f_code.co_filename 161 filename = frame.f_code.co_filename
150 self.cur_file_name = filename 162 if filename != self.cur_file_name or started_context:
151 disp = self.should_trace_cache.get(filename) 163 self.cur_file_name = filename
152 if disp is None: 164 disp = self.should_trace_cache.get(filename)
153 disp = self.should_trace(filename, frame) 165 if disp is None:
154 self.should_trace_cache[filename] = disp 166 disp = self.should_trace(filename, frame)
155 167 self.should_trace_cache[filename] = disp
156 self.cur_file_data = None 168
157 if disp.trace: 169 self.cur_file_data = None
158 tracename = disp.source_filename 170 if disp.trace:
159 if tracename not in self.data: 171 tracename = disp.source_filename
160 self.data[tracename] = set() 172 if tracename not in self.data:
161 self.cur_file_data = self.data[tracename] 173 self.data[tracename] = set()
174 self.cur_file_data = self.data[tracename]
175 else:
176 frame.f_trace_lines = False
177 elif not self.cur_file_data:
178 frame.f_trace_lines = False
179
162 # The call event is really a "start frame" event, and happens for 180 # The call event is really a "start frame" event, and happens for
163 # function calls and re-entering generators. The f_lasti field is 181 # function calls and re-entering generators. The f_lasti field is
164 # -1 for calls, and a real offset for generators. Use <0 as the 182 # -1 for calls, and a real offset for generators. Use <0 as the
165 # line number for calls, and the real line number for generators. 183 # line number for calls, and the real line number for generators.
166 if RESUME is not None: 184 if RESUME is not None:
172 real_call = (getattr(frame, 'f_lasti', -1) < 0) 190 real_call = (getattr(frame, 'f_lasti', -1) < 0)
173 if real_call: 191 if real_call:
174 self.last_line = -frame.f_code.co_firstlineno 192 self.last_line = -frame.f_code.co_firstlineno
175 else: 193 else:
176 self.last_line = frame.f_lineno 194 self.last_line = frame.f_lineno
195
177 elif event == 'line': 196 elif event == 'line':
178 # Record an executed line. 197 # Record an executed line.
179 if self.cur_file_data is not None: 198 if self.cur_file_data is not None:
180 lineno = frame.f_lineno 199 lineno = frame.f_lineno
181 200
182 if self.trace_arcs: 201 if self.trace_arcs:
183 self.cur_file_data.add((self.last_line, lineno)) 202 self.cur_file_data.add((self.last_line, lineno))
184 else: 203 else:
185 self.cur_file_data.add(lineno) 204 self.cur_file_data.add(lineno)
186 self.last_line = lineno 205 self.last_line = lineno
206
187 elif event == 'return': 207 elif event == 'return':
188 if self.trace_arcs and self.cur_file_data: 208 if self.trace_arcs and self.cur_file_data:
189 # Record an arc leaving the function, but beware that a 209 # Record an arc leaving the function, but beware that a
190 # "return" event might just mean yielding from a generator. 210 # "return" event might just mean yielding from a generator.
191 code = frame.f_code.co_code 211 code = frame.f_code.co_code
209 else: 229 else:
210 real_return = True 230 real_return = True
211 if real_return: 231 if real_return:
212 first = frame.f_code.co_firstlineno 232 first = frame.f_code.co_firstlineno
213 self.cur_file_data.add((self.last_line, -first)) 233 self.cur_file_data.add((self.last_line, -first))
234
214 # Leaving this function, pop the filename stack. 235 # Leaving this function, pop the filename stack.
215 self.cur_file_data, self.cur_file_name, self.last_line, self.started_context = ( 236 self.cur_file_data, self.cur_file_name, self.last_line, self.started_context = (
216 self.data_stack.pop() 237 self.data_stack.pop()
217 ) 238 )
218 # Leaving a context? 239 # Leaving a context?
219 if self.started_context: 240 if self.started_context:
220 self.context = None 241 self.context = None
221 self.switch_context(None) 242 self.switch_context(None)
222 return self._trace 243 return self._cached_bound_method_trace
223 244
224 def start(self): 245 def start(self):
225 """Start this Tracer. 246 """Start this Tracer.
226 247
227 Return a Python function suitable for use with sys.settrace(). 248 Return a Python function suitable for use with sys.settrace().
235 if self.thread.ident != self.threading.current_thread().ident: 256 if self.thread.ident != self.threading.current_thread().ident:
236 # Re-starting from a different thread!? Don't set the trace 257 # Re-starting from a different thread!? Don't set the trace
237 # function, but we are marked as running again, so maybe it 258 # function, but we are marked as running again, so maybe it
238 # will be ok? 259 # will be ok?
239 #self.log("~", "starting on different threads") 260 #self.log("~", "starting on different threads")
240 return self._trace 261 return self._cached_bound_method_trace
241 262
242 sys.settrace(self._trace) 263 sys.settrace(self._cached_bound_method_trace)
243 return self._trace 264 return self._cached_bound_method_trace
244 265
245 def stop(self): 266 def stop(self):
246 """Stop this Tracer.""" 267 """Stop this Tracer."""
247 # Get the active tracer callback before setting the stop flag to be 268 # Get the active tracer callback before setting the stop flag to be
248 # able to detect if the tracer was changed prior to stopping it. 269 # able to detect if the tracer was changed prior to stopping it.
263 if self.warn: 284 if self.warn:
264 # PyPy clears the trace function before running atexit functions, 285 # PyPy clears the trace function before running atexit functions,
265 # so don't warn if we are in atexit on PyPy and the trace function 286 # so don't warn if we are in atexit on PyPy and the trace function
266 # has changed to None. 287 # has changed to None.
267 dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None) 288 dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None)
268 if (not dont_warn) and tf != self._trace: # pylint: disable=comparison-with-callable 289 if (not dont_warn) and tf != self._cached_bound_method_trace: # pylint: disable=comparison-with-callable
269 self.warn( 290 self.warn(
270 f"Trace function changed, data is likely wrong: {tf!r} != {self._trace!r}", 291 "Trace function changed, data is likely wrong: " +
292 f"{tf!r} != {self._cached_bound_method_trace!r}",
271 slug="trace-changed", 293 slug="trace-changed",
272 ) 294 )
273 295
274 def activity(self): 296 def activity(self):
275 """Has there been any activity?""" 297 """Has there been any activity?"""

eric ide

mercurial