| 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 # pylint: disable=W0212 | |
| 7 # pylint: disable=W0223 | |
| 8 # pylint: disable=W0231 | |
| 9 | |
| 10 import hashlib | |
| 11 import json | |
| 12 import logging | |
| 13 import os | |
| 14 import shutil | |
| 15 import StringIO | |
| 16 import sys | |
| 17 import tempfile | |
| 18 import threading | |
| 19 import unittest | |
| 20 import urllib | |
| 21 import zlib | |
| 22 | |
| 23 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) | |
| 24 ROOT_DIR = os.path.dirname(BASE_PATH) | |
| 25 sys.path.insert(0, ROOT_DIR) | |
| 26 | |
| 27 import auto_stub | |
| 28 import isolateserver | |
| 29 | |
| 30 from utils import threading_utils | |
| 31 | |
| 32 | |
| 33 ALGO = hashlib.sha1 | |
| 34 | |
| 35 | |
| 36 class TestCase(auto_stub.TestCase): | |
| 37 def setUp(self): | |
| 38 super(TestCase, self).setUp() | |
| 39 self.mock(isolateserver.net, 'url_open', self._url_open) | |
| 40 self.mock(isolateserver.net, 'sleep_before_retry', lambda *_: None) | |
| 41 self._lock = threading.Lock() | |
| 42 self._requests = [] | |
| 43 | |
| 44 def tearDown(self): | |
| 45 try: | |
| 46 self.assertEqual([], self._requests) | |
| 47 finally: | |
| 48 super(TestCase, self).tearDown() | |
| 49 | |
| 50 def _url_open(self, url, **kwargs): | |
| 51 logging.warn('url_open(%s, %s)', url[:500], str(kwargs)[:500]) | |
| 52 with self._lock: | |
| 53 if not self._requests: | |
| 54 return None | |
| 55 # Ignore 'stream' argument, it's not important for these tests. | |
| 56 kwargs.pop('stream', None) | |
| 57 for i, n in enumerate(self._requests): | |
| 58 if n[0] == url: | |
| 59 _, expected_kwargs, result = self._requests.pop(i) | |
| 60 self.assertEqual(expected_kwargs, kwargs) | |
| 61 if result is not None: | |
| 62 return isolateserver.net.HttpResponse.get_fake_response(result, url) | |
| 63 return None | |
| 64 self.fail('Unknown request %s' % url) | |
| 65 | |
| 66 | |
| 67 class TestZipCompression(TestCase): | |
| 68 """Test zip_compress and zip_decompress generators.""" | |
| 69 | |
| 70 def test_compress_and_decompress(self): | |
| 71 """Test data === decompress(compress(data)).""" | |
| 72 original = [str(x) for x in xrange(0, 1000)] | |
| 73 processed = isolateserver.zip_decompress( | |
| 74 isolateserver.zip_compress(original)) | |
| 75 self.assertEqual(''.join(original), ''.join(processed)) | |
| 76 | |
| 77 def test_zip_bomb(self): | |
| 78 """Verify zip_decompress always returns small chunks.""" | |
| 79 original = '\x00' * 100000 | |
| 80 bomb = ''.join(isolateserver.zip_compress(original)) | |
| 81 decompressed = [] | |
| 82 chunk_size = 1000 | |
| 83 for chunk in isolateserver.zip_decompress([bomb], chunk_size): | |
| 84 self.assertLessEqual(len(chunk), chunk_size) | |
| 85 decompressed.append(chunk) | |
| 86 self.assertEqual(original, ''.join(decompressed)) | |
| 87 | |
| 88 def test_bad_zip_file(self): | |
| 89 """Verify decompressing broken file raises IOError.""" | |
| 90 with self.assertRaises(IOError): | |
| 91 ''.join(isolateserver.zip_decompress(['Im not a zip file'])) | |
| 92 | |
| 93 | |
| 94 class FakeItem(isolateserver.Item): | |
| 95 def __init__(self, data, is_isolated=False): | |
| 96 super(FakeItem, self).__init__( | |
| 97 ALGO(data).hexdigest(), len(data), is_isolated) | |
| 98 self.data = data | |
| 99 | |
| 100 def content(self, _chunk_size): | |
| 101 return [self.data] | |
| 102 | |
| 103 @property | |
| 104 def zipped(self): | |
| 105 return zlib.compress(self.data, self.compression_level) | |
| 106 | |
| 107 | |
| 108 class StorageTest(TestCase): | |
| 109 """Tests for Storage methods.""" | |
| 110 | |
| 111 @staticmethod | |
| 112 def mock_push(side_effect=None): | |
| 113 """Returns StorageApi subclass with mocked 'push' method.""" | |
| 114 class MockedStorageApi(isolateserver.StorageApi): | |
| 115 def __init__(self): | |
| 116 self.pushed = [] | |
| 117 def push(self, item, content): | |
| 118 self.pushed.append((item, ''.join(content))) | |
| 119 if side_effect: | |
| 120 side_effect() | |
| 121 return MockedStorageApi() | |
| 122 | |
| 123 def assertEqualIgnoringOrder(self, a, b): | |
| 124 """Asserts that containers |a| and |b| contain same items.""" | |
| 125 self.assertEqual(len(a), len(b)) | |
| 126 self.assertEqual(set(a), set(b)) | |
| 127 | |
| 128 def test_batch_items_for_check(self): | |
| 129 items = [ | |
| 130 isolateserver.Item('foo', 12), | |
| 131 isolateserver.Item('blow', 0), | |
| 132 isolateserver.Item('bizz', 1222), | |
| 133 isolateserver.Item('buzz', 1223), | |
| 134 ] | |
| 135 expected = [ | |
| 136 [items[3], items[2], items[0], items[1]], | |
| 137 ] | |
| 138 batches = list(isolateserver.Storage.batch_items_for_check(items)) | |
| 139 self.assertEqual(batches, expected) | |
| 140 | |
| 141 def test_get_missing_items(self): | |
| 142 items = [ | |
| 143 isolateserver.Item('foo', 12), | |
| 144 isolateserver.Item('blow', 0), | |
| 145 isolateserver.Item('bizz', 1222), | |
| 146 isolateserver.Item('buzz', 1223), | |
| 147 ] | |
| 148 missing = [ | |
| 149 [items[2], items[3]], | |
| 150 ] | |
| 151 | |
| 152 class MockedStorageApi(isolateserver.StorageApi): | |
| 153 def contains(self, _items): | |
| 154 return missing | |
| 155 storage = isolateserver.Storage(MockedStorageApi(), use_zip=False) | |
| 156 | |
| 157 # 'get_missing_items' is a generator, materialize its result in a list. | |
| 158 result = list(storage.get_missing_items(items)) | |
| 159 self.assertEqual(missing, result) | |
| 160 | |
| 161 def test_async_push(self): | |
| 162 for use_zip in (False, True): | |
| 163 item = FakeItem('1234567') | |
| 164 storage_api = self.mock_push() | |
| 165 storage = isolateserver.Storage(storage_api, use_zip) | |
| 166 channel = threading_utils.TaskChannel() | |
| 167 storage.async_push(channel, 0, item) | |
| 168 # Wait for push to finish. | |
| 169 pushed_item = channel.pull() | |
| 170 self.assertEqual(item, pushed_item) | |
| 171 # StorageApi.push was called with correct arguments. | |
| 172 self.assertEqual( | |
| 173 [(item, item.zipped if use_zip else item.data)], storage_api.pushed) | |
| 174 | |
| 175 def test_async_push_generator_errors(self): | |
| 176 class FakeException(Exception): | |
| 177 pass | |
| 178 | |
| 179 def faulty_generator(_chunk_size): | |
| 180 yield 'Hi!' | |
| 181 raise FakeException('fake exception') | |
| 182 | |
| 183 for use_zip in (False, True): | |
| 184 item = FakeItem('') | |
| 185 self.mock(item, 'content', faulty_generator) | |
| 186 storage_api = self.mock_push() | |
| 187 storage = isolateserver.Storage(storage_api, use_zip) | |
| 188 channel = threading_utils.TaskChannel() | |
| 189 storage.async_push(channel, 0, item) | |
| 190 with self.assertRaises(FakeException): | |
| 191 channel.pull() | |
| 192 # StorageApi's push should never complete when data can not be read. | |
| 193 self.assertEqual(0, len(storage_api.pushed)) | |
| 194 | |
| 195 def test_async_push_upload_errors(self): | |
| 196 chunk = 'data_chunk' | |
| 197 | |
| 198 def _generator(_chunk_size): | |
| 199 yield chunk | |
| 200 | |
| 201 def push_side_effect(): | |
| 202 raise IOError('Nope') | |
| 203 | |
| 204 # TODO(vadimsh): Retrying push when fetching data from a generator is | |
| 205 # broken now (it reuses same generator instance when retrying). | |
| 206 content_sources = ( | |
| 207 # generator(), | |
| 208 lambda _chunk_size: [chunk], | |
| 209 ) | |
| 210 | |
| 211 for use_zip in (False, True): | |
| 212 for source in content_sources: | |
| 213 item = FakeItem(chunk) | |
| 214 self.mock(item, 'content', source) | |
| 215 storage_api = self.mock_push(push_side_effect) | |
| 216 storage = isolateserver.Storage(storage_api, use_zip) | |
| 217 channel = threading_utils.TaskChannel() | |
| 218 storage.async_push(channel, 0, item) | |
| 219 with self.assertRaises(IOError): | |
| 220 channel.pull() | |
| 221 # First initial attempt + all retries. | |
| 222 attempts = 1 + isolateserver.WorkerPool.RETRIES | |
| 223 # Single push attempt parameters. | |
| 224 expected_push = (item, item.zipped if use_zip else item.data) | |
| 225 # Ensure all pushes are attempted. | |
| 226 self.assertEqual( | |
| 227 [expected_push] * attempts, storage_api.pushed) | |
| 228 | |
| 229 def test_upload_tree(self): | |
| 230 root = 'root' | |
| 231 files = { | |
| 232 'a': { | |
| 233 's': 100, | |
| 234 'h': 'hash_a', | |
| 235 }, | |
| 236 'b': { | |
| 237 's': 200, | |
| 238 'h': 'hash_b', | |
| 239 }, | |
| 240 'c': { | |
| 241 's': 300, | |
| 242 'h': 'hash_c', | |
| 243 }, | |
| 244 'a_copy': { | |
| 245 's': 100, | |
| 246 'h': 'hash_a', | |
| 247 }, | |
| 248 } | |
| 249 files_data = dict((k, 'x' * files[k]['s']) for k in files) | |
| 250 all_hashes = set(f['h'] for f in files.itervalues()) | |
| 251 missing_hashes = set(['hash_a', 'hash_b']) | |
| 252 | |
| 253 # Files read by mocked_file_read. | |
| 254 read_calls = [] | |
| 255 # 'contains' calls. | |
| 256 contains_calls = [] | |
| 257 # 'push' calls. | |
| 258 push_calls = [] | |
| 259 | |
| 260 def mocked_file_read(filepath, _chunk_size=0): | |
| 261 self.assertEqual(root, os.path.dirname(filepath)) | |
| 262 filename = os.path.basename(filepath) | |
| 263 self.assertIn(filename, files_data) | |
| 264 read_calls.append(filename) | |
| 265 return files_data[filename] | |
| 266 self.mock(isolateserver, 'file_read', mocked_file_read) | |
| 267 | |
| 268 class MockedStorageApi(isolateserver.StorageApi): | |
| 269 def contains(self, items): | |
| 270 contains_calls.append(items) | |
| 271 return [i for i in items | |
| 272 if os.path.basename(i.digest) in missing_hashes] | |
| 273 | |
| 274 def push(self, item, content): | |
| 275 push_calls.append((item, ''.join(content))) | |
| 276 | |
| 277 storage_api = MockedStorageApi() | |
| 278 storage = isolateserver.Storage(storage_api, use_zip=False) | |
| 279 storage.upload_tree(root, files) | |
| 280 | |
| 281 # Was reading only missing files. | |
| 282 self.assertEqualIgnoringOrder( | |
| 283 missing_hashes, | |
| 284 [files[path]['h'] for path in read_calls]) | |
| 285 # 'contains' checked for existence of all files. | |
| 286 self.assertEqualIgnoringOrder( | |
| 287 all_hashes, | |
| 288 [i.digest for i in sum(contains_calls, [])]) | |
| 289 # Pushed only missing files. | |
| 290 self.assertEqualIgnoringOrder( | |
| 291 missing_hashes, | |
| 292 [call[0].digest for call in push_calls]) | |
| 293 # Pushing with correct data, size and push urls. | |
| 294 for pushed_item, pushed_content in push_calls: | |
| 295 filenames = [ | |
| 296 name for name, metadata in files.iteritems() | |
| 297 if metadata['h'] == pushed_item.digest | |
| 298 ] | |
| 299 # If there are multiple files that map to same hash, upload_tree chooses | |
| 300 # a first one. | |
| 301 filename = filenames[0] | |
| 302 self.assertEqual(os.path.join(root, filename), pushed_item.path) | |
| 303 self.assertEqual(files_data[filename], pushed_content) | |
| 304 | |
| 305 | |
| 306 class IsolateServerStorageApiTest(TestCase): | |
| 307 @staticmethod | |
| 308 def mock_handshake_request(server, token='fake token', error=None): | |
| 309 handshake_request = { | |
| 310 'client_app_version': isolateserver.__version__, | |
| 311 'fetcher': True, | |
| 312 'protocol_version': isolateserver.ISOLATE_PROTOCOL_VERSION, | |
| 313 'pusher': True, | |
| 314 } | |
| 315 handshake_response = { | |
| 316 'access_token': token, | |
| 317 'error': error, | |
| 318 'protocol_version': isolateserver.ISOLATE_PROTOCOL_VERSION, | |
| 319 'server_app_version': 'mocked server T1000', | |
| 320 } | |
| 321 return ( | |
| 322 server + '/content-gs/handshake', | |
| 323 { | |
| 324 'content_type': 'application/json', | |
| 325 'method': 'POST', | |
| 326 'data': json.dumps(handshake_request, separators=(',', ':')), | |
| 327 }, | |
| 328 json.dumps(handshake_response), | |
| 329 ) | |
| 330 | |
| 331 @staticmethod | |
| 332 def mock_fetch_request(server, namespace, item, data): | |
| 333 return ( | |
| 334 server + '/content-gs/retrieve/%s/%s' % (namespace, item), | |
| 335 {'retry_404': True, 'read_timeout': 60}, | |
| 336 data, | |
| 337 ) | |
| 338 | |
| 339 @staticmethod | |
| 340 def mock_contains_request(server, namespace, token, request, response): | |
| 341 url = server + '/content-gs/pre-upload/%s?token=%s' % ( | |
| 342 namespace, urllib.quote(token)) | |
| 343 return ( | |
| 344 url, | |
| 345 { | |
| 346 'data': json.dumps(request, separators=(',', ':')), | |
| 347 'content_type': 'application/json', | |
| 348 'method': 'POST', | |
| 349 }, | |
| 350 json.dumps(response), | |
| 351 ) | |
| 352 | |
| 353 def test_server_capabilities_success(self): | |
| 354 server = 'http://example.com' | |
| 355 namespace = 'default' | |
| 356 access_token = 'fake token' | |
| 357 self._requests = [ | |
| 358 self.mock_handshake_request(server, access_token), | |
| 359 ] | |
| 360 storage = isolateserver.IsolateServer(server, namespace) | |
| 361 caps = storage._server_capabilities | |
| 362 self.assertEqual(access_token, caps['access_token']) | |
| 363 | |
| 364 def test_server_capabilities_network_failure(self): | |
| 365 self.mock(isolateserver.net, 'url_open', lambda *_args, **_kwargs: None) | |
| 366 with self.assertRaises(isolateserver.MappingError): | |
| 367 storage = isolateserver.IsolateServer('http://example.com', 'default') | |
| 368 _ = storage._server_capabilities | |
| 369 | |
| 370 def test_server_capabilities_format_failure(self): | |
| 371 server = 'http://example.com' | |
| 372 namespace = 'default' | |
| 373 handshake_req = self.mock_handshake_request(server) | |
| 374 self._requests = [ | |
| 375 (handshake_req[0], handshake_req[1], 'Im a bad response'), | |
| 376 ] | |
| 377 storage = isolateserver.IsolateServer(server, namespace) | |
| 378 with self.assertRaises(isolateserver.MappingError): | |
| 379 _ = storage._server_capabilities | |
| 380 | |
| 381 def test_server_capabilities_respects_error(self): | |
| 382 server = 'http://example.com' | |
| 383 namespace = 'default' | |
| 384 error = 'Im sorry, Dave. Im afraid I cant do that.' | |
| 385 self._requests = [ | |
| 386 self.mock_handshake_request(server, error=error) | |
| 387 ] | |
| 388 storage = isolateserver.IsolateServer(server, namespace) | |
| 389 with self.assertRaises(isolateserver.MappingError) as context: | |
| 390 _ = storage._server_capabilities | |
| 391 # Server error message should be reported to user. | |
| 392 self.assertIn(error, str(context.exception)) | |
| 393 | |
| 394 def test_fetch_success(self): | |
| 395 server = 'http://example.com' | |
| 396 namespace = 'default' | |
| 397 data = ''.join(str(x) for x in xrange(1000)) | |
| 398 item = ALGO(data).hexdigest() | |
| 399 self._requests = [ | |
| 400 self.mock_fetch_request(server, namespace, item, data), | |
| 401 ] | |
| 402 storage = isolateserver.IsolateServer(server, namespace) | |
| 403 fetched = ''.join(storage.fetch(item)) | |
| 404 self.assertEqual(data, fetched) | |
| 405 | |
| 406 def test_fetch_failure(self): | |
| 407 server = 'http://example.com' | |
| 408 namespace = 'default' | |
| 409 item = ALGO('something').hexdigest() | |
| 410 self._requests = [ | |
| 411 self.mock_fetch_request(server, namespace, item, None), | |
| 412 ] | |
| 413 storage = isolateserver.IsolateServer(server, namespace) | |
| 414 with self.assertRaises(IOError): | |
| 415 _ = ''.join(storage.fetch(item)) | |
| 416 | |
| 417 def test_push_success(self): | |
| 418 server = 'http://example.com' | |
| 419 namespace = 'default' | |
| 420 token = 'fake token' | |
| 421 data = ''.join(str(x) for x in xrange(1000)) | |
| 422 item = FakeItem(data) | |
| 423 push_urls = (server + '/push_here', server + '/call_this') | |
| 424 contains_request = [{'h': item.digest, 's': item.size, 'i': 0}] | |
| 425 contains_response = [push_urls] | |
| 426 self._requests = [ | |
| 427 self.mock_handshake_request(server, token), | |
| 428 self.mock_contains_request( | |
| 429 server, namespace, token, contains_request, contains_response), | |
| 430 ( | |
| 431 push_urls[0], | |
| 432 { | |
| 433 'data': data, | |
| 434 'content_type': 'application/octet-stream', | |
| 435 'method': 'PUT', | |
| 436 }, | |
| 437 '' | |
| 438 ), | |
| 439 ( | |
| 440 push_urls[1], | |
| 441 { | |
| 442 'data': '', | |
| 443 'content_type': 'application/json', | |
| 444 'method': 'POST', | |
| 445 }, | |
| 446 '' | |
| 447 ), | |
| 448 ] | |
| 449 storage = isolateserver.IsolateServer(server, namespace) | |
| 450 missing = storage.contains([item]) | |
| 451 self.assertEqual([item], missing) | |
| 452 storage.push(item, [data]) | |
| 453 self.assertTrue(item.push_state.uploaded) | |
| 454 self.assertTrue(item.push_state.finalized) | |
| 455 | |
| 456 def test_push_failure_upload(self): | |
| 457 server = 'http://example.com' | |
| 458 namespace = 'default' | |
| 459 token = 'fake token' | |
| 460 data = ''.join(str(x) for x in xrange(1000)) | |
| 461 item = FakeItem(data) | |
| 462 push_urls = (server + '/push_here', server + '/call_this') | |
| 463 contains_request = [{'h': item.digest, 's': item.size, 'i': 0}] | |
| 464 contains_response = [push_urls] | |
| 465 self._requests = [ | |
| 466 self.mock_handshake_request(server, token), | |
| 467 self.mock_contains_request( | |
| 468 server, namespace, token, contains_request, contains_response), | |
| 469 ( | |
| 470 push_urls[0], | |
| 471 { | |
| 472 'data': data, | |
| 473 'content_type': 'application/octet-stream', | |
| 474 'method': 'PUT', | |
| 475 }, | |
| 476 None | |
| 477 ), | |
| 478 ] | |
| 479 storage = isolateserver.IsolateServer(server, namespace) | |
| 480 missing = storage.contains([item]) | |
| 481 self.assertEqual([item], missing) | |
| 482 with self.assertRaises(IOError): | |
| 483 storage.push(item, [data]) | |
| 484 self.assertFalse(item.push_state.uploaded) | |
| 485 self.assertFalse(item.push_state.finalized) | |
| 486 | |
| 487 def test_push_failure_finalize(self): | |
| 488 server = 'http://example.com' | |
| 489 namespace = 'default' | |
| 490 token = 'fake token' | |
| 491 data = ''.join(str(x) for x in xrange(1000)) | |
| 492 item = FakeItem(data) | |
| 493 push_urls = (server + '/push_here', server + '/call_this') | |
| 494 contains_request = [{'h': item.digest, 's': item.size, 'i': 0}] | |
| 495 contains_response = [push_urls] | |
| 496 self._requests = [ | |
| 497 self.mock_handshake_request(server, token), | |
| 498 self.mock_contains_request( | |
| 499 server, namespace, token, contains_request, contains_response), | |
| 500 ( | |
| 501 push_urls[0], | |
| 502 { | |
| 503 'data': data, | |
| 504 'content_type': 'application/octet-stream', | |
| 505 'method': 'PUT', | |
| 506 }, | |
| 507 '' | |
| 508 ), | |
| 509 ( | |
| 510 push_urls[1], | |
| 511 { | |
| 512 'data': '', | |
| 513 'content_type': 'application/json', | |
| 514 'method': 'POST', | |
| 515 }, | |
| 516 None | |
| 517 ), | |
| 518 ] | |
| 519 storage = isolateserver.IsolateServer(server, namespace) | |
| 520 missing = storage.contains([item]) | |
| 521 self.assertEqual([item], missing) | |
| 522 with self.assertRaises(IOError): | |
| 523 storage.push(item, [data]) | |
| 524 self.assertTrue(item.push_state.uploaded) | |
| 525 self.assertFalse(item.push_state.finalized) | |
| 526 | |
| 527 def test_contains_success(self): | |
| 528 server = 'http://example.com' | |
| 529 namespace = 'default' | |
| 530 token = 'fake token' | |
| 531 files = [ | |
| 532 FakeItem('1', is_isolated=True), | |
| 533 FakeItem('2' * 100), | |
| 534 FakeItem('3' * 200), | |
| 535 ] | |
| 536 request = [ | |
| 537 {'h': files[0].digest, 's': files[0].size, 'i': 1}, | |
| 538 {'h': files[1].digest, 's': files[1].size, 'i': 0}, | |
| 539 {'h': files[2].digest, 's': files[2].size, 'i': 0}, | |
| 540 ] | |
| 541 response = [ | |
| 542 None, | |
| 543 ['http://example/upload_here_1', None], | |
| 544 ['http://example/upload_here_2', 'http://example/call_this'], | |
| 545 ] | |
| 546 missing = [ | |
| 547 files[1], | |
| 548 files[2], | |
| 549 ] | |
| 550 self._requests = [ | |
| 551 self.mock_handshake_request(server, token), | |
| 552 self.mock_contains_request(server, namespace, token, request, response), | |
| 553 ] | |
| 554 storage = isolateserver.IsolateServer(server, namespace) | |
| 555 result = storage.contains(files) | |
| 556 self.assertEqual(missing, result) | |
| 557 self.assertEqual( | |
| 558 [x for x in response if x], | |
| 559 [[i.push_state.upload_url, i.push_state.finalize_url] for i in missing]) | |
| 560 | |
| 561 def test_contains_network_failure(self): | |
| 562 server = 'http://example.com' | |
| 563 namespace = 'default' | |
| 564 token = 'fake token' | |
| 565 req = self.mock_contains_request(server, namespace, token, [], []) | |
| 566 self._requests = [ | |
| 567 self.mock_handshake_request(server, token), | |
| 568 (req[0], req[1], None), | |
| 569 ] | |
| 570 storage = isolateserver.IsolateServer(server, namespace) | |
| 571 with self.assertRaises(isolateserver.MappingError): | |
| 572 storage.contains([]) | |
| 573 | |
| 574 def test_contains_format_failure(self): | |
| 575 server = 'http://example.com' | |
| 576 namespace = 'default' | |
| 577 token = 'fake token' | |
| 578 self._requests = [ | |
| 579 self.mock_handshake_request(server, token), | |
| 580 self.mock_contains_request(server, namespace, token, [], [1, 2, 3]) | |
| 581 ] | |
| 582 storage = isolateserver.IsolateServer(server, namespace) | |
| 583 with self.assertRaises(isolateserver.MappingError): | |
| 584 storage.contains([]) | |
| 585 | |
| 586 | |
| 587 class IsolateServerDownloadTest(TestCase): | |
| 588 tempdir = None | |
| 589 | |
| 590 def tearDown(self): | |
| 591 try: | |
| 592 if self.tempdir: | |
| 593 shutil.rmtree(self.tempdir) | |
| 594 finally: | |
| 595 super(IsolateServerDownloadTest, self).tearDown() | |
| 596 | |
| 597 def test_download_two_files(self): | |
| 598 # Test downloading two files. | |
| 599 actual = {} | |
| 600 def out(key, generator): | |
| 601 actual[key] = ''.join(generator) | |
| 602 self.mock(isolateserver, 'file_write', out) | |
| 603 server = 'http://example.com' | |
| 604 self._requests = [ | |
| 605 ( | |
| 606 server + '/content-gs/retrieve/default-gzip/sha-1', | |
| 607 {'read_timeout': 60, 'retry_404': True}, | |
| 608 zlib.compress('Coucou'), | |
| 609 ), | |
| 610 ( | |
| 611 server + '/content-gs/retrieve/default-gzip/sha-2', | |
| 612 {'read_timeout': 60, 'retry_404': True}, | |
| 613 zlib.compress('Bye Bye'), | |
| 614 ), | |
| 615 ] | |
| 616 cmd = [ | |
| 617 'download', | |
| 618 '--isolate-server', server, | |
| 619 '--target', ROOT_DIR, | |
| 620 '--file', 'sha-1', 'path/to/a', | |
| 621 '--file', 'sha-2', 'path/to/b', | |
| 622 ] | |
| 623 self.assertEqual(0, isolateserver.main(cmd)) | |
| 624 expected = { | |
| 625 os.path.join(ROOT_DIR, 'path/to/a'): 'Coucou', | |
| 626 os.path.join(ROOT_DIR, 'path/to/b'): 'Bye Bye', | |
| 627 } | |
| 628 self.assertEqual(expected, actual) | |
| 629 | |
| 630 def test_download_isolated(self): | |
| 631 # Test downloading an isolated tree. | |
| 632 self.tempdir = tempfile.mkdtemp(prefix='isolateserver') | |
| 633 actual = {} | |
| 634 def file_write_mock(key, generator): | |
| 635 actual[key] = ''.join(generator) | |
| 636 self.mock(isolateserver, 'file_write', file_write_mock) | |
| 637 self.mock(os, 'makedirs', lambda _: None) | |
| 638 stdout = StringIO.StringIO() | |
| 639 self.mock(sys, 'stdout', stdout) | |
| 640 server = 'http://example.com' | |
| 641 | |
| 642 files = { | |
| 643 'a/foo': 'Content', | |
| 644 'b': 'More content', | |
| 645 } | |
| 646 isolated = { | |
| 647 'command': ['Absurb', 'command'], | |
| 648 'relative_cwd': 'a', | |
| 649 'files': dict( | |
| 650 (k, {'h': ALGO(v).hexdigest(), 's': len(v)}) | |
| 651 for k, v in files.iteritems()), | |
| 652 } | |
| 653 isolated_data = json.dumps(isolated, sort_keys=True, separators=(',',':')) | |
| 654 isolated_hash = ALGO(isolated_data).hexdigest() | |
| 655 requests = [(v['h'], files[k]) for k, v in isolated['files'].iteritems()] | |
| 656 requests.append((isolated_hash, isolated_data)) | |
| 657 self._requests = [ | |
| 658 ( | |
| 659 server + '/content-gs/retrieve/default-gzip/' + h, | |
| 660 { | |
| 661 'read_timeout': isolateserver.DOWNLOAD_READ_TIMEOUT, | |
| 662 'retry_404': True, | |
| 663 }, | |
| 664 zlib.compress(v), | |
| 665 ) for h, v in requests | |
| 666 ] | |
| 667 cmd = [ | |
| 668 'download', | |
| 669 '--isolate-server', server, | |
| 670 '--target', self.tempdir, | |
| 671 '--isolated', isolated_hash, | |
| 672 ] | |
| 673 self.assertEqual(0, isolateserver.main(cmd)) | |
| 674 expected = dict( | |
| 675 (os.path.join(self.tempdir, k), v) for k, v in files.iteritems()) | |
| 676 self.assertEqual(expected, actual) | |
| 677 expected_stdout = ( | |
| 678 'To run this test please run from the directory %s:\n Absurb command\n' | |
| 679 % os.path.join(self.tempdir, 'a')) | |
| 680 self.assertEqual(expected_stdout, stdout.getvalue()) | |
| 681 | |
| 682 | |
| 683 class TestIsolated(unittest.TestCase): | |
| 684 def test_load_isolated_empty(self): | |
| 685 m = isolateserver.load_isolated('{}', None, ALGO) | |
| 686 self.assertEqual({}, m) | |
| 687 | |
| 688 def test_load_isolated_good(self): | |
| 689 data = { | |
| 690 u'command': [u'foo', u'bar'], | |
| 691 u'files': { | |
| 692 u'a': { | |
| 693 u'l': u'somewhere', | |
| 694 }, | |
| 695 u'b': { | |
| 696 u'm': 123, | |
| 697 u'h': u'0123456789abcdef0123456789abcdef01234567', | |
| 698 u's': 3, | |
| 699 } | |
| 700 }, | |
| 701 u'includes': [u'0123456789abcdef0123456789abcdef01234567'], | |
| 702 u'os': 'oPhone', | |
| 703 u'read_only': False, | |
| 704 u'relative_cwd': u'somewhere_else' | |
| 705 } | |
| 706 m = isolateserver.load_isolated(json.dumps(data), None, ALGO) | |
| 707 self.assertEqual(data, m) | |
| 708 | |
| 709 def test_load_isolated_bad(self): | |
| 710 data = { | |
| 711 u'files': { | |
| 712 u'a': { | |
| 713 u'l': u'somewhere', | |
| 714 u'h': u'0123456789abcdef0123456789abcdef01234567' | |
| 715 } | |
| 716 }, | |
| 717 } | |
| 718 try: | |
| 719 isolateserver.load_isolated(json.dumps(data), None, ALGO) | |
| 720 self.fail() | |
| 721 except isolateserver.ConfigError: | |
| 722 pass | |
| 723 | |
| 724 def test_load_isolated_os_only(self): | |
| 725 data = { | |
| 726 u'os': 'HP/UX', | |
| 727 } | |
| 728 m = isolateserver.load_isolated(json.dumps(data), 'HP/UX', ALGO) | |
| 729 self.assertEqual(data, m) | |
| 730 | |
| 731 def test_load_isolated_os_bad(self): | |
| 732 data = { | |
| 733 u'os': 'foo', | |
| 734 } | |
| 735 try: | |
| 736 isolateserver.load_isolated(json.dumps(data), 'AS/400', ALGO) | |
| 737 self.fail() | |
| 738 except isolateserver.ConfigError: | |
| 739 pass | |
| 740 | |
| 741 def test_load_isolated_path(self): | |
| 742 # Automatically convert the path case. | |
| 743 wrong_path_sep = u'\\' if os.path.sep == '/' else u'/' | |
| 744 def gen_data(path_sep): | |
| 745 return { | |
| 746 u'command': [u'foo', u'bar'], | |
| 747 u'files': { | |
| 748 path_sep.join(('a', 'b')): { | |
| 749 u'l': path_sep.join(('..', 'somewhere')), | |
| 750 }, | |
| 751 }, | |
| 752 u'os': u'oPhone', | |
| 753 u'relative_cwd': path_sep.join(('somewhere', 'else')), | |
| 754 } | |
| 755 | |
| 756 data = gen_data(wrong_path_sep) | |
| 757 actual = isolateserver.load_isolated(json.dumps(data), None, ALGO) | |
| 758 expected = gen_data(os.path.sep) | |
| 759 self.assertEqual(expected, actual) | |
| 760 | |
| 761 | |
| 762 if __name__ == '__main__': | |
| 763 if '-v' in sys.argv: | |
| 764 unittest.TestCase.maxDiff = None | |
| 765 logging.basicConfig( | |
| 766 level=(logging.DEBUG if '-v' in sys.argv else logging.ERROR)) | |
| 767 unittest.main() | |
| OLD | NEW |