Commit 807e8ee9 authored by Arnaud Fontaine's avatar Arnaud Fontaine

ZODB Components: Use pylint to report source code errors/warnings each time it is modified.

Before, 'exec' was used but it is not safe as it executes the source code and
also not so useful as it stops at the first error. So, use pylint instead:

* Report errors and warnings (naming conventions could later be checked too).
* Allow to jump directly in Ace Editor to the message line/column by clicking on it.
* Define properly dynamic packages by setting __path__, otherwise pylint fails
  when a Component imports another one.

Also, split up consistency checking done in validated/modified state and
checking source code everytime the source code is modified:

  Source code error message was previously stored as a workflow variable,
  which didn't make any sense because user would only get error messages on
  Workflow transition, thus use existing Interaction Workflow to check source
  code and set newly defined error/warning properties everytime the source
  code is modified.
parent d8289e10
......@@ -86,6 +86,7 @@
position: absolute;\n
bottom: 0;\n
right: 20px;\n
width: 40%;\n
z-index: 424242;\n
padding: 20px;\n
background-color: #DAE6F6;\n
......@@ -273,7 +274,6 @@
{duration: 3000, queue: false});\n
\n
var maximize_fullscreen_message = data;\n
var error_arr = [];\n
\n
var validation_state_span = $(\'div.input > .ace_editor_validation_state\');\n
if(validation_state_span.length) {\n
......@@ -290,30 +290,27 @@
success: getTranslatedValidationStateTitleHandler});\n
}\n
\n
var error_element = $(\'div.input > .error\');\n
updateErrorWarningMessageDivWithJump();\n
\n
// Animate fields to emphasize the change\n
if(error_element.length) {\n
// Animate field to emphasize the change\n
function getErrorMessageListHandler(data) {\n
error_arr = $.parseJSON(data);\n
error_element.css(\'opacity\', 0.0);\n
error_element.html(error_arr.join(\'<br />\'));\n
error_element.animate({opacity: 1.0},\n
{duration: 3000, queue: false});\n
}\n
error_element.css(\'opacity\', 0.0);\n
error_element.animate({opacity: 1.0}, {duration: 3000, queue: false});\n
}\n
\n
$.ajax({type: \'GET\',\n
async: false,\n
url: \'getErrorMessageList\',\n
data: \'as_json:int=1\',\n
success: getErrorMessageListHandler});\n
if(warning_element.length) {\n
warning_element.css(\'opacity\', 0.0);\n
warning_element.animate({opacity: 1.0}, {duration: 3000, queue: false});\n
}\n
\n
if($(\'.maximize\').length ||\n
(document.fullScreenElement && document.fullScreenElement !== null &&\n
(document.mozFullScreen || document.webkitIsFullScreen))) {\n
var msg_elem_classes = \'ace_editor_maximize_fullscreen_message\';\n
if(error_arr.length) {\n
maximize_fullscreen_message = error_arr.join(\'<br />\');\n
if(error_arr.length || warning_arr.length) {\n
maximize_fullscreen_message = (error_arr.join(\'<br />\') + \'<br />\' +\n
warning_arr.join(\'<br />\'));\n
\n
msg_elem_classes += \' ace_editor_maximize_fullscreen_error_message\';\n
}\n
\n
......@@ -349,6 +346,45 @@
\n
return false;\n
}\n
\n
function fillMessageElementAndArray(list, elem, arr) {\n
$.each(list, function(i, dict) {\n
line = dict[\'line\'];\n
column = dict[\'column\'];\n
if(line != null && column != null)\n
arr.push(\'<a href=&quot;#&quot; onclick=&quot;c=ace_editor.getCursorPosition();c.row=\' + (line - 1) + \';c.column=\' + column + \';ace_editor.gotoLine(line);ace_editor.moveCursorToPosition(c);ace_editor.focus();event.stopPropagation();event.preventDefault();&quot;>\' + dict[\'message\'] + \'</a>\');\n
else\n
arr.push(dict[\'message\']);\n
});\n
\n
elem.html(arr.join(\'<br />\'));\n
}\n
\n
function getErrorWarningMessageDictHandler(data) {\n
error_warning_dict = $.parseJSON(data);\n
\n
if(error_element.length) {\n
error_arr.length = 0;\n
fillMessageElementAndArray(error_warning_dict[\'error_list\'],\n
error_element, error_arr);\n
}\n
\n
if(warning_element.length) {\n
warning_arr.length = 0;\n
fillMessageElementAndArray(error_warning_dict[\'warning_list\'],\n
warning_element, warning_arr);\n
}\n
}\n
\n
function updateErrorWarningMessageDivWithJump() {\n
if(!error_element.length && !warning_element.length)\n
return;\n
\n
$.ajax({type: \'GET\',\n
async: false,\n
url: \'Component_getErrorWarningMessageDictAsJson\',\n
success: getErrorWarningMessageDictHandler});\n
}\n
\n
window.onload = function() {\n
ace_editor_container_div = $(\'#${container_div_id}\');\n
......@@ -387,6 +423,13 @@
*/\n
if($$(\'div.actions > button.save[name=Base_edit:method]\').length)\n
$$(\'${save_button}\').appendTo($(\'#${div_id}\'));\n
\n
error_element = $$(\'div.input > .error\');\n
error_arr = [];\n
warning_element = $$(\'div.input > .warning\');\n
warning_arr = [];\n
\n
updateErrorWarningMessageDivWithJump();\n
\n
if(typeof document.cancelFullScreen != \'undefined\' ||\n
(typeof document.mozFullScreenEnabled != \'undefined\' && document.mozFullScreenEnabled) ||\n
......
2013-08-15 arnaud.fontaine
* ZODB Components: pylint is now used and reports warnings. Also, allow to jump to error line/column by clicking on the error/warning message.
2013-08-13 arnaud.fontaine
* ZODB Components: Force resize of Ace Editor on maximize/fullscreen. Otherwise, without passing the new height/width to resize() and without force, text is not visible when switching to maximize/fullscreen mode.
......
13
\ No newline at end of file
14
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string encoding="cdata"><![CDATA[
import re\n
message_re = re.compile(\'[CRWEF]:\\s*(?P<line>\\d+),\\s*(?P<column>\\d+):\\s*.*\')\n
error_warning_dict = {\'error_list\': [], \'warning_list\': []}\n
\n
def getParsedMessageList(message_list):\n
result_list = []\n
for message in message_list:\n
line = None\n
column = None\n
message_obj = message_re.match(message)\n
if message_obj:\n
line = int(message_obj.group(\'line\'))\n
column = int(message_obj.group(\'column\'))\n
\n
result_list.append({\'line\': line, \'column\': column, \'message\': message})\n
\n
return result_list\n
\n
import json\n
return json.dumps({\'error_list\': getParsedMessageList(context.getTextContentErrorMessageList()),\n
\'warning_list\': getParsedMessageList(context.getTextContentWarningMessageList())})\n
]]></string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Component_getErrorWarningMessageDictAsJson</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -98,7 +98,8 @@
<string>my_id</string>
<string>my_reference</string>
<string>my_version</string>
<string>my_error_message_list</string>
<string>my_text_content_error_message_list</string>
<string>my_text_content_warning_message_list</string>
</list>
</value>
</item>
......
......@@ -19,7 +19,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_error_message_list</string> </value>
<value> <string>my_text_content_error_message_list</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>css_class</string>
<string>editable</string>
<string>title</string>
<string>view_separator</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_text_content_warning_message_list</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>css_class</string> </key>
<value> <string>warning</string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_lines_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Warnings</string> </value>
</item>
<item>
<key> <string>view_separator</string> </key>
<value> <string encoding="cdata"><![CDATA[
<br />
]]></string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -34,7 +34,7 @@
<key> <string>before_commit_script_name</string> </key>
<value>
<list>
<string>Component_validateAfterModified</string>
<string>Component_checkSourceCodeAndValidateAfterModified</string>
</list>
</value>
</item>
......@@ -45,7 +45,7 @@
<item>
<key> <string>guard</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
<none/>
</value>
</item>
<item>
......@@ -76,11 +76,17 @@
</list>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>script_name</string> </key>
<value>
<list>
<string>Component_setModifiedState</string>
<string>Component_setModifiedStateIfValidated</string>
</list>
</value>
</item>
......@@ -99,32 +105,4 @@
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Guard" module="Products.DCWorkflow.Guard"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>expr</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>python: here.getValidationState() in (\'validated\', \'modified\')</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -50,7 +50,14 @@
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>state_change[\'object\'].checkConsistencyAndValidate()\n
<value> <string>obj = state_change[\'object\']\n
\n
error_list, warning_list = obj.checkSourceCode()\n
obj.setTextContentWarningMessageList(warning_list)\n
obj.setTextContentErrorMessageList(error_list)\n
\n
if not error_list and obj.getValidationState() == \'modified\':\n
obj.checkConsistencyAndValidate()\n
</string> </value>
</item>
<item>
......@@ -59,7 +66,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Component_validateAfterModified</string> </value>
<value> <string>Component_checkSourceCodeAndValidateAfterModified</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -50,7 +50,9 @@
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>state_change[\'object\'].modify()\n
<value> <string>obj = state_change[\'object\']\n
if obj.getValidationState() == \'validated\':\n
obj.modify()\n
</string> </value>
</item>
<item>
......@@ -59,7 +61,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Component_setModifiedState</string> </value>
<value> <string>Component_setModifiedStateIfValidated</string> </value>
</item>
</dictionary>
</pickle>
......
2013-08-15 arnaud.fontaine
* ZODB Components: Add pylint support and use a property for error message rather than workflow variable (error messages are now reported everytime the source code is changed).
2013-07-19 arnaud.fontaine
* Add save button to Ace Editor to save source code while staying on the same page.
......
41107
\ No newline at end of file
41108
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Property Sheet" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_count</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>_mt_index</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
<item>
<key> <string>_tree</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>ZODB Components properties common to Document Component, Extension Component and Test Component</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Component</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Property Sheet</string> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Length" module="BTrees.Length"/>
</pickle>
<pickle> <int>0</int> </pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="OOBTree" module="BTrees.OOBTree"/>
</pickle>
<pickle>
<none/>
</pickle>
</record>
</ZopeData>
......@@ -2,45 +2,33 @@
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="VariableDefinition" module="Products.DCWorkflow.Variables"/>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_expr</string> </key>
<key> <string>categories</string> </key>
<value>
<none/>
<tuple>
<string>elementary_type/lines</string>
</tuple>
</value>
</item>
<item>
<key> <string>default_value</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>for_catalog</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>for_status</string> </key>
<value> <int>1</int> </value>
<value> <string>Source Code Error Messages</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>error_message</string> </value>
<value> <string>text_content_error_message_property</string> </value>
</item>
<item>
<key> <string>info_guard</string> </key>
<value>
<none/>
</value>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>update_always</string> </key>
<value> <int>1</int> </value>
<key> <string>property_default</string> </key>
<value> <string>python: []</string> </value>
</item>
</dictionary>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Standard Property" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>elementary_type/lines</string>
</tuple>
</value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string>Source Code Warning Messages</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>text_content_warning_message_property</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Standard Property</string> </value>
</item>
<item>
<key> <string>property_default</string> </key>
<value> <string>python: []</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
2013-08-15 arnaud.fontaine
* ZODB Components: Add error and warning property for Components.
2012-09-04 arnaud.fontaine
* message_attribute_does_not_match was incorrectly renamed to message_attribute_match when migrating StringAttributeMatch constraint from filesystem to ZODB.
......
61
\ No newline at end of file
62
\ No newline at end of file
......@@ -57,6 +57,7 @@ CategoryRelatedMembershipArityConstraint
Chain
Codification
Comment
Component
Computer
Configurable
ConfiguratorItem
......
......@@ -31,6 +31,13 @@ from types import ModuleType
from . import aq_method_lock
import sys
class PackageType(ModuleType):
"""
If a module has a __path__attribute, it will be treated as a package
(PEP 302), this is required for Introspection (for example pylint)
"""
__path__ = []
class DynamicModule(ModuleType):
"""This module may generate new objects at runtime."""
# it's useful to have such a generic utility
......@@ -95,7 +102,7 @@ def initializeDynamicModules():
erp5.component.test:
holds Live Test modules previously found in bt5 in $INSTANCE_HOME/test
"""
erp5 = ModuleType("erp5")
erp5 = PackageType("erp5")
sys.modules["erp5"] = erp5
# Document classes without physical import path
......@@ -106,6 +113,7 @@ def initializeDynamicModules():
from accessor_holder import AccessorHolderType, AccessorHolderModuleType
erp5.accessor_holder = AccessorHolderModuleType("erp5.accessor_holder")
erp5.accessor_holder.__path__ = []
sys.modules["erp5.accessor_holder"] = erp5.accessor_holder
erp5.accessor_holder.property_sheet = \
......@@ -127,7 +135,7 @@ def initializeDynamicModules():
loadTempPortalTypeClass)
# ZODB Components
erp5.component = ModuleType("erp5.component")
erp5.component = PackageType("erp5.component")
sys.modules["erp5.component"] = erp5.component
from component_package import ComponentDynamicPackage
......
......@@ -43,6 +43,15 @@ from zLOG import LOG, INFO
from ExtensionClass import ExtensionClass
from Products.ERP5Type.Utils import convertToUpperCase
# Pylint imports
from pylint.lint import Run
from pylint.reporters.text import TextReporter
import cStringIO
import tempfile
import sys
import re
pylint_message_re = re.compile('^(?P<type>[CRWEF]):\s*\d+,\s*\d+:\s*.*$')
class RecordablePropertyMetaClass(ExtensionClass):
"""
Meta-class for extension classes with registered setters and getters wrapped
......@@ -132,8 +141,10 @@ class ComponentMixin(PropertyRecordableMixin, Base):
(ERP5Type.patches.{User,PropertiedUser}) and modifications in
ERP5Security.ERP5UserFactory.
XXX-arnau: add tests to ERP5 itself to make sure all securities are defined
properly everywhere (see naming convention test)
Component source code is checked upon modification of text_content property
whatever its Workflow state (checkSourceCode). On validated and modified
state, checkConsistency() is called to check id, reference, version and
errors/warnings messages (set when the Component is modified).
"""
__metaclass__ = RecordablePropertyMetaClass
......@@ -152,7 +163,8 @@ class ComponentMixin(PropertyRecordableMixin, Base):
'DublinCore',
'Version',
'Reference',
'TextDocument')
'TextDocument',
'Component')
_recorded_property_name_tuple = (
'reference',
......@@ -166,9 +178,7 @@ class ComponentMixin(PropertyRecordableMixin, Base):
_message_version_not_set = "Version must be set"
_message_invalid_version = "Version cannot start with '_'"
_message_text_content_not_set = "No source code"
_message_invalid_text_content = "Source code: ${error_message}"
_message_text_content_syntax_error = "Syntax error in source code: "\
"${error_message} (line: ${line_number}, column: ${column_number})"
_message_text_content_error = "Error in Source Code: ${error_message}"
security.declareProtected(Permissions.ModifyPortalContent, 'checkConsistency')
def checkConsistency(self, *args, **kw):
......@@ -223,27 +233,11 @@ class ComponentMixin(PropertyRecordableMixin, Base):
message=self._message_text_content_not_set,
mapping={}))
else:
message = None
try:
# Check for any error in the source code by trying to load it
self.load({}, text_content=text_content)
except SyntaxError, e:
mapping = dict(error_message=str(e),
line_number=e.lineno,
column_number=e.offset)
message = self._message_text_content_syntax_error
except Exception, e:
mapping = dict(error_message=str(e))
message = self._message_invalid_text_content
if message:
error_list.append(
ConsistencyMessage(self,
object_relative_url=self.getRelativeUrl(),
message=message,
mapping=mapping))
for error_message in self.getTextContentErrorMessageList():
error_list.append(ConsistencyMessage(self,
object_relative_url=object_relative_url,
message=self._message_text_content_error,
mapping=dict(error_message=error_message)))
return error_list
......@@ -257,67 +251,73 @@ class ComponentMixin(PropertyRecordableMixin, Base):
stays in modified state and previously validated values are used for
reference, version and text_content
"""
error_list = self.checkConsistency()
if error_list:
workflow = self.workflow_history['component_validation_workflow'][-1]
# When checking consistency with validate_action, messages are stored
# into error_message workflow attribute as Message instances
workflow['error_message'] = [error.getMessage() for error in error_list]
else:
for property_name in self._recorded_property_name_tuple:
self.clearRecordedProperty(property_name)
self.validate()
security.declareProtected(Permissions.AccessContentsInformation,
'getErrorMessageList')
def hasErrorMessageList(self):
"""
Check whether there are error messages, useful to display errors in the UI
without calling getErrorMessageList() as it translates error messages
"""
workflow = self.workflow_history['component_validation_workflow'][-1]
return bool(workflow['error_message'])
security.declareProtected(Permissions.AccessContentsInformation,
'getErrorMessageList')
def getErrorMessageList(self, as_json=False):
"""
Return the checkConsistency errors which may have occurred when
the Component has been modified after being validated once
"""
current_workflow = self.workflow_history['component_validation_workflow'][-1]
error_list = [error.translate()
for error in current_workflow.get('error_message', [])]
# Dirty hack until RenderJS is used to save the source code
# (erp5_ace_editor/ace_editor_support)
if as_json:
import json
return json.dumps(error_list)
return error_list
security.declareProtected(Permissions.ModifyPortalContent, 'load')
def load(self, namespace_dict, validated_only=False, text_content=None):
if not self.checkConsistency():
text_content = self.getTextContent()
# Even if pylint should report all errors, make sure that there is no
# error when executing the source code pylint before validating
try:
exec text_content in {}
except BaseException, e:
self.setErrorMessageList(self.getTextContentErrorMessageList() +
[str(e)])
else:
for property_name in self._recorded_property_name_tuple:
self.clearRecordedProperty(property_name)
self.validate()
security.declareProtected(Permissions.ModifyPortalContent, 'checkSourceCode')
def checkSourceCode(self):
"""
Load the source code into the given dict. Using exec() rather than
imp.load_source() as the latter would required creating an intermediary
file. Also, for traceback readability sake, the destination module
__dict__ is given rather than creating an empty dict and returning it.
Check source code with pylint
Initially, namespace_dict default parameter value was an empty dict to
allow checking the source code before validate, but this is completely
wrong as the object reference is kept accross each call
TODO-arnau: Not used anymore in component_package, so this could be
removed as soon as pyflakes is used instead
TODO-arnau: Get rid of NamedTemporaryFile (require a patch on pylint to
allow passing a string)
"""
if text_content is None:
text_content = self.getTextContent(validated_only=validated_only)
exec text_content in namespace_dict
source_code = self.getTextContent()
# checkConsistency() ensures that it cannot happen once validated/modified
if not source_code:
return [], []
#import time
#started = time.time()
error_list = []
warning_list = []
output_file = cStringIO.StringIO()
# pylint prints directly on stderr/stdout (only reporter content matters)
stderr = sys.stderr
stdout = sys.stdout
try:
sys.stderr = cStringIO.StringIO()
sys.stdout = cStringIO.StringIO()
with tempfile.NamedTemporaryFile() as input_file:
input_file.write(source_code)
input_file.seek(0)
Run([input_file.name, '--reports=n', '--indent-string=" "', '--zope=y',
'--disable=C'], reporter=TextReporter(output_file), exit=False)
output_file.reset()
for line in output_file:
message_obj = pylint_message_re.match(line)
if message_obj:
line = line.strip()
if line[0] in ('E', 'F'):
error_list.append(line)
else:
warning_list.append(line)
finally:
output_file.close()
sys.stderr = stderr
sys.stdout = stdout
#LOG('component', INFO, 'Checking time (pylint): %.2f' % (time.time() -
# started))
return error_list, warning_list
security.declareProtected(Permissions.ModifyPortalContent, 'PUT')
def PUT(self, REQUEST, RESPONSE):
......
......@@ -1437,7 +1437,7 @@ class _TestZodbComponent(SecurityTestCase):
ComponentTool.reset = assertResetCalled
try:
component = self._newComponent(valid_reference,
'def foobar(*args, **kwargs):\n return 42')
'def foobar():\n return 42')
component.validate()
self.tic()
......@@ -1448,7 +1448,9 @@ class _TestZodbComponent(SecurityTestCase):
ComponentTool._reset_performed = False
self.assertEquals(component.getValidationState(), 'validated')
self.assertEquals(component.getErrorMessageList(), [])
self.assertEquals(component.checkConsistency(), [])
self.assertEquals(component.getTextContentErrorMessageList(), [])
self.assertEquals(component.getTextContentWarningMessageList(), [])
self.assertEquals(component.getReference(), valid_reference)
self.assertEquals(component.getReference(validated_only=True), valid_reference)
self.assertModuleImportable(valid_reference)
......@@ -1456,7 +1458,6 @@ class _TestZodbComponent(SecurityTestCase):
# Check that checkConsistency returns the proper error message for the
# following reserved keywords
invalid_reference_dict = {
None: ComponentMixin._message_reference_not_set,
# '_version' could clash with Version package name
'ReferenceReservedKeywords_version': ComponentMixin._message_invalid_reference,
# Besides of clashing with protected attributes/methods, it does not
......@@ -1479,10 +1480,11 @@ class _TestZodbComponent(SecurityTestCase):
# Should be in modified state as an error has been encountered
self.assertEquals(component.getValidationState(), 'modified')
error_list = component.getErrorMessageList()
self.assertNotEquals(error_list, [])
self.assertEquals(len(error_list), 1)
self.assertEquals(error_message, error_list[0])
self.assertEquals([m.getMessage().translate()
for m in component.checkConsistency()],
[error_message])
self.assertEquals(component.getTextContentErrorMessageList(), [])
self.assertEquals(component.getTextContentWarningMessageList(), [])
self.assertEquals(component.getReference(), invalid_reference)
self.assertEquals(component.getReference(validated_only=True), valid_reference)
self._component_tool.reset(force=True,
......@@ -1502,7 +1504,9 @@ class _TestZodbComponent(SecurityTestCase):
ComponentTool._reset_performed = False
self.assertEquals(component.getValidationState(), 'validated')
self.assertEquals(component.getErrorMessageList(), [])
self.assertEquals(component.checkConsistency(), [])
self.assertEquals(component.getTextContentErrorMessageList(), [])
self.assertEquals(component.getTextContentWarningMessageList(), [])
self.assertEquals(component.getReference(), valid_reference)
self.assertEquals(component.getReference(validated_only=True), valid_reference)
self.assertModuleImportable(valid_reference)
......@@ -1521,7 +1525,7 @@ class _TestZodbComponent(SecurityTestCase):
ComponentTool.reset = assertResetCalled
try:
component = self._newComponent(reference,
'def foobar(*args, **kwargs):\n return 42',
'def foobar():\n return 42',
valid_version)
component.validate()
......@@ -1533,7 +1537,9 @@ class _TestZodbComponent(SecurityTestCase):
ComponentTool._reset_performed = False
self.assertEquals(component.getValidationState(), 'validated')
self.assertEquals(component.getErrorMessageList(), [])
self.assertEquals(component.checkConsistency(), [])
self.assertEquals(component.getTextContentErrorMessageList(), [])
self.assertEquals(component.getTextContentWarningMessageList(), [])
self.assertEquals(component.getVersion(), valid_version)
self.assertEquals(component.getVersion(validated_only=True), valid_version)
self.assertModuleImportable(reference)
......@@ -1557,10 +1563,11 @@ class _TestZodbComponent(SecurityTestCase):
# Should be in modified state as an error has been encountered
self.assertEquals(component.getValidationState(), 'modified')
error_list = component.getErrorMessageList()
self.assertNotEquals(error_list, [])
self.assertEquals(len(error_list), 1)
self.assertEquals(error_message, error_list[0])
self.assertEquals([m.getMessage().translate()
for m in component.checkConsistency()],
[error_message])
self.assertEquals(component.getTextContentErrorMessageList(), [])
self.assertEquals(component.getTextContentWarningMessageList(), [])
self.assertEquals(component.getVersion(), invalid_version)
self.assertEquals(component.getVersion(validated_only=True), valid_version)
self._component_tool.reset(force=True,
......@@ -1580,7 +1587,9 @@ class _TestZodbComponent(SecurityTestCase):
ComponentTool._reset_performed = False
self.assertEquals(component.getValidationState(), 'validated')
self.assertEquals(component.getErrorMessageList(), [])
self.assertEquals(component.checkConsistency(), [])
self.assertEquals(component.getTextContentErrorMessageList(), [])
self.assertEquals(component.getTextContentWarningMessageList(), [])
self.assertEquals(component.getVersion(), valid_version)
self.assertEquals(component.getVersion(validated_only=True), valid_version)
self.assertModuleImportable(reference)
......@@ -1594,10 +1603,35 @@ class _TestZodbComponent(SecurityTestCase):
validated but not when an error was encountered (implemented in
dynamic_class_generation_interaction_workflow)
"""
valid_code = 'def foobar(*args, **kwargs):\n return 42'
# Error/Warning properties must be set everytime the source code is
# modified, even in Draft state
component = self._newComponent('TestComponentWithSyntaxError', 'print "ok"')
self.tic()
self.assertEquals(component.checkConsistency(), [])
self.assertEquals(component.getTextContentErrorMessageList(), [])
self.assertEquals(component.getTextContentWarningMessageList(), [])
component.setTextContent('import sys')
self.tic()
self.assertEquals(component.checkConsistency(), [])
self.assertEquals(component.getTextContentErrorMessageList(), [])
self.assertEquals(component.getTextContentWarningMessageList(),
["W: 1, 0: Unused import sys (unused-import)"])
component.setTextContent('import unexistent_module')
self.tic()
self.assertEquals(
[m.getMessage().translate() for m in component.checkConsistency()],
["Error in Source Code: F: 1, 0: Unable to import 'unexistent_module' (import-error)"])
self.assertEquals(component.getTextContentErrorMessageList(),
["F: 1, 0: Unable to import 'unexistent_module' (import-error)"])
self.assertEquals(component.getTextContentWarningMessageList(),
["W: 1, 0: Unused import unexistent_module (unused-import)"])
valid_code = 'def foobar():\n return 42'
ComponentTool.reset = assertResetCalled
try:
component = self._newComponent('TestComponentWithSyntaxError', valid_code)
component.setTextContent(valid_code)
component.validate()
self.tic()
......@@ -1607,7 +1641,9 @@ class _TestZodbComponent(SecurityTestCase):
ComponentTool._reset_performed = False
self.assertEquals(component.getValidationState(), 'validated')
self.assertEquals(component.getErrorMessageList(), [])
self.assertEquals(component.checkConsistency(), [])
self.assertEquals(component.getTextContentErrorMessageList(), [])
self.assertEquals(component.getTextContentWarningMessageList(), [])
self.assertEquals(component.getTextContent(), valid_code)
self.assertEquals(component.getTextContent(validated_only=True), valid_code)
self.assertModuleImportable('TestComponentWithSyntaxError')
......@@ -1615,13 +1651,27 @@ class _TestZodbComponent(SecurityTestCase):
# Check that checkConsistency returns the proper error message for the
# following Python errors
invalid_code_dict = (
(None, ComponentMixin._message_text_content_not_set),
('def foobar(*args, **kwargs)\n return 42', 'Syntax error in source code:'),
(None,
# There could be no source code until validated, so checkConsistency()
# is used instead
[ComponentMixin._message_text_content_not_set],
[],
[]),
('def foobar(*args, **kwargs)\n return 42',
["Error in Source Code: E: 1, 0: invalid syntax (syntax-error)"],
["E: 1, 0: invalid syntax (syntax-error)"],
[]),
# Make sure that foobar NameError is at the end to make sure that after
# defining foobar function, it is not available at all
('foobar', 'Source code:'))
for invalid_code, error_message in invalid_code_dict:
('foobar',
["Error in Source Code: E: 1, 0: Undefined variable 'foobar' (undefined-variable)"],
["E: 1, 0: Undefined variable 'foobar' (undefined-variable)"],
["W: 1, 0: Statement seems to have no effect (pointless-statement)"]))
for (invalid_code,
check_consistency_list,
error_list,
warning_list) in invalid_code_dict:
# Reset should not be performed
ComponentTool.reset = assertResetNotCalled
try:
......@@ -1632,10 +1682,12 @@ class _TestZodbComponent(SecurityTestCase):
# Should be in modified state as an error has been encountered
self.assertEquals(component.getValidationState(), 'modified')
error_list = component.getErrorMessageList()
self.assertNotEqual(error_list, [])
self.assertEquals(len(error_list), 1)
self.assertTrue(error_list[0].startswith(error_message))
self.assertEquals([m.getMessage().translate()
for m in component.checkConsistency()],
check_consistency_list)
self.assertEquals(component.getTextContentErrorMessageList(), error_list)
self.assertEquals(component.getTextContentWarningMessageList(), warning_list)
self.assertEquals(component.getTextContent(), invalid_code)
self.assertEquals(component.getTextContent(validated_only=True), valid_code)
self._component_tool.reset(force=True,
......@@ -1655,7 +1707,9 @@ class _TestZodbComponent(SecurityTestCase):
ComponentTool._reset_performed = False
self.assertEquals(component.getValidationState(), 'validated')
self.assertEquals(component.getErrorMessageList(), [])
self.assertEquals(component.checkConsistency(), [])
self.assertEquals(component.getTextContentErrorMessageList(), [])
self.assertEquals(component.getTextContentWarningMessageList(), [])
self.assertEquals(component.getTextContent(), valid_code)
self.assertEquals(component.getTextContent(validated_only=True), valid_code)
self.assertModuleImportable('TestComponentWithSyntaxError')
......
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