Commit 2de58ec4 authored by Jérome Perrin's avatar Jérome Perrin

Merge remote-tracking branch 'upstream/master' into zope4py2

parents 341fdcf0 f269e6ca
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_jio_action</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_jio_action</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>change_function</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>Modify portal content</string>
</tuple>
</value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>2.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Change Function</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/Base_viewChangeIdDialog</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
...@@ -44,6 +44,7 @@ Embedded File | fullsize_view ...@@ -44,6 +44,7 @@ Embedded File | fullsize_view
Embedded File | view Embedded File | view
Embedded File | web_view Embedded File | web_view
Embedded Folder | view Embedded Folder | view
External Identifier | change_function
External Identifier | view External Identifier | view
Fax | change_function Fax | change_function
Fax | view Fax | view
......
...@@ -402,9 +402,9 @@ return printed ...@@ -402,9 +402,9 @@ return printed
'Base_viewGeek', 'Base_viewGeek',
'View') 'View')
form = skin_folder._getOb('Base_viewGeek', None) form = skin_folder._getOb('Base_viewGeek', None)
form.manage_addField('my_title', 'Title', 'ProxyField') form.manage_addField('my_proxy_field', 'Proxy', 'ProxyField')
field = form.my_title field = form.my_proxy_field
self.assertFalse(form.get_fields()) self.assertFalse(form.get_fields())
self.assertEqual([field], form.get_fields(include_disabled=True)) self.assertEqual([field], form.get_fields(include_disabled=True))
...@@ -413,10 +413,22 @@ return printed ...@@ -413,10 +413,22 @@ return printed
self.assertEqual('', field.get_tales('default')) self.assertEqual('', field.get_tales('default'))
regexp = '^%s$' % re.escape("Can't find the template field of" regexp = '^%s$' % re.escape("Can't find the template field of"
" <ProxyField at /%s/portal_skins/erp5_geek/Base_viewGeek/my_title>" " <ProxyField at /%s/portal_skins/erp5_geek/Base_viewGeek/my_proxy_field>"
% self.portal.getId()) % self.portal.getId())
for func in ( field.render for func in ( field.render
, partial(field.get_value, 'default') , partial(field.get_value, 'default')
, partial(field.get_recursive_tales, 'default') , partial(field.get_recursive_tales, 'default')
): ):
self.assertRaisesRegexp(BrokenProxyField, regexp, func) self.assertRaisesRegexp(BrokenProxyField, regexp, func)
# we can still view the field in ZMI
form.manage_main()
field.manage_main()
# and repair it
form.manage_addField('my_field', 'Title', 'StringField')
field.manage_edit(
{
'field_form_id': 'Base_viewGeek',
'field_field_id': 'my_field',
})
self.assertEqual(field.getTemplateField(), form.my_field)
...@@ -84,7 +84,6 @@ class TestZODBHistory(ERP5TypeTestCase): ...@@ -84,7 +84,6 @@ class TestZODBHistory(ERP5TypeTestCase):
self.assertTrue(len(history_list) > 0) self.assertTrue(len(history_list) > 0)
d = history_list[0] d = history_list[0]
changes = d['changes'] changes = d['changes']
self.assertEqual(changes['portal_type'], 'Organisation')
self.assertEqual(changes['id'], 'org') self.assertEqual(changes['id'], 'org')
self.assertTrue(changes['uid'] is not None) self.assertTrue(changes['uid'] is not None)
...@@ -140,7 +139,7 @@ class TestZODBHistory(ERP5TypeTestCase): ...@@ -140,7 +139,7 @@ class TestZODBHistory(ERP5TypeTestCase):
document.edit(title='ネクセディ', default_address_city='千代田区') document.edit(title='ネクセディ', default_address_city='千代田区')
self.commit() self.commit()
_, change, = document.Base_getZODBHistoryList() _, change, = document.Base_getZODBHistoryList()
self.assertIn('title:ネクセディ', change.getProperty('changes')) self.assertIn('title: ネクセディ', change.getProperty('changes'))
# no encoding error # no encoding error
document.Base_viewZODBHistory() document.Base_viewZODBHistory()
...@@ -167,9 +166,9 @@ class TestZODBHistory(ERP5TypeTestCase): ...@@ -167,9 +166,9 @@ class TestZODBHistory(ERP5TypeTestCase):
document.Base_viewZODBHistory() document.Base_viewZODBHistory()
change, = document.Base_getZODBHistoryList() change, = document.Base_getZODBHistoryList()
self.assertIn('data:(binary)', change.getProperty('changes')) self.assertIn('data: (binary)', change.getProperty('changes'))
self.assertIn('content_type:image/png', change.getProperty('changes')) self.assertIn('content_type: image/png', change.getProperty('changes'))
self.assertIn('title:ロゴ', change.getProperty('changes')) self.assertIn('title: ロゴ', change.getProperty('changes'))
def test_suite(): def test_suite():
......
...@@ -104,6 +104,9 @@ class TestTemplateTool(ERP5TypeTestCase): ...@@ -104,6 +104,9 @@ class TestTemplateTool(ERP5TypeTestCase):
self.assertEqual(test_web.getPortalType(), 'Business Template') self.assertEqual(test_web.getPortalType(), 'Business Template')
self.assertEqual(test_web.getTitle(), 'test_web') self.assertEqual(test_web.getTitle(), 'test_web')
self.assertEqual(len(test_web.getRevision()), 28) self.assertEqual(len(test_web.getRevision()), 28)
self.assertEqual(
test_web.getPublicationUrl(),
'http://www.erp5.org/dists/snapshot/test_bt5/test_web.bt5')
def _svn_setup_ssl(self): def _svn_setup_ssl(self):
""" """
...@@ -139,6 +142,8 @@ class TestTemplateTool(ERP5TypeTestCase): ...@@ -139,6 +142,8 @@ class TestTemplateTool(ERP5TypeTestCase):
self.assertEqual(test_web.getPortalType(), 'Business Template') self.assertEqual(test_web.getPortalType(), 'Business Template')
self.assertEqual(test_web.getTitle(), 'test_web') self.assertEqual(test_web.getTitle(), 'test_web')
self.assertEqual(len(test_web.getRevision()), 28) self.assertEqual(len(test_web.getRevision()), 28)
self.assertEqual(
test_web.getPublicationUrl(), bt5_url)
def test_00_updateBusinessTemplateFromUrl_simple(self): def test_00_updateBusinessTemplateFromUrl_simple(self):
""" """
...@@ -549,6 +554,7 @@ class TestTemplateTool(ERP5TypeTestCase): ...@@ -549,6 +554,7 @@ class TestTemplateTool(ERP5TypeTestCase):
bt = self.templates_tool.getInstalledBusinessTemplate(bt5_name, strict=True) bt = self.templates_tool.getInstalledBusinessTemplate(bt5_name, strict=True)
self.assertNotEquals(bt, None) self.assertNotEquals(bt, None)
self.assertEqual(bt.getTitle(), bt5_name) self.assertEqual(bt.getTitle(), bt5_name)
self.assertEqual(bt.getPublicationUrl(), self._getBTPathAndIdList([bt5_name])[0][0])
# Repeat operation, the bt5 should be ignored # Repeat operation, the bt5 should be ignored
self.templates_tool.installBusinessTemplateListFromRepository([bt5_name]) self.templates_tool.installBusinessTemplateListFromRepository([bt5_name])
......
import itertools import itertools
import time import time
from Products.CMFActivity.Activity.Queue import VALIDATION_ERROR_DELAY from Products.CMFActivity.Activity.Queue import VALIDATION_ERROR_DELAY
from Products.CMFActivity.ActivityTool import getCurrentNode
def waitForActivities(self, delay=100, count=None): def waitForActivities(self, delay=100, count=None):
""" """
...@@ -9,13 +10,17 @@ def waitForActivities(self, delay=100, count=None): ...@@ -9,13 +10,17 @@ def waitForActivities(self, delay=100, count=None):
RuntimeError is raised in case there is no way RuntimeError is raised in case there is no way
to finish activities. to finish activities.
""" """
activity_tool = self.getPortalObject().portal_activities
assert not (
activity_tool.isSubscribed()
and getCurrentNode() in activity_tool.getProcessingNodeList())
if count is not None: # BBB if count is not None: # BBB
# completely arbitrary conversion factor: count used to default to 1000 # completely arbitrary conversion factor: count used to default to 1000
# and I (just as arbitrarily) converted that into a 100s default maximum # and I (just as arbitrarily) converted that into a 100s default maximum
# tolerable wait delay before bailing. # tolerable wait delay before bailing.
delay = count / 10. delay = count / 10.
deadline = time.time() + delay deadline = time.time() + delay
activity_tool = self.getPortalObject().portal_activities
for call_count in itertools.count(): for call_count in itertools.count():
x = activity_tool.getMessageList() x = activity_tool.getMessageList()
if not x: if not x:
......
...@@ -786,7 +786,7 @@ class TestUpgrader(ERP5TypeTestCase): ...@@ -786,7 +786,7 @@ class TestUpgrader(ERP5TypeTestCase):
def stepCheckPersonTitleHistory(self, sequence=None): def stepCheckPersonTitleHistory(self, sequence=None):
self.assertEqual( self.assertEqual(
[x.changes for x in self.portal.person_module['1'].Base_getZODBHistoryList()[-3:]], [x.changes for x in self.portal.person_module['1'].Base_getZODBHistoryList()[-3:]],
[('title:M. pre_upgrade',), ('title:M. upgrader',), ('title:M. post_upgrade',)]) [('title: M. pre_upgrade',), ('title: M. upgrader',), ('title: M. post_upgrade',)])
def test_upgrade_activities_are_run_sequentially(self): def test_upgrade_activities_are_run_sequentially(self):
""" """
......
...@@ -77,17 +77,18 @@ from Products.ERP5Type.TransactionalVariable import getTransactionalVariable ...@@ -77,17 +77,18 @@ from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from OFS.Traversable import NotFound from OFS.Traversable import NotFound
from OFS import SimpleItem from OFS import SimpleItem
from OFS.Image import Pdata from OFS.Image import Pdata
import coverage
from io import BytesIO from io import BytesIO
from copy import deepcopy from copy import deepcopy
from zExceptions import BadRequest from zExceptions import BadRequest
from Products.ERP5Type.XMLExportImport import exportXML, customImporters from Products.ERP5Type.XMLExportImport import exportXML, customImporters
from Products.ERP5Type.Workflow import WorkflowHistoryList from Products.ERP5Type.Workflow import WorkflowHistoryList
from zLOG import LOG, WARNING, INFO from zLOG import LOG, WARNING, INFO, PROBLEM
from warnings import warn from warnings import warn
from lxml.etree import parse from lxml.etree import parse
from xml.sax.saxutils import escape from xml.sax.saxutils import escape
from Products.CMFCore.Expression import Expression from Products.CMFCore.Expression import Expression
from six.moves.urllib.parse import quote, unquote from six.moves.urllib.parse import quote, unquote, urlparse
from difflib import unified_diff from difflib import unified_diff
import posixpath import posixpath
import transaction import transaction
...@@ -97,6 +98,7 @@ import threading ...@@ -97,6 +98,7 @@ import threading
from ZODB.broken import Broken, BrokenModified from ZODB.broken import Broken, BrokenModified
from Products.ERP5.genbt5list import BusinessTemplateRevision, \ from Products.ERP5.genbt5list import BusinessTemplateRevision, \
item_name_list, item_set item_name_list, item_set
from Products.ERP5Type.mixin.component import ComponentMixin
CACHE_DATABASE_PATH = None CACHE_DATABASE_PATH = None
try: try:
...@@ -1156,22 +1158,33 @@ class ObjectTemplateItem(BaseTemplateItem): ...@@ -1156,22 +1158,33 @@ class ObjectTemplateItem(BaseTemplateItem):
""" """
pass pass
def onNewObject(self, obj): def onNewObject(self, obj, context):
""" """
Installation hook. Installation hook.
Called when installation process determined that object to install is Called when installation process determined that object to install is
new on current site (it's not replacing an existing object). new on current site (it's not replacing an existing object).
`obj` parameter is the newly created object in its acquisition context. `obj` parameter is the newly created object in its acquisition context.
`context` is the business template instance, in its acquisition context.
Can be overridden by subclasses. Can be overridden by subclasses.
""" """
pass if isinstance(obj, (PythonScript, ComponentMixin)) and coverage.Coverage.current():
relative_path = '/'.join(obj.getPhysicalPath()[len(context.getPortalObject().getPhysicalPath()):])
filename = os.path.join(
context.getPublicationUrl(),
self.__class__.__name__,
relative_path + '.py')
if os.path.exists(filename):
obj._erp5_coverage_filename = filename
else:
LOG('BusinessTemplate', PROBLEM, 'Could not find file for %s' % filename)
def onReplaceObject(self, obj): def onReplaceObject(self, obj, context):
""" """
Installation hook. Installation hook.
Called when installation process determined that object to install is Called when installation process determined that object to install is
to replace an existing object on current site (it's not new). to replace an existing object on current site (it's not new).
`obj` parameter is the replaced object in its acquisition context. `obj` parameter is the replaced object in its acquisition context.
`context` is the business template instance, in its acquisition context.
Can be overridden by subclasses. Can be overridden by subclasses.
""" """
pass pass
...@@ -1416,9 +1429,9 @@ class ObjectTemplateItem(BaseTemplateItem): ...@@ -1416,9 +1429,9 @@ class ObjectTemplateItem(BaseTemplateItem):
if not object_existed: if not object_existed:
# A new object was added, call the hook # A new object was added, call the hook
self.onNewObject(obj) self.onNewObject(obj, context)
else: else:
self.onReplaceObject(obj) self.onReplaceObject(obj, context)
# mark a business template installation so in 'PortalType_afterClone' scripts # mark a business template installation so in 'PortalType_afterClone' scripts
# we can implement logical for reseting or not attributes (i.e reference). # we can implement logical for reseting or not attributes (i.e reference).
...@@ -1972,9 +1985,11 @@ class CategoryTemplateItem(ObjectTemplateItem): ...@@ -1972,9 +1985,11 @@ class CategoryTemplateItem(ObjectTemplateItem):
def beforeInstall(self): def beforeInstall(self):
self._installed_new_category = False self._installed_new_category = False
return super(CategoryTemplateItem, self).beforeInstall()
def onNewObject(self, obj): def onNewObject(self, obj, context):
self._installed_new_category = True self._installed_new_category = True
return super(CategoryTemplateItem, self).onNewObject(obj, context)
def afterInstall(self): def afterInstall(self):
if self._installed_new_category: if self._installed_new_category:
...@@ -2392,7 +2407,8 @@ class WorkflowTemplateItem(ObjectTemplateItem): ...@@ -2392,7 +2407,8 @@ class WorkflowTemplateItem(ObjectTemplateItem):
continue continue
raise raise
container_ids = container.objectIds() container_ids = container.objectIds()
if object_id in container_ids: # Object already exists object_existed = object_id in container_ids
if object_existed:
self._backupObject(action, trashbin, container_path, object_id, keep_subobjects=1) self._backupObject(action, trashbin, container_path, object_id, keep_subobjects=1)
container.manage_delObjects([object_id]) container.manage_delObjects([object_id])
obj = self._objects[path] obj = self._objects[path]
...@@ -2402,6 +2418,11 @@ class WorkflowTemplateItem(ObjectTemplateItem): ...@@ -2402,6 +2418,11 @@ class WorkflowTemplateItem(ObjectTemplateItem):
obj = container._getOb(object_id) obj = container._getOb(object_id)
obj.manage_afterClone(obj) obj.manage_afterClone(obj)
obj.wl_clearLocks() obj.wl_clearLocks()
if not object_existed:
# A new object was added, call the hook
self.onNewObject(obj, context)
else:
self.onReplaceObject(obj, context)
def uninstall(self, context, **kw): def uninstall(self, context, **kw):
object_path = kw.get('object_path', None) object_path = kw.get('object_path', None)
...@@ -4218,7 +4239,7 @@ class _ZodbComponentTemplateItem(ObjectTemplateItem): ...@@ -4218,7 +4239,7 @@ class _ZodbComponentTemplateItem(ObjectTemplateItem):
raise NotImplementedError raise NotImplementedError
def __init__(self, id_list, tool_id='portal_components', **kw): def __init__(self, id_list, tool_id='portal_components', **kw):
ObjectTemplateItem.__init__(self, id_list, tool_id=tool_id, **kw) super(_ZodbComponentTemplateItem, self).__init__(id_list, tool_id=tool_id, **kw)
def isKeepWorkflowObjectLastHistoryOnly(self, path): def isKeepWorkflowObjectLastHistoryOnly(self, path):
""" """
...@@ -4248,9 +4269,13 @@ class _ZodbComponentTemplateItem(ObjectTemplateItem): ...@@ -4248,9 +4269,13 @@ class _ZodbComponentTemplateItem(ObjectTemplateItem):
obj.workflow_history[wf_id] = WorkflowHistoryList([wf_history]) obj.workflow_history[wf_id] = WorkflowHistoryList([wf_history])
def onNewObject(self, _): def onNewObject(self, obj, context):
self._do_reset = True self._do_reset = True
onReplaceObject = onNewObject return super(_ZodbComponentTemplateItem, self).onNewObject(obj, context)
def onReplaceObject(self, obj, context):
self._do_reset = True
return super(_ZodbComponentTemplateItem, self).onReplaceObject(obj, context)
def afterInstall(self): def afterInstall(self):
""" """
...@@ -5718,6 +5743,9 @@ Business Template is a set of definitions, such as skins, portal types and categ ...@@ -5718,6 +5743,9 @@ Business Template is a set of definitions, such as skins, portal types and categ
value = self.getProperty(id) value = self.getProperty(id)
if not value: if not value:
continue continue
if id == 'publication_url':
if urlparse(value).scheme in ('file', ''):
continue
if prop_type in ('text', 'string', 'int', 'boolean'): if prop_type in ('text', 'string', 'int', 'boolean'):
bta.addObject(str(value), name=id, path='bt', ext='') bta.addObject(str(value), name=id, path='bt', ext='')
elif prop_type in ('lines', 'tokens'): elif prop_type in ('lines', 'tokens'):
......
...@@ -401,6 +401,7 @@ class TemplateTool (BaseTool): ...@@ -401,6 +401,7 @@ class TemplateTool (BaseTool):
bt = self._download_local(path, id) bt = self._download_local(path, id)
bt.build(no_action=True) bt.build(no_action=True)
bt.setPublicationUrl(url)
return bt return bt
security.declareProtected('Import/Export objects', 'importBase64EncodedText') security.declareProtected('Import/Export objects', 'importBase64EncodedText')
......
...@@ -16,7 +16,7 @@ def beautifyChange(change_dict): ...@@ -16,7 +16,7 @@ def beautifyChange(change_dict):
six.text_type(property_value, 'utf-8') six.text_type(property_value, 'utf-8')
except UnicodeDecodeError: except UnicodeDecodeError:
property_value = '(binary)' property_value = '(binary)'
change_list.append('{}:{}'.format(property_name, property_value)) change_list.append('{}: {}'.format(property_name, property_value))
return change_list return change_list
try: try:
......
...@@ -171,8 +171,9 @@ class ProxyField(ZMIField): ...@@ -171,8 +171,9 @@ class ProxyField(ZMIField):
Surcharged values from proxied field. Surcharged values from proxied field.
""" """
# Edit template field attributes # Edit template field attributes
template_field = self.getRecursiveTemplateField() template_field = self.getTemplateField()
if template_field is not None: if template_field is not None:
template_field = self.getRecursiveTemplateField()
# Check the surcharged checkboxes # Check the surcharged checkboxes
surcharge_list = [] surcharge_list = []
...@@ -581,6 +582,14 @@ class ProxyField(ZMIField): ...@@ -581,6 +582,14 @@ class ProxyField(ZMIField):
# ("form_id and field_id don't define a valid template") # ("form_id and field_id don't define a valid template")
pass pass
security.declareProtected('View', 'title')
def title(self):
"""The title of this field."""
try:
return super(ProxyField, self).title()
except BrokenProxyField:
return 'broken'
security.declareProtected('Access contents information', 'has_value') security.declareProtected('Access contents information', 'has_value')
def has_value(self, id): def has_value(self, id):
""" """
......
...@@ -11,7 +11,7 @@ Surcharge <dtml-var meta_type> properties here. ...@@ -11,7 +11,7 @@ Surcharge <dtml-var meta_type> properties here.
<form action="manage_edit" method="POST"> <form action="manage_edit" method="POST">
<table cellspacing="0" cellpadding="2" border="0"> <table cellspacing="0" cellpadding="2" border="0">
<dtml-let proxy_field="this()" <dtml-let proxy_field="this()"
current_field="proxy_field.getRecursiveTemplateField()"> current_field="None if proxy_field.getTemplateField() is None else proxy_field.getRecursiveTemplateField()">
<!-- First, display ProxyField properties --> <!-- First, display ProxyField properties -->
<!-- see: Formulator/dtml/fieldEdit.dtml --> <!-- see: Formulator/dtml/fieldEdit.dtml -->
......
...@@ -68,7 +68,7 @@ This tab can therefore not be used. ...@@ -68,7 +68,7 @@ This tab can therefore not be used.
<!-- XXX Loop until find not a proxy field --> <!-- XXX Loop until find not a proxy field -->
<dtml-let proxy_field="this()" <dtml-let proxy_field="this()"
current_field="proxy_field.getRecursiveTemplateField()"> current_field="None if proxy_field.getTemplateField() is None else proxy_field.getRecursiveTemplateField()">
<dtml-if "current_field is not None"> <dtml-if "current_field is not None">
<dtml-let form="current_field.tales_form"> <dtml-let form="current_field.tales_form">
......
...@@ -399,6 +399,7 @@ class ERP5TypeInformation(XMLObject, ...@@ -399,6 +399,7 @@ class ERP5TypeInformation(XMLObject,
self.getId(), self.getId(),
temp=temp_object) temp=temp_object)
base_ob = klass(id) base_ob = klass(id)
assert base_ob.portal_type == self.getId()
ob = base_ob.__of__(container) ob = base_ob.__of__(container)
if temp_object: if temp_object:
...@@ -422,10 +423,6 @@ class ERP5TypeInformation(XMLObject, ...@@ -422,10 +423,6 @@ class ERP5TypeInformation(XMLObject,
if getattr(base_ob, 'uid', None) is None: if getattr(base_ob, 'uid', None) is None:
ob.uid = portal.portal_catalog.newUid() ob.uid = portal.portal_catalog.newUid()
# Portal type has to be set before setting other attributes
# in order to initialize aq_dynamic
ob.portal_type = self.getId()
if compute_local_role: if compute_local_role:
# Do not reindex object because it's already done by manage_afterAdd # Do not reindex object because it's already done by manage_afterAdd
self.updateLocalRolesOnDocument(ob, reindex=False) self.updateLocalRolesOnDocument(ob, reindex=False)
......
...@@ -36,6 +36,7 @@ import imp ...@@ -36,6 +36,7 @@ import imp
import collections import collections
from six import reraise from six import reraise
import coverage
from Products.ERP5Type.Utils import ensure_list from Products.ERP5Type.Utils import ensure_list
from Products.ERP5.ERP5Site import getSite from Products.ERP5.ERP5Site import getSite
from Products.ERP5Type import product_path as ERP5Type_product_path from Products.ERP5Type import product_path as ERP5Type_product_path
...@@ -333,6 +334,14 @@ class ComponentDynamicPackage(ModuleType): ...@@ -333,6 +334,14 @@ class ComponentDynamicPackage(ModuleType):
# This must be set for imports at least (see PEP 302) # This must be set for imports at least (see PEP 302)
module.__file__ = '<' + relative_url + '>' module.__file__ = '<' + relative_url + '>'
if coverage.Coverage.current():
if hasattr(component, '_erp5_coverage_filename'):
module.__file__ = component._erp5_coverage_filename
else:
LOG(
"ERP5Type.Tool.ComponentTool",
WARNING,
"No coverage filesystem mapping for %s" % (module_fullname_alias or module_fullname))
# Only useful for get_source(), do it before exec'ing the source code # Only useful for get_source(), do it before exec'ing the source code
# so that the source code is properly display in case of error # so that the source code is properly display in case of error
......
...@@ -420,6 +420,9 @@ class ERP5TypeFunctionalTestCase(ERP5TypeTestCase): ...@@ -420,6 +420,9 @@ class ERP5TypeFunctionalTestCase(ERP5TypeTestCase):
# create browser_id_manager # create browser_id_manager
if not "browser_id_manager" in self.portal.objectIds(): if not "browser_id_manager" in self.portal.objectIds():
self.portal.manage_addProduct['Sessions'].constructBrowserIdManager() self.portal.manage_addProduct['Sessions'].constructBrowserIdManager()
# unsubscribe from activities, we'll use Zuite_waitForActivities to
# process activities
self.portal.portal_activities.unsubscribe()
self.commit() self.commit()
self.setSystemPreference() self.setSystemPreference()
# non-recursive results clean of portal_tests/ or portal_tests/``run_only`` # non-recursive results clean of portal_tests/ or portal_tests/``run_only``
......
...@@ -140,6 +140,9 @@ def main(): ...@@ -140,6 +140,9 @@ def main():
args.test_node_title, suite.allow_restart, test_suite_title, args.test_node_title, suite.allow_restart, test_suite_title,
args.project_title) args.project_title)
if test_result is not None: if test_result is not None:
os.environ['ERP5_TEST_RESULT_REVISION'] = test_result.revision
os.environ['ERP5_TEST_RESULT_ID'] = test_result.test_result_path.split('/')[-1]
assert revision == test_result.revision, (revision, test_result.revision) assert revision == test_result.revision, (revision, test_result.revision)
while suite.acquire(): while suite.acquire():
test = test_result.start(suite.running.keys()) test = test_result.start(suite.running.keys())
......
...@@ -12,10 +12,7 @@ import errno ...@@ -12,10 +12,7 @@ import errno
import random import random
import transaction import transaction
from glob import glob from glob import glob
try:
from coverage import coverage
except ImportError:
coverage = None
WIN = os.name == 'nt' WIN = os.name == 'nt'
...@@ -27,8 +24,6 @@ Options: ...@@ -27,8 +24,6 @@ Options:
-v, --verbose produce verbose output -v, --verbose produce verbose output
-h, --help this help screen -h, --help this help screen
-p, --profile print profiling results at the end -p, --profile print profiling results at the end
--coverage=STRING Use the given path as a coverage config file and
thus enable code coverateg report
--portal_id=STRING force id of the portal. Useful when using --portal_id=STRING force id of the portal. Useful when using
--data_fs_path to run tests on an existing --data_fs_path to run tests on an existing
Data.fs Data.fs
...@@ -640,11 +635,6 @@ def runUnitTestList(test_list, verbosity=1, debug=0, run_only=None): ...@@ -640,11 +635,6 @@ def runUnitTestList(test_list, verbosity=1, debug=0, run_only=None):
signal.signal(signal.SIGINT, shutdown) signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGHUP, shutdown) signal.signal(signal.SIGHUP, shutdown)
coverage_config = os.environ.get('coverage', None)
if coverage_config:
coverage_process = coverage(config_file=coverage_config)
coverage_process.start()
try: try:
save = int(os.environ.get('erp5_save_data_fs', 0)) save = int(os.environ.get('erp5_save_data_fs', 0))
load = int(os.environ.get('erp5_load_data_fs', 0)) load = int(os.environ.get('erp5_load_data_fs', 0))
...@@ -719,11 +709,6 @@ def runUnitTestList(test_list, verbosity=1, debug=0, run_only=None): ...@@ -719,11 +709,6 @@ def runUnitTestList(test_list, verbosity=1, debug=0, run_only=None):
# disconnected from it. # disconnected from it.
wcfs_server.stop() wcfs_server.stop()
if coverage_config:
coverage_process.stop()
coverage_process.save()
coverage_process.html_report()
if save and save_mysql: if save and save_mysql:
save_mysql(verbosity) save_mysql(verbosity)
...@@ -749,7 +734,7 @@ def main(argument_list=None): ...@@ -749,7 +734,7 @@ def main(argument_list=None):
sys.argv.extend(old_argv[1:]) sys.argv.extend(old_argv[1:])
try: try:
opts, args = getopt.getopt(sys.argv[1:], opts, args = getopt.getopt(sys.argv[1:],
"hpvD", ["help", "verbose", "profile", "coverage=", "portal_id=", "hpvD", ["help", "verbose", "profile", "portal_id=",
"data_fs_path=", "data_fs_path=",
"bt5_path=", "bt5_path=",
"firefox_bin=", "firefox_bin=",
...@@ -812,11 +797,6 @@ def main(argument_list=None): ...@@ -812,11 +797,6 @@ def main(argument_list=None):
elif opt == '-D': elif opt == '-D':
debug = 1 debug = 1
os.environ["erp5_debug_mode"] = str(debug) os.environ["erp5_debug_mode"] = str(debug)
elif opt == "--coverage":
if coverage:
os.environ['coverage'] = arg
else:
_print("WARNING Coverage module not found")
elif opt in ("-p", "--profile"): elif opt in ("-p", "--profile"):
os.environ['PROFILE_TESTS'] = "1" os.environ['PROFILE_TESTS'] = "1"
# profiling of setup and teardown is disabled by default, just set # profiling of setup and teardown is disabled by default, just set
......
...@@ -1867,7 +1867,8 @@ class Catalog(Folder, ...@@ -1867,7 +1867,8 @@ class Catalog(Folder,
else: else:
if related_key_definition is not None: if related_key_definition is not None:
search_key = search_key.getSearchKey(sql_catalog=self, search_key = search_key.getSearchKey(sql_catalog=self,
related_key_definition=related_key_definition) related_key_definition=related_key_definition,
search_key_name=search_key_name)
return search_key return search_key
security.declareProtected(access_contents_information, 'buildSingleQuery') security.declareProtected(access_contents_information, 'buildSingleQuery')
...@@ -1885,17 +1886,18 @@ class Catalog(Folder, ...@@ -1885,17 +1886,18 @@ class Catalog(Folder,
from it. from it.
""" """
return self._buildQuery( return self._buildQuery(
buildQueryFromSearchKey=lambda search_key: search_key.buildQuery( lambda search_key: search_key.buildQuery(
value, value,
logical_operator=logical_operator, logical_operator=logical_operator,
comparison_operator=comparison_operator, comparison_operator=comparison_operator,
), ),
key=key, key,
search_key_name=search_key_name, search_key_name,
ignore_unknown_columns=ignore_unknown_columns, ignore_unknown_columns,
) )
def _buildQueryFromAbstractSyntaxTreeNode(self, node, search_key, wrap, ignore_unknown_columns): def _buildQueryFromAbstractSyntaxTreeNode(self, node, search_key, wrap,
ignore_unknown_columns):
""" """
node node
Abstract syntax tree node (see SearchText/AdvancedSearchTextParser.py, Abstract syntax tree node (see SearchText/AdvancedSearchTextParser.py,
...@@ -1952,7 +1954,10 @@ class Catalog(Folder, ...@@ -1952,7 +1954,10 @@ class Catalog(Folder,
return result return result
security.declareProtected(access_contents_information, 'buildQueryFromAbstractSyntaxTreeNode') security.declareProtected(access_contents_information, 'buildQueryFromAbstractSyntaxTreeNode')
def buildQueryFromAbstractSyntaxTreeNode(self, node, key, wrap=lambda x: x, ignore_unknown_columns=False): def buildQueryFromAbstractSyntaxTreeNode(self, node, key,
search_key_name=None,
wrap=lambda x: x,
ignore_unknown_columns=False):
""" """
Build a query from given Abstract Syntax Tree (AST) node by recursing in Build a query from given Abstract Syntax Tree (AST) node by recursing in
its childs. its childs.
...@@ -1966,17 +1971,18 @@ class Catalog(Folder, ...@@ -1966,17 +1971,18 @@ class Catalog(Folder,
Expected node API is described in interfaces/abstract_syntax_node.py . Expected node API is described in interfaces/abstract_syntax_node.py .
""" """
return self._buildQuery( return self._buildQuery(
buildQueryFromSearchKey=lambda search_key: self._buildQueryFromAbstractSyntaxTreeNode( lambda search_key: self._buildQueryFromAbstractSyntaxTreeNode(
node, node,
search_key, search_key,
wrap, wrap,
ignore_unknown_columns, ignore_unknown_columns,
), ),
key=key, key,
ignore_unknown_columns=ignore_unknown_columns, search_key_name,
ignore_unknown_columns,
) )
def _buildQuery(self, buildQueryFromSearchKey, key, search_key_name=None, ignore_unknown_columns=False): def _buildQuery(self, buildQueryFromSearchKey, key, search_key_name, ignore_unknown_columns):
""" """
Determine the SearchKey to use to generate a Query, and call buildQueryFromSearchKey with it. Determine the SearchKey to use to generate a Query, and call buildQueryFromSearchKey with it.
""" """
...@@ -1996,7 +2002,7 @@ class Catalog(Folder, ...@@ -1996,7 +2002,7 @@ class Catalog(Folder,
related_key_definition=related_key_definition, related_key_definition=related_key_definition,
search_key_name=search_key_name, search_key_name=search_key_name,
) )
result = buildQueryFromSearchKey(search_key=build_key) result = buildQueryFromSearchKey(build_key)
if related_key_definition is not None: if related_key_definition is not None:
result = search_key.buildQuery(sql_catalog=self, result = search_key.buildQuery(sql_catalog=self,
related_key_definition=related_key_definition, related_key_definition=related_key_definition,
...@@ -2049,32 +2055,35 @@ class Catalog(Folder, ...@@ -2049,32 +2055,35 @@ class Catalog(Folder,
# We have an empty value, do not create a query from it # We have an empty value, do not create a query from it
empty_value_dict[key] = value empty_value_dict[key] = value
else: else:
if isinstance(value, dict):
# Dictionnary: might contain the search key to use.
search_key_name = value.get('key')
# Backward compatibility: former "Keyword" key is now named
# "KeywordKey".
if search_key_name == 'Keyword':
search_key_name = value['key'] = 'KeywordKey'
# Backward compatibility: former "ExactMatch" is now only available
# as "RawKey"
elif search_key_name == 'ExactMatch':
search_key_name = value['key'] = 'RawKey'
if isinstance(value, BaseQuery): if isinstance(value, BaseQuery):
# Query instance: use as such, ignore key. # Query instance: use as such, ignore key.
result = value result = value
elif isinstance(value, (basestring, dict)): elif isinstance(value, (basestring, dict)):
# String: parse using key's default search key. # String: parse using key's default search key.
raw_value = value raw_value = value
wrap = lambda x: x
if isinstance(value, dict): if isinstance(value, dict):
# De-wrap value for parsing, and re-wrap when building queries. # Dictionary: might contain the search key to use.
def wrap(x): search_key_name = value.pop('key', None)
result = raw_value.copy() # Backward compatibility: former "Keyword" key is now named
result['query'] = x # "KeywordKey".
return result if search_key_name == 'Keyword':
search_key_name = 'KeywordKey'
# Backward compatibility: former "ExactMatch" is now only available
# as "RawKey"
elif search_key_name == 'ExactMatch':
search_key_name = 'RawKey'
# De-wrap value for parsing.
value = value['query'] value = value['query']
# If necessary, re-wrap when building queries.
if len(raw_value) > 1:
def wrap(x):
result = raw_value.copy()
result['query'] = x
return result
else:
raw_value = value
else: else:
wrap = lambda x: x
search_key_name = None search_key_name = None
search_key = self.getColumnDefaultSearchKey(key, search_key = self.getColumnDefaultSearchKey(key,
search_key_name=search_key_name) search_key_name=search_key_name)
...@@ -2093,7 +2102,7 @@ class Catalog(Folder, ...@@ -2093,7 +2102,7 @@ class Catalog(Folder,
) )
else: else:
result = self.buildQueryFromAbstractSyntaxTreeNode( result = self.buildQueryFromAbstractSyntaxTreeNode(
abstract_syntax_tree, key, wrap, abstract_syntax_tree, key, search_key_name, wrap,
ignore_unknown_columns=ignore_unknown_columns, ignore_unknown_columns=ignore_unknown_columns,
) )
else: else:
......
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