123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- """ Test collection for the RetryingClient. """
- import functools
- import unittest
- import mock
- import pytest
- from .test_client import ClientTestMixin, MockSocket
- from library.pymemcache.client.retrying import RetryingClient
- from library.pymemcache.client.base import Client
- from library.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(None, **kwargs)
- # mock out client._connect() rather than hard-settting 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(None, **kwargs)
- # mock out client._connect() rather than hard-settting 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 collectino 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 collectino 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 interacction 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")
|