Commits

Ned Batchelder committed 39a76e1

The thread-startup dance caused Thread.run() to not be measured. This fixes it, I hope without introducing too much more new code. Fixes #85.

Comments (0)

Files changed (4)

   ``[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

coverage/collector.py

         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."""
         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
         # 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."""

coverage/tracer.c

 
 #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)
 #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;
  * 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;
     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);
 
 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") },
 

test/test_oddball.py

 
     def test_threading(self):
         self.check_coverage("""\
-            import time, threading
+            import threading
 
             def fromMainThread():
                 return "called from main thread"
             """,
             [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."""