summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2011-04-27 09:49:23 -0400
committerNed Batchelder <ned@nedbatchelder.com>2011-04-27 09:49:23 -0400
commite91c97da931e54b27a838106d25d6ae5529256ae (patch)
tree352b9857bd6fbee4f6f07dfd80a85b8d5b286a3c
parentf1d4dcbe9e5b8b8b335521f6aa806f8438fde917 (diff)
parenta243a6a2b34eb9ef5cba3346796160e06ba77930 (diff)
downloadpython-coveragepy-git-e91c97da931e54b27a838106d25d6ae5529256ae.tar.gz
Merge bug_123 work into default.
-rw-r--r--AUTHORS.txt1
-rw-r--r--CHANGES.txt7
-rw-r--r--coverage/tracer.c34
-rw-r--r--test/farm/run/run_timid.py5
-rw-r--r--test/farm/run/src/showtrace.py9
-rw-r--r--test/test_oddball.py34
6 files changed, 80 insertions, 10 deletions
diff --git a/AUTHORS.txt b/AUTHORS.txt
index e6a9ed78..0f921841 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -15,6 +15,7 @@ Martin Fuzzey
Imri Goldberg
Bill Hart
Christian Heimes
+Devin Jeanpierre
Ross Lawley
Edward Loper
Sandra Martocchia
diff --git a/CHANGES.txt b/CHANGES.txt
index 6d0664ed..4d9bf1e2 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -37,6 +37,10 @@ Version 3.5
coverage.py will issue a warning, at least alerting you to the problem.
Closes `issue 93`_. Thanks to Marius Gedminas for the idea.
+- The C-based trace function now behaves properly when saved and restored
+ with ``sys.gettrace()`` and ``sys.settrace()``. This fixes `issue 125`_
+ and `issue 123`_. Thanks, Devin Jeanpierre.
+
- Source files are now opened with Python 3.2's ``tokenize.open()`` where
possible, to get the best handling of Python source files with encodings.
Closes `issue 107`_, thanks, Brett Cannon.
@@ -61,7 +65,8 @@ Version 3.5
.. _issue 107: https://bitbucket.org/ned/coveragepy/issue/107/codeparser-not-opening-source-files-with
.. _issue 115: https://bitbucket.org/ned/coveragepy/issue/115/fail-gracefully-when-reporting-on-file
.. _issue 121: https://bitbucket.org/ned/coveragepy/issue/121/filename-patterns-are-applied-stupidly
-
+.. _issue 123: https://bitbucket.org/ned/coveragepy/issue/123/pyeval_settrace-used-in-way-that-breaks
+.. _issue 125: https://bitbucket.org/ned/coveragepy/issue/125/coverage-removes-decoratortoolss-tracing
Version 3.4 --- 19 September 2010
---------------------------------
diff --git a/coverage/tracer.c b/coverage/tracer.c
index e046596a..1017c9d4 100644
--- a/coverage/tracer.c
+++ b/coverage/tracer.c
@@ -365,6 +365,9 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg_unused)
}
}
self->cur_file_data = file_data;
+ /* Make the frame right in case settrace(gettrace()) happens. */
+ Py_INCREF(self);
+ frame->f_trace = (PyObject*)self;
SHOWLOG(self->depth, frame->f_lineno, filename, "traced");
}
else {
@@ -462,7 +465,7 @@ Tracer_pytrace(Tracer *self, PyObject *args)
{
PyFrameObject *frame;
PyObject *what_str;
- PyObject *arg_unused;
+ PyObject *arg;
int what;
static char *what_names[] = {
"call", "exception", "line", "return",
@@ -470,8 +473,12 @@ Tracer_pytrace(Tracer *self, PyObject *args)
NULL
};
+ #if WHAT_LOG
+ printf("pytrace\n");
+ #endif
+
if (!PyArg_ParseTuple(args, "O!O!O:Tracer_pytrace",
- &PyFrame_Type, &frame, &MyText_Type, &what_str, &arg_unused)) {
+ &PyFrame_Type, &frame, &MyText_Type, &what_str, &arg)) {
goto done;
}
@@ -484,7 +491,7 @@ Tracer_pytrace(Tracer *self, PyObject *args)
}
/* Invoke the C function, and return ourselves. */
- if (Tracer_trace(self, frame, what, arg_unused) == RET_OK) {
+ if (Tracer_trace(self, frame, what, arg) == RET_OK) {
return PyObject_GetAttrString((PyObject*)self, "pytrace");
}
@@ -492,6 +499,24 @@ done:
return NULL;
}
+/*
+ * Python has two ways to set the trace function: sys.settrace(fn), which
+ * takes a Python callable, and PyEval_SetTrace(func, obj), which takes
+ * a C function and a Python object. The way these work together is that
+ * sys.settrace(pyfn) calls PyEval_SetTrace(builtin_func, pyfn), using the
+ * Python callable as the object in PyEval_SetTrace. So sys.gettrace()
+ * simply returns the Python object used as the second argument to
+ * PyEval_SetTrace. So sys.gettrace() will return our self parameter, which
+ * means it must be callable to be used in sys.settrace().
+ *
+ * So we make our self callable, equivalent to invoking our trace function.
+ */
+static PyObject *
+Tracer_call(Tracer *self, PyObject *args, PyObject *kwds_unused)
+{
+ return Tracer_pytrace(self, args);
+}
+
static PyObject *
Tracer_start(Tracer *self, PyObject *args_unused)
{
@@ -590,7 +615,7 @@ TracerType = {
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
- 0, /*tp_call*/
+ (ternaryfunc)Tracer_call, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
@@ -678,3 +703,4 @@ inittracer(void)
}
#endif /* Py3k */
+
diff --git a/test/farm/run/run_timid.py b/test/farm/run/run_timid.py
index 3810e6db..19651a1c 100644
--- a/test/farm/run/run_timid.py
+++ b/test/farm/run/run_timid.py
@@ -20,9 +20,8 @@ contains("out/showtraceout.txt", "timid PyTracer")
if os.environ.get('COVERAGE_TEST_TRACER', 'c') == 'c':
# If the C trace function is being tested, then regular running should have
- # the C function (shown as None in f_trace since it isn't a Python
- # function).
- contains("out/showtraceout.txt", "regular None")
+ # the C function, which registers itself as f_trace.
+ contains("out/showtraceout.txt", "regular Tracer")
else:
# If the Python trace function is being tested, then regular running will
# also show the Python function.
diff --git a/test/farm/run/src/showtrace.py b/test/farm/run/src/showtrace.py
index c3b4356c..e97412e0 100644
--- a/test/farm/run/src/showtrace.py
+++ b/test/farm/run/src/showtrace.py
@@ -4,7 +4,7 @@
import sys
# Show what the trace function is. If a C-based function is used, then f_trace
-# is None.
+# may be None.
trace_fn = sys._getframe(0).f_trace
if trace_fn is None:
trace_name = "None"
@@ -13,6 +13,11 @@ else:
try:
trace_name = trace_fn.im_class.__name__
except AttributeError:
- trace_name = trace_fn.__self__.__class__.__name__
+ try:
+ trace_name = trace_fn.__self__.__class__.__name__
+ except AttributeError:
+ # A C-based function could also manifest as an f_trace value
+ # which doesn't have im_class or __self__.
+ trace_name = trace_fn.__class__.__name__
print("%s %s" % (sys.argv[1], trace_name))
diff --git a/test/test_oddball.py b/test/test_oddball.py
index e94e2bad..859648fa 100644
--- a/test/test_oddball.py
+++ b/test/test_oddball.py
@@ -348,3 +348,37 @@ if sys.version_info >= (2, 5):
doctest.testmod(sys.modules[__name__]) # we're not __main__ :(
''',
[1,11,12,14,16,17], "")
+
+
+if hasattr(sys, 'gettrace'):
+ class GettraceTest(CoverageTest):
+ """Tests that we work properly with `sys.gettrace()`."""
+ def test_round_trip(self):
+ self.check_coverage('''\
+ import sys
+ def foo(n):
+ return 3*n
+ def bar(n):
+ return 5*n
+ a = foo(6)
+ sys.settrace(sys.gettrace())
+ a = bar(8)
+ ''',
+ [1,2,3,4,5,6,7,8], "")
+
+ def test_multi_layers(self):
+ self.check_coverage('''\
+ import sys
+ def level1():
+ a = 3
+ level2()
+ b = 5
+ def level2():
+ c = 7
+ sys.settrace(sys.gettrace())
+ d = 9
+ e = 10
+ level1()
+ f = 12
+ ''',
+ [1,2,3,4,5,6,7,8,9,10,11,12], "")