Repository: pytest-dev/pytest
File: src/_pytest/unraisableexception.py (Python)

Code before:
    finally:
        sys.unraisablehook = prev_hook


def unraisable_hook(unraisable: sys.UnraisableHookArgs) -> None:
    try:
        err_msg = (
            "Exception ignored in" if unraisable.err_msg is None else unraisable.err_msg
        )
        summary = f"{err_msg}: {unraisable.object!r}"
        traceback_message = "\n\n" + "".join(
            traceback.format_exception(
                unraisable.exc_type,
                unraisable.exc_value,
                unraisable.exc_traceback,
            )
        )
        tracemalloc_tb = tracemalloc_message(unraisable.object)
        msg = summary + traceback_message + tracemalloc_tb
        cause_msg = summary + tracemalloc_tb

        _unraisable_exceptions.append(
            UnraisableMeta(
                # we need to compute these strings here as they might change after
                # the unraisablehook finishes and before the unraisable object is
                # collected by a hook
                msg=msg,
                cause_msg=cause_msg,
                exc_value=unraisable.exc_value,
            )
        )
    except BaseException as e:
        _unraisable_exceptions.append(e)


def pytest_configure(config: Config) -> None:
    prev_hook = sys.unraisablehook
    config.add_cleanup(functools.partial(_cleanup, prev_hook))
    sys.unraisablehook = unraisable_hook


@pytest.hookimpl(trylast=True)
def pytest_runtest_setup() -> None:
    collect_unraisable()


@pytest.hookimpl(trylast=True)
def pytest_runtest_call() -> None:
    collect_unraisable()



Review comment (bug):
If I understand correctly according to the tests, the thing that might raise in this try block is the `format_exception`, is this right or is there somthing else?

Can you reduce the scope of the `try` to the minimal parts that can raise? This makes it more clear what's going on, and also avoids "over catching" in case there is some unrelated bug.

Code after:
        if errors:
            raise ExceptionGroup("multiple unraisable exception warnings", errors)
    finally:
        del errors, meta, hook_error


def cleanup(
    *, config: Config, prev_hook: Callable[[sys.UnraisableHookArgs], object]
) -> None:
    try:
        try:
            gc_collect_harder()
            collect_unraisable(config)
        finally:
            sys.unraisablehook = prev_hook
    finally:
        del config.stash[unraisable_exceptions]


def unraisable_hook(
    unraisable: sys.UnraisableHookArgs,
    /,
    *,
    append: Callable[[UnraisableMeta | BaseException], object],
) -> None:
    try:
        err_msg = (
            "Exception ignored in" if unraisable.err_msg is None else unraisable.err_msg
        )
        summary = f"{err_msg}: {unraisable.object!r}"
        traceback_message = "\n\n" + "".join(
            traceback.format_exception(
                unraisable.exc_type,
                unraisable.exc_value,
                unraisable.exc_traceback,
            )
        )
        tracemalloc_tb = "\n" + tracemalloc_message(unraisable.object)
        msg = summary + traceback_message + tracemalloc_tb
        cause_msg = summary + tracemalloc_tb

        append(
            UnraisableMeta(
                # we need to compute these strings here as they might change after
                # the unraisablehook finishes and before the unraisable object is
                # collected by a hook
                msg=msg,
                cause_msg=cause_msg,
                exc_value=unraisable.exc_value,
            )
        )