test_tap.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.web.tap}.
  5. """
  6. from __future__ import absolute_import, division
  7. import os
  8. import stat
  9. from twisted.internet import reactor, endpoints
  10. from twisted.internet.interfaces import IReactorUNIX
  11. from twisted.python.filepath import FilePath
  12. from twisted.python.reflect import requireModule
  13. from twisted.python.threadpool import ThreadPool
  14. from twisted.python.usage import UsageError
  15. from twisted.spread.pb import PBServerFactory
  16. from twisted.trial.unittest import TestCase
  17. from twisted.web.distrib import ResourcePublisher, UserDirectory
  18. from twisted.web.script import PythonScript
  19. from twisted.web.server import Site
  20. from twisted.web.static import Data, File
  21. from twisted.web.tap import Options, makeService
  22. from twisted.web.tap import makePersonalServerFactory
  23. from twisted.web.twcgi import CGIScript
  24. from twisted.web.wsgi import WSGIResource
  25. application = object()
  26. class ServiceTests(TestCase):
  27. """
  28. Tests for the service creation APIs in L{twisted.web.tap}.
  29. """
  30. def _pathOption(self):
  31. """
  32. Helper for the I{--path} tests which creates a directory and creates
  33. an L{Options} object which uses that directory as its static
  34. filesystem root.
  35. @return: A two-tuple of a L{FilePath} referring to the directory and
  36. the value associated with the C{'root'} key in the L{Options}
  37. instance after parsing a I{--path} option.
  38. """
  39. path = FilePath(self.mktemp())
  40. path.makedirs()
  41. options = Options()
  42. options.parseOptions(['--path', path.path])
  43. root = options['root']
  44. return path, root
  45. def test_path(self):
  46. """
  47. The I{--path} option causes L{Options} to create a root resource
  48. which serves responses from the specified path.
  49. """
  50. path, root = self._pathOption()
  51. self.assertIsInstance(root, File)
  52. self.assertEqual(root.path, path.path)
  53. def test_pathServer(self):
  54. """
  55. The I{--path} option to L{makeService} causes it to return a service
  56. which will listen on the server address given by the I{--port} option.
  57. """
  58. path = FilePath(self.mktemp())
  59. path.makedirs()
  60. port = self.mktemp()
  61. options = Options()
  62. options.parseOptions(['--port', 'unix:' + port, '--path', path.path])
  63. service = makeService(options)
  64. service.startService()
  65. self.addCleanup(service.stopService)
  66. self.assertIsInstance(service.services[0].factory.resource, File)
  67. self.assertEqual(service.services[0].factory.resource.path, path.path)
  68. self.assertTrue(os.path.exists(port))
  69. self.assertTrue(stat.S_ISSOCK(os.stat(port).st_mode))
  70. if not IReactorUNIX.providedBy(reactor):
  71. test_pathServer.skip = (
  72. "The reactor does not support UNIX domain sockets")
  73. def test_cgiProcessor(self):
  74. """
  75. The I{--path} option creates a root resource which serves a
  76. L{CGIScript} instance for any child with the C{".cgi"} extension.
  77. """
  78. path, root = self._pathOption()
  79. path.child("foo.cgi").setContent(b"")
  80. self.assertIsInstance(root.getChild("foo.cgi", None), CGIScript)
  81. def test_epyProcessor(self):
  82. """
  83. The I{--path} option creates a root resource which serves a
  84. L{PythonScript} instance for any child with the C{".epy"} extension.
  85. """
  86. path, root = self._pathOption()
  87. path.child("foo.epy").setContent(b"")
  88. self.assertIsInstance(root.getChild("foo.epy", None), PythonScript)
  89. def test_rpyProcessor(self):
  90. """
  91. The I{--path} option creates a root resource which serves the
  92. C{resource} global defined by the Python source in any child with
  93. the C{".rpy"} extension.
  94. """
  95. path, root = self._pathOption()
  96. path.child("foo.rpy").setContent(
  97. b"from twisted.web.static import Data\n"
  98. b"resource = Data('content', 'major/minor')\n")
  99. child = root.getChild("foo.rpy", None)
  100. self.assertIsInstance(child, Data)
  101. self.assertEqual(child.data, 'content')
  102. self.assertEqual(child.type, 'major/minor')
  103. def test_makePersonalServerFactory(self):
  104. """
  105. L{makePersonalServerFactory} returns a PB server factory which has
  106. as its root object a L{ResourcePublisher}.
  107. """
  108. # The fact that this pile of objects can actually be used somehow is
  109. # verified by twisted.web.test.test_distrib.
  110. site = Site(Data(b"foo bar", "text/plain"))
  111. serverFactory = makePersonalServerFactory(site)
  112. self.assertIsInstance(serverFactory, PBServerFactory)
  113. self.assertIsInstance(serverFactory.root, ResourcePublisher)
  114. self.assertIdentical(serverFactory.root.site, site)
  115. def test_personalServer(self):
  116. """
  117. The I{--personal} option to L{makeService} causes it to return a
  118. service which will listen on the server address given by the I{--port}
  119. option.
  120. """
  121. port = self.mktemp()
  122. options = Options()
  123. options.parseOptions(['--port', 'unix:' + port, '--personal'])
  124. service = makeService(options)
  125. service.startService()
  126. self.addCleanup(service.stopService)
  127. self.assertTrue(os.path.exists(port))
  128. self.assertTrue(stat.S_ISSOCK(os.stat(port).st_mode))
  129. if not IReactorUNIX.providedBy(reactor):
  130. test_personalServer.skip = (
  131. "The reactor does not support UNIX domain sockets")
  132. def test_defaultPersonalPath(self):
  133. """
  134. If the I{--port} option not specified but the I{--personal} option is,
  135. L{Options} defaults the port to C{UserDirectory.userSocketName} in the
  136. user's home directory.
  137. """
  138. options = Options()
  139. options.parseOptions(['--personal'])
  140. path = os.path.expanduser(
  141. os.path.join('~', UserDirectory.userSocketName))
  142. self.assertEqual(
  143. endpoints._parseServer(options['port'], None)[:2],
  144. ('UNIX', (path, None)))
  145. if not IReactorUNIX.providedBy(reactor):
  146. test_defaultPersonalPath.skip = (
  147. "The reactor does not support UNIX domain sockets")
  148. def test_defaultPort(self):
  149. """
  150. If the I{--port} option is not specified, L{Options} defaults the port
  151. to C{8080}.
  152. """
  153. options = Options()
  154. options.parseOptions([])
  155. self.assertEqual(
  156. endpoints._parseServer(options['port'], None)[:2],
  157. ('TCP', (8080, None)))
  158. def test_wsgi(self):
  159. """
  160. The I{--wsgi} option takes the fully-qualifed Python name of a WSGI
  161. application object and creates a L{WSGIResource} at the root which
  162. serves that application.
  163. """
  164. options = Options()
  165. options.parseOptions(['--wsgi', __name__ + '.application'])
  166. root = options['root']
  167. self.assertTrue(root, WSGIResource)
  168. self.assertIdentical(root._reactor, reactor)
  169. self.assertTrue(isinstance(root._threadpool, ThreadPool))
  170. self.assertIdentical(root._application, application)
  171. # The threadpool should start and stop with the reactor.
  172. self.assertFalse(root._threadpool.started)
  173. reactor.fireSystemEvent('startup')
  174. self.assertTrue(root._threadpool.started)
  175. self.assertFalse(root._threadpool.joined)
  176. reactor.fireSystemEvent('shutdown')
  177. self.assertTrue(root._threadpool.joined)
  178. def test_invalidApplication(self):
  179. """
  180. If I{--wsgi} is given an invalid name, L{Options.parseOptions}
  181. raises L{UsageError}.
  182. """
  183. options = Options()
  184. for name in [__name__ + '.nosuchthing', 'foo.']:
  185. exc = self.assertRaises(
  186. UsageError, options.parseOptions, ['--wsgi', name])
  187. self.assertEqual(str(exc),
  188. "No such WSGI application: %r" % (name,))
  189. def test_HTTPSFailureOnMissingSSL(self):
  190. """
  191. An L{UsageError} is raised when C{https} is requested but there is no
  192. support for SSL.
  193. """
  194. options = Options()
  195. exception = self.assertRaises(
  196. UsageError, options.parseOptions, ['--https=443'])
  197. self.assertEqual('SSL support not installed', exception.args[0])
  198. if requireModule('OpenSSL.SSL') is not None:
  199. test_HTTPSFailureOnMissingSSL.skip = 'SSL module is available.'
  200. def test_HTTPSAcceptedOnAvailableSSL(self):
  201. """
  202. When SSL support is present, it accepts the --https option.
  203. """
  204. options = Options()
  205. options.parseOptions(['--https=443'])
  206. self.assertEqual('443', options['https'])
  207. if requireModule('OpenSSL.SSL') is None:
  208. test_HTTPSAcceptedOnAvailableSSL.skip = 'SSL module is not available.'