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 '"&<>') 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 ""&<>") 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 '"&<>') 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), " ") 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> </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=" "></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=""><&"></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=""><&"></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>&</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>"&<></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&bar=baz#quux%2F</div>') 606 self.assertStringEqual( 607 self.flatten(div(foo=link)), 608 '<div foo="http://foo/fu?bar=baz&bar=baz#quux%2F"></div>') 609 self.assertStringEqual( 610 self.flatten(div[div(foo=link)]), 611 '<div><div foo="http://foo/fu?bar=baz&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>"&<></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>"&<></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=""&<>"></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>"&<></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"' 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=""&<>"></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>"&<></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>"&<></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, ""&<>") 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, '"&<>') 1351 return finished 1352