""" 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")