", repr(XMLFile(fobj))) # type: ignore[arg-type]
test_file.suppress = [_xmlFileSuppress] # type: ignore[attr-defined]
class XMLLoaderTestsMixin:
deprecatedUse: bool
"""
C{True} if this use of L{XMLFile} is deprecated and should emit
a C{DeprecationWarning}.
"""
templateString = "Hello, world.
"
"""
Simple template to use to exercise the loaders.
"""
def loaderFactory(self) -> ITemplateLoader:
raise NotImplementedError
def test_load(self) -> None:
"""
Verify that the loader returns a tag with the correct children.
"""
assert isinstance(self, TestCase)
loader = self.loaderFactory()
(tag,) = loader.load()
assert isinstance(tag, Tag)
warnings = self.flushWarnings(offendingFunctions=[self.loaderFactory])
if self.deprecatedUse:
self.assertEqual(len(warnings), 1)
self.assertEqual(warnings[0]["category"], DeprecationWarning)
self.assertEqual(
warnings[0]["message"],
"Passing filenames or file objects to XMLFile is "
"deprecated since Twisted 12.1. Pass a FilePath instead.",
)
else:
self.assertEqual(len(warnings), 0)
self.assertEqual(tag.tagName, "p")
self.assertEqual(tag.children, ["Hello, world."])
def test_loadTwice(self) -> None:
"""
If {load()} can be called on a loader twice the result should be the
same.
"""
assert isinstance(self, TestCase)
loader = self.loaderFactory()
tags1 = loader.load()
tags2 = loader.load()
self.assertEqual(tags1, tags2)
test_loadTwice.suppress = [_xmlFileSuppress] # type: ignore[attr-defined]
class XMLStringLoaderTests(TestCase, XMLLoaderTestsMixin):
"""
Tests for L{twisted.web.template.XMLString}
"""
deprecatedUse = False
def loaderFactory(self) -> ITemplateLoader:
"""
@return: an L{XMLString} constructed with C{self.templateString}.
"""
return XMLString(self.templateString)
class XMLFileWithFilePathTests(TestCase, XMLLoaderTestsMixin):
"""
Tests for L{twisted.web.template.XMLFile}'s L{FilePath} support.
"""
deprecatedUse = False
def loaderFactory(self) -> ITemplateLoader:
"""
@return: an L{XMLString} constructed with a L{FilePath} pointing to a
file that contains C{self.templateString}.
"""
fp = FilePath(self.mktemp())
fp.setContent(self.templateString.encode("utf8"))
return XMLFile(fp)
class XMLFileWithFileTests(TestCase, XMLLoaderTestsMixin):
"""
Tests for L{twisted.web.template.XMLFile}'s deprecated file object support.
"""
deprecatedUse = True
def loaderFactory(self) -> ITemplateLoader:
"""
@return: an L{XMLString} constructed with a file object that contains
C{self.templateString}.
"""
return XMLFile(StringIO(self.templateString)) # type: ignore[arg-type]
class XMLFileWithFilenameTests(TestCase, XMLLoaderTestsMixin):
"""
Tests for L{twisted.web.template.XMLFile}'s deprecated filename support.
"""
deprecatedUse = True
def loaderFactory(self) -> ITemplateLoader:
"""
@return: an L{XMLString} constructed with a filename that points to a
file containing C{self.templateString}.
"""
fp = FilePath(self.mktemp())
fp.setContent(self.templateString.encode("utf8"))
return XMLFile(fp.path)
class FlattenIntegrationTests(FlattenTestCase):
"""
Tests for integration between L{Element} and
L{twisted.web._flatten.flatten}.
"""
def test_roundTrip(self) -> None:
"""
Given a series of parsable XML strings, verify that
L{twisted.web._flatten.flatten} will flatten the L{Element} back to the
input when sent on a round trip.
"""
fragments = [
b"Hello, world.
",
b"",
b"",
b'' b"",
b'',
b"\xe2\x98\x83
",
]
for xml in fragments:
self.assertFlattensImmediately(Element(loader=XMLString(xml)), xml)
def test_entityConversion(self) -> None:
"""
When flattening an HTML entity, it should flatten out to the utf-8
representation if possible.
"""
element = Element(loader=XMLString("☃
"))
self.assertFlattensImmediately(element, b"\xe2\x98\x83
")
def test_missingTemplateLoader(self) -> None:
"""
Rendering an Element without a loader attribute raises the appropriate
exception.
"""
self.assertFlatteningRaises(Element(), MissingTemplateLoader)
def test_missingRenderMethod(self) -> None:
"""
Flattening an L{Element} with a C{loader} which has a tag with a render
directive fails with L{FlattenerError} if there is no available render
method to satisfy that directive.
"""
element = Element(
loader=XMLString(
"""
"""
)
)
self.assertFlatteningRaises(element, MissingRenderMethod)
def test_transparentRendering(self) -> None:
"""
A C{transparent} element should be eliminated from the DOM and rendered as
only its children.
"""
element = Element(
loader=XMLString(
"'
"Hello, world."
""
)
)
self.assertFlattensImmediately(element, b"Hello, world.")
def test_attrRendering(self) -> None:
"""
An Element with an attr tag renders the vaule of its attr tag as an
attribute of its containing tag.
"""
element = Element(
loader=XMLString(
''
'http://example.com'
"Hello, world."
""
)
)
self.assertFlattensImmediately(
element, b'Hello, world.'
)
def test_synchronousDeferredRecursion(self) -> None:
"""
When rendering a large number of already-fired Deferreds we should not
encounter any recursion errors or stack-depth issues.
"""
self.assertFlattensImmediately([succeed("x") for i in range(250)], b"x" * 250)
def test_errorToplevelAttr(self) -> None:
"""
A template with a toplevel C{attr} tag will not load; it will raise
L{AssertionError} if you try.
"""
self.assertRaises(
AssertionError,
XMLString,
"""hello
""",
)
def test_errorUnnamedAttr(self) -> None:
"""
A template with an C{attr} tag with no C{name} attribute will not load;
it will raise L{AssertionError} if you try.
"""
self.assertRaises(
AssertionError,
XMLString,
"""hello""",
)
def test_lenientPrefixBehavior(self) -> None:
"""
If the parser sees a prefix it doesn't recognize on an attribute, it
will pass it on through to serialization.
"""
theInput = (
''
"This is a made-up tag."
)
element = Element(loader=XMLString(theInput))
self.assertFlattensTo(element, theInput.encode("utf8"))
def test_deferredRendering(self) -> None:
"""
An Element with a render method which returns a Deferred will render
correctly.
"""
class RenderfulElement(Element):
@renderer
def renderMethod(
self, request: Optional[IRequest], tag: Tag
) -> Flattenable:
return succeed("Hello, world.")
element = RenderfulElement(
loader=XMLString(
"""
Goodbye, world.
"""
)
)
self.assertFlattensImmediately(element, b"Hello, world.")
def test_loaderClassAttribute(self) -> None:
"""
If there is a non-None loader attribute on the class of an Element
instance but none on the instance itself, the class attribute is used.
"""
class SubElement(Element):
loader = XMLString("Hello, world.
")
self.assertFlattensImmediately(SubElement(), b"Hello, world.
")
def test_directiveRendering(self) -> None:
"""
An Element with a valid render directive has that directive invoked and
the result added to the output.
"""
renders = []
class RenderfulElement(Element):
@renderer
def renderMethod(
self, request: Optional[IRequest], tag: Tag
) -> Flattenable:
renders.append((self, request))
return tag("Hello, world.")
element = RenderfulElement(
loader=XMLString(
"""
"""
)
)
self.assertFlattensImmediately(element, b"Hello, world.
")
def test_directiveRenderingOmittingTag(self) -> None:
"""
An Element with a render method which omits the containing tag
successfully removes that tag from the output.
"""
class RenderfulElement(Element):
@renderer
def renderMethod(
self, request: Optional[IRequest], tag: Tag
) -> Flattenable:
return "Hello, world."
element = RenderfulElement(
loader=XMLString(
"""
Goodbye, world.
"""
)
)
self.assertFlattensImmediately(element, b"Hello, world.")
def test_elementContainingStaticElement(self) -> None:
"""
An Element which is returned by the render method of another Element is
rendered properly.
"""
class RenderfulElement(Element):
@renderer
def renderMethod(
self, request: Optional[IRequest], tag: Tag
) -> Flattenable:
return tag(Element(loader=XMLString("Hello, world.")))
element = RenderfulElement(
loader=XMLString(
"""
"""
)
)
self.assertFlattensImmediately(element, b"Hello, world.
")
def test_elementUsingSlots(self) -> None:
"""
An Element which is returned by the render method of another Element is
rendered properly.
"""
class RenderfulElement(Element):
@renderer
def renderMethod(
self, request: Optional[IRequest], tag: Tag
) -> Flattenable:
return tag.fillSlots(test2="world.")
element = RenderfulElement(
loader=XMLString(
''
''
''
"
"
)
)
self.assertFlattensImmediately(element, b"Hello, world.
")
def test_elementContainingDynamicElement(self) -> None:
"""
Directives in the document factory of an Element returned from a render
method of another Element are satisfied from the correct object: the
"inner" Element.
"""
class OuterElement(Element):
@renderer
def outerMethod(self, request: Optional[IRequest], tag: Tag) -> Flattenable:
return tag(
InnerElement(
loader=XMLString(
"""
"""
)
)
)
class InnerElement(Element):
@renderer
def innerMethod(self, request: Optional[IRequest], tag: Tag) -> Flattenable:
return "Hello, world."
element = OuterElement(
loader=XMLString(
"""
"""
)
)
self.assertFlattensImmediately(element, b"Hello, world.
")
def test_sameLoaderTwice(self) -> None:
"""
Rendering the output of a loader, or even the same element, should
return different output each time.
"""
sharedLoader = XMLString(
''
' '
''
"
"
)
class DestructiveElement(Element):
count = 0
instanceCount = 0
loader = sharedLoader
@renderer
def classCounter(
self, request: Optional[IRequest], tag: Tag
) -> Flattenable:
DestructiveElement.count += 1
return tag(str(DestructiveElement.count))
@renderer
def instanceCounter(
self, request: Optional[IRequest], tag: Tag
) -> Flattenable:
self.instanceCount += 1
return tag(str(self.instanceCount))
e1 = DestructiveElement()
e2 = DestructiveElement()
self.assertFlattensImmediately(e1, b"1 1
")
self.assertFlattensImmediately(e1, b"2 2
")
self.assertFlattensImmediately(e2, b"3 1
")
class TagLoaderTests(FlattenTestCase):
"""
Tests for L{TagLoader}.
"""
def setUp(self) -> None:
self.loader = TagLoader(tags.i("test"))
def test_interface(self) -> None:
"""
An instance of L{TagLoader} provides L{ITemplateLoader}.
"""
self.assertTrue(verifyObject(ITemplateLoader, self.loader))
def test_loadsList(self) -> None:
"""
L{TagLoader.load} returns a list, per L{ITemplateLoader}.
"""
self.assertIsInstance(self.loader.load(), list)
def test_flatten(self) -> None:
"""
L{TagLoader} can be used in an L{Element}, and flattens as the tag used
to construct the L{TagLoader} would flatten.
"""
e = Element(self.loader)
self.assertFlattensImmediately(e, b"test")
class TestElement(Element):
"""
An L{Element} that can be rendered successfully.
"""
loader = XMLString(
''
"Hello, world."
"
"
)
class TestFailureElement(Element):
"""
An L{Element} that can be used in place of L{FailureElement} to verify
that L{renderElement} can render failures properly.
"""
loader = XMLString(
''
"I failed."
"
"
)
def __init__(self, failure: Failure, loader: object = None) -> None:
self.failure = failure
class FailingElement(Element):
"""
An element that raises an exception when rendered.
"""
def render(self, request: Optional[IRequest]) -> "Flattenable":
a = 42
b = 0
return f"{a // b}"
class FakeSite:
"""
A minimal L{Site} object that we can use to test displayTracebacks
"""
displayTracebacks = False
@implementer(IRequest)
class DummyRenderRequest(DummyRequest): # type: ignore[misc]
"""
A dummy request object that has a C{site} attribute.
This does not implement the full IRequest interface, but enough of it
for this test suite.
"""
def __init__(self) -> None:
super().__init__([b""])
self.site = FakeSite()
class RenderElementTests(TestCase):
"""
Test L{renderElement}
"""
def setUp(self) -> None:
"""
Set up a common L{DummyRenderRequest}.
"""
self.request = DummyRenderRequest()
def test_simpleRender(self) -> Deferred[None]:
"""
L{renderElement} returns NOT_DONE_YET and eventually
writes the rendered L{Element} to the request before finishing the
request.
"""
element = TestElement()
d = self.request.notifyFinish()
def check(_: object) -> None:
self.assertEqual(
b"".join(self.request.written),
b"\n" b"Hello, world.
",
)
self.assertTrue(self.request.finished)
d.addCallback(check)
self.assertIdentical(NOT_DONE_YET, renderElement(self.request, element))
return d
def test_simpleFailure(self) -> Deferred[None]:
"""
L{renderElement} handles failures by writing a minimal
error message to the request and finishing it.
"""
element = FailingElement()
d = self.request.notifyFinish()
def check(_: object) -> None:
flushed = self.flushLoggedErrors(FlattenerError)
self.assertEqual(len(flushed), 1)
self.assertEqual(
b"".join(self.request.written),
(
b"\n"
b'An error occurred while rendering the response.
'
),
)
self.assertTrue(self.request.finished)
d.addCallback(check)
self.assertIdentical(NOT_DONE_YET, renderElement(self.request, element))
return d
def test_simpleFailureWithTraceback(self) -> Deferred[None]:
"""
L{renderElement} will render a traceback when rendering of
the element fails and our site is configured to display tracebacks.
"""
logObserver = EventLoggingObserver.createWithCleanup(self, globalLogPublisher)
self.request.site.displayTracebacks = True
element = FailingElement()
d = self.request.notifyFinish()
def check(_: object) -> None:
self.assertEquals(1, len(logObserver))
f = logObserver[0]["log_failure"]
self.assertIsInstance(f.value, FlattenerError)
flushed = self.flushLoggedErrors(FlattenerError)
self.assertEqual(len(flushed), 1)
self.assertEqual(
b"".join(self.request.written), b"\nI failed.
"
)
self.assertTrue(self.request.finished)
d.addCallback(check)
renderElement(self.request, element, _failElement=TestFailureElement)
return d
def test_nonDefaultDoctype(self) -> Deferred[None]:
"""
L{renderElement} will write the doctype string specified by the
doctype keyword argument.
"""
element = TestElement()
d = self.request.notifyFinish()
def check(_: object) -> None:
self.assertEqual(
b"".join(self.request.written),
(
b'\n'
b"Hello, world.
"
),
)
d.addCallback(check)
renderElement(
self.request,
element,
doctype=(
b''
),
)
return d
def test_noneDoctype(self) -> Deferred[None]:
"""
L{renderElement} will not write out a doctype if the doctype keyword
argument is L{None}.
"""
element = TestElement()
d = self.request.notifyFinish()
def check(_: object) -> None:
self.assertEqual(b"".join(self.request.written), b"Hello, world.
")
d.addCallback(check)
renderElement(self.request, element, doctype=None)
return d