code.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981
  1. from __future__ import absolute_import, division, print_function
  2. import inspect
  3. import pprint
  4. import sys
  5. import traceback
  6. from inspect import CO_VARARGS, CO_VARKEYWORDS
  7. import attr
  8. import re
  9. from weakref import ref
  10. from _pytest.compat import _PY2, _PY3, PY35, safe_str
  11. from six import text_type
  12. import py
  13. import six
  14. builtin_repr = repr
  15. if _PY3:
  16. from traceback import format_exception_only
  17. else:
  18. from ._py2traceback import format_exception_only
  19. class Code(object):
  20. """ wrapper around Python code objects """
  21. def __init__(self, rawcode):
  22. if not hasattr(rawcode, "co_filename"):
  23. rawcode = getrawcode(rawcode)
  24. try:
  25. self.filename = rawcode.co_filename
  26. self.firstlineno = rawcode.co_firstlineno - 1
  27. self.name = rawcode.co_name
  28. except AttributeError:
  29. raise TypeError("not a code object: %r" % (rawcode,))
  30. self.raw = rawcode
  31. def __eq__(self, other):
  32. return self.raw == other.raw
  33. __hash__ = None
  34. def __ne__(self, other):
  35. return not self == other
  36. @property
  37. def path(self):
  38. """ return a path object pointing to source code (note that it
  39. might not point to an actually existing file). """
  40. try:
  41. p = py.path.local(self.raw.co_filename)
  42. # maybe don't try this checking
  43. if not p.check():
  44. raise OSError("py.path check failed.")
  45. except OSError:
  46. # XXX maybe try harder like the weird logic
  47. # in the standard lib [linecache.updatecache] does?
  48. p = self.raw.co_filename
  49. return p
  50. @property
  51. def fullsource(self):
  52. """ return a _pytest._code.Source object for the full source file of the code
  53. """
  54. from _pytest._code import source
  55. full, _ = source.findsource(self.raw)
  56. return full
  57. def source(self):
  58. """ return a _pytest._code.Source object for the code object's source only
  59. """
  60. # return source only for that part of code
  61. import _pytest._code
  62. return _pytest._code.Source(self.raw)
  63. def getargs(self, var=False):
  64. """ return a tuple with the argument names for the code object
  65. if 'var' is set True also return the names of the variable and
  66. keyword arguments when present
  67. """
  68. # handfull shortcut for getting args
  69. raw = self.raw
  70. argcount = raw.co_argcount
  71. if var:
  72. argcount += raw.co_flags & CO_VARARGS
  73. argcount += raw.co_flags & CO_VARKEYWORDS
  74. return raw.co_varnames[:argcount]
  75. class Frame(object):
  76. """Wrapper around a Python frame holding f_locals and f_globals
  77. in which expressions can be evaluated."""
  78. def __init__(self, frame):
  79. self.lineno = frame.f_lineno - 1
  80. self.f_globals = frame.f_globals
  81. self.f_locals = frame.f_locals
  82. self.raw = frame
  83. self.code = Code(frame.f_code)
  84. @property
  85. def statement(self):
  86. """ statement this frame is at """
  87. import _pytest._code
  88. if self.code.fullsource is None:
  89. return _pytest._code.Source("")
  90. return self.code.fullsource.getstatement(self.lineno)
  91. def eval(self, code, **vars):
  92. """ evaluate 'code' in the frame
  93. 'vars' are optional additional local variables
  94. returns the result of the evaluation
  95. """
  96. f_locals = self.f_locals.copy()
  97. f_locals.update(vars)
  98. return eval(code, self.f_globals, f_locals)
  99. def exec_(self, code, **vars):
  100. """ exec 'code' in the frame
  101. 'vars' are optiona; additional local variables
  102. """
  103. f_locals = self.f_locals.copy()
  104. f_locals.update(vars)
  105. six.exec_(code, self.f_globals, f_locals)
  106. def repr(self, object):
  107. """ return a 'safe' (non-recursive, one-line) string repr for 'object'
  108. """
  109. return py.io.saferepr(object)
  110. def is_true(self, object):
  111. return object
  112. def getargs(self, var=False):
  113. """ return a list of tuples (name, value) for all arguments
  114. if 'var' is set True also include the variable and keyword
  115. arguments when present
  116. """
  117. retval = []
  118. for arg in self.code.getargs(var):
  119. try:
  120. retval.append((arg, self.f_locals[arg]))
  121. except KeyError:
  122. pass # this can occur when using Psyco
  123. return retval
  124. class TracebackEntry(object):
  125. """ a single entry in a traceback """
  126. _repr_style = None
  127. exprinfo = None
  128. def __init__(self, rawentry, excinfo=None):
  129. self._excinfo = excinfo
  130. self._rawentry = rawentry
  131. self.lineno = rawentry.tb_lineno - 1
  132. def set_repr_style(self, mode):
  133. assert mode in ("short", "long")
  134. self._repr_style = mode
  135. @property
  136. def frame(self):
  137. import _pytest._code
  138. return _pytest._code.Frame(self._rawentry.tb_frame)
  139. @property
  140. def relline(self):
  141. return self.lineno - self.frame.code.firstlineno
  142. def __repr__(self):
  143. return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
  144. @property
  145. def statement(self):
  146. """ _pytest._code.Source object for the current statement """
  147. source = self.frame.code.fullsource
  148. return source.getstatement(self.lineno)
  149. @property
  150. def path(self):
  151. """ path to the source code """
  152. return self.frame.code.path
  153. def getlocals(self):
  154. return self.frame.f_locals
  155. locals = property(getlocals, None, None, "locals of underlaying frame")
  156. def getfirstlinesource(self):
  157. # on Jython this firstlineno can be -1 apparently
  158. return max(self.frame.code.firstlineno, 0)
  159. def getsource(self, astcache=None):
  160. """ return failing source code. """
  161. # we use the passed in astcache to not reparse asttrees
  162. # within exception info printing
  163. from _pytest._code.source import getstatementrange_ast
  164. source = self.frame.code.fullsource
  165. if source is None:
  166. return None
  167. key = astnode = None
  168. if astcache is not None:
  169. key = self.frame.code.path
  170. if key is not None:
  171. astnode = astcache.get(key, None)
  172. start = self.getfirstlinesource()
  173. try:
  174. astnode, _, end = getstatementrange_ast(
  175. self.lineno, source, astnode=astnode
  176. )
  177. except SyntaxError:
  178. end = self.lineno + 1
  179. else:
  180. if key is not None:
  181. astcache[key] = astnode
  182. return source[start:end]
  183. source = property(getsource)
  184. def ishidden(self):
  185. """ return True if the current frame has a var __tracebackhide__
  186. resolving to True
  187. If __tracebackhide__ is a callable, it gets called with the
  188. ExceptionInfo instance and can decide whether to hide the traceback.
  189. mostly for internal use
  190. """
  191. try:
  192. tbh = self.frame.f_locals["__tracebackhide__"]
  193. except KeyError:
  194. try:
  195. tbh = self.frame.f_globals["__tracebackhide__"]
  196. except KeyError:
  197. return False
  198. if callable(tbh):
  199. return tbh(None if self._excinfo is None else self._excinfo())
  200. else:
  201. return tbh
  202. def __str__(self):
  203. try:
  204. fn = str(self.path)
  205. except py.error.Error:
  206. fn = "???"
  207. name = self.frame.code.name
  208. try:
  209. line = str(self.statement).lstrip()
  210. except KeyboardInterrupt:
  211. raise
  212. except: # noqa
  213. line = "???"
  214. return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
  215. def name(self):
  216. return self.frame.code.raw.co_name
  217. name = property(name, None, None, "co_name of underlaying code")
  218. class Traceback(list):
  219. """ Traceback objects encapsulate and offer higher level
  220. access to Traceback entries.
  221. """
  222. Entry = TracebackEntry
  223. def __init__(self, tb, excinfo=None):
  224. """ initialize from given python traceback object and ExceptionInfo """
  225. self._excinfo = excinfo
  226. if hasattr(tb, "tb_next"):
  227. def f(cur):
  228. while cur is not None:
  229. yield self.Entry(cur, excinfo=excinfo)
  230. cur = cur.tb_next
  231. list.__init__(self, f(tb))
  232. else:
  233. list.__init__(self, tb)
  234. def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
  235. """ return a Traceback instance wrapping part of this Traceback
  236. by provding any combination of path, lineno and firstlineno, the
  237. first frame to start the to-be-returned traceback is determined
  238. this allows cutting the first part of a Traceback instance e.g.
  239. for formatting reasons (removing some uninteresting bits that deal
  240. with handling of the exception/traceback)
  241. """
  242. for x in self:
  243. code = x.frame.code
  244. codepath = code.path
  245. if (
  246. (path is None or codepath == path)
  247. and (
  248. excludepath is None
  249. or not hasattr(codepath, "relto")
  250. or not codepath.relto(excludepath)
  251. )
  252. and (lineno is None or x.lineno == lineno)
  253. and (firstlineno is None or x.frame.code.firstlineno == firstlineno)
  254. ):
  255. return Traceback(x._rawentry, self._excinfo)
  256. return self
  257. def __getitem__(self, key):
  258. val = super(Traceback, self).__getitem__(key)
  259. if isinstance(key, type(slice(0))):
  260. val = self.__class__(val)
  261. return val
  262. def filter(self, fn=lambda x: not x.ishidden()):
  263. """ return a Traceback instance with certain items removed
  264. fn is a function that gets a single argument, a TracebackEntry
  265. instance, and should return True when the item should be added
  266. to the Traceback, False when not
  267. by default this removes all the TracebackEntries which are hidden
  268. (see ishidden() above)
  269. """
  270. return Traceback(filter(fn, self), self._excinfo)
  271. def getcrashentry(self):
  272. """ return last non-hidden traceback entry that lead
  273. to the exception of a traceback.
  274. """
  275. for i in range(-1, -len(self) - 1, -1):
  276. entry = self[i]
  277. if not entry.ishidden():
  278. return entry
  279. return self[-1]
  280. def recursionindex(self):
  281. """ return the index of the frame/TracebackEntry where recursion
  282. originates if appropriate, None if no recursion occurred
  283. """
  284. cache = {}
  285. for i, entry in enumerate(self):
  286. # id for the code.raw is needed to work around
  287. # the strange metaprogramming in the decorator lib from pypi
  288. # which generates code objects that have hash/value equality
  289. # XXX needs a test
  290. key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
  291. # print "checking for recursion at", key
  292. values = cache.setdefault(key, [])
  293. if values:
  294. f = entry.frame
  295. loc = f.f_locals
  296. for otherloc in values:
  297. if f.is_true(
  298. f.eval(
  299. co_equal,
  300. __recursioncache_locals_1=loc,
  301. __recursioncache_locals_2=otherloc,
  302. )
  303. ):
  304. return i
  305. values.append(entry.frame.f_locals)
  306. return None
  307. co_equal = compile(
  308. "__recursioncache_locals_1 == __recursioncache_locals_2", "?", "eval"
  309. )
  310. class ExceptionInfo(object):
  311. """ wraps sys.exc_info() objects and offers
  312. help for navigating the traceback.
  313. """
  314. _striptext = ""
  315. _assert_start_repr = (
  316. "AssertionError(u'assert " if _PY2 else "AssertionError('assert "
  317. )
  318. def __init__(self, tup=None, exprinfo=None):
  319. import _pytest._code
  320. if tup is None:
  321. tup = sys.exc_info()
  322. if exprinfo is None and isinstance(tup[1], AssertionError):
  323. exprinfo = getattr(tup[1], "msg", None)
  324. if exprinfo is None:
  325. exprinfo = py.io.saferepr(tup[1])
  326. if exprinfo and exprinfo.startswith(self._assert_start_repr):
  327. self._striptext = "AssertionError: "
  328. self._excinfo = tup
  329. #: the exception class
  330. self.type = tup[0]
  331. #: the exception instance
  332. self.value = tup[1]
  333. #: the exception raw traceback
  334. self.tb = tup[2]
  335. #: the exception type name
  336. self.typename = self.type.__name__
  337. #: the exception traceback (_pytest._code.Traceback instance)
  338. self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self))
  339. def __repr__(self):
  340. return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
  341. def exconly(self, tryshort=False):
  342. """ return the exception as a string
  343. when 'tryshort' resolves to True, and the exception is a
  344. _pytest._code._AssertionError, only the actual exception part of
  345. the exception representation is returned (so 'AssertionError: ' is
  346. removed from the beginning)
  347. """
  348. lines = format_exception_only(self.type, self.value)
  349. text = "".join(lines)
  350. text = text.rstrip()
  351. if tryshort:
  352. if text.startswith(self._striptext):
  353. text = text[len(self._striptext) :]
  354. return text
  355. def errisinstance(self, exc):
  356. """ return True if the exception is an instance of exc """
  357. return isinstance(self.value, exc)
  358. def _getreprcrash(self):
  359. exconly = self.exconly(tryshort=True)
  360. entry = self.traceback.getcrashentry()
  361. path, lineno = entry.frame.code.raw.co_filename, entry.lineno
  362. return ReprFileLocation(path, lineno + 1, exconly)
  363. def getrepr(
  364. self,
  365. showlocals=False,
  366. style="long",
  367. abspath=False,
  368. tbfilter=True,
  369. funcargs=False,
  370. truncate_locals=True,
  371. ):
  372. """ return str()able representation of this exception info.
  373. showlocals: show locals per traceback entry
  374. style: long|short|no|native traceback style
  375. tbfilter: hide entries (where __tracebackhide__ is true)
  376. in case of style==native, tbfilter and showlocals is ignored.
  377. """
  378. if style == "native":
  379. return ReprExceptionInfo(
  380. ReprTracebackNative(
  381. traceback.format_exception(
  382. self.type, self.value, self.traceback[0]._rawentry
  383. )
  384. ),
  385. self._getreprcrash(),
  386. )
  387. fmt = FormattedExcinfo(
  388. showlocals=showlocals,
  389. style=style,
  390. abspath=abspath,
  391. tbfilter=tbfilter,
  392. funcargs=funcargs,
  393. truncate_locals=truncate_locals,
  394. )
  395. return fmt.repr_excinfo(self)
  396. def __str__(self):
  397. entry = self.traceback[-1]
  398. loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
  399. return str(loc)
  400. def __unicode__(self):
  401. entry = self.traceback[-1]
  402. loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
  403. return text_type(loc)
  404. def match(self, regexp):
  405. """
  406. Match the regular expression 'regexp' on the string representation of
  407. the exception. If it matches then True is returned (so that it is
  408. possible to write 'assert excinfo.match()'). If it doesn't match an
  409. AssertionError is raised.
  410. """
  411. __tracebackhide__ = True
  412. if not re.search(regexp, str(self.value)):
  413. assert 0, "Pattern '{!s}' not found in '{!s}'".format(regexp, self.value)
  414. return True
  415. @attr.s
  416. class FormattedExcinfo(object):
  417. """ presenting information about failing Functions and Generators. """
  418. # for traceback entries
  419. flow_marker = ">"
  420. fail_marker = "E"
  421. showlocals = attr.ib(default=False)
  422. style = attr.ib(default="long")
  423. abspath = attr.ib(default=True)
  424. tbfilter = attr.ib(default=True)
  425. funcargs = attr.ib(default=False)
  426. truncate_locals = attr.ib(default=True)
  427. astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
  428. def _getindent(self, source):
  429. # figure out indent for given source
  430. try:
  431. s = str(source.getstatement(len(source) - 1))
  432. except KeyboardInterrupt:
  433. raise
  434. except: # noqa
  435. try:
  436. s = str(source[-1])
  437. except KeyboardInterrupt:
  438. raise
  439. except: # noqa
  440. return 0
  441. return 4 + (len(s) - len(s.lstrip()))
  442. def _getentrysource(self, entry):
  443. source = entry.getsource(self.astcache)
  444. if source is not None:
  445. source = source.deindent()
  446. return source
  447. def _saferepr(self, obj):
  448. return py.io.saferepr(obj)
  449. def repr_args(self, entry):
  450. if self.funcargs:
  451. args = []
  452. for argname, argvalue in entry.frame.getargs(var=True):
  453. args.append((argname, self._saferepr(argvalue)))
  454. return ReprFuncArgs(args)
  455. def get_source(self, source, line_index=-1, excinfo=None, short=False):
  456. """ return formatted and marked up source lines. """
  457. import _pytest._code
  458. lines = []
  459. if source is None or line_index >= len(source.lines):
  460. source = _pytest._code.Source("???")
  461. line_index = 0
  462. if line_index < 0:
  463. line_index += len(source)
  464. space_prefix = " "
  465. if short:
  466. lines.append(space_prefix + source.lines[line_index].strip())
  467. else:
  468. for line in source.lines[:line_index]:
  469. lines.append(space_prefix + line)
  470. lines.append(self.flow_marker + " " + source.lines[line_index])
  471. for line in source.lines[line_index + 1 :]:
  472. lines.append(space_prefix + line)
  473. if excinfo is not None:
  474. indent = 4 if short else self._getindent(source)
  475. lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
  476. return lines
  477. def get_exconly(self, excinfo, indent=4, markall=False):
  478. lines = []
  479. indent = " " * indent
  480. # get the real exception information out
  481. exlines = excinfo.exconly(tryshort=True).split("\n")
  482. failindent = self.fail_marker + indent[1:]
  483. for line in exlines:
  484. lines.append(failindent + line)
  485. if not markall:
  486. failindent = indent
  487. return lines
  488. def repr_locals(self, locals):
  489. if self.showlocals:
  490. lines = []
  491. keys = [loc for loc in locals if loc[0] != "@"]
  492. keys.sort()
  493. for name in keys:
  494. value = locals[name]
  495. if name == "__builtins__":
  496. lines.append("__builtins__ = <builtins>")
  497. else:
  498. # This formatting could all be handled by the
  499. # _repr() function, which is only reprlib.Repr in
  500. # disguise, so is very configurable.
  501. if self.truncate_locals:
  502. str_repr = self._saferepr(value)
  503. else:
  504. str_repr = pprint.pformat(value)
  505. # if len(str_repr) < 70 or not isinstance(value,
  506. # (list, tuple, dict)):
  507. lines.append("%-10s = %s" % (name, str_repr))
  508. # else:
  509. # self._line("%-10s =\\" % (name,))
  510. # # XXX
  511. # pprint.pprint(value, stream=self.excinfowriter)
  512. return ReprLocals(lines)
  513. def repr_traceback_entry(self, entry, excinfo=None):
  514. import _pytest._code
  515. source = self._getentrysource(entry)
  516. if source is None:
  517. source = _pytest._code.Source("???")
  518. line_index = 0
  519. else:
  520. # entry.getfirstlinesource() can be -1, should be 0 on jython
  521. line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
  522. lines = []
  523. style = entry._repr_style
  524. if style is None:
  525. style = self.style
  526. if style in ("short", "long"):
  527. short = style == "short"
  528. reprargs = self.repr_args(entry) if not short else None
  529. s = self.get_source(source, line_index, excinfo, short=short)
  530. lines.extend(s)
  531. if short:
  532. message = "in %s" % (entry.name)
  533. else:
  534. message = excinfo and excinfo.typename or ""
  535. path = self._makepath(entry.path)
  536. filelocrepr = ReprFileLocation(path, entry.lineno + 1, message)
  537. localsrepr = None
  538. if not short:
  539. localsrepr = self.repr_locals(entry.locals)
  540. return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
  541. if excinfo:
  542. lines.extend(self.get_exconly(excinfo, indent=4))
  543. return ReprEntry(lines, None, None, None, style)
  544. def _makepath(self, path):
  545. if not self.abspath:
  546. try:
  547. np = py.path.local().bestrelpath(path)
  548. except OSError:
  549. return path
  550. if len(np) < len(str(path)):
  551. path = np
  552. return path
  553. def repr_traceback(self, excinfo):
  554. traceback = excinfo.traceback
  555. if self.tbfilter:
  556. traceback = traceback.filter()
  557. if is_recursion_error(excinfo):
  558. traceback, extraline = self._truncate_recursive_traceback(traceback)
  559. else:
  560. extraline = None
  561. last = traceback[-1]
  562. entries = []
  563. for index, entry in enumerate(traceback):
  564. einfo = (last == entry) and excinfo or None
  565. reprentry = self.repr_traceback_entry(entry, einfo)
  566. entries.append(reprentry)
  567. return ReprTraceback(entries, extraline, style=self.style)
  568. def _truncate_recursive_traceback(self, traceback):
  569. """
  570. Truncate the given recursive traceback trying to find the starting point
  571. of the recursion.
  572. The detection is done by going through each traceback entry and finding the
  573. point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``.
  574. Handle the situation where the recursion process might raise an exception (for example
  575. comparing numpy arrays using equality raises a TypeError), in which case we do our best to
  576. warn the user of the error and show a limited traceback.
  577. """
  578. try:
  579. recursionindex = traceback.recursionindex()
  580. except Exception as e:
  581. max_frames = 10
  582. extraline = (
  583. "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
  584. " The following exception happened when comparing locals in the stack frame:\n"
  585. " {exc_type}: {exc_msg}\n"
  586. " Displaying first and last {max_frames} stack frames out of {total}."
  587. ).format(
  588. exc_type=type(e).__name__,
  589. exc_msg=safe_str(e),
  590. max_frames=max_frames,
  591. total=len(traceback),
  592. )
  593. traceback = traceback[:max_frames] + traceback[-max_frames:]
  594. else:
  595. if recursionindex is not None:
  596. extraline = "!!! Recursion detected (same locals & position)"
  597. traceback = traceback[: recursionindex + 1]
  598. else:
  599. extraline = None
  600. return traceback, extraline
  601. def repr_excinfo(self, excinfo):
  602. if _PY2:
  603. reprtraceback = self.repr_traceback(excinfo)
  604. reprcrash = excinfo._getreprcrash()
  605. return ReprExceptionInfo(reprtraceback, reprcrash)
  606. else:
  607. repr_chain = []
  608. e = excinfo.value
  609. descr = None
  610. seen = set()
  611. while e is not None and id(e) not in seen:
  612. seen.add(id(e))
  613. if excinfo:
  614. reprtraceback = self.repr_traceback(excinfo)
  615. reprcrash = excinfo._getreprcrash()
  616. else:
  617. # fallback to native repr if the exception doesn't have a traceback:
  618. # ExceptionInfo objects require a full traceback to work
  619. reprtraceback = ReprTracebackNative(
  620. traceback.format_exception(type(e), e, None)
  621. )
  622. reprcrash = None
  623. repr_chain += [(reprtraceback, reprcrash, descr)]
  624. if e.__cause__ is not None:
  625. e = e.__cause__
  626. excinfo = (
  627. ExceptionInfo((type(e), e, e.__traceback__))
  628. if e.__traceback__
  629. else None
  630. )
  631. descr = "The above exception was the direct cause of the following exception:"
  632. elif e.__context__ is not None and not e.__suppress_context__:
  633. e = e.__context__
  634. excinfo = (
  635. ExceptionInfo((type(e), e, e.__traceback__))
  636. if e.__traceback__
  637. else None
  638. )
  639. descr = "During handling of the above exception, another exception occurred:"
  640. else:
  641. e = None
  642. repr_chain.reverse()
  643. return ExceptionChainRepr(repr_chain)
  644. class TerminalRepr(object):
  645. def __str__(self):
  646. s = self.__unicode__()
  647. if _PY2:
  648. s = s.encode("utf-8")
  649. return s
  650. def __unicode__(self):
  651. # FYI this is called from pytest-xdist's serialization of exception
  652. # information.
  653. io = py.io.TextIO()
  654. tw = py.io.TerminalWriter(file=io)
  655. self.toterminal(tw)
  656. return io.getvalue().strip()
  657. def __repr__(self):
  658. return "<%s instance at %0x>" % (self.__class__, id(self))
  659. class ExceptionRepr(TerminalRepr):
  660. def __init__(self):
  661. self.sections = []
  662. def addsection(self, name, content, sep="-"):
  663. self.sections.append((name, content, sep))
  664. def toterminal(self, tw):
  665. for name, content, sep in self.sections:
  666. tw.sep(sep, name)
  667. tw.line(content)
  668. class ExceptionChainRepr(ExceptionRepr):
  669. def __init__(self, chain):
  670. super(ExceptionChainRepr, self).__init__()
  671. self.chain = chain
  672. # reprcrash and reprtraceback of the outermost (the newest) exception
  673. # in the chain
  674. self.reprtraceback = chain[-1][0]
  675. self.reprcrash = chain[-1][1]
  676. def toterminal(self, tw):
  677. for element in self.chain:
  678. element[0].toterminal(tw)
  679. if element[2] is not None:
  680. tw.line("")
  681. tw.line(element[2], yellow=True)
  682. super(ExceptionChainRepr, self).toterminal(tw)
  683. class ReprExceptionInfo(ExceptionRepr):
  684. def __init__(self, reprtraceback, reprcrash):
  685. super(ReprExceptionInfo, self).__init__()
  686. self.reprtraceback = reprtraceback
  687. self.reprcrash = reprcrash
  688. def toterminal(self, tw):
  689. self.reprtraceback.toterminal(tw)
  690. super(ReprExceptionInfo, self).toterminal(tw)
  691. class ReprTraceback(TerminalRepr):
  692. entrysep = "_ "
  693. def __init__(self, reprentries, extraline, style):
  694. self.reprentries = reprentries
  695. self.extraline = extraline
  696. self.style = style
  697. def toterminal(self, tw):
  698. # the entries might have different styles
  699. for i, entry in enumerate(self.reprentries):
  700. if entry.style == "long":
  701. tw.line("")
  702. entry.toterminal(tw)
  703. if i < len(self.reprentries) - 1:
  704. next_entry = self.reprentries[i + 1]
  705. if (
  706. entry.style == "long"
  707. or entry.style == "short"
  708. and next_entry.style == "long"
  709. ):
  710. tw.sep(self.entrysep)
  711. if self.extraline:
  712. tw.line(self.extraline)
  713. class ReprTracebackNative(ReprTraceback):
  714. def __init__(self, tblines):
  715. self.style = "native"
  716. self.reprentries = [ReprEntryNative(tblines)]
  717. self.extraline = None
  718. class ReprEntryNative(TerminalRepr):
  719. style = "native"
  720. def __init__(self, tblines):
  721. self.lines = tblines
  722. def toterminal(self, tw):
  723. tw.write("".join(self.lines))
  724. class ReprEntry(TerminalRepr):
  725. localssep = "_ "
  726. def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
  727. self.lines = lines
  728. self.reprfuncargs = reprfuncargs
  729. self.reprlocals = reprlocals
  730. self.reprfileloc = filelocrepr
  731. self.style = style
  732. def toterminal(self, tw):
  733. if self.style == "short":
  734. self.reprfileloc.toterminal(tw)
  735. for line in self.lines:
  736. red = line.startswith("E ")
  737. tw.line(line, bold=True, red=red)
  738. # tw.line("")
  739. return
  740. if self.reprfuncargs:
  741. self.reprfuncargs.toterminal(tw)
  742. for line in self.lines:
  743. red = line.startswith("E ")
  744. tw.line(line, bold=True, red=red)
  745. if self.reprlocals:
  746. # tw.sep(self.localssep, "Locals")
  747. tw.line("")
  748. self.reprlocals.toterminal(tw)
  749. if self.reprfileloc:
  750. if self.lines:
  751. tw.line("")
  752. self.reprfileloc.toterminal(tw)
  753. def __str__(self):
  754. return "%s\n%s\n%s" % ("\n".join(self.lines), self.reprlocals, self.reprfileloc)
  755. class ReprFileLocation(TerminalRepr):
  756. def __init__(self, path, lineno, message):
  757. self.path = str(path)
  758. self.lineno = lineno
  759. self.message = message
  760. def toterminal(self, tw):
  761. # filename and lineno output for each entry,
  762. # using an output format that most editors unterstand
  763. msg = self.message
  764. i = msg.find("\n")
  765. if i != -1:
  766. msg = msg[:i]
  767. tw.write(self.path, bold=True, red=True)
  768. tw.line(":%s: %s" % (self.lineno, msg))
  769. class ReprLocals(TerminalRepr):
  770. def __init__(self, lines):
  771. self.lines = lines
  772. def toterminal(self, tw):
  773. for line in self.lines:
  774. tw.line(line)
  775. class ReprFuncArgs(TerminalRepr):
  776. def __init__(self, args):
  777. self.args = args
  778. def toterminal(self, tw):
  779. if self.args:
  780. linesofar = ""
  781. for name, value in self.args:
  782. ns = "%s = %s" % (safe_str(name), safe_str(value))
  783. if len(ns) + len(linesofar) + 2 > tw.fullwidth:
  784. if linesofar:
  785. tw.line(linesofar)
  786. linesofar = ns
  787. else:
  788. if linesofar:
  789. linesofar += ", " + ns
  790. else:
  791. linesofar = ns
  792. if linesofar:
  793. tw.line(linesofar)
  794. tw.line("")
  795. def getrawcode(obj, trycall=True):
  796. """ return code object for given function. """
  797. try:
  798. return obj.__code__
  799. except AttributeError:
  800. obj = getattr(obj, "im_func", obj)
  801. obj = getattr(obj, "func_code", obj)
  802. obj = getattr(obj, "f_code", obj)
  803. obj = getattr(obj, "__code__", obj)
  804. if trycall and not hasattr(obj, "co_firstlineno"):
  805. if hasattr(obj, "__call__") and not inspect.isclass(obj):
  806. x = getrawcode(obj.__call__, trycall=False)
  807. if hasattr(x, "co_firstlineno"):
  808. return x
  809. return obj
  810. if PY35: # RecursionError introduced in 3.5
  811. def is_recursion_error(excinfo):
  812. return excinfo.errisinstance(RecursionError) # noqa
  813. else:
  814. def is_recursion_error(excinfo):
  815. if not excinfo.errisinstance(RuntimeError):
  816. return False
  817. try:
  818. return "maximum recursion depth exceeded" in str(excinfo.value)
  819. except UnicodeError:
  820. return False