1# Copyright (c) 2008 Divmod.
2# See LICENSE for details.
3
4"""
5Tests for L{nevow._flat}.
6"""
7
8import sys, traceback, StringIO
9
10from zope.interface import implements
11
12from twisted.trial.unittest import TestCase
13from twisted.internet.defer import Deferred, succeed
14
15from nevow.inevow import IRequest, IQ, IRenderable, IData
16from nevow._flat import FlattenerError, UnsupportedType, UnfilledSlot
17from nevow._flat import flatten, deferflatten
18from nevow.tags import Proto, Tag, slot, raw, xml
19from nevow.tags import invisible, br, div, directive
20from nevow.entities import nbsp
21from nevow.url import URL
22from nevow.rend import Fragment
23from nevow.loaders import stan
24from nevow.flat import flatten as oldFlatten, precompile as oldPrecompile
25from nevow.flat.ten import registerFlattener
26from nevow.testutil import FakeRequest
27from nevow.context import WovenContext
28
29
30# Use the co_filename mechanism (instead of the __file__ mechanism) because
31# it is the mechanism traceback formatting uses.  The two do not necessarily
32# agree with each other.  This requires a code object compiled in this file.
33# The easiest way to get a code object is with a new function.  I'll use a
34# lambda to avoid adding anything else to this namespace.  The result will
35# be a string which agrees with the one the traceback module will put into a
36# traceback for frames associated with functions defined in this file.
37HERE = (lambda: None).func_code.co_filename
38
39
40class TrivialRenderable(object):
41    """
42    An object which renders to a parameterized value.
43
44    @ivar result: The object which will be returned by the render method.
45    @ivar requests: A list of all the objects passed to the render method.
46    """
47    implements(IRenderable)
48
49    def __init__(self, result):
50        self.result = result
51        self.requests = []
52
53
54    def render(self, request):
55        """
56        Give back the canned response and record the given request.
57        """
58        self.requests.append(request)
59        return self.result
60
61
62
63class RenderRenderable(object):
64    """
65    An object which renders to a parameterized value and has a render method
66    which records how it is invoked.
67
68    @ivar renders: A list of two-tuples giving the arguments the render method
69        has been invoked with.
70    @ivar name: A string giving the name of the render method.
71    @ivar tag: The value which will be returned from C{render}.
72    @ivar result: The value which will be returned from the render method.
73    """
74    implements(IRenderable)
75
76    def __init__(self, renders, name, tag, result):
77        self.renders = renders
78        self.name = name
79        self.tag = tag
80        self.result = result
81
82
83    def renderer(self, name):
84        if name == self.name:
85            return self.renderMethod
86        raise ValueError("Invalid renderer name")
87
88
89    def renderMethod(self, request, tag):
90        self.renders.append((self, request))
91        return self.result
92
93
94    def render(self, request):
95        return self.tag
96
97
98
99class FlattenMixin:
100    """
101    Helper defining an assertion method useful for flattener tests.
102    """
103    def assertStringEqual(self, value, expected):
104        """
105        Assert that the given value is a C{str} instance and that it equals the
106        expected value.
107        """
108        self.assertTrue(isinstance(value, str))
109        self.assertEqual(value, expected)
110
111
112
113class FlattenTests(TestCase, FlattenMixin):
114    """
115    Tests for L{nevow._flat.flatten}.
116    """
117    def flatten(self, root, request=None, inAttribute=False, inXML=False):
118        """
119        Helper to get a string from L{flatten}.
120        """
121        s = StringIO.StringIO()
122        for _ in flatten(request, s.write, root, inAttribute, inXML):
123            pass
124        return s.getvalue()
125
126
127    def test_unflattenable(self):
128        """
129        Flattening an object which references an unflattenable object fails
130        with L{FlattenerError} which gives the flattener's stack at the
131        point of failure and which has an L{UnsupportedType} exception in
132        its arguments.
133        """
134        unflattenable = object()
135        deepest = [unflattenable]
136        middlest = (deepest,)
137        outermost = [middlest]
138        err = self.assertRaises(FlattenerError, self.flatten, outermost)
139        self.assertEqual(
140            err._roots, [outermost, middlest, deepest, unflattenable])
141        self.assertTrue(isinstance(err.args[0], UnsupportedType))
142
143
144    def test_str(self):
145        """
146        An instance of L{str} is flattened to itself.
147        """
148        self.assertStringEqual(self.flatten('bytes<>&"\0'), 'bytes<>&"\0')
149
150
151    def test_raw(self):
152        """
153        An instance of L{raw} is flattened to itself.
154        """
155        self.assertStringEqual(
156            self.flatten(raw('bytes<>^&"\0')), 'bytes<>^&"\0')
157
158
159    def test_attributeRaw(self):
160        """
161        An instance of L{raw} is flattened with " quoting if C{True} is passed
162        for C{inAttribute}.  L{raw} instances are expected to have already had
163        &, <, and > quoted.  L{raw} support is primarily for backwards
164        compatibility.
165        """
166        self.assertStringEqual(
167            self.flatten(raw('"&<>'), inAttribute=True, inXML=True),
168            '&quot;&<>')
169
170
171    def test_attributeString(self):
172        """
173        An instance of L{str} is flattened with attribute quoting rules if
174        C{True} is passed for C{inAttribute}.
175        """
176        self.assertStringEqual(
177            self.flatten('"&<>', inAttribute=True, inXML=False),
178            "&quot;&amp;&lt;&gt;")
179
180
181    def test_textNodeString(self):
182        """
183        An instance of L{str} is flattened with XML quoting rules if C{True} is
184        passed for C{inXML}.
185        """
186        self.assertStringEqual(
187            self.flatten('"&<>', inAttribute=False, inXML=True),
188            '"&amp;&lt;&gt;')
189
190
191    def test_unicode(self):
192        """
193        An instance of L{unicode} is flattened to the UTF-8 representation of
194        itself.
195        """
196        self.assertStringEqual(self.flatten(u'bytes<>&"\0'), 'bytes<>&"\0')
197        unich = u"\N{LATIN CAPITAL LETTER E WITH GRAVE}"
198        self.assertStringEqual(self.flatten(unich), unich.encode('utf-8'))
199
200
201    def test_xml(self):
202        """
203        An L{xml} instance is flattened to the UTF-8 representation of itself.
204        """
205        self.assertStringEqual(self.flatten(xml("foo")), "foo")
206        unich = u"\N{LATIN CAPITAL LETTER E WITH GRAVE}"
207        self.assertStringEqual(self.flatten(xml(unich)), unich.encode('utf-8'))
208
209
210    def test_entity(self):
211        """
212        An instance of L{Entity} is flattened to the XML representation of an
213        arbitrary codepoint.
214        """
215        self.assertStringEqual(self.flatten(nbsp), "&#160;")
216
217
218    def test_entityChild(self):
219        """
220        An instance of L{Entity} which is a child of a L{Tag} is flattened to
221        the XML representation of an arbitrary codepoint.
222        """
223        self.assertStringEqual(
224            self.flatten(div[nbsp]), "<div>&#160;</div>")
225
226
227    def test_entityAttribute(self):
228        """
229        An instance of L{Entity} which is the value of an attribute of a L{Tag}
230        is flattened to the XML representation of an arbitrary codepoint.
231        """
232        self.assertStringEqual(
233            self.flatten(div(foo=nbsp)), '<div foo="&#160;"></div>')
234
235
236    def test_iterable(self):
237        """
238        A C{list}, C{tuple} or C{generator} is flattened to the concatenation
239        of whatever its elements flatten to, in order.
240        """
241        sequence = ("bytes", "<", ">", "&")
242        result = "bytes<>&"
243        self.assertStringEqual(self.flatten(tuple(sequence)), result)
244        self.assertStringEqual(self.flatten(list(sequence)), result)
245        def gen():
246            for e in sequence:
247                yield e
248        self.assertStringEqual(self.flatten(gen()), result)
249
250
251    def test_singletonProto(self):
252        """
253        L{Proto} instances corresponding to tags which are allowed to be
254        self-closing are flattened that way.
255        """
256        self.assertStringEqual(self.flatten(br), "<br />")
257
258
259    def test_nonSingletonProto(self):
260        """
261        L{Proto} instances corresponding to tags which are not allowed to be
262        self-closing are not flattened that way.
263        """
264        self.assertStringEqual(self.flatten(div), "<div></div>")
265
266
267    def test_invisibleProto(self):
268        """
269        L{Proto} instances with an empty C{name} attribute don't have markup
270        generated for them.
271        """
272        self.assertStringEqual(self.flatten(invisible), "")
273        self.assertStringEqual(self.flatten(Proto("")), "")
274
275
276    def test_emptySingletonTag(self):
277        """
278        L{Tag} instances which are allowed to be self-closing are flattened
279        that way.
280        """
281        self.assertStringEqual(self.flatten(br()), "<br />")
282
283
284    def test_emptyNonSingletonTag(self):
285        """
286        L{Tag} instances which are not allowed to be self-closing are not
287        flattened that way.
288        """
289        self.assertStringEqual(self.flatten(div()), "<div></div>")
290
291
292    def test_invisibleTag(self):
293        """
294        L{Tag} instances with an empty C{tagName} attribute don't have markup
295        generated for them, only for their children.
296        """
297        self.assertStringEqual(self.flatten(invisible["foo"]), "foo")
298        self.assertStringEqual(self.flatten(Tag("")["foo"]), "foo")
299
300
301    def test_unicodeTagName(self):
302        """
303        A L{Tag} with a C{tagName} attribute which is C{unicode} instead of
304        C{str} is flattened to an XML representation.
305        """
306        self.assertStringEqual(self.flatten(Tag(u'div')), "<div></div>")
307        self.assertStringEqual(self.flatten(Tag(u'div')['']), "<div></div>")
308
309
310    def test_unicodeAttributeName(self):
311        """
312        A L{Tag} with an attribute name which is C{unicode} instead of C{str}
313        is flattened to an XML representation.
314        """
315        self.assertStringEqual(
316            self.flatten(Tag(u'div', {u'foo': 'bar'})), '<div foo="bar"></div>')
317
318
319    def test_stringTagAttributes(self):
320        """
321        C{str} L{Tag} attribute values are flattened by applying XML attribute
322        value quoting rules.
323        """
324        self.assertStringEqual(
325            self.flatten(div(foo="bar")), '<div foo="bar"></div>')
326        self.assertStringEqual(
327            self.flatten(div(foo='"><&')),
328            '<div foo="&quot;&gt;&lt;&amp;"></div>')
329
330
331    def test_tupleTagAttributes(self):
332        """
333        C{tuple} L{Tag} attribute values are flattened by flattening the tuple
334        and applying XML attribute value quoting rules to the result.
335        """
336        self.assertStringEqual(
337            self.flatten(div(foo=('"', ">", "<", "&"))),
338            '<div foo="&quot;&gt;&lt;&amp;"></div>')
339
340
341    def test_tagChildren(self):
342        """
343        The children of a L{Tag} are flattened to strings included inside the
344        XML markup delimiting the tag.
345        """
346        self.assertStringEqual(
347            self.flatten(div["baz"]), '<div>baz</div>')
348        self.assertStringEqual(
349            self.flatten(div[["b", "a", "z"]]), '<div>baz</div>')
350
351
352    def test_nestedTags(self):
353        """
354        The contents of a L{Tag} which is a child of another L{Tag} should be
355        quoted just once.
356        """
357        self.assertStringEqual(
358            self.flatten(div[div['&']]),
359            "<div><div>&amp;</div></div>")
360
361
362    def test_patternTag(self):
363        """
364        A L{Tag} with a I{pattern} special is omitted from the flattened
365        output.
366        """
367        self.assertStringEqual(self.flatten(div(pattern="foo")), "")
368
369
370    def test_onePatternTag(self):
371        """
372        A L{Tag} returned from L{IQ.onePattern} is represented in the flattened
373        output.
374        """
375        self.assertStringEqual(
376            self.flatten(IQ(div(pattern="foo")).onePattern("foo")),
377            "<div></div>")
378
379
380    def test_renderAttribute(self):
381        """
382        A L{Tag} with a I{render} special is replaced with the return value of
383        the corresponding render method on the L{IRenderable} above the tag.
384        """
385        result = ("foo", " ", "bar")
386        renders = []
387        class RendererRenderable(TrivialRenderable):
388            def render_foo(self, request, tag):
389                renders.append((request, tag))
390                return result
391
392            def renderer(self, name):
393                return getattr(self, 'render_' + name)
394
395        request = object()
396        tag = div(render="foo", bar="baz")["quux"]
397        renderer = RendererRenderable(tag)
398        self.assertStringEqual(self.flatten(renderer, request), "".join(result))
399        self.assertEqual(len(renders), 1)
400        self.assertIdentical(renders[0][0], request)
401        self.assertEqual(renders[0][1].tagName, tag.tagName)
402        self.assertEqual(renders[0][1].attributes, {"bar": "baz"})
403        self.assertEqual(renders[0][1].children, ["quux"])
404
405
406    def test_renderDirectiveAttribute(self):
407        """
408        A L{Tag} with a I{render} special which is a L{directive} is treated
409        the same way as if the special value were just a string.
410        """
411        result = ("foo", " ", "bar")
412        renders = []
413        class RendererRenderable(TrivialRenderable):
414            def render_foo(self, request, tag):
415                renders.append((request, tag))
416                return result
417
418            def renderer(self, name):
419                return getattr(self, 'render_' + name)
420
421        request = object()
422        tag = div(render=directive("foo"), bar="baz")["quux"]
423        renderer = RendererRenderable(tag)
424        self.assertStringEqual(self.flatten(renderer, request), "".join(result))
425        self.assertEqual(len(renders), 1)
426        self.assertIdentical(renders[0][0], request)
427        self.assertEqual(renders[0][1].tagName, tag.tagName)
428        self.assertEqual(renders[0][1].attributes, {"bar": "baz"})
429        self.assertEqual(renders[0][1].children, ["quux"])
430
431
432    def test_renderAttributeOnRenderableNestedInRenderable(self):
433        """
434        A L{Tag} with a renderer which returns an L{IRenderable} which renders
435        to a L{Tag} with a I{render} special is replaced with the return value
436        of the corresponding render method on the nested L{IRenderable}.
437        """
438        result = ("foo", " ", "bar")
439        request = object()
440        renders = []
441        inner = RenderRenderable(renders, "bar", div(render="bar"), result)
442        outer = RenderRenderable(renders, "foo", div(render="foo"), inner)
443        self.assertStringEqual(self.flatten(outer, request), "".join(result))
444        self.assertEqual(renders, [(outer, request), (inner, request)])
445
446
447    def test_renderAttributeNestedInList(self):
448        """
449        A L{Tag} with a renderer which is in a list which is returned by
450        L{IRenderable.render} is replaced with the result of the named renderer
451        on the L{IRenderable} which returned the list.
452        """
453        result = ("foo", " ", "bar")
454        renders = []
455        renderable = RenderRenderable(
456            renders, "foo", [div(render="foo")], result)
457        self.assertStringEqual(
458            self.flatten(renderable, None), "".join(result))
459
460
461    def test_renderAttributeNestedInTag(self):
462        """
463        A L{Tag} with a renderer which is a child of a L{Tag} which was
464        returned by L{IRenderable.render} is replaced with the result of the
465        named renderer on the L{IRenderable} which returned the L{Tag}.
466        """
467        result = "quux"
468        renders = []
469        tag = div[div(render="foo")]
470        renderable = RenderRenderable(renders, "foo", tag, result)
471        self.assertStringEqual(
472            self.flatten(renderable, None), "<div>quux</div>")
473
474
475    def test_renderAttributeNestedInAttributeValue(self):
476        """
477        A L{Tag} with a renderer which is the value of an attribute of a L{Tag}
478        which was returned by L{IRenderable.render} is replaced with the result
479        of the named renderer on the L{IRenderable} which returned the L{Tag}.
480        """
481        result = "quux"
482        renders = []
483        request = object()
484        tag = div(foo=invisible(render="bar"))
485        renderable = RenderRenderable(renders, "bar", tag, result)
486        self.assertStringEqual(
487            self.flatten(renderable, request), '<div foo="quux"></div>')
488        self.assertEqual(renders, [(renderable, request)])
489
490
491    def test_renderAttributeNestedInSlot(self):
492        """
493        A L{Tag} with a renderer which is used as the value of a L{slot} which
494        was returned by L{IRenderable.render} is replaced with the result of
495        the named renderer on the L{IRenderable} which returned the L{slot}.
496        """
497        result = "quux"
498        renders = []
499        outer = div[slot("bar")]
500        inner = div(render="foo")
501        outer.fillSlots("bar", inner)
502        renderable = RenderRenderable(renders, "foo", outer, result)
503        self.assertStringEqual(
504            self.flatten(renderable, None), "<div>quux</div>")
505
506
507    def test_renderAttributeNestedInPrecompiledSlot(self):
508        """
509        A L{Tag} with a renderer which is used as the value of a
510        L{_PrecompiledSlot} which was returned by L{IRenderable.render} is
511        replaced with the result of the named renderer on the L{IRenderable}
512        which returned the L{_PrecompiledSlot}.
513        """
514        result = "quux"
515        renders = []
516        request = object()
517        outer = invisible[stan(div[slot("bar")]).load()]
518        inner = div(render="foo")
519        outer.fillSlots("bar", inner)
520        renderable = RenderRenderable(renders, "foo", outer, result)
521        self.assertStringEqual(
522            self.flatten(renderable, request), "<div>quux</div>")
523        self.assertEqual(renders, [(renderable, request)])
524
525
526    def test_renderAttributedNestedInRenderResult(self):
527        """
528        A L{Tag} with a renderer which is returned by a render method is
529        replaced with the return value of the named renderer on the
530        L{IRenderable} which had the render method which returned the L{Tag}.
531        """
532        class TwoRenderers(object):
533            implements(IRenderable)
534
535            def renderer(self, name):
536                return getattr(self, name)
537
538            def foo(self, request, tag):
539                return div(render="bar")
540
541            def bar(self, request, tag):
542                return "baz"
543
544            def render(self, request):
545                return div(render="foo")
546
547        renderable = TwoRenderers()
548        self.assertStringEqual(self.flatten(renderable), "baz")
549
550
551    def test_slotsNestedInRenderResult(self):
552        """
553        A L{slot} in the return value of a render function is replaced by the
554        value of that slot as found on the tag which had the render directive.
555        """
556        tag = div(render="foo")
557        tag.fillSlots("bar", '"&<>')
558        renderable = RenderRenderable([], "foo", tag, slot("bar"))
559        self.assertStringEqual(self.flatten(renderable), '"&<>')
560
561
562    def test_renderTextDataQuoted(self):
563        """
564        Strings returned by a render method on an L{IRenderable} provider which
565        is a child of a L{Tag} are XML quoted.
566        """
567        tag = div[RenderRenderable([], "foo", div(render="foo"), '"&<>')]
568        self.assertStringEqual(self.flatten(tag), '<div>"&amp;&lt;&gt;</div>')
569
570
571    def test_renderMethodReturnsInputTag(self):
572        """
573        If a render method returns the tag it was passed, the tag is flattened
574        as though it did not have a render directive.
575        """
576        class IdempotentRenderable(object):
577            implements(IRenderable)
578
579            def renderer(self, name):
580                return getattr(self, name)
581
582            def foo(self, request, tag):
583                return tag
584
585            def render(self, request):
586                return div(render="foo", bar="baz")["hello, world"]
587
588        renderable = IdempotentRenderable()
589        self.assertStringEqual(
590            self.flatten(renderable), '<div bar="baz">hello, world</div>')
591
592
593    def test_url(self):
594        """
595        An L{URL} object is flattened to the appropriate representation of
596        itself, whether it is the child of a tag or the value of a tag
597        attribute.
598        """
599        link = URL.fromString("http://foo/fu?bar=baz&bar=baz#quux%2f")
600        self.assertStringEqual(
601            self.flatten(link),
602            "http://foo/fu?bar=baz&bar=baz#quux%2F")
603        self.assertStringEqual(
604            self.flatten(div[link]),
605            '<div>http://foo/fu?bar=baz&amp;bar=baz#quux%2F</div>')
606        self.assertStringEqual(
607            self.flatten(div(foo=link)),
608            '<div foo="http://foo/fu?bar=baz&amp;bar=baz#quux%2F"></div>')
609        self.assertStringEqual(
610            self.flatten(div[div(foo=link)]),
611            '<div><div foo="http://foo/fu?bar=baz&amp;bar=baz#quux%2F"></div>'
612            '</div>')
613
614        link = URL.fromString("http://foo/fu?%2f=%7f")
615        self.assertStringEqual(
616            self.flatten(link),
617            "http://foo/fu?%2F=%7F")
618        self.assertStringEqual(
619            self.flatten(div[link]),
620            '<div>http://foo/fu?%2F=%7F</div>')
621        self.assertStringEqual(
622            self.flatten(div(foo=link)),
623            '<div foo="http://foo/fu?%2F=%7F"></div>')
624
625
626    def test_unfilledSlot(self):
627        """
628        Flattening a slot which has no known value results in an
629        L{FlattenerError} exception which has an L{UnfilledSlot} exception
630        in its arguments.
631        """
632        exc = self.assertRaises(FlattenerError, self.flatten, slot("foo"))
633        self.assertTrue(isinstance(exc.args[0], UnfilledSlot))
634
635
636    def test_filledSlotTagChild(self):
637        """
638        Flattening a slot as a child of a L{Tag} which has been given a value
639        for that slot results in the slot being replaced with the value in the
640        output.
641        """
642        tag = div[slot("foo")]
643        tag.fillSlots("foo", "bar")
644        self.assertStringEqual(self.flatten(tag), "<div>bar</div>")
645
646
647    def test_filledSlotTagChildEscaping(self):
648        """
649        Flattening a slot as a child of a L{Tag} which has been given a string
650        value results in that string value being XML escaped in the output.
651        """
652        tag = div[slot("foo")]
653        tag.fillSlots("foo", '"&<>')
654        self.assertStringEqual(self.flatten(tag), '<div>"&amp;&lt;&gt;</div>')
655
656
657    def test_filledSlotNestedTagChild(self):
658        """
659        Flattening a slot as a child of a L{Tag} which is itself a child of a
660        L{Tag} which has been given a value for that slot results in the slot
661        being replaced with the value in the output.
662        """
663        tag = div[div[slot("foo")]]
664        tag.fillSlots("foo", "bar")
665        self.assertStringEqual(self.flatten(tag), "<div><div>bar</div></div>")
666
667
668    def test_filledSlotTagAttribute(self):
669        """
670        Flattening a slot which is the value of an attribute of a L{Tag}
671        results in the value of the slot appearing as the attribute value in
672        the output.
673        """
674        tag = div(foo=slot("bar"))
675        tag.fillSlots("bar", "baz")
676        self.assertStringEqual(self.flatten(tag), '<div foo="baz"></div>')
677
678
679    def test_slotFilledWithProto(self):
680        """
681        Filling a slot with a L{Proto} results in the slot being replaced with
682        the serialized form of the tag in the output.
683        """
684        tag = div[slot("foo")]
685        tag.fillSlots("foo", br)
686        self.assertStringEqual(self.flatten(tag), "<div><br /></div>")
687
688
689    def test_unfilledPrecompiledSlot(self):
690        """
691        Flattening a L{_PrecompiledSlot} for which no value has been supplied
692        results in an L{FlattenerError} exception.
693        """
694        tag = oldPrecompile(div[slot("foo")])
695        self.assertRaises(FlattenerError, self.flatten, tag)
696
697
698    def test_precompiledSlot(self):
699        """
700        A L{_PrecompiledSlot} is replaced with the value of that slot when
701        flattened.
702        """
703        tag = invisible[oldPrecompile(div[slot("foo")])]
704        tag.fillSlots("foo", '"&<>')
705        self.assertStringEqual(self.flatten(tag), '<div>"&amp;&lt;&gt;</div>')
706
707
708    def test_precompiledSlotTagAttribute(self):
709        """
710        A L{_PrecompiledSlot} which is the value of an attribute is replaced
711        with the value of the slot with XML attribute quoting applied.
712        """
713        tag = invisible[oldPrecompile(div(foo=slot("foo")))]
714        tag.fillSlots("foo", '"&<>')
715        self.assertStringEqual(self.flatten(tag), '<div foo="&quot;&amp;&lt;&gt;"></div>')
716
717
718    def test_precompiledSlotFilledWithSlot(self):
719        """
720        A L{_PrecompiledSlot} slot which is filled with another slot is
721        replaced with the value the other slot is filled with.
722        """
723        tag = invisible[oldPrecompile(div[slot("foo")])]
724        tag.fillSlots("foo", slot("bar"))
725        tag.fillSlots("bar", '"&<>')
726        self.assertStringEqual(self.flatten(tag), '<div>"&amp;&lt;&gt;</div>')
727
728
729    def test_renderable(self):
730        """
731        Flattening an object which provides L{IRenderable} results in
732        something based on the result of the object's C{render} method.
733        """
734        request = object()
735        renderable = TrivialRenderable("bytes")
736        self.assertStringEqual(
737            self.flatten(renderable, request), "bytes")
738        self.assertEqual(renderable.requests, [request])
739
740
741    def test_renderableNestingRenderable(self):
742        """
743        Flattening an L{IRenderable} provider which returns another
744        L{IRenderable} from its C{render} method results in the result of
745        flattening the result of the inner L{IRenderable}'s C{render} method
746        which is called with the request object.
747        """
748        request = object()
749        inner = TrivialRenderable("bytes")
750        outer = TrivialRenderable(inner)
751        self.assertStringEqual(self.flatten(outer, request), "bytes")
752        self.assertEqual(inner.requests, [request])
753
754
755    def test_listNestingRenderable(self):
756        """
757        Flattening a C{list} which has an object providing L{IRenderable} as a
758        child results in the result of the L{IRenderable}'s C{render} method
759        which is called with the request object.
760        """
761        request = object()
762        renderable = TrivialRenderable("bytes")
763        self.assertStringEqual(self.flatten([renderable], request), "bytes")
764        self.assertEqual(renderable.requests, [request])
765
766
767    def test_tagNestingRenderable(self):
768        """
769        Flattening a L{Tag} which has an object providing L{IRenderable} as a
770        child results in markup for the tag with child data from the
771        L{IRenderable}'s C{render} which is called with the request object.
772        """
773        request = object()
774        inner = TrivialRenderable("bytes")
775        outer = div[inner]
776        self.assertStringEqual(
777            self.flatten(outer, request), "<div>bytes</div>")
778        self.assertEqual(inner.requests, [request])
779
780
781    def test_slotNestingRenderable(self):
782        """
783        Flattening a L{slot} which is filled with an object providing
784        L{IRenderable} results in the result of the L{IRenderable}'s C{render}
785        method which is called with the request object.
786        """
787        request = object()
788        inner = TrivialRenderable("bytes")
789        outer = slot("foo")
790        tag = div[outer]
791        tag.fillSlots("foo", inner)
792        self.assertStringEqual(self.flatten(tag, request), "<div>bytes</div>")
793        self.assertEqual(inner.requests, [request])
794
795
796    def test_slotFromRenderable(self):
797        """
798        An L{IRenderable} provider which returns a C{Tag} inside a C{slot}
799        from its C{render} method has that slot filled with the slot data
800        available on the tag.
801        """
802        tag = div[slot("foo")]
803        tag.fillSlots("foo", "bar")
804        renderable = TrivialRenderable(tag)
805        self.assertStringEqual(self.flatten(renderable), "<div>bar</div>")
806
807
808    def _nestingTest(self, nestedObject, expected):
809        limit = sys.getrecursionlimit()
810        sys.setrecursionlimit(100)
811        try:
812            self.assertStringEqual(self.flatten(nestedObject), expected)
813        finally:
814            sys.setrecursionlimit(limit)
815
816
817    def test_deeplyNestedList(self):
818        """
819        Flattening succeeds for an object with a level of list nesting
820        significantly greater than the Python maximum recursion limit.
821        """
822        obj = ["foo"]
823        for i in xrange(1000):
824            obj = [obj]
825        self._nestingTest(obj, "foo")
826
827
828    def test_deeplyNestedSlot(self):
829        """
830        Flattening succeeds for an object with a level of slot nesting
831        significantly greater than the Python maximum recursion limit.
832        """
833        tag = div()[slot("foo-0")]
834        for i in xrange(1000):
835            tag.fillSlots("foo-" + str(i), slot("foo-" + str(i + 1)))
836        tag.fillSlots("foo-1000", "bar")
837        self._nestingTest(tag, "<div>bar</div>")
838
839
840    def test_deeplyNestedTag(self):
841        """
842        Flattening succeeds for a tag with a level of nesting significantly
843        greater than the Python maximum recursion limit.
844        """
845        n = 1000
846        tag = div["foo"]
847        for i in xrange(n - 1):
848            tag = div[tag]
849        self._nestingTest(tag, "<div>" * n + "foo" + "</div>" * n)
850
851
852    def test_deeplyNestedRenderables(self):
853        """
854        Flattening succeeds for an object with a level of L{IRenderable}
855        nesting significantly greater than the Python maximum recursion limit.
856        """
857        obj = TrivialRenderable("foo")
858        for i in xrange(1000):
859            obj = TrivialRenderable(obj)
860        self._nestingTest(obj, "foo")
861
862
863    def test_legacyRenderer(self):
864        """
865        Flattening an L{IRenderer} succeeds with the same result as using the
866        old flattener.
867        """
868        class Legacy(Fragment):
869            docFactory = stan(invisible(render=directive('foo')))
870            def render_foo(self, ctx, data):
871                return '"&<>'
872        fragment = Legacy()
873        self.assertEqual(
874            self.flatten(fragment), oldFlatten(fragment))
875        self.assertEqual(
876            self.flatten(div(foo=fragment)),
877            oldFlatten(div(foo=fragment)))
878
879
880    def test_legacySerializable(self):
881        """
882        Flattening an object for which a flattener was registered with
883        L{registerFlattener} succeeds with the result of the registered
884        flattener function.
885        """
886        request = FakeRequest()
887        result = 'bytes&quot;'
888        serializable = LegacySerializable(result)
889        self.assertEqual(
890            self.flatten(div(foo=serializable), request),
891            '<div foo="' + result + '"></div>')
892        [context] = serializable.flattenedWith
893        self.assertTrue(isinstance(context, WovenContext))
894        self.assertFalse(context.precompile)
895        self.assertTrue(context.isAttrib)
896        self.assertIdentical(context.locate(IRequest), request)
897        self.assertIdentical(context.locate(IData), None)
898
899
900    def test_legacySerializableReturnsSlot(self):
901        """
902        A slot returned by a flattener registered with L{registerFlattener} is
903        filled with the value of a slot from "outside" the L{ISerializable}.
904        """
905        request = FakeRequest()
906        result = slot('foo')
907        serializable = LegacySerializable(result)
908        tag = div(foo=serializable)
909        tag.fillSlots("foo", "bar")
910        self.assertEqual(self.flatten(tag, request), '<div foo="bar"></div>')
911
912
913    def test_flattenExceptionStack(self):
914        """
915        If an exception is raised by a render method, L{FlattenerError} is
916        raised with information about the stack between the flattener and the
917        frame which raised the exception.
918        """
919        def broken():
920            raise RuntimeError("foo")
921
922        class BrokenRenderable(object):
923            implements(IRenderable)
924
925            def render(self, request):
926                # insert another stack frame before the exception
927                broken()
928
929
930        request = object()
931        renderable = BrokenRenderable()
932        exc = self.assertRaises(
933            FlattenerError, self.flatten, renderable, request)
934        self.assertEqual(
935            # There are probably some frames above this, but I don't care what
936            # they are.
937            exc._traceback[-2:],
938            [(HERE, 927, 'render', 'broken()'),
939             (HERE, 920, 'broken', 'raise RuntimeError("foo")')])
940
941
942
943class FlattenerErrorTests(TestCase):
944    """
945    Tests for L{FlattenerError}.
946    """
947    def test_string(self):
948        """
949        If a L{FlattenerError} is created with a string root, up to around 40
950        bytes from that string are included in the string representation of the
951        exception.
952        """
953        self.assertEqual(
954            str(FlattenerError(RuntimeError("reason"), ['abc123xyz'], [])),
955            "Exception while flattening:\n"
956            "  'abc123xyz'\n"
957            "RuntimeError: reason\n")
958        self.assertEqual(
959            str(FlattenerError(
960                    RuntimeError("reason"), ['0123456789' * 10], [])),
961            "Exception while flattening:\n"
962            "  '01234567890123456789<...>01234567890123456789'\n"
963            "RuntimeError: reason\n")
964
965
966    def test_unicode(self):
967        """
968        If a L{FlattenerError} is created with a unicode root, up to around 40
969        characters from that string are included in the string representation
970        of the exception.
971        """
972        self.assertEqual(
973            str(FlattenerError(
974                    RuntimeError("reason"), [u'abc\N{SNOWMAN}xyz'], [])),
975            "Exception while flattening:\n"
976            "  u'abc\\u2603xyz'\n" # Codepoint for SNOWMAN
977            "RuntimeError: reason\n")
978        self.assertEqual(
979            str(FlattenerError(
980                    RuntimeError("reason"), [u'01234567\N{SNOWMAN}9' * 10],
981                    [])),
982            "Exception while flattening:\n"
983            "  u'01234567\\u2603901234567\\u26039<...>01234567\\u2603901234567"
984            "\\u26039'\n"
985            "RuntimeError: reason\n")
986
987
988    def test_renderable(self):
989        """
990        If a L{FlattenerError} is created with an L{IRenderable} provider root,
991        the repr of that object is included in the string representation of the
992        exception.
993        """
994        class Renderable(object):
995            implements(IRenderable)
996
997            def __repr__(self):
998                return "renderable repr"
999
1000        self.assertEqual(
1001            str(FlattenerError(
1002                    RuntimeError("reason"), [Renderable()], [])),
1003            "Exception while flattening:\n"
1004            "  renderable repr\n"
1005            "RuntimeError: reason\n")
1006
1007
1008    def test_tag(self):
1009        """
1010        If a L{FlattenerError} is created with a L{Tag} instance with source
1011        location information, the source location is included in the string
1012        representation of the exception.
1013        """
1014        tag = Tag(
1015            'div', filename='/foo/filename.xhtml', lineNumber=17, columnNumber=12)
1016
1017        self.assertEqual(
1018            str(FlattenerError(RuntimeError("reason"), [tag], [])),
1019            "Exception while flattening:\n"
1020            "  File \"/foo/filename.xhtml\", line 17, column 12, in \"div\"\n"
1021            "RuntimeError: reason\n")
1022
1023
1024    def test_tagWithoutLocation(self):
1025        """
1026        If a L{FlattenerError} is created with a L{Tag} instance without source
1027        location information, only the tagName is included in the string
1028        representation of the exception.
1029        """
1030        self.assertEqual(
1031            str(FlattenerError(RuntimeError("reason"), [Tag('span')], [])),
1032            "Exception while flattening:\n"
1033            "  Tag <span>\n"
1034            "RuntimeError: reason\n")
1035
1036
1037    def test_traceback(self):
1038        """
1039        If a L{FlattenerError} is created with traceback frames, they are
1040        included in the string representation of the exception.
1041        """
1042        # Try to be realistic in creating the data passed in for the traceback
1043        # frames.
1044        def f():
1045            g()
1046        def g():
1047            raise RuntimeError("reason")
1048
1049        try:
1050            f()
1051        except RuntimeError, exc:
1052            # Get the traceback, minus the info for *this* frame
1053            tbinfo = traceback.extract_tb(sys.exc_info()[2])[1:]
1054        else:
1055            self.fail("f() must raise RuntimeError")
1056
1057        self.assertEqual(
1058            str(FlattenerError(exc, [], tbinfo)),
1059            "Exception while flattening:\n"
1060            "  File \"%s\", line %d, in f\n"
1061            "    g()\n"
1062            "  File \"%s\", line %d, in g\n"
1063            "    raise RuntimeError(\"reason\")\n"
1064            "RuntimeError: reason\n" % (
1065                HERE, f.func_code.co_firstlineno + 1,
1066                HERE, g.func_code.co_firstlineno + 1))
1067
1068
1069
1070class LegacySerializable(object):
1071    """
1072    An object for which a legacy flattener is registered and which can only be
1073    flattened using that flattener.
1074    """
1075    def __init__(self, value):
1076        self.value = value
1077        self.flattenedWith = []
1078
1079
1080
1081def flattenLegacySerializable(legacy, context):
1082    """
1083    Old-style flattener for L{LegacySerializable}.
1084    """
1085    legacy.flattenedWith.append(context)
1086    return [legacy.value]
1087
1088
1089
1090registerFlattener(flattenLegacySerializable, LegacySerializable)
1091
1092
1093
1094class DeferflattenTests(TestCase, FlattenMixin):
1095    """
1096    Tests for L{nevow._flat.deferflatten}.
1097    """
1098    def deferflatten(self, root, request=None):
1099        """
1100        Helper to get a string from L{deferflatten}.
1101        """
1102        result = []
1103        d = deferflatten(request, root, False, False, result.append)
1104        def cbFlattened(ignored):
1105            return "".join(result)
1106        d.addCallback(cbFlattened)
1107        return d
1108
1109
1110    def test_unflattenable(self):
1111        """
1112        L{deferflatten} returns a L{Deferred} which fails with
1113        L{FlattenerError} if it is passed an object which cannot be flattened.
1114        """
1115        return self.assertFailure(
1116            self.deferflatten(object()), FlattenerError)
1117
1118
1119    def test_unfilledSlotDeferredResult(self):
1120        """
1121        Flattening a L{Deferred} which results in an unfilled L{slot} results
1122        in a L{FlattenerError} failure.
1123        """
1124        return self.assertFailure(
1125            self.deferflatten(succeed(slot("foo"))),
1126            FlattenerError)
1127
1128
1129    def test_renderable(self):
1130        """
1131        Flattening an object which provides L{IRenderable} results in the
1132        result of the object's C{render} method which is called with the
1133        request.
1134        """
1135        request = object()
1136        renderable = TrivialRenderable("bytes")
1137        def cbFlattened(result):
1138            self.assertStringEqual(result, "bytes")
1139            self.assertEqual(renderable.requests, [request])
1140        flattened = self.deferflatten(renderable, request)
1141        flattened.addCallback(cbFlattened)
1142        return flattened
1143
1144
1145    def test_renderableException(self):
1146        """
1147        Flattening an object which provides L{IRenderable} with a C{render}
1148        method which synchronously raises an exception results in a L{Deferred}
1149        which fails with L{FlattenerError}.
1150        """
1151        class TestException(Exception):
1152            pass
1153        class BrokenRenderable(object):
1154            implements(IRenderable)
1155
1156            def render(self, request):
1157                raise TestException()
1158        flattened = self.deferflatten(BrokenRenderable())
1159        return self.assertFailure(flattened, FlattenerError)
1160
1161
1162    def test_deferredRenderAttribute(self):
1163        """
1164        Flattening an object which provides L{IRenderable} with a C{render}
1165        method which returns a L{Deferred} which is called back with a L{Tag}
1166        with a render attribute results in the return value of the named
1167        renderer from the L{IRenderer} which returned the L{Deferred}.
1168        """
1169        flattened = self.deferflatten(
1170            RenderRenderable([], "foo", succeed(div(render="foo")), "bar"))
1171        flattened.addCallback(self.assertStringEqual, "bar")
1172        return flattened
1173
1174
1175    def test_synchronousDeferredSlot(self):
1176        """
1177        Flattening a L{slot} which is filled with a L{Deferred} which has a
1178        result already results in the result of the L{Deferred}.
1179        """
1180        tag = div[slot("foo")]
1181        tag.fillSlots("foo", succeed("bar"))
1182        flattened = self.deferflatten(tag)
1183        flattened.addCallback(self.assertStringEqual, "<div>bar</div>")
1184        return flattened
1185
1186
1187    def test_asynchronousDeferredSlot(self):
1188        """
1189        Flattening a L{slot} which is filled with a L{Deferred} which does not
1190        have a result already results in the result of the L{Deferred} when it
1191        becomes available.
1192        """
1193        tag = div[slot("foo")]
1194        deferred = Deferred()
1195        tag.fillSlots("foo", deferred)
1196        flattened = self.deferflatten(tag)
1197        flattened.addCallback(self.assertStringEqual, "<div>bar</div>")
1198        deferred.callback("bar")
1199        return flattened
1200
1201
1202    def test_deferredNestingRenderable(self):
1203        """
1204        Flattening a L{Deferred} which has an object providing L{IRenderable}
1205        as the result results in the result of the L{IRenderable}'s C{render}
1206        method.
1207        """
1208        request = object()
1209        renderable = TrivialRenderable("bytes")
1210        deferred = succeed(renderable)
1211        def cbFlattened(result):
1212            self.assertStringEqual(result, "bytes")
1213            self.assertEqual(renderable.requests, [request])
1214        flattened = self.deferflatten(deferred, request)
1215        flattened.addCallback(cbFlattened)
1216        return deferred
1217
1218
1219    def test_reusedDeferred(self):
1220        """
1221        Flattening a C{list} which contains the same L{Deferred} twice results
1222        in the result of the L{Deferred} twice.
1223        """
1224        deferred = succeed("bytes")
1225        root = [deferred, deferred]
1226        flattened = self.deferflatten(root)
1227        flattened.addCallback(self.assertStringEqual, "bytesbytes")
1228        return flattened
1229
1230
1231    def test_manySynchronousDeferreds(self):
1232        """
1233        Flattening a structure with many more L{Deferreds} than there are
1234        frames allowed by the Python recursion limit succeeds if all the
1235        L{Deferred}s have results already.
1236        """
1237        results = [str(i) for i in xrange(1000)]
1238        deferreds = map(succeed, results)
1239        limit = sys.getrecursionlimit()
1240        sys.setrecursionlimit(100)
1241        try:
1242            flattened = self.deferflatten(deferreds)
1243        except:
1244            sys.setrecursionlimit(limit)
1245            raise
1246        else:
1247            def cb(passthrough):
1248                sys.setrecursionlimit(limit)
1249                return passthrough
1250            flattened.addBoth(cb)
1251            flattened.addCallback(self.assertStringEqual, "".join(results))
1252            return flattened
1253
1254
1255    def test_deferredQuoting(self):
1256        """
1257        Flattening a L{Deferred} results in the result of the L{Deferred}
1258        without any quoting.
1259        """
1260        flattened = self.deferflatten(succeed('"&<>'))
1261        flattened.addCallback(self.assertStringEqual, '"&<>')
1262        return flattened
1263
1264
1265    def test_deferredAttributeValueQuoting(self):
1266        """
1267        Flattening a L{Tag} which has an attribute value which is a L{Deferred}
1268        results in the result of the L{Deferred} being XML attribute quoted and
1269        included as the value for that attribute of the tag.
1270        """
1271        tag = div(foo=succeed('"&<>'))
1272        flattened = self.deferflatten(tag)
1273        flattened.addCallback(
1274            self.assertStringEqual, '<div foo="&quot;&amp;&lt;&gt;"></div>')
1275        return flattened
1276
1277
1278    def test_deferredTagChildQuoting(self):
1279        """
1280        Flattening a L{Tag} which has a child which is a L{Deferred} results in
1281        the result of the L{Deferred} being XML quoted and included as a child
1282        value for the tag.
1283        """
1284        tag = div[succeed('"&<>')]
1285        flattened = self.deferflatten(tag)
1286        flattened.addCallback(
1287            self.assertStringEqual, '<div>"&amp;&lt;&gt;</div>')
1288        return flattened
1289
1290
1291    def test_slotDeferredResultQuoting(self):
1292        """
1293        Flattening a L{Tag} with a L{Deferred} as a child which results in a
1294        L{slot} results in the value of the slot being XML quoted and included
1295        as a child value for the tag.
1296        """
1297        deferred = succeed(slot("foo"))
1298        tag = div[deferred]
1299        tag.fillSlots("foo", '"&<>')
1300        flattened = self.deferflatten(tag)
1301        flattened.addCallback(
1302            self.assertStringEqual, '<div>"&amp;&lt;&gt;</div>')
1303        return flattened
1304
1305
1306    def test_legacyAsynchronousRenderer(self):
1307        """
1308        Flattening an L{IRenderer} which returns a L{Deferred} from one of its
1309        render methods succeeds with therthe same result as using the old
1310        flattener.
1311        """
1312        deferredResult = Deferred()
1313        rendererCalled = []
1314        class Legacy(Fragment):
1315            docFactory = stan(invisible(render=directive('foo')))
1316            def render_foo(self, ctx, data):
1317                rendererCalled.append(None)
1318                return deferredResult
1319        fragment = Legacy()
1320        finished = self.deferflatten(fragment)
1321        finished.addCallback(
1322            self.assertStringEqual, "foobarbaz")
1323        # Sanity check - we do not want the Deferred to have been called back
1324        # before it is returned from the render method.
1325        self.assertTrue(rendererCalled)
1326        deferredResult.callback("foobarbaz")
1327        return finished
1328
1329
1330    def test_attributeString(self):
1331        """
1332        An instance of L{str} is flattened with attribute quoting rules if
1333        C{True} is passed for C{inAttribute}.
1334        """
1335        result = []
1336        finished = deferflatten(None, '"&<>', True, False, result.append)
1337        finished.addCallback(lambda ignored: "".join(result))
1338        finished.addCallback(self.assertStringEqual, "&quot;&amp;&lt;&gt;")
1339        return finished
1340
1341
1342    def test_textNodeString(self):
1343        """
1344        An instance of L{str} is flattened with XML quoting rules if C{True} is
1345        passed for C{inXML}.
1346        """
1347        result = []
1348        finished = deferflatten(None, '"&<>', False, True, result.append)
1349        finished.addCallback(lambda ignored: "".join(result))
1350        finished.addCallback(self.assertStringEqual, '"&amp;&lt;&gt;')
1351        return finished
1352