DebugClients/Python/coverage/pytracer.py

changeset 6219
d6c795b5ce33
parent 5178
878ce843ca9f
equal deleted inserted replaced
6218:bedab77d0fa3 6219:d6c795b5ce33
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 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 2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
3 3
4 """Raw data collector for coverage.py.""" 4 """Raw data collector for coverage.py."""
5 5
6 import atexit
6 import dis 7 import dis
7 import sys 8 import sys
8 9
9 from coverage import env 10 from coverage import env
10 11
41 self.should_trace_cache = None 42 self.should_trace_cache = None
42 self.warn = None 43 self.warn = None
43 # The threading module to use, if any. 44 # The threading module to use, if any.
44 self.threading = None 45 self.threading = None
45 46
46 self.cur_file_dict = [] 47 self.cur_file_dict = None
47 self.last_line = [0] 48 self.last_line = 0 # int, but uninitialized.
49 self.cur_file_name = None
48 50
49 self.data_stack = [] 51 self.data_stack = []
50 self.last_exc_back = None 52 self.last_exc_back = None
51 self.last_exc_firstlineno = 0 53 self.last_exc_firstlineno = 0
52 self.thread = None 54 self.thread = None
53 self.stopped = False 55 self.stopped = False
56 self._activity = False
57
58 self.in_atexit = False
59 # On exit, self.in_atexit = True
60 atexit.register(setattr, self, 'in_atexit', True)
54 61
55 def __repr__(self): 62 def __repr__(self):
56 return "<PyTracer at 0x{0:0x}: {1} lines in {2} files>".format( 63 return "<PyTracer at {0}: {1} lines in {2} files>".format(
57 id(self), 64 id(self),
58 sum(len(v) for v in self.data.values()), 65 sum(len(v) for v in self.data.values()),
59 len(self.data), 66 len(self.data),
60 ) 67 )
61 68
69 def log(self, marker, *args):
70 """For hard-core logging of what this tracer is doing."""
71 with open("/tmp/debug_trace.txt", "a") as f:
72 f.write("{} {:x}.{:x}[{}] {:x} {}\n".format(
73 marker,
74 id(self),
75 self.thread.ident,
76 len(self.data_stack),
77 self.threading.currentThread().ident,
78 " ".join(map(str, args))
79 ))
80
62 def _trace(self, frame, event, arg_unused): 81 def _trace(self, frame, event, arg_unused):
63 """The trace function passed to sys.settrace.""" 82 """The trace function passed to sys.settrace."""
64 83
65 if self.stopped: 84 #self.log(":", frame.f_code.co_filename, frame.f_lineno, event)
66 return 85
86 if (self.stopped and sys.gettrace() == self._trace):
87 # The PyTrace.stop() method has been called, possibly by another
88 # thread, let's deactivate ourselves now.
89 #self.log("X", frame.f_code.co_filename, frame.f_lineno)
90 sys.settrace(None)
91 return None
67 92
68 if self.last_exc_back: 93 if self.last_exc_back:
69 if frame == self.last_exc_back: 94 if frame == self.last_exc_back:
70 # Someone forgot a return event. 95 # Someone forgot a return event.
71 if self.trace_arcs and self.cur_file_dict: 96 if self.trace_arcs and self.cur_file_dict:
72 pair = (self.last_line, -self.last_exc_firstlineno) 97 pair = (self.last_line, -self.last_exc_firstlineno)
73 self.cur_file_dict[pair] = None 98 self.cur_file_dict[pair] = None
74 self.cur_file_dict, self.last_line = self.data_stack.pop() 99 self.cur_file_dict, self.cur_file_name, self.last_line = self.data_stack.pop()
75 self.last_exc_back = None 100 self.last_exc_back = None
76 101
77 if event == 'call': 102 if event == 'call':
78 # Entering a new function context. Decide if we should trace 103 # Entering a new function context. Decide if we should trace
79 # in this file. 104 # in this file.
80 self.data_stack.append((self.cur_file_dict, self.last_line)) 105 self._activity = True
106 self.data_stack.append((self.cur_file_dict, self.cur_file_name, self.last_line))
81 filename = frame.f_code.co_filename 107 filename = frame.f_code.co_filename
108 self.cur_file_name = filename
82 disp = self.should_trace_cache.get(filename) 109 disp = self.should_trace_cache.get(filename)
83 if disp is None: 110 if disp is None:
84 disp = self.should_trace(filename, frame) 111 disp = self.should_trace(filename, frame)
85 self.should_trace_cache[filename] = disp 112 self.should_trace_cache[filename] = disp
86 113
92 self.cur_file_dict = self.data[tracename] 119 self.cur_file_dict = self.data[tracename]
93 # The call event is really a "start frame" event, and happens for 120 # The call event is really a "start frame" event, and happens for
94 # function calls and re-entering generators. The f_lasti field is 121 # function calls and re-entering generators. The f_lasti field is
95 # -1 for calls, and a real offset for generators. Use <0 as the 122 # -1 for calls, and a real offset for generators. Use <0 as the
96 # line number for calls, and the real line number for generators. 123 # line number for calls, and the real line number for generators.
97 if frame.f_lasti < 0: 124 if getattr(frame, 'f_lasti', -1) < 0:
98 self.last_line = -frame.f_code.co_firstlineno 125 self.last_line = -frame.f_code.co_firstlineno
99 else: 126 else:
100 self.last_line = frame.f_lineno 127 self.last_line = frame.f_lineno
101 elif event == 'line': 128 elif event == 'line':
102 # Record an executed line. 129 # Record an executed line.
103 if self.cur_file_dict is not None: 130 if self.cur_file_dict is not None:
104 lineno = frame.f_lineno 131 lineno = frame.f_lineno
132 #if frame.f_code.co_filename != self.cur_file_name:
133 # self.log("*", frame.f_code.co_filename, self.cur_file_name, lineno)
105 if self.trace_arcs: 134 if self.trace_arcs:
106 self.cur_file_dict[(self.last_line, lineno)] = None 135 self.cur_file_dict[(self.last_line, lineno)] = None
107 else: 136 else:
108 self.cur_file_dict[lineno] = None 137 self.cur_file_dict[lineno] = None
109 self.last_line = lineno 138 self.last_line = lineno
110 elif event == 'return': 139 elif event == 'return':
111 if self.trace_arcs and self.cur_file_dict: 140 if self.trace_arcs and self.cur_file_dict:
112 # Record an arc leaving the function, but beware that a 141 # Record an arc leaving the function, but beware that a
113 # "return" event might just mean yielding from a generator. 142 # "return" event might just mean yielding from a generator.
114 bytecode = frame.f_code.co_code[frame.f_lasti] 143 # Jython seems to have an empty co_code, so just assume return.
115 if bytecode != YIELD_VALUE: 144 code = frame.f_code.co_code
145 if (not code) or code[frame.f_lasti] != YIELD_VALUE:
116 first = frame.f_code.co_firstlineno 146 first = frame.f_code.co_firstlineno
117 self.cur_file_dict[(self.last_line, -first)] = None 147 self.cur_file_dict[(self.last_line, -first)] = None
118 # Leaving this function, pop the filename stack. 148 # Leaving this function, pop the filename stack.
119 self.cur_file_dict, self.last_line = self.data_stack.pop() 149 self.cur_file_dict, self.cur_file_name, self.last_line = self.data_stack.pop()
120 elif event == 'exception': 150 elif event == 'exception':
121 self.last_exc_back = frame.f_back 151 self.last_exc_back = frame.f_back
122 self.last_exc_firstlineno = frame.f_code.co_firstlineno 152 self.last_exc_firstlineno = frame.f_code.co_firstlineno
123 return self._trace 153 return self._trace
124 154
126 """Start this Tracer. 156 """Start this Tracer.
127 157
128 Return a Python function suitable for use with sys.settrace(). 158 Return a Python function suitable for use with sys.settrace().
129 159
130 """ 160 """
161 self.stopped = False
131 if self.threading: 162 if self.threading:
132 self.thread = self.threading.currentThread() 163 if self.thread is None:
164 self.thread = self.threading.currentThread()
165 else:
166 if self.thread.ident != self.threading.currentThread().ident:
167 # Re-starting from a different thread!? Don't set the trace
168 # function, but we are marked as running again, so maybe it
169 # will be ok?
170 #self.log("~", "starting on different threads")
171 return self._trace
172
133 sys.settrace(self._trace) 173 sys.settrace(self._trace)
134 self.stopped = False
135 return self._trace 174 return self._trace
136 175
137 def stop(self): 176 def stop(self):
138 """Stop this Tracer.""" 177 """Stop this Tracer."""
178 # Get the activate tracer callback before setting the stop flag to be
179 # able to detect if the tracer was changed prior to stopping it.
180 tf = sys.gettrace()
181
182 # Set the stop flag. The actual call to sys.settrace(None) will happen
183 # in the self._trace callback itself to make sure to call it from the
184 # right thread.
139 self.stopped = True 185 self.stopped = True
186
140 if self.threading and self.thread.ident != self.threading.currentThread().ident: 187 if self.threading and self.thread.ident != self.threading.currentThread().ident:
141 # Called on a different thread than started us: we can't unhook 188 # Called on a different thread than started us: we can't unhook
142 # ourselves, but we've set the flag that we should stop, so we 189 # ourselves, but we've set the flag that we should stop, so we
143 # won't do any more tracing. 190 # won't do any more tracing.
191 #self.log("~", "stopping on different threads")
144 return 192 return
145 193
146 if self.warn: 194 if self.warn:
147 if sys.gettrace() != self._trace: 195 # PyPy clears the trace function before running atexit functions,
148 msg = "Trace function changed, measurement is likely wrong: %r" 196 # so don't warn if we are in atexit on PyPy and the trace function
149 self.warn(msg % (sys.gettrace(),)) 197 # has changed to None.
150 198 dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None)
151 sys.settrace(None) 199 if (not dont_warn) and tf != self._trace:
200 self.warn(
201 "Trace function changed, measurement is likely wrong: %r" % (tf,),
202 slug="trace-changed",
203 )
204
205 def activity(self):
206 """Has there been any activity?"""
207 return self._activity
208
209 def reset_activity(self):
210 """Reset the activity() flag."""
211 self._activity = False
152 212
153 def get_stats(self): 213 def get_stats(self):
154 """Return a dictionary of statistics, or None.""" 214 """Return a dictionary of statistics, or None."""
155 return None 215 return None

eric ide

mercurial