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 Optional 

19from typing import Pattern 

20from typing import Sequence 

21from typing import Set 

22from typing import Tuple 

23from typing import TypeVar 

24from typing import Union 

25from weakref import ref 

26 

27import attr 

28import pluggy 

29import py 

30 

31import _pytest 

32from _pytest._io.saferepr import safeformat 

33from _pytest._io.saferepr import saferepr 

34from _pytest.compat import overload 

35from _pytest.compat import TYPE_CHECKING 

36 

37if TYPE_CHECKING: 

38 from typing import Type 

39 from typing_extensions import Literal 

40 from weakref import ReferenceType # noqa: F401 

41 

42 from _pytest._code import Source 

43 

44 _TracebackStyle = Literal["long", "short", "line", "no", "native"] 

45 

46 

47class Code: 

48 """ wrapper around Python code objects """ 

49 

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

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

52 rawcode = getrawcode(rawcode) 

53 if not isinstance(rawcode, CodeType): 

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

55 self.filename = rawcode.co_filename 

56 self.firstlineno = rawcode.co_firstlineno - 1 

57 self.name = rawcode.co_name 

58 self.raw = rawcode 

59 

60 def __eq__(self, other): 

61 return self.raw == other.raw 

62 

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

64 __hash__ = None # type: ignore 

65 

66 def __ne__(self, other): 

67 return not self == other 

68 

69 @property 

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

71 """ return a path object pointing to source code (note that it 

72 might not point to an actually existing file). """ 

73 try: 

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

75 # maybe don't try this checking 

76 if not p.check(): 

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

78 except OSError: 

79 # XXX maybe try harder like the weird logic 

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

81 p = self.raw.co_filename 

82 

83 return p 

84 

85 @property 

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

87 """ return a _pytest._code.Source object for the full source file of the code 

88 """ 

89 from _pytest._code import source 

90 

91 full, _ = source.findsource(self.raw) 

92 return full 

93 

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

95 """ return a _pytest._code.Source object for the code object's source only 

96 """ 

97 # return source only for that part of code 

98 import _pytest._code 

99 

100 return _pytest._code.Source(self.raw) 

101 

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

103 """ return a tuple with the argument names for the code object 

104 

105 if 'var' is set True also return the names of the variable and 

106 keyword arguments when present 

107 """ 

108 # handfull shortcut for getting args 

109 raw = self.raw 

110 argcount = raw.co_argcount 

111 if var: 

112 argcount += raw.co_flags & CO_VARARGS 

113 argcount += raw.co_flags & CO_VARKEYWORDS 

114 return raw.co_varnames[:argcount] 

115 

116 

117class Frame: 

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

119 in which expressions can be evaluated.""" 

120 

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

122 self.lineno = frame.f_lineno - 1 

123 self.f_globals = frame.f_globals 

124 self.f_locals = frame.f_locals 

125 self.raw = frame 

126 self.code = Code(frame.f_code) 

127 

128 @property 

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

130 """ statement this frame is at """ 

131 import _pytest._code 

132 

133 if self.code.fullsource is None: 

134 return _pytest._code.Source("") 

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

136 

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

138 """ evaluate 'code' in the frame 

139 

140 'vars' are optional additional local variables 

141 

142 returns the result of the evaluation 

143 """ 

144 f_locals = self.f_locals.copy() 

145 f_locals.update(vars) 

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

147 

148 def exec_(self, code, **vars) -> None: 

149 """ exec 'code' in the frame 

150 

151 'vars' are optional; additional local variables 

152 """ 

153 f_locals = self.f_locals.copy() 

154 f_locals.update(vars) 

155 exec(code, self.f_globals, f_locals) 

156 

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

158 """ return a 'safe' (non-recursive, one-line) string repr for 'object' 

159 """ 

160 return saferepr(object) 

161 

162 def is_true(self, object): 

163 return object 

164 

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

166 """ return a list of tuples (name, value) for all arguments 

167 

168 if 'var' is set True also include the variable and keyword 

169 arguments when present 

170 """ 

171 retval = [] 

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

173 try: 

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

175 except KeyError: 

176 pass # this can occur when using Psyco 

177 return retval 

178 

179 

180class TracebackEntry: 

181 """ a single entry in a traceback """ 

182 

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

184 exprinfo = None 

185 

186 def __init__(self, rawentry: TracebackType, excinfo=None) -> None: 

187 self._excinfo = excinfo 

188 self._rawentry = rawentry 

189 self.lineno = rawentry.tb_lineno - 1 

190 

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

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

193 self._repr_style = mode 

194 

195 @property 

196 def frame(self) -> Frame: 

197 return Frame(self._rawentry.tb_frame) 

198 

199 @property 

200 def relline(self) -> int: 

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

202 

203 def __repr__(self) -> str: 

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

205 

206 @property 

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

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

209 source = self.frame.code.fullsource 

210 assert source is not None 

211 return source.getstatement(self.lineno) 

212 

213 @property 

214 def path(self): 

215 """ path to the source code """ 

216 return self.frame.code.path 

217 

218 @property 

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

220 """ locals of underlying frame """ 

221 return self.frame.f_locals 

222 

223 def getfirstlinesource(self) -> int: 

224 return self.frame.code.firstlineno 

225 

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

227 """ return failing source code. """ 

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

229 # within exception info printing 

230 from _pytest._code.source import getstatementrange_ast 

231 

232 source = self.frame.code.fullsource 

233 if source is None: 

234 return None 

235 key = astnode = None 

236 if astcache is not None: 

237 key = self.frame.code.path 

238 if key is not None: 

239 astnode = astcache.get(key, None) 

240 start = self.getfirstlinesource() 

241 try: 

242 astnode, _, end = getstatementrange_ast( 

243 self.lineno, source, astnode=astnode 

244 ) 

245 except SyntaxError: 

246 end = self.lineno + 1 

247 else: 

248 if key is not None: 

249 astcache[key] = astnode 

250 return source[start:end] 

251 

252 source = property(getsource) 

253 

254 def ishidden(self): 

255 """ return True if the current frame has a var __tracebackhide__ 

256 resolving to True. 

257 

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

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

260 

261 mostly for internal use 

262 """ 

263 f = self.frame 

264 tbh = f.f_locals.get( 

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

266 ) 

267 if tbh and callable(tbh): 

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

269 return tbh 

270 

271 def __str__(self) -> str: 

272 try: 

273 fn = str(self.path) 

274 except py.error.Error: 

275 fn = "???" 

276 name = self.frame.code.name 

277 try: 

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

279 except KeyboardInterrupt: 

280 raise 

281 except: # noqa 

282 line = "???" 

283 return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line) 

284 

285 @property 

286 def name(self) -> str: 

287 """ co_name of underlying code """ 

288 return self.frame.code.raw.co_name 

289 

290 

291class Traceback(List[TracebackEntry]): 

292 """ Traceback objects encapsulate and offer higher level 

293 access to Traceback entries. 

294 """ 

295 

296 def __init__( 

297 self, 

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

299 excinfo: Optional["ReferenceType[ExceptionInfo]"] = None, 

300 ) -> None: 

301 """ initialize from given python traceback object and ExceptionInfo """ 

302 self._excinfo = excinfo 

303 if isinstance(tb, TracebackType): 

304 

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

306 cur_ = cur # type: Optional[TracebackType] 

307 while cur_ is not None: 

308 yield TracebackEntry(cur_, excinfo=excinfo) 

309 cur_ = cur_.tb_next 

310 

311 super().__init__(f(tb)) 

312 else: 

313 super().__init__(tb) 

314 

315 def cut( 

316 self, 

317 path=None, 

318 lineno: Optional[int] = None, 

319 firstlineno: Optional[int] = None, 

320 excludepath=None, 

321 ) -> "Traceback": 

322 """ return a Traceback instance wrapping part of this Traceback 

323 

324 by providing any combination of path, lineno and firstlineno, the 

325 first frame to start the to-be-returned traceback is determined 

326 

327 this allows cutting the first part of a Traceback instance e.g. 

328 for formatting reasons (removing some uninteresting bits that deal 

329 with handling of the exception/traceback) 

330 """ 

331 for x in self: 

332 code = x.frame.code 

333 codepath = code.path 

334 if ( 

335 (path is None or codepath == path) 

336 and ( 

337 excludepath is None 

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

339 or not codepath.relto(excludepath) 

340 ) 

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

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

343 ): 

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

345 return self 

346 

347 @overload 

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

349 raise NotImplementedError() 

350 

351 @overload # noqa: F811 

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

353 raise NotImplementedError() 

354 

355 def __getitem__( # noqa: F811 

356 self, key: Union[int, slice] 

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

358 if isinstance(key, slice): 

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

360 else: 

361 return super().__getitem__(key) 

362 

363 def filter( 

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

365 ) -> "Traceback": 

366 """ return a Traceback instance with certain items removed 

367 

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

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

370 to the Traceback, False when not 

371 

372 by default this removes all the TracebackEntries which are hidden 

373 (see ishidden() above) 

374 """ 

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

376 

377 def getcrashentry(self) -> TracebackEntry: 

378 """ return last non-hidden traceback entry that lead 

379 to the exception of a traceback. 

380 """ 

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

382 entry = self[i] 

383 if not entry.ishidden(): 

384 return entry 

385 return self[-1] 

386 

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

388 """ return the index of the frame/TracebackEntry where recursion 

389 originates if appropriate, None if no recursion occurred 

390 """ 

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

392 for i, entry in enumerate(self): 

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

394 # the strange metaprogramming in the decorator lib from pypi 

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

396 # XXX needs a test 

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

398 # print "checking for recursion at", key 

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

400 if values: 

401 f = entry.frame 

402 loc = f.f_locals 

403 for otherloc in values: 

404 if f.is_true( 

405 f.eval( 

406 co_equal, 

407 __recursioncache_locals_1=loc, 

408 __recursioncache_locals_2=otherloc, 

409 ) 

410 ): 

411 return i 

412 values.append(entry.frame.f_locals) 

413 return None 

414 

415 

416co_equal = compile( 

417 "__recursioncache_locals_1 == __recursioncache_locals_2", "?", "eval" 

418) 

419 

420 

421_E = TypeVar("_E", bound=BaseException) 

422 

423 

424@attr.s(repr=False) 

425class ExceptionInfo(Generic[_E]): 

426 """ wraps sys.exc_info() objects and offers 

427 help for navigating the traceback. 

428 """ 

429 

430 _assert_start_repr = "AssertionError('assert " 

431 

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

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

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

435 

436 @classmethod 

437 def from_exc_info( 

438 cls, 

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

440 exprinfo: Optional[str] = None, 

441 ) -> "ExceptionInfo[_E]": 

442 """returns an ExceptionInfo for an existing exc_info tuple. 

443 

444 .. warning:: 

445 

446 Experimental API 

447 

448 

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

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

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

452 """ 

453 _striptext = "" 

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

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

456 if exprinfo is None: 

457 exprinfo = saferepr(exc_info[1]) 

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

459 _striptext = "AssertionError: " 

460 

461 return cls(exc_info, _striptext) 

462 

463 @classmethod 

464 def from_current( 

465 cls, exprinfo: Optional[str] = None 

466 ) -> "ExceptionInfo[BaseException]": 

467 """returns an ExceptionInfo matching the current traceback 

468 

469 .. warning:: 

470 

471 Experimental API 

472 

473 

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

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

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

477 """ 

478 tup = sys.exc_info() 

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

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

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

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

483 return ExceptionInfo.from_exc_info(exc_info, exprinfo) 

484 

485 @classmethod 

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

487 """return an unfilled ExceptionInfo 

488 """ 

489 return cls(None) 

490 

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

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

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

494 self._excinfo = exc_info 

495 

496 @property 

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

498 """the exception class""" 

499 assert ( 

500 self._excinfo is not None 

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

502 return self._excinfo[0] 

503 

504 @property 

505 def value(self) -> _E: 

506 """the exception value""" 

507 assert ( 

508 self._excinfo is not None 

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

510 return self._excinfo[1] 

511 

512 @property 

513 def tb(self) -> TracebackType: 

514 """the exception raw traceback""" 

515 assert ( 

516 self._excinfo is not None 

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

518 return self._excinfo[2] 

519 

520 @property 

521 def typename(self) -> str: 

522 """the type name of the exception""" 

523 assert ( 

524 self._excinfo is not None 

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

526 return self.type.__name__ 

527 

528 @property 

529 def traceback(self) -> Traceback: 

530 """the traceback""" 

531 if self._traceback is None: 

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

533 return self._traceback 

534 

535 @traceback.setter 

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

537 self._traceback = value 

538 

539 def __repr__(self) -> str: 

540 if self._excinfo is None: 

541 return "<ExceptionInfo for raises contextmanager>" 

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

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

544 ) 

545 

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

547 """ return the exception as a string 

548 

549 when 'tryshort' resolves to True, and the exception is a 

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

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

552 removed from the beginning) 

553 """ 

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

555 text = "".join(lines) 

556 text = text.rstrip() 

557 if tryshort: 

558 if text.startswith(self._striptext): 

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

560 return text 

561 

562 def errisinstance( 

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

564 ) -> bool: 

565 """ return True if the exception is an instance of exc """ 

566 return isinstance(self.value, exc) 

567 

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

569 exconly = self.exconly(tryshort=True) 

570 entry = self.traceback.getcrashentry() 

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

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

573 

574 def getrepr( 

575 self, 

576 showlocals: bool = False, 

577 style: "_TracebackStyle" = "long", 

578 abspath: bool = False, 

579 tbfilter: bool = True, 

580 funcargs: bool = False, 

581 truncate_locals: bool = True, 

582 chain: bool = True, 

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

584 """ 

585 Return str()able representation of this exception info. 

586 

587 :param bool showlocals: 

588 Show locals per traceback entry. 

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

590 

591 :param str style: long|short|no|native traceback style 

592 

593 :param bool abspath: 

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

595 

596 :param bool tbfilter: 

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

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

599 

600 :param bool funcargs: 

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

602 

603 :param bool truncate_locals: 

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

605 

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

607 

608 .. versionchanged:: 3.9 

609 

610 Added the ``chain`` parameter. 

611 """ 

612 if style == "native": 

613 return ReprExceptionInfo( 

614 ReprTracebackNative( 

615 traceback.format_exception( 

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

617 ) 

618 ), 

619 self._getreprcrash(), 

620 ) 

621 

622 fmt = FormattedExcinfo( 

623 showlocals=showlocals, 

624 style=style, 

625 abspath=abspath, 

626 tbfilter=tbfilter, 

627 funcargs=funcargs, 

628 truncate_locals=truncate_locals, 

629 chain=chain, 

630 ) 

631 return fmt.repr_excinfo(self) 

632 

633 def match(self, regexp: "Union[str, Pattern]") -> bool: 

634 """ 

635 Check whether the regular expression 'regexp' is found in the string 

636 representation of the exception using ``re.search``. If it matches 

637 then True is returned (so that it is possible to write 

638 ``assert excinfo.match()``). If it doesn't match an AssertionError is 

639 raised. 

640 """ 

641 __tracebackhide__ = True 

642 if not re.search(regexp, str(self.value)): 

643 assert 0, "Pattern {!r} not found in {!r}".format(regexp, str(self.value)) 

644 return True 

645 

646 

647@attr.s 

648class FormattedExcinfo: 

649 """ presenting information about failing Functions and Generators. """ 

650 

651 # for traceback entries 

652 flow_marker = ">" 

653 fail_marker = "E" 

654 

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

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

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

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

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

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

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

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

663 

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

665 # figure out indent for given source 

666 try: 

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

668 except KeyboardInterrupt: 

669 raise 

670 except: # noqa 

671 try: 

672 s = str(source[-1]) 

673 except KeyboardInterrupt: 

674 raise 

675 except: # noqa 

676 return 0 

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

678 

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

680 source = entry.getsource(self.astcache) 

681 if source is not None: 

682 source = source.deindent() 

683 return source 

684 

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

686 if self.funcargs: 

687 args = [] 

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

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

690 return ReprFuncArgs(args) 

691 return None 

692 

693 def get_source( 

694 self, 

695 source: "Source", 

696 line_index: int = -1, 

697 excinfo: Optional[ExceptionInfo] = None, 

698 short: bool = False, 

699 ) -> List[str]: 

700 """ return formatted and marked up source lines. """ 

701 import _pytest._code 

702 

703 lines = [] 

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

705 source = _pytest._code.Source("???") 

706 line_index = 0 

707 if line_index < 0: 

708 line_index += len(source) 

709 space_prefix = " " 

710 if short: 

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

712 else: 

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

714 lines.append(space_prefix + line) 

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

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

717 lines.append(space_prefix + line) 

718 if excinfo is not None: 

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

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

721 return lines 

722 

723 def get_exconly( 

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

725 ) -> List[str]: 

726 lines = [] 

727 indentstr = " " * indent 

728 # get the real exception information out 

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

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

731 for line in exlines: 

732 lines.append(failindent + line) 

733 if not markall: 

734 failindent = indentstr 

735 return lines 

736 

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

738 if self.showlocals: 

739 lines = [] 

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

741 keys.sort() 

742 for name in keys: 

743 value = locals[name] 

744 if name == "__builtins__": 

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

746 else: 

747 # This formatting could all be handled by the 

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

749 # disguise, so is very configurable. 

750 if self.truncate_locals: 

751 str_repr = saferepr(value) 

752 else: 

753 str_repr = safeformat(value) 

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

755 # (list, tuple, dict)): 

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

757 # else: 

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

759 # # XXX 

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

761 return ReprLocals(lines) 

762 return None 

763 

764 def repr_traceback_entry( 

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

766 ) -> "ReprEntry": 

767 import _pytest._code 

768 

769 source = self._getentrysource(entry) 

770 if source is None: 

771 source = _pytest._code.Source("???") 

772 line_index = 0 

773 else: 

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

775 

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

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

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

779 short = style == "short" 

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

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

782 lines.extend(s) 

783 if short: 

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

785 else: 

786 message = excinfo and excinfo.typename or "" 

787 path = self._makepath(entry.path) 

788 filelocrepr = ReprFileLocation(path, entry.lineno + 1, message) 

789 localsrepr = None 

790 if not short: 

791 localsrepr = self.repr_locals(entry.locals) 

792 return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style) 

793 if excinfo: 

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

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

796 

797 def _makepath(self, path): 

798 if not self.abspath: 

799 try: 

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

801 except OSError: 

802 return path 

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

804 path = np 

805 return path 

806 

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

808 traceback = excinfo.traceback 

809 if self.tbfilter: 

810 traceback = traceback.filter() 

811 

812 if excinfo.errisinstance(RecursionError): 

813 traceback, extraline = self._truncate_recursive_traceback(traceback) 

814 else: 

815 extraline = None 

816 

817 last = traceback[-1] 

818 entries = [] 

819 for index, entry in enumerate(traceback): 

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

821 reprentry = self.repr_traceback_entry(entry, einfo) 

822 entries.append(reprentry) 

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

824 

825 def _truncate_recursive_traceback( 

826 self, traceback: Traceback 

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

828 """ 

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

830 of the recursion. 

831 

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

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

834 

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

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

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

838 """ 

839 try: 

840 recursionindex = traceback.recursionindex() 

841 except Exception as e: 

842 max_frames = 10 

843 extraline = ( 

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

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

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

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

848 ).format( 

849 exc_type=type(e).__name__, 

850 exc_msg=str(e), 

851 max_frames=max_frames, 

852 total=len(traceback), 

853 ) # type: Optional[str] 

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

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

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

857 else: 

858 if recursionindex is not None: 

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

860 traceback = traceback[: recursionindex + 1] 

861 else: 

862 extraline = None 

863 

864 return traceback, extraline 

865 

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

867 repr_chain = ( 

868 [] 

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

870 e = excinfo.value 

871 excinfo_ = excinfo # type: Optional[ExceptionInfo] 

872 descr = None 

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

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

875 seen.add(id(e)) 

876 if excinfo_: 

877 reprtraceback = self.repr_traceback(excinfo_) 

878 reprcrash = excinfo_._getreprcrash() # type: Optional[ReprFileLocation] 

879 else: 

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

881 # ExceptionInfo objects require a full traceback to work 

882 reprtraceback = ReprTracebackNative( 

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

884 ) 

885 reprcrash = None 

886 

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

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

889 e = e.__cause__ 

890 excinfo_ = ( 

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

892 if e.__traceback__ 

893 else None 

894 ) 

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

896 elif ( 

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

898 ): 

899 e = e.__context__ 

900 excinfo_ = ( 

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

902 if e.__traceback__ 

903 else None 

904 ) 

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

906 else: 

907 e = None 

908 repr_chain.reverse() 

909 return ExceptionChainRepr(repr_chain) 

910 

911 

912class TerminalRepr: 

913 def __str__(self) -> str: 

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

915 # information. 

916 io = StringIO() 

917 tw = py.io.TerminalWriter(file=io) 

918 self.toterminal(tw) 

919 return io.getvalue().strip() 

920 

921 def __repr__(self) -> str: 

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

923 

924 def toterminal(self, tw: py.io.TerminalWriter) -> None: 

925 raise NotImplementedError() 

926 

927 

928class ExceptionRepr(TerminalRepr): 

929 def __init__(self) -> None: 

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

931 

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

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

934 

935 def toterminal(self, tw: py.io.TerminalWriter) -> None: 

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

937 tw.sep(sep, name) 

938 tw.line(content) 

939 

940 

941class ExceptionChainRepr(ExceptionRepr): 

942 def __init__( 

943 self, 

944 chain: Sequence[ 

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

946 ], 

947 ) -> None: 

948 super().__init__() 

949 self.chain = chain 

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

951 # in the chain 

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

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

954 

955 def toterminal(self, tw: py.io.TerminalWriter) -> None: 

956 for element in self.chain: 

957 element[0].toterminal(tw) 

958 if element[2] is not None: 

959 tw.line("") 

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

961 super().toterminal(tw) 

962 

963 

964class ReprExceptionInfo(ExceptionRepr): 

965 def __init__( 

966 self, reprtraceback: "ReprTraceback", reprcrash: "ReprFileLocation" 

967 ) -> None: 

968 super().__init__() 

969 self.reprtraceback = reprtraceback 

970 self.reprcrash = reprcrash 

971 

972 def toterminal(self, tw: py.io.TerminalWriter) -> None: 

973 self.reprtraceback.toterminal(tw) 

974 super().toterminal(tw) 

975 

976 

977class ReprTraceback(TerminalRepr): 

978 entrysep = "_ " 

979 

980 def __init__( 

981 self, 

982 reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]], 

983 extraline: Optional[str], 

984 style: "_TracebackStyle", 

985 ) -> None: 

986 self.reprentries = reprentries 

987 self.extraline = extraline 

988 self.style = style 

989 

990 def toterminal(self, tw: py.io.TerminalWriter) -> None: 

991 # the entries might have different styles 

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

993 if entry.style == "long": 

994 tw.line("") 

995 entry.toterminal(tw) 

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

997 next_entry = self.reprentries[i + 1] 

998 if ( 

999 entry.style == "long" 

1000 or entry.style == "short" 

1001 and next_entry.style == "long" 

1002 ): 

1003 tw.sep(self.entrysep) 

1004 

1005 if self.extraline: 

1006 tw.line(self.extraline) 

1007 

1008 

1009class ReprTracebackNative(ReprTraceback): 

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

1011 self.style = "native" 

1012 self.reprentries = [ReprEntryNative(tblines)] 

1013 self.extraline = None 

1014 

1015 

1016class ReprEntryNative(TerminalRepr): 

1017 style = "native" # type: _TracebackStyle 

1018 

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

1020 self.lines = tblines 

1021 

1022 def toterminal(self, tw: py.io.TerminalWriter) -> None: 

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

1024 

1025 

1026class ReprEntry(TerminalRepr): 

1027 def __init__( 

1028 self, 

1029 lines: Sequence[str], 

1030 reprfuncargs: Optional["ReprFuncArgs"], 

1031 reprlocals: Optional["ReprLocals"], 

1032 filelocrepr: Optional["ReprFileLocation"], 

1033 style: "_TracebackStyle", 

1034 ) -> None: 

1035 self.lines = lines 

1036 self.reprfuncargs = reprfuncargs 

1037 self.reprlocals = reprlocals 

1038 self.reprfileloc = filelocrepr 

1039 self.style = style 

1040 

1041 def toterminal(self, tw: py.io.TerminalWriter) -> None: 

1042 if self.style == "short": 

1043 assert self.reprfileloc is not None 

1044 self.reprfileloc.toterminal(tw) 

1045 for line in self.lines: 

1046 red = line.startswith("E ") 

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

1048 return 

1049 if self.reprfuncargs: 

1050 self.reprfuncargs.toterminal(tw) 

1051 for line in self.lines: 

1052 red = line.startswith("E ") 

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

1054 if self.reprlocals: 

1055 tw.line("") 

1056 self.reprlocals.toterminal(tw) 

1057 if self.reprfileloc: 

1058 if self.lines: 

1059 tw.line("") 

1060 self.reprfileloc.toterminal(tw) 

1061 

1062 def __str__(self) -> str: 

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

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

1065 ) 

1066 

1067 

1068class ReprFileLocation(TerminalRepr): 

1069 def __init__(self, path, lineno: int, message: str) -> None: 

1070 self.path = str(path) 

1071 self.lineno = lineno 

1072 self.message = message 

1073 

1074 def toterminal(self, tw: py.io.TerminalWriter) -> None: 

1075 # filename and lineno output for each entry, 

1076 # using an output format that most editors understand 

1077 msg = self.message 

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

1079 if i != -1: 

1080 msg = msg[:i] 

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

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

1083 

1084 

1085class ReprLocals(TerminalRepr): 

1086 def __init__(self, lines: Sequence[str]) -> None: 

1087 self.lines = lines 

1088 

1089 def toterminal(self, tw: py.io.TerminalWriter) -> None: 

1090 for line in self.lines: 

1091 tw.line(line) 

1092 

1093 

1094class ReprFuncArgs(TerminalRepr): 

1095 def __init__(self, args: Sequence[Tuple[str, object]]) -> None: 

1096 self.args = args 

1097 

1098 def toterminal(self, tw: py.io.TerminalWriter) -> None: 

1099 if self.args: 

1100 linesofar = "" 

1101 for name, value in self.args: 

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

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

1104 if linesofar: 

1105 tw.line(linesofar) 

1106 linesofar = ns 

1107 else: 

1108 if linesofar: 

1109 linesofar += ", " + ns 

1110 else: 

1111 linesofar = ns 

1112 if linesofar: 

1113 tw.line(linesofar) 

1114 tw.line("") 

1115 

1116 

1117def getrawcode(obj, trycall: bool = True): 

1118 """ return code object for given function. """ 

1119 try: 

1120 return obj.__code__ 

1121 except AttributeError: 

1122 obj = getattr(obj, "f_code", obj) 

1123 obj = getattr(obj, "__code__", obj) 

1124 if trycall and not hasattr(obj, "co_firstlineno"): 

1125 if hasattr(obj, "__call__") and not inspect.isclass(obj): 

1126 x = getrawcode(obj.__call__, trycall=False) 

1127 if hasattr(x, "co_firstlineno"): 

1128 return x 

1129 return obj 

1130 

1131 

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

1133# see filter_traceback 

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

1135# for better maintenance 

1136 

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

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

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

1140 _PLUGGY_DIR = _PLUGGY_DIR.dirpath() 

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

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

1143 

1144 

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

1146 """Return True if a TracebackEntry instance should be removed from tracebacks: 

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

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

1149 """ 

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

1151 # points to dynamically generated code 

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

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

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

1155 if is_generated: 

1156 return False 

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

1158 # also return a str object. see #1133 

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

1160 return ( 

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

1162 )