diff --git a/awscli/customizations/s3/s3handler.py b/awscli/customizations/s3/s3handler.py index 29c6c49103ce..8c44487342c7 100644 --- a/awscli/customizations/s3/s3handler.py +++ b/awscli/customizations/s3/s3handler.py @@ -260,8 +260,9 @@ def _enqueue_range_download_tasks(self, filename, remove_remote_file=False): chunksize = find_chunksize(filename.size, self.chunksize) num_downloads = int(filename.size / chunksize) context = tasks.MultipartDownloadContext(num_downloads) - create_file_task = tasks.CreateLocalFileTask(context=context, - filename=filename) + create_file_task = tasks.CreateLocalFileTask( + context=context, filename=filename, + result_queue=self.result_queue) self.executor.submit(create_file_task) self._do_enqueue_range_download_tasks( filename=filename, chunksize=chunksize, diff --git a/awscli/customizations/s3/tasks.py b/awscli/customizations/s3/tasks.py index 59a649f645fe..c2c66a4a322a 100644 --- a/awscli/customizations/s3/tasks.py +++ b/awscli/customizations/s3/tasks.py @@ -263,9 +263,10 @@ def __call__(self): class CreateLocalFileTask(OrderableTask): - def __init__(self, context, filename): + def __init__(self, context, filename, result_queue): self._context = context self._filename = filename + self._result_queue = result_queue def __call__(self): dirname = os.path.dirname(self._filename.dest) @@ -284,6 +285,11 @@ def __call__(self): with open(self._filename.dest, 'wb'): pass except Exception as e: + message = print_operation(self._filename, failed=True, + dryrun=False) + message += '\n' + str(e) + result = {'message': message, 'error': True} + self._result_queue.put(PrintTask(**result)) self._context.cancel() else: self._context.announce_file_created() diff --git a/tests/unit/customizations/s3/test_tasks.py b/tests/unit/customizations/s3/test_tasks.py index b30267bec069..4d4f854be886 100644 --- a/tests/unit/customizations/s3/test_tasks.py +++ b/tests/unit/customizations/s3/test_tasks.py @@ -15,6 +15,10 @@ import threading import mock import socket +import os +import tempfile +import shutil +from six.moves import queue from botocore.exceptions import IncompleteReadError from botocore.vendored.requests.packages.urllib3.exceptions import \ @@ -326,6 +330,39 @@ def test_print_operation(self): self.assertIn(r'e:\foo', message) +class TestCreateLocalFileTask(unittest.TestCase): + def setUp(self): + self.result_queue = queue.Queue() + self.tempdir = tempfile.mkdtemp() + self.filename = mock.Mock() + self.filename.src = 'bucket/key' + self.filename.dest = os.path.join(self.tempdir, 'local', 'file') + self.filename.operation_name = 'download' + self.context = mock.Mock() + self.task = CreateLocalFileTask(self.context, + self.filename, + self.result_queue) + + def tearDown(self): + shutil.rmtree(self.tempdir) + + def test_creates_file_and_announces(self): + self.task() + self.assertTrue(os.path.isfile(self.filename.dest)) + self.context.announce_file_created.assert_called_with() + self.assertTrue(self.result_queue.empty()) + + def test_cancel_command_on_exception(self): + # Set destination directory to read-only + os.chmod(self.tempdir, 444) + self.task() + self.assertFalse(os.path.isfile(self.filename.dest)) + self.context.cancel.assert_called_with() + self.assertFalse(self.result_queue.empty()) + error_message = self.result_queue.get() + self.assertIn("download failed", error_message.message) + + class TestDownloadPartTask(unittest.TestCase): def setUp(self): self.result_queue = mock.Mock() @@ -495,7 +532,7 @@ def setUp(self): def create_task(self): # We don't actually care about the arguments, we just want to test # the ordering of the tasks. - return CreateLocalFileTask(None, None) + return CreateLocalFileTask(None, None, None) def complete_task(self): return CompleteDownloadTask(None, None, None, None, None)