b29dc34f248db4b3e8252a7d43ebe8c2a5abef14
[obnox/samba/samba-obnox.git] / lib / testtools / testtools / tests / test_compat.py
1 # Copyright (c) 2010 testtools developers. See LICENSE for details.
2
3 """Tests for miscellaneous compatibility functions"""
4
5 import linecache
6 import os
7 import sys
8 import tempfile
9 import traceback
10
11 import testtools
12
13 from testtools.compat import (
14     _b,
15     _detect_encoding,
16     _get_source_encoding,
17     _u,
18     reraise,
19     str_is_unicode,
20     text_repr,
21     unicode_output_stream,
22     )
23 from testtools.matchers import (
24     MatchesException,
25     Not,
26     Raises,
27     )
28
29
30 class TestDetectEncoding(testtools.TestCase):
31     """Test detection of Python source encodings"""
32
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))
41
42     def test_examples_from_pep(self):
43         """Check the examples given in PEP 263 all work as specified
44
45         See 'Examples' section of <http://www.python.org/dev/peps/pep-0263/>
46         """
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",
51             "import os, sys\n"))
52         self._check_encoding("iso-8859-15", (
53             "#!/usr/bin/python\n",
54             "# -*- coding: iso-8859-15 -*-\n",
55             "import os, sys\n"))
56         self._check_encoding("ascii", (
57             "#!/usr/bin/python\n",
58             "# -*- coding: ascii -*-\n",
59             "import os, sys\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",
63             "import os, sys\n"))
64         # Text editors might have different ways of defining the file's
65         # encoding, e.g.
66         self._check_encoding("latin-1", (
67             "#!/usr/local/bin/python\n",
68             "# coding: latin-1\n",
69             "import os, sys\n"))
70         # Without encoding comment, Python's parser will assume ASCII text:
71         self._check_encoding("ascii", (
72             "#!/usr/local/bin/python\n",
73             "import os, sys\n"))
74         # Encoding comments which don't work:
75         #   Missing "coding:" prefix:
76         self._check_encoding("ascii", (
77             "#!/usr/local/bin/python\n",
78             "# latin-1\n",
79             "import os, sys\n"))
80         #   Encoding comment not on line 1 or 2:
81         self._check_encoding("ascii", (
82             "#!/usr/local/bin/python\n",
83             "#\n",
84             "# -*- coding: latin-1 -*-\n",
85             "import os, sys\n"))
86         #   Unsupported encoding:
87         self._check_encoding("ascii", (
88             "#!/usr/local/bin/python\n",
89             "# -*- coding: utf-42 -*-\n",
90             "import os, sys\n"),
91             possibly_invalid=True)
92
93     def test_bom(self):
94         """Test the UTF-8 BOM counts as an encoding declaration"""
95         self._check_encoding("utf-8", (
96             "\xef\xbb\xbfimport sys\n",
97             ))
98         self._check_encoding("utf-8", (
99             "\xef\xbb\xbf# File encoding: utf-8\n",
100             ))
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'))
110
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"))
128
129
130 class TestGetSourceEncoding(testtools.TestCase):
131     """Test reading and caching the encodings of source files"""
132
133     def setUp(self):
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
139
140     def put_source(self, text):
141         f = open(self.filename, "w")
142         try:
143             f.write(text)
144         finally:
145             f.close()
146             if not self._written:
147                 self._written = True
148                 self.addCleanup(os.remove, self.filename)
149                 self.addCleanup(linecache.cache.pop, self.filename, None)
150
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))
154
155     def test_encoding_is_cached(self):
156         """The encoding should stay the same if the cache isn't invalidated"""
157         self.put_source(
158             "# coding: iso-8859-13\n"
159             "import os\n")
160         self.assertEquals("iso-8859-13", _get_source_encoding(self.filename))
161         self.put_source(
162             "# coding: rot-13\n"
163             "vzcbeg bf\n")
164         self.assertEquals("iso-8859-13", _get_source_encoding(self.filename))
165
166     def test_traceback_rechecks_encoding(self):
167         """A traceback function checks the cache and resets the encoding"""
168         self.put_source(
169             "# coding: iso-8859-8\n"
170             "import os\n")
171         self.assertEquals("iso-8859-8", _get_source_encoding(self.filename))
172         self.put_source(
173             "# coding: utf-8\n"
174             "import os\n")
175         try:
176             exec (compile("raise RuntimeError\n", self.filename, "exec"))
177         except RuntimeError:
178             traceback.extract_tb(sys.exc_info()[2])
179         else:
180             self.fail("RuntimeError not raised")
181         self.assertEquals("utf-8", _get_source_encoding(self.filename))
182
183
184 class _FakeOutputStream(object):
185     """A simple file-like object for testing"""
186
187     def __init__(self):
188         self.writelog = []
189
190     def write(self, obj):
191         self.writelog.append(obj)
192
193
194 class TestUnicodeOutputStream(testtools.TestCase):
195     """Test wrapping output streams so they work with arbitrary unicode"""
196
197     uni = _u("pa\u026a\u03b8\u0259n")
198
199     def setUp(self):
200         super(TestUnicodeOutputStream, self).setUp()
201         if sys.platform == "cli":
202             self.skip("IronPython shouldn't wrap streams to do encoding")
203
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)
209
210     def test_encoding_as_none_becomes_ascii(self):
211         """A stream with encoding value of None gets ascii/replace strings"""
212         sout = _FakeOutputStream()
213         sout.encoding = None
214         unicode_output_stream(sout).write(self.uni)
215         self.assertEqual([_b("pa???n")], sout.writelog)
216
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)
223
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)
230
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")
238         uout.write(self.uni)
239         self.assertEqual([_b("pa\xc9\xaa\xce\xb8\xc9\x99n")], sout.writelog)
240
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)
248
249     def test_stringio(self):
250         """A StringIO object should maybe get an ascii native str type"""
251         try:
252             from cStringIO import StringIO
253             newio = False
254         except ImportError:
255             from io import StringIO
256             newio = True
257         sout = StringIO()
258         soutwrapper = unicode_output_stream(sout)
259         if newio:
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())
265
266
267 class TestTextRepr(testtools.TestCase):
268     """Ensure in extending repr, basic behaviours are not being broken"""
269
270     ascii_examples = (
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'''"),
284
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\\\\\\'\\'\\''''"),
295         )
296
297     # Bytes with the high bit set should always be escaped
298     bytes_examples = (
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'''"),
304         )
305
306     # Unicode doesn't escape printable characters as per the Python 3 model
307     unicode_examples = (
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
324         )
325
326     b_prefix = repr(_b(""))[:-2]
327     u_prefix = repr(_u(""))[:-2]
328
329     def test_ascii_examples_oneline_bytes(self):
330         for s, expected, _ in self.ascii_examples:
331             b = _b(s)
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)
336
337     def test_ascii_examples_oneline_unicode(self):
338         for s, expected, _ in self.ascii_examples:
339             u = _u(s)
340             actual = text_repr(u, multiline=False)
341             self.assertEqual(actual, self.u_prefix + expected)
342             self.assertEqual(eval(actual), u)
343
344     def test_ascii_examples_multiline_bytes(self):
345         for s, _, expected in self.ascii_examples:
346             b = _b(s)
347             actual = text_repr(b, multiline=True)
348             self.assertEqual(actual, self.b_prefix + expected)
349             self.assertEqual(eval(actual), b)
350
351     def test_ascii_examples_multiline_unicode(self):
352         for s, _, expected in self.ascii_examples:
353             u = _u(s)
354             actual = text_repr(u, multiline=True)
355             self.assertEqual(actual, self.u_prefix + expected)
356             self.assertEqual(eval(actual), u)
357
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)
362
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)
367
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)
373
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)
379
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)
385
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)
391
392
393
394 class TestReraise(testtools.TestCase):
395     """Tests for trivial reraise wrapper needed for Python 2/3 changes"""
396
397     def test_exc_info(self):
398         """After reraise exc_info matches plus some extra traceback"""
399         try:
400             raise ValueError("Bad value")
401         except ValueError:
402             _exc_info = sys.exc_info()
403         try:
404             reraise(*_exc_info)
405         except ValueError:
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):])
412
413     def test_custom_exception_no_args(self):
414         """Reraising does not require args attribute to contain params"""
415
416         class CustomException(Exception):
417             """Exception that expects and sets attrs but not args"""
418
419             def __init__(self, value):
420                 Exception.__init__(self)
421                 self.value = value
422
423         try:
424             raise CustomException("Some value")
425         except CustomException:
426             _exc_info = sys.exc_info()
427         self.assertRaises(CustomException, reraise, *_exc_info)
428
429
430 def test_suite():
431     from unittest import TestLoader
432     return TestLoader().loadTestsFromName(__name__)