test_client_retry.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. """ Test collection for the RetryingClient. """
  2. import functools
  3. import unittest
  4. import mock
  5. import pytest
  6. from .test_client import ClientTestMixin, MockSocket
  7. from library.pymemcache.client.retrying import RetryingClient
  8. from library.pymemcache.client.base import Client
  9. from library.pymemcache.exceptions import MemcacheUnknownError, MemcacheClientError
  10. # Test pure passthroughs with no retry action.
  11. class TestRetryingClientPassthrough(ClientTestMixin, unittest.TestCase):
  12. def make_base_client(self, mock_socket_values, **kwargs):
  13. base_client = Client(None, **kwargs)
  14. # mock out client._connect() rather than hard-settting client.sock to
  15. # ensure methods are checking whether self.sock is None before
  16. # attempting to use it
  17. sock = MockSocket(list(mock_socket_values))
  18. base_client._connect = mock.Mock(side_effect=functools.partial(
  19. setattr, base_client, "sock", sock))
  20. return base_client
  21. def make_client(self, mock_socket_values, **kwargs):
  22. # Create a base client to wrap.
  23. base_client = self.make_base_client(
  24. mock_socket_values=mock_socket_values, **kwargs
  25. )
  26. # Wrap the client in the retrying class, disable retries.
  27. client = RetryingClient(base_client, attempts=1)
  28. return client
  29. # Retry specific tests.
  30. @pytest.mark.unit()
  31. class TestRetryingClient(object):
  32. def make_base_client(self, mock_socket_values, **kwargs):
  33. """ Creates a regular mock client to wrap in the RetryClient. """
  34. base_client = Client(None, **kwargs)
  35. # mock out client._connect() rather than hard-settting client.sock to
  36. # ensure methods are checking whether self.sock is None before
  37. # attempting to use it
  38. sock = MockSocket(list(mock_socket_values))
  39. base_client._connect = mock.Mock(side_effect=functools.partial(
  40. setattr, base_client, "sock", sock))
  41. return base_client
  42. def make_client(self, mock_socket_values, **kwargs):
  43. """
  44. Creates a RetryingClient that will respond with the given values,
  45. configured using kwargs.
  46. """
  47. # Create a base client to wrap.
  48. base_client = self.make_base_client(
  49. mock_socket_values=mock_socket_values
  50. )
  51. # Wrap the client in the retrying class, and pass kwargs on.
  52. client = RetryingClient(base_client, **kwargs)
  53. return client
  54. # Start testing.
  55. def test_constructor_default(self):
  56. base_client = self.make_base_client([])
  57. RetryingClient(base_client)
  58. with pytest.raises(TypeError):
  59. RetryingClient()
  60. def test_constructor_attempts(self):
  61. base_client = self.make_base_client([])
  62. rc = RetryingClient(base_client, attempts=1)
  63. assert rc._attempts == 1
  64. with pytest.raises(ValueError):
  65. RetryingClient(base_client, attempts=0)
  66. def test_constructor_retry_for(self):
  67. base_client = self.make_base_client([])
  68. # Try none/default.
  69. rc = RetryingClient(base_client, retry_for=None)
  70. assert rc._retry_for == tuple()
  71. # Try with tuple.
  72. rc = RetryingClient(base_client, retry_for=tuple([Exception]))
  73. assert rc._retry_for == tuple([Exception])
  74. # Try with list.
  75. rc = RetryingClient(base_client, retry_for=[Exception])
  76. assert rc._retry_for == tuple([Exception])
  77. # Try with multi element list.
  78. rc = RetryingClient(base_client, retry_for=[Exception, IOError])
  79. assert rc._retry_for == (Exception, IOError)
  80. # With string?
  81. with pytest.raises(ValueError):
  82. RetryingClient(base_client, retry_for="haha!")
  83. # With collectino of string and exceptions?
  84. with pytest.raises(ValueError):
  85. RetryingClient(base_client, retry_for=[Exception, str])
  86. def test_constructor_do_no_retry_for(self):
  87. base_client = self.make_base_client([])
  88. # Try none/default.
  89. rc = RetryingClient(base_client, do_not_retry_for=None)
  90. assert rc._do_not_retry_for == tuple()
  91. # Try with tuple.
  92. rc = RetryingClient(base_client, do_not_retry_for=tuple([Exception]))
  93. assert rc._do_not_retry_for == tuple([Exception])
  94. # Try with list.
  95. rc = RetryingClient(base_client, do_not_retry_for=[Exception])
  96. assert rc._do_not_retry_for == tuple([Exception])
  97. # Try with multi element list.
  98. rc = RetryingClient(base_client, do_not_retry_for=[Exception, IOError])
  99. assert rc._do_not_retry_for == (Exception, IOError)
  100. # With string?
  101. with pytest.raises(ValueError):
  102. RetryingClient(base_client, do_not_retry_for="haha!")
  103. # With collectino of string and exceptions?
  104. with pytest.raises(ValueError):
  105. RetryingClient(base_client, do_not_retry_for=[Exception, str])
  106. def test_constructor_both_filters(self):
  107. base_client = self.make_base_client([])
  108. # Try none/default.
  109. rc = RetryingClient(base_client, retry_for=None, do_not_retry_for=None)
  110. assert rc._retry_for == tuple()
  111. assert rc._do_not_retry_for == tuple()
  112. # Try a valid config.
  113. rc = RetryingClient(
  114. base_client,
  115. retry_for=[Exception, IOError],
  116. do_not_retry_for=[ValueError, MemcacheUnknownError]
  117. )
  118. assert rc._retry_for == (Exception, IOError)
  119. assert rc._do_not_retry_for == (ValueError, MemcacheUnknownError)
  120. # Try with overlapping filters
  121. with pytest.raises(ValueError):
  122. rc = RetryingClient(
  123. base_client,
  124. retry_for=[Exception, IOError, MemcacheUnknownError],
  125. do_not_retry_for=[ValueError, MemcacheUnknownError]
  126. )
  127. def test_dir_passthrough(self):
  128. base = self.make_base_client([])
  129. client = RetryingClient(base)
  130. assert dir(base) == dir(client)
  131. def test_retry_dict_set_is_supported(self):
  132. client = self.make_client([b'UNKNOWN\r\n', b'STORED\r\n'])
  133. client[b'key'] = b'value'
  134. def test_retry_dict_get_is_supported(self):
  135. client = self.make_client(
  136. [
  137. b'UNKNOWN\r\n',
  138. b'VALUE key 0 5\r\nvalue\r\nEND\r\n'
  139. ]
  140. )
  141. assert client[b'key'] == b'value'
  142. def test_retry_dict_get_not_found_is_supported(self):
  143. client = self.make_client([b'UNKNOWN\r\n', b'END\r\n'])
  144. with pytest.raises(KeyError):
  145. client[b'key']
  146. def test_retry_dict_del_is_supported(self):
  147. client = self.make_client([b'UNKNOWN\r\n', b'DELETED\r\n'])
  148. del client[b'key']
  149. def test_retry_get_found(self):
  150. client = self.make_client([
  151. b'UNKNOWN\r\n',
  152. b'VALUE key 0 5\r\nvalue\r\nEND\r\n'
  153. ], attempts=2)
  154. result = client.get("key")
  155. assert result == b'value'
  156. def test_retry_get_not_found(self):
  157. client = self.make_client([
  158. b'UNKNOWN\r\n',
  159. b'END\r\n'
  160. ], attempts=2)
  161. result = client.get("key")
  162. assert result is None
  163. def test_retry_get_exception(self):
  164. client = self.make_client([
  165. b'UNKNOWN\r\n',
  166. b'UNKNOWN\r\n'
  167. ], attempts=2)
  168. with pytest.raises(MemcacheUnknownError):
  169. client.get("key")
  170. def test_retry_set_success(self):
  171. client = self.make_client([
  172. b'UNKNOWN\r\n',
  173. b'STORED\r\n'
  174. ], attempts=2)
  175. result = client.set("key", "value", noreply=False)
  176. assert result is True
  177. def test_retry_set_fail(self):
  178. client = self.make_client([
  179. b'UNKNOWN\r\n',
  180. b'UNKNOWN\r\n',
  181. b'STORED\r\n'
  182. ], attempts=2)
  183. with pytest.raises(MemcacheUnknownError):
  184. client.set("key", "value", noreply=False)
  185. def test_no_retry(self):
  186. client = self.make_client([
  187. b'UNKNOWN\r\n',
  188. b'VALUE key 0 5\r\nvalue\r\nEND\r\n'
  189. ], attempts=1)
  190. with pytest.raises(MemcacheUnknownError):
  191. client.get("key")
  192. def test_retry_for_exception_success(self):
  193. # Test that we retry for the exception specified.
  194. client = self.make_client(
  195. [
  196. MemcacheClientError("Whoops."),
  197. b'VALUE key 0 5\r\nvalue\r\nEND\r\n'
  198. ],
  199. attempts=2,
  200. retry_for=tuple([MemcacheClientError])
  201. )
  202. result = client.get("key")
  203. assert result == b'value'
  204. def test_retry_for_exception_fail(self):
  205. # Test that we do not retry for unapproved exception.
  206. client = self.make_client(
  207. [
  208. MemcacheUnknownError("Whoops."),
  209. b'VALUE key 0 5\r\nvalue\r\nEND\r\n'
  210. ],
  211. attempts=2,
  212. retry_for=tuple([MemcacheClientError])
  213. )
  214. with pytest.raises(MemcacheUnknownError):
  215. client.get("key")
  216. def test_do_not_retry_for_exception_success(self):
  217. # Test that we retry for exceptions not specified.
  218. client = self.make_client(
  219. [
  220. MemcacheClientError("Whoops."),
  221. b'VALUE key 0 5\r\nvalue\r\nEND\r\n'
  222. ],
  223. attempts=2,
  224. do_not_retry_for=tuple([MemcacheUnknownError])
  225. )
  226. result = client.get("key")
  227. assert result == b'value'
  228. def test_do_not_retry_for_exception_fail(self):
  229. # Test that we do not retry for the exception specified.
  230. client = self.make_client(
  231. [
  232. MemcacheClientError("Whoops."),
  233. b'VALUE key 0 5\r\nvalue\r\nEND\r\n'
  234. ],
  235. attempts=2,
  236. do_not_retry_for=tuple([MemcacheClientError])
  237. )
  238. with pytest.raises(MemcacheClientError):
  239. client.get("key")
  240. def test_both_exception_filters(self):
  241. # Test interacction between both exception filters.
  242. client = self.make_client(
  243. [
  244. MemcacheClientError("Whoops."),
  245. b'VALUE key 0 5\r\nvalue\r\nEND\r\n',
  246. MemcacheUnknownError("Whoops."),
  247. b'VALUE key 0 5\r\nvalue\r\nEND\r\n',
  248. ],
  249. attempts=2,
  250. retry_for=tuple([MemcacheClientError]),
  251. do_not_retry_for=tuple([MemcacheUnknownError])
  252. )
  253. # Check that we succeed where allowed.
  254. result = client.get("key")
  255. assert result == b'value'
  256. # Check that no retries are attempted for the banned exception.
  257. with pytest.raises(MemcacheUnknownError):
  258. client.get("key")