parent
0c726a3fbe
commit
d9cea3b8b0
5 changed files with 375 additions and 16 deletions
@ -0,0 +1,337 @@ |
||||
# -*- coding: utf-8 -*- |
||||
""" |
||||
jinja2.debug |
||||
~~~~~~~~~~~~ |
||||
|
||||
Implements the debug interface for Jinja. This module does some pretty |
||||
ugly stuff with the Python traceback system in order to achieve tracebacks |
||||
with correct line numbers, locals and contents. |
||||
|
||||
:copyright: (c) 2010 by the Jinja Team. |
||||
:license: BSD, see LICENSE for more details. |
||||
""" |
||||
import sys |
||||
import traceback |
||||
from types import TracebackType, CodeType |
||||
from jinja2.utils import missing, internal_code |
||||
from jinja2.exceptions import TemplateSyntaxError |
||||
from jinja2._compat import iteritems, reraise |
||||
|
||||
# on pypy we can take advantage of transparent proxies |
||||
try: |
||||
from __pypy__ import tproxy |
||||
except ImportError: |
||||
tproxy = None |
||||
|
||||
|
||||
# how does the raise helper look like? |
||||
try: |
||||
exec("raise TypeError, 'foo'") |
||||
except SyntaxError: |
||||
raise_helper = 'raise __jinja_exception__[1]' |
||||
except TypeError: |
||||
raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]' |
||||
|
||||
|
||||
class TracebackFrameProxy(object): |
||||
"""Proxies a traceback frame.""" |
||||
|
||||
def __init__(self, tb): |
||||
self.tb = tb |
||||
self._tb_next = None |
||||
|
||||
@property |
||||
def tb_next(self): |
||||
return self._tb_next |
||||
|
||||
def set_next(self, next): |
||||
if tb_set_next is not None: |
||||
try: |
||||
tb_set_next(self.tb, next and next.tb or None) |
||||
except Exception: |
||||
# this function can fail due to all the hackery it does |
||||
# on various python implementations. We just catch errors |
||||
# down and ignore them if necessary. |
||||
pass |
||||
self._tb_next = next |
||||
|
||||
@property |
||||
def is_jinja_frame(self): |
||||
return '__jinja_template__' in self.tb.tb_frame.f_globals |
||||
|
||||
def __getattr__(self, name): |
||||
return getattr(self.tb, name) |
||||
|
||||
|
||||
def make_frame_proxy(frame): |
||||
proxy = TracebackFrameProxy(frame) |
||||
if tproxy is None: |
||||
return proxy |
||||
def operation_handler(operation, *args, **kwargs): |
||||
if operation in ('__getattribute__', '__getattr__'): |
||||
return getattr(proxy, args[0]) |
||||
elif operation == '__setattr__': |
||||
proxy.__setattr__(*args, **kwargs) |
||||
else: |
||||
return getattr(proxy, operation)(*args, **kwargs) |
||||
return tproxy(TracebackType, operation_handler) |
||||
|
||||
|
||||
class ProcessedTraceback(object): |
||||
"""Holds a Jinja preprocessed traceback for printing or reraising.""" |
||||
|
||||
def __init__(self, exc_type, exc_value, frames): |
||||
assert frames, 'no frames for this traceback?' |
||||
self.exc_type = exc_type |
||||
self.exc_value = exc_value |
||||
self.frames = frames |
||||
|
||||
# newly concatenate the frames (which are proxies) |
||||
prev_tb = None |
||||
for tb in self.frames: |
||||
if prev_tb is not None: |
||||
prev_tb.set_next(tb) |
||||
prev_tb = tb |
||||
prev_tb.set_next(None) |
||||
|
||||
def render_as_text(self, limit=None): |
||||
"""Return a string with the traceback.""" |
||||
lines = traceback.format_exception(self.exc_type, self.exc_value, |
||||
self.frames[0], limit=limit) |
||||
return ''.join(lines).rstrip() |
||||
|
||||
def render_as_html(self, full=False): |
||||
"""Return a unicode string with the traceback as rendered HTML.""" |
||||
from jinja2.debugrenderer import render_traceback |
||||
return u'%s\n\n<!--\n%s\n-->' % ( |
||||
render_traceback(self, full=full), |
||||
self.render_as_text().decode('utf-8', 'replace') |
||||
) |
||||
|
||||
@property |
||||
def is_template_syntax_error(self): |
||||
"""`True` if this is a template syntax error.""" |
||||
return isinstance(self.exc_value, TemplateSyntaxError) |
||||
|
||||
@property |
||||
def exc_info(self): |
||||
"""Exception info tuple with a proxy around the frame objects.""" |
||||
return self.exc_type, self.exc_value, self.frames[0] |
||||
|
||||
@property |
||||
def standard_exc_info(self): |
||||
"""Standard python exc_info for re-raising""" |
||||
tb = self.frames[0] |
||||
# the frame will be an actual traceback (or transparent proxy) if |
||||
# we are on pypy or a python implementation with support for tproxy |
||||
if type(tb) is not TracebackType: |
||||
tb = tb.tb |
||||
return self.exc_type, self.exc_value, tb |
||||
|
||||
|
||||
def make_traceback(exc_info, source_hint=None): |
||||
"""Creates a processed traceback object from the exc_info.""" |
||||
exc_type, exc_value, tb = exc_info |
||||
if isinstance(exc_value, TemplateSyntaxError): |
||||
exc_info = translate_syntax_error(exc_value, source_hint) |
||||
initial_skip = 0 |
||||
else: |
||||
initial_skip = 1 |
||||
return translate_exception(exc_info, initial_skip) |
||||
|
||||
|
||||
def translate_syntax_error(error, source=None): |
||||
"""Rewrites a syntax error to please traceback systems.""" |
||||
error.source = source |
||||
error.translated = True |
||||
exc_info = (error.__class__, error, None) |
||||
filename = error.filename |
||||
if filename is None: |
||||
filename = '<unknown>' |
||||
return fake_exc_info(exc_info, filename, error.lineno) |
||||
|
||||
|
||||
def translate_exception(exc_info, initial_skip=0): |
||||
"""If passed an exc_info it will automatically rewrite the exceptions |
||||
all the way down to the correct line numbers and frames. |
||||
""" |
||||
tb = exc_info[2] |
||||
frames = [] |
||||
|
||||
# skip some internal frames if wanted |
||||
for x in range(initial_skip): |
||||
if tb is not None: |
||||
tb = tb.tb_next |
||||
initial_tb = tb |
||||
|
||||
while tb is not None: |
||||
# skip frames decorated with @internalcode. These are internal |
||||
# calls we can't avoid and that are useless in template debugging |
||||
# output. |
||||
if tb.tb_frame.f_code in internal_code: |
||||
tb = tb.tb_next |
||||
continue |
||||
|
||||
# save a reference to the next frame if we override the current |
||||
# one with a faked one. |
||||
next = tb.tb_next |
||||
|
||||
# fake template exceptions |
||||
template = tb.tb_frame.f_globals.get('__jinja_template__') |
||||
if template is not None: |
||||
lineno = template.get_corresponding_lineno(tb.tb_lineno) |
||||
tb = fake_exc_info(exc_info[:2] + (tb,), template.filename, |
||||
lineno)[2] |
||||
|
||||
frames.append(make_frame_proxy(tb)) |
||||
tb = next |
||||
|
||||
# if we don't have any exceptions in the frames left, we have to |
||||
# reraise it unchanged. |
||||
# XXX: can we backup here? when could this happen? |
||||
if not frames: |
||||
reraise(exc_info[0], exc_info[1], exc_info[2]) |
||||
|
||||
return ProcessedTraceback(exc_info[0], exc_info[1], frames) |
||||
|
||||
|
||||
def fake_exc_info(exc_info, filename, lineno): |
||||
"""Helper for `translate_exception`.""" |
||||
exc_type, exc_value, tb = exc_info |
||||
|
||||
# figure the real context out |
||||
if tb is not None: |
||||
real_locals = tb.tb_frame.f_locals.copy() |
||||
ctx = real_locals.get('context') |
||||
if ctx: |
||||
locals = ctx.get_all() |
||||
else: |
||||
locals = {} |
||||
for name, value in iteritems(real_locals): |
||||
if name.startswith('l_') and value is not missing: |
||||
locals[name[2:]] = value |
||||
|
||||
# if there is a local called __jinja_exception__, we get |
||||
# rid of it to not break the debug functionality. |
||||
locals.pop('__jinja_exception__', None) |
||||
else: |
||||
locals = {} |
||||
|
||||
# assamble fake globals we need |
||||
globals = { |
||||
'__name__': filename, |
||||
'__file__': filename, |
||||
'__jinja_exception__': exc_info[:2], |
||||
|
||||
# we don't want to keep the reference to the template around |
||||
# to not cause circular dependencies, but we mark it as Jinja |
||||
# frame for the ProcessedTraceback |
||||
'__jinja_template__': None |
||||
} |
||||
|
||||
# and fake the exception |
||||
code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec') |
||||
|
||||
# if it's possible, change the name of the code. This won't work |
||||
# on some python environments such as google appengine |
||||
try: |
||||
if tb is None: |
||||
location = 'template' |
||||
else: |
||||
function = tb.tb_frame.f_code.co_name |
||||
if function == 'root': |
||||
location = 'top-level template code' |
||||
elif function.startswith('block_'): |
||||
location = 'block "%s"' % function[6:] |
||||
else: |
||||
location = 'template' |
||||
code = CodeType(0, code.co_nlocals, code.co_stacksize, |
||||
code.co_flags, code.co_code, code.co_consts, |
||||
code.co_names, code.co_varnames, filename, |
||||
location, code.co_firstlineno, |
||||
code.co_lnotab, (), ()) |
||||
except: |
||||
pass |
||||
|
||||
# execute the code and catch the new traceback |
||||
try: |
||||
exec(code, globals, locals) |
||||
except: |
||||
exc_info = sys.exc_info() |
||||
new_tb = exc_info[2].tb_next |
||||
|
||||
# return without this frame |
||||
return exc_info[:2] + (new_tb,) |
||||
|
||||
|
||||
def _init_ugly_crap(): |
||||
"""This function implements a few ugly things so that we can patch the |
||||
traceback objects. The function returned allows resetting `tb_next` on |
||||
any python traceback object. Do not attempt to use this on non cpython |
||||
interpreters |
||||
""" |
||||
import ctypes |
||||
from types import TracebackType |
||||
|
||||
# figure out side of _Py_ssize_t |
||||
if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'): |
||||
_Py_ssize_t = ctypes.c_int64 |
||||
else: |
||||
_Py_ssize_t = ctypes.c_int |
||||
|
||||
# regular python |
||||
class _PyObject(ctypes.Structure): |
||||
pass |
||||
_PyObject._fields_ = [ |
||||
('ob_refcnt', _Py_ssize_t), |
||||
('ob_type', ctypes.POINTER(_PyObject)) |
||||
] |
||||
|
||||
# python with trace |
||||
if hasattr(sys, 'getobjects'): |
||||
class _PyObject(ctypes.Structure): |
||||
pass |
||||
_PyObject._fields_ = [ |
||||
('_ob_next', ctypes.POINTER(_PyObject)), |
||||
('_ob_prev', ctypes.POINTER(_PyObject)), |
||||
('ob_refcnt', _Py_ssize_t), |
||||
('ob_type', ctypes.POINTER(_PyObject)) |
||||
] |
||||
|
||||
class _Traceback(_PyObject): |
||||
pass |
||||
_Traceback._fields_ = [ |
||||
('tb_next', ctypes.POINTER(_Traceback)), |
||||
('tb_frame', ctypes.POINTER(_PyObject)), |
||||
('tb_lasti', ctypes.c_int), |
||||
('tb_lineno', ctypes.c_int) |
||||
] |
||||
|
||||
def tb_set_next(tb, next): |
||||
"""Set the tb_next attribute of a traceback object.""" |
||||
if not (isinstance(tb, TracebackType) and |
||||
(next is None or isinstance(next, TracebackType))): |
||||
raise TypeError('tb_set_next arguments must be traceback objects') |
||||
obj = _Traceback.from_address(id(tb)) |
||||
if tb.tb_next is not None: |
||||
old = _Traceback.from_address(id(tb.tb_next)) |
||||
old.ob_refcnt -= 1 |
||||
if next is None: |
||||
obj.tb_next = ctypes.POINTER(_Traceback)() |
||||
else: |
||||
next = _Traceback.from_address(id(next)) |
||||
next.ob_refcnt += 1 |
||||
obj.tb_next = ctypes.pointer(next) |
||||
|
||||
return tb_set_next |
||||
|
||||
|
||||
# try to get a tb_set_next implementation if we don't have transparent |
||||
# proxies. |
||||
tb_set_next = None |
||||
if tproxy is None: |
||||
try: |
||||
tb_set_next = _init_ugly_crap() |
||||
except: |
||||
pass |
||||
del _init_ugly_crap |
@ -1,5 +1,28 @@ |
||||
# compile the test sources |
||||
file(GLOB SOURCE_FILES "*.cpp") |
||||
add_custom_target(opencv_test_matlab_sources ALL) |
||||
foreach(SOURCE_FILE ${SOURCE_FILES}) |
||||
get_filename_component(FILENAME ${SOURCE_FILE} NAME_WE) |
||||
# compile the source file using mex |
||||
add_custom_command(TARGET opencv_test_matlab_sources PRE_BUILD |
||||
COMMAND echo ${MATLAB_MEX_SCRIPT} ${MEX_INCLUDES} |
||||
${SOURCE_FILE} |
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} |
||||
) |
||||
endforeach() |
||||
|
||||
# copy the test files into the build dir |
||||
file(GLOB TEST_FILES "*.m") |
||||
foreach(TEST_FILE ${TEST_FILES}) |
||||
add_custom_command(TARGET opencv_test_matlab_sources PRE_BUILD |
||||
COMMAND ${CMAKE_COMMAND} -E |
||||
copy ${TEST_FILE} ${CMAKE_CURRENT_BINARY_DIR} |
||||
) |
||||
endforeach() |
||||
|
||||
|
||||
# run the matlab test suite |
||||
add_test(opencv_matlab_test |
||||
add_test(opencv_test_matlab |
||||
COMMAND ${MATLAB_BIN} "-nodisplay" "-r" "testsuite.m" |
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} |
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} |
||||
) |
||||
|
Loading…
Reference in new issue