impuls/lib/python3.11/site-packages/pymemcache/test/test_client_retry.py

287 lines
10 KiB
Python
Raw Normal View History

2023-11-06 16:54:45 +01:00
""" Test collection for the RetryingClient. """
import functools
import unittest
from unittest import mock
import pytest
from .test_client import ClientTestMixin, MockSocket
from pymemcache.client.retrying import RetryingClient
from pymemcache.client.base import Client
from pymemcache.exceptions import MemcacheUnknownError, MemcacheClientError
# Test pure passthroughs with no retry action.
class TestRetryingClientPassthrough(ClientTestMixin, unittest.TestCase):
def make_base_client(self, mock_socket_values, **kwargs):
base_client = Client("localhost", **kwargs)
# mock out client._connect() rather than hard-setting client.sock to
# ensure methods are checking whether self.sock is None before
# attempting to use it
sock = MockSocket(list(mock_socket_values))
base_client._connect = mock.Mock(
side_effect=functools.partial(setattr, base_client, "sock", sock)
)
return base_client
def make_client(self, mock_socket_values, **kwargs):
# Create a base client to wrap.
base_client = self.make_base_client(
mock_socket_values=mock_socket_values, **kwargs
)
# Wrap the client in the retrying class, disable retries.
client = RetryingClient(base_client, attempts=1)
return client
# Retry specific tests.
@pytest.mark.unit()
class TestRetryingClient(object):
def make_base_client(self, mock_socket_values, **kwargs):
"""Creates a regular mock client to wrap in the RetryClient."""
base_client = Client("localhost", **kwargs)
# mock out client._connect() rather than hard-setting client.sock to
# ensure methods are checking whether self.sock is None before
# attempting to use it
sock = MockSocket(list(mock_socket_values))
base_client._connect = mock.Mock(
side_effect=functools.partial(setattr, base_client, "sock", sock)
)
return base_client
def make_client(self, mock_socket_values, **kwargs):
"""
Creates a RetryingClient that will respond with the given values,
configured using kwargs.
"""
# Create a base client to wrap.
base_client = self.make_base_client(mock_socket_values=mock_socket_values)
# Wrap the client in the retrying class, and pass kwargs on.
client = RetryingClient(base_client, **kwargs)
return client
# Start testing.
def test_constructor_default(self):
base_client = self.make_base_client([])
RetryingClient(base_client)
with pytest.raises(TypeError):
RetryingClient()
def test_constructor_attempts(self):
base_client = self.make_base_client([])
rc = RetryingClient(base_client, attempts=1)
assert rc._attempts == 1
with pytest.raises(ValueError):
RetryingClient(base_client, attempts=0)
def test_constructor_retry_for(self):
base_client = self.make_base_client([])
# Try none/default.
rc = RetryingClient(base_client, retry_for=None)
assert rc._retry_for == tuple()
# Try with tuple.
rc = RetryingClient(base_client, retry_for=tuple([Exception]))
assert rc._retry_for == tuple([Exception])
# Try with list.
rc = RetryingClient(base_client, retry_for=[Exception])
assert rc._retry_for == tuple([Exception])
# Try with multi element list.
rc = RetryingClient(base_client, retry_for=[Exception, IOError])
assert rc._retry_for == (Exception, IOError)
# With string?
with pytest.raises(ValueError):
RetryingClient(base_client, retry_for="haha!")
# With collection of string and exceptions?
with pytest.raises(ValueError):
RetryingClient(base_client, retry_for=[Exception, str])
def test_constructor_do_no_retry_for(self):
base_client = self.make_base_client([])
# Try none/default.
rc = RetryingClient(base_client, do_not_retry_for=None)
assert rc._do_not_retry_for == tuple()
# Try with tuple.
rc = RetryingClient(base_client, do_not_retry_for=tuple([Exception]))
assert rc._do_not_retry_for == tuple([Exception])
# Try with list.
rc = RetryingClient(base_client, do_not_retry_for=[Exception])
assert rc._do_not_retry_for == tuple([Exception])
# Try with multi element list.
rc = RetryingClient(base_client, do_not_retry_for=[Exception, IOError])
assert rc._do_not_retry_for == (Exception, IOError)
# With string?
with pytest.raises(ValueError):
RetryingClient(base_client, do_not_retry_for="haha!")
# With collection of string and exceptions?
with pytest.raises(ValueError):
RetryingClient(base_client, do_not_retry_for=[Exception, str])
def test_constructor_both_filters(self):
base_client = self.make_base_client([])
# Try none/default.
rc = RetryingClient(base_client, retry_for=None, do_not_retry_for=None)
assert rc._retry_for == tuple()
assert rc._do_not_retry_for == tuple()
# Try a valid config.
rc = RetryingClient(
base_client,
retry_for=[Exception, IOError],
do_not_retry_for=[ValueError, MemcacheUnknownError],
)
assert rc._retry_for == (Exception, IOError)
assert rc._do_not_retry_for == (ValueError, MemcacheUnknownError)
# Try with overlapping filters
with pytest.raises(ValueError):
rc = RetryingClient(
base_client,
retry_for=[Exception, IOError, MemcacheUnknownError],
do_not_retry_for=[ValueError, MemcacheUnknownError],
)
def test_dir_passthrough(self):
base = self.make_base_client([])
client = RetryingClient(base)
assert dir(base) == dir(client)
def test_retry_dict_set_is_supported(self):
client = self.make_client([b"UNKNOWN\r\n", b"STORED\r\n"])
client[b"key"] = b"value"
def test_retry_dict_get_is_supported(self):
client = self.make_client(
[b"UNKNOWN\r\n", b"VALUE key 0 5\r\nvalue\r\nEND\r\n"]
)
assert client[b"key"] == b"value"
def test_retry_dict_get_not_found_is_supported(self):
client = self.make_client([b"UNKNOWN\r\n", b"END\r\n"])
with pytest.raises(KeyError):
client[b"key"]
def test_retry_dict_del_is_supported(self):
client = self.make_client([b"UNKNOWN\r\n", b"DELETED\r\n"])
del client[b"key"]
def test_retry_get_found(self):
client = self.make_client(
[b"UNKNOWN\r\n", b"VALUE key 0 5\r\nvalue\r\nEND\r\n"], attempts=2
)
result = client.get("key")
assert result == b"value"
def test_retry_get_not_found(self):
client = self.make_client([b"UNKNOWN\r\n", b"END\r\n"], attempts=2)
result = client.get("key")
assert result is None
def test_retry_get_exception(self):
client = self.make_client([b"UNKNOWN\r\n", b"UNKNOWN\r\n"], attempts=2)
with pytest.raises(MemcacheUnknownError):
client.get("key")
def test_retry_set_success(self):
client = self.make_client([b"UNKNOWN\r\n", b"STORED\r\n"], attempts=2)
result = client.set("key", "value", noreply=False)
assert result is True
def test_retry_set_fail(self):
client = self.make_client(
[b"UNKNOWN\r\n", b"UNKNOWN\r\n", b"STORED\r\n"], attempts=2
)
with pytest.raises(MemcacheUnknownError):
client.set("key", "value", noreply=False)
def test_no_retry(self):
client = self.make_client(
[b"UNKNOWN\r\n", b"VALUE key 0 5\r\nvalue\r\nEND\r\n"], attempts=1
)
with pytest.raises(MemcacheUnknownError):
client.get("key")
def test_retry_for_exception_success(self):
# Test that we retry for the exception specified.
client = self.make_client(
[MemcacheClientError("Whoops."), b"VALUE key 0 5\r\nvalue\r\nEND\r\n"],
attempts=2,
retry_for=tuple([MemcacheClientError]),
)
result = client.get("key")
assert result == b"value"
def test_retry_for_exception_fail(self):
# Test that we do not retry for unapproved exception.
client = self.make_client(
[MemcacheUnknownError("Whoops."), b"VALUE key 0 5\r\nvalue\r\nEND\r\n"],
attempts=2,
retry_for=tuple([MemcacheClientError]),
)
with pytest.raises(MemcacheUnknownError):
client.get("key")
def test_do_not_retry_for_exception_success(self):
# Test that we retry for exceptions not specified.
client = self.make_client(
[MemcacheClientError("Whoops."), b"VALUE key 0 5\r\nvalue\r\nEND\r\n"],
attempts=2,
do_not_retry_for=tuple([MemcacheUnknownError]),
)
result = client.get("key")
assert result == b"value"
def test_do_not_retry_for_exception_fail(self):
# Test that we do not retry for the exception specified.
client = self.make_client(
[MemcacheClientError("Whoops."), b"VALUE key 0 5\r\nvalue\r\nEND\r\n"],
attempts=2,
do_not_retry_for=tuple([MemcacheClientError]),
)
with pytest.raises(MemcacheClientError):
client.get("key")
def test_both_exception_filters(self):
# Test interaction between both exception filters.
client = self.make_client(
[
MemcacheClientError("Whoops."),
b"VALUE key 0 5\r\nvalue\r\nEND\r\n",
MemcacheUnknownError("Whoops."),
b"VALUE key 0 5\r\nvalue\r\nEND\r\n",
],
attempts=2,
retry_for=tuple([MemcacheClientError]),
do_not_retry_for=tuple([MemcacheUnknownError]),
)
# Check that we succeed where allowed.
result = client.get("key")
assert result == b"value"
# Check that no retries are attempted for the banned exception.
with pytest.raises(MemcacheUnknownError):
client.get("key")