Repository: aio-libs-abandoned/aioredis-py

Issue:
RuntimeWarning: coroutine 'Connection.disconnect' was never awaited
When running tests that need mocking an `aioredis` connection, the following Runtime Warning gets triggered each time, and is mumbled together with some random code, which makes me think it looks like a fairly low-level bug:

```
tests/foo/test_foo_bar.py ...                                    [100%]
/usr/local/lib/python3.9/site-packages/pkg_resources/_vendor/packaging/version.py:490:
RuntimeWarning: coroutine 'Connection.disconnect' was never awaited
  reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
```

I'm not sure where that comes from but not from my own code, as I never mention a `disconnect` method anywhere.

```python
import pytest
import aioredis

from foo import api


@pytest.fixture
async def aioredis_conn():
    conn = await aioredis.Redis(
        decode_responses=True,
        single_connection_client=True,
        **config.redis_test_credentials
    )
    yield conn
    await conn.flushdb()


@pytest.fixture
def mock_redis_conn(mocker, redis_conn, aioredis_conn):
    mocker.patch("foo.api.aioredis_conn", new=aioredis_conn)

def test_foobar(mock_redis_conn):
    do_something()
```

Am I doing this approximately right? Is there any specific reason why this warning occurs?


Discussion:
The same warning as it gets captured by pytest:

```
=============================== warnings summary ===============================
tests/foo/test_foo_bar.py::test_api_online_features
  /usr/local/lib/python3.9/site-packages/aioredis/connection.py:681: RuntimeWarning: coroutine 'Connection.disconnect' was never awaited
    reader, writer = await asyncio.open_connection(
```
This may be because async_timeout should be doing `async with` https://github.com/aio-libs/aioredis-py/blob/8ebc6e8c9c5ea293f1588769a89704fc9041d2f7/aioredis/connection.py#L680
I just isolated it, it's happening in `Connection.__delete__()`:

https://github.com/aio-libs/aioredis-py/blob/8ebc6e8c9c5ea293f1588769a89704fc9041d2f7/aioredis/connection.py#L630-L635

Resolving patch:
diff --git a/aioredis/connection.py b/aioredis/connection.py
--- a/aioredis/connection.py
+++ b/aioredis/connection.py
@@ -632,7 +632,7 @@ def __del__(self):
                 if loop.is_running():
                     loop.create_task(coro)
                 else:
-                    loop.run_until_complete(self.disconnect())
+                    loop.run_until_complete(coro)
         except Exception:
             pass
 
@@ -677,7 +677,7 @@ async def connect(self):
 
     async def _connect(self):
         """Create a TCP socket connection"""
-        with async_timeout.timeout(self.socket_connect_timeout):
+        async with async_timeout.timeout(self.socket_connect_timeout):
             reader, writer = await asyncio.open_connection(
                 host=self.host, port=self.port, ssl=self.ssl_context, loop=self._loop
             )
@@ -844,7 +844,7 @@ async def can_read(self, timeout: float = 0):
     async def read_response(self):
         """Read the response from a previously sent command"""
         try:
-            with async_timeout.timeout(self.socket_timeout):
+            async with async_timeout.timeout(self.socket_timeout):
                 response = await self._parser.read_response()
         except asyncio.TimeoutError:
             await self.disconnect()
@@ -1071,7 +1071,7 @@ def repr_pieces(self) -> Iterable[Tuple[str, Union[str, int]]]:
         return pieces
 
     async def _connect(self):
-        with async_timeout.timeout(self._connect_timeout):
+        async with async_timeout.timeout(self._connect_timeout):
             reader, writer = await asyncio.open_unix_connection(path=self.path)
         self._reader = reader
         self._writer = writer


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -131,11 +131,12 @@ def create_redis(request, event_loop):
     single_connection = request.param
 
     async def f(url: str = request.config.getoption("--redis-url"), **kwargs):
+        single = kwargs.pop("single_connection_client", False) or single_connection
         url_options = parse_url(url)
         url_options.update(kwargs)
         pool = aioredis.ConnectionPool(**url_options)
         client: aioredis.Redis = aioredis.Redis(connection_pool=pool)
-        if single_connection:
+        if single:
             client = client.client()
             await client.initialize()
 
diff --git a/tests/test_multiprocessing.py b/tests/test_multiprocessing.py
--- a/tests/test_multiprocessing.py
+++ b/tests/test_multiprocessing.py
@@ -26,9 +26,8 @@ class TestMultiprocessing:
     # use a multi-connection client as that's the only type that is
     # actually fork/process-safe
     @pytest.fixture()
-    async def r(self, create_redis, server):
+    async def r(self, create_redis):
         redis = await create_redis(
-            server.tcp_address,
             single_connection_client=False,
         )
         yield redis
diff --git a/tests/test_sentinel.py b/tests/test_sentinel.py
--- a/tests/test_sentinel.py
+++ b/tests/test_sentinel.py
@@ -66,15 +66,13 @@ def client(self, host, port, **kwargs):
 
 
 @pytest.fixture()
-def cluster(request, master_ip):
-    def teardown():
-        aioredis.sentinel.Redis = saved_Redis
+async def cluster(master_ip):
 
     cluster = SentinelTestCluster(ip=master_ip)
     saved_Redis = aioredis.sentinel.Redis
     aioredis.sentinel.Redis = cluster.client
-    request.addfinalizer(teardown)
-    return cluster
+    yield cluster
+    aioredis.sentinel.Redis = saved_Redis
 
 
 @pytest.fixture()