Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: tests/isolateserver_test.py

Issue 25093003: Client side implementation of new /content-gs isolate protocol. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/swarm_client
Patch Set: tests Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
19 import urllib
21 import zlib 20 import zlib
22 21
23 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) 22 BASE_PATH = os.path.dirname(os.path.abspath(__file__))
24 ROOT_DIR = os.path.dirname(BASE_PATH) 23 ROOT_DIR = os.path.dirname(BASE_PATH)
25 sys.path.insert(0, ROOT_DIR) 24 sys.path.insert(0, ROOT_DIR)
26 25
27 import auto_stub 26 import auto_stub
28 import isolateserver 27 import isolateserver
29 28
30 from utils import threading_utils 29 from utils import threading_utils
(...skipping 26 matching lines...) Expand all
57 for i, n in enumerate(self._requests): 56 for i, n in enumerate(self._requests):
58 if n[0] == url: 57 if n[0] == url:
59 _, expected_kwargs, result = self._requests.pop(i) 58 _, expected_kwargs, result = self._requests.pop(i)
60 self.assertEqual(expected_kwargs, kwargs) 59 self.assertEqual(expected_kwargs, kwargs)
61 if result is not None: 60 if result is not None:
62 return isolateserver.net.HttpResponse.get_fake_response(result, url) 61 return isolateserver.net.HttpResponse.get_fake_response(result, url)
63 return None 62 return None
64 self.fail('Unknown request %s' % url) 63 self.fail('Unknown request %s' % url)
65 64
66 65
66 class TestZipCompression(TestCase):
67 """Test zip_compress and zip_decompress generators."""
68
69 def test_compress_and_decompress(self):
70 """Test data === decompress(compress(data))."""
71 original = [str(x) for x in xrange(0, 1000)]
72 processed = isolateserver.zip_decompress(
73 isolateserver.zip_compress(original))
74 self.assertEqual(''.join(original), ''.join(processed))
75
76 def test_zip_bomb(self):
77 """Verify zip_decompress always returns small chunks."""
78 original = '\x00' * 100000
79 bomb = ''.join(isolateserver.zip_compress(original))
80 decompressed = []
81 chunk_size = 1000
82 for chunk in isolateserver.zip_decompress([bomb], chunk_size):
83 self.assertLessEqual(len(chunk), chunk_size)
84 decompressed.append(chunk)
85 self.assertEqual(original, ''.join(decompressed))
86
87 def test_bad_zip_file(self):
88 """Verify decompressing broken file raises IOError."""
89 with self.assertRaises(IOError):
90 ''.join(isolateserver.zip_decompress(['Im not a zip file']))
91
92
93 class FakeItem(isolateserver.Item):
94 def __init__(self, data, is_isolated=False):
95 super(FakeItem, self).__init__(
96 ALGO(data).hexdigest(), len(data), is_isolated)
97 self.data = data
98
99 def content(self, _chunk_size):
100 return [self.data]
101
102 @property
103 def zipped(self):
104 return zlib.compress(self.data, self.compression_level)
105
106
67 class StorageTest(TestCase): 107 class StorageTest(TestCase):
68 """Tests for Storage methods.""" 108 """Tests for Storage methods."""
69 109
70 @staticmethod 110 @staticmethod
71 def mock_push(side_effect=None): 111 def mock_push(side_effect=None):
72 """Returns StorageApi subclass with mocked 'push' method.""" 112 """Returns StorageApi subclass with mocked 'push' method."""
73 class MockedStorageApi(isolateserver.StorageApi): 113 class MockedStorageApi(isolateserver.StorageApi):
74 def __init__(self): 114 def __init__(self):
75 self.pushed = [] 115 self.pushed = []
76 def push(self, item, expected_size, content_generator, push_urls=None): 116 def push(self, item, content, size):
77 self.pushed.append( 117 self.pushed.append((item, ''.join(content), size))
78 (item, expected_size, ''.join(content_generator), push_urls))
79 if side_effect: 118 if side_effect:
80 side_effect() 119 side_effect()
81 return MockedStorageApi() 120 return MockedStorageApi()
82 121
83 def test_batch_files_for_check(self): 122 def test_batch_items_for_check(self):
84 items = { 123 items = [
85 'foo': {'s': 12}, 124 isolateserver.Item('foo', 12),
86 'bar': {}, 125 isolateserver.Item('blow', 0),
87 'blow': {'s': 0}, 126 isolateserver.Item('bizz', 1222),
88 'bizz': {'s': 1222}, 127 isolateserver.Item('buzz', 1223),
89 'buzz': {'s': 1223}, 128 ]
90 }
91 expected = [ 129 expected = [
92 [ 130 [items[3], items[2], items[0], items[1]],
93 ('buzz', {'s': 1223}),
94 ('bizz', {'s': 1222}),
95 ('foo', {'s': 12}),
96 ('blow', {'s': 0}),
97 ],
98 ] 131 ]
99 batches = list(isolateserver.Storage.batch_files_for_check(items)) 132 batches = list(isolateserver.Storage.batch_items_for_check(items))
100 self.assertEqual(batches, expected) 133 self.assertEqual(batches, expected)
101 134
102 def test_get_missing_files(self): 135 def test_get_missing_items(self):
103 items = { 136 items = [
104 'foo': {'s': 12}, 137 isolateserver.Item('foo', 12),
105 'bar': {}, 138 isolateserver.Item('blow', 0),
106 'blow': {'s': 0}, 139 isolateserver.Item('bizz', 1222),
107 'bizz': {'s': 1222}, 140 isolateserver.Item('buzz', 1223),
108 'buzz': {'s': 1223}, 141 ]
109 } 142 missing = [
110 missing = { 143 [items[2], items[3]],
111 'bizz': {'s': 1222}, 144 ]
112 'buzz': {'s': 1223},
113 }
114 fake_upload_urls = ('a', 'b')
115 145
116 class MockedStorageApi(isolateserver.StorageApi): 146 class MockedStorageApi(isolateserver.StorageApi):
117 def contains(self, files): 147 def contains(self, _items):
118 return [f + (fake_upload_urls,) for f in files if f[0] in missing] 148 return missing
119 storage = isolateserver.Storage(MockedStorageApi(), use_zip=False) 149 storage = isolateserver.Storage(MockedStorageApi(), use_zip=False)
120 150
121 # 'get_missing_files' is a generator, materialize its result in a list. 151 # 'get_missing_items' is a generator, materialize its result in a list.
122 result = list(storage.get_missing_files(items)) 152 result = list(storage.get_missing_items(items))
123 153 self.assertEqual(missing, result)
124 # Ensure it's a list of triplets.
125 self.assertTrue(all(len(x) == 3 for x in result))
126 # Verify upload urls are set.
127 self.assertTrue(all(x[2] == fake_upload_urls for x in result))
128 # 'get_missing_files' doesn't guarantee order of its results, so convert
129 # it to unordered dict and compare dicts.
130 self.assertEqual(dict(x[:2] for x in result), missing)
131 154
132 def test_async_push(self): 155 def test_async_push(self):
133 data_to_push = '1234567'
134 digest = ALGO(data_to_push).hexdigest()
135 compression_level = 5
136 zipped = zlib.compress(data_to_push, compression_level)
137 push_urls = ('fake1', 'fake2')
138
139 for use_zip in (False, True): 156 for use_zip in (False, True):
157 item = FakeItem('1234567')
140 storage_api = self.mock_push() 158 storage_api = self.mock_push()
141 storage = isolateserver.Storage(storage_api, use_zip) 159 storage = isolateserver.Storage(storage_api, use_zip)
142 channel = threading_utils.TaskChannel() 160 channel = threading_utils.TaskChannel()
143 storage.async_push( 161 storage.async_push(channel, 0, item)
144 channel, 0, digest, len(data_to_push), [data_to_push],
145 compression_level, push_urls)
146 # Wait for push to finish. 162 # Wait for push to finish.
147 pushed_item = channel.pull() 163 pushed_item = channel.pull()
148 self.assertEqual(digest, pushed_item) 164 self.assertEqual(item, pushed_item)
149 # StorageApi.push was called with correct arguments. 165 # StorageApi.push was called with correct arguments.
150 if use_zip: 166 if use_zip:
151 expected_data = zipped 167 expected_data = item.zipped
152 expected_size = isolateserver.UNKNOWN_FILE_SIZE 168 expected_size = isolateserver.UNKNOWN_FILE_SIZE
153 else: 169 else:
154 expected_data = data_to_push 170 expected_data = item.data
155 expected_size = len(data_to_push) 171 expected_size = len(item.data)
156 self.assertEqual( 172 self.assertEqual(
157 [(digest, expected_size, expected_data, push_urls)], 173 [(item, expected_data, expected_size)],
158 storage_api.pushed) 174 storage_api.pushed)
159 175
160 def test_async_push_generator_errors(self): 176 def test_async_push_generator_errors(self):
161 class FakeException(Exception): 177 class FakeException(Exception):
162 pass 178 pass
163 179
164 def faulty_generator(): 180 def faulty_generator(_chunk_size):
165 yield 'Hi!' 181 yield 'Hi!'
166 raise FakeException('fake exception') 182 raise FakeException('fake exception')
167 183
168 for use_zip in (False, True): 184 for use_zip in (False, True):
185 item = FakeItem('')
186 self.mock(item, 'content', faulty_generator)
169 storage_api = self.mock_push() 187 storage_api = self.mock_push()
170 storage = isolateserver.Storage(storage_api, use_zip) 188 storage = isolateserver.Storage(storage_api, use_zip)
171 channel = threading_utils.TaskChannel() 189 channel = threading_utils.TaskChannel()
172 storage.async_push( 190 storage.async_push(channel, 0, item)
173 channel, 0, 'item', isolateserver.UNKNOWN_FILE_SIZE,
174 faulty_generator(), 0, None)
175 with self.assertRaises(FakeException): 191 with self.assertRaises(FakeException):
176 channel.pull() 192 channel.pull()
177 # StorageApi's push should never complete when data can not be read. 193 # StorageApi's push should never complete when data can not be read.
178 self.assertEqual(0, len(storage_api.pushed)) 194 self.assertEqual(0, len(storage_api.pushed))
179 195
180 def test_async_push_upload_errors(self): 196 def test_async_push_upload_errors(self):
181 chunk = 'data_chunk' 197 chunk = 'data_chunk'
182 compression_level = 5
183 zipped = zlib.compress(chunk, compression_level)
184 198
185 def _generator(): 199 def _generator(_chunk_size):
186 yield chunk 200 yield chunk
187 201
188 def push_side_effect(): 202 def push_side_effect():
189 raise IOError('Nope') 203 raise IOError('Nope')
190 204
191 # TODO(vadimsh): Retrying push when fetching data from a generator is 205 # TODO(vadimsh): Retrying push when fetching data from a generator is
192 # broken now (it reuses same generator instance when retrying). 206 # broken now (it reuses same generator instance when retrying).
193 content_sources = ( 207 content_sources = (
194 # generator(), 208 # generator(),
195 [chunk], 209 lambda _chunk_size: [chunk],
196 ) 210 )
197 211
198 for use_zip in (False, True): 212 for use_zip in (False, True):
199 for source in content_sources: 213 for source in content_sources:
214 item = FakeItem(chunk)
215 self.mock(item, 'content', source)
200 storage_api = self.mock_push(push_side_effect) 216 storage_api = self.mock_push(push_side_effect)
201 storage = isolateserver.Storage(storage_api, use_zip) 217 storage = isolateserver.Storage(storage_api, use_zip)
202 channel = threading_utils.TaskChannel() 218 channel = threading_utils.TaskChannel()
203 storage.async_push( 219 storage.async_push(channel, 0, item)
204 channel, 0, 'item', isolateserver.UNKNOWN_FILE_SIZE,
205 source, compression_level, None)
206 with self.assertRaises(IOError): 220 with self.assertRaises(IOError):
207 channel.pull() 221 channel.pull()
208 # First initial attempt + all retries. 222 # First initial attempt + all retries.
209 attempts = 1 + isolateserver.WorkerPool.RETRIES 223 attempts = 1 + isolateserver.WorkerPool.RETRIES
210 # Single push attempt parameters. 224 # Single push attempt parameters.
211 expected_push = ( 225 expected_push = (
212 'item', isolateserver.UNKNOWN_FILE_SIZE, 226 item,
213 zipped if use_zip else chunk, None) 227 item.zipped if use_zip else item.data,
228 isolateserver.UNKNOWN_FILE_SIZE if use_zip else item.size)
214 # Ensure all pushes are attempted. 229 # Ensure all pushes are attempted.
215 self.assertEqual( 230 self.assertEqual(
216 [expected_push] * attempts, storage_api.pushed) 231 [expected_push] * attempts, storage_api.pushed)
217 232
218 def test_upload_tree(self): 233 def test_upload_tree(self):
219 root = 'root' 234 root = 'root'
220 files = { 235 files = {
221 'a': { 236 'a': {
222 's': 100, 237 's': 100,
223 'h': 'hash_a', 238 'h': 'hash_a',
224 }, 239 },
225 'b': { 240 'b': {
226 's': 200, 241 's': 200,
227 'h': 'hash_b', 242 'h': 'hash_b',
228 }, 243 },
229 'c': { 244 'c': {
230 's': 300, 245 's': 300,
231 'h': 'hash_c', 246 'h': 'hash_c',
232 }, 247 },
233 } 248 }
234 push_urls = { 249 files_data = dict((k, 'x' * files[k]['s']) for k in files)
235 'a': ('upload_a', 'finalize_a'),
236 'b': ('upload_b', None),
237 'c': ('upload_c', None),
238 }
239 files_data = dict((k, k * files[k]['s']) for k in files)
240 missing = set(['a', 'b']) 250 missing = set(['a', 'b'])
241 251
242 # Files read by mocked_file_read. 252 # Files read by mocked_file_read.
243 read_calls = [] 253 read_calls = []
244 # 'contains' calls. 254 # 'contains' calls.
245 contains_calls = [] 255 contains_calls = []
246 # 'push' calls. 256 # 'push' calls.
247 push_calls = [] 257 push_calls = []
248 258
249 def mocked_file_read(filepath, _chunk_size=0): 259 def mocked_file_read(filepath, _chunk_size=0):
250 self.assertEqual(root, os.path.dirname(filepath)) 260 self.assertEqual(root, os.path.dirname(filepath))
251 filename = os.path.basename(filepath) 261 filename = os.path.basename(filepath)
252 self.assertIn(filename, files_data) 262 self.assertIn(filename, files_data)
253 read_calls.append(filename) 263 read_calls.append(filename)
254 return files_data[filename] 264 return files_data[filename]
255 self.mock(isolateserver, 'file_read', mocked_file_read) 265 self.mock(isolateserver, 'file_read', mocked_file_read)
256 266
257 class MockedStorageApi(isolateserver.StorageApi): 267 class MockedStorageApi(isolateserver.StorageApi):
258 def contains(self, files): 268 def contains(self, items):
259 contains_calls.append(files) 269 contains_calls.append(items)
260 return [f + (push_urls[f[0]],) for f in files if f[0] in missing] 270 return [i for i in items if os.path.basename(i.path) in missing]
261 271
262 def push(self, item, expected_size, content_generator, push_urls=None): 272 def push(self, item, content, size):
263 push_calls.append( 273 push_calls.append((item, ''.join(content), size))
264 (item, expected_size, ''.join(content_generator), push_urls))
265 274
266 storage_api = MockedStorageApi() 275 storage_api = MockedStorageApi()
267 storage = isolateserver.Storage(storage_api, use_zip=False) 276 storage = isolateserver.Storage(storage_api, use_zip=False)
268 storage.upload_tree(root, files) 277 storage.upload_tree(root, files)
269 278
270 # Was reading only missing files. 279 # Was reading only missing files.
271 self.assertEqual(missing, set(read_calls)) 280 self.assertEqual(missing, set(read_calls))
272 # 'contains' checked for existence of all files. 281 # 'contains' checked for existence of all files.
273 self.assertEqual(files, dict(sum(contains_calls, []))) 282 self.assertEqual(
283 set(f['h'] for f in files.itervalues()),
284 set(i.digest for i in sum(contains_calls, [])))
274 # Pushed only missing files. 285 # Pushed only missing files.
275 self.assertEqual( 286 self.assertEqual(
276 set(files[name]['h'] for name in missing), 287 set(files[name]['h'] for name in missing),
277 set(call[0] for call in push_calls)) 288 set(call[0].digest for call in push_calls))
278 # Pushing with correct data, size and push urls. 289 # Pushing with correct data, size and push urls.
279 for push_call in push_calls: 290 for pushed_item, pushed_content, pushed_size in push_calls:
280 digest = push_call[0]
281 filenames = [ 291 filenames = [
282 name for name, metadata in files.iteritems() 292 name for name, metadata in files.iteritems()
283 if metadata['h'] == digest 293 if metadata['h'] == pushed_item.digest
284 ] 294 ]
285 self.assertEqual(1, len(filenames)) 295 self.assertEqual(1, len(filenames))
286 filename = filenames[0] 296 filename = filenames[0]
287 data = files_data[filename] 297 self.assertEqual(os.path.join(root, filename), pushed_item.path)
288 self.assertEqual( 298 self.assertEqual(files_data[filename], pushed_content)
289 (digest, len(data), data, push_urls[filename]), 299 self.assertEqual(len(files_data[filename]), pushed_size)
290 push_call) 300
291 301
292 302 class IsolateServerStorageApiTest(TestCase):
293 class IsolateServerArchiveTest(TestCase): 303 @staticmethod
294 def setUp(self): 304 def mock_handshake_request(server, token='fake token', error=None):
295 super(IsolateServerArchiveTest, self).setUp() 305 handshake_request = {
296 self.mock(isolateserver, 'randomness', lambda: 'not_really_random') 306 'client_app_version': isolateserver.__version__,
297 self.mock(sys, 'stdout', StringIO.StringIO()) 307 'fetcher': True,
298 308 'protocol_version': isolateserver.ISOLATE_PROTOCOL_VERSION,
299 def test_present(self): 309 'pusher': True,
310 }
311 handshake_response = {
312 'access_token': token,
313 'error': error,
314 'protocol_version': isolateserver.ISOLATE_PROTOCOL_VERSION,
315 'server_app_version': 'mocked server T1000',
316 }
317 return (
318 server + '/content-gs/handshake',
319 {
320 'content_type': 'application/json',
321 'method': 'POST',
322 'data': json.dumps(handshake_request, separators=(',', ':')),
323 },
324 json.dumps(handshake_response),
325 )
326
327 @staticmethod
328 def mock_fetch_request(server, namespace, item, data):
329 return (
330 server + '/content-gs/retrieve/%s/%s' % (namespace, item),
331 {'retry_404': True, 'read_timeout': 60},
332 data,
333 )
334
335 @staticmethod
336 def mock_contains_request(server, namespace, token, request, response):
337 url = server + '/content-gs/pre-upload/%s?token=%s' % (
338 namespace, urllib.quote(token))
339 return (
340 url,
341 {
342 'data': json.dumps(request, separators=(',', ':')),
343 'content_type': 'application/json',
344 'method': 'POST',
345 },
346 json.dumps(response),
347 )
348
349 def test_server_capabilities_success(self):
350 server = 'http://example.com'
351 namespace = 'default'
352 access_token = 'fake token'
353 self._requests = [
354 self.mock_handshake_request(server, access_token),
355 ]
356 storage = isolateserver.IsolateServer(server, namespace)
357 caps = storage.server_capabilities
358 self.assertEqual(access_token, caps['access_token'])
359
360 def test_server_capabilities_network_failure(self):
361 self.mock(isolateserver.net, 'url_open', lambda *_args, **_kwargs: None)
362 with self.assertRaises(isolateserver.MappingError):
363 storage = isolateserver.IsolateServer('http://example.com', 'default')
364 _ = storage.server_capabilities
365
366 def test_server_capabilities_format_failure(self):
367 server = 'http://example.com'
368 namespace = 'default'
369 handshake_req = self.mock_handshake_request(server)
370 self._requests = [
371 (handshake_req[0], handshake_req[1], 'Im a bad response'),
372 ]
373 storage = isolateserver.IsolateServer(server, namespace)
374 with self.assertRaises(isolateserver.MappingError):
375 _ = storage.server_capabilities
376
377 def test_server_capabilities_respects_error(self):
378 server = 'http://example.com'
379 namespace = 'default'
380 error = 'Im sorry, Dave. Im afraid I cant do that.'
381 self._requests = [
382 self.mock_handshake_request(server, error=error)
383 ]
384 storage = isolateserver.IsolateServer(server, namespace)
385 with self.assertRaises(isolateserver.MappingError) as context:
386 _ = storage.server_capabilities
387 # Server error message should be reported to user.
388 self.assertIn(error, str(context.exception))
389
390 def test_fetch_success_default(self):
391 server = 'http://example.com'
392 namespace = 'default'
393 data = ''.join(str(x) for x in xrange(1000))
394 item = ALGO(data).hexdigest()
395 self._requests = [
396 self.mock_fetch_request(server, namespace, item, data),
397 ]
398 storage = isolateserver.IsolateServer(server, namespace)
399 fetched = ''.join(storage.fetch(item, len(data)))
400 self.assertEqual(data, fetched)
401
402 def test_fetch_success_default_gzip(self):
403 server = 'http://example.com'
404 namespace = 'default-gzip'
405 data = ''.join(str(x) for x in xrange(1000))
406 item = ALGO(data).hexdigest()
407 self._requests = [
408 self.mock_fetch_request(server, namespace, item, zlib.compress(data)),
409 ]
410 storage = isolateserver.IsolateServer(server, namespace)
411 fetched = ''.join(storage.fetch(item, len(data)))
412 self.assertEqual(data, fetched)
413
414 def test_fetch_failure_missing(self):
415 server = 'http://example.com'
416 namespace = 'default'
417 item = ALGO('something').hexdigest()
418 self._requests = [
419 self.mock_fetch_request(server, namespace, item, None),
420 ]
421 storage = isolateserver.IsolateServer(server, namespace)
422 with self.assertRaises(IOError):
423 _ = ''.join(storage.fetch(item, isolateserver.UNKNOWN_FILE_SIZE))
424
425 def test_fetch_failure_bad_size(self):
426 server = 'http://example.com'
427 namespace = 'default'
428 data = ''.join(str(x) for x in xrange(1000))
429 expected_size = len(data)
430 item = ALGO(data).hexdigest()
431 self._requests = [
432 self.mock_fetch_request(server, namespace, item, data[:100]),
433 ]
434 storage = isolateserver.IsolateServer(server, namespace)
435 with self.assertRaises(IOError):
436 _ = ''.join(storage.fetch(item, expected_size))
437
438 def test_fetch_failure_bad_zip(self):
439 server = 'http://example.com'
440 namespace = 'default-gzip'
441 item = ALGO('something').hexdigest()
442 self._requests = [
443 self.mock_fetch_request(server, namespace, item, 'Im not a zip'),
444 ]
445 storage = isolateserver.IsolateServer(server, namespace)
446 with self.assertRaises(IOError):
447 _ = ''.join(storage.fetch(item, isolateserver.UNKNOWN_FILE_SIZE))
448
449 def test_push_success(self):
450 server = 'http://example.com'
451 namespace = 'default'
452 token = 'fake token'
453 data = ''.join(str(x) for x in xrange(1000))
454 item = FakeItem(data)
455 push_urls = (server + '/push_here', server + '/call_this')
456 contains_request = [{'h': item.digest, 's': item.size, 'i': 0}]
457 contains_response = [push_urls]
458 self._requests = [
459 self.mock_handshake_request(server, token),
460 self.mock_contains_request(
461 server, namespace, token, contains_request, contains_response),
462 (
463 push_urls[0],
464 {
465 'data': data,
466 'content_type': 'application/octet-stream',
467 'method': 'PUT',
468 },
469 ''
470 ),
471 (
472 push_urls[1],
473 {
474 'data': '',
475 'content_type': 'application/json',
476 'method': 'POST',
477 },
478 ''
479 ),
480 ]
481 storage = isolateserver.IsolateServer(server, namespace)
482 missing = storage.contains([item])
483 self.assertEqual([item], missing)
484 storage.push(item, [data], len(data))
485 self.assertTrue(item.push_state.uploaded)
486 self.assertTrue(item.push_state.finalized)
487
488 def test_push_failure_upload(self):
489 server = 'http://example.com'
490 namespace = 'default'
491 token = 'fake token'
492 data = ''.join(str(x) for x in xrange(1000))
493 item = FakeItem(data)
494 push_urls = (server + '/push_here', server + '/call_this')
495 contains_request = [{'h': item.digest, 's': item.size, 'i': 0}]
496 contains_response = [push_urls]
497 self._requests = [
498 self.mock_handshake_request(server, token),
499 self.mock_contains_request(
500 server, namespace, token, contains_request, contains_response),
501 (
502 push_urls[0],
503 {
504 'data': data,
505 'content_type': 'application/octet-stream',
506 'method': 'PUT',
507 },
508 None
509 ),
510 ]
511 storage = isolateserver.IsolateServer(server, namespace)
512 missing = storage.contains([item])
513 self.assertEqual([item], missing)
514 with self.assertRaises(IOError):
515 storage.push(item, [data], len(data))
516 self.assertFalse(item.push_state.uploaded)
517 self.assertFalse(item.push_state.finalized)
518
519 def test_push_failure_finalize(self):
520 server = 'http://example.com'
521 namespace = 'default'
522 token = 'fake token'
523 data = ''.join(str(x) for x in xrange(1000))
524 item = FakeItem(data)
525 push_urls = (server + '/push_here', server + '/call_this')
526 contains_request = [{'h': item.digest, 's': item.size, 'i': 0}]
527 contains_response = [push_urls]
528 self._requests = [
529 self.mock_handshake_request(server, token),
530 self.mock_contains_request(
531 server, namespace, token, contains_request, contains_response),
532 (
533 push_urls[0],
534 {
535 'data': data,
536 'content_type': 'application/octet-stream',
537 'method': 'PUT',
538 },
539 ''
540 ),
541 (
542 push_urls[1],
543 {
544 'data': '',
545 'content_type': 'application/json',
546 'method': 'POST',
547 },
548 None
549 ),
550 ]
551 storage = isolateserver.IsolateServer(server, namespace)
552 missing = storage.contains([item])
553 self.assertEqual([item], missing)
554 with self.assertRaises(IOError):
555 storage.push(item, [data], len(data))
556 self.assertTrue(item.push_state.uploaded)
557 self.assertFalse(item.push_state.finalized)
558
559 def test_contains_success(self):
560 server = 'http://example.com'
561 namespace = 'default'
562 token = 'fake token'
300 files = [ 563 files = [
301 os.path.join(BASE_PATH, 'isolateserver', f) 564 FakeItem('1', is_isolated=True),
302 for f in ('small_file.txt', 'empty_file.txt') 565 FakeItem('2' * 100),
303 ] 566 FakeItem('3' * 200),
304 hash_encoded = ''.join( 567 ]
305 binascii.unhexlify(isolateserver.hash_file(f, ALGO)) for f in files) 568 request = [
306 path = 'http://random/' 569 {'h': files[0].digest, 's': files[0].size, 'i': 1},
307 self._requests = [ 570 {'h': files[1].digest, 's': files[1].size, 'i': 0},
308 (path + 'content/get_token', {}, 'foo bar'), 571 {'h': files[2].digest, 's': files[2].size, 'i': 0},
309 ( 572 ]
310 path + 'content/contains/default-gzip?token=foo%20bar', 573 response = [
311 {'data': hash_encoded, 'content_type': 'application/octet-stream'}, 574 None,
312 '\1\1', 575 ['http://example/upload_here_1', None],
313 ), 576 ['http://example/upload_here_2', 'http://example/call_this'],
314 ] 577 ]
315 result = isolateserver.main(['archive', '--isolate-server', path] + files) 578 missing = [
316 self.assertEqual(0, result) 579 files[1],
317 580 files[2],
318 def test_missing(self): 581 ]
319 files = [ 582 self._requests = [
320 os.path.join(BASE_PATH, 'isolateserver', f) 583 self.mock_handshake_request(server, token),
321 for f in ('small_file.txt', 'empty_file.txt') 584 self.mock_contains_request(server, namespace, token, request, response),
322 ] 585 ]
323 hashes = [isolateserver.hash_file(f, ALGO) for f in files] 586 storage = isolateserver.IsolateServer(server, namespace)
324 hash_encoded = ''.join(map(binascii.unhexlify, hashes)) 587 result = storage.contains(files)
325 compressed = [ 588 self.assertEqual(missing, result)
326 zlib.compress( 589 self.assertEqual(
327 open(f, 'rb').read(), 590 [x for x in response if x],
328 isolateserver.get_zip_compression_level(f)) 591 [[i.push_state.upload_url, i.push_state.finalize_url] for i in missing])
329 for f in files 592
330 ] 593 def test_contains_network_failure(self):
331 path = 'http://random/' 594 server = 'http://example.com'
332 self._requests = [ 595 namespace = 'default'
333 (path + 'content/get_token', {}, 'foo bar'), 596 token = 'fake token'
334 ( 597 req = self.mock_contains_request(server, namespace, token, [], [])
335 path + 'content/contains/default-gzip?token=foo%20bar', 598 self._requests = [
336 {'data': hash_encoded, 'content_type': 'application/octet-stream'}, 599 self.mock_handshake_request(server, token),
337 '\0\0', 600 (req[0], req[1], None),
338 ), 601 ]
339 ( 602 storage = isolateserver.IsolateServer(server, namespace)
340 path + 'content/store/default-gzip/%s?token=foo%%20bar' % hashes[0], 603 with self.assertRaises(isolateserver.MappingError):
341 {'data': compressed[0], 'content_type': 'application/octet-stream'}, 604 storage.contains([])
342 'ok', 605
343 ), 606 def test_contains_format_failure(self):
344 ( 607 server = 'http://example.com'
345 path + 'content/store/default-gzip/%s?token=foo%%20bar' % hashes[1], 608 namespace = 'default'
346 {'data': compressed[1], 'content_type': 'application/octet-stream'}, 609 token = 'fake token'
347 'ok', 610 self._requests = [
348 ), 611 self.mock_handshake_request(server, token),
349 ] 612 self.mock_contains_request(server, namespace, token, [], [1, 2, 3])
350 result = isolateserver.main(['archive', '--isolate-server', path] + files) 613 ]
351 self.assertEqual(0, result) 614 storage = isolateserver.IsolateServer(server, namespace)
352 615 with self.assertRaises(isolateserver.MappingError):
353 def test_large(self): 616 storage.contains([])
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 617
477 618
478 class IsolateServerDownloadTest(TestCase): 619 class IsolateServerDownloadTest(TestCase):
479 tempdir = None 620 tempdir = None
480 621
481 def tearDown(self): 622 def tearDown(self):
482 try: 623 try:
483 if self.tempdir: 624 if self.tempdir:
484 shutil.rmtree(self.tempdir) 625 shutil.rmtree(self.tempdir)
485 finally: 626 finally:
486 super(IsolateServerDownloadTest, self).tearDown() 627 super(IsolateServerDownloadTest, self).tearDown()
487 628
488 def test_download_two_files(self): 629 def test_download_two_files(self):
489 # Test downloading two files. 630 # Test downloading two files.
490 actual = {} 631 actual = {}
491 def out(key, generator): 632 def out(key, generator):
492 actual[key] = ''.join(generator) 633 actual[key] = ''.join(generator)
493 self.mock(isolateserver, 'file_write', out) 634 self.mock(isolateserver, 'file_write', out)
494 server = 'http://example.com' 635 server = 'http://example.com'
495 self._requests = [ 636 self._requests = [
496 ( 637 (
497 server + '/content/retrieve/default-gzip/sha-1', 638 server + '/content-gs/retrieve/default-gzip/sha-1',
498 {'read_timeout': 60, 'retry_404': True}, 639 {'read_timeout': 60, 'retry_404': True},
499 zlib.compress('Coucou'), 640 zlib.compress('Coucou'),
500 ), 641 ),
501 ( 642 (
502 server + '/content/retrieve/default-gzip/sha-2', 643 server + '/content-gs/retrieve/default-gzip/sha-2',
503 {'read_timeout': 60, 'retry_404': True}, 644 {'read_timeout': 60, 'retry_404': True},
504 zlib.compress('Bye Bye'), 645 zlib.compress('Bye Bye'),
505 ), 646 ),
506 ] 647 ]
507 cmd = [ 648 cmd = [
508 'download', 649 'download',
509 '--isolate-server', server, 650 '--isolate-server', server,
510 '--target', ROOT_DIR, 651 '--target', ROOT_DIR,
511 '--file', 'sha-1', 'path/to/a', 652 '--file', 'sha-1', 'path/to/a',
512 '--file', 'sha-2', 'path/to/b', 653 '--file', 'sha-2', 'path/to/b',
(...skipping 27 matching lines...) Expand all
540 'files': dict( 681 'files': dict(
541 (k, {'h': ALGO(v).hexdigest(), 's': len(v)}) 682 (k, {'h': ALGO(v).hexdigest(), 's': len(v)})
542 for k, v in files.iteritems()), 683 for k, v in files.iteritems()),
543 } 684 }
544 isolated_data = json.dumps(isolated, sort_keys=True, separators=(',',':')) 685 isolated_data = json.dumps(isolated, sort_keys=True, separators=(',',':'))
545 isolated_hash = ALGO(isolated_data).hexdigest() 686 isolated_hash = ALGO(isolated_data).hexdigest()
546 requests = [(v['h'], files[k]) for k, v in isolated['files'].iteritems()] 687 requests = [(v['h'], files[k]) for k, v in isolated['files'].iteritems()]
547 requests.append((isolated_hash, isolated_data)) 688 requests.append((isolated_hash, isolated_data))
548 self._requests = [ 689 self._requests = [
549 ( 690 (
550 server + '/content/retrieve/default-gzip/' + h, 691 server + '/content-gs/retrieve/default-gzip/' + h,
551 { 692 {
552 'read_timeout': isolateserver.DOWNLOAD_READ_TIMEOUT, 693 'read_timeout': isolateserver.DOWNLOAD_READ_TIMEOUT,
553 'retry_404': True, 694 'retry_404': True,
554 }, 695 },
555 zlib.compress(v), 696 zlib.compress(v),
556 ) for h, v in requests 697 ) for h, v in requests
557 ] 698 ]
558 cmd = [ 699 cmd = [
559 'download', 700 'download',
560 '--isolate-server', server, 701 '--isolate-server', server,
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
649 expected = gen_data(os.path.sep) 790 expected = gen_data(os.path.sep)
650 self.assertEqual(expected, actual) 791 self.assertEqual(expected, actual)
651 792
652 793
653 if __name__ == '__main__': 794 if __name__ == '__main__':
654 if '-v' in sys.argv: 795 if '-v' in sys.argv:
655 unittest.TestCase.maxDiff = None 796 unittest.TestCase.maxDiff = None
656 logging.basicConfig( 797 logging.basicConfig(
657 level=(logging.DEBUG if '-v' in sys.argv else logging.ERROR)) 798 level=(logging.DEBUG if '-v' in sys.argv else logging.ERROR))
658 unittest.main() 799 unittest.main()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698