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
.error_reporting
import SafeString
, ErrorString
, ErrorOutput
40 from docutils
._compat
import b
, bytes
43 if sys
.version_info
< (3,0): # problems solved in py3k
45 import locale
# module missing in Jython
46 oldlocale
= locale
.getlocale()
47 # Why does getlocale return the defaultlocale in Python 3.2 ????
48 # oldlocale = (None, None) # test suite runs without locale
50 print ('cannot test error reporting with problematic locales,\n'
51 '`import locale` failed.')
54 # locales confirmed to use non-ASCII chars in the IOError message
55 # for a missing file (https://bugs.gentoo.org/show_bug.cgi?id=349101)
56 # TODO: add more confirmed problematic locales
57 problematic_locales
= ['cs_CZ', 'cs_CZ.UTF8',
58 'el_GR', 'el_GR.UTF-8',
59 # 'fr_FR.UTF-8', # only OSError
61 'ru_RU', 'ru_RU.KOI8-R',
63 '', # default locale: might be non-problematic
66 if oldlocale
is not None:
67 # find a supported problematic locale:
68 for testlocale
in problematic_locales
:
70 locale
.setlocale(locale
.LC_ALL
, testlocale
)
75 locale
.setlocale(locale
.LC_ALL
, oldlocale
) # reset
79 class SafeStringTests(unittest
.TestCase
):
80 # the error message in EnvironmentError instances comes from the OS
81 # and in some locales (e.g. ru_RU), contains high bit chars.
82 # -> see the test in test_error_reporting.py
85 bs
= b('\xfc') # unicode(bs) fails, str(bs) in Python 3 return repr()
86 us
= u
'\xfc' # bytes(us) fails; str(us) fails in Python 2
87 be
= Exception(bs
) # unicode(be) fails
88 ue
= Exception(us
) # bytes(ue) fails, str(ue) fails in Python 2;
89 # unicode(ue) fails in Python < 2.6 (issue2517_)
90 # .. _issue2517: http://bugs.python.org/issue2517
98 # wrapping (not required with 7-bit chars) must not change the
99 # result of conversions:
104 self
.assertEqual(str(42), str(SafeString(42)))
105 self
.assertEqual(str(bs7
), str(SafeString(bs7
)))
106 self
.assertEqual(str(us7
), str(SafeString(us7
)))
107 self
.assertEqual(str(be7
), str(SafeString(be7
)))
108 self
.assertEqual(str(ue7
), str(SafeString(ue7
)))
109 self
.assertEqual(unicode(7), unicode(SafeString(7)))
110 self
.assertEqual(unicode(bs7
), unicode(SafeString(bs7
)))
111 self
.assertEqual(unicode(us7
), unicode(SafeString(us7
)))
112 self
.assertEqual(unicode(be7
), unicode(SafeString(be7
)))
113 self
.assertEqual(unicode(ue7
), unicode(SafeString(ue7
)))
116 """Test conversion to a unicode-string."""
117 # unicode(self.bs) fails
118 self
.assertEqual(unicode, type(unicode(self
.wbs
)))
119 self
.assertEqual(unicode(self
.us
), unicode(self
.wus
))
120 # unicode(self.be) fails
121 self
.assertEqual(unicode, type(unicode(self
.wbe
)))
122 # unicode(ue) fails in Python < 2.6 (issue2517_)
123 self
.assertEqual(unicode, type(unicode(self
.wue
)))
124 self
.assertEqual(self
.us
, unicode(self
.wue
))
127 """Test conversion to a string (bytes in Python 2, unicode in Python 3)."""
128 self
.assertEqual(str(self
.bs
), str(self
.wbs
))
129 self
.assertEqual(str(self
.be
), str(self
.be
))
130 # str(us) fails in Python 2
131 self
.assertEqual(str, type(str(self
.wus
)))
132 # str(ue) fails in Python 2
133 self
.assertEqual(str, type(str(self
.wue
)))
136 class ErrorStringTests(unittest
.TestCase
):
137 bs
= b('\xfc') # unicode(bs) fails, str(bs) in Python 3 return repr()
138 us
= u
'\xfc' # bytes(us) fails; str(us) fails in Python 2
141 self
.assertEqual('Exception: spam',
142 str(ErrorString(Exception('spam'))))
143 self
.assertEqual('IndexError: '+str(self
.bs
),
144 str(ErrorString(IndexError(self
.bs
))))
145 self
.assertEqual('ImportError: %s' % SafeString(self
.us
),
146 str(ErrorString(ImportError(self
.us
))))
148 def test_unicode(self
):
149 self
.assertEqual(u
'Exception: spam',
150 unicode(ErrorString(Exception(u
'spam'))))
151 self
.assertEqual(u
'IndexError: '+self
.us
,
152 unicode(ErrorString(IndexError(self
.us
))))
153 self
.assertEqual(u
'ImportError: %s' % SafeString(self
.bs
),
154 unicode(ErrorString(ImportError(self
.bs
))))
160 # Stub: Buffer with 'strict' auto-conversion of input to byte string:
161 class BBuf(BytesIO
, object):
162 def write(self
, data
):
163 if isinstance(data
, unicode):
164 data
.encode('ascii', 'strict')
165 super(BBuf
, self
).write(data
)
167 # Stub: Buffer expecting unicode string:
168 class UBuf(StringIO
, object):
169 def write(self
, data
):
170 # emulate Python 3 handling of stdout, stderr
171 if isinstance(data
, bytes
):
172 raise TypeError('must be unicode, not bytes')
173 super(UBuf
, self
).write(data
)
175 class ErrorOutputTests(unittest
.TestCase
):
176 def test_defaults(self
):
178 self
.assertEqual(e
.stream
, sys
.stderr
)
181 buf
= BBuf() # buffer storing byte string
182 e
= ErrorOutput(buf
, encoding
='ascii')
183 # write byte-string as-is
185 self
.assertEqual(buf
.getvalue(), b('b\xfc'))
186 # encode unicode data with backslashescape fallback replacement:
188 self
.assertEqual(buf
.getvalue(), b('b\xfc u\\xfc'))
189 # handle Exceptions with Unicode string args
190 # unicode(Exception(u'e\xfc')) # fails in Python < 2.6
191 e
.write(AttributeError(u
' e\xfc'))
192 self
.assertEqual(buf
.getvalue(), b('b\xfc u\\xfc e\\xfc'))
193 # encode with `encoding` attribute
196 self
.assertEqual(buf
.getvalue(), b('b\xfc u\\xfc e\\xfc u\xc3\xbc'))
199 buf
= UBuf() # buffer only accepting unicode string
200 # decode of binary strings
201 e
= ErrorOutput(buf
, encoding
='ascii')
203 self
.assertEqual(buf
.getvalue(), u
'b\ufffd') # use REPLACEMENT CHARACTER
204 # write Unicode string and Exceptions with Unicode args
206 self
.assertEqual(buf
.getvalue(), u
'b\ufffd u\xfc')
207 e
.write(AttributeError(u
' e\xfc'))
208 self
.assertEqual(buf
.getvalue(), u
'b\ufffd u\xfc e\xfc')
209 # decode with `encoding` attribute
210 e
.encoding
= 'latin1'
212 self
.assertEqual(buf
.getvalue(), u
'b\ufffd u\xfc e\xfc b\xfc')
216 class SafeStringTests_locale(unittest
.TestCase
):
218 Test docutils.SafeString with 'problematic' locales.
220 The error message in `EnvironmentError` instances comes from the OS
221 and in some locales (e.g. ru_RU), contains high bit chars.
224 locale
.setlocale(locale
.LC_ALL
, testlocale
)
230 except IOError, e
: # in Python 3 the name for the exception instance
231 bioe
= e
# is local to the except clause
236 except UnicodeEncodeError:
238 open(u
'\xfc'.encode(sys
.getfilesystemencoding(), 'replace'))
249 except UnicodeEncodeError:
251 os
.chdir(u
'\xfc'.encode(sys
.getfilesystemencoding(), 'replace'))
255 wbioe
= SafeString(bioe
)
256 wuioe
= SafeString(uioe
)
257 wbose
= SafeString(bose
)
258 wuose
= SafeString(uose
)
261 locale
.setlocale(locale
.LC_ALL
, oldlocale
)
264 """Test conversion to a unicode-string."""
265 # unicode(bioe) fails with e.g. 'ru_RU.utf8' locale
266 self
.assertEqual(unicode, type(unicode(self
.wbioe
)))
267 self
.assertEqual(unicode, type(unicode(self
.wuioe
)))
268 self
.assertEqual(unicode, type(unicode(self
.wbose
)))
269 self
.assertEqual(unicode, type(unicode(self
.wuose
)))
272 """Test conversion to a string (bytes in Python 2, unicode in Python 3)."""
273 self
.assertEqual(str(self
.bioe
), str(self
.wbioe
))
274 self
.assertEqual(str(self
.uioe
), str(self
.wuioe
))
275 self
.assertEqual(str(self
.bose
), str(self
.wbose
))
276 self
.assertEqual(str(self
.uose
), str(self
.wuose
))
280 class ErrorReportingTests(unittest
.TestCase
):
282 Test cases where error reporting can go wrong.
284 Do not test the exact output (as this varies with the locale), just
285 ensure that the correct exception is thrown.
288 # These tests fail with a 'problematic locale' and
289 # (revision < 7035) and Python-2.
291 parser
= parsers
.rst
.Parser()
292 """Parser shared by all ParserTestCases."""
294 option_parser
= frontend
.OptionParser(components
=(parsers
.rst
.Parser
,))
295 settings
= option_parser
.get_default_values()
296 settings
.report_level
= 1
297 settings
.halt_level
= 1
298 settings
.warning_stream
= ''
299 document
= utils
.new_document('test data', settings
)
303 locale
.setlocale(locale
.LC_ALL
, testlocale
)
307 locale
.setlocale(locale
.LC_ALL
, oldlocale
)
309 def test_include(self
):
310 source
= ('.. include:: bogus.txt')
311 self
.assertRaises(utils
.SystemMessage
,
312 self
.parser
.parse
, source
, self
.document
)
314 def test_raw_file(self
):
315 source
= ('.. raw:: html\n'
316 ' :file: bogus.html\n')
317 self
.assertRaises(utils
.SystemMessage
,
318 self
.parser
.parse
, source
, self
.document
)
320 def test_raw_url(self
):
321 source
= ('.. raw:: html\n'
322 ' :url: http://bogus.html\n')
323 self
.assertRaises(utils
.SystemMessage
,
324 self
.parser
.parse
, source
, self
.document
)
326 def test_csv_table(self
):
327 source
= ('.. csv-table:: external file\n'
328 ' :file: bogus.csv\n')
329 self
.assertRaises(utils
.SystemMessage
,
330 self
.parser
.parse
, source
, self
.document
)
332 def test_csv_table_url(self
):
333 source
= ('.. csv-table:: external URL\n'
334 ' :url: ftp://bogus.csv\n')
335 self
.assertRaises(utils
.SystemMessage
,
336 self
.parser
.parse
, source
, self
.document
)
338 if __name__
== '__main__':