Kludex / starlette-testclient

A backport of Starlette's TestClient using requests! ⏪️
BSD 3-Clause "New" or "Revised" License
12 stars 4 forks source link

Unclosed MemoryObjectReceiveStream #10

Open Enimalojd opened 2 hours ago

Enimalojd commented 2 hours ago

An error occurred when running the tests. I studied the problems in the main Starlette package and noticed that you solved this problem there. But for some reason this solution does not help in Starlette-testclient.

――――――――――――――――――――――――――――――――――――――――― test_exception_in_middleware[asyncio] ――――――――――――――――――――――――――――――――――――――――――

cls = <class '_pytest.runner.CallInfo'>, func = <function call_and_report.<locals>.<lambda> at 0x7f75a624f880>
when = 'call', reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: Callable[[], TResult],
        when: Literal["collect", "setup", "call", "teardown"],
        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
    ) -> CallInfo[TResult]:
        """Call func, wrapping the result in a CallInfo.

        :param func:
            The function to call. Called without arguments.
        :type func: Callable[[], _pytest.runner.TResult]
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: TResult | None = func()

.venv/lib64/python3/site-packages/_pytest/runner.py:341: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib64/python3/site-packages/_pytest/runner.py:242: in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
.venv/lib64/python3/site-packages/pluggy/_hooks.py:513: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
.venv/lib64/python3/site-packages/pluggy/_manager.py:120: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
.venv/lib64/python3/site-packages/_pytest/threadexception.py:92: in pytest_runtest_call
    yield from thread_exception_runtest_hook()
.venv/lib64/python3/site-packages/_pytest/threadexception.py:68: in thread_exception_runtest_hook
    yield
.venv/lib64/python3/site-packages/_pytest/unraisableexception.py:95: in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def unraisable_exception_runtest_hook() -> Generator[None]:
        with catch_unraisable_exception() as cm:
            try:
                yield
            finally:
                if cm.unraisable:
                    if cm.unraisable.err_msg is not None:
                        err_msg = cm.unraisable.err_msg
                    else:
                        err_msg = "Exception ignored in"
                    msg = f"{err_msg}: {cm.unraisable.object!r}\n\n"
                    msg += "".join(
                        traceback.format_exception(
                            cm.unraisable.exc_type,
                            cm.unraisable.exc_value,
                            cm.unraisable.exc_traceback,
                        )
                    )
>                   warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
E                   pytest.PytestUnraisableExceptionWarning: Exception ignored in: <function MemoryObjectReceiveStream.__del__ at 0x7f75a4b71260>
E                   
E                   Traceback (most recent call last):
E                     File "/home/martynenko/sources/starlette-testclient/.venv/lib64/python3/site-packages/anyio/streams/memory.py", line 183, in __del__
E                       warnings.warn(
E                   ResourceWarning: Unclosed <MemoryObjectReceiveStream at 7f75a4086240>

.venv/lib64/python3/site-packages/_pytest/unraisableexception.py:85: PytestUnraisableExceptionWarning

 tests/test_testclient.py ⨯✓✓✓✓✓✓✓                                                                          69% ██████▉   

――――――――――――――――――――――――――――――――――――――――――― test_exception_in_middleware[trio] ―――――――――――――――――――――――――――――――――――――――――――

cls = <class '_pytest.runner.CallInfo'>, func = <function call_and_report.<locals>.<lambda> at 0x7f75a36a37e0>
when = 'call', reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: Callable[[], TResult],
        when: Literal["collect", "setup", "call", "teardown"],
        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
    ) -> CallInfo[TResult]:
        """Call func, wrapping the result in a CallInfo.

        :param func:
            The function to call. Called without arguments.
        :type func: Callable[[], _pytest.runner.TResult]
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: TResult | None = func()

.venv/lib64/python3/site-packages/_pytest/runner.py:341: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib64/python3/site-packages/_pytest/runner.py:242: in <lambda>
    lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
.venv/lib64/python3/site-packages/pluggy/_hooks.py:513: in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
.venv/lib64/python3/site-packages/pluggy/_manager.py:120: in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
.venv/lib64/python3/site-packages/_pytest/threadexception.py:92: in pytest_runtest_call
    yield from thread_exception_runtest_hook()
.venv/lib64/python3/site-packages/_pytest/threadexception.py:68: in thread_exception_runtest_hook
    yield
.venv/lib64/python3/site-packages/_pytest/unraisableexception.py:95: in pytest_runtest_call
    yield from unraisable_exception_runtest_hook()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def unraisable_exception_runtest_hook() -> Generator[None]:
        with catch_unraisable_exception() as cm:
            try:
                yield
            finally:
                if cm.unraisable:
                    if cm.unraisable.err_msg is not None:
                        err_msg = cm.unraisable.err_msg
                    else:
                        err_msg = "Exception ignored in"
                    msg = f"{err_msg}: {cm.unraisable.object!r}\n\n"
                    msg += "".join(
                        traceback.format_exception(
                            cm.unraisable.exc_type,
                            cm.unraisable.exc_value,
                            cm.unraisable.exc_traceback,
                        )
                    )
>                   warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
E                   pytest.PytestUnraisableExceptionWarning: Exception ignored in: <function MemoryObjectReceiveStream.__del__ at 0x7f75a4b71260>
E                   
E                   Traceback (most recent call last):
E                     File "/home/martynenko/sources/starlette-testclient/.venv/lib64/python3/site-packages/anyio/streams/memory.py", line 183, in __del__
E                       warnings.warn(
E                   ResourceWarning: Unclosed <MemoryObjectReceiveStream at 7f75a1e73380>

.venv/lib64/python3/site-packages/_pytest/unraisableexception.py:85: PytestUnraisableExceptionWarning

 tests/test_testclient.py ⨯✓✓✓✓                                                                            100% ██████████
================================================ short test summary info =================================================
FAILED tests/test_testclient.py::test_exception_in_middleware[asyncio] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <function MemoryObjectReceiveStream.__del__ at 0x7f75a...
FAILED tests/test_testclient.py::test_exception_in_middleware[trio] - pytest.PytestUnraisableExceptionWarning: Exception ignored in: <function MemoryObjectReceiveStream.__del__ at 0x7f75a...

Results (0.67s):
      14 passed
       2 failed
         - tests/test_testclient.py:154 test_exception_in_middleware[asyncio]
         - tests/test_testclient.py:154 test_exception_in_middleware[trio]
Enimalojd commented 2 hours ago

This output with a fix already applied:

    async def wait_shutdown(self) -> None:
        async def receive() -> typing.Any:
            message = await self.stream_send.receive()
            if message is None:
                self.task.result()
            return message

        async with self.stream_send, self.stream_receive:
            await self.stream_receive.send({"type": "lifespan.shutdown"})
            message = await receive()
            assert message["type"] in (
                "lifespan.shutdown.complete",
                "lifespan.shutdown.failed",
            )
            if message["type"] == "lifespan.shutdown.failed":
                await receive()