4 # Author: Günter Milde <milde@users.sourceforge.net>
5 # Copyright: This module has been placed in the public domain.
8 Test `EnvironmentError` reporting.
10 In some locales, the `errstr` argument of IOError and OSError contains
13 In Python 2, converting an exception instance to `str` or `unicode`
14 might fail, with non-ASCII chars in arguments and the default encoding
15 and errors ('ascii', 'strict').
17 Therefore, Docutils must not use string interpolation with exception
18 instances like, e.g., ::
22 except IOError, error:
23 print 'Found %s' % error
25 unless the minimal required Python version has this problem fixed.
31 try: # from standard library module `io`
32 from io
import StringIO
, BytesIO
33 except ImportError: # new in Python 2.6
34 from StringIO
import StringIO
37 import DocutilsTestSupport
# must be imported before docutils
38 from docutils
import core
, parsers
, frontend
, utils
39 from docutils
.utils
.error_reporting
import SafeString
, ErrorString
, ErrorOutput
42 if sys
.version_info
< (3,0): # problems solved in py3k
44 import locale
# module missing in Jython
45 oldlocale
= locale
.getlocale()
46 # Why does getlocale return the defaultlocale in Python 3.2 ????
47 # oldlocale = (None, None) # test suite runs without locale
49 print ('cannot test error reporting with problematic locales,\n'
50 '`import locale` failed.')
53 # locales confirmed to use non-ASCII chars in the IOError message
54 # for a missing file (https://bugs.gentoo.org/show_bug.cgi?id=349101)
55 # TODO: add more confirmed problematic locales
56 problematic_locales
= ['cs_CZ', 'cs_CZ.UTF8',
57 'el_GR', 'el_GR.UTF-8',
58 # 'fr_FR.UTF-8', # only OSError
60 'ru_RU', 'ru_RU.KOI8-R',
62 '', # default locale: might be non-problematic
65 if oldlocale
is not None:
66 # find a supported problematic locale:
67 for testlocale
in problematic_locales
:
69 locale
.setlocale(locale
.LC_ALL
, testlocale
)
74 locale
.setlocale(locale
.LC_ALL
, oldlocale
) # reset
78 class SafeStringTests(unittest
.TestCase
):
79 # the error message in EnvironmentError instances comes from the OS
80 # and in some locales (e.g. ru_RU), contains high bit chars.
81 # -> see the test in test_error_reporting.py
84 bs
= b
'\xfc' # unicode(bs) fails, str(bs) in Python 3 return repr()
85 us
= u
'\xfc' # bytes(us) fails; str(us) fails in Python 2
86 be
= Exception(bs
) # unicode(be) fails
87 ue
= Exception(us
) # bytes(ue) fails, str(ue) fails in Python 2;
88 # unicode(ue) fails in Python < 2.6 (issue2517_)
89 # .. _issue2517: http://bugs.python.org/issue2517
97 # wrapping (not required with 7-bit chars) must not change the
98 # result of conversions:
103 self
.assertEqual(str(42), str(SafeString(42)))
104 self
.assertEqual(str(bs7
), str(SafeString(bs7
)))
105 self
.assertEqual(str(us7
), str(SafeString(us7
)))
106 self
.assertEqual(str(be7
), str(SafeString(be7
)))
107 self
.assertEqual(str(ue7
), str(SafeString(ue7
)))
108 self
.assertEqual(unicode(7), unicode(SafeString(7)))
109 self
.assertEqual(unicode(bs7
), unicode(SafeString(bs7
)))
110 self
.assertEqual(unicode(us7
), unicode(SafeString(us7
)))
111 self
.assertEqual(unicode(be7
), unicode(SafeString(be7
)))
112 self
.assertEqual(unicode(ue7
), unicode(SafeString(ue7
)))
115 """Test conversion to a unicode-string."""
116 # unicode(self.bs) fails
117 self
.assertEqual(unicode, type(unicode(self
.wbs
)))
118 self
.assertEqual(unicode(self
.us
), unicode(self
.wus
))
119 # unicode(self.be) fails
120 self
.assertEqual(unicode, type(unicode(self
.wbe
)))
121 # unicode(ue) fails in Python < 2.6 (issue2517_)
122 self
.assertEqual(unicode, type(unicode(self
.wue
)))
123 self
.assertEqual(self
.us
, unicode(self
.wue
))
126 """Test conversion to a string (bytes in Python 2, unicode in Python 3)."""
127 self
.assertEqual(str(self
.bs
), str(self
.wbs
))
128 self
.assertEqual(str(self
.be
), str(self
.be
))
129 # str(us) fails in Python 2
130 self
.assertEqual(str, type(str(self
.wus
)))
131 # str(ue) fails in Python 2
132 self
.assertEqual(str, type(str(self
.wue
)))
135 class ErrorStringTests(unittest
.TestCase
):
136 bs
= b
'\xfc' # unicode(bs) fails, str(bs) in Python 3 return repr()
137 us
= u
'\xfc' # bytes(us) fails; str(us) fails in Python 2
140 self
.assertEqual('Exception: spam',
141 str(ErrorString(Exception('spam'))))
142 self
.assertEqual('IndexError: '+str(self
.bs
),
143 str(ErrorString(IndexError(self
.bs
))))
144 self
.assertEqual('ImportError: %s' % SafeString(self
.us
),
145 str(ErrorString(ImportError(self
.us
))))
147 def test_unicode(self
):
148 self
.assertEqual(u
'Exception: spam',
149 unicode(ErrorString(Exception(u
'spam'))))
150 self
.assertEqual(u
'IndexError: '+self
.us
,
151 unicode(ErrorString(IndexError(self
.us
))))
152 self
.assertEqual(u
'ImportError: %s' % SafeString(self
.bs
),
153 unicode(ErrorString(ImportError(self
.bs
))))
159 # Stub: Buffer with 'strict' auto-conversion of input to byte string:
161 def write(self
, data
):
162 if isinstance(data
, unicode):
163 data
.encode('ascii', 'strict')
164 super(BBuf
, self
).write(data
)
166 # Stub: Buffer expecting unicode string:
167 class UBuf(StringIO
):
168 def write(self
, data
):
169 # emulate Python 3 handling of stdout, stderr
170 if isinstance(data
, bytes
):
171 raise TypeError('must be unicode, not bytes')
172 super(UBuf
, self
).write(data
)
174 class ErrorOutputTests(unittest
.TestCase
):
175 def test_defaults(self
):
177 self
.assertEqual(e
.stream
, sys
.stderr
)
180 buf
= BBuf() # buffer storing byte string
181 e
= ErrorOutput(buf
, encoding
='ascii')
182 # write byte-string as-is
184 self
.assertEqual(buf
.getvalue(), b
'b\xfc')
185 # encode unicode data with backslashescape fallback replacement:
187 self
.assertEqual(buf
.getvalue(), b
'b\xfc u\\xfc')
188 # handle Exceptions with Unicode string args
189 # unicode(Exception(u'e\xfc')) # fails in Python < 2.6
190 e
.write(AttributeError(u
' e\xfc'))
191 self
.assertEqual(buf
.getvalue(), b
'b\xfc u\\xfc e\\xfc')
192 # encode with `encoding` attribute
195 self
.assertEqual(buf
.getvalue(), b
'b\xfc u\\xfc e\\xfc u\xc3\xbc')
198 buf
= UBuf() # buffer only accepting unicode string
199 # decode of binary strings
200 e
= ErrorOutput(buf
, encoding
='ascii')
202 self
.assertEqual(buf
.getvalue(), u
'b\ufffd') # use REPLACEMENT CHARACTER
203 # write Unicode string and Exceptions with Unicode args
205 self
.assertEqual(buf
.getvalue(), u
'b\ufffd u\xfc')
206 e
.write(AttributeError(u
' e\xfc'))
207 self
.assertEqual(buf
.getvalue(), u
'b\ufffd u\xfc e\xfc')
208 # decode with `encoding` attribute
209 e
.encoding
= 'latin1'
211 self
.assertEqual(buf
.getvalue(), u
'b\ufffd u\xfc e\xfc b\xfc')
215 class SafeStringTests_locale(unittest
.TestCase
):
217 Test docutils.SafeString with 'problematic' locales.
219 The error message in `EnvironmentError` instances comes from the OS
220 and in some locales (e.g. ru_RU), contains high bit chars.
223 locale
.setlocale(locale
.LC_ALL
, testlocale
)
229 except IOError, e
: # in Python 3 the name for the exception instance
230 bioe
= e
# is local to the except clause
235 except UnicodeEncodeError:
237 open(u
'\xfc'.encode(sys
.getfilesystemencoding(), 'replace'))
248 except UnicodeEncodeError:
250 os
.chdir(u
'\xfc'.encode(sys
.getfilesystemencoding(), 'replace'))
254 wbioe
= SafeString(bioe
)
255 wuioe
= SafeString(uioe
)
256 wbose
= SafeString(bose
)
257 wuose
= SafeString(uose
)
260 locale
.setlocale(locale
.LC_ALL
, oldlocale
)
263 """Test conversion to a unicode-string."""
264 # unicode(bioe) fails with e.g. 'ru_RU.utf8' locale
265 self
.assertEqual(unicode, type(unicode(self
.wbioe
)))
266 self
.assertEqual(unicode, type(unicode(self
.wuioe
)))
267 self
.assertEqual(unicode, type(unicode(self
.wbose
)))
268 self
.assertEqual(unicode, type(unicode(self
.wuose
)))
271 """Test conversion to a string (bytes in Python 2, unicode in Python 3)."""
272 self
.assertEqual(str(self
.bioe
), str(self
.wbioe
))
273 self
.assertEqual(str(self
.uioe
), str(self
.wuioe
))
274 self
.assertEqual(str(self
.bose
), str(self
.wbose
))
275 self
.assertEqual(str(self
.uose
), str(self
.wuose
))
279 class ErrorReportingTests(unittest
.TestCase
):
281 Test cases where error reporting can go wrong.
283 Do not test the exact output (as this varies with the locale), just
284 ensure that the correct exception is thrown.
287 # These tests fail with a 'problematic locale',
288 # Docutils revision < 7035, and Python 2:
290 parser
= parsers
.rst
.Parser()
291 """Parser shared by all ParserTestCases."""
293 option_parser
= frontend
.OptionParser(components
=(parsers
.rst
.Parser
,))
294 settings
= option_parser
.get_default_values()
295 settings
.report_level
= 1
296 settings
.halt_level
= 1
297 settings
.warning_stream
= ''
298 document
= utils
.new_document('test data', settings
)
302 locale
.setlocale(locale
.LC_ALL
, testlocale
)
306 locale
.setlocale(locale
.LC_ALL
, oldlocale
)
308 def test_include(self
):
309 source
= ('.. include:: bogus.txt')
310 self
.assertRaises(utils
.SystemMessage
,
311 self
.parser
.parse
, source
, self
.document
)
313 def test_raw_file(self
):
314 source
= ('.. raw:: html\n'
315 ' :file: bogus.html\n')
316 self
.assertRaises(utils
.SystemMessage
,
317 self
.parser
.parse
, source
, self
.document
)
319 def test_csv_table(self
):
320 source
= ('.. csv-table:: external file\n'
321 ' :file: bogus.csv\n')
322 self
.assertRaises(utils
.SystemMessage
,
323 self
.parser
.parse
, source
, self
.document
)
325 if __name__
== '__main__':