mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Update dukpy
This commit is contained in:
parent
a623077d05
commit
27672b7832
@ -67,7 +67,7 @@ if iswindows:
|
||||
extensions = [
|
||||
|
||||
Extension('dukpy',
|
||||
['duktape/%s.c' % x for x in 'context conversions proxy module duktape/duktape'.split()],
|
||||
['duktape/%s.c' % x for x in 'errors context conversions proxy module duktape/duktape'.split()],
|
||||
headers=['duktape/dukpy.h', 'duktape/duktape/duktape.h'],
|
||||
optimize_level=2,
|
||||
),
|
||||
|
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
__all__ = ['dukpy', 'Context', 'undefined', 'JSError', 'to_python']
|
||||
|
||||
import errno, os, sys, numbers, hashlib
|
||||
import errno, os, sys, numbers, hashlib, json
|
||||
from functools import partial
|
||||
|
||||
from calibre.constants import plugins
|
||||
@ -20,6 +20,7 @@ del err
|
||||
Context_, undefined = dukpy.Context, dukpy.undefined
|
||||
|
||||
fs = '''
|
||||
exports.writeFileSync = Duktape.writefile;
|
||||
exports.readFileSync = Duktape.readfile;
|
||||
'''
|
||||
vm = '''
|
||||
@ -43,7 +44,12 @@ exports.runInContext = function(code, ctx) {
|
||||
return handle_result(Duktape.run_in_context(code, ctx));
|
||||
};
|
||||
exports.runInThisContext = function(code, options) {
|
||||
return handle_result(Duktape.run_in_this_context(code, options.filename));
|
||||
try {
|
||||
return eval(code);
|
||||
} catch (e) {
|
||||
console.error('Error:' + e + ' while evaluating: ' + options.filename);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
'''
|
||||
path = '''
|
||||
@ -51,40 +57,79 @@ exports.join = function () { return arguments[0] + '/' + arguments[1]; }
|
||||
'''
|
||||
util = '''
|
||||
exports.inspect = function(x) { return x.toString(); };
|
||||
exports.inherits = function(ctor, superCtor) {
|
||||
try {
|
||||
ctor.super_ = superCtor;
|
||||
ctor.prototype = Object.create(superCtor.prototype, {
|
||||
constructor: {
|
||||
value: ctor,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
} catch(e) { console.log('util.inherits() failed with error:', e); throw e; }
|
||||
};
|
||||
'''
|
||||
|
||||
_assert = '''
|
||||
module.exports = function(x) {if (!x) throw x + " is false"; };
|
||||
exports.ok = module.exports;
|
||||
exports.notStrictEqual = exports.strictEqual = exports.deepEqual = function() {};
|
||||
'''
|
||||
|
||||
stream = '''
|
||||
module.exports = {};
|
||||
'''
|
||||
|
||||
def sha1sum(x):
|
||||
return hashlib.sha1(x).hexdigest()
|
||||
|
||||
def load_file(base_dirs, builtin_modules, name):
|
||||
ans = builtin_modules.get(name)
|
||||
if ans is not None:
|
||||
return ans
|
||||
ans = {'fs':fs, 'vm':vm, 'path':path, 'util':util}.get(name)
|
||||
if ans is not None:
|
||||
return ans
|
||||
if not name.endswith('.js'):
|
||||
name += '.js'
|
||||
def do_open(*args):
|
||||
with open(os.path.join(*args), 'rb') as f:
|
||||
return f.read().decode('utf-8')
|
||||
try:
|
||||
ans = builtin_modules.get(name)
|
||||
if ans is not None:
|
||||
return [True, ans]
|
||||
ans = {'fs':fs, 'vm':vm, 'path':path, 'util':util, 'assert':_assert, 'stream':stream}.get(name)
|
||||
if ans is not None:
|
||||
return [True, ans]
|
||||
if not name.endswith('.js'):
|
||||
name += '.js'
|
||||
def do_open(*args):
|
||||
with open(os.path.join(*args), 'rb') as f:
|
||||
return [True, f.read().decode('utf-8')]
|
||||
|
||||
for b in base_dirs:
|
||||
try:
|
||||
return do_open(b, name)
|
||||
except EnvironmentError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
raise EnvironmentError('No module named: %s found in the base directories: %s' % (name, os.pathsep.join(base_dirs)))
|
||||
for b in base_dirs:
|
||||
try:
|
||||
return do_open(b, name)
|
||||
except EnvironmentError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
raise EnvironmentError('No module named: %s found in the base directories: %s' % (name, os.pathsep.join(base_dirs)))
|
||||
except Exception as e:
|
||||
return [False, str(e)]
|
||||
|
||||
def readfile(path, enc='utf-8'):
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
return [f.read().decode(enc), None, None]
|
||||
return [f.read().decode(enc or 'utf-8'), None, None]
|
||||
except UnicodeDecodeError as e:
|
||||
return None, 0, 'Failed to decode the file: %s with specified encoding: %s' % (path, enc)
|
||||
return None, '', 'Failed to decode the file: %s with specified encoding: %s' % (path, enc)
|
||||
except EnvironmentError as e:
|
||||
return [None, errno.errorcode[e.errno], 'Failed to read from file: %s with error: %s' % (path, e.message)]
|
||||
return [None, errno.errorcode[e.errno], 'Failed to read from file: %s with error: %s' % (path, e.message or e)]
|
||||
|
||||
def writefile(path, data, enc='utf-8'):
|
||||
if enc == undefined:
|
||||
enc = 'utf-8'
|
||||
try:
|
||||
if isinstance(data, type('')):
|
||||
data = data.encode(enc or 'utf-8')
|
||||
with open(path, 'wb') as f:
|
||||
f.write(data)
|
||||
except UnicodeEncodeError as e:
|
||||
return '', 'Failed to encode the data for file: %s with specified encoding: %s' % (path, enc)
|
||||
except EnvironmentError as e:
|
||||
return [errno.errorcode[e.errno], 'Failed to write to file: %s with error: %s' % (path, e.message or e)]
|
||||
|
||||
class Function(object):
|
||||
|
||||
@ -127,16 +172,24 @@ def to_python(x):
|
||||
|
||||
class JSError(Exception):
|
||||
|
||||
def __init__(self, e):
|
||||
e = e.args[0]
|
||||
if hasattr(e, 'toString()'):
|
||||
msg = '%s:%s:%s' % (e.fileName, e.lineNumber, e.toString())
|
||||
Exception.__init__(self, msg)
|
||||
self.name = e.name
|
||||
self.js_message = e.message
|
||||
self.fileName = e.fileName
|
||||
self.lineNumber = e.lineNumber
|
||||
self.stack = e.stack
|
||||
def __init__(self, ex):
|
||||
e = ex.args[0]
|
||||
if isinstance(e, dict):
|
||||
if 'message' in e:
|
||||
fn, ln = e.get('fileName'), e.get('lineNumber')
|
||||
msg = type('')(e['message'])
|
||||
if ln:
|
||||
msg = type('')(ln) + ':' + msg
|
||||
if fn:
|
||||
msg = type('')(fn) + ':' + msg
|
||||
Exception.__init__(self, msg)
|
||||
for k, v in e.iteritems():
|
||||
if k != 'message':
|
||||
setattr(self, k, v)
|
||||
else:
|
||||
setattr(self, 'js_message', v)
|
||||
else:
|
||||
Exception.__init__(self, type('')(e))
|
||||
else:
|
||||
# Happens if js code throws a string or integer rather than a
|
||||
# subclass of Error
|
||||
@ -169,6 +222,10 @@ def run_in_context(code, ctx, options=None):
|
||||
ans = c.eval(code)
|
||||
except JSError as e:
|
||||
return [False, e.as_dict()]
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return [False, {'message':type('')(e)}]
|
||||
return [True, to_python(ans)]
|
||||
|
||||
class Context(object):
|
||||
@ -178,19 +235,24 @@ class Context(object):
|
||||
self.g = self._ctx.g
|
||||
self.g.Duktape.load_file = partial(load_file, base_dirs or (os.getcwdu(),), builtin_modules or {})
|
||||
self.g.Duktape.pyreadfile = readfile
|
||||
self.g.Duktape.pywritefile = writefile
|
||||
self.g.Duktape.create_context = partial(create_context, base_dirs)
|
||||
self.g.Duktape.run_in_context = run_in_context
|
||||
self.g.Duktape.run_in_this_context = self.run_in_this_context
|
||||
self.g.Duktape.cwd = os.getcwdu
|
||||
self.g.Duktape.sha1sum = sha1sum
|
||||
self.g.Duktape.errprint = lambda *args: print(*args, file=sys.stderr)
|
||||
self.eval('''
|
||||
console = {
|
||||
log: function() { print(Array.prototype.join.call(arguments, ' ')); },
|
||||
error: function() { print(Array.prototype.join.call(arguments, ' ')); },
|
||||
error: function() { Duktape.errprint(Array.prototype.join.call(arguments, ' ')); },
|
||||
debug: function() { print(Array.prototype.join.call(arguments, ' ')); }
|
||||
};
|
||||
|
||||
Duktape.modSearch = function (id, require, exports, module) { return Duktape.load_file(id); }
|
||||
Duktape.modSearch = function (id, require, exports, module) {
|
||||
var ans = Duktape.load_file(id);
|
||||
if (ans[0]) return ans[1];
|
||||
throw ans[1];
|
||||
}
|
||||
|
||||
if (!String.prototype.trim) {
|
||||
(function() {
|
||||
@ -243,32 +305,37 @@ class Context(object):
|
||||
return data;
|
||||
}
|
||||
|
||||
Duktape.writefile = function(path, data, encoding) {
|
||||
var x = Duktape.pywritefile(path, data, encoding);
|
||||
var errcode = x[0]; var errmsg = x[1];
|
||||
if (errmsg !== null) throw {code:errcode, message:errmsg};
|
||||
}
|
||||
|
||||
process = {
|
||||
'platform': 'duktape',
|
||||
'env': {'HOME': '_HOME_'},
|
||||
'env': {'HOME': _HOME_, 'TERM':_TERM_},
|
||||
'exit': function() {},
|
||||
'cwd':Duktape.cwd
|
||||
}
|
||||
|
||||
''')
|
||||
'''.replace(
|
||||
'_HOME_', json.dumps(os.path.expanduser('~'))).replace('_TERM_', json.dumps(os.environ.get('TERM', ''))),
|
||||
'<init>')
|
||||
|
||||
def reraise(self, e):
|
||||
raise JSError(e), None, sys.exc_info()[2]
|
||||
|
||||
def eval(self, code='', fname='<eval>', noreturn=False):
|
||||
try:
|
||||
return self._ctx.eval(code, noreturn, fname)
|
||||
except dukpy.JSError as e:
|
||||
raise JSError(e)
|
||||
|
||||
def run_in_this_context(self, code, fname='<eval>'):
|
||||
try:
|
||||
return [True, self._ctx.eval(code, False, fname or '<eval>')]
|
||||
except dukpy.JSError as e:
|
||||
return [False, JSError(e).as_dict()]
|
||||
self.reraise(e)
|
||||
|
||||
def eval_file(self, path, noreturn=False):
|
||||
try:
|
||||
return self._ctx.eval_file(path, noreturn)
|
||||
except dukpy.JSError as e:
|
||||
raise JSError(e)
|
||||
self.reraise(e)
|
||||
|
||||
def test_build():
|
||||
import unittest
|
||||
|
@ -108,7 +108,7 @@ static PyObject *DukContext_eval(DukContext *self, PyObject *args, PyObject *kw)
|
||||
temp = duk_to_python(self->ctx, -1);
|
||||
duk_pop(self->ctx);
|
||||
if (temp) {
|
||||
PyErr_SetObject(JSError, temp);
|
||||
set_dukpy_error(temp);
|
||||
Py_DECREF(temp);
|
||||
} else PyErr_SetString(PyExc_RuntimeError, "The was an error during eval(), but the error could not be read of the stack");
|
||||
return NULL;
|
||||
@ -145,7 +145,7 @@ static PyObject *DukContext_eval_file(DukContext *self, PyObject *args, PyObject
|
||||
temp = duk_to_python(self->ctx, -1);
|
||||
duk_pop(self->ctx);
|
||||
if (temp) {
|
||||
PyErr_SetObject(JSError, temp);
|
||||
set_dukpy_error(temp);
|
||||
Py_DECREF(temp);
|
||||
} else PyErr_SetString(PyExc_RuntimeError, "The was an error during eval_file(), but the error could not be read of the stack");
|
||||
return NULL;
|
||||
|
@ -24,7 +24,7 @@ static duk_ret_t python_function_caller(duk_context *ctx)
|
||||
DukContext *dctx;
|
||||
duk_idx_t nargs, i;
|
||||
static char buf1[200], buf2[1024];
|
||||
int gil_acquired = 0, ret = 1;
|
||||
int gil_acquired = 0, ret = 1, err_occured;
|
||||
|
||||
dctx = DukContext_get(ctx);
|
||||
nargs = duk_get_top(ctx);
|
||||
@ -60,13 +60,15 @@ static duk_ret_t python_function_caller(duk_context *ctx)
|
||||
Py_DECREF(args);
|
||||
|
||||
if (!result) {
|
||||
err_occured = PyErr_Occurred() != NULL;
|
||||
get_repr(func, buf1, 200);
|
||||
if (!PyErr_Occurred()) {
|
||||
if (!err_occured) {
|
||||
if (gil_acquired) {
|
||||
dctx->py_thread_state = PyEval_SaveThread();
|
||||
gil_acquired = 0;
|
||||
}
|
||||
duk_error(ctx, DUK_ERR_ERROR, "Python function (%s) failed", buf1);
|
||||
get_repr(func, buf1, 200);
|
||||
duk_error(ctx, DUK_ERR_ERROR, "Function (%s) failed", buf1);
|
||||
}
|
||||
PyErr_Fetch(&ptype, &pval, &tb);
|
||||
if (!get_repr(pval, buf2, 1024)) get_repr(ptype, buf2, 1024);
|
||||
@ -76,7 +78,8 @@ static duk_ret_t python_function_caller(duk_context *ctx)
|
||||
dctx->py_thread_state = PyEval_SaveThread();
|
||||
gil_acquired = 0;
|
||||
}
|
||||
duk_error(ctx, DUK_ERR_ERROR, "Python function (%s) failed with error: %s", buf1, buf2);
|
||||
get_repr(func, buf1, 200);
|
||||
duk_error(ctx, DUK_ERR_ERROR, "Function (%s) failed with error: %s", buf1, buf2);
|
||||
|
||||
}
|
||||
python_to_duk(ctx, result);
|
||||
@ -142,7 +145,7 @@ int python_to_duk(duk_context *ctx, PyObject *value)
|
||||
else if (value == Py_False) {
|
||||
duk_push_false(ctx);
|
||||
}
|
||||
else if (Py_TYPE(value) == &DukObject_Type) {
|
||||
else if (Py_TYPE(value) == &DukObject_Type || Py_TYPE(value) == &DukFunction_Type || Py_TYPE(value) == &DukArray_Type) {
|
||||
DukObject_push((DukObject *)value, ctx);
|
||||
}
|
||||
else if (PyUnicode_Check(value)) {
|
||||
|
@ -70,5 +70,6 @@ DukEnum *DukEnum_from_DukContext(DukContext *context, dukenum_mode_t mode);
|
||||
int python_to_duk(duk_context *ctx, PyObject *value);
|
||||
PyObject *duk_to_python(duk_context *ctx, duk_idx_t index);
|
||||
|
||||
void set_dukpy_error(PyObject *obj);
|
||||
|
||||
#endif /* DUKPY_H */
|
||||
|
45
src/duktape/errors.c
Normal file
45
src/duktape/errors.c
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* errors.c
|
||||
* Copyright (C) 2015 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "dukpy.h"
|
||||
|
||||
static int copy_error_attr(PyObject *obj, const char* name, PyObject *dest) {
|
||||
PyObject *value = NULL;
|
||||
if (!PyObject_HasAttrString(obj, name)) return NULL;
|
||||
value = PyObject_GetAttrString(obj, name);
|
||||
if (value == NULL) return 0;
|
||||
if (PyDict_SetItemString(dest, name, value) != 0) {Py_DECREF(value); return 0;}
|
||||
Py_DECREF(value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void set_dukpy_error(PyObject *obj) {
|
||||
PyObject *err = NULL, *iterator = NULL, *item = NULL;
|
||||
Py_ssize_t i = 0;
|
||||
if (Py_TYPE(obj) == &DukObject_Type) {
|
||||
err = PyDict_New();
|
||||
if (err == NULL) { PyErr_NoMemory(); return; }
|
||||
|
||||
// Look for the common error object properties that may be up the prototype chain
|
||||
if (!copy_error_attr(obj, "name", err)) { Py_DECREF(err); return; }
|
||||
if (!copy_error_attr(obj, "message", err)) { Py_DECREF(err); return; }
|
||||
if (!copy_error_attr(obj, "fileName", err)) { Py_DECREF(err); return; }
|
||||
if (!copy_error_attr(obj, "lineNumber", err)) { Py_DECREF(err); return; }
|
||||
if (!copy_error_attr(obj, "stack", err)) { Py_DECREF(err); return; }
|
||||
|
||||
// Now copy over own properties
|
||||
iterator = PyObject_CallMethod(obj, "items", NULL);
|
||||
if (iterator == NULL) { Py_DECREF(err); return; }
|
||||
while (item = PyIter_Next(iterator)) {
|
||||
PyDict_SetItem(err, PyTuple_GET_ITEM(item, 0), PyTuple_GET_ITEM(item, 1));
|
||||
Py_DECREF(item);
|
||||
}
|
||||
|
||||
PyErr_SetObject(JSError, err);
|
||||
Py_DECREF(err); Py_DECREF(iterator);
|
||||
} else PyErr_SetObject(JSError, obj);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
#include "dukpy.h"
|
||||
|
||||
/* DukObject */
|
||||
/* DukObject {{{ */
|
||||
|
||||
static void DukObject_INIT(DukObject *self, DukContext *context,
|
||||
duk_idx_t index)
|
||||
@ -231,9 +231,9 @@ PyTypeObject DukObject_Type = {
|
||||
0, /* tp_alloc */
|
||||
0 /* tp_new */
|
||||
};
|
||||
// }}}
|
||||
|
||||
|
||||
/* DukArray */
|
||||
/* DukArray {{{ */
|
||||
|
||||
DukObject *DukArray_from_ctx(duk_context *ctx, duk_idx_t index)
|
||||
{
|
||||
@ -370,9 +370,9 @@ PyTypeObject DukArray_Type = {
|
||||
0, /* tp_weaklistoffset */
|
||||
(getiterfunc)DukArray_iter /* tp_iter */
|
||||
};
|
||||
/// }}}
|
||||
|
||||
|
||||
/* DukFunction */
|
||||
/* DukFunction {{{ */
|
||||
|
||||
DukObject *DukFunction_from_ctx(duk_context *ctx, duk_idx_t index)
|
||||
{
|
||||
@ -392,6 +392,14 @@ DukObject *DukFunction_from_ctx(duk_context *ctx, duk_idx_t index)
|
||||
return self;
|
||||
}
|
||||
|
||||
PyObject* DukFunction_repr(DukObject *self) {
|
||||
PyObject *ans = NULL;
|
||||
PyObject *name = PyObject_GetAttrString((PyObject*)self, "name"), *fname = PyObject_GetAttrString((PyObject*)self, "fileName");
|
||||
ans = PyUnicode_FromFormat("[Function proxy: %S() in filename: %S]", name, fname);
|
||||
Py_XDECREF(name); Py_XDECREF(fname);
|
||||
return ans;
|
||||
}
|
||||
|
||||
PyObject* DukFunction_call(DukObject *self, PyObject *args, PyObject *kw)
|
||||
{
|
||||
duk_context *ctx = self->context->ctx;
|
||||
@ -442,7 +450,7 @@ PyObject* DukFunction_call(DukObject *self, PyObject *args, PyObject *kw)
|
||||
temp = duk_to_python(ctx, -1);
|
||||
duk_pop(ctx);
|
||||
if (temp) {
|
||||
PyErr_SetObject(JSError, temp);
|
||||
set_dukpy_error(temp);
|
||||
Py_DECREF(temp);
|
||||
} else PyErr_SetString(PyExc_RuntimeError, "The was an error during call(), but the error could not be read of the stack");
|
||||
return NULL;
|
||||
@ -470,7 +478,7 @@ PyTypeObject DukFunction_Type = {
|
||||
0, /* tp_getattr */
|
||||
0, /* tp_setattr */
|
||||
0, /* tp_reserved */
|
||||
0, /* tp_repr */
|
||||
(reprfunc)DukFunction_repr, /* tp_repr */
|
||||
0, /* tp_as_number */
|
||||
0, /* tp_as_sequence */
|
||||
0, /* tp_as_mapping */
|
||||
@ -483,9 +491,9 @@ PyTypeObject DukFunction_Type = {
|
||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
||||
"Duktape function proxy" /* tp_doc */
|
||||
};
|
||||
// }}}
|
||||
|
||||
|
||||
/* DukEnum */
|
||||
/* DukEnum {{{ */
|
||||
|
||||
DukEnum *DukEnum_from_DukContext(DukContext *context, dukenum_mode_t mode)
|
||||
{
|
||||
@ -575,3 +583,4 @@ PyTypeObject DukEnum_Type = {
|
||||
(getiterfunc)DukEnum_iter, /* tp_iter */
|
||||
(iternextfunc)DukEnum_iternext, /* tp_iternext */
|
||||
};
|
||||
// }}}
|
||||
|
@ -30,6 +30,12 @@ class ContextTests(unittest.TestCase):
|
||||
def test_undefined(self):
|
||||
self.assertEqual(repr(undefined), 'undefined')
|
||||
|
||||
def test_roundtrip(self):
|
||||
self.g.g = self.ctx.eval('function f() {return 1;}; f')
|
||||
self.assertEqual(self.g.g.name, 'f')
|
||||
self.g.a = self.ctx.eval('[1,2,3]')
|
||||
self.assertEqual(self.g.a[2], 3)
|
||||
|
||||
|
||||
class ValueTests(unittest.TestCase):
|
||||
|
||||
@ -112,8 +118,8 @@ class ValueTests(unittest.TestCase):
|
||||
self.assert_('No error raised for bad function')
|
||||
except JSError as e:
|
||||
e = e.args[0]
|
||||
self.assertEqual('ReferenceError', e.name)
|
||||
self.assertIn('nonexistent', e.toString())
|
||||
self.assertEqual('ReferenceError', e['name'])
|
||||
self.assertIn('nonexistent', e['message'])
|
||||
|
||||
|
||||
class EvalTests(unittest.TestCase):
|
||||
@ -148,19 +154,19 @@ class EvalTests(unittest.TestCase):
|
||||
self.assert_('No error raised for malformed js')
|
||||
except JSError as e:
|
||||
e = e.args[0]
|
||||
self.assertEqual('SyntaxError', e.name)
|
||||
self.assertEqual('<eval>', e.fileName)
|
||||
self.assertEqual(1, e.lineNumber)
|
||||
self.assertIn('line 1', e.toString())
|
||||
self.assertEqual('SyntaxError', e['name'])
|
||||
self.assertEqual('<eval>', e['fileName'])
|
||||
self.assertEqual(1, e['lineNumber'])
|
||||
self.assertIn('line 1', e['message'])
|
||||
|
||||
try:
|
||||
self.ctx.eval('\na()', fname='xxx')
|
||||
self.assert_('No error raised for malformed js')
|
||||
except JSError as e:
|
||||
e = e.args[0]
|
||||
self.assertEqual('ReferenceError', e.name)
|
||||
self.assertEqual('xxx', e.fileName)
|
||||
self.assertEqual(2, e.lineNumber)
|
||||
self.assertEqual('ReferenceError', e['name'])
|
||||
self.assertEqual('xxx', e['fileName'])
|
||||
self.assertEqual(2, e['lineNumber'])
|
||||
|
||||
def test_eval_multithreading(self):
|
||||
ev = Event()
|
||||
|
Loading…
x
Reference in New Issue
Block a user