Consistently use "utf-8" (not "utf8") in magic comment giving source encoding.
[docutils.git] / test / test_error_reporting.py
blobd5c2234511227145dd7d918eb0cd7865ac3383fa
1 #! /usr/bin/env python
2 # .. coding: utf-8
3 # $Id$
4 # Author: Günter Milde <milde@users.sourceforge.net>
5 # Copyright: This module has been placed in the public domain.
7 """
8 Test `EnvironmentError` reporting.
10 In some locales, the `errstr` argument of IOError and OSError contains
11 non-ASCII chars.
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., ::
20 try:
21 something
22 except IOError, error:
23 print 'Found %s' % error
25 unless the minimal required Python version has this problem fixed.
26 """
28 import unittest
29 import sys, os
30 import codecs
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
35 BytesIO = 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
40 from docutils._compat import b, bytes
42 oldlocale = None
43 if sys.version_info < (3,0): # problems solved in py3k
44 try:
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
49 except ImportError:
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
60 'ja_JP.UTF-8',
61 'ru_RU', 'ru_RU.KOI8-R',
62 'ru_RU.UTF-8',
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:
69 try:
70 locale.setlocale(locale.LC_ALL, testlocale)
71 except locale.Error:
72 testlocale = None
73 else:
74 break
75 locale.setlocale(locale.LC_ALL, oldlocale) # reset
76 else:
77 testlocale = None
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
84 # test data:
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
91 # wrapped test data:
92 wbs = SafeString(bs)
93 wus = SafeString(us)
94 wbe = SafeString(be)
95 wue = SafeString(ue)
97 def test_7bit(self):
98 # wrapping (not required with 7-bit chars) must not change the
99 # result of conversions:
100 bs7 = b('foo')
101 us7 = u'foo'
102 be7 = Exception(bs7)
103 ue7 = Exception(us7)
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)))
115 def test_ustr(self):
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))
126 def test_str(self):
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
140 def test_str(self):
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))))
157 # ErrorOutput tests
158 # -----------------
160 # Stub: Buffer with 'strict' auto-conversion of input to byte string:
161 class BBuf(BytesIO, object): # super class object required by Python <= 2.5
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): # super class object required by Python <= 2.5
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):
177 e = ErrorOutput()
178 self.assertEqual(e.stream, sys.stderr)
180 def test_bbuf(self):
181 buf = BBuf() # buffer storing byte string
182 e = ErrorOutput(buf, encoding='ascii')
183 # write byte-string as-is
184 e.write(b('b\xfc'))
185 self.assertEqual(buf.getvalue(), b('b\xfc'))
186 # encode unicode data with backslashescape fallback replacement:
187 e.write(u' u\xfc')
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
194 e.encoding = 'utf8'
195 e.write(u' u\xfc')
196 self.assertEqual(buf.getvalue(), b('b\xfc u\\xfc e\\xfc u\xc3\xbc'))
198 def test_ubuf(self):
199 buf = UBuf() # buffer only accepting unicode string
200 # decode of binary strings
201 e = ErrorOutput(buf, encoding='ascii')
202 e.write(b('b\xfc'))
203 self.assertEqual(buf.getvalue(), u'b\ufffd') # use REPLACEMENT CHARACTER
204 # write Unicode string and Exceptions with Unicode args
205 e.write(u' u\xfc')
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'
211 e.write(b(' b\xfc'))
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.
223 if testlocale:
224 locale.setlocale(locale.LC_ALL, testlocale)
225 # test data:
226 bs = b('\xfc')
227 us = u'\xfc'
228 try:
229 open(b('\xfc'))
230 except IOError, e: # in Python 3 the name for the exception instance
231 bioe = e # is local to the except clause
232 try:
233 open(u'\xfc')
234 except IOError, e:
235 uioe = e
236 except UnicodeEncodeError:
237 try:
238 open(u'\xfc'.encode(sys.getfilesystemencoding(), 'replace'))
239 except IOError, e:
240 uioe = e
241 try:
242 os.chdir(b('\xfc'))
243 except OSError, e:
244 bose = e
245 try:
246 os.chdir(u'\xfc')
247 except OSError, e:
248 uose = e
249 except UnicodeEncodeError:
250 try:
251 os.chdir(u'\xfc'.encode(sys.getfilesystemencoding(), 'replace'))
252 except OSError, e:
253 uose = e
254 # wrapped test data:
255 wbioe = SafeString(bioe)
256 wuioe = SafeString(uioe)
257 wbose = SafeString(bose)
258 wuose = SafeString(uose)
259 # reset locale
260 if testlocale:
261 locale.setlocale(locale.LC_ALL, oldlocale)
263 def test_ustr(self):
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)))
271 def test_str(self):
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)
301 def setUp(self):
302 if testlocale:
303 locale.setlocale(locale.LC_ALL, testlocale)
305 def tearDown(self):
306 if 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__':
339 unittest.main()