]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add tests using the Redbot library for HTTP validation and caching.
authorBen Darnell <ben@bendarnell.com>
Sun, 2 Dec 2012 16:13:42 +0000 (11:13 -0500)
committerBen Darnell <ben@bendarnell.com>
Sun, 2 Dec 2012 16:13:42 +0000 (11:13 -0500)
maint/test/redbot/README [new file with mode: 0644]
maint/test/redbot/red_test.py [new file with mode: 0644]
maint/test/redbot/tox.ini [new file with mode: 0644]

diff --git a/maint/test/redbot/README b/maint/test/redbot/README
new file mode 100644 (file)
index 0000000..ad3f15d
--- /dev/null
@@ -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
\ No newline at end of file
diff --git a/maint/test/redbot/red_test.py b/maint/test/redbot/red_test.py
new file mode 100644 (file)
index 0000000..cc00242
--- /dev/null
@@ -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()
diff --git a/maint/test/redbot/tox.ini b/maint/test/redbot/tox.ini
new file mode 100644 (file)
index 0000000..46ec3a7
--- /dev/null
@@ -0,0 +1,8 @@
+[tox]
+envlist = py27
+setupdir=../../..
+
+[testenv]
+commands = python red_test.py
+deps =
+     git+https://github.com/mnot/redbot.git