From 30185afd9a917a862de8928075c6fb34ad4fbba7 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 6 Nov 2015 21:46:20 -0500 Subject: Bump version --- coverage/version.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coverage/version.py b/coverage/version.py index 8f44be0d..30f4bee1 100644 --- a/coverage/version.py +++ b/coverage/version.py @@ -5,7 +5,8 @@ # This file is exec'ed in setup.py, don't import anything! # Same semantics as sys.version_info. -version_info = (4, 0, 2, 'final', 0) +# Word is 'alpha', 'beta', 'candidate', or 'final'. +version_info = (4, 1, 0, 'alpha', 0) def _make_version(major, minor, micro, releaselevel, serial): -- cgit v1.2.1 From 11c38d0708cd2969ed08ee538ca83ec006e21796 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 14 Nov 2015 06:12:24 -0500 Subject: TRUE and FALSE for booleans --- coverage/ctracer/tracer.c | 8 ++++---- coverage/ctracer/util.h | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/coverage/ctracer/tracer.c b/coverage/ctracer/tracer.c index dba8a11c..6fa099d0 100644 --- a/coverage/ctracer/tracer.c +++ b/coverage/ctracer/tracer.c @@ -139,7 +139,7 @@ indent(int n) return spaces + strlen(spaces) - n*2; } -static int logging = 0; +static int logging = FALSE; /* Set these constants to be a file substring and line number to start logging. */ static const char * start_file = "tests/views"; static int start_line = 27; @@ -778,7 +778,7 @@ CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unuse #if TRACE_LOG ascii = MyText_AS_BYTES(frame->f_code->co_filename); if (strstr(MyBytes_AS_STRING(ascii), start_file) && frame->f_lineno == start_line) { - logging = 1; + logging = TRUE; } Py_DECREF(ascii); #endif @@ -941,7 +941,7 @@ static PyObject * CTracer_start(CTracer *self, PyObject *args_unused) { PyEval_SetTrace((Py_tracefunc)CTracer_trace, (PyObject*)self); - self->started = 1; + self->started = TRUE; self->tracing_arcs = self->trace_arcs && PyObject_IsTrue(self->trace_arcs); self->cur_entry.last_line = -1; @@ -955,7 +955,7 @@ CTracer_stop(CTracer *self, PyObject *args_unused) { if (self->started) { PyEval_SetTrace(NULL, NULL); - self->started = 0; + self->started = FALSE; } Py_RETURN_NONE; diff --git a/coverage/ctracer/util.h b/coverage/ctracer/util.h index bb3ad5a3..ad8f49d6 100644 --- a/coverage/ctracer/util.h +++ b/coverage/ctracer/util.h @@ -49,4 +49,8 @@ #define RET_OK 0 #define RET_ERROR -1 +/* Nicer booleans */ +#define FALSE 0 +#define TRUE 1 + #endif /* _COVERAGE_UTIL_H */ -- cgit v1.2.1 From e118f7b220c7292291c645313e38eb559739c836 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 14 Nov 2015 06:13:40 -0500 Subject: WIP: record contexts for who tests what --- coverage/collector.py | 22 ++++++++++++++- coverage/ctracer/datastack.h | 2 ++ coverage/ctracer/stats.h | 1 + coverage/ctracer/tracer.c | 64 ++++++++++++++++++++++++++++++++++++++++++-- coverage/ctracer/tracer.h | 3 +++ coverage/ctracer/util.h | 2 ++ 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 -- cgit v1.2.1 From d31c2f3b6e422b85df6ff9e46f9fcf646cc0c15a Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 30 Jan 2016 11:24:15 -0500 Subject: Fix a bad variable --- coverage/ctracer/tracer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverage/ctracer/tracer.c b/coverage/ctracer/tracer.c index 7c8065cb..42587b1e 100644 --- a/coverage/ctracer/tracer.c +++ b/coverage/ctracer/tracer.c @@ -764,7 +764,7 @@ CTracer_handle_return(CTracer *self, PyFrameObject *frame) Py_INCREF(self->context); STATS( self->stats.pycalls++; ) - val = PyObject_CallFunctionObjArgs(self->switch_context, context, NULL); + val = PyObject_CallFunctionObjArgs(self->switch_context, self->context, NULL); if (val == NULL) { goto error; } -- cgit v1.2.1 From 60297f4fda470bc7d44de20384efd96fbbaea7de Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 30 Jan 2016 15:46:05 -0500 Subject: Clean up WTW so that it is safe to merge --- coverage/collector.py | 21 +++++++++++++++------ coverage/ctracer/tracer.c | 4 ++-- coverage/ctracer/util.h | 2 -- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/coverage/collector.py b/coverage/collector.py index fff4f6ad..aabf10b7 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -121,6 +121,10 @@ class Collector(object): "Couldn't trace with concurrency=%s, the module isn't installed." % concurrency ) + # 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: @@ -215,10 +219,11 @@ 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 + 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 fn = tracer.start() self.tracers.append(tracer) @@ -321,7 +326,6 @@ class Collector(object): 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 @@ -336,11 +340,16 @@ 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: covdata.add_lines(abs_file_dict(self.data)) covdata.add_file_tracers(abs_file_dict(self.file_tracers)) + if self.wtw: + # Just a hack, so just hack it. + import pprint + with open("coverage_wtw.py", "w") as wtw_out: + pprint.pprint(self.contexts, wtw_out) + self.reset() diff --git a/coverage/ctracer/tracer.c b/coverage/ctracer/tracer.c index 42587b1e..3266ff2f 100644 --- a/coverage/ctracer/tracer.c +++ b/coverage/ctracer/tracer.c @@ -353,7 +353,7 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame) 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) { + if (self->should_start_context && 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++; ) @@ -1067,7 +1067,7 @@ CTracer_members[] = { { "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, + { "switch_context", T_OBJECT, offsetof(CTracer, switch_context), 0, PyDoc_STR("Function for switch to a new context.") }, { NULL } diff --git a/coverage/ctracer/util.h b/coverage/ctracer/util.h index 2f962c3f..ad8f49d6 100644 --- a/coverage/ctracer/util.h +++ b/coverage/ctracer/util.h @@ -11,8 +11,6 @@ #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 -- cgit v1.2.1