[Testbot] Plone 5.0 - Python 2.7 - Build # 3927 - Fixed! - 0 failure(s)

jenkins at plone.org jenkins at plone.org
Wed Jan 7 14:03:02 UTC 2015


-------------------------------------------------------------------------------
Plone 5.0 - Python 2.7 - Build # 3927 - Fixed!
-------------------------------------------------------------------------------

http://jenkins.plone.org/job/plone-5.0-python-2.7/3927/


-------------------------------------------------------------------------------
CHANGES
-------------------------------------------------------------------------------

Repository: plone.app.upgrade
Branch: refs/heads/master
Date: 2015-01-07T14:28:06+01:00
Author: Timo Stollenwerk (tisto) <tisto at plone.org>
Commit: https://github.com/plone/plone.app.upgrade/commit/2be4aa9eed9e56c020d9b0ecf20865433aa4adfe

Revert "Ported to plone.app.testing"

Files changed:
A plone/app/upgrade/v25/tests.py
A plone/app/upgrade/v30/tests.py
A plone/app/upgrade/v31/tests.py
A plone/app/upgrade/v32/tests.py
A plone/app/upgrade/v33/tests.py
A plone/app/upgrade/v40/tests.py
A plone/app/upgrade/v41/tests.py
A plone/app/upgrade/v42/tests.py
M CHANGES.rst
M plone/app/upgrade/tests/base.py
M setup.py

diff --git a/CHANGES.rst b/CHANGES.rst
index 6e27679..1bf846a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,7 +1,7 @@
 Changelog
 =========
 
-2.0 (unreleased)
+1.3.9 (unreleased)
 ------------------
 
 - Add upgrade step for the security control panel.
@@ -13,11 +13,6 @@ Changelog
 - Add upgrade steps for markup control panel.
   [thet]
 
-- Ported tests to plone.app.testing
-  [tomgross]
-
-- Removed all tests for migrations older than 4.3 -> 5.0
-  [tomgross]
 
 1.3.8 (2014-11-01)
 ------------------
diff --git a/plone/app/upgrade/tests/base.py b/plone/app/upgrade/tests/base.py
index f25f530..840416c 100644
--- a/plone/app/upgrade/tests/base.py
+++ b/plone/app/upgrade/tests/base.py
@@ -9,7 +9,10 @@
 import transaction
 from zope.site.hooks import setSite
 
-from plone.app.testing.bbb import PloneTestCase
+from Testing.ZopeTestCase.sandbox import Sandboxed
+from Products.PloneTestCase.layer import PloneSiteLayer
+from Products.PloneTestCase.ptc import PloneTestCase
+from Products.PloneTestCase.ptc import setupPloneSite
 
 from Products.CMFCore.interfaces import IActionCategory
 from Products.CMFCore.interfaces import IActionInfo
@@ -17,6 +20,8 @@
 from Products.CMFCore.tests.base.testcase import WarningInterceptor
 from Products.GenericSetup.context import TarballImportContext
 
+setupPloneSite()
+
 
 class MigrationTest(PloneTestCase):
 
@@ -106,10 +111,21 @@ def removeSkinLayer(self, layer, skin='Plone Default'):
             skins.addSkinSelection(skin, ','.join(path))
 
 
+class FunctionalUpgradeLayer(PloneSiteLayer):
+
+    @classmethod
+    def setUp(cls):
+        pass
+
+    @classmethod
+    def tearDown(cls):
+        pass
+
 
-class FunctionalUpgradeTestCase(PloneTestCase, WarningInterceptor):
+class FunctionalUpgradeTestCase(Sandboxed, PloneTestCase, WarningInterceptor):
 
     _setup_fixture = 0
+    layer = FunctionalUpgradeLayer
     site_id = 'test'
 
     def afterSetUp(self):
diff --git a/plone/app/upgrade/v25/tests.py b/plone/app/upgrade/v25/tests.py
new file mode 100644
index 0000000..036e8b5
--- /dev/null
+++ b/plone/app/upgrade/v25/tests.py
@@ -0,0 +1,124 @@
+from Products.CMFPlone.UnicodeSplitter import Splitter
+from Products.CMFPlone.UnicodeSplitter import CaseNormalizer
+
+from plone.app.upgrade.tests.base import FunctionalUpgradeTestCase
+from plone.app.upgrade.tests.base import MigrationTest
+from plone.app.upgrade.utils import loadMigrationProfile
+
+from plone.app.upgrade.v25 import fixupPloneLexicon
+from plone.app.upgrade.v25 import setLoginFormInCookieAuth
+from plone.app.upgrade.v25 import addMissingMimeTypes
+
+
+class TestMigrations_v2_5_0(MigrationTest):
+
+    def afterSetUp(self):
+        self.profile = 'profile-plone.app.upgrade.v25:2.5final-2.5.1'
+        self.actions = self.portal.portal_actions
+        self.css = self.portal.portal_css
+
+    def tesFixObjDeleteAction(self):
+        # Prepare delete actions test
+        editActions = ('delete',)
+        for a in editActions:
+            self.removeActionFromTool(a, category='object_buttons')
+        loadMigrationProfile(self.portal, self.profile, ('actions', ))
+        # delete action tests
+        actions = [x.id for x in self.actions.object_buttons.listActions()
+                   if x.id in editActions]
+        # check that all of our deleted actions are now present
+        for a in editActions:
+            self.assertTrue(a in actions)
+        # ensure that they are present only once
+        self.assertEqual(len(editActions), len(actions))
+
+    def testFixupPloneLexicon(self):
+        # Should update the plone_lexicon pipeline
+        lexicon = self.portal.portal_catalog.plone_lexicon
+        lexicon._pipeline = (object(), object())
+        # Test it twice
+        for i in range(2):
+            fixupPloneLexicon(self.portal)
+            self.assertTrue(isinstance(lexicon._pipeline[0], Splitter))
+            self.assertTrue(isinstance(lexicon._pipeline[1], CaseNormalizer))
+
+
+class TestMigrations_v2_5_1(MigrationTest):
+
+    def afterSetUp(self):
+        self.actions = self.portal.portal_actions
+        self.memberdata = self.portal.portal_memberdata
+        self.catalog = self.portal.portal_catalog
+        self.skins = self.portal.portal_skins
+        self.types = self.portal.portal_types
+        self.workflow = self.portal.portal_workflow
+        self.css = self.portal.portal_css
+
+    def testSetLoginFormInCookieAuth(self):
+        setLoginFormInCookieAuth(self.portal)
+        cookie_auth = self.portal.acl_users.credentials_cookie_auth
+        self.assertEqual(cookie_auth.getProperty('login_path'),
+                             'require_login')
+
+    def testSetLoginFormNoCookieAuth(self):
+        # Shouldn't error
+        uf = self.portal.acl_users
+        uf._delOb('credentials_cookie_auth')
+        setLoginFormInCookieAuth(self.portal)
+
+    def testSetLoginFormAlreadyChanged(self):
+        # Shouldn't change the value if it's not the default
+        cookie_auth = self.portal.acl_users.credentials_cookie_auth
+        cookie_auth.manage_changeProperties(login_path='foo')
+        setLoginFormInCookieAuth(self.portal)
+        self.assertTrue(cookie_auth.getProperty('login_path') != 'require_login')
+
+class TestMigrations_v2_5_2(MigrationTest):
+
+    def afterSetUp(self):
+        self.mimetypes = self.portal.mimetypes_registry
+
+    def testMissingMimeTypes(self):
+        # we're testing for 'text/x-web-markdown' and 'text/x-web-textile'
+        missing_types = ['text/x-web-markdown', 'text/x-web-textile']
+        # since we're running a full 2.5.4 instance in this test, the missing
+        # types might in fact already be there:
+        current_types = self.mimetypes.list_mimetypes()
+        types_to_delete = []
+        for mtype in missing_types:
+            if mtype in current_types:
+                types_to_delete.append(mtype)
+        if types_to_delete:
+            self.mimetypes.manage_delObjects(types_to_delete)
+        # now they're gone:
+        self.assertFalse(set(self.mimetypes.list_mimetypes()).issuperset(set(missing_types)))
+        addMissingMimeTypes(self.portal)
+        # now they're back:
+        self.assertTrue(set(self.mimetypes.list_mimetypes()).issuperset(set(missing_types)))
+
+
+class TestFunctionalMigrations(FunctionalUpgradeTestCase):
+
+    def testUpgrade(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testDCMIStorageUpdated(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        dcmi = getattr(oldsite.portal_metadata, 'DCMI', None)
+        self.assertFalse(dcmi is None)
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestMigrations_v2_5_0))
+    suite.addTest(makeSuite(TestMigrations_v2_5_1))
+    suite.addTest(makeSuite(TestMigrations_v2_5_2))
+    suite.addTest(makeSuite(TestFunctionalMigrations))
+    return suite
diff --git a/plone/app/upgrade/v30/tests.py b/plone/app/upgrade/v30/tests.py
new file mode 100644
index 0000000..9325d0d
--- /dev/null
+++ b/plone/app/upgrade/v30/tests.py
@@ -0,0 +1,1109 @@
+from Acquisition import aq_base
+
+from five.localsitemanager.registry import FiveVerifyingAdapterLookup
+
+from plone.contentrules.engine.interfaces import IRuleStorage
+from plone.app.i18n.locales.interfaces import IContentLanguages
+from plone.app.i18n.locales.interfaces import ICountries
+from plone.app.i18n.locales.interfaces import IMetadataLanguages
+from plone.app.portlets import portlets
+from plone.app.redirector.interfaces import IRedirectionStorage
+from plone.portlets.interfaces import IPortletManager
+from plone.portlets.interfaces import IPortletAssignmentMapping
+from plone.portlets.interfaces import ILocalPortletAssignmentManager
+from plone.portlets.constants import CONTEXT_CATEGORY as CONTEXT_PORTLETS
+
+from zope.location.interfaces import ISite
+from zope.component import getGlobalSiteManager
+from zope.component import getSiteManager
+from zope.component import getUtility, getMultiAdapter
+from zope.component.hooks import clearSite
+
+from Products.Archetypes.interfaces import IArchetypeTool
+from Products.Archetypes.interfaces import IReferenceCatalog
+from Products.Archetypes.interfaces import IUIDCatalog
+from Products.CMFActionIcons.interfaces import IActionIconsTool
+from Products.CMFCalendar.interfaces import ICalendarTool
+from Products.CMFCore.ActionInformation import Action
+from Products.CMFCore.ActionInformation import ActionCategory
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.utils import getToolInterface
+from Products.CMFCore.Expression import Expression
+from Products.CMFCore.permissions import AccessInactivePortalContent
+from Products.CMFCore.interfaces import IActionsTool
+from Products.CMFCore.interfaces import ICachingPolicyManager
+from Products.CMFCore.interfaces import ICatalogTool
+from Products.CMFCore.interfaces import IContentTypeRegistry
+from Products.CMFCore.interfaces import IDiscussionTool
+from Products.CMFCore.interfaces import IMemberDataTool
+from Products.CMFCore.interfaces import IMembershipTool
+from Products.CMFCore.interfaces import IMetadataTool
+from Products.CMFCore.interfaces import IPropertiesTool
+from Products.CMFCore.interfaces import IRegistrationTool
+from Products.CMFCore.interfaces import ISiteRoot
+from Products.CMFCore.interfaces import ISkinsTool
+from Products.CMFCore.interfaces import ISyndicationTool
+from Products.CMFCore.interfaces import ITypesTool
+from Products.CMFCore.interfaces import IURLTool
+from Products.CMFCore.interfaces import IConfigurableWorkflowTool
+from Products.CMFCore.ActionInformation import ActionInformation
+from Products.CMFDiffTool.interfaces import IDiffTool
+from Products.CMFEditions.interfaces import IArchivistTool
+from Products.CMFEditions.interfaces import IPortalModifierTool
+from Products.CMFEditions.interfaces import IPurgePolicyTool
+from Products.CMFEditions.interfaces.IRepository import IRepositoryTool
+from Products.CMFEditions.interfaces import IStorageTool
+from Products.CMFFormController.interfaces import IFormControllerTool
+from Products.CMFQuickInstallerTool.interfaces import IQuickInstallerTool
+from Products.CMFPlone.interfaces import IPloneSiteRoot
+from Products.CMFPlone.interfaces import IPloneTool
+from Products.CMFPlone.interfaces import ITranslationServiceTool
+from Products.CMFUid.interfaces import IUniqueIdAnnotationManagement
+from Products.CMFUid.interfaces import IUniqueIdGenerator
+from Products.CMFUid.interfaces import IUniqueIdHandler
+from Products.GenericSetup.interfaces import ISetupTool
+from Products.MailHost.interfaces import IMailHost
+from Products.MimetypesRegistry.interfaces import IMimetypesRegistryTool
+from Products.PortalTransforms.interfaces import IPortalTransformsTool
+from Products.PloneLanguageTool.interfaces import ILanguageTool
+from Products.PlonePAS.interfaces.group import IGroupTool
+from Products.PlonePAS.interfaces.group import IGroupDataTool
+from Products.ResourceRegistries.interfaces import ICSSRegistry
+from Products.ResourceRegistries.interfaces import IJSRegistry
+
+from plone.app.upgrade.tests.base import FunctionalUpgradeTestCase
+from plone.app.upgrade.tests.base import MigrationTest
+from plone.app.upgrade.utils import loadMigrationProfile
+
+from plone.app.upgrade.v30.alphas import enableZope3Site
+from plone.app.upgrade.v30.alphas import migrateOldActions
+from plone.app.upgrade.v30.alphas import updateActionsI18NDomain
+from plone.app.upgrade.v30.alphas import updateFTII18NDomain
+from plone.app.upgrade.v30.alphas import convertLegacyPortlets
+from plone.app.upgrade.v30.alphas import registerToolsAsUtilities
+from plone.app.upgrade.v30.alphas import registration
+from plone.app.upgrade.v30.alphas import addReaderAndEditorRoles
+from plone.app.upgrade.v30.alphas import migrateLocalroleForm
+from plone.app.upgrade.v30.alphas import reorderUserActions
+from plone.app.upgrade.v30.alphas import updatePASPlugins
+from plone.app.upgrade.v30.alphas import updateConfigletTitles
+from plone.app.upgrade.v30.alphas import addCacheForResourceRegistry
+from plone.app.upgrade.v30.alphas import removeTablelessSkin
+from plone.app.upgrade.v30.alphas import addObjectProvidesIndex
+from plone.app.upgrade.v30.alphas import restorePloneTool
+from plone.app.upgrade.v30.alphas import installProduct
+
+from plone.app.upgrade.v30.betas import migrateHistoryTab
+from plone.app.upgrade.v30.betas import changeOrderOfActionProviders
+from plone.app.upgrade.v30.betas import cleanupOldActions
+from plone.app.upgrade.v30.betas import cleanDefaultCharset
+from plone.app.upgrade.v30.betas import addAutoGroupToPAS
+from plone.app.upgrade.v30.betas import removeS5Actions
+from plone.app.upgrade.v30.betas import addContributorToCreationPermissions
+from plone.app.upgrade.v30.betas import removeSharingAction
+from plone.app.upgrade.v30.betas import addEditorToSecondaryEditorPermissions
+from plone.app.upgrade.v30.betas import updateEditActionConditionForLocking
+from plone.app.upgrade.v30.betas import addOnFormUnloadJS
+
+from plone.app.upgrade.v30.betas import updateTopicTitle
+from plone.app.upgrade.v30.betas import cleanupActionProviders
+from plone.app.upgrade.v30.betas import hidePropertiesAction
+
+from plone.app.upgrade.v30.rcs import addIntelligentText
+
+from plone.app.upgrade.v30.final_three0x import installNewModifiers
+
+try:
+    from Products.ATContentTypes.interface import IATCTTool
+    HAS_ATCT = True
+except ImportError:
+    HAS_ATCT = False
+
+try:
+    from Products.CMFPlone.interfaces import IFactoryTool
+except:
+    from Products.ATContentTypes.interfaces import IFactoryTool
+
+
+class TestMigrations_v3_0_Actions(MigrationTest):
+
+    def afterSetUp(self):
+        self.actions = self.portal.portal_actions
+        self.types = self.portal.portal_types
+        self.workflow = self.portal.portal_workflow
+        self._migrate_reply_action()
+
+    def _migrate_reply_action(self):
+        # Create dummy old ActionInformation
+        reply = ActionInformation('reply',
+            title='Reply',
+            category='reply_actions',
+            condition='context/replyAllowed',
+            permissions=(AccessInactivePortalContent, ),
+            priority=10,
+            visible=True,
+            action='context/reply'
+        )
+
+        from OFS.SimpleItem import SimpleItem
+        class DummyTool(SimpleItem):
+            pass
+        dummy = DummyTool()
+        dummy._actions = (reply,)
+        self.portal._setObject('dummy', dummy)
+
+    def testMigrateActions(self):
+        # Test it twice
+        for i in range(2):
+            migrateOldActions(self.portal)
+            reply_actions = getattr(self.actions, 'reply_actions', None)
+            self.assertFalse(reply_actions is None)
+            reply = getattr(reply_actions, 'reply', None)
+            self.assertFalse(reply is None)
+            self.assertTrue(isinstance(reply, Action))
+            # Verify all data has been upgraded correctly to the new Action
+            data = reply.getInfoData()[0]
+            self.assertEqual(data['category'], 'reply_actions')
+            self.assertEqual(data['title'], 'Reply')
+            self.assertEqual(data['visible'], True)
+            self.assertEqual(data['permissions'], (AccessInactivePortalContent, ))
+            self.assertEqual(data['available'].text, 'context/replyAllowed')
+            self.assertEqual(data['url'].text, 'context/reply')
+            # Make sure the original action has been removed
+            self.assertEqual(len(self.portal.dummy._actions), 0)
+
+    def testUpdateActionsI18NDomain(self):
+        migrateOldActions(self.portal)
+        reply = self.actions.reply_actions.reply
+        self.assertEqual(reply.i18n_domain, '')
+        # Test it twice
+        for i in range(2):
+            updateActionsI18NDomain(self.portal)
+            self.assertEqual(reply.i18n_domain, 'plone')
+
+    def testUpdateActionsI18NDomainNonAscii(self):
+        migrateOldActions(self.portal)
+        reply = self.actions.reply_actions.reply
+        reply.title = 'Foo\xc3'
+        self.assertEqual(reply.i18n_domain, '')
+        self.assertEqual(reply.title, 'Foo\xc3')
+
+        updateActionsI18NDomain(self.portal)
+
+        self.assertEqual(reply.i18n_domain, '')
+
+    def testHistoryActionID(self):
+        # Test it twice
+        for i in range(2):
+            migrateHistoryTab(self.portal)
+            objects = getattr(self.actions, 'object', None)
+            self.assertFalse('rss' in objects.objectIds())
+
+    def testProviderCleanup(self):
+        self.actions.addActionProvider("portal_membership")
+        self.assertTrue("portal_membership" in self.actions.listActionProviders())
+        # Test it twice
+        for i in range(2):
+            cleanupActionProviders(self.portal)
+            self.assertFalse("portal_membership" in self.actions.listActionProviders())
+
+    def testRemovePropertiesActions(self):
+        ti = self.types.getTypeInfo("Document")
+        if ti.getActionObject("object/properties") is None:
+            ti.addAction("metadata", "name", "action", "condition",
+                    "permission", "object",)
+        # Test it twice
+        for i in range(2):
+            hidePropertiesAction(self.portal)
+            self.assertTrue(ti.getActionObject("object/metadata") is None)
+
+    def tearDown(self):
+        self.portal._delObject('dummy')
+
+
+class TestMigrations_v2_5_x(MigrationTest):
+
+    def afterSetUp(self):
+        self.profile = 'profile-plone.app.upgrade.v30:2.5.x-3.0a1'
+        self.types = self.portal.portal_types
+        self.properties = self.portal.portal_properties
+
+        for legacy_tool in ('portal_discussion', 'portal_actionicons'):
+            if legacy_tool not in self.portal:
+                from OFS.SimpleItem import SimpleItem
+                self.portal._setObject(legacy_tool, SimpleItem())
+
+    def disableSite(self, obj, iface=ISite):
+        # We need our own disableSite method as the CMF portal implements
+        # ISite directly, so we cannot remove it, like the disableSite method
+        # in Five.component would have done
+        from ZPublisher.BeforeTraverse import unregisterBeforeTraverse
+        from Products.Five.component import HOOK_NAME
+        obj = aq_base(obj)
+        if not iface.providedBy(obj):
+            raise TypeError('Object must be a site.')
+        unregisterBeforeTraverse(obj, HOOK_NAME)
+        if hasattr(obj, HOOK_NAME):
+            delattr(obj, HOOK_NAME)
+
+    def testEnableZope3Site(self):
+        # First we remove the site and site manager
+        self.disableSite(self.portal)
+        clearSite(self.portal)
+        self.portal.setSiteManager(None)
+        gsm = getGlobalSiteManager()
+        # Test it twice
+        for i in range(2):
+            enableZope3Site(self.portal)
+            # And see if we have an ISite with a local site manager
+            self.assertTrue(ISite.providedBy(self.portal))
+            sm = getSiteManager(self.portal)
+            self.assertFalse(gsm is sm)
+            lc = sm.utilities.LookupClass
+            self.assertEqual(lc, FiveVerifyingAdapterLookup)
+
+        # Test the lookupclass migration
+        sm.utilities.LookupClass = None
+        # Test it twice
+        for i in range(2):
+            enableZope3Site(self.portal)
+            self.assertEqual(sm.utilities.LookupClass, FiveVerifyingAdapterLookup)
+            self.assertEqual(sm.utilities.__parent__, sm)
+            self.assertEqual(sm.__parent__, self.portal)
+
+    def testUpdateFTII18NDomain(self):
+        doc = self.types.Document
+        doc.i18n_domain = ''
+        # Test it twice
+        for i in range(2):
+            updateFTII18NDomain(self.portal)
+            self.assertEqual(doc.i18n_domain, 'plone')
+
+    def testUpdateFTII18NDomainNonAscii(self):
+        doc = self.types.Document
+        doc.i18n_domain = ''
+        doc.title = 'Foo\xc3'
+        # Update FTI's
+        updateFTII18NDomain(self.portal)
+        # domain should have been updated
+        self.assertEqual(doc.i18n_domain, '')
+
+    def testAddDefaultAndForbiddenContentTypesProperties(self):
+        # Should add the forbidden_contenttypes and default_contenttype property
+        self.removeSiteProperty('forbidden_contenttypes')
+        self.removeSiteProperty('default_contenttype')
+        self.assertFalse(self.properties.site_properties.hasProperty('forbidden_contenttypes'))
+        self.assertFalse(self.properties.site_properties.hasProperty('default_contenttype'))
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('propertiestool', ))
+            self.assertTrue(self.properties.site_properties.hasProperty('forbidden_contenttypes'))
+            self.assertTrue(self.properties.site_properties.hasProperty('default_contenttype'))
+            self.assertEqual(self.properties.site_properties.forbidden_contenttypes,
+                ('text/structured', 'text/restructured', 'text/x-rst',
+                'text/plain', 'text/plain-pre', 'text/x-python',
+                'text/x-web-markdown', 'text/x-web-intelligent', 'text/x-web-textile')
+            )
+
+    def testTablelessRemoval(self):
+        st = getToolByName(self.portal, "portal_skins")
+        if "Plone Tableless" not in st.getSkinSelections():
+            st.addSkinSelection('Plone Tableless', 'one,two', make_default=True)
+        # Test it twice
+        for i in range(2):
+            removeTablelessSkin(self.portal)
+            self.assertFalse('Plone Tableless' in st.getSkinSelections())
+            self.assertFalse(st.default_skin == 'Plone Tableless')
+
+    def testLegacyPortletsConverted(self):
+        self.setRoles(('Manager',))
+        leftColumn = getUtility(IPortletManager, name=u'plone.leftcolumn', context=self.portal)
+        rightColumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=self.portal)
+
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        for k in left:
+            del left[k]
+        for k in right:
+            del right[k]
+
+        self.portal.left_slots = ['here/portlet_recent/macros/portlet',
+                                  'here/portlet_news/macros/portlet',
+                                  'here/portlet_related/macros/portlet']
+        self.portal.right_slots = ['here/portlet_login/macros/portlet',
+                                   'here/portlet_languages/macros/portlet']
+
+        self.portal.Members.right_slots = []
+
+        # Test it twice
+        for i in range(2):
+            convertLegacyPortlets(self.portal)
+
+            self.assertEqual(self.portal.left_slots, [])
+            self.assertEqual(self.portal.right_slots, [])
+
+            left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+            right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+            lp = left.values()
+            self.assertEqual(2, len(lp))
+
+            self.assertTrue(isinstance(lp[0], portlets.recent.Assignment))
+            self.assertTrue(isinstance(lp[1], portlets.news.Assignment))
+
+            rp = right.values()
+            self.assertEqual(1, len(rp))
+            self.assertTrue(isinstance(rp[0], portlets.login.Assignment))
+
+            members = self.portal.Members
+            portletAssignments = getMultiAdapter((members, rightColumn,), ILocalPortletAssignmentManager)
+            self.assertEqual(True, portletAssignments.getBlacklistStatus(CONTEXT_PORTLETS))
+
+    def testLegacyPortletsConvertedNoSlots(self):
+        self.setRoles(('Manager',))
+        leftColumn = getUtility(IPortletManager, name=u'plone.leftcolumn', context=self.portal)
+        rightColumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=self.portal)
+
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        for k in left:
+            del left[k]
+        for k in right:
+            del right[k]
+
+        self.portal.left_slots = ['here/portlet_recent/macros/portlet',
+                                  'here/portlet_news/macros/portlet']
+
+        self.portal.Members.right_slots = []
+
+        if hasattr(self.portal.aq_base, 'right_slots'):
+            delattr(self.portal, 'right_slots')
+
+        convertLegacyPortlets(self.portal)
+
+        self.assertEqual(self.portal.left_slots, [])
+
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        lp = left.values()
+        self.assertEqual(2, len(lp))
+
+        self.assertTrue(isinstance(lp[0], portlets.recent.Assignment))
+        self.assertTrue(isinstance(lp[1], portlets.news.Assignment))
+
+        rp = right.values()
+        self.assertEqual(0, len(rp))
+
+        members = self.portal.Members
+        portletAssignments = getMultiAdapter((members, rightColumn,), ILocalPortletAssignmentManager)
+        self.assertEqual(True, portletAssignments.getBlacklistStatus(CONTEXT_PORTLETS))
+
+    def testLegacyPortletsConvertedBadSlots(self):
+        self.setRoles(('Manager',))
+        leftColumn = getUtility(IPortletManager, name=u'plone.leftcolumn', context=self.portal)
+        rightColumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=self.portal)
+
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        for k in left:
+            del left[k]
+        for k in right:
+            del right[k]
+
+        self.portal.left_slots = ['here/portlet_recent/macros/portlet',
+                                  'here/portlet_news/macros/portlet',
+                                  'foobar',]
+        self.portal.right_slots = ['here/portlet_login/macros/portlet']
+
+        self.portal.Members.right_slots = []
+
+        convertLegacyPortlets(self.portal)
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        self.assertEqual(self.portal.left_slots, [])
+        self.assertEqual(self.portal.right_slots, [])
+
+        lp = left.values()
+        self.assertEqual(2, len(lp))
+
+        self.assertTrue(isinstance(lp[0], portlets.recent.Assignment))
+        self.assertTrue(isinstance(lp[1], portlets.news.Assignment))
+
+        rp = right.values()
+        self.assertEqual(1, len(rp))
+        self.assertTrue(isinstance(rp[0], portlets.login.Assignment))
+
+        members = self.portal.Members
+        portletAssignments = getMultiAdapter((members, rightColumn,), ILocalPortletAssignmentManager)
+        self.assertEqual(True, portletAssignments.getBlacklistStatus(CONTEXT_PORTLETS))
+
+    def testLegacyPortletsConvertedNoMembersFolder(self):
+        self.setRoles(('Manager',))
+        leftColumn = getUtility(IPortletManager, name=u'plone.leftcolumn', context=self.portal)
+        rightColumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=self.portal)
+
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        for k in left:
+            del left[k]
+        for k in right:
+            del right[k]
+
+        self.portal.left_slots = ['here/portlet_recent/macros/portlet',
+                                  'here/portlet_news/macros/portlet',
+                                  'foobar',]
+        self.portal.right_slots = ['here/portlet_login/macros/portlet']
+
+        self.portal._delObject('Members')
+
+        convertLegacyPortlets(self.portal)
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        self.assertEqual(self.portal.left_slots, [])
+        self.assertEqual(self.portal.right_slots, [])
+
+        lp = left.values()
+        self.assertEqual(2, len(lp))
+
+        self.assertTrue(isinstance(lp[0], portlets.recent.Assignment))
+        self.assertTrue(isinstance(lp[1], portlets.news.Assignment))
+
+        rp = right.values()
+        self.assertEqual(1, len(rp))
+        self.assertTrue(isinstance(rp[0], portlets.login.Assignment))
+
+    def testRegisterToolsAsUtilities(self):
+        sm = getSiteManager(self.portal)
+        interfaces = (ISiteRoot, IPloneSiteRoot,
+                      IActionIconsTool, ISyndicationTool,
+                      IMetadataTool, IPropertiesTool, IMailHost,
+                      IUniqueIdAnnotationManagement, IUniqueIdGenerator,
+                      IDiffTool, IMimetypesRegistryTool,
+                      IPortalTransformsTool, IDiscussionTool, )
+        if HAS_ATCT:
+            interfaces += (IATCTTool,)
+        for i in interfaces:
+            sm.unregisterUtility(provided=i)
+        registerToolsAsUtilities(self.portal)
+        for i in interfaces:
+            self.assertFalse(sm.queryUtility(i) is None)
+
+        for i in interfaces:
+            sm.unregisterUtility(provided=i)
+        registerToolsAsUtilities(self.portal)
+        registerToolsAsUtilities(self.portal)
+        for i in interfaces:
+            self.assertFalse(sm.queryUtility(i) is None)
+
+    def testDontRegisterToolsAsUtilities(self):
+        sm = getSiteManager(self.portal)
+        interfaces = (ILanguageTool, IArchivistTool, IPortalModifierTool,
+                      IPurgePolicyTool, IRepositoryTool, IStorageTool,
+                      IFormControllerTool, IReferenceCatalog, IUIDCatalog,
+                      ICalendarTool, IActionsTool, ICatalogTool,
+                      IContentTypeRegistry, ISkinsTool, ITypesTool, IURLTool,
+                      IConfigurableWorkflowTool, IPloneTool, ICSSRegistry,
+                      IJSRegistry, IUniqueIdHandler, IFactoryTool,
+                      IMembershipTool, IGroupTool, IGroupDataTool,
+                      IMemberDataTool, IArchetypeTool, ICachingPolicyManager,
+                      IRegistrationTool, ITranslationServiceTool,
+                      ISetupTool, IQuickInstallerTool,
+                     )
+        for i in interfaces:
+            sm.unregisterUtility(provided=i)
+        registerToolsAsUtilities(self.portal)
+        for i in interfaces:
+            self.assertTrue(sm.queryUtility(i) is None)
+
+        for i in interfaces:
+            sm.unregisterUtility(provided=i)
+        registerToolsAsUtilities(self.portal)
+        registerToolsAsUtilities(self.portal)
+        for i in interfaces:
+            self.assertTrue(sm.queryUtility(i) is None)
+
+    def testToolRegistration(self):
+        for (tool_id, interface) in registration:
+            self.assertEqual(getToolInterface(tool_id), interface)
+
+
+class TestMigrations_v3_0_alpha1(MigrationTest):
+
+    def afterSetUp(self):
+        self.profile = 'profile-plone.app.upgrade.v30:3.0a1-3.0a2'
+        self.actions = self.portal.portal_actions
+
+    def testInstallRedirectorUtility(self):
+        sm = getSiteManager(self.portal)
+        sm.unregisterUtility(provided=IRedirectionStorage)
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('componentregistry', ))
+            self.assertFalse(sm.queryUtility(IRedirectionStorage) is None)
+
+    def testAddReaderEditorRoles(self):
+        self.portal._delRoles(['Reader', 'Editor'])
+        # Test it twice
+        for i in range(2):
+            addReaderAndEditorRoles(self.portal)
+            self.assertTrue('Reader' in self.portal.valid_roles())
+            self.assertTrue('Editor' in self.portal.valid_roles())
+            self.assertTrue('Reader' in self.portal.acl_users.portal_role_manager.listRoleIds())
+            self.assertTrue('Editor' in self.portal.acl_users.portal_role_manager.listRoleIds())
+            self.assertTrue('View' in [r['name'] for r in self.portal.permissionsOfRole('Reader') if r['selected']])
+            self.assertTrue('Modify portal content' in [r['name'] for r in self.portal.permissionsOfRole('Editor') if r['selected']])
+
+    def testAddReaderEditorRolesPermissionOnly(self):
+        self.portal.manage_permission('View', [], True)
+        self.portal.manage_permission('Modify portal content', [], True)
+        # Test it twice
+        for i in range(2):
+            addReaderAndEditorRoles(self.portal)
+            self.assertTrue('Reader' in self.portal.valid_roles())
+            self.assertTrue('Editor' in self.portal.valid_roles())
+            self.assertTrue('Reader' in self.portal.acl_users.portal_role_manager.listRoleIds())
+            self.assertTrue('Editor' in self.portal.acl_users.portal_role_manager.listRoleIds())
+            self.assertTrue('View' in [r['name'] for r in self.portal.permissionsOfRole('Reader') if r['selected']])
+            self.assertTrue('Modify portal content' in [r['name'] for r in self.portal.permissionsOfRole('Editor') if r['selected']])
+
+    def testMigrateLocalroleForm(self):
+        fti = self.portal.portal_types['Document']
+        aliases = fti.getMethodAliases()
+        aliases['sharing'] = 'folder_localrole_form'
+        fti.setMethodAliases(aliases)
+        fti.addAction('test', 'Test', 'string:${object_url}/folder_localrole_form', None, 'View', 'object')
+        # Test it twice
+        for i in range(2):
+            migrateLocalroleForm(self.portal)
+            self.assertEqual('@@sharing', fti.getMethodAliases()['sharing'])
+            test_action = fti.listActions()[-1]
+            self.assertEqual('string:${object_url}/@@sharing', test_action.getActionExpression())
+
+    def testReorderUserActions(self):
+        self.actions.user.moveObjectsToTop(['logout', 'undo', 'join'])
+        # Test it twice
+        for i in range(2):
+            reorderUserActions(self.portal)
+            # build a dict that has the position as the value to make it easier
+            # to compare postions in the ordered list of actions
+            n = 0
+            sort = {}
+            for action in self.actions.user.objectIds():
+                sort[action] = n
+                n += 1
+            self.assertTrue(sort['preferences'] < sort['undo'])
+            self.assertTrue(sort['undo'] < sort['logout'])
+            self.assertTrue(sort['login'] < sort['join'])
+
+    def testReorderUserActionsIncompleteActions(self):
+        self.actions.user.moveObjectsToTop(['logout', 'undo', 'join'])
+        self.actions.user._delObject('preferences')
+        # Test it twice
+        for i in range(2):
+            reorderUserActions(self.portal)
+            n = 0
+            sort = {}
+            for action in self.actions.user.objectIds():
+                sort[action] = n
+                n += 1
+            self.assertTrue(sort['undo'] < sort['logout'])
+            self.assertTrue(sort['login'] < sort['join'])
+
+
+class TestMigrations_v3_0_alpha2(MigrationTest):
+
+    def afterSetUp(self):
+        self.profile = 'profile-plone.app.upgrade.v30:3.0a2-3.0b1'
+        self.actions = self.portal.portal_actions
+        self.properties = self.portal.portal_properties
+        self.cp = self.portal.portal_controlpanel
+
+    def testAddVariousProperties(self):
+        PROPERTIES = ('enable_link_integrity_checks', 'enable_sitemap',
+                      'external_links_open_new_window', 'many_groups',
+                      'number_of_days_to_keep', 'webstats_js')
+        for prop in PROPERTIES:
+            self.removeSiteProperty(prop)
+        sheet = self.properties.site_properties
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('propertiestool', ))
+            for prop in PROPERTIES:
+                self.assertTrue(sheet.hasProperty(prop))
+
+    def testInstallContentrulesAndLanguageUtilities(self):
+        sm = getSiteManager()
+        INTERFACES = (IRuleStorage, ICountries, IContentLanguages,
+                      IMetadataLanguages)
+        for i in INTERFACES:
+            sm.unregisterUtility(provided=i)
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('componentregistry', ))
+            for i in INTERFACES:
+                self.assertFalse(sm.queryUtility(i) is None)
+
+    def testAddEmailCharsetProperty(self):
+        if self.portal.hasProperty('email_charset'):
+            self.portal.manage_delProperties(['email_charset'])
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('properties', ))
+            self.assertTrue(self.portal.hasProperty('email_charset'))
+            self.assertEqual(self.portal.getProperty('email_charset'), 'utf-8')
+
+    def testUpdateMemberSecurity(self):
+        pprop = getToolByName(self.portal, 'portal_properties')
+        self.assertEqual(
+                pprop.site_properties.getProperty('allowAnonymousViewAbout'),
+                False)
+
+        pmembership = getToolByName(self.portal, 'portal_membership')
+        self.assertEqual(pmembership.memberareaCreationFlag, False)
+        self.assertEqual(self.portal.getProperty('validate_email'), True)
+
+        app_roles = self.portal.rolesOfPermission(permission='Add portal member')
+        app_perms = self.portal.permission_settings(permission='Add portal member')
+        acquire_check = app_perms[0]['acquire']
+        reg_roles = []
+        for appperm in app_roles:
+            if appperm['selected'] == 'SELECTED':
+                reg_roles.append(appperm['name'])
+        self.assertTrue('Manager' in reg_roles)
+        self.assertTrue('Owner' in reg_roles)
+        self.assertEqual(acquire_check, '')
+
+    def testPASPluginInterfaces(self):
+        pas = self.portal.acl_users
+        from Products.PluggableAuthService.interfaces.plugins import IUserEnumerationPlugin
+        pas.plugins.deactivatePlugin(IUserEnumerationPlugin, 'mutable_properties')
+        updatePASPlugins(self.portal)
+
+        plugin = pas.mutable_properties
+        for intf_id in plugin.listInterfaces():
+            try:
+                intf = pas.plugins._getInterfaceFromName(intf_id)
+                self.assertTrue('mutable_properties' in pas.plugins.listPluginIds(intf))
+            except KeyError:
+                # Ignore unregistered interface types
+                pass
+
+    def testUpdateConfigletTitles(self):
+        collection = self.cp.getActionObject('Plone/portal_atct')
+        language = self.cp.getActionObject('Plone/PloneLanguageTool')
+        navigation = self.cp.getActionObject('Plone/NavigationSettings')
+        types = self.cp.getActionObject('Plone/TypesSettings')
+        users = self.cp.getActionObject('Plone/UsersGroups')
+        users2 = self.cp.getActionObject('Plone/UsersGroups2')
+        # test it twice
+        for i in range(2):
+            updateConfigletTitles(self.portal)
+            self.assertEqual(collection.title, 'Collection')
+            self.assertEqual(language.title, 'Language')
+            self.assertEqual(navigation.title, 'Navigation')
+            self.assertEqual(types.title, 'Types')
+            self.assertEqual(users.title, 'Users and Groups')
+            self.assertEqual(users2.title, 'Users and Groups')
+
+    def testAddCacheForResourceRegistry(self):
+        ram_cache_id = 'ResourceRegistryCache'
+        # first remove the cache manager and make sure it's removed
+        self.portal._delObject(ram_cache_id)
+        self.assertFalse(ram_cache_id in self.portal.objectIds())
+        cssreg = self.portal.portal_css
+        cssreg.ZCacheable_setEnabled(0)
+        cssreg.ZCacheable_setManagerId(None)
+        self.assertFalse(cssreg.ZCacheable_enabled())
+        self.assertTrue(cssreg.ZCacheable_getManagerId() is None)
+        jsreg = self.portal.portal_javascripts
+        jsreg.ZCacheable_setEnabled(0)
+        jsreg.ZCacheable_setManagerId(None)
+        self.assertFalse(jsreg.ZCacheable_enabled())
+        self.assertTrue(jsreg.ZCacheable_getManagerId() is None)
+        # Test it twice
+        for i in range(2):
+            addCacheForResourceRegistry(self.portal)
+            self.assertTrue(ram_cache_id in self.portal.objectIds())
+            self.assertTrue(cssreg.ZCacheable_enabled())
+            self.assertFalse(cssreg.ZCacheable_getManagerId() is None)
+            self.assertTrue(jsreg.ZCacheable_enabled())
+            self.assertFalse(jsreg.ZCacheable_getManagerId() is None)
+
+    def testObjectProvidesIndex(self):
+        catalog = getToolByName(self.portal, 'portal_catalog')
+        if 'object_provides' in catalog.indexes():
+            catalog.delIndex('object_provides')
+        self.assertFalse('object_provides' in catalog.indexes())
+        # Test it twice
+        for i in range(2):
+            addObjectProvidesIndex(self.portal)
+            self.assertTrue('object_provides' in catalog.indexes())
+
+    def testMigratePloneTool(self):
+        tool = self.portal.plone_utils
+        tool.meta_type = 'PlonePAS Utilities Tool'
+        # Test it twice
+        for i in range(2):
+            restorePloneTool(self.portal)
+            tool = self.portal.plone_utils
+            self.assertEqual('Plone Utility Tool', tool.meta_type)
+
+    def testInstallPloneLanguageTool(self):
+        super(self.portal.__class__, self.portal).manage_delObjects(
+            ['portal_languages'])
+        self.uninstallProduct('PloneLanguageTool')
+        qi = getToolByName(self.portal, "portal_quickinstaller")
+        # Test it twice
+        for i in range(2):
+            installProduct('PloneLanguageTool', self.portal)
+            self.assertTrue(qi.isProductInstalled('PloneLanguageTool'))
+            self.assertTrue('portal_languages' in self.portal.keys())
+
+
+class TestMigrations_v3_0(MigrationTest):
+
+    def afterSetUp(self):
+        self.profile = 'profile-plone.app.upgrade.v30:3.0b1-3.0b2'
+        self.actions = self.portal.portal_actions
+        self.skins = self.portal.portal_skins
+        self.types = self.portal.portal_types
+        self.workflow = self.portal.portal_workflow
+        self.properties = getToolByName(self.portal, 'portal_properties')
+
+    def testAddContentRulesAction(self):
+        self.portal.portal_actions.object._delObject('contentrules')
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('actions', ))
+            self.assertTrue('contentrules' in self.portal.portal_actions.object.objectIds())
+
+    def testChangeOrderOfActionProviders(self):
+        self.actions.deleteActionProvider('portal_types')
+        self.actions.addActionProvider('portal_types')
+        self.assertEqual(
+            self.actions.listActionProviders(),
+            ('portal_workflow', 'portal_actions', 'portal_types'))
+        # Test it twice
+        for i in range(2):
+            changeOrderOfActionProviders(self.portal)
+            self.assertEqual(
+                self.actions.listActionProviders(),
+                ('portal_workflow', 'portal_types', 'portal_actions'))
+
+    def testCleanupOldActions(self):
+        reply = Action('reply', title='Reply')
+        logged_in = Action('logged_in', title='Logged in')
+        change_ownership = Action('change_ownership', title='Change ownership')
+
+        object_ = self.actions.object
+        object_tabs = getattr(self.actions, 'object_tabs', None)
+        if object_tabs is None:
+            category = 'object_tabs'
+            self.actions._setObject(category, ActionCategory(id=category))
+            object_tabs = self.actions.object_tabs
+        if getattr(self.actions, 'global', None) is None:
+            category = 'global'
+            self.actions._setObject(category, ActionCategory(id=category))
+
+        if not 'reply' in object_.keys():
+            object_._setObject('reply', reply)
+        user = self.actions.user
+        if not 'logged_in' in user.keys():
+            user._setObject('logged_in', logged_in)
+        if not 'change_ownership' in object_tabs.keys():
+            object_tabs._setObject('change_ownership', change_ownership)
+        del object_tabs
+
+        # Test it twice
+        for i in range(2):
+            cleanupOldActions(self.portal)
+            self.assertFalse('reply' in object_.keys())
+            self.assertFalse('logged_in' in user.keys())
+            self.assertFalse('object_tabs' in self.actions.keys())
+            self.assertFalse('global' in self.actions.keys())
+
+    def testCharsetCleanup(self):
+        if not self.portal.hasProperty('default_charset'):
+            self.portal.manage_addProperty('default_charset', '', 'string')
+        # Test it twice
+        for i in range(2):
+            self.portal.manage_changeProperties(default_charset = 'latin1')
+            cleanDefaultCharset(self.portal)
+            self.assertEqual(self.portal.getProperty('default_charset', 'nothere'),
+                    'latin1')
+        # Test it twice
+        for i in range(2):
+            self.portal.manage_changeProperties(default_charset = '')
+            cleanDefaultCharset(self.portal)
+            self.assertEqual(self.portal.getProperty('default_charset', 'nothere'),
+                    'nothere')
+
+    def testAutoGroupCreated(self):
+        pas = self.portal.acl_users
+        ids = pas.objectIds(['Automatic Group Plugin'])
+        if ids:
+            pas.manage_delObjects(ids)
+        addAutoGroupToPAS(self.portal)
+        self.assertEqual(pas.objectIds(['Automatic Group Plugin']),
+                ['auto_group'])
+        plugin = pas.auto_group
+        interfaces = [info['interface'] for info in pas.plugins.listPluginTypeInfo()]
+        for iface in interfaces:
+            if plugin.testImplements(iface):
+                self.assertFalse('auto_group' not in pas.plugins.listPluginIds(iface))
+        self.assertEqual(len(pas.searchGroups(id='AuthenticatedUsers',
+                                              exact_match=True)), 1)
+
+    def testPloneS5(self):
+        pt = getToolByName(self.portal, "portal_types")
+        document = pt.restrictedTraverse('Document')
+        document.addAction('s5_presentation',
+            name='View as presentation',
+            action="string:${object/absolute_url}/document_s5_presentation",
+            condition='python:object.document_s5_alter(test=True)',
+            permission='View',
+            category='document_actions',
+            visible=1,
+            )
+        action_ids = [x.getId() for x in document.listActions()]
+        self.assertTrue("s5_presentation" in action_ids)
+        # Test it twice
+        for i in range(2):
+            removeS5Actions(self.portal)
+            action_ids = [x.getId() for x in document.listActions()]
+            self.assertFalse("s5_presentation" in action_ids)
+
+    def testAddContributorToCreationPermissions(self):
+        self.portal._delRoles(['Contributor',])
+        for p in ['Add portal content', 'Add portal folders', 'ATContentTypes: Add Document',
+                    'ATContentTypes: Add Event',
+                    'ATContentTypes: Add File', 'ATContentTypes: Add Folder',
+                    'ATContentTypes: Add Image', 'ATContentTypes: Add Link',
+                    'ATContentTypes: Add News Item', ]:
+            self.portal.manage_permission(p, ['Manager', 'Owner'], True)
+        # Test it twice
+        for i in range(2):
+            addContributorToCreationPermissions(self.portal)
+            self.assertTrue('Contributor' in self.portal.valid_roles())
+            self.assertTrue('Contributor' in self.portal.acl_users.portal_role_manager.listRoleIds())
+            for p in ['Add portal content', 'Add portal folders', 'ATContentTypes: Add Document',
+                        'ATContentTypes: Add Event',
+                        'ATContentTypes: Add File', 'ATContentTypes: Add Folder',
+                        'ATContentTypes: Add Image', 'ATContentTypes: Add Link',
+                        'ATContentTypes: Add News Item', ]:
+                self.assertTrue(p in [r['name'] for r in
+                                    self.portal.permissionsOfRole('Contributor') if r['selected']])
+
+    def testAddContributerToCreationPermissionsNoStomp(self):
+        self.portal.manage_permission('Add portal content', ['Manager'], False)
+        # Test it twice
+        for i in range(2):
+            addContributorToCreationPermissions(self.portal)
+            roles = sorted([r['name'] for r in self.portal.rolesOfPermission('Add portal content') if r['selected']])
+            self.assertEqual(['Contributor', 'Manager'], roles)
+            self.assertEqual(False, bool(self.portal.acquiredRolesAreUsedBy('Add portal content')))
+
+    def testAddBeta2VersioningPermissionsToNewRoles(self):
+        # This upgrade just uses GS to apply the role changes,
+        # these permissions will not have been installed previously,
+        # so this should be safe
+        for p in ['CMFEditions: Apply version control',
+                  'CMFEditions: Save new version',
+                  'CMFEditions: Access previous versions',
+                  'CMFEditions: Revert to previous versions',
+                  'CMFEditions: Checkout to location']:
+            self.portal.manage_permission(p, ['Manager', 'Owner'], True)
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal,
+                    'profile-plone.app.upgrade.v30:3.0b1-3.0b2',
+                    steps=["rolemap"])
+            for p in ['CMFEditions: Apply version control',
+                      'CMFEditions: Save new version',
+                      'CMFEditions: Access previous versions']:
+                self.assertTrue(p in [r['name'] for r in
+                                    self.portal.permissionsOfRole('Contributor') if r['selected']])
+                self.assertTrue(p in [r['name'] for r in
+                                    self.portal.permissionsOfRole('Editor') if r['selected']])
+            for p in ['CMFEditions: Revert to previous versions',
+                      'CMFEditions: Checkout to location']:
+                self.assertTrue(p in [r['name'] for r in
+                                    self.portal.permissionsOfRole('Editor') if r['selected']])
+
+    def testRemoveSharingAction(self):
+        fti = self.types['Document']
+        fti.addAction(id='local_roles', name='Sharing',
+                      action='string:${object_url}/sharing',
+                      condition=None, permission='Manage properties',
+                      category='object')
+        # Test it twice
+        for i in range(2):
+            removeSharingAction(self.portal)
+            self.assertFalse('local_roles' in [a.id for a in fti.listActions()])
+
+    def testAddEditorToCreationPermissions(self):
+        for p in ['Manage properties', 'Modify view template', 'Request review']:
+            self.portal.manage_permission(p, ['Manager', 'Owner'], True)
+        # Test it twice
+        for i in range(2):
+            addEditorToSecondaryEditorPermissions(self.portal)
+            for p in ['Manage properties', 'Modify view template', 'Request review']:
+                self.assertTrue(p in [r['name'] for r in
+                    self.portal.permissionsOfRole('Editor') if r['selected']])
+
+    def testAddEditorToCreationPermissionsNoStomp(self):
+        self.portal.manage_permission('Manage properties', ['Manager'], False)
+        # Test it twice
+        for i in range(2):
+            addEditorToSecondaryEditorPermissions(self.portal)
+            roles = sorted([r['name'] for r in self.portal.rolesOfPermission('Manage properties') if r['selected']])
+            self.assertEqual(['Editor', 'Manager'], roles)
+            self.assertEqual(False, bool(self.portal.acquiredRolesAreUsedBy('Manage properties')))
+
+    def testUpdateEditActionConditionForLocking(self):
+        lockable_types = ['Document', 'Event', 'File', 'Folder',
+                          'Image', 'Link', 'News Item', 'Topic']
+        for contentType in lockable_types:
+            fti = self.types.getTypeInfo(contentType)
+            for action in fti.listActions():
+                if action.getId() == 'edit':
+                    action.condition = ''
+        # Test it twice
+        for i in range(2):
+            updateEditActionConditionForLocking(self.portal)
+            for contentType in lockable_types:
+                fti = self.types.getTypeInfo(contentType)
+                for action in fti.listActions():
+                    if action.getId() == 'edit':
+                        self.assertEqual(action.condition.text,
+                            "not:object/@@plone_lock_info/is_locked_for_current_user|python:True")
+
+    def testUpdateEditExistingActionConditionForLocking(self):
+        fti = self.types.getTypeInfo('Document')
+        for action in fti.listActions():
+            if action.getId() == 'edit':
+                action.condition = Expression("foo")
+        # Test it twice
+        for i in range(2):
+            updateEditActionConditionForLocking(self.portal)
+            fti = self.types.getTypeInfo('Document')
+            for action in fti.listActions():
+                if action.getId() == 'edit':
+                    self.assertEqual(action.condition.text, 'foo')
+
+    def testAddOnFormUnloadRegistrationJS(self):
+        jsreg = self.portal.portal_javascripts
+        # unregister first
+        jsreg.unregisterResource('unlockOnFormUnload.js')
+        script_ids = jsreg.getResourceIds()
+        self.assertFalse('unlockOnFormUnload.js' in script_ids)
+        # Test it twice
+        for i in range(2):
+            addOnFormUnloadJS(self.portal)
+            script_ids = jsreg.getResourceIds()
+            self.assertTrue('unlockOnFormUnload.js' in script_ids)
+
+    def testUpdateTopicTitle(self):
+        topic = self.types.get('Topic')
+        topic.title = 'Old'
+        # Test it twice
+        for i in range(2):
+            updateTopicTitle(self.portal)
+            self.assertEqual(topic.title, 'Collection')
+
+    def testAddIntelligentText(self):
+        # Before the upgrade, the mime type and transforms of intelligent text
+        # are not available. They *are* here in a fresh site, so we may need
+        # to remove them first for testing. First we remove the transforms,
+        # as they depend on the mimetype being there.
+        missing_transforms = ["web_intelligent_plain_text_to_html",
+                              "html_to_web_intelligent_plain_text"]
+        ptr = self.portal.portal_transforms
+        current_transforms = ptr.objectIds()
+        for trans in missing_transforms:
+            if trans in current_transforms:
+                ptr.unregisterTransform(trans)
+        # Then we remove the mime type
+        mime_type = 'text/x-web-intelligent'
+        mtr = self.portal.mimetypes_registry
+        current_types = mtr.list_mimetypes()
+        if mime_type in current_types:
+            mtr.manage_delObjects((mime_type,))
+        # now all are gone:
+        self.assertFalse(mime_type in mtr.list_mimetypes())
+        self.assertFalse(set(ptr.objectIds()).issuperset(set(missing_transforms)))
+        # Test it twice
+        for i in range(2):
+            addIntelligentText(self.portal)
+            # now all are back:
+            self.assertTrue(mime_type in mtr.list_mimetypes())
+            self.assertTrue(set(ptr.objectIds()).issuperset(set(missing_transforms)))
+
+    def testInstallNewModifiers(self):
+        # ensure the new modifiers are installed
+        modifiers = self.portal.portal_modifier
+        self.assertTrue('AbortVersioningOfLargeFilesAndImages' in
+                                                          modifiers.objectIds())
+        modifiers.manage_delObjects(['AbortVersioningOfLargeFilesAndImages',
+                                     'SkipVersioningOfLargeFilesAndImages'])
+        self.assertFalse('AbortVersioningOfLargeFilesAndImages' in
+                                                          modifiers.objectIds())
+        installNewModifiers(self.portal)
+        self.assertTrue('AbortVersioningOfLargeFilesAndImages' in
+                                                          modifiers.objectIds())
+        self.assertTrue('SkipVersioningOfLargeFilesAndImages' in
+                                                          modifiers.objectIds())
+
+    def testInstallNewModifiersTwice(self):
+        # ensure that we get no errors when run twice
+        installNewModifiers(self.portal)
+        installNewModifiers(self.portal)
+
+    def testInstallNewModifiersDoesNotStompChanges(self):
+        # ensure that reinstalling doesn't kill customizations
+        modifiers = self.portal.portal_modifier
+        modifiers.AbortVersioningOfLargeFilesAndImages.max_size = 1000
+        installNewModifiers(self.portal)
+        self.assertEqual(modifiers.AbortVersioningOfLargeFilesAndImages.max_size,
+                         1000)
+
+    def testInstallNewModifiersNoTool(self):
+        # make sure there are no errors if the tool is missing
+        self.portal._delObject('portal_modifier')
+        installNewModifiers(self.portal)
+
+
+class TestFunctionalMigrations(FunctionalUpgradeTestCase):
+
+    def testBaseUpgrade(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testFullUpgrade(self):
+        self.importFile(__file__, 'test-full.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestMigrations_v2_5_x))
+    suite.addTest(makeSuite(TestMigrations_v3_0_Actions))
+    suite.addTest(makeSuite(TestMigrations_v3_0_alpha1))
+    suite.addTest(makeSuite(TestMigrations_v3_0_alpha2))
+    suite.addTest(makeSuite(TestMigrations_v3_0))
+    suite.addTest(makeSuite(TestFunctionalMigrations))
+    return suite
diff --git a/plone/app/upgrade/v31/tests.py b/plone/app/upgrade/v31/tests.py
new file mode 100644
index 0000000..01005b3
--- /dev/null
+++ b/plone/app/upgrade/v31/tests.py
@@ -0,0 +1,118 @@
+from borg.localrole.utils import replace_local_role_manager
+from zope.interface import noLongerProvides
+
+from Products.PlonePAS.interfaces.plugins import ILocalRolesPlugin
+
+from plone.app.upgrade.tests.base import FunctionalUpgradeTestCase
+from plone.app.upgrade.tests.base import MigrationTest
+
+from plone.app.upgrade.v31.betas import reinstallCMFPlacefulWorkflow
+
+
+class TestMigrations_v3_1(MigrationTest):
+
+    def afterSetUp(self):
+        self.qi = self.portal.portal_quickinstaller
+        self.wf = self.portal.portal_workflow
+        self.ps = self.portal.portal_setup
+
+    def testReinstallCMFPlacefulWorkflow(self):
+        try:
+            from Products.CMFPlacefulWorkflow.interfaces import IPlacefulMarker
+        except ImportError:
+            return
+        # first the product needs to be installed
+        self.qi.installProduct('CMFPlacefulWorkflow')
+        # Delete existing logs to prevent race condition
+        self.ps.manage_delObjects(self.ps.objectIds())
+        # We remove the new marker, to ensure it's added on reinstall
+        if IPlacefulMarker.providedBy(self.wf):
+            noLongerProvides(self.wf, IPlacefulMarker)
+        reinstallCMFPlacefulWorkflow(self.portal, [])
+        self.assertTrue(IPlacefulMarker.providedBy(self.wf))
+
+    def testReinstallCMFPlacefulWorkflowDoesNotInstall(self):
+        reinstallCMFPlacefulWorkflow(self.portal, [])
+        self.assertFalse(self.qi.isProductInstalled('CMFPlacefulWorkflow'))
+
+    def testReinstallCMFPlacefulWorkflowNoTool(self):
+        self.portal._delObject('portal_quickinstaller')
+        reinstallCMFPlacefulWorkflow(self.portal, [])
+
+    def testReplaceLocalRoleManager(self):
+        # first we replace the local role manager with the one from PlonePAS
+        uf = self.portal.acl_users
+        # deactivate and remove the borg plugin
+        uf.plugins.removePluginById('borg_localroles')
+        uf.manage_delObjects(['borg_localroles'])
+        # activate the standard plugin
+        uf.plugins.activatePlugin(ILocalRolesPlugin, 'local_roles')
+        # Bring things back to normal
+        replace_local_role_manager(self.portal)
+        plugins = uf.plugins.listPlugins(ILocalRolesPlugin)
+        self.assertEqual(len(plugins), 1)
+        self.assertEqual(plugins[0][0], 'borg_localroles')
+
+    def testReplaceLocalRoleManagerTwice(self):
+        # first we replace the local role manager with the one from PlonePAS
+        uf = self.portal.acl_users
+        # deactivate and remove the borg plugin
+        uf.plugins.removePluginById('borg_localroles')
+        uf.manage_delObjects(['borg_localroles'])
+        # activate the standard plugin
+        uf.plugins.activatePlugin(ILocalRolesPlugin, 'local_roles')
+        # run the upgrade twice
+        replace_local_role_manager(self.portal)
+        replace_local_role_manager(self.portal)
+        plugins = uf.plugins.listPlugins(ILocalRolesPlugin)
+        self.assertEqual(len(plugins), 1)
+        self.assertEqual(plugins[0][0], 'borg_localroles')
+
+    def testReplaceLocalRoleManagerNoPlugin(self):
+        # first we replace the local role manager with the one from PlonePAS
+        uf = self.portal.acl_users
+        # deactivate and remove the borg plugin
+        uf.plugins.removePluginById('borg_localroles')
+        uf.manage_delObjects(['borg_localroles'])
+        # delete the standard plugin
+        uf.manage_delObjects(['local_roles'])
+        # Run the upgrade, which shouldn't fail even if the expected
+        # plugin is missing
+        replace_local_role_manager(self.portal)
+        plugins = uf.plugins.listPlugins(ILocalRolesPlugin)
+        self.assertEqual(len(plugins), 1)
+        self.assertEqual(plugins[0][0], 'borg_localroles')
+
+    def testReplaceLocalRoleManagerNoPAS(self):
+        uf = self.portal.acl_users
+        # delete the plugin registry
+        uf._delObject('plugins')
+        replace_local_role_manager(self.portal)
+
+    def testReplaceLocalRoleManagerNoUF(self):
+        # Delete the user folder
+        replace_local_role_manager(self.portal)
+
+class TestFunctionalMigrations(FunctionalUpgradeTestCase):
+
+    def testBaseUpgrade(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testFullUpgrade(self):
+        self.importFile(__file__, 'test-full.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestMigrations_v3_1))
+    suite.addTest(makeSuite(TestFunctionalMigrations))
+    return suite
diff --git a/plone/app/upgrade/v32/tests.py b/plone/app/upgrade/v32/tests.py
new file mode 100644
index 0000000..bc0e7a1
--- /dev/null
+++ b/plone/app/upgrade/v32/tests.py
@@ -0,0 +1,50 @@
+from plone.app.upgrade.tests.base import FunctionalUpgradeTestCase
+from plone.app.upgrade.tests.base import MigrationTest
+from plone.app.upgrade.v32.betas import three1_beta1
+
+class TestMigrations_v3_2(MigrationTest):
+
+    def afterSetUp(self):
+        self.qi = self.portal.portal_quickinstaller
+        self.actions = self.portal.portal_actions
+        self.migration = self.portal.portal_migration
+
+    def testIterateActionsMigratedIfIterateInstalled(self):
+        self.qi.installProduct('plone.app.iterate')
+        self.actions.object_buttons.iterate_checkin.permissions = (
+            'Modify portal content',)
+        three1_beta1(self.portal)
+        self.assertEqual(
+            self.actions.object_buttons.iterate_checkin.permissions,
+            ('iterate : Check in content',))
+
+    def testIterateInstalledButActionMissing(self):
+        self.qi.installProduct('plone.app.iterate')
+        self.actions.object_buttons.manage_delObjects(['iterate_checkin'])
+        three1_beta1(self.portal)
+        self.assertFalse('iterate_checkin' in
+                    self.actions.object_buttons.objectIds())
+
+class TestFunctionalMigrations(FunctionalUpgradeTestCase):
+
+    def testBaseUpgrade(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testFullUpgrade(self):
+        self.importFile(__file__, 'test-full.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestMigrations_v3_2))
+    suite.addTest(makeSuite(TestFunctionalMigrations))
+    return suite
diff --git a/plone/app/upgrade/v33/tests.py b/plone/app/upgrade/v33/tests.py
new file mode 100644
index 0000000..01a5de8
--- /dev/null
+++ b/plone/app/upgrade/v33/tests.py
@@ -0,0 +1,93 @@
+from Products.CMFCore.utils import getToolByName
+
+from plone.app.upgrade.tests.base import FunctionalUpgradeTestCase
+from plone.app.upgrade.tests.base import MigrationTest
+from plone.app.upgrade.v33 import three2_three3
+
+class TestMigrations_v3_3(MigrationTest):
+
+    def afterSetUp(self):
+        self.types = self.portal.portal_types
+        self.properties = getToolByName(self.portal, 'portal_properties')
+
+    def _upgrade(self):
+        three2_three3(self.portal)
+
+    def testRedirectLinksProperty(self):
+        del self.properties.site_properties.redirect_links
+        self._upgrade()
+        self.assertEqual(True,
+            self.properties.site_properties.getProperty('redirect_links'))
+
+    def testLinkDefaultView(self):
+        self.types.Link.default_view = 'link_view'
+        self.types.Link.immediate_view = 'link_view'
+        self.types.Link.view_methods = ('link_view',)
+        self._upgrade()
+        self.assertEqual(self.types.Link.default_view, 'link_redirect_view')
+        self.assertEqual(self.types.Link.immediate_view, 'link_redirect_view')
+        self.assertEqual(self.types.Link.view_methods, ('link_redirect_view',))
+
+    def testCustomizedLinkDefaultView(self):
+        # but only change if old default was 'link_view'
+        self.types.Link.default_view = 'foobar'
+        self.types.Link.immediate_view = 'foobar'
+        self.types.Link.view_methods = ('foobar',)
+        self._upgrade()
+        self.assertEqual(self.types.Link.default_view, 'foobar')
+        self.assertEqual(self.types.Link.immediate_view, 'foobar')
+        self.assertEqual(self.types.Link.view_methods, ('foobar',))
+
+class TestFunctionalMigrations(FunctionalUpgradeTestCase):
+
+    def testBaseUpgrade(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testFullUpgrade(self):
+        self.importFile(__file__, 'test-full.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testFolderUpgrade(self):
+        from plone.folder.interfaces import IOrderableFolder
+        self.importFile(__file__, 'test-full.zexp')
+        # `portal_type` and `Type` can be checked before migration...
+        oldsite = getattr(self.app, self.site_id)
+        ids = 'news', 'events', 'Members'
+        for id in ids:
+            obj = oldsite[id]
+            self.assertEqual(obj.portal_type, 'Large Plone Folder')
+            self.assertEqual(obj.Type(), 'Large Folder')
+            brain, = oldsite.portal_catalog(getId=id)   # asserts only one
+            self.assertEqual(brain.portal_type, 'Large Plone Folder')
+            self.assertEqual(brain.Type, 'Large Folder')
+        # now let's migrate...
+        oldsite, result = self.migrate()
+        self.assertFalse(oldsite.portal_migration.needUpgrading())
+        # after migration `/news`, `/events` and `/Members` are based on
+        # `plone.(app.)folder`, but still have no ordering set...
+        for id in ids:
+            obj = oldsite[id]
+            self.assertTrue(IOrderableFolder.providedBy(obj),
+                '%s not orderable?' % id)
+            self.assertEqual(obj._ordering, 'unordered',
+                '%s has no `_ordering`?' % id)
+            self.assertEqual(obj.portal_type, 'Folder')
+            self.assertEqual(obj.Type(), 'Folder')
+            brain, = oldsite.portal_catalog(getId=id)   # asserts only one
+            self.assertEqual(brain.portal_type, 'Folder')
+            self.assertEqual(brain.Type, 'Folder')
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestMigrations_v3_3))
+    suite.addTest(makeSuite(TestFunctionalMigrations))
+    return suite
diff --git a/plone/app/upgrade/v40/tests.py b/plone/app/upgrade/v40/tests.py
new file mode 100644
index 0000000..c350619
--- /dev/null
+++ b/plone/app/upgrade/v40/tests.py
@@ -0,0 +1,666 @@
+import time
+
+from zope.component import getMultiAdapter
+from zope.component import getSiteManager
+from zope.component import getUtility
+from zope.component import queryUtility
+from zope.ramcache.interfaces.ram import IRAMCache
+
+from Products.CMFCore.ActionInformation import Action
+from Products.CMFCore.Expression import Expression
+from Products.CMFCore.utils import getToolByName
+from Products.MailHost.interfaces import IMailHost
+
+from plone.app.upgrade.utils import loadMigrationProfile
+from plone.app.upgrade.v40.alphas import _KNOWN_ACTION_ICONS
+from plone.app.upgrade.v40.alphas import migrateActionIcons
+from plone.app.upgrade.v40.alphas import migrateTypeIcons
+from plone.app.upgrade.v40.alphas import addOrReplaceRamCache
+from plone.app.upgrade.v40.alphas import changeWorkflowActorVariableExpression
+from plone.app.upgrade.v40.alphas import changeAuthenticatedResourcesCondition
+from plone.app.upgrade.v40.alphas import setupReferencebrowser
+from plone.app.upgrade.v40.alphas import migrateMailHost
+from plone.app.upgrade.v40.alphas import migrateFolders
+from plone.app.upgrade.v40.alphas import renameJoinFormFields
+from plone.app.upgrade.v40.alphas import updateLargeFolderType
+from plone.app.upgrade.v40.alphas import addRecursiveGroupsPlugin
+from plone.app.upgrade.v40.alphas import cleanUpClassicThemeResources
+from plone.app.upgrade.v40.alphas import migrateStaticTextPortlets
+from plone.app.upgrade.v40.betas import repositionRecursiveGroupsPlugin
+from plone.app.upgrade.v40.betas import updateIconMetadata
+from plone.app.upgrade.v40.betas import removeLargePloneFolder
+from plone.app.upgrade.tests.base import MigrationTest
+
+from plone.portlet.static import static
+from plone.portlets.interfaces import IPortletAssignmentMapping
+from plone.portlets.interfaces import IPortletAssignmentSettings
+from plone.portlets.interfaces import IPortletManager
+
+
+class FakeSecureMailHost(object):
+
+    meta_type = 'Secure Mail Host'
+    id = 'MailHost'
+    title = 'Fake MailHost'
+    smtp_host = 'smtp.example.com'
+    smtp_port = 587
+    smtp_userid='me'
+    smtp_pass='secret'
+    smtp_notls=False
+
+    def manage_fixupOwnershipAfterAdd(self):
+        pass
+
+
+class TestMigrations_v4_0alpha1(MigrationTest):
+
+    profile = "profile-plone.app.upgrade.v40:3-4alpha1"
+
+    def afterSetUp(self):
+        self.atool = getToolByName(self.portal, 'portal_actions')
+        self.cptool = getToolByName(self.portal, 'portal_controlpanel')
+        self.wftool = getToolByName(self.portal, 'portal_workflow')
+        self.csstool = getToolByName(self.portal, 'portal_css')
+        self.jstool = getToolByName(self.portal, 'portal_javascripts')
+
+        if 'portal_actionicons' not in self.portal:
+            from plone.app.upgrade.bbb import ActionIconsTool
+            self.portal._setObject('portal_actionicons', ActionIconsTool())
+        self.aitool = self.portal.portal_actionicons
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        self.setRoles(['Manager'])
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testMigrateActionIcons(self):
+        _KNOWN_ACTION_ICONS['object_buttons'].extend(['test_id', 'test2_id'])
+        self.aitool.addActionIcon(
+            category='object_buttons',
+            action_id='test_id',
+            icon_expr='test.gif',
+            title='Test my icon',
+            )
+        self.aitool.addActionIcon(
+            category='object_buttons',
+            action_id='test2_id',
+            icon_expr='python:context.getIcon()',
+            title='Test my second icon',
+            )
+        test_action = Action('test_id',
+            title='Test me',
+            description='',
+            url_expr='',
+            icon_expr='',
+            available_expr='',
+            permissions=('View', ),
+            visible = True)
+        test2_action = Action('test2_id',
+            title='Test me too',
+            description='',
+            url_expr='',
+            icon_expr='',
+            available_expr='',
+            permissions=('View', ),
+            visible = True)
+
+        object_buttons = self.atool.object_buttons
+        if getattr(object_buttons, 'test_id', None) is None:
+            object_buttons._setObject('test_id', test_action)
+        if getattr(object_buttons, 'test2_id', None) is None:
+            object_buttons._setObject('test2_id', test2_action)
+
+        self.assertEqual(object_buttons.test_id.icon_expr, '')
+        self.assertEqual(object_buttons.test2_id.icon_expr, '')
+        # Test it twice
+        for i in range(2):
+            migrateActionIcons(self.portal)
+            icons = [ic._action_id for ic in self.aitool.listActionIcons()]
+            self.assertFalse('test_id' in icons)
+            self.assertFalse('test2_id' in icons)
+            self.assertEqual(object_buttons.test_id.icon_expr,
+                             'string:$portal_url/test.gif')
+            self.assertEqual(object_buttons.test2_id.icon_expr,
+                             'python:context.getIcon()')
+
+    def testMigrateControlPanelActionIcons(self):
+        _KNOWN_ACTION_ICONS['controlpanel'].extend(['test_id'])
+        self.aitool.addActionIcon(
+            category='controlpanel',
+            action_id='test_id',
+            icon_expr='test.gif',
+            title='Test my icon',
+            )
+
+        self.cptool.registerConfiglet(
+            id='test_id',
+            name='Test Configlet',
+            action='string:${portal_url}/test',
+            permission='Manage portal',
+            category='Plone',
+            visible=True,
+            appId='',
+            icon_expr='',
+            )
+
+        action = self.cptool.getActionObject('Plone/test_id')
+        self.assertEqual(action.getIconExpression(), '')
+        # Test it twice
+        for i in range(2):
+            migrateActionIcons(self.portal)
+            icons = [ic._action_id for ic in self.aitool.listActionIcons()]
+            self.assertFalse('test_id' in icons)
+            self.assertEqual(action.getIconExpression(),
+                             'string:$portal_url/test.gif')
+
+    def testContentTypeIconExpressions(self):
+        """
+        FTIs should now be using icon_expr instead of content_icon.
+        (The former caches the expression object.)
+        """
+        tt = getToolByName(self.portal, "portal_types")
+        tt.Document.icon_expr = None
+        loadMigrationProfile(self.portal, self.profile, ('typeinfo', ))
+        self.assertEqual(tt.Document.icon_expr,
+                         "string:${portal_url}/document_icon.png")
+
+    def testMigrateTypeIcons(self):
+        """
+        FTIs having content_icon should be upgraded to icon_expr.
+        """
+        tt = getToolByName(self.portal, "portal_types")
+        del tt.Document.icon_expr
+        tt.Document.content_icon = 'document_icon.gif'
+        migrateTypeIcons(self.portal)
+        self.assertEqual(tt.Document.icon_expr,
+                         "string:${portal_url}/document_icon.gif")
+        self.assertTrue(hasattr(tt.Document, 'icon_expr_object'))
+
+        #Don't upgrade if there is already an icon_expr.
+        tt.Document.icon_expr = "string:${portal_url}/document_icon.png"
+        tt.Document.content_icon = 'document_icon.gif'
+        migrateTypeIcons(self.portal)
+        self.assertEqual(tt.Document.icon_expr,
+                         "string:${portal_url}/document_icon.png")
+
+    def testPngContentIcons(self):
+        tt = getToolByName(self.portal, "portal_types")
+        tt.Document.icon_expr = "string:${portal_url}/document_icon.gif"
+        loadMigrationProfile(self.portal, self.profile, ('typeinfo', ))
+        self.assertEqual(tt.Document.icon_expr,
+            "string:${portal_url}/document_icon.png")
+
+    def testAddRAMCache(self):
+        # Test it twice
+        for i in range(2):
+            sm = getSiteManager()
+            sm.unregisterUtility(provided=IRAMCache)
+            util = queryUtility(IRAMCache)
+            self.assertEqual(util.maxAge, 86400)
+            addOrReplaceRamCache(self.portal)
+            util = queryUtility(IRAMCache)
+            self.assertEqual(util.maxAge, 3600)
+
+    def testReplaceOldRamCache(self):
+        sm = getSiteManager()
+
+        # Test it twice
+        for i in range(2):
+            sm.unregisterUtility(provided=IRAMCache)
+            from zope.app.cache.interfaces.ram import IRAMCache as OldIRAMCache
+            from zope.app.cache.ram import RAMCache as OldRAMCache
+            sm.registerUtility(factory=OldRAMCache, provided=OldIRAMCache)
+
+            addOrReplaceRamCache(self.portal)
+            util = queryUtility(IRAMCache)
+            self.assertEqual(util.maxAge, 3600)
+
+    def testChangeWorkflowActorVariableExpression(self):
+        self.wftool.intranet_folder_workflow.variables.actor.setProperties('')
+
+        for i in range(2):
+            changeWorkflowActorVariableExpression(self.portal)
+            wf = self.wftool.intranet_folder_workflow
+            self.assertEqual(wf.variables.actor.getDefaultExprText(),
+                             'user/getId')
+            wf = self.wftool.one_state_workflow
+            self.assertEqual(wf.variables.actor.getDefaultExprText(),
+                             'user/getId')
+            wf = self.wftool.simple_publication_workflow
+            self.assertEqual(wf.variables.actor.getDefaultExprText(),
+                             'user/getId')
+
+        # make sure it doesn't break if the workflow is missing
+        wf = self.wftool.intranet_folder_workflow
+        self.wftool._delOb('intranet_folder_workflow')
+        changeWorkflowActorVariableExpression(self.portal)
+        self.wftool._setOb('intranet_folder_workflow', wf)
+
+    def testChangeAuthenticatedResourcesCondition(self):
+        # make sure CSS resource is updated
+        res = self.csstool.getResource('member.css')
+        if res is None:
+            return
+        res.setAuthenticated(False)
+        res.setExpression('not: portal/portal_membership/isAnonymousUser')
+        # test it twice
+        for i in range(2):
+            changeAuthenticatedResourcesCondition(self.portal)
+            self.assertEqual(res.getExpression(), '')
+            self.assertTrue(res.getAuthenticated())
+
+        # make sure it doesn't update it if the expression has been
+        # customized
+        res.setExpression('python:False')
+        changeAuthenticatedResourcesCondition(self.portal)
+        self.assertEqual(res.getExpression(), 'python:False')
+
+    def testAddedUseEmailProperty(self):
+        tool = getToolByName(self.portal, 'portal_properties')
+        sheet = getattr(tool, 'site_properties')
+        #self.assertEqual(sheet.getProperty('use_email_as_login'), False)
+        self.removeSiteProperty('use_email_as_login')
+        loadMigrationProfile(self.portal, self.profile, ('propertiestool', ))
+        self.assertEqual(sheet.getProperty('use_email_as_login'), False)
+
+    def testReplaceReferencebrowser(self):
+        self.setRoles(['Manager'])
+        skins_tool = getToolByName(self.portal, 'portal_skins')
+        if 'referencebrowser' not in skins_tool:
+            return
+        sels = skins_tool._getSelections()
+        for skinname, layer in sels.items():
+            layers = layer.split(',')
+            self.assertFalse('ATReferenceBrowserWidget' in layers)
+            layers.remove('referencebrowser')
+            new_layers = ','.join(layers)
+            sels[skinname] = new_layers
+
+        from .alphas import threeX_alpha1
+        threeX_alpha1(self.portal)
+        setupReferencebrowser(self.portal)
+
+        sels = skins_tool._getSelections()
+        for skinname, layer in sels.items():
+            layers = layer.split(',')
+            self.assertTrue('referencebrowser' in layers)
+
+    def testInstallNewDependencies(self):
+        from plone.app.upgrade.v40.alphas import threeX_alpha1
+        self.setRoles(['Manager'])
+        # test for running the TinyMCE profile by checking for the skin layer
+        # it installs (the profile is marked as noninstallable, so we can't
+        # ask the quick installer)
+        skins_tool = getToolByName(self.portal, 'portal_skins')
+        if 'tinymce' not in skins_tool:
+            # Skip test in new Plones that don't have tinymce to begin with
+            return
+        del skins_tool['tinymce']
+        for i in range(2):
+            threeX_alpha1(self.portal)
+            self.assertTrue('tinymce' in skins_tool)
+            # sleep to avoid a GS log filename collision :-o
+            time.sleep(1)
+
+    def testReplaceSecureMailHost(self):
+        portal = self.portal
+        sm = getSiteManager(context=portal)
+        # try it with an unmodified site to ensure it doesn't give any errors
+        migrateMailHost(portal.portal_setup)
+        portal._delObject('MailHost')
+        # Run it with our MailHost replaced
+        portal._setObject('MailHost', FakeSecureMailHost())
+        self.assertEqual(portal.MailHost.meta_type, 'Secure Mail Host')
+        sm.unregisterUtility(provided=IMailHost)
+        sm.registerUtility(portal.MailHost, provided=IMailHost)
+        migrateMailHost(portal)
+        new_mh = portal.MailHost
+        self.assertEqual(new_mh.meta_type, 'Mail Host')
+        self.assertEqual(new_mh.title, 'Fake MailHost')
+        self.assertEqual(new_mh.smtp_host, 'smtp.example.com')
+        self.assertEqual(new_mh.smtp_port, 587)
+        self.assertEqual(new_mh.smtp_uid, 'me')
+        self.assertEqual(new_mh.smtp_pwd, 'secret')
+        #Force TLS is always false, because SMH has no equivalent option
+        self.assertEqual(new_mh.force_tls, False)
+
+    def testFolderMigration(self):
+        from plone.app.folder.tests.content import create
+        from plone.app.folder.tests.test_migration import reverseMigrate
+        from plone.app.folder.tests.test_migration import isSaneBTreeFolder
+        # create a folder in an unmigrated state & check it's broken...
+        folder = create('Folder', self.portal, 'foo', title='Foo')
+        reverseMigrate(self.portal)
+        self.assertFalse(isSaneBTreeFolder(self.portal.foo))
+        # now run the migration step...
+        migrateFolders(self.portal)
+        folder = self.portal.foo
+        self.assertTrue(isSaneBTreeFolder(folder))
+        self.assertEqual(folder.getId(), 'foo')
+        self.assertEqual(folder.Title(), 'Foo')
+
+    def testMigrateStaticTextPortlets(self):
+        class HiddenAssignment(static.Assignment):
+            hide = True
+
+        self.setRoles(["Manager"])
+        self.portal.invokeFactory('Folder', id="statictest")
+        folder = self.portal['statictest']
+
+        manager = getUtility(
+                IPortletManager, name='plone.rightcolumn',
+                context=folder)
+        assignments = getMultiAdapter(
+                (folder, manager), IPortletAssignmentMapping)
+        hidden_portlet = HiddenAssignment()
+        visible_portlet = static.Assignment()
+        assignments['hidden'] = hidden_portlet
+        assignments['visible'] = visible_portlet
+
+        migrateStaticTextPortlets(self.portal)
+
+        self.assertFalse(
+                IPortletAssignmentSettings(hidden_portlet).get(
+                        'visible', True))
+        self.assertTrue(
+                IPortletAssignmentSettings(visible_portlet).get(
+                        'visible', True))
+
+
+
+class TestMigrations_v4_0alpha2(MigrationTest):
+
+    def testMigrateJoinFormFields(self):
+        ptool = getToolByName(self.portal, 'portal_properties')
+        sheet = getattr(ptool, 'site_properties')
+        self.removeSiteProperty('user_registration_fields')
+        self.addSiteProperty('join_form_fields')
+        sheet.join_form_fields = (
+            'username', 'password', 'email', 'mail_me', 'groups')
+        renameJoinFormFields(self)
+        self.assertEqual(sheet.hasProperty('join_form_fields'), False)
+        self.assertEqual(sheet.hasProperty('user_registration_fields'), True)
+        self.assertEqual(sheet.getProperty('user_registration_fields'),
+                         ('username', 'password', 'email', 'mail_me'))
+
+
+class TestMigrations_v4_0alpha3(MigrationTest):
+
+    profile = "profile-plone.app.upgrade.v40:4alpha2-4alpha3"
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testJoinActionURL(self):
+        self.portal.portal_actions.user.join.url_expr = 'foo'
+        loadMigrationProfile(self.portal, self.profile, ('actions', ))
+        self.assertEqual(self.portal.portal_actions.user.join.url_expr,
+            'string:${globals_view/navigationRootUrl}/@@register')
+
+
+class TestMigrations_v4_0alpha5(MigrationTest):
+
+    profile = "profile-plone.app.upgrade.v40:4alpha4-4alpha5"
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testMigrateLargeFolderType(self):
+        portal = self.portal
+        catalog = getToolByName(portal, 'portal_catalog')
+        # set things up in the old way...
+        ids = 'news', 'events', 'Members'
+        for id in ids:
+            obj = portal[id]
+            obj._setPortalTypeName('Large Plone Folder')
+            obj.reindexObject()
+            self.assertEqual(obj.portal_type, 'Large Plone Folder')
+            # Type falls back to meta_type since there's no
+            # Large Plone Folder FTI
+            self.assertEqual(obj.Type(), 'ATFolder')
+            brain, = catalog(getId=id)
+            self.assertEqual(brain.portal_type, 'Large Plone Folder')
+            self.assertEqual(brain.Type, 'ATFolder')
+        # migrate & check again...
+        updateLargeFolderType(self.portal)
+        for id in ids:
+            obj = portal[id]
+            self.assertEqual(obj.portal_type, 'Folder')
+            self.assertEqual(obj.Type(), 'Folder')
+            brain, = catalog(getId=id)
+            self.assertEqual(brain.portal_type, 'Folder')
+            self.assertEqual(brain.Type, 'Folder')
+
+    def testAddRecursiveGroupsPlugin(self):
+        acl = getToolByName(self.portal, 'acl_users')
+        addRecursiveGroupsPlugin(self.portal)
+        self.assertTrue('recursive_groups' in acl)
+        # Now that we have an existing one, let's make sure it's handled
+        # properly if this migration is run again.
+        addRecursiveGroupsPlugin(self.portal)
+        self.assertTrue('recursive_groups' in acl)
+
+    def testClassicThemeResourcesCleanUp(self):
+        """Test that the plonetheme.classic product doesn't have any
+        registered CSS resource in its metadata after migration.
+        """
+        portal = self.portal
+        qi = getToolByName(portal, 'portal_quickinstaller')
+        qi.installProduct('plonetheme.classic')
+        classictheme = qi['plonetheme.classic']
+        classictheme.resources_css = ['something'] # add a random resource
+        cleanUpClassicThemeResources(portal)
+        self.assertEqual(classictheme.resources_css, [])
+
+    def testGetObjPositionInParentIndex(self):
+        from plone.app.folder.nogopip import GopipIndex
+        catalog = self.portal.portal_catalog
+        catalog.delIndex('getObjPositionInParent')
+        catalog.addIndex('getObjPositionInParent', 'FieldIndex')
+        self.assertFalse(isinstance(catalog.Indexes['getObjPositionInParent'],
+            GopipIndex))
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue('getObjPositionInParent' in catalog.indexes())
+        self.assertTrue(isinstance(catalog.Indexes['getObjPositionInParent'],
+            GopipIndex))
+
+    def testGetEventTypeIndex(self):
+        catalog = self.portal.portal_catalog
+        catalog.addIndex('getEventType', 'KeywordIndex')
+        self.assertTrue('getEventType' in catalog.indexes())
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertFalse('getEventType' in catalog.indexes())
+
+
+class TestMigrations_v4_0beta1(MigrationTest):
+
+    profile = "profile-plone.app.upgrade.v40:4alpha5-4beta1"
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testRepositionRecursiveGroupsPlugin(self):
+        # Ensure that the recursive groups plugin is moved to the bottom
+        # of the IGroups plugins list, if active.
+        addRecursiveGroupsPlugin(self.portal)
+        # Plugin is installed, but not active, run against this state.
+        from Products.PluggableAuthService.interfaces.plugins import \
+            IGroupsPlugin
+        acl = getToolByName(self.portal, 'acl_users')
+        plugins = acl.plugins
+        # The plugin was originally moved to the top of the list of
+        # IGroupsPlugin plugins by p.a.controlpanel. Recreate that state.
+        while (plugins.getAllPlugins('IGroupsPlugin')['active'].index(
+               'recursive_groups') > 0):
+            plugins.movePluginsUp(IGroupsPlugin, ['recursive_groups'])
+
+        active_groups = plugins.getAllPlugins('IGroupsPlugin')['active']
+        self.assertEqual(active_groups[0], 'recursive_groups')
+
+        # Rerun the migration, making sure that it's now the last item in the
+        # list of IGroupsPlugin plugins.
+        repositionRecursiveGroupsPlugin(self.portal)
+        active_groups = plugins.getAllPlugins('IGroupsPlugin')['active']
+        self.assertEqual(active_groups[-1], 'recursive_groups')
+
+
+class TestMigrations_v4_0beta2(MigrationTest):
+
+    profile = "profile-plone.app.upgrade.v40:4beta1-4beta2"
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testCoreContentIconExprCleared(self):
+        types = getToolByName(self.portal, 'portal_types')
+        catalog = getToolByName(self.portal, 'portal_catalog')
+        # Reinstate the now-empty icon expression for the Document type
+        doc_icon_expr = Expression('string:${portal_url}/document_icon.png')
+        types['Document'].icon_expr_object = doc_icon_expr
+        front = self.portal['front-page']
+        catalog.reindexObject(front)
+        old_modified = front.modified()
+        # Make sure the getIcon metadata column shows the "original" value
+        brains = catalog(id='front-page')
+        self.assertEqual(brains[0].getIcon, 'document_icon.png')
+        # Run the migration
+        loadMigrationProfile(self.portal, self.profile)
+        updateIconMetadata(self.portal)
+        # The getIcon column should now be empty
+        self.assertEqual(catalog(id='front-page')[0].getIcon, '')
+        self.assertEqual(front.modified(), old_modified)
+
+
+class TestMigrations_v4_0beta4(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4beta3-4beta4'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testRemoveLargePloneFolder(self):
+        # re-create pre-migration settings
+        ptool = self.portal.portal_properties
+        nav_props = ptool.navtree_properties
+        l = list(nav_props.parentMetaTypesNotToQuery)
+        nav_props.parentMetaTypesNotToQuery = l + ['Large Plone Folder']
+        site_props = ptool.site_properties
+        l = list(site_props.typesLinkToFolderContentsInFC)
+        site_props.typesLinkToFolderContentsInFC = l + ['Large Plone Folder']
+        temp_folder_fti = self.portal.portal_types['TempFolder']
+        l = list(temp_folder_fti.allowed_content_types)
+        temp_folder_fti.allowed_content_types = l + ['Large Plone Folder']
+        l = set(self.portal.portal_factory.getFactoryTypes())
+        l.add('Large Plone Folder')
+        ftool = self.portal.portal_factory
+        ftool.manage_setPortalFactoryTypes(listOfTypeIds=list(l))
+
+        for i in xrange(2):
+            loadMigrationProfile(self.portal, self.profile)
+            removeLargePloneFolder(self.portal)
+            self.assertFalse('Large Plone Folder' in self.portal.portal_types)
+            self.assertFalse('Large Plone Folder' in
+                        temp_folder_fti.allowed_content_types)
+            self.assertTrue('Folder' in temp_folder_fti.allowed_content_types)
+            self.assertFalse('Large Plone Folder' in ftool.getFactoryTypes())
+            self.assertTrue('Folder' in ftool.getFactoryTypes())
+            self.assertFalse('Large Plone Folder' in
+                        nav_props.parentMetaTypesNotToQuery)
+            self.assertTrue('TempFolder' in
+                            nav_props.parentMetaTypesNotToQuery)
+            self.assertFalse('Large Plone Folder' in
+                        site_props.typesLinkToFolderContentsInFC)
+            self.assertTrue('Folder' in
+                            site_props.typesLinkToFolderContentsInFC)
+            # sleep to avoid a GS log filename collision :-o
+            time.sleep(1)
+
+
+class TestMigrations_v4_0beta5(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4beta4-4beta5'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+
+class TestMigrations_v4_0rc1(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4beta5-4rc1'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4rc1-4final'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0_1(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4.0-4.0.1'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0_2(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4.0.1-4.0.2'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0_3(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4.0.2-4.0.3'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0_4(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4.0.3-4.0.4'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0_5(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4.0.4-4.0.5'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+
+def test_suite():
+    from unittest import defaultTestLoader
+    return defaultTestLoader.loadTestsFromName(__name__)
diff --git a/plone/app/upgrade/v41/tests.py b/plone/app/upgrade/v41/tests.py
new file mode 100644
index 0000000..4570a4c
--- /dev/null
+++ b/plone/app/upgrade/v41/tests.py
@@ -0,0 +1,19 @@
+import unittest
+from Products.ZCatalog.ZCatalog import ZCatalog
+from Products.ZCTextIndex.ZCTextIndex import ZCTextIndex, PLexicon
+from Products.ZCTextIndex.OkapiIndex import OkapiIndex
+
+
+class MigrationUnitTests(unittest.TestCase):
+
+    def test_fixOkapiIndexes(self):
+        catalog = ZCatalog('catalog')
+        catalog.lexicon = PLexicon('lexicon')
+        catalog.addIndex('test',
+            ZCTextIndex('test', index_factory=OkapiIndex,
+                        caller=catalog, lexicon_id='lexicon'))
+        catalog.Indexes['test'].index._totaldoclen = -1000
+
+        from plone.app.upgrade.v41.final import fixOkapiIndexes
+        fixOkapiIndexes(catalog)
+        self.assertEqual(0L, catalog.Indexes['test'].index._totaldoclen())
diff --git a/plone/app/upgrade/v42/tests.py b/plone/app/upgrade/v42/tests.py
new file mode 100644
index 0000000..0ff501e
--- /dev/null
+++ b/plone/app/upgrade/v42/tests.py
@@ -0,0 +1,20 @@
+from plone.app.upgrade.tests.base import MigrationTest
+from plone.app.upgrade.utils import loadMigrationProfile
+
+
+class TestMigrations_v4_2beta1(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v42:to42beta1'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testAddSiteAdminToKeywordRoles(self):
+        ptool = self.portal.portal_properties
+        site_props = ptool.site_properties
+        site_props.allowRolesToAddKeywords = ('Manager', 'Reviewer')
+        loadMigrationProfile(self.portal, self.profile)
+        roles = site_props.allowRolesToAddKeywords
+        self.assertEqual(roles, ('Manager', 'Reviewer', 'Site Administrator'))
diff --git a/setup.py b/setup.py
index c500ee7..1dd33a5 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 from setuptools import setup, find_packages
 
-version = '2.0.dev0'
+version = '1.3.9.dev0'
 
 setup(name='plone.app.upgrade',
       version=version,
@@ -10,12 +10,10 @@
       classifiers=[
           "Environment :: Web Environment",
           "Framework :: Plone",
-          "Framework :: Plone :: 5.0",
           "Framework :: Zope2",
           "License :: OSI Approved :: GNU General Public License (GPL)",
           "Operating System :: OS Independent",
           "Programming Language :: Python",
-          "Programming Language :: Python :: 2.7",
         ],
       keywords='Plone upgrade migration',
       author='Plone Foundation',
@@ -30,13 +28,13 @@
         test=[
             'Products.CMFPlacefulWorkflow',
             'Products.CMFQuickInstallerTool',
+            'Products.PloneTestCase',
             'plone.contentrules',
             'plone.app.i18n',
             'plone.app.iterate',
             'plone.app.openid',
             'plone.app.redirector',
             'plone.app.viewletmanager',
-            'plone.app.testing',
             'plone.app.theming',
         ]
       ),


Repository: plone.app.upgrade
Branch: refs/heads/master
Date: 2015-01-07T14:28:14+01:00
Author: Timo Stollenwerk (tisto) <tisto at plone.org>
Commit: https://github.com/plone/plone.app.upgrade/commit/8de8c57b74520df8d7241ea63109cd1271f39acb

Merge pull request #23 from plone/revert-22-tomgross-noptc

Revert "Ported to plone.app.testing"

Files changed:
A plone/app/upgrade/v25/tests.py
A plone/app/upgrade/v30/tests.py
A plone/app/upgrade/v31/tests.py
A plone/app/upgrade/v32/tests.py
A plone/app/upgrade/v33/tests.py
A plone/app/upgrade/v40/tests.py
A plone/app/upgrade/v41/tests.py
A plone/app/upgrade/v42/tests.py
M CHANGES.rst
M plone/app/upgrade/tests/base.py
M setup.py

diff --git a/CHANGES.rst b/CHANGES.rst
index 6e27679..1bf846a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,7 +1,7 @@
 Changelog
 =========
 
-2.0 (unreleased)
+1.3.9 (unreleased)
 ------------------
 
 - Add upgrade step for the security control panel.
@@ -13,11 +13,6 @@ Changelog
 - Add upgrade steps for markup control panel.
   [thet]
 
-- Ported tests to plone.app.testing
-  [tomgross]
-
-- Removed all tests for migrations older than 4.3 -> 5.0
-  [tomgross]
 
 1.3.8 (2014-11-01)
 ------------------
diff --git a/plone/app/upgrade/tests/base.py b/plone/app/upgrade/tests/base.py
index f25f530..840416c 100644
--- a/plone/app/upgrade/tests/base.py
+++ b/plone/app/upgrade/tests/base.py
@@ -9,7 +9,10 @@
 import transaction
 from zope.site.hooks import setSite
 
-from plone.app.testing.bbb import PloneTestCase
+from Testing.ZopeTestCase.sandbox import Sandboxed
+from Products.PloneTestCase.layer import PloneSiteLayer
+from Products.PloneTestCase.ptc import PloneTestCase
+from Products.PloneTestCase.ptc import setupPloneSite
 
 from Products.CMFCore.interfaces import IActionCategory
 from Products.CMFCore.interfaces import IActionInfo
@@ -17,6 +20,8 @@
 from Products.CMFCore.tests.base.testcase import WarningInterceptor
 from Products.GenericSetup.context import TarballImportContext
 
+setupPloneSite()
+
 
 class MigrationTest(PloneTestCase):
 
@@ -106,10 +111,21 @@ def removeSkinLayer(self, layer, skin='Plone Default'):
             skins.addSkinSelection(skin, ','.join(path))
 
 
+class FunctionalUpgradeLayer(PloneSiteLayer):
+
+    @classmethod
+    def setUp(cls):
+        pass
+
+    @classmethod
+    def tearDown(cls):
+        pass
+
 
-class FunctionalUpgradeTestCase(PloneTestCase, WarningInterceptor):
+class FunctionalUpgradeTestCase(Sandboxed, PloneTestCase, WarningInterceptor):
 
     _setup_fixture = 0
+    layer = FunctionalUpgradeLayer
     site_id = 'test'
 
     def afterSetUp(self):
diff --git a/plone/app/upgrade/v25/tests.py b/plone/app/upgrade/v25/tests.py
new file mode 100644
index 0000000..036e8b5
--- /dev/null
+++ b/plone/app/upgrade/v25/tests.py
@@ -0,0 +1,124 @@
+from Products.CMFPlone.UnicodeSplitter import Splitter
+from Products.CMFPlone.UnicodeSplitter import CaseNormalizer
+
+from plone.app.upgrade.tests.base import FunctionalUpgradeTestCase
+from plone.app.upgrade.tests.base import MigrationTest
+from plone.app.upgrade.utils import loadMigrationProfile
+
+from plone.app.upgrade.v25 import fixupPloneLexicon
+from plone.app.upgrade.v25 import setLoginFormInCookieAuth
+from plone.app.upgrade.v25 import addMissingMimeTypes
+
+
+class TestMigrations_v2_5_0(MigrationTest):
+
+    def afterSetUp(self):
+        self.profile = 'profile-plone.app.upgrade.v25:2.5final-2.5.1'
+        self.actions = self.portal.portal_actions
+        self.css = self.portal.portal_css
+
+    def tesFixObjDeleteAction(self):
+        # Prepare delete actions test
+        editActions = ('delete',)
+        for a in editActions:
+            self.removeActionFromTool(a, category='object_buttons')
+        loadMigrationProfile(self.portal, self.profile, ('actions', ))
+        # delete action tests
+        actions = [x.id for x in self.actions.object_buttons.listActions()
+                   if x.id in editActions]
+        # check that all of our deleted actions are now present
+        for a in editActions:
+            self.assertTrue(a in actions)
+        # ensure that they are present only once
+        self.assertEqual(len(editActions), len(actions))
+
+    def testFixupPloneLexicon(self):
+        # Should update the plone_lexicon pipeline
+        lexicon = self.portal.portal_catalog.plone_lexicon
+        lexicon._pipeline = (object(), object())
+        # Test it twice
+        for i in range(2):
+            fixupPloneLexicon(self.portal)
+            self.assertTrue(isinstance(lexicon._pipeline[0], Splitter))
+            self.assertTrue(isinstance(lexicon._pipeline[1], CaseNormalizer))
+
+
+class TestMigrations_v2_5_1(MigrationTest):
+
+    def afterSetUp(self):
+        self.actions = self.portal.portal_actions
+        self.memberdata = self.portal.portal_memberdata
+        self.catalog = self.portal.portal_catalog
+        self.skins = self.portal.portal_skins
+        self.types = self.portal.portal_types
+        self.workflow = self.portal.portal_workflow
+        self.css = self.portal.portal_css
+
+    def testSetLoginFormInCookieAuth(self):
+        setLoginFormInCookieAuth(self.portal)
+        cookie_auth = self.portal.acl_users.credentials_cookie_auth
+        self.assertEqual(cookie_auth.getProperty('login_path'),
+                             'require_login')
+
+    def testSetLoginFormNoCookieAuth(self):
+        # Shouldn't error
+        uf = self.portal.acl_users
+        uf._delOb('credentials_cookie_auth')
+        setLoginFormInCookieAuth(self.portal)
+
+    def testSetLoginFormAlreadyChanged(self):
+        # Shouldn't change the value if it's not the default
+        cookie_auth = self.portal.acl_users.credentials_cookie_auth
+        cookie_auth.manage_changeProperties(login_path='foo')
+        setLoginFormInCookieAuth(self.portal)
+        self.assertTrue(cookie_auth.getProperty('login_path') != 'require_login')
+
+class TestMigrations_v2_5_2(MigrationTest):
+
+    def afterSetUp(self):
+        self.mimetypes = self.portal.mimetypes_registry
+
+    def testMissingMimeTypes(self):
+        # we're testing for 'text/x-web-markdown' and 'text/x-web-textile'
+        missing_types = ['text/x-web-markdown', 'text/x-web-textile']
+        # since we're running a full 2.5.4 instance in this test, the missing
+        # types might in fact already be there:
+        current_types = self.mimetypes.list_mimetypes()
+        types_to_delete = []
+        for mtype in missing_types:
+            if mtype in current_types:
+                types_to_delete.append(mtype)
+        if types_to_delete:
+            self.mimetypes.manage_delObjects(types_to_delete)
+        # now they're gone:
+        self.assertFalse(set(self.mimetypes.list_mimetypes()).issuperset(set(missing_types)))
+        addMissingMimeTypes(self.portal)
+        # now they're back:
+        self.assertTrue(set(self.mimetypes.list_mimetypes()).issuperset(set(missing_types)))
+
+
+class TestFunctionalMigrations(FunctionalUpgradeTestCase):
+
+    def testUpgrade(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testDCMIStorageUpdated(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        dcmi = getattr(oldsite.portal_metadata, 'DCMI', None)
+        self.assertFalse(dcmi is None)
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestMigrations_v2_5_0))
+    suite.addTest(makeSuite(TestMigrations_v2_5_1))
+    suite.addTest(makeSuite(TestMigrations_v2_5_2))
+    suite.addTest(makeSuite(TestFunctionalMigrations))
+    return suite
diff --git a/plone/app/upgrade/v30/tests.py b/plone/app/upgrade/v30/tests.py
new file mode 100644
index 0000000..9325d0d
--- /dev/null
+++ b/plone/app/upgrade/v30/tests.py
@@ -0,0 +1,1109 @@
+from Acquisition import aq_base
+
+from five.localsitemanager.registry import FiveVerifyingAdapterLookup
+
+from plone.contentrules.engine.interfaces import IRuleStorage
+from plone.app.i18n.locales.interfaces import IContentLanguages
+from plone.app.i18n.locales.interfaces import ICountries
+from plone.app.i18n.locales.interfaces import IMetadataLanguages
+from plone.app.portlets import portlets
+from plone.app.redirector.interfaces import IRedirectionStorage
+from plone.portlets.interfaces import IPortletManager
+from plone.portlets.interfaces import IPortletAssignmentMapping
+from plone.portlets.interfaces import ILocalPortletAssignmentManager
+from plone.portlets.constants import CONTEXT_CATEGORY as CONTEXT_PORTLETS
+
+from zope.location.interfaces import ISite
+from zope.component import getGlobalSiteManager
+from zope.component import getSiteManager
+from zope.component import getUtility, getMultiAdapter
+from zope.component.hooks import clearSite
+
+from Products.Archetypes.interfaces import IArchetypeTool
+from Products.Archetypes.interfaces import IReferenceCatalog
+from Products.Archetypes.interfaces import IUIDCatalog
+from Products.CMFActionIcons.interfaces import IActionIconsTool
+from Products.CMFCalendar.interfaces import ICalendarTool
+from Products.CMFCore.ActionInformation import Action
+from Products.CMFCore.ActionInformation import ActionCategory
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.utils import getToolInterface
+from Products.CMFCore.Expression import Expression
+from Products.CMFCore.permissions import AccessInactivePortalContent
+from Products.CMFCore.interfaces import IActionsTool
+from Products.CMFCore.interfaces import ICachingPolicyManager
+from Products.CMFCore.interfaces import ICatalogTool
+from Products.CMFCore.interfaces import IContentTypeRegistry
+from Products.CMFCore.interfaces import IDiscussionTool
+from Products.CMFCore.interfaces import IMemberDataTool
+from Products.CMFCore.interfaces import IMembershipTool
+from Products.CMFCore.interfaces import IMetadataTool
+from Products.CMFCore.interfaces import IPropertiesTool
+from Products.CMFCore.interfaces import IRegistrationTool
+from Products.CMFCore.interfaces import ISiteRoot
+from Products.CMFCore.interfaces import ISkinsTool
+from Products.CMFCore.interfaces import ISyndicationTool
+from Products.CMFCore.interfaces import ITypesTool
+from Products.CMFCore.interfaces import IURLTool
+from Products.CMFCore.interfaces import IConfigurableWorkflowTool
+from Products.CMFCore.ActionInformation import ActionInformation
+from Products.CMFDiffTool.interfaces import IDiffTool
+from Products.CMFEditions.interfaces import IArchivistTool
+from Products.CMFEditions.interfaces import IPortalModifierTool
+from Products.CMFEditions.interfaces import IPurgePolicyTool
+from Products.CMFEditions.interfaces.IRepository import IRepositoryTool
+from Products.CMFEditions.interfaces import IStorageTool
+from Products.CMFFormController.interfaces import IFormControllerTool
+from Products.CMFQuickInstallerTool.interfaces import IQuickInstallerTool
+from Products.CMFPlone.interfaces import IPloneSiteRoot
+from Products.CMFPlone.interfaces import IPloneTool
+from Products.CMFPlone.interfaces import ITranslationServiceTool
+from Products.CMFUid.interfaces import IUniqueIdAnnotationManagement
+from Products.CMFUid.interfaces import IUniqueIdGenerator
+from Products.CMFUid.interfaces import IUniqueIdHandler
+from Products.GenericSetup.interfaces import ISetupTool
+from Products.MailHost.interfaces import IMailHost
+from Products.MimetypesRegistry.interfaces import IMimetypesRegistryTool
+from Products.PortalTransforms.interfaces import IPortalTransformsTool
+from Products.PloneLanguageTool.interfaces import ILanguageTool
+from Products.PlonePAS.interfaces.group import IGroupTool
+from Products.PlonePAS.interfaces.group import IGroupDataTool
+from Products.ResourceRegistries.interfaces import ICSSRegistry
+from Products.ResourceRegistries.interfaces import IJSRegistry
+
+from plone.app.upgrade.tests.base import FunctionalUpgradeTestCase
+from plone.app.upgrade.tests.base import MigrationTest
+from plone.app.upgrade.utils import loadMigrationProfile
+
+from plone.app.upgrade.v30.alphas import enableZope3Site
+from plone.app.upgrade.v30.alphas import migrateOldActions
+from plone.app.upgrade.v30.alphas import updateActionsI18NDomain
+from plone.app.upgrade.v30.alphas import updateFTII18NDomain
+from plone.app.upgrade.v30.alphas import convertLegacyPortlets
+from plone.app.upgrade.v30.alphas import registerToolsAsUtilities
+from plone.app.upgrade.v30.alphas import registration
+from plone.app.upgrade.v30.alphas import addReaderAndEditorRoles
+from plone.app.upgrade.v30.alphas import migrateLocalroleForm
+from plone.app.upgrade.v30.alphas import reorderUserActions
+from plone.app.upgrade.v30.alphas import updatePASPlugins
+from plone.app.upgrade.v30.alphas import updateConfigletTitles
+from plone.app.upgrade.v30.alphas import addCacheForResourceRegistry
+from plone.app.upgrade.v30.alphas import removeTablelessSkin
+from plone.app.upgrade.v30.alphas import addObjectProvidesIndex
+from plone.app.upgrade.v30.alphas import restorePloneTool
+from plone.app.upgrade.v30.alphas import installProduct
+
+from plone.app.upgrade.v30.betas import migrateHistoryTab
+from plone.app.upgrade.v30.betas import changeOrderOfActionProviders
+from plone.app.upgrade.v30.betas import cleanupOldActions
+from plone.app.upgrade.v30.betas import cleanDefaultCharset
+from plone.app.upgrade.v30.betas import addAutoGroupToPAS
+from plone.app.upgrade.v30.betas import removeS5Actions
+from plone.app.upgrade.v30.betas import addContributorToCreationPermissions
+from plone.app.upgrade.v30.betas import removeSharingAction
+from plone.app.upgrade.v30.betas import addEditorToSecondaryEditorPermissions
+from plone.app.upgrade.v30.betas import updateEditActionConditionForLocking
+from plone.app.upgrade.v30.betas import addOnFormUnloadJS
+
+from plone.app.upgrade.v30.betas import updateTopicTitle
+from plone.app.upgrade.v30.betas import cleanupActionProviders
+from plone.app.upgrade.v30.betas import hidePropertiesAction
+
+from plone.app.upgrade.v30.rcs import addIntelligentText
+
+from plone.app.upgrade.v30.final_three0x import installNewModifiers
+
+try:
+    from Products.ATContentTypes.interface import IATCTTool
+    HAS_ATCT = True
+except ImportError:
+    HAS_ATCT = False
+
+try:
+    from Products.CMFPlone.interfaces import IFactoryTool
+except:
+    from Products.ATContentTypes.interfaces import IFactoryTool
+
+
+class TestMigrations_v3_0_Actions(MigrationTest):
+
+    def afterSetUp(self):
+        self.actions = self.portal.portal_actions
+        self.types = self.portal.portal_types
+        self.workflow = self.portal.portal_workflow
+        self._migrate_reply_action()
+
+    def _migrate_reply_action(self):
+        # Create dummy old ActionInformation
+        reply = ActionInformation('reply',
+            title='Reply',
+            category='reply_actions',
+            condition='context/replyAllowed',
+            permissions=(AccessInactivePortalContent, ),
+            priority=10,
+            visible=True,
+            action='context/reply'
+        )
+
+        from OFS.SimpleItem import SimpleItem
+        class DummyTool(SimpleItem):
+            pass
+        dummy = DummyTool()
+        dummy._actions = (reply,)
+        self.portal._setObject('dummy', dummy)
+
+    def testMigrateActions(self):
+        # Test it twice
+        for i in range(2):
+            migrateOldActions(self.portal)
+            reply_actions = getattr(self.actions, 'reply_actions', None)
+            self.assertFalse(reply_actions is None)
+            reply = getattr(reply_actions, 'reply', None)
+            self.assertFalse(reply is None)
+            self.assertTrue(isinstance(reply, Action))
+            # Verify all data has been upgraded correctly to the new Action
+            data = reply.getInfoData()[0]
+            self.assertEqual(data['category'], 'reply_actions')
+            self.assertEqual(data['title'], 'Reply')
+            self.assertEqual(data['visible'], True)
+            self.assertEqual(data['permissions'], (AccessInactivePortalContent, ))
+            self.assertEqual(data['available'].text, 'context/replyAllowed')
+            self.assertEqual(data['url'].text, 'context/reply')
+            # Make sure the original action has been removed
+            self.assertEqual(len(self.portal.dummy._actions), 0)
+
+    def testUpdateActionsI18NDomain(self):
+        migrateOldActions(self.portal)
+        reply = self.actions.reply_actions.reply
+        self.assertEqual(reply.i18n_domain, '')
+        # Test it twice
+        for i in range(2):
+            updateActionsI18NDomain(self.portal)
+            self.assertEqual(reply.i18n_domain, 'plone')
+
+    def testUpdateActionsI18NDomainNonAscii(self):
+        migrateOldActions(self.portal)
+        reply = self.actions.reply_actions.reply
+        reply.title = 'Foo\xc3'
+        self.assertEqual(reply.i18n_domain, '')
+        self.assertEqual(reply.title, 'Foo\xc3')
+
+        updateActionsI18NDomain(self.portal)
+
+        self.assertEqual(reply.i18n_domain, '')
+
+    def testHistoryActionID(self):
+        # Test it twice
+        for i in range(2):
+            migrateHistoryTab(self.portal)
+            objects = getattr(self.actions, 'object', None)
+            self.assertFalse('rss' in objects.objectIds())
+
+    def testProviderCleanup(self):
+        self.actions.addActionProvider("portal_membership")
+        self.assertTrue("portal_membership" in self.actions.listActionProviders())
+        # Test it twice
+        for i in range(2):
+            cleanupActionProviders(self.portal)
+            self.assertFalse("portal_membership" in self.actions.listActionProviders())
+
+    def testRemovePropertiesActions(self):
+        ti = self.types.getTypeInfo("Document")
+        if ti.getActionObject("object/properties") is None:
+            ti.addAction("metadata", "name", "action", "condition",
+                    "permission", "object",)
+        # Test it twice
+        for i in range(2):
+            hidePropertiesAction(self.portal)
+            self.assertTrue(ti.getActionObject("object/metadata") is None)
+
+    def tearDown(self):
+        self.portal._delObject('dummy')
+
+
+class TestMigrations_v2_5_x(MigrationTest):
+
+    def afterSetUp(self):
+        self.profile = 'profile-plone.app.upgrade.v30:2.5.x-3.0a1'
+        self.types = self.portal.portal_types
+        self.properties = self.portal.portal_properties
+
+        for legacy_tool in ('portal_discussion', 'portal_actionicons'):
+            if legacy_tool not in self.portal:
+                from OFS.SimpleItem import SimpleItem
+                self.portal._setObject(legacy_tool, SimpleItem())
+
+    def disableSite(self, obj, iface=ISite):
+        # We need our own disableSite method as the CMF portal implements
+        # ISite directly, so we cannot remove it, like the disableSite method
+        # in Five.component would have done
+        from ZPublisher.BeforeTraverse import unregisterBeforeTraverse
+        from Products.Five.component import HOOK_NAME
+        obj = aq_base(obj)
+        if not iface.providedBy(obj):
+            raise TypeError('Object must be a site.')
+        unregisterBeforeTraverse(obj, HOOK_NAME)
+        if hasattr(obj, HOOK_NAME):
+            delattr(obj, HOOK_NAME)
+
+    def testEnableZope3Site(self):
+        # First we remove the site and site manager
+        self.disableSite(self.portal)
+        clearSite(self.portal)
+        self.portal.setSiteManager(None)
+        gsm = getGlobalSiteManager()
+        # Test it twice
+        for i in range(2):
+            enableZope3Site(self.portal)
+            # And see if we have an ISite with a local site manager
+            self.assertTrue(ISite.providedBy(self.portal))
+            sm = getSiteManager(self.portal)
+            self.assertFalse(gsm is sm)
+            lc = sm.utilities.LookupClass
+            self.assertEqual(lc, FiveVerifyingAdapterLookup)
+
+        # Test the lookupclass migration
+        sm.utilities.LookupClass = None
+        # Test it twice
+        for i in range(2):
+            enableZope3Site(self.portal)
+            self.assertEqual(sm.utilities.LookupClass, FiveVerifyingAdapterLookup)
+            self.assertEqual(sm.utilities.__parent__, sm)
+            self.assertEqual(sm.__parent__, self.portal)
+
+    def testUpdateFTII18NDomain(self):
+        doc = self.types.Document
+        doc.i18n_domain = ''
+        # Test it twice
+        for i in range(2):
+            updateFTII18NDomain(self.portal)
+            self.assertEqual(doc.i18n_domain, 'plone')
+
+    def testUpdateFTII18NDomainNonAscii(self):
+        doc = self.types.Document
+        doc.i18n_domain = ''
+        doc.title = 'Foo\xc3'
+        # Update FTI's
+        updateFTII18NDomain(self.portal)
+        # domain should have been updated
+        self.assertEqual(doc.i18n_domain, '')
+
+    def testAddDefaultAndForbiddenContentTypesProperties(self):
+        # Should add the forbidden_contenttypes and default_contenttype property
+        self.removeSiteProperty('forbidden_contenttypes')
+        self.removeSiteProperty('default_contenttype')
+        self.assertFalse(self.properties.site_properties.hasProperty('forbidden_contenttypes'))
+        self.assertFalse(self.properties.site_properties.hasProperty('default_contenttype'))
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('propertiestool', ))
+            self.assertTrue(self.properties.site_properties.hasProperty('forbidden_contenttypes'))
+            self.assertTrue(self.properties.site_properties.hasProperty('default_contenttype'))
+            self.assertEqual(self.properties.site_properties.forbidden_contenttypes,
+                ('text/structured', 'text/restructured', 'text/x-rst',
+                'text/plain', 'text/plain-pre', 'text/x-python',
+                'text/x-web-markdown', 'text/x-web-intelligent', 'text/x-web-textile')
+            )
+
+    def testTablelessRemoval(self):
+        st = getToolByName(self.portal, "portal_skins")
+        if "Plone Tableless" not in st.getSkinSelections():
+            st.addSkinSelection('Plone Tableless', 'one,two', make_default=True)
+        # Test it twice
+        for i in range(2):
+            removeTablelessSkin(self.portal)
+            self.assertFalse('Plone Tableless' in st.getSkinSelections())
+            self.assertFalse(st.default_skin == 'Plone Tableless')
+
+    def testLegacyPortletsConverted(self):
+        self.setRoles(('Manager',))
+        leftColumn = getUtility(IPortletManager, name=u'plone.leftcolumn', context=self.portal)
+        rightColumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=self.portal)
+
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        for k in left:
+            del left[k]
+        for k in right:
+            del right[k]
+
+        self.portal.left_slots = ['here/portlet_recent/macros/portlet',
+                                  'here/portlet_news/macros/portlet',
+                                  'here/portlet_related/macros/portlet']
+        self.portal.right_slots = ['here/portlet_login/macros/portlet',
+                                   'here/portlet_languages/macros/portlet']
+
+        self.portal.Members.right_slots = []
+
+        # Test it twice
+        for i in range(2):
+            convertLegacyPortlets(self.portal)
+
+            self.assertEqual(self.portal.left_slots, [])
+            self.assertEqual(self.portal.right_slots, [])
+
+            left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+            right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+            lp = left.values()
+            self.assertEqual(2, len(lp))
+
+            self.assertTrue(isinstance(lp[0], portlets.recent.Assignment))
+            self.assertTrue(isinstance(lp[1], portlets.news.Assignment))
+
+            rp = right.values()
+            self.assertEqual(1, len(rp))
+            self.assertTrue(isinstance(rp[0], portlets.login.Assignment))
+
+            members = self.portal.Members
+            portletAssignments = getMultiAdapter((members, rightColumn,), ILocalPortletAssignmentManager)
+            self.assertEqual(True, portletAssignments.getBlacklistStatus(CONTEXT_PORTLETS))
+
+    def testLegacyPortletsConvertedNoSlots(self):
+        self.setRoles(('Manager',))
+        leftColumn = getUtility(IPortletManager, name=u'plone.leftcolumn', context=self.portal)
+        rightColumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=self.portal)
+
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        for k in left:
+            del left[k]
+        for k in right:
+            del right[k]
+
+        self.portal.left_slots = ['here/portlet_recent/macros/portlet',
+                                  'here/portlet_news/macros/portlet']
+
+        self.portal.Members.right_slots = []
+
+        if hasattr(self.portal.aq_base, 'right_slots'):
+            delattr(self.portal, 'right_slots')
+
+        convertLegacyPortlets(self.portal)
+
+        self.assertEqual(self.portal.left_slots, [])
+
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        lp = left.values()
+        self.assertEqual(2, len(lp))
+
+        self.assertTrue(isinstance(lp[0], portlets.recent.Assignment))
+        self.assertTrue(isinstance(lp[1], portlets.news.Assignment))
+
+        rp = right.values()
+        self.assertEqual(0, len(rp))
+
+        members = self.portal.Members
+        portletAssignments = getMultiAdapter((members, rightColumn,), ILocalPortletAssignmentManager)
+        self.assertEqual(True, portletAssignments.getBlacklistStatus(CONTEXT_PORTLETS))
+
+    def testLegacyPortletsConvertedBadSlots(self):
+        self.setRoles(('Manager',))
+        leftColumn = getUtility(IPortletManager, name=u'plone.leftcolumn', context=self.portal)
+        rightColumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=self.portal)
+
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        for k in left:
+            del left[k]
+        for k in right:
+            del right[k]
+
+        self.portal.left_slots = ['here/portlet_recent/macros/portlet',
+                                  'here/portlet_news/macros/portlet',
+                                  'foobar',]
+        self.portal.right_slots = ['here/portlet_login/macros/portlet']
+
+        self.portal.Members.right_slots = []
+
+        convertLegacyPortlets(self.portal)
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        self.assertEqual(self.portal.left_slots, [])
+        self.assertEqual(self.portal.right_slots, [])
+
+        lp = left.values()
+        self.assertEqual(2, len(lp))
+
+        self.assertTrue(isinstance(lp[0], portlets.recent.Assignment))
+        self.assertTrue(isinstance(lp[1], portlets.news.Assignment))
+
+        rp = right.values()
+        self.assertEqual(1, len(rp))
+        self.assertTrue(isinstance(rp[0], portlets.login.Assignment))
+
+        members = self.portal.Members
+        portletAssignments = getMultiAdapter((members, rightColumn,), ILocalPortletAssignmentManager)
+        self.assertEqual(True, portletAssignments.getBlacklistStatus(CONTEXT_PORTLETS))
+
+    def testLegacyPortletsConvertedNoMembersFolder(self):
+        self.setRoles(('Manager',))
+        leftColumn = getUtility(IPortletManager, name=u'plone.leftcolumn', context=self.portal)
+        rightColumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=self.portal)
+
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        for k in left:
+            del left[k]
+        for k in right:
+            del right[k]
+
+        self.portal.left_slots = ['here/portlet_recent/macros/portlet',
+                                  'here/portlet_news/macros/portlet',
+                                  'foobar',]
+        self.portal.right_slots = ['here/portlet_login/macros/portlet']
+
+        self.portal._delObject('Members')
+
+        convertLegacyPortlets(self.portal)
+        left = getMultiAdapter((self.portal, leftColumn,), IPortletAssignmentMapping, context=self.portal)
+        right = getMultiAdapter((self.portal, rightColumn,), IPortletAssignmentMapping, context=self.portal)
+
+        self.assertEqual(self.portal.left_slots, [])
+        self.assertEqual(self.portal.right_slots, [])
+
+        lp = left.values()
+        self.assertEqual(2, len(lp))
+
+        self.assertTrue(isinstance(lp[0], portlets.recent.Assignment))
+        self.assertTrue(isinstance(lp[1], portlets.news.Assignment))
+
+        rp = right.values()
+        self.assertEqual(1, len(rp))
+        self.assertTrue(isinstance(rp[0], portlets.login.Assignment))
+
+    def testRegisterToolsAsUtilities(self):
+        sm = getSiteManager(self.portal)
+        interfaces = (ISiteRoot, IPloneSiteRoot,
+                      IActionIconsTool, ISyndicationTool,
+                      IMetadataTool, IPropertiesTool, IMailHost,
+                      IUniqueIdAnnotationManagement, IUniqueIdGenerator,
+                      IDiffTool, IMimetypesRegistryTool,
+                      IPortalTransformsTool, IDiscussionTool, )
+        if HAS_ATCT:
+            interfaces += (IATCTTool,)
+        for i in interfaces:
+            sm.unregisterUtility(provided=i)
+        registerToolsAsUtilities(self.portal)
+        for i in interfaces:
+            self.assertFalse(sm.queryUtility(i) is None)
+
+        for i in interfaces:
+            sm.unregisterUtility(provided=i)
+        registerToolsAsUtilities(self.portal)
+        registerToolsAsUtilities(self.portal)
+        for i in interfaces:
+            self.assertFalse(sm.queryUtility(i) is None)
+
+    def testDontRegisterToolsAsUtilities(self):
+        sm = getSiteManager(self.portal)
+        interfaces = (ILanguageTool, IArchivistTool, IPortalModifierTool,
+                      IPurgePolicyTool, IRepositoryTool, IStorageTool,
+                      IFormControllerTool, IReferenceCatalog, IUIDCatalog,
+                      ICalendarTool, IActionsTool, ICatalogTool,
+                      IContentTypeRegistry, ISkinsTool, ITypesTool, IURLTool,
+                      IConfigurableWorkflowTool, IPloneTool, ICSSRegistry,
+                      IJSRegistry, IUniqueIdHandler, IFactoryTool,
+                      IMembershipTool, IGroupTool, IGroupDataTool,
+                      IMemberDataTool, IArchetypeTool, ICachingPolicyManager,
+                      IRegistrationTool, ITranslationServiceTool,
+                      ISetupTool, IQuickInstallerTool,
+                     )
+        for i in interfaces:
+            sm.unregisterUtility(provided=i)
+        registerToolsAsUtilities(self.portal)
+        for i in interfaces:
+            self.assertTrue(sm.queryUtility(i) is None)
+
+        for i in interfaces:
+            sm.unregisterUtility(provided=i)
+        registerToolsAsUtilities(self.portal)
+        registerToolsAsUtilities(self.portal)
+        for i in interfaces:
+            self.assertTrue(sm.queryUtility(i) is None)
+
+    def testToolRegistration(self):
+        for (tool_id, interface) in registration:
+            self.assertEqual(getToolInterface(tool_id), interface)
+
+
+class TestMigrations_v3_0_alpha1(MigrationTest):
+
+    def afterSetUp(self):
+        self.profile = 'profile-plone.app.upgrade.v30:3.0a1-3.0a2'
+        self.actions = self.portal.portal_actions
+
+    def testInstallRedirectorUtility(self):
+        sm = getSiteManager(self.portal)
+        sm.unregisterUtility(provided=IRedirectionStorage)
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('componentregistry', ))
+            self.assertFalse(sm.queryUtility(IRedirectionStorage) is None)
+
+    def testAddReaderEditorRoles(self):
+        self.portal._delRoles(['Reader', 'Editor'])
+        # Test it twice
+        for i in range(2):
+            addReaderAndEditorRoles(self.portal)
+            self.assertTrue('Reader' in self.portal.valid_roles())
+            self.assertTrue('Editor' in self.portal.valid_roles())
+            self.assertTrue('Reader' in self.portal.acl_users.portal_role_manager.listRoleIds())
+            self.assertTrue('Editor' in self.portal.acl_users.portal_role_manager.listRoleIds())
+            self.assertTrue('View' in [r['name'] for r in self.portal.permissionsOfRole('Reader') if r['selected']])
+            self.assertTrue('Modify portal content' in [r['name'] for r in self.portal.permissionsOfRole('Editor') if r['selected']])
+
+    def testAddReaderEditorRolesPermissionOnly(self):
+        self.portal.manage_permission('View', [], True)
+        self.portal.manage_permission('Modify portal content', [], True)
+        # Test it twice
+        for i in range(2):
+            addReaderAndEditorRoles(self.portal)
+            self.assertTrue('Reader' in self.portal.valid_roles())
+            self.assertTrue('Editor' in self.portal.valid_roles())
+            self.assertTrue('Reader' in self.portal.acl_users.portal_role_manager.listRoleIds())
+            self.assertTrue('Editor' in self.portal.acl_users.portal_role_manager.listRoleIds())
+            self.assertTrue('View' in [r['name'] for r in self.portal.permissionsOfRole('Reader') if r['selected']])
+            self.assertTrue('Modify portal content' in [r['name'] for r in self.portal.permissionsOfRole('Editor') if r['selected']])
+
+    def testMigrateLocalroleForm(self):
+        fti = self.portal.portal_types['Document']
+        aliases = fti.getMethodAliases()
+        aliases['sharing'] = 'folder_localrole_form'
+        fti.setMethodAliases(aliases)
+        fti.addAction('test', 'Test', 'string:${object_url}/folder_localrole_form', None, 'View', 'object')
+        # Test it twice
+        for i in range(2):
+            migrateLocalroleForm(self.portal)
+            self.assertEqual('@@sharing', fti.getMethodAliases()['sharing'])
+            test_action = fti.listActions()[-1]
+            self.assertEqual('string:${object_url}/@@sharing', test_action.getActionExpression())
+
+    def testReorderUserActions(self):
+        self.actions.user.moveObjectsToTop(['logout', 'undo', 'join'])
+        # Test it twice
+        for i in range(2):
+            reorderUserActions(self.portal)
+            # build a dict that has the position as the value to make it easier
+            # to compare postions in the ordered list of actions
+            n = 0
+            sort = {}
+            for action in self.actions.user.objectIds():
+                sort[action] = n
+                n += 1
+            self.assertTrue(sort['preferences'] < sort['undo'])
+            self.assertTrue(sort['undo'] < sort['logout'])
+            self.assertTrue(sort['login'] < sort['join'])
+
+    def testReorderUserActionsIncompleteActions(self):
+        self.actions.user.moveObjectsToTop(['logout', 'undo', 'join'])
+        self.actions.user._delObject('preferences')
+        # Test it twice
+        for i in range(2):
+            reorderUserActions(self.portal)
+            n = 0
+            sort = {}
+            for action in self.actions.user.objectIds():
+                sort[action] = n
+                n += 1
+            self.assertTrue(sort['undo'] < sort['logout'])
+            self.assertTrue(sort['login'] < sort['join'])
+
+
+class TestMigrations_v3_0_alpha2(MigrationTest):
+
+    def afterSetUp(self):
+        self.profile = 'profile-plone.app.upgrade.v30:3.0a2-3.0b1'
+        self.actions = self.portal.portal_actions
+        self.properties = self.portal.portal_properties
+        self.cp = self.portal.portal_controlpanel
+
+    def testAddVariousProperties(self):
+        PROPERTIES = ('enable_link_integrity_checks', 'enable_sitemap',
+                      'external_links_open_new_window', 'many_groups',
+                      'number_of_days_to_keep', 'webstats_js')
+        for prop in PROPERTIES:
+            self.removeSiteProperty(prop)
+        sheet = self.properties.site_properties
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('propertiestool', ))
+            for prop in PROPERTIES:
+                self.assertTrue(sheet.hasProperty(prop))
+
+    def testInstallContentrulesAndLanguageUtilities(self):
+        sm = getSiteManager()
+        INTERFACES = (IRuleStorage, ICountries, IContentLanguages,
+                      IMetadataLanguages)
+        for i in INTERFACES:
+            sm.unregisterUtility(provided=i)
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('componentregistry', ))
+            for i in INTERFACES:
+                self.assertFalse(sm.queryUtility(i) is None)
+
+    def testAddEmailCharsetProperty(self):
+        if self.portal.hasProperty('email_charset'):
+            self.portal.manage_delProperties(['email_charset'])
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('properties', ))
+            self.assertTrue(self.portal.hasProperty('email_charset'))
+            self.assertEqual(self.portal.getProperty('email_charset'), 'utf-8')
+
+    def testUpdateMemberSecurity(self):
+        pprop = getToolByName(self.portal, 'portal_properties')
+        self.assertEqual(
+                pprop.site_properties.getProperty('allowAnonymousViewAbout'),
+                False)
+
+        pmembership = getToolByName(self.portal, 'portal_membership')
+        self.assertEqual(pmembership.memberareaCreationFlag, False)
+        self.assertEqual(self.portal.getProperty('validate_email'), True)
+
+        app_roles = self.portal.rolesOfPermission(permission='Add portal member')
+        app_perms = self.portal.permission_settings(permission='Add portal member')
+        acquire_check = app_perms[0]['acquire']
+        reg_roles = []
+        for appperm in app_roles:
+            if appperm['selected'] == 'SELECTED':
+                reg_roles.append(appperm['name'])
+        self.assertTrue('Manager' in reg_roles)
+        self.assertTrue('Owner' in reg_roles)
+        self.assertEqual(acquire_check, '')
+
+    def testPASPluginInterfaces(self):
+        pas = self.portal.acl_users
+        from Products.PluggableAuthService.interfaces.plugins import IUserEnumerationPlugin
+        pas.plugins.deactivatePlugin(IUserEnumerationPlugin, 'mutable_properties')
+        updatePASPlugins(self.portal)
+
+        plugin = pas.mutable_properties
+        for intf_id in plugin.listInterfaces():
+            try:
+                intf = pas.plugins._getInterfaceFromName(intf_id)
+                self.assertTrue('mutable_properties' in pas.plugins.listPluginIds(intf))
+            except KeyError:
+                # Ignore unregistered interface types
+                pass
+
+    def testUpdateConfigletTitles(self):
+        collection = self.cp.getActionObject('Plone/portal_atct')
+        language = self.cp.getActionObject('Plone/PloneLanguageTool')
+        navigation = self.cp.getActionObject('Plone/NavigationSettings')
+        types = self.cp.getActionObject('Plone/TypesSettings')
+        users = self.cp.getActionObject('Plone/UsersGroups')
+        users2 = self.cp.getActionObject('Plone/UsersGroups2')
+        # test it twice
+        for i in range(2):
+            updateConfigletTitles(self.portal)
+            self.assertEqual(collection.title, 'Collection')
+            self.assertEqual(language.title, 'Language')
+            self.assertEqual(navigation.title, 'Navigation')
+            self.assertEqual(types.title, 'Types')
+            self.assertEqual(users.title, 'Users and Groups')
+            self.assertEqual(users2.title, 'Users and Groups')
+
+    def testAddCacheForResourceRegistry(self):
+        ram_cache_id = 'ResourceRegistryCache'
+        # first remove the cache manager and make sure it's removed
+        self.portal._delObject(ram_cache_id)
+        self.assertFalse(ram_cache_id in self.portal.objectIds())
+        cssreg = self.portal.portal_css
+        cssreg.ZCacheable_setEnabled(0)
+        cssreg.ZCacheable_setManagerId(None)
+        self.assertFalse(cssreg.ZCacheable_enabled())
+        self.assertTrue(cssreg.ZCacheable_getManagerId() is None)
+        jsreg = self.portal.portal_javascripts
+        jsreg.ZCacheable_setEnabled(0)
+        jsreg.ZCacheable_setManagerId(None)
+        self.assertFalse(jsreg.ZCacheable_enabled())
+        self.assertTrue(jsreg.ZCacheable_getManagerId() is None)
+        # Test it twice
+        for i in range(2):
+            addCacheForResourceRegistry(self.portal)
+            self.assertTrue(ram_cache_id in self.portal.objectIds())
+            self.assertTrue(cssreg.ZCacheable_enabled())
+            self.assertFalse(cssreg.ZCacheable_getManagerId() is None)
+            self.assertTrue(jsreg.ZCacheable_enabled())
+            self.assertFalse(jsreg.ZCacheable_getManagerId() is None)
+
+    def testObjectProvidesIndex(self):
+        catalog = getToolByName(self.portal, 'portal_catalog')
+        if 'object_provides' in catalog.indexes():
+            catalog.delIndex('object_provides')
+        self.assertFalse('object_provides' in catalog.indexes())
+        # Test it twice
+        for i in range(2):
+            addObjectProvidesIndex(self.portal)
+            self.assertTrue('object_provides' in catalog.indexes())
+
+    def testMigratePloneTool(self):
+        tool = self.portal.plone_utils
+        tool.meta_type = 'PlonePAS Utilities Tool'
+        # Test it twice
+        for i in range(2):
+            restorePloneTool(self.portal)
+            tool = self.portal.plone_utils
+            self.assertEqual('Plone Utility Tool', tool.meta_type)
+
+    def testInstallPloneLanguageTool(self):
+        super(self.portal.__class__, self.portal).manage_delObjects(
+            ['portal_languages'])
+        self.uninstallProduct('PloneLanguageTool')
+        qi = getToolByName(self.portal, "portal_quickinstaller")
+        # Test it twice
+        for i in range(2):
+            installProduct('PloneLanguageTool', self.portal)
+            self.assertTrue(qi.isProductInstalled('PloneLanguageTool'))
+            self.assertTrue('portal_languages' in self.portal.keys())
+
+
+class TestMigrations_v3_0(MigrationTest):
+
+    def afterSetUp(self):
+        self.profile = 'profile-plone.app.upgrade.v30:3.0b1-3.0b2'
+        self.actions = self.portal.portal_actions
+        self.skins = self.portal.portal_skins
+        self.types = self.portal.portal_types
+        self.workflow = self.portal.portal_workflow
+        self.properties = getToolByName(self.portal, 'portal_properties')
+
+    def testAddContentRulesAction(self):
+        self.portal.portal_actions.object._delObject('contentrules')
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal, self.profile, ('actions', ))
+            self.assertTrue('contentrules' in self.portal.portal_actions.object.objectIds())
+
+    def testChangeOrderOfActionProviders(self):
+        self.actions.deleteActionProvider('portal_types')
+        self.actions.addActionProvider('portal_types')
+        self.assertEqual(
+            self.actions.listActionProviders(),
+            ('portal_workflow', 'portal_actions', 'portal_types'))
+        # Test it twice
+        for i in range(2):
+            changeOrderOfActionProviders(self.portal)
+            self.assertEqual(
+                self.actions.listActionProviders(),
+                ('portal_workflow', 'portal_types', 'portal_actions'))
+
+    def testCleanupOldActions(self):
+        reply = Action('reply', title='Reply')
+        logged_in = Action('logged_in', title='Logged in')
+        change_ownership = Action('change_ownership', title='Change ownership')
+
+        object_ = self.actions.object
+        object_tabs = getattr(self.actions, 'object_tabs', None)
+        if object_tabs is None:
+            category = 'object_tabs'
+            self.actions._setObject(category, ActionCategory(id=category))
+            object_tabs = self.actions.object_tabs
+        if getattr(self.actions, 'global', None) is None:
+            category = 'global'
+            self.actions._setObject(category, ActionCategory(id=category))
+
+        if not 'reply' in object_.keys():
+            object_._setObject('reply', reply)
+        user = self.actions.user
+        if not 'logged_in' in user.keys():
+            user._setObject('logged_in', logged_in)
+        if not 'change_ownership' in object_tabs.keys():
+            object_tabs._setObject('change_ownership', change_ownership)
+        del object_tabs
+
+        # Test it twice
+        for i in range(2):
+            cleanupOldActions(self.portal)
+            self.assertFalse('reply' in object_.keys())
+            self.assertFalse('logged_in' in user.keys())
+            self.assertFalse('object_tabs' in self.actions.keys())
+            self.assertFalse('global' in self.actions.keys())
+
+    def testCharsetCleanup(self):
+        if not self.portal.hasProperty('default_charset'):
+            self.portal.manage_addProperty('default_charset', '', 'string')
+        # Test it twice
+        for i in range(2):
+            self.portal.manage_changeProperties(default_charset = 'latin1')
+            cleanDefaultCharset(self.portal)
+            self.assertEqual(self.portal.getProperty('default_charset', 'nothere'),
+                    'latin1')
+        # Test it twice
+        for i in range(2):
+            self.portal.manage_changeProperties(default_charset = '')
+            cleanDefaultCharset(self.portal)
+            self.assertEqual(self.portal.getProperty('default_charset', 'nothere'),
+                    'nothere')
+
+    def testAutoGroupCreated(self):
+        pas = self.portal.acl_users
+        ids = pas.objectIds(['Automatic Group Plugin'])
+        if ids:
+            pas.manage_delObjects(ids)
+        addAutoGroupToPAS(self.portal)
+        self.assertEqual(pas.objectIds(['Automatic Group Plugin']),
+                ['auto_group'])
+        plugin = pas.auto_group
+        interfaces = [info['interface'] for info in pas.plugins.listPluginTypeInfo()]
+        for iface in interfaces:
+            if plugin.testImplements(iface):
+                self.assertFalse('auto_group' not in pas.plugins.listPluginIds(iface))
+        self.assertEqual(len(pas.searchGroups(id='AuthenticatedUsers',
+                                              exact_match=True)), 1)
+
+    def testPloneS5(self):
+        pt = getToolByName(self.portal, "portal_types")
+        document = pt.restrictedTraverse('Document')
+        document.addAction('s5_presentation',
+            name='View as presentation',
+            action="string:${object/absolute_url}/document_s5_presentation",
+            condition='python:object.document_s5_alter(test=True)',
+            permission='View',
+            category='document_actions',
+            visible=1,
+            )
+        action_ids = [x.getId() for x in document.listActions()]
+        self.assertTrue("s5_presentation" in action_ids)
+        # Test it twice
+        for i in range(2):
+            removeS5Actions(self.portal)
+            action_ids = [x.getId() for x in document.listActions()]
+            self.assertFalse("s5_presentation" in action_ids)
+
+    def testAddContributorToCreationPermissions(self):
+        self.portal._delRoles(['Contributor',])
+        for p in ['Add portal content', 'Add portal folders', 'ATContentTypes: Add Document',
+                    'ATContentTypes: Add Event',
+                    'ATContentTypes: Add File', 'ATContentTypes: Add Folder',
+                    'ATContentTypes: Add Image', 'ATContentTypes: Add Link',
+                    'ATContentTypes: Add News Item', ]:
+            self.portal.manage_permission(p, ['Manager', 'Owner'], True)
+        # Test it twice
+        for i in range(2):
+            addContributorToCreationPermissions(self.portal)
+            self.assertTrue('Contributor' in self.portal.valid_roles())
+            self.assertTrue('Contributor' in self.portal.acl_users.portal_role_manager.listRoleIds())
+            for p in ['Add portal content', 'Add portal folders', 'ATContentTypes: Add Document',
+                        'ATContentTypes: Add Event',
+                        'ATContentTypes: Add File', 'ATContentTypes: Add Folder',
+                        'ATContentTypes: Add Image', 'ATContentTypes: Add Link',
+                        'ATContentTypes: Add News Item', ]:
+                self.assertTrue(p in [r['name'] for r in
+                                    self.portal.permissionsOfRole('Contributor') if r['selected']])
+
+    def testAddContributerToCreationPermissionsNoStomp(self):
+        self.portal.manage_permission('Add portal content', ['Manager'], False)
+        # Test it twice
+        for i in range(2):
+            addContributorToCreationPermissions(self.portal)
+            roles = sorted([r['name'] for r in self.portal.rolesOfPermission('Add portal content') if r['selected']])
+            self.assertEqual(['Contributor', 'Manager'], roles)
+            self.assertEqual(False, bool(self.portal.acquiredRolesAreUsedBy('Add portal content')))
+
+    def testAddBeta2VersioningPermissionsToNewRoles(self):
+        # This upgrade just uses GS to apply the role changes,
+        # these permissions will not have been installed previously,
+        # so this should be safe
+        for p in ['CMFEditions: Apply version control',
+                  'CMFEditions: Save new version',
+                  'CMFEditions: Access previous versions',
+                  'CMFEditions: Revert to previous versions',
+                  'CMFEditions: Checkout to location']:
+            self.portal.manage_permission(p, ['Manager', 'Owner'], True)
+        # Test it twice
+        for i in range(2):
+            loadMigrationProfile(self.portal,
+                    'profile-plone.app.upgrade.v30:3.0b1-3.0b2',
+                    steps=["rolemap"])
+            for p in ['CMFEditions: Apply version control',
+                      'CMFEditions: Save new version',
+                      'CMFEditions: Access previous versions']:
+                self.assertTrue(p in [r['name'] for r in
+                                    self.portal.permissionsOfRole('Contributor') if r['selected']])
+                self.assertTrue(p in [r['name'] for r in
+                                    self.portal.permissionsOfRole('Editor') if r['selected']])
+            for p in ['CMFEditions: Revert to previous versions',
+                      'CMFEditions: Checkout to location']:
+                self.assertTrue(p in [r['name'] for r in
+                                    self.portal.permissionsOfRole('Editor') if r['selected']])
+
+    def testRemoveSharingAction(self):
+        fti = self.types['Document']
+        fti.addAction(id='local_roles', name='Sharing',
+                      action='string:${object_url}/sharing',
+                      condition=None, permission='Manage properties',
+                      category='object')
+        # Test it twice
+        for i in range(2):
+            removeSharingAction(self.portal)
+            self.assertFalse('local_roles' in [a.id for a in fti.listActions()])
+
+    def testAddEditorToCreationPermissions(self):
+        for p in ['Manage properties', 'Modify view template', 'Request review']:
+            self.portal.manage_permission(p, ['Manager', 'Owner'], True)
+        # Test it twice
+        for i in range(2):
+            addEditorToSecondaryEditorPermissions(self.portal)
+            for p in ['Manage properties', 'Modify view template', 'Request review']:
+                self.assertTrue(p in [r['name'] for r in
+                    self.portal.permissionsOfRole('Editor') if r['selected']])
+
+    def testAddEditorToCreationPermissionsNoStomp(self):
+        self.portal.manage_permission('Manage properties', ['Manager'], False)
+        # Test it twice
+        for i in range(2):
+            addEditorToSecondaryEditorPermissions(self.portal)
+            roles = sorted([r['name'] for r in self.portal.rolesOfPermission('Manage properties') if r['selected']])
+            self.assertEqual(['Editor', 'Manager'], roles)
+            self.assertEqual(False, bool(self.portal.acquiredRolesAreUsedBy('Manage properties')))
+
+    def testUpdateEditActionConditionForLocking(self):
+        lockable_types = ['Document', 'Event', 'File', 'Folder',
+                          'Image', 'Link', 'News Item', 'Topic']
+        for contentType in lockable_types:
+            fti = self.types.getTypeInfo(contentType)
+            for action in fti.listActions():
+                if action.getId() == 'edit':
+                    action.condition = ''
+        # Test it twice
+        for i in range(2):
+            updateEditActionConditionForLocking(self.portal)
+            for contentType in lockable_types:
+                fti = self.types.getTypeInfo(contentType)
+                for action in fti.listActions():
+                    if action.getId() == 'edit':
+                        self.assertEqual(action.condition.text,
+                            "not:object/@@plone_lock_info/is_locked_for_current_user|python:True")
+
+    def testUpdateEditExistingActionConditionForLocking(self):
+        fti = self.types.getTypeInfo('Document')
+        for action in fti.listActions():
+            if action.getId() == 'edit':
+                action.condition = Expression("foo")
+        # Test it twice
+        for i in range(2):
+            updateEditActionConditionForLocking(self.portal)
+            fti = self.types.getTypeInfo('Document')
+            for action in fti.listActions():
+                if action.getId() == 'edit':
+                    self.assertEqual(action.condition.text, 'foo')
+
+    def testAddOnFormUnloadRegistrationJS(self):
+        jsreg = self.portal.portal_javascripts
+        # unregister first
+        jsreg.unregisterResource('unlockOnFormUnload.js')
+        script_ids = jsreg.getResourceIds()
+        self.assertFalse('unlockOnFormUnload.js' in script_ids)
+        # Test it twice
+        for i in range(2):
+            addOnFormUnloadJS(self.portal)
+            script_ids = jsreg.getResourceIds()
+            self.assertTrue('unlockOnFormUnload.js' in script_ids)
+
+    def testUpdateTopicTitle(self):
+        topic = self.types.get('Topic')
+        topic.title = 'Old'
+        # Test it twice
+        for i in range(2):
+            updateTopicTitle(self.portal)
+            self.assertEqual(topic.title, 'Collection')
+
+    def testAddIntelligentText(self):
+        # Before the upgrade, the mime type and transforms of intelligent text
+        # are not available. They *are* here in a fresh site, so we may need
+        # to remove them first for testing. First we remove the transforms,
+        # as they depend on the mimetype being there.
+        missing_transforms = ["web_intelligent_plain_text_to_html",
+                              "html_to_web_intelligent_plain_text"]
+        ptr = self.portal.portal_transforms
+        current_transforms = ptr.objectIds()
+        for trans in missing_transforms:
+            if trans in current_transforms:
+                ptr.unregisterTransform(trans)
+        # Then we remove the mime type
+        mime_type = 'text/x-web-intelligent'
+        mtr = self.portal.mimetypes_registry
+        current_types = mtr.list_mimetypes()
+        if mime_type in current_types:
+            mtr.manage_delObjects((mime_type,))
+        # now all are gone:
+        self.assertFalse(mime_type in mtr.list_mimetypes())
+        self.assertFalse(set(ptr.objectIds()).issuperset(set(missing_transforms)))
+        # Test it twice
+        for i in range(2):
+            addIntelligentText(self.portal)
+            # now all are back:
+            self.assertTrue(mime_type in mtr.list_mimetypes())
+            self.assertTrue(set(ptr.objectIds()).issuperset(set(missing_transforms)))
+
+    def testInstallNewModifiers(self):
+        # ensure the new modifiers are installed
+        modifiers = self.portal.portal_modifier
+        self.assertTrue('AbortVersioningOfLargeFilesAndImages' in
+                                                          modifiers.objectIds())
+        modifiers.manage_delObjects(['AbortVersioningOfLargeFilesAndImages',
+                                     'SkipVersioningOfLargeFilesAndImages'])
+        self.assertFalse('AbortVersioningOfLargeFilesAndImages' in
+                                                          modifiers.objectIds())
+        installNewModifiers(self.portal)
+        self.assertTrue('AbortVersioningOfLargeFilesAndImages' in
+                                                          modifiers.objectIds())
+        self.assertTrue('SkipVersioningOfLargeFilesAndImages' in
+                                                          modifiers.objectIds())
+
+    def testInstallNewModifiersTwice(self):
+        # ensure that we get no errors when run twice
+        installNewModifiers(self.portal)
+        installNewModifiers(self.portal)
+
+    def testInstallNewModifiersDoesNotStompChanges(self):
+        # ensure that reinstalling doesn't kill customizations
+        modifiers = self.portal.portal_modifier
+        modifiers.AbortVersioningOfLargeFilesAndImages.max_size = 1000
+        installNewModifiers(self.portal)
+        self.assertEqual(modifiers.AbortVersioningOfLargeFilesAndImages.max_size,
+                         1000)
+
+    def testInstallNewModifiersNoTool(self):
+        # make sure there are no errors if the tool is missing
+        self.portal._delObject('portal_modifier')
+        installNewModifiers(self.portal)
+
+
+class TestFunctionalMigrations(FunctionalUpgradeTestCase):
+
+    def testBaseUpgrade(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testFullUpgrade(self):
+        self.importFile(__file__, 'test-full.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestMigrations_v2_5_x))
+    suite.addTest(makeSuite(TestMigrations_v3_0_Actions))
+    suite.addTest(makeSuite(TestMigrations_v3_0_alpha1))
+    suite.addTest(makeSuite(TestMigrations_v3_0_alpha2))
+    suite.addTest(makeSuite(TestMigrations_v3_0))
+    suite.addTest(makeSuite(TestFunctionalMigrations))
+    return suite
diff --git a/plone/app/upgrade/v31/tests.py b/plone/app/upgrade/v31/tests.py
new file mode 100644
index 0000000..01005b3
--- /dev/null
+++ b/plone/app/upgrade/v31/tests.py
@@ -0,0 +1,118 @@
+from borg.localrole.utils import replace_local_role_manager
+from zope.interface import noLongerProvides
+
+from Products.PlonePAS.interfaces.plugins import ILocalRolesPlugin
+
+from plone.app.upgrade.tests.base import FunctionalUpgradeTestCase
+from plone.app.upgrade.tests.base import MigrationTest
+
+from plone.app.upgrade.v31.betas import reinstallCMFPlacefulWorkflow
+
+
+class TestMigrations_v3_1(MigrationTest):
+
+    def afterSetUp(self):
+        self.qi = self.portal.portal_quickinstaller
+        self.wf = self.portal.portal_workflow
+        self.ps = self.portal.portal_setup
+
+    def testReinstallCMFPlacefulWorkflow(self):
+        try:
+            from Products.CMFPlacefulWorkflow.interfaces import IPlacefulMarker
+        except ImportError:
+            return
+        # first the product needs to be installed
+        self.qi.installProduct('CMFPlacefulWorkflow')
+        # Delete existing logs to prevent race condition
+        self.ps.manage_delObjects(self.ps.objectIds())
+        # We remove the new marker, to ensure it's added on reinstall
+        if IPlacefulMarker.providedBy(self.wf):
+            noLongerProvides(self.wf, IPlacefulMarker)
+        reinstallCMFPlacefulWorkflow(self.portal, [])
+        self.assertTrue(IPlacefulMarker.providedBy(self.wf))
+
+    def testReinstallCMFPlacefulWorkflowDoesNotInstall(self):
+        reinstallCMFPlacefulWorkflow(self.portal, [])
+        self.assertFalse(self.qi.isProductInstalled('CMFPlacefulWorkflow'))
+
+    def testReinstallCMFPlacefulWorkflowNoTool(self):
+        self.portal._delObject('portal_quickinstaller')
+        reinstallCMFPlacefulWorkflow(self.portal, [])
+
+    def testReplaceLocalRoleManager(self):
+        # first we replace the local role manager with the one from PlonePAS
+        uf = self.portal.acl_users
+        # deactivate and remove the borg plugin
+        uf.plugins.removePluginById('borg_localroles')
+        uf.manage_delObjects(['borg_localroles'])
+        # activate the standard plugin
+        uf.plugins.activatePlugin(ILocalRolesPlugin, 'local_roles')
+        # Bring things back to normal
+        replace_local_role_manager(self.portal)
+        plugins = uf.plugins.listPlugins(ILocalRolesPlugin)
+        self.assertEqual(len(plugins), 1)
+        self.assertEqual(plugins[0][0], 'borg_localroles')
+
+    def testReplaceLocalRoleManagerTwice(self):
+        # first we replace the local role manager with the one from PlonePAS
+        uf = self.portal.acl_users
+        # deactivate and remove the borg plugin
+        uf.plugins.removePluginById('borg_localroles')
+        uf.manage_delObjects(['borg_localroles'])
+        # activate the standard plugin
+        uf.plugins.activatePlugin(ILocalRolesPlugin, 'local_roles')
+        # run the upgrade twice
+        replace_local_role_manager(self.portal)
+        replace_local_role_manager(self.portal)
+        plugins = uf.plugins.listPlugins(ILocalRolesPlugin)
+        self.assertEqual(len(plugins), 1)
+        self.assertEqual(plugins[0][0], 'borg_localroles')
+
+    def testReplaceLocalRoleManagerNoPlugin(self):
+        # first we replace the local role manager with the one from PlonePAS
+        uf = self.portal.acl_users
+        # deactivate and remove the borg plugin
+        uf.plugins.removePluginById('borg_localroles')
+        uf.manage_delObjects(['borg_localroles'])
+        # delete the standard plugin
+        uf.manage_delObjects(['local_roles'])
+        # Run the upgrade, which shouldn't fail even if the expected
+        # plugin is missing
+        replace_local_role_manager(self.portal)
+        plugins = uf.plugins.listPlugins(ILocalRolesPlugin)
+        self.assertEqual(len(plugins), 1)
+        self.assertEqual(plugins[0][0], 'borg_localroles')
+
+    def testReplaceLocalRoleManagerNoPAS(self):
+        uf = self.portal.acl_users
+        # delete the plugin registry
+        uf._delObject('plugins')
+        replace_local_role_manager(self.portal)
+
+    def testReplaceLocalRoleManagerNoUF(self):
+        # Delete the user folder
+        replace_local_role_manager(self.portal)
+
+class TestFunctionalMigrations(FunctionalUpgradeTestCase):
+
+    def testBaseUpgrade(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testFullUpgrade(self):
+        self.importFile(__file__, 'test-full.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestMigrations_v3_1))
+    suite.addTest(makeSuite(TestFunctionalMigrations))
+    return suite
diff --git a/plone/app/upgrade/v32/tests.py b/plone/app/upgrade/v32/tests.py
new file mode 100644
index 0000000..bc0e7a1
--- /dev/null
+++ b/plone/app/upgrade/v32/tests.py
@@ -0,0 +1,50 @@
+from plone.app.upgrade.tests.base import FunctionalUpgradeTestCase
+from plone.app.upgrade.tests.base import MigrationTest
+from plone.app.upgrade.v32.betas import three1_beta1
+
+class TestMigrations_v3_2(MigrationTest):
+
+    def afterSetUp(self):
+        self.qi = self.portal.portal_quickinstaller
+        self.actions = self.portal.portal_actions
+        self.migration = self.portal.portal_migration
+
+    def testIterateActionsMigratedIfIterateInstalled(self):
+        self.qi.installProduct('plone.app.iterate')
+        self.actions.object_buttons.iterate_checkin.permissions = (
+            'Modify portal content',)
+        three1_beta1(self.portal)
+        self.assertEqual(
+            self.actions.object_buttons.iterate_checkin.permissions,
+            ('iterate : Check in content',))
+
+    def testIterateInstalledButActionMissing(self):
+        self.qi.installProduct('plone.app.iterate')
+        self.actions.object_buttons.manage_delObjects(['iterate_checkin'])
+        three1_beta1(self.portal)
+        self.assertFalse('iterate_checkin' in
+                    self.actions.object_buttons.objectIds())
+
+class TestFunctionalMigrations(FunctionalUpgradeTestCase):
+
+    def testBaseUpgrade(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testFullUpgrade(self):
+        self.importFile(__file__, 'test-full.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestMigrations_v3_2))
+    suite.addTest(makeSuite(TestFunctionalMigrations))
+    return suite
diff --git a/plone/app/upgrade/v33/tests.py b/plone/app/upgrade/v33/tests.py
new file mode 100644
index 0000000..01a5de8
--- /dev/null
+++ b/plone/app/upgrade/v33/tests.py
@@ -0,0 +1,93 @@
+from Products.CMFCore.utils import getToolByName
+
+from plone.app.upgrade.tests.base import FunctionalUpgradeTestCase
+from plone.app.upgrade.tests.base import MigrationTest
+from plone.app.upgrade.v33 import three2_three3
+
+class TestMigrations_v3_3(MigrationTest):
+
+    def afterSetUp(self):
+        self.types = self.portal.portal_types
+        self.properties = getToolByName(self.portal, 'portal_properties')
+
+    def _upgrade(self):
+        three2_three3(self.portal)
+
+    def testRedirectLinksProperty(self):
+        del self.properties.site_properties.redirect_links
+        self._upgrade()
+        self.assertEqual(True,
+            self.properties.site_properties.getProperty('redirect_links'))
+
+    def testLinkDefaultView(self):
+        self.types.Link.default_view = 'link_view'
+        self.types.Link.immediate_view = 'link_view'
+        self.types.Link.view_methods = ('link_view',)
+        self._upgrade()
+        self.assertEqual(self.types.Link.default_view, 'link_redirect_view')
+        self.assertEqual(self.types.Link.immediate_view, 'link_redirect_view')
+        self.assertEqual(self.types.Link.view_methods, ('link_redirect_view',))
+
+    def testCustomizedLinkDefaultView(self):
+        # but only change if old default was 'link_view'
+        self.types.Link.default_view = 'foobar'
+        self.types.Link.immediate_view = 'foobar'
+        self.types.Link.view_methods = ('foobar',)
+        self._upgrade()
+        self.assertEqual(self.types.Link.default_view, 'foobar')
+        self.assertEqual(self.types.Link.immediate_view, 'foobar')
+        self.assertEqual(self.types.Link.view_methods, ('foobar',))
+
+class TestFunctionalMigrations(FunctionalUpgradeTestCase):
+
+    def testBaseUpgrade(self):
+        self.importFile(__file__, 'test-base.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testFullUpgrade(self):
+        self.importFile(__file__, 'test-full.zexp')
+        oldsite, result = self.migrate()
+
+        mig = oldsite.portal_migration
+        self.assertFalse(mig.needUpgrading())
+
+    def testFolderUpgrade(self):
+        from plone.folder.interfaces import IOrderableFolder
+        self.importFile(__file__, 'test-full.zexp')
+        # `portal_type` and `Type` can be checked before migration...
+        oldsite = getattr(self.app, self.site_id)
+        ids = 'news', 'events', 'Members'
+        for id in ids:
+            obj = oldsite[id]
+            self.assertEqual(obj.portal_type, 'Large Plone Folder')
+            self.assertEqual(obj.Type(), 'Large Folder')
+            brain, = oldsite.portal_catalog(getId=id)   # asserts only one
+            self.assertEqual(brain.portal_type, 'Large Plone Folder')
+            self.assertEqual(brain.Type, 'Large Folder')
+        # now let's migrate...
+        oldsite, result = self.migrate()
+        self.assertFalse(oldsite.portal_migration.needUpgrading())
+        # after migration `/news`, `/events` and `/Members` are based on
+        # `plone.(app.)folder`, but still have no ordering set...
+        for id in ids:
+            obj = oldsite[id]
+            self.assertTrue(IOrderableFolder.providedBy(obj),
+                '%s not orderable?' % id)
+            self.assertEqual(obj._ordering, 'unordered',
+                '%s has no `_ordering`?' % id)
+            self.assertEqual(obj.portal_type, 'Folder')
+            self.assertEqual(obj.Type(), 'Folder')
+            brain, = oldsite.portal_catalog(getId=id)   # asserts only one
+            self.assertEqual(brain.portal_type, 'Folder')
+            self.assertEqual(brain.Type, 'Folder')
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(TestMigrations_v3_3))
+    suite.addTest(makeSuite(TestFunctionalMigrations))
+    return suite
diff --git a/plone/app/upgrade/v40/tests.py b/plone/app/upgrade/v40/tests.py
new file mode 100644
index 0000000..c350619
--- /dev/null
+++ b/plone/app/upgrade/v40/tests.py
@@ -0,0 +1,666 @@
+import time
+
+from zope.component import getMultiAdapter
+from zope.component import getSiteManager
+from zope.component import getUtility
+from zope.component import queryUtility
+from zope.ramcache.interfaces.ram import IRAMCache
+
+from Products.CMFCore.ActionInformation import Action
+from Products.CMFCore.Expression import Expression
+from Products.CMFCore.utils import getToolByName
+from Products.MailHost.interfaces import IMailHost
+
+from plone.app.upgrade.utils import loadMigrationProfile
+from plone.app.upgrade.v40.alphas import _KNOWN_ACTION_ICONS
+from plone.app.upgrade.v40.alphas import migrateActionIcons
+from plone.app.upgrade.v40.alphas import migrateTypeIcons
+from plone.app.upgrade.v40.alphas import addOrReplaceRamCache
+from plone.app.upgrade.v40.alphas import changeWorkflowActorVariableExpression
+from plone.app.upgrade.v40.alphas import changeAuthenticatedResourcesCondition
+from plone.app.upgrade.v40.alphas import setupReferencebrowser
+from plone.app.upgrade.v40.alphas import migrateMailHost
+from plone.app.upgrade.v40.alphas import migrateFolders
+from plone.app.upgrade.v40.alphas import renameJoinFormFields
+from plone.app.upgrade.v40.alphas import updateLargeFolderType
+from plone.app.upgrade.v40.alphas import addRecursiveGroupsPlugin
+from plone.app.upgrade.v40.alphas import cleanUpClassicThemeResources
+from plone.app.upgrade.v40.alphas import migrateStaticTextPortlets
+from plone.app.upgrade.v40.betas import repositionRecursiveGroupsPlugin
+from plone.app.upgrade.v40.betas import updateIconMetadata
+from plone.app.upgrade.v40.betas import removeLargePloneFolder
+from plone.app.upgrade.tests.base import MigrationTest
+
+from plone.portlet.static import static
+from plone.portlets.interfaces import IPortletAssignmentMapping
+from plone.portlets.interfaces import IPortletAssignmentSettings
+from plone.portlets.interfaces import IPortletManager
+
+
+class FakeSecureMailHost(object):
+
+    meta_type = 'Secure Mail Host'
+    id = 'MailHost'
+    title = 'Fake MailHost'
+    smtp_host = 'smtp.example.com'
+    smtp_port = 587
+    smtp_userid='me'
+    smtp_pass='secret'
+    smtp_notls=False
+
+    def manage_fixupOwnershipAfterAdd(self):
+        pass
+
+
+class TestMigrations_v4_0alpha1(MigrationTest):
+
+    profile = "profile-plone.app.upgrade.v40:3-4alpha1"
+
+    def afterSetUp(self):
+        self.atool = getToolByName(self.portal, 'portal_actions')
+        self.cptool = getToolByName(self.portal, 'portal_controlpanel')
+        self.wftool = getToolByName(self.portal, 'portal_workflow')
+        self.csstool = getToolByName(self.portal, 'portal_css')
+        self.jstool = getToolByName(self.portal, 'portal_javascripts')
+
+        if 'portal_actionicons' not in self.portal:
+            from plone.app.upgrade.bbb import ActionIconsTool
+            self.portal._setObject('portal_actionicons', ActionIconsTool())
+        self.aitool = self.portal.portal_actionicons
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        self.setRoles(['Manager'])
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testMigrateActionIcons(self):
+        _KNOWN_ACTION_ICONS['object_buttons'].extend(['test_id', 'test2_id'])
+        self.aitool.addActionIcon(
+            category='object_buttons',
+            action_id='test_id',
+            icon_expr='test.gif',
+            title='Test my icon',
+            )
+        self.aitool.addActionIcon(
+            category='object_buttons',
+            action_id='test2_id',
+            icon_expr='python:context.getIcon()',
+            title='Test my second icon',
+            )
+        test_action = Action('test_id',
+            title='Test me',
+            description='',
+            url_expr='',
+            icon_expr='',
+            available_expr='',
+            permissions=('View', ),
+            visible = True)
+        test2_action = Action('test2_id',
+            title='Test me too',
+            description='',
+            url_expr='',
+            icon_expr='',
+            available_expr='',
+            permissions=('View', ),
+            visible = True)
+
+        object_buttons = self.atool.object_buttons
+        if getattr(object_buttons, 'test_id', None) is None:
+            object_buttons._setObject('test_id', test_action)
+        if getattr(object_buttons, 'test2_id', None) is None:
+            object_buttons._setObject('test2_id', test2_action)
+
+        self.assertEqual(object_buttons.test_id.icon_expr, '')
+        self.assertEqual(object_buttons.test2_id.icon_expr, '')
+        # Test it twice
+        for i in range(2):
+            migrateActionIcons(self.portal)
+            icons = [ic._action_id for ic in self.aitool.listActionIcons()]
+            self.assertFalse('test_id' in icons)
+            self.assertFalse('test2_id' in icons)
+            self.assertEqual(object_buttons.test_id.icon_expr,
+                             'string:$portal_url/test.gif')
+            self.assertEqual(object_buttons.test2_id.icon_expr,
+                             'python:context.getIcon()')
+
+    def testMigrateControlPanelActionIcons(self):
+        _KNOWN_ACTION_ICONS['controlpanel'].extend(['test_id'])
+        self.aitool.addActionIcon(
+            category='controlpanel',
+            action_id='test_id',
+            icon_expr='test.gif',
+            title='Test my icon',
+            )
+
+        self.cptool.registerConfiglet(
+            id='test_id',
+            name='Test Configlet',
+            action='string:${portal_url}/test',
+            permission='Manage portal',
+            category='Plone',
+            visible=True,
+            appId='',
+            icon_expr='',
+            )
+
+        action = self.cptool.getActionObject('Plone/test_id')
+        self.assertEqual(action.getIconExpression(), '')
+        # Test it twice
+        for i in range(2):
+            migrateActionIcons(self.portal)
+            icons = [ic._action_id for ic in self.aitool.listActionIcons()]
+            self.assertFalse('test_id' in icons)
+            self.assertEqual(action.getIconExpression(),
+                             'string:$portal_url/test.gif')
+
+    def testContentTypeIconExpressions(self):
+        """
+        FTIs should now be using icon_expr instead of content_icon.
+        (The former caches the expression object.)
+        """
+        tt = getToolByName(self.portal, "portal_types")
+        tt.Document.icon_expr = None
+        loadMigrationProfile(self.portal, self.profile, ('typeinfo', ))
+        self.assertEqual(tt.Document.icon_expr,
+                         "string:${portal_url}/document_icon.png")
+
+    def testMigrateTypeIcons(self):
+        """
+        FTIs having content_icon should be upgraded to icon_expr.
+        """
+        tt = getToolByName(self.portal, "portal_types")
+        del tt.Document.icon_expr
+        tt.Document.content_icon = 'document_icon.gif'
+        migrateTypeIcons(self.portal)
+        self.assertEqual(tt.Document.icon_expr,
+                         "string:${portal_url}/document_icon.gif")
+        self.assertTrue(hasattr(tt.Document, 'icon_expr_object'))
+
+        #Don't upgrade if there is already an icon_expr.
+        tt.Document.icon_expr = "string:${portal_url}/document_icon.png"
+        tt.Document.content_icon = 'document_icon.gif'
+        migrateTypeIcons(self.portal)
+        self.assertEqual(tt.Document.icon_expr,
+                         "string:${portal_url}/document_icon.png")
+
+    def testPngContentIcons(self):
+        tt = getToolByName(self.portal, "portal_types")
+        tt.Document.icon_expr = "string:${portal_url}/document_icon.gif"
+        loadMigrationProfile(self.portal, self.profile, ('typeinfo', ))
+        self.assertEqual(tt.Document.icon_expr,
+            "string:${portal_url}/document_icon.png")
+
+    def testAddRAMCache(self):
+        # Test it twice
+        for i in range(2):
+            sm = getSiteManager()
+            sm.unregisterUtility(provided=IRAMCache)
+            util = queryUtility(IRAMCache)
+            self.assertEqual(util.maxAge, 86400)
+            addOrReplaceRamCache(self.portal)
+            util = queryUtility(IRAMCache)
+            self.assertEqual(util.maxAge, 3600)
+
+    def testReplaceOldRamCache(self):
+        sm = getSiteManager()
+
+        # Test it twice
+        for i in range(2):
+            sm.unregisterUtility(provided=IRAMCache)
+            from zope.app.cache.interfaces.ram import IRAMCache as OldIRAMCache
+            from zope.app.cache.ram import RAMCache as OldRAMCache
+            sm.registerUtility(factory=OldRAMCache, provided=OldIRAMCache)
+
+            addOrReplaceRamCache(self.portal)
+            util = queryUtility(IRAMCache)
+            self.assertEqual(util.maxAge, 3600)
+
+    def testChangeWorkflowActorVariableExpression(self):
+        self.wftool.intranet_folder_workflow.variables.actor.setProperties('')
+
+        for i in range(2):
+            changeWorkflowActorVariableExpression(self.portal)
+            wf = self.wftool.intranet_folder_workflow
+            self.assertEqual(wf.variables.actor.getDefaultExprText(),
+                             'user/getId')
+            wf = self.wftool.one_state_workflow
+            self.assertEqual(wf.variables.actor.getDefaultExprText(),
+                             'user/getId')
+            wf = self.wftool.simple_publication_workflow
+            self.assertEqual(wf.variables.actor.getDefaultExprText(),
+                             'user/getId')
+
+        # make sure it doesn't break if the workflow is missing
+        wf = self.wftool.intranet_folder_workflow
+        self.wftool._delOb('intranet_folder_workflow')
+        changeWorkflowActorVariableExpression(self.portal)
+        self.wftool._setOb('intranet_folder_workflow', wf)
+
+    def testChangeAuthenticatedResourcesCondition(self):
+        # make sure CSS resource is updated
+        res = self.csstool.getResource('member.css')
+        if res is None:
+            return
+        res.setAuthenticated(False)
+        res.setExpression('not: portal/portal_membership/isAnonymousUser')
+        # test it twice
+        for i in range(2):
+            changeAuthenticatedResourcesCondition(self.portal)
+            self.assertEqual(res.getExpression(), '')
+            self.assertTrue(res.getAuthenticated())
+
+        # make sure it doesn't update it if the expression has been
+        # customized
+        res.setExpression('python:False')
+        changeAuthenticatedResourcesCondition(self.portal)
+        self.assertEqual(res.getExpression(), 'python:False')
+
+    def testAddedUseEmailProperty(self):
+        tool = getToolByName(self.portal, 'portal_properties')
+        sheet = getattr(tool, 'site_properties')
+        #self.assertEqual(sheet.getProperty('use_email_as_login'), False)
+        self.removeSiteProperty('use_email_as_login')
+        loadMigrationProfile(self.portal, self.profile, ('propertiestool', ))
+        self.assertEqual(sheet.getProperty('use_email_as_login'), False)
+
+    def testReplaceReferencebrowser(self):
+        self.setRoles(['Manager'])
+        skins_tool = getToolByName(self.portal, 'portal_skins')
+        if 'referencebrowser' not in skins_tool:
+            return
+        sels = skins_tool._getSelections()
+        for skinname, layer in sels.items():
+            layers = layer.split(',')
+            self.assertFalse('ATReferenceBrowserWidget' in layers)
+            layers.remove('referencebrowser')
+            new_layers = ','.join(layers)
+            sels[skinname] = new_layers
+
+        from .alphas import threeX_alpha1
+        threeX_alpha1(self.portal)
+        setupReferencebrowser(self.portal)
+
+        sels = skins_tool._getSelections()
+        for skinname, layer in sels.items():
+            layers = layer.split(',')
+            self.assertTrue('referencebrowser' in layers)
+
+    def testInstallNewDependencies(self):
+        from plone.app.upgrade.v40.alphas import threeX_alpha1
+        self.setRoles(['Manager'])
+        # test for running the TinyMCE profile by checking for the skin layer
+        # it installs (the profile is marked as noninstallable, so we can't
+        # ask the quick installer)
+        skins_tool = getToolByName(self.portal, 'portal_skins')
+        if 'tinymce' not in skins_tool:
+            # Skip test in new Plones that don't have tinymce to begin with
+            return
+        del skins_tool['tinymce']
+        for i in range(2):
+            threeX_alpha1(self.portal)
+            self.assertTrue('tinymce' in skins_tool)
+            # sleep to avoid a GS log filename collision :-o
+            time.sleep(1)
+
+    def testReplaceSecureMailHost(self):
+        portal = self.portal
+        sm = getSiteManager(context=portal)
+        # try it with an unmodified site to ensure it doesn't give any errors
+        migrateMailHost(portal.portal_setup)
+        portal._delObject('MailHost')
+        # Run it with our MailHost replaced
+        portal._setObject('MailHost', FakeSecureMailHost())
+        self.assertEqual(portal.MailHost.meta_type, 'Secure Mail Host')
+        sm.unregisterUtility(provided=IMailHost)
+        sm.registerUtility(portal.MailHost, provided=IMailHost)
+        migrateMailHost(portal)
+        new_mh = portal.MailHost
+        self.assertEqual(new_mh.meta_type, 'Mail Host')
+        self.assertEqual(new_mh.title, 'Fake MailHost')
+        self.assertEqual(new_mh.smtp_host, 'smtp.example.com')
+        self.assertEqual(new_mh.smtp_port, 587)
+        self.assertEqual(new_mh.smtp_uid, 'me')
+        self.assertEqual(new_mh.smtp_pwd, 'secret')
+        #Force TLS is always false, because SMH has no equivalent option
+        self.assertEqual(new_mh.force_tls, False)
+
+    def testFolderMigration(self):
+        from plone.app.folder.tests.content import create
+        from plone.app.folder.tests.test_migration import reverseMigrate
+        from plone.app.folder.tests.test_migration import isSaneBTreeFolder
+        # create a folder in an unmigrated state & check it's broken...
+        folder = create('Folder', self.portal, 'foo', title='Foo')
+        reverseMigrate(self.portal)
+        self.assertFalse(isSaneBTreeFolder(self.portal.foo))
+        # now run the migration step...
+        migrateFolders(self.portal)
+        folder = self.portal.foo
+        self.assertTrue(isSaneBTreeFolder(folder))
+        self.assertEqual(folder.getId(), 'foo')
+        self.assertEqual(folder.Title(), 'Foo')
+
+    def testMigrateStaticTextPortlets(self):
+        class HiddenAssignment(static.Assignment):
+            hide = True
+
+        self.setRoles(["Manager"])
+        self.portal.invokeFactory('Folder', id="statictest")
+        folder = self.portal['statictest']
+
+        manager = getUtility(
+                IPortletManager, name='plone.rightcolumn',
+                context=folder)
+        assignments = getMultiAdapter(
+                (folder, manager), IPortletAssignmentMapping)
+        hidden_portlet = HiddenAssignment()
+        visible_portlet = static.Assignment()
+        assignments['hidden'] = hidden_portlet
+        assignments['visible'] = visible_portlet
+
+        migrateStaticTextPortlets(self.portal)
+
+        self.assertFalse(
+                IPortletAssignmentSettings(hidden_portlet).get(
+                        'visible', True))
+        self.assertTrue(
+                IPortletAssignmentSettings(visible_portlet).get(
+                        'visible', True))
+
+
+
+class TestMigrations_v4_0alpha2(MigrationTest):
+
+    def testMigrateJoinFormFields(self):
+        ptool = getToolByName(self.portal, 'portal_properties')
+        sheet = getattr(ptool, 'site_properties')
+        self.removeSiteProperty('user_registration_fields')
+        self.addSiteProperty('join_form_fields')
+        sheet.join_form_fields = (
+            'username', 'password', 'email', 'mail_me', 'groups')
+        renameJoinFormFields(self)
+        self.assertEqual(sheet.hasProperty('join_form_fields'), False)
+        self.assertEqual(sheet.hasProperty('user_registration_fields'), True)
+        self.assertEqual(sheet.getProperty('user_registration_fields'),
+                         ('username', 'password', 'email', 'mail_me'))
+
+
+class TestMigrations_v4_0alpha3(MigrationTest):
+
+    profile = "profile-plone.app.upgrade.v40:4alpha2-4alpha3"
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testJoinActionURL(self):
+        self.portal.portal_actions.user.join.url_expr = 'foo'
+        loadMigrationProfile(self.portal, self.profile, ('actions', ))
+        self.assertEqual(self.portal.portal_actions.user.join.url_expr,
+            'string:${globals_view/navigationRootUrl}/@@register')
+
+
+class TestMigrations_v4_0alpha5(MigrationTest):
+
+    profile = "profile-plone.app.upgrade.v40:4alpha4-4alpha5"
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testMigrateLargeFolderType(self):
+        portal = self.portal
+        catalog = getToolByName(portal, 'portal_catalog')
+        # set things up in the old way...
+        ids = 'news', 'events', 'Members'
+        for id in ids:
+            obj = portal[id]
+            obj._setPortalTypeName('Large Plone Folder')
+            obj.reindexObject()
+            self.assertEqual(obj.portal_type, 'Large Plone Folder')
+            # Type falls back to meta_type since there's no
+            # Large Plone Folder FTI
+            self.assertEqual(obj.Type(), 'ATFolder')
+            brain, = catalog(getId=id)
+            self.assertEqual(brain.portal_type, 'Large Plone Folder')
+            self.assertEqual(brain.Type, 'ATFolder')
+        # migrate & check again...
+        updateLargeFolderType(self.portal)
+        for id in ids:
+            obj = portal[id]
+            self.assertEqual(obj.portal_type, 'Folder')
+            self.assertEqual(obj.Type(), 'Folder')
+            brain, = catalog(getId=id)
+            self.assertEqual(brain.portal_type, 'Folder')
+            self.assertEqual(brain.Type, 'Folder')
+
+    def testAddRecursiveGroupsPlugin(self):
+        acl = getToolByName(self.portal, 'acl_users')
+        addRecursiveGroupsPlugin(self.portal)
+        self.assertTrue('recursive_groups' in acl)
+        # Now that we have an existing one, let's make sure it's handled
+        # properly if this migration is run again.
+        addRecursiveGroupsPlugin(self.portal)
+        self.assertTrue('recursive_groups' in acl)
+
+    def testClassicThemeResourcesCleanUp(self):
+        """Test that the plonetheme.classic product doesn't have any
+        registered CSS resource in its metadata after migration.
+        """
+        portal = self.portal
+        qi = getToolByName(portal, 'portal_quickinstaller')
+        qi.installProduct('plonetheme.classic')
+        classictheme = qi['plonetheme.classic']
+        classictheme.resources_css = ['something'] # add a random resource
+        cleanUpClassicThemeResources(portal)
+        self.assertEqual(classictheme.resources_css, [])
+
+    def testGetObjPositionInParentIndex(self):
+        from plone.app.folder.nogopip import GopipIndex
+        catalog = self.portal.portal_catalog
+        catalog.delIndex('getObjPositionInParent')
+        catalog.addIndex('getObjPositionInParent', 'FieldIndex')
+        self.assertFalse(isinstance(catalog.Indexes['getObjPositionInParent'],
+            GopipIndex))
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue('getObjPositionInParent' in catalog.indexes())
+        self.assertTrue(isinstance(catalog.Indexes['getObjPositionInParent'],
+            GopipIndex))
+
+    def testGetEventTypeIndex(self):
+        catalog = self.portal.portal_catalog
+        catalog.addIndex('getEventType', 'KeywordIndex')
+        self.assertTrue('getEventType' in catalog.indexes())
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertFalse('getEventType' in catalog.indexes())
+
+
+class TestMigrations_v4_0beta1(MigrationTest):
+
+    profile = "profile-plone.app.upgrade.v40:4alpha5-4beta1"
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testRepositionRecursiveGroupsPlugin(self):
+        # Ensure that the recursive groups plugin is moved to the bottom
+        # of the IGroups plugins list, if active.
+        addRecursiveGroupsPlugin(self.portal)
+        # Plugin is installed, but not active, run against this state.
+        from Products.PluggableAuthService.interfaces.plugins import \
+            IGroupsPlugin
+        acl = getToolByName(self.portal, 'acl_users')
+        plugins = acl.plugins
+        # The plugin was originally moved to the top of the list of
+        # IGroupsPlugin plugins by p.a.controlpanel. Recreate that state.
+        while (plugins.getAllPlugins('IGroupsPlugin')['active'].index(
+               'recursive_groups') > 0):
+            plugins.movePluginsUp(IGroupsPlugin, ['recursive_groups'])
+
+        active_groups = plugins.getAllPlugins('IGroupsPlugin')['active']
+        self.assertEqual(active_groups[0], 'recursive_groups')
+
+        # Rerun the migration, making sure that it's now the last item in the
+        # list of IGroupsPlugin plugins.
+        repositionRecursiveGroupsPlugin(self.portal)
+        active_groups = plugins.getAllPlugins('IGroupsPlugin')['active']
+        self.assertEqual(active_groups[-1], 'recursive_groups')
+
+
+class TestMigrations_v4_0beta2(MigrationTest):
+
+    profile = "profile-plone.app.upgrade.v40:4beta1-4beta2"
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testCoreContentIconExprCleared(self):
+        types = getToolByName(self.portal, 'portal_types')
+        catalog = getToolByName(self.portal, 'portal_catalog')
+        # Reinstate the now-empty icon expression for the Document type
+        doc_icon_expr = Expression('string:${portal_url}/document_icon.png')
+        types['Document'].icon_expr_object = doc_icon_expr
+        front = self.portal['front-page']
+        catalog.reindexObject(front)
+        old_modified = front.modified()
+        # Make sure the getIcon metadata column shows the "original" value
+        brains = catalog(id='front-page')
+        self.assertEqual(brains[0].getIcon, 'document_icon.png')
+        # Run the migration
+        loadMigrationProfile(self.portal, self.profile)
+        updateIconMetadata(self.portal)
+        # The getIcon column should now be empty
+        self.assertEqual(catalog(id='front-page')[0].getIcon, '')
+        self.assertEqual(front.modified(), old_modified)
+
+
+class TestMigrations_v4_0beta4(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4beta3-4beta4'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testRemoveLargePloneFolder(self):
+        # re-create pre-migration settings
+        ptool = self.portal.portal_properties
+        nav_props = ptool.navtree_properties
+        l = list(nav_props.parentMetaTypesNotToQuery)
+        nav_props.parentMetaTypesNotToQuery = l + ['Large Plone Folder']
+        site_props = ptool.site_properties
+        l = list(site_props.typesLinkToFolderContentsInFC)
+        site_props.typesLinkToFolderContentsInFC = l + ['Large Plone Folder']
+        temp_folder_fti = self.portal.portal_types['TempFolder']
+        l = list(temp_folder_fti.allowed_content_types)
+        temp_folder_fti.allowed_content_types = l + ['Large Plone Folder']
+        l = set(self.portal.portal_factory.getFactoryTypes())
+        l.add('Large Plone Folder')
+        ftool = self.portal.portal_factory
+        ftool.manage_setPortalFactoryTypes(listOfTypeIds=list(l))
+
+        for i in xrange(2):
+            loadMigrationProfile(self.portal, self.profile)
+            removeLargePloneFolder(self.portal)
+            self.assertFalse('Large Plone Folder' in self.portal.portal_types)
+            self.assertFalse('Large Plone Folder' in
+                        temp_folder_fti.allowed_content_types)
+            self.assertTrue('Folder' in temp_folder_fti.allowed_content_types)
+            self.assertFalse('Large Plone Folder' in ftool.getFactoryTypes())
+            self.assertTrue('Folder' in ftool.getFactoryTypes())
+            self.assertFalse('Large Plone Folder' in
+                        nav_props.parentMetaTypesNotToQuery)
+            self.assertTrue('TempFolder' in
+                            nav_props.parentMetaTypesNotToQuery)
+            self.assertFalse('Large Plone Folder' in
+                        site_props.typesLinkToFolderContentsInFC)
+            self.assertTrue('Folder' in
+                            site_props.typesLinkToFolderContentsInFC)
+            # sleep to avoid a GS log filename collision :-o
+            time.sleep(1)
+
+
+class TestMigrations_v4_0beta5(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4beta4-4beta5'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+
+class TestMigrations_v4_0rc1(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4beta5-4rc1'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4rc1-4final'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0_1(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4.0-4.0.1'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0_2(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4.0.1-4.0.2'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0_3(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4.0.2-4.0.3'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0_4(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4.0.3-4.0.4'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+class TestMigrations_v4_0_5(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v40:4.0.4-4.0.5'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+
+def test_suite():
+    from unittest import defaultTestLoader
+    return defaultTestLoader.loadTestsFromName(__name__)
diff --git a/plone/app/upgrade/v41/tests.py b/plone/app/upgrade/v41/tests.py
new file mode 100644
index 0000000..4570a4c
--- /dev/null
+++ b/plone/app/upgrade/v41/tests.py
@@ -0,0 +1,19 @@
+import unittest
+from Products.ZCatalog.ZCatalog import ZCatalog
+from Products.ZCTextIndex.ZCTextIndex import ZCTextIndex, PLexicon
+from Products.ZCTextIndex.OkapiIndex import OkapiIndex
+
+
+class MigrationUnitTests(unittest.TestCase):
+
+    def test_fixOkapiIndexes(self):
+        catalog = ZCatalog('catalog')
+        catalog.lexicon = PLexicon('lexicon')
+        catalog.addIndex('test',
+            ZCTextIndex('test', index_factory=OkapiIndex,
+                        caller=catalog, lexicon_id='lexicon'))
+        catalog.Indexes['test'].index._totaldoclen = -1000
+
+        from plone.app.upgrade.v41.final import fixOkapiIndexes
+        fixOkapiIndexes(catalog)
+        self.assertEqual(0L, catalog.Indexes['test'].index._totaldoclen())
diff --git a/plone/app/upgrade/v42/tests.py b/plone/app/upgrade/v42/tests.py
new file mode 100644
index 0000000..0ff501e
--- /dev/null
+++ b/plone/app/upgrade/v42/tests.py
@@ -0,0 +1,20 @@
+from plone.app.upgrade.tests.base import MigrationTest
+from plone.app.upgrade.utils import loadMigrationProfile
+
+
+class TestMigrations_v4_2beta1(MigrationTest):
+
+    profile = 'profile-plone.app.upgrade.v42:to42beta1'
+
+    def testProfile(self):
+        # This tests the whole upgrade profile can be loaded
+        loadMigrationProfile(self.portal, self.profile)
+        self.assertTrue(True)
+
+    def testAddSiteAdminToKeywordRoles(self):
+        ptool = self.portal.portal_properties
+        site_props = ptool.site_properties
+        site_props.allowRolesToAddKeywords = ('Manager', 'Reviewer')
+        loadMigrationProfile(self.portal, self.profile)
+        roles = site_props.allowRolesToAddKeywords
+        self.assertEqual(roles, ('Manager', 'Reviewer', 'Site Administrator'))
diff --git a/setup.py b/setup.py
index c500ee7..1dd33a5 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 from setuptools import setup, find_packages
 
-version = '2.0.dev0'
+version = '1.3.9.dev0'
 
 setup(name='plone.app.upgrade',
       version=version,
@@ -10,12 +10,10 @@
       classifiers=[
           "Environment :: Web Environment",
           "Framework :: Plone",
-          "Framework :: Plone :: 5.0",
           "Framework :: Zope2",
           "License :: OSI Approved :: GNU General Public License (GPL)",
           "Operating System :: OS Independent",
           "Programming Language :: Python",
-          "Programming Language :: Python :: 2.7",
         ],
       keywords='Plone upgrade migration',
       author='Plone Foundation',
@@ -30,13 +28,13 @@
         test=[
             'Products.CMFPlacefulWorkflow',
             'Products.CMFQuickInstallerTool',
+            'Products.PloneTestCase',
             'plone.contentrules',
             'plone.app.i18n',
             'plone.app.iterate',
             'plone.app.openid',
             'plone.app.redirector',
             'plone.app.viewletmanager',
-            'plone.app.testing',
             'plone.app.theming',
         ]
       ),




-------------------------------------------------------------------------------


More information about the Testbot mailing list