You are viewing a plain text version of this content. The canonical link for it is here.
Posted to mod_python-commits@quetz.apache.org by jg...@apache.org on 2006/10/14 17:45:38 UTC

svn commit: r463963 [4/4] - in /httpd/mod_python/branches/jgallacher/tools/test-ng: ./ conf/ htdocs/ htdocs/core/ htdocs/core/subdir/ htdocs/leaktests/ mptest/ mptest/core/ mptest/leaktest/

Added: httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/httpdconf.py
URL: http://svn.apache.org/viewvc/httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/httpdconf.py?view=auto&rev=463963
==============================================================================
--- httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/httpdconf.py (added)
+++ httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/httpdconf.py Sat Oct 14 08:45:35 2006
@@ -0,0 +1,319 @@
+#
+# Copyright 2006 Apache Software Foundation 
+# 
+# Licensed under the Apache License, Version 2.0 (the "License"); you
+# may not use this file except in compliance with the License.  You
+# may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.  See the License for the specific language governing
+# permissions and limitations under the License.
+#
+# $Id$
+#
+
+"""Config maker, a la HTMLGen. This could grow into something useful."""
+
+class Directive:
+
+    def __init__(self, name, val, flipslash=1):
+        self.name = name
+        self.val = val
+        self.indent = 0
+        self.flipslash = flipslash
+
+    def __str__(self):
+
+        i = " " * self.indent
+        s = i + '%s %s\n' % (self.name, self.val)
+        if self.flipslash:
+            s = s.replace("\\", "/")
+        return s
+
+class Container:
+    
+    def __init__(self, *args):
+        self.args = list(args)
+        self.indent = 0
+    
+    def append(self, value):
+        self.args.append(value)
+
+    def __str__(self):
+
+        i = " " * self.indent
+        s = "\n"
+        for arg in self.args:
+            s += i + "%s" % str(arg)
+
+        return s
+
+class ContainerTag:
+
+    def __init__(self, tag, attr, args, flipslash=1):
+        self.tag = tag
+        self.attr = attr
+        self.args = args
+        self.indent = 0
+        self.flipslash = flipslash
+
+    def __str__(self):
+
+        i = " " * self.indent
+
+        s = i + "<%s %s>\n" % (self.tag, self.attr)
+        if self.flipslash:
+            s = s.replace("\\", "/")
+        for arg in self.args:
+            arg.indent = self.indent + 2
+            s += i + "%s" % str(arg)
+        s += i + "</%s>\n" % self.tag
+
+        return s
+
+class AddHandler(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class AddOutputFilter(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class AddType(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class AuthBasicAuthoritative(Directive):
+    # New in Apache 2.2
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class AuthBasicProvider(Directive):
+    # New in Apache 2.2
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class AuthType(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class AuthName(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class CustomLog(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class Directory(ContainerTag):
+    def __init__(self, dir, *args):
+        ContainerTag.__init__(self, self.__class__.__name__, dir, args)
+
+class DirectoryIndex(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class DocumentRoot(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class ErrorLog(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class Files(ContainerTag):
+    def __init__(self, dir, *args):
+        ContainerTag.__init__(self, self.__class__.__name__, dir, args)
+
+class IfModule(ContainerTag):
+    def __init__(self, dir, *args):
+        ContainerTag.__init__(self, self.__class__.__name__, dir, args)
+
+class KeepAliveTimeout(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class Listen(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class LoadModule(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class LogLevel(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class LogFormat(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val, flipslash=0)
+
+class LockFile(Directive):
+    def __init__(self, val):
+        import sys
+        if sys.platform!='win32':
+            Directive.__init__(self, self.__class__.__name__, val)
+        else:
+            Directive.__init__(self, '#'+self.__class__.__name__, val)
+
+class MaxClients(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class MaxRequestsPerChild(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class MaxSpareServers(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class MaxSpareThreads(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class MaxThreadsPerChild(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class MinSpareThreads(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class NumServers(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class Options(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PidFile(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonAuthenHandler(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonAuthzHandler(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonConnectionHandler(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonDebug(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonHandler(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonAccessHandler(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonPostReadRequestHandler(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonTransHandler(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonFixupHandler(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonImport(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonPath(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val, flipslash=0)
+
+class PythonOutputFilter(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+def __no_output__():
+    # used by PythonOption when the option is disabled
+    return ''
+
+class PythonOption(Directive):
+    def __init__(self, val, enabled=True):
+        if enabled:
+            Directive.__init__(self, self.__class__.__name__, val)
+        else:
+            self.__str__ = __no_output__
+
+class Require(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class SetHandler(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class ServerAdmin(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class ServerName(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class ServerPath(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class ServerRoot(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class StartServers(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class StartThreads(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class ThreadsPerChild(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class Timeout(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class TypesConfig(Directive):
+    def __init__(self, val):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonInterpPerDirectory(Directive):
+    def __init__(self, val='Off'):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class PythonInterpPerDirective(Directive):
+    def __init__(self, val='Off'):
+        Directive.__init__(self, self.__class__.__name__, val)
+
+class VirtualHost(ContainerTag):
+    def __init__(self, addr, *args):
+        ContainerTag.__init__(self, self.__class__.__name__, addr, args)
+
+
+
+    
+             
+    

Added: httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/leaktest/__init__.py
URL: http://svn.apache.org/viewvc/httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/leaktest/__init__.py?view=auto&rev=463963
==============================================================================
    (empty)

Added: httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testconf.py
URL: http://svn.apache.org/viewvc/httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testconf.py?view=auto&rev=463963
==============================================================================
--- httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testconf.py (added)
+++ httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testconf.py Sat Oct 14 08:45:35 2006
@@ -0,0 +1,243 @@
+#
+# Copyright 2006 Apache Software Foundation 
+# 
+# Licensed under the Apache License, Version 2.0 (the "License"); you
+# may not use this file except in compliance with the License.  You
+# may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.  See the License for the specific language governing
+# permissions and limitations under the License.
+#
+# $Id$
+#
+
+"""Test base classes derived from unittest.TestCase.
+
+Mod_python unittests should subclass these classes rather than
+directly subclassing TestCase.
+"""
+
+
+from cStringIO import StringIO
+import httplib
+import os
+import random
+import sys
+import time
+import unittest
+
+readBlockSize = 65368
+
+
+def make_server_name(obj):
+    return  '%s.%s' % (obj.__module__.split('.',1)[1], obj.__class__.__name__)
+
+def make_handler_name(obj):
+    return  '%s::handler_%s' % (obj.__module__.split('.')[-1], obj.__class__.__name__)
+    
+def make_handler_filename(obj):
+    return  '%s.py' % (obj.__module__.split('.')[2])
+
+def make_document_root_name(obj):
+    return  '%s/%s' % (obj.httpd.document_root, obj.__module__.split('.')[1])
+
+
+class MpTestBase(object):
+    def markLogStart(self):
+        self.log_start = 0
+        if os.path.exists(self.httpd.error_log):
+            self.httpd.log_start = os.stat(self.httpd.error_log).st_size 
+
+    def archiveConfigFile(self):
+        self.config_archive = open(self.httpd.config_file, 'r').read()
+
+    def archiveLogFile(self):
+        f = open(self.httpd.error_log)
+        f.seek(self.log_start)
+        self.log_archive = f.read()
+        f.close()
+
+    def scanLog(search_str):
+        raise NotImplementedError
+
+
+
+class PerInstanceTestBase(unittest.TestCase, MpTestBase):
+    """PerInstanceTestBase subclasses will get a clean 
+    apache config file and an httpd restart
+    """
+
+    priority = 0
+
+    # All tests are enabled by default
+    # If disable == True, the test will be skipped
+    # You can force the test to run with the -f option
+    # eg.
+    # $ python test.py -f some.test.name
+    disable = False
+
+    def __init__(self, httpd=None, document_root=None):
+        super(PerInstanceTestBase, self).__init__()
+        self.httpd = httpd
+
+        if document_root:
+            self.document_root = document_root
+ 
+        if not hasattr(self, 'document_root'):
+            self.document_root = make_document_root_name(self)
+        if not hasattr(self, 'server_name'):
+            self.server_name = make_server_name(self)
+        if not hasattr(self, 'handler'):
+            self.handler = make_handler_name(self)
+        if not hasattr(self, 'handler_file'):
+            self.handler_file = make_handler_filename(self)
+        
+       
+ 
+    def setUp(self):
+        self.markLogStart()
+        self.httpd.start()
+
+    def tearDown(self):
+        if self.httpd.httpd_running:
+            self.httpd.stop()
+        self.archiveConfigFile()
+        self.archiveLogFile()
+
+    def config(self):
+        return ''
+
+
+class PerRequestTestBase(unittest.TestCase, MpTestBase):
+    priority = 0
+
+    # All tests are enabled by default
+    # If disable == True, the test will be skipped
+    # You can force the test to run with the -f option
+    # eg.
+    # $ python test.py -f some.test.name
+    disable = False
+
+
+    def __init__(self, httpd=None, document_root=None):
+        super(PerRequestTestBase, self).__init__()
+        self.httpd = httpd
+
+        if document_root:
+            self.document_root = document_root
+
+        if not hasattr(self, 'document_root'):
+            self.document_root = make_document_root_name(self)
+        if not hasattr(self, 'server_name'):
+            self.server_name = make_server_name(self)
+        if not hasattr(self, 'handler'):
+            self.handler = make_handler_name(self)
+        if not hasattr(self, 'handler_file'):
+            self.handler_file = make_handler_filename(self)
+        
+
+    def setUp(self):
+        self.markLogStart()
+        if not self.httpd.httpd_running:
+            self.httpd.start()
+
+    def tearDown(self):
+        self.archiveConfigFile()
+        self.archiveLogFile()
+
+    def config(self):
+        return ''
+
+    def vhost_get(self, vhost, path=None):
+        # allows to specify a custom host: header
+        if path is None:
+            if hasattr(self, 'handler_file'):
+                path = '/%s' % self.handler_file
+            else:
+                path = '/tests.py'
+
+        conn = httplib.HTTPConnection("127.0.0.1:%s" % self.httpd.port)
+        conn.putrequest("GET", path, skip_host=1)
+        conn.putheader("Host", "%s:%s" % (vhost, self.httpd.port))
+        conn.endheaders()
+        response = conn.getresponse()
+        rsp = response.read()
+        conn.close()
+
+        return rsp
+
+
+    def vhost_post_multipart_form_data(self, vhost, path=None,variables={}, files={}):
+        # variables is a { name : value } dict
+        # files is a { name : (filename, content) } dict
+
+
+        if path is None:
+            if hasattr(self, 'handler_file'):
+                path = '/%s' % self.handler_file
+            else:
+                path = '/tests.py'
+
+        # build the POST entity
+        entity = StringIO()
+        # This is the MIME boundary
+        boundary = "============="+''.join( [ random.choice('0123456789') for x in range(10) ] )+'=='
+        # A part for each variable
+        for name, value in variables.iteritems():
+            entity.write('--')
+            entity.write(boundary)
+            entity.write('\r\n')
+            entity.write('Content-Type: text/plain\r\n')
+            entity.write('Content-Disposition: form-data; name="%s"\r\n'%name)
+            entity.write('\r\n')
+            entity.write(str(value))
+            entity.write('\r\n')
+
+        # A part for each file
+        for name, filespec in files.iteritems():
+            filename, content = filespec
+            # if content is readable, read it
+            try:
+                content = content.read()
+            except:
+                pass
+
+            entity.write('--')
+            entity.write(boundary)
+            entity.write('\r\n')
+
+            entity.write('Content-Type: application/octet-stream\r\n')
+            entity.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n'%(name,filename))
+            entity.write('\r\n')
+            entity.write(content)
+            entity.write('\r\n')
+
+        # The final boundary
+        entity.write('--')
+        entity.write(boundary)
+        entity.write('--\r\n')
+        
+        entity = entity.getvalue()
+
+        conn = httplib.HTTPConnection("127.0.0.1:%s" % self.httpd.port)
+        # conn.set_debuglevel(1000)
+        conn.putrequest("POST", path, skip_host=1)
+        conn.putheader("Host", "%s:%s" % (vhost, self.httpd.port))
+        conn.putheader("Content-Type", 'multipart/form-data; boundary="%s"'%boundary)
+        conn.putheader("Content-Length", '%s'%(len(entity)))
+        conn.endheaders()
+
+        start = time.time()
+        conn.send(entity)
+        response = conn.getresponse()
+        rsp = response.read()
+        conn.close()
+        print '    --> Send + process + receive took %.3f s'%(time.time()-start)
+
+        return rsp
+

Added: httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testrunner.py
URL: http://svn.apache.org/viewvc/httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testrunner.py?view=auto&rev=463963
==============================================================================
--- httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testrunner.py (added)
+++ httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testrunner.py Sat Oct 14 08:45:35 2006
@@ -0,0 +1,76 @@
+
+#
+# Copyright 2006 Apache Software Foundation 
+# 
+# Licensed under the Apache License, Version 2.0 (the "License"); you
+# may not use this file except in compliance with the License.  You
+# may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.  See the License for the specific language governing
+# permissions and limitations under the License.
+#
+# $Id$
+#
+
+"""Alternative unittest test runners.
+"""
+
+
+
+from unittest import TestResult, _TextTestResult, _WritelnDecorator
+import sys
+import time
+
+
+
+# This class is adapted from unittest.TextTestRunner
+
+class TextTestRunner:
+    """A test runner class that displays results in textual form.
+
+    It prints out the names of tests as they are run, errors as they
+    occur, and a summary of the results at the end of the test run.
+    """
+    def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1):
+        self.stream = _WritelnDecorator(stream)
+        self.descriptions = descriptions
+        self.verbosity = verbosity
+        self.elapsedTime = 0
+        self.result = _TextTestResult(self.stream, self.descriptions, self.verbosity)
+
+    def run(self, test):
+        "Run the given test case or test suite."
+        #result = self._makeResult()
+        result = self.result
+        startTime = time.time()
+        test(result)
+        stopTime = time.time()
+        timeTaken = stopTime - startTime
+        self.elapsedTime += timeTaken
+        return result
+
+    def report(self):
+        result = self.result
+        result.printErrors()
+        self.stream.writeln(result.separator2)
+        run = result.testsRun
+        self.stream.writeln("Ran %d test%s in %.3fs" %
+                            (run, run != 1 and "s" or "", self.elapsedTime))
+        self.stream.writeln()
+        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(")")
+        else:
+            self.stream.writeln("OK")
+        return result

Added: httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testsetup.py
URL: http://svn.apache.org/viewvc/httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testsetup.py?view=auto&rev=463963
==============================================================================
--- httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testsetup.py (added)
+++ httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/testsetup.py Sat Oct 14 08:45:35 2006
@@ -0,0 +1,65 @@
+#
+# Copyright 2006 Apache Software Foundation 
+# 
+# Licensed under the Apache License, Version 2.0 (the "License"); you
+# may not use this file except in compliance with the License.  You
+# may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.  See the License for the specific language governing
+# permissions and limitations under the License.
+#
+# $Id$
+#
+
+"""testsetup provides facilities for registering new tests with the framework.
+Test class should register themselves by calling
+
+testsetup.register(YourTestClass)
+"""
+
+
+import os
+
+tests = [] 
+failed_tests = []
+debug = False
+
+exclude = ['__init__.py',]
+
+        
+def load_tests(path=None, root='mptest'):
+    if path is None:
+        path = os.path.split(__file__)[0]
+
+    package_names = [ d for d in os.listdir(path)
+                if os.path.isdir(os.path.join(path,d))
+                and not d.startswith('.') ]
+
+
+    for pkg_name in package_names:
+        module_path = os.path.join(path, pkg_name)
+        for module_file in filter(
+                    lambda n: n[-3:]=='.py' and n not in exclude,
+                    os.listdir(module_path)):
+
+            f, e = os.path.splitext(module_file)
+            if debug:
+                print 'loading tests in %s.%s' % (pkg_name, f)
+            __import__('%s.%s.%s' % (root,pkg_name,f), globals(), locals(), [])
+
+
+
+def register(obj):
+    if debug:
+        node = '%s.%s' % (obj.__module__.split('.', 1)[1], obj.__name__)
+        print '  registered', node
+    tests.append(obj)
+
+
+def log_failure(obj):
+    failed_tests.append(obj)

Added: httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/util.py
URL: http://svn.apache.org/viewvc/httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/util.py?view=auto&rev=463963
==============================================================================
--- httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/util.py (added)
+++ httpd/mod_python/branches/jgallacher/tools/test-ng/mptest/util.py Sat Oct 14 08:45:35 2006
@@ -0,0 +1,56 @@
+
+#
+# Copyright 2006 Apache Software Foundation 
+# 
+# Licensed under the Apache License, Version 2.0 (the "License"); you
+# may not use this file except in compliance with the License.  You
+# may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.  See the License for the specific language governing
+# permissions and limitations under the License.
+#
+# $Id$
+#
+
+"""Useful utility functions for running tests."""
+
+
+import os
+import socket
+import unittest
+
+def get_ab_path(httpd):
+    """ Find the location of the ab (apache benchmark) program """
+    for name in ['ab', 'ab2', 'ab.exe', 'ab2.exe']:
+        path = os.path.join(os.path.split(httpd)[0], name)
+        if os.path.exists(path):
+            return quoteIfSpace(path)
+
+    return None
+
+def quoteIfSpace(s):
+
+    # Windows doesn't like quotes when there are
+    # no spaces, but needs them otherwise
+    if s.find(" ") != -1:
+        s = '"%s"' % s
+    return s
+
+def findUnusedPort():
+
+    # bind to port 0 which makes the OS find the next
+    # unused port.
+
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.bind(("127.0.0.1", 0))
+    port = s.getsockname()[1]
+    s.close()
+
+    return port
+
+

Added: httpd/mod_python/branches/jgallacher/tools/test-ng/test.py
URL: http://svn.apache.org/viewvc/httpd/mod_python/branches/jgallacher/tools/test-ng/test.py?view=auto&rev=463963
==============================================================================
--- httpd/mod_python/branches/jgallacher/tools/test-ng/test.py (added)
+++ httpd/mod_python/branches/jgallacher/tools/test-ng/test.py Sat Oct 14 08:45:35 2006
@@ -0,0 +1,947 @@
+#!/usr/bin/env python
+
+#
+# Copyright 2006 Apache Software Foundation 
+# 
+# Licensed under the Apache License, Version 2.0 (the "License"); you
+# may not use this file except in compliance with the License.  You
+# may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.  See the License for the specific language governing
+# permissions and limitations under the License.
+#
+# $Id$
+#
+
+
+__version__ = "0.2.0"
+
+import glob
+import inspect
+import os
+import shutil
+import socket
+import sys
+import time
+import unittest
+
+from optparse import OptionParser
+import ConfigParser
+
+import mptest
+import mptest.testrunner
+from mptest.httpdconf import *
+import mptest.testconf
+from mptest.util import quoteIfSpace, findUnusedPort
+from mptest import testsetup
+
+
+
+def dist_clean(path=None):
+    """This is the same as make dist-clean in the old test framework."""
+
+    if path is None:
+        path = os.path.split(__file__)[0]
+
+    shutil.rmtree(os.path.join(path, 'logs/'), ignore_errors=True)
+    shutil.rmtree(os.path.join(path, 'tmp/'), ignore_errors=True)
+    shutil.rmtree(os.path.join(path, 'modules/'), ignore_errors=True)
+
+    for name in ['failure.log', 'conf/test.conf']:
+        f = os.path.join(path, name)
+        if os.path.exists(f):
+            os.remove(f)
+
+    
+    for root, dirs, files in os.walk(path):
+        # Don't visit any subversion subdirectories
+        # This makes it safe to run dist_clean() in place
+        # in a checked out svn copy.
+        try:
+            dirs.remove('.svn')
+        except:
+            pass
+
+        for f in files:
+            if f.endswith('.pyc'):
+                os.remove(os.path.join(root, f))
+
+
+def get_test_name(obj):
+    return '%s.%s' % (obj.__module__.split('.',1)[1], obj.__name__)
+
+def list_tests(args, verbose=0):
+    test_list = []
+    for t in testsetup.tests:
+        name = get_test_name(t)
+
+        if hasattr(t, 'disable') and t.disable == True:
+            msg = '  ' + ' (DISABLED)'.rjust(64 -len(name)).replace(' ', '.')
+        else:
+            msg = ''
+
+        if not args:
+            test_list.append('%s%s' % (name,msg))
+        else:
+            for arg in args:
+                if name.lower().startswith(arg.lower()):
+                    test_list.append('%s%s' % (name,msg))
+
+    test_list.sort()
+    for t in test_list:
+        print t
+
+    # prepend the test count with a '#' character so the output of
+    # this list can be used as the input of a new test run.
+    print '#', len(test_list), 'tests found'
+
+def list_test_groups(args=None, path='mptest/', verbose=0):
+    """TODO - implement globbing"""
+    exclude = ['__init__.py',]
+    # get the test packages
+    pkg_dirs = [d for d in os.listdir(path)
+        if os.path.isdir(os.path.join(path,d)) 
+        and not d.startswith('.svn')]
+
+    if args:
+        pkg_dirs = filter(lambda n: n in args, pkg_dirs)
+    
+    pkg_dirs.sort()
+    count = 0
+    for pkg in pkg_dirs:
+        files = [ os.path.split(f)[1]
+                    for f in glob.glob(os.path.join(path, pkg, '*.py')) ]
+        files.sort()
+        for f in [ os.path.splitext(f)[0] for f in files if f not in exclude ]:
+            count += 1
+            print '%s.%s' % (pkg,f)
+    
+    print "#", count, "test groups found"
+
+def list_test_packages(args=None, path='mptest/', verbose=0):
+    # TODO - implement globbing
+    dirs = [d for d in os.listdir(path)
+        if os.path.isdir(os.path.join(path,d)) 
+        and not d.startswith('.svn')]
+
+    if args:
+        dirs = filter(lambda n: n in args  , dirs)
+    
+    dirs.sort()
+    for d in dirs:
+        print d
+
+    print "#", len(dirs), "test groups found"
+
+def add_test(args, options):
+    # add_test can only handle one test at a time
+    name = args[0]
+    names = {}
+
+    parts = name.split('.')
+    print parts
+
+    pkg,grp,cls = parts
+    names['test_class'] = cls
+    names['base_class'] = 'PerRequestTestBase'
+    pkg_path = os.path.join('mptest', pkg)
+    if not os.path.exists(pkg_path):
+        os.mkdir(pkg_path)
+        f = open(os.path.join(pkg_path, '__init__.py'), 'w')
+        print >>f, '"""TEST PACKAGE DESCRIPTION DOCSTRING GOES HERE"""'
+        f.close()
+    
+    module_file = os.path.join(pkg_path,'%s.py' % grp)
+    if not os.path.exists(module_file):
+        # FIXME - this should just copy a template file into the new module
+        f = open(module_file, 'w')
+        print >>f, '''"""TEST GROUP DESCRIPTION GOES HERE"""
+        
+
+from mptest.httpdconf import *
+from mptest.testconf import * 
+from mptest.testsetup import register, apache_version
+from mptest.util import findUnusedPort
+'''
+      
+        f.close()
+   
+
+    f = open(module_file, 'a+')
+    print >>f, ''' 
+
+class %(test_class)s(%(base_class)s):
+    #handler = "override default handler here"
+    #server_name = "override default server_name here"
+    #document_root = "ovrride default document_root here"
+    disable = False # set disable = True to disable this test
+
+    def config(self):
+        return VirtualHost("*",
+                        ServerName(self.server_name),
+                        DocumentRoot(self.document_root),
+                        Directory(self.document_root,
+                                  SetHandler("mod_python"),
+                                  PythonHandler(self.handler),
+                                  PythonDebug("On")))
+
+    def runTest(self):
+        """%(test_class)s: SHORT DESCRIPTION OF FEATURE BEING TESTED"""
+
+        print "\\n  * Testing", self.shortDescription()
+        rsp = self.vhost_get(self.server_name)
+
+        if rsp != 'test ok': 
+            self.fail(`rsp`)
+
+register(%(test_class)s)
+
+''' % (names)
+    
+    # create the handler
+    document_root = os.path.join('htdocs', pkg)
+    if not os.path.exists(document_root):
+        os.mkdir(document_root)
+    module_file = os.path.join(document_root, '%s.py' % grp)
+    if not os.path.exists(module_file):
+        # FIXME - this should just copy a template file into the new module
+        f = open(module_file, 'w')
+        print >>f, '''"""DOCSTRING"""
+        
+from mod_python import apache 
+'''
+      
+        f.close()
+   
+
+    f = open(module_file, 'a+')
+    print >>f, '''
+
+def handler_%(test_class)s(req):
+    req.write('test ok')
+    return apache.OK
+
+''' % names
+
+def get_httpd(search_path=None, apxs=None):
+    # This needs to be integrated into or used by the HttpdControl class
+    # TODO: pass in a search path paramter to restrict the search
+    # TODO: pass in the value of --with-apxs to skip searching the path
+    # completely
+    
+    httpd_list = []
+
+    # apxs may be named apxs or apxs2
+    apxs_names = ['apxs2', 'apxs']
+
+    # build the search path
+    if not search_path:
+        if os.name == 'posix':
+            path_env_sep = ':'
+        
+        elif os.name == 'nt':
+            path_env_sep = ';'
+        else:
+            raise Exception, 'Unknown OS'
+        search_path = os.environ['PATH'].split(path_env_sep)
+    
+
+    apxs_path = None
+
+    # build a list of apxs candidates
+    apxs_list = []
+    if apxs:
+        apxs_list.append(apxs)
+    else:
+        for p in search_path:
+            for name in apxs_names:
+                apxs = quoteIfSpace(os.path.join(p, name))
+                if os.path.exists(apxs):
+                    apxs_list.append(apxs)
+
+    # get the httpd version
+    for apxs in apxs_list:
+        if not os.path.exists(apxs):
+            print apxs, 'does not exist'
+        else:
+            (stdin,stdout) = os.popen2('%s -q TARGET' % apxs)
+            httpd = stdout.readline().strip()
+            
+            (stdin,stdout) = os.popen2('%s -q SBINDIR' % apxs)
+            sbindir = stdout.readline().strip()
+
+            (stdin,stdout) = os.popen2('%s -q LIBEXECDIR' % apxs)
+            libexecdir = stdout.readline().strip()
+
+            httpd = os.path.join(sbindir, httpd)
+            httpd = quoteIfSpace(httpd)
+
+            cmd = '%s -v' % (httpd)
+            (stdin,stdout) = os.popen2(cmd)
+            for line in stdout:
+                if line.startswith('Server version'):
+                    version_str = line.strip()
+                    break
+            else:
+                print "Can't find Apache 'Server version' string for ", httpd
+
+            version_str = version_str.split('/')[1]
+            version = [ int(p) for p in version_str.split('.',3) ]
+
+            httpd_list.append([httpd, version, libexecdir, apxs])
+
+    return httpd_list
+
+class HttpdControl(object):
+
+    def __init__(self, options, document_root='htdocs'):
+        self._apxs = options.apxs_path  # path to the apxs binary
+        self._httpd = None      # path to the httpd binary
+        self._libexecdir = None # path to apache modules
+        self._modpython_so = None # path to the mod_python.so module
+        self._version = None    # the apache version
+
+        # attrbutes used in the apache conf file
+        self.test_home = os.getcwd()
+        self.server_root = self.test_home
+        self.document_root = os.path.join(self.test_home, document_root)
+        self.config_file = os.path.join(self.test_home, 'conf', 'test.conf')
+        self.error_log = 'logs/error_log'
+        self.tmp_dir = os.path.join(self.test_home, 'tmp')
+        self.port = 0
+        self.httpd_running = 0
+
+        self.legacy_importer_enabled = options.legacy_importer
+
+        # rotate_count is used to keep track of log files for
+        # log rotation
+        self.rotate_count = 1
+
+        self.tests = {} 
+        self.checkFiles()
+
+    def _get_apxs(self):
+        if not self._apxs:
+
+            # apxs may be named apxs or apxs2
+            apxs_names = ['apxs2', 'apxs']
+            if os.name == 'posix':
+                path_env_sep = ':'
+            
+            elif os.name == 'nt':
+                path_env_sep = ';'
+            else:
+                raise Exception, 'Unknown OS'
+           
+            path_parts = os.environ['PATH'].split(path_env_sep)
+
+            apxs_path = None
+            for p in path_parts:
+                for name in apxs_names:
+                    apxs_path = os.path.join(p, name)
+                    if os.path.exists(apxs_path):
+                        self._apxs = apxs_path
+                        break
+                else:
+                    continue
+                break
+
+        # TODO - check if apxs was not found and raise an exception
+        if self._apxs is None:
+            raise Exception, "Can't find apxs or apxs2 on PATH - aborting\n  Try using 'test.py --with-apxs=/path/to/apxs'"
+
+        elif not os.path.exists(self._apxs):
+            raise Exception, """Can't find apxs "%s" - aborting""" % self._apxs
+        
+        return quoteIfSpace(self._apxs)
+
+    def _set_apxs(self, value):
+        self._apxs = value
+
+    apxs = property(_get_apxs, _set_apxs) 
+
+    def _get_httpd(self):
+        if not self._httpd:
+            (stdin,stdout) = os.popen2('%s -q TARGET' % self.apxs)
+            httpd = stdout.readline().strip()
+            
+            (stdin,stdout) = os.popen2('%s -q SBINDIR' % self.apxs)
+            path = stdout.readline().strip()
+
+            httpd_path = os.path.join(path, httpd)
+            self._httpd = httpd_path
+        return quoteIfSpace(self._httpd)
+
+    def _set_httpd(self, value):
+        self._httpd = value
+        # just in case this is a different http
+        # we'll force the version to re-read
+        # next time it's accessed
+        self._version = None
+
+    httpd = property(_get_httpd, _set_httpd)
+
+    def _get_libexecdir(self):
+        if not self._libexecdir:
+            (stdin,stdout) = os.popen2('%s -q LIBEXECDIR' % self.apxs)
+            self._libexecdir = stdout.readline().strip()
+
+        return quoteIfSpace(self._libexecdir)
+
+    def _set_libexecdir(self, value):
+        self._libexecdir = value
+
+    libexecdir = property(_get_libexecdir, _set_libexecdir)
+
+
+    def _set_modpython_so(self, value):
+        self._modpython_so = value
+
+    def _get_modpython_so(self):
+        if not self._modpython_so:
+            self._modpython_so = os.path.join(self.libexecdir, 'mod_python.so')
+
+        return quoteIfSpace(self._modpython_so)
+
+    mod_python_so = property(_get_modpython_so, _set_modpython_so)
+
+    def _get_version(self):
+        if not self._version:
+            httpd = quoteIfSpace(self.httpd)
+            cmd = '%s -v' % (httpd)
+            (stdin,stdout) = os.popen2(cmd)
+            for line in stdout:
+                if line.startswith('Server version'):
+                    version_str = line.strip()
+                    break
+            else:
+                # TODO - should raise an exception here
+                pass
+
+            version_str = version_str.split('/')[1]
+            self._version = [ int(p) for p in version_str.split('.',3) ]
+
+        return self._version
+
+    version = property(_get_version)
+
+    def start(self, extra=''):
+        print "  Starting Apache...."
+        cmd = '%s %s -k start -f %s' % (self.httpd, extra, self.config_file)
+        print "    ", cmd
+        os.system(cmd)
+        time.sleep(1)
+        self.httpd_running = 1
+
+    def stop(self):
+        print "  Stopping Apache..."
+        cmd = '%s -k stop -f %s' % (self.httpd, self.config_file)
+        print "    ", cmd
+        os.system(cmd)
+        time.sleep(1)
+
+        # Wait for apache to stop by checking for the existence of pid the 
+        # file. If pid file still exists after 20 seconds raise an error.
+        # This check is here to facilitate testing on the qemu emulator.
+        # Qemu will run about 1/10 the native speed, so 1 second may
+        # not be long enough for apache to shut down.
+        count = 0
+        pid_file = os.path.join(self.test_home, 'logs/httpd.pid')
+        while os.path.exists(pid_file):
+            time.sleep(1)
+            count += 1
+            if count > 20:
+                # give up - apache refuses to die - or died a horrible
+                # death and never removed the pid_file.
+                raise RuntimeError, "  Trouble stopping apache"
+
+        self.httpd_running = 0
+
+    def rotate_logs(self):
+        # this code is adapted from python logging.RotatingFileHandler.doRollover()
+        # TODO - FIXME
+        log_files = ['access_log', 'error_log'] 
+
+        if self.rotate_count > 0:
+            for baseFilename in log_files:
+                for i in range(self.rotate_count - 1, 0, -1):
+                    sfn = os.path.join(self.test_home, 'logs', "%s.%d" % (baseFilename, i))
+                    dfn = os.path.join(self.test_home, 'logs', "%s.%d" % (baseFilename, i + 1))
+                    if os.path.exists(sfn):
+                        #print "%s -> %s" % (sfn, dfn)
+                        if os.path.exists(dfn):
+                            os.remove(dfn)
+                        os.rename(sfn, dfn)
+                dfn =  os.path.join(self.test_home, 'logs', baseFilename + ".1")
+                if os.path.exists(dfn):
+                    os.remove(dfn)
+                try:
+                    os.rename(os.path.join(self.test_home, 'logs', baseFilename), dfn)
+                except (KeyboardInterrupt, SystemExit):
+                    raise
+                except:
+                    print 'oops'
+                    #self.handleError(record)
+                    raise
+        self.rotate_count += 1
+
+    def checkFiles(self):
+        modules = os.path.join(self.server_root, "modules")
+        if not os.path.exists(modules):
+            os.mkdir(modules)
+        if not os.path.exists(self.tmp_dir):
+            os.mkdir(self.tmp_dir)
+
+    def clean(self):
+        self.clean_logs()
+        self.clean_tmp()
+
+    def clean_logs(self):
+        logs = os.path.join(self.server_root, "logs")
+        if os.path.exists(logs):
+            shutil.rmtree(logs)
+        os.mkdir(logs)
+
+    def clean_tmp(self):
+        if os.path.exists(self.tmp_dir):
+            shutil.rmtree(self.tmp_dir)
+        os.mkdir(self.tmp_dir)
+
+
+    def makeConfig(self, append=""):
+
+        # create config files, etc
+
+        print "  Creating config...."
+
+        self.checkFiles()
+
+        self.port = findUnusedPort()
+        print "    listen port:", self.port
+
+        s = Container(
+            IfModule("prefork.c",
+                     StartServers("3"),
+                     MaxSpareServers("1")),
+            IfModule("worker.c",
+                     StartServers("2"),
+                     MaxClients("6"),
+                     MinSpareThreads("1"),
+                     MaxSpareThreads("1"),
+                     ThreadsPerChild("3"),
+                     MaxRequestsPerChild("0")),
+            IfModule("perchild.c",
+                     NumServers("2"),
+                     StartThreads("2"),
+                     MaxSpareThreads("1"),
+                     MaxThreadsPerChild("2")),
+            IfModule("mpm_winnt.c",
+                     ThreadsPerChild("5"),
+                     MaxRequestsPerChild("0")),
+            IfModule("!mod_mime.c",
+                     LoadModule("mime_module %s" %
+                                quoteIfSpace(os.path.join(self.libexecdir, "mod_mime.so")))),
+            IfModule("!mod_log_config.c",
+                     LoadModule("log_config_module %s" %
+                                quoteIfSpace(os.path.join(self.libexecdir, "mod_log_config.so")))),
+            IfModule("!mod_dir.c",
+                     LoadModule("dir_module %s" %
+                                quoteIfSpace(os.path.join(self.libexecdir, "mod_dir.so")))),
+            IfModule("!mod_include.c",
+                     LoadModule("include_module %s" %
+                                quoteIfSpace(os.path.join(self.libexecdir, "mod_include.so")))),
+            ServerRoot(self.server_root),
+            ErrorLog(self.error_log),
+            LogLevel("debug"),
+            LogFormat(r'"%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined'),
+            CustomLog("logs/access_log combined"),
+            LockFile("logs/accept.lock"),
+            TypesConfig("conf/mime.types"),
+            PidFile("logs/httpd.pid"),
+            ServerName("127.0.0.1"),
+            Listen(self.port),
+            PythonOption('mod_python.mutex_directory %s' % self.tmp_dir),
+            PythonOption('PythonOptionTest sample_value'),
+            PythonOption('mod_python.legacy.importer *', self.legacy_importer_enabled),
+            DocumentRoot(self.document_root),
+            LoadModule("python_module %s" % quoteIfSpace(self.mod_python_so)))
+
+        if self.version[0] == 2 and self.version[1] == 2:
+            # mod_auth has been split into mod_auth_basic and some other modules
+            s.append(IfModule("!mod_auth_basic.c",
+                     LoadModule("auth_basic_module %s" %
+                                quoteIfSpace(os.path.join(self.libexecdir, "mod_auth_basic.so")))))
+
+            # Default KeepAliveTimeout is 5 for apache 2.2, but 15 in apache 2.0
+            # Explicitly set the value so it's the same as 2.0
+            s.append(KeepAliveTimeout("15"))
+        else:
+            s.append(IfModule("!mod_auth.c",
+                     LoadModule("auth_module %s" %
+                                quoteIfSpace(os.path.join(self.libexecdir, "mod_auth.so")))))
+
+        s.append("\n# --APPENDED-- \n\n" + str(append))
+
+        f = open(self.config_file, "w")
+        f.write(str(s))
+        f.close()
+        self.config_exists = True
+        
+    def appendConfig(self, append):
+        append = str(append)
+        if not self.config_exists:
+            self.makeConfig(append)
+        else:
+            f = open(self.config_file, "a+")
+            f.write(str(append))
+            f.close()
+
+    def deleteConfig(self):
+        if os.path.exists(self.config_file):
+            os.remove(self.config_file)
+        self.config_exists = False
+
+
+
+def run_tests(options, args=None):
+    server_tests = []
+    vhost_tests = []
+
+
+    for obj in testsetup.tests:
+        grp,module = obj.__module__.split('.')[1:]
+        name = '%s.%s.%s' % (grp, module, obj.__name__)
+        if hasattr(obj, 'disable') and obj.disable == True \
+                    and not options.force:
+            if options.verbose or options.debug:
+                print 'skipping', name, '(DISABLED)' 
+                
+            continue
+        
+        include_test = False
+        # TODO - need to do proper filtering of names
+        for arg in args:
+            if name.startswith(arg):
+                include_test = True
+                if options.verbose or options.debug:
+                    print 'including', name
+                break
+        if include_test:
+            if issubclass(obj, (mptest.testconf.PerInstanceTestBase)):
+                server_tests.append(obj)
+            else:
+                vhost_tests.append(obj)
+                
+
+    tr = mptest.testrunner.TextTestRunner()
+
+    # run the server tests first
+    # each test gets a new httpd instance
+    for obj in server_tests:
+        document_root = 'htdocs/%s' % obj.__module__.split('.')[1]
+        httpd = HttpdControl(options, document_root=document_root)
+        httpd.clean()
+        httpd.makeConfig()
+        t = obj(httpd=httpd, document_root=httpd.document_root)
+        httpd.appendConfig(t.config())
+        tr.run(t)
+
+    # run the virtual host tests
+    # All tests use the same httpd instance
+
+    if vhost_tests:
+        httpd = HttpdControl(options)
+        httpd.clean()
+        httpd.makeConfig()
+        httpd.appendConfig("\nNameVirtualHost *\n\n")
+        suite = unittest.TestSuite()
+        for obj in vhost_tests:
+            t = obj(httpd=httpd)
+            t.config_file = httpd.appendConfig(t.config())
+            suite.addTest(t)
+        tr.run(suite)
+
+    # print the final report
+    test_results = tr.report()
+
+    # make sure httpd is stopped
+    httpd.stop()
+
+   
+    # Dump logs and test.conf for failed tests
+    # FIXME - This really should be written to a file rather
+    # than stdout.
+    failure_log = []
+
+    for tc,tb in test_results.failures:
+        name = '%s.%s' % (tc.__module__,tc.__class__.__name__)
+        failure_log.append('%s FAIL' % (name))
+        if options.debug:
+            print '=' * 50
+            print 'FAIL', name
+            print '=' * 50
+            print "TEST CONF"
+            print '-' * 50
+            print tc.config_archive
+            print '-' * 50
+            print "ERROR LOG"
+            print '-' * 50
+            print tc.log_archive
+        
+    for tc,tb in test_results.errors:
+        name = '%s.%s' % (tc.__module__,tc.__class__.__name__)
+        failure_log.append('%s ERR' % (name))
+        if options.debug:
+            print '=' * 50
+            print 'ERROR', name
+            print '=' * 50
+            print tc.log_start
+            print '-' * 50
+            print "TEST CONF"
+            print '-' * 50
+            print tc.config_archive
+            print '-' * 50
+            print "ERROR LOG"
+            print '-' * 50
+            print tc.log_archive
+
+    failure_filename = 'failures.txt'    
+    if os.path.exists(failure_filename):
+        os.remove(failure_filename)
+        if failure_log:
+            failure_log.sort()
+            fout = open(failure_filename, 'w')
+            fout.write('\n'.join(failure_log))
+            fout.write('\n')
+            fout.close()
+
+def main():
+    usage = """usage: %prog [options] [tests]
+   
+    When [tests] are omitted the default behaviour is to run all the tests
+    in the core package. This is equivalent to running "python test.py"
+    in the old test framework.
+
+    You can specify test packages, test groups or individual tests cases or
+    use globbing to select the tests to run.
+
+    
+
+    Test Names
+        TODO - Some of this should go in the README instead as it
+        contains information on writing tests rather than using them.
+
+        Test names are case insensitive..
+
+        The format of a test name is:
+            <test_package>.<test_group>.<test_case>
+        where
+            test_package:
+                corresponds to a subdirectory of mptest/.
+            
+            test_group:
+                corresponds to a *.py module file in directory 
+                mptest/test_group/.  The test_group directory must contain
+                a __init__.py file for the tests to be properly registered.
+            
+            test_case:
+                corresponds to a test class in the test_group module file.
+                The test_case must regisiter itself in order to be found
+                by the test system.
+
+                eg.
+                from mptest.testset import register
+                
+                class MyTestCase(object): pass
+
+                register(MyTestCase)
+                
+            
+
+    Examples
+
+        Run all tests in the core package
+        $ python test.py core
+
+        Run all tests in the leaktests package
+        $ python test.py leaktests
+
+        Run all tests the core.session get package 
+        $ python test.py core.session
+
+        Specify individual tests
+        $ python test.py core.session.tests.foo core.session.BadSid
+
+        Specify tests in 2 different test groups
+        $ python test.py core.session core.publisher
+ 
+        Specify tests using globbing
+        $ python test.py core.publisher.auth*
+        would run any test cases starting with "auth".
+        Note that test.py --list always assumes test_name.*
+
+        At this time only test_name* globbing is supported. If there is demand
+        for it full globbing or regex match can be added in the future.
+
+        Also be aware that you may get unexpected results if 
+        test_name_frament* matches any files or directories in the current
+        working directory, due to shell file globbing. To avoid this you can
+        put your test name pattern. eg.
+
+        $ python test.py "con*"
+       
+    """
+    parser = OptionParser(usage=usage, version="%s" % __version__)
+   
+    # no config file handling just yet.
+    # parser.add_option("-c", "--config", dest="config_file",
+                      # metavar="FILE",
+                      # help="Configuration file")
+
+    parser.add_option("-a", "--add",
+                      action="store_true", dest="add_test", default=False,
+                      help="Add a new test package, test group or test case. "
+                      "NOT IMPLEMENTED")
+
+    parser.add_option("--clean",
+                      action="store_true", dest="cleanup", default=False,
+                      help="Removes conf/test.conf, logs/*, tmp/ "
+                      "and *.pyc files and the exits. "
+                      "This corresponds to 'make dist-clean' in "
+                      "the old test framework.")
+
+    parser.add_option("-f", "--force",
+                      action="store_true", dest="force", default=False,
+                      help="Force disable tests to run.")
+
+    parser.add_option("-i", "--in-file", 
+                      dest="tests_file", default=None,
+                      metavar="FILE",
+                      help="Run tests listed in FILE. "
+                      "These tests will be added to the command line arguments.")
+
+    parser.add_option("-k", "--keep",
+                      action="store_true", dest="keep", default=False,
+                      help="Keep a copy of test.conf and logs for later "
+                           "analysis. NOT IMPLEMENTED.")
+
+    parser.add_option("-l", "--list-tests",
+                      action="store_true", dest="list_test_cases", default=False,
+                      help="List test modules."
+                           "")
+
+    parser.add_option("-p", "--list-packages",
+                      action="store_true", dest="list_test_packages", default=False,
+                      help="List test packages."
+                           "")
+
+    parser.add_option("-g", "--list-groups",
+                      action="store_true", dest="list_test_groups", default=False,
+                      help="List test groups."
+                           "")
+
+    parser.add_option("-v", "--verbose", 
+                      action="count", dest="verbose", default=0,
+                      help="Increase verbosity. "\
+                           "You can specify -v up to 3 times to get a greater"
+                           "level of detail. Using -v -v -v will cause the http"
+                           "response to be printed to stderr. The most verbose"
+                           "setting could result in a large amount of output "
+                           "and should likely be used in conjuction with -n 1.")
+
+    parser.add_option("--with-apxs",
+                      dest="apxs_path", type="string", default=None,
+                      metavar="FILE",
+                      help="Specify an alternative path to apxs. "
+                           "By default test.py will attempt to locate apxs2 "
+                           "or apxs in the current PATH. "
+                           "--with-apxs allows you to override this.")
+
+    parser.add_option("--with-legacy-importer",
+                      action="store_true", dest="legacy_importer", default=False,
+                      help="Enable the legacy importer.")
+
+
+    parser.add_option("-d", "--debug",
+                      action="store_true", dest="debug", default=False,
+                      help="Output debugging messages.")
+
+    parser.add_option("--find-httpd",
+                      action="store_true", dest="thing", default=False,
+                      help="Searches the PATH for apxs and exits."
+                      "prints a list of [httpd_path, httpd_version, libexecdir, apxs_path)")
+
+
+
+    (options, args) = parser.parse_args()
+
+    # end of cmd line parsing
+
+
+    if options.cleanup:
+        dist_clean()
+        sys.exit(0)
+
+    testsetup.debug = options.debug
+
+
+    if options.thing:
+        rs = get_httpd()
+        for r in rs:
+            print r
+        
+        sys.exit(0)
+
+    # test modules may need the apache version
+    # to decide if they are going to register themselves
+    testsetup.apache_version = HttpdControl(options).version
+    testsetup.load_tests()
+
+    if options.add_test:
+        if not args:
+            print 'Usage: test.py -a <test_package>.<test_group>.<test_case>' 
+            sys.exit(0)
+        add_test(args, options)
+        sys.exit(0)
+
+    if options.list_test_packages:
+        list_test_packages(args,verbose=options.verbose)
+        sys.exit(0)
+
+    if options.list_test_groups:
+        list_test_groups(args,verbose=options.verbose)
+        sys.exit(0)
+
+    if options.list_test_cases:
+        list_tests(args, verbose=options.verbose)
+        sys.exit(0)
+        
+    if options.tests_file:
+        # include tests list in file
+        # lines starting with # will be ignored
+        # testnames must not include whitespace
+        if not os.path.exists(options.tests_file):
+            print "No such file: '%s'" % options.tests_file
+            sys.exit(0)
+
+        test_list = open(options.tests_file, 'r').readlines()
+        test_list = [ line.strip().split(' ')[0]
+                        for line in test_list 
+                        if not line.startswith('#') ] 
+
+        args = args + test_list
+
+
+    if not args:
+        # run the default tests
+        # this is the same as the old test framework
+        args = ['core', ]
+    run_tests(options, args)
+
+if __name__ == '__main__':
+    main()

Propchange: httpd/mod_python/branches/jgallacher/tools/test-ng/test.py
------------------------------------------------------------------------------
    svn:executable = *