| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import json | 6 import json |
| 7 import logging | 7 import logging |
| 8 import math | |
| 9 import os | 8 import os |
| 10 import shutil | 9 import shutil |
| 11 import StringIO | |
| 12 import sys | 10 import sys |
| 13 import tempfile | 11 import tempfile |
| 14 import time | 12 import time |
| 15 import unittest | 13 import unittest |
| 16 import urllib2 | |
| 17 | 14 |
| 18 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | 15 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 19 sys.path.insert(0, ROOT_DIR) | 16 sys.path.insert(0, ROOT_DIR) |
| 20 | 17 |
| 21 import auto_stub | 18 import auto_stub |
| 22 import run_isolated | 19 import run_isolated |
| 23 | 20 |
| 24 # Number of times self._now() is called per loop in HttpService._retry_loop(). | 21 from utils import net |
| 25 NOW_CALLS_PER_OPEN = 2 | |
| 26 | 22 |
| 27 | 23 |
| 28 class RemoteTest(run_isolated.Remote): | 24 class RemoteTest(run_isolated.Remote): |
| 29 def get_file_handler(self, _): # pylint: disable=R0201 | 25 def get_file_handler(self, _): # pylint: disable=R0201 |
| 30 def upload_file(item, _dest): | 26 def upload_file(item, _dest): |
| 31 if type(item) == type(Exception) and issubclass(item, Exception): | 27 if type(item) == type(Exception) and issubclass(item, Exception): |
| 32 raise item() | 28 raise item() |
| 33 elif isinstance(item, int): | 29 elif isinstance(item, int): |
| 34 time.sleep(int(item) / 100) | 30 time.sleep(int(item) / 100) |
| 35 return upload_file | 31 return upload_file |
| 36 | 32 |
| 37 | 33 |
| 38 def fake_http_response(content, url): | |
| 39 """Returns run_isolated.HttpResponse with predefined content.""" | |
| 40 stream = StringIO.StringIO(content) | |
| 41 stream.headers = {'content-length': len(content)} | |
| 42 return run_isolated.HttpResponse(stream, url) | |
| 43 | |
| 44 | |
| 45 class RunIsolatedTest(auto_stub.TestCase): | 34 class RunIsolatedTest(auto_stub.TestCase): |
| 46 def setUp(self): | 35 def setUp(self): |
| 47 super(RunIsolatedTest, self).setUp() | 36 super(RunIsolatedTest, self).setUp() |
| 48 self.tempdir = tempfile.mkdtemp(prefix='run_isolated') | 37 self.tempdir = tempfile.mkdtemp(prefix='run_isolated') |
| 49 os.chdir(self.tempdir) | 38 os.chdir(self.tempdir) |
| 50 | 39 |
| 51 def tearDown(self): | 40 def tearDown(self): |
| 52 os.chdir(ROOT_DIR) | 41 os.chdir(ROOT_DIR) |
| 53 shutil.rmtree(self.tempdir) | 42 shutil.rmtree(self.tempdir) |
| 54 super(RunIsolatedTest, self).tearDown() | 43 super(RunIsolatedTest, self).tearDown() |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 130 | 119 |
| 131 def RaiseIOError(*_): | 120 def RaiseIOError(*_): |
| 132 raise IOError() | 121 raise IOError() |
| 133 remote._do_item = RaiseIOError | 122 remote._do_item = RaiseIOError |
| 134 remote.add_item(run_isolated.Remote.MED, 'ignored', '', | 123 remote.add_item(run_isolated.Remote.MED, 'ignored', '', |
| 135 run_isolated.UNKNOWN_FILE_SIZE) | 124 run_isolated.UNKNOWN_FILE_SIZE) |
| 136 self.assertRaises(IOError, remote.join) | 125 self.assertRaises(IOError, remote.join) |
| 137 self.assertEqual([], remote.join()) | 126 self.assertEqual([], remote.join()) |
| 138 | 127 |
| 139 def test_zip_header_error(self): | 128 def test_zip_header_error(self): |
| 140 self.mock(run_isolated, 'url_open', | 129 self.mock(run_isolated.net, 'url_open', |
| 141 lambda url, **_kwargs: fake_http_response('111', url)) | 130 lambda url, **_kwargs: net.HttpResponse.get_fake_response('111', url)) |
| 142 self.mock(run_isolated.time, 'sleep', lambda _x: None) | 131 self.mock(run_isolated.time, 'sleep', lambda _x: None) |
| 143 | 132 |
| 144 remote = run_isolated.Remote('https://fake-CAD.com/') | 133 remote = run_isolated.Remote('https://fake-CAD.com/') |
| 145 | 134 |
| 146 # Both files will fail to be unzipped due to incorrect headers, | 135 # Both files will fail to be unzipped due to incorrect headers, |
| 147 # ensure that we don't accept the files (even if the size is unknown)}. | 136 # ensure that we don't accept the files (even if the size is unknown)}. |
| 148 remote.add_item(run_isolated.Remote.MED, 'zipped_A', 'A', | 137 remote.add_item(run_isolated.Remote.MED, 'zipped_A', 'A', |
| 149 run_isolated.UNKNOWN_FILE_SIZE) | 138 run_isolated.UNKNOWN_FILE_SIZE) |
| 150 remote.add_item(run_isolated.Remote.MED, 'zipped_B', 'B', 5) | 139 remote.add_item(run_isolated.Remote.MED, 'zipped_B', 'B', 5) |
| 151 self.assertRaises(IOError, remote.get_one_result) | 140 self.assertRaises(IOError, remote.get_one_result) |
| 152 self.assertRaises(IOError, remote.get_one_result) | 141 self.assertRaises(IOError, remote.get_one_result) |
| 153 # Need to use join here, since get_one_result will hang. | 142 # Need to use join here, since get_one_result will hang. |
| 154 self.assertEqual([], remote.join()) | 143 self.assertEqual([], remote.join()) |
| 155 | 144 |
| 156 | 145 |
| 157 | |
| 158 class HttpServiceTest(auto_stub.TestCase): | |
| 159 | |
| 160 # HttpService that doesn't sleep in retries and doesn't write cookie files. | |
| 161 class HttpServiceNoSideEffects(run_isolated.HttpService): | |
| 162 def __init__(self, *args, **kwargs): | |
| 163 super(HttpServiceTest.HttpServiceNoSideEffects, self).__init__( | |
| 164 *args, **kwargs) | |
| 165 self.sleeps = [] | |
| 166 self._count = 0. | |
| 167 | |
| 168 def _now(self): # pylint: disable=W0221 | |
| 169 x = self._count | |
| 170 self._count += 1 | |
| 171 return x | |
| 172 | |
| 173 def sleep_before_retry(self, attempt, max_wait): | |
| 174 self.sleeps.append((attempt, max_wait)) | |
| 175 | |
| 176 def load_cookie_jar(self): # pylint: disable=W0221 | |
| 177 pass | |
| 178 | |
| 179 def save_cookie_jar(self): # pylint: disable=W0221 | |
| 180 pass | |
| 181 | |
| 182 def mocked_http_service(self, url='http://example.com', **kwargs): | |
| 183 service = HttpServiceTest.HttpServiceNoSideEffects(url) | |
| 184 for name, fn in kwargs.iteritems(): | |
| 185 self.assertTrue(getattr(service, name)) | |
| 186 setattr(service, name, fn) | |
| 187 return service | |
| 188 | |
| 189 def test_request_GET_success(self): | |
| 190 service_url = 'http://example.com' | |
| 191 request_url = '/some_request' | |
| 192 response = 'True' | |
| 193 | |
| 194 def mock_url_open(request, **_kwargs): | |
| 195 self.assertTrue(request.get_full_url().startswith(service_url + | |
| 196 request_url)) | |
| 197 return StringIO.StringIO(response) | |
| 198 | |
| 199 service = self.mocked_http_service(url=service_url, _url_open=mock_url_open) | |
| 200 self.assertEqual(service.request(request_url).read(), response) | |
| 201 self.assertEqual([], service.sleeps) | |
| 202 | |
| 203 def test_request_POST_success(self): | |
| 204 service_url = 'http://example.com' | |
| 205 request_url = '/some_request' | |
| 206 response = 'True' | |
| 207 | |
| 208 def mock_url_open(request, **_kwargs): | |
| 209 self.assertTrue(request.get_full_url().startswith(service_url + | |
| 210 request_url)) | |
| 211 self.assertEqual('', request.get_data()) | |
| 212 return StringIO.StringIO(response) | |
| 213 | |
| 214 service = self.mocked_http_service(url=service_url, _url_open=mock_url_open) | |
| 215 self.assertEqual(service.request(request_url, data={}).read(), response) | |
| 216 self.assertEqual([], service.sleeps) | |
| 217 | |
| 218 def test_request_success_after_failure(self): | |
| 219 response = 'True' | |
| 220 | |
| 221 def mock_url_open(request, **_kwargs): | |
| 222 if run_isolated.COUNT_KEY + '=1' not in request.get_data(): | |
| 223 raise urllib2.URLError('url') | |
| 224 return StringIO.StringIO(response) | |
| 225 | |
| 226 service = self.mocked_http_service(_url_open=mock_url_open) | |
| 227 self.assertEqual(service.request('/', data={}).read(), response) | |
| 228 self.assertEqual( | |
| 229 [(0, run_isolated.URL_OPEN_TIMEOUT - NOW_CALLS_PER_OPEN)], | |
| 230 service.sleeps) | |
| 231 | |
| 232 def test_request_failure_max_attempts_default(self): | |
| 233 def mock_url_open(_request, **_kwargs): | |
| 234 raise urllib2.URLError('url') | |
| 235 service = self.mocked_http_service(_url_open=mock_url_open) | |
| 236 self.assertEqual(service.request('/'), None) | |
| 237 retries = [ | |
| 238 (i, run_isolated.URL_OPEN_TIMEOUT - NOW_CALLS_PER_OPEN * (i + 1)) | |
| 239 for i in xrange(run_isolated.URL_OPEN_MAX_ATTEMPTS) | |
| 240 ] | |
| 241 self.assertEqual(retries, service.sleeps) | |
| 242 | |
| 243 def test_request_failure_max_attempts(self): | |
| 244 def mock_url_open(_request, **_kwargs): | |
| 245 raise urllib2.URLError('url') | |
| 246 service = self.mocked_http_service(_url_open=mock_url_open) | |
| 247 self.assertEqual(service.request('/', max_attempts=23), None) | |
| 248 retries = [ | |
| 249 (i, run_isolated.URL_OPEN_TIMEOUT - NOW_CALLS_PER_OPEN * (i + 1)) | |
| 250 for i in xrange(23) | |
| 251 ] | |
| 252 self.assertEqual(retries, service.sleeps) | |
| 253 | |
| 254 def test_request_failure_timeout(self): | |
| 255 def mock_url_open(_request, **_kwargs): | |
| 256 raise urllib2.URLError('url') | |
| 257 service = self.mocked_http_service(_url_open=mock_url_open) | |
| 258 self.assertEqual(service.request('/', max_attempts=10000), None) | |
| 259 retries = [ | |
| 260 (i, run_isolated.URL_OPEN_TIMEOUT - NOW_CALLS_PER_OPEN * (i + 1)) | |
| 261 # Currently 179 for timeout == 360. | |
| 262 for i in xrange( | |
| 263 int(run_isolated.URL_OPEN_TIMEOUT) / NOW_CALLS_PER_OPEN - 1) | |
| 264 ] | |
| 265 self.assertEqual(retries, service.sleeps) | |
| 266 | |
| 267 def test_request_failure_timeout_default(self): | |
| 268 def mock_url_open(_request, **_kwargs): | |
| 269 raise urllib2.URLError('url') | |
| 270 service = self.mocked_http_service(_url_open=mock_url_open) | |
| 271 self.assertEqual(service.request('/', timeout=10.), None) | |
| 272 retries = [(i, 8. - (NOW_CALLS_PER_OPEN * i)) for i in xrange(4)] | |
| 273 self.assertEqual(retries, service.sleeps) | |
| 274 | |
| 275 def test_request_HTTP_error_no_retry(self): | |
| 276 count = [] | |
| 277 def mock_url_open(request, **_kwargs): | |
| 278 count.append(request) | |
| 279 raise urllib2.HTTPError( | |
| 280 'url', 400, 'error message', None, StringIO.StringIO()) | |
| 281 | |
| 282 service = self.mocked_http_service(_url_open=mock_url_open) | |
| 283 self.assertEqual(service.request('/', data={}), None) | |
| 284 self.assertEqual(1, len(count)) | |
| 285 self.assertEqual([], service.sleeps) | |
| 286 | |
| 287 def test_request_HTTP_error_retry_404(self): | |
| 288 response = 'data' | |
| 289 def mock_url_open(request, **_kwargs): | |
| 290 if run_isolated.COUNT_KEY + '=1' in request.get_data(): | |
| 291 return StringIO.StringIO(response) | |
| 292 raise urllib2.HTTPError( | |
| 293 'url', 404, 'error message', None, StringIO.StringIO()) | |
| 294 | |
| 295 service = self.mocked_http_service(_url_open=mock_url_open) | |
| 296 result = service.request('/', data={}, retry_404=True) | |
| 297 self.assertEqual(result.read(), response) | |
| 298 self.assertEqual( | |
| 299 [(0, run_isolated.URL_OPEN_TIMEOUT - NOW_CALLS_PER_OPEN)], | |
| 300 service.sleeps) | |
| 301 | |
| 302 def test_request_HTTP_error_with_retry(self): | |
| 303 response = 'response' | |
| 304 | |
| 305 def mock_url_open(request, **_kwargs): | |
| 306 if run_isolated.COUNT_KEY + '=1' not in request.get_data(): | |
| 307 raise urllib2.HTTPError( | |
| 308 'url', 500, 'error message', None, StringIO.StringIO()) | |
| 309 return StringIO.StringIO(response) | |
| 310 | |
| 311 service = self.mocked_http_service(_url_open=mock_url_open) | |
| 312 self.assertTrue(service.request('/', data={}).read(), response) | |
| 313 self.assertEqual( | |
| 314 [(0, run_isolated.URL_OPEN_TIMEOUT - NOW_CALLS_PER_OPEN)], | |
| 315 service.sleeps) | |
| 316 | |
| 317 def test_count_key_in_data_failure(self): | |
| 318 data = {run_isolated.COUNT_KEY: 1} | |
| 319 service = self.mocked_http_service() | |
| 320 self.assertEqual(service.request('/', data=data), None) | |
| 321 self.assertEqual([], service.sleeps) | |
| 322 | |
| 323 def test_auth_success(self): | |
| 324 count = [] | |
| 325 response = 'response' | |
| 326 def mock_url_open(_request, **_kwargs): | |
| 327 if not count: | |
| 328 raise urllib2.HTTPError( | |
| 329 'url', 403, 'error message', None, StringIO.StringIO()) | |
| 330 return StringIO.StringIO(response) | |
| 331 def mock_authenticate(): | |
| 332 self.assertEqual(len(count), 0) | |
| 333 count.append(1) | |
| 334 return True | |
| 335 service = self.mocked_http_service(_url_open=mock_url_open, | |
| 336 authenticate=mock_authenticate) | |
| 337 self.assertEqual(service.request('/').read(), response) | |
| 338 self.assertEqual(len(count), 1) | |
| 339 self.assertEqual([], service.sleeps) | |
| 340 | |
| 341 def test_auth_failure(self): | |
| 342 count = [] | |
| 343 def mock_url_open(_request, **_kwargs): | |
| 344 raise urllib2.HTTPError( | |
| 345 'url', 403, 'error message', None, StringIO.StringIO()) | |
| 346 def mock_authenticate(): | |
| 347 count.append(1) | |
| 348 return False | |
| 349 service = self.mocked_http_service(_url_open=mock_url_open, | |
| 350 authenticate=mock_authenticate) | |
| 351 self.assertEqual(service.request('/'), None) | |
| 352 self.assertEqual(len(count), 1) # single attempt only | |
| 353 self.assertEqual([], service.sleeps) | |
| 354 | |
| 355 def test_request_attempted_before_auth(self): | |
| 356 calls = [] | |
| 357 def mock_url_open(_request, **_kwargs): | |
| 358 calls.append('url_open') | |
| 359 raise urllib2.HTTPError( | |
| 360 'url', 403, 'error message', None, StringIO.StringIO()) | |
| 361 def mock_authenticate(): | |
| 362 calls.append('authenticate') | |
| 363 return False | |
| 364 service = self.mocked_http_service(_url_open=mock_url_open, | |
| 365 authenticate=mock_authenticate) | |
| 366 self.assertEqual(service.request('/'), None) | |
| 367 self.assertEqual(calls, ['url_open', 'authenticate']) | |
| 368 self.assertEqual([], service.sleeps) | |
| 369 | |
| 370 def test_sleep_before_retry(self): | |
| 371 # Verifies bounds. Because it's using a pseudo-random number generator and | |
| 372 # not a read random source, it's basically guaranteed to never return the | |
| 373 # same value twice consecutively. | |
| 374 a = run_isolated.HttpService.calculate_sleep_before_retry(0, 0) | |
| 375 b = run_isolated.HttpService.calculate_sleep_before_retry(0, 0) | |
| 376 self.assertTrue(a >= math.pow(1.5, -1), a) | |
| 377 self.assertTrue(b >= math.pow(1.5, -1), b) | |
| 378 self.assertTrue(a < 1.5 + math.pow(1.5, -1), a) | |
| 379 self.assertTrue(b < 1.5 + math.pow(1.5, -1), b) | |
| 380 self.assertNotEqual(a, b) | |
| 381 | |
| 382 def test_url_read(self): | |
| 383 # Successfully reads the data. | |
| 384 self.mock(run_isolated, 'url_open', | |
| 385 lambda url, **_kwargs: fake_http_response('111', url)) | |
| 386 self.assertEqual(run_isolated.url_read('https://fake_url.com/test'), '111') | |
| 387 | |
| 388 # Respects url_open connection errors. | |
| 389 self.mock(run_isolated, 'url_open', lambda _url, **_kwargs: None) | |
| 390 self.assertIsNone(run_isolated.url_read('https://fake_url.com/test')) | |
| 391 | |
| 392 # Respects read timeout errors. | |
| 393 def timeouting_http_response(url): | |
| 394 def read_mock(_size=None): | |
| 395 raise run_isolated.TimeoutError() | |
| 396 stream = StringIO.StringIO('') | |
| 397 stream.headers = {'content-length': 0} | |
| 398 response = run_isolated.HttpResponse(stream, url) | |
| 399 response.read = read_mock | |
| 400 return response | |
| 401 | |
| 402 self.mock(run_isolated, 'url_open', | |
| 403 lambda url, **_kwargs: timeouting_http_response(url)) | |
| 404 self.assertIsNone(run_isolated.url_read('https://fake_url.com/test')) | |
| 405 | |
| 406 | |
| 407 if __name__ == '__main__': | 146 if __name__ == '__main__': |
| 408 logging.basicConfig( | 147 logging.basicConfig( |
| 409 level=(logging.DEBUG if '-v' in sys.argv else logging.FATAL)) | 148 level=(logging.DEBUG if '-v' in sys.argv else logging.FATAL)) |
| 410 if '-v' in sys.argv: | 149 if '-v' in sys.argv: |
| 411 unittest.TestCase.maxDiff = None | 150 unittest.TestCase.maxDiff = None |
| 412 unittest.main() | 151 unittest.main() |
| OLD | NEW |