diff options
-rw-r--r-- | CHANGES.txt | 5 | ||||
-rw-r--r-- | coverage/collector.py | 20 | ||||
-rw-r--r-- | coverage/tracer.c | 56 | ||||
-rw-r--r-- | test/test_oddball.py | 21 |
4 files changed, 90 insertions, 12 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 88a28b04..65fc10d5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -14,9 +14,14 @@ Version 3.4b2 ``[report] precision`` config file setting. Default is still 0. Completes ``issue 16``. +- Threads derived from ``threading.Thread`` with an overridden `run` method + would report no coverage for the `run` method. This is now fixed, closing + ``issue 85``. + .. _issue 70: http://bitbucket.org/ned/coveragepy/issue/70/text-report-and-html-report-disagree-on-coverage .. _issue 41: http://bitbucket.org/ned/coveragepy/issue/41/report-says-100-when-it-isnt-quite-there .. _issue 16: http://bitbucket.org/ned/coveragepy/issue/16/allow-configuration-of-accuracy-of-percentage-totals +.. _issue 85: http://bitbucket.org/ned/coveragepy/issue/85/threadrun-isnt-measured Version 3.4b1 --- 21 August 2010 diff --git a/coverage/collector.py b/coverage/collector.py index 55211f2a..d20e42cc 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -96,8 +96,13 @@ class PyTracer(object): return self._trace def start(self): - """Start this Tracer.""" + """Start this Tracer. + + Return a Python function suitable for use with sys.settrace(). + + """ sys.settrace(self._trace) + return self._trace def stop(self): """Stop this Tracer.""" @@ -186,8 +191,9 @@ class Collector(object): tracer.arcs = self.branch tracer.should_trace = self.should_trace tracer.should_trace_cache = self.should_trace_cache - tracer.start() + fn = tracer.start() self.tracers.append(tracer) + return fn # The trace function has to be set individually on each thread before # execution begins. Ironically, the only support the threading module has @@ -200,9 +206,13 @@ class Collector(object): # Remove ourselves as the trace function sys.settrace(None) # Install the real tracer. - self._start_tracer() - # Return None to reiterate that we shouldn't be used for tracing. - return None + fn = self._start_tracer() + # Invoke the real trace function with the current event, to be sure + # not to lose an event. + if fn: + fn = fn(frame_unused, event_unused, arg_unused) + # Return the new trace function to continue tracing in this scope. + return fn def start(self): """Start collecting trace information.""" diff --git a/coverage/tracer.c b/coverage/tracer.c index 1d227295..583ed80e 100644 --- a/coverage/tracer.c +++ b/coverage/tracer.c @@ -25,14 +25,16 @@ #if PY_MAJOR_VERSION >= 3
+#define MyText_Type PyUnicode_Type
#define MyText_Check(o) PyUnicode_Check(o)
-#define MyText_AS_STRING(o) FOOEY_DONT_KNOW_YET(o)
+#define MyText_AS_STRING(o) PyBytes_AS_STRING(PyUnicode_AsASCIIString(o))
#define MyInt_FromLong(l) PyLong_FromLong(l)
#define MyType_HEAD_INIT PyVarObject_HEAD_INIT(NULL, 0)
#else
+#define MyText_Type PyString_Type
#define MyText_Check(o) PyString_Check(o)
#define MyText_AS_STRING(o) PyString_AS_STRING(o)
#define MyInt_FromLong(l) PyInt_FromLong(l)
@@ -117,7 +119,7 @@ typedef struct { #define STACK_DELTA 100
static int
-Tracer_init(Tracer *self, PyObject *args, PyObject *kwds)
+Tracer_init(Tracer *self, PyObject *args_unused, PyObject *kwds_unused)
{
#if COLLECT_STATS
self->stats.calls = 0;
@@ -247,7 +249,7 @@ Tracer_record_pair(Tracer *self, int l1, int l2) * The Trace Function
*/
static int
-Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
+Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg_unused)
{
int ret = RET_OK;
PyObject * filename = NULL;
@@ -449,19 +451,58 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg) return RET_OK;
}
+/*
+ * A sys.settrace-compatible function that invokes our C trace function.
+ */
static PyObject *
-Tracer_start(Tracer *self, PyObject *args)
+Tracer_pytrace(Tracer *self, PyObject *args)
+{
+ PyFrameObject *frame;
+ PyObject *what_str;
+ PyObject *arg_unused;
+ int what;
+ static char *what_names[] = {
+ "call", "exception", "line", "return",
+ "c_call", "c_exception", "c_return",
+ NULL
+ };
+
+ if (!PyArg_ParseTuple(args, "O!O!O:Tracer_pytrace",
+ &PyFrame_Type, &frame, &MyText_Type, &what_str, &arg_unused)) {
+ goto done;
+ }
+
+ /* In Python, the what argument is a string, we need to find an int
+ for the C function. */
+ for (what = 0; what_names[what]; what++) {
+ if (!strcmp(MyText_AS_STRING(what_str), what_names[what])) {
+ break;
+ }
+ }
+
+ /* Invoke the C function, and return ourselves. */
+ if (Tracer_trace(self, frame, what, arg_unused) == RET_OK) {
+ return PyObject_GetAttrString((PyObject*)self, "pytrace");
+ }
+
+done:
+ return NULL;
+}
+
+static PyObject *
+Tracer_start(Tracer *self, PyObject *args_unused)
{
PyEval_SetTrace((Py_tracefunc)Tracer_trace, (PyObject*)self);
self->started = 1;
self->tracing_arcs = self->arcs && PyObject_IsTrue(self->arcs);
self->last_line = -1;
- return Py_BuildValue("");
+ /* start() returns a trace function usable with sys.settrace() */
+ return PyObject_GetAttrString((PyObject*)self, "pytrace");
}
static PyObject *
-Tracer_stop(Tracer *self, PyObject *args)
+Tracer_stop(Tracer *self, PyObject *args_unused)
{
if (self->started) {
PyEval_SetTrace(NULL, NULL);
@@ -512,6 +553,9 @@ Tracer_members[] = { static PyMethodDef
Tracer_methods[] = {
+ { "pytrace", (PyCFunction) Tracer_pytrace, METH_VARARGS,
+ PyDoc_STR("A trace function compatible with sys.settrace()") },
+
{ "start", (PyCFunction) Tracer_start, METH_VARARGS,
PyDoc_STR("Start the tracer") },
diff --git a/test/test_oddball.py b/test/test_oddball.py index f5d21aa9..3d5bf097 100644 --- a/test/test_oddball.py +++ b/test/test_oddball.py @@ -12,7 +12,7 @@ class ThreadingTest(CoverageTest): def test_threading(self): self.check_coverage("""\ - import time, threading + import threading def fromMainThread(): return "called from main thread" @@ -30,6 +30,25 @@ class ThreadingTest(CoverageTest): """, [1,3,4,6,7,9,10,12,13,14,15], "10") + def test_thread_run(self): + self.check_coverage("""\ + import threading + + class TestThread(threading.Thread): + def run(self): + self.a = 5 + self.do_work() + self.a = 7 + + def do_work(self): + self.a = 10 + + thd = TestThread() + thd.start() + thd.join() + """, + [1,3,4,5,6,7,9,10,12,13,14], "") + class RecursionTest(CoverageTest): """Check what happens when recursive code gets near limits.""" |