summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt5
-rw-r--r--coverage/collector.py20
-rw-r--r--coverage/tracer.c56
-rw-r--r--test/test_oddball.py21
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."""