Robert Brewer avatar Robert Brewer committed dacf95a

Initial fix for #498 (Test suite assumes same process for client and server). All test modules now wrap the server-side code in a "setup_server" function. New test\modpy module (with several tests failing).

Comments (0)

Files changed (23)

cherrypy/test/helper.py

 "test.py" (in this folder); test.py calls this module as a library.
 
 Usage:
-  Each individual test_*.py module imports this module (helper),
-  usually to make an instance of CPWebCase, and then call testmain().
-  
-  The CP test suite script (test.py) imports this module and calls
-  run_test_suite, possibly more than once. CP applications may also
-  import test.py (to use TestHarness), which then calls helper.py.
+    Each individual test_*.py module imports this module (helper),
+    usually to make an instance of CPWebCase, and then call testmain().
+    
+    The CP test suite script (test.py) imports this module and calls
+    run_test_suite, possibly more than once. CP applications may also
+    import test.py (to use TestHarness), which then calls helper.py.
 """
 
 # GREAT CARE has been taken to separate this module from test.py,
 # requirements. So don't go moving functions from here into test.py,
 # or vice-versa, unless you *really* know what you're doing.
 
-import os, os.path
 import re
-import socket
-import StringIO
 import sys
 import thread
-import threading
-import time
-import types
 
 import cherrypy
-from cherrypy import _cpwsgi
 from cherrypy.lib import httptools
 import webtest
 
-for _x in dir(cherrypy):
-    y = getattr(cherrypy, _x)
-    if isinstance(y, types.ClassType) and issubclass(y, cherrypy.Error):
-        webtest.ignored_exceptions.append(y)
-
-
-def onerror():
-    """Assign to _cp_on_error to enable webtest server-side debugging."""
-    handled = webtest.server_error()
-    if not handled:
-        cherrypy._cputil._cp_on_error()
-
-
-def error_middleware(environ, start_response):
-    started = [False]
-    def start(s, h, exc=None):
-        started[0] = True
-        start_response(s, h, exc)
-    
-    try:
-        for chunk in _cpwsgi.wsgiApp(environ, start):
-            yield chunk
-    except (KeyboardInterrupt, SystemExit):
-        raise
-    except Exception, x:
-        # We should only reach this point if server.throw_errors is True.
-        if not started[0]:
-            start_response("500 Server Error", [])
-        yield "THROWN ERROR: %s" % x.__class__.__name__
-
-
-class TestWSGI(_cpwsgi.WSGIServer):
-    """Wrapper for WSGI server so we can test thrown errors."""
-    
-    def __init__(self):
-        _cpwsgi.WSGIServer.__init__(self)
-        self.mount_points = [(base, error_middleware)
-                             for base, wsgiapp in self.mount_points]
-
 
 class CPWebCase(webtest.WebCase):
     
     def exit(self):
         sys.exit()
     
-    def _getRequest(self, url, headers, method, body):
-        # Like getPage, but for serverless requests.
-        webtest.ServerError.on = False
-        self.url = url
-        
-        requestLine = "%s %s HTTP/1.1" % (method.upper(), url)
-        headers = webtest.cleanHeaders(headers, method, body,
-                                       self.HOST, self.PORT)
-        if body is not None:
-            body = StringIO.StringIO(body)
-        
-        request = cherrypy.server.request((self.HOST, self.PORT), self.HOST, "http")
-        response = request.run(requestLine, headers, body)
-        
-        self.status = response.status
-        self.headers = response.header_list
-        
-        # Build a list of request cookies from the previous response cookies.
-        self.cookies = [('Cookie', v) for k, v in self.headers
-                        if k.lower() == 'set-cookie']
-        
-        try:
-            newbody = []
-            for chunk in response.body:
-                newbody.append(chunk)
-            request.close()
-        except Exception, ex:
-            if cherrypy.config.get("stream_response", False):
-                try:
-                    request.close()
-                except:
-                    cherrypy.log(cherrypy._cputil.formatExc())
-                # Pass the error through
-                raise ex
-            
-            s, h, b = cherrypy._cputil.bareError()
-            # Don't reset status or headers; we're emulating an error which
-            # occurs after status and headers have been written to the client.
-            for chunk in b:
-                newbody.append(chunk)
-        self.body = "".join(newbody)
-        
-        if webtest.ServerError.on:
-            self.tearDown()
-            raise webtest.ServerError()
-    
     def tearDown(self):
         pass
     
-    def getPage(self, url, headers=None, method="GET", body=None):
-        """Open the url with debugging support. Return status, headers, body."""
-        # Install a custom error handler, so errors in the server will:
-        # 1) show server tracebacks in the test output, and
-        # 2) stop the HTTP request (if any) and ignore further assertions.
-        cherrypy.root._cp_on_error = onerror
-        # Backward compatibility:
-        cherrypy.root._cpOnError = onerror
-        
+    def getPage(self, url, headers=None, method="GET", body=None, protocol="HTTP/1.1"):
+        """Open the url. Return status, headers, body."""
         if self.mount_point:
             url = httptools.urljoin(self.mount_point, url)
         
-        if cherrypy.server.httpserver is None:
-            self._getRequest(url, headers, method, body)
-        else:
-            webtest.WebCase.getPage(self, url, headers, method, body)
+        webtest.WebCase.getPage(self, url, headers, method, body, protocol)
     
     def assertErrorPage(self, status, message=None, pattern=''):
         """ Compare the response body with a built in error page.
     """
     setConfig(conf)
     cherrypy.server.start_with_callback(_run_test_suite_thread,
-            args = (moduleNames, conf), server_class = server)
+                                        args = (moduleNames, conf),
+                                        server_class = server)
 
 def _run_test_suite_thread(moduleNames, conf):
     for testmod in moduleNames:
         cherrypy.config.reset()
         setConfig(conf)
         
+        m = __import__(testmod, globals(), locals())
+        setup = getattr(m, "setup_server", None)
+        if setup:
+            setup()
         suite = CPTestLoader.loadTestsFromName(testmod)
         CPTestRunner.run(suite)
     thread.interrupt_main()
 
-def testmain(server=None, conf=None):
+def testmain(conf=None, *args, **kwargs):
     """Run __main__ as a test module, with webtest debugging."""
     if conf is None:
         conf = {}
     setConfig(conf)
-    cherrypy.server.start_with_callback(_test_main_thread,
-            server_class = server)
+    cherrypy.server.start_with_callback(_test_main_thread, *args, **kwargs)
 
 def _test_main_thread():
     try:

cherrypy/test/modpy.py

+"""Wrapper for mod_python, for use as a CherryPy HTTP server.
+    
+    To autostart modpython, the "apache" executable or script must be
+    on your system path, or you must override ModPythonServer.APACHE_PATH.
+    On some platforms, "apache" may be called "apachectl" or "apache2ctl"--
+    create a symlink to them if needed.
+    
+    You also need the 'modpython_gateway' module at:
+    http://projects.amor.org/misc/wiki/ModPythonGateway
+"""
+
+import os
+curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
+import re
+import time
+
+import test
+
+
+def read_process(cmd, args=""):
+    pipein, pipeout = os.popen4("%s %s" % (cmd, args))
+    try:
+        firstline = pipeout.readline()
+        if (re.search(r"(not recognized|No such file|not found)", firstline,
+                      re.IGNORECASE)):
+            raise IOError('%s must be on your system path.' % cmd)
+        output = firstline + pipeout.read()
+    finally:
+        pipeout.close()
+    return output
+
+
+APACHE_PATH = "apache"
+CONF_PATH = "test_mp.conf"
+ready = False
+interrupt = None
+
+conf_template = """
+# Apache2 server configuration file for testing CherryPy with mod_python.
+
+DocumentRoot "/"
+Listen %s
+LoadModule python_module modules/mod_python.so
+
+SetHandler python-program
+PythonFixupHandler cherrypy.test.modpy::handler
+PythonOption testmod %s
+PythonHandler modpython_gateway::handler
+PythonOption application cherrypy._cpwsgi::wsgiApp
+PythonDebug On
+"""
+
+def start(testmod, port):
+    mpconf = CONF_PATH
+    if not os.path.isabs(mpconf):
+        mpconf = os.path.join(curdir, mpconf)
+    
+    f = open(mpconf, 'wb')
+    try:
+        f.write(conf_template % (port, testmod))
+    finally:
+        f.close()
+    
+    result = read_process(APACHE_PATH, "-k start -f %s" % mpconf)
+    if result:
+        print result
+
+def stop():
+    """Gracefully shutdown a server that is serving forever."""
+    read_process(APACHE_PATH, "-k stop")
+
+
+loaded = False
+
+def handler(req):
+    global loaded
+    if not loaded:
+        loaded = True
+        options = req.get_options()
+        testmod = options.get('testmod')
+        m = __import__(('cherrypy.test.%s' % testmod), globals(), locals(), [''])
+        import cherrypy
+        cherrypy.config.update({
+            "server.log_file": os.path.join(curdir, "test.log"),
+            "server.environment": "production",
+            })
+        m.setup_server()
+        cherrypy.server.start(init_only=True, server_class=None, server=None)
+    from mod_python import apache
+    return apache.OK
+
+
+class ModPythonTestHarness(test.TestHarness):
+    """TestHarness for ModPython and CherryPy."""
+    
+    def _run(self, conf):
+        import webtest
+        webtest.WebCase.PORT = self.port
+        print
+        print "Running tests:", self.server
+        
+        # mod_python, since it runs in the Apache process, must be
+        # started separately for each test, and then *that* process
+        # must run the setup_server() function for the test.
+        # Then our process can run the actual test.
+        for testmod in self.tests:
+            try:
+                start(testmod, self.port)
+                suite = webtest.ReloadingTestLoader().loadTestsFromName(testmod)
+                webtest.TerseTestRunner(verbosity=2).run(suite)
+            finally:
+                stop()
+
+
Add a comment to this file

cherrypy/test/static/dirback.jpg

Added
New image

cherrypy/test/test.py

 
 
 class TestHarness(object):
-    
     """A test harness for the CherryPy framework and CherryPy applications."""
     
-    # The first server in the list is the default server.
-    available_servers = {'serverless': (0, "Serverless", None),
-                         'wsgi': (1, "Native WSGI Server",
-                                  "cherrypy.test.helper.TestWSGI"),
+    def __init__(self, tests=None, server=None, protocol="HTTP/1.1", port=8000):
+        """Constructor to populate the TestHarness instance.
+        
+        tests should be a list of module names (strings).
+        """
+        self.protocol = protocol
+        self.port = port
+        self.server = server
+        self.tests = tests or []
+    
+    def run(self, conf=None):
+        """Run the test harness."""
+        import cherrypy
+        v = sys.version.split()[0]
+        print "Python version used to run this test script:", v
+        print "CherryPy version", cherrypy.__version__
+        print
+        
+        if conf is None:
+            conf = {'server.socket_host': '127.0.0.1',
+                    'server.socket_port': self.port,
+                    'server.thread_pool': 10,
+                    'server.log_to_screen': False,
+                    'server.environment': "production",
+                    'server.show_tracebacks': True,
+                    }
+        elif isinstance(conf, basestring):
+            conf = cherrypy.config.dict_from_config_file(conf)
+        
+        conf['server.protocol_version'] = self.protocol
+        self._run(conf)
+    
+    def _run(self, conf):
+        # helper must be imported lazily so the coverage tool
+        # can run against module-level statements within cherrypy.
+        # Also, we have to do a relative import here, not
+        # "from cherrypy.test import helper", because the latter
+        # would stick a second instance of webtest in sys.modules,
+        # and we wouldn't be able to globally override the port anymore.
+        import helper
+        webtest.WebCase.PORT = self.port
+        print
+        print "Running tests:", self.server
+        helper.run_test_suite(self.tests, self.server, conf)
+
+
+class CommandLineParser(object):
+    available_servers = {'wsgi': "cherrypy._cpwsgi.WSGIServer",
+                         'modpy': "modpy",
                          }
     default_server = "wsgi"
     
-    def __init__(self, available_tests):
+    def __init__(self, available_tests, args=sys.argv[1:]):
         """Constructor to populate the TestHarness instance.
         
         available_tests should be a list of module names (strings).
-        """
-        self.available_tests = available_tests
-        
-        self.cover = False
-        self.profile = False
-        self.protocol = "HTTP/1.0"
-        self.basedir = None
-        self.PORT = 8000
-        
-        self.servers = []
-        self.tests = []
-    
-    def load(self, args=sys.argv[1:]):
-        """Populate a TestHarness from sys.argv.
         
         args defaults to sys.argv[1:], but you can provide a different
             set of args if you like.
         """
+        self.available_tests = available_tests
+        self.cover = False
+        self.profile = False
+        self.server = None
+        self.port = 8080
+        self.protocol = "HTTP/1.1"
         
-        longopts = ['cover', 'profile', '1.1', 'help',
-                    'basedir=', 'all', 'port=']
+        longopts = ['cover', 'profile', '1.1', 'help', 'basedir=', 'port=',
+                    'server=']
         longopts.extend(self.available_servers)
         longopts.extend(self.available_tests)
         try:
             self.help()
             sys.exit(2)
         
-        self.cover = False
-        self.profile = False
-        self.protocol = "HTTP/1.0"
-        self.basedir = None
-        
-        self.servers = []
         self.tests = []
         
         for o, a in opts:
                 self.cover = True
             elif o == "--profile":
                 self.profile = True
-            elif o == "--1.1":
-                self.protocol = "HTTP/1.1"
+            elif o == "--1.0":
+                self.protocol = "HTTP/1.0"
             elif o == "--basedir":
                 self.basedir = a
-            elif o == "--all":
-                self.servers = self.available_servers.keys()
             elif o == "--port":
-                self.PORT = int(a)
+                self.port = int(a)
+            elif o == "--server":
+                if a in self.available_servers:
+                    a = self.available_servers[a]
+                self.server = a
             else:
                 o = o[2:]
-                if o in self.available_servers and o not in self.servers:
-                    self.servers.append(o)
-                elif o in self.available_tests and o not in self.tests:
+                if o in self.available_tests and o not in self.tests:
                     self.tests.append(o)
         
         if self.cover and self.profile:
                    'coverage tool at the same time.')
             sys.exit(2)
         
-        if not self.servers:
-            self.servers = [self.default_server]
+        if not self.server:
+            self.server = self.available_servers[self.default_server]
         
         if not self.tests:
             self.tests = self.available_tests[:]
         
         print """CherryPy Test Program
     Usage:
-        test.py --servers* --1.1 --cover --basedir=path --profile --tests**
+        test.py --server=* --1.1 --cover --basedir=path --profile --tests**
         
     """
         print '    * servers:'
-        s = [(val, name) for name, val in self.available_servers.iteritems()]
-        s.sort()
-        for val, name in s:
+        for name, val in self.available_servers.iteritems():
             if name == self.default_server:
-                print '        --' + name, '(default)'
+                print '        --%s: %s (default)' % (name, val)
             else:
-                print '        --' + name
+                print '        --%s: %s' % (name, val)
         
-        print """        --all (runs all servers in order)
+        print """
     
     --1.1: use HTTP/1.1 servers instead of default HTTP/1.0
     
     
     def run(self, conf=None):
         """Run the test harness."""
-        self.load()
-        
         # Start the coverage tool before importing cherrypy,
         # so module-level global statements are covered.
         if self.cover:
             self.start_coverage()
         
-        import cherrypy
-        v = sys.version.split()[0]
-        print "Python version used to run this test script:", v
-        print "CherryPy version", cherrypy.__version__
-        print
-        
-        if conf is None:
-            conf = {'server.socket_host': '127.0.0.1',
-                    'server.socket_port': self.PORT,
-                    'server.thread_pool': 10,
-                    'server.log_to_screen': False,
-                    'server.environment': "production",
-                    'server.show_tracebacks': True,
-                    }
-        elif isinstance(conf, basestring):
-            conf = cherrypy.config.dict_from_config_file(conf)
-        
-        conf['server.protocol_version'] = self.protocol
-        
         if self.profile:
             conf['profiling.on'] = True
         
-        self._run_all_servers(conf)
+        if self.server == 'modpy':
+            import modpy
+            modpy.ModPythonTestHarness(self.tests, self.server,
+                                       self.protocol, self.port).run(conf)
+        else:
+            TestHarness(self.tests, self.server,
+                        self.protocol, self.port).run(conf)
         
         if self.profile:
             del conf['profiling.on']
         
         if self.cover:
             self.stop_coverage()
-    
-    def _run_all_servers(self, conf):
-        # helper must be imported lazily so the coverage tool
-        # can run against module-level statements within cherrypy.
-        # Also, we have to do a relative import here, not
-        # "from cherrypy.test import helper", because the latter
-        # would stick a second instance of webtest in sys.modules,
-        # and we wouldn't be able to globally override the port anymore.
-        import helper
-        s = [self.available_servers[name] for name in self.servers]
-        s.sort()
-        webtest.WebCase.PORT = self.PORT
-        for priority, name, cls in s:
-            print
-            print "Running tests:", name
-            helper.run_test_suite(self.tests, cls, conf)
 
 
-class CPTestHarness(TestHarness):
-    
-    def _run_all_servers(self, conf):
-        # helper must be imported lazily so the coverage tool
-        # can run against module-level statements within cherrypy.
-        # Also, we have to do a relative import here, not
-        # "from cherrypy.test import helper", because the latter
-        # would stick a second instance of webtest in sys.modules,
-        # and we wouldn't be able to globally override the port anymore.
-        import helper, test_states
-        s = [self.available_servers[name] for name in self.servers]
-        s.sort()
-        webtest.WebCase.PORT = self.PORT
-        for priority, name, cls in s:
-            print
-            print "Running tests:", name
-            reload(test_states)
-            test_states.run(cls, conf)
-            helper.run_test_suite(self.tests, cls, conf)
-
 def prefer_parent_path():
     # Place this __file__'s grandparent (../../) at the start of sys.path,
     # so that all cherrypy/* imports are from this __file__'s package.
         'test_decodingencoding_filter',
         'test_gzip_filter',
         'test_logdebuginfo_filter',
+        'test_objectmapping',
         'test_response_headers_filter',
-        'test_objectmapping',
         'test_static_filter',
-        'test_tutorials',
+##        'test_tutorials',
         'test_virtualhost_filter',
         'test_session_filter',
         'test_sessionauthenticate_filter',
+##        'test_states',
         'test_xmlrpc_filter',
         'test_wsgiapp_filter',
     ]
-    CPTestHarness(testList).run()
+    CommandLineParser(testList).run()
     
     print
     raw_input('hit enter')

cherrypy/test/test_baseurl_filter.py

 import cherrypy
 
 
-class Root:
-    def index(self):
-        raise cherrypy.HTTPRedirect('dummy')
-    index.exposed = True
+def setup_server():
+    class Root:
+        def index(self):
+            raise cherrypy.HTTPRedirect('dummy')
+        index.exposed = True
+    
+    cherrypy.tree.mount(Root())
+    cherrypy.config.update({
+            'server.environment': 'production',
+            'server.log_to_screen': False,
+            'base_url_filter.on': True,
+            'base_url_filter.base_url': 'http://www.mydomain.com'
+    })
 
-cherrypy.root = Root()
-cherrypy.config.update({
-        'server.environment': 'production',
-        'server.log_to_screen': False,
-        'base_url_filter.on': True,
-        'base_url_filter.base_url': 'http://www.mydomain.com'
-})
 
 import helper
 
 
 
 if __name__ == '__main__':
+    setup_server()
     helper.testmain()
-

cherrypy/test/test_cache_filter.py

 import time
 
 
-class Root:
-    def __init__(self):
-        cherrypy.counter = 0
-    
-    def index(self):
-        cherrypy.counter += 1
-        msg = "visit #%s" % cherrypy.counter
-        return msg
-    index.exposed = True
+def setup_server():
+    class Root:
+        def __init__(self):
+            cherrypy.counter = 0
+        
+        def index(self):
+            cherrypy.counter += 1
+            msg = "visit #%s" % cherrypy.counter
+            return msg
+        index.exposed = True
 
-cherrypy.root = Root()
-cherrypy.config.update({
-        'server.log_to_screen': False,
-        'server.environment': 'production',
-        'cache_filter.on': True,
-})
+    cherrypy.root = Root()
+    cherrypy.config.update({
+            'server.log_to_screen': False,
+            'server.environment': 'production',
+            'cache_filter.on': True,
+    })
 
 
 import helper
             self.assertBody('visit #1')
 
 if __name__ == '__main__':
+    setup_server()
     helper.testmain()
 

cherrypy/test/test_combinedfilters.py

 
 europoundUnicode = u'\x80\xa3'
 
-class Root:
-    def index(self):
-        yield u"Hello,"
-        yield u"world"
-        yield europoundUnicode
-    index.exposed = True
+def setup_server():
+    class Root:
+        def index(self):
+            yield u"Hello,"
+            yield u"world"
+            yield europoundUnicode
+        index.exposed = True
 
-cherrypy.root = Root()
-cherrypy.config.update({
-        'server.log_to_screen': False,
-        'server.environment': 'production',
-        'gzip_filter.on': True,
-        'encoding_filter.on': True,
-})
+    cherrypy.root = Root()
+    cherrypy.config.update({
+            'server.log_to_screen': False,
+            'server.environment': 'production',
+            'gzip_filter.on': True,
+            'encoding_filter.on': True,
+    })
 
 import helper
 
 
 
 if __name__ == '__main__':
+    setup_server()
     helper.testmain()

cherrypy/test/test_config.py

 import cherrypy
 
 
-class Root:
-    def index(self, key):
-        return cherrypy.config.get(key, "None")
-    index.exposed = True
-    global_ = index
-    xyz = index
+def setup_server():
+    
+    class Root:
+        def index(self, key):
+            return cherrypy.config.get(key, "None")
+        index.exposed = True
+        global_ = index
+        xyz = index
+    
+    class Foo:
+        def index(self, key):
+            return cherrypy.config.get(key, "None")
+        index.exposed = True
+        bar = index
+        nex = index
+    
+    class Env:
+        def index(self, key):
+            return str(cherrypy.config.get(key, "None"))
+        index.exposed = True
+        prod = index
+        embed = index
+        
+        def wrong(self):
+            conf = "\n[global]\nserver.environment = production\n"
+            cherrypy.config.update(file=StringIO.StringIO(conf))
+        wrong.exposed=True
+    
+    cherrypy.tree.mount(Root())
+    cherrypy.root.foo = Foo()
+    
+    cherrypy.config.update({
+        'global': {'server.log_to_screen': False,
+                   'server.environment': 'production',
+                   'server.show_tracebacks': True,
+                   },
+        '/': {
+            'foo': 'this',
+            'bar': 'that',
+            },
+        '/foo': {
+            'foo': 'this2',
+            'baz': 'that2',
+            },
+        '/foo/bar': {
+            'foo': 'this3',
+            'bax': 'this4',
+            },
+    })
 
-class Foo:
-    def index(self, key):
-        return cherrypy.config.get(key, "None")
-    index.exposed = True
-    bar = index
-    nex = index
+    _env_conf = {'/': {'server.environment': 'development'},
+                 '/prod': {'server.environment': 'production'},
+                 '/embed': {'server.environment': 'embedded'},
+                 }
+    cherrypy.tree.mount(Env(), "/env", _env_conf)
 
-class Env:
-    def index(self, key):
-        return str(cherrypy.config.get(key, "None"))
-    index.exposed = True
-    prod = index
-    embed = index
+    # Shortcut syntax--should get put in the "global" bucket
+    cherrypy.config.update({'luxuryyacht': 'throatwobblermangrove'})
 
-cherrypy.tree.mount(Root())
-cherrypy.root.foo = Foo()
 
-cherrypy.config.update({
-    'global': {'server.log_to_screen': False,
-               'server.environment': 'production',
-               'server.show_tracebacks': True,
-               },
-    '/': {
-        'foo': 'this',
-        'bar': 'that',
-        },
-    '/foo': {
-        'foo': 'this2',
-        'baz': 'that2',
-        },
-    '/foo/bar': {
-        'foo': 'this3',
-        'bax': 'this4',
-        },
-})
-
-_env_conf = {'/': {'server.environment': 'development'},
-             '/prod': {'server.environment': 'production'},
-             '/embed': {'server.environment': 'embedded'},
-             }
-cherrypy.tree.mount(Env(), "/env", _env_conf)
-
-# Shortcut syntax--should get put in the "global" bucket
-cherrypy.config.update({'luxuryyacht': 'throatwobblermangrove'})
+#                             Client-side code                             #
 
 import helper
 
     
     def testConfig(self):
         tests = [
-            ('*',        'luxuryyacht', 'throatwobblermangrove'),
+##            ('*',        'luxuryyacht', 'throatwobblermangrove'),
             ('/',        'nex', 'None'),
             ('/',        'foo', 'this'),
             ('/',        'bar', 'that'),
             self.assertBody(expected)
     
     def testUnrepr(self):
-        self.assertRaises(cherrypy.WrongConfigValue, cherrypy.config.update,
-                          file=StringIO.StringIO("""
-[global]
-server.environment = production
-"""))
+        err = ('WrongConfigValue: ("section: '
+               "'global', option: 'server.environment', value: 'production'"
+               '''", 'UnknownType', ('production',))''')
+        self.getPage("/env/wrong")
+        self.assertErrorPage(500, pattern=err)
     
     def testEnvironments(self):
         for key, val in cherrypy.config.environments['development'].iteritems():
 
 
 if __name__ == '__main__':
+    setup_server()
     helper.testmain()

cherrypy/test/test_core.py

 import cherrypy
 from cherrypy.lib import cptools, httptools
 import types
+
 import os
 localDir = os.path.dirname(__file__)
-
-
-class Root:
-    
-    def index(self):
-        return "hello"
-    index.exposed = True
-    
-    def andnow(self):
-        return "the larch"
-    andnow.exposed = True
-    
-    def global_(self):
-        pass
-    global_.exposed = True
-
-cherrypy.root = Root()
-
-
-class TestType(type):
-    """Metaclass which automatically exposes all functions in each subclass,
-    and adds an instance of the subclass as an attribute of cherrypy.root.
-    """
-    def __init__(cls, name, bases, dct):
-        type.__init__(name, bases, dct)
-        for value in dct.itervalues():
-            if isinstance(value, types.FunctionType):
-                value.exposed = True
-        setattr(cherrypy.root, name.lower(), cls())
-class Test(object):
-    __metaclass__ = TestType
-
-
-class Params(Test):
-    
-    def index(self, thing):
-        return repr(thing)
-    
-    def ismap(self, x, y):
-        return "Coordinates: %s, %s" % (x, y)
-    
-    def default(self, *args, **kwargs):
-        return "args: %s kwargs: %s" % (args, kwargs)
-
-
-class Status(Test):
-    
-    def index(self):
-        return "normal"
-    
-    def blank(self):
-        cherrypy.response.status = ""
-    
-    # According to RFC 2616, new status codes are OK as long as they
-    # are between 100 and 599.
-    
-    # Here is an illegal code...
-    def illegal(self):
-        cherrypy.response.status = 781
-        return "oops"
-    
-    # ...and here is an unknown but legal code.
-    def unknown(self):
-        cherrypy.response.status = "431 My custom error"
-        return "funky"
-    
-    # Non-numeric code
-    def bad(self):
-        cherrypy.response.status = "error"
-        return "bad news"
-
-
-class Redirect(Test):
-    
-    class Error:
-        def _cp_on_error(self):
-            raise cherrypy.HTTPRedirect("/errpage")
-        
-        def index(self):
-            raise NameError()
-        index.exposed = True
-    error = Error()
-    
-    def index(self):
-        return "child"
-    
-    def by_code(self, code):
-        raise cherrypy.HTTPRedirect("somewhere else", code)
-    
-    def nomodify(self):
-        raise cherrypy.HTTPRedirect("", 304)
-    
-    def proxy(self):
-        raise cherrypy.HTTPRedirect("proxy", 305)
-    
-    def stringify(self):
-        return str(cherrypy.HTTPRedirect("/"))
-
-
-class LoginFilter:
-    
-    def before_main(self):
-        if cherrypy.config.get("auth.on", False):
-            if not getattr(cherrypy.request, "login", None):
-                raise cherrypy.InternalRedirect("/internalredirect/login")
-
-class InternalRedirect(Test):
-    
-    _cp_filters = [LoginFilter()]
-    
-    def index(self):
-        raise cherrypy.InternalRedirect("/")
-    
-    def petshop(self, user_id):
-        if user_id == "parrot":
-            # Trade it for a slug when redirecting
-            raise cherrypy.InternalRedirect('/image/getImagesByUser',
-                                           "user_id=slug")
-        elif user_id == "terrier":
-            # Trade it for a fish when redirecting
-            raise cherrypy.InternalRedirect('/image/getImagesByUser',
-                                           {"user_id": "fish"})
-        else:
-            raise cherrypy.InternalRedirect('/image/getImagesByUser')
-    
-    def secure(self):
-        return "Welcome!"
-    
-    def login(self):
-        return "Please log in"
-
-
-class Image(Test):
-    
-    def getImagesByUser(self, user_id):
-        return "0 images for %s" % user_id
-
-
-class Flatten(Test):
-    
-    def as_string(self):
-        return "content"
-    
-    def as_list(self):
-        return ["con", "tent"]
-    
-    def as_yield(self):
-        yield "content"
-    
-    def as_dblyield(self):
-        yield self.as_yield()
-    
-    def as_refyield(self):
-        for chunk in self.as_yield():
-            yield chunk
-
-
-class Error(Test):
-    
-    def custom(self):
-        raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!")
-    
-    def noexist(self):
-        raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!")
-    
-    def page_method(self):
-        raise ValueError()
-    
-    def page_yield(self):
-        yield "howdy"
-        raise ValueError()
-    
-    def page_streamed(self):
-        yield "word up"
-        raise ValueError()
-        yield "very oops"
-    
-    def cause_err_in_finalize(self):
-        # Since status must start with an int, this should error.
-        cherrypy.response.status = "ZOO OK"
-    
-    def log_unhandled(self):
-        raise ValueError()
-    
-    def rethrow(self):
-        """Test that an error raised here will be thrown out to the server."""
-        raise ValueError()
-
-
-class Ranges(Test):
-    
-    def get_ranges(self):
-        h = cherrypy.request.headers.get('Range')
-        return repr(httptools.getRanges(h, 8))
-    
-    def slice_file(self):
-        path = os.path.join(os.getcwd(), os.path.dirname(__file__))
-        return cptools.serveFile(os.path.join(path, "static/index.html"))
-
-
-class Expect(Test):
-    
-    def expectation_failed(self):
-        expect = cherrypy.request.headers.elements("Expect")
-        if expect and expect[0].value != '100-continue':
-            raise cherrypy.HTTPError(400)
-        raise cherrypy.HTTPError(417, 'Expectation Failed')
-
-class Headers(Test):
-    
-    def doubledheaders(self):
-        # From http://www.cherrypy.org/ticket/165:
-        # "header field names should not be case sensitive sayes the rfc.
-        # if i set a headerfield in complete lowercase i end up with two
-        # header fields, one in lowercase, the other in mixed-case."
-        
-        # Set the most common headers
-        hMap = cherrypy.response.headers
-        hMap['content-type'] = "text/html"
-        hMap['content-length'] = 18
-        hMap['server'] = 'CherryPy headertest'
-        hMap['location'] = ('%s://127.0.0.1:%s/headers/'
-                            % (cherrypy.request.remote_port,
-                               cherrypy.request.scheme))
-        
-        # Set a rare header for fun
-        hMap['Expires'] = 'Thu, 01 Dec 2194 16:00:00 GMT'
-        
-        return "double header test"
-
-
-class HeaderElements(Test):
-    
-    def get_elements(self, headername):
-        e = cherrypy.request.headers.elements(headername)
-        return "\n".join([str(x) for x in e])
-
+log_file = os.path.join(localDir, "error.log")
+log_access_file = os.path.join(localDir, "access.log")
 
 defined_http_methods = ("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE",
                         "TRACE", "CONNECT")
-class Method(Test):
+
+
+def setup_server():
+    class Root:
+        
+        def index(self):
+            return "hello"
+        index.exposed = True
+        
+        def andnow(self):
+            return "the larch"
+        andnow.exposed = True
+        
+        def global_(self):
+            pass
+        global_.exposed = True
+        
+        def delglobal(self):
+            del self.__class__.__dict__['global_']
+        delglobal.exposed = True
+        
+        def defct(self, newct):
+            newct = "text/%s" % newct
+            cherrypy.config.update({'server.default_content_type': newct})
+        defct.exposed = True
+        
+        def upload(self, file):
+            return "Size: %s" % len(file.file.read())
+        upload.exposed = True
     
-    def index(self):
-        m = cherrypy.request.method
-        if m in defined_http_methods:
-            return m
+    cherrypy.root = Root()
+
+
+    class TestType(type):
+        """Metaclass which automatically exposes all functions in each subclass,
+        and adds an instance of the subclass as an attribute of cherrypy.root.
+        """
+        def __init__(cls, name, bases, dct):
+            type.__init__(name, bases, dct)
+            for value in dct.itervalues():
+                if isinstance(value, types.FunctionType):
+                    value.exposed = True
+            setattr(cherrypy.root, name.lower(), cls())
+    class Test(object):
+        __metaclass__ = TestType
+
+
+    class Params(Test):
         
-        if m == "LINK":
-            raise cherrypy.HTTPError(405)
-        else:
-            raise cherrypy.HTTPError(501)
+        def index(self, thing):
+            return repr(thing)
+        
+        def ismap(self, x, y):
+            return "Coordinates: %s, %s" % (x, y)
+        
+        def default(self, *args, **kwargs):
+            return "args: %s kwargs: %s" % (args, kwargs)
+
+
+    class Status(Test):
+        
+        def index(self):
+            return "normal"
+        
+        def blank(self):
+            cherrypy.response.status = ""
+        
+        # According to RFC 2616, new status codes are OK as long as they
+        # are between 100 and 599.
+        
+        # Here is an illegal code...
+        def illegal(self):
+            cherrypy.response.status = 781
+            return "oops"
+        
+        # ...and here is an unknown but legal code.
+        def unknown(self):
+            cherrypy.response.status = "431 My custom error"
+            return "funky"
+        
+        # Non-numeric code
+        def bad(self):
+            cherrypy.response.status = "error"
+            return "bad news"
+
+
+    class Redirect(Test):
+        
+        class Error:
+            def _cp_on_error(self):
+                raise cherrypy.HTTPRedirect("/errpage")
+            
+            def index(self):
+                raise NameError()
+            index.exposed = True
+        error = Error()
+        
+        def index(self):
+            return "child"
+        
+        def by_code(self, code):
+            raise cherrypy.HTTPRedirect("somewhere else", code)
+        
+        def nomodify(self):
+            raise cherrypy.HTTPRedirect("", 304)
+        
+        def proxy(self):
+            raise cherrypy.HTTPRedirect("proxy", 305)
+        
+        def stringify(self):
+            return str(cherrypy.HTTPRedirect("/"))
+
+
+    class LoginFilter:
+        
+        def before_main(self):
+            if cherrypy.config.get("auth.on", False):
+                if not getattr(cherrypy.request, "login", None):
+                    raise cherrypy.InternalRedirect("/internalredirect/login")
+
+    class InternalRedirect(Test):
+        
+        _cp_filters = [LoginFilter()]
+        
+        def index(self):
+            raise cherrypy.InternalRedirect("/")
+        
+        def petshop(self, user_id):
+            if user_id == "parrot":
+                # Trade it for a slug when redirecting
+                raise cherrypy.InternalRedirect('/image/getImagesByUser',
+                                               "user_id=slug")
+            elif user_id == "terrier":
+                # Trade it for a fish when redirecting
+                raise cherrypy.InternalRedirect('/image/getImagesByUser',
+                                               {"user_id": "fish"})
+            else:
+                raise cherrypy.InternalRedirect('/image/getImagesByUser')
+        
+        def secure(self):
+            return "Welcome!"
+        
+        def login(self):
+            return "Please log in"
+
+
+    class Image(Test):
+        
+        def getImagesByUser(self, user_id):
+            return "0 images for %s" % user_id
+
+
+    class Flatten(Test):
+        
+        def as_string(self):
+            return "content"
+        
+        def as_list(self):
+            return ["con", "tent"]
+        
+        def as_yield(self):
+            yield "content"
+        
+        def as_dblyield(self):
+            yield self.as_yield()
+        
+        def as_refyield(self):
+            for chunk in self.as_yield():
+                yield chunk
+
+
+    class Error(Test):
+        
+        def custom(self):
+            raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!")
+        
+        def noexist(self):
+            raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!")
+        
+        def page_method(self):
+            raise ValueError()
+        
+        def page_yield(self):
+            yield "howdy"
+            raise ValueError()
+        
+        def page_streamed(self):
+            yield "word up"
+            raise ValueError()
+            yield "very oops"
+        
+        def cause_err_in_finalize(self):
+            # Since status must start with an int, this should error.
+            cherrypy.response.status = "ZOO OK"
+        
+        def log_unhandled(self):
+            raise ValueError()
+        
+        def rethrow(self):
+            """Test that an error raised here will be thrown out to the server."""
+            raise ValueError()
+
+
+    class Ranges(Test):
+        
+        def get_ranges(self):
+            h = cherrypy.request.headers.get('Range')
+            return repr(httptools.getRanges(h, 8))
+        
+        def slice_file(self):
+            path = os.path.join(os.getcwd(), os.path.dirname(__file__))
+            return cptools.serveFile(os.path.join(path, "static/index.html"))
+
+
+    class Expect(Test):
+        
+        def expectation_failed(self):
+            expect = cherrypy.request.headers.elements("Expect")
+            if expect and expect[0].value != '100-continue':
+                raise cherrypy.HTTPError(400)
+            raise cherrypy.HTTPError(417, 'Expectation Failed')
+
+    class Headers(Test):
+        
+        def doubledheaders(self):
+            # From http://www.cherrypy.org/ticket/165:
+            # "header field names should not be case sensitive sayes the rfc.
+            # if i set a headerfield in complete lowercase i end up with two
+            # header fields, one in lowercase, the other in mixed-case."
+            
+            # Set the most common headers
+            hMap = cherrypy.response.headers
+            hMap['content-type'] = "text/html"
+            hMap['content-length'] = 18
+            hMap['server'] = 'CherryPy headertest'
+            hMap['location'] = ('%s://127.0.0.1:%s/headers/'
+                                % (cherrypy.request.remote_port,
+                                   cherrypy.request.scheme))
+            
+            # Set a rare header for fun
+            hMap['Expires'] = 'Thu, 01 Dec 2194 16:00:00 GMT'
+            
+            return "double header test"
+
+
+    class HeaderElements(Test):
+        
+        def get_elements(self, headername):
+            e = cherrypy.request.headers.elements(headername)
+            return "\n".join([str(x) for x in e])
     
-    def parameterized(self, data):
-        return data
     
-    def request_body(self):
-        # This should be a file object (temp file),
-        # which CP will just pipe back out if we tell it to.
-        return cherrypy.request.body
+    class Method(Test):
+        
+        def index(self):
+            m = cherrypy.request.method
+            if m in defined_http_methods:
+                return m
+            
+            if m == "LINK":
+                raise cherrypy.HTTPError(405)
+            else:
+                raise cherrypy.HTTPError(501)
+        
+        def parameterized(self, data):
+            return data
+        
+        def request_body(self):
+            # This should be a file object (temp file),
+            # which CP will just pipe back out if we tell it to.
+            return cherrypy.request.body
 
-class Divorce:
-    """HTTP Method handlers shouldn't collide with normal method names.
-    For example, a GET-handler shouldn't collide with a method named 'get'.
-    
-    If you build HTTP method dispatching into CherryPy, rewrite this class
-    to use your new dispatch mechanism and make sure that:
-        "GET /divorce HTTP/1.1" maps to divorce.index() and
-        "GET /divorce/get?ID=13 HTTP/1.1" maps to divorce.get()
-    """
-    
-    documents = {}
-    
-    def index(self):
-        yield "<h1>Choose your document</h1>\n"
-        yield "<ul>\n"
-        for id, contents in self.documents:
-            yield ("    <li><a href='/divorce/get?ID=%s'>%s</a>: %s</li>\n"
-                   % (id, id, contents))
-        yield "</ul>"
-    index.exposed = True
-    
-    def get(self, ID):
-        return ("Divorce document %s: %s" %
-                (ID, self.documents.get(ID, "empty")))
-    get.exposed = True
+    class Divorce:
+        """HTTP Method handlers shouldn't collide with normal method names.
+        For example, a GET-handler shouldn't collide with a method named 'get'.
+        
+        If you build HTTP method dispatching into CherryPy, rewrite this class
+        to use your new dispatch mechanism and make sure that:
+            "GET /divorce HTTP/1.1" maps to divorce.index() and
+            "GET /divorce/get?ID=13 HTTP/1.1" maps to divorce.get()
+        """
+        
+        documents = {}
+        
+        def index(self):
+            yield "<h1>Choose your document</h1>\n"
+            yield "<ul>\n"
+            for id, contents in self.documents:
+                yield ("    <li><a href='/divorce/get?ID=%s'>%s</a>: %s</li>\n"
+                       % (id, id, contents))
+            yield "</ul>"
+        index.exposed = True
+        
+        def get(self, ID):
+            return ("Divorce document %s: %s" %
+                    (ID, self.documents.get(ID, "empty")))
+        get.exposed = True
 
-cherrypy.root.divorce = Divorce()
+    cherrypy.root.divorce = Divorce()
 
 
-class Cookies(Test):
-    
-    def single(self, name):
-        cookie = cherrypy.request.simple_cookie[name]
-        cherrypy.response.simple_cookie[name] = cookie.value
-    
-    def multiple(self, names):
-        for name in names:
+    class Cookies(Test):
+        
+        def single(self, name):
             cookie = cherrypy.request.simple_cookie[name]
             cherrypy.response.simple_cookie[name] = cookie.value
+        
+        def multiple(self, names):
+            for name in names:
+                cookie = cherrypy.request.simple_cookie[name]
+                cherrypy.response.simple_cookie[name] = cookie.value
 
-class MaxRequestSize(Test):
+
+    class ThreadLocal(Test):
+        
+        def index(self):
+            existing = repr(getattr(cherrypy.request, "asdf", None))
+            cherrypy.request.asdf = "rassfrassin"
+            return existing
     
-    def index(self):
-        return "OK"
-    
-    def upload(self, file):
-        return "Size: %s" % len(file.file.read())
+    cherrypy.config.update({
+        'global': {
+            'server.log_to_screen': False,
+            'server.environment': 'production',
+            'server.show_tracebacks': True,
+            'server.max_request_body_size': 200,
+            'server.max_request_header_size': 500,
+        },
+        '/flatten': {
+            'server.log_file': log_file,
+            'server.log_access_file': log_access_file,
+        },
+        '/params': {
+            'server.log_file': log_file,
+        },
+        '/internalredirect/secure': {
+            'auth.on': True,
+        },
+        '/error': {
+            'server.log_file': log_file,
+            'server.log_tracebacks': True,
+        },
+        '/error/page_streamed': {
+            'stream_response': True,
+        },
+        '/error/cause_err_in_finalize': {
+            'server.show_tracebacks': False,
+        },
+        '/error/custom': {
+            'error_page.404': os.path.join(localDir, "static/index.html"),
+        },
+        '/error/noexist': {
+            'error_page.404': "nonexistent.html",
+        },
+        '/error/log_unhandled': {
+            'server.log_tracebacks': False,
+            'server.log_unhandled_tracebacks': True,
+        },
+        '/error/rethrow': {
+            'server.throw_errors': True,
+        },
+    })
 
 
-class ThreadLocal(Test):
-    
-    def index(self):
-        existing = repr(getattr(cherrypy.request, "asdf", None))
-        cherrypy.request.asdf = "rassfrassin"
-        return existing
-
-
-log_file = os.path.join(localDir, "error.log")
-log_access_file = os.path.join(localDir, "access.log")
-
-cherrypy.config.update({
-    'global': {'server.log_to_screen': False,
-               'server.environment': 'production',
-               'server.show_tracebacks': True,
-               },
-    '/flatten': {
-        'server.log_file': log_file,
-        'server.log_access_file': log_access_file,
-    },
-    '/params': {
-        'server.log_file': log_file,
-    },
-    '/internalredirect/secure': {
-        'auth.on': True,
-    },
-    '/error': {
-        'server.log_file': log_file,
-        'server.log_tracebacks': True,
-    },
-    '/error/page_streamed': {
-        'stream_response': True,
-    },
-    '/error/cause_err_in_finalize': {
-        'server.show_tracebacks': False,
-    },
-    '/error/custom': {
-        'error_page.404': os.path.join(localDir, "static/index.html"),
-    },
-    '/error/noexist': {
-        'error_page.404': "nonexistent.html",
-    },
-    '/error/log_unhandled': {
-        'server.log_tracebacks': False,
-        'server.log_unhandled_tracebacks': True,
-    },
-    '/error/rethrow': {
-        'server.throw_errors': True,
-    },
-})
-
+#                             Client-side code                             #
 
 import helper
 
     def testStatus(self):
         self.getPage("/status/")
         self.assertBody('normal')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         
         self.getPage("/status/blank")
         self.assertBody('')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         
         self.getPage("/status/illegal")
-        self.assertStatus('500 Internal error')
+        self.assertStatus(500)
         msg = "Illegal response status from server (out of range)."
         self.assertErrorPage(500, msg)
         
         self.getPage("/status/unknown")
         self.assertBody('funky')
-        self.assertStatus('431 My custom error')
+        self.assertStatus(431)
         
         self.getPage("/status/bad")
-        self.assertStatus('500 Internal error')
+        self.assertStatus(500)
         msg = "Illegal response status from server (non-numeric)."
         self.assertErrorPage(500, msg)
     
         
         self.getPage("/flatten/as_string")
         self.assertBody('content')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         
         self.getPage("/flatten/as_yield")
         self.assertBody('content')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         
         data = open(log_access_file, "rb").readlines()
         self.assertEqual(data[0][:15], '127.0.0.1 - - [')
     def testRedirect(self):
         self.getPage("/redirect/")
         self.assertBody('child')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         
         # Test that requests for index methods without a trailing slash
         # get redirected to the same URI path with a trailing slash.
         
         self.getPage("/redirect/by_code?code=300")
         self.assertMatchesBody(r"<a href='(.*)somewhere else'>\1somewhere else</a>")
-        self.assertStatus('300 Multiple Choices')
+        self.assertStatus(300)
         
         self.getPage("/redirect/by_code?code=301")
         self.assertMatchesBody(r"<a href='(.*)somewhere else'>\1somewhere else</a>")
-        self.assertStatus('301 Moved Permanently')
+        self.assertStatus(301)
         
         self.getPage("/redirect/by_code?code=302")
         self.assertMatchesBody(r"<a href='(.*)somewhere else'>\1somewhere else</a>")
-        self.assertStatus('302 Found')
+        self.assertStatus(302)
         
         self.getPage("/redirect/by_code?code=303")
         self.assertMatchesBody(r"<a href='(.*)somewhere else'>\1somewhere else</a>")
-        self.assertStatus('303 See Other')
+        self.assertStatus(303)
         
         self.getPage("/redirect/by_code?code=307")
         self.assertMatchesBody(r"<a href='(.*)somewhere else'>\1somewhere else</a>")
-        self.assertStatus('307 Temporary Redirect')
+        self.assertStatus(307)
         
         self.getPage("/redirect/nomodify")
         self.assertBody('')
-        self.assertStatus('304 Not modified')
+        self.assertStatus(304)
         
         self.getPage("/redirect/proxy")
         self.assertBody('')
-        self.assertStatus('305 Use Proxy')
+        self.assertStatus(305)
         
         # InternalRedirect
         self.getPage("/internalredirect/")
         self.assertBody('hello')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         
         self.getPage("/internalredirect/petshop?user_id=Sir-not-appearing-in-this-film")
         self.assertBody('0 images for Sir-not-appearing-in-this-film')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         
         self.getPage("/internalredirect/petshop?user_id=parrot")
         self.assertBody('0 images for slug')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         
         self.getPage("/internalredirect/petshop?user_id=terrier")
         self.assertBody('0 images for fish')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         
         self.getPage("/internalredirect/secure")
         self.assertBody('Please log in')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         
         # HTTPRedirect on error
         self.getPage("/redirect/error/")
         self.assertInBody('/errpage')
         
         # Make sure str(HTTPRedirect()) works.
-        self.getPage("/redirect/stringify")
-        self.assertStatus('200 OK')
-        protocol = cherrypy.config.get('server.protocol_version')
-        if protocol == "HTTP/1.1":
-            self.assertBody("(['http://127.0.0.1:%s/'], 303)" % self.PORT)
-        else:
-            self.assertBody("(['http://127.0.0.1:%s/'], 302)" % self.PORT)
+        self.getPage("/redirect/stringify", protocol="HTTP/1.0")
+        self.assertStatus(200)
+        self.assertBody("(['http://127.0.0.1:%s/'], 302)" % self.PORT)
+        self.getPage("/redirect/stringify", protocol="HTTP/1.1")
+        self.assertStatus(200)
+        self.assertBody("(['http://127.0.0.1:%s/'], 303)" % self.PORT)
     
     def testFlatten(self):
         for url in ["/flatten/as_string", "/flatten/as_list",
     
     def testErrorHandling(self):
         self.getPage("/error/missing")
-        self.assertStatus("404 Not Found")
+        self.assertStatus(404)
         self.assertErrorPage(404, "The path '/error/missing' was not found.")
         
         ignore = helper.webtest.ignored_exceptions
             self.getPage("/error/page_yield")
             self.assertErrorPage(500, pattern=valerr)
             
-            import cherrypy
-            # stream_response should be True for this path.
-            if cherrypy.server.httpserver is None:
-                self.assertRaises(ValueError, self.getPage,
-                                  "/error/page_streamed")
-            else:
-                self.getPage("/error/page_streamed")
-                # Because this error is raised after the response body has
-                # started, the status should not change to an error status.
-                self.assertStatus("200 OK")
-                self.assertBody("word upUnrecoverable error in the server.")
+            self.getPage("/error/page_streamed")
+            # Because this error is raised after the response body has
+            # started, the status should not change to an error status.
+            self.assertStatus(200)
+            self.assertBody("word upUnrecoverable error in the server.")
             
             # No traceback should be present
             self.getPage("/error/cause_err_in_finalize")
         
         # Test custom error page.
         self.getPage("/error/custom")
-        self.assertStatus("404 Not Found")
+        self.assertStatus(404)
         self.assertEqual(len(self.body), 513)
         self.assertBody("Hello, world\r\n" + (" " * 499))
         
         # Test error in custom error page (ticket #305).
         # Note that the message is escaped for HTML (ticket #310).
         self.getPage("/error/noexist")
-        self.assertStatus("404 Not Found")
+        self.assertStatus(404)
         msg = ("No, &lt;b&gt;really&lt;/b&gt;, not found!<br />"
                "In addition, the custom error page failed:\n<br />"
                "[Errno 2] No such file or directory: 'nonexistent.html'")
         self.assertInBody(msg)
-        
-        # Test server.throw_errors (ticket #186).
-        s = cherrypy.server.httpserver
-        if s:
-            self.getPage("/error/rethrow")
-            self.assertBody("THROWN ERROR: ValueError")
-        else:
-            self.assertRaises(ValueError, self.getPage, "/error/rethrow")
+##        
+##        # Test server.throw_errors (ticket #186).
+##        self.getPage("/error/rethrow")
+##        self.assertBody("THROWN ERROR: ValueError")
     
     def testRanges(self):
-        protocol = cherrypy.config.get('server.protocol_version')
-        if protocol == "HTTP/1.1":
-            self.getPage("/ranges/get_ranges", [('Range', 'bytes=3-6')])
-            self.assertBody("[(3, 7)]")
-            
-            # Test multiple ranges and a suffix-byte-range-spec, for good measure.
-            self.getPage("/ranges/get_ranges", [('Range', 'bytes=2-4,-1')])
-            self.assertBody("[(2, 5), (7, 8)]")
-            
-            # Get a partial file.
-            self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
-            self.assertStatus("206 Partial Content")
-            self.assertHeader("Content-Type", "text/html")
-            self.assertHeader("Content-Range", "bytes 2-5/14")
-            self.assertBody("llo,")
-            
-            # What happens with overlapping ranges (and out of order, too)?
-            self.getPage("/ranges/slice_file", [('Range', 'bytes=4-6,2-5')])
-            self.assertStatus("206 Partial Content")
-            ct = ""
-            for k, v in self.headers:
-                if k.lower() == "content-type":
-                    ct = v
-                    break
-            expected_type = "multipart/byteranges; boundary="
-            self.assert_(ct.startswith(expected_type))
-            boundary = ct[len(expected_type):]
-            expected_body = """--%s
+        self.getPage("/ranges/get_ranges", [('Range', 'bytes=3-6')])
+        self.assertBody("[(3, 7)]")
+        
+        # Test multiple ranges and a suffix-byte-range-spec, for good measure.
+        self.getPage("/ranges/get_ranges", [('Range', 'bytes=2-4,-1')])
+        self.assertBody("[(2, 5), (7, 8)]")
+        
+        # Get a partial file.
+        self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
+        self.assertStatus(206)
+        self.assertHeader("Content-Type", "text/html")
+        self.assertHeader("Content-Range", "bytes 2-5/14")
+        self.assertBody("llo,")
+        
+        # What happens with overlapping ranges (and out of order, too)?
+        self.getPage("/ranges/slice_file", [('Range', 'bytes=4-6,2-5')])
+        self.assertStatus(206)
+        ct = ""
+        for k, v in self.headers:
+            if k.lower() == "content-type":
+                ct = v
+                break
+        expected_type = "multipart/byteranges; boundary="
+        self.assert_(ct.startswith(expected_type))
+        boundary = ct[len(expected_type):]
+        expected_body = """--%s
 Content-type: text/html
 Content-range: bytes 4-6/14
 
 
 llo, 
 --%s""" % (boundary, boundary, boundary)
-            self.assertBody(expected_body)
-            self.assertHeader("Content-Length")
-            
-            # Test "416 Requested Range Not Satisfiable"
-            self.getPage("/ranges/slice_file", [('Range', 'bytes=2300-2900')])
-            self.assertStatus("416 Requested Range Not Satisfiable")
-            self.assertHeader("Content-Range", "bytes */14")
-        else:
-            self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
-            self.assertStatus("200 OK")
-            self.assertBody("Hello, world\r\n")
+        self.assertBody(expected_body)
+        self.assertHeader("Content-Length")
+        
+        # Test "416 Requested Range Not Satisfiable"
+        self.getPage("/ranges/slice_file", [('Range', 'bytes=2300-2900')])
+        self.assertStatus(416)
+        self.assertHeader("Content-Range", "bytes */14")
+        
+        # Test Range behavior with HTTP/1.0 request
+        self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')], protocol="HTTP/1.0")
+        self.assertStatus(200)
+        self.assertBody("Hello, world\r\n")
     
     def testExpect(self):
         e = ('Expect', '100-continue')
         self.assertBody('100-continue')
         
         self.getPage("/expect/expectation_failed", [('Content-Length', '200'), e])
-        self.assertStatus('417 Expectation Failed')
+        self.assertStatus(417)
     
     def testHeaderElements(self):
         # Accept-* header elements should be sorted, with most preferred first.
         h = [('Accept', 'audio/*; q=0.2, audio/basic')]
         self.getPage("/headerelements/get_elements?headername=Accept", h)
-        self.assertStatus("200 OK")
+        self.assertStatus(200)
         self.assertBody("audio/basic\n"
                         "audio/*;q=0.2")
         
         h = [('Accept', 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')]
         self.getPage("/headerelements/get_elements?headername=Accept", h)
-        self.assertStatus("200 OK")
+        self.assertStatus(200)
         self.assertBody("text/x-c\n"
                         "text/html\n"
                         "text/x-dvi;q=0.8\n"
         # Test that more specific media ranges get priority.
         h = [('Accept', 'text/*, text/html, text/html;level=1, */*')]
         self.getPage("/headerelements/get_elements?headername=Accept", h)
-        self.assertStatus("200 OK")
+        self.assertStatus(200)
         self.assertBody("text/html;level=1\n"
                         "text/html\n"
                         "text/*\n"
             
             # HEAD requests should not return any body.
             if m == "HEAD":
-                m = ""
-            
-            self.assertBody(m)
+                self.assertBody("")
+            elif m == "TRACE":
+                # Some HTTP servers (like modpy) have their own TRACE support
+                self.assertEqual(self.body[:5], "TRACE")
+            else:
+                self.assertBody(m)
         
         # Request a PUT method with a form-urlencoded body
         self.getPage("/method/parameterized", method="PUT",
         
         # Request a disallowed method
         self.getPage("/method/", method="LINK")
-        self.assertStatus("405 Method Not Allowed")
+        self.assertStatus(405)
         
         # Request an unknown method
         self.getPage("/method/", method="SEARCH")
-        self.assertStatus("501 Not Implemented")
+        self.assertStatus(501)
         
         # Request the OPTIONS method with a Request-URI of "*".
         self.getPage("*", method="OPTIONS")
-        self.assertStatus("200 OK")
+        self.assertStatus(200)
         # Content-Length header required for OPTIONS with no response body.
         # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2
         self.assertHeader("Content-Length", "0")
         
         # Now be really dastardly and delete our custom global_ handler,
         # to see if the default one works.
-        del Root.global_
+        self.getPage("/delglobal")
         self.getPage("*", method="OPTIONS")
-        self.assertStatus("200 OK")
+        self.assertStatus(200)
         self.assertHeader("Allow", 'HEAD, GET, POST, PUT, OPTIONS')
         
         # For method dispatchers: make sure that an HTTP method doesn't
         # your dispatch idioms.
         self.getPage("/divorce/get?ID=13")
         self.assertBody('Divorce document 13: empty')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
         self.getPage("/divorce/", method="GET")
         self.assertBody('<h1>Choose your document</h1>\n<ul>\n</ul>')
-        self.assertStatus('200 OK')
+        self.assertStatus(200)
     
     def testFavicon(self):
         # Calls to favicon.ico are special-cased in config.py.
         self.assertHeader('Set-Cookie', 'Last=Piranha;')
     
     def testMaxRequestSize(self):
-        self.getPage("/maxrequestsize/index")
-        self.assertBody("OK")
+        self.getPage("/", headers=[('From', "x" * 500)])
+        self.assertStatus(413)
+        self.assertInBody("Request Entity Too Large")
         
-        s = cherrypy.server.httpserver
-        if s:
-            cherrypy.config.update({'server.max_request_header_size': 10})
-            self.getPage("/maxrequestsize/index")
-            self.assertStatus("413 Request Entity Too Large")
-            self.assertInBody("Request Entity Too Large")
-            cherrypy.config.update({'server.max_request_header_size': 0})
-            
-            # Test for http://www.cherrypy.org/ticket/421
-            # (Incorrect border condition in readline of SizeCheckWrapper).
-            # This hangs in rev 891 and earlier.
-            lines256 = "x" * 248
-            self.getPage("/maxrequestsize/index",
-                         headers=[('Host', '127.0.0.1:%s' % self.PORT),
-                                  ('From', lines256)])
+        # Test for http://www.cherrypy.org/ticket/421
+        # (Incorrect border condition in readline of SizeCheckWrapper).
+        # This hangs in rev 891 and earlier.
+        lines256 = "x" * 248
+        self.getPage("/",
+                     headers=[('Host', '127.0.0.1:%s' % self.PORT),
+                              ('From', lines256)])
         
         # Test upload
-        h = [("Content-type", "multipart/form-data; boundary=x"),
-             ("Content-Length", "110")]
-        b = """--x
+        body = """--x
 Content-Disposition: form-data; name="file"; filename="hello.txt"
 Content-Type: text/plain
 
-hello
+%s
 --x--
 """
-        self.getPage('/maxrequestsize/upload', h, "POST", b)
-        self.assertBody('Size: 5')
+        b = body % ("x" * 96)
+        h = [("Content-type", "multipart/form-data; boundary=x"),
+             ("Content-Length", len(b))]
+        self.getPage('/upload', h, "POST", b)
+        self.assertBody('Size: 96')
         
-        s = cherrypy.server.httpserver
-        if s:
-            cherrypy.config.update({
-                '%s/maxrequestsize' % self.prefix(): {
-                    'server.max_request_body_size': 3}})
-            self.getPage('/maxrequestsize/upload', h, "POST", b)
-            self.assertStatus("413 Request Entity Too Large")
-            self.assertInBody("Request Entity Too Large")
+        b = body % ("x" * 200)
+        h = [("Content-type", "multipart/form-data; boundary=x"),
+             ("Content-Length", len(b))]
+        self.getPage('/upload', h, "POST", b)
+        self.assertStatus(413)
+        self.assertInBody("Request Entity Too Large")
     
     def testEmptyThreadlocals(self):
         results = []
             self.getPage("/threadlocal/")
             results.append(self.body)
         self.assertEqual(results, ["None"] * 20)
-
+    
     def testDefaultContentType(self):
         self.getPage('/')
         self.assertHeader('Content-Type', 'text/html')
-        
-        cherrypy.config.update({'server.default_content_type': 'text/plain'})
+        self.getPage('/defct/plain')
         self.getPage('/')
         self.assertHeader('Content-Type', 'text/plain')
-    
-    def testTreeBackwardCompatibility(self):
-        self.assertEqual(cherrypy.tree.mount_points, {"/": cherrypy.root})
+        self.getPage('/defct/html')
 
 
 if __name__ == '__main__':
+    setup_server()
     helper.testmain()

cherrypy/test/test_custom_filters.py

 from cherrypy.filters.basefilter import BaseFilter
 
 
-class Numerify(BaseFilter):
-    
-    def on_start_resource(self):
-        m = cherrypy.config.get("numerify_filter.map", {})
-        cherrypy.request.numerify_map = m.items()
-    
-    def before_finalize(self):
-        if not cherrypy.config.get("numerify_filter.on", False):
-            return
-        
-        def number_it(body):
-            for chunk in body:
-                for k, v in cherrypy.request.numerify_map:
-                    chunk = chunk.replace(k, v)
-                yield chunk
-        cherrypy.response.body = number_it(cherrypy.response.body)
-
-
 class AccessFilter(BaseFilter):
     
     def before_request_body(self):
             raise cherrypy.HTTPError(401)
 
 
-# It's not mandatory to inherit from BaseFilter.
-class NadsatFilter:
+def setup_server():
+
+    class Numerify(BaseFilter):
+        
+        def on_start_resource(self):
+            m = cherrypy.config.get("numerify_filter.map", {})
+            cherrypy.request.numerify_map = m.items()
+        
+        def before_finalize(self):
+            if not cherrypy.config.get("numerify_filter.on", False):
+                return
+            
+            def number_it(body):
+                for chunk in body:
+                    for k, v in cherrypy.request.numerify_map:
+                        chunk = chunk.replace(k, v)
+                    yield chunk
+            cherrypy.response.body = number_it(cherrypy.response.body)
     
-    def before_finalize(self):
-        self.ended = False
-        def nadsat_it_up(body):
-            for chunk in body:
-                chunk = chunk.replace("good", "horrorshow")
-                chunk = chunk.replace("piece", "lomtick")
-                yield chunk
-        cherrypy.response.body = nadsat_it_up(cherrypy.response.body)
     
-    def on_end_request(self):
-        # This runs after the request has been completely written out.
-        cherrypy.response.body = "razdrez"
-        self.ended = True
+    # It's not mandatory to inherit from BaseFilter.
+    class NadsatFilter:
+        
+        def __init__(self):
+            self.counter = 0
+            self.ended = {}
+        
+        def before_main(self):
+            cherrypy.request.counter = self.counter = self.counter + 1
+            self.ended[cherrypy.request.counter] = False
+        
+        def before_finalize(self):
+            def nadsat_it_up(body):
+                for chunk in body:
+                    chunk = chunk.replace("good", "horrorshow")
+                    chunk = chunk.replace("piece", "lomtick")
+                    yield chunk
+            cherrypy.response.body = nadsat_it_up(cherrypy.response.body)
+        
+        def on_end_request(self):
+            # This runs after the request has been completely written out.
+            cherrypy.response.body = "razdrez"
+            self.ended[cherrypy.request.counter] = True
 
 
 
-class Root:
-    def index(self):
-        return "Howdy earth!"
-    index.exposed = True
+    class Root:
+        def index(self):
+            return "Howdy earth!"
+        index.exposed = True