Commit 82206749 authored by Jérome Perrin's avatar Jérome Perrin

Test Suite for Coding Style test

A new test suite to perform static check on business templates using  `CodingStyleTestCase`.

This runs a coding style test for each business template, after installing the business template and its dependencies listed in business template metadata.

---

In order to start with a test suite with no failure, business templates that does not pass this test today (because they don't install or because pylint issues still needs to be reviewed) are skipped with an ad-hoc  `bt/skip_coding_style_test` in the business template. Note that this file is not created by business template system as it is a temporary measure, business templates must pass pylint and other checks from `CodingStyleTestCase`.

---

I took the approach of creating one independent test per business template, instead of the approach of creating a site with all business templates like we did in testNamingConventions or testHTML. This appears as another "Coding Style" test, like Performance or Scalability tests.

The idea behind this was that if we extend our business template checks ( for example when we translate the [ERP5 Module Creation Guidelines](https://www.erp5.com/documentation/developer/guideline/module) to some scripts we can run on a business template ), we can check that each business template contain what it should contain and not just that the global result is OK.



/reviewed-on !629
parents 8515e0ac 7b1040d8
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
1
\ No newline at end of file
......@@ -59,28 +59,31 @@ def getSkinPrefixList(self):
skin_prefix_list.append(portal_prefix)
# Add document classes prefix
from Products.ERP5Type import Document
for document_class in Document.__dict__.keys():
if not document_class.startswith('add') and \
not document_class.startswith('new') and \
not document_class.startswith('_'):
skin_prefix_list.append(document_class)
skin_prefix_list.extend(self.portal_types.getDocumentTypeList())
# Add mixins prefix
skin_prefix_list.extend(self.portal_types.getMixinTypeList())
# Add interfaces prefix
skin_prefix_list.extend(self.portal_types.getInterfaceTypeList())
# XXX getInterfaceTypeList seems empty ... keep this low-level way for now.
from Products.ERP5Type import interfaces
for interface_name in interfaces.__dict__.keys():
if interface_name.startswith('I'):
skin_prefix_list.append(interface_name[1:])
# XXX do we really add with the I prefix ?
skin_prefix_list.append(interface_name)
# Add other prefix
skin_prefix_list.extend((
'Base',
'ERP5Site',
'ERP5Type',
'Entity', # A base class for Person / Organisation
'Zuite', # Products.Zelenium test suites
'Form', # Acceptable for ERP5 Forms which will soon become portal types too
'ListBox',
'DCWorkflow', # some workflow script use this, not sure it's correct.
'Zuite', # from Zelenium
'Brain', # Catalog brains
))
......
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import os
import unittest
from glob import glob
from Products.ERP5Type.tests.CodingStyleTestCase import CodingStyleTestCase
class CodingStyleTest(CodingStyleTestCase):
"""Run a coding style test for business template defined by
TESTED_BUSINESS_TEMPLATE environment variable, that is set by
ERP5BusinessTemplateCodingStyleTestSuite in test/__init__.py
"""
def getBusinessTemplateList(self):
# install erp5_administration to check with tools from erp5_administration
# XXX also install erp5_full_text_myisam_catalog to workaround missing test
# dependencies and the fact that test dependencies are not checked
# recursively.
return (
'erp5_administration',
'erp5_full_text_myisam_catalog',
self.tested_business_template)
def _installBusinessTemplateList(self,
bt_list,
update_repository_bt_list=True,
*args,
**kwargs):
"""Install depencencies automatically
taken from runUnitTest._ZodbTestComponentBootstrapOnly.
"""
template_tool = self.portal.portal_templates
from Products.ERP5.ERP5Site import getBootstrapDirectory
bt5_path_list = [os.environ.get('erp5_tests_bootstrap_path') or
getBootstrapDirectory()]
for path in os.environ['erp5_tests_bt5_path'].split(','):
if os.path.exists(os.path.join(path, "bt5list")):
bt5_path_list.append(path)
for path in glob(os.path.join(path, "*", "bt5list")):
bt5_path_list.append(os.path.dirname(path))
template_tool.updateRepositoryBusinessTemplateList(bt5_path_list)
url_bt_tuple_list = [
('%s/%s' % (repository, bt_title), bt_title) for repository, bt_title in
template_tool.resolveBusinessTemplateListDependency(
[x[1] for x in bt_list],
with_test_dependency_list=True)]
return super(CodingStyleTest,
self)._installBusinessTemplateList(url_bt_tuple_list,
*args, **kwargs)
def test_suite():
suite = unittest.TestSuite()
tested_business_template = os.environ['TESTED_BUSINESS_TEMPLATE']
testclass = type(
'CodingStyleTest %s' % tested_business_template,
(CodingStyleTest, ),
{'tested_business_template': tested_business_template})
suite.addTest(unittest.makeSuite(testclass))
return suite
......@@ -82,6 +82,7 @@ class CodingStyleTestCase(ERP5TypeTestCase):
search_sub=True):
if getattr(aq_base(document), 'checkConsistency', None) is not None:
message_list.extend(document.checkConsistency())
self.maxDiff = None
self.assertEqual([], message_list)
def test_PythonSourceCode(self):
......
##############################################################################
#
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
# Kazuhiko <kazuhiko@nexedi.com>
#
# 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
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
#
# ERP5 Naming Convention Test script.
#
# usage: python runUnitTest.py [OPTION]... namingConventionTest.py
#
import re
import unittest
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
class TestNamingConvention(ERP5TypeTestCase):
def getBusinessTemplateList(self):
"""
Return the list of business templates.
"""
# include all standard Business Templates, i.e. erp5_*
return ('erp5_base', 'erp5_pdm', 'erp5_trade', 'erp5_accounting',
'erp5_apparel', 'erp5_mrp', 'erp5_project', 'erp5_dms',
'erp5_web', 'erp5_csv_style', 'erp5_crm',
'erp5_budget', 'erp5_item', 'erp5_ui_test',
'erp5_accounting_l10n_fr', 'erp5_accounting_ui_test',
'erp5_banking_core', 'erp5_banking_cash', 'erp5_banking_check',
'erp5_banking_inventory', 'erp5_commerce', 'erp5_consulting',
'erp5_dummy_movement', 'erp5_forge', 'erp5_html_style',
'erp5_immobilisation', 'erp5_mobile', 'erp5_payroll',
'erp5_payroll_ui_test', 'erp5_pdf_editor', 'erp5_publication',
'erp5_sxc_style', 'erp5_dms_ui_test',
# skip l10n templates to save time.
# 'erp5_l10n_fr', 'erp5_l10n_ja',
# 'erp5_l10n_pl_PL', 'erp5_l10n_pt-BR',
# 'erp5_accounting_l10n_fr_m14', 'erp5_accounting_l10n_fr_m9',
# 'erp5_accounting_l10n_pl',
# 'erp5_accounting_l10n_sn',
# 'erp5_accounting_l10n_in',
)
def getTitle(self):
return "Naming Convention"
def testNamingConvention(self):
result = 'installed templates: %s\n' % repr(self.getBusinessTemplateList())
result += self.portal.portal_skins.erp5_core.ERP5Site_checkNamingConventions(html_output=None)
problems_re = re.compile('([0-9]+) problems found')
problems = int(problems_re.search(result).group(1))
self.assertEqual(0, problems, result)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestNamingConvention))
return suite
#!/usr/bin/env python2.7
import os
import re
import signal
import sys
import shutil
import getopt
from time import sleep
import urllib2
from subprocess import Popen, PIPE
from sendMail import sendMail
import atexit
print "DEPRECATION WARNING: This command 'runFunctionalTest' is Deprecated, " + \
"you should use ERP5TypeFunctionalTestCase and runUnitTest instead." + \
"You can take a look at testFunctionalCore.py"
__doc__ = """%(program)s: Zelenium functional test runner for the ERP5 Project
usage: %(program)s [options]
Options:
-h, --help this help screen
--email_to_address=STRING send results to this address by email (defaults to
erp5-report@erp5.org)
--smtp_host=hostname specify SMTP server
-s, --stdout print the results on stdout instead of sending
results by email (unless email_to_address is also
passed explicitly)
-d, --debug run firefox on current DISPLAY instead of on Xvfb
--host the hostname of this ERP5 instance
--port the port of this ERP5 instance
--portal_name the ID of the ERP5 site
URLs will start with:
http://${host}:${port}/${portal_name}/
--run_only=STRING run only specified test suite (should be only one)
--email_subject the email subject to be sent
--xvfb_display=STRING Define a xvfb display to be used.
Notes:
* You need to prepare first test environment by using following command:
./runUnitTest.py --save prepareFunctionalTest.py
"""
tests_framework_home = os.path.dirname(os.path.abspath(__file__))
# handle 'system global' instance
if tests_framework_home.startswith('/usr/lib'):
real_instance_home = '/var/lib/erp5'
else:
real_instance_home = os.path.sep.join(
tests_framework_home.split(os.path.sep)[:-3])
instance_home = os.path.join(real_instance_home, 'unit_test')
bt5_dir_list = ','.join([
os.path.join(instance_home, 'Products/ERP5/bootstrap'),
os.path.join(instance_home, 'bt5')])
class FunctionalTestRunner:
"""
Used to run Functional tests
"""
def __init__(self, instance_home):
self.instance_home = instance_home
self.xvfb_fbdir = instance_home
self.send_mail = 0
self.stdout = 0
self.debug = 0
self.email_to_address = 'erp5-report@erp5.org'
self.smtp_host = ''
self.host = 'localhost'
self.port = 8080
self.user = 'ERP5TypeTestCase'
self.password = ''
self.portal_name = 'erp5_portal'
self.run_only = ''
self.email_subject = 'ERP5'
self.xvfb_display = '123'
self.profile_dir = os.path.join(instance_home, 'profile')
def usage(self, stream, msg=None):
if msg:
print >>stream, msg
print >>stream
program = os.path.basename(sys.argv[0])
print >>stream, __doc__ % {"program": program}
def parseArgs(self, arguments=None):
if arguments is None:
arguments = sys.argv[1:]
try:
opts, args = getopt.getopt(arguments,
"hsd", ["help", "stdout", "debug",
"email_to_address=", "host=", "port=",
"portal_name=", "run_only=", "user=",
"password=", "alarms=",
"email_subject=", "smtp_host=", "xvfb_display="] )
except getopt.GetoptError, msg:
self.usage(sys.stderr, msg)
sys.exit(2)
for opt, arg in opts:
if opt in ("-s", "--stdout"):
self.stdout = 1
elif opt in ("-d", "--debug"):
self.debug = 1
elif opt == '--email_to_address':
self.email_to_address = arg
self.send_mail = 1
elif opt == '--smtp_host':
self.smtp_host = arg
elif opt in ('-h', '--help'):
self.usage(sys.stdout)
sys.exit()
elif opt == "--host":
self.host = arg
elif opt == "--port":
self.port = int(arg)
elif opt == "--portal_name":
self.portal_name = arg
elif opt == "--run_only":
self.run_only = arg
elif opt == "--user":
user = arg
elif opt == "--password":
password = arg
elif opt == "--email_subject":
self.email_subject = arg
elif opt == "--xvfb_display":
self.xvfb_display = arg
if not self.stdout:
self.send_mail = 1
self.portal_url = "http://%s:%d/%s" % (self.host, self.port, self.portal_name)
def openUrl(self, url):
# Send Accept-Charset headers to activate the UnicodeConflictResolver
# (imitating firefox 3.5.9 here)
headers = { 'Accept-Charset' : 'ISO-8859-1,utf-8;q=0.7,*;q=0.7' }
request = urllib2.Request(url, headers=headers)
# Try to use long timeout, this is needed when there is many
# activities runing
try:
f = urllib2.urlopen(request, timeout=3600*4)
except TypeError:
f = urllib2.urlopen(request)
file_content = f.read()
f.close()
return file_content
def main(self):
self.setUp()
self.launchFuntionalTest()
def launchFuntionalTest(self):
status = self.getStatus()
xvfb_pid = None
firefox_pid = None
try:
if not self.debug:
xvfb_pid = self.runXvfb(self.xvfb_display)
firefox_pid = self.runFirefox(self.xvfb_display)
while True:
sleep(10)
cur_status = self.getStatus()
if status != cur_status:
break
finally:
if xvfb_pid:
os.kill(xvfb_pid, signal.SIGTERM)
if firefox_pid:
os.kill(firefox_pid, signal.SIGTERM)
def startZope(self):
os.environ['erp5_save_data_fs'] = "1"
os.system('%s/bin/zopectl start' % self.instance_home)
sleep(2) # ad hoc
def stopZope(self):
os.system('%s/bin/zopectl stop' % self.instance_home)
def runXvfb(self, xvfb_display):
pid = os.spawnlp(os.P_NOWAIT, 'Xvfb', 'Xvfb',
'-fbdir' , '%s' % self.xvfb_fbdir ,
':%s' % xvfb_display)
display = os.environ.get('DISPLAY')
if display:
auth = Popen(['xauth', 'list', display], stdout=PIPE).communicate()[0]
if auth:
(displayname, protocolname, hexkey) = auth.split()
Popen(['xauth', 'add', 'localhost/unix:%s' %xvfb_display, protocolname, hexkey])
print 'Xvfb : %d' % pid
print 'Take screenshots using xwud -in %s/Xvfb_screen0' % self.xvfb_fbdir
return pid
def getPrefJs(self, host, port):
prefs_js = """
// Don't ask if we want to switch default browsers
user_pref("browser.shell.checkDefaultBrowser", false);
// Disable pop-up blocking
user_pref("browser.allowpopups", true);
user_pref("dom.disable_open_during_load", false);
// Configure us as the local proxy
//user_pref("network.proxy.type", 2);
// Disable security warnings
user_pref("security.warn_submit_insecure", false);
user_pref("security.warn_submit_insecure.show_once", false);
user_pref("security.warn_entering_secure", false);
user_pref("security.warn_entering_secure.show_once", false);
user_pref("security.warn_entering_weak", false);
user_pref("security.warn_entering_weak.show_once", false);
user_pref("security.warn_leaving_secure", false);
user_pref("security.warn_leaving_secure.show_once", false);
user_pref("security.warn_viewing_mixed", false);
user_pref("security.warn_viewing_mixed.show_once", false);
// Disable "do you want to remember this password?"
user_pref("signon.rememberSignons", false);
// increase the timeout before warning of unresponsive script
user_pref("dom.max_script_run_time", 120);
// this is required to upload files
user_pref("capability.principal.codebase.p1.granted", "UniversalFileRead");
user_pref("signed.applets.codebase_principal_support", true);
user_pref("capability.principal.codebase.p1.id", "http://%s");
user_pref("capability.principal.codebase.p1.subjectName", "");""" % \
'%s:%s' % (host, port)
return prefs_js
def prepareFirefox(self, prefs_js=''):
os.system("rm -rf %s" % self.profile_dir)
os.mkdir(self.profile_dir)
pref_file = open(os.path.join(self.profile_dir, 'prefs.js'), 'w')
pref_file.write(prefs_js)
pref_file.close()
def runFirefox(self,xvfb_display):
prefs_js = self.getPrefJs(self.host, self.port)
self.prepareFirefox(prefs_js)
if self.debug:
try:
shutil.copy2(os.path.expanduser('~/.Xauthority'), '%s/.Xauthority' % self.profile_dir)
except IOError:
pass
else:
os.environ['DISPLAY'] = ':%s' %xvfb_display
os.environ['MOZ_NO_REMOTE'] = '1'
os.environ['HOME'] = self.profile_dir
os.environ['LC_ALL'] = 'C'
# check if old zelenium or new zelenium
try:
urllib2.urlopen("%s/portal_tests/core/scripts/selenium-version.js" % self.portal_url)
except urllib2.HTTPError:
# Zelenium 0.8
url_string = "%s/portal_tests/?auto=true&__ac_name=%s&__ac_password=%s" % (self.portal_url, self.user, self.password)
else:
# Zelenium 0.8+ or later
url_string = "%s/portal_tests/core/TestRunner.html?test=../test_suite_html&auto=on&resultsUrl=%s/portal_tests/postResults&__ac_name=%s&__ac_password=%s" % (self.portal_url, self.portal_url, self.user, self.password)
if self.run_only:
url_string = url_string.replace('/portal_tests/', '/portal_tests/%s/' % self.run_only, 1)
pid = os.spawnlp(os.P_NOWAIT, "firefox", "firefox",
"-no-remote", "-profile", self.profile_dir,
url_string)
os.environ['MOZ_NO_REMOTE'] = '0'
print 'firefox : %d' % pid
return pid
def getStatus(self):
try:
status = self.openUrl('%s/portal_tests/TestTool_getResults'
% (self.portal_url))
except urllib2.HTTPError, e:
if e.msg == "No Content" :
status = ""
else:
raise
return status
def setPreference(self):
conversion_server_url = os.environ.get('conversion_server_url', '')
conversion_server_hostname = os.environ.get('conversion_server_hostname',
'localhost')
conversion_server_port = os.environ.get('conversion_server_port', '8008')
urllib2.urlopen('%s/Zuite_setPreference?__ac_name='
'%s&__ac_password=%s&working_copy_list=%s'
'&conversion_server_url=%s'%
(self.portal_url, self.user, self.password,
bt5_dir_list, conversion_server_url,))
def unsubscribeFromTimerService(self):
urllib2.urlopen('%s/portal_activities/?unsubscribe:method='
'&__ac_name=%s&__ac_password=%s' %
(self.portal_url, self.user, self.password))
def setUp(self):
self.setPreference()
self.unsubscribeFromTimerService()
def getSvnRevision(self):
"""Get svn revision used."""
import pysvn
return pysvn.Client().info(os.path.dirname(__file__)).revision.number
def sendResult(self):
file_content = self.openUrl('%s/portal_tests/TestTool_getResults' % self.portal_url)
passes_re = re.compile('<th[^>]*>Tests passed</th>\n\s*<td[^>]*>([^<]*)')
failures_re = re.compile('<th[^>]*>Tests failed</th>\n\s*<td[^>]*>([^<]*)')
image_re = re.compile('<img[^>]*?>')
error_title_re = re.compile('(?:error.gif.*?>|title status_failed"><td[^>]*>)([^>]*?)</td></tr>', re.S)
result_re = re.compile('<div style="padding-top: 10px;">\s*<p>\s*'
'<img.*?</div>\s.*?</div>\s*', re.S)
error_result_re = re.compile('.*(?:error.gif|title status_failed).*', re.S)
passes = passes_re.search(file_content).group(1)
failures = failures_re.search(file_content).group(1)
error_titles = [re.compile('\s+').sub(' ', x).strip()
for x in error_title_re.findall(file_content)]
revision = self.getSvnRevision()
subject = "%s r%s: Functional Tests, %s Passes, %s Failures" \
% (self.email_subject, revision, passes, failures)
summary = """
Test Summary
Tests passed: %4s
Tests failed: %4s
Following tests failed:
%s""" % (passes, failures, "\n".join(error_titles))
detail = ''
for e in result_re.findall(file_content):
if error_result_re.match(e):
detail += e
detail = image_re.sub('', detail)
if detail:
detail = '''<html>
<head>
<style type="text/css">tr.status_failed { background-color:red };</style>
</head>
<body>%s</body>
</html>''' % detail
status = (not failures)
if self.send_mail:
sendMail(subject=subject,
body=summary,
status=status,
attachments=[detail],
from_mail='nobody@svn.erp5.org',
to_mail=[self.email_to_address],
smtp_host=self.smtp_host)
if self.stdout:
print '-' * 79
print subject
print '-' * 79
print summary
print '-' * 79
print detail
return int(failures)
if __name__ == "__main__":
test_runner = FunctionalTestRunner(instance_home)
test_runner.parseArgs()
test_runner.startZope()
atexit.register(test_runner.stopZope)
test_runner.main()
sys.exit(test_runner.sendResult())
##############################################################################
#
# Copyright (c) 2007 Nexedi SARL and Contributors. All Rights Reserved.
# Kazuhiko <kazuhiko@nexedi.com>
#
# 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
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
"""Send a mail with attachments.
"""
import smtplib
import re
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.message import Message
def sendMail(subject,
body,
attachments = [],
status = False,
from_mail = 'nobody@erp5.org',
to_mail = [ 'erp5-report@erp5.org' ],
smtp_host = ''):
if attachments:
msg = MIMEMultipart()
else:
msg = Message()
msg['Subject'] = subject
msg['From'] = from_mail
msg['To'] = ', '.join(to_mail)
msg['X-ERP5-Tests'] = 'ERP5'
if status:
msg['X-ERP5-Tests-Status'] = 'OK'
# Guarantees the message ends in a newline
msg.preamble = subject
msg.epilogue = ''
if attachments:
mime_text = MIMEText(body)
mime_text.add_header('Content-Disposition', 'attachment',
filename='body')
msg.attach(mime_text)
html_re = re.compile('<html>', re.I)
for item in attachments:
mime_text = MIMEText(item)
if html_re.match(item):
mime_text.set_type('text/html')
mime_text.add_header('Content-Disposition', 'attachment',
filename='attachment.html')
else:
mime_text.add_header('Content-Disposition', 'attachment',
filename='attachment.txt')
msg.attach(mime_text)
else:
msg.set_payload(body)
# Send the email via SMTP server.
if smtp_host:
s = smtplib.SMTP(smtp_host)
else:
s = smtplib.SMTP()
s.connect()
s.sendmail(from_mail, to_mail, msg.as_string())
s.close()
......@@ -173,3 +173,24 @@ class FunctionalTests(ERP5):
def _getAllTestList(self):
return [x for x in super(FunctionalTests, self)._getAllTestList()
if x.startswith('testFunctional') or ':testFunctional' in x]
class ERP5BusinessTemplateCodingStyleTestSuite(_ERP5):
"""Run coding style test on all business templates.
"""
def getTestList(self):
test_list = []
for business_template_path in (
glob('%s/../bt5/*' % HERE)
+ glob('%s/../product/ERP5/bootstrap/*' % HERE)):
# we skip coding style check for business templates having this marker
# property. Since the property is not exported (on purpose), modified business templates
# will be candidate for coding style test again.
if os.path.isdir(business_template_path) and \
not os.path.exists(os.path.join(business_template_path, 'bt/skip_coding_style_test')):
test_list.append(os.path.basename(business_template_path))
return test_list
def run(self, full_test):
return self.runUnitTest('CodingStyleTest', TESTED_BUSINESS_TEMPLATE=full_test)
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