/Lib/test/test_wsgiref.py

http://unladen-swallow.googlecode.com/ · Python · 612 lines · 609 code · 3 blank · 0 comment · 3 complexity · 12088bb311360c320c0529601c1ed705 MD5 · raw file

  1. from __future__ import nested_scopes # Backward compat for 2.1
  2. from unittest import TestCase
  3. from wsgiref.util import setup_testing_defaults
  4. from wsgiref.headers import Headers
  5. from wsgiref.handlers import BaseHandler, BaseCGIHandler
  6. from wsgiref import util
  7. from wsgiref.validate import validator
  8. from wsgiref.simple_server import WSGIServer, WSGIRequestHandler, demo_app
  9. from wsgiref.simple_server import make_server
  10. from StringIO import StringIO
  11. from SocketServer import BaseServer
  12. import re, sys
  13. from test import test_support
  14. class MockServer(WSGIServer):
  15. """Non-socket HTTP server"""
  16. def __init__(self, server_address, RequestHandlerClass):
  17. BaseServer.__init__(self, server_address, RequestHandlerClass)
  18. self.server_bind()
  19. def server_bind(self):
  20. host, port = self.server_address
  21. self.server_name = host
  22. self.server_port = port
  23. self.setup_environ()
  24. class MockHandler(WSGIRequestHandler):
  25. """Non-socket HTTP handler"""
  26. def setup(self):
  27. self.connection = self.request
  28. self.rfile, self.wfile = self.connection
  29. def finish(self):
  30. pass
  31. def hello_app(environ,start_response):
  32. start_response("200 OK", [
  33. ('Content-Type','text/plain'),
  34. ('Date','Mon, 05 Jun 2006 18:49:54 GMT')
  35. ])
  36. return ["Hello, world!"]
  37. def run_amock(app=hello_app, data="GET / HTTP/1.0\n\n"):
  38. server = make_server("", 80, app, MockServer, MockHandler)
  39. inp, out, err, olderr = StringIO(data), StringIO(), StringIO(), sys.stderr
  40. sys.stderr = err
  41. try:
  42. server.finish_request((inp,out), ("127.0.0.1",8888))
  43. finally:
  44. sys.stderr = olderr
  45. return out.getvalue(), err.getvalue()
  46. def compare_generic_iter(make_it,match):
  47. """Utility to compare a generic 2.1/2.2+ iterator with an iterable
  48. If running under Python 2.2+, this tests the iterator using iter()/next(),
  49. as well as __getitem__. 'make_it' must be a function returning a fresh
  50. iterator to be tested (since this may test the iterator twice)."""
  51. it = make_it()
  52. n = 0
  53. for item in match:
  54. if not it[n]==item: raise AssertionError
  55. n+=1
  56. try:
  57. it[n]
  58. except IndexError:
  59. pass
  60. else:
  61. raise AssertionError("Too many items from __getitem__",it)
  62. try:
  63. iter, StopIteration
  64. except NameError:
  65. pass
  66. else:
  67. # Only test iter mode under 2.2+
  68. it = make_it()
  69. if not iter(it) is it: raise AssertionError
  70. for item in match:
  71. if not it.next()==item: raise AssertionError
  72. try:
  73. it.next()
  74. except StopIteration:
  75. pass
  76. else:
  77. raise AssertionError("Too many items from .next()",it)
  78. class IntegrationTests(TestCase):
  79. def check_hello(self, out, has_length=True):
  80. self.assertEqual(out,
  81. "HTTP/1.0 200 OK\r\n"
  82. "Server: WSGIServer/0.1 Python/"+sys.version.split()[0]+"\r\n"
  83. "Content-Type: text/plain\r\n"
  84. "Date: Mon, 05 Jun 2006 18:49:54 GMT\r\n" +
  85. (has_length and "Content-Length: 13\r\n" or "") +
  86. "\r\n"
  87. "Hello, world!"
  88. )
  89. def test_plain_hello(self):
  90. out, err = run_amock()
  91. self.check_hello(out)
  92. def test_validated_hello(self):
  93. out, err = run_amock(validator(hello_app))
  94. # the middleware doesn't support len(), so content-length isn't there
  95. self.check_hello(out, has_length=False)
  96. def test_simple_validation_error(self):
  97. def bad_app(environ,start_response):
  98. start_response("200 OK", ('Content-Type','text/plain'))
  99. return ["Hello, world!"]
  100. out, err = run_amock(validator(bad_app))
  101. self.failUnless(out.endswith(
  102. "A server error occurred. Please contact the administrator."
  103. ))
  104. self.assertEqual(
  105. err.splitlines()[-2],
  106. "AssertionError: Headers (('Content-Type', 'text/plain')) must"
  107. " be of type list: <type 'tuple'>"
  108. )
  109. class UtilityTests(TestCase):
  110. def checkShift(self,sn_in,pi_in,part,sn_out,pi_out):
  111. env = {'SCRIPT_NAME':sn_in,'PATH_INFO':pi_in}
  112. util.setup_testing_defaults(env)
  113. self.assertEqual(util.shift_path_info(env),part)
  114. self.assertEqual(env['PATH_INFO'],pi_out)
  115. self.assertEqual(env['SCRIPT_NAME'],sn_out)
  116. return env
  117. def checkDefault(self, key, value, alt=None):
  118. # Check defaulting when empty
  119. env = {}
  120. util.setup_testing_defaults(env)
  121. if isinstance(value,StringIO):
  122. self.failUnless(isinstance(env[key],StringIO))
  123. else:
  124. self.assertEqual(env[key],value)
  125. # Check existing value
  126. env = {key:alt}
  127. util.setup_testing_defaults(env)
  128. self.failUnless(env[key] is alt)
  129. def checkCrossDefault(self,key,value,**kw):
  130. util.setup_testing_defaults(kw)
  131. self.assertEqual(kw[key],value)
  132. def checkAppURI(self,uri,**kw):
  133. util.setup_testing_defaults(kw)
  134. self.assertEqual(util.application_uri(kw),uri)
  135. def checkReqURI(self,uri,query=1,**kw):
  136. util.setup_testing_defaults(kw)
  137. self.assertEqual(util.request_uri(kw,query),uri)
  138. def checkFW(self,text,size,match):
  139. def make_it(text=text,size=size):
  140. return util.FileWrapper(StringIO(text),size)
  141. compare_generic_iter(make_it,match)
  142. it = make_it()
  143. self.failIf(it.filelike.closed)
  144. for item in it:
  145. pass
  146. self.failIf(it.filelike.closed)
  147. it.close()
  148. self.failUnless(it.filelike.closed)
  149. def testSimpleShifts(self):
  150. self.checkShift('','/', '', '/', '')
  151. self.checkShift('','/x', 'x', '/x', '')
  152. self.checkShift('/','', None, '/', '')
  153. self.checkShift('/a','/x/y', 'x', '/a/x', '/y')
  154. self.checkShift('/a','/x/', 'x', '/a/x', '/')
  155. def testNormalizedShifts(self):
  156. self.checkShift('/a/b', '/../y', '..', '/a', '/y')
  157. self.checkShift('', '/../y', '..', '', '/y')
  158. self.checkShift('/a/b', '//y', 'y', '/a/b/y', '')
  159. self.checkShift('/a/b', '//y/', 'y', '/a/b/y', '/')
  160. self.checkShift('/a/b', '/./y', 'y', '/a/b/y', '')
  161. self.checkShift('/a/b', '/./y/', 'y', '/a/b/y', '/')
  162. self.checkShift('/a/b', '///./..//y/.//', '..', '/a', '/y/')
  163. self.checkShift('/a/b', '///', '', '/a/b/', '')
  164. self.checkShift('/a/b', '/.//', '', '/a/b/', '')
  165. self.checkShift('/a/b', '/x//', 'x', '/a/b/x', '/')
  166. self.checkShift('/a/b', '/.', None, '/a/b', '')
  167. def testDefaults(self):
  168. for key, value in [
  169. ('SERVER_NAME','127.0.0.1'),
  170. ('SERVER_PORT', '80'),
  171. ('SERVER_PROTOCOL','HTTP/1.0'),
  172. ('HTTP_HOST','127.0.0.1'),
  173. ('REQUEST_METHOD','GET'),
  174. ('SCRIPT_NAME',''),
  175. ('PATH_INFO','/'),
  176. ('wsgi.version', (1,0)),
  177. ('wsgi.run_once', 0),
  178. ('wsgi.multithread', 0),
  179. ('wsgi.multiprocess', 0),
  180. ('wsgi.input', StringIO("")),
  181. ('wsgi.errors', StringIO()),
  182. ('wsgi.url_scheme','http'),
  183. ]:
  184. self.checkDefault(key,value)
  185. def testCrossDefaults(self):
  186. self.checkCrossDefault('HTTP_HOST',"foo.bar",SERVER_NAME="foo.bar")
  187. self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="on")
  188. self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="1")
  189. self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="yes")
  190. self.checkCrossDefault('wsgi.url_scheme',"http",HTTPS="foo")
  191. self.checkCrossDefault('SERVER_PORT',"80",HTTPS="foo")
  192. self.checkCrossDefault('SERVER_PORT',"443",HTTPS="on")
  193. def testGuessScheme(self):
  194. self.assertEqual(util.guess_scheme({}), "http")
  195. self.assertEqual(util.guess_scheme({'HTTPS':"foo"}), "http")
  196. self.assertEqual(util.guess_scheme({'HTTPS':"on"}), "https")
  197. self.assertEqual(util.guess_scheme({'HTTPS':"yes"}), "https")
  198. self.assertEqual(util.guess_scheme({'HTTPS':"1"}), "https")
  199. def testAppURIs(self):
  200. self.checkAppURI("http://127.0.0.1/")
  201. self.checkAppURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
  202. self.checkAppURI("http://spam.example.com:2071/",
  203. HTTP_HOST="spam.example.com:2071", SERVER_PORT="2071")
  204. self.checkAppURI("http://spam.example.com/",
  205. SERVER_NAME="spam.example.com")
  206. self.checkAppURI("http://127.0.0.1/",
  207. HTTP_HOST="127.0.0.1", SERVER_NAME="spam.example.com")
  208. self.checkAppURI("https://127.0.0.1/", HTTPS="on")
  209. self.checkAppURI("http://127.0.0.1:8000/", SERVER_PORT="8000",
  210. HTTP_HOST=None)
  211. def testReqURIs(self):
  212. self.checkReqURI("http://127.0.0.1/")
  213. self.checkReqURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
  214. self.checkReqURI("http://127.0.0.1/spammity/spam",
  215. SCRIPT_NAME="/spammity", PATH_INFO="/spam")
  216. self.checkReqURI("http://127.0.0.1/spammity/spam?say=ni",
  217. SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
  218. self.checkReqURI("http://127.0.0.1/spammity/spam", 0,
  219. SCRIPT_NAME="/spammity", PATH_INFO="/spam",QUERY_STRING="say=ni")
  220. def testFileWrapper(self):
  221. self.checkFW("xyz"*50, 120, ["xyz"*40,"xyz"*10])
  222. def testHopByHop(self):
  223. for hop in (
  224. "Connection Keep-Alive Proxy-Authenticate Proxy-Authorization "
  225. "TE Trailers Transfer-Encoding Upgrade"
  226. ).split():
  227. for alt in hop, hop.title(), hop.upper(), hop.lower():
  228. self.failUnless(util.is_hop_by_hop(alt))
  229. # Not comprehensive, just a few random header names
  230. for hop in (
  231. "Accept Cache-Control Date Pragma Trailer Via Warning"
  232. ).split():
  233. for alt in hop, hop.title(), hop.upper(), hop.lower():
  234. self.failIf(util.is_hop_by_hop(alt))
  235. class HeaderTests(TestCase):
  236. def testMappingInterface(self):
  237. test = [('x','y')]
  238. self.assertEqual(len(Headers([])),0)
  239. self.assertEqual(len(Headers(test[:])),1)
  240. self.assertEqual(Headers(test[:]).keys(), ['x'])
  241. self.assertEqual(Headers(test[:]).values(), ['y'])
  242. self.assertEqual(Headers(test[:]).items(), test)
  243. self.failIf(Headers(test).items() is test) # must be copy!
  244. h=Headers([])
  245. del h['foo'] # should not raise an error
  246. h['Foo'] = 'bar'
  247. for m in h.has_key, h.__contains__, h.get, h.get_all, h.__getitem__:
  248. self.failUnless(m('foo'))
  249. self.failUnless(m('Foo'))
  250. self.failUnless(m('FOO'))
  251. self.failIf(m('bar'))
  252. self.assertEqual(h['foo'],'bar')
  253. h['foo'] = 'baz'
  254. self.assertEqual(h['FOO'],'baz')
  255. self.assertEqual(h.get_all('foo'),['baz'])
  256. self.assertEqual(h.get("foo","whee"), "baz")
  257. self.assertEqual(h.get("zoo","whee"), "whee")
  258. self.assertEqual(h.setdefault("foo","whee"), "baz")
  259. self.assertEqual(h.setdefault("zoo","whee"), "whee")
  260. self.assertEqual(h["foo"],"baz")
  261. self.assertEqual(h["zoo"],"whee")
  262. def testRequireList(self):
  263. self.assertRaises(TypeError, Headers, "foo")
  264. def testExtras(self):
  265. h = Headers([])
  266. self.assertEqual(str(h),'\r\n')
  267. h.add_header('foo','bar',baz="spam")
  268. self.assertEqual(h['foo'], 'bar; baz="spam"')
  269. self.assertEqual(str(h),'foo: bar; baz="spam"\r\n\r\n')
  270. h.add_header('Foo','bar',cheese=None)
  271. self.assertEqual(h.get_all('foo'),
  272. ['bar; baz="spam"', 'bar; cheese'])
  273. self.assertEqual(str(h),
  274. 'foo: bar; baz="spam"\r\n'
  275. 'Foo: bar; cheese\r\n'
  276. '\r\n'
  277. )
  278. class ErrorHandler(BaseCGIHandler):
  279. """Simple handler subclass for testing BaseHandler"""
  280. def __init__(self,**kw):
  281. setup_testing_defaults(kw)
  282. BaseCGIHandler.__init__(
  283. self, StringIO(''), StringIO(), StringIO(), kw,
  284. multithread=True, multiprocess=True
  285. )
  286. class TestHandler(ErrorHandler):
  287. """Simple handler subclass for testing BaseHandler, w/error passthru"""
  288. def handle_error(self):
  289. raise # for testing, we want to see what's happening
  290. class HandlerTests(TestCase):
  291. def checkEnvironAttrs(self, handler):
  292. env = handler.environ
  293. for attr in [
  294. 'version','multithread','multiprocess','run_once','file_wrapper'
  295. ]:
  296. if attr=='file_wrapper' and handler.wsgi_file_wrapper is None:
  297. continue
  298. self.assertEqual(getattr(handler,'wsgi_'+attr),env['wsgi.'+attr])
  299. def checkOSEnviron(self,handler):
  300. empty = {}; setup_testing_defaults(empty)
  301. env = handler.environ
  302. from os import environ
  303. for k,v in environ.items():
  304. if not empty.has_key(k):
  305. self.assertEqual(env[k],v)
  306. for k,v in empty.items():
  307. self.failUnless(env.has_key(k))
  308. def testEnviron(self):
  309. h = TestHandler(X="Y")
  310. h.setup_environ()
  311. self.checkEnvironAttrs(h)
  312. self.checkOSEnviron(h)
  313. self.assertEqual(h.environ["X"],"Y")
  314. def testCGIEnviron(self):
  315. h = BaseCGIHandler(None,None,None,{})
  316. h.setup_environ()
  317. for key in 'wsgi.url_scheme', 'wsgi.input', 'wsgi.errors':
  318. self.assert_(h.environ.has_key(key))
  319. def testScheme(self):
  320. h=TestHandler(HTTPS="on"); h.setup_environ()
  321. self.assertEqual(h.environ['wsgi.url_scheme'],'https')
  322. h=TestHandler(); h.setup_environ()
  323. self.assertEqual(h.environ['wsgi.url_scheme'],'http')
  324. def testAbstractMethods(self):
  325. h = BaseHandler()
  326. for name in [
  327. '_flush','get_stdin','get_stderr','add_cgi_vars'
  328. ]:
  329. self.assertRaises(NotImplementedError, getattr(h,name))
  330. self.assertRaises(NotImplementedError, h._write, "test")
  331. def testContentLength(self):
  332. # Demo one reason iteration is better than write()... ;)
  333. def trivial_app1(e,s):
  334. s('200 OK',[])
  335. return [e['wsgi.url_scheme']]
  336. def trivial_app2(e,s):
  337. s('200 OK',[])(e['wsgi.url_scheme'])
  338. return []
  339. h = TestHandler()
  340. h.run(trivial_app1)
  341. self.assertEqual(h.stdout.getvalue(),
  342. "Status: 200 OK\r\n"
  343. "Content-Length: 4\r\n"
  344. "\r\n"
  345. "http")
  346. h = TestHandler()
  347. h.run(trivial_app2)
  348. self.assertEqual(h.stdout.getvalue(),
  349. "Status: 200 OK\r\n"
  350. "\r\n"
  351. "http")
  352. def testBasicErrorOutput(self):
  353. def non_error_app(e,s):
  354. s('200 OK',[])
  355. return []
  356. def error_app(e,s):
  357. raise AssertionError("This should be caught by handler")
  358. h = ErrorHandler()
  359. h.run(non_error_app)
  360. self.assertEqual(h.stdout.getvalue(),
  361. "Status: 200 OK\r\n"
  362. "Content-Length: 0\r\n"
  363. "\r\n")
  364. self.assertEqual(h.stderr.getvalue(),"")
  365. h = ErrorHandler()
  366. h.run(error_app)
  367. self.assertEqual(h.stdout.getvalue(),
  368. "Status: %s\r\n"
  369. "Content-Type: text/plain\r\n"
  370. "Content-Length: %d\r\n"
  371. "\r\n%s" % (h.error_status,len(h.error_body),h.error_body))
  372. self.failUnless(h.stderr.getvalue().find("AssertionError")<>-1)
  373. def testErrorAfterOutput(self):
  374. MSG = "Some output has been sent"
  375. def error_app(e,s):
  376. s("200 OK",[])(MSG)
  377. raise AssertionError("This should be caught by handler")
  378. h = ErrorHandler()
  379. h.run(error_app)
  380. self.assertEqual(h.stdout.getvalue(),
  381. "Status: 200 OK\r\n"
  382. "\r\n"+MSG)
  383. self.failUnless(h.stderr.getvalue().find("AssertionError")<>-1)
  384. def testHeaderFormats(self):
  385. def non_error_app(e,s):
  386. s('200 OK',[])
  387. return []
  388. stdpat = (
  389. r"HTTP/%s 200 OK\r\n"
  390. r"Date: \w{3}, [ 0123]\d \w{3} \d{4} \d\d:\d\d:\d\d GMT\r\n"
  391. r"%s" r"Content-Length: 0\r\n" r"\r\n"
  392. )
  393. shortpat = (
  394. "Status: 200 OK\r\n" "Content-Length: 0\r\n" "\r\n"
  395. )
  396. for ssw in "FooBar/1.0", None:
  397. sw = ssw and "Server: %s\r\n" % ssw or ""
  398. for version in "1.0", "1.1":
  399. for proto in "HTTP/0.9", "HTTP/1.0", "HTTP/1.1":
  400. h = TestHandler(SERVER_PROTOCOL=proto)
  401. h.origin_server = False
  402. h.http_version = version
  403. h.server_software = ssw
  404. h.run(non_error_app)
  405. self.assertEqual(shortpat,h.stdout.getvalue())
  406. h = TestHandler(SERVER_PROTOCOL=proto)
  407. h.origin_server = True
  408. h.http_version = version
  409. h.server_software = ssw
  410. h.run(non_error_app)
  411. if proto=="HTTP/0.9":
  412. self.assertEqual(h.stdout.getvalue(),"")
  413. else:
  414. self.failUnless(
  415. re.match(stdpat%(version,sw), h.stdout.getvalue()),
  416. (stdpat%(version,sw), h.stdout.getvalue())
  417. )
  418. # This epilogue is needed for compatibility with the Python 2.5 regrtest module
  419. def test_main():
  420. test_support.run_unittest(__name__)
  421. if __name__ == "__main__":
  422. test_main()
  423. # the above lines intentionally left blank