1 # Copyright (c) 2010 testtools developers. See LICENSE for details.
3 """Tests for miscellaneous compatibility functions"""
13 from testtools.compat import (
19 unicode_output_stream,
21 from testtools.matchers import (
28 class TestDetectEncoding(testtools.TestCase):
29 """Test detection of Python source encodings"""
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))
40 def test_examples_from_pep(self):
41 """Check the examples given in PEP 263 all work as specified
43 See 'Examples' section of <http://www.python.org/dev/peps/pep-0263/>
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",
50 self._check_encoding("iso-8859-15", (
51 "#!/usr/bin/python\n",
52 "# -*- coding: iso-8859-15 -*-\n",
54 self._check_encoding("ascii", (
55 "#!/usr/bin/python\n",
56 "# -*- coding: ascii -*-\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",
62 # Text editors might have different ways of defining the file's
64 self._check_encoding("latin-1", (
65 "#!/usr/local/bin/python\n",
66 "# coding: latin-1\n",
68 # Without encoding comment, Python's parser will assume ASCII text:
69 self._check_encoding("ascii", (
70 "#!/usr/local/bin/python\n",
72 # Encoding comments which don't work:
73 # Missing "coding:" prefix:
74 self._check_encoding("ascii", (
75 "#!/usr/local/bin/python\n",
78 # Encoding comment not on line 1 or 2:
79 self._check_encoding("ascii", (
80 "#!/usr/local/bin/python\n",
82 "# -*- coding: latin-1 -*-\n",
84 # Unsupported encoding:
85 self._check_encoding("ascii", (
86 "#!/usr/local/bin/python\n",
87 "# -*- coding: utf-42 -*-\n",
89 possibly_invalid=True)
92 """Test the UTF-8 BOM counts as an encoding declaration"""
93 self._check_encoding("utf-8", (
94 "\xef\xbb\xbfimport sys\n",
96 self._check_encoding("utf-8", (
97 "\xef\xbb\xbf# File encoding: UTF-8\n",
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'))
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"))
128 class TestGetSourceEncoding(testtools.TestCase):
129 """Test reading and caching the encodings of source files"""
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
138 def put_source(self, text):
139 f = open(self.filename, "w")
144 if not self._written:
146 self.addCleanup(os.remove, self.filename)
147 self.addCleanup(linecache.cache.pop, self.filename, None)
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))
153 def test_encoding_is_cached(self):
154 """The encoding should stay the same if the cache isn't invalidated"""
156 "# coding: iso-8859-13\n"
158 self.assertEquals("iso-8859-13", _get_source_encoding(self.filename))
162 self.assertEquals("iso-8859-13", _get_source_encoding(self.filename))
164 def test_traceback_rechecks_encoding(self):
165 """A traceback function checks the cache and resets the encoding"""
167 "# coding: iso-8859-8\n"
169 self.assertEquals("iso-8859-8", _get_source_encoding(self.filename))
174 exec (compile("raise RuntimeError\n", self.filename, "exec"))
176 traceback.extract_tb(sys.exc_info()[2])
178 self.fail("RuntimeError not raised")
179 self.assertEquals("utf-8", _get_source_encoding(self.filename))
182 class _FakeOutputStream(object):
183 """A simple file-like object for testing"""
188 def write(self, obj):
189 self.writelog.append(obj)
192 class TestUnicodeOutputStream(testtools.TestCase):
193 """Test wrapping output streams so they work with arbitrary unicode"""
195 uni = _u("pa\u026a\u03b8\u0259n")
198 super(TestUnicodeOutputStream, self).setUp()
199 if sys.platform == "cli":
200 self.skip("IronPython shouldn't wrap streams to do encoding")
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)
208 def test_encoding_as_none_becomes_ascii(self):
209 """A stream with encoding value of None gets ascii/replace strings"""
210 sout = _FakeOutputStream()
212 unicode_output_stream(sout).write(self.uni)
213 self.assertEqual([_b("pa???n")], sout.writelog)
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)
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)
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")
237 self.assertEqual([_b("pa\xc9\xaa\xce\xb8\xc9\x99n")], sout.writelog)
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)
247 def test_stringio(self):
248 """A StringIO object should maybe get an ascii native str type"""
250 from cStringIO import StringIO
253 from io import StringIO
256 soutwrapper = unicode_output_stream(sout)
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())
266 from unittest import TestLoader
267 return TestLoader().loadTestsFromName(__name__)