| OLD | NEW |
| 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- |
| 11 # lowing conditions: | 11 # lowing conditions: |
| 12 # | 12 # |
| 13 # The above copyright notice and this permission notice shall be included | 13 # The above copyright notice and this permission notice shall be included |
| 14 # in all copies or substantial portions of the Software. | 14 # in all copies or substantial portions of the Software. |
| 15 # | 15 # |
| 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 17 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- | 17 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 18 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | 18 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 19 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | 19 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 20 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 20 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | 21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 22 # IN THE SOFTWARE. | 22 # IN THE SOFTWARE. |
| 23 | 23 |
| 24 """ | 24 """ |
| 25 Tests of resumable uploads. | 25 Tests of Google Cloud Storage resumable uploads. |
| 26 """ | 26 """ |
| 27 | 27 |
| 28 import errno | 28 import errno |
| 29 import getopt | 29 import getopt |
| 30 import os | 30 import os |
| 31 import random | 31 import random |
| 32 import re | 32 import re |
| 33 import shutil | 33 import shutil |
| 34 import socket | 34 import socket |
| 35 import StringIO | 35 import StringIO |
| 36 import sys | 36 import sys |
| 37 import tempfile | 37 import tempfile |
| 38 import time | 38 import time |
| 39 import unittest | 39 import unittest |
| 40 | 40 |
| 41 import boto | 41 import boto |
| 42 from boto.exception import GSResponseError | 42 from boto.exception import GSResponseError |
| 43 from boto.gs.resumable_upload_handler import ResumableUploadHandler | 43 from boto.gs.resumable_upload_handler import ResumableUploadHandler |
| 44 from boto.exception import InvalidUriError |
| 44 from boto.exception import ResumableTransferDisposition | 45 from boto.exception import ResumableTransferDisposition |
| 45 from boto.exception import ResumableUploadException | 46 from boto.exception import ResumableUploadException |
| 46 from boto.exception import StorageResponseError | 47 from boto.exception import StorageResponseError |
| 47 from boto.tests.cb_test_harnass import CallbackTestHarnass | 48 from cb_test_harnass import CallbackTestHarnass |
| 49 |
| 50 # We don't use the OAuth2 authentication plugin directly; importing it here |
| 51 # ensures that it's loaded and available by default. |
| 52 try: |
| 53 from oauth2_plugin import oauth2_plugin |
| 54 except ImportError: |
| 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). |
| 57 pass |
| 48 | 58 |
| 49 | 59 |
| 50 class ResumableUploadTests(unittest.TestCase): | 60 class ResumableUploadTests(unittest.TestCase): |
| 51 """ | 61 """ |
| 52 Resumable upload test suite. | 62 Resumable upload test suite. |
| 53 """ | 63 """ |
| 54 | 64 |
| 55 def get_suite_description(self): | 65 def get_suite_description(self): |
| 56 return 'Resumable upload test suite' | 66 return 'Resumable upload test suite' |
| 57 | 67 |
| 58 @classmethod | 68 def setUp(self): |
| 59 def setUp(cls): | |
| 60 """ | 69 """ |
| 61 Creates dst_key needed by all tests. | 70 Creates dst_key needed by all tests. |
| 62 | 71 |
| 63 This method's namingCase is required by the unittest framework. | 72 This method's namingCase is required by the unittest framework. |
| 64 """ | 73 """ |
| 65 cls.dst_key = cls.dst_key_uri.new_key(validate=False) | 74 self.dst_key = self.dst_key_uri.new_key(validate=False) |
| 66 | 75 |
| 67 @classmethod | 76 def tearDown(self): |
| 68 def tearDown(cls): | |
| 69 """ | 77 """ |
| 70 Deletes any objects or files created by last test run. | 78 Deletes any objects or files created by last test run. |
| 71 | 79 |
| 72 This method's namingCase is required by the unittest framework. | 80 This method's namingCase is required by the unittest framework. |
| 73 """ | 81 """ |
| 74 try: | 82 try: |
| 75 cls.dst_key_uri.delete_key() | 83 self.dst_key_uri.delete_key() |
| 76 except GSResponseError: | 84 except GSResponseError: |
| 77 # Ignore possible not-found error. | 85 # Ignore possible not-found error. |
| 78 pass | 86 pass |
| 79 # Recursively delete dst dir and then re-create it, so in effect we | 87 # Recursively delete dst dir and then re-create it, so in effect we |
| 80 # remove all dirs and files under that directory. | 88 # remove all dirs and files under that directory. |
| 81 shutil.rmtree(cls.tmp_dir) | 89 shutil.rmtree(self.tmp_dir) |
| 82 os.mkdir(cls.tmp_dir) | 90 os.mkdir(self.tmp_dir) |
| 83 | 91 |
| 84 @staticmethod | 92 @staticmethod |
| 85 def build_test_input_file(size): | 93 def build_test_input_file(size): |
| 86 buf = [] | 94 buf = [] |
| 87 # I manually construct the random data here instead of calling | 95 # I manually construct the random data here instead of calling |
| 88 # os.urandom() because I want to constrain the range of data (in | 96 # os.urandom() because I want to constrain the range of data (in |
| 89 # this case to 0'..'9') so the test | 97 # this case to 0'..'9') so the test |
| 90 # code can easily overwrite part of the StringIO file with | 98 # code can easily overwrite part of the StringIO file with |
| 91 # known-to-be-different values. | 99 # known-to-be-different values. |
| 92 for i in range(size): | 100 for i in range(size): |
| 93 buf.append(str(random.randint(0, 9))) | 101 buf.append(str(random.randint(0, 9))) |
| 94 file_as_string = ''.join(buf) | 102 file_as_string = ''.join(buf) |
| 95 return (file_as_string, StringIO.StringIO(file_as_string)) | 103 return (file_as_string, StringIO.StringIO(file_as_string)) |
| 96 | 104 |
| 97 @classmethod | 105 @classmethod |
| 106 def get_dst_bucket_uri(cls, debug): |
| 107 """A unique bucket to test.""" |
| 108 hostname = socket.gethostname().split('.')[0] |
| 109 uri_base_str = 'gs://res-upload-test-%s-%s-%s' % ( |
| 110 hostname, os.getpid(), int(time.time())) |
| 111 return boto.storage_uri('%s-dst' % uri_base_str, debug=debug) |
| 112 |
| 113 @classmethod |
| 114 def get_dst_key_uri(cls): |
| 115 """A key to test.""" |
| 116 return cls.dst_bucket_uri.clone_replace_name('obj') |
| 117 |
| 118 @classmethod |
| 119 def get_staged_host(cls): |
| 120 """URL of an existing bucket.""" |
| 121 return 'pub.commondatastorage.googleapis.com' |
| 122 |
| 123 @classmethod |
| 124 def get_invalid_upload_id(cls): |
| 125 return ( |
| 126 'http://%s/?upload_id=' |
| 127 'AyzB2Uo74W4EYxyi5dp_-r68jz8rtbvshsv4TX7srJVkJ57CxTY5Dw2' % ( |
| 128 cls.get_staged_host())) |
| 129 |
| 130 @classmethod |
| 98 def set_up_class(cls, debug): | 131 def set_up_class(cls, debug): |
| 99 """ | 132 """ |
| 100 Initializes test suite. | 133 Initializes test suite. |
| 101 """ | 134 """ |
| 102 | 135 |
| 103 # Use a designated tmpdir prefix to make it easy to find the end of | 136 # Use a designated tmpdir prefix to make it easy to find the end of |
| 104 # the tmp path. | 137 # the tmp path. |
| 105 cls.tmpdir_prefix = 'tmp_resumable_upload_test' | 138 cls.tmpdir_prefix = 'tmp_resumable_upload_test' |
| 106 | 139 |
| 107 # Create test source file data. | 140 # Create test source file data. |
| 108 cls.empty_src_file_size = 0 | 141 cls.empty_src_file_size = 0 |
| 109 (cls.empty_src_file_as_string, cls.empty_src_file) = ( | 142 (cls.empty_src_file_as_string, cls.empty_src_file) = ( |
| 110 cls.build_test_input_file(cls.empty_src_file_size)) | 143 cls.build_test_input_file(cls.empty_src_file_size)) |
| 111 cls.small_src_file_size = 2 * 1024 # 2 KB. | 144 cls.small_src_file_size = 2 * 1024 # 2 KB. |
| 112 (cls.small_src_file_as_string, cls.small_src_file) = ( | 145 (cls.small_src_file_as_string, cls.small_src_file) = ( |
| 113 cls.build_test_input_file(cls.small_src_file_size)) | 146 cls.build_test_input_file(cls.small_src_file_size)) |
| 114 cls.larger_src_file_size = 500 * 1024 # 500 KB. | 147 cls.larger_src_file_size = 500 * 1024 # 500 KB. |
| 115 (cls.larger_src_file_as_string, cls.larger_src_file) = ( | 148 (cls.larger_src_file_as_string, cls.larger_src_file) = ( |
| 116 cls.build_test_input_file(cls.larger_src_file_size)) | 149 cls.build_test_input_file(cls.larger_src_file_size)) |
| 117 cls.largest_src_file_size = 1024 * 1024 # 1 MB. | 150 cls.largest_src_file_size = 1024 * 1024 # 1 MB. |
| 118 (cls.largest_src_file_as_string, cls.largest_src_file) = ( | 151 (cls.largest_src_file_as_string, cls.largest_src_file) = ( |
| 119 cls.build_test_input_file(cls.largest_src_file_size)) | 152 cls.build_test_input_file(cls.largest_src_file_size)) |
| 120 | 153 |
| 121 # Create temp dir. | 154 # Create temp dir. |
| 122 cls.tmp_dir = tempfile.mkdtemp(prefix=cls.tmpdir_prefix) | 155 cls.tmp_dir = tempfile.mkdtemp(prefix=cls.tmpdir_prefix) |
| 123 | 156 |
| 124 # Create the test bucket. | 157 # Create the test bucket. |
| 125 hostname = socket.gethostname().split('.')[0] | 158 cls.dst_bucket_uri = cls.get_dst_bucket_uri(debug) |
| 126 cls.uri_base_str = 'gs://res_upload_test_%s_%s_%s' % ( | |
| 127 hostname, os.getpid(), int(time.time())) | |
| 128 cls.dst_bucket_uri = boto.storage_uri('%s_dst' % | |
| 129 cls.uri_base_str, debug=debug) | |
| 130 cls.dst_bucket_uri.create_bucket() | 159 cls.dst_bucket_uri.create_bucket() |
| 131 cls.dst_key_uri = cls.dst_bucket_uri.clone_replace_name('obj') | 160 cls.dst_key_uri = cls.get_dst_key_uri() |
| 132 | 161 |
| 133 cls.tracker_file_name = '%s%suri_tracker' % (cls.tmp_dir, os.sep) | 162 cls.tracker_file_name = '%s%suri_tracker' % (cls.tmp_dir, os.sep) |
| 134 | 163 |
| 135 cls.syntactically_invalid_tracker_file_name = ( | 164 cls.syntactically_invalid_tracker_file_name = ( |
| 136 '%s%ssynt_invalid_uri_tracker' % (cls.tmp_dir, os.sep)) | 165 '%s%ssynt_invalid_uri_tracker' % (cls.tmp_dir, os.sep)) |
| 137 f = open(cls.syntactically_invalid_tracker_file_name, 'w') | 166 f = open(cls.syntactically_invalid_tracker_file_name, 'w') |
| 138 f.write('ftp://example.com') | 167 f.write('ftp://example.com') |
| 139 f.close() | 168 f.close() |
| 140 | 169 |
| 141 cls.invalid_upload_id = ( | 170 cls.invalid_upload_id = cls.get_invalid_upload_id() |
| 142 'http://pub.commondatastorage.googleapis.com/?upload_id=' | |
| 143 'AyzB2Uo74W4EYxyi5dp_-r68jz8rtbvshsv4TX7srJVkJ57CxTY5Dw2') | |
| 144 cls.invalid_upload_id_tracker_file_name = ( | 171 cls.invalid_upload_id_tracker_file_name = ( |
| 145 '%s%sinvalid_upload_id_tracker' % (cls.tmp_dir, os.sep)) | 172 '%s%sinvalid_upload_id_tracker' % (cls.tmp_dir, os.sep)) |
| 146 f = open(cls.invalid_upload_id_tracker_file_name, 'w') | 173 f = open(cls.invalid_upload_id_tracker_file_name, 'w') |
| 147 f.write(cls.invalid_upload_id) | 174 f.write(cls.invalid_upload_id) |
| 148 f.close() | 175 f.close() |
| 149 | 176 |
| 150 cls.created_test_data = True | 177 cls.created_test_data = True |
| 151 | 178 |
| 152 @classmethod | 179 @classmethod |
| 153 def tear_down_class(cls): | 180 def tear_down_class(cls): |
| 154 """ | 181 """ |
| 155 Deletes bucket and tmp dir created by set_up_class. | 182 Deletes bucket and tmp dir created by set_up_class. |
| 156 """ | 183 """ |
| 157 if not hasattr(cls, 'created_test_data'): | 184 if not hasattr(cls, 'created_test_data'): |
| 158 return | 185 return |
| 159 # Call cls.tearDown() in case the tests got interrupted, to ensure | |
| 160 # dst objects get deleted. | |
| 161 cls.tearDown() | |
| 162 | 186 |
| 163 # Retry (for up to 2 minutes) the bucket gets deleted (it may not | 187 # Retry (for up to 2 minutes) the bucket gets deleted (it may not |
| 164 # the first time round, due to eventual consistency of bucket delete | 188 # the first time round, due to eventual consistency of bucket delete |
| 165 # operations). | 189 # operations). |
| 166 for i in range(60): | 190 for i in range(60): |
| 167 try: | 191 try: |
| 168 cls.dst_bucket_uri.delete_bucket() | 192 cls.dst_bucket_uri.delete_bucket() |
| 169 break | 193 break |
| 170 except StorageResponseError: | 194 except StorageResponseError: |
| 171 print 'Test bucket (%s) not yet deleted, still trying' % ( | 195 print 'Test bucket (%s) not yet deleted, still trying' % ( |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 203 tracker_file_name=self.tracker_file_name, num_retries=0) | 227 tracker_file_name=self.tracker_file_name, num_retries=0) |
| 204 try: | 228 try: |
| 205 self.dst_key.set_contents_from_file( | 229 self.dst_key.set_contents_from_file( |
| 206 self.small_src_file, cb=harnass.call, | 230 self.small_src_file, cb=harnass.call, |
| 207 res_upload_handler=res_upload_handler) | 231 res_upload_handler=res_upload_handler) |
| 208 self.fail('Did not get expected ResumableUploadException') | 232 self.fail('Did not get expected ResumableUploadException') |
| 209 except ResumableUploadException, e: | 233 except ResumableUploadException, e: |
| 210 # We'll get a ResumableUploadException at this point because | 234 # We'll get a ResumableUploadException at this point because |
| 211 # of CallbackTestHarnass (above). Check that the tracker file was | 235 # of CallbackTestHarnass (above). Check that the tracker file was |
| 212 # created correctly. | 236 # created correctly. |
| 213 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | 237 self.assertEqual(e.disposition, |
| 238 ResumableTransferDisposition.ABORT_CUR_PROCESS) |
| 214 self.assertTrue(os.path.exists(self.tracker_file_name)) | 239 self.assertTrue(os.path.exists(self.tracker_file_name)) |
| 215 f = open(self.tracker_file_name) | 240 f = open(self.tracker_file_name) |
| 216 uri_from_file = f.readline().strip() | 241 uri_from_file = f.readline().strip() |
| 217 f.close() | 242 f.close() |
| 218 self.assertEqual(uri_from_file, | 243 self.assertEqual(uri_from_file, |
| 219 res_upload_handler.get_tracker_uri()) | 244 res_upload_handler.get_tracker_uri()) |
| 220 | 245 |
| 221 def test_retryable_exception_recovery(self): | 246 def test_retryable_exception_recovery(self): |
| 222 """ | 247 """ |
| 223 Tests handling of a retryable exception | 248 Tests handling of a retryable exception |
| 224 """ | 249 """ |
| 225 # Test one of the RETRYABLE_EXCEPTIONS. | 250 # Test one of the RETRYABLE_EXCEPTIONS. |
| 226 exception = ResumableUploadHandler.RETRYABLE_EXCEPTIONS[0] | 251 exception = ResumableUploadHandler.RETRYABLE_EXCEPTIONS[0] |
| 227 harnass = CallbackTestHarnass(exception=exception) | 252 harnass = CallbackTestHarnass(exception=exception) |
| 228 res_upload_handler = ResumableUploadHandler(num_retries=1) | 253 res_upload_handler = ResumableUploadHandler(num_retries=1) |
| 229 self.dst_key.set_contents_from_file( | 254 self.dst_key.set_contents_from_file( |
| 230 self.small_src_file, cb=harnass.call, | 255 self.small_src_file, cb=harnass.call, |
| 231 res_upload_handler=res_upload_handler) | 256 res_upload_handler=res_upload_handler) |
| 232 # Ensure uploaded object has correct content. | 257 # Ensure uploaded object has correct content. |
| 233 self.assertEqual(self.small_src_file_size, self.dst_key.size) | 258 self.assertEqual(self.small_src_file_size, self.dst_key.size) |
| 234 self.assertEqual(self.small_src_file_as_string, | 259 self.assertEqual(self.small_src_file_as_string, |
| 235 self.dst_key.get_contents_as_string()) | 260 self.dst_key.get_contents_as_string()) |
| 236 | 261 |
| 262 def test_broken_pipe_recovery(self): |
| 263 """ |
| 264 Tests handling of a Broken Pipe (which interacts with an httplib bug) |
| 265 """ |
| 266 exception = IOError(errno.EPIPE, "Broken pipe") |
| 267 harnass = CallbackTestHarnass(exception=exception) |
| 268 res_upload_handler = ResumableUploadHandler(num_retries=1) |
| 269 self.dst_key.set_contents_from_file( |
| 270 self.small_src_file, cb=harnass.call, |
| 271 res_upload_handler=res_upload_handler) |
| 272 # Ensure uploaded object has correct content. |
| 273 self.assertEqual(self.small_src_file_size, self.dst_key.size) |
| 274 self.assertEqual(self.small_src_file_as_string, |
| 275 self.dst_key.get_contents_as_string()) |
| 276 |
| 237 def test_non_retryable_exception_handling(self): | 277 def test_non_retryable_exception_handling(self): |
| 238 """ | 278 """ |
| 239 Tests a resumable upload that fails with a non-retryable exception | 279 Tests a resumable upload that fails with a non-retryable exception |
| 240 """ | 280 """ |
| 241 harnass = CallbackTestHarnass( | 281 harnass = CallbackTestHarnass( |
| 242 exception=OSError(errno.EACCES, 'Permission denied')) | 282 exception=OSError(errno.EACCES, 'Permission denied')) |
| 243 res_upload_handler = ResumableUploadHandler(num_retries=1) | 283 res_upload_handler = ResumableUploadHandler(num_retries=1) |
| 244 try: | 284 try: |
| 245 self.dst_key.set_contents_from_file( | 285 self.dst_key.set_contents_from_file( |
| 246 self.small_src_file, cb=harnass.call, | 286 self.small_src_file, cb=harnass.call, |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 291 harnass = CallbackTestHarnass( | 331 harnass = CallbackTestHarnass( |
| 292 fail_after_n_bytes=self.larger_src_file_size/2, num_times_to_fail=2) | 332 fail_after_n_bytes=self.larger_src_file_size/2, num_times_to_fail=2) |
| 293 res_upload_handler = ResumableUploadHandler( | 333 res_upload_handler = ResumableUploadHandler( |
| 294 tracker_file_name=self.tracker_file_name, num_retries=1) | 334 tracker_file_name=self.tracker_file_name, num_retries=1) |
| 295 try: | 335 try: |
| 296 self.dst_key.set_contents_from_file( | 336 self.dst_key.set_contents_from_file( |
| 297 self.larger_src_file, cb=harnass.call, | 337 self.larger_src_file, cb=harnass.call, |
| 298 res_upload_handler=res_upload_handler) | 338 res_upload_handler=res_upload_handler) |
| 299 self.fail('Did not get expected ResumableUploadException') | 339 self.fail('Did not get expected ResumableUploadException') |
| 300 except ResumableUploadException, e: | 340 except ResumableUploadException, e: |
| 301 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | 341 self.assertEqual(e.disposition, |
| 342 ResumableTransferDisposition.ABORT_CUR_PROCESS) |
| 302 # Ensure a tracker file survived. | 343 # Ensure a tracker file survived. |
| 303 self.assertTrue(os.path.exists(self.tracker_file_name)) | 344 self.assertTrue(os.path.exists(self.tracker_file_name)) |
| 304 # Try it one more time; this time should succeed. | 345 # Try it one more time; this time should succeed. |
| 305 self.dst_key.set_contents_from_file( | 346 self.dst_key.set_contents_from_file( |
| 306 self.larger_src_file, cb=harnass.call, | 347 self.larger_src_file, cb=harnass.call, |
| 307 res_upload_handler=res_upload_handler) | 348 res_upload_handler=res_upload_handler) |
| 308 self.assertEqual(self.larger_src_file_size, self.dst_key.size) | 349 self.assertEqual(self.larger_src_file_size, self.dst_key.size) |
| 309 self.assertEqual(self.larger_src_file_as_string, | 350 self.assertEqual(self.larger_src_file_as_string, |
| 310 self.dst_key.get_contents_as_string()) | 351 self.dst_key.get_contents_as_string()) |
| 311 self.assertFalse(os.path.exists(self.tracker_file_name)) | 352 self.assertFalse(os.path.exists(self.tracker_file_name)) |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 381 # retries (initial upload request will establish expected size to | 422 # retries (initial upload request will establish expected size to |
| 382 # upload server). | 423 # upload server). |
| 383 res_upload_handler = ResumableUploadHandler( | 424 res_upload_handler = ResumableUploadHandler( |
| 384 tracker_file_name=self.tracker_file_name, num_retries=0) | 425 tracker_file_name=self.tracker_file_name, num_retries=0) |
| 385 try: | 426 try: |
| 386 self.dst_key.set_contents_from_file( | 427 self.dst_key.set_contents_from_file( |
| 387 self.larger_src_file, cb=harnass.call, | 428 self.larger_src_file, cb=harnass.call, |
| 388 res_upload_handler=res_upload_handler) | 429 res_upload_handler=res_upload_handler) |
| 389 self.fail('Did not get expected ResumableUploadException') | 430 self.fail('Did not get expected ResumableUploadException') |
| 390 except ResumableUploadException, e: | 431 except ResumableUploadException, e: |
| 391 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | 432 # First abort (from harnass-forced failure) should be |
| 433 # ABORT_CUR_PROCESS. |
| 434 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT_C
UR_PROCESS) |
| 392 # Ensure a tracker file survived. | 435 # Ensure a tracker file survived. |
| 393 self.assertTrue(os.path.exists(self.tracker_file_name)) | 436 self.assertTrue(os.path.exists(self.tracker_file_name)) |
| 394 # Try it again, this time with different size source file. | 437 # Try it again, this time with different size source file. |
| 395 # Wait 1 second between retry attempts, to give upload server a | 438 # Wait 1 second between retry attempts, to give upload server a |
| 396 # chance to save state so it can respond to changed file size with | 439 # chance to save state so it can respond to changed file size with |
| 397 # 500 response in the next attempt. | 440 # 500 response in the next attempt. |
| 398 time.sleep(1) | 441 time.sleep(1) |
| 399 try: | 442 try: |
| 400 self.dst_key.set_contents_from_file( | 443 self.dst_key.set_contents_from_file( |
| 401 self.largest_src_file, res_upload_handler=res_upload_handler) | 444 self.largest_src_file, res_upload_handler=res_upload_handler) |
| 402 self.fail('Did not get expected ResumableUploadException') | 445 self.fail('Did not get expected ResumableUploadException') |
| 403 except ResumableUploadException, e: | 446 except ResumableUploadException, e: |
| 447 # This abort should be a hard abort (file size changing during |
| 448 # transfer). |
| 404 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | 449 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) |
| 405 self.assertNotEqual( | 450 self.assertNotEqual(e.message.find('file size changed'), -1, e.messa
ge) |
| 406 e.message.find('attempt to upload a different size file'), -1) | |
| 407 | 451 |
| 408 def test_upload_with_file_size_change_during_upload(self): | 452 def test_upload_with_file_size_change_during_upload(self): |
| 409 """ | 453 """ |
| 410 Tests resumable upload on a file that changes sizes while upload | 454 Tests resumable upload on a file that changes sizes while upload |
| 411 in progress | 455 in progress |
| 412 """ | 456 """ |
| 413 # Create a file we can change during the upload. | 457 # Create a file we can change during the upload. |
| 414 test_file_size = 500 * 1024 # 500 KB. | 458 test_file_size = 500 * 1024 # 500 KB. |
| 415 test_file = self.build_test_input_file(test_file_size)[1] | 459 test_file = self.build_test_input_file(test_file_size)[1] |
| 416 harnass = CallbackTestHarnass(fp_to_change=test_file, | 460 harnass = CallbackTestHarnass(fp_to_change=test_file, |
| (...skipping 29 matching lines...) Expand all Loading... |
| 446 res_upload_handler=res_upload_handler) | 490 res_upload_handler=res_upload_handler) |
| 447 self.fail('Did not get expected ResumableUploadException') | 491 self.fail('Did not get expected ResumableUploadException') |
| 448 except ResumableUploadException, e: | 492 except ResumableUploadException, e: |
| 449 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | 493 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) |
| 450 # Ensure the file size didn't change. | 494 # Ensure the file size didn't change. |
| 451 test_file.seek(0, os.SEEK_END) | 495 test_file.seek(0, os.SEEK_END) |
| 452 self.assertEqual(test_file_size, test_file.tell()) | 496 self.assertEqual(test_file_size, test_file.tell()) |
| 453 self.assertNotEqual( | 497 self.assertNotEqual( |
| 454 e.message.find('md5 signature doesn\'t match etag'), -1) | 498 e.message.find('md5 signature doesn\'t match etag'), -1) |
| 455 # Ensure the bad data wasn't left around. | 499 # Ensure the bad data wasn't left around. |
| 456 all_keys = self.dst_key_uri.get_all_keys() | 500 try: |
| 457 self.assertEqual(0, len(all_keys)) | 501 self.dst_key_uri.get_key() |
| 502 self.fail('Did not get expected InvalidUriError') |
| 503 except InvalidUriError, e: |
| 504 pass |
| 458 | 505 |
| 459 def test_upload_with_content_length_header_set(self): | 506 def test_upload_with_content_length_header_set(self): |
| 460 """ | 507 """ |
| 461 Tests resumable upload on a file when the user supplies a | 508 Tests resumable upload on a file when the user supplies a |
| 462 Content-Length header. This is used by gsutil, for example, | 509 Content-Length header. This is used by gsutil, for example, |
| 463 to set the content length when gzipping a file. | 510 to set the content length when gzipping a file. |
| 464 """ | 511 """ |
| 465 res_upload_handler = ResumableUploadHandler() | 512 res_upload_handler = ResumableUploadHandler() |
| 466 try: | 513 try: |
| 467 self.dst_key.set_contents_from_file( | 514 self.dst_key.set_contents_from_file( |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 542 # that do it, with camelCase versions of these names). | 589 # that do it, with camelCase versions of these names). |
| 543 try: | 590 try: |
| 544 print 'Setting up %s...' % test_class.get_suite_description() | 591 print 'Setting up %s...' % test_class.get_suite_description() |
| 545 test_class.set_up_class(debug) | 592 test_class.set_up_class(debug) |
| 546 print 'Running %s...' % test_class.get_suite_description() | 593 print 'Running %s...' % test_class.get_suite_description() |
| 547 unittest.TextTestRunner(verbosity=2).run(suite) | 594 unittest.TextTestRunner(verbosity=2).run(suite) |
| 548 finally: | 595 finally: |
| 549 print 'Cleaning up after %s...' % test_class.get_suite_description() | 596 print 'Cleaning up after %s...' % test_class.get_suite_description() |
| 550 test_class.tear_down_class() | 597 test_class.tear_down_class() |
| 551 print '' | 598 print '' |
| OLD | NEW |