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 |