You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

797 lines
27KB

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