3
from inspect import CO_VARARGS, CO_VARKEYWORDS
7
reprlib = py.builtin._tryimport('repr', 'reprlib')
9
if sys.version_info[0] >= 3:
10
from traceback import format_exception_only
12
from py._code._py2traceback import format_exception_only
15
""" wrapper around Python code objects """
16
def __init__(self, rawcode):
17
if not hasattr(rawcode, "co_filename"):
18
rawcode = py.code.getrawcode(rawcode)
20
self.filename = rawcode.co_filename
21
self.firstlineno = rawcode.co_firstlineno - 1
22
self.name = rawcode.co_name
23
except AttributeError:
24
raise TypeError("not a code object: %r" %(rawcode,))
27
def __eq__(self, other):
28
return self.raw == other.raw
30
def __ne__(self, other):
31
return not self == other
35
""" return a path object pointing to source code (note that it
36
might not point to an actually existing file). """
37
p = py.path.local(self.raw.co_filename)
38
# maybe don't try this checking
40
# XXX maybe try harder like the weird logic
41
# in the standard lib [linecache.updatecache] does?
42
p = self.raw.co_filename
47
""" return a py.code.Source object for the full source file of the code
49
from py._code import source
50
full, _ = source.findsource(self.raw)
54
""" return a py.code.Source object for the code object's source only
56
# return source only for that part of code
57
return py.code.Source(self.raw)
59
def getargs(self, var=False):
60
""" return a tuple with the argument names for the code object
62
if 'var' is set True also return the names of the variable and
63
keyword arguments when present
65
# handfull shortcut for getting args
67
argcount = raw.co_argcount
69
argcount += raw.co_flags & CO_VARARGS
70
argcount += raw.co_flags & CO_VARKEYWORDS
71
return raw.co_varnames[:argcount]
74
"""Wrapper around a Python frame holding f_locals and f_globals
75
in which expressions can be evaluated."""
77
def __init__(self, frame):
78
self.lineno = frame.f_lineno - 1
79
self.f_globals = frame.f_globals
80
self.f_locals = frame.f_locals
82
self.code = py.code.Code(frame.f_code)
86
""" statement this frame is at """
87
if self.code.fullsource is None:
88
return py.code.Source("")
89
return self.code.fullsource.getstatement(self.lineno)
91
def eval(self, code, **vars):
92
""" evaluate 'code' in the frame
94
'vars' are optional additional local variables
96
returns the result of the evaluation
98
f_locals = self.f_locals.copy()
100
return eval(code, self.f_globals, f_locals)
102
def exec_(self, code, **vars):
103
""" exec 'code' in the frame
105
'vars' are optiona; additional local variables
107
f_locals = self.f_locals.copy()
108
f_locals.update(vars)
109
py.builtin.exec_(code, self.f_globals, f_locals )
111
def repr(self, object):
112
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
114
return py.io.saferepr(object)
116
def is_true(self, object):
119
def getargs(self, var=False):
120
""" return a list of tuples (name, value) for all arguments
122
if 'var' is set True also include the variable and keyword
123
arguments when present
126
for arg in self.code.getargs(var):
128
retval.append((arg, self.f_locals[arg]))
130
pass # this can occur when using Psyco
133
class TracebackEntry(object):
134
""" a single entry in a traceback """
139
def __init__(self, rawentry):
140
self._rawentry = rawentry
141
self.lineno = rawentry.tb_lineno - 1
143
def set_repr_style(self, mode):
144
assert mode in ("short", "long")
145
self._repr_style = mode
149
return py.code.Frame(self._rawentry.tb_frame)
153
return self.lineno - self.frame.code.firstlineno
156
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
160
""" py.code.Source object for the current statement """
161
source = self.frame.code.fullsource
162
return source.getstatement(self.lineno)
166
""" path to the source code """
167
return self.frame.code.path
170
return self.frame.f_locals
171
locals = property(getlocals, None, None, "locals of underlaying frame")
173
def reinterpret(self):
174
"""Reinterpret the failing statement and returns a detailed information
175
about what operations are performed."""
176
if self.exprinfo is None:
177
source = str(self.statement).strip()
178
x = py.code._reinterpret(source, self.frame, should_fail=True)
179
if not isinstance(x, str):
180
raise TypeError("interpret returned non-string %r" % (x,))
184
def getfirstlinesource(self):
185
# on Jython this firstlineno can be -1 apparently
186
return max(self.frame.code.firstlineno, 0)
188
def getsource(self, astcache=None):
189
""" return failing source code. """
190
# we use the passed in astcache to not reparse asttrees
191
# within exception info printing
192
from py._code.source import getstatementrange_ast
193
source = self.frame.code.fullsource
197
if astcache is not None:
198
key = self.frame.code.path
200
astnode = astcache.get(key, None)
201
start = self.getfirstlinesource()
203
astnode, _, end = getstatementrange_ast(self.lineno, source,
206
end = self.lineno + 1
209
astcache[key] = astnode
210
return source[start:end]
212
source = property(getsource)
215
""" return True if the current frame has a var __tracebackhide__
218
mostly for internal use
221
return self.frame.f_locals['__tracebackhide__']
224
return self.frame.f_globals['__tracebackhide__']
231
except py.error.Error:
233
name = self.frame.code.name
235
line = str(self.statement).lstrip()
236
except KeyboardInterrupt:
240
return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
243
return self.frame.code.raw.co_name
244
name = property(name, None, None, "co_name of underlaying code")
246
class Traceback(list):
247
""" Traceback objects encapsulate and offer higher level
248
access to Traceback entries.
250
Entry = TracebackEntry
251
def __init__(self, tb):
252
""" initialize from given python traceback object. """
253
if hasattr(tb, 'tb_next'):
255
while cur is not None:
256
yield self.Entry(cur)
258
list.__init__(self, f(tb))
260
list.__init__(self, tb)
262
def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
263
""" return a Traceback instance wrapping part of this Traceback
265
by provding any combination of path, lineno and firstlineno, the
266
first frame to start the to-be-returned traceback is determined
268
this allows cutting the first part of a Traceback instance e.g.
269
for formatting reasons (removing some uninteresting bits that deal
270
with handling of the exception/traceback)
275
if ((path is None or codepath == path) and
276
(excludepath is None or not hasattr(codepath, 'relto') or
277
not codepath.relto(excludepath)) and
278
(lineno is None or x.lineno == lineno) and
279
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
280
return Traceback(x._rawentry)
283
def __getitem__(self, key):
284
val = super(Traceback, self).__getitem__(key)
285
if isinstance(key, type(slice(0))):
286
val = self.__class__(val)
289
def filter(self, fn=lambda x: not x.ishidden()):
290
""" return a Traceback instance with certain items removed
292
fn is a function that gets a single argument, a TracebackItem
293
instance, and should return True when the item should be added
294
to the Traceback, False when not
296
by default this removes all the TracebackItems which are hidden
297
(see ishidden() above)
299
return Traceback(filter(fn, self))
301
def getcrashentry(self):
302
""" return last non-hidden traceback entry that lead
303
to the exception of a traceback.
305
for i in range(-1, -len(self)-1, -1):
307
if not entry.ishidden():
311
def recursionindex(self):
312
""" return the index of the frame/TracebackItem where recursion
313
originates if appropriate, None if no recursion occurred
316
for i, entry in enumerate(self):
317
# id for the code.raw is needed to work around
318
# the strange metaprogramming in the decorator lib from pypi
319
# which generates code objects that have hash/value equality
321
key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
322
#print "checking for recursion at", key
323
l = cache.setdefault(key, [])
328
if f.is_true(f.eval(co_equal,
329
__recursioncache_locals_1=loc,
330
__recursioncache_locals_2=otherloc)):
332
l.append(entry.frame.f_locals)
335
co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
338
class ExceptionInfo(object):
339
""" wraps sys.exc_info() objects and offers
340
help for navigating the traceback.
343
def __init__(self, tup=None, exprinfo=None):
346
if exprinfo is None and isinstance(tup[1], AssertionError):
347
exprinfo = getattr(tup[1], 'msg', None)
349
exprinfo = str(tup[1])
350
if exprinfo and exprinfo.startswith('assert '):
351
self._striptext = 'AssertionError: '
353
#: the exception class
355
#: the exception instance
357
#: the exception raw traceback
359
#: the exception type name
360
self.typename = self.type.__name__
361
#: the exception traceback (py.code.Traceback instance)
362
self.traceback = py.code.Traceback(self.tb)
365
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
367
def exconly(self, tryshort=False):
368
""" return the exception as a string
370
when 'tryshort' resolves to True, and the exception is a
371
py.code._AssertionError, only the actual exception part of
372
the exception representation is returned (so 'AssertionError: ' is
373
removed from the beginning)
375
lines = format_exception_only(self.type, self.value)
376
text = ''.join(lines)
379
if text.startswith(self._striptext):
380
text = text[len(self._striptext):]
383
def errisinstance(self, exc):
384
""" return True if the exception is an instance of exc """
385
return isinstance(self.value, exc)
387
def _getreprcrash(self):
388
exconly = self.exconly(tryshort=True)
389
entry = self.traceback.getcrashentry()
390
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
391
return ReprFileLocation(path, lineno+1, exconly)
393
def getrepr(self, showlocals=False, style="long",
394
abspath=False, tbfilter=True, funcargs=False):
395
""" return str()able representation of this exception info.
396
showlocals: show locals per traceback entry
397
style: long|short|no|native traceback style
398
tbfilter: hide entries (where __tracebackhide__ is true)
400
in case of style==native, tbfilter and showlocals is ignored.
402
if style == 'native':
403
return ReprExceptionInfo(ReprTracebackNative(
404
py.std.traceback.format_exception(
407
self.traceback[0]._rawentry,
408
)), self._getreprcrash())
410
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
411
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
412
return fmt.repr_excinfo(self)
415
entry = self.traceback[-1]
416
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
419
def __unicode__(self):
420
entry = self.traceback[-1]
421
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
425
class FormattedExcinfo(object):
426
""" presenting information about failing Functions and Generators. """
427
# for traceback entries
431
def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
432
self.showlocals = showlocals
434
self.tbfilter = tbfilter
435
self.funcargs = funcargs
436
self.abspath = abspath
439
def _getindent(self, source):
440
# figure out indent for given source
442
s = str(source.getstatement(len(source)-1))
443
except KeyboardInterrupt:
448
except KeyboardInterrupt:
452
return 4 + (len(s) - len(s.lstrip()))
454
def _getentrysource(self, entry):
455
source = entry.getsource(self.astcache)
456
if source is not None:
457
source = source.deindent()
460
def _saferepr(self, obj):
461
return py.io.saferepr(obj)
463
def repr_args(self, entry):
466
for argname, argvalue in entry.frame.getargs(var=True):
467
args.append((argname, self._saferepr(argvalue)))
468
return ReprFuncArgs(args)
470
def get_source(self, source, line_index=-1, excinfo=None, short=False):
471
""" return formatted and marked up source lines. """
473
if source is None or line_index >= len(source.lines):
474
source = py.code.Source("???")
477
line_index += len(source)
480
lines.append(space_prefix + source.lines[line_index].strip())
482
for line in source.lines[:line_index]:
483
lines.append(space_prefix + line)
484
lines.append(self.flow_marker + " " + source.lines[line_index])
485
for line in source.lines[line_index+1:]:
486
lines.append(space_prefix + line)
487
if excinfo is not None:
488
indent = 4 if short else self._getindent(source)
489
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
492
def get_exconly(self, excinfo, indent=4, markall=False):
494
indent = " " * indent
495
# get the real exception information out
496
exlines = excinfo.exconly(tryshort=True).split('\n')
497
failindent = self.fail_marker + indent[1:]
499
lines.append(failindent + line)
504
def repr_locals(self, locals):
507
keys = [loc for loc in locals if loc[0] != "@"]
511
if name == '__builtins__':
512
lines.append("__builtins__ = <builtins>")
514
# This formatting could all be handled by the
515
# _repr() function, which is only reprlib.Repr in
516
# disguise, so is very configurable.
517
str_repr = self._saferepr(value)
518
#if len(str_repr) < 70 or not isinstance(value,
519
# (list, tuple, dict)):
520
lines.append("%-10s = %s" %(name, str_repr))
522
# self._line("%-10s =\\" % (name,))
524
# py.std.pprint.pprint(value, stream=self.excinfowriter)
525
return ReprLocals(lines)
527
def repr_traceback_entry(self, entry, excinfo=None):
528
source = self._getentrysource(entry)
530
source = py.code.Source("???")
533
# entry.getfirstlinesource() can be -1, should be 0 on jython
534
line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
537
style = entry._repr_style
540
if style in ("short", "long"):
541
short = style == "short"
542
reprargs = self.repr_args(entry) if not short else None
543
s = self.get_source(source, line_index, excinfo, short=short)
546
message = "in %s" %(entry.name)
548
message = excinfo and excinfo.typename or ""
549
path = self._makepath(entry.path)
550
filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
553
localsrepr = self.repr_locals(entry.locals)
554
return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
556
lines.extend(self.get_exconly(excinfo, indent=4))
557
return ReprEntry(lines, None, None, None, style)
559
def _makepath(self, path):
562
np = py.path.local().bestrelpath(path)
565
if len(np) < len(str(path)):
569
def repr_traceback(self, excinfo):
570
traceback = excinfo.traceback
572
traceback = traceback.filter()
573
recursionindex = None
574
if excinfo.errisinstance(RuntimeError):
575
if "maximum recursion depth exceeded" in str(excinfo.value):
576
recursionindex = traceback.recursionindex()
580
for index, entry in enumerate(traceback):
581
einfo = (last == entry) and excinfo or None
582
reprentry = self.repr_traceback_entry(entry, einfo)
583
entries.append(reprentry)
584
if index == recursionindex:
585
extraline = "!!! Recursion detected (same locals & position)"
587
return ReprTraceback(entries, extraline, style=self.style)
589
def repr_excinfo(self, excinfo):
590
reprtraceback = self.repr_traceback(excinfo)
591
reprcrash = excinfo._getreprcrash()
592
return ReprExceptionInfo(reprtraceback, reprcrash)
596
s = self.__unicode__()
597
if sys.version_info[0] < 3:
598
s = s.encode('utf-8')
601
def __unicode__(self):
602
# FYI this is called from pytest-xdist's serialization of exception
605
tw = py.io.TerminalWriter(file=io)
607
return io.getvalue().strip()
610
return "<%s instance at %0x>" %(self.__class__, id(self))
613
class ReprExceptionInfo(TerminalRepr):
614
def __init__(self, reprtraceback, reprcrash):
615
self.reprtraceback = reprtraceback
616
self.reprcrash = reprcrash
619
def addsection(self, name, content, sep="-"):
620
self.sections.append((name, content, sep))
622
def toterminal(self, tw):
623
self.reprtraceback.toterminal(tw)
624
for name, content, sep in self.sections:
628
class ReprTraceback(TerminalRepr):
631
def __init__(self, reprentries, extraline, style):
632
self.reprentries = reprentries
633
self.extraline = extraline
636
def toterminal(self, tw):
637
# the entries might have different styles
639
for i, entry in enumerate(self.reprentries):
640
if entry.style == "long":
643
if i < len(self.reprentries) - 1:
644
next_entry = self.reprentries[i+1]
645
if entry.style == "long" or \
646
entry.style == "short" and next_entry.style == "long":
647
tw.sep(self.entrysep)
650
tw.line(self.extraline)
652
class ReprTracebackNative(ReprTraceback):
653
def __init__(self, tblines):
654
self.style = "native"
655
self.reprentries = [ReprEntryNative(tblines)]
656
self.extraline = None
658
class ReprEntryNative(TerminalRepr):
661
def __init__(self, tblines):
664
def toterminal(self, tw):
665
tw.write("".join(self.lines))
667
class ReprEntry(TerminalRepr):
670
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
672
self.reprfuncargs = reprfuncargs
673
self.reprlocals = reprlocals
674
self.reprfileloc = filelocrepr
677
def toterminal(self, tw):
678
if self.style == "short":
679
self.reprfileloc.toterminal(tw)
680
for line in self.lines:
681
red = line.startswith("E ")
682
tw.line(line, bold=True, red=red)
685
if self.reprfuncargs:
686
self.reprfuncargs.toterminal(tw)
687
for line in self.lines:
688
red = line.startswith("E ")
689
tw.line(line, bold=True, red=red)
691
#tw.sep(self.localssep, "Locals")
693
self.reprlocals.toterminal(tw)
697
self.reprfileloc.toterminal(tw)
700
return "%s\n%s\n%s" % ("\n".join(self.lines),
704
class ReprFileLocation(TerminalRepr):
705
def __init__(self, path, lineno, message):
706
self.path = str(path)
708
self.message = message
710
def toterminal(self, tw):
711
# filename and lineno output for each entry,
712
# using an output format that most editors unterstand
717
tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
719
class ReprLocals(TerminalRepr):
720
def __init__(self, lines):
723
def toterminal(self, tw):
724
for line in self.lines:
727
class ReprFuncArgs(TerminalRepr):
728
def __init__(self, args):
731
def toterminal(self, tw):
734
for name, value in self.args:
735
ns = "%s = %s" %(name, value)
736
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
742
linesofar += ", " + ns
753
def patch_builtins(assertion=True, compile=True):
754
""" put compile and AssertionError builtins to Python's builtins. """
756
from py._code import assertion
757
l = oldbuiltins.setdefault('AssertionError', [])
758
l.append(py.builtin.builtins.AssertionError)
759
py.builtin.builtins.AssertionError = assertion.AssertionError
761
l = oldbuiltins.setdefault('compile', [])
762
l.append(py.builtin.builtins.compile)
763
py.builtin.builtins.compile = py.code.compile
765
def unpatch_builtins(assertion=True, compile=True):
766
""" remove compile and AssertionError builtins from Python builtins. """
768
py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
770
py.builtin.builtins.compile = oldbuiltins['compile'].pop()
772
def getrawcode(obj, trycall=True):
773
""" return code object for given function. """
776
except AttributeError:
777
obj = getattr(obj, 'im_func', obj)
778
obj = getattr(obj, 'func_code', obj)
779
obj = getattr(obj, 'f_code', obj)
780
obj = getattr(obj, '__code__', obj)
781
if trycall and not hasattr(obj, 'co_firstlineno'):
782
if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj):
783
x = getrawcode(obj.__call__, trycall=False)
784
if hasattr(x, 'co_firstlineno'):