changeset 46:a0f677cfc679

Added basic functionality for passing useful exceptions between Python and JS code.
author Atul Varma <varmaa@toolness.com>
date Mon, 06 Jul 2009 01:37:16 -0700
parents 03aec8572461
children 3f4982759e55
files context.c function.c test_pymonkey.py utils.c utils.h
diffstat 5 files changed, 100 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/context.c	Mon Jul 06 00:09:42 2009 -0700
+++ b/context.c	Mon Jul 06 01:37:16 2009 -0700
@@ -3,6 +3,18 @@
 #include "function.h"
 #include "utils.h"
 
+// Default JSErrorReporter for pymonkey-owned JS contexts. We just throw an
+// appropriate exception into Python-space.
+static void
+PYM_reportError(JSContext *cx, const char *message, JSErrorReport *report)
+{
+  if (report->filename)
+    PyErr_Format(PYM_error, "File \"%s\", line %d: %s",
+                 report->filename, report->lineno, message);
+  else
+    PyErr_SetString(PYM_error, message);
+}
+
 static void
 PYM_JSContextDealloc(PYM_JSContextObject *self)
 {
@@ -109,8 +121,7 @@
   jsval rval;
   if (!JS_EvaluateScript(self->cx, object->obj, source, sourceLen,
                          filename, lineNo, &rval)) {
-    // TODO: Actually get the error that was raised.
-    PyErr_SetString(PYM_error, "Script failed");
+    PYM_jsExceptionToPython(self);
     JS_EndRequest(self->cx);
     return NULL;
   }
@@ -181,11 +192,7 @@
   // detail.
   if (!JS_CallFunction(self->cx, obj->obj, (JSFunction *) fun->base.obj,
                        argc, argv, &rval)) {
-    // TODO: There's a pending exception on the JS context, should we
-    // do something about it?
-
-    // TODO: Convert the JS exception to a Python one.
-    PyErr_SetString(PYM_error, "Function failed");
+    PYM_jsExceptionToPython(self);
     return NULL;
   }
 
@@ -288,6 +295,7 @@
 
   context->cx = cx;
   JS_SetContextPrivate(cx, context);
+  JS_SetErrorReporter(cx, PYM_reportError);
 
 #ifdef JS_GC_ZEAL
   // TODO: Consider exposing JS_SetGCZeal() to Python instead of
--- a/function.c	Mon Jul 06 00:09:42 2009 -0700
+++ b/function.c	Mon Jul 06 01:37:16 2009 -0700
@@ -42,8 +42,7 @@
 
   PyObject *result = PyObject_Call(callable, args, NULL);
   if (result == NULL) {
-    // TODO: Get the actual exception.
-    JS_ReportError(cx, "Python function failed.");
+    PYM_pythonExceptionToJs(context);
     return JS_FALSE;
   }
 
@@ -51,8 +50,7 @@
   Py_DECREF(result);
 
   if (error) {
-    // TODO: Get the actual exception.
-    JS_ReportError(cx, "Python function failed.");
+    PYM_pythonExceptionToJs(context);
     return JS_FALSE;
   }
   return JS_TRUE;
--- a/test_pymonkey.py	Mon Jul 06 00:09:42 2009 -0700
+++ b/test_pymonkey.py	Mon Jul 06 01:37:16 2009 -0700
@@ -1,4 +1,6 @@
+import sys
 import unittest
+
 import pymonkey
 
 class PymonkeyTests(unittest.TestCase):
@@ -17,6 +19,15 @@
         cx.define_property(obj, func.__name__, jsfunc)
         return cx.evaluate_script(obj, code, '<string>', 1)
 
+    def assertRaises(self, exctype, func, *args):
+        was_raised = False
+        try:
+            func(*args)
+        except exctype, e:
+            self.last_exception = e
+            was_raised = True
+        self.assertTrue(was_raised)
+
     def testJsWrappedPythonFuncPassesContext(self):
         contexts = []
 
@@ -39,6 +50,15 @@
         self.assertEqual(self._evalJsWrappedPyFunc(hai2u, 'hai2u()'),
                          u"o hai")
 
+    def testJsWrappedPythonFunctionThrowsException(self):
+        def hai2u(cx):
+            raise Exception("hello")
+        self.assertRaises(pymonkey.error,
+                          self._evalJsWrappedPyFunc,
+                          hai2u, 'hai2u()')
+        self.assertEqual(self.last_exception.message,
+                         "uncaught exception: hello")
+
     def testJsWrappedPythonFunctionReturnsNone(self):
         def hai2u(cx):
             pass
@@ -157,6 +177,13 @@
     def testUndefinedCannotBeInstantiated(self):
         self.assertRaises(TypeError, pymonkey.undefined)
 
+    def testEvaluateThrowsException(self):
+        self.assertRaises(pymonkey.error,
+                          self._evaljs, 'hai2u()')
+        self.assertEqual(self.last_exception.message,
+                         'File "<string>", line 1: ReferenceError: '
+                         'hai2u is not defined')
+
     def testEvaluateReturnsUndefined(self):
         retval = self._evaljs("")
         self.assertTrue(retval is pymonkey.undefined)
@@ -212,11 +239,12 @@
             '(function boop(a, b) { blarg(); })',
             '<string>', 1
             )
-        self.assertRaises(
-            pymonkey.error,
-            cx.call_function,
-            obj, obj, (1,)
-            )
+        self.assertRaises(pymonkey.error,
+                          cx.call_function,
+                          obj, obj, (1,))
+        self.assertEqual(self.last_exception.message,
+                         'File "<string>", line 1: ReferenceError: '
+                         'blarg is not defined')
 
     def testCallFunctionWorks(self):
         cx = pymonkey.Runtime().new_context()
--- a/utils.c	Mon Jul 06 00:09:42 2009 -0700
+++ b/utils.c	Mon Jul 06 01:37:16 2009 -0700
@@ -132,3 +132,47 @@
                   "Data type conversion not implemented.");
   return NULL;
 }
+
+void
+PYM_pythonExceptionToJs(PYM_JSContextObject *context)
+{
+  PyObject *type;
+  PyObject *value;
+  PyObject *traceback;
+
+  PyErr_Fetch(&type, &value, &traceback);
+  PyObject *str = PyObject_Unicode(value);
+  if (str == NULL)
+    JS_ReportError(context->cx, "Python exception occurred");
+  else {
+    jsval val;
+    if (PYM_pyObjectToJsval(context, str, &val) == 0)
+      // TODO: Include filename/line information.
+      JS_SetPendingException(context->cx, val);
+    else
+      JS_ReportError(context->cx, "Python exception occurred");
+  }
+
+  Py_DECREF(type);
+  Py_DECREF(value);
+  Py_DECREF(traceback);
+}
+
+void
+PYM_jsExceptionToPython(PYM_JSContextObject *context)
+{
+  if (!JS_IsExceptionPending(context->cx) &&
+      PyErr_Occurred())
+    return;
+
+  jsval val;
+  if (JS_GetPendingException(context->cx, &val)) {
+    JSString *str = JS_ValueToString(context->cx, val);
+    if (str != NULL) {
+      const char *chars = JS_GetStringBytes(str);
+      PyErr_SetString(PYM_error, chars);
+    } else
+      PyErr_SetString(PYM_error, "JS exception occurred");
+  } else
+    PyErr_SetString(PYM_error, "JS_GetPendingException() failed");
+}
--- a/utils.h	Mon Jul 06 00:09:42 2009 -0700
+++ b/utils.h	Mon Jul 06 01:37:16 2009 -0700
@@ -22,4 +22,10 @@
 extern PyObject *
 PYM_jsvalToPyObject(PYM_JSContextObject *context, jsval value);
 
+extern void
+PYM_pythonExceptionToJs(PYM_JSContextObject *context);
+
+void
+PYM_jsExceptionToPython(PYM_JSContextObject *context);
+
 #endif