from __future__ import annotations

import importlib
import io
import operator
from pathlib import Path
import queue
import re
import sys
import textwrap
from typing import Any
from typing import TYPE_CHECKING

import _pytest._code
from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import FormattedExcinfo
from _pytest._io import TerminalWriter
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pathlib import bestrelpath
from _pytest.pathlib import import_path
from _pytest.pytester import LineMatcher
from _pytest.pytester import Pytester
import pytest


if TYPE_CHECKING:
    from _pytest._code.code import _TracebackStyle

if sys.version_info[:2] < (3, 11):
    from exceptiongroup import ExceptionGroup


@pytest.fixture
def limited_recursion_depth():
    before = sys.getrecursionlimit()
    sys.setrecursionlimit(150)
    yield
    sys.setrecursionlimit(before)


def test_excinfo_simple() -> None:
    try:
        raise ValueError
    except ValueError:
        info = _pytest._code.ExceptionInfo.from_current()
    assert info.type == ValueError


def test_excinfo_from_exc_info_simple() -> None:
    try:
        raise ValueError
    except ValueError as e:
        assert e.__traceback__ is not None
        info = _pytest._code.ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
    assert info.type == ValueError


def test_excinfo_from_exception_simple() -> None:
    try:
        raise ValueError
    except ValueError as e:
        assert e.__traceback__ is not None
        info = _pytest._code.ExceptionInfo.from_exception(e)
    assert info.type == ValueError


def test_excinfo_from_exception_missing_traceback_assertion() -> None:
    with pytest.raises(AssertionError, match=r"must have.*__traceback__"):
        _pytest._code.ExceptionInfo.from_exception(ValueError())


def test_excinfo_getstatement():
    def g():
        raise ValueError

    def f():
        g()

    try:
        f()
    except ValueError:
        excinfo = _pytest._code.ExceptionInfo.from_current()
    linenumbers = [
        f.__code__.co_firstlineno - 1 + 4,
        f.__code__.co_firstlineno - 1 + 1,
        g.__code__.co_firstlineno - 1 + 1,
    ]
    values = list(excinfo.traceback)
    foundlinenumbers = [x.lineno for x in values]
    assert foundlinenumbers == linenumbers
    # for x in info:
    #    print "%s:%d  %s" %(x.path.relto(root), x.lineno, x.statement)
    # xxx


# testchain for getentries test below


def f():
    #
    raise ValueError
    #


def g():
    #
    __tracebackhide__ = True
    f()
    #


def h():
    #
    g()
    #


class TestTraceback_f_g_h:
    def setup_method(self, method):
        try:
            h()
        except ValueError:
            self.excinfo = _pytest._code.ExceptionInfo.from_current()

    def test_traceback_entries(self):
        tb = self.excinfo.traceback
        entries = list(tb)
        assert len(tb) == 4  # maybe fragile test
        assert len(entries) == 4  # maybe fragile test
        names = ["f", "g", "h"]
        for entry in entries:
            try:
                names.remove(entry.frame.code.name)
            except ValueError:
                pass
        assert not names

    def test_traceback_entry_getsource(self):
        tb = self.excinfo.traceback
        s = str(tb[-1].getsource())
        assert s.startswith("def f():")
        assert s.endswith("raise ValueError")

    def test_traceback_entry_getsource_in_construct(self):
        def xyz():
            try:
                raise ValueError
            except somenoname:  # type: ignore[name-defined] # noqa: F821
                pass  # pragma: no cover

        try:
            xyz()
        except NameError:
            excinfo = _pytest._code.ExceptionInfo.from_current()
        else:
            assert False, "did not raise NameError"

        tb = excinfo.traceback
        source = tb[-1].getsource()
        assert source is not None
        assert source.deindent().lines == [
            "def xyz():",
            "    try:",
            "        raise ValueError",
            "    except somenoname:  # type: ignore[name-defined] # noqa: F821",
        ]

    def test_traceback_cut(self) -> None:
        co = _pytest._code.Code.from_function(f)
        path, firstlineno = co.path, co.firstlineno
        assert isinstance(path, Path)
        traceback = self.excinfo.traceback
        newtraceback = traceback.cut(path=path, firstlineno=firstlineno)
        assert len(newtraceback) == 1
        newtraceback = traceback.cut(path=path, lineno=firstlineno + 2)
        assert len(newtraceback) == 1

    def test_traceback_cut_excludepath(self, pytester: Pytester) -> None:
        p = pytester.makepyfile("def f(): raise ValueError")
        with pytest.raises(ValueError) as excinfo:
            import_path(p, root=pytester.path).f()  # type: ignore[attr-defined]
        basedir = Path(pytest.__file__).parent
        newtraceback = excinfo.traceback.cut(excludepath=basedir)
        for x in newtraceback:
            assert isinstance(x.path, Path)
            assert basedir not in x.path.parents
        assert newtraceback[-1].frame.code.path == p

    def test_traceback_filter(self):
        traceback = self.excinfo.traceback
        ntraceback = traceback.filter(self.excinfo)
        assert len(ntraceback) == len(traceback) - 1

    @pytest.mark.parametrize(
        "tracebackhide, matching",
        [
            (lambda info: True, True),
            (lambda info: False, False),
            (operator.methodcaller("errisinstance", ValueError), True),
            (operator.methodcaller("errisinstance", IndexError), False),
        ],
    )
    def test_traceback_filter_selective(self, tracebackhide, matching):
        def f():
            #
            raise ValueError
            #

        def g():
            #
            __tracebackhide__ = tracebackhide
            f()
            #

        def h():
            #
            g()
            #

        excinfo = pytest.raises(ValueError, h)
        traceback = excinfo.traceback
        ntraceback = traceback.filter(excinfo)
        print(f"old: {traceback!r}")
        print(f"new: {ntraceback!r}")

        if matching:
            assert len(ntraceback) == len(traceback) - 2
        else:
            # -1 because of the __tracebackhide__ in pytest.raises
            assert len(ntraceback) == len(traceback) - 1

    def test_traceback_recursion_index(self):
        def f(n):
            if n < 10:
                n += 1
            f(n)

        excinfo = pytest.raises(RuntimeError, f, 8)
        traceback = excinfo.traceback
        recindex = traceback.recursionindex()
        assert recindex == 3

    def test_traceback_only_specific_recursion_errors(self, monkeypatch):
        def f(n):
            if n == 0:
                raise RuntimeError("hello")
            f(n - 1)

        excinfo = pytest.raises(RuntimeError, f, 25)
        monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
        repr = excinfo.getrepr()
        assert "RuntimeError: hello" in str(repr.reprcrash)

    def test_traceback_no_recursion_index(self) -> None:
        def do_stuff() -> None:
            raise RuntimeError

        def reraise_me() -> None:
            import sys

            exc, val, tb = sys.exc_info()
            assert val is not None
            raise val.with_traceback(tb)

        def f(n: int) -> None:
            try:
                do_stuff()
            except BaseException:
                reraise_me()

        excinfo = pytest.raises(RuntimeError, f, 8)
        assert excinfo is not None
        traceback = excinfo.traceback
        recindex = traceback.recursionindex()
        assert recindex is None

    def test_traceback_messy_recursion(self):
        # XXX: simplified locally testable version
        decorator = pytest.importorskip("decorator").decorator

        def log(f, *k, **kw):
            print(f"{k} {kw}")
            f(*k, **kw)

        log = decorator(log)

        def fail():
            raise ValueError("")

        fail = log(log(fail))

        excinfo = pytest.raises(ValueError, fail)
        assert excinfo.traceback.recursionindex() is None

    def test_getreprcrash(self):
        def i():
            __tracebackhide__ = True
            raise ValueError

        def h():
            i()

        def g():
            __tracebackhide__ = True
            h()

        def f():
            g()

        excinfo = pytest.raises(ValueError, f)
        reprcrash = excinfo._getreprcrash()
        assert reprcrash is not None
        co = _pytest._code.Code.from_function(h)
        assert reprcrash.path == str(co.path)
        assert reprcrash.lineno == co.firstlineno + 1 + 1

    def test_getreprcrash_empty(self):
        def g():
            __tracebackhide__ = True
            raise ValueError

        def f():
            __tracebackhide__ = True
            g()

        excinfo = pytest.raises(ValueError, f)
        assert excinfo._getreprcrash() is None


def test_excinfo_exconly():
    excinfo = pytest.raises(ValueError, h)
    assert excinfo.exconly().startswith("ValueError")
    with pytest.raises(ValueError) as excinfo:
        raise ValueError("hello\nworld")
    msg = excinfo.exconly(tryshort=True)
    assert msg.startswith("ValueError")
    assert msg.endswith("world")


def test_excinfo_repr_str() -> None:
    excinfo1 = pytest.raises(ValueError, h)
    assert repr(excinfo1) == "<ExceptionInfo ValueError() tblen=4>"
    assert str(excinfo1) == "<ExceptionInfo ValueError() tblen=4>"

    class CustomException(Exception):
        def __repr__(self):
            return "custom_repr"

    def raises() -> None:
        raise CustomException()

    excinfo2 = pytest.raises(CustomException, raises)
    assert repr(excinfo2) == "<ExceptionInfo custom_repr tblen=2>"
    assert str(excinfo2) == "<ExceptionInfo custom_repr tblen=2>"


def test_excinfo_for_later() -> None:
    e = ExceptionInfo[BaseException].for_later()
    assert "for raises" in repr(e)
    assert "for raises" in str(e)


def test_excinfo_errisinstance():
    excinfo = pytest.raises(ValueError, h)
    assert excinfo.errisinstance(ValueError)


def test_excinfo_no_sourcecode():
    try:
        exec("raise ValueError()")
    except ValueError:
        excinfo = _pytest._code.ExceptionInfo.from_current()
    s = str(excinfo.traceback[-1])
    assert s == "  File '<string>':1 in <module>\n  ???\n"


def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None:
    # XXX: simplified locally testable version
    tmp_path.joinpath("test.txt").write_text("{{ h()}}:", encoding="utf-8")

    jinja2 = pytest.importorskip("jinja2")
    loader = jinja2.FileSystemLoader(str(tmp_path))
    env = jinja2.Environment(loader=loader)
    template = env.get_template("test.txt")
    excinfo = pytest.raises(ValueError, template.render, h=h)
    for item in excinfo.traceback:
        print(item)  # XXX: for some reason jinja.Template.render is printed in full
        item.source  # shouldn't fail
        if isinstance(item.path, Path) and item.path.name == "test.txt":
            assert str(item.source) == "{{ h()}}:"


def test_entrysource_Queue_example():
    try:
        queue.Queue().get(timeout=0.001)
    except queue.Empty:
        excinfo = _pytest._code.ExceptionInfo.from_current()
    entry = excinfo.traceback[-1]
    source = entry.getsource()
    assert source is not None
    s = str(source).strip()
    assert s.startswith("def get")


def test_codepath_Queue_example() -> None:
    try:
        queue.Queue().get(timeout=0.001)
    except queue.Empty:
        excinfo = _pytest._code.ExceptionInfo.from_current()
    entry = excinfo.traceback[-1]
    path = entry.path
    assert isinstance(path, Path)
    assert path.name.lower() == "queue.py"
    assert path.exists()


def test_match_succeeds():
    with pytest.raises(ZeroDivisionError) as excinfo:
        0 // 0
    excinfo.match(r".*zero.*")


def test_match_raises_error(pytester: Pytester) -> None:
    pytester.makepyfile(
        """
        import pytest
        def test_division_zero():
            with pytest.raises(ZeroDivisionError) as excinfo:
                0 / 0
            excinfo.match(r'[123]+')
    """
    )
    result = pytester.runpytest("--tb=short")
    assert result.ret != 0

    match = [
        r"E .* AssertionError: Regex pattern did not match.",
        r"E .* Regex: '\[123\]\+'",
        r"E .* Input: 'division by zero'",
    ]
    result.stdout.re_match_lines(match)
    result.stdout.no_fnmatch_line("*__tracebackhide__ = True*")

    result = pytester.runpytest("--fulltrace")
    assert result.ret != 0
    result.stdout.re_match_lines([r".*__tracebackhide__ = True.*", *match])


class TestGroupContains:
    def test_contains_exception_type(self) -> None:
        exc_group = ExceptionGroup("", [RuntimeError()])
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert exc_info.group_contains(RuntimeError)

    def test_doesnt_contain_exception_type(self) -> None:
        exc_group = ExceptionGroup("", [ValueError()])
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert not exc_info.group_contains(RuntimeError)

    def test_contains_exception_match(self) -> None:
        exc_group = ExceptionGroup("", [RuntimeError("exception message")])
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert exc_info.group_contains(RuntimeError, match=r"^exception message$")

    def test_doesnt_contain_exception_match(self) -> None:
        exc_group = ExceptionGroup("", [RuntimeError("message that will not match")])
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert not exc_info.group_contains(RuntimeError, match=r"^exception message$")

    def test_contains_exception_type_unlimited_depth(self) -> None:
        exc_group = ExceptionGroup("", [ExceptionGroup("", [RuntimeError()])])
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert exc_info.group_contains(RuntimeError)

    def test_contains_exception_type_at_depth_1(self) -> None:
        exc_group = ExceptionGroup("", [RuntimeError()])
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert exc_info.group_contains(RuntimeError, depth=1)

    def test_doesnt_contain_exception_type_past_depth(self) -> None:
        exc_group = ExceptionGroup("", [ExceptionGroup("", [RuntimeError()])])
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert not exc_info.group_contains(RuntimeError, depth=1)

    def test_contains_exception_type_specific_depth(self) -> None:
        exc_group = ExceptionGroup("", [ExceptionGroup("", [RuntimeError()])])
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert exc_info.group_contains(RuntimeError, depth=2)

    def test_contains_exception_match_unlimited_depth(self) -> None:
        exc_group = ExceptionGroup(
            "", [ExceptionGroup("", [RuntimeError("exception message")])]
        )
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert exc_info.group_contains(RuntimeError, match=r"^exception message$")

    def test_contains_exception_match_at_depth_1(self) -> None:
        exc_group = ExceptionGroup("", [RuntimeError("exception message")])
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert exc_info.group_contains(
            RuntimeError, match=r"^exception message$", depth=1
        )

    def test_doesnt_contain_exception_match_past_depth(self) -> None:
        exc_group = ExceptionGroup(
            "", [ExceptionGroup("", [RuntimeError("exception message")])]
        )
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert not exc_info.group_contains(
            RuntimeError, match=r"^exception message$", depth=1
        )

    def test_contains_exception_match_specific_depth(self) -> None:
        exc_group = ExceptionGroup(
            "", [ExceptionGroup("", [RuntimeError("exception message")])]
        )
        with pytest.raises(ExceptionGroup) as exc_info:
            raise exc_group
        assert exc_info.group_contains(
            RuntimeError, match=r"^exception message$", depth=2
        )


class TestFormattedExcinfo:
    @pytest.fixture
    def importasmod(self, tmp_path: Path, _sys_snapshot):
        def importasmod(source):
            source = textwrap.dedent(source)
            modpath = tmp_path.joinpath("mod.py")
            tmp_path.joinpath("__init__.py").touch()
            modpath.write_text(source, encoding="utf-8")
            importlib.invalidate_caches()
            return import_path(modpath, root=tmp_path)

        return importasmod

    def test_repr_source(self):
        pr = FormattedExcinfo()
        source = _pytest._code.Source(
            """\
            def f(x):
                pass
            """
        ).strip()
        pr.flow_marker = "|"  # type: ignore[misc]
        lines = pr.get_source(source, 0)
        assert len(lines) == 2
        assert lines[0] == "|   def f(x):"
        assert lines[1] == "        pass"

    def test_repr_source_out_of_bounds(self):
        pr = FormattedExcinfo()
        source = _pytest._code.Source(
            """\
            def f(x):
                pass
            """
        ).strip()
        pr.flow_marker = "|"  # type: ignore[misc]

        lines = pr.get_source(source, 100)
        assert len(lines) == 1
        assert lines[0] == "|   ???"

        lines = pr.get_source(source, -100)
        assert len(lines) == 1
        assert lines[0] == "|   ???"

    def test_repr_source_excinfo(self) -> None:
        """Check if indentation is right."""
        try:

            def f():
                1 / 0

            f()

        except BaseException:
            excinfo = _pytest._code.ExceptionInfo.from_current()
        else:
            assert False, "did not raise"

        pr = FormattedExcinfo()
        source = pr._getentrysource(excinfo.traceback[-1])
        assert source is not None
        lines = pr.get_source(source, 1, excinfo)
        for line in lines:
            print(line)
        assert lines == [
            "    def f():",
            ">       1 / 0",
            "E       ZeroDivisionError: division by zero",
        ]

    def test_repr_source_not_existing(self):
        pr = FormattedExcinfo()
        co = compile("raise ValueError()", "", "exec")
        try:
            exec(co)
        except ValueError:
            excinfo = _pytest._code.ExceptionInfo.from_current()
        repr = pr.repr_excinfo(excinfo)
        assert repr.reprtraceback.reprentries[1].lines[0] == ">   ???"
        assert repr.chain[0][0].reprentries[1].lines[0] == ">   ???"

    def test_repr_many_line_source_not_existing(self):
        pr = FormattedExcinfo()
        co = compile(
            """
a = 1
raise ValueError()
""",
            "",
            "exec",
        )
        try:
            exec(co)
        except ValueError:
            excinfo = _pytest._code.ExceptionInfo.from_current()
        repr = pr.repr_excinfo(excinfo)
        assert repr.reprtraceback.reprentries[1].lines[0] == ">   ???"
        assert repr.chain[0][0].reprentries[1].lines[0] == ">   ???"

    def test_repr_source_failing_fullsource(self, monkeypatch) -> None:
        pr = FormattedExcinfo()

        try:
            1 / 0
        except ZeroDivisionError:
            excinfo = ExceptionInfo.from_current()

        with monkeypatch.context() as m:
            m.setattr(_pytest._code.Code, "fullsource", property(lambda self: None))
            repr = pr.repr_excinfo(excinfo)

        assert repr.reprtraceback.reprentries[0].lines[0] == ">   ???"
        assert repr.chain[0][0].reprentries[0].lines[0] == ">   ???"

    def test_repr_local(self) -> None:
        p = FormattedExcinfo(showlocals=True)
        loc = {"y": 5, "z": 7, "x": 3, "@x": 2, "__builtins__": {}}
        reprlocals = p.repr_locals(loc)
        assert reprlocals is not None
        assert reprlocals.lines
        assert reprlocals.lines[0] == "__builtins__ = <builtins>"
        assert reprlocals.lines[1] == "x          = 3"
        assert reprlocals.lines[2] == "y          = 5"
        assert reprlocals.lines[3] == "z          = 7"

    def test_repr_local_with_error(self) -> None:
        class ObjWithErrorInRepr:
            def __repr__(self):
                raise NotImplementedError

        p = FormattedExcinfo(showlocals=True, truncate_locals=False)
        loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}}
        reprlocals = p.repr_locals(loc)
        assert reprlocals is not None
        assert reprlocals.lines
        assert reprlocals.lines[0] == "__builtins__ = <builtins>"
        assert "[NotImplementedError() raised in repr()]" in reprlocals.lines[1]

    def test_repr_local_with_exception_in_class_property(self) -> None:
        class ExceptionWithBrokenClass(Exception):
            # Type ignored because it's bypassed intentionally.
            @property  # type: ignore
            def __class__(self):
                raise TypeError("boom!")

        class ObjWithErrorInRepr:
            def __repr__(self):
                raise ExceptionWithBrokenClass()

        p = FormattedExcinfo(showlocals=True, truncate_locals=False)
        loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}}
        reprlocals = p.repr_locals(loc)
        assert reprlocals is not None
        assert reprlocals.lines
        assert reprlocals.lines[0] == "__builtins__ = <builtins>"
        assert "[ExceptionWithBrokenClass() raised in repr()]" in reprlocals.lines[1]

    def test_repr_local_truncated(self) -> None:
        loc = {"l": [i for i in range(10)]}
        p = FormattedExcinfo(showlocals=True)
        truncated_reprlocals = p.repr_locals(loc)
        assert truncated_reprlocals is not None
        assert truncated_reprlocals.lines
        assert truncated_reprlocals.lines[0] == "l          = [0, 1, 2, 3, 4, 5, ...]"

        q = FormattedExcinfo(showlocals=True, truncate_locals=False)
        full_reprlocals = q.repr_locals(loc)
        assert full_reprlocals is not None
        assert full_reprlocals.lines
        assert full_reprlocals.lines[0] == "l          = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"

    def test_repr_tracebackentry_lines(self, importasmod) -> None:
        mod = importasmod(
            """
            def func1():
                raise ValueError("hello\\nworld")
        """
        )
        excinfo = pytest.raises(ValueError, mod.func1)
        excinfo.traceback = excinfo.traceback.filter(excinfo)
        p = FormattedExcinfo()
        reprtb = p.repr_traceback_entry(excinfo.traceback[-1])

        # test as intermittent entry
        lines = reprtb.lines
        assert lines[0] == "    def func1():"
        assert lines[1] == '>       raise ValueError("hello\\nworld")'

        # test as last entry
        p = FormattedExcinfo(showlocals=True)
        repr_entry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
        lines = repr_entry.lines
        assert lines[0] == "    def func1():"
        assert lines[1] == '>       raise ValueError("hello\\nworld")'
        assert lines[2] == "E       ValueError: hello"
        assert lines[3] == "E       world"
        assert not lines[4:]

        loc = repr_entry.reprfileloc
        assert loc is not None
        assert loc.path == mod.__file__
        assert loc.lineno == 3
        # assert loc.message == "ValueError: hello"

    def test_repr_tracebackentry_lines2(self, importasmod, tw_mock) -> None:
        mod = importasmod(
            """
            def func1(m, x, y, z):
                raise ValueError("hello\\nworld")
        """
        )
        excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120)
        excinfo.traceback = excinfo.traceback.filter(excinfo)
        entry = excinfo.traceback[-1]
        p = FormattedExcinfo(funcargs=True)
        reprfuncargs = p.repr_args(entry)
        assert reprfuncargs is not None
        assert reprfuncargs.args[0] == ("m", repr("m" * 90))
        assert reprfuncargs.args[1] == ("x", "5")
        assert reprfuncargs.args[2] == ("y", "13")
        assert reprfuncargs.args[3] == ("z", repr("z" * 120))

        p = FormattedExcinfo(funcargs=True)
        repr_entry = p.repr_traceback_entry(entry)
        assert repr_entry.reprfuncargs is not None
        assert repr_entry.reprfuncargs.args == reprfuncargs.args
        repr_entry.toterminal(tw_mock)
        assert tw_mock.lines[0] == "m = " + repr("m" * 90)
        assert tw_mock.lines[1] == "x = 5, y = 13"
        assert tw_mock.lines[2] == "z = " + repr("z" * 120)

    def test_repr_tracebackentry_lines_var_kw_args(self, importasmod, tw_mock) -> None:
        mod = importasmod(
            """
            def func1(x, *y, **z):
                raise ValueError("hello\\nworld")
        """
        )
        excinfo = pytest.raises(ValueError, mod.func1, "a", "b", c="d")
        excinfo.traceback = excinfo.traceback.filter(excinfo)
        entry = excinfo.traceback[-1]
        p = FormattedExcinfo(funcargs=True)
        reprfuncargs = p.repr_args(entry)
        assert reprfuncargs is not None
        assert reprfuncargs.args[0] == ("x", repr("a"))
        assert reprfuncargs.args[1] == ("y", repr(("b",)))
        assert reprfuncargs.args[2] == ("z", repr({"c": "d"}))

        p = FormattedExcinfo(funcargs=True)
        repr_entry = p.repr_traceback_entry(entry)
        assert repr_entry.reprfuncargs
        assert repr_entry.reprfuncargs.args == reprfuncargs.args
        repr_entry.toterminal(tw_mock)
        assert tw_mock.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}"

    def test_repr_tracebackentry_short(self, importasmod) -> None:
        mod = importasmod(
            """
            def func1():
                raise ValueError("hello")
            def entry():
                func1()
        """
        )
        excinfo = pytest.raises(ValueError, mod.entry)
        p = FormattedExcinfo(style="short")
        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
        lines = reprtb.lines
        basename = Path(mod.__file__).name
        assert lines[0] == "    func1()"
        assert reprtb.reprfileloc is not None
        assert basename in str(reprtb.reprfileloc.path)
        assert reprtb.reprfileloc.lineno == 5

        # test last entry
        p = FormattedExcinfo(style="short")
        reprtb = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
        lines = reprtb.lines
        assert lines[0] == '    raise ValueError("hello")'
        assert lines[1] == "E   ValueError: hello"
        assert reprtb.reprfileloc is not None
        assert basename in str(reprtb.reprfileloc.path)
        assert reprtb.reprfileloc.lineno == 3

    def test_repr_tracebackentry_no(self, importasmod):
        mod = importasmod(
            """
            def func1():
                raise ValueError("hello")
            def entry():
                func1()
        """
        )
        excinfo = pytest.raises(ValueError, mod.entry)
        p = FormattedExcinfo(style="no")
        p.repr_traceback_entry(excinfo.traceback[-2])

        p = FormattedExcinfo(style="no")
        reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
        lines = reprentry.lines
        assert lines[0] == "E   ValueError: hello"
        assert not lines[1:]

    def test_repr_traceback_tbfilter(self, importasmod):
        mod = importasmod(
            """
            def f(x):
                raise ValueError(x)
            def entry():
                f(0)
        """
        )
        excinfo = pytest.raises(ValueError, mod.entry)
        p = FormattedExcinfo(tbfilter=True)
        reprtb = p.repr_traceback(excinfo)
        assert len(reprtb.reprentries) == 2
        p = FormattedExcinfo(tbfilter=False)
        reprtb = p.repr_traceback(excinfo)
        assert len(reprtb.reprentries) == 3

    def test_traceback_short_no_source(
        self,
        importasmod,
        monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        mod = importasmod(
            """
            def func1():
                raise ValueError("hello")
            def entry():
                func1()
        """
        )
        excinfo = pytest.raises(ValueError, mod.entry)
        from _pytest._code.code import Code

        with monkeypatch.context() as mp:
            mp.setattr(Code, "path", "bogus")
            p = FormattedExcinfo(style="short")
            reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
            lines = reprtb.lines
            last_p = FormattedExcinfo(style="short")
            last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
            last_lines = last_reprtb.lines
        assert lines[0] == "    func1()"

        assert last_lines[0] == '    raise ValueError("hello")'
        assert last_lines[1] == "E   ValueError: hello"

    def test_repr_traceback_and_excinfo(self, importasmod) -> None:
        mod = importasmod(
            """
            def f(x):
                raise ValueError(x)
            def entry():
                f(0)
        """
        )
        excinfo = pytest.raises(ValueError, mod.entry)

        styles: tuple[_TracebackStyle, ...] = ("long", "short")
        for style in styles:
            p = FormattedExcinfo(style=style)
            reprtb = p.repr_traceback(excinfo)
            assert len(reprtb.reprentries) == 2
            assert reprtb.style == style
            assert not reprtb.extraline
            repr = p.repr_excinfo(excinfo)
            assert repr.reprtraceback
            assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries)

            assert repr.chain[0][0]
            assert len(repr.chain[0][0].reprentries) == len(reprtb.reprentries)
            assert repr.reprcrash is not None
            assert repr.reprcrash.path.endswith("mod.py")
            assert repr.reprcrash.message == "ValueError: 0"

    def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch) -> None:
        mod = importasmod(
            """
            def f(x):
                raise ValueError(x)
            def entry():
                f(0)
        """
        )
        excinfo = pytest.raises(ValueError, mod.entry)

        p = FormattedExcinfo(abspath=False)

        raised = 0

        orig_path_cwd = Path.cwd

        def raiseos():
            nonlocal raised
            upframe = sys._getframe().f_back
            assert upframe is not None
            if upframe.f_code.co_name == "_makepath":
                # Only raise with expected calls, but not via e.g. inspect for
                # py38-windows.
                raised += 1
                raise OSError(2, "custom_oserror")
            return orig_path_cwd()

        monkeypatch.setattr(Path, "cwd", raiseos)
        assert p._makepath(Path(__file__)) == __file__
        assert raised == 1
        repr_tb = p.repr_traceback(excinfo)

        matcher = LineMatcher(str(repr_tb).splitlines())
        matcher.fnmatch_lines(
            [
                "def entry():",
                ">       f(0)",
                "",
                f"{mod.__file__}:5: ",
                "_ _ *",
                "",
                "    def f(x):",
                ">       raise ValueError(x)",
                "E       ValueError: 0",
                "",
                f"{mod.__file__}:3: ValueError",
            ]
        )
        assert raised == 3

    def test_repr_excinfo_addouterr(self, importasmod, tw_mock):
        mod = importasmod(
            """
            def entry():
                raise ValueError()
        """
        )
        excinfo = pytest.raises(ValueError, mod.entry)
        repr = excinfo.getrepr()
        repr.addsection("title", "content")
        repr.toterminal(tw_mock)
        assert tw_mock.lines[-1] == "content"
        assert tw_mock.lines[-2] == ("-", "title")

    def test_repr_excinfo_reprcrash(self, importasmod) -> None:
        mod = importasmod(
            """
            def entry():
                raise ValueError()
        """
        )
        excinfo = pytest.raises(ValueError, mod.entry)
        repr = excinfo.getrepr()
        assert repr.reprcrash is not None
        assert repr.reprcrash.path.endswith("mod.py")
        assert repr.reprcrash.lineno == 3
        assert repr.reprcrash.message == "ValueError"
        assert str(repr.reprcrash).endswith("mod.py:3: ValueError")

    def test_repr_traceback_recursion(self, importasmod):
        mod = importasmod(
            """
            def rec2(x):
                return rec1(x+1)
            def rec1(x):
                return rec2(x-1)
            def entry():
                rec1(42)
        """
        )
        excinfo = pytest.raises(RuntimeError, mod.entry)

        for style in ("short", "long", "no"):
            p = FormattedExcinfo(style="short")
            reprtb = p.repr_traceback(excinfo)
            assert reprtb.extraline == "!!! Recursion detected (same locals & position)"
            assert str(reprtb)

    def test_reprexcinfo_getrepr(self, importasmod) -> None:
        mod = importasmod(
            """
            def f(x):
                raise ValueError(x)
            def entry():
                f(0)
        """
        )
        excinfo = pytest.raises(ValueError, mod.entry)

        styles: tuple[_TracebackStyle, ...] = ("short", "long", "no")
        for style in styles:
            for showlocals in (True, False):
                repr = excinfo.getrepr(style=style, showlocals=showlocals)
                assert repr.reprtraceback.style == style

                assert isinstance(repr, ExceptionChainRepr)
                for r in repr.chain:
                    assert r[0].style == style

    def test_reprexcinfo_unicode(self):
        from _pytest._code.code import TerminalRepr

        class MyRepr(TerminalRepr):
            def toterminal(self, tw: TerminalWriter) -> None:
                tw.line("я")

        x = str(MyRepr())
        assert x == "я"

    def test_toterminal_long(self, importasmod, tw_mock):
        mod = importasmod(
            """
            def g(x):
                raise ValueError(x)
            def f():
                g(3)
        """
        )
        excinfo = pytest.raises(ValueError, mod.f)
        excinfo.traceback = excinfo.traceback.filter(excinfo)
        repr = excinfo.getrepr()
        repr.toterminal(tw_mock)
        assert tw_mock.lines[0] == ""
        tw_mock.lines.pop(0)
        assert tw_mock.lines[0] == "    def f():"
        assert tw_mock.lines[1] == ">       g(3)"
        assert tw_mock.lines[2] == ""
        line = tw_mock.get_write_msg(3)
        assert line.endswith("mod.py")
        assert tw_mock.lines[4] == (":5: ")
        assert tw_mock.lines[5] == ("_ ", None)
        assert tw_mock.lines[6] == ""
        assert tw_mock.lines[7] == "    def g(x):"
        assert tw_mock.lines[8] == ">       raise ValueError(x)"
        assert tw_mock.lines[9] == "E       ValueError: 3"
        assert tw_mock.lines[10] == ""
        line = tw_mock.get_write_msg(11)
        assert line.endswith("mod.py")
        assert tw_mock.lines[12] == ":3: ValueError"

    def test_toterminal_long_missing_source(
        self, importasmod, tmp_path: Path, tw_mock
    ) -> None:
        mod = importasmod(
            """
            def g(x):
                raise ValueError(x)
            def f():
                g(3)
        """
        )
        excinfo = pytest.raises(ValueError, mod.f)
        tmp_path.joinpath("mod.py").unlink()
        excinfo.traceback = excinfo.traceback.filter(excinfo)
        repr = excinfo.getrepr()
        repr.toterminal(tw_mock)
        assert tw_mock.lines[0] == ""
        tw_mock.lines.pop(0)
        assert tw_mock.lines[0] == ">   ???"
        assert tw_mock.lines[1] == ""
        line = tw_mock.get_write_msg(2)
        assert line.endswith("mod.py")
        assert tw_mock.lines[3] == ":5: "
        assert tw_mock.lines[4] == ("_ ", None)
        assert tw_mock.lines[5] == ""
        assert tw_mock.lines[6] == ">   ???"
        assert tw_mock.lines[7] == "E   ValueError: 3"
        assert tw_mock.lines[8] == ""
        line = tw_mock.get_write_msg(9)
        assert line.endswith("mod.py")
        assert tw_mock.lines[10] == ":3: ValueError"

    def test_toterminal_long_incomplete_source(
        self, importasmod, tmp_path: Path, tw_mock
    ) -> None:
        mod = importasmod(
            """
            def g(x):
                raise ValueError(x)
            def f():
                g(3)
        """
        )
        excinfo = pytest.raises(ValueError, mod.f)
        tmp_path.joinpath("mod.py").write_text("asdf", encoding="utf-8")
        excinfo.traceback = excinfo.traceback.filter(excinfo)
        repr = excinfo.getrepr()
        repr.toterminal(tw_mock)
        assert tw_mock.lines[0] == ""
        tw_mock.lines.pop(0)
        assert tw_mock.lines[0] == ">   ???"
        assert tw_mock.lines[1] == ""
        line = tw_mock.get_write_msg(2)
        assert line.endswith("mod.py")
        assert tw_mock.lines[3] == ":5: "
        assert tw_mock.lines[4] == ("_ ", None)
        assert tw_mock.lines[5] == ""
        assert tw_mock.lines[6] == ">   ???"
        assert tw_mock.lines[7] == "E   ValueError: 3"
        assert tw_mock.lines[8] == ""
        line = tw_mock.get_write_msg(9)
        assert line.endswith("mod.py")
        assert tw_mock.lines[10] == ":3: ValueError"

    def test_toterminal_long_filenames(
        self, importasmod, tw_mock, monkeypatch: MonkeyPatch
    ) -> None:
        mod = importasmod(
            """
            def f():
                raise ValueError()
        """
        )
        excinfo = pytest.raises(ValueError, mod.f)
        path = Path(mod.__file__)
        monkeypatch.chdir(path.parent)
        repr = excinfo.getrepr(abspath=False)
        repr.toterminal(tw_mock)
        x = bestrelpath(Path.cwd(), path)
        if len(x) < len(str(path)):
            msg = tw_mock.get_write_msg(-2)
            assert msg == "mod.py"
            assert tw_mock.lines[-1] == ":3: ValueError"

        repr = excinfo.getrepr(abspath=True)
        repr.toterminal(tw_mock)
        msg = tw_mock.get_write_msg(-2)
        assert msg == str(path)
        line = tw_mock.lines[-1]
        assert line == ":3: ValueError"

    @pytest.mark.parametrize(
        "reproptions",
        [
            pytest.param(
                {
                    "style": style,
                    "showlocals": showlocals,
                    "funcargs": funcargs,
                    "tbfilter": tbfilter,
                },
                id=f"style={style},showlocals={showlocals},funcargs={funcargs},tbfilter={tbfilter}",
            )
            for style in ["long", "short", "line", "no", "native", "value", "auto"]
            for showlocals in (True, False)
            for tbfilter in (True, False)
            for funcargs in (True, False)
        ],
    )
    def test_format_excinfo(self, reproptions: dict[str, Any]) -> None:
        def bar():
            assert False, "some error"

        def foo():
            bar()

        # using inline functions as opposed to importasmod so we get source code lines
        # in the tracebacks (otherwise getinspect doesn't find the source code).
        with pytest.raises(AssertionError) as excinfo:
            foo()
        file = io.StringIO()
        tw = TerminalWriter(file=file)
        repr = excinfo.getrepr(**reproptions)
        repr.toterminal(tw)
        assert file.getvalue()

    def test_traceback_repr_style(self, importasmod, tw_mock):
        mod = importasmod(
            """
            def f():
                g()
            def g():
                h()
            def h():
                i()
            def i():
                raise ValueError()
        """
        )
        excinfo = pytest.raises(ValueError, mod.f)
        excinfo.traceback = excinfo.traceback.filter(excinfo)
        excinfo.traceback = _pytest._code.Traceback(
            entry if i not in (1, 2) else entry.with_repr_style("short")
            for i, entry in enumerate(excinfo.traceback)
        )
        r = excinfo.getrepr(style="long")
        r.toterminal(tw_mock)
        for line in tw_mock.lines:
            print(line)
        assert tw_mock.lines[0] == ""
        assert tw_mock.lines[1] == "    def f():"
        assert tw_mock.lines[2] == ">       g()"
        assert tw_mock.lines[3] == ""
        msg = tw_mock.get_write_msg(4)
        assert msg.endswith("mod.py")
        assert tw_mock.lines[5] == ":3: "
        assert tw_mock.lines[6] == ("_ ", None)
        tw_mock.get_write_msg(7)
        assert tw_mock.lines[8].endswith("in g")
        assert tw_mock.lines[9] == "    h()"
        tw_mock.get_write_msg(10)
        assert tw_mock.lines[11].endswith("in h")
        assert tw_mock.lines[12] == "    i()"
        assert tw_mock.lines[13] == ("_ ", None)
        assert tw_mock.lines[14] == ""
        assert tw_mock.lines[15] == "    def i():"
        assert tw_mock.lines[16] == ">       raise ValueError()"
        assert tw_mock.lines[17] == "E       ValueError"
        assert tw_mock.lines[18] == ""
        msg = tw_mock.get_write_msg(19)
        msg.endswith("mod.py")
        assert tw_mock.lines[20] == ":9: ValueError"

    def test_exc_chain_repr(self, importasmod, tw_mock):
        mod = importasmod(
            """
            class Err(Exception):
                pass
            def f():
                try:
                    g()
                except Exception as e:
                    raise Err() from e
                finally:
                    h()
            def g():
                raise ValueError()

            def h():
                raise AttributeError()
        """
        )
        excinfo = pytest.raises(AttributeError, mod.f)
        r = excinfo.getrepr(style="long")
        r.toterminal(tw_mock)
        for line in tw_mock.lines:
            print(line)
        assert tw_mock.lines[0] == ""
        assert tw_mock.lines[1] == "    def f():"
        assert tw_mock.lines[2] == "        try:"
        assert tw_mock.lines[3] == ">           g()"
        assert tw_mock.lines[4] == ""
        line = tw_mock.get_write_msg(5)
        assert line.endswith("mod.py")
        assert tw_mock.lines[6] == ":6: "
        assert tw_mock.lines[7] == ("_ ", None)
        assert tw_mock.lines[8] == ""
        assert tw_mock.lines[9] == "    def g():"
        assert tw_mock.lines[10] == ">       raise ValueError()"
        assert tw_mock.lines[11] == "E       ValueError"
        assert tw_mock.lines[12] == ""
        line = tw_mock.get_write_msg(13)
        assert line.endswith("mod.py")
        assert tw_mock.lines[14] == ":12: ValueError"
        assert tw_mock.lines[15] == ""
        assert (
            tw_mock.lines[16]
            == "The above exception was the direct cause of the following exception:"
        )
        assert tw_mock.lines[17] == ""
        assert tw_mock.lines[18] == "    def f():"
        assert tw_mock.lines[19] == "        try:"
        assert tw_mock.lines[20] == "            g()"
        assert tw_mock.lines[21] == "        except Exception as e:"
        assert tw_mock.lines[22] == ">           raise Err() from e"
        assert tw_mock.lines[23] == "E           test_exc_chain_repr0.mod.Err"
        assert tw_mock.lines[24] == ""
        line = tw_mock.get_write_msg(25)
        assert line.endswith("mod.py")
        assert tw_mock.lines[26] == ":8: Err"
        assert tw_mock.lines[27] == ""
        assert (
            tw_mock.lines[28]
            == "During handling of the above exception, another exception occurred:"
        )
        assert tw_mock.lines[29] == ""
        assert tw_mock.lines[30] == "    def f():"
        assert tw_mock.lines[31] == "        try:"
        assert tw_mock.lines[32] == "            g()"
        assert tw_mock.lines[33] == "        except Exception as e:"
        assert tw_mock.lines[34] == "            raise Err() from e"
        assert tw_mock.lines[35] == "        finally:"
        assert tw_mock.lines[36] == ">           h()"
        assert tw_mock.lines[37] == ""
        line = tw_mock.get_write_msg(38)
        assert line.endswith("mod.py")
        assert tw_mock.lines[39] == ":10: "
        assert tw_mock.lines[40] == ("_ ", None)
        assert tw_mock.lines[41] == ""
        assert tw_mock.lines[42] == "    def h():"
        assert tw_mock.lines[43] == ">       raise AttributeError()"
        assert tw_mock.lines[44] == "E       AttributeError"
        assert tw_mock.lines[45] == ""
        line = tw_mock.get_write_msg(46)
        assert line.endswith("mod.py")
        assert tw_mock.lines[47] == ":15: AttributeError"

    @pytest.mark.parametrize("mode", ["from_none", "explicit_suppress"])
    def test_exc_repr_chain_suppression(self, importasmod, mode, tw_mock):
        """Check that exc repr does not show chained exceptions in Python 3.
        - When the exception is raised with "from None"
        - Explicitly suppressed with "chain=False" to ExceptionInfo.getrepr().
        """
        raise_suffix = " from None" if mode == "from_none" else ""
        mod = importasmod(
            f"""
            def f():
                try:
                    g()
                except Exception:
                    raise AttributeError(){raise_suffix}
            def g():
                raise ValueError()
        """
        )
        excinfo = pytest.raises(AttributeError, mod.f)
        r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress")
        r.toterminal(tw_mock)
        for line in tw_mock.lines:
            print(line)
        assert tw_mock.lines[0] == ""
        assert tw_mock.lines[1] == "    def f():"
        assert tw_mock.lines[2] == "        try:"
        assert tw_mock.lines[3] == "            g()"
        assert tw_mock.lines[4] == "        except Exception:"
        assert tw_mock.lines[5] == f">           raise AttributeError(){raise_suffix}"
        assert tw_mock.lines[6] == "E           AttributeError"
        assert tw_mock.lines[7] == ""
        line = tw_mock.get_write_msg(8)
        assert line.endswith("mod.py")
        assert tw_mock.lines[9] == ":6: AttributeError"
        assert len(tw_mock.lines) == 10

    @pytest.mark.parametrize(
        "reason, description",
        [
            pytest.param(
                "cause",
                "The above exception was the direct cause of the following exception:",
                id="cause",
            ),
            pytest.param(
                "context",
                "During handling of the above exception, another exception occurred:",
                id="context",
            ),
        ],
    )
    def test_exc_chain_repr_without_traceback(self, importasmod, reason, description):
        """
        Handle representation of exception chains where one of the exceptions doesn't have a
        real traceback, such as those raised in a subprocess submitted by the multiprocessing
        module (#1984).
        """
        exc_handling_code = " from e" if reason == "cause" else ""
        mod = importasmod(
            f"""
            def f():
                try:
                    g()
                except Exception as e:
                    raise RuntimeError('runtime problem'){exc_handling_code}
            def g():
                raise ValueError('invalid value')
        """
        )

        with pytest.raises(RuntimeError) as excinfo:
            mod.f()

        # emulate the issue described in #1984
        attr = "__%s__" % reason
        getattr(excinfo.value, attr).__traceback__ = None

        r = excinfo.getrepr()
        file = io.StringIO()
        tw = TerminalWriter(file=file)
        tw.hasmarkup = False
        r.toterminal(tw)

        matcher = LineMatcher(file.getvalue().splitlines())
        matcher.fnmatch_lines(
            [
                "ValueError: invalid value",
                description,
                "* except Exception as e:",
                "> * raise RuntimeError('runtime problem')" + exc_handling_code,
                "E *RuntimeError: runtime problem",
            ]
        )

    def test_exc_chain_repr_cycle(self, importasmod, tw_mock):
        mod = importasmod(
            """
            class Err(Exception):
                pass
            def fail():
                return 0 / 0
            def reraise():
                try:
                    fail()
                except ZeroDivisionError as e:
                    raise Err() from e
            def unreraise():
                try:
                    reraise()
                except Err as e:
                    raise e.__cause__
        """
        )
        excinfo = pytest.raises(ZeroDivisionError, mod.unreraise)
        r = excinfo.getrepr(style="short")
        r.toterminal(tw_mock)
        out = "\n".join(line for line in tw_mock.lines if isinstance(line, str))
        expected_out = textwrap.dedent(
            """\
            :13: in unreraise
                reraise()
            :10: in reraise
                raise Err() from e
            E   test_exc_chain_repr_cycle0.mod.Err

            During handling of the above exception, another exception occurred:
            :15: in unreraise
                raise e.__cause__
            :8: in reraise
                fail()
            :5: in fail
                return 0 / 0
            E   ZeroDivisionError: division by zero"""
        )
        assert out == expected_out

    def test_exec_type_error_filter(self, importasmod):
        """See #7742"""
        mod = importasmod(
            """\
            def f():
                exec("a = 1", {}, [])
            """
        )
        with pytest.raises(TypeError) as excinfo:
            mod.f()
        # previously crashed with `AttributeError: list has no attribute get`
        excinfo.traceback.filter(excinfo)


@pytest.mark.parametrize("style", ["short", "long"])
@pytest.mark.parametrize("encoding", [None, "utf8", "utf16"])
def test_repr_traceback_with_unicode(style, encoding):
    if encoding is None:
        msg: str | bytes = "☹"
    else:
        msg = "☹".encode(encoding)
    try:
        raise RuntimeError(msg)
    except RuntimeError:
        e_info = ExceptionInfo.from_current()
    formatter = FormattedExcinfo(style=style)
    repr_traceback = formatter.repr_traceback(e_info)
    assert repr_traceback is not None


def test_cwd_deleted(pytester: Pytester) -> None:
    pytester.makepyfile(
        """
        import os

        def test(tmp_path):
            os.chdir(tmp_path)
            tmp_path.unlink()
            assert False
    """
    )
    result = pytester.runpytest()
    result.stdout.fnmatch_lines(["* 1 failed in *"])
    result.stdout.no_fnmatch_line("*INTERNALERROR*")
    result.stderr.no_fnmatch_line("*INTERNALERROR*")


def test_regression_nagative_line_index(pytester: Pytester) -> None:
    """
    With Python 3.10 alphas, there was an INTERNALERROR reported in
    https://github.com/pytest-dev/pytest/pull/8227
    This test ensures it does not regress.
    """
    pytester.makepyfile(
        """
        import ast
        import pytest


        def test_literal_eval():
            with pytest.raises(ValueError, match="^$"):
                ast.literal_eval("pytest")
    """
    )
    result = pytester.runpytest()
    result.stdout.fnmatch_lines(["* 1 failed in *"])
    result.stdout.no_fnmatch_line("*INTERNALERROR*")
    result.stderr.no_fnmatch_line("*INTERNALERROR*")


@pytest.mark.usefixtures("limited_recursion_depth")
def test_exception_repr_extraction_error_on_recursion():
    """
    Ensure we can properly detect a recursion error even
    if some locals raise error on comparison (#2459).
    """

    class numpy_like:
        def __eq__(self, other):
            if type(other) is numpy_like:
                raise ValueError(
                    "The truth value of an array "
                    "with more than one element is ambiguous."
                )

    def a(x):
        return b(numpy_like())

    def b(x):
        return a(numpy_like())

    with pytest.raises(RuntimeError) as excinfo:
        a(numpy_like())

    matcher = LineMatcher(str(excinfo.getrepr()).splitlines())
    matcher.fnmatch_lines(
        [
            "!!! Recursion error detected, but an error occurred locating the origin of recursion.",
            "*The following exception happened*",
            "*ValueError: The truth value of an array*",
        ]
    )


@pytest.mark.usefixtures("limited_recursion_depth")
def test_no_recursion_index_on_recursion_error():
    """
    Ensure that we don't break in case we can't find the recursion index
    during a recursion error (#2486).
    """

    class RecursionDepthError:
        def __getattr__(self, attr):
            return getattr(self, "_" + attr)

    with pytest.raises(RuntimeError) as excinfo:
        RecursionDepthError().trigger
    assert "maximum recursion" in str(excinfo.getrepr())


def _exceptiongroup_common(
    pytester: Pytester,
    outer_chain: str,
    inner_chain: str,
    native: bool,
) -> None:
    pre_raise = "exceptiongroup." if not native else ""
    pre_catch = pre_raise if sys.version_info < (3, 11) else ""
    filestr = f"""
    {"import exceptiongroup" if not native else ""}
    import pytest

    def f(): raise ValueError("From f()")
    def g(): raise BaseException("From g()")

    def inner(inner_chain):
        excs = []
        for callback in [f, g]:
            try:
                callback()
            except BaseException as err:
                excs.append(err)
        if excs:
            if inner_chain == "none":
                raise {pre_raise}BaseExceptionGroup("Oops", excs)
            try:
                raise SyntaxError()
            except SyntaxError as e:
                if inner_chain == "from":
                    raise {pre_raise}BaseExceptionGroup("Oops", excs) from e
                else:
                    raise {pre_raise}BaseExceptionGroup("Oops", excs)

    def outer(outer_chain, inner_chain):
        try:
            inner(inner_chain)
        except {pre_catch}BaseExceptionGroup as e:
            if outer_chain == "none":
                raise
            if outer_chain == "from":
                raise IndexError() from e
            else:
                raise IndexError()


    def test():
        outer("{outer_chain}", "{inner_chain}")
    """
    pytester.makepyfile(test_excgroup=filestr)
    result = pytester.runpytest()
    match_lines = []
    if inner_chain in ("another", "from"):
        match_lines.append(r"SyntaxError: <no detail available>")

    match_lines += [
        r"  + Exception Group Traceback (most recent call last):",
        rf"  \| {pre_catch}BaseExceptionGroup: Oops \(2 sub-exceptions\)",
        r"    \| ValueError: From f\(\)",
        r"    \| BaseException: From g\(\)",
        r"=* short test summary info =*",
    ]
    if outer_chain in ("another", "from"):
        match_lines.append(r"FAILED test_excgroup.py::test - IndexError")
    else:
        match_lines.append(
            rf"FAILED test_excgroup.py::test - {pre_catch}BaseExceptionGroup: Oops \(2.*"
        )
    result.stdout.re_match_lines(match_lines)


@pytest.mark.skipif(
    sys.version_info < (3, 11), reason="Native ExceptionGroup not implemented"
)
@pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
def test_native_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
    _exceptiongroup_common(pytester, outer_chain, inner_chain, native=True)


@pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
    # with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it
    pytest.importorskip("exceptiongroup")
    _exceptiongroup_common(pytester, outer_chain, inner_chain, native=False)


@pytest.mark.parametrize("tbstyle", ("long", "short", "auto", "line", "native"))
def test_all_entries_hidden(pytester: Pytester, tbstyle: str) -> None:
    """Regression test for #10903."""
    pytester.makepyfile(
        """
        def test():
            __tracebackhide__ = True
            1 / 0
    """
    )
    result = pytester.runpytest("--tb", tbstyle)
    assert result.ret == 1
    if tbstyle != "line":
        result.stdout.fnmatch_lines(["*ZeroDivisionError: division by zero"])
    if tbstyle not in ("line", "native"):
        result.stdout.fnmatch_lines(["All traceback entries are hidden.*"])


def test_hidden_entries_of_chained_exceptions_are_not_shown(pytester: Pytester) -> None:
    """Hidden entries of chained exceptions are not shown (#1904)."""
    p = pytester.makepyfile(
        """
        def g1():
            __tracebackhide__ = True
            str.does_not_exist

        def f3():
            __tracebackhide__ = True
            1 / 0

        def f2():
            try:
                f3()
            except Exception:
                g1()

        def f1():
            __tracebackhide__ = True
            f2()

        def test():
            f1()
        """
    )
    result = pytester.runpytest(str(p), "--tb=short")
    assert result.ret == 1
    result.stdout.fnmatch_lines(
        [
            "*.py:11: in f2",
            "    f3()",
            "E   ZeroDivisionError: division by zero",
            "",
            "During handling of the above exception, another exception occurred:",
            "*.py:20: in test",
            "    f1()",
            "*.py:13: in f2",
            "    g1()",
            "E   AttributeError:*'does_not_exist'",
        ],
        consecutive=True,
    )


def add_note(err: BaseException, msg: str) -> None:
    """Adds a note to an exception inplace."""
    if sys.version_info < (3, 11):
        err.__notes__ = getattr(err, "__notes__", []) + [msg]  # type: ignore[attr-defined]
    else:
        err.add_note(msg)


@pytest.mark.parametrize(
    "error,notes,match",
    [
        (Exception("test"), [], "test"),
        (AssertionError("foo"), ["bar"], "bar"),
        (AssertionError("foo"), ["bar", "baz"], "bar"),
        (AssertionError("foo"), ["bar", "baz"], "baz"),
        (ValueError("foo"), ["bar", "baz"], re.compile(r"bar\nbaz", re.MULTILINE)),
        (ValueError("foo"), ["bar", "baz"], re.compile(r"BAZ", re.IGNORECASE)),
    ],
)
def test_check_error_notes_success(
    error: Exception, notes: list[str], match: str
) -> None:
    for note in notes:
        add_note(error, note)

    with pytest.raises(Exception, match=match):
        raise error


@pytest.mark.parametrize(
    "error, notes, match",
    [
        (Exception("test"), [], "foo"),
        (AssertionError("foo"), ["bar"], "baz"),
        (AssertionError("foo"), ["bar"], "foo\nbaz"),
    ],
)
def test_check_error_notes_failure(
    error: Exception, notes: list[str], match: str
) -> None:
    for note in notes:
        add_note(error, note)

    with pytest.raises(AssertionError):
        with pytest.raises(type(error), match=match):
            raise error
