diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2009-03-05 21:21:43 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2009-03-05 21:21:43 -0500 |
commit | 0cc8fd18714cb214327a0a58b704401a9efdf8dc (patch) | |
tree | 0a6ac59eaf1b7acaf26053a5f3c69304b9ddbda3 /coverage/collector.py | |
download | python-coveragepy-git-0cc8fd18714cb214327a0a58b704401a9efdf8dc.tar.gz |
Initial coverage.py 3.0 beta 1
Diffstat (limited to 'coverage/collector.py')
-rw-r--r-- | coverage/collector.py | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/coverage/collector.py b/coverage/collector.py new file mode 100644 index 00000000..dfcfeb6d --- /dev/null +++ b/coverage/collector.py @@ -0,0 +1,110 @@ +"""Raw data collector for coverage.py.""" + +import sys, threading + +try: + # Use the C extension code when we can, for speed. + from coverage.tracer import Tracer +except ImportError: + # If we don't have the C tracer, use this Python one. + class Tracer: + """Python implementation of the raw data tracer.""" + def __init__(self): + self.cur_filename = None + self.filename_stack = [] + + def _global_trace(self, frame, event, arg_unused): + """The trace function passed to sys.settrace.""" + if event == 'call': + filename = frame.f_code.co_filename + tracename = self.should_trace_cache.get(filename) + if tracename is None: + tracename = self.should_trace(filename) + self.should_trace_cache[filename] = tracename + if tracename: + self.filename_stack.append(self.cur_filename) + self.cur_filename = tracename + return self._local_trace + else: + return None + return self.trace + + def _local_trace(self, frame, event, arg_unused): + if event == 'line': + self.data[(self.cur_filename, frame.f_lineno)] = True + elif event == 'return': + self.cur_filename = self.filename_stack.pop() + return self._local_trace + + def start(self): + sys.settrace(self._global_trace) + + def stop(self): + sys.settrace(None) + + +class Collector: + """Collects trace data. + + Creates a Tracer object for each thread, since they track stack information. + Each Tracer points to the same shared data, contributing traced data points. + + """ + + def __init__(self, should_trace): + """Create a collector. + + `should_trace` is a function, taking a filename, and returns a + canonicalized filename, or False depending on whether the file should be + traced or not. + + """ + self.should_trace = should_trace + self.reset() + + def reset(self): + # A dictionary with an entry for (Python source file name, line number + # in that file) if that line has been executed. + self.data = {} + + # A cache of the decision about whether to trace execution in a file. + # A dict of filename to boolean. + self.should_trace_cache = {} + + def _start_tracer(self): + tracer = Tracer() + tracer.data = self.data + tracer.should_trace = self.should_trace + tracer.should_trace_cache = self.should_trace_cache + tracer.start() + return tracer + + # The trace function has to be set individually on each thread before + # execution begins. Ironically, the only support the threading module has + # for running code before the thread main is the tracing function. So we + # install this as a trace function, and the first time it's called, it does + # the real trace installation. + + def _installation_trace(self, frame_unused, event_unused, arg_unused): + """Called on new threads, installs the real tracer.""" + # Remove ourselves as the trace function + sys.settrace(None) + # Install the real tracer + self._start_tracer() + # Return None to reiterate that we shouldn't be used for tracing. + return None + + def start(self): + # Install the tracer on this thread. + self.tracer = self._start_tracer() + # Install our installation tracer in threading, to jump start other + # threads. + threading.settrace(self._installation_trace) + + def stop(self): + self.tracer.stop() + threading.settrace(None) + + def data_points(self): + """Return the (filename, lineno) pairs collected.""" + return self.data.keys() |