1import io 2import operator 3import os 4import queue 5import sys 6import textwrap 7from typing import Any 8from typing import Dict 9from typing import Tuple 10from typing import Union 11 12import py 13 14import _pytest 15import pytest 16from _pytest._code.code import ExceptionChainRepr 17from _pytest._code.code import ExceptionInfo 18from _pytest._code.code import FormattedExcinfo 19from _pytest._io import TerminalWriter 20from _pytest.compat import TYPE_CHECKING 21from _pytest.pytester import LineMatcher 22 23try: 24 import importlib 25except ImportError: 26 invalidate_import_caches = None 27else: 28 invalidate_import_caches = getattr(importlib, "invalidate_caches", None) 29 30if TYPE_CHECKING: 31 from _pytest._code.code import _TracebackStyle 32 33 34@pytest.fixture 35def limited_recursion_depth(): 36 before = sys.getrecursionlimit() 37 sys.setrecursionlimit(150) 38 yield 39 sys.setrecursionlimit(before) 40 41 42def test_excinfo_simple() -> None: 43 try: 44 raise ValueError 45 except ValueError: 46 info = _pytest._code.ExceptionInfo.from_current() 47 assert info.type == ValueError 48 49 50def test_excinfo_from_exc_info_simple() -> None: 51 try: 52 raise ValueError 53 except ValueError as e: 54 assert e.__traceback__ is not None 55 info = _pytest._code.ExceptionInfo.from_exc_info((type(e), e, e.__traceback__)) 56 assert info.type == ValueError 57 58 59def test_excinfo_getstatement(): 60 def g(): 61 raise ValueError 62 63 def f(): 64 g() 65 66 try: 67 f() 68 except ValueError: 69 excinfo = _pytest._code.ExceptionInfo.from_current() 70 linenumbers = [ 71 f.__code__.co_firstlineno - 1 + 4, 72 f.__code__.co_firstlineno - 1 + 1, 73 g.__code__.co_firstlineno - 1 + 1, 74 ] 75 values = list(excinfo.traceback) 76 foundlinenumbers = [x.lineno for x in values] 77 assert foundlinenumbers == linenumbers 78 # for x in info: 79 # print "%s:%d %s" %(x.path.relto(root), x.lineno, x.statement) 80 # xxx 81 82 83# testchain for getentries test below 84 85 86def f(): 87 # 88 raise ValueError 89 # 90 91 92def g(): 93 # 94 __tracebackhide__ = True 95 f() 96 # 97 98 99def h(): 100 # 101 g() 102 # 103 104 105class TestTraceback_f_g_h: 106 def setup_method(self, method): 107 try: 108 h() 109 except ValueError: 110 self.excinfo = _pytest._code.ExceptionInfo.from_current() 111 112 def test_traceback_entries(self): 113 tb = self.excinfo.traceback 114 entries = list(tb) 115 assert len(tb) == 4 # maybe fragile test 116 assert len(entries) == 4 # maybe fragile test 117 names = ["f", "g", "h"] 118 for entry in entries: 119 try: 120 names.remove(entry.frame.code.name) 121 except ValueError: 122 pass 123 assert not names 124 125 def test_traceback_entry_getsource(self): 126 tb = self.excinfo.traceback 127 s = str(tb[-1].getsource()) 128 assert s.startswith("def f():") 129 assert s.endswith("raise ValueError") 130 131 def test_traceback_entry_getsource_in_construct(self): 132 def xyz(): 133 try: 134 raise ValueError 135 except somenoname: # type: ignore[name-defined] # noqa: F821 136 pass # pragma: no cover 137 138 try: 139 xyz() 140 except NameError: 141 excinfo = _pytest._code.ExceptionInfo.from_current() 142 else: 143 assert False, "did not raise NameError" 144 145 tb = excinfo.traceback 146 source = tb[-1].getsource() 147 assert source is not None 148 assert source.deindent().lines == [ 149 "def xyz():", 150 " try:", 151 " raise ValueError", 152 " except somenoname: # type: ignore[name-defined] # noqa: F821", 153 ] 154 155 def test_traceback_cut(self): 156 co = _pytest._code.Code(f) 157 path, firstlineno = co.path, co.firstlineno 158 traceback = self.excinfo.traceback 159 newtraceback = traceback.cut(path=path, firstlineno=firstlineno) 160 assert len(newtraceback) == 1 161 newtraceback = traceback.cut(path=path, lineno=firstlineno + 2) 162 assert len(newtraceback) == 1 163 164 def test_traceback_cut_excludepath(self, testdir): 165 p = testdir.makepyfile("def f(): raise ValueError") 166 with pytest.raises(ValueError) as excinfo: 167 p.pyimport().f() 168 basedir = py.path.local(pytest.__file__).dirpath() 169 newtraceback = excinfo.traceback.cut(excludepath=basedir) 170 for x in newtraceback: 171 if hasattr(x, "path"): 172 assert not py.path.local(x.path).relto(basedir) 173 assert newtraceback[-1].frame.code.path == p 174 175 def test_traceback_filter(self): 176 traceback = self.excinfo.traceback 177 ntraceback = traceback.filter() 178 assert len(ntraceback) == len(traceback) - 1 179 180 @pytest.mark.parametrize( 181 "tracebackhide, matching", 182 [ 183 (lambda info: True, True), 184 (lambda info: False, False), 185 (operator.methodcaller("errisinstance", ValueError), True), 186 (operator.methodcaller("errisinstance", IndexError), False), 187 ], 188 ) 189 def test_traceback_filter_selective(self, tracebackhide, matching): 190 def f(): 191 # 192 raise ValueError 193 # 194 195 def g(): 196 # 197 __tracebackhide__ = tracebackhide 198 f() 199 # 200 201 def h(): 202 # 203 g() 204 # 205 206 excinfo = pytest.raises(ValueError, h) 207 traceback = excinfo.traceback 208 ntraceback = traceback.filter() 209 print("old: {!r}".format(traceback)) 210 print("new: {!r}".format(ntraceback)) 211 212 if matching: 213 assert len(ntraceback) == len(traceback) - 2 214 else: 215 # -1 because of the __tracebackhide__ in pytest.raises 216 assert len(ntraceback) == len(traceback) - 1 217 218 def test_traceback_recursion_index(self): 219 def f(n): 220 if n < 10: 221 n += 1 222 f(n) 223 224 excinfo = pytest.raises(RuntimeError, f, 8) 225 traceback = excinfo.traceback 226 recindex = traceback.recursionindex() 227 assert recindex == 3 228 229 def test_traceback_only_specific_recursion_errors(self, monkeypatch): 230 def f(n): 231 if n == 0: 232 raise RuntimeError("hello") 233 f(n - 1) 234 235 excinfo = pytest.raises(RuntimeError, f, 25) 236 monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex") 237 repr = excinfo.getrepr() 238 assert "RuntimeError: hello" in str(repr.reprcrash) 239 240 def test_traceback_no_recursion_index(self) -> None: 241 def do_stuff() -> None: 242 raise RuntimeError 243 244 def reraise_me() -> None: 245 import sys 246 247 exc, val, tb = sys.exc_info() 248 assert val is not None 249 raise val.with_traceback(tb) 250 251 def f(n: int) -> None: 252 try: 253 do_stuff() 254 except BaseException: 255 reraise_me() 256 257 excinfo = pytest.raises(RuntimeError, f, 8) 258 assert excinfo is not None 259 traceback = excinfo.traceback 260 recindex = traceback.recursionindex() 261 assert recindex is None 262 263 def test_traceback_messy_recursion(self): 264 # XXX: simplified locally testable version 265 decorator = pytest.importorskip("decorator").decorator 266 267 def log(f, *k, **kw): 268 print("{} {}".format(k, kw)) 269 f(*k, **kw) 270 271 log = decorator(log) 272 273 def fail(): 274 raise ValueError("") 275 276 fail = log(log(fail)) 277 278 excinfo = pytest.raises(ValueError, fail) 279 assert excinfo.traceback.recursionindex() is None 280 281 def test_traceback_getcrashentry(self): 282 def i(): 283 __tracebackhide__ = True 284 raise ValueError 285 286 def h(): 287 i() 288 289 def g(): 290 __tracebackhide__ = True 291 h() 292 293 def f(): 294 g() 295 296 excinfo = pytest.raises(ValueError, f) 297 tb = excinfo.traceback 298 entry = tb.getcrashentry() 299 co = _pytest._code.Code(h) 300 assert entry.frame.code.path == co.path 301 assert entry.lineno == co.firstlineno + 1 302 assert entry.frame.code.name == "h" 303 304 def test_traceback_getcrashentry_empty(self): 305 def g(): 306 __tracebackhide__ = True 307 raise ValueError 308 309 def f(): 310 __tracebackhide__ = True 311 g() 312 313 excinfo = pytest.raises(ValueError, f) 314 tb = excinfo.traceback 315 entry = tb.getcrashentry() 316 co = _pytest._code.Code(g) 317 assert entry.frame.code.path == co.path 318 assert entry.lineno == co.firstlineno + 2 319 assert entry.frame.code.name == "g" 320 321 322def test_excinfo_exconly(): 323 excinfo = pytest.raises(ValueError, h) 324 assert excinfo.exconly().startswith("ValueError") 325 with pytest.raises(ValueError) as excinfo: 326 raise ValueError("hello\nworld") 327 msg = excinfo.exconly(tryshort=True) 328 assert msg.startswith("ValueError") 329 assert msg.endswith("world") 330 331 332def test_excinfo_repr_str() -> None: 333 excinfo1 = pytest.raises(ValueError, h) 334 assert repr(excinfo1) == "<ExceptionInfo ValueError() tblen=4>" 335 assert str(excinfo1) == "<ExceptionInfo ValueError() tblen=4>" 336 337 class CustomException(Exception): 338 def __repr__(self): 339 return "custom_repr" 340 341 def raises() -> None: 342 raise CustomException() 343 344 excinfo2 = pytest.raises(CustomException, raises) 345 assert repr(excinfo2) == "<ExceptionInfo custom_repr tblen=2>" 346 assert str(excinfo2) == "<ExceptionInfo custom_repr tblen=2>" 347 348 349def test_excinfo_for_later() -> None: 350 e = ExceptionInfo[BaseException].for_later() 351 assert "for raises" in repr(e) 352 assert "for raises" in str(e) 353 354 355def test_excinfo_errisinstance(): 356 excinfo = pytest.raises(ValueError, h) 357 assert excinfo.errisinstance(ValueError) 358 359 360def test_excinfo_no_sourcecode(): 361 try: 362 exec("raise ValueError()") 363 except ValueError: 364 excinfo = _pytest._code.ExceptionInfo.from_current() 365 s = str(excinfo.traceback[-1]) 366 assert s == " File '<string>':1 in <module>\n ???\n" 367 368 369def test_excinfo_no_python_sourcecode(tmpdir): 370 # XXX: simplified locally testable version 371 tmpdir.join("test.txt").write("{{ h()}}:") 372 373 jinja2 = pytest.importorskip("jinja2") 374 loader = jinja2.FileSystemLoader(str(tmpdir)) 375 env = jinja2.Environment(loader=loader) 376 template = env.get_template("test.txt") 377 excinfo = pytest.raises(ValueError, template.render, h=h) 378 for item in excinfo.traceback: 379 print(item) # XXX: for some reason jinja.Template.render is printed in full 380 item.source # shouldn't fail 381 if isinstance(item.path, py.path.local) and item.path.basename == "test.txt": 382 assert str(item.source) == "{{ h()}}:" 383 384 385def test_entrysource_Queue_example(): 386 try: 387 queue.Queue().get(timeout=0.001) 388 except queue.Empty: 389 excinfo = _pytest._code.ExceptionInfo.from_current() 390 entry = excinfo.traceback[-1] 391 source = entry.getsource() 392 assert source is not None 393 s = str(source).strip() 394 assert s.startswith("def get") 395 396 397def test_codepath_Queue_example(): 398 try: 399 queue.Queue().get(timeout=0.001) 400 except queue.Empty: 401 excinfo = _pytest._code.ExceptionInfo.from_current() 402 entry = excinfo.traceback[-1] 403 path = entry.path 404 assert isinstance(path, py.path.local) 405 assert path.basename.lower() == "queue.py" 406 assert path.check() 407 408 409def test_match_succeeds(): 410 with pytest.raises(ZeroDivisionError) as excinfo: 411 0 // 0 412 excinfo.match(r".*zero.*") 413 414 415def test_match_raises_error(testdir): 416 testdir.makepyfile( 417 """ 418 import pytest 419 def test_division_zero(): 420 with pytest.raises(ZeroDivisionError) as excinfo: 421 0 / 0 422 excinfo.match(r'[123]+') 423 """ 424 ) 425 result = testdir.runpytest() 426 assert result.ret != 0 427 428 exc_msg = "Regex pattern '[[]123[]]+' does not match 'division by zero'." 429 result.stdout.fnmatch_lines(["E * AssertionError: {}".format(exc_msg)]) 430 result.stdout.no_fnmatch_line("*__tracebackhide__ = True*") 431 432 result = testdir.runpytest("--fulltrace") 433 assert result.ret != 0 434 result.stdout.fnmatch_lines( 435 ["*__tracebackhide__ = True*", "E * AssertionError: {}".format(exc_msg)] 436 ) 437 438 439class TestFormattedExcinfo: 440 @pytest.fixture 441 def importasmod(self, request, _sys_snapshot): 442 def importasmod(source): 443 source = textwrap.dedent(source) 444 tmpdir = request.getfixturevalue("tmpdir") 445 modpath = tmpdir.join("mod.py") 446 tmpdir.ensure("__init__.py") 447 modpath.write(source) 448 if invalidate_import_caches is not None: 449 invalidate_import_caches() 450 return modpath.pyimport() 451 452 return importasmod 453 454 def test_repr_source(self): 455 pr = FormattedExcinfo() 456 source = _pytest._code.Source( 457 """\ 458 def f(x): 459 pass 460 """ 461 ).strip() 462 pr.flow_marker = "|" 463 lines = pr.get_source(source, 0) 464 assert len(lines) == 2 465 assert lines[0] == "| def f(x):" 466 assert lines[1] == " pass" 467 468 def test_repr_source_excinfo(self) -> None: 469 """Check if indentation is right.""" 470 try: 471 472 def f(): 473 1 / 0 474 475 f() 476 477 except BaseException: 478 excinfo = _pytest._code.ExceptionInfo.from_current() 479 else: 480 assert False, "did not raise" 481 482 pr = FormattedExcinfo() 483 source = pr._getentrysource(excinfo.traceback[-1]) 484 assert source is not None 485 lines = pr.get_source(source, 1, excinfo) 486 for line in lines: 487 print(line) 488 assert lines == [ 489 " def f():", 490 "> 1 / 0", 491 "E ZeroDivisionError: division by zero", 492 ] 493 494 def test_repr_source_not_existing(self): 495 pr = FormattedExcinfo() 496 co = compile("raise ValueError()", "", "exec") 497 try: 498 exec(co) 499 except ValueError: 500 excinfo = _pytest._code.ExceptionInfo.from_current() 501 repr = pr.repr_excinfo(excinfo) 502 assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" 503 assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" 504 505 def test_repr_many_line_source_not_existing(self): 506 pr = FormattedExcinfo() 507 co = compile( 508 """ 509a = 1 510raise ValueError() 511""", 512 "", 513 "exec", 514 ) 515 try: 516 exec(co) 517 except ValueError: 518 excinfo = _pytest._code.ExceptionInfo.from_current() 519 repr = pr.repr_excinfo(excinfo) 520 assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" 521 assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" 522 523 def test_repr_source_failing_fullsource(self, monkeypatch) -> None: 524 pr = FormattedExcinfo() 525 526 try: 527 1 / 0 528 except ZeroDivisionError: 529 excinfo = ExceptionInfo.from_current() 530 531 with monkeypatch.context() as m: 532 m.setattr(_pytest._code.Code, "fullsource", property(lambda self: None)) 533 repr = pr.repr_excinfo(excinfo) 534 535 assert repr.reprtraceback.reprentries[0].lines[0] == "> ???" 536 assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" 537 538 def test_repr_local(self) -> None: 539 p = FormattedExcinfo(showlocals=True) 540 loc = {"y": 5, "z": 7, "x": 3, "@x": 2, "__builtins__": {}} 541 reprlocals = p.repr_locals(loc) 542 assert reprlocals is not None 543 assert reprlocals.lines 544 assert reprlocals.lines[0] == "__builtins__ = <builtins>" 545 assert reprlocals.lines[1] == "x = 3" 546 assert reprlocals.lines[2] == "y = 5" 547 assert reprlocals.lines[3] == "z = 7" 548 549 def test_repr_local_with_error(self) -> None: 550 class ObjWithErrorInRepr: 551 def __repr__(self): 552 raise NotImplementedError 553 554 p = FormattedExcinfo(showlocals=True, truncate_locals=False) 555 loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}} 556 reprlocals = p.repr_locals(loc) 557 assert reprlocals is not None 558 assert reprlocals.lines 559 assert reprlocals.lines[0] == "__builtins__ = <builtins>" 560 assert "[NotImplementedError() raised in repr()]" in reprlocals.lines[1] 561 562 def test_repr_local_with_exception_in_class_property(self) -> None: 563 class ExceptionWithBrokenClass(Exception): 564 # Type ignored because it's bypassed intentionally. 565 @property # type: ignore 566 def __class__(self): 567 raise TypeError("boom!") 568 569 class ObjWithErrorInRepr: 570 def __repr__(self): 571 raise ExceptionWithBrokenClass() 572 573 p = FormattedExcinfo(showlocals=True, truncate_locals=False) 574 loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}} 575 reprlocals = p.repr_locals(loc) 576 assert reprlocals is not None 577 assert reprlocals.lines 578 assert reprlocals.lines[0] == "__builtins__ = <builtins>" 579 assert "[ExceptionWithBrokenClass() raised in repr()]" in reprlocals.lines[1] 580 581 def test_repr_local_truncated(self) -> None: 582 loc = {"l": [i for i in range(10)]} 583 p = FormattedExcinfo(showlocals=True) 584 truncated_reprlocals = p.repr_locals(loc) 585 assert truncated_reprlocals is not None 586 assert truncated_reprlocals.lines 587 assert truncated_reprlocals.lines[0] == "l = [0, 1, 2, 3, 4, 5, ...]" 588 589 q = FormattedExcinfo(showlocals=True, truncate_locals=False) 590 full_reprlocals = q.repr_locals(loc) 591 assert full_reprlocals is not None 592 assert full_reprlocals.lines 593 assert full_reprlocals.lines[0] == "l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" 594 595 def test_repr_tracebackentry_lines(self, importasmod) -> None: 596 mod = importasmod( 597 """ 598 def func1(): 599 raise ValueError("hello\\nworld") 600 """ 601 ) 602 excinfo = pytest.raises(ValueError, mod.func1) 603 excinfo.traceback = excinfo.traceback.filter() 604 p = FormattedExcinfo() 605 reprtb = p.repr_traceback_entry(excinfo.traceback[-1]) 606 607 # test as intermittent entry 608 lines = reprtb.lines 609 assert lines[0] == " def func1():" 610 assert lines[1] == '> raise ValueError("hello\\nworld")' 611 612 # test as last entry 613 p = FormattedExcinfo(showlocals=True) 614 repr_entry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo) 615 lines = repr_entry.lines 616 assert lines[0] == " def func1():" 617 assert lines[1] == '> raise ValueError("hello\\nworld")' 618 assert lines[2] == "E ValueError: hello" 619 assert lines[3] == "E world" 620 assert not lines[4:] 621 622 loc = repr_entry.reprfileloc 623 assert loc is not None 624 assert loc.path == mod.__file__ 625 assert loc.lineno == 3 626 # assert loc.message == "ValueError: hello" 627 628 def test_repr_tracebackentry_lines2(self, importasmod, tw_mock) -> None: 629 mod = importasmod( 630 """ 631 def func1(m, x, y, z): 632 raise ValueError("hello\\nworld") 633 """ 634 ) 635 excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120) 636 excinfo.traceback = excinfo.traceback.filter() 637 entry = excinfo.traceback[-1] 638 p = FormattedExcinfo(funcargs=True) 639 reprfuncargs = p.repr_args(entry) 640 assert reprfuncargs is not None 641 assert reprfuncargs.args[0] == ("m", repr("m" * 90)) 642 assert reprfuncargs.args[1] == ("x", "5") 643 assert reprfuncargs.args[2] == ("y", "13") 644 assert reprfuncargs.args[3] == ("z", repr("z" * 120)) 645 646 p = FormattedExcinfo(funcargs=True) 647 repr_entry = p.repr_traceback_entry(entry) 648 assert repr_entry.reprfuncargs is not None 649 assert repr_entry.reprfuncargs.args == reprfuncargs.args 650 repr_entry.toterminal(tw_mock) 651 assert tw_mock.lines[0] == "m = " + repr("m" * 90) 652 assert tw_mock.lines[1] == "x = 5, y = 13" 653 assert tw_mock.lines[2] == "z = " + repr("z" * 120) 654 655 def test_repr_tracebackentry_lines_var_kw_args(self, importasmod, tw_mock) -> None: 656 mod = importasmod( 657 """ 658 def func1(x, *y, **z): 659 raise ValueError("hello\\nworld") 660 """ 661 ) 662 excinfo = pytest.raises(ValueError, mod.func1, "a", "b", c="d") 663 excinfo.traceback = excinfo.traceback.filter() 664 entry = excinfo.traceback[-1] 665 p = FormattedExcinfo(funcargs=True) 666 reprfuncargs = p.repr_args(entry) 667 assert reprfuncargs is not None 668 assert reprfuncargs.args[0] == ("x", repr("a")) 669 assert reprfuncargs.args[1] == ("y", repr(("b",))) 670 assert reprfuncargs.args[2] == ("z", repr({"c": "d"})) 671 672 p = FormattedExcinfo(funcargs=True) 673 repr_entry = p.repr_traceback_entry(entry) 674 assert repr_entry.reprfuncargs 675 assert repr_entry.reprfuncargs.args == reprfuncargs.args 676 repr_entry.toterminal(tw_mock) 677 assert tw_mock.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}" 678 679 def test_repr_tracebackentry_short(self, importasmod) -> None: 680 mod = importasmod( 681 """ 682 def func1(): 683 raise ValueError("hello") 684 def entry(): 685 func1() 686 """ 687 ) 688 excinfo = pytest.raises(ValueError, mod.entry) 689 p = FormattedExcinfo(style="short") 690 reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) 691 lines = reprtb.lines 692 basename = py.path.local(mod.__file__).basename 693 assert lines[0] == " func1()" 694 assert reprtb.reprfileloc is not None 695 assert basename in str(reprtb.reprfileloc.path) 696 assert reprtb.reprfileloc.lineno == 5 697 698 # test last entry 699 p = FormattedExcinfo(style="short") 700 reprtb = p.repr_traceback_entry(excinfo.traceback[-1], excinfo) 701 lines = reprtb.lines 702 assert lines[0] == ' raise ValueError("hello")' 703 assert lines[1] == "E ValueError: hello" 704 assert reprtb.reprfileloc is not None 705 assert basename in str(reprtb.reprfileloc.path) 706 assert reprtb.reprfileloc.lineno == 3 707 708 def test_repr_tracebackentry_no(self, importasmod): 709 mod = importasmod( 710 """ 711 def func1(): 712 raise ValueError("hello") 713 def entry(): 714 func1() 715 """ 716 ) 717 excinfo = pytest.raises(ValueError, mod.entry) 718 p = FormattedExcinfo(style="no") 719 p.repr_traceback_entry(excinfo.traceback[-2]) 720 721 p = FormattedExcinfo(style="no") 722 reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo) 723 lines = reprentry.lines 724 assert lines[0] == "E ValueError: hello" 725 assert not lines[1:] 726 727 def test_repr_traceback_tbfilter(self, importasmod): 728 mod = importasmod( 729 """ 730 def f(x): 731 raise ValueError(x) 732 def entry(): 733 f(0) 734 """ 735 ) 736 excinfo = pytest.raises(ValueError, mod.entry) 737 p = FormattedExcinfo(tbfilter=True) 738 reprtb = p.repr_traceback(excinfo) 739 assert len(reprtb.reprentries) == 2 740 p = FormattedExcinfo(tbfilter=False) 741 reprtb = p.repr_traceback(excinfo) 742 assert len(reprtb.reprentries) == 3 743 744 def test_traceback_short_no_source(self, importasmod, monkeypatch) -> None: 745 mod = importasmod( 746 """ 747 def func1(): 748 raise ValueError("hello") 749 def entry(): 750 func1() 751 """ 752 ) 753 excinfo = pytest.raises(ValueError, mod.entry) 754 from _pytest._code.code import Code 755 756 monkeypatch.setattr(Code, "path", "bogus") 757 excinfo.traceback[0].frame.code.path = "bogus" # type: ignore[misc] 758 p = FormattedExcinfo(style="short") 759 reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) 760 lines = reprtb.lines 761 last_p = FormattedExcinfo(style="short") 762 last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo) 763 last_lines = last_reprtb.lines 764 monkeypatch.undo() 765 assert lines[0] == " func1()" 766 767 assert last_lines[0] == ' raise ValueError("hello")' 768 assert last_lines[1] == "E ValueError: hello" 769 770 def test_repr_traceback_and_excinfo(self, importasmod) -> None: 771 mod = importasmod( 772 """ 773 def f(x): 774 raise ValueError(x) 775 def entry(): 776 f(0) 777 """ 778 ) 779 excinfo = pytest.raises(ValueError, mod.entry) 780 781 styles = ("long", "short") # type: Tuple[_TracebackStyle, ...] 782 for style in styles: 783 p = FormattedExcinfo(style=style) 784 reprtb = p.repr_traceback(excinfo) 785 assert len(reprtb.reprentries) == 2 786 assert reprtb.style == style 787 assert not reprtb.extraline 788 repr = p.repr_excinfo(excinfo) 789 assert repr.reprtraceback 790 assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries) 791 792 assert repr.chain[0][0] 793 assert len(repr.chain[0][0].reprentries) == len(reprtb.reprentries) 794 assert repr.reprcrash is not None 795 assert repr.reprcrash.path.endswith("mod.py") 796 assert repr.reprcrash.message == "ValueError: 0" 797 798 def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch) -> None: 799 mod = importasmod( 800 """ 801 def f(x): 802 raise ValueError(x) 803 def entry(): 804 f(0) 805 """ 806 ) 807 excinfo = pytest.raises(ValueError, mod.entry) 808 809 p = FormattedExcinfo(abspath=False) 810 811 raised = 0 812 813 orig_getcwd = os.getcwd 814 815 def raiseos(): 816 nonlocal raised 817 upframe = sys._getframe().f_back 818 assert upframe is not None 819 if upframe.f_code.co_name == "checked_call": 820 # Only raise with expected calls, but not via e.g. inspect for 821 # py38-windows. 822 raised += 1 823 raise OSError(2, "custom_oserror") 824 return orig_getcwd() 825 826 monkeypatch.setattr(os, "getcwd", raiseos) 827 assert p._makepath(__file__) == __file__ 828 assert raised == 1 829 repr_tb = p.repr_traceback(excinfo) 830 831 matcher = LineMatcher(str(repr_tb).splitlines()) 832 matcher.fnmatch_lines( 833 [ 834 "def entry():", 835 "> f(0)", 836 "", 837 "{}:5: ".format(mod.__file__), 838 "_ _ *", 839 "", 840 " def f(x):", 841 "> raise ValueError(x)", 842 "E ValueError: 0", 843 "", 844 "{}:3: ValueError".format(mod.__file__), 845 ] 846 ) 847 assert raised == 3 848 849 def test_repr_excinfo_addouterr(self, importasmod, tw_mock): 850 mod = importasmod( 851 """ 852 def entry(): 853 raise ValueError() 854 """ 855 ) 856 excinfo = pytest.raises(ValueError, mod.entry) 857 repr = excinfo.getrepr() 858 repr.addsection("title", "content") 859 repr.toterminal(tw_mock) 860 assert tw_mock.lines[-1] == "content" 861 assert tw_mock.lines[-2] == ("-", "title") 862 863 def test_repr_excinfo_reprcrash(self, importasmod) -> None: 864 mod = importasmod( 865 """ 866 def entry(): 867 raise ValueError() 868 """ 869 ) 870 excinfo = pytest.raises(ValueError, mod.entry) 871 repr = excinfo.getrepr() 872 assert repr.reprcrash is not None 873 assert repr.reprcrash.path.endswith("mod.py") 874 assert repr.reprcrash.lineno == 3 875 assert repr.reprcrash.message == "ValueError" 876 assert str(repr.reprcrash).endswith("mod.py:3: ValueError") 877 878 def test_repr_traceback_recursion(self, importasmod): 879 mod = importasmod( 880 """ 881 def rec2(x): 882 return rec1(x+1) 883 def rec1(x): 884 return rec2(x-1) 885 def entry(): 886 rec1(42) 887 """ 888 ) 889 excinfo = pytest.raises(RuntimeError, mod.entry) 890 891 for style in ("short", "long", "no"): 892 p = FormattedExcinfo(style="short") 893 reprtb = p.repr_traceback(excinfo) 894 assert reprtb.extraline == "!!! Recursion detected (same locals & position)" 895 assert str(reprtb) 896 897 def test_reprexcinfo_getrepr(self, importasmod) -> None: 898 mod = importasmod( 899 """ 900 def f(x): 901 raise ValueError(x) 902 def entry(): 903 f(0) 904 """ 905 ) 906 excinfo = pytest.raises(ValueError, mod.entry) 907 908 styles = ("short", "long", "no") # type: Tuple[_TracebackStyle, ...] 909 for style in styles: 910 for showlocals in (True, False): 911 repr = excinfo.getrepr(style=style, showlocals=showlocals) 912 assert repr.reprtraceback.style == style 913 914 assert isinstance(repr, ExceptionChainRepr) 915 for r in repr.chain: 916 assert r[0].style == style 917 918 def test_reprexcinfo_unicode(self): 919 from _pytest._code.code import TerminalRepr 920 921 class MyRepr(TerminalRepr): 922 def toterminal(self, tw: TerminalWriter) -> None: 923 tw.line("я") 924 925 x = str(MyRepr()) 926 assert x == "я" 927 928 def test_toterminal_long(self, importasmod, tw_mock): 929 mod = importasmod( 930 """ 931 def g(x): 932 raise ValueError(x) 933 def f(): 934 g(3) 935 """ 936 ) 937 excinfo = pytest.raises(ValueError, mod.f) 938 excinfo.traceback = excinfo.traceback.filter() 939 repr = excinfo.getrepr() 940 repr.toterminal(tw_mock) 941 assert tw_mock.lines[0] == "" 942 tw_mock.lines.pop(0) 943 assert tw_mock.lines[0] == " def f():" 944 assert tw_mock.lines[1] == "> g(3)" 945 assert tw_mock.lines[2] == "" 946 line = tw_mock.get_write_msg(3) 947 assert line.endswith("mod.py") 948 assert tw_mock.lines[4] == (":5: ") 949 assert tw_mock.lines[5] == ("_ ", None) 950 assert tw_mock.lines[6] == "" 951 assert tw_mock.lines[7] == " def g(x):" 952 assert tw_mock.lines[8] == "> raise ValueError(x)" 953 assert tw_mock.lines[9] == "E ValueError: 3" 954 assert tw_mock.lines[10] == "" 955 line = tw_mock.get_write_msg(11) 956 assert line.endswith("mod.py") 957 assert tw_mock.lines[12] == ":3: ValueError" 958 959 def test_toterminal_long_missing_source(self, importasmod, tmpdir, tw_mock): 960 mod = importasmod( 961 """ 962 def g(x): 963 raise ValueError(x) 964 def f(): 965 g(3) 966 """ 967 ) 968 excinfo = pytest.raises(ValueError, mod.f) 969 tmpdir.join("mod.py").remove() 970 excinfo.traceback = excinfo.traceback.filter() 971 repr = excinfo.getrepr() 972 repr.toterminal(tw_mock) 973 assert tw_mock.lines[0] == "" 974 tw_mock.lines.pop(0) 975 assert tw_mock.lines[0] == "> ???" 976 assert tw_mock.lines[1] == "" 977 line = tw_mock.get_write_msg(2) 978 assert line.endswith("mod.py") 979 assert tw_mock.lines[3] == ":5: " 980 assert tw_mock.lines[4] == ("_ ", None) 981 assert tw_mock.lines[5] == "" 982 assert tw_mock.lines[6] == "> ???" 983 assert tw_mock.lines[7] == "E ValueError: 3" 984 assert tw_mock.lines[8] == "" 985 line = tw_mock.get_write_msg(9) 986 assert line.endswith("mod.py") 987 assert tw_mock.lines[10] == ":3: ValueError" 988 989 def test_toterminal_long_incomplete_source(self, importasmod, tmpdir, tw_mock): 990 mod = importasmod( 991 """ 992 def g(x): 993 raise ValueError(x) 994 def f(): 995 g(3) 996 """ 997 ) 998 excinfo = pytest.raises(ValueError, mod.f) 999 tmpdir.join("mod.py").write("asdf") 1000 excinfo.traceback = excinfo.traceback.filter() 1001 repr = excinfo.getrepr() 1002 repr.toterminal(tw_mock) 1003 assert tw_mock.lines[0] == "" 1004 tw_mock.lines.pop(0) 1005 assert tw_mock.lines[0] == "> ???" 1006 assert tw_mock.lines[1] == "" 1007 line = tw_mock.get_write_msg(2) 1008 assert line.endswith("mod.py") 1009 assert tw_mock.lines[3] == ":5: " 1010 assert tw_mock.lines[4] == ("_ ", None) 1011 assert tw_mock.lines[5] == "" 1012 assert tw_mock.lines[6] == "> ???" 1013 assert tw_mock.lines[7] == "E ValueError: 3" 1014 assert tw_mock.lines[8] == "" 1015 line = tw_mock.get_write_msg(9) 1016 assert line.endswith("mod.py") 1017 assert tw_mock.lines[10] == ":3: ValueError" 1018 1019 def test_toterminal_long_filenames(self, importasmod, tw_mock): 1020 mod = importasmod( 1021 """ 1022 def f(): 1023 raise ValueError() 1024 """ 1025 ) 1026 excinfo = pytest.raises(ValueError, mod.f) 1027 path = py.path.local(mod.__file__) 1028 old = path.dirpath().chdir() 1029 try: 1030 repr = excinfo.getrepr(abspath=False) 1031 repr.toterminal(tw_mock) 1032 x = py.path.local().bestrelpath(path) 1033 if len(x) < len(str(path)): 1034 msg = tw_mock.get_write_msg(-2) 1035 assert msg == "mod.py" 1036 assert tw_mock.lines[-1] == ":3: ValueError" 1037 1038 repr = excinfo.getrepr(abspath=True) 1039 repr.toterminal(tw_mock) 1040 msg = tw_mock.get_write_msg(-2) 1041 assert msg == path 1042 line = tw_mock.lines[-1] 1043 assert line == ":3: ValueError" 1044 finally: 1045 old.chdir() 1046 1047 @pytest.mark.parametrize( 1048 "reproptions", 1049 [ 1050 pytest.param( 1051 { 1052 "style": style, 1053 "showlocals": showlocals, 1054 "funcargs": funcargs, 1055 "tbfilter": tbfilter, 1056 }, 1057 id="style={},showlocals={},funcargs={},tbfilter={}".format( 1058 style, showlocals, funcargs, tbfilter 1059 ), 1060 ) 1061 for style in ["long", "short", "line", "no", "native", "value", "auto"] 1062 for showlocals in (True, False) 1063 for tbfilter in (True, False) 1064 for funcargs in (True, False) 1065 ], 1066 ) 1067 def test_format_excinfo(self, reproptions: Dict[str, Any]) -> None: 1068 def bar(): 1069 assert False, "some error" 1070 1071 def foo(): 1072 bar() 1073 1074 # using inline functions as opposed to importasmod so we get source code lines 1075 # in the tracebacks (otherwise getinspect doesn't find the source code). 1076 with pytest.raises(AssertionError) as excinfo: 1077 foo() 1078 file = io.StringIO() 1079 tw = TerminalWriter(file=file) 1080 repr = excinfo.getrepr(**reproptions) 1081 repr.toterminal(tw) 1082 assert file.getvalue() 1083 1084 def test_traceback_repr_style(self, importasmod, tw_mock): 1085 mod = importasmod( 1086 """ 1087 def f(): 1088 g() 1089 def g(): 1090 h() 1091 def h(): 1092 i() 1093 def i(): 1094 raise ValueError() 1095 """ 1096 ) 1097 excinfo = pytest.raises(ValueError, mod.f) 1098 excinfo.traceback = excinfo.traceback.filter() 1099 excinfo.traceback[1].set_repr_style("short") 1100 excinfo.traceback[2].set_repr_style("short") 1101 r = excinfo.getrepr(style="long") 1102 r.toterminal(tw_mock) 1103 for line in tw_mock.lines: 1104 print(line) 1105 assert tw_mock.lines[0] == "" 1106 assert tw_mock.lines[1] == " def f():" 1107 assert tw_mock.lines[2] == "> g()" 1108 assert tw_mock.lines[3] == "" 1109 msg = tw_mock.get_write_msg(4) 1110 assert msg.endswith("mod.py") 1111 assert tw_mock.lines[5] == ":3: " 1112 assert tw_mock.lines[6] == ("_ ", None) 1113 tw_mock.get_write_msg(7) 1114 assert tw_mock.lines[8].endswith("in g") 1115 assert tw_mock.lines[9] == " h()" 1116 tw_mock.get_write_msg(10) 1117 assert tw_mock.lines[11].endswith("in h") 1118 assert tw_mock.lines[12] == " i()" 1119 assert tw_mock.lines[13] == ("_ ", None) 1120 assert tw_mock.lines[14] == "" 1121 assert tw_mock.lines[15] == " def i():" 1122 assert tw_mock.lines[16] == "> raise ValueError()" 1123 assert tw_mock.lines[17] == "E ValueError" 1124 assert tw_mock.lines[18] == "" 1125 msg = tw_mock.get_write_msg(19) 1126 msg.endswith("mod.py") 1127 assert tw_mock.lines[20] == ":9: ValueError" 1128 1129 def test_exc_chain_repr(self, importasmod, tw_mock): 1130 mod = importasmod( 1131 """ 1132 class Err(Exception): 1133 pass 1134 def f(): 1135 try: 1136 g() 1137 except Exception as e: 1138 raise Err() from e 1139 finally: 1140 h() 1141 def g(): 1142 raise ValueError() 1143 1144 def h(): 1145 raise AttributeError() 1146 """ 1147 ) 1148 excinfo = pytest.raises(AttributeError, mod.f) 1149 r = excinfo.getrepr(style="long") 1150 r.toterminal(tw_mock) 1151 for line in tw_mock.lines: 1152 print(line) 1153 assert tw_mock.lines[0] == "" 1154 assert tw_mock.lines[1] == " def f():" 1155 assert tw_mock.lines[2] == " try:" 1156 assert tw_mock.lines[3] == "> g()" 1157 assert tw_mock.lines[4] == "" 1158 line = tw_mock.get_write_msg(5) 1159 assert line.endswith("mod.py") 1160 assert tw_mock.lines[6] == ":6: " 1161 assert tw_mock.lines[7] == ("_ ", None) 1162 assert tw_mock.lines[8] == "" 1163 assert tw_mock.lines[9] == " def g():" 1164 assert tw_mock.lines[10] == "> raise ValueError()" 1165 assert tw_mock.lines[11] == "E ValueError" 1166 assert tw_mock.lines[12] == "" 1167 line = tw_mock.get_write_msg(13) 1168 assert line.endswith("mod.py") 1169 assert tw_mock.lines[14] == ":12: ValueError" 1170 assert tw_mock.lines[15] == "" 1171 assert ( 1172 tw_mock.lines[16] 1173 == "The above exception was the direct cause of the following exception:" 1174 ) 1175 assert tw_mock.lines[17] == "" 1176 assert tw_mock.lines[18] == " def f():" 1177 assert tw_mock.lines[19] == " try:" 1178 assert tw_mock.lines[20] == " g()" 1179 assert tw_mock.lines[21] == " except Exception as e:" 1180 assert tw_mock.lines[22] == "> raise Err() from e" 1181 assert tw_mock.lines[23] == "E test_exc_chain_repr0.mod.Err" 1182 assert tw_mock.lines[24] == "" 1183 line = tw_mock.get_write_msg(25) 1184 assert line.endswith("mod.py") 1185 assert tw_mock.lines[26] == ":8: Err" 1186 assert tw_mock.lines[27] == "" 1187 assert ( 1188 tw_mock.lines[28] 1189 == "During handling of the above exception, another exception occurred:" 1190 ) 1191 assert tw_mock.lines[29] == "" 1192 assert tw_mock.lines[30] == " def f():" 1193 assert tw_mock.lines[31] == " try:" 1194 assert tw_mock.lines[32] == " g()" 1195 assert tw_mock.lines[33] == " except Exception as e:" 1196 assert tw_mock.lines[34] == " raise Err() from e" 1197 assert tw_mock.lines[35] == " finally:" 1198 assert tw_mock.lines[36] == "> h()" 1199 assert tw_mock.lines[37] == "" 1200 line = tw_mock.get_write_msg(38) 1201 assert line.endswith("mod.py") 1202 assert tw_mock.lines[39] == ":10: " 1203 assert tw_mock.lines[40] == ("_ ", None) 1204 assert tw_mock.lines[41] == "" 1205 assert tw_mock.lines[42] == " def h():" 1206 assert tw_mock.lines[43] == "> raise AttributeError()" 1207 assert tw_mock.lines[44] == "E AttributeError" 1208 assert tw_mock.lines[45] == "" 1209 line = tw_mock.get_write_msg(46) 1210 assert line.endswith("mod.py") 1211 assert tw_mock.lines[47] == ":15: AttributeError" 1212 1213 @pytest.mark.parametrize("mode", ["from_none", "explicit_suppress"]) 1214 def test_exc_repr_chain_suppression(self, importasmod, mode, tw_mock): 1215 """Check that exc repr does not show chained exceptions in Python 3. 1216 - When the exception is raised with "from None" 1217 - Explicitly suppressed with "chain=False" to ExceptionInfo.getrepr(). 1218 """ 1219 raise_suffix = " from None" if mode == "from_none" else "" 1220 mod = importasmod( 1221 """ 1222 def f(): 1223 try: 1224 g() 1225 except Exception: 1226 raise AttributeError(){raise_suffix} 1227 def g(): 1228 raise ValueError() 1229 """.format( 1230 raise_suffix=raise_suffix 1231 ) 1232 ) 1233 excinfo = pytest.raises(AttributeError, mod.f) 1234 r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress") 1235 r.toterminal(tw_mock) 1236 for line in tw_mock.lines: 1237 print(line) 1238 assert tw_mock.lines[0] == "" 1239 assert tw_mock.lines[1] == " def f():" 1240 assert tw_mock.lines[2] == " try:" 1241 assert tw_mock.lines[3] == " g()" 1242 assert tw_mock.lines[4] == " except Exception:" 1243 assert tw_mock.lines[5] == "> raise AttributeError(){}".format( 1244 raise_suffix 1245 ) 1246 assert tw_mock.lines[6] == "E AttributeError" 1247 assert tw_mock.lines[7] == "" 1248 line = tw_mock.get_write_msg(8) 1249 assert line.endswith("mod.py") 1250 assert tw_mock.lines[9] == ":6: AttributeError" 1251 assert len(tw_mock.lines) == 10 1252 1253 @pytest.mark.parametrize( 1254 "reason, description", 1255 [ 1256 pytest.param( 1257 "cause", 1258 "The above exception was the direct cause of the following exception:", 1259 id="cause", 1260 ), 1261 pytest.param( 1262 "context", 1263 "During handling of the above exception, another exception occurred:", 1264 id="context", 1265 ), 1266 ], 1267 ) 1268 def test_exc_chain_repr_without_traceback(self, importasmod, reason, description): 1269 """ 1270 Handle representation of exception chains where one of the exceptions doesn't have a 1271 real traceback, such as those raised in a subprocess submitted by the multiprocessing 1272 module (#1984). 1273 """ 1274 exc_handling_code = " from e" if reason == "cause" else "" 1275 mod = importasmod( 1276 """ 1277 def f(): 1278 try: 1279 g() 1280 except Exception as e: 1281 raise RuntimeError('runtime problem'){exc_handling_code} 1282 def g(): 1283 raise ValueError('invalid value') 1284 """.format( 1285 exc_handling_code=exc_handling_code 1286 ) 1287 ) 1288 1289 with pytest.raises(RuntimeError) as excinfo: 1290 mod.f() 1291 1292 # emulate the issue described in #1984 1293 attr = "__%s__" % reason 1294 getattr(excinfo.value, attr).__traceback__ = None 1295 1296 r = excinfo.getrepr() 1297 file = io.StringIO() 1298 tw = TerminalWriter(file=file) 1299 tw.hasmarkup = False 1300 r.toterminal(tw) 1301 1302 matcher = LineMatcher(file.getvalue().splitlines()) 1303 matcher.fnmatch_lines( 1304 [ 1305 "ValueError: invalid value", 1306 description, 1307 "* except Exception as e:", 1308 "> * raise RuntimeError('runtime problem')" + exc_handling_code, 1309 "E *RuntimeError: runtime problem", 1310 ] 1311 ) 1312 1313 def test_exc_chain_repr_cycle(self, importasmod, tw_mock): 1314 mod = importasmod( 1315 """ 1316 class Err(Exception): 1317 pass 1318 def fail(): 1319 return 0 / 0 1320 def reraise(): 1321 try: 1322 fail() 1323 except ZeroDivisionError as e: 1324 raise Err() from e 1325 def unreraise(): 1326 try: 1327 reraise() 1328 except Err as e: 1329 raise e.__cause__ 1330 """ 1331 ) 1332 excinfo = pytest.raises(ZeroDivisionError, mod.unreraise) 1333 r = excinfo.getrepr(style="short") 1334 r.toterminal(tw_mock) 1335 out = "\n".join(line for line in tw_mock.lines if isinstance(line, str)) 1336 expected_out = textwrap.dedent( 1337 """\ 1338 :13: in unreraise 1339 reraise() 1340 :10: in reraise 1341 raise Err() from e 1342 E test_exc_chain_repr_cycle0.mod.Err 1343 1344 During handling of the above exception, another exception occurred: 1345 :15: in unreraise 1346 raise e.__cause__ 1347 :8: in reraise 1348 fail() 1349 :5: in fail 1350 return 0 / 0 1351 E ZeroDivisionError: division by zero""" 1352 ) 1353 assert out == expected_out 1354 1355 def test_exec_type_error_filter(self, importasmod): 1356 """See #7742""" 1357 mod = importasmod( 1358 """\ 1359 def f(): 1360 exec("a = 1", {}, []) 1361 """ 1362 ) 1363 with pytest.raises(TypeError) as excinfo: 1364 mod.f() 1365 # previously crashed with `AttributeError: list has no attribute get` 1366 excinfo.traceback.filter() 1367 1368 1369@pytest.mark.parametrize("style", ["short", "long"]) 1370@pytest.mark.parametrize("encoding", [None, "utf8", "utf16"]) 1371def test_repr_traceback_with_unicode(style, encoding): 1372 if encoding is None: 1373 msg = "☹" # type: Union[str, bytes] 1374 else: 1375 msg = "☹".encode(encoding) 1376 try: 1377 raise RuntimeError(msg) 1378 except RuntimeError: 1379 e_info = ExceptionInfo.from_current() 1380 formatter = FormattedExcinfo(style=style) 1381 repr_traceback = formatter.repr_traceback(e_info) 1382 assert repr_traceback is not None 1383 1384 1385def test_cwd_deleted(testdir): 1386 testdir.makepyfile( 1387 """ 1388 def test(tmpdir): 1389 tmpdir.chdir() 1390 tmpdir.remove() 1391 assert False 1392 """ 1393 ) 1394 result = testdir.runpytest() 1395 result.stdout.fnmatch_lines(["* 1 failed in *"]) 1396 result.stdout.no_fnmatch_line("*INTERNALERROR*") 1397 result.stderr.no_fnmatch_line("*INTERNALERROR*") 1398 1399 1400@pytest.mark.usefixtures("limited_recursion_depth") 1401def test_exception_repr_extraction_error_on_recursion(): 1402 """ 1403 Ensure we can properly detect a recursion error even 1404 if some locals raise error on comparison (#2459). 1405 """ 1406 1407 class numpy_like: 1408 def __eq__(self, other): 1409 if type(other) is numpy_like: 1410 raise ValueError( 1411 "The truth value of an array " 1412 "with more than one element is ambiguous." 1413 ) 1414 1415 def a(x): 1416 return b(numpy_like()) 1417 1418 def b(x): 1419 return a(numpy_like()) 1420 1421 with pytest.raises(RuntimeError) as excinfo: 1422 a(numpy_like()) 1423 1424 matcher = LineMatcher(str(excinfo.getrepr()).splitlines()) 1425 matcher.fnmatch_lines( 1426 [ 1427 "!!! Recursion error detected, but an error occurred locating the origin of recursion.", 1428 "*The following exception happened*", 1429 "*ValueError: The truth value of an array*", 1430 ] 1431 ) 1432 1433 1434@pytest.mark.usefixtures("limited_recursion_depth") 1435def test_no_recursion_index_on_recursion_error(): 1436 """ 1437 Ensure that we don't break in case we can't find the recursion index 1438 during a recursion error (#2486). 1439 """ 1440 1441 class RecursionDepthError: 1442 def __getattr__(self, attr): 1443 return getattr(self, "_" + attr) 1444 1445 with pytest.raises(RuntimeError) as excinfo: 1446 RecursionDepthError().trigger 1447 assert "maximum recursion" in str(excinfo.getrepr()) 1448