[Erp5-report] r19945 - /erp5/trunk/products/ERP5/tests/testXHTML.py

nobody at svn.erp5.org nobody at svn.erp5.org
Mon Mar 17 00:02:54 CET 2008


Author: fabien
Date: Mon Mar 17 00:02:53 2008
New Revision: 19945

URL: http://svn.erp5.org?rev=19945&view=rev
Log:
- add w3c validator which is much better than tidy. Validator can be chosen
between w3c and tidy. There is a variable at the end of the file : 
validator_to_use which permit to select the validator.
- enhance error displaying : it's now easier to find the error and correct it
- the two validators could now handle warnings
- now nearly all view pages are tested
- this new version reveal some errors which were not tested before 
  (action which non existing view)

Modified:
    erp5/trunk/products/ERP5/tests/testXHTML.py

Modified: erp5/trunk/products/ERP5/tests/testXHTML.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5/tests/testXHTML.py?rev=19945&r1=19944&r2=19945&view=diff
==============================================================================
--- erp5/trunk/products/ERP5/tests/testXHTML.py (original)
+++ erp5/trunk/products/ERP5/tests/testXHTML.py Mon Mar 17 00:02:53 2008
@@ -1,7 +1,8 @@
 ##############################################################################
 #
 # Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
-#
+#               Fabien Morin <fabien at nexedi.com
+#               Jacek Medrzycki <jacek at erp5.pl>
 # WARNING: This program as such is intended to be used by professional
 # programmers who take the whole responsability of assessing all potential
 # consequences resulting from its eventual inadequacies and bugs
@@ -27,10 +28,15 @@
 
 import unittest
 import os
+import popen2
+import urllib
 
 from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
 from Products.CMFCore.utils import getToolByName
 from AccessControl.SecurityManagement import newSecurityManager
+from zLOG import LOG
+from xml.dom import minidom
+
 from glob import glob
 
 try:
@@ -44,6 +50,7 @@
 INSTANCE_HOME = os.environ['INSTANCE_HOME']
 bt5_base_path = os.environ.get('erp5_tests_bt5_path',
                                os.path.join(INSTANCE_HOME, 'bt5'))
+bootstrap_base_path = os.path.join(INSTANCE_HOME, 'Products', 'ERP5', 'bootstrap')
 
 # dependency order
 target_business_templates = (
@@ -132,50 +139,249 @@
                                              field.get_value('form_id'),
                                              field.get_value('field_id')))
 
-
-
-def validate_xhtml(source):
-  import popen2
-  if not os.path.exists('/usr/bin/tidy'):
-    raise IOError, 'tidy is not installed at /usr/bin/tidy'
-  stdout, stdin, stderr = popen2.popen3('/usr/bin/tidy -e -q -utf8')
-  stdin.write(source)
-  stdin.close()
-  for i in stderr:
-    data = i.split(' - ')
-    if len(data) >= 2:
-      if data[1].startswith('Error: '):
-        return False
-#       elif data[1].startswith('Warning: '):
-#         return False
-  return True
-
-
-def makeTestMethod(module_id, portal_type, view_name):
+class W3Validator(object):
+
+  def __init__(self, validator_path, show_warnings):
+    self.validator_path = validator_path
+    self.show_warnings = show_warnings
+    self.name = 'w3c'
+
+  def _parse_validation_results(self, result):
+    """
+    parses the validation results, returns a list of tuples:
+    line_number, col_number, error description
+    """
+    error_list=[]
+    warning_list=[]
+    xml_doc = minidom.parseString(result)
+    for error in xml_doc.getElementsByTagName('m:error'):
+      error_line = error.getElementsByTagName('m:line')[0].firstChild.nodeValue
+      error_col = error.getElementsByTagName('m:col')[0].firstChild.nodeValue
+      error_message = error.getElementsByTagName('m:message')[0].firstChild.nodeValue
+      error_list.append((error_line,error_col,error_message))
+    for warning in xml_doc.getElementsByTagName('m:warning'):
+      warning_line = warning.getElementsByTagName('m:line')[0].firstChild.nodeValue
+      warning_col = warning.getElementsByTagName('m:col')[0].firstChild.nodeValue
+      warning_message = warning.getElementsByTagName('m:message')[0].firstChild.nodeValue
+      warning_list.append((warning_line, warning_col, warning_message))
+    return error_list, warning_list
+
+  def getErrorAndWarningList(self, page_source):
+    '''
+      retrun two list : a list of errors and an other for warnings
+    '''
+    source = 'fragment=%s&output=soap12' % urllib.quote_plus(page_source)
+    os.environ['CONTENT_LENGTH'] = str(len(source))
+    os.environ['REQUEST_METHOD'] = 'POST'
+    stdout, stdin, stderr = popen2.popen3(self.validator_path)
+    stdin.write(source)
+    stdin.close()
+    while stdout.readline() != '\n':
+      pass
+    result = stdout.read()
+    return self._parse_validation_results(result)
+
+
+class TidyValidator(object):
+
+  def __init__(self, validator_path):
+    self.validator_path = validator_path
+    self.show_warnings = show_warnings
+    self.name = 'tidy'
+
+  def _parse_validation_results(self, result):
+    """
+    parses the validation results, returns a list of tuples:
+    line_number, col_number, error description
+    """
+    error_list=[]
+    warning_list=[]
+
+    for i in result:
+      data = i.split(' - ')
+      if len(data) >= 2:
+        data[1] = data[1].replace('\n','')
+        if data[1].startswith('Error: '):
+          location_list = data[0].split(' ')
+          line = location_list[1]
+          column = location_list[3]
+          error = True
+          message = data[1].split(': ')[1]
+          error_list.append((line, column, message))
+        elif data[1].startswith('Warning: '):
+          location_list = data[0].split(' ')
+          line = location_list[1]
+          column = location_list[3]
+          warning = True
+          message = data[1].split(': ')[1]
+          warning_list.append((line, column, message))
+    return (error_list, warning_list)
+
+  def getErrorAndWarningList(self, page_source):
+    '''
+      retrun two list : a list of errors and an other for warnings
+    '''
+    stdout, stdin, stderr = popen2.popen3('%s -e -q -utf8' % self.validator_path)
+    stdin.write(page_source)
+    stdin.close()
+    return self._parse_validation_results(stderr)
+
+
+def validate_xhtml(validator, source, view_name, bt_name):
+  '''
+    validate_xhtml return True if there is no error on the page, False else.
+    Now it's possible to show warnings, so, if the option is set to True on the
+    validator object, and there is some warning on the page, the function 
+    return False, even if there is no error.
+  '''
+
+  # display some information when test faild to facilitate debugging
+  message = []
+  message.append('Using %s validator to parse the view "%s" (from %s bt) with warning %s displayed :' %\
+      (validator.name, view_name, bt_name, validator.show_warnings and '' or 'NOT'))
+
+  error_list, warning_list = validator.getErrorAndWarningList(source)
+
+  if error_list:
+    # build error message
+    for error in error_list:
+      message.append('Error: line %s column %s : %s' % error)
+
+  if warning_list and validator.show_warnings:
+    # build error message
+    for warning in warning_list:
+      message.append('Warning: line %s column %s : %s' % warning)
+
+  message = '\n'.join(message)
+  if validator.show_warnings:
+    return ((not (len(error_list) or len(warning_list))), message)
+  return ((not len(error_list)), message)
+
+
+def makeTestMethod(validator, module_id, portal_type, view_name, bt_name):
+
+  def createSubContent(content, portal_type_list):
+    if len(portal_type_list):
+      new_portal_type = portal_type_list[0]
+      new_portal_type_list = portal_type_list[1:]
+      new_content = content.newContent(portal_type=new_portal_type)
+      return createSubContent(new_content, new_portal_type_list)
+    else:
+      return content
+
   def testMethod(self):
     module = getattr(self.portal, module_id)
-    content = module.newContent(portal_type=portal_type)
-    view = getattr(content, view_name)
-    self.assert_(validate_xhtml(view()))
+    portal_type_list = portal_type.split('/')
+
+    object = createSubContent(module, portal_type_list)
+    view = getattr(object, view_name)
+    self.assert_(*validate_xhtml( validator=validator, 
+                                  source=view(), 
+                                  view_name=view_name, 
+                                  bt_name=bt_name))
   return testMethod
 
-
-def addTestMethodDynamically():
+def testPortalTypeViewRecursivly(validator, module_id, business_template_info, 
+    business_template_info_list, portal_type_list, portal_type_path_dict, 
+    base_path, tested_portal_type_list):
+  '''
+  This function go on all portal_type recursivly if the portal_type could 
+  contain other portal_types and make a test for all view that have action
+  '''
+
+  # iteration over all allowed portal_types inside the module/portal_type
+  for portal_type in portal_type_list:
+    portal_path = portal_type_path_dict[portal_type]
+    if portal_type not in tested_portal_type_list:
+      # this portal type haven't been tested yet
+
+      backuped_module_id = module_id 
+      backuped_business_template_info = business_template_info
+
+      if not business_template_info.actions.has_key(portal_type):
+        # search in other bt :
+        business_template_info = None
+        for bt_info in business_template_info_list:
+          if bt_info.actions.has_key(portal_type):
+            business_template_info = bt_info
+            break
+        if not business_template_info:
+          LOG("Can't find the action :", 0, portal_type)
+          break
+        # create the object in portal_trash module
+        module_id = 'portal_trash'
+
+      for action_information in business_template_info.actions[portal_type]:
+        if (action_information['category']=='object_view' and
+            action_information['visible']==1 and
+            action_information['text'].startswith('string:${object_url}/') and
+            len(action_information['text'].split('/'))==2):
+          view_name = action_information['text'].split('/')[-1]
+          method = makeTestMethod(validator,
+                                  module_id, 
+                                  portal_path,
+                                  view_name, 
+                                  business_template_info.title)
+          method_name = 'test.%s.%s.%s' % (business_template_info.title, portal_type, view_name)
+          setattr(TestXHTML, method_name, method)
+          module_id = backuped_module_id
+          business_template_info = backuped_business_template_info
+
+      # add the portal_type to the tested portal_types. This avoid to test many
+      # times a Portal Type wich is many bt.
+      tested_portal_type_list.append(portal_type)
+
+      new_portal_type_list = business_template_info.allowed_content_types.get(portal_type, ())
+      new_portal_type_path_dict = {}
+
+      if base_path != '':
+        next_base_path = '%s/%s' % (base_path, portal_type)
+      # Module portal_type not to have been added to the path because
+      # this portal type object already existing
+      elif 'Module' not in portal_type:
+        next_base_path = portal_type
+      else:
+        next_base_path = ''
+
+      for pt in new_portal_type_list:
+        if next_base_path != '' and 'Module' not in pt:
+          new_portal_type_path_dict[pt] = '%s/%s' % (next_base_path, pt)
+        else:
+          new_portal_type_path_dict[pt] = pt 
+      testPortalTypeViewRecursivly(validator=validator,
+                       module_id=module_id, 
+                       business_template_info=backuped_business_template_info, 
+                       business_template_info_list=business_template_info_list,
+                       portal_type_list=new_portal_type_list, 
+                       portal_type_path_dict=new_portal_type_path_dict,
+                       base_path=next_base_path,
+                       tested_portal_type_list=tested_portal_type_list)
+
+
+def addTestMethodDynamically(validator):
   from Products.ERP5.tests.utils import BusinessTemplateInfoTar
   from Products.ERP5.tests.utils import BusinessTemplateInfoDir
-  for i in target_business_templates:
+  business_template_info_list = []
+
+  # add erp5_core to the list here but not in the target_business_templates 
+  # list to not return it on getBusinessTemplateList call 
+  for i in ('erp5_core',) + target_business_templates:
     business_template = os.path.join(bt5_base_path, i)
 
-    # Look for business templates, like in ERP5TypeTestCase, they can be:
+    # Look for business templates, they can be:
     #  .bt5 files in $INSTANCE_HOME/bt5/
     #  directories in $INSTANCE_HOME/
     #  directories in $INSTANCE_HOME/bt5/*/
+    #  directories in $INSTANCE_HOME/Products/ERP5/bootstrap/
     if not ( os.path.exists(business_template) or
         os.path.exists('%s.bt5' % business_template)):
       # try in $INSTANCE_HOME/bt5/*/
       business_template_glob_list = glob('%s/*/%s' % (bt5_base_path, i))
       if business_template_glob_list:
         business_template = business_template_glob_list[0]
+      else:
+        # try in $INSTANCE_HOME/Products/ERP5/bootstrap
+        business_template = os.path.join(bootstrap_base_path,i) 
 
     if os.path.isdir(business_template):
       business_template_info = BusinessTemplateInfoDir(business_template)
@@ -183,25 +389,53 @@
       business_template_info = BusinessTemplateInfoTar(business_template+'.bt5')
     else:
       raise KeyError, "Can't find the business template: %s" % i
-
+    business_template_info_list.append(business_template_info)
+
+  tested_portal_type_list = []
+  for business_template_info in business_template_info_list:
     for module_id, module_portal_type in business_template_info.modules.items():
-      for portal_type in business_template_info.allowed_content_types.get(
-        module_portal_type, ()):
-        for action_information in business_template_info.actions[portal_type]:
-          if (action_information['category']=='object_view' and
-              action_information['visible']==1 and
-              action_information['text'].startswith('string:${object_url}/') and
-              len(action_information['text'].split('/'))==2):
-            view_name = action_information['text'].split('/')[-1]
-            method = makeTestMethod(module_id, portal_type, view_name)
-            method_name = 'test%s%s' % (portal_type, view_name)
-            setattr(TestXHTML, method_name, method)
-
-# tidy may not be installed in livecd. Then we will skip xhtml validation tests.
-if not os.path.exists('/usr/bin/tidy'):
-  print '*** tidy is not installed at /usr/bin/tidy ***'
-else:
-  addTestMethodDynamically()
+      portal_type_list = business_template_info.allowed_content_types.get(module_portal_type, ())
+      portal_type_path_dict = {}
+      portal_type_path_dict=dict(map(None,portal_type_list,portal_type_list))
+      testPortalTypeViewRecursivly(validator=validator,
+                       module_id=module_id, 
+                       business_template_info=business_template_info, 
+                       business_template_info_list=business_template_info_list,
+                       portal_type_list=portal_type_list, 
+                       portal_type_path_dict=portal_type_path_dict,
+                       base_path = '',
+                       tested_portal_type_list=tested_portal_type_list)
+
+
+# Two validators are available : tidy and the w3c validator
+# It's hightly recommanded to use the w3c validator because tidy dont show
+# all errors and show more warnings that there is.
+validator_to_use = 'w3c'
+show_warnings = True
+
+validator = None
+
+# tidy or w3c may not be installed in livecd. Then we will skip xhtml validation tests.
+# create the validator object
+if validator_to_use == 'w3c':
+  validator_path = '/usr/share/w3c-markup-validator/cgi-bin/check'
+  if not os.path.exists(validator_path):
+    print 'w3c validator is not installed at %s' % validator_path
+  else:
+    validator = W3Validator(validator_path, show_warnings)
+
+elif validator_to_use == 'tidy': 
+  error = False
+  warning = False
+  validator_path = '/usr/bin/tidy'
+  if not os.path.exists(validator_path):
+    print 'tidy is not installed at %s' % validator_path
+  else:
+    validator = TidyValidator(validator_path, show_warnings)
+
+# add the tests
+if validator is not None:
+  addTestMethodDynamically(validator)
 
 def test_suite():
   suite = unittest.TestSuite()




More information about the Erp5-report mailing list