def resetDynamicDocuments(self): """Resets all dynamic documents: force reloading erp.* classes WARNING: COSTLY! Please double-check that resetDynamicDocumentsOnceAtTransactionBoundary can't be used instead. """ synchronizeDynamicModules(self, force=True)
def testAttributeValueComputedFromAccessorHolderList(self): """ Check that attributes such as constraints and _categories, containing respectively all the constraints and categories define on their Property Sheets, loads the portal type class as some static getters (for example getInstanceBaseCategoryList() use _categories directly) """ import erp5.portal_type synchronizeDynamicModules(self.portal, force=True) self.assertTrue(erp5.portal_type.Person.__isghost__) self.assertTrue('constraints' not in erp5.portal_type.Person.__dict__) getattr(erp5.portal_type.Person, 'constraints') self.assertTrue(not erp5.portal_type.Person.__isghost__) self.assertTrue('constraints' in erp5.portal_type.Person.__dict__) synchronizeDynamicModules(self.portal, force=True) self.assertTrue(erp5.portal_type.Person.__isghost__) self.assertTrue('_categories' not in erp5.portal_type.Person.__dict__) getattr(erp5.portal_type.Person, '_categories') self.assertTrue(not erp5.portal_type.Person.__isghost__) self.assertTrue('_categories' in erp5.portal_type.Person.__dict__)
def reset(self, force=False, reset_portal_type_at_transaction_boundary=False): """ Reset all ZODB Component packages. A cache cookie is used to check whether the reset is necessary when force is not specified. This allows to make sure that all ZEO clients get reset (checked in __of__ on ERP5Site) when one given ZEO client gets reset when Component(s) are modified or invalidated. Also, as resetting ZODB Components Package usually implies to reset Portal Type as Classes (because the former are used as bases), perform the reset by default. XXX-arnau: for now, this is a global reset but it might be improved in the future if required... """ portal = self.getPortalObject() global last_sync if force: # Hard invalidation to force sync between nodes portal.newCacheCookie('component_packages') last_sync = portal.getCacheCookie('component_packages') else: cookie = portal.getCacheCookie('component_packages') if cookie == last_sync: return False last_sync = cookie LOG("ERP5Type.Tool.ComponentTool", INFO, "Resetting Components") # Make sure that it is not possible to load Components or load Portal Type # class when Components are reset through aq_method_lock import erp5.component from Products.ERP5Type.dynamic.component_package import ComponentDynamicPackage with aq_method_lock: for package in erp5.component.__dict__.itervalues(): if isinstance(package, ComponentDynamicPackage): package.reset() erp5.component.ref_manager.gc() if reset_portal_type_at_transaction_boundary: portal.portal_types.resetDynamicDocumentsOnceAtTransactionBoundary( ) else: from Products.ERP5Type.dynamic.portal_type_class import synchronizeDynamicModules synchronizeDynamicModules(self, force) return True
def reset(self, force=False, reset_portal_type_at_transaction_boundary=False): """ Reset all ZODB Component packages. A cache cookie is used to check whether the reset is necessary when force is not specified. This allows to make sure that all ZEO clients get reset (checked in __of__ on ERP5Site) when one given ZEO client gets reset when Component(s) are modified or invalidated. Also, as resetting ZODB Components Package usually implies to reset Portal Type as Classes (because the former are used as bases), perform the reset by default. XXX-arnau: for now, this is a global reset but it might be improved in the future if required... """ portal = self.getPortalObject() global last_sync if force: # Hard invalidation to force sync between nodes portal.newCacheCookie('component_packages') last_sync = portal.getCacheCookie('component_packages') else: cookie = portal.getCacheCookie('component_packages') if cookie == last_sync: return False last_sync = cookie LOG("ERP5Type.Tool.ComponentTool", INFO, "Resetting Components") # Make sure that it is not possible to load Components or load Portal Type # class when Components are reset through aq_method_lock import erp5.component from Products.ERP5Type.dynamic.component_package import ComponentDynamicPackage with aq_method_lock: for package in erp5.component.__dict__.itervalues(): if isinstance(package, ComponentDynamicPackage): package.reset() if reset_portal_type_at_transaction_boundary: portal.portal_types.resetDynamicDocumentsOnceAtTransactionBoundary() else: from Products.ERP5Type.dynamic.portal_type_class import synchronizeDynamicModules synchronizeDynamicModules(self, force) return True
def testConstraintAfterClosingZODBConnection(self): """ Make sure that constraint works even if ZODB connection close. This test is added for the bug #20110628-ABAA76. """ # Open new connection and add a new constraint. db = self.app._p_jar.db() con = db.open() app = con.root()['Application'].__of__(self.app.aq_parent) portal = app[self.getPortalName()] from Products.ERP5.ERP5Site import getSite, setSite old_site = getSite() setSite(portal) import erp5 dummy = getattr(erp5.portal_type, 'TALES Constraint')(id='dummy') portal.portal_property_sheets.TestMigration._setObject('dummy', dummy) dummy = portal.portal_property_sheets.TestMigration.dummy dummy.edit(reference='test_dummy_constraint', expression='python: object.getTitle() == "my_tales_constraint_title"') dummy.Predicate_view() transaction.commit() # Recreate class with a newly added constraint synchronizeDynamicModules(portal, force=True) # Load test_module test_module = getattr(portal, 'Test Migration') test_module.objectValues() # Then close this new connection. transaction.abort() con.close() # This code depends on ZODB implementation. for i in db.pool.available[:]: if i[1] == con: db.pool.available.remove(i) db.pool.all.remove(con) del con # Back to the default connection. transaction.abort() self.app._p_jar._resetCache() setSite(old_site) # Call checkConsistency and make sure that ConnectionStateError does not occur. self.assert_(self.test_module.checkConsistency())
def testClassHierarchyAfterReset(self): """ Check that after a class reset, the class hierarchy is unchanged until un-ghostification happens. This is very important for multithreaded environments: Thread A. reset dynamic classes Thread B. in Folder code for instance: CMFBTreeFolder.method(self) If a reset happens before the B) method call, and does not keep the correct hierarchy (for instance Folder superclass is removed from the mro()), a TypeError might be raised: "method expected CMFBTreeFolder instance, got erp5.portal_type.xxx instead" This used to be broken because the ghost state was only what is called lazy_class.InitGhostBase: a "simple" subclass of ERP5Type.Base """ name = "testClassHierarchyAfterReset Module" types_tool = self.portal.portal_types ptype = types_tool.newContent(id=name, type_class="Folder") transaction.commit() module_class = types_tool.getPortalTypeClass(name) module_class.loadClass() # first manually reset and check that everything works from Products.ERP5Type.Core.Folder import Folder self.assertTrue(issubclass(module_class, Folder)) synchronizeDynamicModules(self.portal, force=True) self.assertTrue(issubclass(module_class, Folder)) # then change the type value to something not descending from Folder # and check behavior ptype.setTypeClass('Address') # while the class has not been reset is should still descend from Folder self.assertTrue(issubclass(module_class, Folder)) # finish transaction and trigger workflow/DynamicModule reset transaction.commit() # while the class has not been unghosted it's still a Folder self.assertTrue(issubclass(module_class, Folder)) # but it changes as soon as the class is loaded module_class.loadClass() self.assertFalse(issubclass(module_class, Folder))
def afterSetUp(self): """ Create a test Property Sheet (and its properties) """ portal = self.getPortal() # Create the test Property Sheet try: self.test_property_sheet = portal.portal_property_sheets.TestMigration do_create = False except AttributeError: self.test_property_sheet = \ portal.portal_property_sheets.newContent(id='TestMigration', portal_type='Property Sheet') do_create = True if do_create: # Create a new Standard Property to test constraints and a # Property Existence Constraint in the test Property Sheet self._newStandardProperty('constraint') self._newPropertyExistenceConstraint() # Create a Category Existence Constraint in the test Property # Sheet self._newCategoryExistenceConstraint() # Create an Attribute Equality Constraint in the test Property # Sheet self._newAttributeEqualityConstraint() # Create a Content Existence Constraint in the test Property # Sheet self._newContentExistenceConstraint() # Create a Category Membership Arity Constraint without # acquisition in the test Property Sheet self._newCategoryMembershipArityConstraint( 'test_category_membership_arity_constraint') # Create a Category Membership Arity Constraint with acquisition # in the test Property Sheet self._newCategoryMembershipArityConstraint( 'test_category_membership_arity_constraint_with_acquisition', use_acquisition=True) # Create a Category Related Membership Arity Constraint in the # test Property Sheet self._newCategoryRelatedMembershipArityConstraint() # Create a TALES Constraint in the test Property Sheet self._newTALESConstraint() # Create a Property Type Validity Constraint in the test Property Sheet self._newPropertyTypeValidityConstraint() # Create all the test Properties for operation_type in ('change', 'delete', 'assign'): self._newStandardProperty(operation_type) self._newAcquiredProperty(operation_type) self._newCategoryProperty(operation_type) self._newDynamicCategoryProperty(operation_type) # Bind all test properties to this instance, so they can be # accessed easily in further tests for property in self.test_property_sheet.contentValues(): setattr(self, property.getReference(), property) # Create a Portal Type for the tests, this is necessary, otherwise # there will be no accessor holder generated try: self.test_portal_type = getattr(portal.portal_types, 'Test Migration') except AttributeError: self.test_portal_type = portal.portal_types.newContent( id='Test Migration', portal_type='Base Type', type_class='Folder', type_property_sheet_list=('TestMigration',), type_base_category_list=('test_category_existence_constraint',), type_filter_content_type=False) # Create a Portal Type for subobject of Test Migration try: self.test_subobject_portal_type = getattr(portal.portal_types, 'Test Document') except AttributeError: self.test_subobject_portal_type = portal.portal_types.newContent( id='Test Document', portal_type='Base Type', type_class='Folder', type_filter_content_type=False) self.test_portal_type.setTypeAllowedContentTypeList(['Test Document']) # Create a test module, meaningful to force generation of # TestMigration accessor holders and check the constraints try: self.test_module = getattr(portal, 'Test Migration') except AttributeError: self.test_module = portal.newContent(id='Test Migration', portal_type='Test Migration') # Make sure there is no pending transaction which could interfere # with the tests transaction.commit() self.tic() # Ensure that erp5.acessor_holder is empty synchronizeDynamicModules(portal, force=True)
def test_00_updateBusinessTemplateFromUrl_simple(self): """ Test updateBusinessTemplateFromUrl method By default if a new business template has revision != previous one the new bt5 is not installed, only imported. """ self._svn_setup_ssl() # we make this class a global so that it can be pickled global PropertiesTool # pylint:disable=global-variable-not-assigned class PropertiesTool(ActionsTool): # pylint:disable=redefined-outer-name id = 'portal_properties' cls = PropertiesTool # Assign a fake properties tool to the portal tool = PropertiesTool() self.portal._setObject(tool.id, tool, set_owner=False, suppress_events=True) del tool self.commit() template_tool = self.portal.portal_templates url = 'https://svn.erp5.org/repos/public/erp5/trunk/bt5/erp5_csv_style' template_tool.updateBusinessTemplateFromUrl(url) old_bt = template_tool.getInstalledBusinessTemplate('erp5_csv_style') # fake different revision old_bt.setRevision('') # Break the properties tool self.assertIs(self.portal.portal_properties.__class__, cls) self.commit() self.portal._p_jar.cacheMinimize() del PropertiesTool self.assertIsNot(self.portal.portal_properties.__class__, cls) # Remove portal.portal_properties from Products.ERP5Type.dynamic.portal_type_class import \ _bootstrapped, synchronizeDynamicModules _bootstrapped.remove(self.portal.id) synchronizeDynamicModules(self.portal, force=True) # The bt from this repo url = self._getBTPathAndIdList(('erp5_csv_style', ))[0][0] new_bt = template_tool.updateBusinessTemplateFromUrl(url) self.assertNotEquals(old_bt, new_bt) self.assertEqual('erp5_csv_style', new_bt.getTitle()) # Test Another time with definning an ID old_bt = new_bt old_bt.setRevision('') template_tool.updateBusinessTemplateFromUrl(url, id="new_erp5_csv_style") new_bt = template_tool.getInstalledBusinessTemplate('erp5_csv_style') self.assertNotEquals(old_bt, new_bt) self.assertEqual('erp5_csv_style', new_bt.getTitle()) self.assertEqual('new_erp5_csv_style', new_bt.getId()) # Test if the new instance with same revision is not installed. old_bt = new_bt template_tool.updateBusinessTemplateFromUrl(url, id="not_installed_bt5") new_bt = template_tool.getInstalledBusinessTemplate('erp5_csv_style') self.assertEqual(old_bt, new_bt) self.assertEqual('erp5_csv_style', new_bt.getTitle()) self.assertEqual('new_erp5_csv_style', new_bt.getId()) not_installed_bt5 = template_tool['not_installed_bt5'] self.assertEqual('erp5_csv_style', not_installed_bt5.getTitle()) self.assertEqual(not_installed_bt5.getInstallationState(), "not_installed") self.assertEqual(not_installed_bt5.getRevision(), new_bt.getRevision())