Formatting changes to facilitate integration of "py3" patchset.
[docutils.git] / docutils / test / test_error_reporting.py
blob2159ce2915a19509093e25ad403167d96033a016
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 as error:
23 print('Found %s' % error)
25 unless the minimal required Python version has this problem fixed.
26 """
27 from __future__ import print_function
29 import os
30 import sys
31 import unittest
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
39 oldlocale = None
40 if sys.version_info < (3, 0): # problems solved in py3k
41 try:
42 import locale # module missing in Jython
43 oldlocale = locale.getlocale()
44 except ImportError:
45 print('cannot test error reporting with problematic locales,\n'
46 '`import locale` failed.')
48 if sys.version_info >= (3, 0):
49 unicode = str # noqa
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
58 'ja_JP.UTF-8',
59 'ru_RU', 'ru_RU.KOI8-R',
60 'ru_RU.UTF-8',
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:
67 try:
68 locale.setlocale(locale.LC_ALL, testlocale)
69 except locale.Error:
70 testlocale = None
71 else:
72 break
73 locale.setlocale(locale.LC_ALL, oldlocale) # reset
74 else:
75 testlocale = None
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
82 # test data:
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;
87 # wrapped test data:
88 wbs = SafeString(bs)
89 wus = SafeString(us)
90 wbe = SafeString(be)
91 wue = SafeString(ue)
93 def test_7bit(self):
94 # wrapping (not required with 7-bit chars) must not change the
95 # result of conversions:
96 bs7 = b'foo'
97 us7 = u'foo'
98 be7 = Exception(bs7)
99 ue7 = Exception(us7)
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)))
111 def test_ustr(self):
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))
122 def test_str(self):
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
136 def test_str(self):
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))))
153 # ErrorOutput tests
154 # -----------------
156 # Stub: Buffer with 'strict' auto-conversion of input to byte string:
157 class BBuf(BytesIO):
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):
173 e = ErrorOutput()
174 self.assertEqual(e.stream, sys.stderr)
176 def test_bbuf(self):
177 buf = BBuf() # buffer storing byte string
178 e = ErrorOutput(buf, encoding='ascii')
179 # write byte-string as-is
180 e.write(b'b\xfc')
181 self.assertEqual(buf.getvalue(), b'b\xfc')
182 # encode unicode data with backslashescape fallback replacement:
183 e.write(u' u\xfc')
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
190 e.encoding = 'utf8'
191 e.write(u' u\xfc')
192 self.assertEqual(buf.getvalue(), b'b\xfc u\\xfc e\\xfc u\xc3\xbc')
194 def test_ubuf(self):
195 buf = UBuf() # buffer only accepting unicode string
196 # decode of binary strings
197 e = ErrorOutput(buf, encoding='ascii')
198 e.write(b'b\xfc')
199 self.assertEqual(buf.getvalue(), u'b\ufffd') # use REPLACEMENT CHARACTER
200 # write Unicode string and Exceptions with Unicode args
201 e.write(u' u\xfc')
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'
207 e.write(b' b\xfc')
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.
219 if testlocale:
220 locale.setlocale(locale.LC_ALL, testlocale)
221 # test data:
222 bs = b'\xfc'
223 us = u'\xfc'
224 try:
225 open(b'\xfc')
226 except IOError as e: # in Python 3 the name for the exception instance
227 bioe = e # is local to the except clause
228 try:
229 open(u'\xfc')
230 except IOError as e:
231 uioe = e
232 except UnicodeEncodeError:
233 try:
234 open(u'\xfc'.encode(sys.getfilesystemencoding(), 'replace'))
235 except IOError as e:
236 uioe = e
237 try:
238 os.chdir(b'\xfc')
239 except OSError as e:
240 bose = e
241 try:
242 os.chdir(u'\xfc')
243 except OSError as e:
244 uose = e
245 except UnicodeEncodeError:
246 try:
247 os.chdir(u'\xfc'.encode(sys.getfilesystemencoding(), 'replace'))
248 except OSError as e:
249 uose = e
250 # wrapped test data:
251 wbioe = SafeString(bioe)
252 wuioe = SafeString(uioe)
253 wbose = SafeString(bose)
254 wuose = SafeString(uose)
255 # reset locale
256 if testlocale:
257 locale.setlocale(locale.LC_ALL, oldlocale)
259 def test_ustr(self):
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)))
267 def test_str(self):
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)
297 def setUp(self):
298 if testlocale:
299 locale.setlocale(locale.LC_ALL, testlocale)
301 def tearDown(self):
302 if 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__':
323 unittest.main()