# HG changeset patch # User Atul Varma # Date 1246869436 25200 # Node ID a0f677cfc679dadf84b6d3fd82c0e5e0bc494fab # Parent 03aec85724616e9480c74eab97e3ebb2139af887 Added basic functionality for passing useful exceptions between Python and JS code. diff -r 03aec8572461 -r a0f677cfc679 context.c --- 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 diff -r 03aec8572461 -r a0f677cfc679 function.c --- 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; diff -r 03aec8572461 -r a0f677cfc679 test_pymonkey.py --- 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, '', 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 "", 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(); })', '', 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 "", line 1: ReferenceError: ' + 'blarg is not defined') def testCallFunctionWorks(self): cx = pymonkey.Runtime().new_context() diff -r 03aec8572461 -r a0f677cfc679 utils.c --- 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"); +} diff -r 03aec8572461 -r a0f677cfc679 utils.h --- 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