-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Errors and warnings are printed to stderr. #919
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -274,13 +274,15 @@ def run(self): | |
|
||
def _process_print_task(self, print_task): | ||
print_str = print_task.message | ||
print_to_stderr = False | ||
if print_task.error: | ||
self.num_errors_seen += 1 | ||
print_to_stderr = True | ||
warning = False | ||
if print_task.warning: | ||
if print_task.warning: | ||
warning = True | ||
self.num_warnings_seen += 1 | ||
warning = True | ||
self.num_warnings_seen += 1 | ||
print_to_stderr = True | ||
final_str = '' | ||
if warning: | ||
final_str += print_str.ljust(self._progress_length, ' ') | ||
|
@@ -309,6 +311,12 @@ def _process_print_task(self, print_task): | |
self._num_parts += 1 | ||
self._file_count += 1 | ||
|
||
# If the message is an error or warning, print it to standard error. | ||
if print_to_stderr == True and not self._quiet: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't use equality checks to compare booleans, just |
||
uni_print(final_str, sys.stderr) | ||
sys.stderr.flush() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just noticed that every usage of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
final_str = '' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we want to not return after this line? Do we still want to print the progress on an error? For example:
Looks a bit off to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do not want to return. The progress bar should always be the last thing printed for each print task, unless it is the last file. The example you gave is legitimate since the If I was to put a return at the end of the that code block, there can be scenarios when the user is without the progress bar for a long period of time. For example, if you had a bunch, like thousands of failed uploads, the progress bar would never show up because we never print it, and therefore the user would have no idea when the command is reaching an end. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the example I gave above, it is the last file we try to sync. Specifically I have three files in my directory, 1 that is unreadable. Running the sync command I get:
The last line seems out of place to me and I don't think it should be there. I'm not saying we shouldn't have progress messages, but it seems like there are cases where we print a progress message that shouldn't be printed, such as in my example above. Given this behavior existed prior to this change (I only noticed because you're modifying this area of the code and it stood out), I would be ok with addressing this as a separate PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I just tested this and I cannot repo this output:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It depends on the order of the files. For example:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. It is an easy fix. We just need to put an additional check in there that if I can make another pull request later to fix it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or I meant |
||
|
||
is_done = self._total_files == self._file_count | ||
if not is_done: | ||
prog_str = "Completed %s " % self._num_parts | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -223,24 +223,26 @@ def __init__(self): | |
self.count = 0 | ||
|
||
|
||
def uni_print(statement): | ||
def uni_print(statement, out_file=None): | ||
""" | ||
This function is used to properly write unicode to stdout. It | ||
ensures that the proper encoding is used if the statement is | ||
not in a version type of string. The initial check is to | ||
allow if ``sys.stdout`` does not use an encoding | ||
This function is used to properly write unicode to a file, usually | ||
stdout or stdderr. It ensures that the proper encoding is used if the | ||
statement is not a string type. The initial check is to allow if | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "The initial check is to allow if..." sentence is confusing. |
||
``out_file`` does not use an encoding. | ||
""" | ||
encoding = getattr(sys.stdout, 'encoding', None) | ||
if out_file is None: | ||
out_file = sys.stdout | ||
encoding = getattr(out_file, 'encoding', None) | ||
if encoding is not None and not PY3: | ||
sys.stdout.write(statement.encode(sys.stdout.encoding)) | ||
out_file.write(statement.encode(out_file.encoding)) | ||
else: | ||
try: | ||
sys.stdout.write(statement) | ||
out_file.write(statement) | ||
except UnicodeEncodeError: | ||
# Some file like objects like cStringIO will | ||
# try to decode as ascii. Interestingly enough | ||
# this works with a normal StringIO. | ||
sys.stdout.write(statement.encode('utf-8')) | ||
out_file.write(statement.encode('utf-8')) | ||
|
||
|
||
def guess_content_type(filename): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,12 +90,20 @@ def __call__(self): | |
executor.wait_until_shutdown() | ||
self.assertEqual(open(f.name, 'rb').read(), b'foobar') | ||
|
||
|
||
class TestPrintThread(unittest.TestCase): | ||
def test_print_error(self): | ||
result_queue = queue.Queue() | ||
print_task = PrintTask(message="Fail File.", error=True) | ||
thread = PrintThread(result_queue, False) | ||
with mock.patch('sys.stderr', new=six.StringIO()) as mock_stderr: | ||
thread._process_print_task(print_task) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's frowned upon to test through an internal API. Unless there's a really good reason, the unittests should be using the public APIs of the objects under test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, the only reason I tested these are internal methods of threads, and the only way to call these methods is if I run the thread. So that means I would have to be careful that the threads are cleaned up properly. But, it should not be an issue if I use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a bit of an aside, but I guess I should also mention that you typically don't want to spin up threads in a unittest unless that's actually the behavior you're testing, i.e something related to threading/synchronization. I realize that there are some existing S3 tests that do this, but we really shouldn't be doing this in new tests. In this scenario, the thing you're testing has, as you mentioned, nothing to do with threads, so they should ideally not be spun up. Compare that to a test like If the functionality is too hard to test in the existing class then it might make sense to extract a new class from this code. It also looks like you might be able to accomplish what you want by using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I forgot that there is a separate |
||
self.assertIn("Fail File.", mock_stderr.getvalue()) | ||
|
||
def test_print_warning(self): | ||
result_queue = queue.Queue() | ||
print_task = PrintTask(message="Bad File.", warning=True) | ||
thread = PrintThread(result_queue, False) | ||
with mock.patch('sys.stdout', new=six.StringIO()) as mock_stdout: | ||
with mock.patch('sys.stderr', new=six.StringIO()) as mock_stderr: | ||
thread._process_print_task(print_task) | ||
self.assertIn("Bad File.", mock_stdout.getvalue()) | ||
|
||
self.assertIn("Bad File.", mock_stderr.getvalue()) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -140,7 +140,7 @@ def setUp(self): | |
self.session = FakeSession() | ||
self.service = self.session.get_service('s3') | ||
self.endpoint = self.service.get_endpoint('us-east-1') | ||
params = {'region': 'us-east-1', 'acl': ['private']} | ||
params = {'region': 'us-east-1', 'acl': ['private'], 'quiet': True} | ||
self.s3_handler = S3Handler(self.session, params) | ||
self.s3_handler_multi = S3Handler(self.session, multi_threshold=10, | ||
chunksize=2, | ||
|
@@ -275,7 +275,7 @@ def setUp(self): | |
self.session = FakeSession(True, True) | ||
self.service = self.session.get_service('s3') | ||
self.endpoint = self.service.get_endpoint('us-east-1') | ||
params = {'region': 'us-east-1'} | ||
params = {'region': 'us-east-1', 'quiet': True} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the reason for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some of the tests involve testing failing multipart tasks. However, the failures get printed to stderr now, and those messages were getting mixed with dots when running nose, which was annoying. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that makes sense. It was just that from a reviewer's perspective this change seemed out of place without any comment or explanation in the PR so it wasn't clear if this was a mistake or intentional, and if so what its purpose was. Not opposed to leaving this. |
||
self.s3_handler_multi = S3Handler(self.session, params, | ||
multi_threshold=10, chunksize=2) | ||
self.bucket = create_bucket(self.session) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the newly added lines here, this method is now 62 lines long. Let's split this up to make it easier to manage.