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 |