summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.txt1
-rw-r--r--coverage/cmdline.py7
-rw-r--r--coverage/collector.py30
-rw-r--r--coverage/config.py2
-rw-r--r--coverage/control.py11
-rw-r--r--tests/test_cmdline.py1
-rw-r--r--tox.ini4
7 files changed, 52 insertions, 4 deletions
diff --git a/TODO.txt b/TODO.txt
index 4139d307..36b7c29a 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -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=[],
diff --git a/tox.ini b/tox.ini
index feca080c..e5984660 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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