summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/collector.py22
-rw-r--r--coverage/ctracer/datastack.h2
-rw-r--r--coverage/ctracer/stats.h1
-rw-r--r--coverage/ctracer/tracer.c64
-rw-r--r--coverage/ctracer/tracer.h3
-rw-r--r--coverage/ctracer/util.h2
6 files changed, 91 insertions, 3 deletions
diff --git a/coverage/collector.py b/coverage/collector.py
index 0a43d87c..fff4f6ad 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -37,6 +37,11 @@ class FileDisposition(object):
pass
+def should_start_context(frame):
+ fn_name = frame.f_code.co_name
+ if fn_name.startswith("test"):
+ return fn_name
+
class Collector(object):
"""Collects trace data.
@@ -147,6 +152,10 @@ 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 = {}
@@ -206,6 +215,10 @@ class Collector(object):
tracer.threading = self.threading
if hasattr(tracer, 'check_include'):
tracer.check_include = self.check_include
+ if hasattr(tracer, 'should_start_context'):
+ tracer.should_start_context = should_start_context
+ if hasattr(tracer, 'switch_context'):
+ tracer.switch_context = self.switch_context
fn = tracer.start()
self.tracers.append(tracer)
@@ -294,7 +307,7 @@ class Collector(object):
if stats:
print("\nCoverage.py tracer stats:")
for k in sorted(stats.keys()):
- print("%16s: %s" % (k, stats[k]))
+ print("%20s: %s" % (k, stats[k]))
if self.threading:
self.threading.settrace(None)
@@ -307,6 +320,12 @@ class Collector(object):
else:
self._start_tracer()
+ def switch_context(self, new_context):
+ print("** Switching to {!r}".format(new_context))
+ data = self.contexts.setdefault(new_context, {})
+ for tracer in self.tracers:
+ tracer.data = data
+
def save_data(self, covdata):
"""Save the collected data to a `CoverageData`.
@@ -317,6 +336,7 @@ class Collector(object):
"""Return a dict like d, but with keys modified by `abs_file`."""
return dict((abs_file(k), v) for k, v in iitems(d))
+ import pprint; pprint.pprint(self.contexts)
if self.branch:
covdata.add_arcs(abs_file_dict(self.data))
else:
diff --git a/coverage/ctracer/datastack.h b/coverage/ctracer/datastack.h
index 78f85f7e..13149798 100644
--- a/coverage/ctracer/datastack.h
+++ b/coverage/ctracer/datastack.h
@@ -27,6 +27,8 @@ typedef struct DataStackEntry {
-1 means there was no previous line, as when entering a code object.
*/
int last_line;
+
+ int started_context;
} DataStackEntry;
/* A data stack is a dynamically allocated vector of DataStackEntry's. */
diff --git a/coverage/ctracer/stats.h b/coverage/ctracer/stats.h
index ceba79bd..a72117cf 100644
--- a/coverage/ctracer/stats.h
+++ b/coverage/ctracer/stats.h
@@ -24,6 +24,7 @@ typedef struct Stats {
unsigned int stack_reallocs;
unsigned int errors;
unsigned int pycalls;
+ unsigned int start_context_calls;
#endif
} Stats;
diff --git a/coverage/ctracer/tracer.c b/coverage/ctracer/tracer.c
index 6fa099d0..7c8065cb 100644
--- a/coverage/ctracer/tracer.c
+++ b/coverage/ctracer/tracer.c
@@ -86,6 +86,8 @@ CTracer_init(CTracer *self, PyObject *args_unused, PyObject *kwds_unused)
self->cur_entry.last_line = -1;
+ self->context = Py_None;
+
ret = RET_OK;
goto ok;
@@ -112,6 +114,9 @@ CTracer_dealloc(CTracer *self)
Py_XDECREF(self->data);
Py_XDECREF(self->file_tracers);
Py_XDECREF(self->should_trace_cache);
+ Py_XDECREF(self->should_start_context);
+ Py_XDECREF(self->switch_context);
+ Py_XDECREF(self->context);
DataStack_dealloc(&self->stats, &self->data_stack);
if (self->data_stacks) {
@@ -335,6 +340,7 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame)
STATS( self->stats.calls++; )
+
/* Grow the stack. */
if (CTracer_set_pdata_stack(self) < 0) {
goto error;
@@ -346,6 +352,37 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame)
/* Push the current state on the stack. */
self->pdata_stack->stack[self->pdata_stack->depth] = self->cur_entry;
+ /* See if this frame begins a new context. */
+ 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++; )
+ STATS( self->stats.pycalls++; )
+ context = PyObject_CallFunctionObjArgs(self->should_start_context, frame, NULL);
+ if (context == NULL) {
+ goto error;
+ }
+ if (context != Py_None) {
+ PyObject * val;
+ Py_DECREF(self->context);
+ self->context = context;
+ self->cur_entry.started_context = TRUE;
+ STATS( self->stats.pycalls++; )
+ val = PyObject_CallFunctionObjArgs(self->switch_context, context, NULL);
+ if (val == NULL) {
+ goto error;
+ }
+ Py_DECREF(val);
+ }
+ else {
+ Py_DECREF(context);
+ self->cur_entry.started_context = FALSE;
+ }
+ }
+ else {
+ self->cur_entry.started_context = FALSE;
+ }
+
/* Check if we should trace this line. */
filename = frame->f_code->co_filename;
disposition = PyDict_GetItem(self->should_trace_cache, filename);
@@ -719,6 +756,22 @@ CTracer_handle_return(CTracer *self, PyFrameObject *frame)
}
}
+ /* If this frame started a context, then returning from it ends the context. */
+ if (self->cur_entry.started_context) {
+ PyObject * val;
+ Py_DECREF(self->context);
+ self->context = Py_None;
+ Py_INCREF(self->context);
+ STATS( self->stats.pycalls++; )
+
+ val = PyObject_CallFunctionObjArgs(self->switch_context, context, NULL);
+ if (val == NULL) {
+ goto error;
+ }
+ Py_DECREF(val);
+ }
+
+ /* Pop the stack. */
SHOWLOG(self->pdata_stack->depth, frame->f_lineno, frame->f_code->co_filename, "return");
self->cur_entry = self->pdata_stack->stack[self->pdata_stack->depth];
self->pdata_stack->depth--;
@@ -966,7 +1019,7 @@ CTracer_get_stats(CTracer *self)
{
#if COLLECT_STATS
return Py_BuildValue(
- "{sI,sI,sI,sI,sI,sI,sI,sI,si,sI,sI}",
+ "{sI,sI,sI,sI,sI,sI,sI,sI,si,sI,sI,sI}",
"calls", self->stats.calls,
"lines", self->stats.lines,
"returns", self->stats.returns,
@@ -977,7 +1030,8 @@ CTracer_get_stats(CTracer *self)
"stack_reallocs", self->stats.stack_reallocs,
"stack_alloc", self->pdata_stack->alloc,
"errors", self->stats.errors,
- "pycalls", self->stats.pycalls
+ "pycalls", self->stats.pycalls,
+ "start_context_calls", self->stats.start_context_calls
);
#else
Py_RETURN_NONE;
@@ -1010,6 +1064,12 @@ CTracer_members[] = {
{ "trace_arcs", T_OBJECT, offsetof(CTracer, trace_arcs), 0,
PyDoc_STR("Should we trace arcs, or just lines?") },
+ { "should_start_context", T_OBJECT, offsetof(CTracer, should_start_context), 0,
+ PyDoc_STR("Function for starting contexts.") },
+
+ { "switch_context", T_OBJECT, offsetof(CTracer, switch_context), 0,
+ PyDoc_STR("Function for switch to a new context.") },
+
{ NULL }
};
diff --git a/coverage/ctracer/tracer.h b/coverage/ctracer/tracer.h
index 053fbf62..7769b2c2 100644
--- a/coverage/ctracer/tracer.h
+++ b/coverage/ctracer/tracer.h
@@ -25,6 +25,9 @@ typedef struct CTracer {
PyObject * file_tracers;
PyObject * should_trace_cache;
PyObject * trace_arcs;
+ PyObject * should_start_context;
+ PyObject * switch_context;
+ PyObject * context;
/* Has the tracer been started? */
int started;
diff --git a/coverage/ctracer/util.h b/coverage/ctracer/util.h
index ad8f49d6..2f962c3f 100644
--- a/coverage/ctracer/util.h
+++ b/coverage/ctracer/util.h
@@ -11,6 +11,8 @@
#undef TRACE_LOG /* Define to log our bookkeeping. */
#undef COLLECT_STATS /* Collect counters: stats are printed when tracer is stopped. */
+#define COLLECT_STATS 1
+
/* Py 2.x and 3.x compatibility */
#if PY_MAJOR_VERSION >= 3