| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # Copyright 2013 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 import logging | |
| 7 import math | |
| 8 import os | |
| 9 import sys | |
| 10 import unittest | |
| 11 | |
| 12 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| 13 sys.path.insert(0, ROOT_DIR) | |
| 14 | |
| 15 import auto_stub | |
| 16 from utils import net | |
| 17 | |
| 18 | |
| 19 class RetryLoopMockedTest(auto_stub.TestCase): | |
| 20 """Base class for test cases that mock retry loop.""" | |
| 21 | |
| 22 def setUp(self): | |
| 23 super(RetryLoopMockedTest, self).setUp() | |
| 24 self._retry_attemps_cls = net.RetryAttempt | |
| 25 self.mock(net, 'sleep_before_retry', self.mocked_sleep_before_retry) | |
| 26 self.mock(net, 'current_time', self.mocked_current_time) | |
| 27 self.mock(net, 'RetryAttempt', self.mocked_retry_attempt) | |
| 28 self.sleeps = [] | |
| 29 self.attempts = [] | |
| 30 | |
| 31 def mocked_sleep_before_retry(self, attempt, max_wait): | |
| 32 self.sleeps.append((attempt, max_wait)) | |
| 33 | |
| 34 def mocked_current_time(self): | |
| 35 # One attempt is one virtual second. | |
| 36 return float(len(self.attempts)) | |
| 37 | |
| 38 def mocked_retry_attempt(self, *args, **kwargs): | |
| 39 attempt = self._retry_attemps_cls(*args, **kwargs) | |
| 40 self.attempts.append(attempt) | |
| 41 return attempt | |
| 42 | |
| 43 def assertAttempts(self, attempts, max_timeout): | |
| 44 """Asserts that retry loop executed given number of |attempts|.""" | |
| 45 expected = [(i, max_timeout - i) for i in xrange(attempts)] | |
| 46 actual = [(x.attempt, x.remaining) for x in self.attempts] | |
| 47 self.assertEqual(expected, actual) | |
| 48 | |
| 49 def assertSleeps(self, sleeps): | |
| 50 """Asserts that retry loop slept given number of times.""" | |
| 51 self.assertEqual(sleeps, len(self.sleeps)) | |
| 52 | |
| 53 | |
| 54 class RetryLoopTest(RetryLoopMockedTest): | |
| 55 """Test for retry_loop implementation.""" | |
| 56 | |
| 57 def test_sleep_before_retry(self): | |
| 58 # Verifies bounds. Because it's using a pseudo-random number generator and | |
| 59 # not a read random source, it's basically guaranteed to never return the | |
| 60 # same value twice consecutively. | |
| 61 a = net.calculate_sleep_before_retry(0, 0) | |
| 62 b = net.calculate_sleep_before_retry(0, 0) | |
| 63 self.assertTrue(a >= math.pow(1.5, -1), a) | |
| 64 self.assertTrue(b >= math.pow(1.5, -1), b) | |
| 65 self.assertTrue(a < 1.5 + math.pow(1.5, -1), a) | |
| 66 self.assertTrue(b < 1.5 + math.pow(1.5, -1), b) | |
| 67 self.assertNotEqual(a, b) | |
| 68 | |
| 69 | |
| 70 class GetHttpServiceTest(unittest.TestCase): | |
| 71 """Tests get_http_service implementation.""" | |
| 72 | |
| 73 def test_get_http_service(self): | |
| 74 def assert_is_appengine_service(service): | |
| 75 """Verifies HttpService is configured for App Engine communications.""" | |
| 76 self.assertTrue(service.use_count_key) | |
| 77 self.assertIsNotNone(service.authenticator) | |
| 78 | |
| 79 def assert_is_googlestorage_service(service): | |
| 80 """Verifies HttpService is configured for GS communications.""" | |
| 81 self.assertFalse(service.use_count_key) | |
| 82 self.assertIsNone(service.authenticator) | |
| 83 | |
| 84 # Can recognize app engine URLs. | |
| 85 assert_is_appengine_service( | |
| 86 net.get_http_service('https://appengine-app.appspot.com')) | |
| 87 assert_is_appengine_service( | |
| 88 net.get_http_service('https://version-dot-appengine-app.appspot.com')) | |
| 89 | |
| 90 # Localhost is also sort of appengine when running on dev server... | |
| 91 assert_is_appengine_service( | |
| 92 net.get_http_service('http://localhost:8080')) | |
| 93 | |
| 94 # Check GS urls. | |
| 95 assert_is_googlestorage_service( | |
| 96 net.get_http_service('https://bucket-name.storage.googleapis.com')) | |
| 97 | |
| 98 | |
| 99 class HttpServiceTest(RetryLoopMockedTest): | |
| 100 """Tests for HttpService class.""" | |
| 101 | |
| 102 @staticmethod | |
| 103 def mocked_http_service(url='http://example.com', perform_request=None, | |
| 104 authenticate=None): # pylint: disable=R0201 | |
| 105 class MockedAuthenticator(net.Authenticator): | |
| 106 def authenticate(self): | |
| 107 return authenticate() if authenticate else None | |
| 108 | |
| 109 class MockedRequestEngine(net.RequestEngine): | |
| 110 def perform_request(self, request): | |
| 111 return perform_request(request) if perform_request else None | |
| 112 | |
| 113 return net.HttpService( | |
| 114 url, | |
| 115 authenticator=MockedAuthenticator(), | |
| 116 engine=MockedRequestEngine()) | |
| 117 | |
| 118 def test_request_GET_success(self): | |
| 119 service_url = 'http://example.com' | |
| 120 request_url = '/some_request' | |
| 121 response = 'True' | |
| 122 | |
| 123 def mock_perform_request(request): | |
| 124 self.assertTrue( | |
| 125 request.get_full_url().startswith(service_url + request_url)) | |
| 126 return request.make_fake_response(response) | |
| 127 | |
| 128 service = self.mocked_http_service(url=service_url, | |
| 129 perform_request=mock_perform_request) | |
| 130 self.assertEqual(service.request(request_url).read(), response) | |
| 131 self.assertAttempts(1, net.URL_OPEN_TIMEOUT) | |
| 132 | |
| 133 def test_request_POST_success(self): | |
| 134 service_url = 'http://example.com' | |
| 135 request_url = '/some_request' | |
| 136 response = 'True' | |
| 137 | |
| 138 def mock_perform_request(request): | |
| 139 self.assertTrue( | |
| 140 request.get_full_url().startswith(service_url + request_url)) | |
| 141 self.assertEqual('', request.body) | |
| 142 return request.make_fake_response(response) | |
| 143 | |
| 144 service = self.mocked_http_service(url=service_url, | |
| 145 perform_request=mock_perform_request) | |
| 146 self.assertEqual(service.request(request_url, data={}).read(), response) | |
| 147 self.assertAttempts(1, net.URL_OPEN_TIMEOUT) | |
| 148 | |
| 149 def test_request_PUT_success(self): | |
| 150 service_url = 'http://example.com' | |
| 151 request_url = '/some_request' | |
| 152 request_body = 'data_body' | |
| 153 response_body = 'True' | |
| 154 content_type = 'application/octet-stream' | |
| 155 | |
| 156 def mock_perform_request(request): | |
| 157 self.assertTrue( | |
| 158 request.get_full_url().startswith(service_url + request_url)) | |
| 159 self.assertEqual(request_body, request.body) | |
| 160 self.assertEqual(request.method, 'PUT') | |
| 161 self.assertEqual(request.headers['Content-Type'], content_type) | |
| 162 return request.make_fake_response(response_body) | |
| 163 | |
| 164 service = self.mocked_http_service(url=service_url, | |
| 165 perform_request=mock_perform_request) | |
| 166 response = service.request(request_url, | |
| 167 data=request_body, content_type=content_type, method='PUT') | |
| 168 self.assertEqual(response.read(), response_body) | |
| 169 self.assertAttempts(1, net.URL_OPEN_TIMEOUT) | |
| 170 | |
| 171 | |
| 172 def test_request_success_after_failure(self): | |
| 173 response = 'True' | |
| 174 | |
| 175 def mock_perform_request(request): | |
| 176 params = dict(request.params) | |
| 177 if params.get(net.COUNT_KEY) != 1: | |
| 178 raise net.ConnectionError() | |
| 179 return request.make_fake_response(response) | |
| 180 | |
| 181 service = self.mocked_http_service(perform_request=mock_perform_request) | |
| 182 self.assertEqual(service.request('/', data={}).read(), response) | |
| 183 self.assertAttempts(2, net.URL_OPEN_TIMEOUT) | |
| 184 | |
| 185 def test_request_failure_max_attempts_default(self): | |
| 186 def mock_perform_request(_request): | |
| 187 raise net.ConnectionError() | |
| 188 service = self.mocked_http_service(perform_request=mock_perform_request) | |
| 189 self.assertEqual(service.request('/'), None) | |
| 190 self.assertAttempts(net.URL_OPEN_MAX_ATTEMPTS, net.URL_OPEN_TIMEOUT) | |
| 191 | |
| 192 def test_request_failure_max_attempts(self): | |
| 193 def mock_perform_request(_request): | |
| 194 raise net.ConnectionError() | |
| 195 service = self.mocked_http_service(perform_request=mock_perform_request) | |
| 196 self.assertEqual(service.request('/', max_attempts=23), None) | |
| 197 self.assertAttempts(23, net.URL_OPEN_TIMEOUT) | |
| 198 | |
| 199 def test_request_failure_timeout(self): | |
| 200 def mock_perform_request(_request): | |
| 201 raise net.ConnectionError() | |
| 202 service = self.mocked_http_service(perform_request=mock_perform_request) | |
| 203 self.assertEqual(service.request('/', max_attempts=10000), None) | |
| 204 self.assertAttempts(int(net.URL_OPEN_TIMEOUT) + 1, net.URL_OPEN_TIMEOUT) | |
| 205 | |
| 206 def test_request_failure_timeout_default(self): | |
| 207 def mock_perform_request(_request): | |
| 208 raise net.ConnectionError() | |
| 209 service = self.mocked_http_service(perform_request=mock_perform_request) | |
| 210 self.assertEqual(service.request('/', timeout=10.), None) | |
| 211 self.assertAttempts(11, 10.0) | |
| 212 | |
| 213 def test_request_HTTP_error_no_retry(self): | |
| 214 count = [] | |
| 215 def mock_perform_request(request): | |
| 216 count.append(request) | |
| 217 raise net.HttpError(400) | |
| 218 | |
| 219 service = self.mocked_http_service(perform_request=mock_perform_request) | |
| 220 self.assertEqual(service.request('/', data={}), None) | |
| 221 self.assertEqual(1, len(count)) | |
| 222 self.assertAttempts(1, net.URL_OPEN_TIMEOUT) | |
| 223 | |
| 224 def test_request_HTTP_error_retry_404(self): | |
| 225 response = 'data' | |
| 226 | |
| 227 def mock_perform_request(request): | |
| 228 params = dict(request.params) | |
| 229 if params.get(net.COUNT_KEY) == 1: | |
| 230 return request.make_fake_response(response) | |
| 231 raise net.HttpError(404) | |
| 232 | |
| 233 service = self.mocked_http_service(perform_request=mock_perform_request) | |
| 234 result = service.request('/', data={}, retry_404=True) | |
| 235 self.assertEqual(result.read(), response) | |
| 236 self.assertAttempts(2, net.URL_OPEN_TIMEOUT) | |
| 237 | |
| 238 def test_request_HTTP_error_with_retry(self): | |
| 239 response = 'response' | |
| 240 | |
| 241 def mock_perform_request(request): | |
| 242 params = dict(request.params) | |
| 243 if params.get(net.COUNT_KEY) != 1: | |
| 244 raise net.HttpError(500) | |
| 245 return request.make_fake_response(response) | |
| 246 | |
| 247 service = self.mocked_http_service(perform_request=mock_perform_request) | |
| 248 self.assertTrue(service.request('/', data={}).read(), response) | |
| 249 self.assertAttempts(2, net.URL_OPEN_TIMEOUT) | |
| 250 | |
| 251 def test_auth_success(self): | |
| 252 count = [] | |
| 253 response = 'response' | |
| 254 | |
| 255 def mock_perform_request(request): | |
| 256 if not count: | |
| 257 raise net.HttpError(403) | |
| 258 return request.make_fake_response(response) | |
| 259 | |
| 260 def mock_authenticate(): | |
| 261 self.assertEqual(len(count), 0) | |
| 262 count.append(1) | |
| 263 return True | |
| 264 | |
| 265 service = self.mocked_http_service(perform_request=mock_perform_request, | |
| 266 authenticate=mock_authenticate) | |
| 267 self.assertEqual(service.request('/').read(), response) | |
| 268 self.assertEqual(len(count), 1) | |
| 269 self.assertAttempts(2, net.URL_OPEN_TIMEOUT) | |
| 270 self.assertSleeps(0) | |
| 271 | |
| 272 def test_auth_failure(self): | |
| 273 count = [] | |
| 274 | |
| 275 def mock_perform_request(_request): | |
| 276 raise net.HttpError(403) | |
| 277 | |
| 278 def mock_authenticate(): | |
| 279 count.append(1) | |
| 280 return False | |
| 281 | |
| 282 service = self.mocked_http_service(perform_request=mock_perform_request, | |
| 283 authenticate=mock_authenticate) | |
| 284 self.assertEqual(service.request('/'), None) | |
| 285 self.assertEqual(len(count), 1) | |
| 286 self.assertAttempts(1, net.URL_OPEN_TIMEOUT) | |
| 287 | |
| 288 def test_request_attempted_before_auth(self): | |
| 289 calls = [] | |
| 290 | |
| 291 def mock_perform_request(_request): | |
| 292 calls.append('perform_request') | |
| 293 raise net.HttpError(403) | |
| 294 | |
| 295 def mock_authenticate(): | |
| 296 calls.append('authenticate') | |
| 297 return False | |
| 298 | |
| 299 service = self.mocked_http_service(perform_request=mock_perform_request, | |
| 300 authenticate=mock_authenticate) | |
| 301 self.assertEqual(service.request('/'), None) | |
| 302 self.assertEqual(calls, ['perform_request', 'authenticate']) | |
| 303 self.assertAttempts(1, net.URL_OPEN_TIMEOUT) | |
| 304 | |
| 305 def test_url_read(self): | |
| 306 # Successfully reads the data. | |
| 307 self.mock(net, 'url_open', | |
| 308 lambda url, **_kwargs: net.HttpResponse.get_fake_response('111', url)) | |
| 309 self.assertEqual(net.url_read('https://fake_url.com/test'), '111') | |
| 310 | |
| 311 # Respects url_open connection errors. | |
| 312 self.mock(net, 'url_open', lambda _url, **_kwargs: None) | |
| 313 self.assertIsNone(net.url_read('https://fake_url.com/test')) | |
| 314 | |
| 315 # Respects read timeout errors. | |
| 316 def timeouting_http_response(url): | |
| 317 def read_mock(_size=None): | |
| 318 raise net.TimeoutError() | |
| 319 response = net.HttpResponse.get_fake_response('', url) | |
| 320 self.mock(response, 'read', read_mock) | |
| 321 return response | |
| 322 | |
| 323 self.mock(net, 'url_open', | |
| 324 lambda url, **_kwargs: timeouting_http_response(url)) | |
| 325 self.assertIsNone(net.url_read('https://fake_url.com/test')) | |
| 326 | |
| 327 | |
| 328 if __name__ == '__main__': | |
| 329 logging.basicConfig( | |
| 330 level=(logging.DEBUG if '-v' in sys.argv else logging.FATAL)) | |
| 331 if '-v' in sys.argv: | |
| 332 unittest.TestCase.maxDiff = None | |
| 333 unittest.main() | |
| OLD | NEW |