test_util.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.web.util}.
  5. """
  6. from __future__ import absolute_import, division
  7. import gc
  8. from twisted.python.failure import Failure
  9. from twisted.trial.unittest import SynchronousTestCase, TestCase
  10. from twisted.internet import defer
  11. from twisted.python.compat import _PY3, intToBytes, networkString
  12. from twisted.web import resource, util
  13. from twisted.web.error import FlattenerError
  14. from twisted.web.http import FOUND
  15. from twisted.web.server import Request
  16. from twisted.web.template import TagLoader, flattenString, tags
  17. from twisted.web.test.requesthelper import DummyChannel, DummyRequest
  18. from twisted.web.util import DeferredResource
  19. from twisted.web.util import _SourceFragmentElement, _FrameElement
  20. from twisted.web.util import _StackElement, FailureElement, formatFailure
  21. from twisted.web.util import redirectTo, _SourceLineElement
  22. class RedirectToTests(TestCase):
  23. """
  24. Tests for L{redirectTo}.
  25. """
  26. def test_headersAndCode(self):
  27. """
  28. L{redirectTo} will set the C{Location} and C{Content-Type} headers on
  29. its request, and set the response code to C{FOUND}, so the browser will
  30. be redirected.
  31. """
  32. request = Request(DummyChannel(), True)
  33. request.method = b'GET'
  34. targetURL = b"http://target.example.com/4321"
  35. redirectTo(targetURL, request)
  36. self.assertEqual(request.code, FOUND)
  37. self.assertEqual(
  38. request.responseHeaders.getRawHeaders(b'location'), [targetURL])
  39. self.assertEqual(
  40. request.responseHeaders.getRawHeaders(b'content-type'),
  41. [b'text/html; charset=utf-8'])
  42. def test_redirectToUnicodeURL(self) :
  43. """
  44. L{redirectTo} will raise TypeError if unicode object is passed in URL
  45. """
  46. request = Request(DummyChannel(), True)
  47. request.method = b'GET'
  48. targetURL = u'http://target.example.com/4321'
  49. self.assertRaises(TypeError, redirectTo, targetURL, request)
  50. class FailureElementTests(TestCase):
  51. """
  52. Tests for L{FailureElement} and related helpers which can render a
  53. L{Failure} as an HTML string.
  54. """
  55. def setUp(self):
  56. """
  57. Create a L{Failure} which can be used by the rendering tests.
  58. """
  59. def lineNumberProbeAlsoBroken():
  60. message = "This is a problem"
  61. raise Exception(message)
  62. # Figure out the line number from which the exception will be raised.
  63. self.base = lineNumberProbeAlsoBroken.__code__.co_firstlineno + 1
  64. try:
  65. lineNumberProbeAlsoBroken()
  66. except:
  67. self.failure = Failure(captureVars=True)
  68. self.frame = self.failure.frames[-1]
  69. def test_sourceLineElement(self):
  70. """
  71. L{_SourceLineElement} renders a source line and line number.
  72. """
  73. element = _SourceLineElement(
  74. TagLoader(tags.div(
  75. tags.span(render="lineNumber"),
  76. tags.span(render="sourceLine"))),
  77. 50, " print 'hello'")
  78. d = flattenString(None, element)
  79. expected = (
  80. u"<div><span>50</span><span>"
  81. u" \N{NO-BREAK SPACE} \N{NO-BREAK SPACE}print 'hello'</span></div>")
  82. d.addCallback(
  83. self.assertEqual, expected.encode('utf-8'))
  84. return d
  85. def test_sourceFragmentElement(self):
  86. """
  87. L{_SourceFragmentElement} renders source lines at and around the line
  88. number indicated by a frame object.
  89. """
  90. element = _SourceFragmentElement(
  91. TagLoader(tags.div(
  92. tags.span(render="lineNumber"),
  93. tags.span(render="sourceLine"),
  94. render="sourceLines")),
  95. self.frame)
  96. source = [
  97. u' \N{NO-BREAK SPACE} \N{NO-BREAK SPACE}message = '
  98. u'"This is a problem"',
  99. u' \N{NO-BREAK SPACE} \N{NO-BREAK SPACE}raise Exception(message)',
  100. u'# Figure out the line number from which the exception will be '
  101. u'raised.',
  102. ]
  103. d = flattenString(None, element)
  104. if _PY3:
  105. stringToCheckFor = ''.join([
  106. '<div class="snippet%sLine"><span>%d</span><span>%s</span>'
  107. '</div>' % (
  108. ["", "Highlight"][lineNumber == 1],
  109. self.base + lineNumber,
  110. (u" \N{NO-BREAK SPACE}" * 4 + sourceLine))
  111. for (lineNumber, sourceLine)
  112. in enumerate(source)]).encode("utf8")
  113. else:
  114. stringToCheckFor = ''.join([
  115. '<div class="snippet%sLine"><span>%d</span><span>%s</span>'
  116. '</div>' % (
  117. ["", "Highlight"][lineNumber == 1],
  118. self.base + lineNumber,
  119. (u" \N{NO-BREAK SPACE}" * 4 + sourceLine).encode('utf8'))
  120. for (lineNumber, sourceLine)
  121. in enumerate(source)])
  122. d.addCallback(self.assertEqual, stringToCheckFor)
  123. return d
  124. def test_frameElementFilename(self):
  125. """
  126. The I{filename} renderer of L{_FrameElement} renders the filename
  127. associated with the frame object used to initialize the
  128. L{_FrameElement}.
  129. """
  130. element = _FrameElement(
  131. TagLoader(tags.span(render="filename")),
  132. self.frame)
  133. d = flattenString(None, element)
  134. d.addCallback(
  135. # __file__ differs depending on whether an up-to-date .pyc file
  136. # already existed.
  137. self.assertEqual,
  138. b"<span>" + networkString(__file__.rstrip('c')) + b"</span>")
  139. return d
  140. def test_frameElementLineNumber(self):
  141. """
  142. The I{lineNumber} renderer of L{_FrameElement} renders the line number
  143. associated with the frame object used to initialize the
  144. L{_FrameElement}.
  145. """
  146. element = _FrameElement(
  147. TagLoader(tags.span(render="lineNumber")),
  148. self.frame)
  149. d = flattenString(None, element)
  150. d.addCallback(
  151. self.assertEqual, b"<span>" + intToBytes(self.base + 1) + b"</span>")
  152. return d
  153. def test_frameElementFunction(self):
  154. """
  155. The I{function} renderer of L{_FrameElement} renders the line number
  156. associated with the frame object used to initialize the
  157. L{_FrameElement}.
  158. """
  159. element = _FrameElement(
  160. TagLoader(tags.span(render="function")),
  161. self.frame)
  162. d = flattenString(None, element)
  163. d.addCallback(
  164. self.assertEqual, b"<span>lineNumberProbeAlsoBroken</span>")
  165. return d
  166. def test_frameElementSource(self):
  167. """
  168. The I{source} renderer of L{_FrameElement} renders the source code near
  169. the source filename/line number associated with the frame object used to
  170. initialize the L{_FrameElement}.
  171. """
  172. element = _FrameElement(None, self.frame)
  173. renderer = element.lookupRenderMethod("source")
  174. tag = tags.div()
  175. result = renderer(None, tag)
  176. self.assertIsInstance(result, _SourceFragmentElement)
  177. self.assertIdentical(result.frame, self.frame)
  178. self.assertEqual([tag], result.loader.load())
  179. def test_stackElement(self):
  180. """
  181. The I{frames} renderer of L{_StackElement} renders each stack frame in
  182. the list of frames used to initialize the L{_StackElement}.
  183. """
  184. element = _StackElement(None, self.failure.frames[:2])
  185. renderer = element.lookupRenderMethod("frames")
  186. tag = tags.div()
  187. result = renderer(None, tag)
  188. self.assertIsInstance(result, list)
  189. self.assertIsInstance(result[0], _FrameElement)
  190. self.assertIdentical(result[0].frame, self.failure.frames[0])
  191. self.assertIsInstance(result[1], _FrameElement)
  192. self.assertIdentical(result[1].frame, self.failure.frames[1])
  193. # They must not share the same tag object.
  194. self.assertNotEqual(result[0].loader.load(), result[1].loader.load())
  195. self.assertEqual(2, len(result))
  196. def test_failureElementTraceback(self):
  197. """
  198. The I{traceback} renderer of L{FailureElement} renders the failure's
  199. stack frames using L{_StackElement}.
  200. """
  201. element = FailureElement(self.failure)
  202. renderer = element.lookupRenderMethod("traceback")
  203. tag = tags.div()
  204. result = renderer(None, tag)
  205. self.assertIsInstance(result, _StackElement)
  206. self.assertIdentical(result.stackFrames, self.failure.frames)
  207. self.assertEqual([tag], result.loader.load())
  208. def test_failureElementType(self):
  209. """
  210. The I{type} renderer of L{FailureElement} renders the failure's
  211. exception type.
  212. """
  213. element = FailureElement(
  214. self.failure, TagLoader(tags.span(render="type")))
  215. d = flattenString(None, element)
  216. if _PY3:
  217. exc = b"builtins.Exception"
  218. else:
  219. exc = b"exceptions.Exception"
  220. d.addCallback(
  221. self.assertEqual, b"<span>" + exc + b"</span>")
  222. return d
  223. def test_failureElementValue(self):
  224. """
  225. The I{value} renderer of L{FailureElement} renders the value's exception
  226. value.
  227. """
  228. element = FailureElement(
  229. self.failure, TagLoader(tags.span(render="value")))
  230. d = flattenString(None, element)
  231. d.addCallback(
  232. self.assertEqual, b'<span>This is a problem</span>')
  233. return d
  234. class FormatFailureTests(TestCase):
  235. """
  236. Tests for L{twisted.web.util.formatFailure} which returns an HTML string
  237. representing the L{Failure} instance passed to it.
  238. """
  239. def test_flattenerError(self):
  240. """
  241. If there is an error flattening the L{Failure} instance,
  242. L{formatFailure} raises L{FlattenerError}.
  243. """
  244. self.assertRaises(FlattenerError, formatFailure, object())
  245. def test_returnsBytes(self):
  246. """
  247. The return value of L{formatFailure} is a C{str} instance (not a
  248. C{unicode} instance) with numeric character references for any non-ASCII
  249. characters meant to appear in the output.
  250. """
  251. try:
  252. raise Exception("Fake bug")
  253. except:
  254. result = formatFailure(Failure())
  255. self.assertIsInstance(result, bytes)
  256. if _PY3:
  257. self.assertTrue(all(ch < 128 for ch in result))
  258. else:
  259. self.assertTrue(all(ord(ch) < 128 for ch in result))
  260. # Indentation happens to rely on NO-BREAK SPACE
  261. self.assertIn(b"&#160;", result)
  262. class SDResource(resource.Resource):
  263. def __init__(self,default):
  264. self.default = default
  265. def getChildWithDefault(self, name, request):
  266. d = defer.succeed(self.default)
  267. resource = util.DeferredResource(d)
  268. return resource.getChildWithDefault(name, request)
  269. class DeferredResourceTests(SynchronousTestCase):
  270. """
  271. Tests for L{DeferredResource}.
  272. """
  273. def testDeferredResource(self):
  274. r = resource.Resource()
  275. r.isLeaf = 1
  276. s = SDResource(r)
  277. d = DummyRequest(['foo', 'bar', 'baz'])
  278. resource.getChildForRequest(s, d)
  279. self.assertEqual(d.postpath, ['bar', 'baz'])
  280. def test_render(self):
  281. """
  282. L{DeferredResource} uses the request object's C{render} method to
  283. render the resource which is the result of the L{Deferred} being
  284. handled.
  285. """
  286. rendered = []
  287. request = DummyRequest([])
  288. request.render = rendered.append
  289. result = resource.Resource()
  290. deferredResource = DeferredResource(defer.succeed(result))
  291. deferredResource.render(request)
  292. self.assertEqual(rendered, [result])
  293. def test_renderNoFailure(self):
  294. """
  295. If the L{Deferred} fails, L{DeferredResource} reports the failure via
  296. C{processingFailed}, and does not cause an unhandled error to be
  297. logged.
  298. """
  299. request = DummyRequest([])
  300. d = request.notifyFinish()
  301. failure = Failure(RuntimeError())
  302. deferredResource = DeferredResource(defer.fail(failure))
  303. deferredResource.render(request)
  304. self.assertEqual(self.failureResultOf(d), failure)
  305. del deferredResource
  306. gc.collect()
  307. errors = self.flushLoggedErrors(RuntimeError)
  308. self.assertEqual(errors, [])