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

Code before:
            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

        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:
        append(e)
        # raising this will cause the exception to be logged twice, once in our
        # collect_unraisable and once by the unraisablehook calling machinery
        # which is fine - this should never happen anyway and if it does
        # it should probably be reported as a pytest bug.
        raise


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


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


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


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


Review comment (bug):
Great comment.

```suggestion
        # Raising this will cause the exception to be logged twice, once in our
        # collect_unraisable and once by the unraisablehook calling machinery
        # which is fine - this should never happen anyway and if it does
        # it should probably be reported as a pytest bug.
```

Code after:
    *,
    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,
            )
        )
    except BaseException as e:
        append(e)
        # Raising this will cause the exception to be logged twice, once in our
        # collect_unraisable and once by the unraisablehook calling machinery
        # which is fine - this should never happen anyway and if it does
        # it should probably be reported as a pytest bug.
        raise


def pytest_configure(config: Config) -> None:
    prev_hook = sys.unraisablehook
    deque: collections.deque[UnraisableMeta | BaseException] = collections.deque()
    config.stash[unraisable_exceptions] = deque
    config.add_cleanup(functools.partial(cleanup, config=config, prev_hook=prev_hook))
    sys.unraisablehook = functools.partial(unraisable_hook, append=deque.append)


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