diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index a2e4752222..1419fed678 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -170,7 +170,7 @@ def test_icu(): def test_dukpy(): print ('Testing dukpy') - from duktape.tests import test_build + from duktape import test_build test_build() print ('dukpy OK!') diff --git a/src/duktape/__init__.py b/src/duktape/__init__.py index 9006e31c1d..14ffb4503f 100644 --- a/src/duktape/__init__.py +++ b/src/duktape/__init__.py @@ -7,9 +7,9 @@ __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -__all__ = ['dukpy', 'Context', 'undefined'] +__all__ = ['dukpy', 'Context', 'undefined', 'JSError'] -import errno, os +import errno, os, sys from functools import partial from calibre.constants import plugins @@ -28,15 +28,60 @@ def load_file(base_dirs, name): raise raise EnvironmentError('No module named: %s found in the base directories: %s' % (name, os.pathsep.join(base_dirs))) -def Context(base_dirs=()): - ans = Context_() - if not base_dirs: - base_dirs = (os.getcwdu(),) - ans.g.Duktape.load_file = partial(load_file, base_dirs or (os.getcwdu(),)) - ans.eval(''' -console = { log: function() { print(Array.prototype.join.call(arguments, ' ')); } }; -Duktape.modSearch = function (id, require, exports, module) { - return Duktape.load_file(id); -} -''') - return ans +class JSError(Exception): + + def __init__(self, e): + e = e.args[0] + Exception.__init__(self, e.toString()) + self.name = e.name + self.js_message = e.message + self.fileName = e.fileName + self.lineNumber = e.lineNumber + self.stack = e.stack + +class Context(object): + + def __init__(self, base_dirs=()): + self._ctx = Context_() + self.g = self._ctx.g + self.g.Duktape.load_file = partial(load_file, base_dirs or (os.getcwdu(),)) + self.eval(''' + console = { log: function() { print(Array.prototype.join.call(arguments, ' ')); } }; + Duktape.modSearch = function (id, require, exports, module) { + return Duktape.load_file(id); + } + ''') + + def eval(self, code='', noreturn=False): + try: + self._ctx.eval(code, noreturn) + except dukpy.JSError as e: + raise JSError(e) + + def eval_file(self, path, noreturn=False): + try: + self._ctx.eval_file(path, noreturn) + except dukpy.JSError as e: + raise JSError(e) + +def test_build(): + import unittest + + def load_tests(loader, suite, pattern): + from duktape import tests + for x in vars(tests).itervalues(): + if isinstance(x, type) and issubclass(x, unittest.TestCase): + tests = loader.loadTestsFromTestCase(x) + suite.addTests(tests) + return suite + + class TestRunner(unittest.main): + + def createTests(self): + tl = unittest.TestLoader() + suite = unittest.TestSuite() + self.test = load_tests(tl, suite, None) + + result = TestRunner(verbosity=0, buffer=True, catchbreak=True, failfast=True, argv=sys.argv[:1], exit=False).result + if not result.wasSuccessful(): + raise SystemExit(1) diff --git a/src/duktape/context.c b/src/duktape/context.c index d4c702f7bd..a04ac92a75 100644 --- a/src/duktape/context.c +++ b/src/duktape/context.c @@ -98,8 +98,12 @@ static PyObject *DukContext_eval(DukContext *self, PyObject *args, PyObject *kw) if (temp && PyObject_IsTrue(temp)) noresult = 1; if (duk_peval_string(self->ctx, code) != 0) { - PyErr_Format(PyExc_SyntaxError, "%s", - duk_safe_to_string(self->ctx, -1)); + temp = duk_to_python(self->ctx, -1); + if (temp) { + PyErr_SetObject(JSError, temp); + Py_DECREF(temp); + } + duk_pop(self->ctx); return NULL; } @@ -128,8 +132,12 @@ static PyObject *DukContext_eval_file(DukContext *self, PyObject *args, PyObject if (temp && PyObject_IsTrue(temp)) noresult = 1; if (duk_peval_file(self->ctx, path) != 0) { - PyErr_Format(PyExc_SyntaxError, - "%s:%s", path, duk_safe_to_string(self->ctx, -1)); + temp = duk_to_python(self->ctx, -1); + if (temp) { + PyErr_SetObject(JSError, temp); + Py_DECREF(temp); + } + duk_pop(self->ctx); return NULL; } diff --git a/src/duktape/dukpy.h b/src/duktape/dukpy.h index 4767a58aeb..4b3dafcdcd 100644 --- a/src/duktape/dukpy.h +++ b/src/duktape/dukpy.h @@ -13,6 +13,7 @@ typedef struct DukEnum_ DukEnum; PyObject DukUndefined; #define Duk_undefined (&DukUndefined) +extern PyObject *JSError; /* context.c */ diff --git a/src/duktape/module.c b/src/duktape/module.c index fc00d99f59..864be7f06d 100644 --- a/src/duktape/module.c +++ b/src/duktape/module.c @@ -1,5 +1,7 @@ #include "dukpy.h" +PyObject *JSError = NULL; + /* ARGSUSED */ static PyObject * undefined_repr(PyObject *op) @@ -131,6 +133,9 @@ initdukpy(void) Py_INCREF(Duk_undefined); PyModule_AddObject(mod, "undefined", (PyObject *)Duk_undefined); + + JSError = PyErr_NewException("dukpy.JSError", NULL, NULL); + if (JSError) PyModule_AddObject(mod, "JSError", JSError); } #if PY_MAJOR_VERSION >= 3 diff --git a/src/duktape/tests.py b/src/duktape/tests.py index da9922f22d..6fa47fddaf 100644 --- a/src/duktape/tests.py +++ b/src/duktape/tests.py @@ -1,7 +1,8 @@ import os import sys import unittest -from duktape import Context, undefined +from duktape import dukpy +undefined, JSError, Context = dukpy.undefined, dukpy.JSError, dukpy.Context class ContextTests(unittest.TestCase): def setUp(self): @@ -129,6 +130,24 @@ class EvalTests(unittest.TestCase): def test_eval_kwargs(self): self.assertEqual(self.ctx.eval(code="1+1"), 2) + def test_eval_errors(self): + try: + self.ctx.eval('1+/1') + self.assert_('No error raised for malformed js') + except JSError as e: + e = e.args[0] + self.assertEqual('SyntaxError', e.name) + self.assertEqual(1, e.lineNumber) + self.assertIn('line 1', e.toString()) + + try: + self.ctx.eval('\na()') + self.assert_('No error raised for malformed js') + except JSError as e: + e = e.args[0] + self.assertEqual('ReferenceError', e.name) + self.assertEqual(2, e.lineNumber) + def test_eval_noreturn(self): self.assertIsNone(self.ctx.eval("1+1", noreturn=True)) @@ -148,27 +167,4 @@ class EvalTests(unittest.TestCase): def test_eval_file_noreturn(self): self.assertIsNone(self.ctx.eval_file(self.testfile, noreturn=True)) -def load_tests(loader, suite, pattern): - for x in globals().itervalues(): - if isinstance(x, type) and issubclass(x, unittest.TestCase): - tests = loader.loadTestsFromTestCase(x) - suite.addTests(tests) - return suite -class TestRunner(unittest.main): - - def createTests(self): - tl = unittest.TestLoader() - suite = unittest.TestSuite() - self.test = load_tests(tl, suite, None) - -def run(verbosity=4): - TestRunner(verbosity=verbosity, exit=False) - -def test_build(): - result = TestRunner(verbosity=0, buffer=True, catchbreak=True, failfast=True, argv=sys.argv[:1], exit=False).result - if not result.wasSuccessful(): - raise SystemExit(1) - -if __name__ == '__main__': - run()