Commit 3ae9251f authored by Arnaud Fontaine's avatar Arnaud Fontaine

PortalTransforms: Prepare migration to Components.

Remove unused source code directories and files:
  + unsafe_transforms/
  + tests/
  + profiles/
  + libtransforms/piltransform.py
  + libtransforms/zope27rest.py
  + setuphandlers.py

TransformEngine.py only contains TransformTool class (and TransformTool.py is
just importing it) so rename that file to TransformTool. Also, cache module
only defines Cache class and this is only used in TransformTool so move it to
TransformTool module.
parent bd2f51c9
...@@ -19,7 +19,6 @@ from Products.PageTemplates.PageTemplateFile import PageTemplateFile ...@@ -19,7 +19,6 @@ from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PortalTransforms.data import datastream from Products.PortalTransforms.data import datastream
from Products.PortalTransforms.chain import TransformsChain from Products.PortalTransforms.chain import TransformsChain
from Products.PortalTransforms.chain import chain from Products.PortalTransforms.chain import chain
from Products.PortalTransforms.cache import Cache
from Products.PortalTransforms.interfaces import IDataStream from Products.PortalTransforms.interfaces import IDataStream
from Products.PortalTransforms.interfaces import ITransform from Products.PortalTransforms.interfaces import ITransform
from Products.PortalTransforms.interfaces import IEngine from Products.PortalTransforms.interfaces import IEngine
...@@ -36,6 +35,76 @@ from Products.PortalTransforms.utils import parseContentType ...@@ -36,6 +35,76 @@ from Products.PortalTransforms.utils import parseContentType
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
from zLOG import WARNING from zLOG import WARNING
from time import time
from Acquisition import aq_base
_marker = object()
class Cache:
def __init__(self, obj, context=None, _id='_v_transform_cache'):
self.obj = obj
if context is None:
self.context = obj
else:
self.context = context
self._id =_id
def _genCacheKey(self, identifier, *args):
key = identifier
for arg in args:
key = '%s_%s' % (key, arg)
key = key.replace('/', '_')
key = key.replace('+', '_')
key = key.replace('-', '_')
key = key.replace(' ', '_')
if hasattr(aq_base(self.context), 'absolute_url'):
return key, self.context.absolute_url()
return key
def setCache(self, key, value):
"""cache a value indexed by key"""
if not value.isCacheable():
return
obj = self.obj
key = self._genCacheKey(key)
entry = getattr(aq_base(obj), self._id, None)
if entry is None:
entry = {}
setattr(obj, self._id, entry)
entry[key] = (time(), value)
return key
def getCache(self, key):
"""try to get a cached value for key
return None if not present
else return a tuple (time spent in cache, value)
"""
obj = self.obj
key = self._genCacheKey(key)
dict = getattr(obj, self._id, None)
if dict is None :
return None
try:
orig_time, value = dict.get(key, None)
return time() - orig_time, value
except TypeError:
return None
def purgeCache(self, key=None):
"""Remove cache
"""
obj = self.obj
id = self._id
if getattr(obj, id, _marker) is _marker:
return
if key is None:
delattr(obj, id)
else:
cache = getattr(obj, id)
key = self._genCacheKey(key)
if cache.has_key(key):
del cache[key]
class TransformTool(UniqueObject, ActionProviderBase, Folder): class TransformTool(UniqueObject, ActionProviderBase, Folder):
id = 'portal_transforms' id = 'portal_transforms'
......
from Products.PortalTransforms.TransformEngine import TransformTool
from Products.PortalTransforms.TransformEngine import TransformTool from Products.PortalTransforms.Tool.TransformTool import TransformTool
PKG_NAME = 'PortalTransforms' PKG_NAME = 'PortalTransforms'
......
"""Cache
"""
from time import time
from Acquisition import aq_base
_marker = object()
class Cache:
def __init__(self, obj, context=None, _id='_v_transform_cache'):
self.obj = obj
if context is None:
self.context = obj
else:
self.context = context
self._id =_id
def _genCacheKey(self, identifier, *args):
key = identifier
for arg in args:
key = '%s_%s' % (key, arg)
key = key.replace('/', '_')
key = key.replace('+', '_')
key = key.replace('-', '_')
key = key.replace(' ', '_')
if hasattr(aq_base(self.context), 'absolute_url'):
return key, self.context.absolute_url()
return key
def setCache(self, key, value):
"""cache a value indexed by key"""
if not value.isCacheable():
return
obj = self.obj
key = self._genCacheKey(key)
entry = getattr(aq_base(obj), self._id, None)
if entry is None:
entry = {}
setattr(obj, self._id, entry)
entry[key] = (time(), value)
return key
def getCache(self, key):
"""try to get a cached value for key
return None if not present
else return a tuple (time spent in cache, value)
"""
obj = self.obj
key = self._genCacheKey(key)
dict = getattr(obj, self._id, None)
if dict is None :
return None
try:
orig_time, value = dict.get(key, None)
return time() - orig_time, value
except TypeError:
return None
def purgeCache(self, key=None):
"""Remove cache
"""
obj = self.obj
id = self._id
if getattr(obj, id, _marker) is _marker:
return
if key is None:
delattr(obj, id)
else:
cache = getattr(obj, id)
key = self._genCacheKey(key)
if cache.has_key(key):
del cache[key]
from Products.PortalTransforms.interfaces import ITransform
from zope.interface import implements
from StringIO import StringIO
import PIL.Image
class PILTransforms:
implements(ITransform)
__name__ = "piltransforms"
def __init__(self, name=None):
if name is not None:
self.__name__ = name
def name(self):
return self.__name__
def convert(self, orig, data, **kwargs):
imgio = StringIO()
orig = StringIO(orig)
newwidth = kwargs.get('width',None)
newheight = kwargs.get('height',None)
pil_img = PIL.Image.open(orig)
if(self.format in ['jpeg','ppm']):
pil_img.draft("RGB", pil_img.size)
pil_img = pil_img.convert("RGB")
if(newwidth or newheight):
pil_img.thumbnail((newwidth,newheight),PIL.Image.ANTIALIAS)
pil_img.save(imgio,self.format)
data.setData(imgio.getvalue())
return data
def register():
return PILTransforms()
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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
#
##############################################################################
"""Wrapper to integrate reStructuredText into Zope
This implementation requires docutils 0.3.4+ from http://docutils.sf.net/
Based on the new implementation of Zope 2.7.1 altered for PortalTransforms
"""
try:
import docutils
except ImportError:
raise ImportError, 'Please install docutils 0.3.3+ from http://docutils.sourceforge.net/#download.'
version = docutils.__version__.split('.')
if version < ['0', '3', '3']:
raise ImportError, """Old version of docutils found:
Got: %(version)s, required: 0.3.3+
Please remove docutils from %(path)s and replace it with a new version. You
can download docutils at http://docutils.sourceforge.net/#download.
""" % {'version' : docutils.__version__, 'path' : docutils.__path__[0] }
import sys, os, locale
##from App.config import getConfiguration
from docutils.core import publish_parts
# get encoding
##default_enc = sys.getdefaultencoding()
##default_output_encoding = getConfiguration().rest_output_encoding or default_enc
##default_input_encoding = getConfiguration().rest_input_encoding or default_enc
default_enc = 'utf-8'
default_output_encoding = default_enc
default_input_encoding = default_enc
# starting level for <H> elements (default behaviour inside Zope is <H3>)
default_level = 3
##initial_header_level = getConfiguration().rest_header_level or default_level
initial_header_level = default_level
# default language
##default_lang = getConfiguration().locale or locale.getdefaultlocale()[0]
default_lang = locale.getdefaultlocale()[0]
if default_lang and '_' in default_lang:
default_lang = default_lang[:default_lang.index('_')]
class Warnings:
def __init__(self):
self.messages = []
def write(self, message):
self.messages.append(message)
def render(src,
writer='html4css1',
report_level=1,
stylesheet='default.css',
input_encoding=default_input_encoding,
output_encoding=default_output_encoding,
language_code=default_lang,
initial_header_level = initial_header_level,
settings = {}):
"""get the rendered parts of the document the and warning object
"""
# Docutils settings:
settings = settings.copy()
settings['input_encoding'] = input_encoding
settings['output_encoding'] = output_encoding
settings['stylesheet'] = stylesheet
settings['language_code'] = language_code
# starting level for <H> elements:
settings['initial_header_level'] = initial_header_level + 1
# set the reporting level to something sane:
settings['report_level'] = report_level
# don't break if we get errors:
settings['halt_level'] = 6
# remember warnings:
settings['warning_stream'] = warning_stream = Warnings()
parts = publish_parts(source=src, writer_name=writer,
settings_overrides=settings,
config_section='zope application')
return parts, warning_stream
def HTML(src,
writer='html4css1',
report_level=1,
stylesheet='default.css',
input_encoding=default_input_encoding,
output_encoding=default_output_encoding,
language_code=default_lang,
initial_header_level = initial_header_level,
warnings = None,
settings = {}):
""" render HTML from a reStructuredText string
- 'src' -- string containing a valid reST document
- 'writer' -- docutils writer
- 'report_level' - verbosity of reST parser
- 'stylesheet' - Stylesheet to be used
- 'input_encoding' - encoding of the reST input string
- 'output_encoding' - encoding of the rendered HTML output
- 'report_level' - verbosity of reST parser
- 'language_code' - docutils language
- 'initial_header_level' - level of the first header tag
- 'warnings' - will be overwritten with a string containing the warnings
- 'settings' - dict of settings to pass in to Docutils, with priority
"""
parts, warning_stream = render(src,
writer = writer,
report_level = report_level,
stylesheet = stylesheet,
input_encoding = input_encoding,
output_encoding = output_encoding,
language_code=language_code,
initial_header_level = initial_header_level,
settings = settings)
header = '<h%(level)s class="title">%(title)s</h%(level)s>\n' % {
'level': initial_header_level,
'title': parts['title'],
}
body = '%(docinfo)s%(body)s' % {
'docinfo': parts['docinfo'],
'body': parts['body'],
}
if parts['title']:
output = header + body
else:
output = body
warnings = ''.join(warning_stream.messages)
return output.encode(output_encoding)
__all__ = ("HTML", 'render')
<?xml version="1.0"?>
<componentregistry>
<adapters/>
<utilities>
<utility
interface="Products.PortalTransforms.interfaces.IPortalTransformsTool"
object="portal_transforms"/>
</utilities>
</componentregistry>
<?xml version="1.0"?>
<import-steps>
<import-step id="portal-transforms-various" version="20070309-01"
handler="Products.PortalTransforms.setuphandlers.setupPortalTransforms"
title="PortalTransforms setup">
<dependency step="componentregistry"/>
PortalTransforms installation step.
</import-step>
</import-steps>
<?xml version="1.0"?>
<metadata>
<version>1.6</version>
</metadata>
<?xml version="1.0"?>
<tool-setup>
<required tool_id="portal_transforms"
class="Products.PortalTransforms.TransformEngine.TransformTool"/>
</tool-setup>
"""
PortalTransforms setup handlers.
"""
from StringIO import StringIO
from Products.CMFCore.utils import getToolByName
def correctMapping(out, portal):
pt = getToolByName(portal, 'portal_transforms')
pt_ids = pt.objectIds()
for m_in, m_out_dict in pt._mtmap.items():
for m_out, transforms in m_out_dict.items():
for transform in transforms:
if transform.id not in pt_ids:
#error, mapped transform is no object in portal_transforms. correct it!
print >>out, "have to unmap transform (%s) cause its not in portal_transforms ..." % transform.id
try:
pt._unmapTransform(transform)
except:
raise
else:
print >>out, "...ok"
def updateSafeHtml(out, portal):
print >>out, 'Update safe_html...'
safe_html_id = 'safe_html'
safe_html_module = "Products.PortalTransforms.transforms.safe_html"
pt = getToolByName(portal, 'portal_transforms')
for id in pt.objectIds():
transform = getattr(pt, id)
if transform.id == safe_html_id and transform.module == safe_html_module:
try:
disable_transform = transform.get_parameter_value('disable_transform')
except KeyError:
print >>out, ' replace safe_html (%s, %s) ...' % (transform.name(), transform.module)
try:
pt.unregisterTransform(id)
pt.manage_addTransform(id, safe_html_module)
except:
raise
else:
print >>out, ' ...done'
print >>out, '...done'
def installPortalTransforms(portal):
out = StringIO()
updateSafeHtml(out, portal)
correctMapping(out, portal)
def setupPortalTransforms(context):
"""
Setup PortalTransforms step.
"""
# Only run step if a flag file is present (e.g. not an extension profile)
if context.readDataFile('portal-transforms-various.txt') is None:
return
out = []
site = context.getSite()
installPortalTransforms(site)
h1. Textile test text
_This_ is quite *boring*, but it needs to be "done":http://plone.org, right?
h2. Cheeses
# Gouda
# Roquefort
# Emmentaler
h2. Episodes
* Bicycle Repairman
* Spanish Inquisition
* Fishslapping Dance
## Testing Markdown
`code` and _italic_ and *bold* and even a [link](http://plone.org).
Fööbär
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE rss PUBLIC "-//Netscape Communications//DTD RSS 0.91//EN" "http://my.netscape.com/publish/formats/rss-0.91.dtd">
<rss version="0.91"><channel><title>Logilab.org news</title><language>en</language><item><title>xmltools 1.3.7</title><descr>bugfix in namespace handling</descr></item><item><title>Python-logic</title><descr>Set up of the Python-Logic special interest group</descr></item><item><title>PyReverse 0.2.3</title><descr>New features and bug fixes</descr></item><item><title>xmltools 1.3.6</title><descr>Uses the new APIs in pyxml-0.7 and 4Suite-0.12.0</descr></item><item><title>hmm-0.2</title><descr>New learning algorithms available</descr></item><item><title>Version 1.2a1 is out</title><descr>Overall refactoring of the engine. Backward incompatible changes
in the syntax of recipes and in modules, in order to ease product development.</descr></item><item><title>XMLdiff v0.5.3 (bug fixes)</title><descr>Version 0.5.3 fixes packaging bugs.</descr></item><item><title>hmm-0.1</title><descr>hmm is a module for Hidden Markov Model manipulation.</descr></item><item><title>PyReverse 0.1 (new product)</title><descr>
Beta release for this set of tools for reverse engineering python code
</descr></item><item><title>PyPaSax 0.3 (bug fixes)</title><descr>A few changes in the DTD, improved PyXML compatibility</descr></item><item><title>XMLdiff v0.5.2 (bug fixes)</title><descr>Version 0.5.2 fixes several bugs.</descr></item><item><title>Version 1.1 is out</title><descr>bugfixes over beta 3.</descr></item><item><title>Version 1.1b3 is out</title><descr>Great speed improvement for Horn. All-in-one windows installer.</descr></item><item><title>xmltools-1.3.5</title><descr>Version 1.3.5 code cleanup.</descr></item><item><title>xmltools-1.3.4</title><descr>Version 1.3.4 fixes a sever encoding bug that could cause crashes on windows machines.</descr></item><item><title>Version 1.1b1 is out</title><descr>Version 1.1b1 drops support for Python 1.5.2 in favor of Python 2.1, and features a new version of Horn, with localization support</descr></item><item><title>XMLdiff v0.5 (algorithm change, bug fixes)</title><descr>Version 0.5. The new algorithm makes it now usable either on big
documents and really faster in any cases. Fixes Unicode problem.</descr></item><item><title>XMLtools v1.3.1 (bugfixes)</title><descr>Version 1.3.1. This release fixes some minor glitches that had slipped in 1.3.</descr></item><item><title>XMLdiff v0.2 (performance improvement)</title><descr>Version 0.2. Huge performance improvement, and output cleanup.</descr></item><item><title>XMLdiff v0.1.1 (beta release)</title><descr>Version 0.1.1. Fully functionnal. Beta release.</descr></item><item><title>XPathVis v1.0beta (beta release)</title><descr>Version 1.0beta. Works nicely.</descr></item><item><title>XMLtools v1.3 (new features)</title><descr>Version 1.3. This release is compatible with Python 2.x and Unicode. It is not guaranteed to work with Python 1.5.2.</descr></item><item><title>Narval on developerWorks</title><descr>An Introduction to Narval was published on developerWorks.</descr><link>http://www-106.ibm.com/developerworks/library/l-ai/</link></item><item><title>Version 1.0.1 is out</title><descr>Version 1.0.1 is a bugfix release.</descr></item><item><title>Narval reviewed on AI.About.com</title><descr>AI.About.com published a review of Narval.</descr><link>http://ai.about.com/compute/ai/library/weekly/aa060801a.htm</link></item><item><title>Narval at BotShow 2001</title><descr>Narval was presented at the first BotShow event. The slides will soon be available online.</descr><link>http://www.ptolemee.com/botshow/text/text_fr/edito/edito_set.html</link></item><item><title>Version 1.0 is out</title><descr>Version 1.0. Celebration time, come on!</descr></item><item><title>Network-boot-HOWTO v0.2.1</title><descr>Version 0.2.1 is out.</descr></item><item><title>GuessLang v0.1.0 (beta release)</title><descr>Version 0.1.0 is out.</descr></item><item><title>Network-boot-HOWTO v0.1.1</title><descr>Version 0.1.1 is out.</descr></item><item><title>PyPaSax v0.1</title><descr>Version 0.1 is out.</descr></item><item><title>RC2 is out</title><descr>Release Candidate 2 is out. French documentation will be updated within a few days. We also released several applications (or maybe extension sets?) that are in alpha/beta stage. Give them a try!</descr></item><item><title>VCalSax v0.1 (beta)</title><descr>Version 0.1 is out. Still beta, but fully functional.</descr></item><item><title>Talk at LinuxExpo in English</title><descr>A translation of the talk we gave at Linux Expo 2001 is available on-line.</descr><link>http://www.logilab.com/press/linux-expo2001/</link></item><item><title>RC1 is out</title><descr>Release Candidate 1 is out. Documentation will be updated within a few days. Please help us test this one so that we can release 1.0 quicker.</descr></item><item><title>XMLtools v1.2 (stable release)</title><descr>Version 1.2 is released. Bugfixes, mainly..</descr></item><item><title>Application section on web site</title><descr>We just added a new applications section on Logilab.org web site.</descr><link>http://www.logilab.org/narval/app.html</link></item><item><title>WMgMon v0.4.0</title><descr>version 0.4.0 is out. Bugfixes and new monitor functions. </descr></item><item><title>XmlTools v1.1</title><descr>version 1.1 is out. New features in XmlTree.</descr></item><item><title>Beta5 is out</title><descr>Beta 5 is out. Lots of bugfixes in Narval and Horn, client server communication between the kernel and the graphical interface using SOAP. Windows specific bugfixes.</descr></item><item><title>XmlTools v1.0</title><descr>Initial release.</descr></item><item><title>PyGantt v0.6.0</title><descr>Version 0.6.0 released. New features added.</descr></item><item><title>Beta4 is out</title><descr>Beta 4 is out. Improved Windows compatibility. New features and bugfixes in both Narval and Horn.</descr></item><item><title>Article on Narval in Linux Gazette</title><descr>We published an article in the #59 issue of the Linux Gazette. It describes Narval and its use to set up Gazo, the assistant-coordinator for the translation of the Linux Gazette.</descr><link>http://www.linuxgazette.com/issue59/chauvat.html</link></item><item><title>Beta3 is out</title><descr>Beta 3 is out. No more memory leaks (almost). Time conditions work correctly. A step can be an XSL transform. Changes in the Narval DTD.</descr></item><item><title>Logilab invited at Linux Expo</title><descr>We got invited to give a talk at Linux Expo in Paris, France. The talk will be geared toward business uses of Narval. The title will be Using XML and Intelligent Personnal Assistants to enhance groupware and workflow enterprise applications. Come and meet with us!</descr><link>http://www.linuxexpoparis.com/EN/conferences</link></item><item><title>Beta2 is out</title><descr>Beta 2 is out. Installation is much easier. Tutorial. GUI improvements. Bugfixes.</descr></item><item><title>Beta1 is out</title><descr>Beta 1 is out. Many bug fixes.</descr></item><item><title>Beta0 is out</title><descr>Beta 0 is out. Logilab.org is one-line.</descr><link>http://www.logilab.org</link></item></channel></rss>
This is a test of the *reST* transform
o one
o two
o three
Heading 1
=========
Some text.
Heading 2
---------
Some text, bla ble bli blo blu. Yes, i know this is Stupid_.
.. _Stupid: http://www.example.com
=====
Title
=====
--------
Subtitle
--------
This is a test document to make sure subtitle gets the right heading.
Now the real heading
====================
The brown fox jumped over the lazy dog.
With a subheading
------------------
Some text, bla ble bli blo blu. Yes, i know this is Stupid_.
.. _Stupid: http://www.example.com
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:transform xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
<xsl:strip-space elements='*'/>
<xsl:output method='xml'/>
<!-- Narval prototype ====================================================== -->
<al:prototype xmlns:al="http://www.logilab.org/namespaces/Narval/1.2">
<al:description lang="fr">Transforme du RSS en du HTML.</al:description>
<al:description lang="en">Turns RSS into HTML.</al:description>
<al:input id="input"><al:match>rss</al:match></al:input>
<al:output id="output" list="yes"><al:match>html-body</al:match></al:output>
</al:prototype>
<!-- root ================================================================== -->
<xsl:template match='rss/rss/channel'>
<html-body>
<h2>
<xsl:value-of select='title'/>
</h2>
<p>
<xsl:element name='a'>
<xsl:attribute name='href'><xsl:value-of select='link'/></xsl:attribute>
<xsl:value-of select='title'/>
</xsl:element>
<em><xsl:value-of select='description'/></em>
</p>
<table>
<xsl:apply-templates select='item'/>
</table>
</html-body>
</xsl:template>
<xsl:template match='item'>
<tr>
<td>
<xsl:element name='a'>
<xsl:attribute name='href'><xsl:value-of select='link'/></xsl:attribute>
<xsl:value-of select='title'/>
</xsl:element>
<xsl:apply-templates mode='multi' select='description'/>
</td>
</tr>
</xsl:template>
</xsl:transform>
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.2.8: http://docutils.sourceforge.net/" />
<title>Copying Docutils</title>
<meta name="author" content="David Goodger" />
<meta name="date" content="2002-10-03" />
<link rel="stylesheet" href="tools/stylesheets/default.css" type="text/css" />
</head>
<body>
<div class="document" id="copying-docutils">
<h1 class="title">Copying Docutils</h1>
<table class="docinfo" frame="void" rules="none">
<col class="docinfo-name" />
<col class="docinfo-content" />
<tbody valign="top">
<tr><th class="docinfo-name">Author:</th>
<td>David Goodger</td></tr>
<tr><th class="docinfo-name">Contact:</th>
<td><a class="first last reference" href="mailto:goodger&#64;users.sourceforge.net">goodger&#64;users.sourceforge.net</a></td></tr>
<tr><th class="docinfo-name">Date:</th>
<td>2002-10-03</td></tr>
<tr class="field"><th class="docinfo-name">Web site:</th><td class="field-body"><a class="reference" href="http://docutils.sourceforge.net/">http://docutils.sourceforge.net/</a></td>
</tr>
</tbody>
</table>
<p>Most of the files included in this project are in the public domain,
and therefore have no license requirement and no restrictions on
copying or usage. The exceptions are:</p>
<ul class="simple">
<li>docutils/optik.py, copyright Gregory P. Ward, released under a
BSD-style license (which can be found in the module's source code).</li>
<li>docutils/roman.py, copyright by Mark Pilgrim, released under the
<a class="reference" href="http://www.python.org/2.1.1/license.html">Python 2.1.1 license</a>.</li>
<li>test/difflib.py, copyright by the Python Software Foundation,
released under the <a class="reference" href="http://www.python.org/2.2/license.html">Python 2.2 license</a>. This file is included for
compatibility with Python versions less than 2.2; if you have Python
2.2 or higher, difflib.py is not needed and may be removed. (It's
only used to report test failures anyhow; it isn't installed
anywhere. The included file is a pre-generator version of the
difflib.py module included in Python 2.2.)</li>
</ul>
<p>(Disclaimer: I am not a lawyer.) Both the BSD license and the Python
license are <a class="reference" href="http://opensource.org/licenses/">OSI-approved</a> and <a class="reference" href="http://www.gnu.org/philosophy/license-list.html">GPL-compatible</a>. Although complicated
by multiple owners and lots of legalese, the Python license basically
lets you copy, use, modify, and redistribute files as long as you keep
the copyright attribution intact, note any changes you make, and don't
use the owner's name in vain. The BSD license is similar.</p>
</div>
<hr class="footer"/>
<div class="footer">
Generated on: 2003-04-19 15:32 UTC.
Generated by <a class="reference" href="http://docutils.sourceforge.net/">Docutils</a> from <a class="reference" href="http://docutils.sourceforge.net/rst.html">reStructuredText</a> source.
</div>
</body>
</html>
""" nice docstring """
class A : pass
# comment
def inc(i):
return i+1
def greater(a, b):
"""foo <html />"""
return a > b
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Test page for save html rendering</title>
<meta name="date" content="2005-07-22" />
</head>
<body>
<h1>Test page</h1>
<table>
<tr>
<th>Test1</th>
<td>test2</td>
</tr>
</table>
<p>This is a text used as a blind text.</p>
<div><![CDATA[
Some CDATA text.
]]>
</div>
<ul>
<li>A sample list item1</li>
<li>A sample list item2</li>
</ul>
<p>This is again a blind text with a<br>line break.</p>
<div>
Can we <q>quote</q> or write something we <del>didn't</del> mean to write? Or how is <ins>this</ins> instead?
</div>
<hr>
<div>
<a href="http://www.plone.org"><img src="http://www.plone.org/logo.jpg"/></a> is just great.
</div>
</body>
</html>
<A name=1></a>Chapter&nbsp;44<br>Writing&nbsp;Basic&nbsp;Unit&nbsp;Tests<br>Difficulty<br>Newcomer<br>Skills<br>&nbsp;All&nbsp;you&nbsp;need&nbsp;to&nbsp;know&nbsp;is&nbsp;some&nbsp;Python.<br>Problem/Task<br>As&nbsp;you&nbsp;know&nbsp;by&nbsp;now,&nbsp;Zope&nbsp;3&nbsp;gains&nbsp;its&nbsp;incredible&nbsp;stability&nbsp;from&nbsp;testing&nbsp;any&nbsp;code&nbsp;in&nbsp;great&nbsp;detail.&nbsp;The<br>currently&nbsp;most&nbsp;common&nbsp;method&nbsp;is&nbsp;to&nbsp;write&nbsp;unit&nbsp;tests.&nbsp;This&nbsp;chapter&nbsp;introduces&nbsp;unit&nbsp;tests&nbsp;&nbsp;which<br>are&nbsp;Zope&nbsp;3&nbsp;independent&nbsp;&nbsp;and&nbsp;introduces&nbsp;some&nbsp;of&nbsp;the&nbsp;subtleties.<br>Solution<br>44.1<br>Implementing&nbsp;the&nbsp;Sample&nbsp;Class<br>Before&nbsp;we&nbsp;can&nbsp;write&nbsp;tests,&nbsp;we&nbsp;have&nbsp;to&nbsp;write&nbsp;some&nbsp;code&nbsp;that&nbsp;we&nbsp;can&nbsp;test.&nbsp;Here,&nbsp;we&nbsp;will&nbsp;implement<br>a&nbsp;simple&nbsp;class&nbsp;called&nbsp;Sample&nbsp;with&nbsp;a&nbsp;public&nbsp;attribute&nbsp;title&nbsp;and&nbsp;description&nbsp;that&nbsp;is&nbsp;accessed<br>via&nbsp;getDescription()&nbsp;and&nbsp;mutated&nbsp;using&nbsp;setDescription().&nbsp;Further,&nbsp;the&nbsp;description&nbsp;must&nbsp;be<br>either&nbsp;a&nbsp;regular&nbsp;or&nbsp;unicode&nbsp;string.<br>Since&nbsp;this&nbsp;code&nbsp;will&nbsp;not&nbsp;depend&nbsp;on&nbsp;Zope,&nbsp;open&nbsp;a&nbsp;file&nbsp;named&nbsp;test&nbsp;sample.py&nbsp;anywhere&nbsp;and&nbsp;add<br>the&nbsp;following&nbsp;class:<br>1&nbsp;Sample(object):<br>2<br>&quot;&quot;&quot;A&nbsp;trivial&nbsp;Sample&nbsp;object.&quot;&quot;&quot;<br>3<br>4<br>title&nbsp;=&nbsp;None<br>5<br>6<br>def&nbsp;__init__(self):<br>7<br>&quot;&quot;&quot;Initialize&nbsp;object.&quot;&quot;&quot;<br>8<br>self._description&nbsp;=&nbsp;’’<br>9<br>1<br><hr><A name=2></a>2<br>CHAPTER&nbsp;44.&nbsp;WRITING&nbsp;BASIC&nbsp;UNIT&nbsp;TESTS<br>10<br>def&nbsp;setDescription(self,&nbsp;value):<br>11<br>&quot;&quot;&quot;Change&nbsp;the&nbsp;value&nbsp;of&nbsp;the&nbsp;description.&quot;&quot;&quot;<br>12<br>assert&nbsp;isinstance(value,&nbsp;(str,&nbsp;unicode))<br>13<br>self._description&nbsp;=&nbsp;value<br>14<br>15<br>def&nbsp;getDescription(self):<br>16<br>&quot;&quot;&quot;Change&nbsp;the&nbsp;value&nbsp;of&nbsp;the&nbsp;description.&quot;&quot;&quot;<br>17<br>return&nbsp;self._description<br>Line&nbsp;4:&nbsp;The&nbsp;title&nbsp;is&nbsp;just&nbsp;publicly&nbsp;declared&nbsp;and&nbsp;a&nbsp;value&nbsp;of&nbsp;None&nbsp;is&nbsp;given.&nbsp;Therefore&nbsp;this&nbsp;is&nbsp;just<br>a&nbsp;regular&nbsp;attribute.<br>Line&nbsp;8:&nbsp;The&nbsp;actual&nbsp;description&nbsp;string&nbsp;will&nbsp;be&nbsp;stored&nbsp;in&nbsp;description.<br>Line&nbsp;12:&nbsp;Make&nbsp;sure&nbsp;that&nbsp;the&nbsp;description&nbsp;is&nbsp;only&nbsp;a&nbsp;regular&nbsp;or&nbsp;unicode&nbsp;string,&nbsp;like&nbsp;it&nbsp;was&nbsp;stated&nbsp;in<br>the&nbsp;requirements.<br>If&nbsp;you&nbsp;wish&nbsp;you&nbsp;can&nbsp;now&nbsp;manually&nbsp;test&nbsp;the&nbsp;class&nbsp;with&nbsp;the&nbsp;interactive&nbsp;Python&nbsp;shell.&nbsp;Just&nbsp;start<br>Python&nbsp;by&nbsp;entering&nbsp;python&nbsp;in&nbsp;your&nbsp;shell&nbsp;prompt.&nbsp;Note&nbsp;that&nbsp;you&nbsp;should&nbsp;be&nbsp;in&nbsp;the&nbsp;directory&nbsp;in<br>which&nbsp;test&nbsp;sample.py&nbsp;is&nbsp;located&nbsp;when&nbsp;starting&nbsp;Python&nbsp;(an&nbsp;alternative&nbsp;is&nbsp;of&nbsp;course&nbsp;to&nbsp;specify&nbsp;the<br>directory&nbsp;in&nbsp;your&nbsp;PYTHONPATH.)<br>1&nbsp;&gt;&gt;&gt;&nbsp;from&nbsp;test_sample&nbsp;import&nbsp;Sample<br>2&nbsp;&gt;&gt;&gt;&nbsp;sample&nbsp;=&nbsp;Sample()<br>3&nbsp;&gt;&gt;&gt;&nbsp;print&nbsp;sample.title<br>4&nbsp;None<br>5&nbsp;&gt;&gt;&gt;&nbsp;sample.title&nbsp;=&nbsp;’Title’<br>6&nbsp;&gt;&gt;&gt;&nbsp;print&nbsp;sample.title<br>7&nbsp;Title<br>8&nbsp;&gt;&gt;&gt;&nbsp;print&nbsp;sample.getDescription()<br>9<br>10&nbsp;&gt;&gt;&gt;&nbsp;sample.setDescription(’Hello&nbsp;World’)<br>11&nbsp;&gt;&gt;&gt;&nbsp;print&nbsp;sample.getDescription()<br>12&nbsp;Hello&nbsp;World<br>13&nbsp;&gt;&gt;&gt;&nbsp;sample.setDescription(None)<br>14&nbsp;Traceback&nbsp;(most&nbsp;recent&nbsp;call&nbsp;last):<br>15<br>File&nbsp;&quot;&lt;stdin&gt;&quot;,&nbsp;line&nbsp;1,&nbsp;in&nbsp;?<br>16<br>File&nbsp;&quot;test_sample.py&quot;,&nbsp;line&nbsp;31,&nbsp;in&nbsp;setDescription<br>17<br>assert&nbsp;isinstance(value,&nbsp;(str,&nbsp;unicode))<br>18&nbsp;AssertionError<br>As&nbsp;you&nbsp;can&nbsp;see&nbsp;in&nbsp;the&nbsp;last&nbsp;test,&nbsp;non-string&nbsp;object&nbsp;types&nbsp;are&nbsp;not&nbsp;allowed&nbsp;as&nbsp;descriptions&nbsp;and&nbsp;an<br>AssertionError&nbsp;is&nbsp;raised.<br>44.2<br>Writing&nbsp;the&nbsp;Unit&nbsp;Tests<br>The&nbsp;goal&nbsp;of&nbsp;writing&nbsp;the&nbsp;unit&nbsp;tests&nbsp;is&nbsp;to&nbsp;convert&nbsp;this&nbsp;informal,&nbsp;manual,&nbsp;and&nbsp;interactive&nbsp;testing&nbsp;session<br>into&nbsp;a&nbsp;formal&nbsp;test&nbsp;class.&nbsp;Python&nbsp;provides&nbsp;already&nbsp;a&nbsp;module&nbsp;called&nbsp;unittest&nbsp;for&nbsp;this&nbsp;purpose,&nbsp;which<br>is&nbsp;a&nbsp;port&nbsp;of&nbsp;the&nbsp;Java-based&nbsp;unit&nbsp;testing&nbsp;product,&nbsp;JUnit,&nbsp;by&nbsp;Kent&nbsp;Beck&nbsp;and&nbsp;Erich&nbsp;Gamma.&nbsp;There&nbsp;are<br>three&nbsp;levels&nbsp;to&nbsp;the&nbsp;testing&nbsp;framework&nbsp;(this&nbsp;list&nbsp;deviates&nbsp;a&nbsp;bit&nbsp;from&nbsp;the&nbsp;original&nbsp;definitions&nbsp;as&nbsp;found<br>in&nbsp;the&nbsp;Python&nbsp;library&nbsp;documentation.&nbsp;1).<br>1&nbsp;http://www.python.org/doc/current/lib/module-unittest.html<br><hr><A name=3></a>44.2.&nbsp;WRITING&nbsp;THE&nbsp;UNIT&nbsp;TESTS<br>3<br>The&nbsp;smallest&nbsp;unit&nbsp;is&nbsp;obviously&nbsp;the&nbsp;“test”,&nbsp;which&nbsp;is&nbsp;a&nbsp;single&nbsp;method&nbsp;in&nbsp;a&nbsp;TestCase&nbsp;class&nbsp;that<br>tests&nbsp;the&nbsp;behavior&nbsp;of&nbsp;a&nbsp;small&nbsp;piece&nbsp;of&nbsp;code&nbsp;or&nbsp;a&nbsp;particular&nbsp;aspect&nbsp;of&nbsp;an&nbsp;implementation.&nbsp;The&nbsp;“test<br>case”&nbsp;is&nbsp;then&nbsp;a&nbsp;collection&nbsp;tests&nbsp;that&nbsp;share&nbsp;the&nbsp;same&nbsp;setup/inputs.&nbsp;On&nbsp;top&nbsp;of&nbsp;all&nbsp;of&nbsp;this&nbsp;sits&nbsp;the&nbsp;“test<br>suite”&nbsp;which&nbsp;is&nbsp;a&nbsp;collection&nbsp;of&nbsp;test&nbsp;cases&nbsp;and/or&nbsp;other&nbsp;test&nbsp;suites.&nbsp;Test&nbsp;suites&nbsp;combine&nbsp;tests&nbsp;that<br>should&nbsp;be&nbsp;executed&nbsp;together.&nbsp;With&nbsp;the&nbsp;correct&nbsp;setup&nbsp;(as&nbsp;shown&nbsp;in&nbsp;the&nbsp;example&nbsp;below),&nbsp;you&nbsp;can<br>then&nbsp;execute&nbsp;test&nbsp;suites.&nbsp;For&nbsp;large&nbsp;projects&nbsp;like&nbsp;Zope&nbsp;3,&nbsp;it&nbsp;is&nbsp;useful&nbsp;to&nbsp;know&nbsp;that&nbsp;there&nbsp;is&nbsp;also&nbsp;the<br>concept&nbsp;of&nbsp;a&nbsp;test&nbsp;runner,&nbsp;which&nbsp;manages&nbsp;the&nbsp;test&nbsp;run&nbsp;of&nbsp;all&nbsp;or&nbsp;a&nbsp;set&nbsp;of&nbsp;tests.&nbsp;The&nbsp;runner&nbsp;provides<br>useful&nbsp;feedback&nbsp;to&nbsp;the&nbsp;application,&nbsp;so&nbsp;that&nbsp;various&nbsp;user&nbsp;interaces&nbsp;can&nbsp;be&nbsp;developed&nbsp;on&nbsp;top&nbsp;of&nbsp;it.<br>But&nbsp;enough&nbsp;about&nbsp;the&nbsp;theory.&nbsp;In&nbsp;the&nbsp;following&nbsp;example,&nbsp;which&nbsp;you&nbsp;can&nbsp;simply&nbsp;put&nbsp;into&nbsp;the&nbsp;same<br>file&nbsp;as&nbsp;your&nbsp;code&nbsp;above,&nbsp;you&nbsp;will&nbsp;see&nbsp;a&nbsp;test&nbsp;in&nbsp;common&nbsp;Zope&nbsp;3&nbsp;style.<br>1&nbsp;import&nbsp;unittest<br>2<br>3&nbsp;class&nbsp;SampleTest(unittest.TestCase):<br>4<br>&quot;&quot;&quot;Test&nbsp;the&nbsp;Sample&nbsp;class&quot;&quot;&quot;<br>5<br>6<br>def&nbsp;test_title(self):<br>7<br>sample&nbsp;=&nbsp;Sample()<br>8<br>self.assertEqual(sample.title,&nbsp;None)<br>9<br>sample.title&nbsp;=&nbsp;’Sample&nbsp;Title’<br>10<br>self.assertEqual(sample.title,&nbsp;’Sample&nbsp;Title’)<br>11<br>12<br>def&nbsp;test_getDescription(self):<br>13<br>sample&nbsp;=&nbsp;Sample()<br>14<br>self.assertEqual(sample.getDescription(),&nbsp;’’)<br>15<br>sample._description&nbsp;=&nbsp;&quot;Description&quot;<br>16<br>self.assertEqual(sample.getDescription(),&nbsp;’Description’)<br>17<br>18<br>def&nbsp;test_setDescription(self):<br>19<br>sample&nbsp;=&nbsp;Sample()<br>20<br>self.assertEqual(sample._description,&nbsp;’’)<br>21<br>sample.setDescription(’Description’)<br>22<br>self.assertEqual(sample._description,&nbsp;’Description’)<br>23<br>sample.setDescription(u’Description2’)<br>24<br>self.assertEqual(sample._description,&nbsp;u’Description2’)<br>25<br>self.assertRaises(AssertionError,&nbsp;sample.setDescription,&nbsp;None)<br>26<br>27<br>28&nbsp;def&nbsp;test_suite():<br>29<br>return&nbsp;unittest.TestSuite((<br>30<br>unittest.makeSuite(SampleTest),<br>31<br>))<br>32<br>33&nbsp;if&nbsp;__name__&nbsp;==&nbsp;’__main__’:<br>34<br>unittest.main(defaultTest=’test_suite’)<br>Line&nbsp;3–4:&nbsp;We&nbsp;usually&nbsp;develop&nbsp;test&nbsp;classes&nbsp;which&nbsp;must&nbsp;inherit&nbsp;from&nbsp;TestCase.&nbsp;While&nbsp;often&nbsp;not<br>done,&nbsp;it&nbsp;is&nbsp;a&nbsp;good&nbsp;idea&nbsp;to&nbsp;give&nbsp;the&nbsp;class&nbsp;a&nbsp;meaningful&nbsp;docstring&nbsp;that&nbsp;describes&nbsp;the&nbsp;purpose&nbsp;of&nbsp;the<br>tests&nbsp;it&nbsp;includes.<br>Line&nbsp;6,&nbsp;12&nbsp;&amp;&nbsp;18:&nbsp;When&nbsp;a&nbsp;test&nbsp;case&nbsp;is&nbsp;run,&nbsp;a&nbsp;method&nbsp;called&nbsp;runTests()&nbsp;is&nbsp;executed.&nbsp;While&nbsp;it<br>is&nbsp;possible&nbsp;to&nbsp;overrride&nbsp;this&nbsp;method&nbsp;to&nbsp;run&nbsp;tests&nbsp;differently,&nbsp;the&nbsp;default&nbsp;option&nbsp;will&nbsp;look&nbsp;for&nbsp;any<br>method&nbsp;whose&nbsp;name&nbsp;starts&nbsp;with&nbsp;test&nbsp;and&nbsp;execute&nbsp;it&nbsp;as&nbsp;a&nbsp;single&nbsp;test.&nbsp;This&nbsp;way&nbsp;we&nbsp;can&nbsp;create<br>a&nbsp;“test&nbsp;method”&nbsp;for&nbsp;each&nbsp;aspect,&nbsp;method,&nbsp;function&nbsp;or&nbsp;property&nbsp;of&nbsp;the&nbsp;code&nbsp;to&nbsp;be&nbsp;tested.&nbsp;This<br>default&nbsp;is&nbsp;very&nbsp;sensible&nbsp;and&nbsp;is&nbsp;used&nbsp;everywhere&nbsp;in&nbsp;Zope&nbsp;3.<br><hr><A name=4></a>4<br>CHAPTER&nbsp;44.&nbsp;WRITING&nbsp;BASIC&nbsp;UNIT&nbsp;TESTS<br>Note&nbsp;that&nbsp;there&nbsp;is&nbsp;no&nbsp;docstring&nbsp;for&nbsp;test&nbsp;methods.&nbsp;This&nbsp;is&nbsp;intentional.&nbsp;If&nbsp;a&nbsp;docstring&nbsp;is&nbsp;specified,<br>it&nbsp;is&nbsp;used&nbsp;instead&nbsp;of&nbsp;the&nbsp;method&nbsp;name&nbsp;to&nbsp;identify&nbsp;the&nbsp;test.&nbsp;When&nbsp;specifying&nbsp;a&nbsp;docstring,&nbsp;we&nbsp;have<br>noticed&nbsp;that&nbsp;it&nbsp;is&nbsp;very&nbsp;difficult&nbsp;to&nbsp;identify&nbsp;the&nbsp;test&nbsp;later;&nbsp;therefore&nbsp;the&nbsp;method&nbsp;name&nbsp;is&nbsp;a&nbsp;much<br>better&nbsp;choice.<br>Line&nbsp;8,&nbsp;10,&nbsp;14,&nbsp;.&nbsp;.&nbsp;.&nbsp;:&nbsp;The&nbsp;TestCase&nbsp;class&nbsp;implements&nbsp;a&nbsp;handful&nbsp;of&nbsp;methods&nbsp;that&nbsp;aid&nbsp;you&nbsp;with&nbsp;the<br>testing.&nbsp;Here&nbsp;are&nbsp;some&nbsp;of&nbsp;the&nbsp;most&nbsp;frequently&nbsp;used&nbsp;ones.&nbsp;For&nbsp;a&nbsp;complete&nbsp;list&nbsp;see&nbsp;the&nbsp;standard<br>Python&nbsp;documentation&nbsp;referenced&nbsp;above.<br>&nbsp;assertEqual(first,second[,msg])<br>Checks&nbsp;whether&nbsp;the&nbsp;first&nbsp;and&nbsp;second&nbsp;value&nbsp;are&nbsp;equal.&nbsp;If&nbsp;the&nbsp;test&nbsp;fails,&nbsp;the&nbsp;msg&nbsp;or&nbsp;None<br>is&nbsp;returned.<br>&nbsp;assertNotEqual(first,second[,msg])<br>This&nbsp;is&nbsp;simply&nbsp;the&nbsp;opposite&nbsp;to&nbsp;assertEqual()&nbsp;by&nbsp;checking&nbsp;for&nbsp;non-equality.<br>&nbsp;assertRaises(exception,callable,...)<br>You&nbsp;expect&nbsp;the&nbsp;callable&nbsp;to&nbsp;raise&nbsp;exception&nbsp;when&nbsp;executed.&nbsp;After&nbsp;the&nbsp;callable&nbsp;you&nbsp;can<br>specify&nbsp;any&nbsp;amount&nbsp;of&nbsp;positional&nbsp;and&nbsp;keyword&nbsp;arguments&nbsp;for&nbsp;the&nbsp;callable.&nbsp;If&nbsp;you&nbsp;expect<br>a&nbsp;group&nbsp;of&nbsp;exceptions&nbsp;from&nbsp;the&nbsp;execution,&nbsp;you&nbsp;can&nbsp;make&nbsp;exception&nbsp;a&nbsp;tuple&nbsp;of&nbsp;possible<br>exceptions.<br>&nbsp;assert&nbsp;(expr[,msg])<br>Assert&nbsp;checks&nbsp;whether&nbsp;the&nbsp;specified&nbsp;expression&nbsp;executes&nbsp;correctly.&nbsp;If&nbsp;not,&nbsp;the&nbsp;test&nbsp;fails&nbsp;and<br>msg&nbsp;or&nbsp;None&nbsp;is&nbsp;returned.<br>&nbsp;failUnlessEqual()<br>This&nbsp;testing&nbsp;method&nbsp;is&nbsp;equivalent&nbsp;to&nbsp;assertEqual().<br>&nbsp;failUnless(expr[,msg])<br>This&nbsp;method&nbsp;is&nbsp;equivalent&nbsp;to&nbsp;assert&nbsp;(expr[,msg]).<br>&nbsp;failif()<br>This&nbsp;is&nbsp;the&nbsp;opposite&nbsp;to&nbsp;failUnless().<br>&nbsp;fail([msg])<br>Fails&nbsp;the&nbsp;running&nbsp;test&nbsp;without&nbsp;any&nbsp;evaluation.&nbsp;This&nbsp;is&nbsp;commonly&nbsp;used&nbsp;when&nbsp;testing&nbsp;various<br>possible&nbsp;execution&nbsp;paths&nbsp;at&nbsp;once&nbsp;and&nbsp;you&nbsp;would&nbsp;like&nbsp;to&nbsp;signify&nbsp;a&nbsp;failure&nbsp;if&nbsp;an&nbsp;improper&nbsp;path<br>was&nbsp;taken.<br>Line&nbsp;6–10:&nbsp;This&nbsp;method&nbsp;tests&nbsp;the&nbsp;title&nbsp;attribute&nbsp;of&nbsp;the&nbsp;Sample&nbsp;class.&nbsp;The&nbsp;first&nbsp;test&nbsp;should<br>be&nbsp;of&nbsp;course&nbsp;that&nbsp;the&nbsp;attribute&nbsp;exists&nbsp;and&nbsp;has&nbsp;the&nbsp;expected&nbsp;initial&nbsp;value&nbsp;(line&nbsp;8).&nbsp;Then&nbsp;the&nbsp;title<br>attribute&nbsp;is&nbsp;changed&nbsp;and&nbsp;we&nbsp;check&nbsp;whether&nbsp;the&nbsp;value&nbsp;was&nbsp;really&nbsp;stored.&nbsp;This&nbsp;might&nbsp;seem&nbsp;like<br>overkill,&nbsp;but&nbsp;later&nbsp;you&nbsp;might&nbsp;change&nbsp;the&nbsp;title&nbsp;in&nbsp;a&nbsp;way&nbsp;that&nbsp;it&nbsp;uses&nbsp;properties&nbsp;instead.&nbsp;Then&nbsp;it<br>becomes&nbsp;very&nbsp;important&nbsp;to&nbsp;check&nbsp;whether&nbsp;this&nbsp;test&nbsp;still&nbsp;passes.<br>Line&nbsp;12–16:&nbsp;First&nbsp;we&nbsp;simply&nbsp;check&nbsp;that&nbsp;getDescription()&nbsp;returns&nbsp;the&nbsp;correct&nbsp;default&nbsp;value.<br>Since&nbsp;we&nbsp;do&nbsp;not&nbsp;want&nbsp;to&nbsp;use&nbsp;other&nbsp;API&nbsp;calls&nbsp;like&nbsp;setDescription()&nbsp;we&nbsp;set&nbsp;a&nbsp;new&nbsp;value&nbsp;of&nbsp;the<br>description&nbsp;via&nbsp;the&nbsp;implementation-internal&nbsp;description&nbsp;attribute&nbsp;(line&nbsp;15).&nbsp;This&nbsp;is&nbsp;okay!&nbsp;Unit<br>tests&nbsp;can&nbsp;make&nbsp;use&nbsp;of&nbsp;implementation-specific&nbsp;attributes&nbsp;and&nbsp;methods.&nbsp;Finally&nbsp;we&nbsp;just&nbsp;check&nbsp;that<br>the&nbsp;correct&nbsp;value&nbsp;is&nbsp;returned.<br><hr><A name=5></a>44.3.&nbsp;RUNNING&nbsp;THE&nbsp;TESTS<br>5<br>Line&nbsp;18–25:&nbsp;On&nbsp;line&nbsp;21–24&nbsp;it&nbsp;is&nbsp;checked&nbsp;that&nbsp;both&nbsp;regular&nbsp;and&nbsp;unicode&nbsp;strings&nbsp;are&nbsp;set&nbsp;correctly.<br>In&nbsp;the&nbsp;last&nbsp;line&nbsp;of&nbsp;the&nbsp;test&nbsp;we&nbsp;make&nbsp;sure&nbsp;that&nbsp;no&nbsp;other&nbsp;type&nbsp;of&nbsp;objects&nbsp;can&nbsp;be&nbsp;set&nbsp;as&nbsp;a&nbsp;description<br>and&nbsp;that&nbsp;an&nbsp;error&nbsp;is&nbsp;raised.<br>28–31:&nbsp;This&nbsp;method&nbsp;returns&nbsp;a&nbsp;test&nbsp;suite&nbsp;that&nbsp;includes&nbsp;all&nbsp;test&nbsp;cases&nbsp;created&nbsp;in&nbsp;this&nbsp;module.&nbsp;It&nbsp;is<br>used&nbsp;by&nbsp;the&nbsp;Zope&nbsp;3&nbsp;test&nbsp;runner&nbsp;when&nbsp;it&nbsp;picks&nbsp;up&nbsp;all&nbsp;available&nbsp;tests.&nbsp;You&nbsp;would&nbsp;basically&nbsp;add&nbsp;the<br>line&nbsp;unittest.makeSuite(TestCaseClass)&nbsp;for&nbsp;each&nbsp;additional&nbsp;test&nbsp;case.<br>33–34:&nbsp;In&nbsp;order&nbsp;to&nbsp;make&nbsp;the&nbsp;test&nbsp;module&nbsp;runnable&nbsp;by&nbsp;itself,&nbsp;you&nbsp;can&nbsp;execute&nbsp;unittest.main()<br>when&nbsp;the&nbsp;module&nbsp;is&nbsp;run.<br>44.3<br>Running&nbsp;the&nbsp;Tests<br>You&nbsp;can&nbsp;run&nbsp;the&nbsp;test&nbsp;by&nbsp;simply&nbsp;calling&nbsp;pythontest&nbsp;sample.py&nbsp;from&nbsp;the&nbsp;directory&nbsp;you&nbsp;saved&nbsp;the<br>file&nbsp;in.&nbsp;Here&nbsp;is&nbsp;the&nbsp;result&nbsp;you&nbsp;should&nbsp;see:<br>.<br>--------------------------------------------------------------------<br>n&nbsp;3&nbsp;tests&nbsp;in&nbsp;0.001s<br>The&nbsp;three&nbsp;dots&nbsp;represent&nbsp;the&nbsp;three&nbsp;tests&nbsp;that&nbsp;were&nbsp;run.&nbsp;If&nbsp;a&nbsp;test&nbsp;had&nbsp;failed,&nbsp;it&nbsp;would&nbsp;have&nbsp;been<br>reported&nbsp;pointing&nbsp;out&nbsp;the&nbsp;failing&nbsp;test&nbsp;and&nbsp;providing&nbsp;a&nbsp;small&nbsp;traceback.<br>When&nbsp;using&nbsp;the&nbsp;default&nbsp;Zope&nbsp;3&nbsp;test&nbsp;runner,&nbsp;tests&nbsp;will&nbsp;be&nbsp;picked&nbsp;up&nbsp;as&nbsp;long&nbsp;as&nbsp;they&nbsp;follow&nbsp;some<br>conventions.<br>&nbsp;The&nbsp;tests&nbsp;must&nbsp;either&nbsp;be&nbsp;in&nbsp;a&nbsp;package&nbsp;or&nbsp;be&nbsp;a&nbsp;module&nbsp;called&nbsp;tests.<br>&nbsp;If&nbsp;tests&nbsp;is&nbsp;a&nbsp;package,&nbsp;then&nbsp;all&nbsp;test&nbsp;modules&nbsp;inside&nbsp;must&nbsp;also&nbsp;have&nbsp;a&nbsp;name&nbsp;starting&nbsp;with&nbsp;test,<br>as&nbsp;it&nbsp;is&nbsp;the&nbsp;case&nbsp;with&nbsp;our&nbsp;name&nbsp;test&nbsp;sample.py.<br>&nbsp;The&nbsp;test&nbsp;module&nbsp;must&nbsp;be&nbsp;somewhere&nbsp;in&nbsp;the&nbsp;Zope&nbsp;3&nbsp;source&nbsp;tree,&nbsp;since&nbsp;the&nbsp;test&nbsp;runner&nbsp;looks<br>only&nbsp;for&nbsp;files&nbsp;there.<br>In&nbsp;our&nbsp;case,&nbsp;you&nbsp;could&nbsp;simply&nbsp;create&nbsp;a&nbsp;tests&nbsp;package&nbsp;in&nbsp;ZOPE3/src&nbsp;(do&nbsp;not&nbsp;forget&nbsp;the<br>init&nbsp;.<br>py&nbsp;file).&nbsp;Then&nbsp;place&nbsp;the&nbsp;test&nbsp;sample.py&nbsp;file&nbsp;into&nbsp;this&nbsp;directory.<br>You&nbsp;you&nbsp;can&nbsp;use&nbsp;the&nbsp;test&nbsp;runner&nbsp;to&nbsp;run&nbsp;only&nbsp;the&nbsp;sample&nbsp;tests&nbsp;as&nbsp;follows&nbsp;from&nbsp;the&nbsp;Zope&nbsp;3&nbsp;root<br>directory:<br>python&nbsp;test.py&nbsp;-vp&nbsp;tests.test_sample<br>The&nbsp;-v&nbsp;option&nbsp;stands&nbsp;for&nbsp;verbose&nbsp;mode,&nbsp;so&nbsp;that&nbsp;detailed&nbsp;information&nbsp;about&nbsp;a&nbsp;test&nbsp;failure&nbsp;is<br>provided.&nbsp;The&nbsp;-p&nbsp;option&nbsp;enables&nbsp;a&nbsp;progress&nbsp;bar&nbsp;that&nbsp;tells&nbsp;you&nbsp;how&nbsp;many&nbsp;tests&nbsp;out&nbsp;of&nbsp;all&nbsp;have&nbsp;been<br>completed.&nbsp;There&nbsp;are&nbsp;many&nbsp;more&nbsp;options&nbsp;that&nbsp;can&nbsp;be&nbsp;specified.&nbsp;You&nbsp;can&nbsp;get&nbsp;a&nbsp;full&nbsp;list&nbsp;of&nbsp;them&nbsp;with<br>the&nbsp;option&nbsp;-h:&nbsp;pythontest.py-h.<br>The&nbsp;output&nbsp;of&nbsp;the&nbsp;call&nbsp;above&nbsp;is&nbsp;as&nbsp;follows:<br>nfiguration&nbsp;file&nbsp;found.<br>nning&nbsp;UNIT&nbsp;tests&nbsp;at&nbsp;level&nbsp;1<br>nning&nbsp;UNIT&nbsp;tests&nbsp;from&nbsp;/opt/zope/Zope3<br>3/3&nbsp;(100.0%):&nbsp;test_title&nbsp;(tests.test_sample.SampleTest)<br>--------------------------------------------------------------------<br>n&nbsp;3&nbsp;tests&nbsp;in&nbsp;0.002s<br><hr><A name=6></a>6<br>CHAPTER&nbsp;44.&nbsp;WRITING&nbsp;BASIC&nbsp;UNIT&nbsp;TESTS<br>nning&nbsp;FUNCTIONAL&nbsp;tests&nbsp;at&nbsp;level&nbsp;1<br>nning&nbsp;FUNCTIONAL&nbsp;tests&nbsp;from&nbsp;/opt/zope/Zope3<br>--------------------------------------------------------------------<br>n&nbsp;0&nbsp;tests&nbsp;in&nbsp;0.000s<br>Line&nbsp;1:&nbsp;The&nbsp;test&nbsp;runner&nbsp;uses&nbsp;a&nbsp;configuration&nbsp;file&nbsp;for&nbsp;some&nbsp;setup.&nbsp;This&nbsp;allows&nbsp;developers&nbsp;to&nbsp;use<br>the&nbsp;test&nbsp;runner&nbsp;for&nbsp;other&nbsp;projects&nbsp;as&nbsp;well.&nbsp;This&nbsp;message&nbsp;simply&nbsp;tells&nbsp;us&nbsp;that&nbsp;the&nbsp;configuration&nbsp;file<br>was&nbsp;found.<br>Line&nbsp;2–8:&nbsp;The&nbsp;unit&nbsp;tests&nbsp;are&nbsp;run.&nbsp;On&nbsp;line&nbsp;4&nbsp;you&nbsp;can&nbsp;see&nbsp;the&nbsp;progress&nbsp;bar.<br>Line&nbsp;9–15:&nbsp;The&nbsp;functional&nbsp;tests&nbsp;are&nbsp;run,&nbsp;since&nbsp;the&nbsp;default&nbsp;test&nbsp;runner&nbsp;runs&nbsp;both&nbsp;types&nbsp;of&nbsp;tests.<br>Since&nbsp;we&nbsp;do&nbsp;not&nbsp;have&nbsp;any&nbsp;functional&nbsp;tests&nbsp;in&nbsp;the&nbsp;specified&nbsp;module,&nbsp;there&nbsp;are&nbsp;no&nbsp;tests&nbsp;to&nbsp;run.&nbsp;To<br>just&nbsp;run&nbsp;the&nbsp;unit&nbsp;tests,&nbsp;use&nbsp;option&nbsp;-u&nbsp;and&nbsp;-f&nbsp;for&nbsp;just&nbsp;running&nbsp;the&nbsp;functional&nbsp;tests.&nbsp;See&nbsp;“Writing<br>Functional&nbsp;Tests”&nbsp;for&nbsp;more&nbsp;detials&nbsp;on&nbsp;functional&nbsp;tests.<br><hr><A name=7></a>44.3.&nbsp;RUNNING&nbsp;THE&nbsp;TESTS<br>7<br>Exercises<br>1.&nbsp;It&nbsp;is&nbsp;not&nbsp;very&nbsp;common&nbsp;to&nbsp;do&nbsp;the&nbsp;setup&nbsp;&nbsp;in&nbsp;our&nbsp;case&nbsp;sample=Sample()&nbsp;&nbsp;in&nbsp;every&nbsp;test<br>method.&nbsp;Instead&nbsp;there&nbsp;exists&nbsp;a&nbsp;method&nbsp;called&nbsp;setUp()&nbsp;and&nbsp;its&nbsp;counterpart&nbsp;tearDown&nbsp;that<br>are&nbsp;run&nbsp;before&nbsp;and&nbsp;after&nbsp;each&nbsp;test,&nbsp;respectively.&nbsp;Change&nbsp;the&nbsp;test&nbsp;code&nbsp;above,&nbsp;so&nbsp;that&nbsp;it&nbsp;uses<br>the&nbsp;setUp()&nbsp;method.&nbsp;In&nbsp;later&nbsp;chapters&nbsp;and&nbsp;the&nbsp;rest&nbsp;of&nbsp;the&nbsp;book&nbsp;we&nbsp;will&nbsp;frequently&nbsp;use&nbsp;this<br>method&nbsp;of&nbsp;setting&nbsp;up&nbsp;tests.<br>2.&nbsp;Currently&nbsp;the&nbsp;test&nbsp;setDescription()&nbsp;test&nbsp;only&nbsp;verifies&nbsp;that&nbsp;None&nbsp;is&nbsp;not&nbsp;allowed&nbsp;as&nbsp;input<br>value.<br>(a)&nbsp;Improve&nbsp;the&nbsp;test,&nbsp;so&nbsp;that&nbsp;all&nbsp;other&nbsp;builtin&nbsp;types&nbsp;are&nbsp;tested&nbsp;as&nbsp;well.<br>(b)&nbsp;Also,&nbsp;make&nbsp;sure&nbsp;that&nbsp;any&nbsp;objects&nbsp;inheriting&nbsp;from&nbsp;str&nbsp;or&nbsp;unicode&nbsp;pass&nbsp;as&nbsp;valid&nbsp;values.<br><hr>
\ No newline at end of file
<A name=1></a>Chapter&nbsp;44<br>Writing&nbsp;Basic&nbsp;Unit&nbsp;Tests<br>Difficulty<br>Newcomer<br>Skills<br>•&nbsp;All&nbsp;you&nbsp;need&nbsp;to&nbsp;know&nbsp;is&nbsp;some&nbsp;Python.<br>Problem/Task<br>As&nbsp;you&nbsp;know&nbsp;by&nbsp;now,&nbsp;Zope&nbsp;3&nbsp;gains&nbsp;its&nbsp;incredible&nbsp;stability&nbsp;from&nbsp;testing&nbsp;any&nbsp;code&nbsp;in&nbsp;great&nbsp;detail.&nbsp;The<br>currently&nbsp;most&nbsp;common&nbsp;method&nbsp;is&nbsp;to&nbsp;write&nbsp;unit&nbsp;tests.&nbsp;This&nbsp;chapter&nbsp;introduces&nbsp;unit&nbsp;tests&nbsp;–&nbsp;which<br>are&nbsp;Zope&nbsp;3&nbsp;independent&nbsp;–&nbsp;and&nbsp;introduces&nbsp;some&nbsp;of&nbsp;the&nbsp;subtleties.<br>Solution<br>44.1<br>Implementing&nbsp;the&nbsp;Sample&nbsp;Class<br>Before&nbsp;we&nbsp;can&nbsp;write&nbsp;tests,&nbsp;we&nbsp;have&nbsp;to&nbsp;write&nbsp;some&nbsp;code&nbsp;that&nbsp;we&nbsp;can&nbsp;test.&nbsp;Here,&nbsp;we&nbsp;will&nbsp;implement<br>a&nbsp;simple&nbsp;class&nbsp;called&nbsp;Sample&nbsp;with&nbsp;a&nbsp;public&nbsp;attribute&nbsp;title&nbsp;and&nbsp;description&nbsp;that&nbsp;is&nbsp;accessed<br>via&nbsp;getDescription()&nbsp;and&nbsp;mutated&nbsp;using&nbsp;setDescription().&nbsp;Further,&nbsp;the&nbsp;description&nbsp;must&nbsp;be<br>either&nbsp;a&nbsp;regular&nbsp;or&nbsp;unicode&nbsp;string.<br>Since&nbsp;this&nbsp;code&nbsp;will&nbsp;not&nbsp;depend&nbsp;on&nbsp;Zope,&nbsp;open&nbsp;a&nbsp;file&nbsp;named&nbsp;test&nbsp;sample.py&nbsp;anywhere&nbsp;and&nbsp;add<br>the&nbsp;following&nbsp;class:<br>1&nbsp;Sample(object):<br>2<br>&quot;&quot;&quot;A&nbsp;trivial&nbsp;Sample&nbsp;object.&quot;&quot;&quot;<br>3<br>4<br>title&nbsp;=&nbsp;None<br>5<br>6<br>def&nbsp;__init__(self):<br>7<br>&quot;&quot;&quot;Initialize&nbsp;object.&quot;&quot;&quot;<br>8<br>self._description&nbsp;=&nbsp;’’<br>9<br>1<br><hr><A name=2></a>2<br>CHAPTER&nbsp;44.&nbsp;WRITING&nbsp;BASIC&nbsp;UNIT&nbsp;TESTS<br>10<br>def&nbsp;setDescription(self,&nbsp;value):<br>11<br>&quot;&quot;&quot;Change&nbsp;the&nbsp;value&nbsp;of&nbsp;the&nbsp;description.&quot;&quot;&quot;<br>12<br>assert&nbsp;isinstance(value,&nbsp;(str,&nbsp;unicode))<br>13<br>self._description&nbsp;=&nbsp;value<br>14<br>15<br>def&nbsp;getDescription(self):<br>16<br>&quot;&quot;&quot;Change&nbsp;the&nbsp;value&nbsp;of&nbsp;the&nbsp;description.&quot;&quot;&quot;<br>17<br>return&nbsp;self._description<br>Line&nbsp;4:&nbsp;The&nbsp;title&nbsp;is&nbsp;just&nbsp;publicly&nbsp;declared&nbsp;and&nbsp;a&nbsp;value&nbsp;of&nbsp;None&nbsp;is&nbsp;given.&nbsp;Therefore&nbsp;this&nbsp;is&nbsp;just<br>a&nbsp;regular&nbsp;attribute.<br>Line&nbsp;8:&nbsp;The&nbsp;actual&nbsp;description&nbsp;string&nbsp;will&nbsp;be&nbsp;stored&nbsp;in&nbsp;description.<br>Line&nbsp;12:&nbsp;Make&nbsp;sure&nbsp;that&nbsp;the&nbsp;description&nbsp;is&nbsp;only&nbsp;a&nbsp;regular&nbsp;or&nbsp;unicode&nbsp;string,&nbsp;like&nbsp;it&nbsp;was&nbsp;stated&nbsp;in<br>the&nbsp;requirements.<br>If&nbsp;you&nbsp;wish&nbsp;you&nbsp;can&nbsp;now&nbsp;manually&nbsp;test&nbsp;the&nbsp;class&nbsp;with&nbsp;the&nbsp;interactive&nbsp;Python&nbsp;shell.&nbsp;Just&nbsp;start<br>Python&nbsp;by&nbsp;entering&nbsp;python&nbsp;in&nbsp;your&nbsp;shell&nbsp;prompt.&nbsp;Note&nbsp;that&nbsp;you&nbsp;should&nbsp;be&nbsp;in&nbsp;the&nbsp;directory&nbsp;in<br>which&nbsp;test&nbsp;sample.py&nbsp;is&nbsp;located&nbsp;when&nbsp;starting&nbsp;Python&nbsp;(an&nbsp;alternative&nbsp;is&nbsp;of&nbsp;course&nbsp;to&nbsp;specify&nbsp;the<br>directory&nbsp;in&nbsp;your&nbsp;PYTHONPATH.)<br>1&nbsp;&gt;&gt;&gt;&nbsp;from&nbsp;test_sample&nbsp;import&nbsp;Sample<br>2&nbsp;&gt;&gt;&gt;&nbsp;sample&nbsp;=&nbsp;Sample()<br>3&nbsp;&gt;&gt;&gt;&nbsp;print&nbsp;sample.title<br>4&nbsp;None<br>5&nbsp;&gt;&gt;&gt;&nbsp;sample.title&nbsp;=&nbsp;’Title’<br>6&nbsp;&gt;&gt;&gt;&nbsp;print&nbsp;sample.title<br>7&nbsp;Title<br>8&nbsp;&gt;&gt;&gt;&nbsp;print&nbsp;sample.getDescription()<br>9<br>10&nbsp;&gt;&gt;&gt;&nbsp;sample.setDescription(’Hello&nbsp;World’)<br>11&nbsp;&gt;&gt;&gt;&nbsp;print&nbsp;sample.getDescription()<br>12&nbsp;Hello&nbsp;World<br>13&nbsp;&gt;&gt;&gt;&nbsp;sample.setDescription(None)<br>14&nbsp;Traceback&nbsp;(most&nbsp;recent&nbsp;call&nbsp;last):<br>15<br>File&nbsp;&quot;&lt;stdin&gt;&quot;,&nbsp;line&nbsp;1,&nbsp;in&nbsp;?<br>16<br>File&nbsp;&quot;test_sample.py&quot;,&nbsp;line&nbsp;31,&nbsp;in&nbsp;setDescription<br>17<br>assert&nbsp;isinstance(value,&nbsp;(str,&nbsp;unicode))<br>18&nbsp;AssertionError<br>As&nbsp;you&nbsp;can&nbsp;see&nbsp;in&nbsp;the&nbsp;last&nbsp;test,&nbsp;non-string&nbsp;object&nbsp;types&nbsp;are&nbsp;not&nbsp;allowed&nbsp;as&nbsp;descriptions&nbsp;and&nbsp;an<br>AssertionError&nbsp;is&nbsp;raised.<br>44.2<br>Writing&nbsp;the&nbsp;Unit&nbsp;Tests<br>The&nbsp;goal&nbsp;of&nbsp;writing&nbsp;the&nbsp;unit&nbsp;tests&nbsp;is&nbsp;to&nbsp;convert&nbsp;this&nbsp;informal,&nbsp;manual,&nbsp;and&nbsp;interactive&nbsp;testing&nbsp;session<br>into&nbsp;a&nbsp;formal&nbsp;test&nbsp;class.&nbsp;Python&nbsp;provides&nbsp;already&nbsp;a&nbsp;module&nbsp;called&nbsp;unittest&nbsp;for&nbsp;this&nbsp;purpose,&nbsp;which<br>is&nbsp;a&nbsp;port&nbsp;of&nbsp;the&nbsp;Java-based&nbsp;unit&nbsp;testing&nbsp;product,&nbsp;JUnit,&nbsp;by&nbsp;Kent&nbsp;Beck&nbsp;and&nbsp;Erich&nbsp;Gamma.&nbsp;There&nbsp;are<br>three&nbsp;levels&nbsp;to&nbsp;the&nbsp;testing&nbsp;framework&nbsp;(this&nbsp;list&nbsp;deviates&nbsp;a&nbsp;bit&nbsp;from&nbsp;the&nbsp;original&nbsp;definitions&nbsp;as&nbsp;found<br>in&nbsp;the&nbsp;Python&nbsp;library&nbsp;documentation.&nbsp;1).<br>1&nbsp;http://www.python.org/doc/current/lib/module-unittest.html<br><hr><A name=3></a>44.2.&nbsp;WRITING&nbsp;THE&nbsp;UNIT&nbsp;TESTS<br>3<br>The&nbsp;smallest&nbsp;unit&nbsp;is&nbsp;obviously&nbsp;the&nbsp;“test”,&nbsp;which&nbsp;is&nbsp;a&nbsp;single&nbsp;method&nbsp;in&nbsp;a&nbsp;TestCase&nbsp;class&nbsp;that<br>tests&nbsp;the&nbsp;behavior&nbsp;of&nbsp;a&nbsp;small&nbsp;piece&nbsp;of&nbsp;code&nbsp;or&nbsp;a&nbsp;particular&nbsp;aspect&nbsp;of&nbsp;an&nbsp;implementation.&nbsp;The&nbsp;“test<br>case”&nbsp;is&nbsp;then&nbsp;a&nbsp;collection&nbsp;tests&nbsp;that&nbsp;share&nbsp;the&nbsp;same&nbsp;setup/inputs.&nbsp;On&nbsp;top&nbsp;of&nbsp;all&nbsp;of&nbsp;this&nbsp;sits&nbsp;the&nbsp;“test<br>suite”&nbsp;which&nbsp;is&nbsp;a&nbsp;collection&nbsp;of&nbsp;test&nbsp;cases&nbsp;and/or&nbsp;other&nbsp;test&nbsp;suites.&nbsp;Test&nbsp;suites&nbsp;combine&nbsp;tests&nbsp;that<br>should&nbsp;be&nbsp;executed&nbsp;together.&nbsp;With&nbsp;the&nbsp;correct&nbsp;setup&nbsp;(as&nbsp;shown&nbsp;in&nbsp;the&nbsp;example&nbsp;below),&nbsp;you&nbsp;can<br>then&nbsp;execute&nbsp;test&nbsp;suites.&nbsp;For&nbsp;large&nbsp;projects&nbsp;like&nbsp;Zope&nbsp;3,&nbsp;it&nbsp;is&nbsp;useful&nbsp;to&nbsp;know&nbsp;that&nbsp;there&nbsp;is&nbsp;also&nbsp;the<br>concept&nbsp;of&nbsp;a&nbsp;test&nbsp;runner,&nbsp;which&nbsp;manages&nbsp;the&nbsp;test&nbsp;run&nbsp;of&nbsp;all&nbsp;or&nbsp;a&nbsp;set&nbsp;of&nbsp;tests.&nbsp;The&nbsp;runner&nbsp;provides<br>useful&nbsp;feedback&nbsp;to&nbsp;the&nbsp;application,&nbsp;so&nbsp;that&nbsp;various&nbsp;user&nbsp;interaces&nbsp;can&nbsp;be&nbsp;developed&nbsp;on&nbsp;top&nbsp;of&nbsp;it.<br>But&nbsp;enough&nbsp;about&nbsp;the&nbsp;theory.&nbsp;In&nbsp;the&nbsp;following&nbsp;example,&nbsp;which&nbsp;you&nbsp;can&nbsp;simply&nbsp;put&nbsp;into&nbsp;the&nbsp;same<br>file&nbsp;as&nbsp;your&nbsp;code&nbsp;above,&nbsp;you&nbsp;will&nbsp;see&nbsp;a&nbsp;test&nbsp;in&nbsp;common&nbsp;Zope&nbsp;3&nbsp;style.<br>1&nbsp;import&nbsp;unittest<br>2<br>3&nbsp;class&nbsp;SampleTest(unittest.TestCase):<br>4<br>&quot;&quot;&quot;Test&nbsp;the&nbsp;Sample&nbsp;class&quot;&quot;&quot;<br>5<br>6<br>def&nbsp;test_title(self):<br>7<br>sample&nbsp;=&nbsp;Sample()<br>8<br>self.assertEqual(sample.title,&nbsp;None)<br>9<br>sample.title&nbsp;=&nbsp;’Sample&nbsp;Title’<br>10<br>self.assertEqual(sample.title,&nbsp;’Sample&nbsp;Title’)<br>11<br>12<br>def&nbsp;test_getDescription(self):<br>13<br>sample&nbsp;=&nbsp;Sample()<br>14<br>self.assertEqual(sample.getDescription(),&nbsp;’’)<br>15<br>sample._description&nbsp;=&nbsp;&quot;Description&quot;<br>16<br>self.assertEqual(sample.getDescription(),&nbsp;’Description’)<br>17<br>18<br>def&nbsp;test_setDescription(self):<br>19<br>sample&nbsp;=&nbsp;Sample()<br>20<br>self.assertEqual(sample._description,&nbsp;’’)<br>21<br>sample.setDescription(’Description’)<br>22<br>self.assertEqual(sample._description,&nbsp;’Description’)<br>23<br>sample.setDescription(u’Description2’)<br>24<br>self.assertEqual(sample._description,&nbsp;u’Description2’)<br>25<br>self.assertRaises(AssertionError,&nbsp;sample.setDescription,&nbsp;None)<br>26<br>27<br>28&nbsp;def&nbsp;test_suite():<br>29<br>return&nbsp;unittest.TestSuite((<br>30<br>unittest.makeSuite(SampleTest),<br>31<br>))<br>32<br>33&nbsp;if&nbsp;__name__&nbsp;==&nbsp;’__main__’:<br>34<br>unittest.main(defaultTest=’test_suite’)<br>Line&nbsp;3–4:&nbsp;We&nbsp;usually&nbsp;develop&nbsp;test&nbsp;classes&nbsp;which&nbsp;must&nbsp;inherit&nbsp;from&nbsp;TestCase.&nbsp;While&nbsp;often&nbsp;not<br>done,&nbsp;it&nbsp;is&nbsp;a&nbsp;good&nbsp;idea&nbsp;to&nbsp;give&nbsp;the&nbsp;class&nbsp;a&nbsp;meaningful&nbsp;docstring&nbsp;that&nbsp;describes&nbsp;the&nbsp;purpose&nbsp;of&nbsp;the<br>tests&nbsp;it&nbsp;includes.<br>Line&nbsp;6,&nbsp;12&nbsp;&amp;&nbsp;18:&nbsp;When&nbsp;a&nbsp;test&nbsp;case&nbsp;is&nbsp;run,&nbsp;a&nbsp;method&nbsp;called&nbsp;runTests()&nbsp;is&nbsp;executed.&nbsp;While&nbsp;it<br>is&nbsp;possible&nbsp;to&nbsp;overrride&nbsp;this&nbsp;method&nbsp;to&nbsp;run&nbsp;tests&nbsp;differently,&nbsp;the&nbsp;default&nbsp;option&nbsp;will&nbsp;look&nbsp;for&nbsp;any<br>method&nbsp;whose&nbsp;name&nbsp;starts&nbsp;with&nbsp;test&nbsp;and&nbsp;execute&nbsp;it&nbsp;as&nbsp;a&nbsp;single&nbsp;test.&nbsp;This&nbsp;way&nbsp;we&nbsp;can&nbsp;create<br>a&nbsp;“test&nbsp;method”&nbsp;for&nbsp;each&nbsp;aspect,&nbsp;method,&nbsp;function&nbsp;or&nbsp;property&nbsp;of&nbsp;the&nbsp;code&nbsp;to&nbsp;be&nbsp;tested.&nbsp;This<br>default&nbsp;is&nbsp;very&nbsp;sensible&nbsp;and&nbsp;is&nbsp;used&nbsp;everywhere&nbsp;in&nbsp;Zope&nbsp;3.<br><hr><A name=4></a>4<br>CHAPTER&nbsp;44.&nbsp;WRITING&nbsp;BASIC&nbsp;UNIT&nbsp;TESTS<br>Note&nbsp;that&nbsp;there&nbsp;is&nbsp;no&nbsp;docstring&nbsp;for&nbsp;test&nbsp;methods.&nbsp;This&nbsp;is&nbsp;intentional.&nbsp;If&nbsp;a&nbsp;docstring&nbsp;is&nbsp;specified,<br>it&nbsp;is&nbsp;used&nbsp;instead&nbsp;of&nbsp;the&nbsp;method&nbsp;name&nbsp;to&nbsp;identify&nbsp;the&nbsp;test.&nbsp;When&nbsp;specifying&nbsp;a&nbsp;docstring,&nbsp;we&nbsp;have<br>noticed&nbsp;that&nbsp;it&nbsp;is&nbsp;very&nbsp;difficult&nbsp;to&nbsp;identify&nbsp;the&nbsp;test&nbsp;later;&nbsp;therefore&nbsp;the&nbsp;method&nbsp;name&nbsp;is&nbsp;a&nbsp;much<br>better&nbsp;choice.<br>Line&nbsp;8,&nbsp;10,&nbsp;14,&nbsp;.&nbsp;.&nbsp;.&nbsp;:&nbsp;The&nbsp;TestCase&nbsp;class&nbsp;implements&nbsp;a&nbsp;handful&nbsp;of&nbsp;methods&nbsp;that&nbsp;aid&nbsp;you&nbsp;with&nbsp;the<br>testing.&nbsp;Here&nbsp;are&nbsp;some&nbsp;of&nbsp;the&nbsp;most&nbsp;frequently&nbsp;used&nbsp;ones.&nbsp;For&nbsp;a&nbsp;complete&nbsp;list&nbsp;see&nbsp;the&nbsp;standard<br>Python&nbsp;documentation&nbsp;referenced&nbsp;above.<br>•&nbsp;assertEqual(first,second[,msg])<br>Checks&nbsp;whether&nbsp;the&nbsp;first&nbsp;and&nbsp;second&nbsp;value&nbsp;are&nbsp;equal.&nbsp;If&nbsp;the&nbsp;test&nbsp;fails,&nbsp;the&nbsp;msg&nbsp;or&nbsp;None<br>is&nbsp;returned.<br>•&nbsp;assertNotEqual(first,second[,msg])<br>This&nbsp;is&nbsp;simply&nbsp;the&nbsp;opposite&nbsp;to&nbsp;assertEqual()&nbsp;by&nbsp;checking&nbsp;for&nbsp;non-equality.<br>•&nbsp;assertRaises(exception,callable,...)<br>You&nbsp;expect&nbsp;the&nbsp;callable&nbsp;to&nbsp;raise&nbsp;exception&nbsp;when&nbsp;executed.&nbsp;After&nbsp;the&nbsp;callable&nbsp;you&nbsp;can<br>specify&nbsp;any&nbsp;amount&nbsp;of&nbsp;positional&nbsp;and&nbsp;keyword&nbsp;arguments&nbsp;for&nbsp;the&nbsp;callable.&nbsp;If&nbsp;you&nbsp;expect<br>a&nbsp;group&nbsp;of&nbsp;exceptions&nbsp;from&nbsp;the&nbsp;execution,&nbsp;you&nbsp;can&nbsp;make&nbsp;exception&nbsp;a&nbsp;tuple&nbsp;of&nbsp;possible<br>exceptions.<br>•&nbsp;assert&nbsp;(expr[,msg])<br>Assert&nbsp;checks&nbsp;whether&nbsp;the&nbsp;specified&nbsp;expression&nbsp;executes&nbsp;correctly.&nbsp;If&nbsp;not,&nbsp;the&nbsp;test&nbsp;fails&nbsp;and<br>msg&nbsp;or&nbsp;None&nbsp;is&nbsp;returned.<br>•&nbsp;failUnlessEqual()<br>This&nbsp;testing&nbsp;method&nbsp;is&nbsp;equivalent&nbsp;to&nbsp;assertEqual().<br>•&nbsp;failUnless(expr[,msg])<br>This&nbsp;method&nbsp;is&nbsp;equivalent&nbsp;to&nbsp;assert&nbsp;(expr[,msg]).<br>•&nbsp;failif()<br>This&nbsp;is&nbsp;the&nbsp;opposite&nbsp;to&nbsp;failUnless().<br>•&nbsp;fail([msg])<br>Fails&nbsp;the&nbsp;running&nbsp;test&nbsp;without&nbsp;any&nbsp;evaluation.&nbsp;This&nbsp;is&nbsp;commonly&nbsp;used&nbsp;when&nbsp;testing&nbsp;various<br>possible&nbsp;execution&nbsp;paths&nbsp;at&nbsp;once&nbsp;and&nbsp;you&nbsp;would&nbsp;like&nbsp;to&nbsp;signify&nbsp;a&nbsp;failure&nbsp;if&nbsp;an&nbsp;improper&nbsp;path<br>was&nbsp;taken.<br>Line&nbsp;6–10:&nbsp;This&nbsp;method&nbsp;tests&nbsp;the&nbsp;title&nbsp;attribute&nbsp;of&nbsp;the&nbsp;Sample&nbsp;class.&nbsp;The&nbsp;first&nbsp;test&nbsp;should<br>be&nbsp;of&nbsp;course&nbsp;that&nbsp;the&nbsp;attribute&nbsp;exists&nbsp;and&nbsp;has&nbsp;the&nbsp;expected&nbsp;initial&nbsp;value&nbsp;(line&nbsp;8).&nbsp;Then&nbsp;the&nbsp;title<br>attribute&nbsp;is&nbsp;changed&nbsp;and&nbsp;we&nbsp;check&nbsp;whether&nbsp;the&nbsp;value&nbsp;was&nbsp;really&nbsp;stored.&nbsp;This&nbsp;might&nbsp;seem&nbsp;like<br>overkill,&nbsp;but&nbsp;later&nbsp;you&nbsp;might&nbsp;change&nbsp;the&nbsp;title&nbsp;in&nbsp;a&nbsp;way&nbsp;that&nbsp;it&nbsp;uses&nbsp;properties&nbsp;instead.&nbsp;Then&nbsp;it<br>becomes&nbsp;very&nbsp;important&nbsp;to&nbsp;check&nbsp;whether&nbsp;this&nbsp;test&nbsp;still&nbsp;passes.<br>Line&nbsp;12–16:&nbsp;First&nbsp;we&nbsp;simply&nbsp;check&nbsp;that&nbsp;getDescription()&nbsp;returns&nbsp;the&nbsp;correct&nbsp;default&nbsp;value.<br>Since&nbsp;we&nbsp;do&nbsp;not&nbsp;want&nbsp;to&nbsp;use&nbsp;other&nbsp;API&nbsp;calls&nbsp;like&nbsp;setDescription()&nbsp;we&nbsp;set&nbsp;a&nbsp;new&nbsp;value&nbsp;of&nbsp;the<br>description&nbsp;via&nbsp;the&nbsp;implementation-internal&nbsp;description&nbsp;attribute&nbsp;(line&nbsp;15).&nbsp;This&nbsp;is&nbsp;okay!&nbsp;Unit<br>tests&nbsp;can&nbsp;make&nbsp;use&nbsp;of&nbsp;implementation-specific&nbsp;attributes&nbsp;and&nbsp;methods.&nbsp;Finally&nbsp;we&nbsp;just&nbsp;check&nbsp;that<br>the&nbsp;correct&nbsp;value&nbsp;is&nbsp;returned.<br><hr><A name=5></a>44.3.&nbsp;RUNNING&nbsp;THE&nbsp;TESTS<br>5<br>Line&nbsp;18–25:&nbsp;On&nbsp;line&nbsp;21–24&nbsp;it&nbsp;is&nbsp;checked&nbsp;that&nbsp;both&nbsp;regular&nbsp;and&nbsp;unicode&nbsp;strings&nbsp;are&nbsp;set&nbsp;correctly.<br>In&nbsp;the&nbsp;last&nbsp;line&nbsp;of&nbsp;the&nbsp;test&nbsp;we&nbsp;make&nbsp;sure&nbsp;that&nbsp;no&nbsp;other&nbsp;type&nbsp;of&nbsp;objects&nbsp;can&nbsp;be&nbsp;set&nbsp;as&nbsp;a&nbsp;description<br>and&nbsp;that&nbsp;an&nbsp;error&nbsp;is&nbsp;raised.<br>28–31:&nbsp;This&nbsp;method&nbsp;returns&nbsp;a&nbsp;test&nbsp;suite&nbsp;that&nbsp;includes&nbsp;all&nbsp;test&nbsp;cases&nbsp;created&nbsp;in&nbsp;this&nbsp;module.&nbsp;It&nbsp;is<br>used&nbsp;by&nbsp;the&nbsp;Zope&nbsp;3&nbsp;test&nbsp;runner&nbsp;when&nbsp;it&nbsp;picks&nbsp;up&nbsp;all&nbsp;available&nbsp;tests.&nbsp;You&nbsp;would&nbsp;basically&nbsp;add&nbsp;the<br>line&nbsp;unittest.makeSuite(TestCaseClass)&nbsp;for&nbsp;each&nbsp;additional&nbsp;test&nbsp;case.<br>33–34:&nbsp;In&nbsp;order&nbsp;to&nbsp;make&nbsp;the&nbsp;test&nbsp;module&nbsp;runnable&nbsp;by&nbsp;itself,&nbsp;you&nbsp;can&nbsp;execute&nbsp;unittest.main()<br>when&nbsp;the&nbsp;module&nbsp;is&nbsp;run.<br>44.3<br>Running&nbsp;the&nbsp;Tests<br>You&nbsp;can&nbsp;run&nbsp;the&nbsp;test&nbsp;by&nbsp;simply&nbsp;calling&nbsp;pythontest&nbsp;sample.py&nbsp;from&nbsp;the&nbsp;directory&nbsp;you&nbsp;saved&nbsp;the<br>file&nbsp;in.&nbsp;Here&nbsp;is&nbsp;the&nbsp;result&nbsp;you&nbsp;should&nbsp;see:<br>.<br>--------------------------------------------------------------------<br>n&nbsp;3&nbsp;tests&nbsp;in&nbsp;0.001s<br>The&nbsp;three&nbsp;dots&nbsp;represent&nbsp;the&nbsp;three&nbsp;tests&nbsp;that&nbsp;were&nbsp;run.&nbsp;If&nbsp;a&nbsp;test&nbsp;had&nbsp;failed,&nbsp;it&nbsp;would&nbsp;have&nbsp;been<br>reported&nbsp;pointing&nbsp;out&nbsp;the&nbsp;failing&nbsp;test&nbsp;and&nbsp;providing&nbsp;a&nbsp;small&nbsp;traceback.<br>When&nbsp;using&nbsp;the&nbsp;default&nbsp;Zope&nbsp;3&nbsp;test&nbsp;runner,&nbsp;tests&nbsp;will&nbsp;be&nbsp;picked&nbsp;up&nbsp;as&nbsp;long&nbsp;as&nbsp;they&nbsp;follow&nbsp;some<br>conventions.<br>•&nbsp;The&nbsp;tests&nbsp;must&nbsp;either&nbsp;be&nbsp;in&nbsp;a&nbsp;package&nbsp;or&nbsp;be&nbsp;a&nbsp;module&nbsp;called&nbsp;tests.<br>•&nbsp;If&nbsp;tests&nbsp;is&nbsp;a&nbsp;package,&nbsp;then&nbsp;all&nbsp;test&nbsp;modules&nbsp;inside&nbsp;must&nbsp;also&nbsp;have&nbsp;a&nbsp;name&nbsp;starting&nbsp;with&nbsp;test,<br>as&nbsp;it&nbsp;is&nbsp;the&nbsp;case&nbsp;with&nbsp;our&nbsp;name&nbsp;test&nbsp;sample.py.<br>•&nbsp;The&nbsp;test&nbsp;module&nbsp;must&nbsp;be&nbsp;somewhere&nbsp;in&nbsp;the&nbsp;Zope&nbsp;3&nbsp;source&nbsp;tree,&nbsp;since&nbsp;the&nbsp;test&nbsp;runner&nbsp;looks<br>only&nbsp;for&nbsp;files&nbsp;there.<br>In&nbsp;our&nbsp;case,&nbsp;you&nbsp;could&nbsp;simply&nbsp;create&nbsp;a&nbsp;tests&nbsp;package&nbsp;in&nbsp;ZOPE3/src&nbsp;(do&nbsp;not&nbsp;forget&nbsp;the<br>init&nbsp;.<br>py&nbsp;file).&nbsp;Then&nbsp;place&nbsp;the&nbsp;test&nbsp;sample.py&nbsp;file&nbsp;into&nbsp;this&nbsp;directory.<br>You&nbsp;you&nbsp;can&nbsp;use&nbsp;the&nbsp;test&nbsp;runner&nbsp;to&nbsp;run&nbsp;only&nbsp;the&nbsp;sample&nbsp;tests&nbsp;as&nbsp;follows&nbsp;from&nbsp;the&nbsp;Zope&nbsp;3&nbsp;root<br>directory:<br>python&nbsp;test.py&nbsp;-vp&nbsp;tests.test_sample<br>The&nbsp;-v&nbsp;option&nbsp;stands&nbsp;for&nbsp;verbose&nbsp;mode,&nbsp;so&nbsp;that&nbsp;detailed&nbsp;information&nbsp;about&nbsp;a&nbsp;test&nbsp;failure&nbsp;is<br>provided.&nbsp;The&nbsp;-p&nbsp;option&nbsp;enables&nbsp;a&nbsp;progress&nbsp;bar&nbsp;that&nbsp;tells&nbsp;you&nbsp;how&nbsp;many&nbsp;tests&nbsp;out&nbsp;of&nbsp;all&nbsp;have&nbsp;been<br>completed.&nbsp;There&nbsp;are&nbsp;many&nbsp;more&nbsp;options&nbsp;that&nbsp;can&nbsp;be&nbsp;specified.&nbsp;You&nbsp;can&nbsp;get&nbsp;a&nbsp;full&nbsp;list&nbsp;of&nbsp;them&nbsp;with<br>the&nbsp;option&nbsp;-h:&nbsp;pythontest.py-h.<br>The&nbsp;output&nbsp;of&nbsp;the&nbsp;call&nbsp;above&nbsp;is&nbsp;as&nbsp;follows:<br>nfiguration&nbsp;file&nbsp;found.<br>nning&nbsp;UNIT&nbsp;tests&nbsp;at&nbsp;level&nbsp;1<br>nning&nbsp;UNIT&nbsp;tests&nbsp;from&nbsp;/opt/zope/Zope3<br>3/3&nbsp;(100.0%):&nbsp;test_title&nbsp;(tests.test_sample.SampleTest)<br>--------------------------------------------------------------------<br>n&nbsp;3&nbsp;tests&nbsp;in&nbsp;0.002s<br><hr><A name=6></a>6<br>CHAPTER&nbsp;44.&nbsp;WRITING&nbsp;BASIC&nbsp;UNIT&nbsp;TESTS<br>nning&nbsp;FUNCTIONAL&nbsp;tests&nbsp;at&nbsp;level&nbsp;1<br>nning&nbsp;FUNCTIONAL&nbsp;tests&nbsp;from&nbsp;/opt/zope/Zope3<br>--------------------------------------------------------------------<br>n&nbsp;0&nbsp;tests&nbsp;in&nbsp;0.000s<br>Line&nbsp;1:&nbsp;The&nbsp;test&nbsp;runner&nbsp;uses&nbsp;a&nbsp;configuration&nbsp;file&nbsp;for&nbsp;some&nbsp;setup.&nbsp;This&nbsp;allows&nbsp;developers&nbsp;to&nbsp;use<br>the&nbsp;test&nbsp;runner&nbsp;for&nbsp;other&nbsp;projects&nbsp;as&nbsp;well.&nbsp;This&nbsp;message&nbsp;simply&nbsp;tells&nbsp;us&nbsp;that&nbsp;the&nbsp;configuration&nbsp;file<br>was&nbsp;found.<br>Line&nbsp;2–8:&nbsp;The&nbsp;unit&nbsp;tests&nbsp;are&nbsp;run.&nbsp;On&nbsp;line&nbsp;4&nbsp;you&nbsp;can&nbsp;see&nbsp;the&nbsp;progress&nbsp;bar.<br>Line&nbsp;9–15:&nbsp;The&nbsp;functional&nbsp;tests&nbsp;are&nbsp;run,&nbsp;since&nbsp;the&nbsp;default&nbsp;test&nbsp;runner&nbsp;runs&nbsp;both&nbsp;types&nbsp;of&nbsp;tests.<br>Since&nbsp;we&nbsp;do&nbsp;not&nbsp;have&nbsp;any&nbsp;functional&nbsp;tests&nbsp;in&nbsp;the&nbsp;specified&nbsp;module,&nbsp;there&nbsp;are&nbsp;no&nbsp;tests&nbsp;to&nbsp;run.&nbsp;To<br>just&nbsp;run&nbsp;the&nbsp;unit&nbsp;tests,&nbsp;use&nbsp;option&nbsp;-u&nbsp;and&nbsp;-f&nbsp;for&nbsp;just&nbsp;running&nbsp;the&nbsp;functional&nbsp;tests.&nbsp;See&nbsp;“Writing<br>Functional&nbsp;Tests”&nbsp;for&nbsp;more&nbsp;detials&nbsp;on&nbsp;functional&nbsp;tests.<br><hr><A name=7></a>44.3.&nbsp;RUNNING&nbsp;THE&nbsp;TESTS<br>7<br>Exercises<br>1.&nbsp;It&nbsp;is&nbsp;not&nbsp;very&nbsp;common&nbsp;to&nbsp;do&nbsp;the&nbsp;setup&nbsp;–&nbsp;in&nbsp;our&nbsp;case&nbsp;sample=Sample()&nbsp;–&nbsp;in&nbsp;every&nbsp;test<br>method.&nbsp;Instead&nbsp;there&nbsp;exists&nbsp;a&nbsp;method&nbsp;called&nbsp;setUp()&nbsp;and&nbsp;its&nbsp;counterpart&nbsp;tearDown&nbsp;that<br>are&nbsp;run&nbsp;before&nbsp;and&nbsp;after&nbsp;each&nbsp;test,&nbsp;respectively.&nbsp;Change&nbsp;the&nbsp;test&nbsp;code&nbsp;above,&nbsp;so&nbsp;that&nbsp;it&nbsp;uses<br>the&nbsp;setUp()&nbsp;method.&nbsp;In&nbsp;later&nbsp;chapters&nbsp;and&nbsp;the&nbsp;rest&nbsp;of&nbsp;the&nbsp;book&nbsp;we&nbsp;will&nbsp;frequently&nbsp;use&nbsp;this<br>method&nbsp;of&nbsp;setting&nbsp;up&nbsp;tests.<br>2.&nbsp;Currently&nbsp;the&nbsp;test&nbsp;setDescription()&nbsp;test&nbsp;only&nbsp;verifies&nbsp;that&nbsp;None&nbsp;is&nbsp;not&nbsp;allowed&nbsp;as&nbsp;input<br>value.<br>(a)&nbsp;Improve&nbsp;the&nbsp;test,&nbsp;so&nbsp;that&nbsp;all&nbsp;other&nbsp;builtin&nbsp;types&nbsp;are&nbsp;tested&nbsp;as&nbsp;well.<br>(b)&nbsp;Also,&nbsp;make&nbsp;sure&nbsp;that&nbsp;any&nbsp;objects&nbsp;inheriting&nbsp;from&nbsp;str&nbsp;or&nbsp;unicode&nbsp;pass&nbsp;as&nbsp;valid&nbsp;values.<br><hr>
\ No newline at end of file
P6
24 23
255
̙̙
\ No newline at end of file
<h2> Testing Markdown </h2>
<p> <code>code</code> and <em>italic</em> and <em>bold</em> and even a <a href="http://plone.org">link</a>.
</p>
<p>Fööbär</p>
<?xml version="1.0"?>
Logilab.org newsen<tr><td><a href="">xmltools 1.3.7</a></td></tr><tr><td><a href="">Python-logic</a></td></tr><tr><td><a href="">PyReverse 0.2.3</a></td></tr><tr><td><a href="">xmltools 1.3.6</a></td></tr><tr><td><a href="">hmm-0.2</a></td></tr><tr><td><a href="">Version 1.2a1 is out</a></td></tr><tr><td><a href="">XMLdiff v0.5.3 (bug fixes)</a></td></tr><tr><td><a href="">hmm-0.1</a></td></tr><tr><td><a href="">PyReverse 0.1 (new product)</a></td></tr><tr><td><a href="">PyPaSax 0.3 (bug fixes)</a></td></tr><tr><td><a href="">XMLdiff v0.5.2 (bug fixes)</a></td></tr><tr><td><a href="">Version 1.1 is out</a></td></tr><tr><td><a href="">Version 1.1b3 is out</a></td></tr><tr><td><a href="">xmltools-1.3.5</a></td></tr><tr><td><a href="">xmltools-1.3.4</a></td></tr><tr><td><a href="">Version 1.1b1 is out</a></td></tr><tr><td><a href="">XMLdiff v0.5 (algorithm change, bug fixes)</a></td></tr><tr><td><a href="">XMLtools v1.3.1 (bugfixes)</a></td></tr><tr><td><a href="">XMLdiff v0.2 (performance improvement)</a></td></tr><tr><td><a href="">XMLdiff v0.1.1 (beta release)</a></td></tr><tr><td><a href="">XPathVis v1.0beta (beta release)</a></td></tr><tr><td><a href="">XMLtools v1.3 (new features)</a></td></tr><tr><td><a href="http://www-106.ibm.com/developerworks/library/l-ai/">Narval on developerWorks</a></td></tr><tr><td><a href="">Version 1.0.1 is out</a></td></tr><tr><td><a href="http://ai.about.com/compute/ai/library/weekly/aa060801a.htm">Narval reviewed on AI.About.com</a></td></tr><tr><td><a href="http://www.ptolemee.com/botshow/text/text_fr/edito/edito_set.html">Narval at BotShow 2001</a></td></tr><tr><td><a href="">Version 1.0 is out</a></td></tr><tr><td><a href="">Network-boot-HOWTO v0.2.1</a></td></tr><tr><td><a href="">GuessLang v0.1.0 (beta release)</a></td></tr><tr><td><a href="">Network-boot-HOWTO v0.1.1</a></td></tr><tr><td><a href="">PyPaSax v0.1</a></td></tr><tr><td><a href="">RC2 is out</a></td></tr><tr><td><a href="">VCalSax v0.1 (beta)</a></td></tr><tr><td><a href="http://www.logilab.com/press/linux-expo2001/">Talk at LinuxExpo in English</a></td></tr><tr><td><a href="">RC1 is out</a></td></tr><tr><td><a href="">XMLtools v1.2 (stable release)</a></td></tr><tr><td><a href="http://www.logilab.org/narval/app.html">Application section on web site</a></td></tr><tr><td><a href="">WMgMon v0.4.0</a></td></tr><tr><td><a href="">XmlTools v1.1</a></td></tr><tr><td><a href="">Beta5 is out</a></td></tr><tr><td><a href="">XmlTools v1.0</a></td></tr><tr><td><a href="">PyGantt v0.6.0</a></td></tr><tr><td><a href="">Beta4 is out</a></td></tr><tr><td><a href="http://www.linuxgazette.com/issue59/chauvat.html">Article on Narval in Linux Gazette</a></td></tr><tr><td><a href="">Beta3 is out</a></td></tr><tr><td><a href="http://www.linuxexpoparis.com/EN/conferences">Logilab invited at Linux Expo</a></td></tr><tr><td><a href="">Beta2 is out</a></td></tr><tr><td><a href="">Beta1 is out</a></td></tr><tr><td><a href="http://www.logilab.org">Beta0 is out</a></td></tr>
<p>This is a test of the *reST* transform<br /> o one<br /> o two<br /> o three</p>
\ No newline at end of file
<dl class="docutils">
<dt>This is a test of the <em>reST</em> transform</dt>
<dd>o one
o two
o three</dd>
</dl>
This is a test of the *reST* transform
o one
o two
o three
<h2 class="title">Heading 1</h2>
<p>Some text.</p>
<div class="section" id="heading-2">
<h3>Heading 2</h3>
<p>Some text, bla ble bli blo blu. Yes, i know this is<a class="reference external" href="http://www.example.com">Stupid</a>.</p>
</div>
<h2 class="title">Title</h2>
<h3 class="subtitle">Subtitle</h3>
<p>This is a test document to make sure subtitle gets the right heading.</p>
<div class="section" id="now-the-real-heading">
<h3>Now the real heading</h3>
<p>The brown fox jumped over the lazy dog.</p>
<div class="section" id="with-a-subheading">
<h4>With a subheading</h4>
<p>Some text, bla ble bli blo blu. Yes, i know this is<a class="reference external" href="http://www.example.com">Stupid</a>.</p>
</div>
</div>
Copying Docutils
Copying Docutils
Author:
David Goodger
Contact:
goodger@users.sourceforge.net
Date:
2002-10-03
Web site: http://docutils.sourceforge.net/
Most of the files included in this project are in the public domain,
and therefore have no license requirement and no restrictions on
copying or usage. The exceptions are:
docutils/optik.py, copyright Gregory P. Ward, released under a
BSD-style license (which can be found in the module's source code).
docutils/roman.py, copyright by Mark Pilgrim, released under the
Python 2.1.1 license .
test/difflib.py, copyright by the Python Software Foundation,
released under the Python 2.2 license . This file is included for
compatibility with Python versions less than 2.2; if you have Python
2.2 or higher, difflib.py is not needed and may be removed. (It's
only used to report test failures anyhow; it isn't installed
anywhere. The included file is a pre-generator version of the
difflib.py module included in Python 2.2.)
(Disclaimer: I am not a lawyer.) Both the BSD license and the Python
license are OSI-approved and GPL-compatible . Although complicated
by multiple owners and lots of legalese, the Python license basically
lets you copy, use, modify, and redistribute files as long as you keep
the copyright attribution intact, note any changes you make, and don't
use the owner's name in vain. The BSD license is similar.
Generated on: 2003-04-19 15:32 UTC.
Generated by Docutils from reStructuredText source.
Copying Docutils
Author: David Goodger
Contact: [1]goodger@users.sourceforge.net
Date: 2002-10-03
Web site: [2]http://docutils.sourceforge.net/
Most of the files included in this project are in the public domain,
and therefore have no license requirement and no restrictions on
copying or usage. The exceptions are:
* docutils/optik.py, copyright Gregory P. Ward, released under a
BSD-style license (which can be found in the module's source
code).
* docutils/roman.py, copyright by Mark Pilgrim, released under the
[3]Python 2.1.1 license.
* test/difflib.py, copyright by the Python Software Foundation,
released under the [4]Python 2.2 license. This file is included
for compatibility with Python versions less than 2.2; if you have
Python 2.2 or higher, difflib.py is not needed and may be removed.
(It's only used to report test failures anyhow; it isn't installed
anywhere. The included file is a pre-generator version of the
difflib.py module included in Python 2.2.)
(Disclaimer: I am not a lawyer.) Both the BSD license and the Python
license are [5]OSI-approved and [6]GPL-compatible. Although
complicated by multiple owners and lots of legalese, the Python
license basically lets you copy, use, modify, and redistribute files
as long as you keep the copyright attribution intact, note any changes
you make, and don't use the owner's name in vain. The BSD license is
similar.
_________________________________________________________________
Generated on: 2003-04-19 15:32 UTC. Generated by [7]Docutils from
[8]reStructuredText source.
References
1. mailto:goodger@users.sourceforge.net
2. http://docutils.sourceforge.net/
3. http://www.python.org/2.1.1/license.html
4. http://www.python.org/2.2/license.html
5. http://opensource.org/licenses/
6. http://www.gnu.org/philosophy/license-list.html
7. http://docutils.sourceforge.net/
8. http://docutils.sourceforge.net/rst.html
<pre class="python">
<span style="color: #004080;">&quot;&quot;&quot; nice docstring &quot;&quot;&quot;</span>
<span style="color: #C00000;">class</span> <span style="color: #000000;">A</span> <span style="color: #0000C0;">:</span> <span style="color: #C00000;">pass</span>
<span style="color: #008000;"># comment
</span>
<span style="color: #C00000;">def</span> <span style="color: #000000;">inc</span><span style="color: #0000C0;">(</span><span style="color: #000000;">i</span><span style="color: #0000C0;">)</span><span style="color: #0000C0;">:</span>
<span style="color: #C00000;">return</span> <span style="color: #000000;">i</span><span style="color: #0000C0;">+</span><span style="color: #0080C0;">1</span>
<span style="color: #C00000;">def</span> <span style="color: #000000;">greater</span><span style="color: #0000C0;">(</span><span style="color: #000000;">a</span><span style="color: #0000C0;">,</span> <span style="color: #000000;">b</span><span style="color: #0000C0;">)</span><span style="color: #0000C0;">:</span>
<span style="color: #004080;">&quot;&quot;&quot;foo &lt;html /&gt;&quot;&quot;&quot;</span>
<span style="color: #C00000;">return</span> <span style="color: #000000;">a</span> <span style="color: #0000C0;">&gt;</span> <span style="color: #000000;">b</span>
</pre>
<h1>Test page</h1>
<table>
<tr>
<th>Test1</th>
<td>test2</td>
</tr>
</table>
<p>This is a text used as a blind text.</p>
<div><![CDATA[
Some CDATA text.
]]>
</div>
<ul>
<li>A sample list item1</li>
<li>A sample list item2</li>
</ul>
<p>This is again a blind text with a<br />line break.</p>
<div>
Can we <q>quote</q> or write something we <del>didn't</del> mean to write? Or how is <ins>this</ins> instead?
</div>
<hr />
<div>
<a href="http://www.plone.org"><img src="http://www.plone.org/logo.jpg" /></a> is just great.
</div>
\ No newline at end of file
<br />
<p><div name="Default" align="left" style=" padding: 0.00mm 0.00mm 0.00mm 0.00mm; ">
<p style="text-indent: 0.00mm; text-align: left; line-height: 4.166667mm; color: Black; background-color: White; ">
how odd: blank named file in directory
</p></div>
<h1>Textile test text</h1>
<p><em>This</em> is quite <strong>boring</strong>, but it needs to be <a href="http://plone.org">done</a>, right?</p>
<h2>Cheeses</h2>
<ol>
<li>Gouda</li>
<li>Roquefort</li>
<li>Emmentaler</li>
</ol>
<h2>Episodes</h2>
<ul>
<li>Bicycle Repairman</li>
<li>Spanish Inquisition</li>
<li>Fishslapping Dance</li>
</ul>
import os
from Testing import ZopeTestCase
from Products.PortalTransforms.tests.utils import input_file_path, normalize_html,\
matching_inputs
from Products.PortalTransforms.transforms.image_to_gif import image_to_gif
from Products.PortalTransforms.transforms.image_to_png import image_to_png
from Products.PortalTransforms.transforms.image_to_jpeg import image_to_jpeg
from erp5.component.module.TransformImageToBmp import image_to_bmp
from Products.PortalTransforms.transforms.image_to_tiff import image_to_tiff
from Products.PortalTransforms.transforms.image_to_ppm import image_to_ppm
from erp5.component.module.TransformImageToPcx import image_to_pcx
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
# we have to set locale because lynx output is locale sensitive !
os.environ['LC_ALL'] = 'C'
class ImageMagickTransformsTest(ERP5TypeTestCase, ZopeTestCase.Functional):
def afterSetUp(self):
super(ImageMagickTransformsTest, self).afterSetUp()
self.pt = self.portal.portal_transforms
def test_image_to_bmp(self):
self.pt.registerTransform(image_to_bmp())
imgFile = open(input_file_path('logo.jpg'), 'rb')
data = imgFile.read()
self.assertEqual(self.portal.mimetypes_registry.classify(data),'image/jpeg')
data = self.pt.convertTo(target_mimetype='image/x-ms-bmp',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/x-ms-bmp')
def test_image_to_gif(self):
self.pt.registerTransform(image_to_gif())
imgFile = open(input_file_path('logo.png'), 'rb')
data = imgFile.read()
self.assertEqual(self.portal.mimetypes_registry.classify(data),'image/png')
data = self.pt.convertTo(target_mimetype='image/gif',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/gif')
def test_image_to_jpeg(self):
self.pt.registerTransform(image_to_jpeg())
imgFile = open(input_file_path('logo.gif'), 'rb')
data = imgFile.read()
self.assertEqual(self.portal.mimetypes_registry.classify(data),'image/gif')
data = self.pt.convertTo(target_mimetype='image/jpeg',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/jpeg')
def test_image_to_png(self):
self.pt.registerTransform(image_to_png())
imgFile = open(input_file_path('logo.jpg'), 'rb')
data = imgFile.read()
self.assertEqual(self.portal.mimetypes_registry.classify(data),'image/jpeg')
data = self.pt.convertTo(target_mimetype='image/png',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/png')
def test_image_to_pcx(self):
self.pt.registerTransform(image_to_pcx())
imgFile = open(input_file_path('logo.gif'), 'rb')
data = imgFile.read()
self.assertEqual(self.portal.mimetypes_registry.classify(data),'image/gif')
data = self.pt.convertTo(target_mimetype='image/pcx',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/pcx')
def test_image_to_ppm(self):
self.pt.registerTransform(image_to_ppm())
imgFile = open(input_file_path('logo.png'), 'rb')
data = imgFile.read()
self.assertEqual(self.portal.mimetypes_registry.classify(data),'image/png')
data = self.pt.convertTo(target_mimetype='image/x-portable-pixmap',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/x-portable-pixmap')
def test_image_to_tiff(self):
self.pt.registerTransform(image_to_tiff())
imgFile = open(input_file_path('logo.jpg'), 'rb')
data = imgFile.read()
self.assertEqual(self.portal.mimetypes_registry.classify(data),'image/jpeg')
data = self.pt.convertTo(target_mimetype='image/tiff',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/tiff')
# FIXME missing tests for image_to_html, st
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
suite.addTest(makeSuite(ImageMagickTransformsTest))
return suite
import unittest
from zope.testing import doctestunit
modules = (
'Products.PortalTransforms.transforms.safe_html',
'Products.PortalTransforms.transforms.rest',
)
def test_suite():
return unittest.TestSuite(
[doctestunit.DocTestSuite(module=module) for module in modules]
)
from Products.Archetypes.tests.atsitetestcase import ATSiteTestCase
from zope.interface import implements
from Products.PortalTransforms.utils import TransformException
from Products.PortalTransforms.interfaces import ITransform
from Products.PortalTransforms.chain import chain
import urllib
import re
class BaseTransform:
def name(self):
return getattr(self, '__name__', self.__class__.__name__)
class HtmlToText(BaseTransform):
implements(ITransform)
inputs = ('text/html',)
output = 'text/plain'
def __call__(self, orig, **kwargs):
orig = re.sub('<[^>]*>(?i)(?m)', '', orig)
return urllib.unquote(re.sub('\n+', '\n', orig)).strip()
def convert(self, orig, data, **kwargs):
orig = self.__call__(orig)
data.setData(orig)
return data
class HtmlToTextWithEncoding(HtmlToText):
output_encoding = 'ascii'
class FooToBar(BaseTransform):
implements(ITransform)
inputs = ('text/*',)
output = 'text/plain'
def __call__(self, orig, **kwargs):
orig = re.sub('foo', 'bar', orig)
return urllib.unquote(re.sub('\n+', '\n', orig)).strip()
def convert(self, orig, data, **kwargs):
orig = self.__call__(orig)
data.setData(orig)
return data
class DummyHtmlFilter1(BaseTransform):
implements(ITransform)
__name__ = 'dummy_html_filter1'
inputs = ('text/html',)
output = 'text/html'
def convert(self, orig, data, **kwargs):
data.setData("<span class='dummy'>%s</span>" % orig)
return data
class DummyHtmlFilter2(BaseTransform):
implements(ITransform)
__name__ = 'dummy_html_filter2'
inputs = ('text/html',)
output = 'text/html'
def convert(self, orig, data, **kwargs):
data.setData("<div class='dummy'>%s</div>" % orig)
return data
class QuxToVHost(DummyHtmlFilter1):
__name__ = 'qux_to_vhost'
def convert(self, orig, data, context, **kwargs):
data.setData(re.sub('qux', context.REQUEST['SERVER_URL'], orig))
return data
class TransformNoIO(BaseTransform):
implements(ITransform)
class BadTransformMissingImplements(BaseTransform):
#__implements__ = None
inputs = ('text/*',)
output = 'text/plain'
class BadTransformBadMIMEType1(BaseTransform):
implements(ITransform)
inputs = ('truc/muche',)
output = 'text/plain'
class BadTransformBadMIMEType2(BaseTransform):
implements(ITransform)
inputs = ('text/plain',)
output = 'truc/muche'
class BadTransformNoInput(BaseTransform):
implements(ITransform)
inputs = ()
output = 'text/plain'
class BadTransformWildcardOutput(BaseTransform):
implements(ITransform)
inputs = ('text/plain',)
output = 'text/*'
class TestEngine(ATSiteTestCase):
def afterSetUp(self):
ATSiteTestCase.afterSetUp(self)
self.engine = self.portal.portal_transforms
self.data = '<b>foo</b>'
def register(self):
#A default set of transforms to prove the interfaces work
self.engine.registerTransform(HtmlToText())
self.engine.registerTransform(FooToBar())
def testRegister(self):
self.register()
def testFailRegister(self):
register = self.engine.registerTransform
self.assertRaises(TransformException, register, TransformNoIO())
self.assertRaises(TransformException, register, BadTransformMissingImplements())
self.assertRaises(TransformException, register, BadTransformBadMIMEType1())
self.assertRaises(TransformException, register, BadTransformBadMIMEType2())
self.assertRaises(TransformException, register, BadTransformNoInput())
self.assertRaises(TransformException, register, BadTransformWildcardOutput())
def testCall(self):
self.register()
data = self.engine('HtmlToText', self.data)
self.assertEqual(data, "foo")
data = self.engine('FooToBar', self.data)
self.assertEqual(data, "<b>bar</b>")
def testConvert(self):
self.register()
data = self.engine.convert('HtmlToText', self.data)
self.assertEqual(data.getData(), "foo")
self.assertEqual(data.getMetadata()['mimetype'], 'text/plain')
self.assertEqual(data.getMetadata().get('encoding'), None)
self.assertEqual(data.name(), "HtmlToText")
self.engine.registerTransform(HtmlToTextWithEncoding())
data = self.engine.convert('HtmlToTextWithEncoding', self.data)
self.assertEqual(data.getMetadata()['mimetype'], 'text/plain')
self.assertEqual(data.getMetadata()['encoding'], 'ascii')
self.assertEqual(data.name(), "HtmlToTextWithEncoding")
def testConvertTo(self):
self.register()
data = self.engine.convertTo('text/plain', self.data, mimetype="text/html")
self.assertEqual(data.getData(), "foo")
self.assertEqual(data.getMetadata()['mimetype'], 'text/plain')
self.assertEqual(data.getMetadata().get('encoding'), None)
self.assertEqual(data.name(), "text/plain")
self.engine.unregisterTransform('HtmlToText')
self.engine.unregisterTransform('FooToBar')
self.engine.registerTransform(HtmlToTextWithEncoding())
data = self.engine.convertTo('text/plain', self.data, mimetype="text/html")
self.assertEqual(data.getMetadata()['mimetype'], 'text/plain')
# HtmlToTextWithEncoding. Now None is the right
#self.assertEqual(data.getMetadata()['encoding'], 'ascii')
# XXX the new algorithm is choosing html_to_text instead of
self.assertEqual(data.getMetadata()['encoding'], None)
self.assertEqual(data.name(), "text/plain")
def testChain(self):
self.register()
hb = chain('hbar')
hb.registerTransform(HtmlToText())
hb.registerTransform(FooToBar())
self.engine.registerTransform(hb)
cache = self.engine.convert('hbar', self.data)
self.assertEqual(cache.getData(), "bar")
self.assertEqual(cache.name(), "hbar")
def testPolicy(self):
mt = 'text/x-html-safe'
data = '<script>this_is_unsafe();</script><p>this is safe</p>'
cache = self.engine.convertTo(mt, data, mimetype='text/html')
self.assertEqual(cache.getData(), '<p>this is safe</p>')
self.engine.registerTransform(DummyHtmlFilter1())
self.engine.registerTransform(DummyHtmlFilter2())
required = ['dummy_html_filter1', 'dummy_html_filter2']
self.engine.manage_addPolicy(mt, required)
expected_policy = [('text/x-html-safe',
('dummy_html_filter1', 'dummy_html_filter2'))]
self.assertEqual(self.engine.listPolicies(), expected_policy)
cache = self.engine.convertTo(mt, data, mimetype='text/html')
self.assertEqual(cache.getData(), '<div class="dummy"><span class="dummy"><p>this is safe</p></span></div>')
self.assertEqual(cache.getMetadata()['mimetype'], mt)
self.assertEqual(cache.name(), mt)
path = self.engine._findPath('text/html', mt, required)
self.assertEqual(str(path),
"[<Transform at dummy_html_filter1>, "
"<Transform at dummy_html_filter2>, "
"<Transform at safe_html>]")
def testSame(self):
data = "This is a test"
mt = "text/plain"
out = self.engine.convertTo('text/plain', data, mimetype=mt)
self.assertEqual(out.getData(), data)
self.assertEqual(out.getMetadata()['mimetype'], 'text/plain')
def testCache(self):
data = "This is a test"
other_data = 'some different data'
mt = "text/plain"
self.engine.max_sec_in_cache = 20
out = self.engine.convertTo(mt, data, mimetype=mt, object=self)
self.assertEqual(out.getData(), data, out.getData())
out = self.engine.convertTo(mt, other_data, mimetype=mt, object=self)
self.assertEqual(out.getData(), data, out.getData())
self.engine.max_sec_in_cache = -1
out = self.engine.convertTo(mt, data, mimetype=mt, object=self)
self.assertEqual(out.getData(), data, out.getData())
out = self.engine.convertTo(mt, other_data, mimetype=mt, object=self)
self.assertEqual(out.getData(), other_data, out.getData())
def testCacheWithVHost(self):
"""Ensure that the transform cache key includes virtual
hosting so that transforms which are dependent on the virtual
hosting don't get invalid data from the cache. This happens,
for example, in the resolve UID functionality used by visual
editors."""
mt = 'text/x-html-safe'
self.engine.registerTransform(QuxToVHost())
required = ['qux_to_vhost']
self.engine.manage_addPolicy(mt, required)
data = '<a href="qux">vhost link</a>'
out = self.engine.convertTo(
mt, data, mimetype='text/html', object=self.folder,
context=self.folder)
self.assertEqual(
out.getData(), '<a href="http://nohost">vhost link</a>',
out.getData())
# Test when object is not a context
out = self.engine.convertTo(
mt, data, mimetype='text/html', object=self,
context=self.folder)
self.assertEqual(
out.getData(), '<a href="http://nohost">vhost link</a>',
out.getData())
# Change the virtual hosting
self.folder.REQUEST['SERVER_URL'] = 'http://otherhost'
out = self.engine.convertTo(
mt, data, mimetype='text/html', object=self.folder,
context=self.folder)
self.assertEqual(
out.getData(), '<a href="http://otherhost">vhost link</a>',
out.getData())
# Test when object is not a context
out = self.engine.convertTo(
mt, data, mimetype='text/html', object=self,
context=self.folder)
self.assertEqual(
out.getData(), '<a href="http://otherhost">vhost link</a>',
out.getData())
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
suite.addTest(makeSuite(TestEngine))
return suite
from Products.Archetypes.tests.atsitetestcase import ATSiteTestCase
from utils import input_file_path
FILE_PATH = input_file_path("demo1.pdf")
class TestGraph(ATSiteTestCase):
def afterSetUp(self):
ATSiteTestCase.afterSetUp(self)
self.engine = self.portal.portal_transforms
def testGraph(self):
data = open(FILE_PATH, 'r').read()
requirements = self.engine._policies.get('text/plain', [])
if requirements:
out = self.engine.convertTo('text/plain', data, filename=FILE_PATH)
self.assertTrue(out.getData())
def testFindPath(self):
originalMap = self.engine._mtmap
"""
The dummy map used for this test corresponds to a graph
depicted in ASCII art below :
+---+
| |
| v
+-->1<-->2-->4-->6<--7
^ ^ |
| | |
v | |
3<---+ |
^ |
| |
v |
5<-------+
"""
# we need a DummyTransform class
class DT:
def __init__(self, name):
self._name = name
def name(self):
return self._name
dummyMap1 = {
'1': { '1': [DT('transform1-1')],
'2': [DT('transform1-2')],
'3': [DT('transform1-3')]},
'2': { '1': [DT('transform2-1')],
'3': [DT('transform2-3')],
'4': [DT('transform2-4')]},
'3': { '1': [DT('transform3-1')],
'2': [DT('transform3-2')],
'5': [DT('transform3-5')]},
'4': { '5': [DT('transform4-5')],
'6': [DT('transform4-6')]},
'5': { '3': [DT('transform5-3')]},
'7': { '6': [DT('transform7-6')]}
}
expectedPathes = {
'1-1': [],
'1-2': ['transform1-2'],
'1-3': ['transform1-3'],
'1-4': ['transform1-2', 'transform2-4'],
'1-5': ['transform1-3', 'transform3-5'],
'1-6': ['transform1-2', 'transform2-4', 'transform4-6'],
'1-7': None,
'2-1': ['transform2-1'],
'2-2': [],
'2-4': ['transform2-4'],
'4-2': ['transform4-5', 'transform5-3', 'transform3-2'],
'5-3': ['transform5-3']
}
self.engine._mtmap = dummyMap1
for orig in ['1','2','3','4','5','6','7']:
for target in ['1','2','3','4','5','6','7']:
# build the name of the path
pathName = orig + '-' + target
# do we have any expectation for this path ?
if pathName in expectedPathes.keys():
# we do. Here is the expected shortest path
expectedPath = expectedPathes[pathName]
# what's the shortest path according to the engine ?
gotPath = self.engine._findPath(orig,target)
# just keep the name of the transforms, please
if gotPath is not None:
gotPath = [transform.name() for transform in gotPath]
# this must be the same as in our expectation
self.assertEqual(expectedPath, gotPath)
self.engine._mtmap = originalMap
def testFindPathWithEmptyTransform(self):
""" _findPath should not throw "index out of range" when dealing with
empty transforms list
"""
dummyMap = {'1': {'2': []}}
self.engine._mtmap = dummyMap
self.engine._findPath('1','2')
def testIdentity(self):
orig = 'Some text'
converted = self.engine.convertTo(
'text/plain', 'Some text', mimetype='text/plain')
self.assertEqual(orig, str(converted))
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
suite.addTest(makeSuite(TestGraph))
return suite
# -*- coding: utf-8 -*-
from Products.Archetypes.tests.atsitetestcase import ATSiteTestCase
class TransformTestCase(ATSiteTestCase):
def afterSetUp(self):
ATSiteTestCase.afterSetUp(self)
self.transforms = self.portal.portal_transforms
class TestIntelligentTextToHtml(TransformTestCase):
def performTransform(self, orig, targetMimetype = 'text/html', mimetype='text/x-web-intelligent'):
return self.transforms.convertTo(targetMimetype, orig, context=self.portal, mimetype=mimetype).getData()
def testHyperlinks(self):
orig = "A test http://test.com"
new = self.performTransform(orig)
self.assertEqual(new, 'A test <a href="http://test.com" rel="nofollow">http://test.com</a>')
def testMailto(self):
orig = "A test test@test.com of mailto"
new = self.performTransform(orig)
self.assertEqual(new, 'A test <a href="&#0109;ailto&#0058;test&#0064;test.com">test&#0064;test.com</a> of mailto')
def testTextAndLinks(self):
orig = """A test
URL: http://test.com End
Mail: test@test.com End
URL: http://foo.com End"""
new = self.performTransform(orig)
self.assertEqual(new, 'A test<br />' \
'URL: <a href="http://test.com" rel="nofollow">http://test.com</a> End<br />' \
'Mail: <a href="&#0109;ailto&#0058;test&#0064;test.com">test&#0064;test.com</a> End<br />' \
'URL: <a href="http://foo.com" rel="nofollow">http://foo.com</a> End')
def testTextAndLinksAtEndOfLine(self):
orig = """A test
URL: http://test.com
Mail: test@test.com
URL: http://foo.com"""
new = self.performTransform(orig)
self.assertEqual(new, 'A test<br />' \
'URL: <a href="http://test.com" rel="nofollow">http://test.com</a><br />' \
'Mail: <a href="&#0109;ailto&#0058;test&#0064;test.com">test&#0064;test.com</a><br />' \
'URL: <a href="http://foo.com" rel="nofollow">http://foo.com</a>')
def testIndents(self):
orig = """A test
URL: http://test.com
Mail: test@test.com
URL: http://foo.com"""
new = self.performTransform(orig)
self.assertEqual(new, 'A test<br />' \
'&nbsp;&nbsp;URL: <a href="http://test.com" rel="nofollow">http://test.com</a><br />' \
'&nbsp;&nbsp;&nbsp;&nbsp;Mail: <a href="&#0109;ailto&#0058;test&#0064;test.com">test&#0064;test.com</a><br />' \
'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;URL: <a href="http://foo.com" rel="nofollow">http://foo.com</a>')
def testEntities(self):
orig = "Some & funny < characters"
new = self.performTransform(orig)
self.assertEqual(new, "Some &amp; funny &lt; characters")
def testAccentuatedCharacters(self):
orig = "The French use é à ô ù à and ç"
new = self.performTransform(orig)
self.assertEqual(new, "The French use &eacute; &agrave; &ocirc; &ugrave; &agrave; and &ccedil;")
class TestHtmlToIntelligentText(TransformTestCase):
def performTransform(self, orig, targetMimetype = 'text/x-web-intelligent', mimetype='text/html'):
return self.transforms.convertTo(targetMimetype, orig, context=self.portal, mimetype=mimetype).getData()
def testStripTags(self):
orig = "Some <b>bold</b> text."
new = self.performTransform(orig)
self.assertEqual(new, "Some bold text.")
def testBreaks(self):
orig = "Some<br/>broken<BR/>text<br />"
new = self.performTransform(orig)
self.assertEqual(new, "Some\nbroken\ntext\n")
def testStartBlocks(self):
orig = "A block<dt>there</dt>"
new = self.performTransform(orig)
self.assertEqual(new, "A block\n\nthere")
def testEndBlocks(self):
orig = "<p>Paragraph</p>Other stuff"
new = self.performTransform(orig)
self.assertEqual(new, "Paragraph\n\nOther stuff")
def testIndentBlocks(self):
orig = "A<blockquote>Indented blockquote</blockquote>"
new = self.performTransform(orig)
self.assertEqual(new, "A\n\n Indented blockquote")
def testListBlocks(self):
orig = "A list<ul><li>Foo</li><li>Bar</li></ul>"
new = self.performTransform(orig)
self.assertEqual(new, "A list\n\n - Foo\n\n - Bar\n\n")
def testNbsp(self):
orig = "Some space &nbsp;&nbsp;here"
new = self.performTransform(orig)
self.assertEqual(new, "Some space here")
def testAngles(self):
orig = "Watch &lt;this&gt; and &lsaquo;that&rsaquo;"
new = self.performTransform(orig)
self.assertEqual(new, "Watch <this> and &#8249;that&#8250;")
def testBullets(self):
orig = "A &bull; bullet"
new = self.performTransform(orig)
self.assertEqual(new, "A &#8226; bullet")
def testAmpersands(self):
orig = "An &amp; ampersand"
new = self.performTransform(orig)
self.assertEqual(new, "An & ampersand")
def testEntities(self):
orig = "A &mdash; dash"
new = self.performTransform(orig)
self.assertEqual(new, "A &#8212; dash")
def testPre(self):
orig = "A <pre> pre\n section</pre>"
new = self.performTransform(orig)
self.assertEqual(new, "A \n\n pre\n section\n\n")
def testWhitespace(self):
orig = "A \n\t spaceful, <b> tag-filled</b>, <b> <i> snippet\n</b></i>"
new = self.performTransform(orig)
self.assertEqual(new, "A spaceful, tag-filled, snippet ")
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
suite.addTest(makeSuite(TestIntelligentTextToHtml))
suite.addTest(makeSuite(TestHtmlToIntelligentText))
return suite
import os
import logging
from Products.Archetypes.tests.atsitetestcase import ATSiteTestCase
from Products.CMFCore.utils import getToolByName
from utils import input_file_path, output_file_path, normalize_html,\
load, matching_inputs
from Products.PortalTransforms.data import datastream
from Products.PortalTransforms.interfaces import IDataStream
from Products.PortalTransforms.libtransforms.utils import MissingBinary
from Products.PortalTransforms.transforms.image_to_gif import image_to_gif
from Products.PortalTransforms.transforms.image_to_png import image_to_png
from Products.PortalTransforms.transforms.image_to_jpeg import image_to_jpeg
from erp5.component.module.TransformImageToBmp import image_to_bmp
from Products.PortalTransforms.transforms.image_to_tiff import image_to_tiff
from Products.PortalTransforms.transforms.image_to_ppm import image_to_ppm
from erp5.component.module.TransformImageToPcx import image_to_pcx
from Products.PortalTransforms.transforms.textile_to_html import HAS_TEXTILE
from Products.PortalTransforms.transforms.markdown_to_html import HAS_MARKDOWN
from os.path import exists
# we have to set locale because lynx output is locale sensitive !
os.environ['LC_ALL'] = 'C'
logger = logging.getLogger('PortalTransforms')
class TransformTest(ATSiteTestCase):
def do_convert(self, filename=None):
if filename is None and exists(self.output + '.nofilename'):
output = self.output + '.nofilename'
else:
output = self.output
input = open(self.input)
orig = input.read()
input.close()
data = datastream(self.transform.name())
res_data = self.transform.convert(orig, data, filename=filename)
self.assert_(IDataStream.providedBy(res_data))
got = res_data.getData()
try:
output = open(output)
except IOError:
import sys
print >>sys.stderr, 'No output file found.'
print >>sys.stderr, 'File %s created, check it !' % self.output
output = open(output, 'w')
output.write(got)
output.close()
self.assert_(0)
expected = output.read()
if self.normalize is not None:
expected = self.normalize(expected)
got = self.normalize(got)
output.close()
got_start = got.strip()[:30]
expected_start = expected.strip()[:30]
self.assertEqual(got_start, expected_start,
'[%s]\n\n!=\n\n[%s]\n\nIN %s(%s)' % (
got_start, expected_start, self.transform.name(), self.input))
self.assertEqual(self.subobjects, len(res_data.getSubObjects()),
'%s\n\n!=\n\n%s\n\nIN %s(%s)' % (
self.subobjects, len(res_data.getSubObjects()),
self.transform.name(), self.input))
def testSame(self):
try:
self.do_convert(filename=self.input)
except MissingBinary:
pass
def testSameNoFilename(self):
try:
self.do_convert()
except MissingBinary:
pass
def __repr__(self):
return self.transform.name()
class PILTransformsTest(ATSiteTestCase):
def afterSetUp(self):
ATSiteTestCase.afterSetUp(self)
self.pt = self.portal.portal_transforms
self.mimetypes_registry = getToolByName(self.portal, 'mimetypes_registry')
def test_image_to_bmp(self):
self.pt.registerTransform(image_to_bmp())
imgFile = open(input_file_path('logo.jpg'), 'rb')
data = imgFile.read()
self.assertEqual(self.mimetypes_registry.classify(data),'image/jpeg')
data = self.pt.convertTo(target_mimetype='image/x-ms-bmp',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/x-ms-bmp')
def test_image_to_gif(self):
self.pt.registerTransform(image_to_gif())
imgFile = open(input_file_path('logo.png'), 'rb')
data = imgFile.read()
self.assertEqual(self.mimetypes_registry.classify(data),'image/png')
data = self.pt.convertTo(target_mimetype='image/gif',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/gif')
def test_image_to_jpeg(self):
self.pt.registerTransform(image_to_jpeg())
imgFile = open(input_file_path('logo.gif'), 'rb')
data = imgFile.read()
self.assertEqual(self.mimetypes_registry.classify(data),'image/gif')
data = self.pt.convertTo(target_mimetype='image/jpeg',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/jpeg')
def test_image_to_png(self):
self.pt.registerTransform(image_to_png())
imgFile = open(input_file_path('logo.jpg'), 'rb')
data = imgFile.read()
self.assertEqual(self.mimetypes_registry.classify(data),'image/jpeg')
data = self.pt.convertTo(target_mimetype='image/png',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/png')
def test_image_to_pcx(self):
self.pt.registerTransform(image_to_pcx())
imgFile = open(input_file_path('logo.gif'), 'rb')
data = imgFile.read()
self.assertEqual(self.mimetypes_registry.classify(data),'image/gif')
data = self.pt.convertTo(target_mimetype='image/pcx',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/pcx')
def test_image_to_ppm(self):
self.pt.registerTransform(image_to_ppm())
imgFile = open(input_file_path('logo.png'), 'rb')
data = imgFile.read()
self.assertEqual(self.mimetypes_registry.classify(data),'image/png')
data = self.pt.convertTo(target_mimetype='image/x-portable-pixmap',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/x-portable-pixmap')
def test_image_to_tiff(self):
self.pt.registerTransform(image_to_tiff())
imgFile = open(input_file_path('logo.jpg'), 'rb')
data = imgFile.read()
self.assertEqual(self.mimetypes_registry.classify(data),'image/jpeg')
data = self.pt.convertTo(target_mimetype='image/tiff',orig=data)
self.assertEqual(data.getMetadata()['mimetype'], 'image/tiff')
TRANSFORMS_TESTINFO = (
('Products.PortalTransforms.transforms.pdf_to_html',
"demo1.pdf", "demo1.html", None, 0
),
('Products.PortalTransforms.transforms.word_to_html',
"test.doc", "test_word.html", normalize_html, 0
),
('Products.PortalTransforms.transforms.lynx_dump',
"test_lynx.html", "test_lynx.txt", None, 0
),
('Products.PortalTransforms.transforms.html_to_text',
"test_lynx.html", "test_html_to_text.txt", None, 0
),
('Products.PortalTransforms.transforms.identity',
"rest1.rst", "rest1.rst", None, 0
),
('Products.PortalTransforms.transforms.text_to_html',
"rest1.rst", "rest1.html", None, 0
),
('Products.PortalTransforms.transforms.safe_html',
"test_safehtml.html", "test_safe.html", None, 0
),
('erp5.component.module.TransformImageToBmp',
"logo.jpg", "logo.bmp", None, 0
),
('Products.PortalTransforms.transforms.image_to_gif',
"logo.bmp", "logo.gif", None, 0
),
('Products.PortalTransforms.transforms.image_to_jpeg',
"logo.gif", "logo.jpg", None, 0
),
('Products.PortalTransforms.transforms.image_to_png',
"logo.bmp", "logo.png", None, 0
),
('Products.PortalTransforms.transforms.image_to_ppm',
"logo.gif", "logo.ppm", None, 0
),
('Products.PortalTransforms.transforms.image_to_tiff',
"logo.png", "logo.tiff", None, 0
),
('erp5.component.module.TransformImageToPcx',
"logo.png", "logo.pcx", None, 0
),
)
if HAS_MARKDOWN:
TRANSFORMS_TESTINFO = TRANSFORMS_TESTINFO + (
('Products.PortalTransforms.transforms.markdown_to_html',
"markdown.txt", "markdown.html", None, 0
),
)
if HAS_TEXTILE:
TRANSFORMS_TESTINFO = TRANSFORMS_TESTINFO + (
('Products.PortalTransforms.transforms.textile_to_html',
"input.textile", "textile.html", None, 0
),
)
def initialise(transform, normalize, pattern):
global TRANSFORMS_TESTINFO
for fname in matching_inputs(pattern):
outname = '%s.out' % fname.split('.')[0]
#print transform, fname, outname
TRANSFORMS_TESTINFO += ((transform, fname, outname, normalize, 0),)
# ReST test cases
initialise('Products.PortalTransforms.transforms.rest', normalize_html, "rest*.rst")
# Python test cases
initialise('Products.PortalTransforms.transforms.python', normalize_html, "*.py")
# FIXME missing tests for image_to_html, st
TR_NAMES = None
def make_tests(test_descr=TRANSFORMS_TESTINFO):
"""generate tests classes from test info
return the list of generated test classes
"""
tests = []
for _transform, tr_input, tr_output, _normalize, _subobjects in test_descr:
# load transform if necessary
if type(_transform) is type(''):
try:
_transform = load(_transform).register()
except MissingBinary:
# we are not interessted in tests with missing binaries
continue
except:
import traceback
traceback.print_exc()
continue
if TR_NAMES is not None and not _transform.name() in TR_NAMES:
print 'skip test for', _transform.name()
continue
class TransformTestSubclass(TransformTest):
input = input_file_path(tr_input)
output = output_file_path(tr_output)
transform = _transform
normalize = lambda x, y: _normalize(y)
subobjects = _subobjects
tests.append(TransformTestSubclass)
tests.append(PILTransformsTest)
return tests
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
for test in make_tests():
suite.addTest(makeSuite(test))
return suite
"""
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
from Products.Archetypes.tests.atsitetestcase import ATSiteTestCase
class TestXSSFilter(ATSiteTestCase):
def afterSetUp(self):
ATSiteTestCase.afterSetUp(self)
self.engine = self.portal.portal_transforms
def doTest(self, data_in, data_out):
html = self.engine.convertTo('text/x-html-safe', data_in, mimetype="text/html")
self.assertEqual (data_out,html.getData())
def test_1(self):
data_in = """<html><body><img src="javascript:Alert('XSS');" /></body></html>"""
data_out = """<img />"""
self.doTest(data_in, data_out)
def test_2(self):
data_in = """<img src="javascript:Alert('XSS');" />"""
data_out = """<img />"""
self.doTest(data_in, data_out)
def test_3(self):
data_in = """<html><body><IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;></body></html>"""
data_out = """<img />"""
self.doTest(data_in, data_out)
def test_4(self):
data_in = """<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>"""
data_out = """<img />"""
self.doTest(data_in, data_out)
def test_5(self):
data_in = """<img src="jav
asc
ript:Alert('XSS');" />"""
data_out = """<img />"""
self.doTest(data_in, data_out)
def test_6(self):
data_in = """<img src="jav asc ript:Alert('XSS');"/>"""
data_out = """<img />"""
self.doTest(data_in, data_out)
def test_7(self):
data_in = """<a href=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>test med a-tag</a>"""
data_out = """<a>test med a-tag</a>"""
self.doTest(data_in, data_out)
def test_8(self):
data_in = """<div style="bacground:url(jav asc ript:Alert('XSS')">test</div>"""
data_out = """<div>test</div>"""
self.doTest(data_in, data_out)
def test_9(self):
data_in = """<div style="bacground:url(jav
asc
ript:
Alert('XSS')">test</div>"""
data_out = """<div>test</div>"""
self.doTest(data_in, data_out)
def test_10(self):
data_in = """<div style="bacground:url(&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;">test</div>"""
data_out = """<div>test</div>"""
self.doTest(data_in, data_out)
def test_11(self):
data_in = """<div style="bacground:url(v b sc ript:msgbox('XSS')">test</div>"""
data_out = """<div>test</div>"""
self.doTest(data_in, data_out)
def test_12(self):
data_in = """<img src="vbscript:msgbox('XSS')"/>"""
data_out = """<img />"""
self.doTest(data_in, data_out)
def test_13(self):
data_in = """<img src="vb
sc
ript:msgbox('XSS')"/>"""
data_out = """<img />"""
self.doTest(data_in, data_out)
def test_14(self):
data_in = """<a href="vbscript:Alert('XSS')">test</a>"""
data_out = """<a>test</a>"""
self.doTest(data_in, data_out)
def test_15(self):
data_in = """<div STYLE="width: expression(window.location='http://www.dr.dk';);">div</div>"""
data_out = """<div>div</div>"""
self.doTest(data_in, data_out)
def test_16(self):
data_in = """<div STYLE="width: ex pre ss io n(window.location='http://www.dr.dk';);">div</div>"""
data_out = """<div>div</div>"""
self.doTest(data_in, data_out)
def test_17(self):
data_in = """<div STYLE="width: ex
pre
ss
io
n(window.location='http://www.dr.dk';);">div</div>"""
data_out = """<div>div</div>"""
self.doTest(data_in, data_out)
def test_18(self):
data_in = """<div style="width: 14px;">div</div>"""
data_out = data_in
self.doTest(data_in, data_out)
def test_19(self):
data_in = """<a href="http://www.headnet.dk">headnet</a>"""
data_out = data_in
self.doTest(data_in, data_out)
def test_20(self):
data_in = """<img src="http://www.headnet.dk/log.jpg" />"""
data_out = data_in
self.doTest(data_in, data_out)
def test_21(self):
data_in = """<mustapha name="mustap" tlf="11 11 11 11" address="unknown">bla bla bla</mustapha>"""
data_out = """bla bla bla"""
self.doTest(data_in, data_out)
def test_22(self):
data_in = '<<frame></frame>script>alert("XSS");<<frame></frame>/script>'
data_out = '&lt;script&gt;alert("XSS");&lt;/script&gt;'
self.doTest(data_in, data_out)
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
suite.addTest(makeSuite(TestXSSFilter))
return suite
if __name__ == '__main__':
framework()
import re
import glob
from unittest import TestSuite
from sys import modules
from os.path import join, abspath, dirname, basename
def normalize_html(s):
s = re.sub(r"&nbsp;", " ", s)
s = re.sub(r"\s+", " ", s)
s = re.sub(r"(?s)\s+<", "<", s)
s = re.sub(r"(?s)>\s+", ">", s)
s = re.sub(r"\r", "", s)
return s
def build_test_suite(package_name,module_names,required=1):
"""
Utlitity for building a test suite from a package name
and a list of modules.
If required is false, then ImportErrors will simply result
in that module's tests not being added to the returned
suite.
"""
suite = TestSuite()
try:
for name in module_names:
the_name = package_name+'.'+name
__import__(the_name,globals(),locals())
suite.addTest(modules[the_name].test_suite())
except ImportError:
if required:
raise
return suite
PREFIX = abspath(dirname(__file__))
def input_file_path(file):
return join(PREFIX, 'input', file)
def output_file_path(file):
return join(PREFIX, 'output', file)
def matching_inputs(pattern):
return [basename(path) for path in glob.glob(join(PREFIX, "input", pattern))]
def load(dotted_name, globals=None):
""" load a python module from it's name """
mod = __import__(dotted_name, globals)
components = dotted_name.split('.')
for comp in components[1:]:
mod = getattr(mod, comp)
return mod
from rigging import transformer
import os
from stat import ST_MTIME
## BIG BAD FUNCTIONAL TEST OF OOo Word Conversion
## The interfaces work, but are not quite what we need
## I might have to back fill a chain from source/dest graphing
file = "/tmp/word.doc"
class curry:
def __init__(self, func, *fixed_args):
self.func = func
self.fixed_args = fixed_args
def __call__(self, *variable_args):
return apply(self.func, self.fixed_args +
variable_args)
data = open("/tmp/word.doc", "r").read()
data = transformer.convert("WordToHtml", data, filename="word.doc")
print data.getData()
"""try to build some usefull transformations with the command and xml
transforms and the available binaries
"""
from Products.PortalTransforms.libtransforms.utils import bin_search, MissingBinary
COMMAND_CONFIGS = (
('lynx_dump', '.html',
{'binary_path' : 'lynx',
'command_line' : '-dump %(input)s',
'inputs' : ('text/html',),
'output' : 'text/plain',
}),
('tidy_html', '.html',
{'binary_path' : 'tidy',
'command_line' : '%(input)s',
'inputs' : ('text/html',),
'output' : 'text/html',
}),
('rtf_to_html', None,
{'binary_path' : 'unrtf',
'command_line' : '%(input)s',
'inputs' : ('application/rtf',),
'output' : 'text/html',
}),
('ppt_to_html', None,
{'binary_path' : 'ppthtml',
'command_line' : '%(input)s',
'inputs' : ('application/vnd.ms-powerpoint',),
'output' : 'text/html',
}),
('excel_to_html', None,
{'binary_path' : 'xlhtml',
'command_line' : '-nh -a %(input)s',
'inputs' : ('application/vnd.ms-excel',),
'output' : 'text/html',
}),
('ps_to_text', None,
{'binary_path' : 'ps2ascii',
'command_line' : '%(input)s',
'inputs' : ('application/postscript',),
'output' : 'text/plain',
}),
)
TRANSFORMS = {}
from command import ExternalCommandTransform
for tr_name, extension, config in COMMAND_CONFIGS:
try:
bin = bin_search(config['binary_path'])
except MissingBinary:
print 'no such binary', config['binary_path']
else:
tr = ExternalCommandTransform(tr_name, extension)
tr.config['binary_path'] = bin
tr.__name__ = tr_name
tr.config = config
TRANSFORMS[tr_name] = tr
XMLPROCS_CONF = {
'xsltproc' : '--catalogs --xinclude -o %(output)s %(transform)s %(input)s',
'4xslt' : ' -o %(output)s %(input)s %(transform)s'
}
bin = None
for proc in XMLPROCS_CONF.keys():
try:
bin = bin_search(proc)
break
except MissingBinary:
print 'no such binary', proc
if bin is not None:
print 'Using %s as xslt processor' % bin
from xml import XsltTransform
for output in ('html', 'plain'):
name = "xml_to_" + output
command_line = XMLPROCS_CONF[proc]
tr = XsltTransform(name=name, inputs=('text/xml',), output='text/'+output,
binary_path=bin, command_line=command_line)
TRANSFORMS[name] = tr
def initialize(engine):
for transform in TRANSFORMS.values():
engine.registerTransform(transform)
"""
A custom transform using external command
"""
__revision__ = '$Id: command.py 4439 2005-06-15 16:32:36Z panjunyong $'
import os.path
from os import popen3
from Products.PortalTransforms.interfaces import ITransform
from zope.interface import implements
from Products.PortalTransforms.libtransforms.utils import bin_search, sansext
from Products.PortalTransforms.libtransforms.commandtransform import commandtransform
from Products.PortalTransforms.utils import log
class ExternalCommandTransform(commandtransform):
""" Custom external command
transform content by launching an external command
the command should take the content in an input file (designed by '%s' in
the command line parameters) and return output on stdout.
Input and output mime types must be set correctly !
"""
implements(ITransform)
__name__ = "command_transform"
def __init__(self, name=None, input_extension=None, **kwargs):
self.config = {
'binary_path' : '',
'command_line' : '',
'inputs' : ('text/plain',),
'output' : 'text/plain',
}
self.config_metadata = {
'binary_path' : ('string', 'Binary path',
'Path of the executable on the server.'),
'command_line' : ('string', 'Command line',
'''Additional command line option.
There should be at least the input file (designed by "%(input)s").
The transformation\'s result must be printed on stdout.
'''),
'inputs' : ('list', 'Inputs', 'Input(s) MIME type. Change with care.'),
'output' : ('string', 'Output', 'Output MIME type. Change with care.'),
}
self.config.update(kwargs)
commandtransform.__init__(self, name=name, binary=self.config['binary_path'], **kwargs)
# use the full binary path
self.config.update({'binary_path':self.binary})
self.input_extension = input_extension
def __getattr__(self, attr):
if attr == 'inputs':
return self.config['inputs']
if attr == 'output':
return self.config['output']
raise AttributeError(attr)
def convert(self, data, cache, **kwargs):
filename = kwargs.get('filename') or 'unknown'
if self.input_extension is not None:
kwargs['filename'] = 'unknown' + self.input_extension
else:
kwargs['filename'] = 'unknown' + os.path.splitext(filename)[-1]
tmpdir, fullname = self.initialize_tmpdir(data, **kwargs)
data = self.invokeCommand(fullname)
cache.setData(data)
path, images = self.subObjects(tmpdir)
objects = {}
if images:
self.fixImages(path, images, objects)
cache.setSubObjects(objects)
self.cleanDir(tmpdir)
return cache
def invokeCommand(self, input_name):
command = '%(binary_path)s %(command_line)s' % self.config
input, output, error = popen3(command % input_name)
input.close()
# first read stderr, else we may hang on stout
# but, still hang my windows, so commented it :-(
# error_data = error.read()
error_data = 'error while running "%s"' % (command % input_name)
error.close()
data = output.read()
output.close()
if error_data and not data:
data = error_data
else:
log('Error while running "%s":\n %s' % (command % input_name,
error_data))
return data
def register():
return ExternalCommandTransform()
"""
A custom transform using external command
"""
__revision__ = '$Id: xml.py 4787 2005-08-19 21:43:41Z dreamcatcher $'
from os.path import join, dirname, exists
import re
from os import popen3, popen4, system
from cStringIO import StringIO
from Products.PortalTransforms.interfaces import ITransform
from zope.interface import implements
from Products.PortalTransforms.libtransforms.utils import bin_search, sansext
from Products.PortalTransforms.libtransforms.commandtransform import commandtransform
from Products.PortalTransforms.utils import log
class XsltTransform(commandtransform):
""" Custom external command
transform xml content by launching an external XSLT processor
Input and output mime types must be set correctly !
You can associate different document type to different transformations.
"""
implements(ITransform)
__name__ = "xml_to_html"
def __init__(self, name=None, **kwargs):
self.config = {
# sample configuration
'binary_path' : bin_search('xsltproc'),
'command_line' : '%(transform)s %(input)s',
'inputs' : ('text/xml',),
'output' : 'text/html',
'output_encoding' : 'UTF-8',
'dtds' : {
'-//OASIS//DTD DocBook V4.1//EN' : '/usr/share/sgml/docbook/xsl-stylesheets-1.29/html/docbook.xsl'
},
'default_transform': ''
}
self.config_metadata = {
'binary_path' : ('string', 'Binary path',
'Path of the executable on the server.'),
'command_line' : ('string', 'Command line',
'''Additional command line option.
There should be at least the input file (designed by "%(input)s") and the xsl
file (designed by "%(transform)s").The transformation\'s result must be printed on stdout.
'''),
'inputs' : ('list', 'Inputs', 'Input(s) MIME type. Change with care.'),
'output' : ('string', 'Output', 'Output MIME type. Change with care.'),
'output_encoding': ('string', 'Output encoding', 'Output encoding.'),
'dtds' : ('dict', 'DTDs',
'Association of public ids or dtds to XSL transformations.',
('Public id', 'XSLT path')),
'default_transform' : ('string', 'Default xslt',
'Default xslt, used when no specific transformation is found.'),
}
self.config.update(kwargs)
if name:
self.__name__ = name
def __getattr__(self, attr):
if attr == 'inputs':
return self.config['inputs']
if attr == 'output':
return self.config['output']
if attr == 'output_encoding':
return self.config['output_encoding']
raise AttributeError(attr)
def convert(self, data, cache, **kwargs):
base_name = sansext(kwargs.get("filename") or 'unknown.xml')
dtds = self.config['dtds']
tmpdir, fullname = self.initialize_tmpdir(data, filename=base_name)
try:
try:
doctype = get_doctype(data)
except DTException:
try:
doctype = get_dtd(data)
except DTException:
log('Unable to get doctype nor dtd in %s' % data)
doctype = None
if doctype and dtds.has_key(doctype):
data = self.invokeCommand(fullname, dtds[doctype])
elif self.config['default_transform']:
data = self.invokeCommand(fullname, self.config['default_transform'])
cache.setData(data)
path, images = self.subObjects(tmpdir)
objects = {}
if images:
self.fixImages(path, images, objects)
cache.setSubObjects(objects)
return cache
finally:
self.cleanDir(tmpdir)
def invokeCommand(self, input_name, xsl):
dest_dir = dirname(input_name)
output_file = join(dirname(input_name), 'tr_output')
command = '%(binary_path)s %(command_line)s' % self.config
data = {'input': input_name, 'output': output_file, 'transform': xsl}
system(command % data)
if exists(output_file):
data = open(output_file).read()
else:
data = 'error occurs during transform. See error log'
return data
def register():
return XsltTransform()
DT_RGX = re.compile('<!DOCTYPE \w* PUBLIC \"([^"]*)\" \"([^"]*)\"')
DT_RGX2 = re.compile('<!DOCTYPE \w* SYSTEM \"([^"]*)\"')
class DTException(Exception): pass
def get_doctype(data):
""" return the public id for the doctype given some raw xml data
"""
if not hasattr(data, 'readlines'):
data = StringIO(data)
for line in data.readlines():
line = line.strip()
if not line:
continue
if line.startswith('<?xml') or line.startswith('<!-- '):
continue
m = DT_RGX.match(line)
if m is not None:
return m.group(1)
else:
raise DTException('Unable to match doctype in "%s"' % line)
def get_dtd(data):
""" return the public id for the doctype given some raw xml data
"""
if not hasattr(data, 'readlines'):
data = StringIO(data)
for line in data.readlines():
line = line.strip()
if not line:
continue
if line.startswith('<?xml') or line.startswith('<!-- '):
continue
m = DT_RGX.match(line)
if m is not None:
return m.group(2)
m = DT_RGX2.match(line)
if m is not None:
return m.group(1)
else:
raise DTException('Unable to match doctype in "%s"' % line)
if __name__ == '__main__':
print get_doctype('''<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE article PUBLIC "-//LOGILAB/DTD DocBook V4.1.2-Based Extension V0.1//EN" "dcbk-logilab.dtd" []>
<book id="devtools_user_manual" lang="fr">
''')
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