Add tests using the Redbot library for HTTP validation and caching.

This commit is contained in:
Ben Darnell 2012-12-02 11:13:42 -05:00
parent eea63fc20d
commit 0b1b515fc6
3 changed files with 160 additions and 0 deletions

7
maint/test/redbot/README Normal file
View File

@ -0,0 +1,7 @@
Redbot is an HTTP validator that checks for common problems, especially
related to cacheability. These tests ensure that Tornado's default behavior
is correct (but note that this guarantee does not automatically extend
to applications built on Tornado since application behavior can impact
cacheability.
http://redbot.org/about

View File

@ -0,0 +1,145 @@
#!/usr/bin/env python
import logging
from redbot.droid import ResourceExpertDroid
import redbot.speak as rs
import thor
import threading
from tornado import gen
from tornado.options import parse_command_line
from tornado.testing import AsyncHTTPTestCase, LogTrapTestCase
from tornado.web import RequestHandler, Application, asynchronous
import unittest
class HelloHandler(RequestHandler):
def get(self):
self.write("Hello world")
class RedirectHandler(RequestHandler):
def get(self, path):
self.redirect(path, status=int(self.get_argument('status', '302')))
class PostHandler(RequestHandler):
def post(self):
assert self.get_argument('foo') == 'bar'
self.redirect('/hello', status=303)
class ChunkedHandler(RequestHandler):
@asynchronous
@gen.engine
def get(self):
self.write('hello ')
yield gen.Task(self.flush)
self.write('world')
yield gen.Task(self.flush)
self.finish()
class TestMixin(object):
def get_handlers(self):
return [
('/hello', HelloHandler),
('/redirect(/.*)', RedirectHandler),
('/post', PostHandler),
('/chunked', ChunkedHandler),
]
def get_app_kwargs(self):
return dict(static_path='.')
def check_url(self, path, method='GET', body=None, headers=None,
expected_status=200, allowed_warnings=None):
url = self.get_url(path)
state = self.run_redbot(url, method, body, headers)
if not state.res_complete:
if isinstance(state.res_error, Exception):
logging.warning((state.res_error.desc, vars(state.res_error), url))
raise state.res_error
else:
raise Exception("unknown error; incomplete response")
self.assertEqual(int(state.res_status), expected_status)
allowed_warnings = tuple(allowed_warnings or ())
# We can't set a non-heuristic freshness at the framework level,
# so just ignore this error.
allowed_warnings += (rs.FRESHNESS_HEURISTIC,)
errors = []
warnings = []
for msg in state.messages:
if msg.level == 'bad':
logger = logging.error
errors.append(msg)
elif msg.level == 'warning':
logger = logging.warning
if not isinstance(msg, allowed_warnings):
warnings.append(msg)
elif msg.level in ('good', 'info', 'uri'):
logger = logging.info
else:
raise Exception('unknown level' + msg.level)
logger('%s: %s (%s)', msg.category, msg.show_summary('en'),
msg.__class__.__name__)
logger(msg.show_text('en'))
self.assertEqual(len(warnings) + len(errors), 0,
'Had %d unexpected warnings and %d errors' %
(len(warnings), len(errors)))
def run_redbot(self, url, method, body, headers):
red = ResourceExpertDroid(url, method=method, req_body=body,
req_hdrs=headers)
def work():
red.run(thor.stop)
thor.run()
self.io_loop.add_callback(self.stop)
thread = threading.Thread(target=work)
thread.start()
self.wait()
thread.join()
return red.state
def test_hello(self):
self.check_url('/hello')
def test_static(self):
# TODO: 304 responses SHOULD return the same etag that a full
# response would. We currently do for If-None-Match, but not
# for If-Modified-Since (because IMS does not otherwise
# require us to read the file from disk)
self.check_url('/static/red_test.py',
allowed_warnings=[rs.MISSING_HDRS_304])
def test_static_versioned_url(self):
self.check_url('/static/red_test.py?v=1234',
allowed_warnings=[rs.MISSING_HDRS_304])
def test_redirect(self):
self.check_url('/redirect/hello', expected_status=302)
def test_permanent_redirect(self):
self.check_url('/redirect/hello?status=301', expected_status=301)
def test_404(self):
self.check_url('/404', expected_status=404)
def test_post(self):
body = 'foo=bar'
# Without an explicit Content-Length redbot will try to send the
# request chunked.
self.check_url(
'/post', method='POST', body=body,
headers=[('Content-Length', str(len(body))),
('Content-Type', 'application/x-www-form-urlencoded')],
expected_status=303)
def test_chunked(self):
self.check_url('/chunked')
class DefaultHTTPTest(AsyncHTTPTestCase, LogTrapTestCase, TestMixin):
def get_app(self):
return Application(self.get_handlers(), **self.get_app_kwargs())
if __name__ == '__main__':
parse_command_line()
unittest.main()

View File

@ -0,0 +1,8 @@
[tox]
envlist = py27
setupdir=../../..
[testenv]
commands = python red_test.py
deps =
git+https://github.com/mnot/redbot.git