consolidate py/code files into code.py, simplify SafeRepr code and docs.
--HG-- branch : trunk
This commit is contained in:
parent
94aef0b771
commit
8ee7bef638
|
@ -1,6 +1,8 @@
|
||||||
Changes between 1.0.x and 'trunk'
|
Changes between 1.0.x and 'trunk'
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
* consolidate and cleanup py/code classes and files
|
||||||
|
|
||||||
* cleanup py/misc, move tests to bin-for-dist
|
* cleanup py/misc, move tests to bin-for-dist
|
||||||
|
|
||||||
* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
|
* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
|
||||||
|
|
|
@ -124,9 +124,9 @@ initpkg(__name__,
|
||||||
'code.compile' : ('./code/source.py', 'compile_'),
|
'code.compile' : ('./code/source.py', 'compile_'),
|
||||||
'code.Source' : ('./code/source.py', 'Source'),
|
'code.Source' : ('./code/source.py', 'Source'),
|
||||||
'code.Code' : ('./code/code.py', 'Code'),
|
'code.Code' : ('./code/code.py', 'Code'),
|
||||||
'code.Frame' : ('./code/frame.py', 'Frame'),
|
'code.Frame' : ('./code/code.py', 'Frame'),
|
||||||
'code.ExceptionInfo' : ('./code/excinfo.py', 'ExceptionInfo'),
|
'code.ExceptionInfo' : ('./code/code.py', 'ExceptionInfo'),
|
||||||
'code.Traceback' : ('./code/traceback2.py', 'Traceback'),
|
'code.Traceback' : ('./code/code.py', 'Traceback'),
|
||||||
'code.getfslineno' : ('./code/source.py', 'getfslineno'),
|
'code.getfslineno' : ('./code/source.py', 'getfslineno'),
|
||||||
|
|
||||||
# backports and additions of builtins
|
# backports and additions of builtins
|
||||||
|
|
635
py/code/code.py
635
py/code/code.py
|
@ -1,5 +1,11 @@
|
||||||
import py
|
import py
|
||||||
from py.__.code import source
|
import sys
|
||||||
|
try:
|
||||||
|
import repr
|
||||||
|
except ImportError:
|
||||||
|
import reprlib as repr
|
||||||
|
|
||||||
|
from __builtin__ import repr as builtin_repr
|
||||||
|
|
||||||
class Code(object):
|
class Code(object):
|
||||||
""" wrapper around Python code objects """
|
""" wrapper around Python code objects """
|
||||||
|
@ -76,6 +82,7 @@ class Code(object):
|
||||||
def fullsource(self):
|
def fullsource(self):
|
||||||
""" return a py.code.Source object for the full source file of the code
|
""" return a py.code.Source object for the full source file of the code
|
||||||
"""
|
"""
|
||||||
|
from py.__.code import source
|
||||||
full, _ = source.findsource(self.raw)
|
full, _ = source.findsource(self.raw)
|
||||||
return full
|
return full
|
||||||
fullsource = property(fullsource, None, None,
|
fullsource = property(fullsource, None, None,
|
||||||
|
@ -94,3 +101,629 @@ class Code(object):
|
||||||
raw = self.raw
|
raw = self.raw
|
||||||
return raw.co_varnames[:raw.co_argcount]
|
return raw.co_varnames[:raw.co_argcount]
|
||||||
|
|
||||||
|
class Frame(object):
|
||||||
|
"""Wrapper around a Python frame holding f_locals and f_globals
|
||||||
|
in which expressions can be evaluated."""
|
||||||
|
|
||||||
|
def __init__(self, frame):
|
||||||
|
self.code = py.code.Code(frame.f_code)
|
||||||
|
self.lineno = frame.f_lineno - 1
|
||||||
|
self.f_globals = frame.f_globals
|
||||||
|
self.f_locals = frame.f_locals
|
||||||
|
self.raw = frame
|
||||||
|
|
||||||
|
def statement(self):
|
||||||
|
if self.code.fullsource is None:
|
||||||
|
return py.code.Source("")
|
||||||
|
return self.code.fullsource.getstatement(self.lineno)
|
||||||
|
statement = property(statement, None, None,
|
||||||
|
"statement this frame is at")
|
||||||
|
|
||||||
|
def eval(self, code, **vars):
|
||||||
|
""" evaluate 'code' in the frame
|
||||||
|
|
||||||
|
'vars' are optional additional local variables
|
||||||
|
|
||||||
|
returns the result of the evaluation
|
||||||
|
"""
|
||||||
|
f_locals = self.f_locals.copy()
|
||||||
|
f_locals.update(vars)
|
||||||
|
return eval(code, self.f_globals, f_locals)
|
||||||
|
|
||||||
|
def exec_(self, code, **vars):
|
||||||
|
""" exec 'code' in the frame
|
||||||
|
|
||||||
|
'vars' are optiona; additional local variables
|
||||||
|
"""
|
||||||
|
f_locals = self.f_locals.copy()
|
||||||
|
f_locals.update(vars)
|
||||||
|
exec code in self.f_globals, f_locals
|
||||||
|
|
||||||
|
def repr(self, object):
|
||||||
|
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
|
||||||
|
"""
|
||||||
|
return safe_repr(object)
|
||||||
|
|
||||||
|
def is_true(self, object):
|
||||||
|
return object
|
||||||
|
|
||||||
|
def getargs(self):
|
||||||
|
""" return a list of tuples (name, value) for all arguments
|
||||||
|
"""
|
||||||
|
retval = []
|
||||||
|
for arg in self.code.getargs():
|
||||||
|
try:
|
||||||
|
retval.append((arg, self.f_locals[arg]))
|
||||||
|
except KeyError:
|
||||||
|
pass # this can occur when using Psyco
|
||||||
|
return retval
|
||||||
|
|
||||||
|
class TracebackEntry(object):
|
||||||
|
""" a single entry in a traceback """
|
||||||
|
|
||||||
|
exprinfo = None
|
||||||
|
|
||||||
|
def __init__(self, rawentry):
|
||||||
|
self._rawentry = rawentry
|
||||||
|
self.frame = py.code.Frame(rawentry.tb_frame)
|
||||||
|
# Ugh. 2.4 and 2.5 differs here when encountering
|
||||||
|
# multi-line statements. Not sure about the solution, but
|
||||||
|
# should be portable
|
||||||
|
self.lineno = rawentry.tb_lineno - 1
|
||||||
|
self.relline = self.lineno - self.frame.code.firstlineno
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
|
||||||
|
|
||||||
|
def statement(self):
|
||||||
|
""" return a py.code.Source object for the current statement """
|
||||||
|
source = self.frame.code.fullsource
|
||||||
|
return source.getstatement(self.lineno)
|
||||||
|
statement = property(statement, None, None,
|
||||||
|
"statement of this traceback entry.")
|
||||||
|
|
||||||
|
def path(self):
|
||||||
|
return self.frame.code.path
|
||||||
|
path = property(path, None, None, "path to the full source code")
|
||||||
|
|
||||||
|
def getlocals(self):
|
||||||
|
return self.frame.f_locals
|
||||||
|
locals = property(getlocals, None, None, "locals of underlaying frame")
|
||||||
|
|
||||||
|
def reinterpret(self):
|
||||||
|
"""Reinterpret the failing statement and returns a detailed information
|
||||||
|
about what operations are performed."""
|
||||||
|
if self.exprinfo is None:
|
||||||
|
from py.__.magic import exprinfo
|
||||||
|
source = str(self.statement).strip()
|
||||||
|
x = exprinfo.interpret(source, self.frame, should_fail=True)
|
||||||
|
if not isinstance(x, str):
|
||||||
|
raise TypeError, "interpret returned non-string %r" % (x,)
|
||||||
|
self.exprinfo = x
|
||||||
|
return self.exprinfo
|
||||||
|
|
||||||
|
def getfirstlinesource(self):
|
||||||
|
return self.frame.code.firstlineno
|
||||||
|
|
||||||
|
def getsource(self):
|
||||||
|
""" return failing source code. """
|
||||||
|
source = self.frame.code.fullsource
|
||||||
|
if source is None:
|
||||||
|
return None
|
||||||
|
start = self.getfirstlinesource()
|
||||||
|
end = self.lineno
|
||||||
|
try:
|
||||||
|
_, end = source.getstatementrange(end)
|
||||||
|
except IndexError:
|
||||||
|
end = self.lineno + 1
|
||||||
|
# heuristic to stop displaying source on e.g.
|
||||||
|
# if something: # assume this causes a NameError
|
||||||
|
# # _this_ lines and the one
|
||||||
|
# below we don't want from entry.getsource()
|
||||||
|
for i in range(self.lineno, end):
|
||||||
|
if source[i].rstrip().endswith(':'):
|
||||||
|
end = i + 1
|
||||||
|
break
|
||||||
|
return source[start:end]
|
||||||
|
source = property(getsource)
|
||||||
|
|
||||||
|
def ishidden(self):
|
||||||
|
""" return True if the current frame has a var __tracebackhide__
|
||||||
|
resolving to True
|
||||||
|
|
||||||
|
mostly for internal use
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.frame.eval("__tracebackhide__")
|
||||||
|
except (SystemExit, KeyboardInterrupt):
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
try:
|
||||||
|
fn = str(self.path)
|
||||||
|
except py.error.Error:
|
||||||
|
fn = '???'
|
||||||
|
name = self.frame.code.name
|
||||||
|
try:
|
||||||
|
line = str(self.statement).lstrip()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
line = "???"
|
||||||
|
return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self.frame.code.raw.co_name
|
||||||
|
name = property(name, None, None, "co_name of underlaying code")
|
||||||
|
|
||||||
|
class Traceback(list):
|
||||||
|
""" Traceback objects encapsulate and offer higher level
|
||||||
|
access to Traceback entries.
|
||||||
|
"""
|
||||||
|
Entry = TracebackEntry
|
||||||
|
def __init__(self, tb):
|
||||||
|
""" initialize from given python traceback object. """
|
||||||
|
if hasattr(tb, 'tb_next'):
|
||||||
|
def f(cur):
|
||||||
|
while cur is not None:
|
||||||
|
yield self.Entry(cur)
|
||||||
|
cur = cur.tb_next
|
||||||
|
list.__init__(self, f(tb))
|
||||||
|
else:
|
||||||
|
list.__init__(self, tb)
|
||||||
|
|
||||||
|
def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
|
||||||
|
""" return a Traceback instance wrapping part of this Traceback
|
||||||
|
|
||||||
|
by provding any combination of path, lineno and firstlineno, the
|
||||||
|
first frame to start the to-be-returned traceback is determined
|
||||||
|
|
||||||
|
this allows cutting the first part of a Traceback instance e.g.
|
||||||
|
for formatting reasons (removing some uninteresting bits that deal
|
||||||
|
with handling of the exception/traceback)
|
||||||
|
"""
|
||||||
|
for x in self:
|
||||||
|
code = x.frame.code
|
||||||
|
codepath = code.path
|
||||||
|
if ((path is None or codepath == path) and
|
||||||
|
(excludepath is None or (hasattr(codepath, 'relto') and
|
||||||
|
not codepath.relto(excludepath))) and
|
||||||
|
(lineno is None or x.lineno == lineno) and
|
||||||
|
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
|
||||||
|
return Traceback(x._rawentry)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
val = super(Traceback, self).__getitem__(key)
|
||||||
|
if isinstance(key, type(slice(0))):
|
||||||
|
val = self.__class__(val)
|
||||||
|
return val
|
||||||
|
|
||||||
|
def filter(self, fn=lambda x: not x.ishidden()):
|
||||||
|
""" return a Traceback instance with certain items removed
|
||||||
|
|
||||||
|
fn is a function that gets a single argument, a TracebackItem
|
||||||
|
instance, and should return True when the item should be added
|
||||||
|
to the Traceback, False when not
|
||||||
|
|
||||||
|
by default this removes all the TracebackItems which are hidden
|
||||||
|
(see ishidden() above)
|
||||||
|
"""
|
||||||
|
return Traceback(filter(fn, self))
|
||||||
|
|
||||||
|
def getcrashentry(self):
|
||||||
|
""" return last non-hidden traceback entry that lead
|
||||||
|
to the exception of a traceback.
|
||||||
|
"""
|
||||||
|
tb = self.filter()
|
||||||
|
if not tb:
|
||||||
|
tb = self
|
||||||
|
return tb[-1]
|
||||||
|
|
||||||
|
def recursionindex(self):
|
||||||
|
""" return the index of the frame/TracebackItem where recursion
|
||||||
|
originates if appropriate, None if no recursion occurred
|
||||||
|
"""
|
||||||
|
cache = {}
|
||||||
|
for i, entry in py.builtin.enumerate(self):
|
||||||
|
key = entry.frame.code.path, entry.lineno
|
||||||
|
#print "checking for recursion at", key
|
||||||
|
l = cache.setdefault(key, [])
|
||||||
|
if l:
|
||||||
|
f = entry.frame
|
||||||
|
loc = f.f_locals
|
||||||
|
for otherloc in l:
|
||||||
|
if f.is_true(f.eval(co_equal,
|
||||||
|
__recursioncache_locals_1=loc,
|
||||||
|
__recursioncache_locals_2=otherloc)):
|
||||||
|
return i
|
||||||
|
l.append(entry.frame.f_locals)
|
||||||
|
return None
|
||||||
|
|
||||||
|
co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
|
||||||
|
'?', 'eval')
|
||||||
|
|
||||||
|
class ExceptionInfo(object):
|
||||||
|
""" wraps sys.exc_info() objects and offers
|
||||||
|
help for navigating the traceback.
|
||||||
|
"""
|
||||||
|
_striptext = ''
|
||||||
|
def __init__(self, tup=None, exprinfo=None):
|
||||||
|
# NB. all attributes are private! Subclasses or other
|
||||||
|
# ExceptionInfo-like classes may have different attributes.
|
||||||
|
if tup is None:
|
||||||
|
tup = sys.exc_info()
|
||||||
|
if exprinfo is None and isinstance(tup[1], py.magic.AssertionError):
|
||||||
|
exprinfo = tup[1].msg
|
||||||
|
if exprinfo and exprinfo.startswith('assert '):
|
||||||
|
self._striptext = 'AssertionError: '
|
||||||
|
self._excinfo = tup
|
||||||
|
self.type, self.value, tb = self._excinfo
|
||||||
|
self.typename = self.type.__name__
|
||||||
|
self.traceback = py.code.Traceback(tb)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
|
||||||
|
|
||||||
|
def exconly(self, tryshort=False):
|
||||||
|
""" return the exception as a string
|
||||||
|
|
||||||
|
when 'tryshort' resolves to True, and the exception is a
|
||||||
|
py.magic.AssertionError, only the actual exception part of
|
||||||
|
the exception representation is returned (so 'AssertionError: ' is
|
||||||
|
removed from the beginning)
|
||||||
|
"""
|
||||||
|
lines = py.std.traceback.format_exception_only(self.type, self.value)
|
||||||
|
text = ''.join(lines)
|
||||||
|
text = text.rstrip()
|
||||||
|
if tryshort:
|
||||||
|
if text.startswith(self._striptext):
|
||||||
|
text = text[len(self._striptext):]
|
||||||
|
return text
|
||||||
|
|
||||||
|
def errisinstance(self, exc):
|
||||||
|
""" return True if the exception is an instance of exc """
|
||||||
|
return isinstance(self.value, exc)
|
||||||
|
|
||||||
|
def _getreprcrash(self):
|
||||||
|
exconly = self.exconly(tryshort=True)
|
||||||
|
entry = self.traceback.getcrashentry()
|
||||||
|
path, lineno = entry.path, entry.lineno
|
||||||
|
reprcrash = ReprFileLocation(path, lineno+1, exconly)
|
||||||
|
return reprcrash
|
||||||
|
|
||||||
|
def getrepr(self, showlocals=False, style="long",
|
||||||
|
abspath=False, tbfilter=True, funcargs=False):
|
||||||
|
""" return str()able representation of this exception info.
|
||||||
|
showlocals: show locals per traceback entry
|
||||||
|
style: long|short|no traceback style
|
||||||
|
tbfilter: hide entries (where __tracebackhide__ is true)
|
||||||
|
"""
|
||||||
|
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
|
||||||
|
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
|
||||||
|
return fmt.repr_excinfo(self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
entry = self.traceback[-1]
|
||||||
|
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
||||||
|
return str(loc)
|
||||||
|
|
||||||
|
class FormattedExcinfo(object):
|
||||||
|
""" presenting information about failing Functions and Generators. """
|
||||||
|
# for traceback entries
|
||||||
|
flow_marker = ">"
|
||||||
|
fail_marker = "E"
|
||||||
|
|
||||||
|
def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
|
||||||
|
self.showlocals = showlocals
|
||||||
|
self.style = style
|
||||||
|
self.tbfilter = tbfilter
|
||||||
|
self.funcargs = funcargs
|
||||||
|
self.abspath = abspath
|
||||||
|
|
||||||
|
def _getindent(self, source):
|
||||||
|
# figure out indent for given source
|
||||||
|
try:
|
||||||
|
s = str(source.getstatement(len(source)-1))
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
s = str(source[-1])
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
return 0
|
||||||
|
return 4 + (len(s) - len(s.lstrip()))
|
||||||
|
|
||||||
|
def _getentrysource(self, entry):
|
||||||
|
source = entry.getsource()
|
||||||
|
if source is not None:
|
||||||
|
source = source.deindent()
|
||||||
|
return source
|
||||||
|
|
||||||
|
def _saferepr(self, obj):
|
||||||
|
return safe_repr(obj)
|
||||||
|
|
||||||
|
def repr_args(self, entry):
|
||||||
|
if self.funcargs:
|
||||||
|
args = []
|
||||||
|
for argname, argvalue in entry.frame.getargs():
|
||||||
|
args.append((argname, self._saferepr(argvalue)))
|
||||||
|
return ReprFuncArgs(args)
|
||||||
|
|
||||||
|
def get_source(self, source, line_index=-1, excinfo=None):
|
||||||
|
""" return formatted and marked up source lines. """
|
||||||
|
lines = []
|
||||||
|
if source is None:
|
||||||
|
source = py.code.Source("???")
|
||||||
|
line_index = 0
|
||||||
|
if line_index < 0:
|
||||||
|
line_index += len(source)
|
||||||
|
for i in range(len(source)):
|
||||||
|
if i == line_index:
|
||||||
|
prefix = self.flow_marker + " "
|
||||||
|
else:
|
||||||
|
prefix = " "
|
||||||
|
line = prefix + source[i]
|
||||||
|
lines.append(line)
|
||||||
|
if excinfo is not None:
|
||||||
|
indent = self._getindent(source)
|
||||||
|
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def get_exconly(self, excinfo, indent=4, markall=False):
|
||||||
|
lines = []
|
||||||
|
indent = " " * indent
|
||||||
|
# get the real exception information out
|
||||||
|
exlines = excinfo.exconly(tryshort=True).split('\n')
|
||||||
|
failindent = self.fail_marker + indent[1:]
|
||||||
|
for line in exlines:
|
||||||
|
lines.append(failindent + line)
|
||||||
|
if not markall:
|
||||||
|
failindent = indent
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def repr_locals(self, locals):
|
||||||
|
if self.showlocals:
|
||||||
|
lines = []
|
||||||
|
items = locals.items()
|
||||||
|
items.sort()
|
||||||
|
for name, value in items:
|
||||||
|
if name == '__builtins__':
|
||||||
|
lines.append("__builtins__ = <builtins>")
|
||||||
|
else:
|
||||||
|
# This formatting could all be handled by the
|
||||||
|
# _repr() function, which is only repr.Repr in
|
||||||
|
# disguise, so is very configurable.
|
||||||
|
str_repr = self._saferepr(value)
|
||||||
|
#if len(str_repr) < 70 or not isinstance(value,
|
||||||
|
# (list, tuple, dict)):
|
||||||
|
lines.append("%-10s = %s" %(name, str_repr))
|
||||||
|
#else:
|
||||||
|
# self._line("%-10s =\\" % (name,))
|
||||||
|
# # XXX
|
||||||
|
# py.std.pprint.pprint(value, stream=self.excinfowriter)
|
||||||
|
return ReprLocals(lines)
|
||||||
|
|
||||||
|
def repr_traceback_entry(self, entry, excinfo=None):
|
||||||
|
# excinfo is not None if this is the last tb entry
|
||||||
|
source = self._getentrysource(entry)
|
||||||
|
if source is None:
|
||||||
|
source = py.code.Source("???")
|
||||||
|
line_index = 0
|
||||||
|
else:
|
||||||
|
line_index = entry.lineno - entry.getfirstlinesource()
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
if self.style == "long":
|
||||||
|
reprargs = self.repr_args(entry)
|
||||||
|
lines.extend(self.get_source(source, line_index, excinfo))
|
||||||
|
message = excinfo and excinfo.typename or ""
|
||||||
|
path = self._makepath(entry.path)
|
||||||
|
filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
|
||||||
|
localsrepr = self.repr_locals(entry.locals)
|
||||||
|
return ReprEntry(lines, reprargs, localsrepr, filelocrepr)
|
||||||
|
else:
|
||||||
|
if self.style == "short":
|
||||||
|
line = source[line_index].lstrip()
|
||||||
|
lines.append(' File "%s", line %d, in %s' % (
|
||||||
|
entry.path.basename, entry.lineno+1, entry.name))
|
||||||
|
lines.append(" " + line)
|
||||||
|
if excinfo:
|
||||||
|
lines.extend(self.get_exconly(excinfo, indent=4))
|
||||||
|
return ReprEntry(lines, None, None, None)
|
||||||
|
|
||||||
|
def _makepath(self, path):
|
||||||
|
if not self.abspath:
|
||||||
|
np = py.path.local().bestrelpath(path)
|
||||||
|
if len(np) < len(str(path)):
|
||||||
|
path = np
|
||||||
|
return path
|
||||||
|
|
||||||
|
def repr_traceback(self, excinfo):
|
||||||
|
traceback = excinfo.traceback
|
||||||
|
if self.tbfilter:
|
||||||
|
traceback = traceback.filter()
|
||||||
|
recursionindex = None
|
||||||
|
if excinfo.errisinstance(RuntimeError):
|
||||||
|
recursionindex = traceback.recursionindex()
|
||||||
|
last = traceback[-1]
|
||||||
|
entries = []
|
||||||
|
extraline = None
|
||||||
|
for index, entry in py.builtin.enumerate(traceback):
|
||||||
|
einfo = (last == entry) and excinfo or None
|
||||||
|
reprentry = self.repr_traceback_entry(entry, einfo)
|
||||||
|
entries.append(reprentry)
|
||||||
|
if index == recursionindex:
|
||||||
|
extraline = "!!! Recursion detected (same locals & position)"
|
||||||
|
break
|
||||||
|
return ReprTraceback(entries, extraline, style=self.style)
|
||||||
|
|
||||||
|
def repr_excinfo(self, excinfo):
|
||||||
|
reprtraceback = self.repr_traceback(excinfo)
|
||||||
|
reprcrash = excinfo._getreprcrash()
|
||||||
|
return ReprExceptionInfo(reprtraceback, reprcrash)
|
||||||
|
|
||||||
|
class TerminalRepr:
|
||||||
|
def __str__(self):
|
||||||
|
tw = py.io.TerminalWriter(stringio=True)
|
||||||
|
self.toterminal(tw)
|
||||||
|
return tw.stringio.getvalue().strip()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s instance at %0x>" %(self.__class__, id(self))
|
||||||
|
|
||||||
|
class ReprExceptionInfo(TerminalRepr):
|
||||||
|
def __init__(self, reprtraceback, reprcrash):
|
||||||
|
self.reprtraceback = reprtraceback
|
||||||
|
self.reprcrash = reprcrash
|
||||||
|
self.sections = []
|
||||||
|
|
||||||
|
def addsection(self, name, content, sep="-"):
|
||||||
|
self.sections.append((name, content, sep))
|
||||||
|
|
||||||
|
def toterminal(self, tw):
|
||||||
|
self.reprtraceback.toterminal(tw)
|
||||||
|
for name, content, sep in self.sections:
|
||||||
|
tw.sep(sep, name)
|
||||||
|
tw.line(content)
|
||||||
|
|
||||||
|
class ReprTraceback(TerminalRepr):
|
||||||
|
entrysep = "_ "
|
||||||
|
|
||||||
|
def __init__(self, reprentries, extraline, style):
|
||||||
|
self.reprentries = reprentries
|
||||||
|
self.extraline = extraline
|
||||||
|
self.style = style
|
||||||
|
|
||||||
|
def toterminal(self, tw):
|
||||||
|
sepok = False
|
||||||
|
for entry in self.reprentries:
|
||||||
|
if self.style == "long":
|
||||||
|
if sepok:
|
||||||
|
tw.sep(self.entrysep)
|
||||||
|
tw.line("")
|
||||||
|
sepok = True
|
||||||
|
entry.toterminal(tw)
|
||||||
|
if self.extraline:
|
||||||
|
tw.line(self.extraline)
|
||||||
|
|
||||||
|
class ReprEntry(TerminalRepr):
|
||||||
|
localssep = "_ "
|
||||||
|
|
||||||
|
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr):
|
||||||
|
self.lines = lines
|
||||||
|
self.reprfuncargs = reprfuncargs
|
||||||
|
self.reprlocals = reprlocals
|
||||||
|
self.reprfileloc = filelocrepr
|
||||||
|
|
||||||
|
def toterminal(self, tw):
|
||||||
|
if self.reprfuncargs:
|
||||||
|
self.reprfuncargs.toterminal(tw)
|
||||||
|
for line in self.lines:
|
||||||
|
red = line.startswith("E ")
|
||||||
|
tw.line(line, bold=True, red=red)
|
||||||
|
if self.reprlocals:
|
||||||
|
#tw.sep(self.localssep, "Locals")
|
||||||
|
tw.line("")
|
||||||
|
self.reprlocals.toterminal(tw)
|
||||||
|
if self.reprfileloc:
|
||||||
|
tw.line("")
|
||||||
|
self.reprfileloc.toterminal(tw)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s\n%s\n%s" % ("\n".join(self.lines),
|
||||||
|
self.reprlocals,
|
||||||
|
self.reprfileloc)
|
||||||
|
|
||||||
|
class ReprFileLocation(TerminalRepr):
|
||||||
|
def __init__(self, path, lineno, message):
|
||||||
|
self.path = str(path)
|
||||||
|
self.lineno = lineno
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def toterminal(self, tw):
|
||||||
|
# filename and lineno output for each entry,
|
||||||
|
# using an output format that most editors unterstand
|
||||||
|
msg = self.message
|
||||||
|
i = msg.find("\n")
|
||||||
|
if i != -1:
|
||||||
|
msg = msg[:i]
|
||||||
|
tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
|
||||||
|
|
||||||
|
class ReprLocals(TerminalRepr):
|
||||||
|
def __init__(self, lines):
|
||||||
|
self.lines = lines
|
||||||
|
|
||||||
|
def toterminal(self, tw):
|
||||||
|
for line in self.lines:
|
||||||
|
tw.line(line)
|
||||||
|
|
||||||
|
class ReprFuncArgs(TerminalRepr):
|
||||||
|
def __init__(self, args):
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def toterminal(self, tw):
|
||||||
|
if self.args:
|
||||||
|
linesofar = ""
|
||||||
|
for name, value in self.args:
|
||||||
|
ns = "%s = %s" %(name, value)
|
||||||
|
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
|
||||||
|
if linesofar:
|
||||||
|
tw.line(linesofar)
|
||||||
|
linesofar = ns
|
||||||
|
else:
|
||||||
|
if linesofar:
|
||||||
|
linesofar += ", " + ns
|
||||||
|
else:
|
||||||
|
linesofar = ns
|
||||||
|
if linesofar:
|
||||||
|
tw.line(linesofar)
|
||||||
|
tw.line("")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SafeRepr(repr.Repr):
|
||||||
|
""" subclass of repr.Repr that limits the resulting size of repr()
|
||||||
|
and includes information on exceptions raised during the call.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
repr.Repr.__init__(self, *args, **kwargs)
|
||||||
|
self.maxstring = 240 # 3 * 80 chars
|
||||||
|
self.maxother = 160 # 2 * 80 chars
|
||||||
|
|
||||||
|
def repr(self, x):
|
||||||
|
return self._callhelper(repr.Repr.repr, self, x)
|
||||||
|
|
||||||
|
def repr_instance(self, x, level):
|
||||||
|
return self._callhelper(builtin_repr, x)
|
||||||
|
|
||||||
|
def _callhelper(self, call, x, *args):
|
||||||
|
try:
|
||||||
|
# Try the vanilla repr and make sure that the result is a string
|
||||||
|
s = call(x, *args)
|
||||||
|
except (KeyboardInterrupt, MemoryError, SystemExit):
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
cls, e, tb = sys.exc_info()
|
||||||
|
try:
|
||||||
|
exc_name = cls.__name__
|
||||||
|
except:
|
||||||
|
exc_name = 'unknown'
|
||||||
|
try:
|
||||||
|
exc_info = str(e)
|
||||||
|
except:
|
||||||
|
exc_info = 'unknown'
|
||||||
|
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % (
|
||||||
|
exc_name, exc_info, x.__class__.__name__, id(x))
|
||||||
|
else:
|
||||||
|
if len(s) > self.maxstring:
|
||||||
|
i = max(0, (self.maxstring-3)//2)
|
||||||
|
j = max(0, self.maxstring-3-i)
|
||||||
|
s = s[:i] + '...' + s[len(s)-j:]
|
||||||
|
return s
|
||||||
|
|
||||||
|
safe_repr = SafeRepr().repr
|
||||||
|
|
|
@ -1,345 +0,0 @@
|
||||||
"""
|
|
||||||
|
|
||||||
Exception Info representation + formatting
|
|
||||||
|
|
||||||
"""
|
|
||||||
import py
|
|
||||||
from py.__.code import safe_repr
|
|
||||||
from sys import exc_info
|
|
||||||
|
|
||||||
class ExceptionInfo(object):
|
|
||||||
""" wraps sys.exc_info() objects and offers
|
|
||||||
help for navigating the traceback.
|
|
||||||
"""
|
|
||||||
_striptext = ''
|
|
||||||
def __init__(self, tup=None, exprinfo=None):
|
|
||||||
# NB. all attributes are private! Subclasses or other
|
|
||||||
# ExceptionInfo-like classes may have different attributes.
|
|
||||||
if tup is None:
|
|
||||||
tup = exc_info()
|
|
||||||
if exprinfo is None and isinstance(tup[1], py.magic.AssertionError):
|
|
||||||
exprinfo = tup[1].msg
|
|
||||||
if exprinfo and exprinfo.startswith('assert '):
|
|
||||||
self._striptext = 'AssertionError: '
|
|
||||||
self._excinfo = tup
|
|
||||||
self.type, self.value, tb = self._excinfo
|
|
||||||
self.typename = self.type.__name__
|
|
||||||
self.traceback = py.code.Traceback(tb)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
|
|
||||||
|
|
||||||
def exconly(self, tryshort=False):
|
|
||||||
""" return the exception as a string
|
|
||||||
|
|
||||||
when 'tryshort' resolves to True, and the exception is a
|
|
||||||
py.magic.AssertionError, only the actual exception part of
|
|
||||||
the exception representation is returned (so 'AssertionError: ' is
|
|
||||||
removed from the beginning)
|
|
||||||
"""
|
|
||||||
lines = py.std.traceback.format_exception_only(self.type, self.value)
|
|
||||||
text = ''.join(lines)
|
|
||||||
text = text.rstrip()
|
|
||||||
if tryshort:
|
|
||||||
if text.startswith(self._striptext):
|
|
||||||
text = text[len(self._striptext):]
|
|
||||||
return text
|
|
||||||
|
|
||||||
def errisinstance(self, exc):
|
|
||||||
""" return True if the exception is an instance of exc """
|
|
||||||
return isinstance(self.value, exc)
|
|
||||||
|
|
||||||
def _getreprcrash(self):
|
|
||||||
exconly = self.exconly(tryshort=True)
|
|
||||||
entry = self.traceback.getcrashentry()
|
|
||||||
path, lineno = entry.path, entry.lineno
|
|
||||||
reprcrash = ReprFileLocation(path, lineno+1, exconly)
|
|
||||||
return reprcrash
|
|
||||||
|
|
||||||
def getrepr(self, showlocals=False, style="long",
|
|
||||||
abspath=False, tbfilter=True, funcargs=False):
|
|
||||||
""" return str()able representation of this exception info.
|
|
||||||
showlocals: show locals per traceback entry
|
|
||||||
style: long|short|no traceback style
|
|
||||||
tbfilter: hide entries (where __tracebackhide__ is true)
|
|
||||||
"""
|
|
||||||
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
|
|
||||||
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
|
|
||||||
return fmt.repr_excinfo(self)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
entry = self.traceback[-1]
|
|
||||||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
|
||||||
return str(loc)
|
|
||||||
|
|
||||||
class FormattedExcinfo(object):
|
|
||||||
""" presenting information about failing Functions and Generators. """
|
|
||||||
# for traceback entries
|
|
||||||
flow_marker = ">"
|
|
||||||
fail_marker = "E"
|
|
||||||
|
|
||||||
def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
|
|
||||||
self.showlocals = showlocals
|
|
||||||
self.style = style
|
|
||||||
self.tbfilter = tbfilter
|
|
||||||
self.funcargs = funcargs
|
|
||||||
self.abspath = abspath
|
|
||||||
|
|
||||||
def _getindent(self, source):
|
|
||||||
# figure out indent for given source
|
|
||||||
try:
|
|
||||||
s = str(source.getstatement(len(source)-1))
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
try:
|
|
||||||
s = str(source[-1])
|
|
||||||
except:
|
|
||||||
return 0
|
|
||||||
return 4 + (len(s) - len(s.lstrip()))
|
|
||||||
|
|
||||||
def _getentrysource(self, entry):
|
|
||||||
source = entry.getsource()
|
|
||||||
if source is not None:
|
|
||||||
source = source.deindent()
|
|
||||||
return source
|
|
||||||
|
|
||||||
def _saferepr(self, obj):
|
|
||||||
return safe_repr._repr(obj)
|
|
||||||
|
|
||||||
def repr_args(self, entry):
|
|
||||||
if self.funcargs:
|
|
||||||
args = []
|
|
||||||
for argname, argvalue in entry.frame.getargs():
|
|
||||||
args.append((argname, self._saferepr(argvalue)))
|
|
||||||
return ReprFuncArgs(args)
|
|
||||||
|
|
||||||
def get_source(self, source, line_index=-1, excinfo=None):
|
|
||||||
""" return formatted and marked up source lines. """
|
|
||||||
lines = []
|
|
||||||
if source is None:
|
|
||||||
source = py.code.Source("???")
|
|
||||||
line_index = 0
|
|
||||||
if line_index < 0:
|
|
||||||
line_index += len(source)
|
|
||||||
for i in range(len(source)):
|
|
||||||
if i == line_index:
|
|
||||||
prefix = self.flow_marker + " "
|
|
||||||
else:
|
|
||||||
prefix = " "
|
|
||||||
line = prefix + source[i]
|
|
||||||
lines.append(line)
|
|
||||||
if excinfo is not None:
|
|
||||||
indent = self._getindent(source)
|
|
||||||
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def get_exconly(self, excinfo, indent=4, markall=False):
|
|
||||||
lines = []
|
|
||||||
indent = " " * indent
|
|
||||||
# get the real exception information out
|
|
||||||
exlines = excinfo.exconly(tryshort=True).split('\n')
|
|
||||||
failindent = self.fail_marker + indent[1:]
|
|
||||||
for line in exlines:
|
|
||||||
lines.append(failindent + line)
|
|
||||||
if not markall:
|
|
||||||
failindent = indent
|
|
||||||
return lines
|
|
||||||
|
|
||||||
def repr_locals(self, locals):
|
|
||||||
if self.showlocals:
|
|
||||||
lines = []
|
|
||||||
items = locals.items()
|
|
||||||
items.sort()
|
|
||||||
for name, value in items:
|
|
||||||
if name == '__builtins__':
|
|
||||||
lines.append("__builtins__ = <builtins>")
|
|
||||||
else:
|
|
||||||
# This formatting could all be handled by the
|
|
||||||
# _repr() function, which is only repr.Repr in
|
|
||||||
# disguise, so is very configurable.
|
|
||||||
str_repr = self._saferepr(value)
|
|
||||||
#if len(str_repr) < 70 or not isinstance(value,
|
|
||||||
# (list, tuple, dict)):
|
|
||||||
lines.append("%-10s = %s" %(name, str_repr))
|
|
||||||
#else:
|
|
||||||
# self._line("%-10s =\\" % (name,))
|
|
||||||
# # XXX
|
|
||||||
# py.std.pprint.pprint(value, stream=self.excinfowriter)
|
|
||||||
return ReprLocals(lines)
|
|
||||||
|
|
||||||
def repr_traceback_entry(self, entry, excinfo=None):
|
|
||||||
# excinfo is not None if this is the last tb entry
|
|
||||||
source = self._getentrysource(entry)
|
|
||||||
if source is None:
|
|
||||||
source = py.code.Source("???")
|
|
||||||
line_index = 0
|
|
||||||
else:
|
|
||||||
line_index = entry.lineno - entry.getfirstlinesource()
|
|
||||||
|
|
||||||
lines = []
|
|
||||||
if self.style == "long":
|
|
||||||
reprargs = self.repr_args(entry)
|
|
||||||
lines.extend(self.get_source(source, line_index, excinfo))
|
|
||||||
message = excinfo and excinfo.typename or ""
|
|
||||||
path = self._makepath(entry.path)
|
|
||||||
filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
|
|
||||||
localsrepr = self.repr_locals(entry.locals)
|
|
||||||
return ReprEntry(lines, reprargs, localsrepr, filelocrepr)
|
|
||||||
else:
|
|
||||||
if self.style == "short":
|
|
||||||
line = source[line_index].lstrip()
|
|
||||||
lines.append(' File "%s", line %d, in %s' % (
|
|
||||||
entry.path.basename, entry.lineno+1, entry.name))
|
|
||||||
lines.append(" " + line)
|
|
||||||
if excinfo:
|
|
||||||
lines.extend(self.get_exconly(excinfo, indent=4))
|
|
||||||
return ReprEntry(lines, None, None, None)
|
|
||||||
|
|
||||||
def _makepath(self, path):
|
|
||||||
if not self.abspath:
|
|
||||||
np = py.path.local().bestrelpath(path)
|
|
||||||
if len(np) < len(str(path)):
|
|
||||||
path = np
|
|
||||||
return path
|
|
||||||
|
|
||||||
def repr_traceback(self, excinfo):
|
|
||||||
traceback = excinfo.traceback
|
|
||||||
if self.tbfilter:
|
|
||||||
traceback = traceback.filter()
|
|
||||||
recursionindex = None
|
|
||||||
if excinfo.errisinstance(RuntimeError):
|
|
||||||
recursionindex = traceback.recursionindex()
|
|
||||||
last = traceback[-1]
|
|
||||||
entries = []
|
|
||||||
extraline = None
|
|
||||||
for index, entry in py.builtin.enumerate(traceback):
|
|
||||||
einfo = (last == entry) and excinfo or None
|
|
||||||
reprentry = self.repr_traceback_entry(entry, einfo)
|
|
||||||
entries.append(reprentry)
|
|
||||||
if index == recursionindex:
|
|
||||||
extraline = "!!! Recursion detected (same locals & position)"
|
|
||||||
break
|
|
||||||
return ReprTraceback(entries, extraline, style=self.style)
|
|
||||||
|
|
||||||
def repr_excinfo(self, excinfo):
|
|
||||||
reprtraceback = self.repr_traceback(excinfo)
|
|
||||||
reprcrash = excinfo._getreprcrash()
|
|
||||||
return ReprExceptionInfo(reprtraceback, reprcrash)
|
|
||||||
|
|
||||||
class Repr:
|
|
||||||
def __str__(self):
|
|
||||||
tw = py.io.TerminalWriter(stringio=True)
|
|
||||||
self.toterminal(tw)
|
|
||||||
return tw.stringio.getvalue().strip()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s instance at %0x>" %(self.__class__, id(self))
|
|
||||||
|
|
||||||
class ReprExceptionInfo(Repr):
|
|
||||||
def __init__(self, reprtraceback, reprcrash):
|
|
||||||
self.reprtraceback = reprtraceback
|
|
||||||
self.reprcrash = reprcrash
|
|
||||||
self.sections = []
|
|
||||||
|
|
||||||
def addsection(self, name, content, sep="-"):
|
|
||||||
self.sections.append((name, content, sep))
|
|
||||||
|
|
||||||
def toterminal(self, tw):
|
|
||||||
self.reprtraceback.toterminal(tw)
|
|
||||||
for name, content, sep in self.sections:
|
|
||||||
tw.sep(sep, name)
|
|
||||||
tw.line(content)
|
|
||||||
|
|
||||||
class ReprTraceback(Repr):
|
|
||||||
entrysep = "_ "
|
|
||||||
|
|
||||||
def __init__(self, reprentries, extraline, style):
|
|
||||||
self.reprentries = reprentries
|
|
||||||
self.extraline = extraline
|
|
||||||
self.style = style
|
|
||||||
|
|
||||||
def toterminal(self, tw):
|
|
||||||
sepok = False
|
|
||||||
for entry in self.reprentries:
|
|
||||||
if self.style == "long":
|
|
||||||
if sepok:
|
|
||||||
tw.sep(self.entrysep)
|
|
||||||
tw.line("")
|
|
||||||
sepok = True
|
|
||||||
entry.toterminal(tw)
|
|
||||||
if self.extraline:
|
|
||||||
tw.line(self.extraline)
|
|
||||||
|
|
||||||
class ReprEntry(Repr):
|
|
||||||
localssep = "_ "
|
|
||||||
|
|
||||||
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr):
|
|
||||||
self.lines = lines
|
|
||||||
self.reprfuncargs = reprfuncargs
|
|
||||||
self.reprlocals = reprlocals
|
|
||||||
self.reprfileloc = filelocrepr
|
|
||||||
|
|
||||||
def toterminal(self, tw):
|
|
||||||
if self.reprfuncargs:
|
|
||||||
self.reprfuncargs.toterminal(tw)
|
|
||||||
for line in self.lines:
|
|
||||||
red = line.startswith("E ")
|
|
||||||
tw.line(line, bold=True, red=red)
|
|
||||||
if self.reprlocals:
|
|
||||||
#tw.sep(self.localssep, "Locals")
|
|
||||||
tw.line("")
|
|
||||||
self.reprlocals.toterminal(tw)
|
|
||||||
if self.reprfileloc:
|
|
||||||
tw.line("")
|
|
||||||
self.reprfileloc.toterminal(tw)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s\n%s\n%s" % ("\n".join(self.lines),
|
|
||||||
self.reprlocals,
|
|
||||||
self.reprfileloc)
|
|
||||||
|
|
||||||
class ReprFileLocation(Repr):
|
|
||||||
def __init__(self, path, lineno, message):
|
|
||||||
self.path = str(path)
|
|
||||||
self.lineno = lineno
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def toterminal(self, tw):
|
|
||||||
# filename and lineno output for each entry,
|
|
||||||
# using an output format that most editors unterstand
|
|
||||||
msg = self.message
|
|
||||||
i = msg.find("\n")
|
|
||||||
if i != -1:
|
|
||||||
msg = msg[:i]
|
|
||||||
tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
|
|
||||||
|
|
||||||
class ReprLocals(Repr):
|
|
||||||
def __init__(self, lines):
|
|
||||||
self.lines = lines
|
|
||||||
|
|
||||||
def toterminal(self, tw):
|
|
||||||
for line in self.lines:
|
|
||||||
tw.line(line)
|
|
||||||
|
|
||||||
class ReprFuncArgs(Repr):
|
|
||||||
def __init__(self, args):
|
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def toterminal(self, tw):
|
|
||||||
if self.args:
|
|
||||||
linesofar = ""
|
|
||||||
for name, value in self.args:
|
|
||||||
ns = "%s = %s" %(name, value)
|
|
||||||
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
|
|
||||||
if linesofar:
|
|
||||||
tw.line(linesofar)
|
|
||||||
linesofar = ns
|
|
||||||
else:
|
|
||||||
if linesofar:
|
|
||||||
linesofar += ", " + ns
|
|
||||||
else:
|
|
||||||
linesofar = ns
|
|
||||||
if linesofar:
|
|
||||||
tw.line(linesofar)
|
|
||||||
tw.line("")
|
|
|
@ -1,60 +0,0 @@
|
||||||
import py
|
|
||||||
import py.__.code.safe_repr
|
|
||||||
|
|
||||||
class Frame(object):
|
|
||||||
"""Wrapper around a Python frame holding f_locals and f_globals
|
|
||||||
in which expressions can be evaluated."""
|
|
||||||
|
|
||||||
def __init__(self, frame):
|
|
||||||
self.code = py.code.Code(frame.f_code)
|
|
||||||
self.lineno = frame.f_lineno - 1
|
|
||||||
self.f_globals = frame.f_globals
|
|
||||||
self.f_locals = frame.f_locals
|
|
||||||
self.raw = frame
|
|
||||||
|
|
||||||
def statement(self):
|
|
||||||
if self.code.fullsource is None:
|
|
||||||
return py.code.Source("")
|
|
||||||
return self.code.fullsource.getstatement(self.lineno)
|
|
||||||
statement = property(statement, None, None,
|
|
||||||
"statement this frame is at")
|
|
||||||
|
|
||||||
def eval(self, code, **vars):
|
|
||||||
""" evaluate 'code' in the frame
|
|
||||||
|
|
||||||
'vars' are optional additional local variables
|
|
||||||
|
|
||||||
returns the result of the evaluation
|
|
||||||
"""
|
|
||||||
f_locals = self.f_locals.copy()
|
|
||||||
f_locals.update(vars)
|
|
||||||
return eval(code, self.f_globals, f_locals)
|
|
||||||
|
|
||||||
def exec_(self, code, **vars):
|
|
||||||
""" exec 'code' in the frame
|
|
||||||
|
|
||||||
'vars' are optiona; additional local variables
|
|
||||||
"""
|
|
||||||
f_locals = self.f_locals.copy()
|
|
||||||
f_locals.update(vars)
|
|
||||||
exec code in self.f_globals, f_locals
|
|
||||||
|
|
||||||
def repr(self, object):
|
|
||||||
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
|
|
||||||
"""
|
|
||||||
return py.__.code.safe_repr._repr(object)
|
|
||||||
|
|
||||||
def is_true(self, object):
|
|
||||||
return object
|
|
||||||
|
|
||||||
def getargs(self):
|
|
||||||
""" return a list of tuples (name, value) for all arguments
|
|
||||||
"""
|
|
||||||
retval = []
|
|
||||||
for arg in self.code.getargs():
|
|
||||||
try:
|
|
||||||
retval.append((arg, self.f_locals[arg]))
|
|
||||||
except KeyError:
|
|
||||||
pass # this can occur when using Psyco
|
|
||||||
return retval
|
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
"""Defines a safe repr function. This will always return a string of "reasonable" length
|
|
||||||
no matter what the object does in it's own repr function. Let's examine what can go wrong
|
|
||||||
in an arbitrary repr function.
|
|
||||||
The default repr will return something like (on Win32 anyway):
|
|
||||||
<foo.bar object at 0x008D5650>. Well behaved user-defined repr() methods will do similar.
|
|
||||||
The usual expectation is that repr will return a single line string.
|
|
||||||
|
|
||||||
1. However, the repr method can raise an exception of an arbitrary type.
|
|
||||||
|
|
||||||
Also, the return value may not be as expected:
|
|
||||||
2. The return value may not be a string!
|
|
||||||
3. The return value may not be a single line string, it may contain line breaks.
|
|
||||||
4. The method may enter a loop and never return.
|
|
||||||
5. The return value may be enormous, eg range(100000)
|
|
||||||
|
|
||||||
The standard library has a nice implementation in the repr module that will do the job,
|
|
||||||
but the exception
|
|
||||||
handling is silent, so the the output contains no clue that repr() call raised an
|
|
||||||
exception. I would like to be told if repr raises an exception, it's a serious error, so
|
|
||||||
a sublass of repr overrides the method that does repr for class instances."""
|
|
||||||
|
|
||||||
|
|
||||||
import repr
|
|
||||||
import __builtin__
|
|
||||||
|
|
||||||
|
|
||||||
class SafeRepr(repr.Repr):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
repr.Repr.__init__(self, *args, **kwargs)
|
|
||||||
# Do we need a commandline switch for this?
|
|
||||||
self.maxstring = 240 # 3 * 80 chars
|
|
||||||
self.maxother = 160 # 2 * 80 chars
|
|
||||||
|
|
||||||
def repr(self, x):
|
|
||||||
return self._callhelper(repr.Repr.repr, self, x)
|
|
||||||
|
|
||||||
def repr_instance(self, x, level):
|
|
||||||
return self._callhelper(__builtin__.repr, x)
|
|
||||||
|
|
||||||
def _callhelper(self, call, x, *args):
|
|
||||||
try:
|
|
||||||
# Try the vanilla repr and make sure that the result is a string
|
|
||||||
s = call(x, *args)
|
|
||||||
except (KeyboardInterrupt, MemoryError, SystemExit):
|
|
||||||
raise
|
|
||||||
except Exception ,e:
|
|
||||||
try:
|
|
||||||
exc_name = e.__class__.__name__
|
|
||||||
except:
|
|
||||||
exc_name = 'unknown'
|
|
||||||
try:
|
|
||||||
exc_info = str(e)
|
|
||||||
except:
|
|
||||||
exc_info = 'unknown'
|
|
||||||
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % \
|
|
||||||
(exc_name, exc_info, x.__class__.__name__, id(x))
|
|
||||||
except:
|
|
||||||
try:
|
|
||||||
name = x.__class__.__name__
|
|
||||||
except:
|
|
||||||
name = 'unknown'
|
|
||||||
return '<[unknown exception raised in repr()] %s object at 0x%x>' % \
|
|
||||||
(name, id(x))
|
|
||||||
if len(s) > self.maxstring:
|
|
||||||
i = max(0, (self.maxstring-3)//2)
|
|
||||||
j = max(0, self.maxstring-3-i)
|
|
||||||
s = s[:i] + '...' + s[len(s)-j:]
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
_repr = SafeRepr().repr
|
|
|
@ -181,7 +181,8 @@ class Source(object):
|
||||||
source = "\n".join(self.lines) + '\n'
|
source = "\n".join(self.lines) + '\n'
|
||||||
try:
|
try:
|
||||||
co = cpy_compile(source, filename, mode, flag)
|
co = cpy_compile(source, filename, mode, flag)
|
||||||
except SyntaxError, ex:
|
except SyntaxError:
|
||||||
|
ex = sys.exc_info()[1]
|
||||||
# re-represent syntax errors from parsing python strings
|
# re-represent syntax errors from parsing python strings
|
||||||
msglines = self.lines[:ex.lineno]
|
msglines = self.lines[:ex.lineno]
|
||||||
if ex.offset:
|
if ex.offset:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import generators
|
from __future__ import generators
|
||||||
import py
|
import py
|
||||||
import new
|
import sys, new
|
||||||
|
from py.__.code.code import safe_repr
|
||||||
|
|
||||||
def test_newcode():
|
def test_newcode():
|
||||||
source = "i = 3"
|
source = "i = 3"
|
||||||
|
@ -99,3 +100,69 @@ def test_code_source():
|
||||||
expected = """def x():
|
expected = """def x():
|
||||||
pass"""
|
pass"""
|
||||||
assert str(src) == expected
|
assert str(src) == expected
|
||||||
|
|
||||||
|
def test_frame_getsourcelineno_myself():
|
||||||
|
def func():
|
||||||
|
return sys._getframe(0)
|
||||||
|
f = func()
|
||||||
|
f = py.code.Frame(f)
|
||||||
|
source, lineno = f.code.fullsource, f.lineno
|
||||||
|
assert source[lineno].startswith(" return sys._getframe(0)")
|
||||||
|
|
||||||
|
def test_getstatement_empty_fullsource():
|
||||||
|
def func():
|
||||||
|
return sys._getframe(0)
|
||||||
|
f = func()
|
||||||
|
f = py.code.Frame(f)
|
||||||
|
prop = f.code.__class__.fullsource
|
||||||
|
try:
|
||||||
|
f.code.__class__.fullsource = None
|
||||||
|
assert f.statement == py.code.Source("")
|
||||||
|
finally:
|
||||||
|
f.code.__class__.fullsource = prop
|
||||||
|
|
||||||
|
def test_code_from_func():
|
||||||
|
co = py.code.Code(test_frame_getsourcelineno_myself)
|
||||||
|
assert co.firstlineno
|
||||||
|
assert co.path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestSafeRepr:
|
||||||
|
def test_simple_repr(self):
|
||||||
|
assert safe_repr(1) == '1'
|
||||||
|
assert safe_repr(None) == 'None'
|
||||||
|
|
||||||
|
def test_exceptions(self):
|
||||||
|
class BrokenRepr:
|
||||||
|
def __init__(self, ex):
|
||||||
|
self.ex = ex
|
||||||
|
foo = 0
|
||||||
|
def __repr__(self):
|
||||||
|
raise self.ex
|
||||||
|
class BrokenReprException(Exception):
|
||||||
|
__str__ = None
|
||||||
|
__repr__ = None
|
||||||
|
assert 'Exception' in safe_repr(BrokenRepr(Exception("broken")))
|
||||||
|
s = safe_repr(BrokenReprException("really broken"))
|
||||||
|
assert 'TypeError' in s
|
||||||
|
if py.std.sys.version_info < (2,6):
|
||||||
|
assert 'unknown' in safe_repr(BrokenRepr("string"))
|
||||||
|
else:
|
||||||
|
assert 'TypeError' in safe_repr(BrokenRepr("string"))
|
||||||
|
|
||||||
|
def test_big_repr(self):
|
||||||
|
from py.__.code.code import SafeRepr
|
||||||
|
assert len(safe_repr(range(1000))) <= \
|
||||||
|
len('[' + SafeRepr().maxlist * "1000" + ']')
|
||||||
|
|
||||||
|
def test_repr_on_newstyle(self):
|
||||||
|
class Function(object):
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s>" %(self.name)
|
||||||
|
try:
|
||||||
|
s = safe_repr(Function())
|
||||||
|
except Exception, e:
|
||||||
|
py.test.fail("saferepr failed for newstyle class")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import py
|
import py
|
||||||
from py.__.code.excinfo import FormattedExcinfo, ReprExceptionInfo
|
from py.__.code.code import FormattedExcinfo, ReprExceptionInfo
|
||||||
|
|
||||||
class TWMock:
|
class TWMock:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -1,27 +1,2 @@
|
||||||
import sys
|
import sys
|
||||||
import py
|
import py
|
||||||
|
|
||||||
def test_frame_getsourcelineno_myself():
|
|
||||||
def func():
|
|
||||||
return sys._getframe(0)
|
|
||||||
f = func()
|
|
||||||
f = py.code.Frame(f)
|
|
||||||
source, lineno = f.code.fullsource, f.lineno
|
|
||||||
assert source[lineno].startswith(" return sys._getframe(0)")
|
|
||||||
|
|
||||||
def test_getstatement_empty_fullsource():
|
|
||||||
def func():
|
|
||||||
return sys._getframe(0)
|
|
||||||
f = func()
|
|
||||||
f = py.code.Frame(f)
|
|
||||||
prop = f.code.__class__.fullsource
|
|
||||||
try:
|
|
||||||
f.code.__class__.fullsource = None
|
|
||||||
assert f.statement == py.code.Source("")
|
|
||||||
finally:
|
|
||||||
f.code.__class__.fullsource = prop
|
|
||||||
|
|
||||||
def test_code_from_func():
|
|
||||||
co = py.code.Code(test_frame_getsourcelineno_myself)
|
|
||||||
assert co.firstlineno
|
|
||||||
assert co.path
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
|
|
||||||
import py
|
|
||||||
from py.__.code import safe_repr
|
|
||||||
|
|
||||||
def test_simple_repr():
|
|
||||||
assert safe_repr._repr(1) == '1'
|
|
||||||
assert safe_repr._repr(None) == 'None'
|
|
||||||
|
|
||||||
class BrokenRepr:
|
|
||||||
def __init__(self, ex):
|
|
||||||
self.ex = ex
|
|
||||||
foo = 0
|
|
||||||
def __repr__(self):
|
|
||||||
raise self.ex
|
|
||||||
|
|
||||||
def test_exception():
|
|
||||||
assert 'Exception' in safe_repr._repr(BrokenRepr(Exception("broken")))
|
|
||||||
|
|
||||||
class BrokenReprException(Exception):
|
|
||||||
__str__ = None
|
|
||||||
__repr__ = None
|
|
||||||
|
|
||||||
def test_broken_exception():
|
|
||||||
assert 'Exception' in safe_repr._repr(BrokenRepr(BrokenReprException("really broken")))
|
|
||||||
|
|
||||||
def test_string_exception():
|
|
||||||
if py.std.sys.version_info < (2,6):
|
|
||||||
assert 'unknown' in safe_repr._repr(BrokenRepr("string"))
|
|
||||||
else:
|
|
||||||
assert 'TypeError' in safe_repr._repr(BrokenRepr("string"))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_big_repr():
|
|
||||||
assert len(safe_repr._repr(range(1000))) <= \
|
|
||||||
len('[' + safe_repr.SafeRepr().maxlist * "1000" + ']')
|
|
||||||
|
|
||||||
def test_repr_on_newstyle():
|
|
||||||
class Function(object):
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s>" %(self.name)
|
|
||||||
try:
|
|
||||||
s = safe_repr._repr(Function())
|
|
||||||
except Exception, e:
|
|
||||||
py.test.fail("saferepr failed for newstyle class")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,199 +0,0 @@
|
||||||
from __future__ import generators
|
|
||||||
import py
|
|
||||||
import sys
|
|
||||||
|
|
||||||
class TracebackEntry(object):
|
|
||||||
""" a single entry in a traceback """
|
|
||||||
|
|
||||||
exprinfo = None
|
|
||||||
|
|
||||||
def __init__(self, rawentry):
|
|
||||||
self._rawentry = rawentry
|
|
||||||
self.frame = py.code.Frame(rawentry.tb_frame)
|
|
||||||
# Ugh. 2.4 and 2.5 differs here when encountering
|
|
||||||
# multi-line statements. Not sure about the solution, but
|
|
||||||
# should be portable
|
|
||||||
self.lineno = rawentry.tb_lineno - 1
|
|
||||||
self.relline = self.lineno - self.frame.code.firstlineno
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
|
|
||||||
|
|
||||||
def statement(self):
|
|
||||||
""" return a py.code.Source object for the current statement """
|
|
||||||
source = self.frame.code.fullsource
|
|
||||||
return source.getstatement(self.lineno)
|
|
||||||
statement = property(statement, None, None,
|
|
||||||
"statement of this traceback entry.")
|
|
||||||
|
|
||||||
def path(self):
|
|
||||||
return self.frame.code.path
|
|
||||||
path = property(path, None, None, "path to the full source code")
|
|
||||||
|
|
||||||
def getlocals(self):
|
|
||||||
return self.frame.f_locals
|
|
||||||
locals = property(getlocals, None, None, "locals of underlaying frame")
|
|
||||||
|
|
||||||
def reinterpret(self):
|
|
||||||
"""Reinterpret the failing statement and returns a detailed information
|
|
||||||
about what operations are performed."""
|
|
||||||
if self.exprinfo is None:
|
|
||||||
from py.__.magic import exprinfo
|
|
||||||
source = str(self.statement).strip()
|
|
||||||
x = exprinfo.interpret(source, self.frame, should_fail=True)
|
|
||||||
if not isinstance(x, str):
|
|
||||||
raise TypeError, "interpret returned non-string %r" % (x,)
|
|
||||||
self.exprinfo = x
|
|
||||||
return self.exprinfo
|
|
||||||
|
|
||||||
def getfirstlinesource(self):
|
|
||||||
return self.frame.code.firstlineno
|
|
||||||
|
|
||||||
def getsource(self):
|
|
||||||
""" return failing source code. """
|
|
||||||
source = self.frame.code.fullsource
|
|
||||||
if source is None:
|
|
||||||
return None
|
|
||||||
start = self.getfirstlinesource()
|
|
||||||
end = self.lineno
|
|
||||||
try:
|
|
||||||
_, end = source.getstatementrange(end)
|
|
||||||
except IndexError:
|
|
||||||
end = self.lineno + 1
|
|
||||||
# heuristic to stop displaying source on e.g.
|
|
||||||
# if something: # assume this causes a NameError
|
|
||||||
# # _this_ lines and the one
|
|
||||||
# below we don't want from entry.getsource()
|
|
||||||
for i in range(self.lineno, end):
|
|
||||||
if source[i].rstrip().endswith(':'):
|
|
||||||
end = i + 1
|
|
||||||
break
|
|
||||||
return source[start:end]
|
|
||||||
source = property(getsource)
|
|
||||||
|
|
||||||
def ishidden(self):
|
|
||||||
""" return True if the current frame has a var __tracebackhide__
|
|
||||||
resolving to True
|
|
||||||
|
|
||||||
mostly for internal use
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self.frame.eval("__tracebackhide__")
|
|
||||||
except (SystemExit, KeyboardInterrupt):
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
try:
|
|
||||||
fn = str(self.path)
|
|
||||||
except py.error.Error:
|
|
||||||
fn = '???'
|
|
||||||
name = self.frame.code.name
|
|
||||||
try:
|
|
||||||
line = str(self.statement).lstrip()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
line = "???"
|
|
||||||
return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
|
|
||||||
|
|
||||||
def name(self):
|
|
||||||
return self.frame.code.raw.co_name
|
|
||||||
name = property(name, None, None, "co_name of underlaying code")
|
|
||||||
|
|
||||||
class Traceback(list):
|
|
||||||
""" Traceback objects encapsulate and offer higher level
|
|
||||||
access to Traceback entries.
|
|
||||||
"""
|
|
||||||
Entry = TracebackEntry
|
|
||||||
def __init__(self, tb):
|
|
||||||
""" initialize from given python traceback object. """
|
|
||||||
if hasattr(tb, 'tb_next'):
|
|
||||||
def f(cur):
|
|
||||||
while cur is not None:
|
|
||||||
yield self.Entry(cur)
|
|
||||||
cur = cur.tb_next
|
|
||||||
list.__init__(self, f(tb))
|
|
||||||
else:
|
|
||||||
list.__init__(self, tb)
|
|
||||||
|
|
||||||
def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
|
|
||||||
""" return a Traceback instance wrapping part of this Traceback
|
|
||||||
|
|
||||||
by provding any combination of path, lineno and firstlineno, the
|
|
||||||
first frame to start the to-be-returned traceback is determined
|
|
||||||
|
|
||||||
this allows cutting the first part of a Traceback instance e.g.
|
|
||||||
for formatting reasons (removing some uninteresting bits that deal
|
|
||||||
with handling of the exception/traceback)
|
|
||||||
"""
|
|
||||||
for x in self:
|
|
||||||
code = x.frame.code
|
|
||||||
codepath = code.path
|
|
||||||
if ((path is None or codepath == path) and
|
|
||||||
(excludepath is None or (hasattr(codepath, 'relto') and
|
|
||||||
not codepath.relto(excludepath))) and
|
|
||||||
(lineno is None or x.lineno == lineno) and
|
|
||||||
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
|
|
||||||
return Traceback(x._rawentry)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
val = super(Traceback, self).__getitem__(key)
|
|
||||||
if isinstance(key, type(slice(0))):
|
|
||||||
val = self.__class__(val)
|
|
||||||
return val
|
|
||||||
|
|
||||||
def filter(self, fn=lambda x: not x.ishidden()):
|
|
||||||
""" return a Traceback instance with certain items removed
|
|
||||||
|
|
||||||
fn is a function that gets a single argument, a TracebackItem
|
|
||||||
instance, and should return True when the item should be added
|
|
||||||
to the Traceback, False when not
|
|
||||||
|
|
||||||
by default this removes all the TracebackItems which are hidden
|
|
||||||
(see ishidden() above)
|
|
||||||
"""
|
|
||||||
return Traceback(filter(fn, self))
|
|
||||||
|
|
||||||
def getcrashentry(self):
|
|
||||||
""" return last non-hidden traceback entry that lead
|
|
||||||
to the exception of a traceback.
|
|
||||||
"""
|
|
||||||
tb = self.filter()
|
|
||||||
if not tb:
|
|
||||||
tb = self
|
|
||||||
return tb[-1]
|
|
||||||
|
|
||||||
def recursionindex(self):
|
|
||||||
""" return the index of the frame/TracebackItem where recursion
|
|
||||||
originates if appropriate, None if no recursion occurred
|
|
||||||
"""
|
|
||||||
cache = {}
|
|
||||||
for i, entry in py.builtin.enumerate(self):
|
|
||||||
key = entry.frame.code.path, entry.lineno
|
|
||||||
#print "checking for recursion at", key
|
|
||||||
l = cache.setdefault(key, [])
|
|
||||||
if l:
|
|
||||||
f = entry.frame
|
|
||||||
loc = f.f_locals
|
|
||||||
for otherloc in l:
|
|
||||||
if f.is_true(f.eval(co_equal,
|
|
||||||
__recursioncache_locals_1=loc,
|
|
||||||
__recursioncache_locals_2=otherloc)):
|
|
||||||
return i
|
|
||||||
l.append(entry.frame.f_locals)
|
|
||||||
return None
|
|
||||||
|
|
||||||
# def __str__(self):
|
|
||||||
# for x in self
|
|
||||||
# l = []
|
|
||||||
## for func, entry in self._tblist:
|
|
||||||
# l.append(entry.display())
|
|
||||||
# return "".join(l)
|
|
||||||
|
|
||||||
|
|
||||||
co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
|
|
||||||
'?', 'eval')
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ as well.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py
|
import py
|
||||||
from py.__.code.excinfo import Repr, ReprFileLocation
|
from py.__.code.code import TerminalRepr, ReprFileLocation
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
group = parser.addgroup("doctest options")
|
group = parser.addgroup("doctest options")
|
||||||
|
@ -30,7 +30,7 @@ def pytest_collect_file(path, parent):
|
||||||
if path.check(fnmatch="test_*.txt"):
|
if path.check(fnmatch="test_*.txt"):
|
||||||
return DoctestTextfile(path, parent)
|
return DoctestTextfile(path, parent)
|
||||||
|
|
||||||
class ReprFailDoctest(Repr):
|
class ReprFailDoctest(TerminalRepr):
|
||||||
def __init__(self, reprlocation, lines):
|
def __init__(self, reprlocation, lines):
|
||||||
self.reprlocation = reprlocation
|
self.reprlocation = reprlocation
|
||||||
self.lines = lines
|
self.lines = lines
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import py
|
import py
|
||||||
from py.__.test.plugin import pytest_runner as runner
|
from py.__.test.plugin import pytest_runner as runner
|
||||||
from py.__.code.excinfo import ReprExceptionInfo
|
from py.__.code.code import ReprExceptionInfo
|
||||||
|
|
||||||
class TestSetupState:
|
class TestSetupState:
|
||||||
def test_setup(self, testdir):
|
def test_setup(self, testdir):
|
||||||
|
|
Loading…
Reference in New Issue