2
from inspect import CO_VARARGS, CO_VARKEYWORDS
4
from weakref import ref
9
reprlib = py.builtin._tryimport('repr', 'reprlib')
11
if sys.version_info[0] >= 3:
12
from traceback import format_exception_only
14
from ._py2traceback import format_exception_only
18
""" wrapper around Python code objects """
19
def __init__(self, rawcode):
20
if not hasattr(rawcode, "co_filename"):
21
rawcode = getrawcode(rawcode)
23
self.filename = rawcode.co_filename
24
self.firstlineno = rawcode.co_firstlineno - 1
25
self.name = rawcode.co_name
26
except AttributeError:
27
raise TypeError("not a code object: %r" %(rawcode,))
30
def __eq__(self, other):
31
return self.raw == other.raw
35
def __ne__(self, other):
36
return not self == other
40
""" return a path object pointing to source code (note that it
41
might not point to an actually existing file). """
43
p = py.path.local(self.raw.co_filename)
44
# maybe don't try this checking
46
raise OSError("py.path check failed.")
48
# XXX maybe try harder like the weird logic
49
# in the standard lib [linecache.updatecache] does?
50
p = self.raw.co_filename
56
""" return a _pytest._code.Source object for the full source file of the code
58
from _pytest._code import source
59
full, _ = source.findsource(self.raw)
63
""" return a _pytest._code.Source object for the code object's source only
65
# return source only for that part of code
67
return _pytest._code.Source(self.raw)
69
def getargs(self, var=False):
70
""" return a tuple with the argument names for the code object
72
if 'var' is set True also return the names of the variable and
73
keyword arguments when present
75
# handfull shortcut for getting args
77
argcount = raw.co_argcount
79
argcount += raw.co_flags & CO_VARARGS
80
argcount += raw.co_flags & CO_VARKEYWORDS
81
return raw.co_varnames[:argcount]
84
"""Wrapper around a Python frame holding f_locals and f_globals
85
in which expressions can be evaluated."""
87
def __init__(self, frame):
88
self.lineno = frame.f_lineno - 1
89
self.f_globals = frame.f_globals
90
self.f_locals = frame.f_locals
92
self.code = Code(frame.f_code)
96
""" statement this frame is at """
98
if self.code.fullsource is None:
99
return _pytest._code.Source("")
100
return self.code.fullsource.getstatement(self.lineno)
102
def eval(self, code, **vars):
103
""" evaluate 'code' in the frame
105
'vars' are optional additional local variables
107
returns the result of the evaluation
109
f_locals = self.f_locals.copy()
110
f_locals.update(vars)
111
return eval(code, self.f_globals, f_locals)
113
def exec_(self, code, **vars):
114
""" exec 'code' in the frame
116
'vars' are optiona; additional local variables
118
f_locals = self.f_locals.copy()
119
f_locals.update(vars)
120
py.builtin.exec_(code, self.f_globals, f_locals )
122
def repr(self, object):
123
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
125
return py.io.saferepr(object)
127
def is_true(self, object):
130
def getargs(self, var=False):
131
""" return a list of tuples (name, value) for all arguments
133
if 'var' is set True also include the variable and keyword
134
arguments when present
137
for arg in self.code.getargs(var):
139
retval.append((arg, self.f_locals[arg]))
141
pass # this can occur when using Psyco
144
class TracebackEntry(object):
145
""" a single entry in a traceback """
150
def __init__(self, rawentry, excinfo=None):
151
self._excinfo = excinfo
152
self._rawentry = rawentry
153
self.lineno = rawentry.tb_lineno - 1
155
def set_repr_style(self, mode):
156
assert mode in ("short", "long")
157
self._repr_style = mode
162
return _pytest._code.Frame(self._rawentry.tb_frame)
166
return self.lineno - self.frame.code.firstlineno
169
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
173
""" _pytest._code.Source object for the current statement """
174
source = self.frame.code.fullsource
175
return source.getstatement(self.lineno)
179
""" path to the source code """
180
return self.frame.code.path
183
return self.frame.f_locals
184
locals = property(getlocals, None, None, "locals of underlaying frame")
186
def getfirstlinesource(self):
187
# on Jython this firstlineno can be -1 apparently
188
return max(self.frame.code.firstlineno, 0)
190
def getsource(self, astcache=None):
191
""" return failing source code. """
192
# we use the passed in astcache to not reparse asttrees
193
# within exception info printing
194
from _pytest._code.source import getstatementrange_ast
195
source = self.frame.code.fullsource
199
if astcache is not None:
200
key = self.frame.code.path
202
astnode = astcache.get(key, None)
203
start = self.getfirstlinesource()
205
astnode, _, end = getstatementrange_ast(self.lineno, source,
208
end = self.lineno + 1
211
astcache[key] = astnode
212
return source[start:end]
214
source = property(getsource)
217
""" return True if the current frame has a var __tracebackhide__
220
If __tracebackhide__ is a callable, it gets called with the
221
ExceptionInfo instance and can decide whether to hide the traceback.
223
mostly for internal use
226
tbh = self.frame.f_locals['__tracebackhide__']
229
tbh = self.frame.f_globals['__tracebackhide__']
233
if py.builtin.callable(tbh):
234
return tbh(None if self._excinfo is None else self._excinfo())
241
except py.error.Error:
243
name = self.frame.code.name
245
line = str(self.statement).lstrip()
246
except KeyboardInterrupt:
250
return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
253
return self.frame.code.raw.co_name
254
name = property(name, None, None, "co_name of underlaying code")
256
class Traceback(list):
257
""" Traceback objects encapsulate and offer higher level
258
access to Traceback entries.
260
Entry = TracebackEntry
261
def __init__(self, tb, excinfo=None):
262
""" initialize from given python traceback object and ExceptionInfo """
263
self._excinfo = excinfo
264
if hasattr(tb, 'tb_next'):
266
while cur is not None:
267
yield self.Entry(cur, excinfo=excinfo)
269
list.__init__(self, f(tb))
271
list.__init__(self, tb)
273
def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
274
""" return a Traceback instance wrapping part of this Traceback
276
by provding any combination of path, lineno and firstlineno, the
277
first frame to start the to-be-returned traceback is determined
279
this allows cutting the first part of a Traceback instance e.g.
280
for formatting reasons (removing some uninteresting bits that deal
281
with handling of the exception/traceback)
286
if ((path is None or codepath == path) and
287
(excludepath is None or not hasattr(codepath, 'relto') or
288
not codepath.relto(excludepath)) and
289
(lineno is None or x.lineno == lineno) and
290
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
291
return Traceback(x._rawentry, self._excinfo)
294
def __getitem__(self, key):
295
val = super(Traceback, self).__getitem__(key)
296
if isinstance(key, type(slice(0))):
297
val = self.__class__(val)
300
def filter(self, fn=lambda x: not x.ishidden()):
301
""" return a Traceback instance with certain items removed
303
fn is a function that gets a single argument, a TracebackEntry
304
instance, and should return True when the item should be added
305
to the Traceback, False when not
307
by default this removes all the TracebackEntries which are hidden
308
(see ishidden() above)
310
return Traceback(filter(fn, self), self._excinfo)
312
def getcrashentry(self):
313
""" return last non-hidden traceback entry that lead
314
to the exception of a traceback.
316
for i in range(-1, -len(self)-1, -1):
318
if not entry.ishidden():
322
def recursionindex(self):
323
""" return the index of the frame/TracebackEntry where recursion
324
originates if appropriate, None if no recursion occurred
327
for i, entry in enumerate(self):
328
# id for the code.raw is needed to work around
329
# the strange metaprogramming in the decorator lib from pypi
330
# which generates code objects that have hash/value equality
332
key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
333
#print "checking for recursion at", key
334
l = cache.setdefault(key, [])
339
if f.is_true(f.eval(co_equal,
340
__recursioncache_locals_1=loc,
341
__recursioncache_locals_2=otherloc)):
343
l.append(entry.frame.f_locals)
347
co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
350
class ExceptionInfo(object):
351
""" wraps sys.exc_info() objects and offers
352
help for navigating the traceback.
355
def __init__(self, tup=None, exprinfo=None):
359
if exprinfo is None and isinstance(tup[1], AssertionError):
360
exprinfo = getattr(tup[1], 'msg', None)
362
exprinfo = py._builtin._totext(tup[1])
363
if exprinfo and exprinfo.startswith('assert '):
364
self._striptext = 'AssertionError: '
366
#: the exception class
368
#: the exception instance
370
#: the exception raw traceback
372
#: the exception type name
373
self.typename = self.type.__name__
374
#: the exception traceback (_pytest._code.Traceback instance)
375
self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self))
378
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
380
def exconly(self, tryshort=False):
381
""" return the exception as a string
383
when 'tryshort' resolves to True, and the exception is a
384
_pytest._code._AssertionError, only the actual exception part of
385
the exception representation is returned (so 'AssertionError: ' is
386
removed from the beginning)
388
lines = format_exception_only(self.type, self.value)
389
text = ''.join(lines)
392
if text.startswith(self._striptext):
393
text = text[len(self._striptext):]
396
def errisinstance(self, exc):
397
""" return True if the exception is an instance of exc """
398
return isinstance(self.value, exc)
400
def _getreprcrash(self):
401
exconly = self.exconly(tryshort=True)
402
entry = self.traceback.getcrashentry()
403
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
404
return ReprFileLocation(path, lineno+1, exconly)
406
def getrepr(self, showlocals=False, style="long",
407
abspath=False, tbfilter=True, funcargs=False):
408
""" return str()able representation of this exception info.
409
showlocals: show locals per traceback entry
410
style: long|short|no|native traceback style
411
tbfilter: hide entries (where __tracebackhide__ is true)
413
in case of style==native, tbfilter and showlocals is ignored.
415
if style == 'native':
416
return ReprExceptionInfo(ReprTracebackNative(
417
py.std.traceback.format_exception(
420
self.traceback[0]._rawentry,
421
)), self._getreprcrash())
423
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
424
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
425
return fmt.repr_excinfo(self)
428
entry = self.traceback[-1]
429
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
432
def __unicode__(self):
433
entry = self.traceback[-1]
434
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
437
def match(self, regexp):
439
Match the regular expression 'regexp' on the string representation of
440
the exception. If it matches then True is returned (so that it is
441
possible to write 'assert excinfo.match()'). If it doesn't match an
442
AssertionError is raised.
444
__tracebackhide__ = True
445
if not re.search(regexp, str(self.value)):
446
assert 0, "Pattern '{0!s}' not found in '{1!s}'".format(
451
class FormattedExcinfo(object):
452
""" presenting information about failing Functions and Generators. """
453
# for traceback entries
457
def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
458
self.showlocals = showlocals
460
self.tbfilter = tbfilter
461
self.funcargs = funcargs
462
self.abspath = abspath
465
def _getindent(self, source):
466
# figure out indent for given source
468
s = str(source.getstatement(len(source)-1))
469
except KeyboardInterrupt:
474
except KeyboardInterrupt:
478
return 4 + (len(s) - len(s.lstrip()))
480
def _getentrysource(self, entry):
481
source = entry.getsource(self.astcache)
482
if source is not None:
483
source = source.deindent()
486
def _saferepr(self, obj):
487
return py.io.saferepr(obj)
489
def repr_args(self, entry):
492
for argname, argvalue in entry.frame.getargs(var=True):
493
args.append((argname, self._saferepr(argvalue)))
494
return ReprFuncArgs(args)
496
def get_source(self, source, line_index=-1, excinfo=None, short=False):
497
""" return formatted and marked up source lines. """
500
if source is None or line_index >= len(source.lines):
501
source = _pytest._code.Source("???")
504
line_index += len(source)
507
lines.append(space_prefix + source.lines[line_index].strip())
509
for line in source.lines[:line_index]:
510
lines.append(space_prefix + line)
511
lines.append(self.flow_marker + " " + source.lines[line_index])
512
for line in source.lines[line_index+1:]:
513
lines.append(space_prefix + line)
514
if excinfo is not None:
515
indent = 4 if short else self._getindent(source)
516
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
519
def get_exconly(self, excinfo, indent=4, markall=False):
521
indent = " " * indent
522
# get the real exception information out
523
exlines = excinfo.exconly(tryshort=True).split('\n')
524
failindent = self.fail_marker + indent[1:]
526
lines.append(failindent + line)
531
def repr_locals(self, locals):
534
keys = [loc for loc in locals if loc[0] != "@"]
538
if name == '__builtins__':
539
lines.append("__builtins__ = <builtins>")
541
# This formatting could all be handled by the
542
# _repr() function, which is only reprlib.Repr in
543
# disguise, so is very configurable.
544
str_repr = self._saferepr(value)
545
#if len(str_repr) < 70 or not isinstance(value,
546
# (list, tuple, dict)):
547
lines.append("%-10s = %s" %(name, str_repr))
549
# self._line("%-10s =\\" % (name,))
551
# py.std.pprint.pprint(value, stream=self.excinfowriter)
552
return ReprLocals(lines)
554
def repr_traceback_entry(self, entry, excinfo=None):
556
source = self._getentrysource(entry)
558
source = _pytest._code.Source("???")
561
# entry.getfirstlinesource() can be -1, should be 0 on jython
562
line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
565
style = entry._repr_style
568
if style in ("short", "long"):
569
short = style == "short"
570
reprargs = self.repr_args(entry) if not short else None
571
s = self.get_source(source, line_index, excinfo, short=short)
574
message = "in %s" %(entry.name)
576
message = excinfo and excinfo.typename or ""
577
path = self._makepath(entry.path)
578
filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
581
localsrepr = self.repr_locals(entry.locals)
582
return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
584
lines.extend(self.get_exconly(excinfo, indent=4))
585
return ReprEntry(lines, None, None, None, style)
587
def _makepath(self, path):
590
np = py.path.local().bestrelpath(path)
593
if len(np) < len(str(path)):
597
def repr_traceback(self, excinfo):
598
traceback = excinfo.traceback
600
traceback = traceback.filter()
601
recursionindex = None
602
if is_recursion_error(excinfo):
603
recursionindex = traceback.recursionindex()
607
for index, entry in enumerate(traceback):
608
einfo = (last == entry) and excinfo or None
609
reprentry = self.repr_traceback_entry(entry, einfo)
610
entries.append(reprentry)
611
if index == recursionindex:
612
extraline = "!!! Recursion detected (same locals & position)"
614
return ReprTraceback(entries, extraline, style=self.style)
617
def repr_excinfo(self, excinfo):
618
if sys.version_info[0] < 3:
619
reprtraceback = self.repr_traceback(excinfo)
620
reprcrash = excinfo._getreprcrash()
622
return ReprExceptionInfo(reprtraceback, reprcrash)
629
reprtraceback = self.repr_traceback(excinfo)
630
reprcrash = excinfo._getreprcrash()
632
# fallback to native repr if the exception doesn't have a traceback:
633
# ExceptionInfo objects require a full traceback to work
634
reprtraceback = ReprTracebackNative(py.std.traceback.format_exception(type(e), e, None))
637
repr_chain += [(reprtraceback, reprcrash, descr)]
638
if e.__cause__ is not None:
640
excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None
641
descr = 'The above exception was the direct cause of the following exception:'
642
elif e.__context__ is not None:
644
excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None
645
descr = 'During handling of the above exception, another exception occurred:'
649
return ExceptionChainRepr(repr_chain)
652
class TerminalRepr(object):
654
s = self.__unicode__()
655
if sys.version_info[0] < 3:
656
s = s.encode('utf-8')
659
def __unicode__(self):
660
# FYI this is called from pytest-xdist's serialization of exception
663
tw = py.io.TerminalWriter(file=io)
665
return io.getvalue().strip()
668
return "<%s instance at %0x>" %(self.__class__, id(self))
671
class ExceptionRepr(TerminalRepr):
675
def addsection(self, name, content, sep="-"):
676
self.sections.append((name, content, sep))
678
def toterminal(self, tw):
679
for name, content, sep in self.sections:
684
class ExceptionChainRepr(ExceptionRepr):
685
def __init__(self, chain):
686
super(ExceptionChainRepr, self).__init__()
688
# reprcrash and reprtraceback of the outermost (the newest) exception
690
self.reprtraceback = chain[-1][0]
691
self.reprcrash = chain[-1][1]
693
def toterminal(self, tw):
694
for element in self.chain:
695
element[0].toterminal(tw)
696
if element[2] is not None:
698
tw.line(element[2], yellow=True)
699
super(ExceptionChainRepr, self).toterminal(tw)
702
class ReprExceptionInfo(ExceptionRepr):
703
def __init__(self, reprtraceback, reprcrash):
704
super(ReprExceptionInfo, self).__init__()
705
self.reprtraceback = reprtraceback
706
self.reprcrash = reprcrash
708
def toterminal(self, tw):
709
self.reprtraceback.toterminal(tw)
710
super(ReprExceptionInfo, self).toterminal(tw)
712
class ReprTraceback(TerminalRepr):
715
def __init__(self, reprentries, extraline, style):
716
self.reprentries = reprentries
717
self.extraline = extraline
720
def toterminal(self, tw):
721
# the entries might have different styles
722
for i, entry in enumerate(self.reprentries):
723
if entry.style == "long":
726
if i < len(self.reprentries) - 1:
727
next_entry = self.reprentries[i+1]
728
if entry.style == "long" or \
729
entry.style == "short" and next_entry.style == "long":
730
tw.sep(self.entrysep)
733
tw.line(self.extraline)
735
class ReprTracebackNative(ReprTraceback):
736
def __init__(self, tblines):
737
self.style = "native"
738
self.reprentries = [ReprEntryNative(tblines)]
739
self.extraline = None
741
class ReprEntryNative(TerminalRepr):
744
def __init__(self, tblines):
747
def toterminal(self, tw):
748
tw.write("".join(self.lines))
750
class ReprEntry(TerminalRepr):
753
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
755
self.reprfuncargs = reprfuncargs
756
self.reprlocals = reprlocals
757
self.reprfileloc = filelocrepr
760
def toterminal(self, tw):
761
if self.style == "short":
762
self.reprfileloc.toterminal(tw)
763
for line in self.lines:
764
red = line.startswith("E ")
765
tw.line(line, bold=True, red=red)
768
if self.reprfuncargs:
769
self.reprfuncargs.toterminal(tw)
770
for line in self.lines:
771
red = line.startswith("E ")
772
tw.line(line, bold=True, red=red)
774
#tw.sep(self.localssep, "Locals")
776
self.reprlocals.toterminal(tw)
780
self.reprfileloc.toterminal(tw)
783
return "%s\n%s\n%s" % ("\n".join(self.lines),
787
class ReprFileLocation(TerminalRepr):
788
def __init__(self, path, lineno, message):
789
self.path = str(path)
791
self.message = message
793
def toterminal(self, tw):
794
# filename and lineno output for each entry,
795
# using an output format that most editors unterstand
800
tw.write(self.path, bold=True, red=True)
801
tw.line(":%s: %s" % (self.lineno, msg))
803
class ReprLocals(TerminalRepr):
804
def __init__(self, lines):
807
def toterminal(self, tw):
808
for line in self.lines:
811
class ReprFuncArgs(TerminalRepr):
812
def __init__(self, args):
815
def toterminal(self, tw):
818
for name, value in self.args:
819
ns = "%s = %s" %(name, value)
820
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
826
linesofar += ", " + ns
834
def getrawcode(obj, trycall=True):
835
""" return code object for given function. """
838
except AttributeError:
839
obj = getattr(obj, 'im_func', obj)
840
obj = getattr(obj, 'func_code', obj)
841
obj = getattr(obj, 'f_code', obj)
842
obj = getattr(obj, '__code__', obj)
843
if trycall and not hasattr(obj, 'co_firstlineno'):
844
if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj):
845
x = getrawcode(obj.__call__, trycall=False)
846
if hasattr(x, 'co_firstlineno'):
851
if sys.version_info[:2] >= (3, 5): # RecursionError introduced in 3.5
852
def is_recursion_error(excinfo):
853
return excinfo.errisinstance(RecursionError) # noqa
855
def is_recursion_error(excinfo):
856
if not excinfo.errisinstance(RuntimeError):
859
return "maximum recursion depth exceeded" in str(excinfo.value)