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 as error:
23 print('Found %s' % error)
25 unless the minimal required Python version has this problem fixed.
27 from __future__
import print_function
32 from io
import StringIO
, BytesIO
34 import DocutilsTestSupport
# must be imported before docutils
35 from docutils
import core
, parsers
, frontend
, utils
36 from docutils
.utils
.error_reporting
import SafeString
, ErrorString
, ErrorOutput
40 if sys
.version_info
< (3, 0): # problems solved in py3k
42 import locale
# module missing in Jython
43 oldlocale
= locale
.getlocale()
45 print('cannot test error reporting with problematic locales,\n'
46 '`import locale` failed.')
48 if sys
.version_info
>= (3, 0):
52 # locales confirmed to use non-ASCII chars in the IOError message
53 # for a missing file (https://bugs.gentoo.org/show_bug.cgi?id=349101)
54 # TODO: add more confirmed problematic locales
55 problematic_locales
= ['cs_CZ', 'cs_CZ.UTF8',
56 'el_GR', 'el_GR.UTF-8',
57 # 'fr_FR.UTF-8', # only OSError
59 'ru_RU', 'ru_RU.KOI8-R',
61 '', # default locale: might be non-problematic
64 if oldlocale
is not None:
65 # find a supported problematic locale:
66 for testlocale
in problematic_locales
:
68 locale
.setlocale(locale
.LC_ALL
, testlocale
)
73 locale
.setlocale(locale
.LC_ALL
, oldlocale
) # reset
77 class SafeStringTests(unittest
.TestCase
):
78 # the error message in EnvironmentError instances comes from the OS
79 # and in some locales (e.g. ru_RU), contains high bit chars.
80 # -> see the test in test_error_reporting.py
83 bs
= b
'\xfc' # unicode(bs) fails, str(bs) in Python 3 return repr()
84 us
= u
'\xfc' # bytes(us) fails; str(us) fails in Python 2
85 be
= Exception(bs
) # unicode(be) fails
86 ue
= Exception(us
) # bytes(ue) fails, str(ue) fails in Python 2;
94 # wrapping (not required with 7-bit chars) must not change the
95 # result of conversions:
100 self
.assertEqual(str(42), str(SafeString(42)))
101 self
.assertEqual(str(bs7
), str(SafeString(bs7
)))
102 self
.assertEqual(str(us7
), str(SafeString(us7
)))
103 self
.assertEqual(str(be7
), str(SafeString(be7
)))
104 self
.assertEqual(str(ue7
), str(SafeString(ue7
)))
105 self
.assertEqual(unicode(7), unicode(SafeString(7)))
106 self
.assertEqual(unicode(bs7
), unicode(SafeString(bs7
)))
107 self
.assertEqual(unicode(us7
), unicode(SafeString(us7
)))
108 self
.assertEqual(unicode(be7
), unicode(SafeString(be7
)))
109 self
.assertEqual(unicode(ue7
), unicode(SafeString(ue7
)))
112 """Test conversion to a unicode-string."""
113 # unicode(self.bs) fails
114 self
.assertEqual(unicode, type(unicode(self
.wbs
)))
115 self
.assertEqual(unicode(self
.us
), unicode(self
.wus
))
116 # unicode(self.be) fails
117 self
.assertEqual(unicode, type(unicode(self
.wbe
)))
118 self
.assertEqual(unicode, type(unicode(self
.ue
)))
119 self
.assertEqual(unicode, type(unicode(self
.wue
)))
120 self
.assertEqual(self
.us
, unicode(self
.wue
))
123 """Test conversion to a string (bytes in Python 2, unicode in Python 3)."""
124 self
.assertEqual(str(self
.bs
), str(self
.wbs
))
125 self
.assertEqual(str(self
.be
), str(self
.be
))
126 # str(us) fails in Python 2
127 self
.assertEqual(str, type(str(self
.wus
)))
128 # str(ue) fails in Python 2
129 self
.assertEqual(str, type(str(self
.wue
)))
132 class ErrorStringTests(unittest
.TestCase
):
133 bs
= b
'\xfc' # unicode(bs) fails, str(bs) in Python 3 return repr()
134 us
= u
'\xfc' # bytes(us) fails; str(us) fails in Python 2
137 self
.assertEqual('Exception: spam',
138 str(ErrorString(Exception('spam'))))
139 self
.assertEqual('IndexError: '+str(self
.bs
),
140 str(ErrorString(IndexError(self
.bs
))))
141 self
.assertEqual('ImportError: %s' % SafeString(self
.us
),
142 str(ErrorString(ImportError(self
.us
))))
144 def test_unicode(self
):
145 self
.assertEqual(u
'Exception: spam',
146 unicode(ErrorString(Exception(u
'spam'))))
147 self
.assertEqual(u
'IndexError: '+self
.us
,
148 unicode(ErrorString(IndexError(self
.us
))))
149 self
.assertEqual(u
'ImportError: %s' % SafeString(self
.bs
),
150 unicode(ErrorString(ImportError(self
.bs
))))
156 # Stub: Buffer with 'strict' auto-conversion of input to byte string:
158 def write(self
, data
):
159 if isinstance(data
, unicode):
160 data
.encode('ascii', 'strict')
161 super(BBuf
, self
).write(data
)
163 # Stub: Buffer expecting unicode string:
164 class UBuf(StringIO
):
165 def write(self
, data
):
166 # emulate Python 3 handling of stdout, stderr
167 if isinstance(data
, bytes
):
168 raise TypeError('must be unicode, not bytes')
169 super(UBuf
, self
).write(data
)
171 class ErrorOutputTests(unittest
.TestCase
):
172 def test_defaults(self
):
174 self
.assertEqual(e
.stream
, sys
.stderr
)
177 buf
= BBuf() # buffer storing byte string
178 e
= ErrorOutput(buf
, encoding
='ascii')
179 # write byte-string as-is
181 self
.assertEqual(buf
.getvalue(), b
'b\xfc')
182 # encode unicode data with backslashescape fallback replacement:
184 self
.assertEqual(buf
.getvalue(), b
'b\xfc u\\xfc')
185 # handle Exceptions with Unicode string args
186 # unicode(Exception(u'e\xfc')) # fails in Python < 2.6
187 e
.write(AttributeError(u
' e\xfc'))
188 self
.assertEqual(buf
.getvalue(), b
'b\xfc u\\xfc e\\xfc')
189 # encode with `encoding` attribute
192 self
.assertEqual(buf
.getvalue(), b
'b\xfc u\\xfc e\\xfc u\xc3\xbc')
195 buf
= UBuf() # buffer only accepting unicode string
196 # decode of binary strings
197 e
= ErrorOutput(buf
, encoding
='ascii')
199 self
.assertEqual(buf
.getvalue(), u
'b\ufffd') # use REPLACEMENT CHARACTER
200 # write Unicode string and Exceptions with Unicode args
202 self
.assertEqual(buf
.getvalue(), u
'b\ufffd u\xfc')
203 e
.write(AttributeError(u
' e\xfc'))
204 self
.assertEqual(buf
.getvalue(), u
'b\ufffd u\xfc e\xfc')
205 # decode with `encoding` attribute
206 e
.encoding
= 'latin1'
208 self
.assertEqual(buf
.getvalue(), u
'b\ufffd u\xfc e\xfc b\xfc')
212 class SafeStringTests_locale(unittest
.TestCase
):
214 Test docutils.SafeString with 'problematic' locales.
216 The error message in `EnvironmentError` instances comes from the OS
217 and in some locales (e.g. ru_RU), contains high bit chars.
220 locale
.setlocale(locale
.LC_ALL
, testlocale
)
226 except IOError as e
: # in Python 3 the name for the exception instance
227 bioe
= e
# is local to the except clause
232 except UnicodeEncodeError:
234 open(u
'\xfc'.encode(sys
.getfilesystemencoding(), 'replace'))
245 except UnicodeEncodeError:
247 os
.chdir(u
'\xfc'.encode(sys
.getfilesystemencoding(), 'replace'))
251 wbioe
= SafeString(bioe
)
252 wuioe
= SafeString(uioe
)
253 wbose
= SafeString(bose
)
254 wuose
= SafeString(uose
)
257 locale
.setlocale(locale
.LC_ALL
, oldlocale
)
260 """Test conversion to a unicode-string."""
261 # unicode(bioe) fails with e.g. 'ru_RU.utf8' locale
262 self
.assertEqual(unicode, type(unicode(self
.wbioe
)))
263 self
.assertEqual(unicode, type(unicode(self
.wuioe
)))
264 self
.assertEqual(unicode, type(unicode(self
.wbose
)))
265 self
.assertEqual(unicode, type(unicode(self
.wuose
)))
268 """Test conversion to a string (bytes in Python 2, unicode in Python 3)."""
269 self
.assertEqual(str(self
.bioe
), str(self
.wbioe
))
270 self
.assertEqual(str(self
.uioe
), str(self
.wuioe
))
271 self
.assertEqual(str(self
.bose
), str(self
.wbose
))
272 self
.assertEqual(str(self
.uose
), str(self
.wuose
))
276 class ErrorReportingTests(unittest
.TestCase
):
278 Test cases where error reporting can go wrong.
280 Do not test the exact output (as this varies with the locale), just
281 ensure that the correct exception is thrown.
284 # These tests fail with a 'problematic locale',
285 # Docutils revision < 7035, and Python 2:
287 parser
= parsers
.rst
.Parser()
288 """Parser shared by all ParserTestCases."""
290 option_parser
= frontend
.OptionParser(components
=(parsers
.rst
.Parser
,))
291 settings
= option_parser
.get_default_values()
292 settings
.report_level
= 1
293 settings
.halt_level
= 1
294 settings
.warning_stream
= ''
295 document
= utils
.new_document('test data', settings
)
299 locale
.setlocale(locale
.LC_ALL
, testlocale
)
303 locale
.setlocale(locale
.LC_ALL
, oldlocale
)
305 def test_include(self
):
306 source
= ('.. include:: bogus.txt')
307 self
.assertRaises(utils
.SystemMessage
,
308 self
.parser
.parse
, source
, self
.document
)
310 def test_raw_file(self
):
311 source
= ('.. raw:: html\n'
312 ' :file: bogus.html\n')
313 self
.assertRaises(utils
.SystemMessage
,
314 self
.parser
.parse
, source
, self
.document
)
316 def test_csv_table(self
):
317 source
= ('.. csv-table:: external file\n'
318 ' :file: bogus.csv\n')
319 self
.assertRaises(utils
.SystemMessage
,
320 self
.parser
.parse
, source
, self
.document
)
322 if __name__
== '__main__':