diff options
-rw-r--r-- | TODO.txt | 1 | ||||
-rw-r--r-- | coverage/cmdline.py | 7 | ||||
-rw-r--r-- | coverage/collector.py | 30 | ||||
-rw-r--r-- | coverage/config.py | 2 | ||||
-rw-r--r-- | coverage/control.py | 11 | ||||
-rw-r--r-- | tests/test_cmdline.py | 1 | ||||
-rw-r--r-- | tox.ini | 4 |
7 files changed, 52 insertions, 4 deletions
@@ -22,6 +22,7 @@ Key: - .format() ? + try/except/finally + with assertRaises + - addCleaup instead of tearDown + exec statement can look like a function in py2 (since when?) - runpy ? diff --git a/coverage/cmdline.py b/coverage/cmdline.py index c311976d..19e0536e 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -19,6 +19,10 @@ class Opts(object): '', '--branch', action='store_true', help="Measure branch coverage in addition to statement coverage." ) + coroutine = optparse.make_option( + '', '--coroutine', action='store', metavar="LIB", + help="Properly measure code using coroutines." + ) debug = optparse.make_option( '', '--debug', action='store', metavar="OPTS", help="Debug options, separated by commas" @@ -121,6 +125,7 @@ class CoverageOptionParser(optparse.OptionParser, object): self.set_defaults( actions=[], branch=None, + coroutine=None, debug=None, directory=None, fail_under=None, @@ -315,6 +320,7 @@ CMDS = { [ Opts.append, Opts.branch, + Opts.coroutine, Opts.debug, Opts.pylib, Opts.parallel_mode, @@ -423,6 +429,7 @@ class CoverageScript(object): omit = omit, include = include, debug = debug, + coroutine = options.coroutine, ) if 'debug' in options.actions: diff --git a/coverage/collector.py b/coverage/collector.py index 8ba7d87c..94af5df5 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -1,6 +1,6 @@ """Raw data collector for Coverage.""" -import os, sys, threading +import collections, os, sys, threading try: # Use the C extension code when we can, for speed. @@ -48,11 +48,14 @@ class PyTracer(object): self.cur_file_data = None self.last_line = 0 self.data_stack = [] + self.data_stacks = collections.defaultdict(list) self.last_exc_back = None self.last_exc_firstlineno = 0 self.arcs = False self.thread = None self.stopped = False + self.coroutine_id_func = None + self.last_coroutine = None def _trace(self, frame, event, arg_unused): """The trace function passed to sys.settrace.""" @@ -71,12 +74,17 @@ class PyTracer(object): if self.arcs and self.cur_file_data: pair = (self.last_line, -self.last_exc_firstlineno) self.cur_file_data[pair] = None + if self.coroutine_id_func: + self.data_stack = self.data_stacks[self.coroutine_id_func()] self.cur_file_data, self.last_line = self.data_stack.pop() self.last_exc_back = None if event == 'call': # Entering a new function context. Decide if we should trace # in this file. + if self.coroutine_id_func: + self.data_stack = self.data_stacks[self.coroutine_id_func()] + self.last_coroutine = self.coroutine_id_func() self.data_stack.append((self.cur_file_data, self.last_line)) filename = frame.f_code.co_filename if filename not in self.should_trace_cache: @@ -97,6 +105,8 @@ class PyTracer(object): self.last_line = -1 elif event == 'line': # Record an executed line. + #if self.coroutine_id_func: + # assert self.last_coroutine == self.coroutine_id_func() if self.cur_file_data is not None: if self.arcs: #print("lin", self.last_line, frame.f_lineno) @@ -110,6 +120,9 @@ class PyTracer(object): first = frame.f_code.co_firstlineno self.cur_file_data[(self.last_line, -first)] = None # Leaving this function, pop the filename stack. + if self.coroutine_id_func: + self.data_stack = self.data_stacks[self.coroutine_id_func()] + self.last_coroutine = self.coroutine_id_func() self.cur_file_data, self.last_line = self.data_stack.pop() #print("returned, stack is %d deep" % (len(self.data_stack))) elif event == 'exception': @@ -170,7 +183,7 @@ class Collector(object): # the top, and resumed when they become the top again. _collectors = [] - def __init__(self, should_trace, timid, branch, warn): + def __init__(self, should_trace, timid, branch, warn, coroutine): """Create a collector. `should_trace` is a function, taking a filename, and returning a @@ -193,6 +206,17 @@ class Collector(object): self.should_trace = should_trace self.warn = warn self.branch = branch + if coroutine == "greenlet": + import greenlet + self.coroutine_id_func = greenlet.getcurrent + elif coroutine == "eventlet": + import eventlet.greenthread + self.coroutine_id_func = eventlet.greenthread.getcurrent + elif coroutine == "gevent": + import gevent + self.coroutine_id_func = gevent.getcurrent + else: + self.coroutine_id_func = None self.reset() if timid: @@ -232,6 +256,8 @@ class Collector(object): tracer.should_trace = self.should_trace tracer.should_trace_cache = self.should_trace_cache tracer.warn = self.warn + if hasattr(tracer, 'coroutine_id_func'): + tracer.coroutine_id_func = self.coroutine_id_func fn = tracer.start() self.tracers.append(tracer) return fn diff --git a/coverage/config.py b/coverage/config.py index 6223afda..60ec3f41 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -106,6 +106,7 @@ class CoverageConfig(object): # Defaults for [run] self.branch = False + self.coroutine = None self.cover_pylib = False self.data_file = ".coverage" self.parallel = False @@ -177,6 +178,7 @@ class CoverageConfig(object): CONFIG_FILE_OPTIONS = [ # [run] ('branch', 'run:branch', 'boolean'), + ('coroutine', 'run:coroutine'), ('cover_pylib', 'run:cover_pylib', 'boolean'), ('data_file', 'run:data_file'), ('debug', 'run:debug', 'list'), diff --git a/coverage/control.py b/coverage/control.py index fa6fec74..d5e2c6f8 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -43,7 +43,7 @@ class coverage(object): def __init__(self, data_file=None, data_suffix=None, cover_pylib=None, auto_data=False, timid=None, branch=None, config_file=True, source=None, omit=None, include=None, debug=None, - debug_file=None): + debug_file=None, coroutine=None): """ `data_file` is the base name of the data file to use, defaulting to ".coverage". `data_suffix` is appended (with a dot) to `data_file` to @@ -82,6 +82,11 @@ class coverage(object): desired. `debug_file` is the file to write debug messages to, defaulting to stderr. + `coroutine` is a string indicating the coroutining library being used + in the measured code. Without this, coverage.py will get incorrect + results. Valid strings are "greenlet", "eventlet", or "gevent", which + are all equivalent. + """ from coverage import __version__ @@ -114,6 +119,7 @@ class coverage(object): data_file=data_file, cover_pylib=cover_pylib, timid=timid, branch=branch, parallel=bool_or_none(data_suffix), source=source, omit=omit, include=include, debug=debug, + coroutine=coroutine, ) # Create and configure the debugging controller. @@ -142,7 +148,8 @@ class coverage(object): self.collector = Collector( self._should_trace, timid=self.config.timid, - branch=self.config.branch, warn=self._warn + branch=self.config.branch, warn=self._warn, + coroutine=self.config.coroutine, ) # Suffixes are a bit tricky. We want to use the data suffix only when diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 3e92dc7e..99bae516 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -20,6 +20,7 @@ class CmdLineTest(CoverageTest): defaults.coverage( cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None, + coroutine=None, ) defaults.annotate( directory=None, ignore_errors=None, include=None, omit=None, morfs=[], @@ -31,12 +31,16 @@ deps = nose mock unittest2 + gevent + eventlet [testenv:py27] deps = nose mock unittest2 + gevent + eventlet [testenv:pypy] # PyPy has no C extensions |