Commits

Armin Rigo committed a988b91

Write a full traceback when getting an exception in a callback.

Comments (0)

Files changed (2)

c/_cffi_backend.c

     return convert_from_object(result, ctype, pyobj);
 }
 
+static void _my_PyErr_WriteUnraisable(PyObject *obj)
+{
+    /* like PyErr_WriteUnraisable(), but write a full traceback */
+    PyObject *f, *t, *v, *tb;
+    PyErr_Fetch(&t, &v, &tb);
+    f = PySys_GetObject("stderr");
+    if (f != NULL) {
+        PyFile_WriteString("From callback ", f);
+        PyFile_WriteObject(obj, f, 0);
+        PyFile_WriteString(":\n", f);
+        PyErr_Display(t, v, tb);
+    }
+    Py_XDECREF(t);
+    Py_XDECREF(v);
+    Py_XDECREF(tb);
+}
+
 static void invoke_callback(ffi_cif *cif, void *result, void **args,
                             void *userdata)
 {
     return;
 
  error:
-    PyErr_WriteUnraisable(py_ob);
+    _my_PyErr_WriteUnraisable(py_ob);
     if (SIGNATURE(1)->ct_size > 0) {
         py_rawerr = PyTuple_GET_ITEM(cb_args, 2);
         memcpy(result, PyBytes_AS_STRING(py_rawerr),
     e = py.test.raises(TypeError, f)
     assert str(e.value) == "'int(*)(int)' expects 1 arguments, got 0"
 
+def test_callback_exception():
+    import cStringIO
+    def matches(str, pattern):
+        while '$' in pattern:
+            i = pattern.index('$')
+            assert str[:i] == pattern[:i]
+            j = str.find(pattern[i+1], i)
+            assert i + 1 <= j < str.find('\n', i)
+            str = str[j:]
+            pattern = pattern[i+1:]
+        assert str == pattern
+        return True
+    def check_value(x):
+        if x == 10000:
+            raise ValueError(42)
+    def cb1(x):
+        check_value(x)
+        return x * 3
+    BInt = new_primitive_type("int")
+    BFunc = new_function_type((BInt,), BInt, False)
+    f = callback(BFunc, cb1, -42)
+    orig_stderr = sys.stderr
+    try:
+        sys.stderr = cStringIO.StringIO()
+        assert f(100) == 300
+        assert sys.stderr.getvalue() == ''
+        assert f(10000) == -42
+        assert matches(sys.stderr.getvalue(), """\
+From callback <function cb1 at 0x$>:
+Traceback (most recent call last):
+  File "$", line $, in cb1
+    check_value(x)
+  File "$", line $, in check_value
+    raise ValueError(42)
+ValueError: 42
+""")
+        sys.stderr = cStringIO.StringIO()
+        bigvalue = 1 << (8 * size_of_int() - 2)
+        assert f(bigvalue) == -42
+        assert matches(sys.stderr.getvalue(), """\
+From callback <function cb1 at 0x$>:
+OverflowError: integer %d does not fit 'int'
+""" % (bigvalue * 3,))
+    finally:
+        sys.stderr = orig_stderr
+
 def test_callback_return_type():
     for rettype in ["signed char", "short", "int", "long", "long long",
                     "unsigned char", "unsigned short", "unsigned int",
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.