| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # | |
| 3 # Copyright 2010 Google Inc. | |
| 4 # | |
| 5 # Permission is hereby granted, free of charge, to any person obtaining a | |
| 6 # copy of this software and associated documentation files (the | |
| 7 # "Software"), to deal in the Software without restriction, including | |
| 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 | |
| 10 # persons to whom the Software is furnished to do so, subject to the fol- | |
| 11 # lowing conditions: | |
| 12 # | |
| 13 # The above copyright notice and this permission notice shall be included | |
| 14 # in all copies or substantial portions of the Software. | |
| 15 # | |
| 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- | |
| 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, | |
| 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 | |
| 22 # IN THE SOFTWARE. | |
| 23 | |
| 24 """ | |
| 25 Tests of resumable uploads. | |
| 26 """ | |
| 27 | |
| 28 import errno | |
| 29 import getopt | |
| 30 import os | |
| 31 import random | |
| 32 import re | |
| 33 import shutil | |
| 34 import socket | |
| 35 import StringIO | |
| 36 import sys | |
| 37 import tempfile | |
| 38 import time | |
| 39 import unittest | |
| 40 | |
| 41 import boto | |
| 42 from boto.exception import GSResponseError | |
| 43 from boto.gs.resumable_upload_handler import ResumableUploadHandler | |
| 44 from boto.exception import ResumableTransferDisposition | |
| 45 from boto.exception import ResumableUploadException | |
| 46 from boto.exception import StorageResponseError | |
| 47 from boto.tests.cb_test_harnass import CallbackTestHarnass | |
| 48 | |
| 49 | |
| 50 class ResumableUploadTests(unittest.TestCase): | |
| 51 """ | |
| 52 Resumable upload test suite. | |
| 53 """ | |
| 54 | |
| 55 def get_suite_description(self): | |
| 56 return 'Resumable upload test suite' | |
| 57 | |
| 58 @classmethod | |
| 59 def setUp(cls): | |
| 60 """ | |
| 61 Creates dst_key needed by all tests. | |
| 62 | |
| 63 This method's namingCase is required by the unittest framework. | |
| 64 """ | |
| 65 cls.dst_key = cls.dst_key_uri.new_key(validate=False) | |
| 66 | |
| 67 @classmethod | |
| 68 def tearDown(cls): | |
| 69 """ | |
| 70 Deletes any objects or files created by last test run. | |
| 71 | |
| 72 This method's namingCase is required by the unittest framework. | |
| 73 """ | |
| 74 try: | |
| 75 cls.dst_key_uri.delete_key() | |
| 76 except GSResponseError: | |
| 77 # Ignore possible not-found error. | |
| 78 pass | |
| 79 # Recursively delete dst dir and then re-create it, so in effect we | |
| 80 # remove all dirs and files under that directory. | |
| 81 shutil.rmtree(cls.tmp_dir) | |
| 82 os.mkdir(cls.tmp_dir) | |
| 83 | |
| 84 @staticmethod | |
| 85 def build_test_input_file(size): | |
| 86 buf = [] | |
| 87 # I manually construct the random data here instead of calling | |
| 88 # os.urandom() because I want to constrain the range of data (in | |
| 89 # this case to 0'..'9') so the test | |
| 90 # code can easily overwrite part of the StringIO file with | |
| 91 # known-to-be-different values. | |
| 92 for i in range(size): | |
| 93 buf.append(str(random.randint(0, 9))) | |
| 94 file_as_string = ''.join(buf) | |
| 95 return (file_as_string, StringIO.StringIO(file_as_string)) | |
| 96 | |
| 97 @classmethod | |
| 98 def set_up_class(cls, debug): | |
| 99 """ | |
| 100 Initializes test suite. | |
| 101 """ | |
| 102 | |
| 103 # Use a designated tmpdir prefix to make it easy to find the end of | |
| 104 # the tmp path. | |
| 105 cls.tmpdir_prefix = 'tmp_resumable_upload_test' | |
| 106 | |
| 107 # Create test source file data. | |
| 108 cls.empty_src_file_size = 0 | |
| 109 (cls.empty_src_file_as_string, cls.empty_src_file) = ( | |
| 110 cls.build_test_input_file(cls.empty_src_file_size)) | |
| 111 cls.small_src_file_size = 2 * 1024 # 2 KB. | |
| 112 (cls.small_src_file_as_string, cls.small_src_file) = ( | |
| 113 cls.build_test_input_file(cls.small_src_file_size)) | |
| 114 cls.larger_src_file_size = 500 * 1024 # 500 KB. | |
| 115 (cls.larger_src_file_as_string, cls.larger_src_file) = ( | |
| 116 cls.build_test_input_file(cls.larger_src_file_size)) | |
| 117 cls.largest_src_file_size = 1024 * 1024 # 1 MB. | |
| 118 (cls.largest_src_file_as_string, cls.largest_src_file) = ( | |
| 119 cls.build_test_input_file(cls.largest_src_file_size)) | |
| 120 | |
| 121 # Create temp dir. | |
| 122 cls.tmp_dir = tempfile.mkdtemp(prefix=cls.tmpdir_prefix) | |
| 123 | |
| 124 # Create the test bucket. | |
| 125 hostname = socket.gethostname().split('.')[0] | |
| 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() | |
| 131 cls.dst_key_uri = cls.dst_bucket_uri.clone_replace_name('obj') | |
| 132 | |
| 133 cls.tracker_file_name = '%s%suri_tracker' % (cls.tmp_dir, os.sep) | |
| 134 | |
| 135 cls.syntactically_invalid_tracker_file_name = ( | |
| 136 '%s%ssynt_invalid_uri_tracker' % (cls.tmp_dir, os.sep)) | |
| 137 f = open(cls.syntactically_invalid_tracker_file_name, 'w') | |
| 138 f.write('ftp://example.com') | |
| 139 f.close() | |
| 140 | |
| 141 cls.invalid_upload_id = ( | |
| 142 'http://pub.commondatastorage.googleapis.com/?upload_id=' | |
| 143 'AyzB2Uo74W4EYxyi5dp_-r68jz8rtbvshsv4TX7srJVkJ57CxTY5Dw2') | |
| 144 cls.invalid_upload_id_tracker_file_name = ( | |
| 145 '%s%sinvalid_upload_id_tracker' % (cls.tmp_dir, os.sep)) | |
| 146 f = open(cls.invalid_upload_id_tracker_file_name, 'w') | |
| 147 f.write(cls.invalid_upload_id) | |
| 148 f.close() | |
| 149 | |
| 150 cls.created_test_data = True | |
| 151 | |
| 152 @classmethod | |
| 153 def tear_down_class(cls): | |
| 154 """ | |
| 155 Deletes bucket and tmp dir created by set_up_class. | |
| 156 """ | |
| 157 if not hasattr(cls, 'created_test_data'): | |
| 158 return | |
| 159 # Call cls.tearDown() in case the tests got interrupted, to ensure | |
| 160 # dst objects get deleted. | |
| 161 cls.tearDown() | |
| 162 | |
| 163 # 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 | |
| 165 # operations). | |
| 166 for i in range(60): | |
| 167 try: | |
| 168 cls.dst_bucket_uri.delete_bucket() | |
| 169 break | |
| 170 except StorageResponseError: | |
| 171 print 'Test bucket (%s) not yet deleted, still trying' % ( | |
| 172 cls.dst_bucket_uri.uri) | |
| 173 time.sleep(2) | |
| 174 shutil.rmtree(cls.tmp_dir) | |
| 175 cls.tmp_dir = tempfile.mkdtemp(prefix=cls.tmpdir_prefix) | |
| 176 | |
| 177 def test_non_resumable_upload(self): | |
| 178 """ | |
| 179 Tests that non-resumable uploads work | |
| 180 """ | |
| 181 self.dst_key.set_contents_from_file(self.small_src_file) | |
| 182 self.assertEqual(self.small_src_file_size, self.dst_key.size) | |
| 183 self.assertEqual(self.small_src_file_as_string, | |
| 184 self.dst_key.get_contents_as_string()) | |
| 185 | |
| 186 def test_upload_without_persistent_tracker(self): | |
| 187 """ | |
| 188 Tests a single resumable upload, with no tracker URI persistence | |
| 189 """ | |
| 190 res_upload_handler = ResumableUploadHandler() | |
| 191 self.dst_key.set_contents_from_file( | |
| 192 self.small_src_file, res_upload_handler=res_upload_handler) | |
| 193 self.assertEqual(self.small_src_file_size, self.dst_key.size) | |
| 194 self.assertEqual(self.small_src_file_as_string, | |
| 195 self.dst_key.get_contents_as_string()) | |
| 196 | |
| 197 def test_failed_upload_with_persistent_tracker(self): | |
| 198 """ | |
| 199 Tests that failed resumable upload leaves a correct tracker URI file | |
| 200 """ | |
| 201 harnass = CallbackTestHarnass() | |
| 202 res_upload_handler = ResumableUploadHandler( | |
| 203 tracker_file_name=self.tracker_file_name, num_retries=0) | |
| 204 try: | |
| 205 self.dst_key.set_contents_from_file( | |
| 206 self.small_src_file, cb=harnass.call, | |
| 207 res_upload_handler=res_upload_handler) | |
| 208 self.fail('Did not get expected ResumableUploadException') | |
| 209 except ResumableUploadException, e: | |
| 210 # We'll get a ResumableUploadException at this point because | |
| 211 # of CallbackTestHarnass (above). Check that the tracker file was | |
| 212 # created correctly. | |
| 213 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | |
| 214 self.assertTrue(os.path.exists(self.tracker_file_name)) | |
| 215 f = open(self.tracker_file_name) | |
| 216 uri_from_file = f.readline().strip() | |
| 217 f.close() | |
| 218 self.assertEqual(uri_from_file, | |
| 219 res_upload_handler.get_tracker_uri()) | |
| 220 | |
| 221 def test_retryable_exception_recovery(self): | |
| 222 """ | |
| 223 Tests handling of a retryable exception | |
| 224 """ | |
| 225 # Test one of the RETRYABLE_EXCEPTIONS. | |
| 226 exception = ResumableUploadHandler.RETRYABLE_EXCEPTIONS[0] | |
| 227 harnass = CallbackTestHarnass(exception=exception) | |
| 228 res_upload_handler = ResumableUploadHandler(num_retries=1) | |
| 229 self.dst_key.set_contents_from_file( | |
| 230 self.small_src_file, cb=harnass.call, | |
| 231 res_upload_handler=res_upload_handler) | |
| 232 # Ensure uploaded object has correct content. | |
| 233 self.assertEqual(self.small_src_file_size, self.dst_key.size) | |
| 234 self.assertEqual(self.small_src_file_as_string, | |
| 235 self.dst_key.get_contents_as_string()) | |
| 236 | |
| 237 def test_non_retryable_exception_handling(self): | |
| 238 """ | |
| 239 Tests a resumable upload that fails with a non-retryable exception | |
| 240 """ | |
| 241 harnass = CallbackTestHarnass( | |
| 242 exception=OSError(errno.EACCES, 'Permission denied')) | |
| 243 res_upload_handler = ResumableUploadHandler(num_retries=1) | |
| 244 try: | |
| 245 self.dst_key.set_contents_from_file( | |
| 246 self.small_src_file, cb=harnass.call, | |
| 247 res_upload_handler=res_upload_handler) | |
| 248 self.fail('Did not get expected OSError') | |
| 249 except OSError, e: | |
| 250 # Ensure the error was re-raised. | |
| 251 self.assertEqual(e.errno, 13) | |
| 252 | |
| 253 def test_failed_and_restarted_upload_with_persistent_tracker(self): | |
| 254 """ | |
| 255 Tests resumable upload that fails once and then completes, with tracker | |
| 256 file | |
| 257 """ | |
| 258 harnass = CallbackTestHarnass() | |
| 259 res_upload_handler = ResumableUploadHandler( | |
| 260 tracker_file_name=self.tracker_file_name, num_retries=1) | |
| 261 self.dst_key.set_contents_from_file( | |
| 262 self.small_src_file, cb=harnass.call, | |
| 263 res_upload_handler=res_upload_handler) | |
| 264 # Ensure uploaded object has correct content. | |
| 265 self.assertEqual(self.small_src_file_size, self.dst_key.size) | |
| 266 self.assertEqual(self.small_src_file_as_string, | |
| 267 self.dst_key.get_contents_as_string()) | |
| 268 # Ensure tracker file deleted. | |
| 269 self.assertFalse(os.path.exists(self.tracker_file_name)) | |
| 270 | |
| 271 def test_multiple_in_process_failures_then_succeed(self): | |
| 272 """ | |
| 273 Tests resumable upload that fails twice in one process, then completes | |
| 274 """ | |
| 275 res_upload_handler = ResumableUploadHandler(num_retries=3) | |
| 276 self.dst_key.set_contents_from_file( | |
| 277 self.small_src_file, res_upload_handler=res_upload_handler) | |
| 278 # Ensure uploaded object has correct content. | |
| 279 self.assertEqual(self.small_src_file_size, self.dst_key.size) | |
| 280 self.assertEqual(self.small_src_file_as_string, | |
| 281 self.dst_key.get_contents_as_string()) | |
| 282 | |
| 283 def test_multiple_in_process_failures_then_succeed_with_tracker_file(self): | |
| 284 """ | |
| 285 Tests resumable upload that fails completely in one process, | |
| 286 then when restarted completes, using a tracker file | |
| 287 """ | |
| 288 # Set up test harnass that causes more failures than a single | |
| 289 # ResumableUploadHandler instance will handle, writing enough data | |
| 290 # before the first failure that some of it survives that process run. | |
| 291 harnass = CallbackTestHarnass( | |
| 292 fail_after_n_bytes=self.larger_src_file_size/2, num_times_to_fail=2) | |
| 293 res_upload_handler = ResumableUploadHandler( | |
| 294 tracker_file_name=self.tracker_file_name, num_retries=1) | |
| 295 try: | |
| 296 self.dst_key.set_contents_from_file( | |
| 297 self.larger_src_file, cb=harnass.call, | |
| 298 res_upload_handler=res_upload_handler) | |
| 299 self.fail('Did not get expected ResumableUploadException') | |
| 300 except ResumableUploadException, e: | |
| 301 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | |
| 302 # Ensure a tracker file survived. | |
| 303 self.assertTrue(os.path.exists(self.tracker_file_name)) | |
| 304 # Try it one more time; this time should succeed. | |
| 305 self.dst_key.set_contents_from_file( | |
| 306 self.larger_src_file, cb=harnass.call, | |
| 307 res_upload_handler=res_upload_handler) | |
| 308 self.assertEqual(self.larger_src_file_size, self.dst_key.size) | |
| 309 self.assertEqual(self.larger_src_file_as_string, | |
| 310 self.dst_key.get_contents_as_string()) | |
| 311 self.assertFalse(os.path.exists(self.tracker_file_name)) | |
| 312 # Ensure some of the file was uploaded both before and after failure. | |
| 313 self.assertTrue(len(harnass.transferred_seq_before_first_failure) > 1 | |
| 314 and | |
| 315 len(harnass.transferred_seq_after_first_failure) > 1) | |
| 316 | |
| 317 def test_upload_with_inital_partial_upload_before_failure(self): | |
| 318 """ | |
| 319 Tests resumable upload that successfully uploads some content | |
| 320 before it fails, then restarts and completes | |
| 321 """ | |
| 322 # Set up harnass to fail upload after several hundred KB so upload | |
| 323 # server will have saved something before we retry. | |
| 324 harnass = CallbackTestHarnass( | |
| 325 fail_after_n_bytes=self.larger_src_file_size/2) | |
| 326 res_upload_handler = ResumableUploadHandler(num_retries=1) | |
| 327 self.dst_key.set_contents_from_file( | |
| 328 self.larger_src_file, cb=harnass.call, | |
| 329 res_upload_handler=res_upload_handler) | |
| 330 # Ensure uploaded object has correct content. | |
| 331 self.assertEqual(self.larger_src_file_size, self.dst_key.size) | |
| 332 self.assertEqual(self.larger_src_file_as_string, | |
| 333 self.dst_key.get_contents_as_string()) | |
| 334 # Ensure some of the file was uploaded both before and after failure. | |
| 335 self.assertTrue(len(harnass.transferred_seq_before_first_failure) > 1 | |
| 336 and | |
| 337 len(harnass.transferred_seq_after_first_failure) > 1) | |
| 338 | |
| 339 def test_empty_file_upload(self): | |
| 340 """ | |
| 341 Tests uploading an empty file (exercises boundary conditions). | |
| 342 """ | |
| 343 res_upload_handler = ResumableUploadHandler() | |
| 344 self.dst_key.set_contents_from_file( | |
| 345 self.empty_src_file, res_upload_handler=res_upload_handler) | |
| 346 self.assertEqual(0, self.dst_key.size) | |
| 347 | |
| 348 def test_upload_retains_metadata(self): | |
| 349 """ | |
| 350 Tests that resumable upload correctly sets passed metadata | |
| 351 """ | |
| 352 res_upload_handler = ResumableUploadHandler() | |
| 353 headers = {'Content-Type' : 'text/plain', 'Content-Encoding' : 'gzip', | |
| 354 'x-goog-meta-abc' : 'my meta', 'x-goog-acl' : 'public-read'} | |
| 355 self.dst_key.set_contents_from_file( | |
| 356 self.small_src_file, headers=headers, | |
| 357 res_upload_handler=res_upload_handler) | |
| 358 self.assertEqual(self.small_src_file_size, self.dst_key.size) | |
| 359 self.assertEqual(self.small_src_file_as_string, | |
| 360 self.dst_key.get_contents_as_string()) | |
| 361 self.dst_key.open_read() | |
| 362 self.assertEqual('text/plain', self.dst_key.content_type) | |
| 363 self.assertEqual('gzip', self.dst_key.content_encoding) | |
| 364 self.assertTrue('abc' in self.dst_key.metadata) | |
| 365 self.assertEqual('my meta', str(self.dst_key.metadata['abc'])) | |
| 366 acl = self.dst_key.get_acl() | |
| 367 for entry in acl.entries.entry_list: | |
| 368 if str(entry.scope) == '<AllUsers>': | |
| 369 self.assertEqual('READ', str(acl.entries.entry_list[1].permissio
n)) | |
| 370 return | |
| 371 self.fail('No <AllUsers> scope found') | |
| 372 | |
| 373 def test_upload_with_file_size_change_between_starts(self): | |
| 374 """ | |
| 375 Tests resumable upload on a file that changes sizes between inital | |
| 376 upload start and restart | |
| 377 """ | |
| 378 harnass = CallbackTestHarnass( | |
| 379 fail_after_n_bytes=self.larger_src_file_size/2) | |
| 380 # Set up first process' ResumableUploadHandler not to do any | |
| 381 # retries (initial upload request will establish expected size to | |
| 382 # upload server). | |
| 383 res_upload_handler = ResumableUploadHandler( | |
| 384 tracker_file_name=self.tracker_file_name, num_retries=0) | |
| 385 try: | |
| 386 self.dst_key.set_contents_from_file( | |
| 387 self.larger_src_file, cb=harnass.call, | |
| 388 res_upload_handler=res_upload_handler) | |
| 389 self.fail('Did not get expected ResumableUploadException') | |
| 390 except ResumableUploadException, e: | |
| 391 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | |
| 392 # Ensure a tracker file survived. | |
| 393 self.assertTrue(os.path.exists(self.tracker_file_name)) | |
| 394 # Try it again, this time with different size source file. | |
| 395 # 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 | |
| 397 # 500 response in the next attempt. | |
| 398 time.sleep(1) | |
| 399 try: | |
| 400 self.dst_key.set_contents_from_file( | |
| 401 self.largest_src_file, res_upload_handler=res_upload_handler) | |
| 402 self.fail('Did not get expected ResumableUploadException') | |
| 403 except ResumableUploadException, e: | |
| 404 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | |
| 405 self.assertNotEqual( | |
| 406 e.message.find('attempt to upload a different size file'), -1) | |
| 407 | |
| 408 def test_upload_with_file_size_change_during_upload(self): | |
| 409 """ | |
| 410 Tests resumable upload on a file that changes sizes while upload | |
| 411 in progress | |
| 412 """ | |
| 413 # Create a file we can change during the upload. | |
| 414 test_file_size = 500 * 1024 # 500 KB. | |
| 415 test_file = self.build_test_input_file(test_file_size)[1] | |
| 416 harnass = CallbackTestHarnass(fp_to_change=test_file, | |
| 417 fp_change_pos=test_file_size) | |
| 418 res_upload_handler = ResumableUploadHandler(num_retries=1) | |
| 419 try: | |
| 420 self.dst_key.set_contents_from_file( | |
| 421 test_file, cb=harnass.call, | |
| 422 res_upload_handler=res_upload_handler) | |
| 423 self.fail('Did not get expected ResumableUploadException') | |
| 424 except ResumableUploadException, e: | |
| 425 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | |
| 426 self.assertNotEqual( | |
| 427 e.message.find('File changed during upload'), -1) | |
| 428 | |
| 429 def test_upload_with_file_content_change_during_upload(self): | |
| 430 """ | |
| 431 Tests resumable upload on a file that changes one byte of content | |
| 432 (so, size stays the same) while upload in progress | |
| 433 """ | |
| 434 test_file_size = 500 * 1024 # 500 KB. | |
| 435 test_file = self.build_test_input_file(test_file_size)[1] | |
| 436 harnass = CallbackTestHarnass(fail_after_n_bytes=test_file_size/2, | |
| 437 fp_to_change=test_file, | |
| 438 # Writing at file_size-5 won't change file | |
| 439 # size because CallbackTestHarnass only | |
| 440 # writes 3 bytes. | |
| 441 fp_change_pos=test_file_size-5) | |
| 442 res_upload_handler = ResumableUploadHandler(num_retries=1) | |
| 443 try: | |
| 444 self.dst_key.set_contents_from_file( | |
| 445 test_file, cb=harnass.call, | |
| 446 res_upload_handler=res_upload_handler) | |
| 447 self.fail('Did not get expected ResumableUploadException') | |
| 448 except ResumableUploadException, e: | |
| 449 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | |
| 450 # Ensure the file size didn't change. | |
| 451 test_file.seek(0, os.SEEK_END) | |
| 452 self.assertEqual(test_file_size, test_file.tell()) | |
| 453 self.assertNotEqual( | |
| 454 e.message.find('md5 signature doesn\'t match etag'), -1) | |
| 455 # Ensure the bad data wasn't left around. | |
| 456 all_keys = self.dst_key_uri.get_all_keys() | |
| 457 self.assertEqual(0, len(all_keys)) | |
| 458 | |
| 459 def test_upload_with_content_length_header_set(self): | |
| 460 """ | |
| 461 Tests resumable upload on a file when the user supplies a | |
| 462 Content-Length header. This is used by gsutil, for example, | |
| 463 to set the content length when gzipping a file. | |
| 464 """ | |
| 465 res_upload_handler = ResumableUploadHandler() | |
| 466 try: | |
| 467 self.dst_key.set_contents_from_file( | |
| 468 self.small_src_file, res_upload_handler=res_upload_handler, | |
| 469 headers={'Content-Length' : self.small_src_file_size}) | |
| 470 self.fail('Did not get expected ResumableUploadException') | |
| 471 except ResumableUploadException, e: | |
| 472 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | |
| 473 self.assertNotEqual( | |
| 474 e.message.find('Attempt to specify Content-Length header'), -1) | |
| 475 | |
| 476 def test_upload_with_syntactically_invalid_tracker_uri(self): | |
| 477 """ | |
| 478 Tests resumable upload with a syntactically invalid tracker URI | |
| 479 """ | |
| 480 res_upload_handler = ResumableUploadHandler( | |
| 481 tracker_file_name=self.syntactically_invalid_tracker_file_name) | |
| 482 # An error should be printed about the invalid URI, but then it | |
| 483 # should run the update successfully. | |
| 484 self.dst_key.set_contents_from_file( | |
| 485 self.small_src_file, res_upload_handler=res_upload_handler) | |
| 486 self.assertEqual(self.small_src_file_size, self.dst_key.size) | |
| 487 self.assertEqual(self.small_src_file_as_string, | |
| 488 self.dst_key.get_contents_as_string()) | |
| 489 | |
| 490 def test_upload_with_invalid_upload_id_in_tracker_file(self): | |
| 491 """ | |
| 492 Tests resumable upload with invalid upload ID | |
| 493 """ | |
| 494 res_upload_handler = ResumableUploadHandler( | |
| 495 tracker_file_name=self.invalid_upload_id_tracker_file_name) | |
| 496 # An error should occur, but then the tracker URI should be | |
| 497 # regenerated and the the update should succeed. | |
| 498 self.dst_key.set_contents_from_file( | |
| 499 self.small_src_file, res_upload_handler=res_upload_handler) | |
| 500 self.assertEqual(self.small_src_file_size, self.dst_key.size) | |
| 501 self.assertEqual(self.small_src_file_as_string, | |
| 502 self.dst_key.get_contents_as_string()) | |
| 503 self.assertNotEqual(self.invalid_upload_id, | |
| 504 res_upload_handler.get_tracker_uri()) | |
| 505 | |
| 506 def test_upload_with_unwritable_tracker_file(self): | |
| 507 """ | |
| 508 Tests resumable upload with an unwritable tracker file | |
| 509 """ | |
| 510 # Make dir where tracker_file lives temporarily unwritable. | |
| 511 save_mod = os.stat(self.tmp_dir).st_mode | |
| 512 try: | |
| 513 os.chmod(self.tmp_dir, 0) | |
| 514 res_upload_handler = ResumableUploadHandler( | |
| 515 tracker_file_name=self.tracker_file_name) | |
| 516 except ResumableUploadException, e: | |
| 517 self.assertEqual(e.disposition, ResumableTransferDisposition.ABORT) | |
| 518 self.assertNotEqual( | |
| 519 e.message.find('Couldn\'t write URI tracker file'), -1) | |
| 520 finally: | |
| 521 # Restore original protection of dir where tracker_file lives. | |
| 522 os.chmod(self.tmp_dir, save_mod) | |
| 523 | |
| 524 if __name__ == '__main__': | |
| 525 if sys.version_info[:3] < (2, 5, 1): | |
| 526 sys.exit('These tests must be run on at least Python 2.5.1\n') | |
| 527 | |
| 528 # Use -d to see more HTTP protocol detail during tests. | |
| 529 debug = 0 | |
| 530 opts, args = getopt.getopt(sys.argv[1:], 'd', ['debug']) | |
| 531 for o, a in opts: | |
| 532 if o in ('-d', '--debug'): | |
| 533 debug = 2 | |
| 534 | |
| 535 test_loader = unittest.TestLoader() | |
| 536 test_loader.testMethodPrefix = 'test_' | |
| 537 suite = test_loader.loadTestsFromTestCase(ResumableUploadTests) | |
| 538 # Seems like there should be a cleaner way to find the test_class. | |
| 539 test_class = suite.__getattribute__('_tests')[0] | |
| 540 # We call set_up_class() and tear_down_class() ourselves because we | |
| 541 # don't assume the user has Python 2.7 (which supports classmethods | |
| 542 # that do it, with camelCase versions of these names). | |
| 543 try: | |
| 544 print 'Setting up %s...' % test_class.get_suite_description() | |
| 545 test_class.set_up_class(debug) | |
| 546 print 'Running %s...' % test_class.get_suite_description() | |
| 547 unittest.TextTestRunner(verbosity=2).run(suite) | |
| 548 finally: | |
| 549 print 'Cleaning up after %s...' % test_class.get_suite_description() | |
| 550 test_class.tear_down_class() | |
| 551 print '' | |
| OLD | NEW |