DebugClients/Python/coverage/pytracer.py

branch
debugger speed
changeset 5178
878ce843ca9f
parent 5051
3586ebd9fac8
child 6219
d6c795b5ce33
equal deleted inserted replaced
5174:8c48f5e0cd92 5178:878ce843ca9f
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
4 """Raw data collector for coverage.py."""
5
6 import dis
7 import sys
8
9 from coverage import env
10
11 # We need the YIELD_VALUE opcode below, in a comparison-friendly form.
12 YIELD_VALUE = dis.opmap['YIELD_VALUE']
13 if env.PY2:
14 YIELD_VALUE = chr(YIELD_VALUE)
15
16
17 class PyTracer(object):
18 """Python implementation of the raw data tracer."""
19
20 # Because of poor implementations of trace-function-manipulating tools,
21 # the Python trace function must be kept very simple. In particular, there
22 # must be only one function ever set as the trace function, both through
23 # sys.settrace, and as the return value from the trace function. Put
24 # another way, the trace function must always return itself. It cannot
25 # swap in other functions, or return None to avoid tracing a particular
26 # frame.
27 #
28 # The trace manipulator that introduced this restriction is DecoratorTools,
29 # which sets a trace function, and then later restores the pre-existing one
30 # by calling sys.settrace with a function it found in the current frame.
31 #
32 # Systems that use DecoratorTools (or similar trace manipulations) must use
33 # PyTracer to get accurate results. The command-line --timid argument is
34 # used to force the use of this tracer.
35
36 def __init__(self):
37 # Attributes set from the collector:
38 self.data = None
39 self.trace_arcs = False
40 self.should_trace = None
41 self.should_trace_cache = None
42 self.warn = None
43 # The threading module to use, if any.
44 self.threading = None
45
46 self.cur_file_dict = []
47 self.last_line = [0]
48
49 self.data_stack = []
50 self.last_exc_back = None
51 self.last_exc_firstlineno = 0
52 self.thread = None
53 self.stopped = False
54
55 def __repr__(self):
56 return "<PyTracer at 0x{0:0x}: {1} lines in {2} files>".format(
57 id(self),
58 sum(len(v) for v in self.data.values()),
59 len(self.data),
60 )
61
62 def _trace(self, frame, event, arg_unused):
63 """The trace function passed to sys.settrace."""
64
65 if self.stopped:
66 return
67
68 if self.last_exc_back:
69 if frame == self.last_exc_back:
70 # Someone forgot a return event.
71 if self.trace_arcs and self.cur_file_dict:
72 pair = (self.last_line, -self.last_exc_firstlineno)
73 self.cur_file_dict[pair] = None
74 self.cur_file_dict, self.last_line = self.data_stack.pop()
75 self.last_exc_back = None
76
77 if event == 'call':
78 # Entering a new function context. Decide if we should trace
79 # in this file.
80 self.data_stack.append((self.cur_file_dict, self.last_line))
81 filename = frame.f_code.co_filename
82 disp = self.should_trace_cache.get(filename)
83 if disp is None:
84 disp = self.should_trace(filename, frame)
85 self.should_trace_cache[filename] = disp
86
87 self.cur_file_dict = None
88 if disp.trace:
89 tracename = disp.source_filename
90 if tracename not in self.data:
91 self.data[tracename] = {}
92 self.cur_file_dict = self.data[tracename]
93 # The call event is really a "start frame" event, and happens for
94 # 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
96 # line number for calls, and the real line number for generators.
97 if frame.f_lasti < 0:
98 self.last_line = -frame.f_code.co_firstlineno
99 else:
100 self.last_line = frame.f_lineno
101 elif event == 'line':
102 # Record an executed line.
103 if self.cur_file_dict is not None:
104 lineno = frame.f_lineno
105 if self.trace_arcs:
106 self.cur_file_dict[(self.last_line, lineno)] = None
107 else:
108 self.cur_file_dict[lineno] = None
109 self.last_line = lineno
110 elif event == 'return':
111 if self.trace_arcs and self.cur_file_dict:
112 # Record an arc leaving the function, but beware that a
113 # "return" event might just mean yielding from a generator.
114 bytecode = frame.f_code.co_code[frame.f_lasti]
115 if bytecode != YIELD_VALUE:
116 first = frame.f_code.co_firstlineno
117 self.cur_file_dict[(self.last_line, -first)] = None
118 # Leaving this function, pop the filename stack.
119 self.cur_file_dict, self.last_line = self.data_stack.pop()
120 elif event == 'exception':
121 self.last_exc_back = frame.f_back
122 self.last_exc_firstlineno = frame.f_code.co_firstlineno
123 return self._trace
124
125 def start(self):
126 """Start this Tracer.
127
128 Return a Python function suitable for use with sys.settrace().
129
130 """
131 if self.threading:
132 self.thread = self.threading.currentThread()
133 sys.settrace(self._trace)
134 self.stopped = False
135 return self._trace
136
137 def stop(self):
138 """Stop this Tracer."""
139 self.stopped = True
140 if self.threading and self.thread.ident != self.threading.currentThread().ident:
141 # 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
143 # won't do any more tracing.
144 return
145
146 if self.warn:
147 if sys.gettrace() != self._trace:
148 msg = "Trace function changed, measurement is likely wrong: %r"
149 self.warn(msg % (sys.gettrace(),))
150
151 sys.settrace(None)
152
153 def get_stats(self):
154 """Return a dictionary of statistics, or None."""
155 return None

eric ide

mercurial