Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import inspect 

2import re 

3import sys 

4import traceback 

5from inspect import CO_VARARGS 

6from inspect import CO_VARKEYWORDS 

7from io import StringIO 

8from traceback import format_exception_only 

9from types import CodeType 

10from types import FrameType 

11from types import TracebackType 

12from typing import Any 

13from typing import Callable 

14from typing import Dict 

15from typing import Generic 

16from typing import Iterable 

17from typing import List 

18from typing import Mapping 

19from typing import Optional 

20from typing import Pattern 

21from typing import Sequence 

22from typing import Set 

23from typing import Tuple 

24from typing import TypeVar 

25from typing import Union 

26from weakref import ref 

27 

28import attr 

29import pluggy 

30import py 

31 

32import _pytest 

33from _pytest._code.source import findsource 

34from _pytest._code.source import getrawcode 

35from _pytest._code.source import getstatementrange_ast 

36from _pytest._code.source import Source 

37from _pytest._io import TerminalWriter 

38from _pytest._io.saferepr import safeformat 

39from _pytest._io.saferepr import saferepr 

40from _pytest.compat import ATTRS_EQ_FIELD 

41from _pytest.compat import get_real_func 

42from _pytest.compat import overload 

43from _pytest.compat import TYPE_CHECKING 

44 

45if TYPE_CHECKING: 

46 from typing import Type 

47 from typing_extensions import Literal 

48 from weakref import ReferenceType 

49 

50 _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] 

51 

52 

53class Code: 

54 """Wrapper around Python code objects.""" 

55 

56 def __init__(self, rawcode) -> None: 

57 if not hasattr(rawcode, "co_filename"): 

58 rawcode = getrawcode(rawcode) 

59 if not isinstance(rawcode, CodeType): 

60 raise TypeError("not a code object: {!r}".format(rawcode)) 

61 self.filename = rawcode.co_filename 

62 self.firstlineno = rawcode.co_firstlineno - 1 

63 self.name = rawcode.co_name 

64 self.raw = rawcode 

65 

66 def __eq__(self, other): 

67 return self.raw == other.raw 

68 

69 # Ignore type because of https://github.com/python/mypy/issues/4266. 

70 __hash__ = None # type: ignore 

71 

72 @property 

73 def path(self) -> Union[py.path.local, str]: 

74 """Return a path object pointing to source code (or a str in case 

75 of OSError / non-existing file). 

76 """ 

77 if not self.raw.co_filename: 

78 return "" 

79 try: 

80 p = py.path.local(self.raw.co_filename) 

81 # maybe don't try this checking 

82 if not p.check(): 

83 raise OSError("py.path check failed.") 

84 return p 

85 except OSError: 

86 # XXX maybe try harder like the weird logic 

87 # in the standard lib [linecache.updatecache] does? 

88 return self.raw.co_filename 

89 

90 @property 

91 def fullsource(self) -> Optional["Source"]: 

92 """Return a _pytest._code.Source object for the full source file of the code.""" 

93 full, _ = findsource(self.raw) 

94 return full 

95 

96 def source(self) -> "Source": 

97 """Return a _pytest._code.Source object for the code object's source only.""" 

98 # return source only for that part of code 

99 return Source(self.raw) 

100 

101 def getargs(self, var: bool = False) -> Tuple[str, ...]: 

102 """Return a tuple with the argument names for the code object. 

103 

104 If 'var' is set True also return the names of the variable and 

105 keyword arguments when present. 

106 """ 

107 # Handy shortcut for getting args. 

108 raw = self.raw 

109 argcount = raw.co_argcount 

110 if var: 

111 argcount += raw.co_flags & CO_VARARGS 

112 argcount += raw.co_flags & CO_VARKEYWORDS 

113 return raw.co_varnames[:argcount] 

114 

115 

116class Frame: 

117 """Wrapper around a Python frame holding f_locals and f_globals 

118 in which expressions can be evaluated.""" 

119 

120 def __init__(self, frame: FrameType) -> None: 

121 self.lineno = frame.f_lineno - 1 

122 self.f_globals = frame.f_globals 

123 self.f_locals = frame.f_locals 

124 self.raw = frame 

125 self.code = Code(frame.f_code) 

126 

127 @property 

128 def statement(self) -> "Source": 

129 """Statement this frame is at.""" 

130 if self.code.fullsource is None: 

131 return Source("") 

132 return self.code.fullsource.getstatement(self.lineno) 

133 

134 def eval(self, code, **vars): 

135 """Evaluate 'code' in the frame. 

136 

137 'vars' are optional additional local variables. 

138 

139 Returns the result of the evaluation. 

140 """ 

141 f_locals = self.f_locals.copy() 

142 f_locals.update(vars) 

143 return eval(code, self.f_globals, f_locals) 

144 

145 def repr(self, object: object) -> str: 

146 """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" 

147 return saferepr(object) 

148 

149 def getargs(self, var: bool = False): 

150 """Return a list of tuples (name, value) for all arguments. 

151 

152 If 'var' is set True, also include the variable and keyword arguments 

153 when present. 

154 """ 

155 retval = [] 

156 for arg in self.code.getargs(var): 

157 try: 

158 retval.append((arg, self.f_locals[arg])) 

159 except KeyError: 

160 pass # this can occur when using Psyco 

161 return retval 

162 

163 

164class TracebackEntry: 

165 """A single entry in a Traceback.""" 

166 

167 _repr_style = None # type: Optional[Literal["short", "long"]] 

168 exprinfo = None 

169 

170 def __init__( 

171 self, 

172 rawentry: TracebackType, 

173 excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, 

174 ) -> None: 

175 self._excinfo = excinfo 

176 self._rawentry = rawentry 

177 self.lineno = rawentry.tb_lineno - 1 

178 

179 def set_repr_style(self, mode: "Literal['short', 'long']") -> None: 

180 assert mode in ("short", "long") 

181 self._repr_style = mode 

182 

183 @property 

184 def frame(self) -> Frame: 

185 return Frame(self._rawentry.tb_frame) 

186 

187 @property 

188 def relline(self) -> int: 

189 return self.lineno - self.frame.code.firstlineno 

190 

191 def __repr__(self) -> str: 

192 return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1) 

193 

194 @property 

195 def statement(self) -> "Source": 

196 """_pytest._code.Source object for the current statement.""" 

197 source = self.frame.code.fullsource 

198 assert source is not None 

199 return source.getstatement(self.lineno) 

200 

201 @property 

202 def path(self) -> Union[py.path.local, str]: 

203 """Path to the source code.""" 

204 return self.frame.code.path 

205 

206 @property 

207 def locals(self) -> Dict[str, Any]: 

208 """Locals of underlying frame.""" 

209 return self.frame.f_locals 

210 

211 def getfirstlinesource(self) -> int: 

212 return self.frame.code.firstlineno 

213 

214 def getsource(self, astcache=None) -> Optional["Source"]: 

215 """Return failing source code.""" 

216 # we use the passed in astcache to not reparse asttrees 

217 # within exception info printing 

218 source = self.frame.code.fullsource 

219 if source is None: 

220 return None 

221 key = astnode = None 

222 if astcache is not None: 

223 key = self.frame.code.path 

224 if key is not None: 

225 astnode = astcache.get(key, None) 

226 start = self.getfirstlinesource() 

227 try: 

228 astnode, _, end = getstatementrange_ast( 

229 self.lineno, source, astnode=astnode 

230 ) 

231 except SyntaxError: 

232 end = self.lineno + 1 

233 else: 

234 if key is not None: 

235 astcache[key] = astnode 

236 return source[start:end] 

237 

238 source = property(getsource) 

239 

240 def ishidden(self) -> bool: 

241 """Return True if the current frame has a var __tracebackhide__ 

242 resolving to True. 

243 

244 If __tracebackhide__ is a callable, it gets called with the 

245 ExceptionInfo instance and can decide whether to hide the traceback. 

246 

247 Mostly for internal use. 

248 """ 

249 f = self.frame 

250 tbh = f.f_locals.get( 

251 "__tracebackhide__", f.f_globals.get("__tracebackhide__", False) 

252 ) # type: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] 

253 if tbh and callable(tbh): 

254 return tbh(None if self._excinfo is None else self._excinfo()) 

255 return tbh 

256 

257 def __str__(self) -> str: 

258 name = self.frame.code.name 

259 try: 

260 line = str(self.statement).lstrip() 

261 except KeyboardInterrupt: 

262 raise 

263 except BaseException: 

264 line = "???" 

265 # This output does not quite match Python's repr for traceback entries, 

266 # but changing it to do so would break certain plugins. See 

267 # https://github.com/pytest-dev/pytest/pull/7535/ for details. 

268 return " File %r:%d in %s\n %s\n" % ( 

269 str(self.path), 

270 self.lineno + 1, 

271 name, 

272 line, 

273 ) 

274 

275 @property 

276 def name(self) -> str: 

277 """co_name of underlying code.""" 

278 return self.frame.code.raw.co_name 

279 

280 

281class Traceback(List[TracebackEntry]): 

282 """Traceback objects encapsulate and offer higher level access to Traceback entries.""" 

283 

284 def __init__( 

285 self, 

286 tb: Union[TracebackType, Iterable[TracebackEntry]], 

287 excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, 

288 ) -> None: 

289 """Initialize from given python traceback object and ExceptionInfo.""" 

290 self._excinfo = excinfo 

291 if isinstance(tb, TracebackType): 

292 

293 def f(cur: TracebackType) -> Iterable[TracebackEntry]: 

294 cur_ = cur # type: Optional[TracebackType] 

295 while cur_ is not None: 

296 yield TracebackEntry(cur_, excinfo=excinfo) 

297 cur_ = cur_.tb_next 

298 

299 super().__init__(f(tb)) 

300 else: 

301 super().__init__(tb) 

302 

303 def cut( 

304 self, 

305 path=None, 

306 lineno: Optional[int] = None, 

307 firstlineno: Optional[int] = None, 

308 excludepath: Optional[py.path.local] = None, 

309 ) -> "Traceback": 

310 """Return a Traceback instance wrapping part of this Traceback. 

311 

312 By providing any combination of path, lineno and firstlineno, the 

313 first frame to start the to-be-returned traceback is determined. 

314 

315 This allows cutting the first part of a Traceback instance e.g. 

316 for formatting reasons (removing some uninteresting bits that deal 

317 with handling of the exception/traceback). 

318 """ 

319 for x in self: 

320 code = x.frame.code 

321 codepath = code.path 

322 if ( 

323 (path is None or codepath == path) 

324 and ( 

325 excludepath is None 

326 or not isinstance(codepath, py.path.local) 

327 or not codepath.relto(excludepath) 

328 ) 

329 and (lineno is None or x.lineno == lineno) 

330 and (firstlineno is None or x.frame.code.firstlineno == firstlineno) 

331 ): 

332 return Traceback(x._rawentry, self._excinfo) 

333 return self 

334 

335 @overload 

336 def __getitem__(self, key: int) -> TracebackEntry: 

337 raise NotImplementedError() 

338 

339 @overload # noqa: F811 

340 def __getitem__(self, key: slice) -> "Traceback": # noqa: F811 

341 raise NotImplementedError() 

342 

343 def __getitem__( # noqa: F811 

344 self, key: Union[int, slice] 

345 ) -> Union[TracebackEntry, "Traceback"]: 

346 if isinstance(key, slice): 

347 return self.__class__(super().__getitem__(key)) 

348 else: 

349 return super().__getitem__(key) 

350 

351 def filter( 

352 self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden() 

353 ) -> "Traceback": 

354 """Return a Traceback instance with certain items removed 

355 

356 fn is a function that gets a single argument, a TracebackEntry 

357 instance, and should return True when the item should be added 

358 to the Traceback, False when not. 

359 

360 By default this removes all the TracebackEntries which are hidden 

361 (see ishidden() above). 

362 """ 

363 return Traceback(filter(fn, self), self._excinfo) 

364 

365 def getcrashentry(self) -> TracebackEntry: 

366 """Return last non-hidden traceback entry that lead to the exception of a traceback.""" 

367 for i in range(-1, -len(self) - 1, -1): 

368 entry = self[i] 

369 if not entry.ishidden(): 

370 return entry 

371 return self[-1] 

372 

373 def recursionindex(self) -> Optional[int]: 

374 """Return the index of the frame/TracebackEntry where recursion originates if 

375 appropriate, None if no recursion occurred.""" 

376 cache = {} # type: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] 

377 for i, entry in enumerate(self): 

378 # id for the code.raw is needed to work around 

379 # the strange metaprogramming in the decorator lib from pypi 

380 # which generates code objects that have hash/value equality 

381 # XXX needs a test 

382 key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno 

383 # print "checking for recursion at", key 

384 values = cache.setdefault(key, []) 

385 if values: 

386 f = entry.frame 

387 loc = f.f_locals 

388 for otherloc in values: 

389 if f.eval( 

390 co_equal, 

391 __recursioncache_locals_1=loc, 

392 __recursioncache_locals_2=otherloc, 

393 ): 

394 return i 

395 values.append(entry.frame.f_locals) 

396 return None 

397 

398 

399co_equal = compile( 

400 "__recursioncache_locals_1 == __recursioncache_locals_2", "?", "eval" 

401) 

402 

403 

404_E = TypeVar("_E", bound=BaseException, covariant=True) 

405 

406 

407@attr.s(repr=False) 

408class ExceptionInfo(Generic[_E]): 

409 """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" 

410 

411 _assert_start_repr = "AssertionError('assert " 

412 

413 _excinfo = attr.ib(type=Optional[Tuple["Type[_E]", "_E", TracebackType]]) 

414 _striptext = attr.ib(type=str, default="") 

415 _traceback = attr.ib(type=Optional[Traceback], default=None) 

416 

417 @classmethod 

418 def from_exc_info( 

419 cls, 

420 exc_info: Tuple["Type[_E]", "_E", TracebackType], 

421 exprinfo: Optional[str] = None, 

422 ) -> "ExceptionInfo[_E]": 

423 """Returns an ExceptionInfo for an existing exc_info tuple. 

424 

425 .. warning:: 

426 

427 Experimental API 

428 

429 :param exprinfo: a text string helping to determine if we should 

430 strip ``AssertionError`` from the output, defaults 

431 to the exception message/``__str__()`` 

432 """ 

433 _striptext = "" 

434 if exprinfo is None and isinstance(exc_info[1], AssertionError): 

435 exprinfo = getattr(exc_info[1], "msg", None) 

436 if exprinfo is None: 

437 exprinfo = saferepr(exc_info[1]) 

438 if exprinfo and exprinfo.startswith(cls._assert_start_repr): 

439 _striptext = "AssertionError: " 

440 

441 return cls(exc_info, _striptext) 

442 

443 @classmethod 

444 def from_current( 

445 cls, exprinfo: Optional[str] = None 

446 ) -> "ExceptionInfo[BaseException]": 

447 """Returns an ExceptionInfo matching the current traceback. 

448 

449 .. warning:: 

450 

451 Experimental API 

452 

453 :param exprinfo: a text string helping to determine if we should 

454 strip ``AssertionError`` from the output, defaults 

455 to the exception message/``__str__()`` 

456 """ 

457 tup = sys.exc_info() 

458 assert tup[0] is not None, "no current exception" 

459 assert tup[1] is not None, "no current exception" 

460 assert tup[2] is not None, "no current exception" 

461 exc_info = (tup[0], tup[1], tup[2]) 

462 return ExceptionInfo.from_exc_info(exc_info, exprinfo) 

463 

464 @classmethod 

465 def for_later(cls) -> "ExceptionInfo[_E]": 

466 """Return an unfilled ExceptionInfo.""" 

467 return cls(None) 

468 

469 def fill_unfilled(self, exc_info: Tuple["Type[_E]", _E, TracebackType]) -> None: 

470 """fill an unfilled ExceptionInfo created with for_later()""" 

471 assert self._excinfo is None, "ExceptionInfo was already filled" 

472 self._excinfo = exc_info 

473 

474 @property 

475 def type(self) -> "Type[_E]": 

476 """The exception class.""" 

477 assert ( 

478 self._excinfo is not None 

479 ), ".type can only be used after the context manager exits" 

480 return self._excinfo[0] 

481 

482 @property 

483 def value(self) -> _E: 

484 """The exception value.""" 

485 assert ( 

486 self._excinfo is not None 

487 ), ".value can only be used after the context manager exits" 

488 return self._excinfo[1] 

489 

490 @property 

491 def tb(self) -> TracebackType: 

492 """The exception raw traceback.""" 

493 assert ( 

494 self._excinfo is not None 

495 ), ".tb can only be used after the context manager exits" 

496 return self._excinfo[2] 

497 

498 @property 

499 def typename(self) -> str: 

500 """The type name of the exception.""" 

501 assert ( 

502 self._excinfo is not None 

503 ), ".typename can only be used after the context manager exits" 

504 return self.type.__name__ 

505 

506 @property 

507 def traceback(self) -> Traceback: 

508 """The traceback.""" 

509 if self._traceback is None: 

510 self._traceback = Traceback(self.tb, excinfo=ref(self)) 

511 return self._traceback 

512 

513 @traceback.setter 

514 def traceback(self, value: Traceback) -> None: 

515 self._traceback = value 

516 

517 def __repr__(self) -> str: 

518 if self._excinfo is None: 

519 return "<ExceptionInfo for raises contextmanager>" 

520 return "<{} {} tblen={}>".format( 

521 self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback) 

522 ) 

523 

524 def exconly(self, tryshort: bool = False) -> str: 

525 """Return the exception as a string. 

526 

527 When 'tryshort' resolves to True, and the exception is a 

528 _pytest._code._AssertionError, only the actual exception part of 

529 the exception representation is returned (so 'AssertionError: ' is 

530 removed from the beginning). 

531 """ 

532 lines = format_exception_only(self.type, self.value) 

533 text = "".join(lines) 

534 text = text.rstrip() 

535 if tryshort: 

536 if text.startswith(self._striptext): 

537 text = text[len(self._striptext) :] 

538 return text 

539 

540 def errisinstance( 

541 self, exc: Union["Type[BaseException]", Tuple["Type[BaseException]", ...]] 

542 ) -> bool: 

543 """Return True if the exception is an instance of exc. 

544 

545 Consider using ``isinstance(excinfo.value, exc)`` instead. 

546 """ 

547 return isinstance(self.value, exc) 

548 

549 def _getreprcrash(self) -> "ReprFileLocation": 

550 exconly = self.exconly(tryshort=True) 

551 entry = self.traceback.getcrashentry() 

552 path, lineno = entry.frame.code.raw.co_filename, entry.lineno 

553 return ReprFileLocation(path, lineno + 1, exconly) 

554 

555 def getrepr( 

556 self, 

557 showlocals: bool = False, 

558 style: "_TracebackStyle" = "long", 

559 abspath: bool = False, 

560 tbfilter: bool = True, 

561 funcargs: bool = False, 

562 truncate_locals: bool = True, 

563 chain: bool = True, 

564 ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: 

565 """Return str()able representation of this exception info. 

566 

567 :param bool showlocals: 

568 Show locals per traceback entry. 

569 Ignored if ``style=="native"``. 

570 

571 :param str style: long|short|no|native|value traceback style 

572 

573 :param bool abspath: 

574 If paths should be changed to absolute or left unchanged. 

575 

576 :param bool tbfilter: 

577 Hide entries that contain a local variable ``__tracebackhide__==True``. 

578 Ignored if ``style=="native"``. 

579 

580 :param bool funcargs: 

581 Show fixtures ("funcargs" for legacy purposes) per traceback entry. 

582 

583 :param bool truncate_locals: 

584 With ``showlocals==True``, make sure locals can be safely represented as strings. 

585 

586 :param bool chain: if chained exceptions in Python 3 should be shown. 

587 

588 .. versionchanged:: 3.9 

589 

590 Added the ``chain`` parameter. 

591 """ 

592 if style == "native": 

593 return ReprExceptionInfo( 

594 ReprTracebackNative( 

595 traceback.format_exception( 

596 self.type, self.value, self.traceback[0]._rawentry 

597 ) 

598 ), 

599 self._getreprcrash(), 

600 ) 

601 

602 fmt = FormattedExcinfo( 

603 showlocals=showlocals, 

604 style=style, 

605 abspath=abspath, 

606 tbfilter=tbfilter, 

607 funcargs=funcargs, 

608 truncate_locals=truncate_locals, 

609 chain=chain, 

610 ) 

611 return fmt.repr_excinfo(self) 

612 

613 def match(self, regexp: "Union[str, Pattern]") -> "Literal[True]": 

614 """Check whether the regular expression `regexp` matches the string 

615 representation of the exception using :func:`python:re.search`. 

616 

617 If it matches `True` is returned, otherwise an `AssertionError` is raised. 

618 """ 

619 __tracebackhide__ = True 

620 msg = "Regex pattern {!r} does not match {!r}." 

621 if regexp == str(self.value): 

622 msg += " Did you mean to `re.escape()` the regex?" 

623 assert re.search(regexp, str(self.value)), msg.format(regexp, str(self.value)) 

624 # Return True to allow for "assert excinfo.match()". 

625 return True 

626 

627 

628@attr.s 

629class FormattedExcinfo: 

630 """Presenting information about failing Functions and Generators.""" 

631 

632 # for traceback entries 

633 flow_marker = ">" 

634 fail_marker = "E" 

635 

636 showlocals = attr.ib(type=bool, default=False) 

637 style = attr.ib(type="_TracebackStyle", default="long") 

638 abspath = attr.ib(type=bool, default=True) 

639 tbfilter = attr.ib(type=bool, default=True) 

640 funcargs = attr.ib(type=bool, default=False) 

641 truncate_locals = attr.ib(type=bool, default=True) 

642 chain = attr.ib(type=bool, default=True) 

643 astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False) 

644 

645 def _getindent(self, source: "Source") -> int: 

646 # figure out indent for given source 

647 try: 

648 s = str(source.getstatement(len(source) - 1)) 

649 except KeyboardInterrupt: 

650 raise 

651 except BaseException: 

652 try: 

653 s = str(source[-1]) 

654 except KeyboardInterrupt: 

655 raise 

656 except BaseException: 

657 return 0 

658 return 4 + (len(s) - len(s.lstrip())) 

659 

660 def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]: 

661 source = entry.getsource(self.astcache) 

662 if source is not None: 

663 source = source.deindent() 

664 return source 

665 

666 def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: 

667 if self.funcargs: 

668 args = [] 

669 for argname, argvalue in entry.frame.getargs(var=True): 

670 args.append((argname, saferepr(argvalue))) 

671 return ReprFuncArgs(args) 

672 return None 

673 

674 def get_source( 

675 self, 

676 source: "Source", 

677 line_index: int = -1, 

678 excinfo: Optional[ExceptionInfo] = None, 

679 short: bool = False, 

680 ) -> List[str]: 

681 """Return formatted and marked up source lines.""" 

682 lines = [] 

683 if source is None or line_index >= len(source.lines): 

684 source = Source("???") 

685 line_index = 0 

686 if line_index < 0: 

687 line_index += len(source) 

688 space_prefix = " " 

689 if short: 

690 lines.append(space_prefix + source.lines[line_index].strip()) 

691 else: 

692 for line in source.lines[:line_index]: 

693 lines.append(space_prefix + line) 

694 lines.append(self.flow_marker + " " + source.lines[line_index]) 

695 for line in source.lines[line_index + 1 :]: 

696 lines.append(space_prefix + line) 

697 if excinfo is not None: 

698 indent = 4 if short else self._getindent(source) 

699 lines.extend(self.get_exconly(excinfo, indent=indent, markall=True)) 

700 return lines 

701 

702 def get_exconly( 

703 self, excinfo: ExceptionInfo, indent: int = 4, markall: bool = False 

704 ) -> List[str]: 

705 lines = [] 

706 indentstr = " " * indent 

707 # get the real exception information out 

708 exlines = excinfo.exconly(tryshort=True).split("\n") 

709 failindent = self.fail_marker + indentstr[1:] 

710 for line in exlines: 

711 lines.append(failindent + line) 

712 if not markall: 

713 failindent = indentstr 

714 return lines 

715 

716 def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: 

717 if self.showlocals: 

718 lines = [] 

719 keys = [loc for loc in locals if loc[0] != "@"] 

720 keys.sort() 

721 for name in keys: 

722 value = locals[name] 

723 if name == "__builtins__": 

724 lines.append("__builtins__ = <builtins>") 

725 else: 

726 # This formatting could all be handled by the 

727 # _repr() function, which is only reprlib.Repr in 

728 # disguise, so is very configurable. 

729 if self.truncate_locals: 

730 str_repr = saferepr(value) 

731 else: 

732 str_repr = safeformat(value) 

733 # if len(str_repr) < 70 or not isinstance(value, 

734 # (list, tuple, dict)): 

735 lines.append("{:<10} = {}".format(name, str_repr)) 

736 # else: 

737 # self._line("%-10s =\\" % (name,)) 

738 # # XXX 

739 # pprint.pprint(value, stream=self.excinfowriter) 

740 return ReprLocals(lines) 

741 return None 

742 

743 def repr_traceback_entry( 

744 self, entry: TracebackEntry, excinfo: Optional[ExceptionInfo] = None 

745 ) -> "ReprEntry": 

746 lines = [] # type: List[str] 

747 style = entry._repr_style if entry._repr_style is not None else self.style 

748 if style in ("short", "long"): 

749 source = self._getentrysource(entry) 

750 if source is None: 

751 source = Source("???") 

752 line_index = 0 

753 else: 

754 line_index = entry.lineno - entry.getfirstlinesource() 

755 short = style == "short" 

756 reprargs = self.repr_args(entry) if not short else None 

757 s = self.get_source(source, line_index, excinfo, short=short) 

758 lines.extend(s) 

759 if short: 

760 message = "in %s" % (entry.name) 

761 else: 

762 message = excinfo and excinfo.typename or "" 

763 path = self._makepath(entry.path) 

764 reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) 

765 localsrepr = self.repr_locals(entry.locals) 

766 return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) 

767 elif style == "value": 

768 if excinfo: 

769 lines.extend(str(excinfo.value).split("\n")) 

770 return ReprEntry(lines, None, None, None, style) 

771 else: 

772 if excinfo: 

773 lines.extend(self.get_exconly(excinfo, indent=4)) 

774 return ReprEntry(lines, None, None, None, style) 

775 

776 def _makepath(self, path): 

777 if not self.abspath: 

778 try: 

779 np = py.path.local().bestrelpath(path) 

780 except OSError: 

781 return path 

782 if len(np) < len(str(path)): 

783 path = np 

784 return path 

785 

786 def repr_traceback(self, excinfo: ExceptionInfo) -> "ReprTraceback": 

787 traceback = excinfo.traceback 

788 if self.tbfilter: 

789 traceback = traceback.filter() 

790 

791 if isinstance(excinfo.value, RecursionError): 

792 traceback, extraline = self._truncate_recursive_traceback(traceback) 

793 else: 

794 extraline = None 

795 

796 last = traceback[-1] 

797 entries = [] 

798 if self.style == "value": 

799 reprentry = self.repr_traceback_entry(last, excinfo) 

800 entries.append(reprentry) 

801 return ReprTraceback(entries, None, style=self.style) 

802 

803 for index, entry in enumerate(traceback): 

804 einfo = (last == entry) and excinfo or None 

805 reprentry = self.repr_traceback_entry(entry, einfo) 

806 entries.append(reprentry) 

807 return ReprTraceback(entries, extraline, style=self.style) 

808 

809 def _truncate_recursive_traceback( 

810 self, traceback: Traceback 

811 ) -> Tuple[Traceback, Optional[str]]: 

812 """ 

813 Truncate the given recursive traceback trying to find the starting point 

814 of the recursion. 

815 

816 The detection is done by going through each traceback entry and finding the 

817 point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``. 

818 

819 Handle the situation where the recursion process might raise an exception (for example 

820 comparing numpy arrays using equality raises a TypeError), in which case we do our best to 

821 warn the user of the error and show a limited traceback. 

822 """ 

823 try: 

824 recursionindex = traceback.recursionindex() 

825 except Exception as e: 

826 max_frames = 10 

827 extraline = ( 

828 "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" 

829 " The following exception happened when comparing locals in the stack frame:\n" 

830 " {exc_type}: {exc_msg}\n" 

831 " Displaying first and last {max_frames} stack frames out of {total}." 

832 ).format( 

833 exc_type=type(e).__name__, 

834 exc_msg=str(e), 

835 max_frames=max_frames, 

836 total=len(traceback), 

837 ) # type: Optional[str] 

838 # Type ignored because adding two instaces of a List subtype 

839 # currently incorrectly has type List instead of the subtype. 

840 traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore 

841 else: 

842 if recursionindex is not None: 

843 extraline = "!!! Recursion detected (same locals & position)" 

844 traceback = traceback[: recursionindex + 1] 

845 else: 

846 extraline = None 

847 

848 return traceback, extraline 

849 

850 def repr_excinfo(self, excinfo: ExceptionInfo) -> "ExceptionChainRepr": 

851 repr_chain = ( 

852 [] 

853 ) # type: List[Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]] 

854 e = excinfo.value 

855 excinfo_ = excinfo # type: Optional[ExceptionInfo] 

856 descr = None 

857 seen = set() # type: Set[int] 

858 while e is not None and id(e) not in seen: 

859 seen.add(id(e)) 

860 if excinfo_: 

861 reprtraceback = self.repr_traceback(excinfo_) 

862 reprcrash = ( 

863 excinfo_._getreprcrash() if self.style != "value" else None 

864 ) # type: Optional[ReprFileLocation] 

865 else: 

866 # fallback to native repr if the exception doesn't have a traceback: 

867 # ExceptionInfo objects require a full traceback to work 

868 reprtraceback = ReprTracebackNative( 

869 traceback.format_exception(type(e), e, None) 

870 ) 

871 reprcrash = None 

872 

873 repr_chain += [(reprtraceback, reprcrash, descr)] 

874 if e.__cause__ is not None and self.chain: 

875 e = e.__cause__ 

876 excinfo_ = ( 

877 ExceptionInfo((type(e), e, e.__traceback__)) 

878 if e.__traceback__ 

879 else None 

880 ) 

881 descr = "The above exception was the direct cause of the following exception:" 

882 elif ( 

883 e.__context__ is not None and not e.__suppress_context__ and self.chain 

884 ): 

885 e = e.__context__ 

886 excinfo_ = ( 

887 ExceptionInfo((type(e), e, e.__traceback__)) 

888 if e.__traceback__ 

889 else None 

890 ) 

891 descr = "During handling of the above exception, another exception occurred:" 

892 else: 

893 e = None 

894 repr_chain.reverse() 

895 return ExceptionChainRepr(repr_chain) 

896 

897 

898@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore 

899class TerminalRepr: 

900 def __str__(self) -> str: 

901 # FYI this is called from pytest-xdist's serialization of exception 

902 # information. 

903 io = StringIO() 

904 tw = TerminalWriter(file=io) 

905 self.toterminal(tw) 

906 return io.getvalue().strip() 

907 

908 def __repr__(self) -> str: 

909 return "<{} instance at {:0x}>".format(self.__class__, id(self)) 

910 

911 def toterminal(self, tw: TerminalWriter) -> None: 

912 raise NotImplementedError() 

913 

914 

915# This class is abstract -- only subclasses are instantiated. 

916@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore 

917class ExceptionRepr(TerminalRepr): 

918 # Provided by in subclasses. 

919 reprcrash = None # type: Optional[ReprFileLocation] 

920 reprtraceback = None # type: ReprTraceback 

921 

922 def __attrs_post_init__(self) -> None: 

923 self.sections = [] # type: List[Tuple[str, str, str]] 

924 

925 def addsection(self, name: str, content: str, sep: str = "-") -> None: 

926 self.sections.append((name, content, sep)) 

927 

928 def toterminal(self, tw: TerminalWriter) -> None: 

929 for name, content, sep in self.sections: 

930 tw.sep(sep, name) 

931 tw.line(content) 

932 

933 

934@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore 

935class ExceptionChainRepr(ExceptionRepr): 

936 chain = attr.ib( 

937 type=Sequence[ 

938 Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] 

939 ] 

940 ) 

941 

942 def __attrs_post_init__(self) -> None: 

943 super().__attrs_post_init__() 

944 # reprcrash and reprtraceback of the outermost (the newest) exception 

945 # in the chain 

946 self.reprtraceback = self.chain[-1][0] 

947 self.reprcrash = self.chain[-1][1] 

948 

949 def toterminal(self, tw: TerminalWriter) -> None: 

950 for element in self.chain: 

951 element[0].toterminal(tw) 

952 if element[2] is not None: 

953 tw.line("") 

954 tw.line(element[2], yellow=True) 

955 super().toterminal(tw) 

956 

957 

958@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore 

959class ReprExceptionInfo(ExceptionRepr): 

960 reprtraceback = attr.ib(type="ReprTraceback") 

961 reprcrash = attr.ib(type="ReprFileLocation") 

962 

963 def toterminal(self, tw: TerminalWriter) -> None: 

964 self.reprtraceback.toterminal(tw) 

965 super().toterminal(tw) 

966 

967 

968@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore 

969class ReprTraceback(TerminalRepr): 

970 reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]]) 

971 extraline = attr.ib(type=Optional[str]) 

972 style = attr.ib(type="_TracebackStyle") 

973 

974 entrysep = "_ " 

975 

976 def toterminal(self, tw: TerminalWriter) -> None: 

977 # the entries might have different styles 

978 for i, entry in enumerate(self.reprentries): 

979 if entry.style == "long": 

980 tw.line("") 

981 entry.toterminal(tw) 

982 if i < len(self.reprentries) - 1: 

983 next_entry = self.reprentries[i + 1] 

984 if ( 

985 entry.style == "long" 

986 or entry.style == "short" 

987 and next_entry.style == "long" 

988 ): 

989 tw.sep(self.entrysep) 

990 

991 if self.extraline: 

992 tw.line(self.extraline) 

993 

994 

995class ReprTracebackNative(ReprTraceback): 

996 def __init__(self, tblines: Sequence[str]) -> None: 

997 self.style = "native" 

998 self.reprentries = [ReprEntryNative(tblines)] 

999 self.extraline = None 

1000 

1001 

1002@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore 

1003class ReprEntryNative(TerminalRepr): 

1004 lines = attr.ib(type=Sequence[str]) 

1005 style = "native" # type: _TracebackStyle 

1006 

1007 def toterminal(self, tw: TerminalWriter) -> None: 

1008 tw.write("".join(self.lines)) 

1009 

1010 

1011@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore 

1012class ReprEntry(TerminalRepr): 

1013 lines = attr.ib(type=Sequence[str]) 

1014 reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"]) 

1015 reprlocals = attr.ib(type=Optional["ReprLocals"]) 

1016 reprfileloc = attr.ib(type=Optional["ReprFileLocation"]) 

1017 style = attr.ib(type="_TracebackStyle") 

1018 

1019 def _write_entry_lines(self, tw: TerminalWriter) -> None: 

1020 """Writes the source code portions of a list of traceback entries with syntax highlighting. 

1021 

1022 Usually entries are lines like these: 

1023 

1024 " x = 1" 

1025 "> assert x == 2" 

1026 "E assert 1 == 2" 

1027 

1028 This function takes care of rendering the "source" portions of it (the lines without 

1029 the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" 

1030 character, as doing so might break line continuations. 

1031 """ 

1032 

1033 if not self.lines: 

1034 return 

1035 

1036 # separate indents and source lines that are not failures: we want to 

1037 # highlight the code but not the indentation, which may contain markers 

1038 # such as "> assert 0" 

1039 fail_marker = "{} ".format(FormattedExcinfo.fail_marker) 

1040 indent_size = len(fail_marker) 

1041 indents = [] # type: List[str] 

1042 source_lines = [] # type: List[str] 

1043 failure_lines = [] # type: List[str] 

1044 for index, line in enumerate(self.lines): 

1045 is_failure_line = line.startswith(fail_marker) 

1046 if is_failure_line: 

1047 # from this point on all lines are considered part of the failure 

1048 failure_lines.extend(self.lines[index:]) 

1049 break 

1050 else: 

1051 if self.style == "value": 

1052 source_lines.append(line) 

1053 else: 

1054 indents.append(line[:indent_size]) 

1055 source_lines.append(line[indent_size:]) 

1056 

1057 tw._write_source(source_lines, indents) 

1058 

1059 # failure lines are always completely red and bold 

1060 for line in failure_lines: 

1061 tw.line(line, bold=True, red=True) 

1062 

1063 def toterminal(self, tw: TerminalWriter) -> None: 

1064 if self.style == "short": 

1065 assert self.reprfileloc is not None 

1066 self.reprfileloc.toterminal(tw) 

1067 self._write_entry_lines(tw) 

1068 if self.reprlocals: 

1069 self.reprlocals.toterminal(tw, indent=" " * 8) 

1070 return 

1071 

1072 if self.reprfuncargs: 

1073 self.reprfuncargs.toterminal(tw) 

1074 

1075 self._write_entry_lines(tw) 

1076 

1077 if self.reprlocals: 

1078 tw.line("") 

1079 self.reprlocals.toterminal(tw) 

1080 if self.reprfileloc: 

1081 if self.lines: 

1082 tw.line("") 

1083 self.reprfileloc.toterminal(tw) 

1084 

1085 def __str__(self) -> str: 

1086 return "{}\n{}\n{}".format( 

1087 "\n".join(self.lines), self.reprlocals, self.reprfileloc 

1088 ) 

1089 

1090 

1091@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore 

1092class ReprFileLocation(TerminalRepr): 

1093 path = attr.ib(type=str, converter=str) 

1094 lineno = attr.ib(type=int) 

1095 message = attr.ib(type=str) 

1096 

1097 def toterminal(self, tw: TerminalWriter) -> None: 

1098 # filename and lineno output for each entry, 

1099 # using an output format that most editors understand 

1100 msg = self.message 

1101 i = msg.find("\n") 

1102 if i != -1: 

1103 msg = msg[:i] 

1104 tw.write(self.path, bold=True, red=True) 

1105 tw.line(":{}: {}".format(self.lineno, msg)) 

1106 

1107 

1108@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore 

1109class ReprLocals(TerminalRepr): 

1110 lines = attr.ib(type=Sequence[str]) 

1111 

1112 def toterminal(self, tw: TerminalWriter, indent="") -> None: 

1113 for line in self.lines: 

1114 tw.line(indent + line) 

1115 

1116 

1117@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore 

1118class ReprFuncArgs(TerminalRepr): 

1119 args = attr.ib(type=Sequence[Tuple[str, object]]) 

1120 

1121 def toterminal(self, tw: TerminalWriter) -> None: 

1122 if self.args: 

1123 linesofar = "" 

1124 for name, value in self.args: 

1125 ns = "{} = {}".format(name, value) 

1126 if len(ns) + len(linesofar) + 2 > tw.fullwidth: 

1127 if linesofar: 

1128 tw.line(linesofar) 

1129 linesofar = ns 

1130 else: 

1131 if linesofar: 

1132 linesofar += ", " + ns 

1133 else: 

1134 linesofar = ns 

1135 if linesofar: 

1136 tw.line(linesofar) 

1137 tw.line("") 

1138 

1139 

1140def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: 

1141 """Return source location (path, lineno) for the given object. 

1142 

1143 If the source cannot be determined return ("", -1). 

1144 

1145 The line number is 0-based. 

1146 """ 

1147 # xxx let decorators etc specify a sane ordering 

1148 # NOTE: this used to be done in _pytest.compat.getfslineno, initially added 

1149 # in 6ec13a2b9. It ("place_as") appears to be something very custom. 

1150 obj = get_real_func(obj) 

1151 if hasattr(obj, "place_as"): 

1152 obj = obj.place_as # type: ignore[attr-defined] 

1153 

1154 try: 

1155 code = Code(obj) 

1156 except TypeError: 

1157 try: 

1158 fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type] 

1159 except TypeError: 

1160 return "", -1 

1161 

1162 fspath = fn and py.path.local(fn) or "" 

1163 lineno = -1 

1164 if fspath: 

1165 try: 

1166 _, lineno = findsource(obj) 

1167 except OSError: 

1168 pass 

1169 return fspath, lineno 

1170 

1171 return code.path, code.firstlineno 

1172 

1173 

1174# relative paths that we use to filter traceback entries from appearing to the user; 

1175# see filter_traceback 

1176# note: if we need to add more paths than what we have now we should probably use a list 

1177# for better maintenance 

1178 

1179_PLUGGY_DIR = py.path.local(pluggy.__file__.rstrip("oc")) 

1180# pluggy is either a package or a single module depending on the version 

1181if _PLUGGY_DIR.basename == "__init__.py": 

1182 _PLUGGY_DIR = _PLUGGY_DIR.dirpath() 

1183_PYTEST_DIR = py.path.local(_pytest.__file__).dirpath() 

1184_PY_DIR = py.path.local(py.__file__).dirpath() 

1185 

1186 

1187def filter_traceback(entry: TracebackEntry) -> bool: 

1188 """Return True if a TracebackEntry instance should be included in tracebacks. 

1189 

1190 We hide traceback entries of: 

1191 

1192 * dynamically generated code (no code to show up for it); 

1193 * internal traceback from pytest or its internal libraries, py and pluggy. 

1194 """ 

1195 # entry.path might sometimes return a str object when the entry 

1196 # points to dynamically generated code 

1197 # see https://bitbucket.org/pytest-dev/py/issues/71 

1198 raw_filename = entry.frame.code.raw.co_filename 

1199 is_generated = "<" in raw_filename and ">" in raw_filename 

1200 if is_generated: 

1201 return False 

1202 # entry.path might point to a non-existing file, in which case it will 

1203 # also return a str object. see #1133 

1204 p = py.path.local(entry.path) 

1205 return ( 

1206 not p.relto(_PLUGGY_DIR) and not p.relto(_PYTEST_DIR) and not p.relto(_PY_DIR) 

1207 )