[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