Commit 193c3d7a authored by Arnaud Fontaine's avatar Arnaud Fontaine

ZODB Components not following ID=(document|extension|test).VERSION.REFERENCE...

ZODB Components not following ID=(document|extension|test).VERSION.REFERENCE convention are not importable anymore.

Import mechanism now relies on this convention to find the document in
portal_components.

This is necessary to get rid of _registry_dict (ZODB Components equivalent of
document_class_registry), along with several locks. This used to be a mapping
between (reference, version) to ID stored outside of Zope Transactions as a
dict on global erp5.component.XXX modules.

This did not work for long transactions such as installing many bt5s:
  1. Transaction1: Install a bt5 with ZODB Component "Foo".
     => Trigger a reset and _registry_dict is cleared.
  2. Transaction2: TimerServer kicks in.
     => Generate _registry_dict without "Foo".
  3. Transaction1: Install another bt5 using "Foo".
     => "Foo" not in _registry_dict and thus considered not present.
parent 9f6e6094
...@@ -4234,7 +4234,7 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem): ...@@ -4234,7 +4234,7 @@ class DocumentTemplateItem(FilesystemToZodbTemplateItem):
@staticmethod @staticmethod
def _getZodbObjectId(id): def _getZodbObjectId(id):
return DocumentComponent._getIdPrefix() + '.' + id return DocumentComponent.getIdPrefix() + '.' + id
@staticmethod @staticmethod
def _getFilesystemPath(class_id): def _getFilesystemPath(class_id):
...@@ -4342,7 +4342,7 @@ class ExtensionTemplateItem(DocumentTemplateItem): ...@@ -4342,7 +4342,7 @@ class ExtensionTemplateItem(DocumentTemplateItem):
@staticmethod @staticmethod
def _getZodbObjectId(id): def _getZodbObjectId(id):
return ExtensionComponent._getIdPrefix() + '.' + id return ExtensionComponent.getIdPrefix() + '.' + id
def getTemplateIdList(self): def getTemplateIdList(self):
return self.getTemplateExtensionIdList() return self.getTemplateExtensionIdList()
...@@ -4363,7 +4363,7 @@ class TestTemplateItem(DocumentTemplateItem): ...@@ -4363,7 +4363,7 @@ class TestTemplateItem(DocumentTemplateItem):
@staticmethod @staticmethod
def _getZodbObjectId(id): def _getZodbObjectId(id):
return TestComponent._getIdPrefix() + '.' + id return TestComponent.getIdPrefix() + '.' + id
def getTemplateIdList(self): def getTemplateIdList(self):
return self.getTemplateTestIdList() return self.getTemplateTestIdList()
......
...@@ -7349,7 +7349,7 @@ class TestBusinessTemplate(BusinessTemplateMixin): ...@@ -7349,7 +7349,7 @@ class TestBusinessTemplate(BusinessTemplateMixin):
Folder._setOb(self, id, object) Folder._setOb(self, id, object)
""") """)
component_id_prefix = DocumentComponent._getIdPrefix() component_id_prefix = DocumentComponent.getIdPrefix()
component_portal_type = DocumentComponent.portal_type component_portal_type = DocumentComponent.portal_type
tool_type = 'My Tool' tool_type = 'My Tool'
tool_class = 'MyTool' tool_class = 'MyTool'
...@@ -7859,7 +7859,7 @@ class TestDocumentTemplateItem(BusinessTemplateMixin): ...@@ -7859,7 +7859,7 @@ class TestDocumentTemplateItem(BusinessTemplateMixin):
sequence_list.addSequenceString(sequence_string) sequence_list.addSequenceString(sequence_string)
sequence_list.play(self) sequence_list.play(self)
component_id_prefix = DocumentComponent._getIdPrefix() component_id_prefix = DocumentComponent.getIdPrefix()
component_portal_type = DocumentComponent.portal_type component_portal_type = DocumentComponent.portal_type
def stepCreateZodbDocument(self, sequence=None, **kw): def stepCreateZodbDocument(self, sequence=None, **kw):
...@@ -8345,7 +8345,7 @@ class TestExtensionTemplateItem(TestDocumentTemplateItem): ...@@ -8345,7 +8345,7 @@ class TestExtensionTemplateItem(TestDocumentTemplateItem):
template_property = 'template_extension_id_list' template_property = 'template_extension_id_list'
# Specific to ZODB Extension Component # Specific to ZODB Extension Component
component_id_prefix = ExtensionComponent._getIdPrefix() component_id_prefix = ExtensionComponent.getIdPrefix()
component_portal_type = ExtensionComponent.portal_type component_portal_type = ExtensionComponent.portal_type
from Products.ERP5Type.Core.TestComponent import TestComponent from Products.ERP5Type.Core.TestComponent import TestComponent
...@@ -8364,7 +8364,7 @@ class TestTestTemplateItem(TestDocumentTemplateItem): ...@@ -8364,7 +8364,7 @@ class TestTestTemplateItem(TestDocumentTemplateItem):
template_property = 'template_test_id_list' template_property = 'template_test_id_list'
# Specific to ZODB Extension Component # Specific to ZODB Extension Component
component_id_prefix = TestComponent._getIdPrefix() component_id_prefix = TestComponent.getIdPrefix()
component_portal_type = TestComponent.portal_type component_portal_type = TestComponent.portal_type
def stepAddTestToBusinessTemplate(self, sequence=None, **kw): def stepAddTestToBusinessTemplate(self, sequence=None, **kw):
......
...@@ -60,5 +60,5 @@ class DocumentComponent(ComponentMixin, TextContentHistoryMixin): ...@@ -60,5 +60,5 @@ class DocumentComponent(ComponentMixin, TextContentHistoryMixin):
return 'erp5.component.document' return 'erp5.component.document'
@staticmethod @staticmethod
def _getIdPrefix(): def getIdPrefix():
return 'document' return 'document'
...@@ -59,5 +59,5 @@ class ExtensionComponent(ComponentMixin, TextContentHistoryMixin): ...@@ -59,5 +59,5 @@ class ExtensionComponent(ComponentMixin, TextContentHistoryMixin):
return 'erp5.component.extension' return 'erp5.component.extension'
@staticmethod @staticmethod
def _getIdPrefix(): def getIdPrefix():
return 'extension' return 'extension'
...@@ -60,5 +60,5 @@ class TestComponent(ComponentMixin, TextContentHistoryMixin): ...@@ -60,5 +60,5 @@ class TestComponent(ComponentMixin, TextContentHistoryMixin):
return 'erp5.component.test' return 'erp5.component.test'
@staticmethod @staticmethod
def _getIdPrefix(): def getIdPrefix():
return 'test' return 'test'
...@@ -210,7 +210,23 @@ class TypesTool(TypeProvider): ...@@ -210,7 +210,23 @@ class TypesTool(TypeProvider):
document_type_set = set(document_class_registry) document_type_set = set(document_class_registry)
import erp5.component.document import erp5.component.document
document_type_set.update(erp5.component.document._registry_dict) portal = self.getPortalObject()
version_priority_set = set(portal.getVersionPriorityNameList())
# objectValues should not be used for a large number of objects, but
# this is only done upon reset, moreover using the Catalog is too risky
# as it lags behind and depends upon objects being reindexed
for component in portal.portal_components.objectValues(portal_type='Document Component'):
# Only consider modified or validated states as state transition will
# be handled by component_validation_workflow which will take care of
# updating the registry
validation_state_tuple = component.getValidationState()
if validation_state_tuple in ('modified', 'validated'):
version = component.getVersion(validated_only=True)
# The versions should have always been set on ERP5Site property
# beforehand
if version in version_priority_set:
document_type_set.add(component.getReference(validated_only=True))
return sorted(document_type_set) return sorted(document_type_set)
......
...@@ -39,6 +39,7 @@ from Products.ERP5Type.Globals import get_request ...@@ -39,6 +39,7 @@ from Products.ERP5Type.Globals import get_request
from . import aq_method_lock from . import aq_method_lock
from types import ModuleType from types import ModuleType
from zLOG import LOG, BLATHER, WARNING from zLOG import LOG, BLATHER, WARNING
from Acquisition import aq_base
class ComponentVersionPackage(ModuleType): class ComponentVersionPackage(ModuleType):
""" """
...@@ -73,9 +74,9 @@ class ComponentDynamicPackage(ModuleType): ...@@ -73,9 +74,9 @@ class ComponentDynamicPackage(ModuleType):
self._namespace = namespace self._namespace = namespace
self._namespace_prefix = namespace + '.' self._namespace_prefix = namespace + '.'
self._id_prefix = namespace.rsplit('.', 1)[1]
self._portal_type = portal_type self._portal_type = portal_type
self.__version_suffix_len = len('_version') self.__version_suffix_len = len('_version')
self.__registry_dict = collections.defaultdict(dict)
self.__fullname_source_code_dict = {} self.__fullname_source_code_dict = {}
# Add this module to sys.path for future imports # Add this module to sys.path for future imports
...@@ -84,48 +85,6 @@ class ComponentDynamicPackage(ModuleType): ...@@ -84,48 +85,6 @@ class ComponentDynamicPackage(ModuleType):
# Add the import hook # Add the import hook
sys.meta_path.append(self) sys.meta_path.append(self)
@property
def _registry_dict(self):
"""
Create the component registry, this is very similar to
Products.ERP5Type.document_class_registry and avoids checking whether a
Component exists at each call at the expense of being slower when being
re-generated after a reset. Moreover, it allows to handle reference
easily.
"""
if not self.__registry_dict:
portal = getSite()
try:
component_tool = portal.portal_components
# When installing ERP5 site, erp5_core_components has not been installed
# yet, thus this will obviously failed...
#
# XXX-arnau: Is this needed as it is now done in synchronizeDynamicModules?
except AttributeError:
return {}
version_priority_set = set(portal.getVersionPriorityNameList())
# objectValues should not be used for a large number of objects, but
# this is only done upon reset, moreover using the Catalog is too risky
# as it lags behind and depends upon objects being reindexed
for component in component_tool.objectValues(portal_type=self._portal_type):
# Only consider modified or validated states as state transition will
# be handled by component_validation_workflow which will take care of
# updating the registry
validation_state_tuple = component.getValidationState()
if validation_state_tuple in ('modified', 'validated'):
version = component.getVersion(validated_only=True)
# The versions should have always been set on ERP5Site property
# beforehand
if version in version_priority_set:
reference = component.getReference(validated_only=True)
self.__registry_dict[reference][version] = (component.getId(),
component._p_oid)
return self.__registry_dict
def get_source(self, fullname): def get_source(self, fullname):
""" """
PEP-302 function to get the source code, used mainly by linecache for PEP-302 function to get the source code, used mainly by linecache for
...@@ -160,12 +119,6 @@ class ComponentDynamicPackage(ModuleType): ...@@ -160,12 +119,6 @@ class ComponentDynamicPackage(ModuleType):
except RuntimeError: except RuntimeError:
import_lock_held = False import_lock_held = False
# The import lock has been released, but as _registry_dict may be
# initialized or cleared, no other Components should access this critical
# region
#
# TODO-arnau: Too coarse-grain?
aq_method_lock.acquire()
try: try:
site = getSite() site = getSite()
...@@ -173,6 +126,7 @@ class ComponentDynamicPackage(ModuleType): ...@@ -173,6 +126,7 @@ class ComponentDynamicPackage(ModuleType):
# erp5.component.XXX.YYY.ZZZ where erp5.component.XXX.YYY is the current # erp5.component.XXX.YYY.ZZZ where erp5.component.XXX.YYY is the current
# Component where an import is done # Component where an import is done
name = fullname[len(self._namespace_prefix):] name = fullname[len(self._namespace_prefix):]
# name=VERSION_version.REFERENCE
if '.' in name: if '.' in name:
try: try:
version, name = name.split('.') version, name = name.split('.')
...@@ -180,23 +134,38 @@ class ComponentDynamicPackage(ModuleType): ...@@ -180,23 +134,38 @@ class ComponentDynamicPackage(ModuleType):
except ValueError: except ValueError:
return None return None
try: id_ = "%s.%s.%s" % (self._id_prefix, version, name)
self._registry_dict[name][version] # aq_base() because this should not go up to ERP5Site and trigger
except KeyError: # side-effects, after all this only check for existence...
component = getattr(aq_base(site.portal_components), id_, None)
if component is None or component.getValidationState() not in ('modified',
'validated'):
return None return None
# Skip unavailable components, otherwise Products for example could be # Skip unavailable components, otherwise Products for example could be
# wrongly considered as importable and thus the actual filesystem class # wrongly considered as importable and thus the actual filesystem class
# ignored # ignored
elif (name not in self._registry_dict and #
name[:-self.__version_suffix_len] not in site.getVersionPriorityNameList()): # name=VERSION_version
return None elif name.endswith('_version'):
if name[:-self.__version_suffix_len] not in site.getVersionPriorityNameList():
return None
# name=REFERENCE
else:
component_tool = aq_base(site.portal_components)
for version in site.getVersionPriorityNameList():
id_ = "%s.%s.%s" % (self._id_prefix, version, name)
component = getattr(component_tool, id_, None)
if component is not None and component.getValidationState() in ('modified',
'validated'):
break
else:
return None
return self return self
finally: finally:
aq_method_lock.release()
# Internal release of import lock at the end of import machinery will # Internal release of import lock at the end of import machinery will
# fail if the hook is not acquired # fail if the hook is not acquired
if import_lock_held: if import_lock_held:
...@@ -258,25 +227,17 @@ class ComponentDynamicPackage(ModuleType): ...@@ -258,25 +227,17 @@ class ComponentDynamicPackage(ModuleType):
raise ImportError("%s: should be %s.VERSION.COMPONENT_REFERENCE (%s)" % \ raise ImportError("%s: should be %s.VERSION.COMPONENT_REFERENCE (%s)" % \
(fullname, self._namespace, error)) (fullname, self._namespace, error))
try: component_id = "%s.%s.%s" % (self._id_prefix, version, name)
component_id = self._registry_dict[name][version][0]
except KeyError:
raise ImportError("%s: version %s of Component %s could not be found" % \
(fullname, version, name))
# Otherwise, find the Component with the highest version priority # Otherwise, find the Component with the highest version priority
else: else:
try: component_tool = aq_base(site.portal_components)
component_version_dict = self._registry_dict[name]
except KeyError:
raise ImportError("%s: Component %s could not be found" % (fullname,
name))
# Version priority name list is ordered in descending order # Version priority name list is ordered in descending order
for version in site.getVersionPriorityNameList(): for version in site.getVersionPriorityNameList():
component_id_uid_tuple = component_version_dict.get(version) component_id = "%s.%s.%s" % (self._id_prefix, version, name)
if component_id_uid_tuple is not None: component = getattr(component_tool, component_id, None)
component_id = component_id_uid_tuple[0] if component is not None and component.getValidationState() in ('modified',
'validated'):
break break
else: else:
raise ImportError("%s: no version of Component %s in Site priority" % \ raise ImportError("%s: no version of Component %s in Site priority" % \
...@@ -433,8 +394,7 @@ class ComponentDynamicPackage(ModuleType): ...@@ -433,8 +394,7 @@ class ComponentDynamicPackage(ModuleType):
if sub_package: if sub_package:
package = sub_package package = sub_package
else: else:
# Clear the Component registry and source code dict only once # Clear the source code dict only once
self.__registry_dict.clear()
self.__fullname_source_code_dict.clear() self.__fullname_source_code_dict.clear()
package = self package = self
......
...@@ -63,7 +63,7 @@ class IComponent(Interface): ...@@ -63,7 +63,7 @@ class IComponent(Interface):
Return the module name where Component module are loaded into Return the module name where Component module are loaded into
""" """
def _getIdPrefix(): def getIdPrefix():
""" """
Return the ID prefix for Component objects Return the ID prefix for Component objects
""" """
......
...@@ -180,14 +180,14 @@ class ComponentMixin(PropertyRecordableMixin, Base): ...@@ -180,14 +180,14 @@ class ComponentMixin(PropertyRecordableMixin, Base):
default='') default='')
} }
_message_invalid_id = "ID is invalid, should be '${id_prefix}.VERSION.REFERENCE'"
_message_reference_not_set = "Reference must be set" _message_reference_not_set = "Reference must be set"
_message_invalid_reference = "Reference cannot end with '_version' or "\ _message_invalid_reference = "Reference cannot end with '_version' or "\
"start with '_' or be equal to find_module, load_module or reset" "start with '_' or be equal to find_module, load_module or reset"
_message_version_not_set = "Version must be set" _message_version_not_set = "Version must be set"
_message_invalid_version = "Version cannot start with '_'" _message_invalid_version = "Version cannot start with '_'"
_message_duplicated_version_reference = "${id} is validated has the same "\
"Reference and Version"
_message_text_content_not_set = "No source code" _message_text_content_not_set = "No source code"
_message_text_content_error = "Error in Source Code: ${error_message}" _message_text_content_error = "Error in Source Code: ${error_message}"
...@@ -216,10 +216,27 @@ class ComponentMixin(PropertyRecordableMixin, Base): ...@@ -216,10 +216,27 @@ class ComponentMixin(PropertyRecordableMixin, Base):
""" """
error_list = super(ComponentMixin, self).checkConsistency(*args, **kw) error_list = super(ComponentMixin, self).checkConsistency(*args, **kw)
object_relative_url = self.getRelativeUrl() object_relative_url = self.getRelativeUrl()
is_id_invalid = False
try:
prefix, version, reference = self.getId().split('.')
except ValueError:
is_id_invalid = True
else:
if (prefix != self.getIdPrefix() or
version != self.getVersion() or
reference != self.getReference()):
is_id_invalid = True
if is_id_invalid:
error_list.append(
ConsistencyMessage(self,
object_relative_url,
message=self._message_invalid_id,
mapping={'id_prefix': self.getIdPrefix()}))
reference = self.getReference() reference = self.getReference()
reference_has_error = False
if not reference: if not reference:
reference_has_error = True
error_list.append( error_list.append(
ConsistencyMessage(self, ConsistencyMessage(self,
object_relative_url, object_relative_url,
...@@ -229,7 +246,6 @@ class ComponentMixin(PropertyRecordableMixin, Base): ...@@ -229,7 +246,6 @@ class ComponentMixin(PropertyRecordableMixin, Base):
elif (reference.endswith('_version') or elif (reference.endswith('_version') or
reference[0] == '_' or reference[0] == '_' or
reference in ('find_module', 'load_module', 'reset')): reference in ('find_module', 'load_module', 'reset')):
reference_has_error = True
error_list.append( error_list.append(
ConsistencyMessage(self, ConsistencyMessage(self,
object_relative_url, object_relative_url,
...@@ -247,26 +263,6 @@ class ComponentMixin(PropertyRecordableMixin, Base): ...@@ -247,26 +263,6 @@ class ComponentMixin(PropertyRecordableMixin, Base):
object_relative_url, object_relative_url,
message=self._message_invalid_version, message=self._message_invalid_version,
mapping={})) mapping={}))
else:
package = __import__(self._getDynamicModuleNamespace(), globals(),
fromlist=[self._getDynamicModuleNamespace()], level=0)
component_id = None
component_uid = None
from Products.ERP5Type.dynamic import aq_method_lock
with aq_method_lock:
component_id_uid_tuple = package._registry_dict.get(
self.getReference(), {}).get(self.getVersion(), None)
if component_id_uid_tuple:
component_id, component_uid = component_id_uid_tuple
if (component_id is not None and component_uid is not None and
not reference_has_error and
component_uid != self._p_oid and component_id != self.getId()):
error_list.append(
ConsistencyMessage(self,
object_relative_url,
message=self._message_duplicated_version_reference,
mapping=dict(id=component_id)))
text_content = self.getTextContent() text_content = self.getTextContent()
if not text_content: if not text_content:
...@@ -360,7 +356,7 @@ class ComponentMixin(PropertyRecordableMixin, Base): ...@@ -360,7 +356,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
# needed when importing from filesystem, moreover errors may occur # needed when importing from filesystem, moreover errors may occur
# if in the same transaction a Component is created and another # if in the same transaction a Component is created and another
# one depending upon the former... # one depending upon the former...
object_id = '%s.%s.%s' % (cls._getIdPrefix(), version, reference) object_id = '%s.%s.%s' % (cls.getIdPrefix(), version, reference)
new_component = context.newContent(id=object_id, new_component = context.newContent(id=object_id,
reference=reference, reference=reference,
version=version, version=version,
......
...@@ -1316,7 +1316,7 @@ class _TestZodbComponent(SecurityTestCase): ...@@ -1316,7 +1316,7 @@ class _TestZodbComponent(SecurityTestCase):
def afterSetUp(self): def afterSetUp(self):
self._component_tool = self.portal.portal_components self._component_tool = self.portal.portal_components
self._module = __import__(self._getComponentModuleName(), self._module = __import__(self._document_class._getDynamicModuleNamespace(),
fromlist=['erp5.component']) fromlist=['erp5.component'])
self._component_tool.reset(force=True, self._component_tool.reset(force=True,
reset_portal_type_at_transaction_boundary=True) reset_portal_type_at_transaction_boundary=True)
...@@ -1325,28 +1325,18 @@ class _TestZodbComponent(SecurityTestCase): ...@@ -1325,28 +1325,18 @@ class _TestZodbComponent(SecurityTestCase):
""" """
Create new Component Create new Component
""" """
full_id = '%s.%s.%s' % (self._getComponentModuleName(), if id_ is None:
version + '_version', id_ = '%s.%s.%s' % (self._document_class.getIdPrefix(), version, reference)
reference)
if id_ is not None:
full_id += '.%s' % id_
return self._component_tool.newContent( return self._component_tool.newContent(
id=full_id, id=id_,
version=version, version=version,
reference=reference, reference=reference,
text_content=text_content, text_content=text_content,
portal_type=self._component_portal_type) portal_type=self._portal_type)
@abc.abstractmethod
def _getComponentModuleName(self):
"""
Abstract method defining ZODB Component top-level package name
"""
def _getComponentFullModuleName(self, module_name): def _getComponentFullModuleName(self, module_name):
return self._getComponentModuleName() + '.' + module_name return self._document_class._getDynamicModuleNamespace() + '.' + module_name
def failIfModuleImportable(self, module_name): def failIfModuleImportable(self, module_name):
""" """
...@@ -1380,7 +1370,7 @@ class _TestZodbComponent(SecurityTestCase): ...@@ -1380,7 +1370,7 @@ class _TestZodbComponent(SecurityTestCase):
self._getComponentFullModuleName(module_name)) self._getComponentFullModuleName(module_name))
if expected_default_version is not None: if expected_default_version is not None:
top_module_name = self._getComponentModuleName() top_module_name = self._document_class._getDynamicModuleNamespace()
top_module = __import__(top_module_name, level=0, fromlist=[top_module_name]) top_module = __import__(top_module_name, level=0, fromlist=[top_module_name])
# The module must be available in its default version # The module must be available in its default version
...@@ -1410,7 +1400,7 @@ class _TestZodbComponent(SecurityTestCase): ...@@ -1410,7 +1400,7 @@ class _TestZodbComponent(SecurityTestCase):
def _importModule(self, module_name): def _importModule(self, module_name):
return __import__(self._getComponentFullModuleName(module_name), return __import__(self._getComponentFullModuleName(module_name),
fromlist=[self._getComponentModuleName()], fromlist=[self._document_class._getDynamicModuleNamespace()],
level=0) level=0)
def testValidateInvalidateDelete(self): def testValidateInvalidateDelete(self):
...@@ -1492,6 +1482,37 @@ class _TestZodbComponent(SecurityTestCase): ...@@ -1492,6 +1482,37 @@ class _TestZodbComponent(SecurityTestCase):
if o.getReference() == 'TestValidateInvalidateComponent'], if o.getReference() == 'TestValidateInvalidateComponent'],
[]) [])
def testInvalidId(self):
"""
Check whether checkConsistency has been properly implemented for checking
Component ID which should follow the format 'getIdPrefix().VERSION.REFERENCE'
"""
id_prefix = self._document_class.getIdPrefix()
version = "erp5"
reference = "TestWithInvalidId"
valid_id = "%s.%s.%s" % (id_prefix, version, reference)
component = self._newComponent(reference,
'def foobar():\n return 42',
version,
valid_id)
self.tic()
self.assertEqual(component.checkConsistency(), [])
for invalid_id in ("INVALID_PREFIX.%s.%s" % (version, reference),
"%s.INVALID_VERSION.%s" % (id_prefix, reference),
"%s.%s.INVALID_REFERENCE" % (id_prefix, version)):
component.setId(invalid_id)
self.tic()
self.assertEqual(
[m.getMessage().translate() for m in component.checkConsistency()],
[self.portal.Base_translateString(ComponentMixin._message_invalid_id,
mapping={'id_prefix': id_prefix})])
component.setId(valid_id)
self.tic()
self.assertEqual(component.checkConsistency(), [])
def testReferenceWithReservedKeywords(self): def testReferenceWithReservedKeywords(self):
""" """
Check whether checkConsistency has been properly implemented for checking Check whether checkConsistency has been properly implemented for checking
...@@ -1537,6 +1558,10 @@ class _TestZodbComponent(SecurityTestCase): ...@@ -1537,6 +1558,10 @@ class _TestZodbComponent(SecurityTestCase):
'find_module': ComponentMixin._message_invalid_reference, 'find_module': ComponentMixin._message_invalid_reference,
'load_module': ComponentMixin._message_invalid_reference} 'load_module': ComponentMixin._message_invalid_reference}
invalid_id_error_message = self.portal.Base_translateString(
ComponentMixin._message_invalid_id,
mapping={'id_prefix': self._document_class.getIdPrefix()})
for invalid_reference, error_message in invalid_reference_dict.iteritems(): for invalid_reference, error_message in invalid_reference_dict.iteritems():
# Reset should not be performed # Reset should not be performed
ComponentTool.reset = assertResetNotCalled ComponentTool.reset = assertResetNotCalled
...@@ -1550,7 +1575,8 @@ class _TestZodbComponent(SecurityTestCase): ...@@ -1550,7 +1575,8 @@ class _TestZodbComponent(SecurityTestCase):
self.assertEqual(component.getValidationState(), 'modified') self.assertEqual(component.getValidationState(), 'modified')
self.assertEqual([m.getMessage().translate() self.assertEqual([m.getMessage().translate()
for m in component.checkConsistency()], for m in component.checkConsistency()],
[error_message]) [invalid_id_error_message,
error_message])
self.assertEqual(component.getTextContentErrorMessageList(), []) self.assertEqual(component.getTextContentErrorMessageList(), [])
self.assertEqual(component.getTextContentWarningMessageList(), []) self.assertEqual(component.getTextContentWarningMessageList(), [])
self.assertEqual(component.getReference(), invalid_reference) self.assertEqual(component.getReference(), invalid_reference)
...@@ -1620,6 +1646,10 @@ class _TestZodbComponent(SecurityTestCase): ...@@ -1620,6 +1646,10 @@ class _TestZodbComponent(SecurityTestCase):
# make sense to have reference starting with '_' # make sense to have reference starting with '_'
'_TestVersionWithReservedKeywords': ComponentMixin._message_invalid_version} '_TestVersionWithReservedKeywords': ComponentMixin._message_invalid_version}
invalid_id_error_message = self.portal.Base_translateString(
ComponentMixin._message_invalid_id,
mapping={'id_prefix': self._document_class.getIdPrefix()})
for invalid_version, error_message in invalid_version_dict.iteritems(): for invalid_version, error_message in invalid_version_dict.iteritems():
# Reset should not be performed # Reset should not be performed
ComponentTool.reset = assertResetNotCalled ComponentTool.reset = assertResetNotCalled
...@@ -1633,7 +1663,8 @@ class _TestZodbComponent(SecurityTestCase): ...@@ -1633,7 +1663,8 @@ class _TestZodbComponent(SecurityTestCase):
self.assertEqual(component.getValidationState(), 'modified') self.assertEqual(component.getValidationState(), 'modified')
self.assertEqual([m.getMessage().translate() self.assertEqual([m.getMessage().translate()
for m in component.checkConsistency()], for m in component.checkConsistency()],
[error_message]) [invalid_id_error_message,
error_message])
self.assertEqual(component.getTextContentErrorMessageList(), []) self.assertEqual(component.getTextContentErrorMessageList(), [])
self.assertEqual(component.getTextContentWarningMessageList(), []) self.assertEqual(component.getTextContentWarningMessageList(), [])
self.assertEqual(component.getVersion(), invalid_version) self.assertEqual(component.getVersion(), invalid_version)
...@@ -1797,7 +1828,7 @@ class _TestZodbComponent(SecurityTestCase): ...@@ -1797,7 +1828,7 @@ class _TestZodbComponent(SecurityTestCase):
component.validate() component.validate()
self.tic() self.tic()
top_module_name = self._getComponentModuleName() top_module_name = self._document_class._getDynamicModuleNamespace()
# Create a new Component which uses a specific version of the previously # Create a new Component which uses a specific version of the previously
# created Component # created Component
...@@ -1877,7 +1908,7 @@ def bar(*args, **kwargs): ...@@ -1877,7 +1908,7 @@ def bar(*args, **kwargs):
# added to ERP5Site version priorities # added to ERP5Site version priorities
self.failIfModuleImportable('foo_version.TestVersionPriority') self.failIfModuleImportable('foo_version.TestVersionPriority')
top_module_name = self._getComponentModuleName() top_module_name = self._document_class._getDynamicModuleNamespace()
top_module = __import__(top_module_name, level=0, top_module = __import__(top_module_name, level=0,
fromlist=[top_module_name]) fromlist=[top_module_name])
...@@ -1963,45 +1994,6 @@ def bar(*args, **kwargs): ...@@ -1963,45 +1994,6 @@ def bar(*args, **kwargs):
self.assertUserCanModifyDocument(user_id, component) self.assertUserCanModifyDocument(user_id, component)
self.assertUserCanDeleteDocument(user_id, component) self.assertUserCanDeleteDocument(user_id, component)
def testValidateComponentWithSameReferenceVersionAlreadyValidated(self):
reference = 'ValidateComponentWithSameReferenceVersionAlreadyValidated'
component = self._newComponent(reference, 'def foo():\n print "ok"')
component.validate()
self.tic()
component_dup = self._newComponent(reference, 'def foo():\n print "ok"',
id_='duplicated')
self.tic()
from Products.DCWorkflow.DCWorkflow import ValidationFailed
self.assertRaises(ValidationFailed,
self.portal.portal_workflow.doActionFor,
component_dup, 'validate_action')
self.assertEqual(component_dup.getValidationState(), 'draft')
component_dup.setReference(reference + '_copy')
component_dup.validate()
self.tic()
component_dup.setReference(reference)
self.tic()
self.assertEqual(component_dup.getValidationState(), 'modified')
self.assertEqual(component_dup.getReference(), reference)
self.assertEqual(component_dup.getReference(validated_only=True),
reference + '_copy')
component_dup.invalidate()
self.tic()
component_dup.setReference(reference)
self.assertRaises(ValidationFailed,
self.portal.portal_workflow.doActionFor,
component_dup, 'validate_action')
self.assertEqual(component_dup.getValidationState(), 'invalidated')
from Products.ERP5Type.Core.ExtensionComponent import ExtensionComponent from Products.ERP5Type.Core.ExtensionComponent import ExtensionComponent
class TestZodbExtensionComponent(_TestZodbComponent): class TestZodbExtensionComponent(_TestZodbComponent):
...@@ -2009,10 +2001,8 @@ class TestZodbExtensionComponent(_TestZodbComponent): ...@@ -2009,10 +2001,8 @@ class TestZodbExtensionComponent(_TestZodbComponent):
Tests specific to ZODB Extension Component (previously defined in bt5 and Tests specific to ZODB Extension Component (previously defined in bt5 and
installed on the filesystem in $INSTANCE_HOME/Extensions) installed on the filesystem in $INSTANCE_HOME/Extensions)
""" """
_component_portal_type = 'Extension Component' _portal_type = 'Extension Component'
_document_class = ExtensionComponent
def _getComponentModuleName(self):
return ExtensionComponent._getDynamicModuleNamespace()
def testExternalMethod(self): def testExternalMethod(self):
""" """
...@@ -2109,10 +2099,8 @@ class TestZodbDocumentComponent(_TestZodbComponent): ...@@ -2109,10 +2099,8 @@ class TestZodbDocumentComponent(_TestZodbComponent):
previously defined in bt5 and installed on the filesystem in previously defined in bt5 and installed on the filesystem in
$INSTANCE_HOME/Document. Later on, Product Documents will also be migrated $INSTANCE_HOME/Document. Later on, Product Documents will also be migrated
""" """
_component_portal_type = 'Document Component' _portal_type = 'Document Component'
_document_class = DocumentComponent
def _getComponentModuleName(self):
return DocumentComponent._getDynamicModuleNamespace()
def testAssignToPortalTypeClass(self): def testAssignToPortalTypeClass(self):
""" """
...@@ -2240,10 +2228,8 @@ class TestZodbTestComponent(_TestZodbComponent): ...@@ -2240,10 +2228,8 @@ class TestZodbTestComponent(_TestZodbComponent):
Tests specific to ZODB Test Component (known as Live Tests, and previously Tests specific to ZODB Test Component (known as Live Tests, and previously
defined in bt5 and installed in $INSTANCE_HOME/test) defined in bt5 and installed in $INSTANCE_HOME/test)
""" """
_component_portal_type = 'Test Component' _portal_type = 'Test Component'
_document_class = TestComponent
def _getComponentModuleName(self):
return TestComponent._getDynamicModuleNamespace()
def testRunLiveTest(self): def testRunLiveTest(self):
""" """
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment