summaryrefslogtreecommitdiff
path: root/coverage
diff options
context:
space:
mode:
Diffstat (limited to 'coverage')
-rw-r--r--coverage/collector.py69
-rw-r--r--coverage/config.py2
-rw-r--r--coverage/control.py20
-rw-r--r--coverage/ctracer/tracer.c5
-rw-r--r--coverage/ctracer/tracer.h3
-rw-r--r--coverage/sqldata.py2
6 files changed, 61 insertions, 40 deletions
diff --git a/coverage/collector.py b/coverage/collector.py
index e0144979..4e7058a0 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -34,14 +34,6 @@ except ImportError:
CTracer = None
-def should_start_context(frame):
- """Who-Tests-What hack: Determine whether this frame begins a new who-context."""
- fn_name = frame.f_code.co_name
- if fn_name.startswith("test"):
- return fn_name
- return None
-
-
class Collector(object):
"""Collects trace data.
@@ -66,7 +58,10 @@ class Collector(object):
# The concurrency settings we support here.
SUPPORTED_CONCURRENCIES = set(["greenlet", "eventlet", "gevent", "thread"])
- def __init__(self, should_trace, check_include, timid, branch, warn, concurrency):
+ def __init__(
+ self, should_trace, check_include, should_start_context,
+ timid, branch, warn, concurrency,
+ ):
"""Create a collector.
`should_trace` is a function, taking a file name and a frame, and
@@ -75,6 +70,11 @@ class Collector(object):
`check_include` is a function taking a file name and a frame. It returns
a boolean: True if the file should be traced, False if not.
+ `should_start_context` is a function taking a frame, and returning a
+ string. If the frame should be the start of a new context, the string
+ is the new context. If the frame should not be the start of a new
+ context, return None.
+
If `timid` is true, then a slower simpler trace function will be
used. This is important for some environments where manipulation of
tracing functions make the faster more sophisticated trace function not
@@ -96,6 +96,7 @@ class Collector(object):
"""
self.should_trace = should_trace
self.check_include = check_include
+ self.should_start_context = should_start_context
self.warn = warn
self.branch = branch
self.threading = None
@@ -139,10 +140,6 @@ class Collector(object):
)
)
- # Who-Tests-What is just a hack at the moment, so turn it on with an
- # environment variable.
- self.wtw = int(os.getenv('COVERAGE_WTW', 0))
-
self.reset()
if timid:
@@ -175,7 +172,11 @@ class Collector(object):
def _clear_data(self):
"""Clear out existing data, but stay ready for more collection."""
- self.data.clear()
+ # We used to used self.data.clear(), but that would remove filename
+ # keys and data values that were still in use higher up the stack
+ # when we are called as part of switch_context.
+ for d in self.data.values():
+ d.clear()
for tracer in self.tracers:
tracer.reset_activity()
@@ -187,10 +188,6 @@ class Collector(object):
# pairs as keys (if branch coverage).
self.data = {}
- # A dict mapping contexts to data dictionaries.
- self.contexts = {}
- self.contexts[None] = self.data
-
# A dictionary mapping file names to file tracer plugin names that will
# handle them.
self.file_tracers = {}
@@ -252,11 +249,13 @@ class Collector(object):
tracer.threading = self.threading
if hasattr(tracer, 'check_include'):
tracer.check_include = self.check_include
- if self.wtw:
- if hasattr(tracer, 'should_start_context'):
- tracer.should_start_context = should_start_context
- if hasattr(tracer, 'switch_context'):
- tracer.switch_context = self.switch_context
+ if hasattr(tracer, 'should_start_context'):
+ tracer.should_start_context = self.should_start_context
+ tracer.switch_context = self.switch_context
+ elif self.should_start_context:
+ raise CoverageException(
+ "Can't support dynamic contexts with {}".format(self.tracer_name())
+ )
fn = tracer.start()
self.tracers.append(tracer)
@@ -372,12 +371,15 @@ class Collector(object):
return any(tracer.activity() for tracer in self.tracers)
def switch_context(self, new_context):
- """Who-Tests-What hack: switch to a new who-context."""
- # Make a new data dict, or find the existing one, and switch all the
- # tracers to use it.
- data = self.contexts.setdefault(new_context, {})
- for tracer in self.tracers:
- tracer.data = data
+ """Switch to a new dynamic context."""
+ self.flush_data()
+ if self.static_context:
+ context = self.static_context
+ if new_context:
+ context += ":" + new_context
+ else:
+ context = new_context
+ self.covdata.set_context(context)
def cached_abs_file(self, filename):
"""A locally cached version of `abs_file`."""
@@ -415,7 +417,7 @@ class Collector(object):
else:
raise runtime_err # pylint: disable=raising-bad-type
- return dict((self.cached_abs_file(k), v) for k, v in items)
+ return dict((self.cached_abs_file(k), v) for k, v in items if v)
if self.branch:
self.covdata.add_arcs(abs_file_dict(self.data))
@@ -423,12 +425,5 @@ class Collector(object):
self.covdata.add_lines(abs_file_dict(self.data))
self.covdata.add_file_tracers(abs_file_dict(self.file_tracers))
- if self.wtw:
- # Just a hack, so just hack it.
- import pprint
- out_file = "coverage_wtw_{:06}.py".format(os.getpid())
- with open(out_file, "w") as wtw_out:
- pprint.pprint(self.contexts, wtw_out)
-
self._clear_data()
return True
diff --git a/coverage/config.py b/coverage/config.py
index 9a11323d..2a281875 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -180,6 +180,7 @@ class CoverageConfig(object):
self.data_file = ".coverage"
self.debug = []
self.disable_warnings = []
+ self.dynamic_context = None
self.note = None
self.parallel = False
self.plugins = []
@@ -324,6 +325,7 @@ class CoverageConfig(object):
('data_file', 'run:data_file'),
('debug', 'run:debug', 'list'),
('disable_warnings', 'run:disable_warnings', 'list'),
+ ('dynamic_context', 'run:dynamic_context'),
('note', 'run:note'),
('parallel', 'run:parallel', 'boolean'),
('plugins', 'run:plugins', 'list'),
diff --git a/coverage/control.py b/coverage/control.py
index 678a7b3b..4f2afda0 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -347,9 +347,19 @@ class Coverage(object):
# it for the main process.
self.config.parallel = True
+ if self.config.dynamic_context is None:
+ should_start_context = None
+ elif self.config.dynamic_context == "test_function":
+ should_start_context = should_start_context_test_function
+ else:
+ raise CoverageException(
+ "Don't understand dynamic_context setting: {!r}".format(self.config.dynamic_context)
+ )
+
self._collector = Collector(
should_trace=self._should_trace,
check_include=self._check_include_omit_etc,
+ should_start_context=should_start_context,
timid=self.config.timid,
branch=self.config.branch,
warn=self._warn,
@@ -886,6 +896,16 @@ if int(os.environ.get("COVERAGE_DEBUG_CALLS", 0)): # pragma: debugg
Coverage = decorate_methods(show_calls(show_args=True), butnot=['get_data'])(Coverage)
+def should_start_context_test_function(frame):
+ """Who-Tests-What hack: Determine whether this frame begins a new who-context."""
+ with open("/tmp/ssc.txt", "a") as f:
+ f.write("hello\n")
+ fn_name = frame.f_code.co_name
+ if fn_name.startswith("test"):
+ return fn_name
+ return None
+
+
def process_startup():
"""Call this at Python start-up to perhaps measure coverage.
diff --git a/coverage/ctracer/tracer.c b/coverage/ctracer/tracer.c
index 01f8b19b..7d639112 100644
--- a/coverage/ctracer/tracer.c
+++ b/coverage/ctracer/tracer.c
@@ -341,7 +341,6 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame)
CFileDisposition * pdisp = NULL;
STATS( self->stats.calls++; )
- self->activity = TRUE;
/* Grow the stack. */
if (CTracer_set_pdata_stack(self) < 0) {
@@ -353,7 +352,7 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame)
self->pcur_entry = &self->pdata_stack->stack[self->pdata_stack->depth];
/* See if this frame begins a new context. */
- if (self->should_start_context && self->context == Py_None) {
+ if (self->should_start_context != Py_None && self->context == Py_None) {
PyObject * context;
/* We're looking for our context, ask should_start_context if this is the start. */
STATS( self->stats.start_context_calls++; )
@@ -866,6 +865,8 @@ CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unuse
goto error;
}
+ self->activity = TRUE;
+
switch (what) {
case PyTrace_CALL:
if (CTracer_handle_call(self, frame) < 0) {
diff --git a/coverage/ctracer/tracer.h b/coverage/ctracer/tracer.h
index 61c01b41..a83742dd 100644
--- a/coverage/ctracer/tracer.h
+++ b/coverage/ctracer/tracer.h
@@ -27,7 +27,6 @@ typedef struct CTracer {
PyObject * trace_arcs;
PyObject * should_start_context;
PyObject * switch_context;
- PyObject * context;
/* Has the tracer been started? */
BOOL started;
@@ -35,6 +34,8 @@ typedef struct CTracer {
BOOL tracing_arcs;
/* Have we had any activity? */
BOOL activity;
+ /* The current dynamic context. */
+ PyObject * context;
/*
The data stack is a stack of dictionaries. Each dictionary collects
diff --git a/coverage/sqldata.py b/coverage/sqldata.py
index fb2279c9..738fccef 100644
--- a/coverage/sqldata.py
+++ b/coverage/sqldata.py
@@ -199,6 +199,8 @@ class CoverageSqliteData(SimpleReprMixin):
def set_context(self, context):
"""Set the current context for future `add_lines` etc."""
+ if self._debug and self._debug.should('dataop'):
+ self._debug.write("Setting context: %r" % (context,))
self._start_using()
context = context or ""
with self._connect() as con: