From 1d932e0609f3b40993714c913db087c707a0cb6a Mon Sep 17 00:00:00 2001 From: Chayim Date: Thu, 11 Jul 2024 13:34:50 +0300 Subject: [PATCH] Add support for Python 3.12 (#2979) Add support for Python 3.12. This required a bunch of changes all over the place, listed below in a random order. Fix tests, especially around SSL connections. Stop requiring typing-extensions. Enable tracemalloc for tests, and add the possibility to run the tests with profiling enabled. Fix some issues identified by tracemalloc, like sockets not being closed in case of SSL handshake failures. Remove the CI test reporting plugin, it does not work easily with forked repos anyway. Fix checking of module versions, make the comparison accurate. Not sure how it worked before, but it looks like it did not match exactly the format in the server INFO response, i.e. MMmmPP. Remove loggers from tests, it's just noise in the output. If we don't use asserts, nobody will check the log output from CI. Speed up the computation for slots when initializing a cluster. After profiling, this turned out to be very slow, when it does not have to be. It does not make sense to recompute the same thing over and over in a loop. Run uvloop tests in matrix, i.e. don't bundle two tests executions (without uvloop and with it) in the same job. Easier to spot failures like this, and arguably the jobs can be scheduled in parallel so the overall execution is faster. Unlock urllib version, to be able to use more recent pytest versions. --------- Co-authored-by: Gabriel Erzse --- .flake8 | 1 + .github/workflows/integration.yaml | 89 +++++++++++++--------------- .gitignore | 2 + dev_requirements.txt | 6 +- redis/asyncio/cluster.py | 40 ++++++------- redis/asyncio/connection.py | 2 +- redis/cluster.py | 35 +++++------ redis/commands/graph/query_result.py | 17 +++++- redis/connection.py | 22 ++++++- requirements.txt | 2 +- setup.py | 3 +- tasks.py | 24 ++++---- tests/conftest.py | 19 +++++- tests/test_asyncio/test_cluster.py | 13 ++-- tests/test_asyncio/test_connect.py | 34 ++++------- tests/test_asyncio/test_cwe_404.py | 2 +- tests/test_asyncio/test_lock.py | 6 +- tests/test_asyncio/test_search.py | 4 +- tests/test_connect.py | 38 +++++------- 19 files changed, 194 insertions(+), 165 deletions(-) diff --git a/.flake8 b/.flake8 index d2ee181447..b1bd1d0b75 100644 --- a/.flake8 +++ b/.flake8 @@ -16,6 +16,7 @@ exclude = ignore = E126 E203 + E231 E701 E704 F405 diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index a619275b47..94fe8f35b6 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -25,6 +25,8 @@ permissions: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + # this speeds up coverage with Python 3.12: https://github.com/nedbat/coveragepy/issues/1665 + COVERAGE_CORE: sysmon REDIS_IMAGE: redis:7.4-rc2 REDIS_STACK_IMAGE: redis/redis-stack-server:7.4.0-rc2 @@ -54,26 +56,28 @@ jobs: pip install -r dev_requirements.txt invoke linters - run-tests: + resp2-tests: runs-on: ubuntu-latest timeout-minutes: 60 strategy: max-parallel: 15 fail-fast: false matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', 'pypy-3.8', 'pypy-3.9'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy-3.8', 'pypy-3.9'] test-type: ['standalone', 'cluster'] connection-type: ['hiredis', 'plain'] env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true - name: Python ${{ matrix.python-version }} ${{matrix.test-type}}-${{matrix.connection-type}} tests + name: RESP2 ${{ matrix.python-version }} ${{matrix.test-type}}-${{matrix.connection-type}} steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' - - name: run tests + + - name: Run tests run: | pip install -U setuptools wheel pip install -r requirements.txt @@ -84,52 +88,48 @@ jobs: invoke devenv sleep 10 # time to settle invoke ${{matrix.test-type}}-tests + ls -1 - - uses: actions/upload-artifact@v4 - if: success() || failure() + - name: Upload test results and profiling data + uses: actions/upload-artifact@v4 with: name: pytest-results-${{matrix.test-type}}-${{matrix.connection-type}}-${{matrix.python-version}} - path: '${{matrix.test-type}}*results.xml' + path: | + ${{matrix.test-type}}*-results.xml + prof/** + profile_output* + if-no-files-found: error + retention-days: 10 - name: Upload codecov coverage uses: codecov/codecov-action@v4 with: fail_ci_if_error: false - - name: View Test Results - uses: dorny/test-reporter@v1 - if: success() || failure() - continue-on-error: true - with: - name: Test Results ${{matrix.python-version}} ${{matrix.test-type}}-${{matrix.connection-type}} - path: '*.xml' - reporter: java-junit - list-suites: all - list-tests: all - max-annotations: 10 - fail-on-error: 'false' - - resp3_tests: + resp3-tests: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: ['3.8', '3.11'] + python-version: ['3.8', '3.12'] test-type: ['standalone', 'cluster'] connection-type: ['hiredis', 'plain'] + event-loop: ['asyncio', 'uvloop'] exclude: - test-type: 'cluster' connection-type: 'hiredis' env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true - name: RESP3 [${{ matrix.python-version }} ${{matrix.test-type}}-${{matrix.connection-type}}] + name: RESP3 ${{ matrix.python-version }} ${{matrix.test-type}}-${{matrix.connection-type}}-${{matrix.event-loop}} steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' - - name: run tests + + - name: Run tests run: | pip install -U setuptools wheel pip install -r requirements.txt @@ -139,37 +139,32 @@ jobs: fi invoke devenv sleep 10 # time to settle - invoke ${{matrix.test-type}}-tests --protocol=3 - invoke ${{matrix.test-type}}-tests --uvloop --protocol=3 + if [ "${{matrix.event-loop}}" == "uvloop" ]; then + invoke ${{matrix.test-type}}-tests --uvloop --protocol=3 + else + invoke ${{matrix.test-type}}-tests --protocol=3 + fi - - uses: actions/upload-artifact@v4 - if: success() || failure() + - name: Upload test results and profiling data + uses: actions/upload-artifact@v4 with: - name: pytest-results-${{matrix.test-type}}-${{matrix.connection-type}}-${{matrix.python-version}}-resp3 - path: '${{matrix.test-type}}*results.xml' + name: pytest-results-${{matrix.test-type}}-${{matrix.connection-type}}-${{matrix.python-version}}-${{matrix.event-loop}}-resp3 + path: | + ${{matrix.test-type}}*-results.xml + prof/** + profile_output* + if-no-files-found: error + retention-days: 10 - name: Upload codecov coverage uses: codecov/codecov-action@v4 with: fail_ci_if_error: false - - name: View Test Results - uses: dorny/test-reporter@v1 - if: success() || failure() - continue-on-error: true - with: - name: Test Results ${{matrix.python-version}} ${{matrix.test-type}}-${{matrix.connection-type}}-resp3 - path: '*.xml' - reporter: java-junit - list-suites: all - list-tests: all - max-annotations: 10 - fail-on-error: 'false' - - build_and_test_package: + build-and-test-package: name: Validate building and installing the package runs-on: ubuntu-latest - needs: [run-tests] + needs: [resp2-tests, resp3-tests] strategy: fail-fast: false matrix: @@ -183,13 +178,13 @@ jobs: run: | bash .github/workflows/install_and_test.sh ${{ matrix.extension }} - install_package_from_commit: + install-package-from-commit: name: Install package from commit hash runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', 'pypy-3.8', 'pypy-3.9'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy-3.8', 'pypy-3.9'] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/.gitignore b/.gitignore index 3baa34034f..c6a4efd1ec 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ coverage.xml .venv* *.xml .coverage* +prof +profile_output* docker/stunnel/keys diff --git a/dev_requirements.txt b/dev_requirements.txt index c008b69b1d..e9d5738b69 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -4,15 +4,15 @@ click==8.0.4 flake8-isort==6.0.0 flake8==5.0.4 flynt~=0.69.0 -invoke==1.7.3 -mock==4.0.3 +invoke==2.2.0 +mock packaging>=20.4 pytest pytest-asyncio pytest-cov +pytest-profiling pytest-timeout ujson>=4.2.0 -urllib3<2 uvloop vulture>=2.3.0 wheel>=0.30.0 diff --git a/redis/asyncio/cluster.py b/redis/asyncio/cluster.py index 7198abebd4..382fbcbae9 100644 --- a/redis/asyncio/cluster.py +++ b/redis/asyncio/cluster.py @@ -1317,6 +1317,8 @@ async def initialize(self) -> None: port = int(primary_node[1]) host, port = self.remap_host_port(host, port) + nodes_for_slot = [] + target_node = tmp_nodes_cache.get(get_node_name(host, port)) if not target_node: target_node = ClusterNode( @@ -1324,30 +1326,26 @@ async def initialize(self) -> None: ) # add this node to the nodes cache tmp_nodes_cache[target_node.name] = target_node + nodes_for_slot.append(target_node) + + replica_nodes = slot[3:] + for replica_node in replica_nodes: + host = replica_node[0] + port = replica_node[1] + host, port = self.remap_host_port(host, port) + + target_replica_node = tmp_nodes_cache.get(get_node_name(host, port)) + if not target_replica_node: + target_replica_node = ClusterNode( + host, port, REPLICA, **self.connection_kwargs + ) + # add this node to the nodes cache + tmp_nodes_cache[target_replica_node.name] = target_replica_node + nodes_for_slot.append(target_replica_node) for i in range(int(slot[0]), int(slot[1]) + 1): if i not in tmp_slots: - tmp_slots[i] = [] - tmp_slots[i].append(target_node) - replica_nodes = [slot[j] for j in range(3, len(slot))] - - for replica_node in replica_nodes: - host = replica_node[0] - port = replica_node[1] - host, port = self.remap_host_port(host, port) - - target_replica_node = tmp_nodes_cache.get( - get_node_name(host, port) - ) - if not target_replica_node: - target_replica_node = ClusterNode( - host, port, REPLICA, **self.connection_kwargs - ) - tmp_slots[i].append(target_replica_node) - # add this node to the nodes cache - tmp_nodes_cache[target_replica_node.name] = ( - target_replica_node - ) + tmp_slots[i] = nodes_for_slot else: # Validate that 2 nodes want to use the same slot cache # setup diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index 7295811ae4..93e4518e8c 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -999,7 +999,7 @@ def parse_url(url: str) -> ConnectKwargs: try: kwargs[name] = parser(value) except (TypeError, ValueError): - raise ValueError(f"Invalid value for `{name}` in connection URL.") + raise ValueError(f"Invalid value for '{name}' in connection URL.") else: kwargs[name] = value diff --git a/redis/cluster.py b/redis/cluster.py index 144844ec8a..be7685e9a1 100644 --- a/redis/cluster.py +++ b/redis/cluster.py @@ -1522,6 +1522,8 @@ def _get_or_create_cluster_node(self, host, port, role, tmp_nodes_cache): target_node = ClusterNode(host, port, role) if target_node.server_type != role: target_node.server_type = role + # add this node to the nodes cache + tmp_nodes_cache[target_node.name] = target_node return target_node @@ -1585,31 +1587,26 @@ def initialize(self): port = int(primary_node[1]) host, port = self.remap_host_port(host, port) + nodes_for_slot = [] + target_node = self._get_or_create_cluster_node( host, port, PRIMARY, tmp_nodes_cache ) - # add this node to the nodes cache - tmp_nodes_cache[target_node.name] = target_node + nodes_for_slot.append(target_node) + + replica_nodes = slot[3:] + for replica_node in replica_nodes: + host = str_if_bytes(replica_node[0]) + port = int(replica_node[1]) + host, port = self.remap_host_port(host, port) + target_replica_node = self._get_or_create_cluster_node( + host, port, REPLICA, tmp_nodes_cache + ) + nodes_for_slot.append(target_replica_node) for i in range(int(slot[0]), int(slot[1]) + 1): if i not in tmp_slots: - tmp_slots[i] = [] - tmp_slots[i].append(target_node) - replica_nodes = [slot[j] for j in range(3, len(slot))] - - for replica_node in replica_nodes: - host = str_if_bytes(replica_node[0]) - port = replica_node[1] - host, port = self.remap_host_port(host, port) - - target_replica_node = self._get_or_create_cluster_node( - host, port, REPLICA, tmp_nodes_cache - ) - tmp_slots[i].append(target_replica_node) - # add this node to the nodes cache - tmp_nodes_cache[target_replica_node.name] = ( - target_replica_node - ) + tmp_slots[i] = nodes_for_slot else: # Validate that 2 nodes want to use the same slot cache # setup diff --git a/redis/commands/graph/query_result.py b/redis/commands/graph/query_result.py index 7c7f58b99f..7709081bcf 100644 --- a/redis/commands/graph/query_result.py +++ b/redis/commands/graph/query_result.py @@ -1,6 +1,5 @@ import sys from collections import OrderedDict -from distutils.util import strtobool # from prettytable import PrettyTable from redis import ResponseError @@ -571,3 +570,19 @@ async def parse_array(self, value): """ scalar = [await self.parse_scalar(value[i]) for i in range(len(value))] return scalar + + +def strtobool(val): + """ + Convert a string representation of truth to true (1) or false (0). + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + """ + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + elif val in ("n", "no", "f", "false", "off", "0"): + return False + else: + raise ValueError(f"invalid truth value {val!r}") diff --git a/redis/connection.py b/redis/connection.py index f7d92eb5ba..de9a65d610 100644 --- a/redis/connection.py +++ b/redis/connection.py @@ -813,8 +813,26 @@ def __init__( super().__init__(**kwargs) def _connect(self): - "Wrap the socket with SSL support" + """ + Wrap the socket with SSL support, handling potential errors. + """ sock = super()._connect() + try: + return self._wrap_socket_with_ssl(sock) + except OSError: + sock.close() + raise + + def _wrap_socket_with_ssl(self, sock): + """ + Wraps the socket with SSL support. + + Args: + sock: The plain socket to wrap with SSL. + + Returns: + An SSL wrapped socket. + """ context = ssl.create_default_context() context.check_hostname = self.check_hostname context.verify_mode = self.cert_reqs @@ -957,7 +975,7 @@ def parse_url(url): try: kwargs[name] = parser(value) except (TypeError, ValueError): - raise ValueError(f"Invalid value for `{name}` in connection URL.") + raise ValueError(f"Invalid value for '{name}' in connection URL.") else: kwargs[name] = value diff --git a/requirements.txt b/requirements.txt index a716b84463..3274a80f62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -async-timeout>=4.0.2 +async-timeout>=4.0.3 diff --git a/setup.py b/setup.py index ab1cb9631f..39cd40b807 100644 --- a/setup.py +++ b/setup.py @@ -51,11 +51,12 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ], extras_require={ "hiredis": ["hiredis>=1.0.0"], - "ocsp": ["cryptography>=36.0.1", "pyopenssl==20.0.1", "requests>=2.26.0"], + "ocsp": ["cryptography>=36.0.1", "pyopenssl==23.2.1", "requests>=2.31.0"], }, ) diff --git a/tasks.py b/tasks.py index 7f26081150..79b27852e3 100644 --- a/tasks.py +++ b/tasks.py @@ -42,39 +42,39 @@ def all_tests(c): @task -def tests(c, uvloop=False, protocol=2): - """Run the redis-py test suite against the current python, - with and without hiredis. - """ +def tests(c, uvloop=False, protocol=2, profile=False): + """Run the redis-py test suite against the current python.""" print("Starting Redis tests") - standalone_tests(c, uvloop=uvloop, protocol=protocol) - cluster_tests(c, uvloop=uvloop, protocol=protocol) + standalone_tests(c, uvloop=uvloop, protocol=protocol, profile=profile) + cluster_tests(c, uvloop=uvloop, protocol=protocol, profile=profile) @task -def standalone_tests(c, uvloop=False, protocol=2): +def standalone_tests(c, uvloop=False, protocol=2, profile=False): """Run tests against a standalone redis instance""" + profile_arg = "--profile" if profile else "" if uvloop: run( - f"pytest --protocol={protocol} --cov=./ --cov-report=xml:coverage_redis.xml -W always -m 'not onlycluster' --uvloop --junit-xml=standalone-uvloop-results.xml" + f"pytest {profile_arg} --protocol={protocol} --cov=./ --cov-report=xml:coverage_redis.xml -W always -m 'not onlycluster' --uvloop --junit-xml=standalone-uvloop-results.xml" ) else: run( - f"pytest --protocol={protocol} --cov=./ --cov-report=xml:coverage_redis.xml -W always -m 'not onlycluster' --junit-xml=standalone-results.xml" + f"pytest {profile_arg} --protocol={protocol} --cov=./ --cov-report=xml:coverage_redis.xml -W always -m 'not onlycluster' --junit-xml=standalone-results.xml" ) @task -def cluster_tests(c, uvloop=False, protocol=2): +def cluster_tests(c, uvloop=False, protocol=2, profile=False): """Run tests against a redis cluster""" + profile_arg = "--profile" if profile else "" cluster_url = "redis://localhost:16379/0" if uvloop: run( - f"pytest --protocol={protocol} --cov=./ --cov-report=xml:coverage_cluster.xml -W always -m 'not onlynoncluster and not redismod' --redis-url={cluster_url} --junit-xml=cluster-uvloop-results.xml --uvloop" + f"pytest {profile_arg} --protocol={protocol} --cov=./ --cov-report=xml:coverage_cluster.xml -W always -m 'not onlynoncluster and not redismod' --redis-url={cluster_url} --junit-xml=cluster-uvloop-results.xml --uvloop" ) else: run( - f"pytest --protocol={protocol} --cov=./ --cov-report=xml:coverage_clusteclient.xml -W always -m 'not onlynoncluster and not redismod' --redis-url={cluster_url} --junit-xml=cluster-results.xml" + f"pytest {profile_arg} --protocol={protocol} --cov=./ --cov-report=xml:coverage_clusteclient.xml -W always -m 'not onlynoncluster and not redismod' --redis-url={cluster_url} --junit-xml=cluster-results.xml" ) diff --git a/tests/conftest.py b/tests/conftest.py index e3326f85d5..dd78bb6a2c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -72,6 +72,21 @@ def format_usage(self): return " | ".join(self.option_strings) +@pytest.fixture(scope="session", autouse=True) +def enable_tracemalloc(): + """ + Enable tracemalloc while tests are being executed. + """ + try: + import tracemalloc + + tracemalloc.start() + yield + tracemalloc.stop() + except ImportError: + yield + + def pytest_addoption(parser): parser.addoption( "--redis-url", @@ -246,7 +261,9 @@ def skip_ifmodversion_lt(min_version: str, module_name: str): for j in modules: if module_name == j.get("name"): version = j.get("ver") - mv = int(min_version.replace(".", "")) + mv = int( + "".join(["%02d" % int(segment) for segment in min_version.split(".")]) + ) check = version < mv return pytest.mark.skipif(check, reason="Redis module version") diff --git a/tests/test_asyncio/test_cluster.py b/tests/test_asyncio/test_cluster.py index 80b0b1bff8..a36040f11b 100644 --- a/tests/test_asyncio/test_cluster.py +++ b/tests/test_asyncio/test_cluster.py @@ -58,7 +58,6 @@ class NodeProxy: def __init__(self, addr, redis_addr): self.addr = addr self.redis_addr = redis_addr - self.send_event = asyncio.Event() self.server = None self.task = None self.n_connections = 0 @@ -83,14 +82,20 @@ async def handle(self, reader, writer): await asyncio.gather(pipe1, pipe2) finally: redis_writer.close() + await self.redis_writer.wait_closed() + writer.close() + await writer.wait_closed() async def aclose(self): - self.task.cancel() try: - await self.task + self.task.cancel() + await asyncio.wait_for(self.task, timeout=1) + self.server.close() + await self.server.wait_closed() + except asyncio.TimeoutError: + pass except asyncio.CancelledError: pass - await self.server.wait_closed() async def pipe( self, diff --git a/tests/test_asyncio/test_connect.py b/tests/test_asyncio/test_connect.py index 0df7ebb43a..943540c885 100644 --- a/tests/test_asyncio/test_connect.py +++ b/tests/test_asyncio/test_connect.py @@ -1,5 +1,4 @@ import asyncio -import logging import re import socket import ssl @@ -14,9 +13,6 @@ from ..ssl_utils import get_ssl_filename -_logger = logging.getLogger(__name__) - - _CLIENT_NAME = "test-suite-client" _CMD_SEP = b"\r\n" _SUCCESS_RESP = b"+OK" + _CMD_SEP @@ -125,7 +121,7 @@ async def test_tcp_ssl_version_mismatch(tcp_address): tcp_address, certfile=certfile, keyfile=keyfile, - ssl_version=ssl.TLSVersion.TLSv1_2, + maximum_ssl_version=ssl.TLSVersion.TLSv1_2, ) await conn.disconnect() @@ -135,7 +131,8 @@ async def _assert_connect( server_address, certfile=None, keyfile=None, - ssl_version=None, + minimum_ssl_version=ssl.TLSVersion.TLSv1_2, + maximum_ssl_version=ssl.TLSVersion.TLSv1_3, ): stop_event = asyncio.Event() finished = asyncio.Event() @@ -153,9 +150,8 @@ async def _handler(reader, writer): elif certfile: host, port = server_address context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - if ssl_version is not None: - context.minimum_version = ssl_version - context.maximum_version = ssl_version + context.minimum_version = minimum_ssl_version + context.maximum_version = maximum_ssl_version context.load_cert_chain(certfile=certfile, keyfile=keyfile) server = await asyncio.start_server(_handler, host=host, port=port, ssl=context) else: @@ -178,23 +174,18 @@ async def _handler(reader, writer): async def _redis_request_handler(reader, writer, stop_event): - buffer = b"" command = None command_ptr = None fragment_length = None - while not stop_event.is_set() or buffer: - _logger.info(str(stop_event.is_set())) - try: - buffer += await asyncio.wait_for(reader.read(1024), timeout=0.5) - except TimeoutError: - continue + while not stop_event.is_set(): + buffer = await reader.read(1024) if not buffer: - continue + break parts = re.split(_CMD_SEP, buffer) - buffer = parts[-1] - for fragment in parts[:-1]: + for fragment in parts: fragment = fragment.decode() - _logger.info("Command fragment: %s", fragment) + if not fragment: + continue if fragment.startswith("*") and command is None: command = [None for _ in range(int(fragment[1:]))] @@ -214,10 +205,7 @@ async def _redis_request_handler(reader, writer, stop_event): continue command = " ".join(command) - _logger.info("Command %s", command) resp = _SUPPORTED_CMDS.get(command, _ERROR_RESP) - _logger.info("Response from %s", resp) writer.write(resp) await writer.drain() command = None - _logger.info("Exit handler") diff --git a/tests/test_asyncio/test_cwe_404.py b/tests/test_asyncio/test_cwe_404.py index df46cabc43..e920a3fb98 100644 --- a/tests/test_asyncio/test_cwe_404.py +++ b/tests/test_asyncio/test_cwe_404.py @@ -261,4 +261,4 @@ async def doit(): await asyncio.gather(*[doit() for _ in range(10)]) finally: - await r.close() + await r.aclose() diff --git a/tests/test_asyncio/test_lock.py b/tests/test_asyncio/test_lock.py index c052eae2a0..9eaaed6920 100644 --- a/tests/test_asyncio/test_lock.py +++ b/tests/test_asyncio/test_lock.py @@ -104,16 +104,16 @@ async def test_blocking(self, r): lock_2 = self.get_lock(r, "foo") assert lock_2.blocking - async def test_blocking_timeout(self, r, event_loop): + async def test_blocking_timeout(self, r): lock1 = self.get_lock(r, "foo") assert await lock1.acquire(blocking=False) bt = 0.2 sleep = 0.05 lock2 = self.get_lock(r, "foo", sleep=sleep, blocking_timeout=bt) - start = event_loop.time() + start = asyncio.get_running_loop().time() assert not await lock2.acquire() # The elapsed duration should be less than the total blocking_timeout - assert bt >= (event_loop.time() - start) > bt - sleep + assert bt >= (asyncio.get_running_loop().time() - start) > bt - sleep await lock1.release() async def test_context_manager(self, r): diff --git a/tests/test_asyncio/test_search.py b/tests/test_asyncio/test_search.py index 4d13cb4a38..47d34f9c07 100644 --- a/tests/test_asyncio/test_search.py +++ b/tests/test_asyncio/test_search.py @@ -1519,14 +1519,14 @@ async def test_withsuffixtrie(decoded_r: redis.Redis): # create withsuffixtrie index (text fields) assert await decoded_r.ft().create_index(TextField("t", withsuffixtrie=True)) - waitForIndex(decoded_r, getattr(decoded_r.ft(), "index_name", "idx")) + await waitForIndex(decoded_r, getattr(decoded_r.ft(), "index_name", "idx")) info = await decoded_r.ft().info() assert "WITHSUFFIXTRIE" in info["attributes"][0]["flags"] assert await decoded_r.ft().dropindex("idx") # create withsuffixtrie index (tag field) assert await decoded_r.ft().create_index(TagField("t", withsuffixtrie=True)) - waitForIndex(decoded_r, getattr(decoded_r.ft(), "index_name", "idx")) + await waitForIndex(decoded_r, getattr(decoded_r.ft(), "index_name", "idx")) info = await decoded_r.ft().info() assert "WITHSUFFIXTRIE" in info["attributes"][0]["flags"] diff --git a/tests/test_connect.py b/tests/test_connect.py index d7ca04b651..b11c4446e5 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -1,4 +1,3 @@ -import logging import re import socket import socketserver @@ -7,13 +6,10 @@ import pytest from redis.connection import Connection, SSLConnection, UnixDomainSocketConnection -from redis.exceptions import ConnectionError +from redis.exceptions import RedisError from .ssl_utils import get_ssl_filename -_logger = logging.getLogger(__name__) - - _CLIENT_NAME = "test-suite-client" _CMD_SEP = b"\r\n" _SUCCESS_RESP = b"+OK" + _CMD_SEP @@ -126,16 +122,16 @@ def test_tcp_ssl_version_mismatch(tcp_address): port=port, client_name=_CLIENT_NAME, ssl_ca_certs=certfile, - socket_timeout=10, + socket_timeout=3, ssl_min_version=ssl.TLSVersion.TLSv1_3, ) - with pytest.raises(ConnectionError): + with pytest.raises(RedisError): _assert_connect( conn, tcp_address, certfile=certfile, keyfile=keyfile, - ssl_version=ssl.PROTOCOL_TLSv1_2, + maximum_ssl_version=ssl.TLSVersion.TLSv1_2, ) @@ -164,14 +160,16 @@ def __init__( *args, certfile=None, keyfile=None, - ssl_version=ssl.PROTOCOL_TLS, + minimum_ssl_version=ssl.TLSVersion.TLSv1_2, + maximum_ssl_version=ssl.TLSVersion.TLSv1_3, **kw, ) -> None: self._ready_event = threading.Event() self._stop_requested = False self._certfile = certfile self._keyfile = keyfile - self._ssl_version = ssl_version + self._minimum_ssl_version = minimum_ssl_version + self._maximum_ssl_version = maximum_ssl_version super().__init__(*args, **kw) def service_actions(self): @@ -191,13 +189,11 @@ def get_request(self): if self._certfile is None: return super().get_request() newsocket, fromaddr = self.socket.accept() - connstream = ssl.wrap_socket( - newsocket, - server_side=True, - certfile=self._certfile, - keyfile=self._keyfile, - ssl_version=self._ssl_version, - ) + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + context.load_cert_chain(certfile=self._certfile, keyfile=self._keyfile) + context.minimum_version = self._minimum_ssl_version + context.maximum_version = self._maximum_ssl_version + connstream = context.wrap_socket(newsocket, server_side=True) return connstream, fromaddr @@ -228,10 +224,10 @@ def is_serving(self): class _RedisRequestHandler(socketserver.StreamRequestHandler): def setup(self): - _logger.info("%s connected", self.client_address) + pass def finish(self): - _logger.info("%s disconnected", self.client_address) + pass def handle(self): buffer = b"" @@ -249,7 +245,6 @@ def handle(self): buffer = parts[-1] for fragment in parts[:-1]: fragment = fragment.decode() - _logger.info("Command fragment: %s", fragment) if fragment.startswith("*") and command is None: command = [None for _ in range(int(fragment[1:]))] @@ -269,9 +264,6 @@ def handle(self): continue command = " ".join(command) - _logger.info("Command %s", command) resp = _SUPPORTED_CMDS.get(command, _ERROR_RESP) - _logger.info("Response %s", resp) self.request.sendall(resp) command = None - _logger.info("Exit handler")