1 # Copyright (c) 2010 testtools developers. See LICENSE for details.
3 """Tests for miscellaneous compatibility functions"""
13 from testtools.compat import (
21 unicode_output_stream,
23 from testtools.matchers import (
30 class TestDetectEncoding(testtools.TestCase):
31 """Test detection of Python source encodings"""
33 def _check_encoding(self, expected, lines, possibly_invalid=False):
34 """Check lines are valid Python and encoding is as expected"""
35 if not possibly_invalid:
36 compile(_b("".join(lines)), "<str>", "exec")
37 encoding = _detect_encoding(lines)
38 self.assertEqual(expected, encoding,
39 "Encoding %r expected but got %r from lines %r" %
40 (expected, encoding, lines))
42 def test_examples_from_pep(self):
43 """Check the examples given in PEP 263 all work as specified
45 See 'Examples' section of <http://www.python.org/dev/peps/pep-0263/>
47 # With interpreter binary and using Emacs style file encoding comment:
48 self._check_encoding("latin-1", (
49 "#!/usr/bin/python\n",
50 "# -*- coding: latin-1 -*-\n",
52 self._check_encoding("iso-8859-15", (
53 "#!/usr/bin/python\n",
54 "# -*- coding: iso-8859-15 -*-\n",
56 self._check_encoding("ascii", (
57 "#!/usr/bin/python\n",
58 "# -*- coding: ascii -*-\n",
60 # Without interpreter line, using plain text:
61 self._check_encoding("utf-8", (
62 "# This Python file uses the following encoding: utf-8\n",
64 # Text editors might have different ways of defining the file's
66 self._check_encoding("latin-1", (
67 "#!/usr/local/bin/python\n",
68 "# coding: latin-1\n",
70 # Without encoding comment, Python's parser will assume ASCII text:
71 self._check_encoding("ascii", (
72 "#!/usr/local/bin/python\n",
74 # Encoding comments which don't work:
75 # Missing "coding:" prefix:
76 self._check_encoding("ascii", (
77 "#!/usr/local/bin/python\n",
80 # Encoding comment not on line 1 or 2:
81 self._check_encoding("ascii", (
82 "#!/usr/local/bin/python\n",
84 "# -*- coding: latin-1 -*-\n",
86 # Unsupported encoding:
87 self._check_encoding("ascii", (
88 "#!/usr/local/bin/python\n",
89 "# -*- coding: utf-42 -*-\n",
91 possibly_invalid=True)
94 """Test the UTF-8 BOM counts as an encoding declaration"""
95 self._check_encoding("utf-8", (
96 "\xef\xbb\xbfimport sys\n",
98 self._check_encoding("utf-8", (
99 "\xef\xbb\xbf# File encoding: utf-8\n",
101 self._check_encoding("utf-8", (
102 '\xef\xbb\xbf"""Module docstring\n',
103 '\xef\xbb\xbfThat should just be a ZWNB"""\n'))
104 self._check_encoding("latin-1", (
105 '"""Is this coding: latin-1 or coding: utf-8 instead?\n',
106 '\xef\xbb\xbfThose should be latin-1 bytes"""\n'))
107 self._check_encoding("utf-8", (
108 "\xef\xbb\xbf# Is the coding: utf-8 or coding: euc-jp instead?\n",
109 '"""Module docstring say \xe2\x98\x86"""\n'))
111 def test_multiple_coding_comments(self):
112 """Test only the first of multiple coding declarations counts"""
113 self._check_encoding("iso-8859-1", (
114 "# Is the coding: iso-8859-1\n",
115 "# Or is it coding: iso-8859-2\n"),
116 possibly_invalid=True)
117 self._check_encoding("iso-8859-1", (
118 "#!/usr/bin/python\n",
119 "# Is the coding: iso-8859-1\n",
120 "# Or is it coding: iso-8859-2\n"))
121 self._check_encoding("iso-8859-1", (
122 "# Is the coding: iso-8859-1 or coding: iso-8859-2\n",
123 "# Or coding: iso-8859-3 or coding: iso-8859-4\n"),
124 possibly_invalid=True)
125 self._check_encoding("iso-8859-2", (
126 "# Is the coding iso-8859-1 or coding: iso-8859-2\n",
127 "# Spot the missing colon above\n"))
130 class TestGetSourceEncoding(testtools.TestCase):
131 """Test reading and caching the encodings of source files"""
134 testtools.TestCase.setUp(self)
135 dir = tempfile.mkdtemp()
136 self.addCleanup(os.rmdir, dir)
137 self.filename = os.path.join(dir, self.id().rsplit(".", 1)[1] + ".py")
138 self._written = False
140 def put_source(self, text):
141 f = open(self.filename, "w")
146 if not self._written:
148 self.addCleanup(os.remove, self.filename)
149 self.addCleanup(linecache.cache.pop, self.filename, None)
151 def test_nonexistant_file_as_ascii(self):
152 """When file can't be found, the encoding should default to ascii"""
153 self.assertEquals("ascii", _get_source_encoding(self.filename))
155 def test_encoding_is_cached(self):
156 """The encoding should stay the same if the cache isn't invalidated"""
158 "# coding: iso-8859-13\n"
160 self.assertEquals("iso-8859-13", _get_source_encoding(self.filename))
164 self.assertEquals("iso-8859-13", _get_source_encoding(self.filename))
166 def test_traceback_rechecks_encoding(self):
167 """A traceback function checks the cache and resets the encoding"""
169 "# coding: iso-8859-8\n"
171 self.assertEquals("iso-8859-8", _get_source_encoding(self.filename))
176 exec (compile("raise RuntimeError\n", self.filename, "exec"))
178 traceback.extract_tb(sys.exc_info()[2])
180 self.fail("RuntimeError not raised")
181 self.assertEquals("utf-8", _get_source_encoding(self.filename))
184 class _FakeOutputStream(object):
185 """A simple file-like object for testing"""
190 def write(self, obj):
191 self.writelog.append(obj)
194 class TestUnicodeOutputStream(testtools.TestCase):
195 """Test wrapping output streams so they work with arbitrary unicode"""
197 uni = _u("pa\u026a\u03b8\u0259n")
200 super(TestUnicodeOutputStream, self).setUp()
201 if sys.platform == "cli":
202 self.skip("IronPython shouldn't wrap streams to do encoding")
204 def test_no_encoding_becomes_ascii(self):
205 """A stream with no encoding attribute gets ascii/replace strings"""
206 sout = _FakeOutputStream()
207 unicode_output_stream(sout).write(self.uni)
208 self.assertEqual([_b("pa???n")], sout.writelog)
210 def test_encoding_as_none_becomes_ascii(self):
211 """A stream with encoding value of None gets ascii/replace strings"""
212 sout = _FakeOutputStream()
214 unicode_output_stream(sout).write(self.uni)
215 self.assertEqual([_b("pa???n")], sout.writelog)
217 def test_bogus_encoding_becomes_ascii(self):
218 """A stream with a bogus encoding gets ascii/replace strings"""
219 sout = _FakeOutputStream()
220 sout.encoding = "bogus"
221 unicode_output_stream(sout).write(self.uni)
222 self.assertEqual([_b("pa???n")], sout.writelog)
224 def test_partial_encoding_replace(self):
225 """A string which can be partly encoded correctly should be"""
226 sout = _FakeOutputStream()
227 sout.encoding = "iso-8859-7"
228 unicode_output_stream(sout).write(self.uni)
229 self.assertEqual([_b("pa?\xe8?n")], sout.writelog)
231 @testtools.skipIf(str_is_unicode, "Tests behaviour when str is not unicode")
232 def test_unicode_encodings_wrapped_when_str_is_not_unicode(self):
233 """A unicode encoding is wrapped but needs no error handler"""
234 sout = _FakeOutputStream()
235 sout.encoding = "utf-8"
236 uout = unicode_output_stream(sout)
237 self.assertEqual(uout.errors, "strict")
239 self.assertEqual([_b("pa\xc9\xaa\xce\xb8\xc9\x99n")], sout.writelog)
241 @testtools.skipIf(not str_is_unicode, "Tests behaviour when str is unicode")
242 def test_unicode_encodings_not_wrapped_when_str_is_unicode(self):
243 # No wrapping needed if native str type is unicode
244 sout = _FakeOutputStream()
245 sout.encoding = "utf-8"
246 uout = unicode_output_stream(sout)
247 self.assertIs(uout, sout)
249 def test_stringio(self):
250 """A StringIO object should maybe get an ascii native str type"""
252 from cStringIO import StringIO
255 from io import StringIO
258 soutwrapper = unicode_output_stream(sout)
260 self.expectFailure("Python 3 StringIO expects text not bytes",
261 self.assertThat, lambda: soutwrapper.write(self.uni),
262 Not(Raises(MatchesException(TypeError))))
263 soutwrapper.write(self.uni)
264 self.assertEqual("pa???n", sout.getvalue())
267 class TestTextRepr(testtools.TestCase):
268 """Ensure in extending repr, basic behaviours are not being broken"""
271 # Single character examples
272 # C0 control codes should be escaped except multiline \n
273 ("\x00", "'\\x00'", "'''\\\n\\x00'''"),
274 ("\b", "'\\x08'", "'''\\\n\\x08'''"),
275 ("\t", "'\\t'", "'''\\\n\\t'''"),
276 ("\n", "'\\n'", "'''\\\n\n'''"),
277 ("\r", "'\\r'", "'''\\\n\\r'''"),
278 # Quotes and backslash should match normal repr behaviour
279 ('"', "'\"'", "'''\\\n\"'''"),
280 ("'", "\"'\"", "'''\\\n\\''''"),
281 ("\\", "'\\\\'", "'''\\\n\\\\'''"),
282 # DEL is also unprintable and should be escaped
283 ("\x7F", "'\\x7f'", "'''\\\n\\x7f'''"),
285 # Character combinations that need double checking
286 ("\r\n", "'\\r\\n'", "'''\\\n\\r\n'''"),
287 ("\"'", "'\"\\''", "'''\\\n\"\\''''"),
288 ("'\"", "'\\'\"'", "'''\\\n'\"'''"),
289 ("\\n", "'\\\\n'", "'''\\\n\\\\n'''"),
290 ("\\\n", "'\\\\\\n'", "'''\\\n\\\\\n'''"),
291 ("\\' ", "\"\\\\' \"", "'''\\\n\\\\' '''"),
292 ("\\'\n", "\"\\\\'\\n\"", "'''\\\n\\\\'\n'''"),
293 ("\\'\"", "'\\\\\\'\"'", "'''\\\n\\\\'\"'''"),
294 ("\\'''", "\"\\\\'''\"", "'''\\\n\\\\\\'\\'\\''''"),
297 # Bytes with the high bit set should always be escaped
299 (_b("\x80"), "'\\x80'", "'''\\\n\\x80'''"),
300 (_b("\xA0"), "'\\xa0'", "'''\\\n\\xa0'''"),
301 (_b("\xC0"), "'\\xc0'", "'''\\\n\\xc0'''"),
302 (_b("\xFF"), "'\\xff'", "'''\\\n\\xff'''"),
303 (_b("\xC2\xA7"), "'\\xc2\\xa7'", "'''\\\n\\xc2\\xa7'''"),
306 # Unicode doesn't escape printable characters as per the Python 3 model
308 # C1 codes are unprintable
309 (_u("\x80"), "'\\x80'", "'''\\\n\\x80'''"),
310 (_u("\x9F"), "'\\x9f'", "'''\\\n\\x9f'''"),
311 # No-break space is unprintable
312 (_u("\xA0"), "'\\xa0'", "'''\\\n\\xa0'''"),
313 # Letters latin alphabets are printable
314 (_u("\xA1"), _u("'\xa1'"), _u("'''\\\n\xa1'''")),
315 (_u("\xFF"), _u("'\xff'"), _u("'''\\\n\xff'''")),
316 (_u("\u0100"), _u("'\u0100'"), _u("'''\\\n\u0100'''")),
317 # Line and paragraph seperators are unprintable
318 (_u("\u2028"), "'\\u2028'", "'''\\\n\\u2028'''"),
319 (_u("\u2029"), "'\\u2029'", "'''\\\n\\u2029'''"),
320 # Unpaired surrogates are unprintable
321 (_u("\uD800"), "'\\ud800'", "'''\\\n\\ud800'''"),
322 (_u("\uDFFF"), "'\\udfff'", "'''\\\n\\udfff'''"),
323 # Unprintable general categories not fully tested: Cc, Cf, Co, Cn, Zs
326 b_prefix = repr(_b(""))[:-2]
327 u_prefix = repr(_u(""))[:-2]
329 def test_ascii_examples_oneline_bytes(self):
330 for s, expected, _ in self.ascii_examples:
332 actual = text_repr(b, multiline=False)
333 # Add self.assertIsInstance check?
334 self.assertEqual(actual, self.b_prefix + expected)
335 self.assertEqual(eval(actual), b)
337 def test_ascii_examples_oneline_unicode(self):
338 for s, expected, _ in self.ascii_examples:
340 actual = text_repr(u, multiline=False)
341 self.assertEqual(actual, self.u_prefix + expected)
342 self.assertEqual(eval(actual), u)
344 def test_ascii_examples_multiline_bytes(self):
345 for s, _, expected in self.ascii_examples:
347 actual = text_repr(b, multiline=True)
348 self.assertEqual(actual, self.b_prefix + expected)
349 self.assertEqual(eval(actual), b)
351 def test_ascii_examples_multiline_unicode(self):
352 for s, _, expected in self.ascii_examples:
354 actual = text_repr(u, multiline=True)
355 self.assertEqual(actual, self.u_prefix + expected)
356 self.assertEqual(eval(actual), u)
358 def test_ascii_examples_defaultline_bytes(self):
359 for s, one, multi in self.ascii_examples:
360 expected = "\n" in s and multi or one
361 self.assertEqual(text_repr(_b(s)), self.b_prefix + expected)
363 def test_ascii_examples_defaultline_unicode(self):
364 for s, one, multi in self.ascii_examples:
365 expected = "\n" in s and multi or one
366 self.assertEqual(text_repr(_u(s)), self.u_prefix + expected)
368 def test_bytes_examples_oneline(self):
369 for b, expected, _ in self.bytes_examples:
370 actual = text_repr(b, multiline=False)
371 self.assertEqual(actual, self.b_prefix + expected)
372 self.assertEqual(eval(actual), b)
374 def test_bytes_examples_multiline(self):
375 for b, _, expected in self.bytes_examples:
376 actual = text_repr(b, multiline=True)
377 self.assertEqual(actual, self.b_prefix + expected)
378 self.assertEqual(eval(actual), b)
380 def test_unicode_examples_oneline(self):
381 for u, expected, _ in self.unicode_examples:
382 actual = text_repr(u, multiline=False)
383 self.assertEqual(actual, self.u_prefix + expected)
384 self.assertEqual(eval(actual), u)
386 def test_unicode_examples_multiline(self):
387 for u, _, expected in self.unicode_examples:
388 actual = text_repr(u, multiline=True)
389 self.assertEqual(actual, self.u_prefix + expected)
390 self.assertEqual(eval(actual), u)
394 class TestReraise(testtools.TestCase):
395 """Tests for trivial reraise wrapper needed for Python 2/3 changes"""
397 def test_exc_info(self):
398 """After reraise exc_info matches plus some extra traceback"""
400 raise ValueError("Bad value")
402 _exc_info = sys.exc_info()
406 _new_exc_info = sys.exc_info()
407 self.assertIs(_exc_info[0], _new_exc_info[0])
408 self.assertIs(_exc_info[1], _new_exc_info[1])
409 expected_tb = traceback.extract_tb(_exc_info[2])
410 self.assertEqual(expected_tb,
411 traceback.extract_tb(_new_exc_info[2])[-len(expected_tb):])
413 def test_custom_exception_no_args(self):
414 """Reraising does not require args attribute to contain params"""
416 class CustomException(Exception):
417 """Exception that expects and sets attrs but not args"""
419 def __init__(self, value):
420 Exception.__init__(self)
424 raise CustomException("Some value")
425 except CustomException:
426 _exc_info = sys.exc_info()
427 self.assertRaises(CustomException, reraise, *_exc_info)
431 from unittest import TestLoader
432 return TestLoader().loadTestsFromName(__name__)