Commits

Robert Brewer committed 0616050

Merged new test suite from branches/ticket-177 into trunk.

Comments (0)

Files changed (51)

+2005-06-10:
+    * New test suite (no forks, no exec). (fumanchu)
+    * New lib/profiler module. (fumanchu)
+
 2005-06-09:
     * New sessionauthenticatefilter. This replace CSAuthenticate implementation (based on aspect). (Remi)
     * New nsgmlsfilter. Provides XHTML validation (you need NSGMLS installed). (Remi)

cherrypy/_cpconfig.py

-import _cputil, cperror
-import ConfigParser
-from lib import autoreload
-
-cpg = None # delayed import
+import _cputil, cperror
+import ConfigParser
+from lib import autoreload
+
+cpg = None # delayed import
 
 configMap = {
         '/': {
             'session.cookieName': 'CherryPySession',
             'session.storageFileDir': '',
         },
-    }
-
-def update(updateMap=None, file=None):
-    if updateMap:
-        for section, valueMap in updateMap.items():
-            s = configMap.get(section)
-            if not s:
-                configMap[section] = valueMap
-            else:
-                s.update(valueMap)
-    if file:
-        if file not in autoreload.reloadFiles:
-            autoreload.reloadFiles.append(file)
-        _load(file)
-
-def get(key, defaultValue=None, returnSection=False):
-    # Look, ma, no Python function calls! Uber-fast.
-    global cpg
-    if not cpg:
-        import cpg
-    
-    try:
-        path = cpg.request.path
-    except AttributeError:
-        path = "/"
-    
-    while True:
-        if path == "":
-            path = "/"
-        try:
-            result = configMap[path][key]
-        except KeyError:
-            if path != "/":
-                i = path.rfind("/")
-                if i < 0:
-                    result = defaultValue
-                else:
-                    path = path[:i]
-                    continue
-            else:
-                result = defaultValue
-        break
-    
-    if returnSection:
-        return path
-    else:
-        return result
-
-class CaseSensitiveConfigParser(ConfigParser.ConfigParser):
-    """ Sub-class of ConfigParser that keeps the case of options and
-        that raises an exception if the file cannot be read
-    """
-    def optionxform(self, optionstr):
-        return optionstr
-    def read(self, filenames):
-        if isinstance(filenames, basestring):
-            filenames = [filenames]
-        for filename in filenames:
-            # try:
-            #     fp = open(filename)
-            # except IOError:
-            #     continue
-            fp = open(filename)
-            self._read(fp, filename)
-            fp.close()
-
-def _load(configFile = None):
-    """ Convert an INI file to a dictionary """
-    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
-
-    # Parse config file
-    configParser = CaseSensitiveConfigParser()
-    if hasattr(configFile, 'read'):
-        _cpLogMessage("Reading infos from configFile stream", 'CONFIG')
-        configParser.readfp(configFile)
-    else:
-        _cpLogMessage("Reading infos from configFile: %s" % configFile, 'CONFIG')
-        configParser.read(configFile)
-
-    # Load INI file into cpg.configMap
-    for section in configParser.sections():
-        if section not in configMap:
-            configMap[section] = {}
-        for option in configParser.options(section):
-            value = configParser.get(section, option)
-            try:
-                value = _cputil.unrepr(value)
-            except cperror.WrongUnreprValue, s:
-                msg = ("section: %s, option: %s, value: %s" %
-                       (repr(section), repr(option), repr(value)))
-                raise cperror.WrongConfigValue, msg
-            configMap[section][option] = value
-
-def outputConfigMap():
-    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
-    _cpLogMessage("Server parameters:", 'CONFIG')
-    _cpLogMessage("  server.environment: %s" % get('server.environment'), 'CONFIG')
-    _cpLogMessage("  server.logToScreen: %s" % get('server.logToScreen'), 'CONFIG')
-    _cpLogMessage("  server.logFile: %s" % get('server.logFile'), 'CONFIG')
-    _cpLogMessage("  server.protocolVersion: %s" % get('server.protocolVersion'), 'CONFIG')
-    _cpLogMessage("  server.socketHost: %s" % get('server.socketHost'), 'CONFIG')
-    _cpLogMessage("  server.socketPort: %s" % get('server.socketPort'), 'CONFIG')
-    _cpLogMessage("  server.socketFile: %s" % get('server.socketFile'), 'CONFIG')
-    _cpLogMessage("  server.reverseDNS: %s" % get('server.reverseDNS'), 'CONFIG')
-    _cpLogMessage("  server.socketQueueSize: %s" % get('server.socketQueueSize'), 'CONFIG')
-    _cpLogMessage("  server.threadPool: %s" % get('server.threadPool'), 'CONFIG')
-    _cpLogMessage("  session.storageType: %s" % get('session.storageType'), 'CONFIG')
-    if get('session.storageType'):
-        _cpLogMessage("  session.timeout: %s min" % get('session.timeout'), 'CONFIG')
-        _cpLogMessage("  session.cleanUpDelay: %s min" % get('session.cleanUpDelay'), 'CONFIG')
-        _cpLogMessage("  session.cookieName: %s" % get('session.cookieName'), 'CONFIG')
-        _cpLogMessage("  session.storageFileDir: %s" % get('session.storageFileDir'), 'CONFIG')
-    _cpLogMessage("  staticContent: %s" % get('staticContent'), 'CONFIG')
+    }
+
+def update(updateMap=None, file=None):
+    if updateMap:
+        for section, valueMap in updateMap.items():
+            s = configMap.get(section)
+            if not s:
+                configMap[section] = valueMap
+            else:
+                s.update(valueMap)
+    if file:
+        if file not in autoreload.reloadFiles:
+            autoreload.reloadFiles.append(file)
+        _load(file)
+
+def get(key, defaultValue=None, returnSection=False):
+    # Look, ma, no Python function calls! Uber-fast.
+    global cpg
+    if not cpg:
+        import cpg
+    
+    try:
+        path = cpg.request.path
+    except AttributeError:
+        path = "/"
+    
+    while True:
+        if path == "":
+            path = "/"
+        try:
+            result = configMap[path][key]
+        except KeyError:
+            if path != "/":
+                i = path.rfind("/")
+                if i < 0:
+                    result = defaultValue
+                else:
+                    path = path[:i]
+                    continue
+            else:
+                result = defaultValue
+        break
+    
+    if returnSection:
+        return path
+    else:
+        return result
+
+class CaseSensitiveConfigParser(ConfigParser.ConfigParser):
+    """ Sub-class of ConfigParser that keeps the case of options and
+        that raises an exception if the file cannot be read
+    """
+    def optionxform(self, optionstr):
+        return optionstr
+    def read(self, filenames):
+        if isinstance(filenames, basestring):
+            filenames = [filenames]
+        for filename in filenames:
+            # try:
+            #     fp = open(filename)
+            # except IOError:
+            #     continue
+            fp = open(filename)
+            self._read(fp, filename)
+            fp.close()
+
+def _load(configFile = None):
+    """ Convert an INI file to a dictionary """
+    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
+
+    # Parse config file
+    configParser = CaseSensitiveConfigParser()
+    if hasattr(configFile, 'read'):
+        _cpLogMessage("Reading infos from configFile stream", 'CONFIG')
+        configParser.readfp(configFile)
+    else:
+        _cpLogMessage("Reading infos from configFile: %s" % configFile, 'CONFIG')
+        configParser.read(configFile)
+
+    # Load INI file into cpg.configMap
+    for section in configParser.sections():
+        if section not in configMap:
+            configMap[section] = {}
+        for option in configParser.options(section):
+            value = configParser.get(section, option)
+            try:
+                value = _cputil.unrepr(value)
+            except cperror.WrongUnreprValue, s:
+                msg = ("section: %s, option: %s, value: %s" %
+                       (repr(section), repr(option), repr(value)))
+                raise cperror.WrongConfigValue, msg
+            configMap[section][option] = value
+
+def outputConfigMap():
+    _cpLogMessage = _cputil.getSpecialFunction('_cpLogMessage')
+    _cpLogMessage("Server parameters:", 'CONFIG')
+    _cpLogMessage("  server.environment: %s" % get('server.environment'), 'CONFIG')
+    _cpLogMessage("  server.logToScreen: %s" % get('server.logToScreen'), 'CONFIG')
+    _cpLogMessage("  server.logFile: %s" % get('server.logFile'), 'CONFIG')
+    _cpLogMessage("  server.protocolVersion: %s" % get('server.protocolVersion'), 'CONFIG')
+    _cpLogMessage("  server.socketHost: %s" % get('server.socketHost'), 'CONFIG')
+    _cpLogMessage("  server.socketPort: %s" % get('server.socketPort'), 'CONFIG')
+    _cpLogMessage("  server.socketFile: %s" % get('server.socketFile'), 'CONFIG')
+    _cpLogMessage("  server.reverseDNS: %s" % get('server.reverseDNS'), 'CONFIG')
+    _cpLogMessage("  server.socketQueueSize: %s" % get('server.socketQueueSize'), 'CONFIG')
+    _cpLogMessage("  server.threadPool: %s" % get('server.threadPool'), 'CONFIG')
+    _cpLogMessage("  session.storageType: %s" % get('session.storageType'), 'CONFIG')
+    if get('session.storageType'):
+        _cpLogMessage("  session.timeout: %s min" % get('session.timeout'), 'CONFIG')
+        _cpLogMessage("  session.cleanUpDelay: %s min" % get('session.cleanUpDelay'), 'CONFIG')
+        _cpLogMessage("  session.cookieName: %s" % get('session.cookieName'), 'CONFIG')
+        _cpLogMessage("  session.storageFileDir: %s" % get('session.storageFileDir'), 'CONFIG')
+    _cpLogMessage("  staticContent: %s" % get('staticContent'), 'CONFIG')

cherrypy/_cpdefaults.py

     sessionfilter, staticfilter, nsgmlsfilter, tidyfilter, \
     virtualhostfilter, xmlrpcfilter
 
+_cachefilter = cachefilter.CacheFilter()
+_logdebuginfofilter = logdebuginfofilter.LogDebugInfoFilter()
+_nsgmlsfilter = nsgmlsfilter.NsgmlsFilter()
+_sessionfilter = sessionfilter.SessionFilter()
+_tidyfilter = tidyfilter.TidyFilter()
+_xmlfilter = xmlrpcfilter.XmlRpcFilter()
+
 # These are in order for a reason!
+
 _cpDefaultInputFilterList = [
-    cachefilter.CacheFilter(),
-    logdebuginfofilter.LogDebugInfoFilter(),
+    _cachefilter,
+    _logdebuginfofilter,
     virtualhostfilter.VirtualHostFilter(),
     baseurlfilter.BaseUrlFilter(),
     decodingfilter.DecodingFilter(),
-    sessionfilter.SessionFilter(),
+    _sessionfilter,
     staticfilter.StaticFilter(),
-    nsgmlsfilter.NsgmlsFilter(),
-    tidyfilter.TidyFilter(),
-    xmlrpcfilter.XmlRpcFilter(),
+    _nsgmlsfilter,
+    _tidyfilter,
+    _xmlfilter,
 ]
 _cpDefaultOutputFilterList = [
-    xmlrpcfilter.XmlRpcFilter(),
+    _xmlfilter,
     encodingfilter.EncodingFilter(),
-    tidyfilter.TidyFilter(),
-    nsgmlsfilter.NsgmlsFilter(),
-    logdebuginfofilter.LogDebugInfoFilter(),
+    _tidyfilter,
+    _nsgmlsfilter,
+    _logdebuginfofilter,
     gzipfilter.GzipFilter(),
-    sessionfilter.SessionFilter(),
-    cachefilter.CacheFilter(),
+    _sessionfilter,
+    _cachefilter,
 ]

cherrypy/_cphttpserver.py

     """Selects and instantiates the appropriate server."""
     
     # Set protocol_version
-    CherryHTTPRequestHandler.protocol_version = cpg.config.get('server.protocolVersion')
+    proto = cpg.config.get('server.protocolVersion')
+    if not proto:
+        proto = "HTTP/1.0"
+    CherryHTTPRequestHandler.protocol_version = proto
     
     # Select the appropriate server based on config options
     sockFile = cpg.config.get('server.socketFile')

cherrypy/_cpserver.py

 import threading
 import time
 import sys
-import cpg, _cputil, _cphttptools
-
-try:
-    from threading import local
-except ImportError:
+import cpg, _cputil, _cphttptools
+
+try:
+    from threading import local
+except ImportError:
     from _cpthreadinglocal import local
-
-
-from lib import autoreload
+
+
+from lib import autoreload, profiler
 
 
 # Set some special attributes for adding hooks
     for func in cpg.server.onStartServerList:
         func()
     
+    # Set up the profiler if requested.
+    if cpg.config.get("profiling.on", False):
+        ppath = cpg.config.get("profiling.path", "")
+        cpg.profiler = profiler.Profiler(ppath)
+    else:
+        cpg.profiler = None
+    
     if not initOnly:
         run_server(serverClass)
 
         # Call the functions from cpg.server.onStartThreadList
         for func in cpg.server.onStartThreadList:
             func(i)
-    return _cphttptools.Request(clientAddress, remoteHost,
-                                requestLine, headers, rfile)
+    
+    if cpg.profiler:
+        cpg.profiler.run(_cphttptools.Request, clientAddress, remoteHost,
+                                               requestLine, headers, rfile)
+    else:
+        _cphttptools.Request(clientAddress, remoteHost,
+                             requestLine, headers, rfile)
 
 def stop():
     """Shutdown CherryPy (and any HTTP servers it started)."""
 
 from __init__ import __version__
 
+_httpserver = None
+
 # import server module
 import _cpserver as server
 import _cpconfig as config

cherrypy/lib/filter/sessionfilter.py

 """
 
 from basefilter import BaseFilter
-import random, sha, string, time
-alphanum = string.letters + string.digits
+import random, sha, time
 
 
 class SessionFilter(BaseFilter):
         cpg.response.simpleCookie[cookieName]['version'] = 1
 
 def generateSessionId():
-    s = "%s%s" % (random.random(), time.time())
+    s = "%s%s" % (random.random(), time.time())
     return sha.sha(s).hexdigest()
 
 def cleanupSessionData():

cherrypy/lib/profiler.py

+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+"""Profiler tools for CherryPy.
+
+CherryPy users
+==============
+
+You can profile any of your pages as follows:
+
+    from cherrypy.lib import profile
+    
+    class Root:
+        p = profile.Profiler("/path/to/profile/dir")
+        
+        def index(self):
+            self.p.run(self._index)
+        index.exposed = True
+        
+        def _index(self):
+            return "Hello, world!"
+    
+    cpg.root = Root()
+
+
+CherryPy developers
+===================
+
+This module can be used whenever you make changes to CherryPy, to get a
+quick sanity-check on overall CP performance. Set the config entry:
+"profiling.on = True" to turn on profiling. Then, use the serve()
+function to browse the results in a web browser. If you run this
+module from the command line, it will call serve() for you.
+
+"""
+
+
+import hotshot
+import os, os.path
+import sys
+
+try:
+    import cStringIO as StringIO
+except ImportError:
+    import StringIO
+
+
+class Profiler(object):
+    
+    def __init__(self, path=None):
+        if not path:
+            path = os.path.join(os.path.dirname(__file__), "profile")
+        self.path = path
+        if not os.path.exists(path):
+            os.makedirs(path)
+        self.count = 0
+    
+    def run(self, func, *args):
+        """run(func, *args). Run func, dumping profile data into self.path."""
+        self.count += 1
+        path = os.path.join(self.path, "cp_%04d.prof" % self.count)
+        prof = hotshot.Profile(path)
+        prof.runcall(func, *args)
+        prof.close()
+    
+    def statfiles(self):
+        """statfiles() -> list of available profiles."""
+        return [f for f in os.listdir(self.path)
+                if f.startswith("cp_") and f.endswith(".prof")]
+    
+    def stats(self, filename, sortby='cumulative'):
+        """stats(index) -> output of print_stats() for the given profile."""
+        from hotshot.stats import load
+        s = load(os.path.join(self.path, filename))
+        s.strip_dirs()
+        s.sort_stats(sortby)
+        oldout = sys.stdout
+        try:
+            sys.stdout = sio = StringIO.StringIO()
+            s.print_stats()
+        finally:
+            sys.stdout = oldout
+        response = sio.getvalue()
+        sio.close()
+        return response
+    
+    def index(self):
+        return """<html>
+        <head><title>CherryPy profile data</title></head>
+        <frameset cols='200, 1*'>
+            <frame src='menu' />
+            <frame name='main' src='' />
+        </frameset>
+        </html>
+        """
+    index.exposed = True
+    
+    def menu(self):
+        yield "<h2>Profiling runs</h2>"
+        yield "<p>Click on one of the runs below to see profiling data.</p>"
+        runs = self.statfiles()
+        runs.sort()
+        for i in runs:
+            yield "<a href='report?filename=%s' target='main'>%s</a><br />" % (i, i)
+    menu.exposed = True
+    
+    def report(self, filename):
+        from cherrypy import cpg
+        cpg.response.headerMap['Content-Type'] = 'text/plain'
+        return self.stats(filename)
+    report.exposed = True
+
+
+def serve(path=None, port=8080):
+    from cherrypy import cpg
+    cpg.root = Profiler(path)
+    cpg.config.update({'/': {'server.serverPort': port,
+                             'server.threadPool': 10,
+                             'server.environment': "production",
+                             'session.storageType': "ram",
+                             }
+                       })
+    cpg.server.start()
+
+
+if __name__ == "__main__":
+    serve(*tuple(sys.argv[1:]))
+

cherrypy/test/__init__.py

+"""Regression test suite for CherryPy.
+
+Run test.py to exercise all tests.
+"""
+
+# Ideas for future tests:
+#    - test if tabs and whitespaces are handled correctly in source file (option -W)
+#    - test if absolute pathnames work fine on windows
+#    - test sessions
+#    - test threading server
+#    - test forking server
+#    - test process pooling server
+#    - test SSL
+#    - test compilator errors
+#    - test abstract classes
+#    - test hidden classes
+#    ...
+

cherrypy/test/buildInfoMap.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-
-import os, sys,os
-
-def buildInfoMap(python2):
-    print "Checking which python versions are installed..."
-    for version, infoMap in python2.items():
-        # Check if this version of python is installed:
-        path=infoMap.get('path', sys.executable )
-        if path:
-            exactVersion=os.popen(
-                '%s -c "import sys,os,os.path;' % path+
-                """sys.path.insert(0,os.path.normpath(os.path.join(os.getcwd(),\'../../\')));"""+
-                'import cherrypy;'+
-                'print sys.version;'+
-                'print cherrypy.__version__"').read().strip()
-        if path and exactVersion and exactVersion.find('Traceback') == -1:
-            exactVersionShort=exactVersion.split()[0]
-            print "    Found python version %s with CherryPy version %s " % (exactVersionShort, exactVersion.split()[-1])
-            python2[version]['exactVersion']=exactVersion
-            python2[version]['exactVersionShort']=exactVersionShort
-            python2[version]['path']=path
-            if exactVersionShort.find("2.%s"%version)!=0:
-                print
-                print "*************************"
-                print "Error: the path for python2.%s appears to run python%s"%(version, exactVersionShort)
-                print 'By default, this script expects the python binaries to be in your PATH and to be called "python2.1", "python2.2", ...'
-                print "If your setup is different, please edit this script and change the path for the python binary"
-                sys.exit(-1)
-        else:
-            print "    Version 2.%s not found with cherrypy module, two directories up"%version
-            del python2[version]
-
-    if not python2:
-        print
-        print "*************************"
-        print "Error: couldn't find any python distribution on your machine."
-        print 'By default, this script expects the python binaries to be in your PATH and to be called "python2.1", "python2.2", ...'
-        print "If your setup is different, please edit this script and change the path for the python binary"
-        sys.exit(-1)
-    print
-    print "Finding out what modules are installed for these versions..."
-    for version, infoMap in python2.items():
-        print "    Checking modules for python%s..."%infoMap['exactVersionShort']
-
-        # Test if python has fork
-        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; import os; print hasattr(os,\'fork\')"'%infoMap['path']).read()
-        if res.find('1')!=-1 or res.find('True')!=-1:
-            print "        os.fork available"
-            infoMap['hasFork']=1
-        else:
-            print "        os.fork not available"
-            infoMap['hasFork']=0
-
-        # Test if threads are available
-        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; import thread"'%infoMap['path']).read()
-        if res.find("ImportError")==-1:
-            print "        thread available"
-            infoMap['hasThread']=1
-        else:
-            print "        thread not available"
-            infoMap['hasThread']=0
-
-        # Test if pyOpenSSL is available
-        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; from OpenSSL import SSL"'%infoMap['path']).read()
-        if res.find("ImportError")==-1:
-            print "        pyOpenSSL available"
-            infoMap['hasPyOpenSSL']=1
-        else:
-            print "        pyOpenSSL not available"
-            infoMap['hasPyOpenSSL']=0
-
-        # Test if xmlrpclib is available
-        res=os.popen('%s -c "import sys; sys.stderr=sys.stdout; import xmlrpclib"'%infoMap['path']).read()
-        if res.find("ImportError")==-1:
-            print "        xmlrpclib available"
-            infoMap['hasXmlrpclib']=1
-        else:
-            print "        xmlrpclib not available"
-            infoMap['hasXmlrpclib']=0
-
-    return python2

cherrypy/test/helper.py

 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
-import os,urllib,time,sys,signal,socket,httplib,os.path 
 
-def startServer(infoMap):
-    # Start the server in another thread
-    if not hasattr(os, "fork"): # win32 mostly
-        pid = os.spawnl(os.P_NOWAIT, infoMap['path'], infoMap['path'],
-                        '"' + os.path.join(os.getcwd(), 'testsite.py') + '"')
+import os, os.path
+import time
+import sys
+import socket
+import httplib
+import threading
+from cherrypy import cpg
+
+
+HOST = "127.0.0.1"
+PORT = 8000
+
+def port_is_free():
+    try:
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.connect((HOST, PORT))
+        s.close()
+        return False
+    except socket.error:
+        return True
+
+
+def startServer(serverClass=None):
+    if serverClass is None:
+        cpg.server.start(initOnly=True)
     else:
-        pid = os.fork()
-        if not pid:
-            os.execlp(infoMap['path'], infoMap['path'], 'testsite.py')
-    return pid
+        if not port_is_free():
+            raise IOError("Port %s is in use; perhaps the previous server "
+                          "did not shut down properly." % port)
+        t = threading.Thread(target=cpg.server.start, args=(False, serverClass))
+        t.start()
+        time.sleep(1)
 
-class EmptyClass:
-    pass
 
-def getPage(url, cookies, extraRequestHeader = []):
+def stopServer():
+    cpg.server.stop()
+    if cpg.config.get('server.threadPool') > 1:
+        # With thread-pools, it can take up to 1 sec for the server to stop
+        time.sleep(1.1)
+
+
+def getPage(url, headers=None, method="GET"):
+    
     # The trying 10 times is simply in case of socket errors.
     # Normal case--it should run once.
     trial = 0
     while trial < 10:
         try:
-            conn = httplib.HTTPConnection('127.0.0.1:%s' % PORT)
+            conn = httplib.HTTPConnection('%s:%s' % (HOST, PORT))
 ##            conn.set_debuglevel(1)
-            conn.putrequest("GET", url)
-##            conn.putheader("Host", "127.0.0.1")
+            conn.putrequest(method.upper(), url)
             
-            if cookies:
-                for cookie in cookies:
-                    name, value = cookie.split(":", 1)
-                    conn.putheader("Cookie", value.strip())
-            
-            for key, value in extraRequestHeader:
+            for key, value in headers:
                 conn.putheader(key, value)
-            
             conn.endheaders()
             
+            # Handle response
             response = conn.getresponse()
             
-            cookies = response.msg.getallmatchingheaders("Set-Cookie")
+            cpg.response.status = "%s %s" % (response.status, response.reason)
             
-            cpg = EmptyClass()
-            cpg.response = EmptyClass()
             cpg.response.headerMap = {}
-            cpg.response.status = "%s %s" % (response.status, response.reason)
             for line in response.msg.headers:
                 key, value = line.split(":", 1)
                 cpg.response.headerMap[key.strip()] = value.strip()
+            cpg.response.headers = cpg.response.headerMap
             
-            cpg.response.body = response.read()
+            b = cpg.response.body = response.read()
+##            print "body:", repr(b)
             
             conn.close()
-            return cpg, cookies
+            return
         except socket.error:
             trial += 1
             if trial == 10:
             else:
                 time.sleep(0.5)
 
-def shutdownServer(mode):
-    urllib.urlopen("http://127.0.0.1:%s/shutdown/all" % PORT)
-    if mode.startswith('tp'):
-        # In thread-pool mode, it can take up to 1 sec for the server
-        #   to shutdown
-        time.sleep(1.1)
-    return
 
-def checkResult(testName, infoMap, serverMode, cpg, rule, failedList):
-    result = False
-    try:
-        result = eval(rule)
-        if result:
-            return result 
-    except:
-        pass 
-    if not result:
-        failedList.append(testName +
-            " for python%s" % infoMap['exactVersionShort'] + 
-            " in " + serverMode + " mode failed." + """
-* Rule:
-%s
-* cpg.response.status:
-%s
-* cpg.response.headerMap:
-%s
-* cpg.response.body:
-%s""" % (rule, repr(cpg.response.status),
-         repr(cpg.response.headerMap), repr(cpg.response.body)))
-        return False
+def request(url, headers=None, method="GET"):
+    if headers is None:
+        headers = []
+    
+    if cpg._httpserver is None:
+        requestLine = "%s %s HTTP/1.0" % (method.upper(), url)
+        found = False
+        for k, v in headers:
+            if k.lower() == 'host':
+                found = True
+                break
+        if not found:
+            headers.append(("Host", "%s:%s" % (HOST, PORT)))
+        cpg.server.request(HOST, HOST, requestLine, headers, None)
+        cpg.response.body = "".join(cpg.response.body)
+    else:
+        getPage(url, headers, method)
 
-def prepareCode(code, serverClass):
-    f = open('testsite.py', 'w')
-    
-    includePathsToSysPath = """
-import sys,os,os.path
-sys.path.insert(0,os.path.normpath(os.path.join(os.getcwd(),'../../')))
-"""
-    f.write(includePathsToSysPath)
-    
-    beforeStart = '''
-class Shutdown:
-    def all(self):
-        cpg.server.stop()
-        return "Shut down"
-    all.exposed = True
-cpg.root.shutdown = Shutdown()
-def f(*a, **kw): return ""
-cpg.root._cpLogMessage = f
-cpg.config.update(file = 'testsite.cfg')
-'''
-    newcode = code.replace('cpg.config.update', beforeStart + 'cpg.config.update')
-    if serverClass:
-        serverClass = "serverClass='%s'" % serverClass
-    newcode = newcode.replace('cpg.server.start(',
-                              'cpg.config.configMap["/"]["server.logToScreen"] = False\n'
-                              'cpg.server.start(' + serverClass)
-    f.write(newcode)
-    
-    f.close()
-
-PORT = 8000
-def port_is_free():
-    try:
-        s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        s.connect(('127.0.0.1', PORT))
-        s.close()
-        return False
-    except socket.error:
-        return True
-
-def checkPageResult(testName, infoMap, code, testList, failedList, extraConfig = '', extraRequestHeader = []):
-    response = None
-    
-    if not port_is_free():
-        print "\n### Error: port", PORT, "is busy. The previous server did not shut down properly."
-        sys.exit(-1)
-    
-    # Try it in all 4 modes (regular, threadPooling x normal, WSGI)
-    for name, serverClass in [("native", "cherrypy._cphttpserver.embedded_server"),
-                              ("wsgi", ""),
-                              ]:
-        sys.stdout.write(name + ": ")
-        prepareCode(code, serverClass)
-        for mode, modeConfig in [('r', ""), ('tp', 'server.threadPool = 3')]:
-            sys.stdout.write(mode)
-            sys.stdout.flush()
-            f = open("testsite.cfg", "w")
-            f.write(extraConfig)
-            f.write('''
-[/]
-session.storageType = "ram"
-server.socketPort = %s
-server.environment = "production"
-server.logToScreen = False
-''' % PORT)
-            f.write(modeConfig + "\n")
-            f.close()
-            
-            pid = startServer(infoMap)
-            passed=True
-            cookies=None
-            for url, rule in testList:
-                sys.stdout.write(".")
-                sys.stdout.flush()
-                cpg, cookies = getPage(url, cookies, extraRequestHeader)
-                if not checkResult(testName, infoMap, mode, cpg, rule, failedList):
-                    passed = 0
-                    print "*** FAILED ***",
-                    break
-            shutdownServer(mode)
-            if passed:
-                sys.stdout.write("ok ")
-                sys.stdout.flush()
-            if not passed:
-                break
-    if passed:
-        print "passed"
-    sys.stdout.flush()
-    return response
-

cherrypy/test/test.py

 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 """
 
-# Regression test suite for CherryPy
+import time
+import sys
+import os, os.path
+import unittest
 
-import sys,os,os.path
-sys.path.insert(0,os.path.normpath(os.path.join(os.getcwd(),'../../')))
-if not os.path.exists(os.path.join(os.curdir,'buildInfoMap.py')):
-    print "Run the test from the test directory (cherrypy/test) from the cherrypy you wish to test."
-    print "If no python executables are found, change this file (test.py) near line 31"
-    sys.exit(1)
-if len(sys.argv) == 2 and sys.argv[1] in ('-h', '--help'):
-    print "Usage: unittest.py [testName+]"
-    print "Run from the test directory from within cherrypy"
-    sys.exit(0)
 
-python2={}
-python2[3]={}    # Infos about python-2.3
-python2[4]={}    # Infos about python-2.4
+class CPTestResult(unittest._TextTestResult):
+    def printErrors(self):
+        # Overridden to avoid unnecessary empty line
+        if self.errors or self.failures:
+            if self.dots or self.showAll:
+                self.stream.writeln()
+            self.printErrorList('ERROR', self.errors)
+            self.printErrorList('FAIL', self.failures)
 
-# Edit these lines to match your setup
-if sys.platform=="win32":
-    python2[3]['path']="c:\\python23\\python.exe"
-    python2[4]['path']="c:\\python24\\python.exe"
-else:
-    python2[3]['path']="python2.3"
-    python2[4]['path']="python2.4"
 
-print
+class CPTestRunner(unittest.TextTestRunner):
+    """A test runner class that displays results in textual form."""
+    
+    def _makeResult(self):
+        return CPTestResult(self.stream, self.descriptions, self.verbosity)
+    
+    def run(self, test):
+        "Run the given test case or test suite."
+        # Overridden to remove unnecessary empty lines and separators
+        result = self._makeResult()
+        startTime = time.time()
+        test(result)
+        timeTaken = float(time.time() - startTime)
+        result.printErrors()
+        if not result.wasSuccessful():
+            self.stream.write("FAILED (")
+            failed, errored = map(len, (result.failures, result.errors))
+            if failed:
+                self.stream.write("failures=%d" % failed)
+            if errored:
+                if failed: self.stream.write(", ")
+                self.stream.write("errors=%d" % errored)
+            self.stream.writeln(")")
+        return result
 
-print "Examining your system..."
-print
-print "Python version used to run this test script:", sys.version.split()[0]
-print
-import buildInfoMap
-python2 = buildInfoMap.buildInfoMap(python2)
 
-print
-print "Checking CherryPy version..."
-import os
-try:
-    import cherrypy
-except ImportError:
-    print "Error: couln't find CherryPy !"
-    os._exit(-1)
+class ReloadingTestLoader(unittest.TestLoader):
+    
+    def loadTestsFromName(self, name, module=None):
+        """Return a suite of all tests cases given a string specifier.
 
-print "    Found version " + cherrypy.__version__
-print
+        The name may resolve either to a module, a test case class, a
+        test method within a test case class, or a callable object which
+        returns a TestCase or TestSuite instance.
 
-print "Testing CherryPy..."
-failedList=[]
-skippedList=[]
+        The method optionally resolves the names relative to a given module.
+        """
+        parts = name.split('.')
+        if module is None:
+            if not parts:
+                raise ValueError, "incomplete test name: %s" % name
+            else:
+                parts_copy = parts[:]
+                while parts_copy:
+                    target = ".".join(parts_copy)
+                    if target in sys.modules:
+                        module = reload(sys.modules[target])
+                        break
+                    else:
+                        try:
+                            module = __import__(target)
+                            break
+                        except ImportError:
+                            del parts_copy[-1]
+                            if not parts_copy: raise
+                parts = parts[1:]
+        obj = module
+        for part in parts:
+            obj = getattr(obj, part)
+        
+        import unittest
+        import types
+        if type(obj) == types.ModuleType:
+            return self.loadTestsFromModule(obj)
+        elif (isinstance(obj, (type, types.ClassType)) and
+              issubclass(obj, unittest.TestCase)):
+            return self.loadTestsFromTestCase(obj)
+        elif type(obj) == types.UnboundMethodType:
+            return obj.im_class(obj.__name__)
+        elif callable(obj):
+            test = obj()
+            if not isinstance(test, unittest.TestCase) and \
+               not isinstance(test, unittest.TestSuite):
+                raise ValueError, \
+                      "calling %s returned %s, not a test" % (obj,test)
+            return test
+        else:
+            raise ValueError, "don't know how to make test from: %s" % obj
 
-tutorialTestList = [
-    ('01_helloworld.py',
-     [('/', "cpg.response.body == 'Hello world!'")]),
-    ('02_expose_methods.py',
-     [('/showMessage', "cpg.response.body == 'Hello world!'")]),
-    ('03_get_and_post.py',
-     [('/greetUser?name=Bob', '''cpg.response.body == "Hey Bob, what's up?"''')]),
-    ('03_get_and_post.py',
-     [('/greetUser', """cpg.response.body == 'Please enter your name <a href="./">here</a>.'""")]),
-    ('03_get_and_post.py',
-     [('/greetUser?name=', """cpg.response.body == 'No, really, enter your name <a href="./">here</a>.'""")]),
-    ('04_complex_site.py',
-     [('/links/extra/', r"""cpg.response.body == '\n            <p>Here are some extra useful links:</p>\n\n            <ul>\n                <li><a href="http://del.icio.us">del.icio.us</a></li>\n                <li><a href="http://www.mornography.de">Hendrik\'s weblog</a></li>\n            </ul>\n\n            <p>[<a href="../">Return to links page</a>]</p>\n        '""")]),
-    ('05_derived_objects.py',
-     [('/another/', r"""cpg.response.body == '\n            <html>\n            <head>\n                <title>Another Page</title>\n            <head>\n            <body>\n            <h2>Another Page</h2>\n        \n            <p>\n            And this is the amazing second page!\n            </p>\n        \n            </body>\n            </html>\n        '""")]),
-    ('06_aspects.py',
-     [('/', r"""cpg.response.body == '\n            <html>\n            <head>\n                <title>Tutorial 6 -- Aspect Powered!</title>\n            <head>\n            <body>\n            <h2>Tutorial 6 -- Aspect Powered!</h2>\n        \n            <p>\n            Isn\'t this exciting? There\'s\n            <a href="./another/">another page</a>, too!\n            </p>\n        \n            </body>\n            </html>\n        '""")]),
-    ('07_default_method.py',
-     [('/hendrik', r"""cpg.response.body == 'Hendrik Mans, CherryPy co-developer & crazy German (<a href="./">back</a>)'""")]),
-    ('08_sessions.py',
-     [('/', r'''cpg.response.body == "\n            During your current session, you've viewed this\n            page 1 times! Your life is a patio of fun!\n        "'''), ('/', r'''cpg.response.body == "\n            During your current session, you've viewed this\n            page 2 times! Your life is a patio of fun!\n        "''')]), 
-    ('09_generators_and_yield.py',
-     [('/', r"""cpg.response.body == '<html><body><h2>Generators rule!</h2><h3>List of users:</h3>Remi<br/>Carlos<br/>Hendrik<br/>Lorenzo Lamas<br/></body></html>'""")]),
-]
+CPTestLoader = ReloadingTestLoader()
 
-testList = [
-    'testBaseUrlFilter',
-    'testCacheFilter',
-    'testCombinedFilters',
-    'testCore',
-    'testDecodingEncodingFilter',
-    'testGzipFilter',
-    'testLogDebugInfoFilter',
-    'testObjectMapping',
-    'testStaticFilter',
-    'testVirtualHostFilter',
-]
 
-if len(sys.argv) > 1:
-    # Some specific tests were specified on the command line
-    # Limit the tests to these ones
-    newTutorialTestList = []
-    newTestList = []
-    for number, myTestList in tutorialTestList:
-        if "tutorial%s" % number in sys.argv[1:]:
-            newTutorialTestList.append((number, myTestList))
-    for t in testList:
-        if t in sys.argv[1:]:
-            newTestList.append(t)
-    tutorialTestList = newTutorialTestList
-    testList = newTestList
+def main():
+    # Place our current directory's parent (cherrypy/) at the beginning
+    # of sys.path, so that all imports are from our current directory.
+    localDir = os.path.dirname(__file__)
+    curpath = os.path.normpath(os.path.join(os.getcwd(), localDir))
+    sys.path.insert(0, os.path.normpath(os.path.join(curpath, '../../')))
+    
+    print "Python version used to run this test script:", sys.version.split()[0]
+    try:
+        import cherrypy
+    except ImportError:
+        print "Error: couln't find CherryPy !"
+        os._exit(-1)
+    print "CherryPy version", cherrypy.__version__
+    print
+    
+    testList = [
+        'test_baseurl_filter',
+        'test_cache_filter',
+        'test_combinedfilters',
+        'test_core',
+        'test_decodingencoding_filter',
+        'test_gzip_filter',
+        'test_logdebuginfo_filter',
+        'test_objectmapping',
+        'test_static_filter',
+        'test_tutorials',
+        'test_virtualhost_filter',
+    ]
+    
+    from cherrypy import cpg
+    import helper
+    
+    server_conf = {'server.socketHost': helper.HOST,
+                   'server.socketPort': helper.PORT,
+                   'server.threadPool': 10,
+                   'server.socketQueueSize': 5,
+                   'server.logToScreen': False,
+##                   'profiling.on': True,
+                   }
+    
+    for name, server in [("Serverless", None),
+                         ("Native HTTP Server", "cherrypy._cphttpserver.embedded_server"),
+                         ("Native WSGI Server", "cherrypy._cpwsgi.WSGIServer"),
+                         ]:
+        print
+        print "Running tests:", name
+        
+        cpg.config.update({'/': server_conf.copy()})
+        helper.startServer(server)
+        for testmod in testList:
+            # Must run each module in a separate suite,
+            # because each module uses/overwrites cpg globals.
+            cpg.config.configMap.clear()
+            cpg.config.update({'/': server_conf.copy()})
+            suite = CPTestLoader.loadTestsFromName(testmod)
+            CPTestRunner(verbosity=2).run(suite)
+        helper.stopServer()
+    
+    raw_input('hit enter')
 
-import helper
-import time
-starttime = time.time()
-
-for version, infoMap in python2.items():
-    print
-    print "Running tests for python %s..." % infoMap['exactVersionShort']
-    
-    count = 0
-    # Run tests based on tutorials
-    for filename, myTestList in tutorialTestList:
-        count += 1
-        code = open('../tutorial/%s' % filename, 'r').read()
-        code = code.replace('tutorial.conf', 'testsite.cfg')
-        print "    Testing tutorial %s..." % filename,
-        #if ((version == 1 and number in ('06', '09')) or
-        #        (version == 2 and number in ('09'))):
-        #    print "skipped"
-        #    skippedList.append("Tutorial %s for python2.%s" % (number, version))
-        #    continue
-        
-        helper.checkPageResult('Tutorial %s' % filename, infoMap, code, myTestList, failedList)
-    
-    # Running actual unittests
-    for test in testList:
-        count += 1
-        exec("import " + test)
-        eval(test + ".test(infoMap, failedList, skippedList)")
-
-print
-print
-print "#####################################"
-print "#####################################"
-print "###          TEST RESULT          ###"
-print "#####################################"
-print "#####################################"
-print
-print "%d tests run in %f seconds" % (count, time.time() - starttime)
-
-if skippedList:
-    print
-    print "*** THE FOLLOWING TESTS WERE SKIPPED:"
-    for skipped in skippedList: print skipped
-
-    print "**** THE ABOVE TESTS WERE SKIPPED"
-    print
-
-if failedList:
-    print
-    print "*** THE FOLLOWING TESTS FAILED:"
-    for failed in failedList: print failed
-
-    print "**** THE ABOVE TESTS FAILED"
-    print
-    print "**** Some errors occured: please add a ticket in our Trac system (http://www.cherrypy.org/newticket) with the output of this test script"
-
-else:
-    print
-    print "**** NO TEST FAILED: EVERYTHING LOOKS OK ****"
-
-############"
-# Ideas for future tests:
-#    - test if tabs and whitespaces are handled correctly in source file (option -W)
-#    - test if absolute pathnames work fine on windows
-#    - test sessions
-#    - test threading server
-#    - test forking server
-#    - test process pooling server
-#    - test SSL
-#    - test compilator errors
-#    - test abstract classes
-#    - test hidden classes
-#    ...
-
-raw_input('hit enter')
+if __name__ == '__main__':
+    main()

cherrypy/test/testBaseUrlFilter.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-import helper
-
-code = r"""
-from cherrypy import cpg
-from cherrypy.lib import httptools
-class Root:
-    def index(self):
-        return httptools.redirect('dummy')
-    index.exposed = True
-cpg.root = Root()
-cpg.config.update({
-    '/': {
-        'server.socketPort': 8000,
-        'server.environment': 'production',
-        'baseUrlFilter.on': True,
-        'baseUrlFilter.baseUrl': 'http://www.mydomain.com'
-    }
-})
-cpg.server.start()
-"""
-config = ""
-
-testList = [
-    ('/', "cpg.response.headerMap['Location'] == "
-            "'http://www.mydomain.com/dummy'")
-]
-
-def test(infoMap, failedList, skippedList):
-    print "    Testing baseUrlFilter ...",
-    helper.checkPageResult('baseUrlFilter', infoMap, code, testList, failedList)

cherrypy/test/testCacheFilter.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-import helper, time
-
-code = r"""
-from cherrypy import cpg
-import time
-class Root:
-    def index(self):
-        counter = getattr(cpg, 'counter', 0)
-        counter += 1
-        cpg.counter = counter
-        return str(counter)
-    index.exposed = True
-cpg.root = Root()
-cpg.config.update({
-    '/': {
-        'server.socketPort': 8000,
-        'server.environment': 'production',
-        'cacheFilter.on': True,
-    }
-})
-cpg.server.start()
-"""
-config = ""
-
-
-def test(infoMap, failedList, skippedList):
-    print "    Testing cacheFilter ...",
-    testList = [
-        ('/', "cpg.response.body == '1'"),
-        ('/', "cpg.response.body == '1'"),
-    ]
-    helper.checkPageResult('cacheFilter', infoMap, code, testList, failedList)
-

cherrypy/test/testCombinedFilters.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-import helper, gzip, StringIO
-
-code = r"""
-from cherrypy import cpg
-europoundUnicode = u'\x80\xa3'
-class Root:
-    def index(self):
-        yield u"Hello,"
-        yield u"world"
-        yield europoundUnicode
-    index.exposed = True
-cpg.root = Root()
-cpg.config.update({
-    '/': {
-        'server.socketPort': 8000,
-        'server.environment': 'production',
-        'gzipFilter.on': True,
-        'encodingFilter.on': True,
-    }
-})
-cpg.server.start()
-"""
-config = ""
-europoundUnicode = u'\x80\xa3'
-expectedResult = (u"Hello," + u"world" + europoundUnicode).encode('utf-8')
-zbuf = StringIO.StringIO()
-zfile = gzip.GzipFile(mode='wb', fileobj = zbuf, compresslevel = 9)
-zfile.write(expectedResult)
-zfile.close()
-
-testList = [
-    ('/', '%s in cpg.response.body' % repr(zbuf.getvalue()[:3])),
-]
-
-def test(infoMap, failedList, skippedList):
-    print "    Testing combined filters ...",
-    # Gzip compression doesn't always return the same exact result !
-    # So we just check that the first few bytes are the same
-    helper.checkPageResult('combined filters', infoMap, code, testList, failedList, extraRequestHeader = [("Accept-Encoding", "gzip")])

cherrypy/test/testCore.py

-"""
-Copyright (c) 2005, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-
-"""Basic tests for the CherryPy core: request handling."""
-
-import helper
-
-code = """
-from cherrypy import cpg
-
-class Root:
-    def index(self):
-        return "hello"
-    index.exposed = True
-cpg.root = Root()
-
-
-import types
-class TestType(type):
-    def __init__(cls, name, bases, dct):
-        type.__init__(name, bases, dct)
-        for value in dct.itervalues():
-            if type(value) == types.FunctionType:
-                value.exposed = True
-        setattr(cpg.root, name.lower(), cls())
-class Test(object):
-    __metaclass__ = TestType
-
-
-class Status(Test):
-    
-    def index(self):
-        return "normal"
-    
-    def blank(self):
-        cpg.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):
-        cpg.response.status = 781
-        return "oops"
-    
-    # ...and here is an unknown but legal code.
-    def unknown(self):
-        cpg.response.status = "431 My custom error"
-        return "funky"
-    
-    # Non-numeric code
-    def bad(self):
-        cpg.response.status = "error"
-        return "hello"
-
-class Redirect(Test):
-    
-    def index(self):
-        return "child"
-
-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 page_method(self):
-        raise ValueError
-    
-    def page_yield(self):
-        yield "hello"
-        raise ValueError
-    
-    def page_http_1_1(self):
-        cpg.response.headerMap["Content-Length"] = 39
-        def inner():
-            yield "hello"
-            raise ValueError
-            yield "oops"
-        return inner()
-
-cpg.config.update({
-    '/': {
-        'server.socketPort': 8000,
-        'server.environment': 'production',
-    }
-})
-cpg.server.start()
-"""
-
-testList = [
-    ("/status/", "cpg.response.body == 'normal' and cpg.response.status == '200 OK'"),
-    ("/status/blank", "cpg.response.body == '' and cpg.response.status == '200 OK'"),
-    ("/status/illegal", "cpg.response.body == 'oops' and cpg.response.status == '500 Internal error'"),
-    ("/status/unknown", "cpg.response.body == 'funky' and cpg.response.status == '431 My custom error'"),
-    ("/status/bad", "cpg.response.body == 'hello' and cpg.response.status == '500 Internal error'"),
-
-    ("/redirect/", "cpg.response.body == 'child' and cpg.response.status == '200 OK'"),
-    ("/redirect", "cpg.response.body == '' and cpg.response.status == '302 Found'"),
-
-    ("/flatten/as_string", "cpg.response.body == 'content'"),
-    ("/flatten/as_list", "cpg.response.body == 'content'"),
-    ("/flatten/as_yield", "cpg.response.body == 'content'"),
-    ("/flatten/as_dblyield", "cpg.response.body == 'content'"),
-    ("/flatten/as_refyield", "cpg.response.body == 'content'"),
-
-    ("/error/page_method", r"cpg.response.body.endswith(' in page_method\n    raise ValueError\nValueError\n')"),
-    ("/error/page_yield", r"cpg.response.body.endswith(' in page_yield\n    raise ValueError\nValueError\n')"),
-    ("/error/page_http_1_1", r"cpg.response.body == 'helloUnrecoverable error in the server.'"),
-]
-
-def test(infoMap, failedList, skippedList):
-    print "    Testing core request handling...",
-    helper.checkPageResult('Request handling', infoMap, code, testList, failedList)

cherrypy/test/testDecodingEncodingFilter.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-import helper
-
-code = r"""
-from cherrypy import cpg
-europoundUnicode = u'\x80\xa3'
-class Root:
-    def index(self, param):
-        assert param == europoundUnicode
-        yield europoundUnicode
-    index.exposed = True
-cpg.root = Root()
-cpg.config.update({
-    '/': {
-        'server.socketPort': 8000,
-        'server.environment': 'production',
-        'encodingFilter.on': True,
-        'decodingFilter.on': True
-    }
-})
-cpg.server.start()
-"""
-config = ""
-europoundUtf8 = u'\x80\xa3'.encode('utf-8')
-
-testList = [
-    ('/?param=%s' % europoundUtf8, 
-        'cpg.response.body == "%s"' % europoundUtf8)
-]
-
-def test(infoMap, failedList, skippedList):
-    print "    Testing decodingFilter and encodingFilter ...",
-    helper.checkPageResult('decodingFilter and encodingFilter', infoMap, code, testList, failedList)

cherrypy/test/testGzipFilter.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-import helper, gzip, StringIO
-
-code = r"""
-from cherrypy import cpg
-class Root:
-    def index(self):
-        yield "Hello, world"
-    index.exposed = True
-cpg.root = Root()
-cpg.config.update({
-    '/': {
-        'server.socketPort': 8000,
-        'server.environment': 'production',
-        'gzipFilter.on': True,
-    }
-})
-cpg.server.start()
-"""
-config = ""
-zbuf = StringIO.StringIO()
-zfile = gzip.GzipFile(mode='wb', fileobj = zbuf, compresslevel = 9)
-zfile.write("Hello, world")
-zfile.close()
-
-testList = [('/', '%s in cpg.response.body' % repr(zbuf.getvalue()[:3]))]
-
-def test(infoMap, failedList, skippedList):
-    print "    Testing gzipFilter ...",
-    helper.checkPageResult('gzipFilter', infoMap, code, testList, failedList, extraRequestHeader = [("Accept-Encoding", "gzip")])
-

cherrypy/test/testLogDebugInfoFilter.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-import helper
-
-code = r"""
-from cherrypy import cpg
-class Root:
-    def index(self):
-        yield "Hello, world"
-    index.exposed = True
-cpg.root = Root()
-cpg.config.update({
-    '/': {
-        'server.socketPort': 8000,
-        'server.environment': 'production',
-        'logDebugInfoFilter.on': True,
-    }
-})
-cpg.server.start()
-"""
-config = ""
-
-testList = [
-    ('/', "'Build time' in cpg.response.body and "
-        "'Page size' in cpg.response.body and "
-        "'Session data size' in cpg.response.body"
-)]
-
-def test(infoMap, failedList, skippedList):
-    print "    Testing logDebugInfoFilter ...",
-    helper.checkPageResult('logDebugInfoFilter', infoMap, code, testList, failedList)
-

cherrypy/test/testObjectMapping.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-
-import helper
-
-code = """
-from cherrypy import cpg
-from cherrypy.lib import httptools
-class Root:
-    def index(self, name="world"):
-        return name
-    index.exposed = True
-    def default(self, *params):
-        return "default:"+repr(params)
-    default.exposed = True
-    def other(self):
-        return "other"
-    other.exposed = True
-    def extra(self, *p):
-        return repr(p)
-    extra.exposed = True
-    def redirect(self):
-        return httptools.redirect('dir1/')
-    redirect.exposed = True
-    def notExposed(self):
-        return "not exposed"
-class Dir1:
-    def index(self):
-        return "index for dir1"
-    index.exposed = True
-    def myMethod(self):
-        return "myMethod from dir1, object Path is:" + repr(cpg.request.objectPath)
-    myMethod.exposed = True
-    def default(self, *params):
-        return "default for dir1, param is:" + repr(params)
-    default.exposed = True
-class Dir2:
-    def index(self):
-        return "index for dir2, path is:" + cpg.request.path
-    index.exposed = True
-    def method(self):
-        return "method for dir2"
-    method.exposed = True
-class Dir3:
-    def default(self):
-        return "default for dir3, not exposed"
-class Dir4:
-    def index(self):
-        return "index for dir4, not exposed"
-cpg.root = Root()
-cpg.root.dir1 = Dir1()
-cpg.root.dir1.dir2 = Dir2()
-cpg.root.dir1.dir2.dir3 = Dir3()
-cpg.root.dir1.dir2.dir3.dir4 = Dir4()
-cpg.config.update({
-    '/': {
-        'server.socketPort': 8000,
-    }
-})
-cpg.server.start()
-"""
-
-testList = [
-    ("/", "cpg.response.body == 'world'"),
-    ("/dir1/myMethod", '''cpg.response.body == "myMethod from dir1, object Path is:'/dir1/myMethod'"'''),
-    ("/this/method/does/not/exist", '''cpg.response.body == "default:('this', 'method', 'does', 'not', 'exist')"'''),
-    ("/extra/too/much", '''cpg.response.body == "default:('extra', 'too', 'much')"'''),
-    ("/other", "cpg.response.body == 'other'"),
-    ("/notExposed", '''cpg.response.body == "default:('notExposed',)"'''),
-    ("/dir1/dir2/", "cpg.response.body == 'index for dir2, path is:/dir1/dir2/'"),
-    ("/dir1/dir2", "cpg.response.status == '302 Found'" +
-        " and cpg.response.headerMap['Location'] == 'http://127.0.0.1:8000/dir1/dir2/'"),
-    ("/dir1/dir2/dir3/dir4/index", '''cpg.response.body == "default for dir1, param is:('dir2', 'dir3', 'dir4', 'index')"'''),
-    ("/redirect", "cpg.response.status == '302 Found'" +
-        " and cpg.response.headerMap['Location'] == 'http://127.0.0.1:8000/dir1/'"),
-]
-
-def test(infoMap, failedList, skippedList):
-    print "    Testing object mapping...",
-    helper.checkPageResult('Object mapping', infoMap, code, testList, failedList)

cherrypy/test/testStaticFilter.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-
-import helper
-
-code = """
-from cherrypy import cpg
-class Root: pass
-cpg.root = Root()
-cpg.config.update({
-    '/': {
-        'staticFilter.on': False,
-        'server.socketPort': 8000,
-        'server.environment': 'production',
-    },
-    '/static': {
-        'staticFilter.on': True,
-        'staticFilter.dir': 'static',
-    },
-    '/style.css': {
-        'staticFilter.on': True,
-        'staticFilter.file': 'style.css',
-    }
-})
-cpg.server.start()
-"""
-
-testList = [
-    ("/static/index.html", "cpg.response.headerMap['Content-Type'] == 'text/html' and cpg.response.body == 'Hello, world\\r\\n'"),
-    ("/style.css", "cpg.response.headerMap['Content-Type'] == 'text/css' and cpg.response.body == 'Dummy stylesheet\\n'"),
-]
-
-def test(infoMap, failedList, skippedList):
-    print "    Testing staticFilter ...",
-    helper.checkPageResult('staticFilter', infoMap, code, testList, failedList)

cherrypy/test/testVirtualHostFilter.py

-"""
-Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, 
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, 
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright notice, 
-      this list of conditions and the following disclaimer in the documentation 
-      and/or other materials provided with the distribution.
-    * Neither the name of the CherryPy Team nor the names of its contributors 
-      may be used to endorse or promote products derived from this software 
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-import helper
-
-code = r"""
-from cherrypy import cpg
-class Root:
-    def index2(self):
-        yield "Hello, world"
-    index2.exposed = True
-cpg.root = Root()
-cpg.config.update({
-    '/': {
-        'server.socketPort': 8000,
-        'server.environment': 'production',
-        'virtualHostFilter.on': True,
-        'virtualHostFilter.prefix': '/index2',
-    },
-    '/shutdown': {
-        'virtualHostFilter.on': False,
-    }
-})
-cpg.server.start()
-"""
-config = ""
-
-testList = [
-    ('/', "cpg.response.body == 'Hello, world'"),
-]
-
-def test(infoMap, failedList, skippedList):
-    print "    Testing virtualHostFilter ...",
-    helper.checkPageResult('virtualHostFilter', infoMap, code, testList, failedList)
-
-

cherrypy/test/test_baseurl_filter.py

+"""
+Copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, 
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, 
+      this list of conditions and the following disclaimer in the documentation 
+      and/or other materials provided with the distribution.
+    * Neither the name of the CherryPy Team nor the names of its contributors 
+      may be used to endorse or promote products derived from this software 
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+
+from cherrypy import cpg
+from cherrypy.lib import httptools
+
+class Root:
+    def index(self):
+        return httptools.redirect('dummy')
+    index.exposed = True
+
+cpg.root = Root()
+cpg.config.update({
+    '/': {
+        'server.environment': 'production',
+        'baseUrlFilter.on': True,
+        'baseUrlFilter.baseUrl': 'http://www.mydomain.com'<