From 75487a12d69d639df607d0dbf41e8a9ac6caf161 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 19 Oct 2015 14:15:15 -0400 Subject: [PATCH 01/13] Coverage for 'gcloud._apitools.http_wrapper._Httplib2DebugLevel'. --- gcloud/_apitools/http_wrapper.py | 3 +- gcloud/_apitools/test_http_wrapper.py | 77 +++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 gcloud/_apitools/test_http_wrapper.py diff --git a/gcloud/_apitools/http_wrapper.py b/gcloud/_apitools/http_wrapper.py index 7bfedd1ebdfe..c47b272b4217 100644 --- a/gcloud/_apitools/http_wrapper.py +++ b/gcloud/_apitools/http_wrapper.py @@ -91,8 +91,7 @@ def _Httplib2Debuglevel(http_request, level, http=None): httplib2.debuglevel = old_level if http is not None: for connection_key, old_level in http_levels.items(): - if connection_key in http.connections: - http.connections[connection_key].set_debuglevel(old_level) + http.connections[connection_key].set_debuglevel(old_level) class Request(object): diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py new file mode 100644 index 000000000000..98298a279f38 --- /dev/null +++ b/gcloud/_apitools/test_http_wrapper.py @@ -0,0 +1,77 @@ +# pylint: skip-file +import unittest2 + + +class Test__Httplib2Debuglevel(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud._apitools.http_wrapper import _Httplib2Debuglevel + return _Httplib2Debuglevel + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_wo_loggable_body_wo_http(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + + class _Request(object): + __slots__ = ('loggable_body',) # no other attrs + loggable_body = None + + request = _Request() + LEVEL = 1 + _httplib2 = _Dummy(debuglevel=0) + with _Monkey(MUT, httplib2=_httplib2): + with self._makeOne(request, LEVEL): + self.assertEqual(_httplib2.debuglevel, 0) + + def test_w_loggable_body_wo_http(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + + class _Request(object): + __slots__ = ('loggable_body',) # no other attrs + loggable_body = object() + + request = _Request() + LEVEL = 1 + _httplib2 = _Dummy(debuglevel=0) + with _Monkey(MUT, httplib2=_httplib2): + with self._makeOne(request, LEVEL): + self.assertEqual(_httplib2.debuglevel, LEVEL) + self.assertEqual(_httplib2.debuglevel, 0) + + def test_w_loggable_body_w_http(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + + class _Request(object): + __slots__ = ('loggable_body',) # no other attrs + loggable_body = object() + + class _Connection(object): + debuglevel = 0 + def set_debuglevel(self, value): + self.debuglevel = value + + request = _Request() + LEVEL = 1 + _httplib2 = _Dummy(debuglevel=0) + update_me = _Connection() + skip_me = _Connection() + connections = {'update:me': update_me, 'skip_me': skip_me} + _http = _Dummy(connections=connections) + with _Monkey(MUT, httplib2=_httplib2): + with self._makeOne(request, LEVEL, _http): + self.assertEqual(_httplib2.debuglevel, LEVEL) + self.assertEqual(update_me.debuglevel, LEVEL) + self.assertEqual(skip_me.debuglevel, 0) + self.assertEqual(_httplib2.debuglevel, 0) + self.assertEqual(update_me.debuglevel, 0) + self.assertEqual(skip_me.debuglevel, 0) + + +class _Dummy(object): + def __init__(self, **kw): + self.__dict__.update(kw) From 14e7dd5c71ca23e9d5e693d3a8a449d6352551df Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 19 Oct 2015 14:28:32 -0400 Subject: [PATCH 02/13] Coverage for 'gcloud._apitools.http_wrapper.Request'. --- gcloud/_apitools/test_http_wrapper.py | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py index 98298a279f38..be998d6edeba 100644 --- a/gcloud/_apitools/test_http_wrapper.py +++ b/gcloud/_apitools/test_http_wrapper.py @@ -72,6 +72,46 @@ def set_debuglevel(self, value): self.assertEqual(skip_me.debuglevel, 0) +class Test_Request(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud._apitools.http_wrapper import Request + return Request + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_ctor_defaults(self): + request = self._makeOne() + self.assertEqual(request.url, '') + self.assertEqual(request.http_method, 'GET') + self.assertEqual(request.headers, {'content-length': '0'}) + self.assertEqual(request.body, '') + self.assertEqual(request.loggable_body, None) + + def test_loggable_body_setter_w_body_None(self): + from gcloud._apitools.exceptions import RequestError + request = self._makeOne(body=None) + with self.assertRaises(RequestError): + request.loggable_body = 'abc' + + def test_body_setter_w_None(self): + request = self._makeOne() + request.loggable_body = 'abc' + request.body = None + self.assertEqual(request.headers, {}) + self.assertEqual(request.body, None) + self.assertEqual(request.loggable_body, 'abc') + + def test_body_setter_w_non_string(self): + request = self._makeOne() + request.loggable_body = 'abc' + request.body = body = _Dummy(length=123) + self.assertEqual(request.headers, {'content-length': '123'}) + self.assertTrue(request.body is body) + self.assertEqual(request.loggable_body, '') + + class _Dummy(object): def __init__(self, **kw): self.__dict__.update(kw) From 202afa90e6d77da352dc174a1b10944261405809 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 19 Oct 2015 15:02:42 -0400 Subject: [PATCH 03/13] Coverage for 'gcloud._apitools.http_wrapper.Response'. --- gcloud/_apitools/http_wrapper.py | 2 +- gcloud/_apitools/test_http_wrapper.py | 99 +++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/gcloud/_apitools/http_wrapper.py b/gcloud/_apitools/http_wrapper.py index c47b272b4217..6f1ab5b4e2dc 100644 --- a/gcloud/_apitools/http_wrapper.py +++ b/gcloud/_apitools/http_wrapper.py @@ -164,7 +164,7 @@ def ProcessContentRange(content_range): start, _, end = byte_range.partition('-') return int(end) - int(start) + 1 - if '-content-encoding' in self.info and 'content-range' in self.info: + if 'content-encoding' in self.info and 'content-range' in self.info: # httplib2 rewrites content-length in the case of a compressed # transfer; we can't trust the content-length header in that # case, but we *can* trust content-range, if it's present. diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py index be998d6edeba..30f4b6a3c18e 100644 --- a/gcloud/_apitools/test_http_wrapper.py +++ b/gcloud/_apitools/test_http_wrapper.py @@ -112,6 +112,105 @@ def test_body_setter_w_non_string(self): self.assertEqual(request.loggable_body, '') +class Test_Response(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud._apitools.http_wrapper import Response + return Response + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_ctor(self): + CONTENT = 'CONTENT' + URL = 'http://example.com/api' + info = {'status': '200'} + response = self._makeOne(info, CONTENT, URL) + self.assertEqual(len(response), len(CONTENT)) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.retry_after, None) + self.assertFalse(response.is_redirect) + + def test_length_w_content_encoding_w_content_range(self): + CONTENT = 'CONTENT' + URL = 'http://example.com/api' + RANGE = 'bytes 0-122/5678' + info = { + 'status': '200', + 'content-length': len(CONTENT), + 'content-encoding': 'testing', + 'content-range': RANGE, + } + response = self._makeOne(info, CONTENT, URL) + self.assertEqual(len(response), 123) + + def test_length_w_content_encoding_wo_content_range(self): + CONTENT = 'CONTENT' + URL = 'http://example.com/api' + info = { + 'status': '200', + 'content-length': len(CONTENT), + 'content-encoding': 'testing', + } + response = self._makeOne(info, CONTENT, URL) + self.assertEqual(len(response), len(CONTENT)) + + def test_length_w_content_length_w_content_range(self): + CONTENT = 'CONTENT' + URL = 'http://example.com/api' + RANGE = 'bytes 0-12/5678' + info = { + 'status': '200', + 'content-length': len(CONTENT) * 2, + 'content-range': RANGE, + } + response = self._makeOne(info, CONTENT, URL) + self.assertEqual(len(response), len(CONTENT) * 2) + + def test_length_wo_content_length_w_content_range(self): + CONTENT = 'CONTENT' + URL = 'http://example.com/api' + RANGE = 'bytes 0-122/5678' + info = { + 'status': '200', + 'content-range': RANGE, + } + response = self._makeOne(info, CONTENT, URL) + self.assertEqual(len(response), 123) + + def test_retry_after_w_header(self): + CONTENT = 'CONTENT' + URL = 'http://example.com/api' + RANGE = 'bytes 0-122/5678' + info = { + 'status': '200', + 'retry-after': '123', + } + response = self._makeOne(info, CONTENT, URL) + self.assertEqual(response.retry_after, 123) + + def test_is_redirect_w_code_wo_location(self): + CONTENT = 'CONTENT' + URL = 'http://example.com/api' + RANGE = 'bytes 0-122/5678' + info = { + 'status': '301', + } + response = self._makeOne(info, CONTENT, URL) + self.assertFalse(response.is_redirect) + + def test_is_redirect_w_code_w_location(self): + CONTENT = 'CONTENT' + URL = 'http://example.com/api' + RANGE = 'bytes 0-122/5678' + info = { + 'status': '301', + 'location': 'http://example.com/other', + } + response = self._makeOne(info, CONTENT, URL) + self.assertTrue(response.is_redirect) + + class _Dummy(object): def __init__(self, **kw): self.__dict__.update(kw) From 8b71cfaeb84f9378ba0c6a9840576a63be717106 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 19 Oct 2015 15:24:22 -0400 Subject: [PATCH 04/13] Coverage for 'gcloud._apitools.http_wrapper.CheckResponse'. --- gcloud/_apitools/http_wrapper.py | 3 +- gcloud/_apitools/test_http_wrapper.py | 47 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/gcloud/_apitools/http_wrapper.py b/gcloud/_apitools/http_wrapper.py index 6f1ab5b4e2dc..61e1bf66afe7 100644 --- a/gcloud/_apitools/http_wrapper.py +++ b/gcloud/_apitools/http_wrapper.py @@ -194,8 +194,7 @@ def CheckResponse(response): if response is None: # Caller shouldn't call us if the response is None, but handle anyway. raise exceptions.RequestError( - 'Request to url %s did not return a response.' % - response.request_url) + 'Request did not return a response.') elif (response.status_code >= 500 or response.status_code == TOO_MANY_REQUESTS): raise exceptions.BadStatusCodeError.FromResponse(response) diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py index 30f4b6a3c18e..a5d6339752b1 100644 --- a/gcloud/_apitools/test_http_wrapper.py +++ b/gcloud/_apitools/test_http_wrapper.py @@ -211,6 +211,53 @@ def test_is_redirect_w_code_w_location(self): self.assertTrue(response.is_redirect) +class Test_CheckResponse(unittest2.TestCase): + + def _callFUT(self, *args, **kw): + from gcloud._apitools.http_wrapper import CheckResponse + return CheckResponse(*args, **kw) + + def test_w_none(self): + from gcloud._apitools.exceptions import RequestError + with self.assertRaises(RequestError): + self._callFUT(None) + + def test_w_TOO_MANY_REQUESTS(self): + from gcloud._apitools.exceptions import BadStatusCodeError + from gcloud._apitools.http_wrapper import TOO_MANY_REQUESTS + + with self.assertRaises(BadStatusCodeError): + self._callFUT(_Response(TOO_MANY_REQUESTS)) + + def test_w_50x(self): + from gcloud._apitools.exceptions import BadStatusCodeError + + with self.assertRaises(BadStatusCodeError): + self._callFUT(_Response(500)) + + with self.assertRaises(BadStatusCodeError): + self._callFUT(_Response(503)) + + def test_w_retry_after(self): + from gcloud._apitools.exceptions import RetryAfterError + + with self.assertRaises(RetryAfterError): + self._callFUT(_Response(200, 20)) + + def test_pass(self): + self._callFUT(_Response(200)) + + class _Dummy(object): def __init__(self, **kw): self.__dict__.update(kw) + + +class _Response(object): + content = '' + request_url = 'http://example.com/api' + + def __init__(self, status_code, retry_after=None): + self.info = {} + self.status_code = status_code + self.retry_after = retry_after From de0a158ac6289f3bab8162b86e9e9600e3d2e2dd Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 19 Oct 2015 15:29:53 -0400 Subject: [PATCH 05/13] Coverage for 'gcloud._apitools.http_wrapper.RebuildHttpConnections'. --- gcloud/_apitools/test_http_wrapper.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py index a5d6339752b1..8b58c9a8cfe3 100644 --- a/gcloud/_apitools/test_http_wrapper.py +++ b/gcloud/_apitools/test_http_wrapper.py @@ -248,6 +248,24 @@ def test_pass(self): self._callFUT(_Response(200)) +class Test_RebuildHttpConnections(unittest2.TestCase): + + def _callFUT(self, *args, **kw): + from gcloud._apitools.http_wrapper import RebuildHttpConnections + return RebuildHttpConnections(*args, **kw) + + def test_wo_connections(self): + http = object() + self._callFUT(http) + + def test_w_connections(self): + connections = {'delete:me': object(), 'skip_me': object()} + http = _Dummy(connections=connections) + self._callFUT(http) + self.assertFalse('delete:me' in connections) + self.assertTrue('skip_me' in connections) + + class _Dummy(object): def __init__(self, **kw): self.__dict__.update(kw) From a742950e76685f562bdd429936e7e22a400c8800 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 19 Oct 2015 15:31:37 -0400 Subject: [PATCH 06/13] Delete unused 'gcloud._apitools.http_wrapper.RethrowExceptionHandler'. --- gcloud/_apitools/http_wrapper.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gcloud/_apitools/http_wrapper.py b/gcloud/_apitools/http_wrapper.py index 61e1bf66afe7..c4c00078bde6 100644 --- a/gcloud/_apitools/http_wrapper.py +++ b/gcloud/_apitools/http_wrapper.py @@ -27,7 +27,6 @@ 'RebuildHttpConnections', 'Request', 'Response', - 'RethrowExceptionHandler', ] @@ -221,10 +220,6 @@ def RebuildHttpConnections(http): del http.connections[conn_key] -def RethrowExceptionHandler(*unused_args): - raise - - def HandleExceptionsAndRebuildHttpConnections(retry_args): """Exception handler for http failures. From 4101028ed7b039a51384be355da9299cf3609d36 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 19 Oct 2015 17:15:00 -0400 Subject: [PATCH 07/13] Coverage for 'gcloud._apitools.http_wrapper.HandleExceptionsAndRebuildHttpConnections'. --- gcloud/_apitools/http_wrapper.py | 4 +- gcloud/_apitools/test_http_wrapper.py | 246 +++++++++++++++++++++++++- 2 files changed, 247 insertions(+), 3 deletions(-) diff --git a/gcloud/_apitools/http_wrapper.py b/gcloud/_apitools/http_wrapper.py index c4c00078bde6..837c981302d7 100644 --- a/gcloud/_apitools/http_wrapper.py +++ b/gcloud/_apitools/http_wrapper.py @@ -238,14 +238,14 @@ def HandleExceptionsAndRebuildHttpConnections(retry_args): http_client.ResponseNotReady)): logging.debug('Caught HTTP error %s, retrying: %s', type(retry_args.exc).__name__, retry_args.exc) - elif isinstance(retry_args.exc, socket.error): - logging.debug('Caught socket error, retrying: %s', retry_args.exc) elif isinstance(retry_args.exc, socket.gaierror): logging.debug( 'Caught socket address error, retrying: %s', retry_args.exc) elif isinstance(retry_args.exc, socket.timeout): logging.debug( 'Caught socket timeout error, retrying: %s', retry_args.exc) + elif isinstance(retry_args.exc, socket.error): + logging.debug('Caught socket error, retrying: %s', retry_args.exc) elif isinstance(retry_args.exc, httplib2.ServerNotFoundError): logging.debug( 'Caught server not found error, retrying: %s', retry_args.exc) diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py index 8b58c9a8cfe3..9fd699d462d7 100644 --- a/gcloud/_apitools/test_http_wrapper.py +++ b/gcloud/_apitools/test_http_wrapper.py @@ -266,6 +266,250 @@ def test_w_connections(self): self.assertTrue('skip_me' in connections) +class Test_HandleExceptionsAndRebuildHttpConnections(unittest2.TestCase): + URL = 'http://example.com/api' + + def _callFUT(self, *args, **kw): + from gcloud._apitools.http_wrapper import ( + HandleExceptionsAndRebuildHttpConnections) + return HandleExceptionsAndRebuildHttpConnections(*args, **kw) + + def _monkeyMUT(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + _logged = [] + + def _debug(msg, *args): + _logged.append((msg, args)) + + _logging = _Dummy(debug=_debug) + _slept = [] + + def _sleep(value): + _slept.append(value) + + _time = _Dummy(sleep=_sleep) + monkey = _Monkey(MUT, logging=_logging, time=_time) + return monkey, _logged, _slept + + def _build_retry_args(self, exc, + url=URL, num_retries=0, max_retry_wait=10): + retry_args = _Dummy(exc=exc, + num_retries=num_retries, + max_retry_wait=max_retry_wait) + retry_args.http_request = _Dummy(url=url) + connections = {'delete:me': object(), 'skip_me': object()} + retry_args.http = _Dummy(connections=connections) + return retry_args + + def _verify_logged_slept(self, logged, slept, + expected_msg, expected_args, expected_sleep=1.25): + self.assertEqual(len(logged), 2) + + msg, args = logged[0] + self.assertEqual(msg, expected_msg) + self.assertEqual(args, expected_args) + + msg, args = logged[1] + self.assertEqual(msg, 'Retrying request to url %s after exception %s') + self.assertEqual(len(args), 2) + self.assertEqual(args[0], self.URL) + + self.assertEqual(slept, [expected_sleep]) + + def test_w_BadStatusLine(self): + import random + from gcloud._testing import _Monkey + from six.moves.http_client import BadStatusLine + exc = BadStatusLine('invalid') + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, 'Caught HTTP error %s, retrying: %s', + ('BadStatusLine', exc)) + + def test_w_IncompleteRead(self): + import random + from gcloud._testing import _Monkey + from six.moves.http_client import IncompleteRead + exc = IncompleteRead(50, 100) + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, 'Caught HTTP error %s, retrying: %s', + ('IncompleteRead', exc)) + + def test_w_ResponseNotReady(self): + import random + from gcloud._testing import _Monkey + from six.moves.http_client import ResponseNotReady + exc = ResponseNotReady('uh oh') + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, 'Caught HTTP error %s, retrying: %s', + ('ResponseNotReady', exc)) + + def test_w_socket_gaierror(self): + import random + from gcloud._testing import _Monkey + import socket + exc = socket.gaierror('uh oh') + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, 'Caught socket address error, retrying: %s', (exc,)) + + def test_w_socket_timeout(self): + import random + from gcloud._testing import _Monkey + import socket + exc = socket.timeout('uh oh') + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, 'Caught socket timeout error, retrying: %s', (exc,)) + + def test_w_socket_error(self): + import random + from gcloud._testing import _Monkey + import socket + exc = socket.error('uh oh') + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, 'Caught socket error, retrying: %s', (exc,)) + + def test_w_httplib2_ServerNotFoundError(self): + import random + from gcloud._testing import _Monkey + import httplib2 + exc = httplib2.ServerNotFoundError('uh oh') + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, + 'Caught server not found error, retrying: %s', (exc,)) + + def test_w_ValueError(self): + import random + from gcloud._testing import _Monkey + exc = ValueError('uh oh') + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, + 'Response content was invalid (%s), retrying', (exc,)) + + def test_w_RequestError(self): + import random + from gcloud._testing import _Monkey + from gcloud._apitools.exceptions import RequestError + exc = RequestError('uh oh') + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, 'Request returned no response, retrying', ()) + + def test_w_BadStatusCodeError(self): + import random + from gcloud._testing import _Monkey + from gcloud._apitools.exceptions import BadStatusCodeError + response = _Response(500) + exc = BadStatusCodeError.FromResponse(response) + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, 'Response returned status %s, retrying', (500,)) + + def test_w_RetryAfterError(self): + import random + from gcloud._testing import _Monkey + from gcloud._apitools.exceptions import RetryAfterError + from gcloud._apitools.http_wrapper import TOO_MANY_REQUESTS + RETRY_AFTER = 25 + response = _Response(TOO_MANY_REQUESTS, RETRY_AFTER) + exc = RetryAfterError.FromResponse(response) + retry_args = self._build_retry_args(exc) + monkey, logged, slept = self._monkeyMUT() + + with _Monkey(random, uniform=lambda lower, upper: upper): + with monkey: + self._callFUT(retry_args) + + self._verify_logged_slept( + logged, slept, + 'Response returned a retry-after header, retrying', (), RETRY_AFTER) + + def test_wo_matching_type(self): + + class _Nonesuch(Exception): + pass + + def _raises(): + raise _Nonesuch + + monkey, logged, slept = self._monkeyMUT() + + with monkey: + with self.assertRaises(_Nonesuch): + try: + _raises() + except Exception as exc: + retry_args = _Dummy(exc=exc) + self._callFUT(retry_args) + + class _Dummy(object): def __init__(self, **kw): self.__dict__.update(kw) @@ -276,6 +520,6 @@ class _Response(object): request_url = 'http://example.com/api' def __init__(self, status_code, retry_after=None): - self.info = {} + self.info = {'status': status_code} self.status_code = status_code self.retry_after = retry_after From 5c202292ccc0cacf8ad9aa652c240f3fbefc4e81 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 19 Oct 2015 17:34:49 -0400 Subject: [PATCH 08/13] Allow passing 'wo_retry_func' to 'gcloud._apitools.http_wrapper.MakeRequest'. Separates testing concerns. --- gcloud/_apitools/http_wrapper.py | 91 +++++++++++++++++--------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/gcloud/_apitools/http_wrapper.py b/gcloud/_apitools/http_wrapper.py index 837c981302d7..fa5c24032f96 100644 --- a/gcloud/_apitools/http_wrapper.py +++ b/gcloud/_apitools/http_wrapper.py @@ -274,50 +274,6 @@ def HandleExceptionsAndRebuildHttpConnections(retry_args): retry_args.num_retries, max_wait=retry_args.max_retry_wait)) -def MakeRequest(http, http_request, retries=7, max_retry_wait=60, - redirections=5, - retry_func=HandleExceptionsAndRebuildHttpConnections, - check_response_func=CheckResponse): - """Send http_request via the given http, performing error/retry handling. - - Args: - http: An httplib2.Http instance, or a http multiplexer that delegates to - an underlying http, for example, HTTPMultiplexer. - http_request: A Request to send. - retries: (int, default 7) Number of retries to attempt on retryable - replies (such as 429 or 5XX). - max_retry_wait: (int, default 60) Maximum number of seconds to wait - when retrying. - redirections: (int, default 5) Number of redirects to follow. - retry_func: Function to handle retries on exceptions. Arguments are - (Httplib2.Http, Request, Exception, int num_retries). - check_response_func: Function to validate the HTTP response. - Arguments are (Response, response content, url). - - Raises: - InvalidDataFromServerError: if there is no response after retries. - - Returns: - A Response object. - - """ - retry = 0 - while True: - try: - return _MakeRequestNoRetry( - http, http_request, redirections=redirections, - check_response_func=check_response_func) - # retry_func will consume the exception types it handles and raise. - # pylint: disable=broad-except - except Exception as e: - retry += 1 - if retry >= retries: - raise - else: - retry_func(ExceptionRetryArgs( - http, http_request, e, retry, max_retry_wait)) - - def _MakeRequestNoRetry(http, http_request, redirections=5, check_response_func=CheckResponse): """Send http_request via the given http. @@ -365,6 +321,53 @@ def _MakeRequestNoRetry(http, http_request, redirections=5, return response +def MakeRequest(http, http_request, retries=7, max_retry_wait=60, + redirections=5, + retry_func=HandleExceptionsAndRebuildHttpConnections, + check_response_func=CheckResponse, + wo_retry_func=_MakeRequestNoRetry): + """Send http_request via the given http, performing error/retry handling. + + Args: + http: An httplib2.Http instance, or a http multiplexer that delegates to + an underlying http, for example, HTTPMultiplexer. + http_request: A Request to send. + retries: (int, default 7) Number of retries to attempt on retryable + replies (such as 429 or 5XX). + max_retry_wait: (int, default 60) Maximum number of seconds to wait + when retrying. + redirections: (int, default 5) Number of redirects to follow. + retry_func: Function to handle retries on exceptions. Arguments are + (Httplib2.Http, Request, Exception, int num_retries). + check_response_func: Function to validate the HTTP response. + Arguments are (Response, response content, url). + wo_retry_func: Function to make HTTP request without retries. Arguments + are: (http, http_request, redirections, check_response_func) + + Raises: + InvalidDataFromServerError: if there is no response after retries. + + Returns: + A Response object. + + """ + retry = 0 + while True: + try: + return wo_retry_func( + http, http_request, redirections=redirections, + check_response_func=check_response_func) + # retry_func will consume the exception types it handles and raise. + # pylint: disable=broad-except + except Exception as e: + retry += 1 + if retry >= retries: + raise + else: + retry_func(ExceptionRetryArgs( + http, http_request, e, retry, max_retry_wait)) + + _HTTP_FACTORIES = [] From e9d1df84d67d3e4ef7697be0595bf97fb739e07b Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 20 Oct 2015 11:25:16 -0400 Subject: [PATCH 09/13] Coverage for 'gcloud._apitools.http_wrapper._MakeRequestNoRetry'. --- gcloud/_apitools/test_http_wrapper.py | 163 +++++++++++++++++++++++--- 1 file changed, 148 insertions(+), 15 deletions(-) diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py index 9fd699d462d7..509cdcd966e5 100644 --- a/gcloud/_apitools/test_http_wrapper.py +++ b/gcloud/_apitools/test_http_wrapper.py @@ -15,10 +15,6 @@ def test_wo_loggable_body_wo_http(self): from gcloud._testing import _Monkey from gcloud._apitools import http_wrapper as MUT - class _Request(object): - __slots__ = ('loggable_body',) # no other attrs - loggable_body = None - request = _Request() LEVEL = 1 _httplib2 = _Dummy(debuglevel=0) @@ -30,11 +26,7 @@ def test_w_loggable_body_wo_http(self): from gcloud._testing import _Monkey from gcloud._apitools import http_wrapper as MUT - class _Request(object): - __slots__ = ('loggable_body',) # no other attrs - loggable_body = object() - - request = _Request() + request = _Request(loggable_body=object()) LEVEL = 1 _httplib2 = _Dummy(debuglevel=0) with _Monkey(MUT, httplib2=_httplib2): @@ -46,16 +38,12 @@ def test_w_loggable_body_w_http(self): from gcloud._testing import _Monkey from gcloud._apitools import http_wrapper as MUT - class _Request(object): - __slots__ = ('loggable_body',) # no other attrs - loggable_body = object() - class _Connection(object): debuglevel = 0 def set_debuglevel(self, value): self.debuglevel = value - request = _Request() + request = _Request(loggable_body=object()) LEVEL = 1 _httplib2 = _Dummy(debuglevel=0) update_me = _Connection() @@ -510,16 +498,161 @@ def _raises(): self._callFUT(retry_args) +class Test__MakeRequestNoRetry(unittest2.TestCase): + + def _callFUT(self, *args, **kw): + from gcloud._apitools.http_wrapper import _MakeRequestNoRetry + return _MakeRequestNoRetry(*args, **kw) + + def _verify_requested(self, http, request, + redirections=5, connection_type=None): + self.assertEqual(len(http._requested), 1) + url, kw = http._requested[0] + self.assertEqual(url, request.url) + self.assertEqual(kw['method'], request.http_method) + self.assertEqual(kw['body'], request.body) + self.assertEqual(kw['headers'], request.headers) + self.assertEqual(kw['redirections'], redirections) + self.assertEqual(kw['connection_type'], connection_type) + + def test_defaults_wo_connections(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + INFO = {'status': '200'} + CONTENT = 'CONTENT' + _http = _Http((INFO, CONTENT)) + _httplib2 = _Dummy(debuglevel=1) + _request = _Request() + _checked = [] + with _Monkey(MUT, httplib2=_httplib2): + response = self._callFUT(_http, _request, + check_response_func=_checked.append) + self.assertTrue(isinstance(response, MUT.Response)) + self.assertEqual(response.info, INFO) + self.assertEqual(response.content, CONTENT) + self.assertEqual(response.request_url, _request.url) + self.assertEqual(_checked, [response]) + self._verify_requested(_http, _request) + + def test_w_explicit_redirections(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + INFO = {'status': '200'} + CONTENT = 'CONTENT' + _http = _Http((INFO, CONTENT)) + _httplib2 = _Dummy(debuglevel=1) + _request = _Request() + _checked = [] + with _Monkey(MUT, httplib2=_httplib2): + response = self._callFUT(_http, _request, + redirections=10, + check_response_func=_checked.append) + self.assertTrue(isinstance(response, MUT.Response)) + self.assertEqual(response.info, INFO) + self.assertEqual(response.content, CONTENT) + self.assertEqual(response.request_url, _request.url) + self.assertEqual(_checked, [response]) + self._verify_requested(_http, _request, redirections=10) + + def test_w_http_connections_miss(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + INFO = {'status': '200'} + CONTENT = 'CONTENT' + CONN_TYPE = object() + _http = _Http((INFO, CONTENT)) + _http.connections = {'https': CONN_TYPE} + _httplib2 = _Dummy(debuglevel=1) + _request = _Request() + _checked = [] + with _Monkey(MUT, httplib2=_httplib2): + response = self._callFUT(_http, _request, + check_response_func=_checked.append) + self.assertTrue(isinstance(response, MUT.Response)) + self.assertEqual(response.info, INFO) + self.assertEqual(response.content, CONTENT) + self.assertEqual(response.request_url, _request.url) + self.assertEqual(_checked, [response]) + self._verify_requested(_http, _request) + + def test_w_http_connections_hit(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + INFO = {'status': '200'} + CONTENT = 'CONTENT' + CONN_TYPE = object() + _http = _Http((INFO, CONTENT)) + _http.connections = {'http': CONN_TYPE} + _httplib2 = _Dummy(debuglevel=1) + _request = _Request() + _checked = [] + with _Monkey(MUT, httplib2=_httplib2): + response = self._callFUT(_http, _request, + check_response_func=_checked.append) + self.assertTrue(isinstance(response, MUT.Response)) + self.assertEqual(response.info, INFO) + self.assertEqual(response.content, CONTENT) + self.assertEqual(response.request_url, _request.url) + self.assertEqual(_checked, [response]) + self._verify_requested(_http, _request, connection_type=CONN_TYPE) + + def test_w_request_returning_None(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + from gcloud._apitools.exceptions import RequestError + INFO = None + CONTENT = None + CONN_TYPE = object() + _http = _Http((INFO, CONTENT)) + _http.connections = {'http': CONN_TYPE} + _httplib2 = _Dummy(debuglevel=1) + _request = _Request() + with _Monkey(MUT, httplib2=_httplib2): + with self.assertRaises(RequestError): + self._callFUT(_http, _request) + self._verify_requested(_http, _request, connection_type=CONN_TYPE) + + class _Dummy(object): def __init__(self, **kw): self.__dict__.update(kw) +class _Request(object): + __slots__ = ('url', 'http_method', 'body', 'headers', 'loggable_body',) + URL = 'http://example.com/api' + + def __init__(self, url=URL, http_method='GET', body='', headers=None, + loggable_body=None): + self.url = url + self.http_method = http_method + self.body = body + if headers is None: + headers = {} + self.headers = headers + self.loggable_body = loggable_body + + class _Response(object): content = '' - request_url = 'http://example.com/api' + request_url = _Request.URL def __init__(self, status_code, retry_after=None): self.info = {'status': status_code} self.status_code = status_code self.retry_after = retry_after + + +class _Http(object): + + def __init__(self, *responses): + self._responses = responses + self._requested = [] + + def request(self, url, **kw): + from gcloud._apitools.exceptions import NotFoundError + self._requested.append((url, kw)) + if len(self._responses) == 0: + raise NotFoundError(url) + response, self._responses = self._responses[0], self._responses[1:] + return response From 582c1b74f0b26ef82021eeb0990a42057a71576b Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 20 Oct 2015 12:07:04 -0400 Subject: [PATCH 10/13] Coverage for 'gcloud._apitools.http_wrapper.MakeRequest'. --- gcloud/_apitools/test_http_wrapper.py | 113 ++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py index 509cdcd966e5..fdf9cdd5e86c 100644 --- a/gcloud/_apitools/test_http_wrapper.py +++ b/gcloud/_apitools/test_http_wrapper.py @@ -613,6 +613,119 @@ def test_w_request_returning_None(self): self._verify_requested(_http, _request, connection_type=CONN_TYPE) +class Test_MakeRequest(unittest2.TestCase): + + def _callFUT(self, *args, **kw): + from gcloud._apitools.http_wrapper import MakeRequest + return MakeRequest(*args, **kw) + + def test_wo_exception(self): + HTTP, REQUEST, RESPONSE = object(), object(), object() + _created, _checked = [], [] + + def _wo_exception(*args, **kw): + _created.append((args, kw)) + return RESPONSE + + response = self._callFUT(HTTP, REQUEST, + wo_retry_func=_wo_exception, + check_response_func=_checked.append) + + self.assertTrue(response is RESPONSE) + self.assertEqual(_created, + [((HTTP, REQUEST), { + 'redirections': 5, + 'check_response_func': _checked.append, + })]) + self.assertEqual(_checked, []) # not called by '_wo_exception' + + def test_w_exceptions_lt_max_retries(self): + HTTP, REQUEST, RESPONSE = object(), object(), object() + WAIT = 10, + _created, _checked, _retried = [], [], [] + _counter = [None] * 4 + + class _Retry(Exception): + pass + + def _wo_exception(*args, **kw): + _created.append((args, kw)) + if _counter: + _counter.pop() + raise _Retry() + return RESPONSE + + def _retry(args): + _retried.append(args) + + response = self._callFUT(HTTP, REQUEST, + retries=5, + max_retry_wait=WAIT, + retry_func=_retry, + wo_retry_func=_wo_exception, + check_response_func=_checked.append) + + self.assertTrue(response is RESPONSE) + self.assertEqual(len(_created), 5) + for attempt in _created: + self.assertEqual(attempt, + ((HTTP, REQUEST), { + 'redirections': 5, + 'check_response_func': _checked.append, + })) + self.assertEqual(_checked, []) # not called by '_wo_exception' + self.assertEqual(len(_retried), 4) + for index, retry in enumerate(_retried): + self.assertTrue(retry.http is HTTP) + self.assertTrue(retry.http_request is REQUEST) + self.assertTrue(isinstance(retry.exc, _Retry)) + self.assertEqual(retry.num_retries, index + 1) + self.assertEqual(retry.max_retry_wait, WAIT) + + def test_w_exceptions_gt_max_retries(self): + HTTP, REQUEST, RESPONSE = object(), object(), object() + WAIT = 10, + _created, _checked, _retried = [], [], [] + _counter = [None] * 4 + + class _Retry(Exception): + pass + + def _wo_exception(*args, **kw): + _created.append((args, kw)) + if _counter: + _counter.pop() + raise _Retry() + return RESPONSE + + def _retry(args): + _retried.append(args) + + with self.assertRaises(_Retry): + self._callFUT(HTTP, REQUEST, + retries=3, + max_retry_wait=WAIT, + retry_func=_retry, + wo_retry_func=_wo_exception, + check_response_func=_checked.append) + + self.assertEqual(len(_created), 3) + for attempt in _created: + self.assertEqual(attempt, + ((HTTP, REQUEST), { + 'redirections': 5, + 'check_response_func': _checked.append, + })) + self.assertEqual(_checked, []) # not called by '_wo_exception' + self.assertEqual(len(_retried), 2) + for index, retry in enumerate(_retried): + self.assertTrue(retry.http is HTTP) + self.assertTrue(retry.http_request is REQUEST) + self.assertTrue(isinstance(retry.exc, _Retry)) + self.assertEqual(retry.num_retries, index + 1) + self.assertEqual(retry.max_retry_wait, WAIT) + + class _Dummy(object): def __init__(self, **kw): self.__dict__.update(kw) From 4e09bea3da261497b524ec1b26a1aca80c509737 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 20 Oct 2015 12:10:06 -0400 Subject: [PATCH 11/13] Coverage for 'gcloud._apitools.http_wrapper.RegisterHttpFactory'. --- gcloud/_apitools/test_http_wrapper.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py index fdf9cdd5e86c..3fde7c133452 100644 --- a/gcloud/_apitools/test_http_wrapper.py +++ b/gcloud/_apitools/test_http_wrapper.py @@ -726,6 +726,22 @@ def _retry(args): self.assertEqual(retry.max_retry_wait, WAIT) +class Test_MakeRequest(unittest2.TestCase): + + def _callFUT(self, *args, **kw): + from gcloud._apitools.http_wrapper import _RegisterHttpFactory + return _RegisterHttpFactory(*args, **kw) + + def test_it(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + _factories = [] + def _factory(**kw): + pass + with _Monkey(MUT, _HTTP_FACTORIES=_factories): + self._callFUT(_factory) + self.assertEqual(_factories, [_factory]) + class _Dummy(object): def __init__(self, **kw): self.__dict__.update(kw) From f97ede251c84c490ab25ecf84022fc475d73f614 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 20 Oct 2015 12:15:12 -0400 Subject: [PATCH 12/13] Coverage for 'gcloud._apitools.http_wrapper.GetHttp'. --- gcloud/_apitools/test_http_wrapper.py | 49 ++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py index 3fde7c133452..ae2aacb1032c 100644 --- a/gcloud/_apitools/test_http_wrapper.py +++ b/gcloud/_apitools/test_http_wrapper.py @@ -726,7 +726,7 @@ def _retry(args): self.assertEqual(retry.max_retry_wait, WAIT) -class Test_MakeRequest(unittest2.TestCase): +class Test__RegisterHttpFactory(unittest2.TestCase): def _callFUT(self, *args, **kw): from gcloud._apitools.http_wrapper import _RegisterHttpFactory @@ -736,12 +736,59 @@ def test_it(self): from gcloud._testing import _Monkey from gcloud._apitools import http_wrapper as MUT _factories = [] + def _factory(**kw): pass + with _Monkey(MUT, _HTTP_FACTORIES=_factories): self._callFUT(_factory) self.assertEqual(_factories, [_factory]) + +class Test_GetHttp(unittest2.TestCase): + + def _callFUT(self, *args, **kw): + from gcloud._apitools.http_wrapper import GetHttp + return GetHttp(*args, **kw) + + def test_wo_registered_factories(self): + from httplib2 import Http + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + _factories = [] + + with _Monkey(MUT, _HTTP_FACTORIES=_factories): + http = self._callFUT() + + self.assertTrue(isinstance(http, Http)) + + def test_w_registered_factories(self): + from gcloud._testing import _Monkey + from gcloud._apitools import http_wrapper as MUT + + FOUND = object() + + _misses = [] + + def _miss(**kw): + _misses.append(kw) + return None + + _hits = [] + + def _hit(**kw): + _hits.append(kw) + return FOUND + + _factories = [_miss, _hit] + + with _Monkey(MUT, _HTTP_FACTORIES=_factories): + http = self._callFUT(foo='bar') + + self.assertTrue(http is FOUND) + self.assertEqual(_misses, [{'foo': 'bar'}]) + self.assertEqual(_hits, [{'foo': 'bar'}]) + class _Dummy(object): def __init__(self, **kw): self.__dict__.update(kw) From b85afdea180379a6735f1fdf0fac6370f73a36be Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 20 Oct 2015 12:35:03 -0400 Subject: [PATCH 13/13] Branch coverage fixups in tests. Also, fix pep8 breakage. --- gcloud/_apitools/test_http_wrapper.py | 42 ++++++++++----------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/gcloud/_apitools/test_http_wrapper.py b/gcloud/_apitools/test_http_wrapper.py index ae2aacb1032c..947eea9032ce 100644 --- a/gcloud/_apitools/test_http_wrapper.py +++ b/gcloud/_apitools/test_http_wrapper.py @@ -40,6 +40,7 @@ def test_w_loggable_body_w_http(self): class _Connection(object): debuglevel = 0 + def set_debuglevel(self, value): self.debuglevel = value @@ -461,8 +462,6 @@ def test_w_BadStatusCodeError(self): logged, slept, 'Response returned status %s, retrying', (500,)) def test_w_RetryAfterError(self): - import random - from gcloud._testing import _Monkey from gcloud._apitools.exceptions import RetryAfterError from gcloud._apitools.http_wrapper import TOO_MANY_REQUESTS RETRY_AFTER = 25 @@ -471,13 +470,13 @@ def test_w_RetryAfterError(self): retry_args = self._build_retry_args(exc) monkey, logged, slept = self._monkeyMUT() - with _Monkey(random, uniform=lambda lower, upper: upper): - with monkey: - self._callFUT(retry_args) + with monkey: + self._callFUT(retry_args) self._verify_logged_slept( logged, slept, - 'Response returned a retry-after header, retrying', (), RETRY_AFTER) + 'Response returned a retry-after header, retrying', (), + RETRY_AFTER) def test_wo_matching_type(self): @@ -669,10 +668,10 @@ def _retry(args): self.assertEqual(len(_created), 5) for attempt in _created: self.assertEqual(attempt, - ((HTTP, REQUEST), { + ((HTTP, REQUEST), { 'redirections': 5, 'check_response_func': _checked.append, - })) + })) self.assertEqual(_checked, []) # not called by '_wo_exception' self.assertEqual(len(_retried), 4) for index, retry in enumerate(_retried): @@ -686,17 +685,13 @@ def test_w_exceptions_gt_max_retries(self): HTTP, REQUEST, RESPONSE = object(), object(), object() WAIT = 10, _created, _checked, _retried = [], [], [] - _counter = [None] * 4 class _Retry(Exception): pass def _wo_exception(*args, **kw): _created.append((args, kw)) - if _counter: - _counter.pop() - raise _Retry() - return RESPONSE + raise _Retry() def _retry(args): _retried.append(args) @@ -712,10 +707,10 @@ def _retry(args): self.assertEqual(len(_created), 3) for attempt in _created: self.assertEqual(attempt, - ((HTTP, REQUEST), { + ((HTTP, REQUEST), { 'redirections': 5, 'check_response_func': _checked.append, - })) + })) self.assertEqual(_checked, []) # not called by '_wo_exception' self.assertEqual(len(_retried), 2) for index, retry in enumerate(_retried): @@ -737,12 +732,11 @@ def test_it(self): from gcloud._apitools import http_wrapper as MUT _factories = [] - def _factory(**kw): - pass + FACTORY = object() with _Monkey(MUT, _HTTP_FACTORIES=_factories): - self._callFUT(_factory) - self.assertEqual(_factories, [_factory]) + self._callFUT(FACTORY) + self.assertEqual(_factories, [FACTORY]) class Test_GetHttp(unittest2.TestCase): @@ -789,6 +783,7 @@ def _hit(**kw): self.assertEqual(_misses, [{'foo': 'bar'}]) self.assertEqual(_hits, [{'foo': 'bar'}]) + class _Dummy(object): def __init__(self, **kw): self.__dict__.update(kw) @@ -798,14 +793,12 @@ class _Request(object): __slots__ = ('url', 'http_method', 'body', 'headers', 'loggable_body',) URL = 'http://example.com/api' - def __init__(self, url=URL, http_method='GET', body='', headers=None, + def __init__(self, url=URL, http_method='GET', body='', loggable_body=None): self.url = url self.http_method = http_method self.body = body - if headers is None: - headers = {} - self.headers = headers + self.headers = {} self.loggable_body = loggable_body @@ -826,9 +819,6 @@ def __init__(self, *responses): self._requested = [] def request(self, url, **kw): - from gcloud._apitools.exceptions import NotFoundError self._requested.append((url, kw)) - if len(self._responses) == 0: - raise NotFoundError(url) response, self._responses = self._responses[0], self._responses[1:] return response