Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / web / test / test_flatten.py
1
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 import sys
6 import traceback
7
8 from zope.interface import implements
9
10 from twisted.trial.unittest import TestCase
11 from twisted.internet.defer import succeed, gatherResults
12 from twisted.web._stan import Tag
13 from twisted.web._flatten import flattenString
14 from twisted.web.error import UnfilledSlot, UnsupportedType, FlattenerError
15 from twisted.web.template import tags, Comment, CDATA, CharRef, slot
16 from twisted.web.iweb import IRenderable
17 from twisted.web.test._util import FlattenTestCase
18
19
20 class TestSerialization(FlattenTestCase):
21     """
22     Tests for flattening various things.
23     """
24     def test_nestedTags(self):
25         """
26         Test that nested tags flatten correctly.
27         """
28         return self.assertFlattensTo(
29             tags.html(tags.body('42'), hi='there'),
30             '<html hi="there"><body>42</body></html>')
31
32
33     def test_serializeString(self):
34         """
35         Test that strings will be flattened and escaped correctly.
36         """
37         return gatherResults([
38             self.assertFlattensTo('one', 'one'),
39             self.assertFlattensTo('<abc&&>123', '&lt;abc&amp;&amp;&gt;123'),
40         ])
41
42
43     def test_serializeSelfClosingTags(self):
44         """
45         Test that some tags are normally written out as self-closing tags.
46         """
47         return self.assertFlattensTo(tags.img(src='test'), '<img src="test" />')
48
49
50     def test_serializeComment(self):
51         """
52         Test that comments are correctly flattened and escaped.
53         """
54         return self.assertFlattensTo(Comment('foo bar'), '<!--foo bar-->'),
55
56
57     def test_commentEscaping(self):
58         """
59         The data in a L{Comment} is escaped and mangled in the flattened output
60         so that the result is a legal SGML and XML comment.
61
62         SGML comment syntax is complicated and hard to use. This rule is more
63         restrictive, and more compatible:
64
65         Comments start with <!-- and end with --> and never contain -- or >.
66
67         Also by XML syntax, a comment may not end with '-'.
68
69         @see: U{http://www.w3.org/TR/REC-xml/#sec-comments}
70         """
71         def verifyComment(c):
72             self.assertTrue(
73                 c.startswith('<!--'),
74                 "%r does not start with the comment prefix" % (c,))
75             self.assertTrue(
76                 c.endswith('-->'),
77                 "%r does not end with the comment suffix" % (c,))
78             # If it is shorter than 7, then the prefix and suffix overlap
79             # illegally.
80             self.assertTrue(
81                 len(c) >= 7,
82                 "%r is too short to be a legal comment" % (c,))
83             content = c[4:-3]
84             self.assertNotIn('--', content)
85             self.assertNotIn('>', content)
86             if content:
87                 self.assertNotEqual(content[-1], '-')
88
89         results = []
90         for c in [
91             '',
92             'foo---bar',
93             'foo---bar-',
94             'foo>bar',
95             'foo-->bar',
96             '----------------',
97         ]:
98             d = flattenString(None, Comment(c))
99             d.addCallback(verifyComment)
100             results.append(d)
101         return gatherResults(results)
102
103
104     def test_serializeCDATA(self):
105         """
106         Test that CDATA is correctly flattened and escaped.
107         """
108         return gatherResults([
109             self.assertFlattensTo(CDATA('foo bar'), '<![CDATA[foo bar]]>'),
110             self.assertFlattensTo(
111                 CDATA('foo ]]> bar'),
112                 '<![CDATA[foo ]]]]><![CDATA[> bar]]>'),
113         ])
114
115
116     def test_serializeUnicode(self):
117         """
118         Test that unicode is encoded correctly in the appropriate places, and
119         raises an error when it occurs in inappropriate place.
120         """
121         snowman = u'\N{SNOWMAN}'
122         return gatherResults([
123             self.assertFlattensTo(snowman, '\xe2\x98\x83'),
124             self.assertFlattensTo(tags.p(snowman), '<p>\xe2\x98\x83</p>'),
125             self.assertFlattensTo(Comment(snowman), '<!--\xe2\x98\x83-->'),
126             self.assertFlattensTo(CDATA(snowman), '<![CDATA[\xe2\x98\x83]]>'),
127             self.assertFlatteningRaises(
128                 Tag(snowman), UnicodeEncodeError),
129             self.assertFlatteningRaises(
130                 Tag('p', attributes={snowman: ''}), UnicodeEncodeError),
131         ])
132
133
134     def test_serializeCharRef(self):
135         """
136         A character reference is flattened to a string using the I{&#NNNN;}
137         syntax.
138         """
139         ref = CharRef(ord(u"\N{SNOWMAN}"))
140         return self.assertFlattensTo(ref, "&#9731;")
141
142
143     def test_serializeDeferred(self):
144         """
145         Test that a deferred is substituted with the current value in the
146         callback chain when flattened.
147         """
148         return self.assertFlattensTo(succeed('two'), 'two')
149
150
151     def test_serializeSameDeferredTwice(self):
152         """
153         Test that the same deferred can be flattened twice.
154         """
155         d = succeed('three')
156         return gatherResults([
157             self.assertFlattensTo(d, 'three'),
158             self.assertFlattensTo(d, 'three'),
159         ])
160
161
162     def test_serializeIRenderable(self):
163         """
164         Test that flattening respects all of the IRenderable interface.
165         """
166         class FakeElement(object):
167             implements(IRenderable)
168             def render(ign,ored):
169                 return tags.p(
170                     'hello, ',
171                     tags.transparent(render='test'), ' - ',
172                     tags.transparent(render='test'))
173             def lookupRenderMethod(ign, name):
174                 self.assertEqual(name, 'test')
175                 return lambda ign, node: node('world')
176
177         return gatherResults([
178             self.assertFlattensTo(FakeElement(), '<p>hello, world - world</p>'),
179         ])
180
181
182     def test_serializeSlots(self):
183         """
184         Test that flattening a slot will use the slot value from the tag.
185         """
186         t1 = tags.p(slot('test'))
187         t2 = t1.clone()
188         t2.fillSlots(test='hello, world')
189         return gatherResults([
190             self.assertFlatteningRaises(t1, UnfilledSlot),
191             self.assertFlattensTo(t2, '<p>hello, world</p>'),
192         ])
193
194
195     def test_serializeDeferredSlots(self):
196         """
197         Test that a slot with a deferred as its value will be flattened using
198         the value from the deferred.
199         """
200         t = tags.p(slot('test'))
201         t.fillSlots(test=succeed(tags.em('four>')))
202         return self.assertFlattensTo(t, '<p><em>four&gt;</em></p>')
203
204
205     def test_unknownTypeRaises(self):
206         """
207         Test that flattening an unknown type of thing raises an exception.
208         """
209         return self.assertFlatteningRaises(None, UnsupportedType)
210
211
212 # Use the co_filename mechanism (instead of the __file__ mechanism) because
213 # it is the mechanism traceback formatting uses.  The two do not necessarily
214 # agree with each other.  This requires a code object compiled in this file.
215 # The easiest way to get a code object is with a new function.  I'll use a
216 # lambda to avoid adding anything else to this namespace.  The result will
217 # be a string which agrees with the one the traceback module will put into a
218 # traceback for frames associated with functions defined in this file.
219
220 HERE = (lambda: None).func_code.co_filename
221
222
223 class FlattenerErrorTests(TestCase):
224     """
225     Tests for L{FlattenerError}.
226     """
227
228     def test_string(self):
229         """
230         If a L{FlattenerError} is created with a string root, up to around 40
231         bytes from that string are included in the string representation of the
232         exception.
233         """
234         self.assertEqual(
235             str(FlattenerError(RuntimeError("reason"), ['abc123xyz'], [])),
236             "Exception while flattening:\n"
237             "  'abc123xyz'\n"
238             "RuntimeError: reason\n")
239         self.assertEqual(
240             str(FlattenerError(
241                     RuntimeError("reason"), ['0123456789' * 10], [])),
242             "Exception while flattening:\n"
243             "  '01234567890123456789<...>01234567890123456789'\n"
244             "RuntimeError: reason\n")
245
246
247     def test_unicode(self):
248         """
249         If a L{FlattenerError} is created with a unicode root, up to around 40
250         characters from that string are included in the string representation
251         of the exception.
252         """
253         self.assertEqual(
254             str(FlattenerError(
255                     RuntimeError("reason"), [u'abc\N{SNOWMAN}xyz'], [])),
256             "Exception while flattening:\n"
257             "  u'abc\\u2603xyz'\n" # Codepoint for SNOWMAN
258             "RuntimeError: reason\n")
259         self.assertEqual(
260             str(FlattenerError(
261                     RuntimeError("reason"), [u'01234567\N{SNOWMAN}9' * 10],
262                     [])),
263             "Exception while flattening:\n"
264             "  u'01234567\\u2603901234567\\u26039<...>01234567\\u2603901234567"
265             "\\u26039'\n"
266             "RuntimeError: reason\n")
267
268
269     def test_renderable(self):
270         """
271         If a L{FlattenerError} is created with an L{IRenderable} provider root,
272         the repr of that object is included in the string representation of the
273         exception.
274         """
275         class Renderable(object):
276             implements(IRenderable)
277
278             def __repr__(self):
279                 return "renderable repr"
280
281         self.assertEqual(
282             str(FlattenerError(
283                     RuntimeError("reason"), [Renderable()], [])),
284             "Exception while flattening:\n"
285             "  renderable repr\n"
286             "RuntimeError: reason\n")
287
288
289     def test_tag(self):
290         """
291         If a L{FlattenerError} is created with a L{Tag} instance with source
292         location information, the source location is included in the string
293         representation of the exception.
294         """
295         tag = Tag(
296             'div', filename='/foo/filename.xhtml', lineNumber=17, columnNumber=12)
297
298         self.assertEqual(
299             str(FlattenerError(RuntimeError("reason"), [tag], [])),
300             "Exception while flattening:\n"
301             "  File \"/foo/filename.xhtml\", line 17, column 12, in \"div\"\n"
302             "RuntimeError: reason\n")
303
304
305     def test_tagWithoutLocation(self):
306         """
307         If a L{FlattenerError} is created with a L{Tag} instance without source
308         location information, only the tagName is included in the string
309         representation of the exception.
310         """
311         self.assertEqual(
312             str(FlattenerError(RuntimeError("reason"), [Tag('span')], [])),
313             "Exception while flattening:\n"
314             "  Tag <span>\n"
315             "RuntimeError: reason\n")
316
317
318     def test_traceback(self):
319         """
320         If a L{FlattenerError} is created with traceback frames, they are
321         included in the string representation of the exception.
322         """
323         # Try to be realistic in creating the data passed in for the traceback
324         # frames.
325         def f():
326             g()
327         def g():
328             raise RuntimeError("reason")
329
330         try:
331             f()
332         except RuntimeError, exc:
333             # Get the traceback, minus the info for *this* frame
334             tbinfo = traceback.extract_tb(sys.exc_info()[2])[1:]
335         else:
336             self.fail("f() must raise RuntimeError")
337
338         self.assertEqual(
339             str(FlattenerError(exc, [], tbinfo)),
340             "Exception while flattening:\n"
341             "  File \"%s\", line %d, in f\n"
342             "    g()\n"
343             "  File \"%s\", line %d, in g\n"
344             "    raise RuntimeError(\"reason\")\n"
345             "RuntimeError: reason\n" % (
346                 HERE, f.func_code.co_firstlineno + 1,
347                 HERE, g.func_code.co_firstlineno + 1))
348