From b255c894fa25e05ae280a1e7f938deb0f3673232 Mon Sep 17 00:00:00 2001
From: Julien Muchembled <jm@nexedi.com>
Date: Thu, 23 Dec 2010 16:37:57 +0000
Subject: [PATCH] PortalTransforms: merge upstream 2.0

This fixes test_20_reStructuredText partially.

Conflicts:
	Products/PortalTransforms/TransformEngine.py
	Products/PortalTransforms/libtransforms/commandtransform.py
	Products/PortalTransforms/transforms/safe_html.py
	Products/PortalTransforms/utils.py

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@41726 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/PortalTransforms/TransformEngine.py   | 273 ++++++++++++------
 product/PortalTransforms/cache.py             |  34 ++-
 .../libtransforms/commandtransform.py         |  46 +--
 .../PortalTransforms/libtransforms/utils.py   |  23 +-
 .../PortalTransforms/tests/input/markdown.txt |   2 +
 .../tests/input/test_safehtml.html            |   4 +
 .../PortalTransforms/tests/output/demo1.html  | 210 +-------------
 .../tests/output/demo1.html.nofilename        | 210 +-------------
 .../tests/output/markdown.html                |   1 +
 .../PortalTransforms/tests/output/rest2.out   |   6 +-
 .../PortalTransforms/tests/output/rest3.out   |  10 +-
 .../tests/output/test_safe.html               |   4 +
 product/PortalTransforms/tests/test_engine.py |  55 ++++
 product/PortalTransforms/tests/test_graph.py  |  81 ++++++
 .../PortalTransforms/tests/test_transforms.py |  31 +-
 product/PortalTransforms/tests/utils.py       |   1 +
 .../transforms/markdown_to_html.py            |  20 +-
 .../transforms/office_wvware.py               |   6 +-
 .../PortalTransforms/transforms/safe_html.py  |  57 +++-
 .../transforms/word_to_html.py                |  27 +-
 .../unsafe_transforms/build_transforms.py     |  12 +-
 product/PortalTransforms/utils.py             |   4 +-
 22 files changed, 505 insertions(+), 612 deletions(-)

diff --git a/product/PortalTransforms/TransformEngine.py b/product/PortalTransforms/TransformEngine.py
index 79a7819024..3e41abd76f 100644
--- a/product/PortalTransforms/TransformEngine.py
+++ b/product/PortalTransforms/TransformEngine.py
@@ -1,19 +1,14 @@
 # -*- coding: utf-8 -*-
 from logging import DEBUG
+
+from persistent.list import PersistentList
 from zope.interface import implements
 
 from AccessControl import ClassSecurityInfo
 from Acquisition import aq_base
 from App.class_init import default__class_init__ as InitializeClass
-from Persistence import PersistentMapping
-try:
-    from ZODB.PersistentList import PersistentList
-except ImportError:
-    from persistent.list import PersistentList
 from OFS.Folder import Folder
-
-from Products.PageTemplates.PageTemplateFile import PageTemplateFile
-
+from Persistence import PersistentMapping
 from Products.CMFCore.ActionProviderBase import ActionProviderBase
 from Products.CMFCore.permissions import ManagePortal, View
 try:
@@ -22,18 +17,19 @@ except ImportError: # BACK: Zope 2.8
     registerToolInterface = lambda tool_id, tool_interface: None
 from Products.CMFCore.utils import UniqueObject
 from Products.CMFCore.utils import getToolByName
+from Products.PageTemplates.PageTemplateFile import PageTemplateFile
 
-from Products.PortalTransforms.libtransforms.utils import MissingBinary
-from Products.PortalTransforms import transforms
-from Products.PortalTransforms.interfaces import IDataStream
-from Products.PortalTransforms.interfaces import ITransform
-from Products.PortalTransforms.interfaces import IEngine
-from Products.PortalTransforms.interfaces import IPortalTransformsTool
 from Products.PortalTransforms.data import datastream
 from Products.PortalTransforms.chain import TransformsChain
 from Products.PortalTransforms.chain import chain
 from Products.PortalTransforms.cache import Cache
+from Products.PortalTransforms.interfaces import IDataStream
+from Products.PortalTransforms.interfaces import ITransform
+from Products.PortalTransforms.interfaces import IEngine
+from Products.PortalTransforms.interfaces import IPortalTransformsTool
+from Products.PortalTransforms.libtransforms.utils import MissingBinary
 from Products.PortalTransforms.Transform import Transform
+from Products.PortalTransforms.transforms import initialize
 from Products.PortalTransforms.utils import log
 from Products.PortalTransforms.utils import TransformException
 from Products.PortalTransforms.utils import _www
@@ -50,28 +46,25 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
     implements(IPortalTransformsTool, IEngine)
 
     meta_types = all_meta_types = (
-        { 'name'   : 'Transform',
-          'action' : 'manage_addTransformForm'},
-        { 'name'   : 'TransformsChain',
-          'action' : 'manage_addTransformsChainForm'},
+        {'name': 'Transform', 'action': 'manage_addTransformForm'},
+        {'name': 'TransformsChain', 'action': 'manage_addTransformsChainForm'},
         )
 
     manage_addTransformForm = PageTemplateFile('addTransform', _www)
-    manage_addTransformsChainForm = PageTemplateFile('addTransformsChain', _www)
+    manage_addTransformsChainForm = PageTemplateFile(
+        'addTransformsChain', _www)
     manage_cacheForm = PageTemplateFile('setCacheTime', _www)
-    manage_editTransformationPolicyForm = PageTemplateFile('editTransformationPolicy', _www)
+    manage_editTransformationPolicyForm = PageTemplateFile(
+        'editTransformationPolicy', _www)
     manage_reloadAllTransforms = PageTemplateFile('reloadAllTransforms', _www)
 
-    manage_options = ((Folder.manage_options[0],) + Folder.manage_options[2:] +
-                      (
-        { 'label'   : 'Caches',
-          'action' : 'manage_cacheForm'},
-        { 'label'   : 'Policy',
-          'action' : 'manage_editTransformationPolicyForm'},
-        { 'label'   : 'Reload transforms',
-          'action' : 'manage_reloadAllTransforms'},
-        )
-                      )
+    manage_options = (
+        (Folder.manage_options[0], ) + Folder.manage_options[2:] +
+        ({'label': 'Caches', 'action': 'manage_cacheForm'},
+         {'label': 'Policy', 'action': 'manage_editTransformationPolicyForm'},
+         {'label': 'Reload transforms',
+          'action': 'manage_reloadAllTransforms'},
+        ))
 
     security = ClassSecurityInfo()
 
@@ -81,7 +74,7 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
         self.max_sec_in_cache = max_sec_in_cache
         self._new_style_pt = 1
 
-    # mimetype oriented conversions (iengine interface) ########################
+    # mimetype oriented conversions (iengine interface)
 
     def unregisterTransform(self, name):
         """ unregister a transform
@@ -113,7 +106,7 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
         target_mimetype = str(target_mimetype)
 
         if object is not None:
-            cache = Cache(object)
+            cache = Cache(object, context=context)
             data = cache.getCache(target_mimetype)
             if data is not None:
                 time, data = data
@@ -126,8 +119,8 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
         registry = getToolByName(self, 'mimetypes_registry')
 
         if not getattr(aq_base(registry), 'classify', None):
-            # avoid problems when importing a site with an old mimetype registry
-            # XXX return None or orig?
+            # avoid problems when importing a site with an old mimetype
+            # registry
             return None
 
         orig_mt = registry.classify(orig,
@@ -135,8 +128,9 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
                                     filename=kwargs.get('filename'))
         orig_mt = str(orig_mt)
         if not orig_mt:
-            log('Unable to guess input mime type (filename=%s, mimetype=%s)' %(
-                kwargs.get('mimetype'), kwargs.get('filename')), severity=WARNING)
+            log('Unable to guess input mime type (filename=%s, mimetype=%s)' %
+                (kwargs.get('mimetype'), kwargs.get('filename')),
+                severity=WARNING)
             return None
 
         target_mt = registry.lookup(target_mimetype)
@@ -151,9 +145,7 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
         # If orig_mt and target_mt are the same, we only allow
         # a one-hop transform, a.k.a. filter.
         # XXX disabled filtering for now
-        filter_only = False
         if orig_mt == str(target_mt):
-            filter_only = True
             data.setData(orig)
             md = data.getMetadata()
             md['mimetype'] = str(orig_mt)
@@ -171,9 +163,9 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
             path = self._findPath(orig_mt, target_mt)
 
         if not path:
-            log('NO PATH FROM %s TO %s : %s' % (orig_mt, target_mimetype, path),
-                severity=WARNING)
-            return None #XXX raise TransformError
+            log('NO PATH FROM %s TO %s : %s' %
+                (orig_mt, target_mimetype, path), severity=WARNING)
+            return None
 
         if len(path) > 1:
             ## create a chain on the fly (sly)
@@ -183,7 +175,8 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
         else:
             transform = path[0]
 
-        result = transform.convert(orig, data, context=context, usedby=usedby, **kwargs)
+        result = transform.convert(orig, data, context=context,
+                                   usedby=usedby, **kwargs)
         self._setMetaData(result, transform)
 
         # set cache if possible
@@ -307,8 +300,8 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
                               (output, transform.name())
                         raise TransformException(msg)
                     if len(mto) > 1:
-                        msg = 'Wildcarding not allowed in transform\'s output '\
-                              'MIME type'
+                        msg = ("Wildcarding not allowed in transform's output "
+                               "MIME type")
                         raise TransformException(msg)
 
                     for mt2 in mto[0].mimetypes:
@@ -342,36 +335,124 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
         """return the shortest path for transformation from orig mimetype to
         target mimetype
         """
-        path = []
-
         if not self._mtmap:
             return None
 
-        # naive algorithm :
-        #  find all possible paths with required transforms
-        #  take the shortest
-        #
-        # it should be enough since we should not have so much possible paths
-        shortest, winner = 9999, None
-        for path in self._getPaths(str(orig), str(target), required_transforms):
-            if len(path) < shortest:
-                winner = path
-                shortest = len(path)
-
-        return winner
-
-    def _getPaths(self, orig, target, requirements, path=None, result=None, searched_orig_list=None):
-        """return a all path for transformation from orig mimetype to
-        target mimetype
-        """
-        # don't search the same orig again, otherwise infinite loop occurs.
-        if searched_orig_list is None:
-            searched_orig_list = []
-        if orig in searched_orig_list:
+        orig = str(orig)
+        target = str(target)
+        # First, let's deal with required transforms.
+        if required_transforms:
+            # Let's decompose paths, then.
+            required_transform = required_transforms.pop(0)
+            # The first path must lead to one of the inputs supported
+            # by this first required transform.
+            # Which input types are supported by this transform ?
+            supportedInputs = {}
+            for input, outputs in self._mtmap.items():
+                for output, transforms in outputs.items():
+                    for transform in transforms:
+                        if transform.name() == required_transform:
+                            supportedInputs[input] = 'ok'
+                            # BTW, let's remember the output type
+                            transformOutput = output
+                            # and remember the transform, it is
+                            # useful later
+                            requiredTransform = transform
+            # Which of these inputs will be reachable with the
+            # shortest path ?
+            shortest = 9999 # big enough, I guess
+            shortestFirstPath = None
+            for supportedInput in supportedInputs.keys():
+                # We start from orig
+                firstOrig = orig
+                # And want to reach supportedInput
+                firstTarget = supportedInput
+                # What's the shortest path ?
+                firstPath = self._findPath(firstOrig, firstTarget)
+                if firstPath is not None:
+                    if len(firstPath) < shortest:
+                        # Here is a path which is shorter than others
+                        # which also reach the required transform.
+                        shortest = len(firstPath)
+                        shortestFirstPath = firstPath
+            if shortestFirstPath == None:
+                return None # there is no path leading to this transform
+            # Then we have to take this transform.
+            secondPath = [requiredTransform]
+            # From the output of this transform, we then have to
+            # reach our target, possible through other required
+            # transforms.
+            thirdOrig = transformOutput
+            thirdTarget = target
+            thirdPath = self._findPath(thirdOrig, thirdTarget,
+                                       required_transforms)
+            if thirdPath is None:
+                return None # no path
+            # Final result is the concatenation of these 3 parts
+            return shortestFirstPath + secondPath + thirdPath
+
+        if orig == target:
+            return []
+
+        # Now let's efficiently find the shortest path from orig
+        # to target (without required transforms).
+        # The overall idea is that we build all possible paths
+        # starting from orig and of given length. And we increment
+        # this length until one of these paths reaches our target or
+        # until all reachable types have been reached.
+        currentPathLength = 0
+        pathToType = {orig: []} # all paths we know, by end of path.
+        def typesWithPathOfLength(length):
+            '''Returns the lists of known paths of a given length'''
+            result = []
+            for type_, path in pathToType.items():
+                if len(path) == length:
+                    result.append(type_)
             return result
-        else:
-            searched_orig_list.append(orig)
+        # We will start exploring paths which start from types
+        # reachable in zero steps. That is paths which start from
+        # orig.
+        typesToStartFrom = typesWithPathOfLength(currentPathLength)
+        # Explore paths while there are new paths to be explored
+        while len(typesToStartFrom) > 0:
+            for startingType in typesToStartFrom:
+                # Where can we go in one step starting from here ?
+                outputs = self._mtmap.get(startingType)
+                if outputs:
+                    for reachedType, transforms in outputs.items():
+                        # Does this lead to a type we never reached before ?
+                        if reachedType not in pathToType.keys() and transforms:
+                            # Yes, we did not know any path reaching this type
+                            # Let's remember the path to here
+                            pathToType[reachedType] = (
+                                pathToType[startingType] + [transforms[0]])
+                            if reachedType == target:
+                                # This is the first time we reach our target.
+                                # We have our shortest path to target.
+                                return pathToType[target]
+            # We explored all possible paths of length currentPathLength
+            # Let's increment that length.
+            currentPathLength += 1
+            # What are the next types to start from ?
+            typesToStartFrom = typesWithPathOfLength(currentPathLength)
+        # We are done exploring paths starting from orig
+        # and this exploration did not reach our target.
+        # Hence there is no path from orig to target.
+        return None
+
+    def _getPaths(self, orig, target, requirements, path=None, result=None):
+        """return some of the paths for transformation from orig mimetype to
+        target mimetype with the guarantee that the shortest path is included.
+        If target is the same as orig, then returns an empty path.
+        """
+
+        shortest = 9999
+        if result:
+            for okPath in result:
+                shortest = min(shortest, len(okPath))
 
+        if orig == target:
+            return [[]]
         if path is None:
             result = []
             path = []
@@ -380,9 +461,9 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
         if outputs is None:
             return result
 
-        registry = getToolByName(self, 'mimetypes_registry') 
-        mto = registry.lookup(target) 
-        # target mimetype aliases 
+        registry = getToolByName(self, 'mimetypes_registry')
+        mto = registry.lookup(target)
+        # target mimetype aliases
         target_aliases = mto[0].mimetypes
 
         path.append(None)
@@ -400,8 +481,14 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
                 if o_mt in target_aliases:
                     if not requirements:
                         result.append(path[:])
+                        if len(path[:]) < shortest:
+                            # here is a shorter one !
+                            shortest = len(path)
                 else:
-                    self._getPaths(o_mt, target, requirements, path, result, searched_orig_list)
+                    if len(path) < shortest:
+                        # keep exploring this path, it is still short enough
+                        self._getPaths(o_mt, target, requirements,
+                                       path, result)
                 if required:
                     requirements.append(name)
         path.pop()
@@ -414,14 +501,11 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
         transform tool is added
         """
         Folder.manage_afterAdd(self, item, container)
-        transforms.initialize(self)
-        # XXX required?
-        #try:
-        #    # first initialization
-        #    transforms.initialize(self)
-        #except:
-        #    # may fail on copy
-        #    pass
+        try:
+            initialize(self)
+        except TransformException:
+            # may fail on copy or zexp import
+            pass
 
     security.declareProtected(ManagePortal, 'manage_addTransform')
     def manage_addTransform(self, id, module, REQUEST=None):
@@ -465,28 +549,31 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
             reloaded.append((id, o.module))
         return reloaded
 
-    # Policy handling methods #################################################
+    # Policy handling methods
 
-    def manage_addPolicy(self, output_mimetype, required_transforms, REQUEST=None):
+    def manage_addPolicy(self, output_mimetype, required_transforms,
+                         REQUEST=None):
         """ add a policy for a given output mime types"""
         registry = getToolByName(self, 'mimetypes_registry')
         if not registry.lookup(output_mimetype):
             raise TransformException('Unknown MIME type')
-        if self._policies.has_key(output_mimetype):
+        if output_mimetype in self._policies:
             msg = 'A policy for output %s is yet defined' % output_mimetype
             raise TransformException(msg)
 
         required_transforms = tuple(required_transforms)
         self._policies[output_mimetype] = required_transforms
         if REQUEST is not None:
-            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_editTransformationPolicyForm')
+            REQUEST['RESPONSE'].redirect(self.absolute_url() +
+                '/manage_editTransformationPolicyForm')
 
     def manage_delPolicies(self, outputs, REQUEST=None):
         """ remove policies for given output mime types"""
         for mimetype in outputs:
             del self._policies[mimetype]
         if REQUEST is not None:
-            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_editTransformationPolicyForm')
+            REQUEST['RESPONSE'].redirect(self.absolute_url() +
+                '/manage_editTransformationPolicyForm')
 
     def listPolicies(self):
         """ return the list of defined policies
@@ -498,20 +585,21 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
             self._policies = PersistentMapping()
         return self._policies.items()
 
-    # mimetype oriented conversions (iengine interface) ########################
+    # mimetype oriented conversions (iengine interface)
 
     def registerTransform(self, transform):
         """register a new transform
 
-        transform isn't a Zope Transform (the wrapper) but the wrapped transform
-        the persistence wrapper will be created here
+        transform isn't a Zope Transform (the wrapper) but the wrapped
+        transform the persistence wrapper will be created here
         """
         # needed when call from transform.transforms.initialize which
         # register non zope transform
         module = str(transform.__module__)
         transform = Transform(transform.name(), module, transform)
         if not ITransform.providedBy(transform):
-            raise TransformException('%s does not implement ITransform' % transform)
+            raise TransformException('%s does not implement ITransform' %
+                                     transform)
         name = transform.name()
         __traceback_info__ = (name, transform)
         if name not in self.objectIds():
@@ -539,8 +627,9 @@ class TransformTool(UniqueObject, ActionProviderBase, Folder):
 
     # available mimetypes ####################################################
     def listAvailableTextInputs(self):
-        """ Returns a list of mimetypes that can be used as input for textfields
-            by building a list of the inputs beginning with "text/" of all transforms.
+        """Returns a list of mimetypes that can be used as input for textfields
+        by building a list of the inputs beginning with "text/" of all
+        transforms.
         """
         available_types = []
         candidate_transforms = [object[1] for object in self.objectItems()]
diff --git a/product/PortalTransforms/cache.py b/product/PortalTransforms/cache.py
index 604d064e72..9badbfc665 100644
--- a/product/PortalTransforms/cache.py
+++ b/product/PortalTransforms/cache.py
@@ -3,10 +3,16 @@
 from time import time
 from Acquisition import aq_base
 
+_marker = object()
+
 class Cache:
 
-    def __init__(self, context, _id='_v_transform_cache'):
-        self.context = context
+    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):
@@ -17,17 +23,19 @@ class Cache:
         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
-        context = self.context
+        obj = self.obj
         key = self._genCacheKey(key)
-        if getattr(aq_base(context), self._id, None) is None:
-            setattr(context, self._id, {})
-        getattr(context, self._id)[key] = (time(), value)
+        if getattr(aq_base(obj), self._id, None) is None:
+            setattr(obj, self._id, {})
+        getattr(obj, self._id)[key] = (time(), value)
         return key
 
     def getCache(self, key):
@@ -36,9 +44,9 @@ class Cache:
         return None if not present
         else return a tuple (time spent in cache, value)
         """
-        context = self.context
+        obj = self.obj
         key = self._genCacheKey(key)
-        dict = getattr(context, self._id, None)
+        dict = getattr(obj, self._id, None)
         if dict is None :
             return None
         try:
@@ -46,18 +54,18 @@ class Cache:
             return time() - orig_time, value
         except TypeError:
             return None
-        
+
     def purgeCache(self, key=None):
         """Remove cache
         """
-        context = self.context
+        obj = self.obj
         id = self._id
-        if not shasattr(context, id):
+        if getattr(obj, id, _marker) is _marker:
             return
         if key is None:
-            delattr(context, id)
+            delattr(obj, id)
         else:
-            cache = getattr(context, id)
+            cache = getattr(obj, id)
             key = self._genCacheKey(key)
             if cache.has_key(key):
                 del cache[key]
diff --git a/product/PortalTransforms/libtransforms/commandtransform.py b/product/PortalTransforms/libtransforms/commandtransform.py
index de1ca5fb52..4bae3f69da 100644
--- a/product/PortalTransforms/libtransforms/commandtransform.py
+++ b/product/PortalTransforms/libtransforms/commandtransform.py
@@ -87,28 +87,30 @@ class popentransform:
 
     def convert(self, data, cache, **kwargs):
         command = "%s %s" % (self.binary, self.binaryArgs)
-        if not self.useStdin:
-            tmpfile, tmpname = tempfile.mkstemp(text=False) # create tmp
-            os.write(tmpfile, data) # write data to tmp using a file descriptor
-            os.close(tmpfile)       # close it so the other process can read it
-            command = command % { 'infile' : tmpname } # apply tmp name to command
-
-        cin, couterr = os.popen4(command, 'b')
-
-        if self.useStdin:
-            cin.write(str(data))
-
-        status = cin.close()
-
-        out = self.getData(couterr)
-        couterr.close()
-
-        if not self.useStdin:
-            # remove tmp file
-            os.unlink(tmpname)
-
-        cache.setData(out)
-        return cache
+        tmpname = None
+        try:
+            if not self.useStdin:
+                tmpfile, tmpname = tempfile.mkstemp(text=False) # create tmp
+                os.write(tmpfile, data) # write data to tmp using a file descriptor
+                os.close(tmpfile)       # close it so the other process can read it
+                command = command % { 'infile' : tmpname } # apply tmp name to command
+
+            cin, couterr = os.popen4(command, 'b')
+
+            if self.useStdin:
+                cin.write(str(data))
+
+            status = cin.close()
+
+            out = self.getData(couterr)
+            couterr.close()
+
+            cache.setData(out)
+            return cache
+        finally:
+            if not self.useStdin and tmpname is not None:
+                # remove tmp file
+                os.unlink(tmpname)
 
 from subprocess import Popen, PIPE
 import shlex
diff --git a/product/PortalTransforms/libtransforms/utils.py b/product/PortalTransforms/libtransforms/utils.py
index d68a8a8c9a..9254464104 100644
--- a/product/PortalTransforms/libtransforms/utils.py
+++ b/product/PortalTransforms/libtransforms/utils.py
@@ -1,7 +1,7 @@
 import re
 import os
 import sys
-from sgmllib import SGMLParser
+from sgmllib import SGMLParser, SGMLParseError
 
 try:
     # Need to be imported before win32api to avoid dll loading
@@ -207,7 +207,26 @@ class StrippingParser( SGMLParser ):
 
             self.result = "%s</%s>" % (self.result, tag)
             remTag = '</%s>' % tag
-
+    
+    def parse_declaration(self, i):
+        """Fix handling of CDATA sections. Code borrowed from BeautifulSoup.
+        """
+        j = None
+        if self.rawdata[i:i+9] == '<![CDATA[':
+             k = self.rawdata.find(']]>', i)
+             if k == -1:
+                 k = len(self.rawdata)
+             data = self.rawdata[i+9:k]
+             j = k+3
+             self.result.append("<![CDATA[%s]]>" % data)
+        else:
+            try:
+                j = SGMLParser.parse_declaration(self, i)
+            except SGMLParseError:
+                toHandle = self.rawdata[i:]
+                self.result.append(toHandle)
+                j = i + len(toHandle)
+        return j
 
 def scrubHTML( html ):
     """ Strip illegal HTML tags from string text.  """
diff --git a/product/PortalTransforms/tests/input/markdown.txt b/product/PortalTransforms/tests/input/markdown.txt
index 60d087a62e..ca8adfd2c7 100644
--- a/product/PortalTransforms/tests/input/markdown.txt
+++ b/product/PortalTransforms/tests/input/markdown.txt
@@ -1,3 +1,5 @@
 ## Testing Markdown 
 
 `code` and _italic_ and *bold* and even a [link](http://plone.org).
+
+F枚枚b盲r
diff --git a/product/PortalTransforms/tests/input/test_safehtml.html b/product/PortalTransforms/tests/input/test_safehtml.html
index 6b5e9e80bf..08134472e8 100644
--- a/product/PortalTransforms/tests/input/test_safehtml.html
+++ b/product/PortalTransforms/tests/input/test_safehtml.html
@@ -15,6 +15,10 @@
 </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>
diff --git a/product/PortalTransforms/tests/output/demo1.html b/product/PortalTransforms/tests/output/demo1.html
index 450161bfd8..65a1ec9176 100644
--- a/product/PortalTransforms/tests/output/demo1.html
+++ b/product/PortalTransforms/tests/output/demo1.html
@@ -1,209 +1 @@
-<A name=1></a>Chapter 44<br>
-Writing Basic Unit Tests<br>
-Di铿僣ulty<br>
-Newcomer<br>
-Skills<br>
-鈥� All you need to know is some Python.<br>
-Problem/Task<br>
-As you know by now, Zope 3 gains its incredible stability from testing any code in great detail. The<br>currently most common method is to write unit tests. This chapter introduces unit tests 鈥� which<br>are Zope 3 independent 鈥� and introduces some of the subtleties.<br>
-Solution<br>
-44.1<br>
-Implementing the Sample Class<br>
-Before we can write tests, we have to write some code that we can test. Here, we will implement<br>a simple class called Sample with a public attribute title and description that is accessed<br>via getDescription() and mutated using setDescription(). Further, the description must be<br>either a regular or unicode string.<br>
-Since this code will not depend on Zope, open a 铿乴e named test sample.py anywhere and add<br>
-the following class:<br>
-1 Sample(object):<br>
-2<br>
-&quot;&quot;&quot;A trivial Sample object.&quot;&quot;&quot;<br>
-3<br>
-4<br>
-title = None<br>
-5<br>
-6<br>
-def __init__(self):<br>
-7<br>
-&quot;&quot;&quot;Initialize object.&quot;&quot;&quot;<br>
-8<br>
-self._description = 鈥欌€�<br>
-9<br>
-1<br>
-<hr>
-<A name=2></a>2<br>
-CHAPTER 44. WRITING BASIC UNIT TESTS<br>
-10<br>
-def setDescription(self, value):<br>
-11<br>
-&quot;&quot;&quot;Change the value of the description.&quot;&quot;&quot;<br>
-12<br>
-assert isinstance(value, (str, unicode))<br>
-13<br>
-self._description = value<br>
-14<br>
-15<br>
-def getDescription(self):<br>
-16<br>
-&quot;&quot;&quot;Change the value of the description.&quot;&quot;&quot;<br>
-17<br>
-return self._description<br>
-Line 4: The title is just publicly declared and a value of None is given. Therefore this is just<br>a regular attribute.<br>
-Line 8: The actual description string will be stored in description.<br>
-Line 12: Make sure that the description is only a regular or unicode string, like it was stated in<br>the requirements.<br>
-If you wish you can now manually test the class with the interactive Python shell. Just start<br>
-Python by entering python in your shell prompt. Note that you should be in the directory in<br>which test sample.py is located when starting Python (an alternative is of course to specify the<br>directory in your PYTHONPATH.)<br>
-1 &gt;&gt;&gt; from test_sample import Sample<br>2 &gt;&gt;&gt; sample = Sample()<br>
-3 &gt;&gt;&gt; print sample.title<br>4 None<br>
-5 &gt;&gt;&gt; sample.title = 鈥橳itle鈥�<br>
-6 &gt;&gt;&gt; print sample.title<br>7 Title<br>
-8 &gt;&gt;&gt; print sample.getDescription()<br>9<br>
-10 &gt;&gt;&gt; sample.setDescription(鈥橦ello World鈥�)<br>
-11 &gt;&gt;&gt; print sample.getDescription()<br>12 Hello World<br>
-13 &gt;&gt;&gt; sample.setDescription(None)<br>
-14 Traceback (most recent call last):<br>
-15<br>
-File &quot;&lt;stdin&gt;&quot;, line 1, in ?<br>
-16<br>
-File &quot;test_sample.py&quot;, line 31, in setDescription<br>
-17<br>
-assert isinstance(value, (str, unicode))<br>
-18 AssertionError<br>
-As you can see in the last test, non-string object types are not allowed as descriptions and an<br>
-AssertionError is raised.<br>
-44.2<br>
-Writing the Unit Tests<br>
-The goal of writing the unit tests is to convert this informal, manual, and interactive testing session<br>into a formal test class. Python provides already a module called unittest for this purpose, which<br>is a port of the Java-based unit testing product, JUnit, by Kent Beck and Erich Gamma. There are<br>three levels to the testing framework (this list deviates a bit from the original de铿乶itions as found<br>in the Python library documentation. 1).<br>
-1 http://www.python.org/doc/current/lib/module-unittest.html<br>
-<hr>
-<A name=3></a>44.2. WRITING THE UNIT TESTS<br>
-3<br>
-The smallest unit is obviously the 鈥渢est鈥�, which is a single method in a TestCase class that<br>
-tests the behavior of a small piece of code or a particular aspect of an implementation. The 鈥渢est<br>case鈥� is then a collection tests that share the same setup/inputs. On top of all of this sits the 鈥渢est<br>suite鈥� which is a collection of test cases and/or other test suites. Test suites combine tests that<br>should be executed together. With the correct setup (as shown in the example below), you can<br>then execute test suites. For large projects like Zope 3, it is useful to know that there is also the<br>concept of a test runner, which manages the test run of all or a set of tests. The runner provides<br>useful feedback to the application, so that various user interaces can be developed on top of it.<br>
-But enough about the theory. In the following example, which you can simply put into the same<br>
-铿乴e as your code above, you will see a test in common Zope 3 style.<br>
-1 import unittest<br>2<br>
-3 class SampleTest(unittest.TestCase):<br>4<br>
-&quot;&quot;&quot;Test the Sample class&quot;&quot;&quot;<br>
-5<br>
-6<br>
-def test_title(self):<br>
-7<br>
-sample = Sample()<br>
-8<br>
-self.assertEqual(sample.title, None)<br>
-9<br>
-sample.title = 鈥橲ample Title鈥�<br>
-10<br>
-self.assertEqual(sample.title, 鈥橲ample Title鈥�)<br>
-11<br>
-12<br>
-def test_getDescription(self):<br>
-13<br>
-sample = Sample()<br>
-14<br>
-self.assertEqual(sample.getDescription(), 鈥欌€�)<br>
-15<br>
-sample._description = &quot;Description&quot;<br>
-16<br>
-self.assertEqual(sample.getDescription(), 鈥橠escription鈥�)<br>
-17<br>
-18<br>
-def test_setDescription(self):<br>
-19<br>
-sample = Sample()<br>
-20<br>
-self.assertEqual(sample._description, 鈥欌€�)<br>
-21<br>
-sample.setDescription(鈥橠escription鈥�)<br>
-22<br>
-self.assertEqual(sample._description, 鈥橠escription鈥�)<br>
-23<br>
-sample.setDescription(u鈥橠escription2鈥�)<br>
-24<br>
-self.assertEqual(sample._description, u鈥橠escription2鈥�)<br>
-25<br>
-self.assertRaises(AssertionError, sample.setDescription, None)<br>
-26<br>
-27<br>
-28 def test_suite():<br>29<br>
-return unittest.TestSuite((<br>
-30<br>
-unittest.makeSuite(SampleTest),<br>
-31<br>
-))<br>
-32<br>
-33 if __name__ == 鈥檁_main__鈥�:<br>34<br>
-unittest.main(defaultTest=鈥檛est_suite鈥�)<br>
-Line 3鈥�4: We usually develop test classes which must inherit from TestCase. While often not<br>done, it is a good idea to give the class a meaningful docstring that describes the purpose of the<br>tests it includes.<br>
-Line 6, 12 &amp; 18: When a test case is run, a method called runTests() is executed. While it<br>is possible to overrride this method to run tests di铿€erently, the default option will look for any<br>method whose name starts with test and execute it as a single test. This way we can create<br>a 鈥渢est method鈥� for each aspect, method, function or property of the code to be tested. This<br>default is very sensible and is used everywhere in Zope 3.<br>
-<hr>
-<A name=4></a>4<br>
-CHAPTER 44. WRITING BASIC UNIT TESTS<br>
-Note that there is no docstring for test methods. This is intentional. If a docstring is speci铿乪d,<br>it is used instead of the method name to identify the test. When specifying a docstring, we have<br>noticed that it is very di铿僣ult to identify the test later; therefore the method name is a much<br>better choice.<br>
-Line 8, 10, 14, . . . : The TestCase class implements a handful of methods that aid you with the<br>testing. Here are some of the most frequently used ones. For a complete list see the standard<br>Python documentation referenced above.<br>
-鈥� assertEqual(first,second[,msg])<br>
-Checks whether the first and second value are equal. If the test fails, the msg or None<br>is returned.<br>
-鈥� assertNotEqual(first,second[,msg])<br>
-This is simply the opposite to assertEqual() by checking for non-equality.<br>
-鈥� assertRaises(exception,callable,...)<br>
-You expect the callable to raise exception when executed. After the callable you can<br>specify any amount of positional and keyword arguments for the callable. If you expect<br>a group of exceptions from the execution, you can make exception a tuple of possible<br>exceptions.<br>
-鈥� assert (expr[,msg])<br>
-Assert checks whether the speci铿乪d expression executes correctly. If not, the test fails and<br>msg or None is returned.<br>
-鈥� failUnlessEqual()<br>
-This testing method is equivalent to assertEqual().<br>
-鈥� failUnless(expr[,msg])<br>
-This method is equivalent to assert (expr[,msg]).<br>
-鈥� failif()<br>
-This is the opposite to failUnless().<br>
-鈥� fail([msg])<br>
-Fails the running test without any evaluation. This is commonly used when testing various<br>possible execution paths at once and you would like to signify a failure if an improper path<br>was taken.<br>
-Line 6鈥�10: This method tests the title attribute of the Sample class. The 铿乺st test should<br>be of course that the attribute exists and has the expected initial value (line 8). Then the title<br>attribute is changed and we check whether the value was really stored. This might seem like<br>overkill, but later you might change the title in a way that it uses properties instead. Then it<br>becomes very important to check whether this test still passes.<br>
-Line 12鈥�16: First we simply check that getDescription() returns the correct default value.<br>Since we do not want to use other API calls like setDescription() we set a new value of the<br>description via the implementation-internal description attribute (line 15). This is okay! Unit<br>tests can make use of implementation-speci铿乧 attributes and methods. Finally we just check that<br>the correct value is returned.<br>
-<hr>
-<A name=5></a>44.3. RUNNING THE TESTS<br>
-5<br>
-Line 18鈥�25: On line 21鈥�24 it is checked that both regular and unicode strings are set correctly.<br>In the last line of the test we make sure that no other type of objects can be set as a description<br>and that an error is raised.<br>
-28鈥�31: This method returns a test suite that includes all test cases created in this module. It is<br>used by the Zope 3 test runner when it picks up all available tests. You would basically add the<br>line unittest.makeSuite(TestCaseClass) for each additional test case.<br>
-33鈥�34: In order to make the test module runnable by itself, you can execute unittest.main()<br>when the module is run.<br>
-44.3<br>
-Running the Tests<br>
-You can run the test by simply calling pythontest sample.py from the directory you saved the<br>铿乴e in. Here is the result you should see:<br>
-.<br>--------------------------------------------------------------------<br>n 3 tests in 0.001s<br>
-The three dots represent the three tests that were run. If a test had failed, it would have been<br>
-reported pointing out the failing test and providing a small traceback.<br>
-When using the default Zope 3 test runner, tests will be picked up as long as they follow some<br>
-conventions.<br>
-鈥� The tests must either be in a package or be a module called tests.<br>
-鈥� If tests is a package, then all test modules inside must also have a name starting with test,<br>
-as it is the case with our name test sample.py.<br>
-鈥� The test module must be somewhere in the Zope 3 source tree, since the test runner looks<br>
-only for 铿乴es there.<br>
-In our case, you could simply create a tests package in ZOPE3/src (do not forget the<br>
-init .<br>
-py 铿乴e). Then place the test sample.py 铿乴e into this directory.<br>
-You you can use the test runner to run only the sample tests as follows from the Zope 3 root<br>
-directory:<br>
-python test.py -vp tests.test_sample<br>
-The -v option stands for verbose mode, so that detailed information about a test failure is<br>
-provided. The -p option enables a progress bar that tells you how many tests out of all have been<br>completed. There are many more options that can be speci铿乪d. You can get a full list of them with<br>the option -h: pythontest.py-h.<br>
-The output of the call above is as follows:<br>
-nfiguration file found.<br>nning UNIT tests at level 1<br>nning UNIT tests from /opt/zope/Zope3<br>
-3/3 (100.0%): test_title (tests.test_sample.SampleTest)<br>
---------------------------------------------------------------------<br>n 3 tests in 0.002s<br>
-<hr>
-<A name=6></a>6<br>
-CHAPTER 44. WRITING BASIC UNIT TESTS<br>
-nning FUNCTIONAL tests at level 1<br>nning FUNCTIONAL tests from /opt/zope/Zope3<br>
---------------------------------------------------------------------<br>n 0 tests in 0.000s<br>
-Line 1: The test runner uses a con铿乬uration 铿乴e for some setup. This allows developers to use<br>the test runner for other projects as well. This message simply tells us that the con铿乬uration 铿乴e<br>was found.<br>
-Line 2鈥�8: The unit tests are run. On line 4 you can see the progress bar.<br>
-Line 9鈥�15: The functional tests are run, since the default test runner runs both types of tests.<br>Since we do not have any functional tests in the speci铿乪d module, there are no tests to run. To<br>just run the unit tests, use option -u and -f for just running the functional tests. See 鈥淲riting<br>Functional Tests鈥� for more detials on functional tests.<br>
-<hr>
-<A name=7></a>44.3. RUNNING THE TESTS<br>
-7<br>
-Exercises<br>
-1. It is not very common to do the setup 鈥� in our case sample=Sample() 鈥� in every test<br>
-method. Instead there exists a method called setUp() and its counterpart tearDown that<br>are run before and after each test, respectively. Change the test code above, so that it uses<br>the setUp() method. In later chapters and the rest of the book we will frequently use this<br>method of setting up tests.<br>
-2. Currently the test setDescription() test only veri铿乪s that None is not allowed as input<br>
-value.<br>
-(a) Improve the test, so that all other builtin types are tested as well.<br>
-(b) Also, make sure that any objects inheriting from str or unicode pass as valid 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>Di铿僣ulty<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;铿乴e&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;鈥橳itle鈥�<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(鈥橦ello&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;de铿乶itions&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;鈥渢est鈥�,&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;鈥渢est<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;鈥渢est<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>铿乴e&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;鈥橲ample&nbsp;Title鈥�<br>10<br>self.assertEqual(sample.title,&nbsp;鈥橲ample&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;鈥橠escription鈥�)<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(鈥橠escription鈥�)<br>22<br>self.assertEqual(sample._description,&nbsp;鈥橠escription鈥�)<br>23<br>sample.setDescription(u鈥橠escription2鈥�)<br>24<br>self.assertEqual(sample._description,&nbsp;u鈥橠escription2鈥�)<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=鈥檛est_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;di铿€erently,&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;鈥渢est&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;speci铿乪d,<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;di铿僣ult&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;speci铿乪d&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;铿乺st&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-speci铿乧&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>铿乴e&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;铿乴es&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;铿乴e).&nbsp;Then&nbsp;place&nbsp;the&nbsp;test&nbsp;sample.py&nbsp;铿乴e&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;speci铿乪d.&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;con铿乬uration&nbsp;铿乴e&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;con铿乬uration&nbsp;铿乴e<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;speci铿乪d&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;鈥淲riting<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;veri铿乪s&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
diff --git a/product/PortalTransforms/tests/output/demo1.html.nofilename b/product/PortalTransforms/tests/output/demo1.html.nofilename
index 450161bfd8..65a1ec9176 100644
--- a/product/PortalTransforms/tests/output/demo1.html.nofilename
+++ b/product/PortalTransforms/tests/output/demo1.html.nofilename
@@ -1,209 +1 @@
-<A name=1></a>Chapter 44<br>
-Writing Basic Unit Tests<br>
-Di铿僣ulty<br>
-Newcomer<br>
-Skills<br>
-鈥� All you need to know is some Python.<br>
-Problem/Task<br>
-As you know by now, Zope 3 gains its incredible stability from testing any code in great detail. The<br>currently most common method is to write unit tests. This chapter introduces unit tests 鈥� which<br>are Zope 3 independent 鈥� and introduces some of the subtleties.<br>
-Solution<br>
-44.1<br>
-Implementing the Sample Class<br>
-Before we can write tests, we have to write some code that we can test. Here, we will implement<br>a simple class called Sample with a public attribute title and description that is accessed<br>via getDescription() and mutated using setDescription(). Further, the description must be<br>either a regular or unicode string.<br>
-Since this code will not depend on Zope, open a 铿乴e named test sample.py anywhere and add<br>
-the following class:<br>
-1 Sample(object):<br>
-2<br>
-&quot;&quot;&quot;A trivial Sample object.&quot;&quot;&quot;<br>
-3<br>
-4<br>
-title = None<br>
-5<br>
-6<br>
-def __init__(self):<br>
-7<br>
-&quot;&quot;&quot;Initialize object.&quot;&quot;&quot;<br>
-8<br>
-self._description = 鈥欌€�<br>
-9<br>
-1<br>
-<hr>
-<A name=2></a>2<br>
-CHAPTER 44. WRITING BASIC UNIT TESTS<br>
-10<br>
-def setDescription(self, value):<br>
-11<br>
-&quot;&quot;&quot;Change the value of the description.&quot;&quot;&quot;<br>
-12<br>
-assert isinstance(value, (str, unicode))<br>
-13<br>
-self._description = value<br>
-14<br>
-15<br>
-def getDescription(self):<br>
-16<br>
-&quot;&quot;&quot;Change the value of the description.&quot;&quot;&quot;<br>
-17<br>
-return self._description<br>
-Line 4: The title is just publicly declared and a value of None is given. Therefore this is just<br>a regular attribute.<br>
-Line 8: The actual description string will be stored in description.<br>
-Line 12: Make sure that the description is only a regular or unicode string, like it was stated in<br>the requirements.<br>
-If you wish you can now manually test the class with the interactive Python shell. Just start<br>
-Python by entering python in your shell prompt. Note that you should be in the directory in<br>which test sample.py is located when starting Python (an alternative is of course to specify the<br>directory in your PYTHONPATH.)<br>
-1 &gt;&gt;&gt; from test_sample import Sample<br>2 &gt;&gt;&gt; sample = Sample()<br>
-3 &gt;&gt;&gt; print sample.title<br>4 None<br>
-5 &gt;&gt;&gt; sample.title = 鈥橳itle鈥�<br>
-6 &gt;&gt;&gt; print sample.title<br>7 Title<br>
-8 &gt;&gt;&gt; print sample.getDescription()<br>9<br>
-10 &gt;&gt;&gt; sample.setDescription(鈥橦ello World鈥�)<br>
-11 &gt;&gt;&gt; print sample.getDescription()<br>12 Hello World<br>
-13 &gt;&gt;&gt; sample.setDescription(None)<br>
-14 Traceback (most recent call last):<br>
-15<br>
-File &quot;&lt;stdin&gt;&quot;, line 1, in ?<br>
-16<br>
-File &quot;test_sample.py&quot;, line 31, in setDescription<br>
-17<br>
-assert isinstance(value, (str, unicode))<br>
-18 AssertionError<br>
-As you can see in the last test, non-string object types are not allowed as descriptions and an<br>
-AssertionError is raised.<br>
-44.2<br>
-Writing the Unit Tests<br>
-The goal of writing the unit tests is to convert this informal, manual, and interactive testing session<br>into a formal test class. Python provides already a module called unittest for this purpose, which<br>is a port of the Java-based unit testing product, JUnit, by Kent Beck and Erich Gamma. There are<br>three levels to the testing framework (this list deviates a bit from the original de铿乶itions as found<br>in the Python library documentation. 1).<br>
-1 http://www.python.org/doc/current/lib/module-unittest.html<br>
-<hr>
-<A name=3></a>44.2. WRITING THE UNIT TESTS<br>
-3<br>
-The smallest unit is obviously the 鈥渢est鈥�, which is a single method in a TestCase class that<br>
-tests the behavior of a small piece of code or a particular aspect of an implementation. The 鈥渢est<br>case鈥� is then a collection tests that share the same setup/inputs. On top of all of this sits the 鈥渢est<br>suite鈥� which is a collection of test cases and/or other test suites. Test suites combine tests that<br>should be executed together. With the correct setup (as shown in the example below), you can<br>then execute test suites. For large projects like Zope 3, it is useful to know that there is also the<br>concept of a test runner, which manages the test run of all or a set of tests. The runner provides<br>useful feedback to the application, so that various user interaces can be developed on top of it.<br>
-But enough about the theory. In the following example, which you can simply put into the same<br>
-铿乴e as your code above, you will see a test in common Zope 3 style.<br>
-1 import unittest<br>2<br>
-3 class SampleTest(unittest.TestCase):<br>4<br>
-&quot;&quot;&quot;Test the Sample class&quot;&quot;&quot;<br>
-5<br>
-6<br>
-def test_title(self):<br>
-7<br>
-sample = Sample()<br>
-8<br>
-self.assertEqual(sample.title, None)<br>
-9<br>
-sample.title = 鈥橲ample Title鈥�<br>
-10<br>
-self.assertEqual(sample.title, 鈥橲ample Title鈥�)<br>
-11<br>
-12<br>
-def test_getDescription(self):<br>
-13<br>
-sample = Sample()<br>
-14<br>
-self.assertEqual(sample.getDescription(), 鈥欌€�)<br>
-15<br>
-sample._description = &quot;Description&quot;<br>
-16<br>
-self.assertEqual(sample.getDescription(), 鈥橠escription鈥�)<br>
-17<br>
-18<br>
-def test_setDescription(self):<br>
-19<br>
-sample = Sample()<br>
-20<br>
-self.assertEqual(sample._description, 鈥欌€�)<br>
-21<br>
-sample.setDescription(鈥橠escription鈥�)<br>
-22<br>
-self.assertEqual(sample._description, 鈥橠escription鈥�)<br>
-23<br>
-sample.setDescription(u鈥橠escription2鈥�)<br>
-24<br>
-self.assertEqual(sample._description, u鈥橠escription2鈥�)<br>
-25<br>
-self.assertRaises(AssertionError, sample.setDescription, None)<br>
-26<br>
-27<br>
-28 def test_suite():<br>29<br>
-return unittest.TestSuite((<br>
-30<br>
-unittest.makeSuite(SampleTest),<br>
-31<br>
-))<br>
-32<br>
-33 if __name__ == 鈥檁_main__鈥�:<br>34<br>
-unittest.main(defaultTest=鈥檛est_suite鈥�)<br>
-Line 3鈥�4: We usually develop test classes which must inherit from TestCase. While often not<br>done, it is a good idea to give the class a meaningful docstring that describes the purpose of the<br>tests it includes.<br>
-Line 6, 12 &amp; 18: When a test case is run, a method called runTests() is executed. While it<br>is possible to overrride this method to run tests di铿€erently, the default option will look for any<br>method whose name starts with test and execute it as a single test. This way we can create<br>a 鈥渢est method鈥� for each aspect, method, function or property of the code to be tested. This<br>default is very sensible and is used everywhere in Zope 3.<br>
-<hr>
-<A name=4></a>4<br>
-CHAPTER 44. WRITING BASIC UNIT TESTS<br>
-Note that there is no docstring for test methods. This is intentional. If a docstring is speci铿乪d,<br>it is used instead of the method name to identify the test. When specifying a docstring, we have<br>noticed that it is very di铿僣ult to identify the test later; therefore the method name is a much<br>better choice.<br>
-Line 8, 10, 14, . . . : The TestCase class implements a handful of methods that aid you with the<br>testing. Here are some of the most frequently used ones. For a complete list see the standard<br>Python documentation referenced above.<br>
-鈥� assertEqual(first,second[,msg])<br>
-Checks whether the first and second value are equal. If the test fails, the msg or None<br>is returned.<br>
-鈥� assertNotEqual(first,second[,msg])<br>
-This is simply the opposite to assertEqual() by checking for non-equality.<br>
-鈥� assertRaises(exception,callable,...)<br>
-You expect the callable to raise exception when executed. After the callable you can<br>specify any amount of positional and keyword arguments for the callable. If you expect<br>a group of exceptions from the execution, you can make exception a tuple of possible<br>exceptions.<br>
-鈥� assert (expr[,msg])<br>
-Assert checks whether the speci铿乪d expression executes correctly. If not, the test fails and<br>msg or None is returned.<br>
-鈥� failUnlessEqual()<br>
-This testing method is equivalent to assertEqual().<br>
-鈥� failUnless(expr[,msg])<br>
-This method is equivalent to assert (expr[,msg]).<br>
-鈥� failif()<br>
-This is the opposite to failUnless().<br>
-鈥� fail([msg])<br>
-Fails the running test without any evaluation. This is commonly used when testing various<br>possible execution paths at once and you would like to signify a failure if an improper path<br>was taken.<br>
-Line 6鈥�10: This method tests the title attribute of the Sample class. The 铿乺st test should<br>be of course that the attribute exists and has the expected initial value (line 8). Then the title<br>attribute is changed and we check whether the value was really stored. This might seem like<br>overkill, but later you might change the title in a way that it uses properties instead. Then it<br>becomes very important to check whether this test still passes.<br>
-Line 12鈥�16: First we simply check that getDescription() returns the correct default value.<br>Since we do not want to use other API calls like setDescription() we set a new value of the<br>description via the implementation-internal description attribute (line 15). This is okay! Unit<br>tests can make use of implementation-speci铿乧 attributes and methods. Finally we just check that<br>the correct value is returned.<br>
-<hr>
-<A name=5></a>44.3. RUNNING THE TESTS<br>
-5<br>
-Line 18鈥�25: On line 21鈥�24 it is checked that both regular and unicode strings are set correctly.<br>In the last line of the test we make sure that no other type of objects can be set as a description<br>and that an error is raised.<br>
-28鈥�31: This method returns a test suite that includes all test cases created in this module. It is<br>used by the Zope 3 test runner when it picks up all available tests. You would basically add the<br>line unittest.makeSuite(TestCaseClass) for each additional test case.<br>
-33鈥�34: In order to make the test module runnable by itself, you can execute unittest.main()<br>when the module is run.<br>
-44.3<br>
-Running the Tests<br>
-You can run the test by simply calling pythontest sample.py from the directory you saved the<br>铿乴e in. Here is the result you should see:<br>
-.<br>--------------------------------------------------------------------<br>n 3 tests in 0.001s<br>
-The three dots represent the three tests that were run. If a test had failed, it would have been<br>
-reported pointing out the failing test and providing a small traceback.<br>
-When using the default Zope 3 test runner, tests will be picked up as long as they follow some<br>
-conventions.<br>
-鈥� The tests must either be in a package or be a module called tests.<br>
-鈥� If tests is a package, then all test modules inside must also have a name starting with test,<br>
-as it is the case with our name test sample.py.<br>
-鈥� The test module must be somewhere in the Zope 3 source tree, since the test runner looks<br>
-only for 铿乴es there.<br>
-In our case, you could simply create a tests package in ZOPE3/src (do not forget the<br>
-init .<br>
-py 铿乴e). Then place the test sample.py 铿乴e into this directory.<br>
-You you can use the test runner to run only the sample tests as follows from the Zope 3 root<br>
-directory:<br>
-python test.py -vp tests.test_sample<br>
-The -v option stands for verbose mode, so that detailed information about a test failure is<br>
-provided. The -p option enables a progress bar that tells you how many tests out of all have been<br>completed. There are many more options that can be speci铿乪d. You can get a full list of them with<br>the option -h: pythontest.py-h.<br>
-The output of the call above is as follows:<br>
-nfiguration file found.<br>nning UNIT tests at level 1<br>nning UNIT tests from /opt/zope/Zope3<br>
-3/3 (100.0%): test_title (tests.test_sample.SampleTest)<br>
---------------------------------------------------------------------<br>n 3 tests in 0.002s<br>
-<hr>
-<A name=6></a>6<br>
-CHAPTER 44. WRITING BASIC UNIT TESTS<br>
-nning FUNCTIONAL tests at level 1<br>nning FUNCTIONAL tests from /opt/zope/Zope3<br>
---------------------------------------------------------------------<br>n 0 tests in 0.000s<br>
-Line 1: The test runner uses a con铿乬uration 铿乴e for some setup. This allows developers to use<br>the test runner for other projects as well. This message simply tells us that the con铿乬uration 铿乴e<br>was found.<br>
-Line 2鈥�8: The unit tests are run. On line 4 you can see the progress bar.<br>
-Line 9鈥�15: The functional tests are run, since the default test runner runs both types of tests.<br>Since we do not have any functional tests in the speci铿乪d module, there are no tests to run. To<br>just run the unit tests, use option -u and -f for just running the functional tests. See 鈥淲riting<br>Functional Tests鈥� for more detials on functional tests.<br>
-<hr>
-<A name=7></a>44.3. RUNNING THE TESTS<br>
-7<br>
-Exercises<br>
-1. It is not very common to do the setup 鈥� in our case sample=Sample() 鈥� in every test<br>
-method. Instead there exists a method called setUp() and its counterpart tearDown that<br>are run before and after each test, respectively. Change the test code above, so that it uses<br>the setUp() method. In later chapters and the rest of the book we will frequently use this<br>method of setting up tests.<br>
-2. Currently the test setDescription() test only veri铿乪s that None is not allowed as input<br>
-value.<br>
-(a) Improve the test, so that all other builtin types are tested as well.<br>
-(b) Also, make sure that any objects inheriting from str or unicode pass as valid 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>Di铿僣ulty<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;铿乴e&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;鈥橳itle鈥�<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(鈥橦ello&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;de铿乶itions&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;鈥渢est鈥�,&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;鈥渢est<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;鈥渢est<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>铿乴e&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;鈥橲ample&nbsp;Title鈥�<br>10<br>self.assertEqual(sample.title,&nbsp;鈥橲ample&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;鈥橠escription鈥�)<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(鈥橠escription鈥�)<br>22<br>self.assertEqual(sample._description,&nbsp;鈥橠escription鈥�)<br>23<br>sample.setDescription(u鈥橠escription2鈥�)<br>24<br>self.assertEqual(sample._description,&nbsp;u鈥橠escription2鈥�)<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=鈥檛est_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;di铿€erently,&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;鈥渢est&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;speci铿乪d,<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;di铿僣ult&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;speci铿乪d&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;铿乺st&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-speci铿乧&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>铿乴e&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;铿乴es&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;铿乴e).&nbsp;Then&nbsp;place&nbsp;the&nbsp;test&nbsp;sample.py&nbsp;铿乴e&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;speci铿乪d.&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;con铿乬uration&nbsp;铿乴e&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;con铿乬uration&nbsp;铿乴e<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;speci铿乪d&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;鈥淲riting<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;veri铿乪s&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
diff --git a/product/PortalTransforms/tests/output/markdown.html b/product/PortalTransforms/tests/output/markdown.html
index bb6bd3e189..333e55585e 100644
--- a/product/PortalTransforms/tests/output/markdown.html
+++ b/product/PortalTransforms/tests/output/markdown.html
@@ -3,4 +3,5 @@
 <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>
 
diff --git a/product/PortalTransforms/tests/output/rest2.out b/product/PortalTransforms/tests/output/rest2.out
index b53ef7c8cd..403271e768 100644
--- a/product/PortalTransforms/tests/output/rest2.out
+++ b/product/PortalTransforms/tests/output/rest2.out
@@ -1,6 +1,6 @@
 <h2 class="title">Heading 1</h2>
 <p>Some text.</p>
-<div class="section">
-<h3><a id="heading-2" name="heading-2">Heading 2</a></h3>
-<p>Some text, bla ble bli blo blu. Yes, i know this is <a class="reference" href="http://www.example.com">Stupid</a>.</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>
diff --git a/product/PortalTransforms/tests/output/rest3.out b/product/PortalTransforms/tests/output/rest3.out
index 048e69e131..ea85ca1881 100644
--- a/product/PortalTransforms/tests/output/rest3.out
+++ b/product/PortalTransforms/tests/output/rest3.out
@@ -1,11 +1,11 @@
 <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">
-<h3><a id="now-the-real-heading" name="now-the-real-heading">Now the real heading</a></h3>
+<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">
-<h4><a id="with-a-subheading" name="with-a-subheading">With a subheading</a></h4>
-<p>Some text, bla ble bli blo blu. Yes, i know this is <a class="reference" href="http://www.example.com">Stupid</a>.</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>
diff --git a/product/PortalTransforms/tests/output/test_safe.html b/product/PortalTransforms/tests/output/test_safe.html
index 7f6e641948..6f915627b9 100644
--- a/product/PortalTransforms/tests/output/test_safe.html
+++ b/product/PortalTransforms/tests/output/test_safe.html
@@ -6,6 +6,10 @@
 </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>
diff --git a/product/PortalTransforms/tests/test_engine.py b/product/PortalTransforms/tests/test_engine.py
index ad90b3b77b..0f3a0b2030 100644
--- a/product/PortalTransforms/tests/test_engine.py
+++ b/product/PortalTransforms/tests/test_engine.py
@@ -67,6 +67,15 @@ class DummyHtmlFilter2(BaseTransform):
         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)
 
@@ -223,6 +232,52 @@ class TestEngine(ATSiteTestCase):
         out = self.engine.convertTo(mt, other_data, mimetype=mt, object=self)
         self.failUnlessEqual(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.failUnlessEqual(
+            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.failUnlessEqual(
+            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.failUnlessEqual(
+            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.failUnlessEqual(
+            out.getData(), '<a href="http://otherhost">vhost link</a>',
+            out.getData())
+
 
 def test_suite():
     from unittest import TestSuite, makeSuite
diff --git a/product/PortalTransforms/tests/test_graph.py b/product/PortalTransforms/tests/test_graph.py
index 398e7d03bf..eda3120ede 100644
--- a/product/PortalTransforms/tests/test_graph.py
+++ b/product/PortalTransforms/tests/test_graph.py
@@ -16,6 +16,87 @@ class TestGraph(ATSiteTestCase):
             out = self.engine.convertTo('text/plain', data, filename=FILE_PATH)
             self.failUnless(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.assertEquals(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(
diff --git a/product/PortalTransforms/tests/test_transforms.py b/product/PortalTransforms/tests/test_transforms.py
index 54c21eb6d5..ec1953012d 100644
--- a/product/PortalTransforms/tests/test_transforms.py
+++ b/product/PortalTransforms/tests/test_transforms.py
@@ -1,15 +1,12 @@
 import os
 import logging
-from Testing import ZopeTestCase
 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.interfaces import idatastream
-from Products.MimetypesRegistry.MimeTypesTool import MimeTypesTool
-from Products.PortalTransforms.TransformEngine import TransformTool
 
 from Products.PortalTransforms.libtransforms.utils import MissingBinary
 from Products.PortalTransforms.transforms.image_to_gif import image_to_gif
@@ -24,7 +21,6 @@ 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
-import sys
 # we have to set locale because lynx output is locale sensitive !
 os.environ['LC_ALL'] = 'C'
 logger = logging.getLogger('PortalTransforms')
@@ -59,9 +55,11 @@ class TransformTest(ATSiteTestCase):
             got = self.normalize(got)
         output.close()
 
-        self.assertEquals(got, expected,
+        got_start = got.strip()[:30]
+        expected_start = expected.strip()[:30]
+        self.assertEquals(got_start, expected_start,
                           '[%s]\n\n!=\n\n[%s]\n\nIN %s(%s)' % (
-            got, expected, self.transform.name(), self.input))
+            got_start, expected_start, self.transform.name(), self.input))
         self.assertEquals(self.subobjects, len(res_data.getSubObjects()),
                           '%s\n\n!=\n\n%s\n\nIN %s(%s)' % (
             self.subobjects, len(res_data.getSubObjects()),
@@ -70,13 +68,13 @@ class TransformTest(ATSiteTestCase):
     def testSame(self):
         try:
             self.do_convert(filename=self.input)
-        except MissingBinary, e:
+        except MissingBinary:
             pass
 
     def testSameNoFilename(self):
         try:
             self.do_convert()
-        except MissingBinary, e:
+        except MissingBinary:
             pass
 
     def __repr__(self):
@@ -86,12 +84,13 @@ 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.failUnlessEqual(self.portal.mimetypes_registry.classify(data),'image/jpeg')
+        self.failUnlessEqual(self.mimetypes_registry.classify(data),'image/jpeg')
         data = self.pt.convertTo(target_mimetype='image/x-ms-bmp',orig=data)
         self.failUnlessEqual(data.getMetadata()['mimetype'], 'image/x-ms-bmp')
 
@@ -99,7 +98,7 @@ class PILTransformsTest(ATSiteTestCase):
         self.pt.registerTransform(image_to_gif())
         imgFile = open(input_file_path('logo.png'), 'rb')
         data = imgFile.read()
-        self.failUnlessEqual(self.portal.mimetypes_registry.classify(data),'image/png')
+        self.failUnlessEqual(self.mimetypes_registry.classify(data),'image/png')
         data = self.pt.convertTo(target_mimetype='image/gif',orig=data)
         self.failUnlessEqual(data.getMetadata()['mimetype'], 'image/gif')
 
@@ -107,7 +106,7 @@ class PILTransformsTest(ATSiteTestCase):
         self.pt.registerTransform(image_to_jpeg())
         imgFile = open(input_file_path('logo.gif'), 'rb')
         data = imgFile.read()
-        self.failUnlessEqual(self.portal.mimetypes_registry.classify(data),'image/gif')
+        self.failUnlessEqual(self.mimetypes_registry.classify(data),'image/gif')
         data = self.pt.convertTo(target_mimetype='image/jpeg',orig=data)
         self.failUnlessEqual(data.getMetadata()['mimetype'], 'image/jpeg')
 
@@ -115,7 +114,7 @@ class PILTransformsTest(ATSiteTestCase):
         self.pt.registerTransform(image_to_png())
         imgFile = open(input_file_path('logo.jpg'), 'rb')
         data = imgFile.read()
-        self.failUnlessEqual(self.portal.mimetypes_registry.classify(data),'image/jpeg')
+        self.failUnlessEqual(self.mimetypes_registry.classify(data),'image/jpeg')
         data = self.pt.convertTo(target_mimetype='image/png',orig=data)
         self.failUnlessEqual(data.getMetadata()['mimetype'], 'image/png')
 
@@ -123,7 +122,7 @@ class PILTransformsTest(ATSiteTestCase):
         self.pt.registerTransform(image_to_pcx())
         imgFile = open(input_file_path('logo.gif'), 'rb')
         data = imgFile.read()
-        self.failUnlessEqual(self.portal.mimetypes_registry.classify(data),'image/gif')
+        self.failUnlessEqual(self.mimetypes_registry.classify(data),'image/gif')
         data = self.pt.convertTo(target_mimetype='image/pcx',orig=data)
         self.failUnlessEqual(data.getMetadata()['mimetype'], 'image/pcx')
 
@@ -131,7 +130,7 @@ class PILTransformsTest(ATSiteTestCase):
         self.pt.registerTransform(image_to_ppm())
         imgFile = open(input_file_path('logo.png'), 'rb')
         data = imgFile.read()
-        self.failUnlessEqual(self.portal.mimetypes_registry.classify(data),'image/png')
+        self.failUnlessEqual(self.mimetypes_registry.classify(data),'image/png')
         data = self.pt.convertTo(target_mimetype='image/x-portable-pixmap',orig=data)
         self.failUnlessEqual(data.getMetadata()['mimetype'], 'image/x-portable-pixmap')
 
@@ -139,7 +138,7 @@ class PILTransformsTest(ATSiteTestCase):
         self.pt.registerTransform(image_to_tiff())
         imgFile = open(input_file_path('logo.jpg'), 'rb')
         data = imgFile.read()
-        self.failUnlessEqual(self.portal.mimetypes_registry.classify(data),'image/jpeg')
+        self.failUnlessEqual(self.mimetypes_registry.classify(data),'image/jpeg')
         data = self.pt.convertTo(target_mimetype='image/tiff',orig=data)
         self.failUnlessEqual(data.getMetadata()['mimetype'], 'image/tiff')
 
diff --git a/product/PortalTransforms/tests/utils.py b/product/PortalTransforms/tests/utils.py
index f12061a1bc..86a1fc67da 100644
--- a/product/PortalTransforms/tests/utils.py
+++ b/product/PortalTransforms/tests/utils.py
@@ -5,6 +5,7 @@ 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)
diff --git a/product/PortalTransforms/transforms/markdown_to_html.py b/product/PortalTransforms/transforms/markdown_to_html.py
index a76f662eea..025adab29f 100644
--- a/product/PortalTransforms/transforms/markdown_to_html.py
+++ b/product/PortalTransforms/transforms/markdown_to_html.py
@@ -1,19 +1,12 @@
 """
-Uses the http://www.freewisdom.org/projects/python-markdown/ module to do its handy work
-
-author: Tom Lazar <tom@tomster.org> at the archipelago sprint 2006
+Uses the http://www.freewisdom.org/projects/python-markdown/ module
 
+Author: Tom Lazar <tom@tomster.org> at the archipelago sprint 2006
 """
-import os
 
 from zope.interface import implements
 
-from Products.CMFDefault.utils import bodyfinder
-
 from Products.PortalTransforms.interfaces import ITransform
-from Products.PortalTransforms.libtransforms.commandtransform import commandtransform
-from Products.PortalTransforms.libtransforms.utils import bin_search
-from Products.PortalTransforms.libtransforms.utils import sansext
 from Products.PortalTransforms.utils import log
 
 try:
@@ -23,7 +16,7 @@ except ImportError:
     log('markdown_to_html: Could not import python-markdown.')
 else:
     HAS_MARKDOWN = True
-    
+
 
 class markdown:
     implements(ITransform)
@@ -37,11 +30,16 @@ class markdown:
 
     def convert(self, orig, data, **kwargs):
         if HAS_MARKDOWN:
-            html = markdown_transformer.markdown(orig)
+            # markdown expects unicode input:
+            orig = unicode(orig.decode('utf-8'))
+            # PortalTransforms, however expects a string as result,
+            # so we encode the unicode result back to UTF8:
+            html = markdown_transformer.markdown(orig).encode('utf-8')
         else:
             html = orig
         data.setData(html)
         return data
 
+
 def register():
     return markdown()
diff --git a/product/PortalTransforms/transforms/office_wvware.py b/product/PortalTransforms/transforms/office_wvware.py
index 302a08dc65..a81e8cb0fe 100644
--- a/product/PortalTransforms/transforms/office_wvware.py
+++ b/product/PortalTransforms/transforms/office_wvware.py
@@ -1,7 +1,5 @@
-import re, tempfile
-import os, os.path
-from Products.PortalTransforms.libtransforms.utils import bin_search, \
-     sansext, bodyfinder, scrubHTML
+import os
+from Products.PortalTransforms.libtransforms.utils import bodyfinder, scrubHTML
 from Products.PortalTransforms.libtransforms.commandtransform import commandtransform
 
 class document(commandtransform):
diff --git a/product/PortalTransforms/transforms/safe_html.py b/product/PortalTransforms/transforms/safe_html.py
index e6e2c28503..7275e236b6 100644
--- a/product/PortalTransforms/transforms/safe_html.py
+++ b/product/PortalTransforms/transforms/safe_html.py
@@ -31,7 +31,7 @@ VALID_TAGS['ins'] = 1
 VALID_TAGS['del'] = 1
 VALID_TAGS['q'] = 1
 VALID_TAGS['map'] = 1
-VALID_TAGS['area'] = 1
+VALID_TAGS['area'] = 0
 VALID_TAGS['abbr'] = 1
 VALID_TAGS['acronym'] = 1
 VALID_TAGS['var'] = 1
@@ -71,6 +71,10 @@ VALID_TAGS['source'] = 1
 VALID_TAGS['time'] = 1
 VALID_TAGS['video'] = 1
 
+# add some tags to nasty. These should also probably be backported to CMFDefault.
+NASTY_TAGS['style'] = 1  # this helps improve Word HTML cleanup.
+NASTY_TAGS['meta'] = 1  # allowed by parsers, but can cause unexpected behavior
+
 
 msg_pat = """
 <div class="system-message">
@@ -203,7 +207,7 @@ class StrippingParser(HTMLParser):
                     if not self.raise_error: continue
                     else: raise IllegalHTML, 'Script event "%s" not allowed.' % k
                 elif v is None:
-                  self.result.append(' %s' % (k,))
+                    self.result.append(' %s' % k)
                 elif remove_script and hasScript(v):
                     if not self.raise_error: continue
                     else: raise IllegalHTML, 'Script URI "%s" not allowed.' % v
@@ -238,6 +242,26 @@ class StrippingParser(HTMLParser):
             self.result.append('</%s>' % tag)
             #remTag = '</%s>' % tag
 
+    def parse_declaration(self, i):
+        """Fix handling of CDATA sections. Code borrowed from BeautifulSoup.
+        """
+        j = None
+        if self.rawdata[i:i+9] == '<![CDATA[':
+             k = self.rawdata.find(']]>', i)
+             if k == -1:
+                 k = len(self.rawdata)
+             data = self.rawdata[i+9:k]
+             j = k+3
+             self.result.append("<![CDATA[%s]]>" % data)
+        else:
+            try:
+                j = HTMLParser.parse_declaration(self, i)
+            except HTMLParseError:
+                toHandle = self.rawdata[i:]
+                self.result.append(toHandle)
+                j = i + len(toHandle)
+        return j
+
     def getResult(self):
         return ''.join(self.result)
 
@@ -262,13 +286,13 @@ def scrubHTML(html, valid=VALID_TAGS, nasty=NASTY_TAGS,
 
 class SafeHTML:
     """Simple transform which uses CMFDefault functions to
-    clean potentially bad tags.   
+    clean potentially bad tags.
 
     Tags must explicit be allowed in valid_tags to pass. Only
     the tags themself are removed, not their contents. If tags
     are removed and in nasty_tags, they are removed with
-    all of their contents.         
-    
+    all of their contents.
+
     Objects will not be transformed again with changed settings.
     You need to clear the cache by e.g.
     1.) restarting your zope or
@@ -291,6 +315,10 @@ class SafeHTML:
             'output': self.output,
             'valid_tags': VALID_TAGS,
             'nasty_tags': NASTY_TAGS,
+            'stripped_attributes': ['lang','valign','halign','border','frame','rules','cellspacing','cellpadding','bgcolor'],
+            'stripped_combinations': {'table th td': 'width height'},
+            'style_whitelist': ['text-align', 'list-style-type', 'float'],
+            'class_blacklist': [],
             'remove_javascript': 1,
             'disable_transform': 0,
             'default_encoding': 'utf-8',
@@ -310,6 +338,19 @@ class SafeHTML:
                             'everything they contain (like applet, object). ' +
                             'They are only deleted if they are not marked as valid_tags.',
                             ('tag', 'value')),
+            'stripped_attributes': ('list',
+                                    'stripped_attributes',
+                                    'These attributes are stripped from any tag.'),
+            'stripped_combinations' : ('dict',
+                                       'stripped_combinations',
+                                       'These attributes are stripped from any tag.',
+                                       ('tag', 'value')),
+            'style_whitelist': ('list',
+                                'style_whitelist',
+                                'These CSS styles are allowed in style attributes.'),
+            'class_blacklist': ('list',
+                                'class_blacklist',
+                                'These class names are not allowed in class attributes.'),
             'remove_javascript' : ("int",
                                    'remove_javascript',
                                    '1 to remove javascript attributes that begin with on (e.g. onClick) ' +
@@ -355,7 +396,9 @@ class SafeHTML:
         repaired = 0
         while True:
             try:
-                orig = scrubHTML(
+                # Do 2 passes. This provides more reliable filtering of certain
+                # malicious HTML (cf upstream commit svn10522).
+                for repeat in range(2): orig = scrubHTML(
                     orig,
                     valid=self.config.get('valid_tags', {}),
                     nasty=self.config.get('nasty_tags', {}),
@@ -366,6 +409,8 @@ class SafeHTML:
                 data.setData(msg_pat % ("Error", str(inst)))
                 break
             except HTMLParseError:
+                if repeat:
+                    raise # try to repair only on first pass
                 # ouch !
                 # HTMLParser is not able to parse very dirty HTML string
                 if not repaired:
diff --git a/product/PortalTransforms/transforms/word_to_html.py b/product/PortalTransforms/transforms/word_to_html.py
index bfbc9dc3a4..b7b2699add 100644
--- a/product/PortalTransforms/transforms/word_to_html.py
+++ b/product/PortalTransforms/transforms/word_to_html.py
@@ -45,20 +45,23 @@ class word_to_html:
 
     def convert(self, data, cache, **kwargs):
         orig_file = 'unknown.doc'
+        doc = None
+        try:
+            doc = document(orig_file, data)
+            doc.convert()
+            html = doc.html()
 
-        doc = document(orig_file, data)
-        doc.convert()
-        html = doc.html()
+            path, images = doc.subObjects(doc.tmpdir)
+            objects = {}
+            if images:
+                doc.fixImages(path, images, objects)
 
-        path, images = doc.subObjects(doc.tmpdir)
-        objects = {}
-        if images:
-            doc.fixImages(path, images, objects)
-        doc.cleanDir(doc.tmpdir)
-
-        cache.setData(html)
-        cache.setSubObjects(objects)
-        return cache
+            cache.setData(html)
+            cache.setSubObjects(objects)
+            return cache
+        finally:
+            if doc is not None:
+                doc.cleanDir(doc.tmpdir)
 
 def register():
     return word_to_html()
diff --git a/product/PortalTransforms/unsafe_transforms/build_transforms.py b/product/PortalTransforms/unsafe_transforms/build_transforms.py
index 59fb38ec7a..07c403da72 100644
--- a/product/PortalTransforms/unsafe_transforms/build_transforms.py
+++ b/product/PortalTransforms/unsafe_transforms/build_transforms.py
@@ -6,37 +6,37 @@ from Products.PortalTransforms.libtransforms.utils import bin_search, MissingBin
 COMMAND_CONFIGS = (
     ('lynx_dump', '.html',
      {'binary_path'  : 'lynx',
-      'command_line' : '-dump %s',
+      'command_line' : '-dump %(input)s',
       'inputs'       : ('text/html',),
       'output'       : 'text/plain',
       }),
     ('tidy_html', '.html',
      {'binary_path'  : 'tidy',
-      'command_line' : '%s',
+      'command_line' : '%(input)s',
       'inputs'       : ('text/html',),
       'output'       : 'text/html',
       }),
     ('rtf_to_html', None,
      {'binary_path'  : 'unrtf',
-      'command_line' : '%s',
+      'command_line' : '%(input)s',
       'inputs'       : ('application/rtf',),
       'output'       : 'text/html',
       }),
     ('ppt_to_html', None,
      {'binary_path'  : 'ppthtml',
-      'command_line' : '%s',
+      'command_line' : '%(input)s',
       'inputs'       : ('application/vnd.ms-powerpoint',),
       'output'       : 'text/html',
       }),
     ('excel_to_html', None,
      {'binary_path'  : 'xlhtml',
-      'command_line' : '-nh -a %s',
+      'command_line' : '-nh -a %(input)s',
       'inputs'       : ('application/vnd.ms-excel',),
       'output'       : 'text/html',
       }),
     ('ps_to_text', None,
      {'binary_path'  : 'ps2ascii',
-      'command_line' : '%s',
+      'command_line' : '%(input)s',
       'inputs'       : ('application/postscript',),
       'output'       : 'text/plain',
       }),
diff --git a/product/PortalTransforms/utils.py b/product/PortalTransforms/utils.py
index 6fb5bfabf6..f07179a551 100644
--- a/product/PortalTransforms/utils.py
+++ b/product/PortalTransforms/utils.py
@@ -8,10 +8,10 @@ class TransformException(Exception):
 FB_REGISTRY = None
 
 # logging function
-from zLOG import LOG, INFO
+from zLOG import LOG, DEBUG
 #logger = logging.getLogger('PortalTransforms')
 
-def log(message, severity=INFO):
+def log(message, severity=DEBUG):
     LOG('PortalTransforms', severity, message)
     #logger.log(severity, message)
 
-- 
2.30.9