mirror of
https://github.com/sstent/sublime-text-3.git
synced 2026-01-25 22:51:41 +00:00
2751 lines
84 KiB
Python
2751 lines
84 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
import sys, os, re
|
|
import logging
|
|
import collections
|
|
|
|
is_py3k = sys.version_info[0] > 2
|
|
|
|
if is_py3k:
|
|
import _thread as thread
|
|
|
|
from io import StringIO
|
|
|
|
str = str
|
|
raw_input = input
|
|
else:
|
|
import _thread
|
|
|
|
try:
|
|
from io import StringIO
|
|
except ImportError:
|
|
from io import StringIO
|
|
|
|
try:
|
|
import json
|
|
except ImportError:
|
|
import simplejson as json
|
|
|
|
import _PyV8
|
|
|
|
__author__ = 'Flier Lu <flier.lu@gmail.com>'
|
|
__version__ = '1.0'
|
|
|
|
__all__ = ["ReadOnly", "DontEnum", "DontDelete", "Internal",
|
|
"JSError", "JSObject", "JSArray", "JSFunction",
|
|
"JSClass", "JSEngine", "JSContext",
|
|
"JSObjectSpace", "JSAllocationAction",
|
|
"JSStackTrace", "JSStackFrame", "profiler",
|
|
"JSExtension", "JSLocker", "JSUnlocker", "AST"]
|
|
|
|
class JSAttribute(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
def __call__(self, func):
|
|
setattr(func, "__%s__" % self.name, True)
|
|
|
|
return func
|
|
|
|
ReadOnly = JSAttribute(name='readonly')
|
|
DontEnum = JSAttribute(name='dontenum')
|
|
DontDelete = JSAttribute(name='dontdel')
|
|
Internal = JSAttribute(name='internal')
|
|
|
|
class JSError(Exception):
|
|
def __init__(self, impl):
|
|
Exception.__init__(self)
|
|
|
|
self._impl = impl
|
|
|
|
def __str__(self):
|
|
return str(self._impl)
|
|
|
|
def __unicode__(self, *args, **kwargs):
|
|
return str(self._impl)
|
|
|
|
def __getattribute__(self, attr):
|
|
impl = super(JSError, self).__getattribute__("_impl")
|
|
|
|
try:
|
|
return getattr(impl, attr)
|
|
except AttributeError:
|
|
return super(JSError, self).__getattribute__(attr)
|
|
|
|
RE_FRAME = re.compile(r"\s+at\s(?:new\s)?(?P<func>.+)\s\((?P<file>[^:]+):?(?P<row>\d+)?:?(?P<col>\d+)?\)")
|
|
RE_FUNC = re.compile(r"\s+at\s(?:new\s)?(?P<func>.+)\s\((?P<file>[^\)]+)\)")
|
|
RE_FILE = re.compile(r"\s+at\s(?P<file>[^:]+):?(?P<row>\d+)?:?(?P<col>\d+)?")
|
|
|
|
@staticmethod
|
|
def parse_stack(value):
|
|
stack = []
|
|
|
|
def int_or_nul(value):
|
|
return int(value) if value else None
|
|
|
|
for line in value.split('\n')[1:]:
|
|
m = JSError.RE_FRAME.match(line)
|
|
|
|
if m:
|
|
stack.append((m.group('func'), m.group('file'), int_or_nul(m.group('row')), int_or_nul(m.group('col'))))
|
|
continue
|
|
|
|
m = JSError.RE_FUNC.match(line)
|
|
|
|
if m:
|
|
stack.append((m.group('func'), m.group('file'), None, None))
|
|
continue
|
|
|
|
m = JSError.RE_FILE.match(line)
|
|
|
|
if m:
|
|
stack.append((None, m.group('file'), int_or_nul(m.group('row')), int_or_nul(m.group('col'))))
|
|
continue
|
|
|
|
assert line
|
|
|
|
return stack
|
|
|
|
@property
|
|
def frames(self):
|
|
return self.parse_stack(self.stackTrace)
|
|
|
|
_PyV8._JSError._jsclass = JSError
|
|
|
|
JSObject = _PyV8.JSObject
|
|
JSArray = _PyV8.JSArray
|
|
JSFunction = _PyV8.JSFunction
|
|
|
|
# contribute by e.generalov
|
|
|
|
JS_ESCAPABLE = re.compile(r'([^\x00-\x7f])')
|
|
HAS_UTF8 = re.compile(r'[\x80-\xff]')
|
|
|
|
def _js_escape_unicode_re_callack(match):
|
|
n = ord(match.group(0))
|
|
if n < 0x10000:
|
|
return '\\u%04x' % (n,)
|
|
else:
|
|
# surrogate pair
|
|
n -= 0x10000
|
|
s1 = 0xd800 | ((n >> 10) & 0x3ff)
|
|
s2 = 0xdc00 | (n & 0x3ff)
|
|
return '\\u%04x\\u%04x' % (s1, s2)
|
|
|
|
def js_escape_unicode(text):
|
|
"""Return an ASCII-only representation of a JavaScript string"""
|
|
if isinstance(text, str):
|
|
if HAS_UTF8.search(text) is None:
|
|
return text
|
|
|
|
text = text.decode('UTF-8')
|
|
|
|
return str(JS_ESCAPABLE.sub(_js_escape_unicode_re_callack, text))
|
|
|
|
class JSExtension(_PyV8.JSExtension):
|
|
def __init__(self, name, source, callback=None, dependencies=[], register=True):
|
|
_PyV8.JSExtension.__init__(self, js_escape_unicode(name), js_escape_unicode(source), callback, dependencies, register)
|
|
|
|
def func_apply(self, thisArg, argArray=[]):
|
|
if isinstance(thisArg, JSObject):
|
|
return self.invoke(thisArg, argArray)
|
|
|
|
this = JSContext.current.eval("(%s)" % json.dumps(thisArg))
|
|
|
|
return self.invoke(this, argArray)
|
|
|
|
JSFunction.apply = func_apply
|
|
|
|
class JSLocker(_PyV8.JSLocker):
|
|
def __enter__(self):
|
|
self.enter()
|
|
|
|
if JSContext.entered:
|
|
self.leave()
|
|
raise RuntimeError("Lock should be acquired before enter the context")
|
|
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
if JSContext.entered:
|
|
self.leave()
|
|
raise RuntimeError("Lock should be released after leave the context")
|
|
|
|
self.leave()
|
|
|
|
if is_py3k:
|
|
def __bool__(self):
|
|
return self.entered()
|
|
else:
|
|
def __nonzero__(self):
|
|
return self.entered()
|
|
|
|
class JSUnlocker(_PyV8.JSUnlocker):
|
|
def __enter__(self):
|
|
self.enter()
|
|
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
self.leave()
|
|
|
|
if is_py3k:
|
|
def __bool__(self):
|
|
return self.entered()
|
|
else:
|
|
def __nonzero__(self):
|
|
return self.entered()
|
|
|
|
class JSClass(object):
|
|
__properties__ = {}
|
|
__watchpoints__ = {}
|
|
|
|
def __getattr__(self, name):
|
|
if name == 'constructor':
|
|
return JSClassConstructor(self.__class__)
|
|
|
|
if name == 'prototype':
|
|
return JSClassPrototype(self.__class__)
|
|
|
|
prop = self.__dict__.setdefault('__properties__', {}).get(name, None)
|
|
|
|
if prop and isinstance(prop[0], collections.Callable):
|
|
return prop[0]()
|
|
|
|
raise AttributeError(name)
|
|
|
|
def __setattr__(self, name, value):
|
|
prop = self.__dict__.setdefault('__properties__', {}).get(name, None)
|
|
|
|
if prop and isinstance(prop[1], collections.Callable):
|
|
return prop[1](value)
|
|
|
|
return object.__setattr__(self, name, value)
|
|
|
|
def toString(self):
|
|
"Returns a string representation of an object."
|
|
return "[object %s]" % self.__class__.__name__
|
|
|
|
def toLocaleString(self):
|
|
"Returns a value as a string value appropriate to the host environment's current locale."
|
|
return self.toString()
|
|
|
|
def valueOf(self):
|
|
"Returns the primitive value of the specified object."
|
|
return self
|
|
|
|
def hasOwnProperty(self, name):
|
|
"Returns a Boolean value indicating whether an object has a property with the specified name."
|
|
return hasattr(self, name)
|
|
|
|
def isPrototypeOf(self, obj):
|
|
"Returns a Boolean value indicating whether an object exists in the prototype chain of another object."
|
|
raise NotImplementedError()
|
|
|
|
def __defineGetter__(self, name, getter):
|
|
"Binds an object's property to a function to be called when that property is looked up."
|
|
self.__properties__[name] = (getter, self.__lookupSetter__(name))
|
|
|
|
def __lookupGetter__(self, name):
|
|
"Return the function bound as a getter to the specified property."
|
|
return self.__properties__.get(name, (None, None))[0]
|
|
|
|
def __defineSetter__(self, name, setter):
|
|
"Binds an object's property to a function to be called when an attempt is made to set that property."
|
|
self.__properties__[name] = (self.__lookupGetter__(name), setter)
|
|
|
|
def __lookupSetter__(self, name):
|
|
"Return the function bound as a setter to the specified property."
|
|
return self.__properties__.get(name, (None, None))[1]
|
|
|
|
def watch(self, prop, handler):
|
|
"Watches for a property to be assigned a value and runs a function when that occurs."
|
|
self.__watchpoints__[prop] = handler
|
|
|
|
def unwatch(self, prop):
|
|
"Removes a watchpoint set with the watch method."
|
|
del self.__watchpoints__[prop]
|
|
|
|
class JSClassConstructor(JSClass):
|
|
def __init__(self, cls):
|
|
self.cls = cls
|
|
|
|
@property
|
|
def name(self):
|
|
return self.cls.__name__
|
|
|
|
def toString(self):
|
|
return "function %s() {\n [native code]\n}" % self.name
|
|
|
|
def __call__(self, *args, **kwds):
|
|
return self.cls(*args, **kwds)
|
|
|
|
class JSClassPrototype(JSClass):
|
|
def __init__(self, cls):
|
|
self.cls = cls
|
|
|
|
@property
|
|
def constructor(self):
|
|
return JSClassConstructor(self.cls)
|
|
|
|
@property
|
|
def name(self):
|
|
return self.cls.__name__
|
|
|
|
class JSDebugProtocol(object):
|
|
"""
|
|
Support the V8 debugger JSON based protocol.
|
|
|
|
<http://code.google.com/p/v8/wiki/DebuggerProtocol>
|
|
"""
|
|
class Packet(object):
|
|
REQUEST = 'request'
|
|
RESPONSE = 'response'
|
|
EVENT = 'event'
|
|
|
|
def __init__(self, payload):
|
|
self.data = json.loads(payload) if type(payload) in [str, str] else payload
|
|
|
|
@property
|
|
def seq(self):
|
|
return self.data['seq']
|
|
|
|
@property
|
|
def type(self):
|
|
return self.data['type']
|
|
|
|
class Request(Packet):
|
|
@property
|
|
def cmd(self):
|
|
return self.data['command']
|
|
|
|
@property
|
|
def args(self):
|
|
return self.data['args']
|
|
|
|
class Response(Packet):
|
|
@property
|
|
def request_seq(self):
|
|
return self.data['request_seq']
|
|
|
|
@property
|
|
def cmd(self):
|
|
return self.data['command']
|
|
|
|
@property
|
|
def body(self):
|
|
return self.data['body']
|
|
|
|
@property
|
|
def running(self):
|
|
return self.data['running']
|
|
|
|
@property
|
|
def success(self):
|
|
return self.data['success']
|
|
|
|
@property
|
|
def message(self):
|
|
return self.data['message']
|
|
|
|
class Event(Packet):
|
|
@property
|
|
def event(self):
|
|
return self.data['event']
|
|
|
|
@property
|
|
def body(self):
|
|
return self.data['body']
|
|
|
|
def __init__(self):
|
|
self.seq = 0
|
|
|
|
def nextSeq(self):
|
|
seq = self.seq
|
|
self.seq += 1
|
|
|
|
return seq
|
|
|
|
def parsePacket(self, payload):
|
|
obj = json.loads(payload)
|
|
|
|
return JSDebugProtocol.Event(obj) if obj['type'] == 'event' else JSDebugProtocol.Response(obj)
|
|
|
|
class JSDebugEvent(_PyV8.JSDebugEvent):
|
|
class FrameData(object):
|
|
def __init__(self, frame, count, name, value):
|
|
self.frame = frame
|
|
self.count = count
|
|
self.name = name
|
|
self.value = value
|
|
|
|
def __len__(self):
|
|
return self.count(self.frame)
|
|
|
|
def __iter__(self):
|
|
for i in range(self.count(self.frame)):
|
|
yield (self.name(self.frame, i), self.value(self.frame, i))
|
|
|
|
class Frame(object):
|
|
def __init__(self, frame):
|
|
self.frame = frame
|
|
|
|
@property
|
|
def index(self):
|
|
return int(self.frame.index())
|
|
|
|
@property
|
|
def function(self):
|
|
return self.frame.func()
|
|
|
|
@property
|
|
def receiver(self):
|
|
return self.frame.receiver()
|
|
|
|
@property
|
|
def isConstructCall(self):
|
|
return bool(self.frame.isConstructCall())
|
|
|
|
@property
|
|
def isDebuggerFrame(self):
|
|
return bool(self.frame.isDebuggerFrame())
|
|
|
|
@property
|
|
def argumentCount(self):
|
|
return int(self.frame.argumentCount())
|
|
|
|
def argumentName(self, idx):
|
|
return str(self.frame.argumentName(idx))
|
|
|
|
def argumentValue(self, idx):
|
|
return self.frame.argumentValue(idx)
|
|
|
|
@property
|
|
def arguments(self):
|
|
return JSDebugEvent.FrameData(self, self.argumentCount, self.argumentName, self.argumentValue)
|
|
|
|
def localCount(self, idx):
|
|
return int(self.frame.localCount())
|
|
|
|
def localName(self, idx):
|
|
return str(self.frame.localName(idx))
|
|
|
|
def localValue(self, idx):
|
|
return self.frame.localValue(idx)
|
|
|
|
@property
|
|
def locals(self):
|
|
return JSDebugEvent.FrameData(self, self.localCount, self.localName, self.localValue)
|
|
|
|
@property
|
|
def sourcePosition(self):
|
|
return self.frame.sourcePosition()
|
|
|
|
@property
|
|
def sourceLine(self):
|
|
return int(self.frame.sourceLine())
|
|
|
|
@property
|
|
def sourceColumn(self):
|
|
return int(self.frame.sourceColumn())
|
|
|
|
@property
|
|
def sourceLineText(self):
|
|
return str(self.frame.sourceLineText())
|
|
|
|
def evaluate(self, source, disable_break = True):
|
|
return self.frame.evaluate(source, disable_break)
|
|
|
|
@property
|
|
def invocationText(self):
|
|
return str(self.frame.invocationText())
|
|
|
|
@property
|
|
def sourceAndPositionText(self):
|
|
return str(self.frame.sourceAndPositionText())
|
|
|
|
@property
|
|
def localsText(self):
|
|
return str(self.frame.localsText())
|
|
|
|
def __str__(self):
|
|
return str(self.frame.toText())
|
|
|
|
class Frames(object):
|
|
def __init__(self, state):
|
|
self.state = state
|
|
|
|
def __len__(self):
|
|
return self.state.frameCount
|
|
|
|
def __iter__(self):
|
|
for i in range(self.state.frameCount):
|
|
yield self.state.frame(i)
|
|
|
|
class State(object):
|
|
def __init__(self, state):
|
|
self.state = state
|
|
|
|
@property
|
|
def frameCount(self):
|
|
return int(self.state.frameCount())
|
|
|
|
def frame(self, idx = None):
|
|
return JSDebugEvent.Frame(self.state.frame(idx))
|
|
|
|
@property
|
|
def selectedFrame(self):
|
|
return int(self.state.selectedFrame())
|
|
|
|
@property
|
|
def frames(self):
|
|
return JSDebugEvent.Frames(self)
|
|
|
|
def __repr__(self):
|
|
s = StringIO()
|
|
|
|
try:
|
|
for frame in self.frames:
|
|
s.write(str(frame))
|
|
|
|
return s.getvalue()
|
|
finally:
|
|
s.close()
|
|
|
|
class DebugEvent(object):
|
|
pass
|
|
|
|
class StateEvent(DebugEvent):
|
|
__state = None
|
|
|
|
@property
|
|
def state(self):
|
|
if not self.__state:
|
|
self.__state = JSDebugEvent.State(self.event.executionState())
|
|
|
|
return self.__state
|
|
|
|
class BreakEvent(StateEvent):
|
|
type = _PyV8.JSDebugEvent.Break
|
|
|
|
def __init__(self, event):
|
|
self.event = event
|
|
|
|
class ExceptionEvent(StateEvent):
|
|
type = _PyV8.JSDebugEvent.Exception
|
|
|
|
def __init__(self, event):
|
|
self.event = event
|
|
|
|
class NewFunctionEvent(DebugEvent):
|
|
type = _PyV8.JSDebugEvent.NewFunction
|
|
|
|
def __init__(self, event):
|
|
self.event = event
|
|
|
|
class Script(object):
|
|
def __init__(self, script):
|
|
self.script = script
|
|
|
|
@property
|
|
def source(self):
|
|
return self.script.source()
|
|
|
|
@property
|
|
def id(self):
|
|
return self.script.id()
|
|
|
|
@property
|
|
def name(self):
|
|
return self.script.name()
|
|
|
|
@property
|
|
def lineOffset(self):
|
|
return self.script.lineOffset()
|
|
|
|
@property
|
|
def lineCount(self):
|
|
return self.script.lineCount()
|
|
|
|
@property
|
|
def columnOffset(self):
|
|
return self.script.columnOffset()
|
|
|
|
@property
|
|
def type(self):
|
|
return self.script.type()
|
|
|
|
def __repr__(self):
|
|
return "<%s script %s @ %d:%d> : '%s'" % (self.type, self.name,
|
|
self.lineOffset, self.columnOffset,
|
|
self.source)
|
|
|
|
class CompileEvent(StateEvent):
|
|
def __init__(self, event):
|
|
self.event = event
|
|
|
|
@property
|
|
def script(self):
|
|
if not hasattr(self, "_script"):
|
|
setattr(self, "_script", JSDebugEvent.Script(self.event.script()))
|
|
|
|
return self._script
|
|
|
|
def __str__(self):
|
|
return str(self.script)
|
|
|
|
class BeforeCompileEvent(CompileEvent):
|
|
type = _PyV8.JSDebugEvent.BeforeCompile
|
|
|
|
def __init__(self, event):
|
|
JSDebugEvent.CompileEvent.__init__(self, event)
|
|
|
|
def __repr__(self):
|
|
return "before compile script: %s\n%s" % (repr(self.script), repr(self.state))
|
|
|
|
class AfterCompileEvent(CompileEvent):
|
|
type = _PyV8.JSDebugEvent.AfterCompile
|
|
|
|
def __init__(self, event):
|
|
JSDebugEvent.CompileEvent.__init__(self, event)
|
|
|
|
def __repr__(self):
|
|
return "after compile script: %s\n%s" % (repr(self.script), repr(self.state))
|
|
|
|
onMessage = None
|
|
onBreak = None
|
|
onException = None
|
|
onNewFunction = None
|
|
onBeforeCompile = None
|
|
onAfterCompile = None
|
|
|
|
class JSDebugger(JSDebugProtocol, JSDebugEvent):
|
|
def __init__(self):
|
|
JSDebugProtocol.__init__(self)
|
|
JSDebugEvent.__init__(self)
|
|
|
|
def __enter__(self):
|
|
self.enabled = True
|
|
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
self.enabled = False
|
|
|
|
@property
|
|
def context(self):
|
|
if not hasattr(self, '_context'):
|
|
self._context = JSContext(ctxt=_PyV8.debug().context)
|
|
|
|
return self._context
|
|
|
|
def isEnabled(self):
|
|
return _PyV8.debug().enabled
|
|
|
|
def setEnabled(self, enable):
|
|
dbg = _PyV8.debug()
|
|
|
|
if enable:
|
|
dbg.onDebugEvent = self.onDebugEvent
|
|
dbg.onDebugMessage = self.onDebugMessage
|
|
dbg.onDispatchDebugMessages = self.onDispatchDebugMessages
|
|
else:
|
|
dbg.onDebugEvent = None
|
|
dbg.onDebugMessage = None
|
|
dbg.onDispatchDebugMessages = None
|
|
|
|
dbg.enabled = enable
|
|
|
|
enabled = property(isEnabled, setEnabled)
|
|
|
|
def onDebugMessage(self, msg, data):
|
|
if self.onMessage:
|
|
self.onMessage(json.loads(msg))
|
|
|
|
def onDebugEvent(self, type, state, evt):
|
|
if type == JSDebugEvent.Break:
|
|
if self.onBreak: self.onBreak(JSDebugEvent.BreakEvent(evt))
|
|
elif type == JSDebugEvent.Exception:
|
|
if self.onException: self.onException(JSDebugEvent.ExceptionEvent(evt))
|
|
elif type == JSDebugEvent.NewFunction:
|
|
if self.onNewFunction: self.onNewFunction(JSDebugEvent.NewFunctionEvent(evt))
|
|
elif type == JSDebugEvent.BeforeCompile:
|
|
if self.onBeforeCompile: self.onBeforeCompile(JSDebugEvent.BeforeCompileEvent(evt))
|
|
elif type == JSDebugEvent.AfterCompile:
|
|
if self.onAfterCompile: self.onAfterCompile(JSDebugEvent.AfterCompileEvent(evt))
|
|
|
|
def onDispatchDebugMessages(self):
|
|
return True
|
|
|
|
def debugBreak(self):
|
|
_PyV8.debug().debugBreak()
|
|
|
|
def debugBreakForCommand(self):
|
|
_PyV8.debug().debugBreakForCommand()
|
|
|
|
def cancelDebugBreak(self):
|
|
_PyV8.debug().cancelDebugBreak()
|
|
|
|
def processDebugMessages(self):
|
|
_PyV8.debug().processDebugMessages()
|
|
|
|
def sendCommand(self, cmd, *args, **kwds):
|
|
request = json.dumps({
|
|
'seq': self.nextSeq(),
|
|
'type': 'request',
|
|
'command': cmd,
|
|
'arguments': kwds
|
|
})
|
|
|
|
_PyV8.debug().sendCommand(request)
|
|
|
|
return request
|
|
|
|
def debugContinue(self, action='next', steps=1):
|
|
return self.sendCommand('continue', stepaction=action)
|
|
|
|
def stepNext(self, steps=1):
|
|
"""Step to the next statement in the current function."""
|
|
return self.debugContinue(action='next', steps=steps)
|
|
|
|
def stepIn(self, steps=1):
|
|
"""Step into new functions invoked or the next statement in the current function."""
|
|
return self.debugContinue(action='in', steps=steps)
|
|
|
|
def stepOut(self, steps=1):
|
|
"""Step out of the current function."""
|
|
return self.debugContinue(action='out', steps=steps)
|
|
|
|
def stepMin(self, steps=1):
|
|
"""Perform a minimum step in the current function."""
|
|
return self.debugContinue(action='out', steps=steps)
|
|
|
|
class JSProfiler(_PyV8.JSProfiler):
|
|
@property
|
|
def logs(self):
|
|
pos = 0
|
|
|
|
while True:
|
|
size, buf = self.getLogLines(pos)
|
|
|
|
if size == 0:
|
|
break
|
|
|
|
for line in buf.split('\n'):
|
|
yield line
|
|
|
|
pos += size
|
|
|
|
profiler = JSProfiler()
|
|
|
|
JSObjectSpace = _PyV8.JSObjectSpace
|
|
JSAllocationAction = _PyV8.JSAllocationAction
|
|
|
|
class JSEngine(_PyV8.JSEngine):
|
|
def __init__(self):
|
|
_PyV8.JSEngine.__init__(self)
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
del self
|
|
|
|
JSScript = _PyV8.JSScript
|
|
|
|
JSStackTrace = _PyV8.JSStackTrace
|
|
JSStackTrace.Options = _PyV8.JSStackTraceOptions
|
|
JSStackFrame = _PyV8.JSStackFrame
|
|
|
|
class JSIsolate(_PyV8.JSIsolate):
|
|
def __enter__(self):
|
|
self.enter()
|
|
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
self.leave()
|
|
|
|
del self
|
|
|
|
class JSContext(_PyV8.JSContext):
|
|
def __init__(self, obj=None, extensions=None, ctxt=None):
|
|
if JSLocker.active:
|
|
self.lock = JSLocker()
|
|
self.lock.enter()
|
|
|
|
if ctxt:
|
|
_PyV8.JSContext.__init__(self, ctxt)
|
|
else:
|
|
_PyV8.JSContext.__init__(self, obj, extensions or [])
|
|
|
|
def __enter__(self):
|
|
self.enter()
|
|
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
self.leave()
|
|
|
|
if hasattr(JSLocker, 'lock'):
|
|
self.lock.leave()
|
|
self.lock = None
|
|
|
|
del self
|
|
|
|
# contribute by marc boeker <http://code.google.com/u/marc.boeker/>
|
|
def convert(obj):
|
|
if type(obj) == _PyV8.JSArray:
|
|
return [convert(v) for v in obj]
|
|
|
|
if type(obj) == _PyV8.JSObject:
|
|
return dict([[str(k), convert(obj.__getattr__(str(k)))] for k in (obj.__dir__() if is_py3k else obj.__members__)])
|
|
|
|
return obj
|
|
|
|
class AST:
|
|
Scope = _PyV8.AstScope
|
|
VarMode = _PyV8.AstVariableMode
|
|
Var = _PyV8.AstVariable
|
|
Label = _PyV8.AstLabel
|
|
NodeType = _PyV8.AstNodeType
|
|
Node = _PyV8.AstNode
|
|
Statement = _PyV8.AstStatement
|
|
Expression = _PyV8.AstExpression
|
|
Breakable = _PyV8.AstBreakableStatement
|
|
Block = _PyV8.AstBlock
|
|
Declaration = _PyV8.AstDeclaration
|
|
VariableDeclaration = _PyV8.AstVariableDeclaration
|
|
Module = _PyV8.AstModule
|
|
ModuleDeclaration = _PyV8.AstModuleDeclaration
|
|
ModuleLiteral = _PyV8.AstModuleLiteral
|
|
ModuleVariable = _PyV8.AstModuleVariable
|
|
ModulePath = _PyV8.AstModulePath
|
|
Iteration = _PyV8.AstIterationStatement
|
|
DoWhile = _PyV8.AstDoWhileStatement
|
|
While = _PyV8.AstWhileStatement
|
|
For = _PyV8.AstForStatement
|
|
ForIn = _PyV8.AstForInStatement
|
|
ExpressionStatement = _PyV8.AstExpressionStatement
|
|
Continue = _PyV8.AstContinueStatement
|
|
Break = _PyV8.AstBreakStatement
|
|
Return = _PyV8.AstReturnStatement
|
|
With = _PyV8.AstWithStatement
|
|
Case = _PyV8.AstCaseClause
|
|
Switch = _PyV8.AstSwitchStatement
|
|
Try = _PyV8.AstTryStatement
|
|
TryCatch = _PyV8.AstTryCatchStatement
|
|
TryFinally = _PyV8.AstTryFinallyStatement
|
|
Debugger = _PyV8.AstDebuggerStatement
|
|
Empty = _PyV8.AstEmptyStatement
|
|
Literal = _PyV8.AstLiteral
|
|
MaterializedLiteral = _PyV8.AstMaterializedLiteral
|
|
PropertyKind = _PyV8.AstPropertyKind
|
|
ObjectProperty = _PyV8.AstObjectProperty
|
|
Object = _PyV8.AstObjectLiteral
|
|
RegExp = _PyV8.AstRegExpLiteral
|
|
Array = _PyV8.AstArrayLiteral
|
|
VarProxy = _PyV8.AstVariableProxy
|
|
Property = _PyV8.AstProperty
|
|
Call = _PyV8.AstCall
|
|
CallNew = _PyV8.AstCallNew
|
|
CallRuntime = _PyV8.AstCallRuntime
|
|
Op = _PyV8.AstOperation
|
|
UnaryOp = _PyV8.AstUnaryOperation
|
|
BinOp = _PyV8.AstBinaryOperation
|
|
CountOp = _PyV8.AstCountOperation
|
|
CompOp = _PyV8.AstCompareOperation
|
|
Conditional = _PyV8.AstConditional
|
|
Assignment = _PyV8.AstAssignment
|
|
Throw = _PyV8.AstThrow
|
|
Function = _PyV8.AstFunctionLiteral
|
|
SharedFunction = _PyV8.AstSharedFunctionInfoLiteral
|
|
This = _PyV8.AstThisFunction
|
|
|
|
from datetime import *
|
|
import unittest
|
|
import traceback
|
|
|
|
if is_py3k:
|
|
def toNativeString(s):
|
|
return s
|
|
def toUnicodeString(s):
|
|
return s
|
|
else:
|
|
def toNativeString(s, encoding='utf-8'):
|
|
return s.encode(encoding) if isinstance(s, str) else s
|
|
|
|
def toUnicodeString(s, encoding='utf-8'):
|
|
return s if isinstance(s, str) else str(s, encoding)
|
|
|
|
class TestContext(unittest.TestCase):
|
|
def testMultiNamespace(self):
|
|
self.assertTrue(not bool(JSContext.inContext))
|
|
self.assertTrue(not bool(JSContext.entered))
|
|
|
|
class Global(object):
|
|
name = "global"
|
|
|
|
g = Global()
|
|
|
|
with JSContext(g) as ctxt:
|
|
self.assertTrue(bool(JSContext.inContext))
|
|
self.assertEqual(g.name, str(JSContext.entered.locals.name))
|
|
self.assertEqual(g.name, str(JSContext.current.locals.name))
|
|
|
|
class Local(object):
|
|
name = "local"
|
|
|
|
l = Local()
|
|
|
|
with JSContext(l):
|
|
self.assertTrue(bool(JSContext.inContext))
|
|
self.assertEqual(l.name, str(JSContext.entered.locals.name))
|
|
self.assertEqual(l.name, str(JSContext.current.locals.name))
|
|
|
|
self.assertTrue(bool(JSContext.inContext))
|
|
self.assertEqual(g.name, str(JSContext.entered.locals.name))
|
|
self.assertEqual(g.name, str(JSContext.current.locals.name))
|
|
|
|
self.assertTrue(not bool(JSContext.entered))
|
|
self.assertTrue(not bool(JSContext.inContext))
|
|
|
|
def _testMultiContext(self):
|
|
# Create an environment
|
|
with JSContext() as ctxt0:
|
|
ctxt0.securityToken = "password"
|
|
|
|
global0 = ctxt0.locals
|
|
global0.custom = 1234
|
|
|
|
self.assertEqual(1234, int(global0.custom))
|
|
|
|
# Create an independent environment
|
|
with JSContext() as ctxt1:
|
|
ctxt1.securityToken = ctxt0.securityToken
|
|
|
|
global1 = ctxt1.locals
|
|
global1.custom = 1234
|
|
|
|
with ctxt0:
|
|
self.assertEqual(1234, int(global0.custom))
|
|
self.assertEqual(1234, int(global1.custom))
|
|
|
|
# Now create a new context with the old global
|
|
with JSContext(global1) as ctxt2:
|
|
ctxt2.securityToken = ctxt1.securityToken
|
|
|
|
with ctxt1:
|
|
self.assertEqual(1234, int(global1.custom))
|
|
|
|
def _testSecurityChecks(self):
|
|
with JSContext() as env1:
|
|
env1.securityToken = "foo"
|
|
|
|
# Create a function in env1.
|
|
env1.eval("spy=function(){return spy;}")
|
|
|
|
spy = env1.locals.spy
|
|
|
|
self.assertTrue(isinstance(spy, _PyV8.JSFunction))
|
|
|
|
# Create another function accessing global objects.
|
|
env1.eval("spy2=function(){return 123;}")
|
|
|
|
spy2 = env1.locals.spy2
|
|
|
|
self.assertTrue(isinstance(spy2, _PyV8.JSFunction))
|
|
|
|
# Switch to env2 in the same domain and invoke spy on env2.
|
|
env2 = JSContext()
|
|
|
|
env2.securityToken = "foo"
|
|
|
|
with env2:
|
|
result = spy.apply(env2.locals)
|
|
|
|
self.assertTrue(isinstance(result, _PyV8.JSFunction))
|
|
|
|
env2.securityToken = "bar"
|
|
|
|
# Call cross_domain_call, it should throw an exception
|
|
with env2:
|
|
self.assertRaises(JSError, spy2.apply, env2.locals)
|
|
|
|
def _testCrossDomainDelete(self):
|
|
with JSContext() as env1:
|
|
env2 = JSContext()
|
|
|
|
# Set to the same domain.
|
|
env1.securityToken = "foo"
|
|
env2.securityToken = "foo"
|
|
|
|
env1.locals.prop = 3
|
|
|
|
env2.locals.env1 = env1.locals
|
|
|
|
# Change env2 to a different domain and delete env1.prop.
|
|
#env2.securityToken = "bar"
|
|
|
|
self.assertEqual(3, int(env1.eval("prop")))
|
|
|
|
with env2:
|
|
self.assertEqual(3, int(env2.eval("this.env1.prop")))
|
|
self.assertEqual("false", str(env2.eval("delete env1.prop")))
|
|
|
|
# Check that env1.prop still exists.
|
|
self.assertEqual(3, int(env1.locals.prop))
|
|
|
|
class TestWrapper(unittest.TestCase):
|
|
def testObject(self):
|
|
with JSContext() as ctxt:
|
|
o = ctxt.eval("new Object()")
|
|
|
|
self.assertTrue(hash(o) > 0)
|
|
|
|
o1 = o.clone()
|
|
|
|
self.assertEqual(hash(o1), hash(o))
|
|
self.assertTrue(o != o1)
|
|
|
|
self.assertRaises(UnboundLocalError, o.clone)
|
|
|
|
def testAutoConverter(self):
|
|
with JSContext() as ctxt:
|
|
ctxt.eval("""
|
|
var_i = 1;
|
|
var_f = 1.0;
|
|
var_s = "test";
|
|
var_b = true;
|
|
var_s_obj = new String("test");
|
|
var_b_obj = new Boolean(true);
|
|
var_f_obj = new Number(1.5);
|
|
""")
|
|
|
|
vars = ctxt.locals
|
|
|
|
var_i = vars.var_i
|
|
|
|
self.assertTrue(var_i)
|
|
self.assertEqual(1, int(var_i))
|
|
|
|
var_f = vars.var_f
|
|
|
|
self.assertTrue(var_f)
|
|
self.assertEqual(1.0, float(vars.var_f))
|
|
|
|
var_s = vars.var_s
|
|
self.assertTrue(var_s)
|
|
self.assertEqual("test", str(vars.var_s))
|
|
|
|
var_b = vars.var_b
|
|
self.assertTrue(var_b)
|
|
self.assertTrue(bool(var_b))
|
|
|
|
self.assertEqual("test", vars.var_s_obj)
|
|
self.assertTrue(vars.var_b_obj)
|
|
self.assertEqual(1.5, vars.var_f_obj)
|
|
|
|
attrs = dir(ctxt.locals)
|
|
|
|
self.assertTrue(attrs)
|
|
self.assertTrue("var_i" in attrs)
|
|
self.assertTrue("var_f" in attrs)
|
|
self.assertTrue("var_s" in attrs)
|
|
self.assertTrue("var_b" in attrs)
|
|
self.assertTrue("var_s_obj" in attrs)
|
|
self.assertTrue("var_b_obj" in attrs)
|
|
self.assertTrue("var_f_obj" in attrs)
|
|
|
|
def testExactConverter(self):
|
|
class MyInteger(int, JSClass):
|
|
pass
|
|
|
|
class MyString(str, JSClass):
|
|
pass
|
|
|
|
class MyUnicode(str, JSClass):
|
|
pass
|
|
|
|
class MyDateTime(time, JSClass):
|
|
pass
|
|
|
|
class Global(JSClass):
|
|
var_bool = True
|
|
var_int = 1
|
|
var_float = 1.0
|
|
var_str = 'str'
|
|
var_unicode = 'unicode'
|
|
var_datetime = datetime.now()
|
|
var_date = date.today()
|
|
var_time = time()
|
|
|
|
var_myint = MyInteger()
|
|
var_mystr = MyString('mystr')
|
|
var_myunicode = MyUnicode('myunicode')
|
|
var_mytime = MyDateTime()
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
typename = ctxt.eval("(function (name) { return this[name].constructor.name; })")
|
|
typeof = ctxt.eval("(function (name) { return typeof(this[name]); })")
|
|
|
|
self.assertEqual('Boolean', typename('var_bool'))
|
|
self.assertEqual('Number', typename('var_int'))
|
|
self.assertEqual('Number', typename('var_float'))
|
|
self.assertEqual('String', typename('var_str'))
|
|
self.assertEqual('String', typename('var_unicode'))
|
|
self.assertEqual('Date', typename('var_datetime'))
|
|
self.assertEqual('Date', typename('var_date'))
|
|
self.assertEqual('Date', typename('var_time'))
|
|
|
|
self.assertEqual('MyInteger', typename('var_myint'))
|
|
self.assertEqual('MyString', typename('var_mystr'))
|
|
self.assertEqual('MyUnicode', typename('var_myunicode'))
|
|
self.assertEqual('MyDateTime', typename('var_mytime'))
|
|
|
|
self.assertEqual('object', typeof('var_myint'))
|
|
self.assertEqual('object', typeof('var_mystr'))
|
|
self.assertEqual('object', typeof('var_myunicode'))
|
|
self.assertEqual('object', typeof('var_mytime'))
|
|
|
|
def testJavascriptWrapper(self):
|
|
with JSContext() as ctxt:
|
|
self.assertEqual(type(None), type(ctxt.eval("null")))
|
|
self.assertEqual(type(None), type(ctxt.eval("undefined")))
|
|
self.assertEqual(bool, type(ctxt.eval("true")))
|
|
self.assertEqual(str, type(ctxt.eval("'test'")))
|
|
self.assertEqual(int, type(ctxt.eval("123")))
|
|
self.assertEqual(float, type(ctxt.eval("3.14")))
|
|
self.assertEqual(datetime, type(ctxt.eval("new Date()")))
|
|
self.assertEqual(JSArray, type(ctxt.eval("[1, 2, 3]")))
|
|
self.assertEqual(JSFunction, type(ctxt.eval("(function() {})")))
|
|
self.assertEqual(JSObject, type(ctxt.eval("new Object()")))
|
|
|
|
def testPythonWrapper(self):
|
|
with JSContext() as ctxt:
|
|
typeof = ctxt.eval("(function type(value) { return typeof value; })")
|
|
protoof = ctxt.eval("(function protoof(value) { return Object.prototype.toString.apply(value); })")
|
|
|
|
self.assertEqual('[object Null]', protoof(None))
|
|
self.assertEqual('boolean', typeof(True))
|
|
self.assertEqual('number', typeof(123))
|
|
self.assertEqual('number', typeof(3.14))
|
|
self.assertEqual('string', typeof('test'))
|
|
self.assertEqual('string', typeof('test'))
|
|
|
|
self.assertEqual('[object Date]', protoof(datetime.now()))
|
|
self.assertEqual('[object Date]', protoof(date.today()))
|
|
self.assertEqual('[object Date]', protoof(time()))
|
|
|
|
def test():
|
|
pass
|
|
|
|
self.assertEqual('[object Function]', protoof(abs))
|
|
self.assertEqual('[object Function]', protoof(test))
|
|
self.assertEqual('[object Function]', protoof(self.testPythonWrapper))
|
|
self.assertEqual('[object Function]', protoof(int))
|
|
|
|
def testFunction(self):
|
|
with JSContext() as ctxt:
|
|
func = ctxt.eval("""
|
|
(function ()
|
|
{
|
|
function a()
|
|
{
|
|
return "abc";
|
|
}
|
|
|
|
return a();
|
|
})
|
|
""")
|
|
|
|
self.assertEqual("abc", str(func()))
|
|
self.assertTrue(func != None)
|
|
self.assertFalse(func == None)
|
|
|
|
func = ctxt.eval("(function test() {})")
|
|
|
|
self.assertEqual("test", func.name)
|
|
self.assertEqual("", func.resname)
|
|
self.assertEqual(0, func.linenum)
|
|
self.assertEqual(14, func.colnum)
|
|
self.assertEqual(0, func.lineoff)
|
|
self.assertEqual(0, func.coloff)
|
|
|
|
#TODO fix me, why the setter doesn't work?
|
|
# func.name = "hello"
|
|
# it seems __setattr__ was called instead of CJavascriptFunction::SetName
|
|
|
|
func.setName("hello")
|
|
|
|
self.assertEqual("hello", func.name)
|
|
|
|
def testCall(self):
|
|
class Hello(object):
|
|
def __call__(self, name):
|
|
return "hello " + name
|
|
|
|
class Global(JSClass):
|
|
hello = Hello()
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
self.assertEqual("hello flier", ctxt.eval("hello('flier')"))
|
|
|
|
def testJSFunction(self):
|
|
with JSContext() as ctxt:
|
|
hello = ctxt.eval("(function (name) { return 'hello ' + name; })")
|
|
|
|
self.assertTrue(isinstance(hello, _PyV8.JSFunction))
|
|
self.assertEqual("hello flier", hello('flier'))
|
|
self.assertEqual("hello flier", hello.invoke(['flier']))
|
|
|
|
obj = ctxt.eval("({ 'name': 'flier', 'hello': function (name) { return 'hello ' + name + ' from ' + this.name; }})")
|
|
hello = obj.hello
|
|
self.assertTrue(isinstance(hello, JSFunction))
|
|
self.assertEqual("hello flier from flier", hello('flier'))
|
|
|
|
tester = ctxt.eval("({ 'name': 'tester' })")
|
|
self.assertEqual("hello flier from tester", hello.invoke(tester, ['flier']))
|
|
self.assertEqual("hello flier from json", hello.apply({ 'name': 'json' }, ['flier']))
|
|
|
|
def testConstructor(self):
|
|
with JSContext() as ctx:
|
|
ctx.eval("""
|
|
var Test = function() {
|
|
this.trySomething();
|
|
};
|
|
Test.prototype.trySomething = function() {
|
|
this.name = 'flier';
|
|
};
|
|
|
|
var Test2 = function(first_name, last_name) {
|
|
this.name = first_name + ' ' + last_name;
|
|
};
|
|
""")
|
|
|
|
self.assertTrue(isinstance(ctx.locals.Test, _PyV8.JSFunction))
|
|
|
|
test = JSObject.create(ctx.locals.Test)
|
|
|
|
self.assertTrue(isinstance(ctx.locals.Test, _PyV8.JSObject))
|
|
self.assertEqual("flier", test.name);
|
|
|
|
test2 = JSObject.create(ctx.locals.Test2, ('Flier', 'Lu'))
|
|
|
|
self.assertEqual("Flier Lu", test2.name);
|
|
|
|
test3 = JSObject.create(ctx.locals.Test2, ('Flier', 'Lu'), { 'email': 'flier.lu@gmail.com' })
|
|
|
|
self.assertEqual("flier.lu@gmail.com", test3.email);
|
|
|
|
def testJSError(self):
|
|
with JSContext() as ctxt:
|
|
try:
|
|
ctxt.eval('throw "test"')
|
|
self.fail()
|
|
except:
|
|
self.assertTrue(JSError, sys.exc_info()[0])
|
|
|
|
def testErrorInfo(self):
|
|
with JSContext() as ctxt:
|
|
with JSEngine() as engine:
|
|
try:
|
|
engine.compile("""
|
|
function hello()
|
|
{
|
|
throw Error("hello world");
|
|
}
|
|
|
|
hello();""", "test", 10, 10).run()
|
|
self.fail()
|
|
except JSError as e:
|
|
self.assertTrue(str(e).startswith('JSError: Error: hello world ( test @ 14 : 34 ) ->'))
|
|
self.assertEqual("Error", e.name)
|
|
self.assertEqual("hello world", e.message)
|
|
self.assertEqual("test", e.scriptName)
|
|
self.assertEqual(14, e.lineNum)
|
|
self.assertEqual(102, e.startPos)
|
|
self.assertEqual(103, e.endPos)
|
|
self.assertEqual(34, e.startCol)
|
|
self.assertEqual(35, e.endCol)
|
|
self.assertEqual('throw Error("hello world");', e.sourceLine.strip())
|
|
self.assertEqual('Error: hello world\n' +
|
|
' at Error (<anonymous>)\n' +
|
|
' at hello (test:14:35)\n' +
|
|
' at test:17:25', e.stackTrace)
|
|
|
|
def testParseStack(self):
|
|
self.assertEqual([
|
|
('Error', 'unknown source', None, None),
|
|
('test', 'native', None, None),
|
|
('<anonymous>', 'test0', 3, 5),
|
|
('f', 'test1', 2, 19),
|
|
('g', 'test2', 1, 15),
|
|
(None, 'test3', 1, None),
|
|
(None, 'test3', 1, 1),
|
|
], JSError.parse_stack("""Error: err
|
|
at Error (unknown source)
|
|
at test (native)
|
|
at new <anonymous> (test0:3:5)
|
|
at f (test1:2:19)
|
|
at g (test2:1:15)
|
|
at test3:1
|
|
at test3:1:1"""))
|
|
|
|
def testStackTrace(self):
|
|
class Global(JSClass):
|
|
def GetCurrentStackTrace(self, limit):
|
|
return JSStackTrace.GetCurrentStackTrace(4, JSStackTrace.Options.Detailed)
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
st = ctxt.eval("""
|
|
function a()
|
|
{
|
|
return GetCurrentStackTrace(10);
|
|
}
|
|
function b()
|
|
{
|
|
return eval("a()");
|
|
}
|
|
function c()
|
|
{
|
|
return new b();
|
|
}
|
|
c();""", "test")
|
|
|
|
self.assertEqual(4, len(st))
|
|
self.assertEqual("\tat a (test:4:28)\n\tat (eval)\n\tat b (test:8:28)\n\tat c (test:12:28)\n", str(st))
|
|
self.assertEqual("test.a (4:28)\n. (1:1) eval\ntest.b (8:28) constructor\ntest.c (12:28)",
|
|
"\n".join(["%s.%s (%d:%d)%s%s" % (
|
|
f.scriptName, f.funcName, f.lineNum, f.column,
|
|
' eval' if f.isEval else '',
|
|
' constructor' if f.isConstructor else '') for f in st]))
|
|
|
|
def testPythonException(self):
|
|
class Global(JSClass):
|
|
def raiseException(self):
|
|
raise RuntimeError("Hello")
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
r = ctxt.eval("""
|
|
msg ="";
|
|
try
|
|
{
|
|
this.raiseException()
|
|
}
|
|
catch(e)
|
|
{
|
|
msg += "catch " + e + ";";
|
|
}
|
|
finally
|
|
{
|
|
msg += "finally";
|
|
}""")
|
|
self.assertEqual("catch Error: Hello;finally", str(ctxt.locals.msg))
|
|
|
|
def testExceptionMapping(self):
|
|
class TestException(Exception):
|
|
pass
|
|
|
|
class Global(JSClass):
|
|
def raiseIndexError(self):
|
|
return [1, 2, 3][5]
|
|
|
|
def raiseAttributeError(self):
|
|
None.hello()
|
|
|
|
def raiseSyntaxError(self):
|
|
eval("???")
|
|
|
|
def raiseTypeError(self):
|
|
int(sys)
|
|
|
|
def raiseNotImplementedError(self):
|
|
raise NotImplementedError("Not support")
|
|
|
|
def raiseExceptions(self):
|
|
raise TestException()
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
ctxt.eval("try { this.raiseIndexError(); } catch (e) { msg = e; }")
|
|
|
|
self.assertEqual("RangeError: list index out of range", str(ctxt.locals.msg))
|
|
|
|
ctxt.eval("try { this.raiseAttributeError(); } catch (e) { msg = e; }")
|
|
|
|
self.assertEqual("ReferenceError: 'NoneType' object has no attribute 'hello'", str(ctxt.locals.msg))
|
|
|
|
ctxt.eval("try { this.raiseSyntaxError(); } catch (e) { msg = e; }")
|
|
|
|
self.assertEqual("SyntaxError: invalid syntax", str(ctxt.locals.msg))
|
|
|
|
ctxt.eval("try { this.raiseTypeError(); } catch (e) { msg = e; }")
|
|
|
|
self.assertEqual("TypeError: int() argument must be a string or a number, not 'module'", str(ctxt.locals.msg))
|
|
|
|
ctxt.eval("try { this.raiseNotImplementedError(); } catch (e) { msg = e; }")
|
|
|
|
self.assertEqual("Error: Not support", str(ctxt.locals.msg))
|
|
|
|
self.assertRaises(TestException, ctxt.eval, "this.raiseExceptions();")
|
|
|
|
def testArray(self):
|
|
with JSContext() as ctxt:
|
|
array = ctxt.eval("""
|
|
var array = new Array();
|
|
|
|
for (i=0; i<10; i++)
|
|
{
|
|
array[i] = 10-i;
|
|
}
|
|
|
|
array;
|
|
""")
|
|
|
|
self.assertTrue(isinstance(array, _PyV8.JSArray))
|
|
self.assertEqual(10, len(array))
|
|
|
|
self.assertTrue(5 in array)
|
|
self.assertFalse(15 in array)
|
|
|
|
self.assertEqual(10, len(array))
|
|
|
|
for i in range(10):
|
|
self.assertEqual(10-i, array[i])
|
|
|
|
array[5] = 0
|
|
|
|
self.assertEqual(0, array[5])
|
|
|
|
del array[5]
|
|
|
|
self.assertEqual(None, array[5])
|
|
|
|
# array [10, 9, 8, 7, 6, None, 4, 3, 2, 1]
|
|
# array[4:7] 4^^^^^^^^^7
|
|
# array[-3:-1] -3^^^^^^-1
|
|
# array[0:0] []
|
|
|
|
self.assertEqual([6, None, 4], array[4:7])
|
|
self.assertEqual([3, 2], array[-3:-1])
|
|
self.assertEqual([], array[0:0])
|
|
|
|
array[1:3] = [9, 9, 9]
|
|
|
|
self.assertEqual([10, 9, 9, 9, 7, 6, None, 4, 3, 2, 1], list(array))
|
|
|
|
array[5:8] = [8, 8]
|
|
|
|
self.assertEqual([10, 9, 9, 9, 7, 8, 8, 3, 2, 1], list(array))
|
|
|
|
del array[1:4]
|
|
|
|
self.assertEqual([10, 7, 8, 8, 3, 2, 1], list(array))
|
|
|
|
ctxt.locals.array1 = JSArray(5)
|
|
ctxt.locals.array2 = JSArray([1, 2, 3, 4, 5])
|
|
|
|
for i in range(len(ctxt.locals.array2)):
|
|
ctxt.locals.array1[i] = ctxt.locals.array2[i] * 10
|
|
|
|
ctxt.eval("""
|
|
var sum = 0;
|
|
|
|
for (i=0; i<array1.length; i++)
|
|
sum += array1[i]
|
|
|
|
for (i=0; i<array2.length; i++)
|
|
sum += array2[i]
|
|
""")
|
|
|
|
self.assertEqual(165, ctxt.locals.sum)
|
|
|
|
ctxt.locals.array3 = [1, 2, 3, 4, 5]
|
|
self.assertTrue(ctxt.eval('array3[1] === 2'))
|
|
self.assertTrue(ctxt.eval('array3[9] === undefined'))
|
|
|
|
args = [
|
|
["a = Array(7); for(i=0; i<a.length; i++) a[i] = i; a[3] = undefined; a[a.length-1]; a", "0,1,2,,4,5,6", [0, 1, 2, None, 4, 5, 6]],
|
|
["a = Array(7); for(i=0; i<a.length - 1; i++) a[i] = i; a[a.length-1]; a", "0,1,2,3,4,5,", [0, 1, 2, 3, 4, 5, None]],
|
|
["a = Array(7); for(i=1; i<a.length; i++) a[i] = i; a[a.length-1]; a", ",1,2,3,4,5,6", [None, 1, 2, 3, 4, 5, 6]]
|
|
]
|
|
|
|
for arg in args:
|
|
array = ctxt.eval(arg[0])
|
|
|
|
self.assertEqual(arg[1], str(array))
|
|
self.assertEqual(arg[2], [array[i] for i in range(len(array))])
|
|
|
|
self.assertEqual(3, ctxt.eval("(function (arr) { return arr.length; })")(JSArray([1, 2, 3])))
|
|
self.assertEqual(2, ctxt.eval("(function (arr, idx) { return arr[idx]; })")(JSArray([1, 2, 3]), 1))
|
|
self.assertEqual('[object Array]', ctxt.eval("(function (arr) { return Object.prototype.toString.call(arr); })")(JSArray([1, 2, 3])))
|
|
self.assertEqual('[object Array]', ctxt.eval("(function (arr) { return Object.prototype.toString.call(arr); })")(JSArray((1, 2, 3))))
|
|
self.assertEqual('[object Array]', ctxt.eval("(function (arr) { return Object.prototype.toString.call(arr); })")(JSArray(list(range(3)))))
|
|
|
|
[x for x in JSArray([1,2,3])]
|
|
|
|
def testMultiDimArray(self):
|
|
with JSContext() as ctxt:
|
|
ret = ctxt.eval("""
|
|
({
|
|
'test': function(){
|
|
return [
|
|
[ 1, 'abla' ],
|
|
[ 2, 'ajkss' ],
|
|
]
|
|
}
|
|
})
|
|
""").test()
|
|
|
|
self.assertEqual([[1, 'abla'], [2, 'ajkss']], convert(ret))
|
|
|
|
def testLazyConstructor(self):
|
|
class Globals(JSClass):
|
|
def __init__(self):
|
|
self.array=JSArray([1,2,3])
|
|
|
|
with JSContext(Globals()) as ctxt:
|
|
self.assertEqual(2, ctxt.eval("""array[1]"""))
|
|
|
|
def testForEach(self):
|
|
class NamedClass(object):
|
|
foo = 1
|
|
|
|
def __init__(self):
|
|
self.bar = 2
|
|
|
|
@property
|
|
def foobar(self):
|
|
return self.foo + self.bar
|
|
|
|
def gen(x):
|
|
for i in range(x):
|
|
yield i
|
|
|
|
with JSContext() as ctxt:
|
|
func = ctxt.eval("""(function (k) {
|
|
var result = [];
|
|
for (var prop in k) {
|
|
result.push(prop);
|
|
}
|
|
return result;
|
|
})""")
|
|
|
|
self.assertTrue(set(["bar", "foo", "foobar"]).issubset(set(func(NamedClass()))))
|
|
self.assertEqual(["0", "1", "2"], list(func([1, 2, 3])))
|
|
self.assertEqual(["0", "1", "2"], list(func((1, 2, 3))))
|
|
self.assertEqual(["1", "2", "3"], list(func({1:1, 2:2, 3:3})))
|
|
|
|
self.assertEqual(["0", "1", "2"], list(func(gen(3))))
|
|
|
|
def testDict(self):
|
|
with JSContext() as ctxt:
|
|
obj = ctxt.eval("var r = { 'a' : 1, 'b' : 2 }; r")
|
|
|
|
self.assertEqual(1, obj.a)
|
|
self.assertEqual(2, obj.b)
|
|
|
|
self.assertEqual({ 'a' : 1, 'b' : 2 }, dict(obj))
|
|
|
|
self.assertEqual({ 'a': 1,
|
|
'b': [1, 2, 3],
|
|
'c': { 'str' : 'goofy',
|
|
'float' : 1.234,
|
|
'obj' : { 'name': 'john doe' }},
|
|
'd': True,
|
|
'e': None },
|
|
convert(ctxt.eval("""var x =
|
|
{ a: 1,
|
|
b: [1, 2, 3],
|
|
c: { str: 'goofy',
|
|
float: 1.234,
|
|
obj: { name: 'john doe' }},
|
|
d: true,
|
|
e: null }; x""")))
|
|
|
|
def testDate(self):
|
|
with JSContext() as ctxt:
|
|
now1 = ctxt.eval("new Date();")
|
|
|
|
self.assertTrue(now1)
|
|
|
|
now2 = datetime.utcnow()
|
|
|
|
delta = now2 - now1 if now2 > now1 else now1 - now2
|
|
|
|
self.assertTrue(delta < timedelta(seconds=1))
|
|
|
|
func = ctxt.eval("(function (d) { return d.toString(); })")
|
|
|
|
now = datetime.now()
|
|
|
|
self.assertTrue(str(func(now)).startswith(now.strftime("%a %b %d %Y %H:%M:%S")))
|
|
|
|
def testUnicode(self):
|
|
with JSContext() as ctxt:
|
|
self.assertEqual("人", toUnicodeString(ctxt.eval("\"人\"")))
|
|
self.assertEqual("é", toUnicodeString(ctxt.eval("\"é\"")))
|
|
|
|
func = ctxt.eval("(function (msg) { return msg.length; })")
|
|
|
|
self.assertEqual(2, func("测试"))
|
|
|
|
def testClassicStyleObject(self):
|
|
class FileSystemWarpper:
|
|
@property
|
|
def cwd(self):
|
|
return os.getcwd()
|
|
|
|
class Global:
|
|
@property
|
|
def fs(self):
|
|
return FileSystemWarpper()
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
self.assertEqual(os.getcwd(), ctxt.eval("fs.cwd"))
|
|
|
|
def testRefCount(self):
|
|
count = sys.getrefcount(None)
|
|
|
|
class Global(JSClass):
|
|
pass
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
ctxt.eval("""
|
|
var none = null;
|
|
""")
|
|
|
|
self.assertEqual(count+1, sys.getrefcount(None))
|
|
|
|
ctxt.eval("""
|
|
var none = null;
|
|
""")
|
|
|
|
self.assertEqual(count+1, sys.getrefcount(None))
|
|
|
|
def testProperty(self):
|
|
class Global(JSClass):
|
|
def __init__(self, name):
|
|
self._name = name
|
|
def getname(self):
|
|
return self._name
|
|
def setname(self, name):
|
|
self._name = name
|
|
def delname(self):
|
|
self._name = 'deleted'
|
|
|
|
name = property(getname, setname, delname)
|
|
|
|
g = Global('world')
|
|
|
|
with JSContext(g) as ctxt:
|
|
self.assertEqual('world', ctxt.eval("name"))
|
|
self.assertEqual('flier', ctxt.eval("this.name = 'flier';"))
|
|
self.assertEqual('flier', ctxt.eval("name"))
|
|
self.assertTrue(ctxt.eval("delete name"))
|
|
###
|
|
# FIXME replace the global object with Python object
|
|
#
|
|
#self.assertEqual('deleted', ctxt.eval("name"))
|
|
#ctxt.eval("__defineGetter__('name', function() { return 'fixed'; });")
|
|
#self.assertEqual('fixed', ctxt.eval("name"))
|
|
|
|
def testGetterAndSetter(self):
|
|
class Global(JSClass):
|
|
def __init__(self, testval):
|
|
self.testval = testval
|
|
|
|
with JSContext(Global("Test Value A")) as ctxt:
|
|
self.assertEqual("Test Value A", ctxt.locals.testval)
|
|
ctxt.eval("""
|
|
this.__defineGetter__("test", function() {
|
|
return this.testval;
|
|
});
|
|
this.__defineSetter__("test", function(val) {
|
|
this.testval = val;
|
|
});
|
|
""")
|
|
self.assertEqual("Test Value A", ctxt.locals.test)
|
|
|
|
ctxt.eval("test = 'Test Value B';")
|
|
|
|
self.assertEqual("Test Value B", ctxt.locals.test)
|
|
|
|
def testDestructor(self):
|
|
import gc
|
|
|
|
owner = self
|
|
owner.deleted = False
|
|
|
|
class Hello(object):
|
|
def say(self):
|
|
pass
|
|
|
|
def __del__(self):
|
|
owner.deleted = True
|
|
|
|
def test():
|
|
with JSContext() as ctxt:
|
|
fn = ctxt.eval("(function (obj) { obj.say(); })")
|
|
|
|
obj = Hello()
|
|
|
|
self.assertEqual(2, sys.getrefcount(obj))
|
|
|
|
fn(obj)
|
|
|
|
self.assertEqual(4, sys.getrefcount(obj))
|
|
|
|
del obj
|
|
|
|
test()
|
|
|
|
self.assertFalse(owner.deleted)
|
|
|
|
JSEngine.collect()
|
|
gc.collect()
|
|
|
|
self.assertTrue(owner.deleted)
|
|
|
|
def testNullInString(self):
|
|
with JSContext() as ctxt:
|
|
fn = ctxt.eval("(function (s) { return s; })")
|
|
|
|
self.assertEqual("hello \0 world", fn("hello \0 world"))
|
|
|
|
def testLivingObjectCache(self):
|
|
class Global(JSClass):
|
|
i = 1
|
|
b = True
|
|
o = object()
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
self.assertTrue(ctxt.eval("i == i"))
|
|
self.assertTrue(ctxt.eval("b == b"))
|
|
self.assertTrue(ctxt.eval("o == o"))
|
|
|
|
def testNamedSetter(self):
|
|
class Obj(JSClass):
|
|
@property
|
|
def p(self):
|
|
return self._p
|
|
|
|
@p.setter
|
|
def p(self, value):
|
|
self._p = value
|
|
|
|
class Global(JSClass):
|
|
def __init__(self):
|
|
self.obj = Obj()
|
|
self.d = {}
|
|
self.p = None
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
ctxt.eval("""
|
|
x = obj;
|
|
x.y = 10;
|
|
x.p = 10;
|
|
d.y = 10;
|
|
""")
|
|
self.assertEqual(10, ctxt.eval("obj.y"))
|
|
self.assertEqual(10, ctxt.eval("obj.p"))
|
|
self.assertEqual(10, ctxt.locals.d['y'])
|
|
|
|
def testWatch(self):
|
|
class Obj(JSClass):
|
|
def __init__(self):
|
|
self.p = 1
|
|
|
|
class Global(JSClass):
|
|
def __init__(self):
|
|
self.o = Obj()
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
ctxt.eval("""
|
|
o.watch("p", function (id, oldval, newval) {
|
|
return oldval + newval;
|
|
});
|
|
""")
|
|
|
|
self.assertEqual(1, ctxt.eval("o.p"))
|
|
|
|
ctxt.eval("o.p = 2;")
|
|
|
|
self.assertEqual(3, ctxt.eval("o.p"))
|
|
|
|
ctxt.eval("delete o.p;")
|
|
|
|
self.assertEqual(None, ctxt.eval("o.p"))
|
|
|
|
ctxt.eval("o.p = 2;")
|
|
|
|
self.assertEqual(2, ctxt.eval("o.p"))
|
|
|
|
ctxt.eval("o.unwatch('p');")
|
|
|
|
ctxt.eval("o.p = 1;")
|
|
|
|
self.assertEqual(1, ctxt.eval("o.p"))
|
|
|
|
def testReferenceError(self):
|
|
class Global(JSClass):
|
|
def __init__(self):
|
|
self.s = self
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
self.assertRaises(ReferenceError, ctxt.eval, 'x')
|
|
|
|
self.assertTrue(ctxt.eval("typeof(x) === 'undefined'"))
|
|
|
|
self.assertTrue(ctxt.eval("typeof(String) === 'function'"))
|
|
|
|
self.assertTrue(ctxt.eval("typeof(s.String) === 'undefined'"))
|
|
|
|
self.assertTrue(ctxt.eval("typeof(s.z) === 'undefined'"))
|
|
|
|
def testRaiseExceptionInGetter(self):
|
|
class Document(JSClass):
|
|
def __getattr__(self, name):
|
|
if name == 'y':
|
|
raise TypeError()
|
|
|
|
return JSClass.__getattr__(self, name)
|
|
|
|
class Global(JSClass):
|
|
def __init__(self):
|
|
self.document = Document()
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
self.assertEqual(None, ctxt.eval('document.x'))
|
|
self.assertRaises(TypeError, ctxt.eval, 'document.y')
|
|
|
|
class TestMultithread(unittest.TestCase):
|
|
def testLocker(self):
|
|
self.assertFalse(JSLocker.active)
|
|
self.assertFalse(JSLocker.locked)
|
|
|
|
with JSLocker() as outter_locker:
|
|
self.assertTrue(JSLocker.active)
|
|
self.assertTrue(JSLocker.locked)
|
|
|
|
self.assertTrue(outter_locker)
|
|
|
|
with JSLocker() as inner_locker:
|
|
self.assertTrue(JSLocker.locked)
|
|
|
|
self.assertTrue(outter_locker)
|
|
self.assertTrue(inner_locker)
|
|
|
|
with JSUnlocker() as unlocker:
|
|
self.assertFalse(JSLocker.locked)
|
|
|
|
self.assertTrue(outter_locker)
|
|
self.assertTrue(inner_locker)
|
|
|
|
self.assertTrue(JSLocker.locked)
|
|
|
|
self.assertTrue(JSLocker.active)
|
|
self.assertFalse(JSLocker.locked)
|
|
|
|
locker = JSLocker()
|
|
|
|
with JSContext():
|
|
self.assertRaises(RuntimeError, locker.__enter__)
|
|
self.assertRaises(RuntimeError, locker.__exit__, None, None, None)
|
|
|
|
del locker
|
|
|
|
def testMultiPythonThread(self):
|
|
import time, threading
|
|
|
|
class Global:
|
|
count = 0
|
|
started = threading.Event()
|
|
finished = threading.Semaphore(0)
|
|
|
|
def sleep(self, ms):
|
|
time.sleep(ms / 1000.0)
|
|
|
|
self.count += 1
|
|
|
|
g = Global()
|
|
|
|
def run():
|
|
with JSContext(g) as ctxt:
|
|
ctxt.eval("""
|
|
started.wait();
|
|
|
|
for (i=0; i<10; i++)
|
|
{
|
|
sleep(100);
|
|
}
|
|
|
|
finished.release();
|
|
""")
|
|
|
|
threading.Thread(target=run).start()
|
|
|
|
now = time.time()
|
|
|
|
self.assertEqual(0, g.count)
|
|
|
|
g.started.set()
|
|
g.finished.acquire()
|
|
|
|
self.assertEqual(10, g.count)
|
|
|
|
self.assertTrue((time.time() - now) >= 1)
|
|
|
|
def testMultiJavascriptThread(self):
|
|
import time, threading
|
|
|
|
class Global:
|
|
result = []
|
|
|
|
def add(self, value):
|
|
with JSUnlocker():
|
|
time.sleep(0.1)
|
|
|
|
self.result.append(value)
|
|
|
|
g = Global()
|
|
|
|
def run():
|
|
with JSContext(g) as ctxt:
|
|
ctxt.eval("""
|
|
for (i=0; i<10; i++)
|
|
add(i);
|
|
""")
|
|
|
|
threads = [threading.Thread(target=run), threading.Thread(target=run)]
|
|
|
|
with JSLocker():
|
|
for t in threads: t.start()
|
|
|
|
for t in threads: t.join()
|
|
|
|
self.assertEqual(20, len(g.result))
|
|
|
|
def _testPreemptionJavascriptThreads(self):
|
|
import time, threading
|
|
|
|
class Global:
|
|
result = []
|
|
|
|
def add(self, value):
|
|
# we use preemption scheduler to switch between threads
|
|
# so, just comment the JSUnlocker
|
|
#
|
|
# with JSUnlocker() as unlocker:
|
|
time.sleep(0.1)
|
|
|
|
self.result.append(value)
|
|
|
|
g = Global()
|
|
|
|
def run():
|
|
with JSContext(g) as ctxt:
|
|
ctxt.eval("""
|
|
for (i=0; i<10; i++)
|
|
add(i);
|
|
""")
|
|
|
|
threads = [threading.Thread(target=run), threading.Thread(target=run)]
|
|
|
|
with JSLocker() as locker:
|
|
JSLocker.startPreemption(100)
|
|
|
|
for t in threads: t.start()
|
|
|
|
for t in threads: t.join()
|
|
|
|
self.assertEqual(20, len(g.result))
|
|
|
|
class TestEngine(unittest.TestCase):
|
|
def testClassProperties(self):
|
|
with JSContext() as ctxt:
|
|
self.assertTrue(str(JSEngine.version).startswith("3."))
|
|
self.assertFalse(JSEngine.dead)
|
|
|
|
def testCompile(self):
|
|
with JSContext() as ctxt:
|
|
with JSEngine() as engine:
|
|
s = engine.compile("1+2")
|
|
|
|
self.assertTrue(isinstance(s, _PyV8.JSScript))
|
|
|
|
self.assertEqual("1+2", s.source)
|
|
self.assertEqual(3, int(s.run()))
|
|
|
|
self.assertRaises(SyntaxError, engine.compile, "1+")
|
|
|
|
def testPrecompile(self):
|
|
with JSContext() as ctxt:
|
|
with JSEngine() as engine:
|
|
data = engine.precompile("1+2")
|
|
|
|
self.assertTrue(data)
|
|
self.assertEqual(28, len(data))
|
|
|
|
s = engine.compile("1+2", precompiled=data)
|
|
|
|
self.assertTrue(isinstance(s, _PyV8.JSScript))
|
|
|
|
self.assertEqual("1+2", s.source)
|
|
self.assertEqual(3, int(s.run()))
|
|
|
|
self.assertRaises(SyntaxError, engine.precompile, "1+")
|
|
|
|
def testUnicodeSource(self):
|
|
class Global(JSClass):
|
|
var = '测试'
|
|
|
|
def __getattr__(self, name):
|
|
if (name if is_py3k else name.decode('utf-8')) == '变量':
|
|
return self.var
|
|
|
|
return JSClass.__getattr__(self, name)
|
|
|
|
g = Global()
|
|
|
|
with JSContext(g) as ctxt:
|
|
with JSEngine() as engine:
|
|
src = """
|
|
function 函数() { return 变量.length; }
|
|
|
|
函数();
|
|
|
|
var func = function () {};
|
|
"""
|
|
|
|
data = engine.precompile(src)
|
|
|
|
self.assertTrue(data)
|
|
self.assertEqual(68, len(data))
|
|
|
|
s = engine.compile(src, precompiled=data)
|
|
|
|
self.assertTrue(isinstance(s, _PyV8.JSScript))
|
|
|
|
self.assertEqual(toNativeString(src), s.source)
|
|
self.assertEqual(2, s.run())
|
|
|
|
func_name = toNativeString('函数')
|
|
|
|
self.assertTrue(hasattr(ctxt.locals, func_name))
|
|
|
|
func = getattr(ctxt.locals, func_name)
|
|
|
|
self.assertTrue(isinstance(func, _PyV8.JSFunction))
|
|
|
|
self.assertEqual(func_name, func.name)
|
|
self.assertEqual("", func.resname)
|
|
self.assertEqual(1, func.linenum)
|
|
self.assertEqual(0, func.lineoff)
|
|
self.assertEqual(0, func.coloff)
|
|
|
|
var_name = toNativeString('变量')
|
|
|
|
setattr(ctxt.locals, var_name, '测试长字符串')
|
|
|
|
self.assertEqual(6, func())
|
|
|
|
self.assertEqual("func", ctxt.locals.func.inferredname)
|
|
|
|
def testExtension(self):
|
|
extSrc = """function hello(name) { return "hello " + name + " from javascript"; }"""
|
|
extJs = JSExtension("hello/javascript", extSrc)
|
|
|
|
self.assertTrue(extJs)
|
|
self.assertEqual("hello/javascript", extJs.name)
|
|
self.assertEqual(extSrc, extJs.source)
|
|
self.assertFalse(extJs.autoEnable)
|
|
self.assertTrue(extJs.registered)
|
|
|
|
TestEngine.extJs = extJs
|
|
|
|
with JSContext(extensions=['hello/javascript']) as ctxt:
|
|
self.assertEqual("hello flier from javascript", ctxt.eval("hello('flier')"))
|
|
|
|
# test the auto enable property
|
|
|
|
with JSContext() as ctxt:
|
|
self.assertRaises(ReferenceError, ctxt.eval, "hello('flier')")
|
|
|
|
extJs.autoEnable = True
|
|
self.assertTrue(extJs.autoEnable)
|
|
|
|
with JSContext() as ctxt:
|
|
self.assertEqual("hello flier from javascript", ctxt.eval("hello('flier')"))
|
|
|
|
extJs.autoEnable = False
|
|
self.assertFalse(extJs.autoEnable)
|
|
|
|
with JSContext() as ctxt:
|
|
self.assertRaises(ReferenceError, ctxt.eval, "hello('flier')")
|
|
|
|
extUnicodeSrc = """function helloW(name) { return "hello " + name + " from javascript"; }"""
|
|
extUnicodeJs = JSExtension("helloW/javascript", extUnicodeSrc)
|
|
|
|
self.assertTrue(extUnicodeJs)
|
|
self.assertEqual("helloW/javascript", extUnicodeJs.name)
|
|
self.assertEqual(toNativeString(extUnicodeSrc), extUnicodeJs.source)
|
|
self.assertFalse(extUnicodeJs.autoEnable)
|
|
self.assertTrue(extUnicodeJs.registered)
|
|
|
|
TestEngine.extUnicodeJs = extUnicodeJs
|
|
|
|
with JSContext(extensions=['helloW/javascript']) as ctxt:
|
|
self.assertEqual("hello flier from javascript", ctxt.eval("helloW('flier')"))
|
|
|
|
ret = ctxt.eval("helloW('世界')")
|
|
|
|
self.assertEqual("hello 世界 from javascript", ret if is_py3k else ret.decode('UTF-8'))
|
|
|
|
def testNativeExtension(self):
|
|
extSrc = "native function hello();"
|
|
extPy = JSExtension("hello/python", extSrc, lambda func: lambda name: "hello " + name + " from python", register=False)
|
|
self.assertTrue(extPy)
|
|
self.assertEqual("hello/python", extPy.name)
|
|
self.assertEqual(extSrc, extPy.source)
|
|
self.assertFalse(extPy.autoEnable)
|
|
self.assertFalse(extPy.registered)
|
|
extPy.register()
|
|
self.assertTrue(extPy.registered)
|
|
|
|
TestEngine.extPy = extPy
|
|
|
|
with JSContext(extensions=['hello/python']) as ctxt:
|
|
self.assertEqual("hello flier from python", ctxt.eval("hello('flier')"))
|
|
|
|
def _testSerialize(self):
|
|
data = None
|
|
|
|
self.assertFalse(JSContext.entered)
|
|
|
|
with JSContext() as ctxt:
|
|
self.assertTrue(JSContext.entered)
|
|
|
|
#ctxt.eval("function hello(name) { return 'hello ' + name; }")
|
|
|
|
data = JSEngine.serialize()
|
|
|
|
self.assertTrue(data)
|
|
self.assertTrue(len(data) > 0)
|
|
|
|
self.assertFalse(JSContext.entered)
|
|
|
|
#JSEngine.deserialize()
|
|
|
|
self.assertTrue(JSContext.entered)
|
|
|
|
self.assertEqual('hello flier', JSContext.current.eval("hello('flier');"))
|
|
|
|
def testEval(self):
|
|
with JSContext() as ctxt:
|
|
self.assertEqual(3, int(ctxt.eval("1+2")))
|
|
|
|
def testGlobal(self):
|
|
class Global(JSClass):
|
|
version = "1.0"
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
vars = ctxt.locals
|
|
|
|
# getter
|
|
self.assertEqual(Global.version, str(vars.version))
|
|
self.assertEqual(Global.version, str(ctxt.eval("version")))
|
|
|
|
self.assertRaises(ReferenceError, ctxt.eval, "nonexists")
|
|
|
|
# setter
|
|
self.assertEqual(2.0, float(ctxt.eval("version = 2.0")))
|
|
|
|
self.assertEqual(2.0, float(vars.version))
|
|
|
|
def testThis(self):
|
|
class Global(JSClass):
|
|
version = 1.0
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
self.assertEqual("[object Global]", str(ctxt.eval("this")))
|
|
|
|
self.assertEqual(1.0, float(ctxt.eval("this.version")))
|
|
|
|
def testObjectBuildInMethods(self):
|
|
class Global(JSClass):
|
|
version = 1.0
|
|
|
|
with JSContext(Global()) as ctxt:
|
|
self.assertEqual("[object Global]", str(ctxt.eval("this.toString()")))
|
|
self.assertEqual("[object Global]", str(ctxt.eval("this.toLocaleString()")))
|
|
self.assertEqual(Global.version, float(ctxt.eval("this.valueOf()").version))
|
|
|
|
self.assertTrue(bool(ctxt.eval("this.hasOwnProperty(\"version\")")))
|
|
|
|
self.assertFalse(ctxt.eval("this.hasOwnProperty(\"nonexistent\")"))
|
|
|
|
def testPythonWrapper(self):
|
|
class Global(JSClass):
|
|
s = [1, 2, 3]
|
|
d = {'a': {'b': 'c'}, 'd': ['e', 'f']}
|
|
|
|
g = Global()
|
|
|
|
with JSContext(g) as ctxt:
|
|
ctxt.eval("""
|
|
s[2] = s[1] + 2;
|
|
s[0] = s[1];
|
|
delete s[1];
|
|
""")
|
|
self.assertEqual([2, 4], g.s)
|
|
self.assertEqual('c', ctxt.eval("d.a.b"))
|
|
self.assertEqual(['e', 'f'], ctxt.eval("d.d"))
|
|
ctxt.eval("""
|
|
d.a.q = 4
|
|
delete d.d
|
|
""")
|
|
self.assertEqual(4, g.d['a']['q'])
|
|
self.assertEqual(None, ctxt.eval("d.d"))
|
|
|
|
def _testMemoryAllocationCallback(self):
|
|
alloc = {}
|
|
|
|
def callback(space, action, size):
|
|
alloc[(space, action)] = alloc.setdefault((space, action), 0) + size
|
|
|
|
JSEngine.setMemoryAllocationCallback(callback)
|
|
|
|
with JSContext() as ctxt:
|
|
self.assertFalse((JSObjectSpace.Code, JSAllocationAction.alloc) in alloc)
|
|
|
|
ctxt.eval("var o = new Array(1000);")
|
|
|
|
self.assertTrue((JSObjectSpace.Code, JSAllocationAction.alloc) in alloc)
|
|
|
|
JSEngine.setMemoryAllocationCallback(None)
|
|
|
|
class TestDebug(unittest.TestCase):
|
|
def setUp(self):
|
|
self.engine = JSEngine()
|
|
|
|
def tearDown(self):
|
|
del self.engine
|
|
|
|
events = []
|
|
|
|
def processDebugEvent(self, event):
|
|
try:
|
|
logging.debug("receive debug event: %s", repr(event))
|
|
|
|
self.events.append(repr(event))
|
|
except:
|
|
logging.error("fail to process debug event")
|
|
logging.debug(traceback.extract_stack())
|
|
|
|
def testEventDispatch(self):
|
|
debugger = JSDebugger()
|
|
|
|
self.assertTrue(not debugger.enabled)
|
|
|
|
debugger.onBreak = lambda evt: self.processDebugEvent(evt)
|
|
debugger.onException = lambda evt: self.processDebugEvent(evt)
|
|
debugger.onNewFunction = lambda evt: self.processDebugEvent(evt)
|
|
debugger.onBeforeCompile = lambda evt: self.processDebugEvent(evt)
|
|
debugger.onAfterCompile = lambda evt: self.processDebugEvent(evt)
|
|
|
|
with JSContext() as ctxt:
|
|
debugger.enabled = True
|
|
|
|
self.assertEqual(3, int(ctxt.eval("function test() { text = \"1+2\"; return eval(text) } test()")))
|
|
|
|
debugger.enabled = False
|
|
|
|
self.assertRaises(JSError, JSContext.eval, ctxt, "throw 1")
|
|
|
|
self.assertTrue(not debugger.enabled)
|
|
|
|
self.assertEqual(4, len(self.events))
|
|
|
|
class TestProfile(unittest.TestCase):
|
|
def _testStart(self):
|
|
self.assertFalse(profiler.started)
|
|
|
|
profiler.start()
|
|
|
|
self.assertTrue(profiler.started)
|
|
|
|
profiler.stop()
|
|
|
|
self.assertFalse(profiler.started)
|
|
|
|
def _testResume(self):
|
|
self.assertTrue(profiler.paused)
|
|
|
|
self.assertEqual(profiler.Modules.cpu, profiler.modules)
|
|
|
|
profiler.resume()
|
|
|
|
profiler.resume(profiler.Modules.heap)
|
|
|
|
# TODO enable profiler with resume
|
|
#self.assertFalse(profiler.paused)
|
|
|
|
|
|
class TestAST(unittest.TestCase):
|
|
|
|
class Checker(object):
|
|
def __init__(self, testcase):
|
|
self.testcase = testcase
|
|
self.called = []
|
|
|
|
def __enter__(self):
|
|
self.ctxt = JSContext()
|
|
self.ctxt.enter()
|
|
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
self.ctxt.leave()
|
|
|
|
def __getattr__(self, name):
|
|
return getattr(self.testcase, name)
|
|
|
|
def test(self, script):
|
|
JSEngine().compile(script).visit(self)
|
|
|
|
return self.called
|
|
|
|
def onProgram(self, prog):
|
|
self.ast = prog.toAST()
|
|
self.json = json.loads(prog.toJSON())
|
|
|
|
for decl in prog.scope.declarations:
|
|
decl.visit(self)
|
|
|
|
for stmt in prog.body:
|
|
stmt.visit(self)
|
|
|
|
def onBlock(self, block):
|
|
for stmt in block.statements:
|
|
stmt.visit(self)
|
|
|
|
def onExpressionStatement(self, stmt):
|
|
stmt.expression.visit(self)
|
|
|
|
#print type(stmt.expression), stmt.expression
|
|
|
|
def testBlock(self):
|
|
class BlockChecker(TestAST.Checker):
|
|
def onBlock(self, stmt):
|
|
self.called.append('block')
|
|
|
|
self.assertEqual(AST.NodeType.Block, stmt.type)
|
|
|
|
self.assertTrue(stmt.initializerBlock)
|
|
self.assertFalse(stmt.anonymous)
|
|
|
|
target = stmt.breakTarget
|
|
self.assertTrue(target)
|
|
self.assertFalse(target.bound)
|
|
self.assertTrue(target.unused)
|
|
self.assertFalse(target.linked)
|
|
|
|
self.assertEqual(2, len(stmt.statements))
|
|
|
|
self.assertEqual(['%InitializeVarGlobal("i", 0);', '%InitializeVarGlobal("j", 0);'], [str(s) for s in stmt.statements])
|
|
|
|
with BlockChecker(self) as checker:
|
|
self.assertEqual(['block'], checker.test("var i, j;"))
|
|
self.assertEqual("""FUNC
|
|
. NAME ""
|
|
. INFERRED NAME ""
|
|
. DECLS
|
|
. . VAR "i"
|
|
. . VAR "j"
|
|
. BLOCK INIT
|
|
. . CALL RUNTIME InitializeVarGlobal
|
|
. . . LITERAL "i"
|
|
. . . LITERAL 0
|
|
. . CALL RUNTIME InitializeVarGlobal
|
|
. . . LITERAL "j"
|
|
. . . LITERAL 0
|
|
""", checker.ast)
|
|
self.assertEqual(['FunctionLiteral', {'name': ''},
|
|
['Declaration', {'mode': 'VAR'},
|
|
['Variable', {'name': 'i'}]
|
|
], ['Declaration', {'mode':'VAR'},
|
|
['Variable', {'name': 'j'}]
|
|
], ['Block',
|
|
['ExpressionStatement', ['CallRuntime', {'name': 'InitializeVarGlobal'},
|
|
['Literal', {'handle':'i'}],
|
|
['Literal', {'handle': 0}]]],
|
|
['ExpressionStatement', ['CallRuntime', {'name': 'InitializeVarGlobal'},
|
|
['Literal', {'handle': 'j'}],
|
|
['Literal', {'handle': 0}]]]
|
|
]
|
|
], checker.json)
|
|
|
|
def testIfStatement(self):
|
|
class IfStatementChecker(TestAST.Checker):
|
|
def onIfStatement(self, stmt):
|
|
self.called.append('if')
|
|
|
|
self.assertTrue(stmt)
|
|
self.assertEqual(AST.NodeType.IfStatement, stmt.type)
|
|
|
|
self.assertEqual(7, stmt.pos)
|
|
stmt.pos = 100
|
|
self.assertEqual(100, stmt.pos)
|
|
|
|
self.assertTrue(stmt.hasThenStatement)
|
|
self.assertTrue(stmt.hasElseStatement)
|
|
|
|
self.assertEqual("((value % 2) == 0)", str(stmt.condition))
|
|
self.assertEqual("{ s = \"even\"; }", str(stmt.thenStatement))
|
|
self.assertEqual("{ s = \"odd\"; }", str(stmt.elseStatement))
|
|
|
|
self.assertFalse(stmt.condition.isPropertyName)
|
|
|
|
with IfStatementChecker(self) as checker:
|
|
self.assertEqual(['if'], checker.test("var s; if (value % 2 == 0) { s = 'even'; } else { s = 'odd'; }"))
|
|
|
|
def testForStatement(self):
|
|
class ForStatementChecker(TestAST.Checker):
|
|
def onForStatement(self, stmt):
|
|
self.called.append('for')
|
|
|
|
self.assertEqual("{ j += i; }", str(stmt.body))
|
|
|
|
self.assertEqual("i = 0;", str(stmt.init))
|
|
self.assertEqual("(i < 10)", str(stmt.condition))
|
|
self.assertEqual("(i++);", str(stmt.nextStmt))
|
|
|
|
target = stmt.continueTarget
|
|
|
|
self.assertTrue(target)
|
|
self.assertFalse(target.bound)
|
|
self.assertTrue(target.unused)
|
|
self.assertFalse(target.linked)
|
|
self.assertFalse(stmt.fastLoop)
|
|
|
|
def onForInStatement(self, stmt):
|
|
self.called.append('forIn')
|
|
|
|
self.assertEqual("{ out += name; }", str(stmt.body))
|
|
|
|
self.assertEqual("name", str(stmt.each))
|
|
self.assertEqual("names", str(stmt.enumerable))
|
|
|
|
def onWhileStatement(self, stmt):
|
|
self.called.append('while')
|
|
|
|
self.assertEqual("{ i += 1; }", str(stmt.body))
|
|
|
|
self.assertEqual("(i < 10)", str(stmt.condition))
|
|
|
|
def onDoWhileStatement(self, stmt):
|
|
self.called.append('doWhile')
|
|
|
|
self.assertEqual("{ i += 1; }", str(stmt.body))
|
|
|
|
self.assertEqual("(i < 10)", str(stmt.condition))
|
|
self.assertEqual(281, stmt.conditionPos)
|
|
|
|
with ForStatementChecker(self) as checker:
|
|
self.assertEqual(['for', 'forIn', 'while', 'doWhile'], checker.test("""
|
|
var i, j;
|
|
|
|
for (i=0; i<10; i++) { j+=i; }
|
|
|
|
var names = new Array();
|
|
var out = '';
|
|
|
|
for (name in names) { out += name; }
|
|
|
|
while (i<10) { i += 1; }
|
|
|
|
do { i += 1; } while (i<10);
|
|
"""))
|
|
|
|
def testCallStatements(self):
|
|
class CallStatementChecker(TestAST.Checker):
|
|
def onVariableDeclaration(self, decl):
|
|
self.called.append('var')
|
|
|
|
var = decl.proxy
|
|
|
|
if var.name == 's':
|
|
self.assertEqual(AST.VarMode.var, decl.mode)
|
|
|
|
self.assertTrue(var.isValidLeftHandSide)
|
|
self.assertFalse(var.isArguments)
|
|
self.assertFalse(var.isThis)
|
|
|
|
def onFunctionDeclaration(self, decl):
|
|
self.called.append('func')
|
|
|
|
var = decl.proxy
|
|
|
|
if var.name == 'hello':
|
|
self.assertEqual(AST.VarMode.var, decl.mode)
|
|
self.assertTrue(decl.function)
|
|
self.assertEqual('(function hello(name) { s = ("Hello " + name); })', str(decl.function))
|
|
elif var.name == 'dog':
|
|
self.assertEqual(AST.VarMode.var, decl.mode)
|
|
self.assertTrue(decl.function)
|
|
self.assertEqual('(function dog(name) { (this).name = name; })', str(decl.function))
|
|
|
|
def onCall(self, expr):
|
|
self.called.append('call')
|
|
|
|
self.assertEqual("hello", str(expr.expression))
|
|
self.assertEqual(['"flier"'], [str(arg) for arg in expr.args])
|
|
self.assertEqual(159, expr.pos)
|
|
|
|
def onCallNew(self, expr):
|
|
self.called.append('callNew')
|
|
|
|
self.assertEqual("dog", str(expr.expression))
|
|
self.assertEqual(['"cat"'], [str(arg) for arg in expr.args])
|
|
self.assertEqual(191, expr.pos)
|
|
|
|
def onCallRuntime(self, expr):
|
|
self.called.append('callRuntime')
|
|
|
|
self.assertEqual("InitializeVarGlobal", expr.name)
|
|
self.assertEqual(['"s"', '0'], [str(arg) for arg in expr.args])
|
|
self.assertFalse(expr.isJsRuntime)
|
|
|
|
with CallStatementChecker(self) as checker:
|
|
self.assertEqual(['var', 'func', 'func', 'callRuntime', 'call', 'callNew'], checker.test("""
|
|
var s;
|
|
function hello(name) { s = "Hello " + name; }
|
|
function dog(name) { this.name = name; }
|
|
hello("flier");
|
|
new dog("cat");
|
|
"""))
|
|
|
|
def testTryStatements(self):
|
|
class TryStatementsChecker(TestAST.Checker):
|
|
def onThrow(self, expr):
|
|
self.called.append('try')
|
|
|
|
self.assertEqual('"abc"', str(expr.exception))
|
|
self.assertEqual(66, expr.pos)
|
|
|
|
def onTryCatchStatement(self, stmt):
|
|
self.called.append('catch')
|
|
|
|
self.assertEqual("{ throw \"abc\"; }", str(stmt.tryBlock))
|
|
#FIXME self.assertEqual([], stmt.targets)
|
|
|
|
stmt.tryBlock.visit(self)
|
|
|
|
self.assertEqual("err", str(stmt.variable.name))
|
|
self.assertEqual("{ s = err; }", str(stmt.catchBlock))
|
|
|
|
def onTryFinallyStatement(self, stmt):
|
|
self.called.append('finally')
|
|
|
|
self.assertEqual("{ throw \"abc\"; }", str(stmt.tryBlock))
|
|
#FIXME self.assertEqual([], stmt.targets)
|
|
|
|
self.assertEqual("{ s += \".\"; }", str(stmt.finallyBlock))
|
|
|
|
with TryStatementsChecker(self) as checker:
|
|
self.assertEqual(['catch', 'try', 'finally'], checker.test("""
|
|
var s;
|
|
try {
|
|
throw "abc";
|
|
}
|
|
catch (err) {
|
|
s = err;
|
|
};
|
|
|
|
try {
|
|
throw "abc";
|
|
}
|
|
finally {
|
|
s += ".";
|
|
}
|
|
"""))
|
|
|
|
def testLiterals(self):
|
|
class LiteralChecker(TestAST.Checker):
|
|
def onCallRuntime(self, expr):
|
|
expr.args[1].visit(self)
|
|
|
|
def onLiteral(self, litr):
|
|
self.called.append('literal')
|
|
|
|
self.assertFalse(litr.isPropertyName)
|
|
self.assertFalse(litr.isNull)
|
|
self.assertFalse(litr.isTrue)
|
|
|
|
def onRegExpLiteral(self, litr):
|
|
self.called.append('regex')
|
|
|
|
self.assertEqual("test", litr.pattern)
|
|
self.assertEqual("g", litr.flags)
|
|
|
|
def onObjectLiteral(self, litr):
|
|
self.called.append('object')
|
|
|
|
self.assertEqual('constant:"name"="flier",constant:"sex"=true',
|
|
",".join(["%s:%s=%s" % (prop.kind, prop.key, prop.value) for prop in litr.properties]))
|
|
|
|
def onArrayLiteral(self, litr):
|
|
self.called.append('array')
|
|
|
|
self.assertEqual('"hello","world",42',
|
|
",".join([str(value) for value in litr.values]))
|
|
with LiteralChecker(self) as checker:
|
|
self.assertEqual(['literal', 'regex', 'literal', 'literal'], checker.test("""
|
|
false;
|
|
/test/g;
|
|
var o = { name: 'flier', sex: true };
|
|
var a = ['hello', 'world', 42];
|
|
"""))
|
|
|
|
def testOperations(self):
|
|
class OperationChecker(TestAST.Checker):
|
|
def onUnaryOperation(self, expr):
|
|
self.called.append('unaryOp')
|
|
|
|
self.assertEqual(AST.Op.BIT_NOT, expr.op)
|
|
self.assertEqual("i", expr.expression.name)
|
|
|
|
#print "unary", expr
|
|
|
|
def onIncrementOperation(self, expr):
|
|
self.fail()
|
|
|
|
def onBinaryOperation(self, expr):
|
|
self.called.append('binOp')
|
|
|
|
self.assertEqual(AST.Op.ADD, expr.op)
|
|
self.assertEqual("i", str(expr.left))
|
|
self.assertEqual("j", str(expr.right))
|
|
self.assertEqual(36, expr.pos)
|
|
|
|
#print "bin", expr
|
|
|
|
def onAssignment(self, expr):
|
|
self.called.append('assign')
|
|
|
|
self.assertEqual(AST.Op.ASSIGN_ADD, expr.op)
|
|
self.assertEqual(AST.Op.ADD, expr.binop)
|
|
|
|
self.assertEqual("i", str(expr.target))
|
|
self.assertEqual("1", str(expr.value))
|
|
self.assertEqual(53, expr.pos)
|
|
|
|
self.assertEqual("(i + 1)", str(expr.binOperation))
|
|
|
|
self.assertTrue(expr.compound)
|
|
|
|
def onCountOperation(self, expr):
|
|
self.called.append('countOp')
|
|
|
|
self.assertFalse(expr.prefix)
|
|
self.assertTrue(expr.postfix)
|
|
|
|
self.assertEqual(AST.Op.INC, expr.op)
|
|
self.assertEqual(AST.Op.ADD, expr.binop)
|
|
self.assertEqual(71, expr.pos)
|
|
self.assertEqual("i", expr.expression.name)
|
|
|
|
#print "count", expr
|
|
|
|
def onCompareOperation(self, expr):
|
|
self.called.append('compOp')
|
|
|
|
if len(self.called) == 4:
|
|
self.assertEqual(AST.Op.EQ, expr.op)
|
|
self.assertEqual(88, expr.pos) # i==j
|
|
else:
|
|
self.assertEqual(AST.Op.EQ_STRICT, expr.op)
|
|
self.assertEqual(106, expr.pos) # i===j
|
|
|
|
self.assertEqual("i", str(expr.left))
|
|
self.assertEqual("j", str(expr.right))
|
|
|
|
#print "comp", expr
|
|
|
|
def onConditional(self, expr):
|
|
self.called.append('conditional')
|
|
|
|
self.assertEqual("(i > j)", str(expr.condition))
|
|
self.assertEqual("i", str(expr.thenExpr))
|
|
self.assertEqual("j", str(expr.elseExpr))
|
|
|
|
self.assertEqual(144, expr.thenExprPos)
|
|
self.assertEqual(146, expr.elseExprPos)
|
|
|
|
with OperationChecker(self) as checker:
|
|
self.assertEqual(['binOp', 'assign', 'countOp', 'compOp', 'compOp', 'unaryOp', 'conditional'], checker.test("""
|
|
var i, j;
|
|
i+j;
|
|
i+=1;
|
|
i++;
|
|
i==j;
|
|
i===j;
|
|
~i;
|
|
i>j?i:j;
|
|
"""))
|
|
|
|
def testSwitchStatement(self):
|
|
class SwitchStatementChecker(TestAST.Checker):
|
|
def onSwitchStatement(self, stmt):
|
|
self.called.append('switch')
|
|
|
|
self.assertEqual('expr', stmt.tag.name)
|
|
self.assertEqual(2, len(stmt.cases))
|
|
|
|
case = stmt.cases[0]
|
|
|
|
self.assertFalse(case.isDefault)
|
|
self.assertTrue(case.label.isString)
|
|
self.assertEqual(0, case.bodyTarget.pos)
|
|
self.assertEqual(57, case.position)
|
|
self.assertEqual(1, len(case.statements))
|
|
|
|
case = stmt.cases[1]
|
|
|
|
self.assertTrue(case.isDefault)
|
|
self.assertEqual(None, case.label)
|
|
self.assertEqual(0, case.bodyTarget.pos)
|
|
self.assertEqual(109, case.position)
|
|
self.assertEqual(1, len(case.statements))
|
|
|
|
with SwitchStatementChecker(self) as checker:
|
|
self.assertEqual(['switch'], checker.test("""
|
|
switch (expr) {
|
|
case 'flier':
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
"""))
|
|
|
|
if __name__ == '__main__':
|
|
if "-v" in sys.argv:
|
|
level = logging.DEBUG
|
|
else:
|
|
level = logging.WARN
|
|
|
|
if "-p" in sys.argv:
|
|
sys.argv.remove("-p")
|
|
print("Press any key to continue or attach process #%d..." % os.getpid())
|
|
input()
|
|
|
|
logging.basicConfig(level=level, format='%(asctime)s %(levelname)s %(message)s')
|
|
|
|
logging.info("testing PyV8 module %s with V8 v%s", __version__, JSEngine.version)
|
|
|
|
unittest.main()
|