a33c071aaa469eebc7375311a2dfcb1505d5543a
[bbaumbach/samba-autobuild/.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     str_is_unicode,
19     unicode_output_stream,
20     )
21 from testtools.matchers import (
22     MatchesException,
23     Not,
24     Raises,
25     )
26
27
28 class TestDetectEncoding(testtools.TestCase):
29     """Test detection of Python source encodings"""
30
31     def _check_encoding(self, expected, lines, possibly_invalid=False):
32         """Check lines are valid Python and encoding is as expected"""
33         if not possibly_invalid:
34             compile(_b("".join(lines)), "<str>", "exec")
35         encoding = _detect_encoding(lines)
36         self.assertEqual(expected, encoding,
37             "Encoding %r expected but got %r from lines %r" %
38                 (expected, encoding, lines))
39
40     def test_examples_from_pep(self):
41         """Check the examples given in PEP 263 all work as specified
42
43         See 'Examples' section of <http://www.python.org/dev/peps/pep-0263/>
44         """
45         # With interpreter binary and using Emacs style file encoding comment:
46         self._check_encoding("latin-1", (
47             "#!/usr/bin/python\n",
48             "# -*- coding: latin-1 -*-\n",
49             "import os, sys\n"))
50         self._check_encoding("iso-8859-15", (
51             "#!/usr/bin/python\n",
52             "# -*- coding: iso-8859-15 -*-\n",
53             "import os, sys\n"))
54         self._check_encoding("ascii", (
55             "#!/usr/bin/python\n",
56             "# -*- coding: ascii -*-\n",
57             "import os, sys\n"))
58         # Without interpreter line, using plain text:
59         self._check_encoding("utf-8", (
60             "# This Python file uses the following encoding: utf-8\n",
61             "import os, sys\n"))
62         # Text editors might have different ways of defining the file's
63         # encoding, e.g.
64         self._check_encoding("latin-1", (
65             "#!/usr/local/bin/python\n",
66             "# coding: latin-1\n",
67             "import os, sys\n"))
68         # Without encoding comment, Python's parser will assume ASCII text:
69         self._check_encoding("ascii", (
70             "#!/usr/local/bin/python\n",
71             "import os, sys\n"))
72         # Encoding comments which don't work:
73         #   Missing "coding:" prefix:
74         self._check_encoding("ascii", (
75             "#!/usr/local/bin/python\n",
76             "# latin-1\n",
77             "import os, sys\n"))
78         #   Encoding comment not on line 1 or 2:
79         self._check_encoding("ascii", (
80             "#!/usr/local/bin/python\n",
81             "#\n",
82             "# -*- coding: latin-1 -*-\n",
83             "import os, sys\n"))
84         #   Unsupported encoding:
85         self._check_encoding("ascii", (
86             "#!/usr/local/bin/python\n",
87             "# -*- coding: utf-42 -*-\n",
88             "import os, sys\n"),
89             possibly_invalid=True)
90
91     def test_bom(self):
92         """Test the UTF-8 BOM counts as an encoding declaration"""
93         self._check_encoding("utf-8", (
94             "\xef\xbb\xbfimport sys\n",
95             ))
96         self._check_encoding("utf-8", (
97             "\xef\xbb\xbf# File encoding: UTF-8\n",
98             ))
99         self._check_encoding("utf-8", (
100             '\xef\xbb\xbf"""Module docstring\n',
101             '\xef\xbb\xbfThat should just be a ZWNB"""\n'))
102         self._check_encoding("latin-1", (
103             '"""Is this coding: latin-1 or coding: utf-8 instead?\n',
104             '\xef\xbb\xbfThose should be latin-1 bytes"""\n'))
105         self._check_encoding("utf-8", (
106             "\xef\xbb\xbf# Is the coding: utf-8 or coding: euc-jp instead?\n",
107             '"""Module docstring say \xe2\x98\x86"""\n'))
108
109     def test_multiple_coding_comments(self):
110         """Test only the first of multiple coding declarations counts"""
111         self._check_encoding("iso-8859-1", (
112             "# Is the coding: iso-8859-1\n",
113             "# Or is it coding: iso-8859-2\n"),
114             possibly_invalid=True)
115         self._check_encoding("iso-8859-1", (
116             "#!/usr/bin/python\n",
117             "# Is the coding: iso-8859-1\n",
118             "# Or is it coding: iso-8859-2\n"))
119         self._check_encoding("iso-8859-1", (
120             "# Is the coding: iso-8859-1 or coding: iso-8859-2\n",
121             "# Or coding: iso-8859-3 or coding: iso-8859-4\n"),
122             possibly_invalid=True)
123         self._check_encoding("iso-8859-2", (
124             "# Is the coding iso-8859-1 or coding: iso-8859-2\n",
125             "# Spot the missing colon above\n"))
126
127
128 class TestGetSourceEncoding(testtools.TestCase):
129     """Test reading and caching the encodings of source files"""
130
131     def setUp(self):
132         testtools.TestCase.setUp(self)
133         dir = tempfile.mkdtemp()
134         self.addCleanup(os.rmdir, dir)
135         self.filename = os.path.join(dir, self.id().rsplit(".", 1)[1] + ".py")
136         self._written = False
137
138     def put_source(self, text):
139         f = open(self.filename, "w")
140         try:
141             f.write(text)
142         finally:
143             f.close()
144             if not self._written:
145                 self._written = True
146                 self.addCleanup(os.remove, self.filename)
147                 self.addCleanup(linecache.cache.pop, self.filename, None)
148
149     def test_nonexistant_file_as_ascii(self):
150         """When file can't be found, the encoding should default to ascii"""
151         self.assertEquals("ascii", _get_source_encoding(self.filename))
152
153     def test_encoding_is_cached(self):
154         """The encoding should stay the same if the cache isn't invalidated"""
155         self.put_source(
156             "# coding: iso-8859-13\n"
157             "import os\n")
158         self.assertEquals("iso-8859-13", _get_source_encoding(self.filename))
159         self.put_source(
160             "# coding: rot-13\n"
161             "vzcbeg bf\n")
162         self.assertEquals("iso-8859-13", _get_source_encoding(self.filename))
163
164     def test_traceback_rechecks_encoding(self):
165         """A traceback function checks the cache and resets the encoding"""
166         self.put_source(
167             "# coding: iso-8859-8\n"
168             "import os\n")
169         self.assertEquals("iso-8859-8", _get_source_encoding(self.filename))
170         self.put_source(
171             "# coding: utf-8\n"
172             "import os\n")
173         try:
174             exec (compile("raise RuntimeError\n", self.filename, "exec"))
175         except RuntimeError:
176             traceback.extract_tb(sys.exc_info()[2])
177         else:
178             self.fail("RuntimeError not raised")
179         self.assertEquals("utf-8", _get_source_encoding(self.filename))
180
181
182 class _FakeOutputStream(object):
183     """A simple file-like object for testing"""
184
185     def __init__(self):
186         self.writelog = []
187
188     def write(self, obj):
189         self.writelog.append(obj)
190
191
192 class TestUnicodeOutputStream(testtools.TestCase):
193     """Test wrapping output streams so they work with arbitrary unicode"""
194
195     uni = _u("pa\u026a\u03b8\u0259n")
196
197     def setUp(self):
198         super(TestUnicodeOutputStream, self).setUp()
199         if sys.platform == "cli":
200             self.skip("IronPython shouldn't wrap streams to do encoding")
201
202     def test_no_encoding_becomes_ascii(self):
203         """A stream with no encoding attribute gets ascii/replace strings"""
204         sout = _FakeOutputStream()
205         unicode_output_stream(sout).write(self.uni)
206         self.assertEqual([_b("pa???n")], sout.writelog)
207
208     def test_encoding_as_none_becomes_ascii(self):
209         """A stream with encoding value of None gets ascii/replace strings"""
210         sout = _FakeOutputStream()
211         sout.encoding = None
212         unicode_output_stream(sout).write(self.uni)
213         self.assertEqual([_b("pa???n")], sout.writelog)
214
215     def test_bogus_encoding_becomes_ascii(self):
216         """A stream with a bogus encoding gets ascii/replace strings"""
217         sout = _FakeOutputStream()
218         sout.encoding = "bogus"
219         unicode_output_stream(sout).write(self.uni)
220         self.assertEqual([_b("pa???n")], sout.writelog)
221
222     def test_partial_encoding_replace(self):
223         """A string which can be partly encoded correctly should be"""
224         sout = _FakeOutputStream()
225         sout.encoding = "iso-8859-7"
226         unicode_output_stream(sout).write(self.uni)
227         self.assertEqual([_b("pa?\xe8?n")], sout.writelog)
228
229     @testtools.skipIf(str_is_unicode, "Tests behaviour when str is not unicode")
230     def test_unicode_encodings_wrapped_when_str_is_not_unicode(self):
231         """A unicode encoding is wrapped but needs no error handler"""
232         sout = _FakeOutputStream()
233         sout.encoding = "utf-8"
234         uout = unicode_output_stream(sout)
235         self.assertEqual(uout.errors, "strict")
236         uout.write(self.uni)
237         self.assertEqual([_b("pa\xc9\xaa\xce\xb8\xc9\x99n")], sout.writelog)
238
239     @testtools.skipIf(not str_is_unicode, "Tests behaviour when str is unicode")
240     def test_unicode_encodings_not_wrapped_when_str_is_unicode(self):
241         # No wrapping needed if native str type is unicode
242         sout = _FakeOutputStream()
243         sout.encoding = "utf-8"
244         uout = unicode_output_stream(sout)
245         self.assertIs(uout, sout)
246
247     def test_stringio(self):
248         """A StringIO object should maybe get an ascii native str type"""
249         try:
250             from cStringIO import StringIO
251             newio = False
252         except ImportError:
253             from io import StringIO
254             newio = True
255         sout = StringIO()
256         soutwrapper = unicode_output_stream(sout)
257         if newio:
258             self.expectFailure("Python 3 StringIO expects text not bytes",
259                 self.assertThat, lambda: soutwrapper.write(self.uni),
260                 Not(Raises(MatchesException(TypeError))))
261         soutwrapper.write(self.uni)
262         self.assertEqual("pa???n", sout.getvalue())
263
264
265 def test_suite():
266     from unittest import TestLoader
267     return TestLoader().loadTestsFromName(__name__)