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

Side by Side Diff: third_party/gsutil/boto/tests/s3/test_resumable_downloads.py

Issue 10199002: Upgrade gsutil to 3.4 (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Addressed comments Created 8 years, 8 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 # 2 #
3 # Copyright 2010 Google Inc. 3 # Copyright 2010 Google Inc.
4 # 4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a 5 # Permission is hereby granted, free of charge, to any person obtaining a
6 # copy of this software and associated documentation files (the 6 # copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including 7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish, dis- 8 # without limitation the rights to use, copy, modify, merge, publish, dis-
9 # tribute, sublicense, and/or sell copies of the Software, and to permit 9 # tribute, sublicense, and/or sell copies of the Software, and to permit
10 # persons to whom the Software is furnished to do so, subject to the fol- 10 # persons to whom the Software is furnished to do so, subject to the fol-
(...skipping 27 matching lines...) Expand all
38 import time 38 import time
39 import unittest 39 import unittest
40 40
41 import boto 41 import boto
42 from boto import storage_uri 42 from boto import storage_uri
43 from boto.s3.resumable_download_handler import get_cur_file_size 43 from boto.s3.resumable_download_handler import get_cur_file_size
44 from boto.s3.resumable_download_handler import ResumableDownloadHandler 44 from boto.s3.resumable_download_handler import ResumableDownloadHandler
45 from boto.exception import ResumableTransferDisposition 45 from boto.exception import ResumableTransferDisposition
46 from boto.exception import ResumableDownloadException 46 from boto.exception import ResumableDownloadException
47 from boto.exception import StorageResponseError 47 from boto.exception import StorageResponseError
48 from tests.s3.cb_test_harnass import CallbackTestHarnass 48 from cb_test_harnass import CallbackTestHarnass
49 49
50 # We don't use the OAuth2 authentication plugin directly; importing it here 50 # We don't use the OAuth2 authentication plugin directly; importing it here
51 # ensures that it's loaded and available by default. 51 # ensures that it's loaded and available by default.
52 try: 52 try:
53 from oauth2_plugin import oauth2_plugin 53 from oauth2_plugin import oauth2_plugin
54 except ImportError: 54 except ImportError:
55 # Do nothing - if user doesn't have OAuth2 configured it doesn't matter; 55 # Do nothing - if user doesn't have OAuth2 configured it doesn't matter;
56 # and if they do, the tests will fail (as they should in that case). 56 # and if they do, the tests will fail (as they should in that case).
57 pass 57 pass
58 58
59 59
60 class ResumableDownloadTests(unittest.TestCase): 60 class ResumableDownloadTests(unittest.TestCase):
61 """ 61 """
62 Resumable download test suite. 62 Resumable download test suite.
63 """ 63 """
64 64
65 def get_suite_description(self): 65 def get_suite_description(self):
66 return 'Resumable download test suite' 66 return 'Resumable download test suite'
67 67
68 @staticmethod 68 @staticmethod
69 def resilient_close(key): 69 def resilient_close(key):
70 try: 70 try:
71 key.close() 71 key.close()
72 except StorageResponseError, e: 72 except StorageResponseError, e:
73 pass 73 pass
74 74
75 @classmethod 75 def build_test_input_object(self, obj_name, size):
76 def setUp(cls):
77 """
78 Creates file-like object for detination of each download test.
79
80 This method's namingCase is required by the unittest framework.
81 """
82 cls.dst_fp = open(cls.dst_file_name, 'w')
83
84 @classmethod
85 def tearDown(cls):
86 """
87 Deletes any objects or files created by last test run, and closes
88 any keys in case they were read incompletely (which would leave
89 partial buffers of data for subsequent tests to trip over).
90
91 This method's namingCase is required by the unittest framework.
92 """
93 # Recursively delete dst dir and then re-create it, so in effect we
94 # remove all dirs and files under that directory.
95 shutil.rmtree(cls.tmp_dir)
96 os.mkdir(cls.tmp_dir)
97
98 # Close test objects.
99 cls.resilient_close(cls.empty_src_key)
100 cls.resilient_close(cls.small_src_key)
101 cls.resilient_close(cls.larger_src_key)
102
103 @classmethod
104 def build_test_input_object(cls, obj_name, size, debug):
105 buf = [] 76 buf = []
106 for i in range(size): 77 for i in range(size):
107 buf.append(str(random.randint(0, 9))) 78 buf.append(str(random.randint(0, 9)))
108 string_data = ''.join(buf) 79 string_data = ''.join(buf)
109 uri = cls.src_bucket_uri.clone_replace_name(obj_name) 80 uri = self.src_bucket_uri.clone_replace_name(obj_name)
110 key = uri.new_key(validate=False) 81 key = uri.new_key(validate=False)
111 key.set_contents_from_file(StringIO.StringIO(string_data)) 82 key.set_contents_from_file(StringIO.StringIO(string_data))
112 # Set debug on key's connection after creating data, so only the test
113 # runs will show HTTP output (if called passed debug>0).
114 key.bucket.connection.debug = debug
115 return (string_data, key) 83 return (string_data, key)
116 84
117 @classmethod 85 def setUp(self):
118 def set_up_class(cls, debug):
119 """ 86 """
120 Initializes test suite. 87 Initializes for each test.
121 """ 88 """
122
123 # Create the test bucket. 89 # Create the test bucket.
124 hostname = socket.gethostname().split('.')[0] 90 hostname = socket.gethostname().split('.')[0]
125 uri_base_str = 'gs://res_download_test_%s_%s_%s' % ( 91 uri_base_str = 'gs://res-download-test-%s-%s-%s' % (
126 hostname, os.getpid(), int(time.time())) 92 hostname, os.getpid(), int(time.time()))
127 cls.src_bucket_uri = storage_uri('%s_dst' % uri_base_str) 93 self.src_bucket_uri = storage_uri('%s-dst' % uri_base_str)
128 cls.src_bucket_uri.create_bucket() 94 self.src_bucket_uri.create_bucket()
129 95
130 # Create test source objects. 96 # Create test source objects.
131 cls.empty_src_key_size = 0 97 self.empty_src_key_size = 0
132 (cls.empty_src_key_as_string, cls.empty_src_key) = ( 98 (self.empty_src_key_as_string, self.empty_src_key) = (
133 cls.build_test_input_object('empty', cls.empty_src_key_size, 99 self.build_test_input_object('empty', self.empty_src_key_size))
134 debug=debug)) 100 self.small_src_key_size = 2 * 1024 # 2 KB.
135 cls.small_src_key_size = 2 * 1024 # 2 KB. 101 (self.small_src_key_as_string, self.small_src_key) = (
136 (cls.small_src_key_as_string, cls.small_src_key) = ( 102 self.build_test_input_object('small', self.small_src_key_size))
137 cls.build_test_input_object('small', cls.small_src_key_size, 103 self.larger_src_key_size = 500 * 1024 # 500 KB.
138 debug=debug)) 104 (self.larger_src_key_as_string, self.larger_src_key) = (
139 cls.larger_src_key_size = 500 * 1024 # 500 KB. 105 self.build_test_input_object('larger', self.larger_src_key_size))
140 (cls.larger_src_key_as_string, cls.larger_src_key) = (
141 cls.build_test_input_object('larger', cls.larger_src_key_size,
142 debug=debug))
143 106
144 # Use a designated tmpdir prefix to make it easy to find the end of 107 # Use a designated tmpdir prefix to make it easy to find the end of
145 # the tmp path. 108 # the tmp path.
146 cls.tmpdir_prefix = 'tmp_resumable_download_test' 109 self.tmpdir_prefix = 'tmp_resumable_download_test'
147 110
148 # Create temp dir and name for download file. 111 # Create temp dir and name for download file.
149 cls.tmp_dir = tempfile.mkdtemp(prefix=cls.tmpdir_prefix) 112 self.tmp_dir = tempfile.mkdtemp(prefix=self.tmpdir_prefix)
150 cls.dst_file_name = '%s%sdst_file' % (cls.tmp_dir, os.sep) 113 self.dst_file_name = '%s%sdst_file' % (self.tmp_dir, os.sep)
151 114
152 cls.tracker_file_name = '%s%stracker' % (cls.tmp_dir, os.sep) 115 self.tracker_file_name = '%s%stracker' % (self.tmp_dir, os.sep)
153 116
154 cls.created_test_data = True 117 # Create file-like object for detination of each download test.
118 self.dst_fp = open(self.dst_file_name, 'w')
119 self.created_test_data = True
155 120
156 @classmethod 121 def tearDown(self):
157 def tear_down_class(cls):
158 """ 122 """
159 Deletes test objects and bucket and tmp dir created by set_up_class. 123 Deletes test objects and bucket and tmp dir created by set_up_class,
124 and closes any keys in case they were read incompletely (which would
125 leave partial buffers of data for subsequent tests to trip over).
160 """ 126 """
161 if not hasattr(cls, 'created_test_data'): 127 if not hasattr(self, 'created_test_data'):
162 return 128 return
163 # Call cls.tearDown() in case the tests got interrupted, to ensure 129 # Recursively delete dst dir and then re-create it, so in effect we
164 # dst objects get deleted. 130 # remove all dirs and files under that directory.
165 cls.tearDown() 131 shutil.rmtree(self.tmp_dir)
132 os.mkdir(self.tmp_dir)
133
134 # Close test objects.
135 self.resilient_close(self.empty_src_key)
136 self.resilient_close(self.small_src_key)
137 self.resilient_close(self.larger_src_key)
166 138
167 # Delete test objects. 139 # Delete test objects.
168 cls.empty_src_key.delete() 140 self.empty_src_key.delete()
169 cls.small_src_key.delete() 141 self.small_src_key.delete()
170 cls.larger_src_key.delete() 142 self.larger_src_key.delete()
171 143
172 # Retry (for up to 2 minutes) the bucket gets deleted (it may not 144 # Retry (for up to 2 minutes) the bucket gets deleted (it may not
173 # the first time round, due to eventual consistency of bucket delete 145 # the first time round, due to eventual consistency of bucket delete
174 # operations). 146 # operations).
175 for i in range(60): 147 for i in range(60):
176 try: 148 try:
177 cls.src_bucket_uri.delete_bucket() 149 self.src_bucket_uri.delete_bucket()
178 break 150 break
179 except StorageResponseError: 151 except StorageResponseError:
180 print 'Test bucket (%s) not yet deleted, still trying' % ( 152 print 'Test bucket (%s) not yet deleted, still trying' % (
181 cls.src_bucket_uri.uri) 153 self.src_bucket_uri.uri)
182 time.sleep(2) 154 time.sleep(2)
183 shutil.rmtree(cls.tmp_dir) 155 shutil.rmtree(self.tmp_dir)
184 cls.tmp_dir = tempfile.mkdtemp(prefix=cls.tmpdir_prefix) 156 self.tmp_dir = tempfile.mkdtemp(prefix=self.tmpdir_prefix)
185 157
186 def test_non_resumable_download(self): 158 def test_non_resumable_download(self):
187 """ 159 """
188 Tests that non-resumable downloads work 160 Tests that non-resumable downloads work
189 """ 161 """
190 self.small_src_key.get_contents_to_file(self.dst_fp) 162 self.small_src_key.get_contents_to_file(self.dst_fp)
191 self.assertEqual(self.small_src_key_size, 163 self.assertEqual(self.small_src_key_size,
192 get_cur_file_size(self.dst_fp)) 164 get_cur_file_size(self.dst_fp))
193 self.assertEqual(self.small_src_key_as_string, 165 self.assertEqual(self.small_src_key_as_string,
194 self.small_src_key.get_contents_as_string()) 166 self.small_src_key.get_contents_as_string())
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after
372 344
373 def test_zero_length_object_download(self): 345 def test_zero_length_object_download(self):
374 """ 346 """
375 Tests downloading a zero-length object (exercises boundary conditions). 347 Tests downloading a zero-length object (exercises boundary conditions).
376 """ 348 """
377 res_download_handler = ResumableDownloadHandler() 349 res_download_handler = ResumableDownloadHandler()
378 self.empty_src_key.get_contents_to_file( 350 self.empty_src_key.get_contents_to_file(
379 self.dst_fp, res_download_handler=res_download_handler) 351 self.dst_fp, res_download_handler=res_download_handler)
380 self.assertEqual(0, get_cur_file_size(self.dst_fp)) 352 self.assertEqual(0, get_cur_file_size(self.dst_fp))
381 353
382 def test_download_with_object_size_change_between_starts(self):
383 """
384 Tests resumable download on an object that changes sizes between inital
385 download start and restart
386 """
387 harnass = CallbackTestHarnass(
388 fail_after_n_bytes=self.larger_src_key_size/2, num_times_to_fail=2)
389 # Set up first process' ResumableDownloadHandler not to do any
390 # retries (initial download request will establish expected size to
391 # download server).
392 res_download_handler = ResumableDownloadHandler(
393 tracker_file_name=self.tracker_file_name, num_retries=0)
394 try:
395 self.larger_src_key.get_contents_to_file(
396 self.dst_fp, cb=harnass.call,
397 res_download_handler=res_download_handler)
398 self.fail('Did not get expected ResumableDownloadException')
399 except ResumableDownloadException, e:
400 # First abort (from harnass-forced failure) should be
401 # ABORT_CUR_PROCESS.
402 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT_C UR_PROCESS)
403 # Ensure a tracker file survived.
404 self.assertTrue(os.path.exists(self.tracker_file_name))
405 # Try it again, this time with different src key (simulating an
406 # object that changes sizes between downloads).
407 try:
408 self.small_src_key.get_contents_to_file(
409 self.dst_fp, res_download_handler=res_download_handler)
410 self.fail('Did not get expected ResumableDownloadException')
411 except ResumableDownloadException, e:
412 # This abort should be a hard abort (object size changing during
413 # transfer).
414 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT)
415 self.assertNotEqual(
416 e.message.find('md5 signature doesn\'t match etag'), -1)
417
418 def test_download_with_file_content_change_during_download(self):
419 """
420 Tests resumable download on an object where the file content changes
421 without changing length while download in progress
422 """
423 harnass = CallbackTestHarnass(
424 fail_after_n_bytes=self.larger_src_key_size/2, num_times_to_fail=2)
425 # Set up first process' ResumableDownloadHandler not to do any
426 # retries (initial download request will establish expected size to
427 # download server).
428 res_download_handler = ResumableDownloadHandler(
429 tracker_file_name=self.tracker_file_name, num_retries=0)
430 dst_filename = self.dst_fp.name
431 try:
432 self.larger_src_key.get_contents_to_file(
433 self.dst_fp, cb=harnass.call,
434 res_download_handler=res_download_handler)
435 self.fail('Did not get expected ResumableDownloadException')
436 except ResumableDownloadException, e:
437 # First abort (from harnass-forced failure) should be
438 # ABORT_CUR_PROCESS.
439 self.assertEqual(e.disposition,
440 ResumableTransferDisposition.ABORT_CUR_PROCESS)
441 # Ensure a tracker file survived.
442 self.assertTrue(os.path.exists(self.tracker_file_name))
443 # Before trying again change the first byte of the file fragment
444 # that was already downloaded.
445 orig_size = get_cur_file_size(self.dst_fp)
446 self.dst_fp.seek(0, os.SEEK_SET)
447 self.dst_fp.write('a')
448 # Ensure the file size didn't change.
449 self.assertEqual(orig_size, get_cur_file_size(self.dst_fp))
450 try:
451 self.larger_src_key.get_contents_to_file(
452 self.dst_fp, cb=harnass.call,
453 res_download_handler=res_download_handler)
454 self.fail('Did not get expected ResumableDownloadException')
455 except ResumableDownloadException, e:
456 # This abort should be a hard abort (file content changing during
457 # transfer).
458 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT)
459 self.assertNotEqual(
460 e.message.find('md5 signature doesn\'t match etag'), -1)
461 # Ensure the bad data wasn't left around.
462 self.assertFalse(os.path.exists(dst_filename))
463
464 def test_download_with_invalid_tracker_etag(self): 354 def test_download_with_invalid_tracker_etag(self):
465 """ 355 """
466 Tests resumable download with a tracker file containing an invalid etag 356 Tests resumable download with a tracker file containing an invalid etag
467 """ 357 """
468 invalid_etag_tracker_file_name = ( 358 invalid_etag_tracker_file_name = (
469 '%s%sinvalid_etag_tracker' % (self.tmp_dir, os.sep)) 359 '%s%sinvalid_etag_tracker' % (self.tmp_dir, os.sep))
470 f = open(invalid_etag_tracker_file_name, 'w') 360 f = open(invalid_etag_tracker_file_name, 'w')
471 f.write('3.14159\n') 361 f.write('3.14159\n')
472 f.close() 362 f.close()
473 res_download_handler = ResumableDownloadHandler( 363 res_download_handler = ResumableDownloadHandler(
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
515 os.chmod(self.tmp_dir, 0) 405 os.chmod(self.tmp_dir, 0)
516 res_download_handler = ResumableDownloadHandler( 406 res_download_handler = ResumableDownloadHandler(
517 tracker_file_name=self.tracker_file_name) 407 tracker_file_name=self.tracker_file_name)
518 except ResumableDownloadException, e: 408 except ResumableDownloadException, e:
519 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) 409 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT)
520 self.assertNotEqual( 410 self.assertNotEqual(
521 e.message.find('Couldn\'t write URI tracker file'), -1) 411 e.message.find('Couldn\'t write URI tracker file'), -1)
522 finally: 412 finally:
523 # Restore original protection of dir where tracker_file lives. 413 # Restore original protection of dir where tracker_file lives.
524 os.chmod(self.tmp_dir, save_mod) 414 os.chmod(self.tmp_dir, save_mod)
525
526 if __name__ == '__main__':
527 if sys.version_info[:3] < (2, 5, 1):
528 sys.exit('These tests must be run on at least Python 2.5.1\n')
529
530 # Use -d to see more HTTP protocol detail during tests. Note that
531 # unlike the upload test case, you won't see much for the downloads
532 # because there's no HTTP server state protocol for in the download case
533 # (and the actual Range GET HTTP protocol detail is suppressed by the
534 # normal boto.s3.Key.get_file() processing).
535 debug = 0
536 opts, args = getopt.getopt(sys.argv[1:], 'd', ['debug'])
537 for o, a in opts:
538 if o in ('-d', '--debug'):
539 debug = 2
540
541 test_loader = unittest.TestLoader()
542 test_loader.testMethodPrefix = 'test_'
543 suite = test_loader.loadTestsFromTestCase(ResumableDownloadTests)
544 # Seems like there should be a cleaner way to find the test_class.
545 test_class = suite.__getattribute__('_tests')[0]
546 # We call set_up_class() and tear_down_class() ourselves because we
547 # don't assume the user has Python 2.7 (which supports classmethods
548 # that do it, with camelCase versions of these names).
549 try:
550 print 'Setting up %s...' % test_class.get_suite_description()
551 test_class.set_up_class(debug)
552 print 'Running %s...' % test_class.get_suite_description()
553 unittest.TextTestRunner(verbosity=2).run(suite)
554 finally:
555 print 'Cleaning up after %s...' % test_class.get_suite_description()
556 test_class.tear_down_class()
557 print ''
OLDNEW
« no previous file with comments | « third_party/gsutil/boto/tests/s3/test_pool.py ('k') | third_party/gsutil/boto/tests/s3/test_resumable_uploads.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698