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