| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 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 # pylint: disable=W0223 | 6 # pylint: disable=W0223 |
| 7 # pylint: disable=W0231 | 7 # pylint: disable=W0231 |
| 8 | 8 |
| 9 import binascii | |
| 10 import hashlib | 9 import hashlib |
| 11 import json | 10 import json |
| 12 import logging | 11 import logging |
| 13 import os | 12 import os |
| 14 import random | |
| 15 import shutil | 13 import shutil |
| 16 import StringIO | 14 import StringIO |
| 17 import sys | 15 import sys |
| 18 import tempfile | 16 import tempfile |
| 19 import threading | 17 import threading |
| 20 import unittest | 18 import unittest |
| 21 import zlib | 19 import zlib |
| 22 | 20 |
| 23 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) | 21 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) |
| 24 ROOT_DIR = os.path.dirname(BASE_PATH) | 22 ROOT_DIR = os.path.dirname(BASE_PATH) |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 57 for i, n in enumerate(self._requests): | 55 for i, n in enumerate(self._requests): |
| 58 if n[0] == url: | 56 if n[0] == url: |
| 59 _, expected_kwargs, result = self._requests.pop(i) | 57 _, expected_kwargs, result = self._requests.pop(i) |
| 60 self.assertEqual(expected_kwargs, kwargs) | 58 self.assertEqual(expected_kwargs, kwargs) |
| 61 if result is not None: | 59 if result is not None: |
| 62 return isolateserver.net.HttpResponse.get_fake_response(result, url) | 60 return isolateserver.net.HttpResponse.get_fake_response(result, url) |
| 63 return None | 61 return None |
| 64 self.fail('Unknown request %s' % url) | 62 self.fail('Unknown request %s' % url) |
| 65 | 63 |
| 66 | 64 |
| 65 class TestZipCompression(TestCase): |
| 66 """Test zip_compress and zip_decompress generators.""" |
| 67 |
| 68 def test_compress_and_decompress(self): |
| 69 """Test data === decompress(compress(data)).""" |
| 70 original = [str(x) for x in xrange(0, 1000)] |
| 71 processed = isolateserver.zip_decompress( |
| 72 isolateserver.zip_compress(original)) |
| 73 self.assertEqual(''.join(original), ''.join(processed)) |
| 74 |
| 75 def test_zip_bomb(self): |
| 76 """Verify zip_decompress alwasy returns small chunks.""" |
| 77 chunk_size = 1000 |
| 78 bomb = ''.join(isolateserver.zip_compress('\x00' * 100000)) |
| 79 for chunk in isolateserver.zip_decompress([bomb], chunk_size): |
| 80 self.assertLessEqual(len(chunk), chunk_size) |
| 81 |
| 82 |
| 67 class StorageTest(TestCase): | 83 class StorageTest(TestCase): |
| 68 """Tests for Storage methods.""" | 84 """Tests for Storage methods.""" |
| 69 | 85 |
| 70 @staticmethod | 86 @staticmethod |
| 71 def mock_push(side_effect=None): | 87 def mock_push(side_effect=None): |
| 72 """Returns StorageApi subclass with mocked 'push' method.""" | 88 """Returns StorageApi subclass with mocked 'push' method.""" |
| 73 class MockedStorageApi(isolateserver.StorageApi): | 89 class MockedStorageApi(isolateserver.StorageApi): |
| 74 def __init__(self): | 90 def __init__(self): |
| 75 self.pushed = [] | 91 self.pushed = [] |
| 76 def push(self, item, expected_size, content_generator, push_urls=None): | 92 def push(self, item, expected_size, content_generator, push_urls=None): |
| (...skipping 206 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 283 if metadata['h'] == digest | 299 if metadata['h'] == digest |
| 284 ] | 300 ] |
| 285 self.assertEqual(1, len(filenames)) | 301 self.assertEqual(1, len(filenames)) |
| 286 filename = filenames[0] | 302 filename = filenames[0] |
| 287 data = files_data[filename] | 303 data = files_data[filename] |
| 288 self.assertEqual( | 304 self.assertEqual( |
| 289 (digest, len(data), data, push_urls[filename]), | 305 (digest, len(data), data, push_urls[filename]), |
| 290 push_call) | 306 push_call) |
| 291 | 307 |
| 292 | 308 |
| 293 class IsolateServerArchiveTest(TestCase): | |
| 294 def setUp(self): | |
| 295 super(IsolateServerArchiveTest, self).setUp() | |
| 296 self.mock(isolateserver, 'randomness', lambda: 'not_really_random') | |
| 297 self.mock(sys, 'stdout', StringIO.StringIO()) | |
| 298 | |
| 299 def test_present(self): | |
| 300 files = [ | |
| 301 os.path.join(BASE_PATH, 'isolateserver', f) | |
| 302 for f in ('small_file.txt', 'empty_file.txt') | |
| 303 ] | |
| 304 hash_encoded = ''.join( | |
| 305 binascii.unhexlify(isolateserver.hash_file(f, ALGO)) for f in files) | |
| 306 path = 'http://random/' | |
| 307 self._requests = [ | |
| 308 (path + 'content/get_token', {}, 'foo bar'), | |
| 309 ( | |
| 310 path + 'content/contains/default-gzip?token=foo%20bar', | |
| 311 {'data': hash_encoded, 'content_type': 'application/octet-stream'}, | |
| 312 '\1\1', | |
| 313 ), | |
| 314 ] | |
| 315 result = isolateserver.main(['archive', '--isolate-server', path] + files) | |
| 316 self.assertEqual(0, result) | |
| 317 | |
| 318 def test_missing(self): | |
| 319 files = [ | |
| 320 os.path.join(BASE_PATH, 'isolateserver', f) | |
| 321 for f in ('small_file.txt', 'empty_file.txt') | |
| 322 ] | |
| 323 hashes = [isolateserver.hash_file(f, ALGO) for f in files] | |
| 324 hash_encoded = ''.join(map(binascii.unhexlify, hashes)) | |
| 325 compressed = [ | |
| 326 zlib.compress( | |
| 327 open(f, 'rb').read(), | |
| 328 isolateserver.get_zip_compression_level(f)) | |
| 329 for f in files | |
| 330 ] | |
| 331 path = 'http://random/' | |
| 332 self._requests = [ | |
| 333 (path + 'content/get_token', {}, 'foo bar'), | |
| 334 ( | |
| 335 path + 'content/contains/default-gzip?token=foo%20bar', | |
| 336 {'data': hash_encoded, 'content_type': 'application/octet-stream'}, | |
| 337 '\0\0', | |
| 338 ), | |
| 339 ( | |
| 340 path + 'content/store/default-gzip/%s?token=foo%%20bar' % hashes[0], | |
| 341 {'data': compressed[0], 'content_type': 'application/octet-stream'}, | |
| 342 'ok', | |
| 343 ), | |
| 344 ( | |
| 345 path + 'content/store/default-gzip/%s?token=foo%%20bar' % hashes[1], | |
| 346 {'data': compressed[1], 'content_type': 'application/octet-stream'}, | |
| 347 'ok', | |
| 348 ), | |
| 349 ] | |
| 350 result = isolateserver.main(['archive', '--isolate-server', path] + files) | |
| 351 self.assertEqual(0, result) | |
| 352 | |
| 353 def test_large(self): | |
| 354 content = '' | |
| 355 compressed = '' | |
| 356 while ( | |
| 357 len(compressed) <= isolateserver.MIN_SIZE_FOR_DIRECT_BLOBSTORE): | |
| 358 # The goal here is to generate a file, once compressed, is at least | |
| 359 # MIN_SIZE_FOR_DIRECT_BLOBSTORE. | |
| 360 content += ''.join(chr(random.randint(0, 255)) for _ in xrange(20*1024)) | |
| 361 compressed = zlib.compress( | |
| 362 content, isolateserver.get_zip_compression_level('foo.txt')) | |
| 363 | |
| 364 s = ALGO(content).hexdigest() | |
| 365 infiles = { | |
| 366 'foo.txt': { | |
| 367 's': len(content), | |
| 368 'h': s, | |
| 369 }, | |
| 370 } | |
| 371 path = 'http://random/' | |
| 372 hash_encoded = binascii.unhexlify(s) | |
| 373 content_type, body = isolateserver.encode_multipart_formdata( | |
| 374 [('token', 'foo bar')], [('content', s, compressed)]) | |
| 375 | |
| 376 self._requests = [ | |
| 377 (path + 'content/get_token', {}, 'foo bar'), | |
| 378 ( | |
| 379 path + 'content/contains/default-gzip?token=foo%20bar', | |
| 380 {'data': hash_encoded, 'content_type': 'application/octet-stream'}, | |
| 381 '\0', | |
| 382 ), | |
| 383 ( | |
| 384 path + 'content/generate_blobstore_url/default-gzip/%s' % s, | |
| 385 {'data': [('token', 'foo bar')]}, | |
| 386 'an_url/', | |
| 387 ), | |
| 388 ( | |
| 389 'an_url/', | |
| 390 {'data': body, 'content_type': content_type, 'retry_50x': False}, | |
| 391 'ok', | |
| 392 ), | |
| 393 ] | |
| 394 | |
| 395 # Setup mocks for zip_compress to return |compressed|. | |
| 396 self.mock(isolateserver, 'file_read', lambda *_: None) | |
| 397 self.mock(isolateserver, 'zip_compress', lambda *_: [compressed]) | |
| 398 result = isolateserver.upload_tree( | |
| 399 base_url=path, | |
| 400 indir=os.getcwd(), | |
| 401 infiles=infiles, | |
| 402 namespace='default-gzip') | |
| 403 | |
| 404 self.assertEqual(0, result) | |
| 405 | |
| 406 def test_upload_blobstore_simple(self): | |
| 407 # A tad over 20kb so it triggers uploading to the blob store. | |
| 408 content = '0123456789' * 21*1024 | |
| 409 s = ALGO(content).hexdigest() | |
| 410 path = 'http://example.com:80/' | |
| 411 data = [('token', 'a_token')] | |
| 412 content_type, body = isolateserver.encode_multipart_formdata( | |
| 413 data, [('content', s, content)]) | |
| 414 self._requests = [ | |
| 415 ( | |
| 416 path + 'content/get_token', | |
| 417 {}, | |
| 418 'a_token', | |
| 419 ), | |
| 420 ( | |
| 421 path + 'content/generate_blobstore_url/x/' + s, | |
| 422 {'data': data[:]}, | |
| 423 'http://example.com/an_url/', | |
| 424 ), | |
| 425 ( | |
| 426 'http://example.com/an_url/', | |
| 427 {'data': body, 'content_type': content_type, 'retry_50x': False}, | |
| 428 'ok42', | |
| 429 ), | |
| 430 ] | |
| 431 # |size| is currently ignored. | |
| 432 result = isolateserver.IsolateServer(path, 'x').push(s, -2, [content]) | |
| 433 self.assertEqual('ok42', result) | |
| 434 | |
| 435 def test_upload_blobstore_retry_500(self): | |
| 436 # A tad over 20kb so it triggers uploading to the blob store. | |
| 437 content = '0123456789' * 21*1024 | |
| 438 s = ALGO(content).hexdigest() | |
| 439 path = 'http://example.com:80/' | |
| 440 data = [('token', 'a_token')] | |
| 441 content_type, body = isolateserver.encode_multipart_formdata( | |
| 442 data, [('content', s, content)]) | |
| 443 self._requests = [ | |
| 444 ( | |
| 445 path + 'content/get_token', | |
| 446 {}, | |
| 447 'a_token', | |
| 448 ), | |
| 449 ( | |
| 450 path + 'content/generate_blobstore_url/x/' + s, | |
| 451 {'data': data[:]}, | |
| 452 'http://example.com/an_url/', | |
| 453 ), | |
| 454 ( | |
| 455 'http://example.com/an_url/', | |
| 456 {'data': body, 'content_type': content_type, 'retry_50x': False}, | |
| 457 # Let's say an HTTP 500 was returned. | |
| 458 None, | |
| 459 ), | |
| 460 # In that case, a new url must be generated since the last one may have | |
| 461 # been "consumed". | |
| 462 ( | |
| 463 path + 'content/generate_blobstore_url/x/' + s, | |
| 464 {'data': data[:]}, | |
| 465 'http://example.com/an_url_2/', | |
| 466 ), | |
| 467 ( | |
| 468 'http://example.com/an_url_2/', | |
| 469 {'data': body, 'content_type': content_type, 'retry_50x': False}, | |
| 470 'ok42', | |
| 471 ), | |
| 472 ] | |
| 473 # |size| is currently ignored. | |
| 474 result = isolateserver.IsolateServer(path, 'x').push(s, -2, [content]) | |
| 475 self.assertEqual('ok42', result) | |
| 476 | |
| 477 | |
| 478 class IsolateServerDownloadTest(TestCase): | 309 class IsolateServerDownloadTest(TestCase): |
| 479 tempdir = None | 310 tempdir = None |
| 480 | 311 |
| 481 def tearDown(self): | 312 def tearDown(self): |
| 482 try: | 313 try: |
| 483 if self.tempdir: | 314 if self.tempdir: |
| 484 shutil.rmtree(self.tempdir) | 315 shutil.rmtree(self.tempdir) |
| 485 finally: | 316 finally: |
| 486 super(IsolateServerDownloadTest, self).tearDown() | 317 super(IsolateServerDownloadTest, self).tearDown() |
| 487 | 318 |
| 488 def test_download_two_files(self): | 319 def test_download_two_files(self): |
| 489 # Test downloading two files. | 320 # Test downloading two files. |
| 490 actual = {} | 321 actual = {} |
| 491 def out(key, generator): | 322 def out(key, generator): |
| 492 actual[key] = ''.join(generator) | 323 actual[key] = ''.join(generator) |
| 493 self.mock(isolateserver, 'file_write', out) | 324 self.mock(isolateserver, 'file_write', out) |
| 494 server = 'http://example.com' | 325 server = 'http://example.com' |
| 495 self._requests = [ | 326 self._requests = [ |
| 496 ( | 327 ( |
| 497 server + '/content/retrieve/default-gzip/sha-1', | 328 server + '/content-gs/retrieve/default-gzip/sha-1', |
| 498 {'read_timeout': 60, 'retry_404': True}, | 329 {'read_timeout': 60, 'retry_404': True}, |
| 499 zlib.compress('Coucou'), | 330 zlib.compress('Coucou'), |
| 500 ), | 331 ), |
| 501 ( | 332 ( |
| 502 server + '/content/retrieve/default-gzip/sha-2', | 333 server + '/content-gs/retrieve/default-gzip/sha-2', |
| 503 {'read_timeout': 60, 'retry_404': True}, | 334 {'read_timeout': 60, 'retry_404': True}, |
| 504 zlib.compress('Bye Bye'), | 335 zlib.compress('Bye Bye'), |
| 505 ), | 336 ), |
| 506 ] | 337 ] |
| 507 cmd = [ | 338 cmd = [ |
| 508 'download', | 339 'download', |
| 509 '--isolate-server', server, | 340 '--isolate-server', server, |
| 510 '--target', ROOT_DIR, | 341 '--target', ROOT_DIR, |
| 511 '--file', 'sha-1', 'path/to/a', | 342 '--file', 'sha-1', 'path/to/a', |
| 512 '--file', 'sha-2', 'path/to/b', | 343 '--file', 'sha-2', 'path/to/b', |
| (...skipping 27 matching lines...) Expand all Loading... |
| 540 'files': dict( | 371 'files': dict( |
| 541 (k, {'h': ALGO(v).hexdigest(), 's': len(v)}) | 372 (k, {'h': ALGO(v).hexdigest(), 's': len(v)}) |
| 542 for k, v in files.iteritems()), | 373 for k, v in files.iteritems()), |
| 543 } | 374 } |
| 544 isolated_data = json.dumps(isolated, sort_keys=True, separators=(',',':')) | 375 isolated_data = json.dumps(isolated, sort_keys=True, separators=(',',':')) |
| 545 isolated_hash = ALGO(isolated_data).hexdigest() | 376 isolated_hash = ALGO(isolated_data).hexdigest() |
| 546 requests = [(v['h'], files[k]) for k, v in isolated['files'].iteritems()] | 377 requests = [(v['h'], files[k]) for k, v in isolated['files'].iteritems()] |
| 547 requests.append((isolated_hash, isolated_data)) | 378 requests.append((isolated_hash, isolated_data)) |
| 548 self._requests = [ | 379 self._requests = [ |
| 549 ( | 380 ( |
| 550 server + '/content/retrieve/default-gzip/' + h, | 381 server + '/content-gs/retrieve/default-gzip/' + h, |
| 551 { | 382 { |
| 552 'read_timeout': isolateserver.DOWNLOAD_READ_TIMEOUT, | 383 'read_timeout': isolateserver.DOWNLOAD_READ_TIMEOUT, |
| 553 'retry_404': True, | 384 'retry_404': True, |
| 554 }, | 385 }, |
| 555 zlib.compress(v), | 386 zlib.compress(v), |
| 556 ) for h, v in requests | 387 ) for h, v in requests |
| 557 ] | 388 ] |
| 558 cmd = [ | 389 cmd = [ |
| 559 'download', | 390 'download', |
| 560 '--isolate-server', server, | 391 '--isolate-server', server, |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 649 expected = gen_data(os.path.sep) | 480 expected = gen_data(os.path.sep) |
| 650 self.assertEqual(expected, actual) | 481 self.assertEqual(expected, actual) |
| 651 | 482 |
| 652 | 483 |
| 653 if __name__ == '__main__': | 484 if __name__ == '__main__': |
| 654 if '-v' in sys.argv: | 485 if '-v' in sys.argv: |
| 655 unittest.TestCase.maxDiff = None | 486 unittest.TestCase.maxDiff = None |
| 656 logging.basicConfig( | 487 logging.basicConfig( |
| 657 level=(logging.DEBUG if '-v' in sys.argv else logging.ERROR)) | 488 level=(logging.DEBUG if '-v' in sys.argv else logging.ERROR)) |
| 658 unittest.main() | 489 unittest.main() |
| OLD | NEW |