[Zodb-checkins] CVS: Zope3/src/zope/testing - functional.py:1.1
Marius Gedminas
mgedmin@codeworks.lt
Mon, 14 Apr 2003 08:19:31 -0400
Update of /cvs-repository/Zope3/src/zope/testing
In directory cvs.zope.org:/tmp/cvs-serv8524/src/zope/testing
Added Files:
functional.py
Log Message:
Functional testing framework for Zope 3:
- based on http://dev.zope.org/Zope3/FunctionalTestingFramework
- doc/FTEST.txt contains a short description of the framework
- test.py -f runs functional tests
- ftesting.zcml is the equivalent of site.zcml for functional tests
(it hardcodes some principals with simple passwords; wouldn't want to do
that in the real site.zcml)
- src/zope/app/content/ftests/test_file.py is an example functional test
=== Added File Zope3/src/zope/testing/functional.py ===
"""Functional testing framework for Zope 3.
There should be a file 'ftesting.zcml' in the current directory.
$Id: functional.py,v 1.1 2003/04/14 12:19:29 mgedmin Exp $
"""
import unittest
import sys
import traceback
from cStringIO import StringIO
from transaction import get_transaction
from zodb.db import DB
from zodb.storage.memory import MemoryFullStorage
from zodb.storage.demo import DemoStorage
from zope.app import Application
from zope.app.publication.zopepublication import ZopePublication
from zope.app.traversing import traverse
from zope.publisher.browser import BrowserRequest
from zope.publisher.publish import publish
import logging
class ResponseWrapper:
"""A wrapper that adds several introspective methods to a response."""
def __init__(self, response, outstream, path):
self._response = response
self._outstream = outstream
self._path = path
def getOutput(self):
"""Returns the full HTTP output (headers + body)"""
return self._outstream.getvalue()
def getBody(self):
"""Returns the response body"""
output = self._outstream.getvalue()
idx = output.find('\n\n')
if idx == -1:
return None
else:
return output[idx+2:]
def getPath(self):
"""Returns the path of the request"""
return self._path
def __getattr__(self, attr):
return getattr(self._response, attr)
class FunctionalTestSetup:
"""Keeps shared state across several functional test cases."""
__shared_state = { '_init': False }
def __init__(self, config_file=None):
"""Initializes Zope 3 framework.
Creates a volatile memory storage. Parses Zope3 configuration files.
"""
self.__dict__ = self.__shared_state
if not self._init:
if not config_file:
config_file = 'ftesting.zcml'
self.log = StringIO()
# Make it silent but keep the log available for debugging
logging.root.addHandler(logging.StreamHandler(self.log))
self.base_storage = MemoryFullStorage("Memory Storage")
self.db = DB(self.base_storage)
self.app = Application(self.db, config_file)
self.connection = None
self._config_file = config_file
self._init = 1
elif config_file and config_file != self._config_file:
# Running different tests with different configurations is not
# supported at the moment
raise NotImplementedError('Already configured'
' with a different config file')
def setUp(self):
"""Prepares for a functional test case."""
# Tear down the old demo storage (if any) and create a fresh one
self.db.close()
storage = DemoStorage("Demo Storage", self.base_storage)
self.db = self.app.db = DB(storage)
# Get hold of the root folder
self.connection = self.db.open()
root = self.connection.root()
self.root_folder = root[ZopePublication.root_name]
def tearDown(self):
"""Cleans up after a functional test case."""
get_transaction().abort()
if self.connection:
self.connection.close()
self.connection = None
def getRootFolder(self):
"""Returns the Zope root folder."""
return self.root_folder
def getApplication(self):
"""Returns the Zope application instance."""
return self.app
class FunctionalTestCase(unittest.TestCase):
"""Functional test case."""
def setUp(self):
"""Prepares for a functional test case."""
unittest.TestCase.setUp(self)
FunctionalTestSetup().setUp()
def tearDown(self):
"""Cleans up after a functional test case."""
FunctionalTestSetup().tearDown()
unittest.TestCase.tearDown(self)
def getRootFolder(self):
"""Returns the Zope root folder."""
return FunctionalTestSetup().getRootFolder()
class BrowserTestCase(unittest.TestCase):
"""Functional test case for Browser requests."""
def makeRequest(self, path='', basic=None, form=None, env={},
outstream=None):
"""Creates a new request object.
Arguments:
path -- the path to be traversed (e.g. "/folder1/index.html")
basic -- basic HTTP authentication credentials ("user:password")
form -- a dictionary emulating a form submission
(Note that field values should be Unicode strings)
env -- a dictionary of additional environment variables
(You can emulate HTTP request header
X-Header: foo
by adding 'HTTP_X_HEADER': 'foo' to env)
outstream -- a stream where the HTTP response will be written
"""
if outstream is None:
outstream = StringIO()
environment = {"HTTP_HOST": 'localhost',
"HTTP_REFERER": 'localhost'}
environment.update(env)
app = FunctionalTestSetup().getApplication()
request = app._request(path, '', outstream,
environment=environment,
basic=basic, form=form,
request=BrowserRequest)
return request
def publish(self, path, basic=None, form=None, env={}, handle_errors=False):
"""Renders an object at a given location.
Arguments are the same as in makeRequest with the following exception:
handle_errors -- if False (default), exceptions will not be caught
if True, exceptions will return a formatted error
page.
Returns the response object enhanced with the following methods:
getOutput() -- returns the full HTTP output as a string
getBody() -- returns the full response body as a string
getPath() -- returns the path used in the request
"""
outstream = StringIO()
request = self.makeRequest(path, basic=basic, form=form, env=env,
outstream=outstream)
response = ResponseWrapper(request.response, outstream, path)
publish(request, handle_errors=handle_errors)
return response
def checkForBrokenLinks(self, body, path):
"""Looks for broken links in a page by trying to traverse relative
URIs.
"""
if not body: return
from htmllib import HTMLParser
from formatter import NullFormatter
parser = HTMLParser(NullFormatter())
parser.feed(body)
parser.close()
root = self.getRootFolder()
base = path
if not base.startswith('/'):
base = '/' + base
while not base.endswith('/'):
base = base[:-1]
errors = []
for a in parser.anchorlist:
if a.startswith('http://localhost/'):
a = a[len('http://localhost/') - 1:]
elif a.find(':') != -1:
# XXX assume it is "proto:someuri"
continue
elif not a.startswith('/'):
a = base + a
if a.find('#') != -1:
a = a[:a.index('#') - 1]
rq = self.makeRequest()
try:
rq.traverse(a)
except (KeyError, NameError, AttributeError):
e = traceback.format_exception_only(*sys.exc_info()[:2])[-1]
errors.append((a, e.strip()))
if errors:
self.fail("%s contains broken links:\n" % path
+ "\n".join([" %s:\t%s" % (a, e) for a, e in errors]))
#
# Sample functional test case
#
class SampleFunctionalTest(BrowserTestCase):
def testRootPage(self):
response = self.publish('/')
self.assertEquals(response.getStatus(), 200)
def testRootPage_preferred_languages(self):
response = self.publish('/', env={'HTTP_ACCEPT_LANGUAGE': 'en'})
self.assertEquals(response.getStatus(), 200)
def testNotExisting(self):
response = self.publish('/nosuchthing', handle_errors=1)
self.assertEquals(response.getStatus(), 404)
def testLinks(self):
response = self.publish('/')
self.assertEquals(response.getStatus(), 200)
self.checkForBrokenLinks(response.getBody(), response.getPath())
def sample_test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(SampleFunctionalTest))
return suite
if __name__ == '__main__':
unittest.main()