-
-
Save mustafat0k/5ff732babcb3f2dd362a3a263c162ae8 to your computer and use it in GitHub Desktop.
Sublime text bootstrap plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 <[email protected]>' | |
__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': '[email protected]' }) | |
self.assertEqual("[email protected]", 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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment