1 diff -up nose-1.3.7/AUTHORS.unicode nose-1.3.7/AUTHORS
2 diff -up nose-1.3.7/CHANGELOG.unicode nose-1.3.7/CHANGELOG
3 diff -up nose-1.3.7/nose/plugins/capture.py.unicode nose-1.3.7/nose/plugins/capture.py
4 --- nose-1.3.7/nose/plugins/capture.py.unicode 2015-04-04 02:52:52.000000000 -0600
5 +++ nose-1.3.7/nose/plugins/capture.py 2016-11-15 13:58:18.713025335 -0700
6 @@ -12,6 +12,7 @@ the options ``-s`` or ``--nocapture``.
11 from nose.plugins.base import Plugin
12 from nose.pyversion import exc_to_unicode, force_unicode
13 from nose.util import ln
14 @@ -71,26 +72,56 @@ class Capture(Plugin):
15 def formatError(self, test, err):
16 """Add captured output to error report.
18 - test.capturedOutput = output = self.buffer
19 + test.capturedOutput = output = ''
20 + output_exc_info = None
22 + test.capturedOutput = output = self.buffer
23 + except UnicodeError:
24 + # python2's StringIO.StringIO [1] class has this warning:
26 + # The StringIO object can accept either Unicode or 8-bit strings,
27 + # but mixing the two may take some care. If both are used, 8-bit
28 + # strings that cannot be interpreted as 7-bit ASCII (that use the
29 + # 8th bit) will cause a UnicodeError to be raised when getvalue()
32 + # This exception handler is a protection against issue #816 [2].
33 + # Capturing the exception info allows us to display it back to the
36 + # [1] <https://github.com/python/cpython/blob/2.7/Lib/StringIO.py#L258>
37 + # [2] <https://github.com/nose-devs/nose/issues/816>
38 + output_exc_info = sys.exc_info()
41 + if (not output) and (not output_exc_info):
42 # Don't return None as that will prevent other
43 # formatters from formatting and remove earlier formatters
44 # formats, instead return the err we got
47 - return (ec, self.addCaptureToErr(ev, output), tb)
48 + return (ec, self.addCaptureToErr(ev, output, output_exc_info=output_exc_info), tb)
50 def formatFailure(self, test, err):
51 """Add captured output to failure report.
53 return self.formatError(test, err)
55 - def addCaptureToErr(self, ev, output):
56 + def addCaptureToErr(self, ev, output, output_exc_info=None):
57 + # If given, output_exc_info should be a 3-tuple from sys.exc_info(),
58 + # from an exception raised while trying to get the captured output.
59 ev = exc_to_unicode(ev)
60 output = force_unicode(output)
61 - return u'\n'.join([ev, ln(u'>> begin captured stdout <<'),
62 - output, ln(u'>> end captured stdout <<')])
63 + error_text = [ev, ln(u'>> begin captured stdout <<'),
64 + output, ln(u'>> end captured stdout <<')]
66 + error_text.extend([u'OUTPUT ERROR: Could not get captured output.',
67 + # <https://github.com/python/cpython/blob/2.7/Lib/StringIO.py#L258>
68 + # <https://github.com/nose-devs/nose/issues/816>
69 + u"The test might've printed both 'unicode' strings and non-ASCII 8-bit 'str' strings.",
70 + ln(u'>> begin captured stdout exception traceback <<'),
71 + u''.join(traceback.format_exception(*output_exc_info)),
72 + ln(u'>> end captured stdout exception traceback <<')])
73 + return u'\n'.join(error_text)
76 self.stdout.append(sys.stdout)
77 diff -up nose-1.3.7/unit_tests/test_capture_plugin.py.unicode nose-1.3.7/unit_tests/test_capture_plugin.py
78 --- nose-1.3.7/unit_tests/test_capture_plugin.py.unicode 2012-09-29 02:18:54.000000000 -0600
79 +++ nose-1.3.7/unit_tests/test_capture_plugin.py 2016-11-15 13:58:18.714025330 -0700
80 @@ -4,6 +4,12 @@ import unittest
81 from optparse import OptionParser
82 from nose.config import Config
83 from nose.plugins.capture import Capture
84 +from nose.pyversion import force_unicode
86 +if sys.version_info[0] == 2:
91 class TestCapturePlugin(unittest.TestCase):
93 @@ -62,6 +68,35 @@ class TestCapturePlugin(unittest.TestCas
95 self.assertEqual(c.buffer, "test 日本\n")
97 + def test_does_not_crash_with_mixed_unicode_and_nonascii_str(self):
103 + printed_nonascii_str = force_unicode("test 日本").encode('utf-8')
104 + printed_unicode = force_unicode("Hello")
105 + print printed_nonascii_str
106 + print printed_unicode
108 + raise Exception("boom")
110 + err = sys.exc_info()
111 + formatted = c.formatError(d, err)
112 + _, fev, _ = formatted
115 + for string in [force_unicode(printed_nonascii_str, encoding='utf-8'), printed_unicode]:
116 + assert string not in fev, "Output unexpectedly found in error message"
117 + assert d.capturedOutput == '', "capturedOutput unexpectedly non-empty"
118 + assert "OUTPUT ERROR" in fev
119 + assert "captured stdout exception traceback" in fev
120 + assert "UnicodeDecodeError" in fev
122 + for string in [repr(printed_nonascii_str), printed_unicode]:
123 + assert string in fev, "Output not found in error message"
124 + assert string in d.capturedOutput, "Output not attached to test"
126 def test_format_error(self):