diff --git a/CHANGES b/CHANGES index 0f0da4f6c..f4ae5abaa 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,8 @@ Bugs fixed * #1843: Fix documentation of descriptor classes that have a custom metaclass. Thanks to Erik Bray. * #3190: util.split_docinfo fails to parse multi-line field bodies +* #3024, #3037: In Python3, application.Sphinx._log crushed when the log message cannot + be encoded into console encoding. Release 1.4.9 (released Nov 23, 2016) ===================================== diff --git a/sphinx/application.py b/sphinx/application.py index 9d99227c5..45d5d5986 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -322,7 +322,9 @@ class Sphinx(object): wfile.write(message) except UnicodeEncodeError: encoding = getattr(wfile, 'encoding', 'ascii') or 'ascii' - wfile.write(message.encode(encoding, 'replace')) + # wfile.write accept only str, not bytes.So, we encode and replace + # non-encodable characters, then decode them. + wfile.write(message.encode(encoding, 'replace').decode(encoding)) if not nonl: wfile.write('\n') if hasattr(wfile, 'flush'): diff --git a/tests/test_application.py b/tests/test_application.py index 420680451..b0ad4b8d6 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -8,6 +8,7 @@ :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +import codecs from docutils import nodes @@ -49,21 +50,40 @@ def test_emit_with_nonascii_name_node(app, status, warning): @with_app() def test_output(app, status, warning): + # info with newline status.truncate(0) # __init__ writes to status status.seek(0) app.info("Nothing here...") assert status.getvalue() == "Nothing here...\n" + # info without newline status.truncate(0) status.seek(0) app.info("Nothing here...", True) assert status.getvalue() == "Nothing here..." + # warning old_count = app._warncount app.warn("Bad news!") assert warning.getvalue() == "WARNING: Bad news!\n" assert app._warncount == old_count + 1 +@with_app() +def test_output_with_unencodable_char(app, status, warning): + + class StreamWriter(codecs.StreamWriter): + def write(self, object): + self.stream.write(object.encode('cp1252').decode('cp1252')) + + app._status = StreamWriter(status) + + # info with UnicodeEncodeError + status.truncate(0) + status.seek(0) + app.info(u"unicode \u206d...") + assert status.getvalue() == "unicode ?...\n" + + @with_app() def test_extensions(app, status, warning): app.setup_extension('shutil')