Commit 7ea3a528 authored by Jim Fulton's avatar Jim Fulton

include zope.testing

parent b3bf20dd
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Set up testing environment
$Id$
"""
import os
def patchTracebackModule():
"""Use the ExceptionFormatter to show more info in tracebacks.
"""
from zope.exceptions.exceptionformatter import format_exception
import traceback
traceback.format_exception = format_exception
# Don't use the new exception formatter by default, since it
# doesn't show filenames.
if os.environ.get('NEW_ZOPE_EXCEPTION_FORMATTER', 0):
patchTracebackModule()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Provide a standard cleanup registry
Unit tests that change global data should include the CleanUp base
class, which provides simpler setUp and tearDown methods that call
global-data cleanup routines::
class Test(CleanUp, unittest.TestCase):
....
If custom setUp or tearDown are needed, then the base routines should
be called, as in::
def tearDown(self):
super(Test, self).tearDown()
....
Cleanup routines for global data should be registered by passing them to
addCleanup::
addCleanUp(pigRegistry._clear)
$Id$
"""
_cleanups = []
def addCleanUp(func, args=(), kw={}):
"""Register a cleanup routines
Pass a function to be called to cleanup global data.
Optional argument tuple and keyword arguments may be passed.
"""
_cleanups.append((func, args, kw))
class CleanUp(object):
"""Mix-in class providing clean-up setUp and tearDown routines."""
def cleanUp(self):
"""Clean up global data."""
for func, args, kw in _cleanups:
func(*args, **kw)
setUp = tearDown = cleanUp
This source diff could not be displayed because it is too large. You can view the blob instead.
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Extension to use doctest tests as unit tests
This module provides a DocTestSuite contructor for converting doctest
tests to unit tests.
$Id$
"""
from doctest import DocFileSuite, DocTestSuite
from doctest import debug_src, debug
def pprint():
from pprint import PrettyPrinter
def pprint(ob, **opts):
if 'width' not in opts:
opts['width'] = 1
return PrettyPrinter(**opts).pprint(ob)
return pprint
pprint = pprint()
"""HTML parser that extracts form information.
This is intended to support functional tests that need to extract
information from HTML forms returned by the publisher.
See *formparser.txt* for documentation.
"""
__docformat__ = "reStructuredText"
import HTMLParser
import urlparse
def parse(data, base=None):
"""Return a form collection parsed from `data`.
`base` should be the URL from which `data` was retrieved.
"""
parser = FormParser(data, base)
return parser.parse()
class FormParser(object):
def __init__(self, data, base=None):
self.data = data
self.base = base
self._parser = HTMLParser.HTMLParser()
self._parser.handle_data = self._handle_data
self._parser.handle_endtag = self._handle_endtag
self._parser.handle_starttag = self._handle_starttag
self._parser.handle_startendtag = self._handle_starttag
self._buffer = []
self.current = None
self.forms = FormCollection()
def parse(self):
"""Parse the document, returning the collection of forms."""
self._parser.feed(self.data)
self._parser.close()
return self.forms
# HTMLParser handlers
def _handle_data(self, data):
self._buffer.append(data)
def _handle_endtag(self, tag):
if tag == "textarea":
self.textarea.value = "".join(self._buffer)
self.textarea = None
elif tag == "select":
self.select = None
elif tag == "option":
option = self.select.options[-1]
label = "".join(self._buffer)
if not option.label:
option.label = label
if not option.value:
option.value = label
if option.selected:
if self.select.multiple:
self.select.value.append(option.value)
else:
self.select.value = option.value
def _handle_starttag(self, tag, attrs):
del self._buffer[:]
d = {}
for name, value in attrs:
d[name] = value
name = d.get("name")
id = d.get("id") or d.get("xml:id")
if tag == "form":
method = kwattr(d, "method", "get")
action = d.get("action", "").strip() or None
if self.base and action:
action = urlparse.urljoin(self.base, action)
enctype = kwattr(d, "enctype", "application/x-www-form-urlencoded")
self.current = Form(name, id, method, action, enctype)
self.forms.append(self.current)
elif tag == "input":
type = kwattr(d, "type", "text")
checked = "checked" in d
disabled = "disabled" in d
readonly = "readonly" in d
src = d.get("src", "").strip() or None
if self.base and src:
src = urlparse.urljoin(self.base, src)
value = d.get("value")
size = intattr(d, "size")
maxlength = intattr(d, "maxlength")
self.current[name] = Input(name, id, type, value,
checked, disabled, readonly,
src, size, maxlength)
elif tag == "button":
pass
elif tag == "textarea":
disabled = "disabled" in d
readonly = "readonly" in d
self.textarea = Input(name, id, "textarea", None,
None, disabled, readonly,
None, None, None)
self.textarea.rows = intattr(d, "rows")
self.textarea.cols = intattr(d, "cols")
self.current[name] = self.textarea
# The value will be set when the </textarea> is seen.
elif tag == "base":
href = d.get("href", "").strip()
if href and self.base:
href = urlparse.urljoin(self.base, href)
self.base = href
elif tag == "select":
disabled = "disabled" in d
multiple = "multiple" in d
size = intattr(d, "size")
self.select = Select(name, id, disabled, multiple, size)
self.current[name] = self.select
elif tag == "option":
disabled = "disabled" in d
selected = "selected" in d
value = d.get("value")
label = d.get("label")
option = Option(id, value, selected, label, disabled)
self.select.options.append(option)
def kwattr(d, name, default=None):
"""Return attribute, converted to lowercase."""
v = d.get(name, default)
if v != default and v is not None:
v = v.strip().lower()
v = v or default
return v
def intattr(d, name):
"""Return attribute as an integer, or None."""
if name in d:
v = d[name].strip()
return int(v)
else:
return None
class FormCollection(list):
"""Collection of all forms from a page."""
def __getattr__(self, name):
for form in self:
if form.name == name:
return form
raise AttributeError, name
class Form(dict):
"""A specific form within a page."""
def __init__(self, name, id, method, action, enctype):
super(Form, self).__init__()
self.name = name
self.id = id
self.method = method
self.action = action
self.enctype = enctype
class Input(object):
"""Input element."""
rows = None
cols = None
def __init__(self, name, id, type, value, checked, disabled, readonly,
src, size, maxlength):
super(Input, self).__init__()
self.name = name
self.id = id
self.type = type
self.value = value
self.checked = checked
self.disabled = disabled
self.readonly = readonly
self.src = src
self.size = size
self.maxlength = maxlength
class Select(Input):
"""Select element."""
def __init__(self, name, id, disabled, multiple, size):
super(Select, self).__init__(name, id, "select", None, None,
disabled, None, None, size, None)
self.options = []
self.multiple = multiple
if multiple:
self.value = []
class Option(object):
"""Individual value representation for a select element."""
def __init__(self, id, value, selected, label, disabled):
super(Option, self).__init__()
self.id = id
self.value = value
self.selected = selected
self.label = label
self.disabled = disabled
==================
Parsing HTML Forms
==================
Sometimes in functional tests, information from a generated form must
be extracted in order to re-submit it as part of a subsequent request.
The `zope.testing.formparser` module can be used for this purpose.
The scanner is implemented using the `FormParser` class. The
constructor arguments are the page data containing the form and
(optionally) the URL from which the page was retrieved::
>>> import zope.testing.formparser
>>> page_text = '''\
... <html><body>
... <form name="form1" action="/cgi-bin/foobar.py" method="POST">
... <input type="hidden" name="f1" value="today" />
... <input type="submit" name="do-it-now" value="Go for it!" />
... <input type="IMAGE" name="not-really" value="Don't."
... src="dont.png" />
... <select name="pick-two" size="3" multiple>
... <option value="one" selected>First</option>
... <option value="two" label="Second">Another</option>
... <optgroup>
... <option value="three">Third</option>
... <option selected="selected">Fourth</option>
... </optgroup>
... </select>
... </form>
...
... Just for fun, a second form, after specifying a base:
... <base href="http://www.example.com/base/" />
... <form action = 'sproing/sprung.html' enctype="multipart/form">
... <textarea name="sometext" rows="5">Some text.</textarea>
... <input type="Image" name="action" value="Do something."
... src="else.png" />
... </form>
... </body></html>
... '''
>>> parser = zope.testing.formparser.FormParser(page_text)
>>> forms = parser.parse()
>>> len(forms)
2
>>> forms.form1 is forms[0]
True
>>> forms.form1 is forms[1]
False
More often, the `parse()` convenience function is all that's needed::
>>> forms = zope.testing.formparser.parse(
... page_text, "http://cgi.example.com/somewhere/form.html")
>>> len(forms)
2
>>> forms.form1 is forms[0]
True
>>> forms.form1 is forms[1]
False
Once we have the form we're interested in, we can check form
attributes and individual field values::
>>> form = forms.form1
>>> form.enctype
'application/x-www-form-urlencoded'
>>> form.method
'post'
>>> keys = form.keys()
>>> keys.sort()
>>> keys
['do-it-now', 'f1', 'not-really', 'pick-two']
>>> not_really = form["not-really"]
>>> not_really.type
'image'
>>> not_really.value
"Don't."
>>> not_really.readonly
False
>>> not_really.disabled
False
Note that relative URLs are converted to absolute URLs based on the
``<base>`` element (if present) or using the base passed in to the
constructor.
>>> form.action
'http://cgi.example.com/cgi-bin/foobar.py'
>>> not_really.src
'http://cgi.example.com/somewhere/dont.png'
>>> forms[1].action
'http://www.example.com/base/sproing/sprung.html'
>>> forms[1]["action"].src
'http://www.example.com/base/else.png'
The ``<textarea>`` element provides some additional attributes::
>>> ta = forms[1]["sometext"]
>>> print ta.rows
5
>>> print ta.cols
None
>>> ta.value
'Some text.'
The ``<select>`` element provides access to the options as well::
>>> select = form["pick-two"]
>>> select.multiple
True
>>> select.size
3
>>> select.type
'select'
>>> select.value
['one', 'Fourth']
>>> options = select.options
>>> len(options)
4
>>> [opt.label for opt in options]
['First', 'Second', 'Third', 'Fourth']
>>> [opt.value for opt in options]
['one', 'two', 'three', 'Fourth']
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Support for testing logging code
If you want to test that your code generates proper log output, you
can create and install a handler that collects output:
>>> handler = InstalledHandler('foo.bar')
The handler is installed into loggers for all of the names passed. In
addition, the logger level is set to 1, which means, log
everything. If you want to log less than everything, you can provide a
level keyword argument. The level setting effects only the named
loggers.
Then, any log output is collected in the handler:
>>> logging.getLogger('foo.bar').exception('eek')
>>> logging.getLogger('foo.bar').info('blah blah')
>>> for record in handler.records:
... print record.name, record.levelname
... print ' ', record.getMessage()
foo.bar ERROR
eek
foo.bar INFO
blah blah
A similar effect can be gotten by just printing the handler:
>>> print handler
foo.bar ERROR
eek
foo.bar INFO
blah blah
After checking the log output, you need to uninstall the handler:
>>> handler.uninstall()
At which point, the handler won't get any more log output.
Let's clear the handler:
>>> handler.clear()
>>> handler.records
[]
And then log something:
>>> logging.getLogger('foo.bar').info('blah')
and, sure enough, we still have no output:
>>> handler.records
[]
$Id$
"""
import logging
class Handler(logging.Handler):
def __init__(self, *names, **kw):
logging.Handler.__init__(self)
self.names = names
self.records = []
self.setLoggerLevel(**kw)
def setLoggerLevel(self, level=1):
self.level = level
self.oldlevels = {}
def emit(self, record):
self.records.append(record)
def clear(self):
del self.records[:]
def install(self):
for name in self.names:
logger = logging.getLogger(name)
self.oldlevels[name] = logger.level
logger.setLevel(self.level)
logger.addHandler(self)
def uninstall(self):
for name in self.names:
logger = logging.getLogger(name)
logger.setLevel(self.oldlevels[name])
logger.removeHandler(self)
def __str__(self):
return '\n'.join(
[("%s %s\n %s" %
(record.name, record.levelname,
'\n'.join([line
for line in record.getMessage().split('\n')
if line.strip()])
)
)
for record in self.records]
)
class InstalledHandler(Handler):
def __init__(self, *names):
Handler.__init__(self, *names)
self.install()
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""logging handler for tests that check logging output.
$Id$
"""
import logging
class Handler(logging.Handler):
"""Handler for use with unittest.TestCase objects.
The handler takes a TestCase instance as a constructor argument.
It can be registered with one or more loggers and collects log
records they generate.
The assertLogsMessage() and failIfLogsMessage() methods can be
used to check the logger output and causes the test to fail as
appropriate.
"""
def __init__(self, testcase, propagate=False):
logging.Handler.__init__(self)
self.records = []
# loggers stores (logger, propagate) tuples
self.loggers = []
self.closed = False
self.propagate = propagate
self.testcase = testcase
def close(self):
"""Remove handler from any loggers it was added to."""
if self.closed:
return
for logger, propagate in self.loggers:
logger.removeHandler(self)
logger.propagate = propagate
self.closed = True
def add(self, name):
"""Add handler to logger named name."""
logger = logging.getLogger(name)
old_prop = logger.propagate
logger.addHandler(self)
if self.propagate:
logger.propagate = 1
else:
logger.propagate = 0
self.loggers.append((logger, old_prop))
def emit(self, record):
self.records.append(record)
def assertLogsMessage(self, msg, level=None):
for r in self.records:
if r.getMessage() == msg:
if level is not None and r.levelno == level:
return
msg = "No log message contained %r" % msg
if level is not None:
msg += " at level %d" % level
self.testcase.fail(msg)
def failIfLogsMessage(self, msg):
for r in self.records:
if r.getMessage() == msg:
self.testcase.fail("Found log message %r" % msg)
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Fake module support
$Id$
"""
import sys
class FakeModule:
def __init__(self, dict):
self.__dict = dict
def __getattr__(self, name):
try:
return self.__dict[name]
except KeyError:
raise AttributeError, name
def setUp(test, name='README.txt'):
dict = test.globs
dict['__name__'] = name
sys.modules[name] = FakeModule(dict)
def tearDown(test, name='README.txt'):
del sys.modules[name]
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Tests for the testing framework.
$Id$
"""
import unittest
from zope.testing.doctestunit import DocTestSuite, DocFileSuite
def test_suite():
return unittest.TestSuite((
DocFileSuite('formparser.txt'),
DocTestSuite('zope.testing.loggingsupport'),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment