[Zope-CVS] CVS: Packages/FunctionalTests/FunctionalTests - Request.py:1.8 ScenarioGenerator.py:1.9 interfaces.py:1.4

Tres Seaver tseaver at zope.com
Sat Apr 2 18:22:23 EST 2005


Update of /cvs-repository/Packages/FunctionalTests/FunctionalTests
In directory cvs.zope.org:/tmp/cvs-serv26739/FunctionalTests

Modified Files:
	Request.py ScenarioGenerator.py interfaces.py 
Log Message:
 - Lay in last set of updates (not yet finished).


=== Packages/FunctionalTests/FunctionalTests/Request.py 1.7 => 1.8 ===
--- Packages/FunctionalTests/FunctionalTests/Request.py:1.7	Sun Jun 15 06:01:25 2003
+++ Packages/FunctionalTests/FunctionalTests/Request.py	Sat Apr  2 18:21:53 2005
@@ -1,13 +1,16 @@
 """ Classes:  RequestError, HTTPRequest, ZEORequest
     Functions: buildRequest
 
-$Id
+$Id $
 """
 import re
 import httplib
 import urllib
 import urlparse
 import base64
+import MimeWriter
+import StringIO
+import rfc822
 
 from interfaces import IPluginFunction
 from interfaces import IRequest
@@ -183,6 +186,8 @@
         self._expected_redirect = None
         self._expected_cookies = ()
         self._payload_checker = None
+        self._contentTypeCache = None
+        self._dataCache = None
     
     def _initURLParts( self, URL ):
 
@@ -198,7 +203,7 @@
             self._host, self._port = hp[0], int( hp[1] )
         else:
             self._host = self._netloc
-            self._port = 80 # XXX s.b. default for scheme?
+            self._port = None
 
         self._path, self._params, self._query, self._fragment = parts[2:]
     #
@@ -316,12 +321,23 @@
         """ See IHTTPRequest.
         """
         return self._expected_cookies is not None
+
+    def setMultipart( self ):
+
+        """ See IHTTPRequest.
+        """
+        self._has_file_field = 1
     
     def getContentType( self ):
 
         """ See IHTTPRequest.
         """
-        return self._has_file_field and _MULTIPART or _URLENCODED
+        if self._has_file_field:
+            self._setMultipartDataAndType()
+            return self._contentTypeCache
+
+        return _URLENCODED
+
 
     def getData( self ):
 
@@ -329,7 +345,8 @@
         """
         if self._has_file_field:
 
-            return _mime_encode( self._fields )
+            self._setMultipartDataAndType()
+            return self._dataCache
 
         else:
 
@@ -368,8 +385,11 @@
 
         """ See IHTTPRequest.
         """
+        if self._dataCache:
+            # adding could change boundary which may have been gotten
+            raise RequestError, "cannot add field after getting data or type"
         field = _buildField( field_desc )
-        if field[2] == 'file':
+        if field[1] == 'file':
             self._has_file_field = 1
         self._fields.append( field )
 
@@ -414,6 +434,21 @@
         """
         self._payload_checker = PluginFunction( filename, function )
 
+    # we need to cache because content-type and data must both state
+    # the same boundary
+    def _setMultipartDataAndType( self ):
+        """ See IHTTPRequest.
+        """
+        if not self._has_file_field:
+            raise RuntimeError, \
+                "attempt to cache data and type of non-multipart type request"
+        if not self._contentTypeCache:
+            mess = _mime_encode( self._fields )
+            type = mess['content-type']
+            type = type.replace('\n ', ' ')
+            self._dataCache = mess.fp.read()[1:]
+            self._contentTypeCache = type
+
     #
     #   Request invocation
     #
@@ -428,14 +463,28 @@
             invocation.beginRequest()
 
         host, port = self.getHost(), self.getPort()
+        if port:
+            conport = port
+        else:
+            conport = 80                # XXX s.b. default for scheme
         try:
-            connection = httplib.HTTP( host, port )
+            connection = httplib.HTTP( host, conport )
             connection.putrequest( self.getMethod(), self.getURI() )
-            connection.putheader( 'Host', '%s:%d' % ( host, port ) )
 
+            if port:
+                connection.putheader( 'Host', '%s:%d' % ( host, port ) )
+            else:
+                # Port could be set to 80, or the default for the scheme,
+                # RFC2616 says that giving no port is equivalent to giving the
+                # default.  However, in practice (Apache multihosting), a port
+                # 80 URI is sometimes redirected to the portless URI, so we
+                # give what the config file asked.
+                connection.putheader( 'Host', '%s' % host )
             token = self.getAuthenticationToken()
             if token:
                 scrambled = base64.encodestring( token )
+                if scrambled[-1] == '\n':
+                    scrambled = scrambled[:-1]
                 connection.putheader( 'Authorization', 'Basic %s' % scrambled )
 
             for key, value in self.getHeaders():
@@ -489,6 +538,12 @@
 
         self._plugin = PluginFunction( filename, function )
 
+    def setExpectedResult( self, result_code ):
+
+        """ See IZEORequest.
+        """
+        self._expected_result = result_code
+
     #
     #   Request invocation
     #
@@ -565,14 +620,29 @@
     raise RequestError, 'Invalid field_desc: %s' % field_desc
 
 _URLENCODED = 'application/x-www-form-urlencoded'
-_MULTIPART  = 'multipart/form-data'
+#_MULTIPART  = 'multipart/form-data'
 
 def _mime_encode( fields ):
 
     """ Build a 'multipart/form-data' representation of the
         (name,type,value) items in fields.
     """
-    raise NotImplementedError
+    # MimeWriter has subtle differences from the spec, will ususally be OK
+    # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306    
+    fp = StringIO.StringIO()
+    writer = MimeWriter.MimeWriter( fp )
+    writer.startmultipartbody( 'form-data' )
+    for field in fields:
+        if field[1] == 'file':
+            raise NotImplementedError
+        subwriter = writer.nextpart()
+        subwriter.addheader( 'content-disposition'
+                           , 'form-data; name="%s"' % field[0] )
+        f = subwriter.startbody( 'text/plain' )
+        f.write( field[2] )
+    writer.lastpart()
+    fp.seek(0)
+    return rfc822.Message( fp )
 
 def _buildSleepRequest( cp, section ):
 


=== Packages/FunctionalTests/FunctionalTests/ScenarioGenerator.py 1.8 => 1.9 ===
--- Packages/FunctionalTests/FunctionalTests/ScenarioGenerator.py:1.8	Thu Mar  4 08:51:30 2004
+++ Packages/FunctionalTests/FunctionalTests/ScenarioGenerator.py	Sat Apr  2 18:21:53 2005
@@ -13,6 +13,8 @@
 import mimetools
 import urllib
 import urlparse
+import multifile
+import StringIO
 
 _SCENARIO_SKELETON = """\
 [Scenario]
@@ -64,17 +66,18 @@
         LoggingProxy) into a scenario file (to be consumed by
         FT_Runner).
     """
-    _verbosity          = 1
-    _capture_cookies    = 0
-    _logfile_directory  = '/tmp'
-    _logfile_prefix     = 'watch'
-    _logfile_extension  = 'request'
-    _output_file        = None
-    _exclude_patterns   = []
-    _exclude_file       = None
-    _exclude_regex      = None
-    _site_host         = None
-    _site_path          = None
+    _verbosity              = 1
+    _capture_cookies        = 0
+    _logfile_directory      = '/tmp'
+    _logfile_prefix         = 'watch'
+    _logfile_extension_req  = 'request'
+    _logfile_extension_resp = 'response'
+    _output_file            = None
+    _exclude_patterns       = []
+    _exclude_file           = None
+    _exclude_regex          = None
+    _site_host              = None
+    _site_path              = None
 
     def __init__( self, args ):
 
@@ -106,8 +109,11 @@
     -f, --logfile-prefix            Prefix for log file names
                                     (default '%(LOGFILE_PREFIX)s')
 
-    -e, --logfile-extension         Extension for log file names
-                                    (default '%(LOGFILE_EXTENSION)s')
+    -e, --logfile-extension         Extension for log file request names
+                                    (default '%(LOGFILE_EXTENSION_REQ)s')
+
+    -E, --logfile-response          Extension for log file response names
+                                    (default '%(LOGFILE_EXTENSION_RESP)s')
 
     -o, --output-file               Write to 'file', instead of default
                                     (%(LOGFILE_PREFIX)s.zft). 
@@ -128,7 +134,8 @@
 %(MESSAGE)s\n""" % { 'GENERATOR_EXE'        : sys.argv[0]
                    , 'LOGFILE_DIRECTORY'    : self._logfile_directory
                    , 'LOGFILE_PREFIX'       : self._logfile_prefix
-                   , 'LOGFILE_EXTENSION'    : self._logfile_extension
+                   , 'LOGFILE_EXTENSION_REQ'    : self._logfile_extension_req
+                   , 'LOGFILE_EXTENSION_RESP'    : self._logfile_extension_resp
                    , 'MESSAGE'              : msg or ''
                    } )
         sys.exit( 1 )
@@ -139,12 +146,13 @@
         """
         verbosity = self._verbosity
         capture_cookies = self._capture_cookies
-        logfile_directory = logfile_prefix = logfile_extension = None
+        logfile_directory = logfile_prefix = None
+        logfile_extension_req = logfile_extension_resp = None
         output_file = exclude_file = site_host = site_path = None
 
         try:
             opts, ignored = getopt.getopt( args
-                                         , "?vqcCl:f:e:o:x:X:h:p:"
+                                         , "?vqcCl:f:e:E:o:x:X:h:p:"
                                          , [ 'help'
                                            , 'verbose'
                                            , 'quiet'
@@ -153,6 +161,7 @@
                                            , 'logfile-directory='
                                            , 'logfile-prefix='
                                            , 'logfile-extension='
+                                           , 'logfile-extension-response='
                                            , 'output-file'
                                            , 'exclude-pattern'
                                            , 'exclude-file'
@@ -187,7 +196,10 @@
                 logfile_prefix = v
 
             if o == '-e' or o == '--logfile-extension':
-                logfile_extension = v
+                logfile_extension_req = v
+
+            if o == '-E' or o == '--logfile-extension-response':
+                logfile_extension_resp = v
 
             if o == '-o' or o == '--output-file':
                 output_file = v
@@ -213,8 +225,11 @@
         if logfile_prefix is not None:
             self._logfile_prefix = logfile_prefix
 
-        if logfile_extension is not None:
-            self._logfile_extension = logfile_extension
+        if logfile_extension_req is not None:
+            self._logfile_extension_req = logfile_extension_req
+
+        if logfile_extension_resp is not None:
+            self._logfile_extension_resp = logfile_extension_resp
 
         if site_host is not None:
             self._site_host = site_host
@@ -298,24 +313,28 @@
         return self._exclude_regex
 
     def processFile( self
-                   , filename
+                   , infilename
+                   , outfilename
                    , request_num
                    , parms={}
                    , REQUEST_LINE=re.compile( r'^([^\s]+)\s+'
                                               r'([^\s]+)\s+'
                                               r'([^\s]+)$' )
+                   , RESPONSE_LINE=re.compile( r'^([^\s]+)\s+'
+                                               r'([0-9][0-9][0-9])\s+'
+                                               r'(.*)$' )
                    ):
         """
             Process a single request file;  record global context
             in parms.
         """
-        self._log( 'Scanning file: %s' % filename, 1 )
+        self._log( 'Scanning request file: %s' % infilename, 1 )
 
         parms[ 'content_type' ] = None
         parms[ 'header_count' ] = 0
         parms[ 'cookie_count' ] = 0
 
-        f = open( filename )
+        f = open( infilename )
         all_text = f.read()
         body_end = f.tell()
         f.seek( 0 )
@@ -422,8 +441,19 @@
         payload = f.read()
         f.close()
 
+        if outfilename:
+            self._log( 'Scanning response file: %s' % outfilename, 1 )
+            f = open( outfilename )
+            # could exclude here as well
+            status = f.readline().rstrip()
+            match = RESPONSE_LINE.match( status )
+            http_verb, code, reason = match.groups()        
+            f.close()
+        else:
+            code = 200
+
         self._print( _RESULT_SKELETON
-                   , RESULT_CODE=200    # TODO:  Read from response?
+                   , RESULT_CODE=int( code )
                    )
 
         return request_num
@@ -431,16 +461,22 @@
     def processScenario( self ):
         """
             Read all files in '_logfile_directory' whose prefix
-            matches '_logfile_prefix' and '_logfile_extension;
+            matches '_logfile_prefix', '_logfile_extension_req', and
+            '_logfile_extension_resp';
             create a scenario file with one section per request file,
             dumping to specified output file.
         """
         self._print( _SCENARIO_SKELETON )
 
-        glob_pattern = '%s/%s*.%s' % ( self._logfile_directory
-                                     , self._logfile_prefix
-                                     , self._logfile_extension
-                                     )
+        glob__in_pattern = '%s/%s*.%s' % ( self._logfile_directory
+                                         , self._logfile_prefix
+                                         , self._logfile_extension_req
+                                         )
+
+        glob__out_pattern = '%s/%s*.%s' % ( self._logfile_directory
+                                          , self._logfile_prefix
+                                          , self._logfile_extension_resp
+                                          )
 
         request_num = 0
         parms = { 'saw_authentication' : 0
@@ -448,11 +484,30 @@
                 , 'site_path' : self._site_path
                 }
 
-        filenames = glob.glob( glob_pattern )
-        filenames.sort()
-        for filename in filenames:
-            request_num = self.processFile( filename, request_num, parms )
-
+        infilenames = glob.glob( glob__in_pattern )
+        infilenames.sort()
+        outfilenames = glob.glob( glob__out_pattern )
+        outfilenames.sort()
+        for infilename in infilenames:
+            # find the response file name that matches this request file
+            outfilename = re.sub( self._logfile_extension_req + '$'
+                                , self._logfile_extension_resp
+                                , infilename
+                                )
+            # XXX error if missing?  Optional outfile processing?
+            if outfilename in outfilenames:
+                request_num = self.processFile( infilename
+                                              , outfilename
+                                              , request_num
+                                              , parms
+                                              )
+            else:
+                request_num = self.processFile( infilename
+                                              , None
+                                              , request_num
+                                              , parms
+                                              )
+                
         self._print( _DEFAULT_SKELETON % parms )
 
 if __name__ == '__main__':


=== Packages/FunctionalTests/FunctionalTests/interfaces.py 1.3 => 1.4 ===
--- Packages/FunctionalTests/FunctionalTests/interfaces.py:1.3	Fri Jun 13 23:22:55 2003
+++ Packages/FunctionalTests/FunctionalTests/interfaces.py	Sat Apr  2 18:21:53 2005
@@ -1,6 +1,6 @@
 """ Interfaces:  IRequestInvocation
 
-$Id$
+$Id $
 """
 
 try:
@@ -204,6 +204,10 @@
         """ Should invocations of this request retain cookies?
         """
 
+    def setMultipart():
+        """ Set content-type to multipart/form-data.
+        """
+
     def getContentType():
 
         """ Return the HTTP 'Content-type' to use for this request.
@@ -222,6 +226,13 @@
         o Return None if no function is assigned.
         """
 
+    def getHook():
+
+        """ Return a function used to change configuration variables.
+
+        o Return None if no function is assigned.
+        """
+
     def addHeader( header_desc ):
 
         """ Construct a header tuple and add it to our list.
@@ -267,6 +278,15 @@
         """ Create a checker function as a plugin function.
         """
 
+    def _setMultipartDataAndType():
+        """ Encode and cache the data and content-type.
+        """
+
+    def setHook( filename, function ):
+
+        """ Create a hook function as a plugin function.
+        """
+
 
 class IResult( Interface ):
 
@@ -327,6 +347,11 @@
         """ Should we validate elapsed times?
         """
 
+    def checkHook():
+
+        """ Should we call any hooks?
+        """
+
     def listInvocations( roll_up=1 ):
 
         """ Return the list of invocations we manage.
@@ -366,6 +391,11 @@
         """ Setup our parent request.
         """
 
+    def updateDefaults( dict ):
+
+        """ Update our defaults dict with dict.
+        """
+
     def setCookies( cookies ):
 
         """ Store our cookies.
@@ -443,3 +473,8 @@
     
     o Executed via ZEO, rather than via HTTP.
     """
+
+    def setExpectedResult( result_code ):
+
+        """ Override the default expected result code.
+        """



More information about the Zope-CVS mailing list