Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
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.

failure.py 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. # -*- test-case-name: twisted.test.test_failure -*-
  2. # See also test suite twisted.test.test_pbfailure
  3. # Copyright (c) Twisted Matrix Laboratories.
  4. # See LICENSE for details.
  5. """
  6. Asynchronous-friendly error mechanism.
  7. See L{Failure}.
  8. """
  9. # System Imports
  10. import builtins
  11. import copy
  12. import inspect
  13. import linecache
  14. import sys
  15. from inspect import getmro
  16. from io import StringIO
  17. from typing import NoReturn
  18. import opcode
  19. from twisted.python import reflect
  20. count = 0
  21. traceupLength = 4
  22. class DefaultException(Exception):
  23. pass
  24. def format_frames(frames, write, detail="default"):
  25. """
  26. Format and write frames.
  27. @param frames: is a list of frames as used by Failure.frames, with
  28. each frame being a list of
  29. (funcName, fileName, lineNumber, locals.items(), globals.items())
  30. @type frames: list
  31. @param write: this will be called with formatted strings.
  32. @type write: callable
  33. @param detail: Four detail levels are available:
  34. default, brief, verbose, and verbose-vars-not-captured.
  35. C{Failure.printDetailedTraceback} uses the latter when the caller asks
  36. for verbose, but no vars were captured, so that an explicit warning
  37. about the missing data is shown.
  38. @type detail: string
  39. """
  40. if detail not in ("default", "brief", "verbose", "verbose-vars-not-captured"):
  41. raise ValueError(
  42. "Detail must be default, brief, verbose, or "
  43. "verbose-vars-not-captured. (not %r)" % (detail,)
  44. )
  45. w = write
  46. if detail == "brief":
  47. for method, filename, lineno, localVars, globalVars in frames:
  48. w(f"{filename}:{lineno}:{method}\n")
  49. elif detail == "default":
  50. for method, filename, lineno, localVars, globalVars in frames:
  51. w(f' File "{filename}", line {lineno}, in {method}\n')
  52. w(" %s\n" % linecache.getline(filename, lineno).strip())
  53. elif detail == "verbose-vars-not-captured":
  54. for method, filename, lineno, localVars, globalVars in frames:
  55. w("%s:%d: %s(...)\n" % (filename, lineno, method))
  56. w(" [Capture of Locals and Globals disabled (use captureVars=True)]\n")
  57. elif detail == "verbose":
  58. for method, filename, lineno, localVars, globalVars in frames:
  59. w("%s:%d: %s(...)\n" % (filename, lineno, method))
  60. w(" [ Locals ]\n")
  61. # Note: the repr(val) was (self.pickled and val) or repr(val)))
  62. for name, val in localVars:
  63. w(f" {name} : {repr(val)}\n")
  64. w(" ( Globals )\n")
  65. for name, val in globalVars:
  66. w(f" {name} : {repr(val)}\n")
  67. # slyphon: i have a need to check for this value in trial
  68. # so I made it a module-level constant
  69. EXCEPTION_CAUGHT_HERE = "--- <exception caught here> ---"
  70. class NoCurrentExceptionError(Exception):
  71. """
  72. Raised when trying to create a Failure from the current interpreter
  73. exception state and there is no current exception state.
  74. """
  75. def _Traceback(stackFrames, tbFrames):
  76. """
  77. Construct a fake traceback object using a list of frames.
  78. It should have the same API as stdlib to allow interaction with
  79. other tools.
  80. @param stackFrames: [(methodname, filename, lineno, locals, globals), ...]
  81. @param tbFrames: [(methodname, filename, lineno, locals, globals), ...]
  82. """
  83. assert len(tbFrames) > 0, "Must pass some frames"
  84. # We deliberately avoid using recursion here, as the frames list may be
  85. # long.
  86. # 'stackFrames' is a list of frames above (ie, older than) the point the
  87. # exception was caught, with oldest at the start. Start by building these
  88. # into a linked list of _Frame objects (with the f_back links pointing back
  89. # towards the oldest frame).
  90. stack = None
  91. for sf in stackFrames:
  92. stack = _Frame(sf, stack)
  93. # 'tbFrames' is a list of frames from the point the exception was caught,
  94. # down to where it was thrown, with the oldest at the start. Add these to
  95. # the linked list of _Frames, but also wrap each one with a _Traceback
  96. # frame which is linked in the opposite direction (towards the newest
  97. # frame).
  98. stack = _Frame(tbFrames[0], stack)
  99. firstTb = tb = _TracebackFrame(stack)
  100. for sf in tbFrames[1:]:
  101. stack = _Frame(sf, stack)
  102. tb.tb_next = _TracebackFrame(stack)
  103. tb = tb.tb_next
  104. # Return the first _TracebackFrame.
  105. return firstTb
  106. # The set of attributes for _TracebackFrame, _Frame and _Code were taken from
  107. # https://docs.python.org/3.11/library/inspect.html Other Pythons may have a
  108. # few more attributes that should be added if needed.
  109. class _TracebackFrame:
  110. """
  111. Fake traceback object which can be passed to functions in the standard
  112. library L{traceback} module.
  113. """
  114. def __init__(self, frame):
  115. """
  116. @param frame: _Frame object
  117. """
  118. self.tb_frame = frame
  119. self.tb_lineno = frame.f_lineno
  120. self.tb_lasti = frame.f_lasti
  121. self.tb_next = None
  122. class _Frame:
  123. """
  124. A fake frame object, used by L{_Traceback}.
  125. @ivar f_code: fake L{code<types.CodeType>} object
  126. @ivar f_lineno: line number
  127. @ivar f_globals: fake f_globals dictionary (usually empty)
  128. @ivar f_locals: fake f_locals dictionary (usually empty)
  129. @ivar f_back: previous stack frame (towards the caller)
  130. """
  131. def __init__(self, frameinfo, back):
  132. """
  133. @param frameinfo: (methodname, filename, lineno, locals, globals)
  134. @param back: previous (older) stack frame
  135. @type back: C{frame}
  136. """
  137. name, filename, lineno, localz, globalz = frameinfo
  138. self.f_code = _Code(name, filename)
  139. self.f_lineno = lineno
  140. self.f_globals = dict(globalz or {})
  141. self.f_locals = dict(localz or {})
  142. self.f_back = back
  143. self.f_lasti = 0
  144. self.f_builtins = vars(builtins).copy()
  145. self.f_trace = None
  146. class _Code:
  147. """
  148. A fake code object, used by L{_Traceback} via L{_Frame}.
  149. It is intended to have the same API as the stdlib code type to allow
  150. interoperation with other tools based on that interface.
  151. """
  152. def __init__(self, name, filename):
  153. self.co_name = name
  154. self.co_filename = filename
  155. self.co_lnotab = b""
  156. self.co_firstlineno = 0
  157. self.co_argcount = 0
  158. self.co_varnames = []
  159. self.co_code = b""
  160. self.co_cellvars = ()
  161. self.co_consts = ()
  162. self.co_flags = 0
  163. self.co_freevars = ()
  164. self.co_posonlyargcount = 0
  165. self.co_kwonlyargcount = 0
  166. self.co_names = ()
  167. self.co_nlocals = 0
  168. self.co_stacksize = 0
  169. def co_positions(self):
  170. return ((None, None, None, None),)
  171. _inlineCallbacksExtraneous = []
  172. def _extraneous(f):
  173. """
  174. Mark the given callable as extraneous to inlineCallbacks exception
  175. reporting; don't show these functions.
  176. @param f: a function that you NEVER WANT TO SEE AGAIN in ANY TRACEBACK
  177. reported by Failure.
  178. @type f: function
  179. @return: f
  180. """
  181. _inlineCallbacksExtraneous.append(f.__code__)
  182. return f
  183. class Failure(BaseException):
  184. """
  185. A basic abstraction for an error that has occurred.
  186. This is necessary because Python's built-in error mechanisms are
  187. inconvenient for asynchronous communication.
  188. The C{stack} and C{frame} attributes contain frames. Each frame is a tuple
  189. of (funcName, fileName, lineNumber, localsItems, globalsItems), where
  190. localsItems and globalsItems are the contents of
  191. C{locals().items()}/C{globals().items()} for that frame, or an empty tuple
  192. if those details were not captured.
  193. @ivar value: The exception instance responsible for this failure.
  194. @ivar type: The exception's class.
  195. @ivar stack: list of frames, innermost last, excluding C{Failure.__init__}.
  196. @ivar frames: list of frames, innermost first.
  197. """
  198. pickled = 0
  199. stack = None
  200. # The opcode of "yield" in Python bytecode. We need this in
  201. # _findFailure in order to identify whether an exception was
  202. # thrown by a throwExceptionIntoGenerator.
  203. # on PY3, b'a'[0] == 97 while in py2 b'a'[0] == b'a' opcodes
  204. # are stored in bytes so we need to properly account for this
  205. # difference.
  206. _yieldOpcode = opcode.opmap["YIELD_VALUE"]
  207. def __init__(self, exc_value=None, exc_type=None, exc_tb=None, captureVars=False):
  208. """
  209. Initialize me with an explanation of the error.
  210. By default, this will use the current C{exception}
  211. (L{sys.exc_info}()). However, if you want to specify a
  212. particular kind of failure, you can pass an exception as an
  213. argument.
  214. If no C{exc_value} is passed, then an "original" C{Failure} will
  215. be searched for. If the current exception handler that this
  216. C{Failure} is being constructed in is handling an exception
  217. raised by L{raiseException}, then this C{Failure} will act like
  218. the original C{Failure}.
  219. For C{exc_tb} only L{traceback} instances or L{None} are allowed.
  220. If L{None} is supplied for C{exc_value}, the value of C{exc_tb} is
  221. ignored, otherwise if C{exc_tb} is L{None}, it will be found from
  222. execution context (ie, L{sys.exc_info}).
  223. @param captureVars: if set, capture locals and globals of stack
  224. frames. This is pretty slow, and makes no difference unless you
  225. are going to use L{printDetailedTraceback}.
  226. """
  227. global count
  228. count = count + 1
  229. self.count = count
  230. self.type = self.value = tb = None
  231. self.captureVars = captureVars
  232. if isinstance(exc_value, str) and exc_type is None:
  233. raise TypeError("Strings are not supported by Failure")
  234. stackOffset = 0
  235. if exc_value is None:
  236. exc_value = self._findFailure()
  237. if exc_value is None:
  238. self.type, self.value, tb = sys.exc_info()
  239. if self.type is None:
  240. raise NoCurrentExceptionError()
  241. stackOffset = 1
  242. elif exc_type is None:
  243. if isinstance(exc_value, Exception):
  244. self.type = exc_value.__class__
  245. else:
  246. # Allow arbitrary objects.
  247. self.type = type(exc_value)
  248. self.value = exc_value
  249. else:
  250. self.type = exc_type
  251. self.value = exc_value
  252. if isinstance(self.value, Failure):
  253. self._extrapolate(self.value)
  254. return
  255. if hasattr(self.value, "__failure__"):
  256. # For exceptions propagated through coroutine-awaiting (see
  257. # Deferred.send, AKA Deferred.__next__), which can't be raised as
  258. # Failure because that would mess up the ability to except: them:
  259. self._extrapolate(self.value.__failure__)
  260. # Clean up the inherently circular reference established by storing
  261. # the failure there. This should make the common case of a Twisted
  262. # / Deferred-returning coroutine somewhat less hard on the garbage
  263. # collector.
  264. del self.value.__failure__
  265. return
  266. if tb is None:
  267. if exc_tb:
  268. tb = exc_tb
  269. elif getattr(self.value, "__traceback__", None):
  270. # Python 3
  271. tb = self.value.__traceback__
  272. frames = self.frames = []
  273. stack = self.stack = []
  274. # Added 2003-06-23 by Chris Armstrong. Yes, I actually have a
  275. # use case where I need this traceback object, and I've made
  276. # sure that it'll be cleaned up.
  277. self.tb = tb
  278. if tb:
  279. f = tb.tb_frame
  280. elif not isinstance(self.value, Failure):
  281. # We don't do frame introspection since it's expensive,
  282. # and if we were passed a plain exception with no
  283. # traceback, it's not useful anyway
  284. f = stackOffset = None
  285. while stackOffset and f:
  286. # This excludes this Failure.__init__ frame from the
  287. # stack, leaving it to start with our caller instead.
  288. f = f.f_back
  289. stackOffset -= 1
  290. # Keeps the *full* stack. Formerly in spread.pb.print_excFullStack:
  291. #
  292. # The need for this function arises from the fact that several
  293. # PB classes have the peculiar habit of discarding exceptions
  294. # with bareword "except:"s. This premature exception
  295. # catching means tracebacks generated here don't tend to show
  296. # what called upon the PB object.
  297. while f:
  298. if captureVars:
  299. localz = f.f_locals.copy()
  300. if f.f_locals is f.f_globals:
  301. globalz = {}
  302. else:
  303. globalz = f.f_globals.copy()
  304. for d in globalz, localz:
  305. if "__builtins__" in d:
  306. del d["__builtins__"]
  307. localz = localz.items()
  308. globalz = globalz.items()
  309. else:
  310. localz = globalz = ()
  311. stack.insert(
  312. 0,
  313. (
  314. f.f_code.co_name,
  315. f.f_code.co_filename,
  316. f.f_lineno,
  317. localz,
  318. globalz,
  319. ),
  320. )
  321. f = f.f_back
  322. while tb is not None:
  323. f = tb.tb_frame
  324. if captureVars:
  325. localz = f.f_locals.copy()
  326. if f.f_locals is f.f_globals:
  327. globalz = {}
  328. else:
  329. globalz = f.f_globals.copy()
  330. for d in globalz, localz:
  331. if "__builtins__" in d:
  332. del d["__builtins__"]
  333. localz = list(localz.items())
  334. globalz = list(globalz.items())
  335. else:
  336. localz = globalz = ()
  337. frames.append(
  338. (
  339. f.f_code.co_name,
  340. f.f_code.co_filename,
  341. tb.tb_lineno,
  342. localz,
  343. globalz,
  344. )
  345. )
  346. tb = tb.tb_next
  347. if inspect.isclass(self.type) and issubclass(self.type, Exception):
  348. parentCs = getmro(self.type)
  349. self.parents = list(map(reflect.qual, parentCs))
  350. else:
  351. self.parents = [self.type]
  352. def _extrapolate(self, otherFailure):
  353. """
  354. Extrapolate from one failure into another, copying its stack frames.
  355. @param otherFailure: Another L{Failure}, whose traceback information,
  356. if any, should be preserved as part of the stack presented by this
  357. one.
  358. @type otherFailure: L{Failure}
  359. """
  360. # Copy all infos from that failure (including self.frames).
  361. self.__dict__ = copy.copy(otherFailure.__dict__)
  362. # If we are re-throwing a Failure, we merge the stack-trace stored in
  363. # the failure with the current exception's stack. This integrated with
  364. # throwExceptionIntoGenerator and allows to provide full stack trace,
  365. # even if we go through several layers of inlineCallbacks.
  366. _, _, tb = sys.exc_info()
  367. frames = []
  368. while tb is not None:
  369. f = tb.tb_frame
  370. if f.f_code not in _inlineCallbacksExtraneous:
  371. frames.append(
  372. (f.f_code.co_name, f.f_code.co_filename, tb.tb_lineno, (), ())
  373. )
  374. tb = tb.tb_next
  375. # Merging current stack with stack stored in the Failure.
  376. frames.extend(self.frames)
  377. self.frames = frames
  378. def trap(self, *errorTypes):
  379. """
  380. Trap this failure if its type is in a predetermined list.
  381. This allows you to trap a Failure in an error callback. It will be
  382. automatically re-raised if it is not a type that you expect.
  383. The reason for having this particular API is because it's very useful
  384. in Deferred errback chains::
  385. def _ebFoo(self, failure):
  386. r = failure.trap(Spam, Eggs)
  387. print('The Failure is due to either Spam or Eggs!')
  388. if r == Spam:
  389. print('Spam did it!')
  390. elif r == Eggs:
  391. print('Eggs did it!')
  392. If the failure is not a Spam or an Eggs, then the Failure will be
  393. 'passed on' to the next errback. In Python 2 the Failure will be
  394. raised; in Python 3 the underlying exception will be re-raised.
  395. @type errorTypes: L{Exception}
  396. """
  397. error = self.check(*errorTypes)
  398. if not error:
  399. self.raiseException()
  400. return error
  401. def check(self, *errorTypes):
  402. """
  403. Check if this failure's type is in a predetermined list.
  404. @type errorTypes: list of L{Exception} classes or
  405. fully-qualified class names.
  406. @returns: the matching L{Exception} type, or None if no match.
  407. """
  408. for error in errorTypes:
  409. err = error
  410. if inspect.isclass(error) and issubclass(error, Exception):
  411. err = reflect.qual(error)
  412. if err in self.parents:
  413. return error
  414. return None
  415. def raiseException(self) -> NoReturn:
  416. """
  417. raise the original exception, preserving traceback
  418. information if available.
  419. """
  420. raise self.value.with_traceback(self.tb)
  421. @_extraneous
  422. def throwExceptionIntoGenerator(self, g):
  423. """
  424. Throw the original exception into the given generator,
  425. preserving traceback information if available.
  426. @return: The next value yielded from the generator.
  427. @raise StopIteration: If there are no more values in the generator.
  428. @raise anything else: Anything that the generator raises.
  429. """
  430. # Note that the actual magic to find the traceback information
  431. # is done in _findFailure.
  432. return g.throw(self.type, self.value, self.tb)
  433. @classmethod
  434. def _findFailure(cls):
  435. """
  436. Find the failure that represents the exception currently in context.
  437. """
  438. tb = sys.exc_info()[-1]
  439. if not tb:
  440. return
  441. secondLastTb = None
  442. lastTb = tb
  443. while lastTb.tb_next:
  444. secondLastTb = lastTb
  445. lastTb = lastTb.tb_next
  446. lastFrame = lastTb.tb_frame
  447. # NOTE: f_locals.get('self') is used rather than
  448. # f_locals['self'] because psyco frames do not contain
  449. # anything in their locals() dicts. psyco makes debugging
  450. # difficult anyhow, so losing the Failure objects (and thus
  451. # the tracebacks) here when it is used is not that big a deal.
  452. # Handle raiseException-originated exceptions
  453. if lastFrame.f_code is cls.raiseException.__code__:
  454. return lastFrame.f_locals.get("self")
  455. # Handle throwExceptionIntoGenerator-originated exceptions
  456. # this is tricky, and differs if the exception was caught
  457. # inside the generator, or above it:
  458. # It is only really originating from
  459. # throwExceptionIntoGenerator if the bottom of the traceback
  460. # is a yield.
  461. # Pyrex and Cython extensions create traceback frames
  462. # with no co_code, but they can't yield so we know it's okay to
  463. # just return here.
  464. if (not lastFrame.f_code.co_code) or lastFrame.f_code.co_code[
  465. lastTb.tb_lasti
  466. ] != cls._yieldOpcode:
  467. return
  468. # If the exception was caught above the generator.throw
  469. # (outside the generator), it will appear in the tb (as the
  470. # second last item):
  471. if secondLastTb:
  472. frame = secondLastTb.tb_frame
  473. if frame.f_code is cls.throwExceptionIntoGenerator.__code__:
  474. return frame.f_locals.get("self")
  475. # If the exception was caught below the generator.throw
  476. # (inside the generator), it will appear in the frames' linked
  477. # list, above the top-level traceback item (which must be the
  478. # generator frame itself, thus its caller is
  479. # throwExceptionIntoGenerator).
  480. frame = tb.tb_frame.f_back
  481. if frame and frame.f_code is cls.throwExceptionIntoGenerator.__code__:
  482. return frame.f_locals.get("self")
  483. def __repr__(self) -> str:
  484. return "<{} {}: {}>".format(
  485. reflect.qual(self.__class__),
  486. reflect.qual(self.type),
  487. self.getErrorMessage(),
  488. )
  489. def __str__(self) -> str:
  490. return "[Failure instance: %s]" % self.getBriefTraceback()
  491. def __getstate__(self):
  492. """Avoid pickling objects in the traceback."""
  493. if self.pickled:
  494. return self.__dict__
  495. c = self.__dict__.copy()
  496. c["frames"] = [
  497. [
  498. v[0],
  499. v[1],
  500. v[2],
  501. _safeReprVars(v[3]),
  502. _safeReprVars(v[4]),
  503. ]
  504. for v in self.frames
  505. ]
  506. # Added 2003-06-23. See comment above in __init__
  507. c["tb"] = None
  508. if self.stack is not None:
  509. # XXX: This is a band-aid. I can't figure out where these
  510. # (failure.stack is None) instances are coming from.
  511. c["stack"] = [
  512. [
  513. v[0],
  514. v[1],
  515. v[2],
  516. _safeReprVars(v[3]),
  517. _safeReprVars(v[4]),
  518. ]
  519. for v in self.stack
  520. ]
  521. c["pickled"] = 1
  522. return c
  523. def cleanFailure(self):
  524. """
  525. Remove references to other objects, replacing them with strings.
  526. On Python 3, this will also set the C{__traceback__} attribute of the
  527. exception instance to L{None}.
  528. """
  529. self.__dict__ = self.__getstate__()
  530. if getattr(self.value, "__traceback__", None):
  531. # Python 3
  532. self.value.__traceback__ = None
  533. def getTracebackObject(self):
  534. """
  535. Get an object that represents this Failure's stack that can be passed
  536. to traceback.extract_tb.
  537. If the original traceback object is still present, return that. If this
  538. traceback object has been lost but we still have the information,
  539. return a fake traceback object (see L{_Traceback}). If there is no
  540. traceback information at all, return None.
  541. """
  542. if self.tb is not None:
  543. return self.tb
  544. elif len(self.frames) > 0:
  545. return _Traceback(self.stack, self.frames)
  546. else:
  547. return None
  548. def getErrorMessage(self) -> str:
  549. """
  550. Get a string of the exception which caused this Failure.
  551. """
  552. if isinstance(self.value, Failure):
  553. return self.value.getErrorMessage()
  554. return reflect.safe_str(self.value)
  555. def getBriefTraceback(self) -> str:
  556. io = StringIO()
  557. self.printBriefTraceback(file=io)
  558. return io.getvalue()
  559. def getTraceback(self, elideFrameworkCode: int = 0, detail: str = "default") -> str:
  560. io = StringIO()
  561. self.printTraceback(
  562. file=io, elideFrameworkCode=elideFrameworkCode, detail=detail
  563. )
  564. return io.getvalue()
  565. def printTraceback(self, file=None, elideFrameworkCode=False, detail="default"):
  566. """
  567. Emulate Python's standard error reporting mechanism.
  568. @param file: If specified, a file-like object to which to write the
  569. traceback.
  570. @param elideFrameworkCode: A flag indicating whether to attempt to
  571. remove uninteresting frames from within Twisted itself from the
  572. output.
  573. @param detail: A string indicating how much information to include
  574. in the traceback. Must be one of C{'brief'}, C{'default'}, or
  575. C{'verbose'}.
  576. """
  577. if file is None:
  578. from twisted.python import log
  579. file = log.logerr
  580. w = file.write
  581. if detail == "verbose" and not self.captureVars:
  582. # We don't have any locals or globals, so rather than show them as
  583. # empty make the output explicitly say that we don't have them at
  584. # all.
  585. formatDetail = "verbose-vars-not-captured"
  586. else:
  587. formatDetail = detail
  588. # Preamble
  589. if detail == "verbose":
  590. w(
  591. "*--- Failure #%d%s---\n"
  592. % (self.count, (self.pickled and " (pickled) ") or " ")
  593. )
  594. elif detail == "brief":
  595. if self.frames:
  596. hasFrames = "Traceback"
  597. else:
  598. hasFrames = "Traceback (failure with no frames)"
  599. w(
  600. "%s: %s: %s\n"
  601. % (hasFrames, reflect.safe_str(self.type), reflect.safe_str(self.value))
  602. )
  603. else:
  604. w("Traceback (most recent call last):\n")
  605. # Frames, formatted in appropriate style
  606. if self.frames:
  607. if not elideFrameworkCode:
  608. format_frames(self.stack[-traceupLength:], w, formatDetail)
  609. w(f"{EXCEPTION_CAUGHT_HERE}\n")
  610. format_frames(self.frames, w, formatDetail)
  611. elif not detail == "brief":
  612. # Yeah, it's not really a traceback, despite looking like one...
  613. w("Failure: ")
  614. # Postamble, if any
  615. if not detail == "brief":
  616. w(f"{reflect.qual(self.type)}: {reflect.safe_str(self.value)}\n")
  617. # Chaining
  618. if isinstance(self.value, Failure):
  619. # TODO: indentation for chained failures?
  620. file.write(" (chained Failure)\n")
  621. self.value.printTraceback(file, elideFrameworkCode, detail)
  622. if detail == "verbose":
  623. w("*--- End of Failure #%d ---\n" % self.count)
  624. def printBriefTraceback(self, file=None, elideFrameworkCode=0):
  625. """
  626. Print a traceback as densely as possible.
  627. """
  628. self.printTraceback(file, elideFrameworkCode, detail="brief")
  629. def printDetailedTraceback(self, file=None, elideFrameworkCode=0):
  630. """
  631. Print a traceback with detailed locals and globals information.
  632. """
  633. self.printTraceback(file, elideFrameworkCode, detail="verbose")
  634. def _safeReprVars(varsDictItems):
  635. """
  636. Convert a list of (name, object) pairs into (name, repr) pairs.
  637. L{twisted.python.reflect.safe_repr} is used to generate the repr, so no
  638. exceptions will be raised by faulty C{__repr__} methods.
  639. @param varsDictItems: a sequence of (name, value) pairs as returned by e.g.
  640. C{locals().items()}.
  641. @returns: a sequence of (name, repr) pairs.
  642. """
  643. return [(name, reflect.safe_repr(obj)) for (name, obj) in varsDictItems]
  644. # slyphon: make post-morteming exceptions tweakable
  645. DO_POST_MORTEM = True
  646. def _debuginit(
  647. self,
  648. exc_value=None,
  649. exc_type=None,
  650. exc_tb=None,
  651. captureVars=False,
  652. Failure__init__=Failure.__init__,
  653. ):
  654. """
  655. Initialize failure object, possibly spawning pdb.
  656. """
  657. if (exc_value, exc_type, exc_tb) == (None, None, None):
  658. exc = sys.exc_info()
  659. if not exc[0] == self.__class__ and DO_POST_MORTEM:
  660. try:
  661. strrepr = str(exc[1])
  662. except BaseException:
  663. strrepr = "broken str"
  664. print(
  665. "Jumping into debugger for post-mortem of exception '{}':".format(
  666. strrepr
  667. )
  668. )
  669. import pdb
  670. pdb.post_mortem(exc[2])
  671. Failure__init__(self, exc_value, exc_type, exc_tb, captureVars)
  672. def startDebugMode():
  673. """
  674. Enable debug hooks for Failures.
  675. """
  676. Failure.__init__ = _debuginit