Added a SAP connector and integrated the test system with coverage.py
This commit is contained in:
parent
b541ecb651
commit
01487db688
8 changed files with 450 additions and 115 deletions
110
gen/generator.py
110
gen/generator.py
|
@ -1,6 +1,5 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, sys, parser, symbol, token
|
||||
from optparse import OptionParser
|
||||
from appy.gen import Type, State, Config, Tool, Flavour
|
||||
from appy.gen.descriptors import *
|
||||
from appy.gen.utils import produceNiceMessage
|
||||
|
@ -322,113 +321,4 @@ class Generator:
|
|||
for wfDescr in self.workflows: self.generateWorkflow(wfDescr)
|
||||
self.finalize()
|
||||
print 'Done.'
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
ERROR_CODE = 1
|
||||
VALID_PRODUCT_TYPES = ('plone25', 'odt')
|
||||
APP_NOT_FOUND = 'Application not found at %s.'
|
||||
WRONG_NG_OF_ARGS = 'Wrong number of arguments.'
|
||||
WRONG_OUTPUT_FOLDER = 'Output folder not found. Please create it first.'
|
||||
PRODUCT_TYPE_ERROR = 'Wrong product type. Product type may be one of the ' \
|
||||
'following: %s' % str(VALID_PRODUCT_TYPES)
|
||||
C_OPTION = 'Removes from i18n files all labels that are not automatically ' \
|
||||
'generated from your gen-application. It can be useful during ' \
|
||||
'development, when you do lots of name changes (classes, ' \
|
||||
'attributes, states, transitions, etc): in this case, the Appy ' \
|
||||
'i18n label generation machinery produces lots of labels that ' \
|
||||
'then become obsolete.'
|
||||
S_OPTION = 'Sorts all i18n labels. If you use this option, among the ' \
|
||||
'generated i18n files, you will find first all labels ' \
|
||||
'that are automatically generated by appy.gen, in some logical ' \
|
||||
'order (ie: field-related labels appear together, in the order ' \
|
||||
'they are declared in the gen-class). Then, if you have added ' \
|
||||
'labels manually, they will appear afterwards. Sorting labels ' \
|
||||
'may not be desired under development. Indeed, when no sorting ' \
|
||||
'occurs, every time you add or modify a field, class, state, etc, ' \
|
||||
'newly generated labels will all appear together at the end of ' \
|
||||
'the file; so it will be easy to translate them all. When sorting ' \
|
||||
'occurs, those elements may be spread at different places in the ' \
|
||||
'i18n file. When the development is finished, it may be a good ' \
|
||||
'idea to sort the labels to get a clean and logically ordered ' \
|
||||
'set of translation files.'
|
||||
|
||||
class GeneratorScript:
|
||||
'''usage: %prog [options] app productType outputFolder
|
||||
|
||||
"app" is the path to your Appy application, which may be a
|
||||
Python module (= a file than ends with .py) or a Python
|
||||
package (= a folder containing a file named __init__.py).
|
||||
Your app may reside anywhere (but it needs to be
|
||||
accessible by the underlying application server, ie Zope),
|
||||
excepted within the generated product. Typically, if you
|
||||
generate a Plone product, it may reside within
|
||||
<yourZopeInstance>/lib/python, but not within the
|
||||
generated product (typically stored in
|
||||
<yourZopeInstance>/Products).
|
||||
"productType" is the kind of product you want to generate
|
||||
(currently, only "plone25" and 'odt' are supported;
|
||||
in the near future, the "plone25" target will also produce
|
||||
Plone 3-compliant code that will still work with
|
||||
Plone 2.5).
|
||||
"outputFolder" is the folder where the product will be generated.
|
||||
For example, if you specify /my/output/folder for your
|
||||
application /home/gde/MyApp.py, this script will create
|
||||
a folder /my/output/folder/MyApp and put in it the
|
||||
generated product.
|
||||
|
||||
Example: generating a Plone product
|
||||
-----------------------------------
|
||||
In your Zope instance named myZopeInstance, create a folder
|
||||
"myZopeInstance/lib/python/MyApp". Create into it your Appy application
|
||||
(we suppose here that it is a Python package, containing a __init__.py
|
||||
file and other files). Then, chdir into this folder and type
|
||||
"python <appyPath>/gen/generator.py . plone25 ../../../Products" and the
|
||||
product will be generated in myZopeInstance/Products/MyApp.
|
||||
"python" must refer to a Python interpreter that knows package appy.'''
|
||||
|
||||
def generateProduct(self, options, application, productType, outputFolder):
|
||||
exec 'from appy.gen.%s.generator import Generator' % productType
|
||||
Generator(application, outputFolder, options).run()
|
||||
|
||||
def manageArgs(self, parser, options, args):
|
||||
# Check number of args
|
||||
if len(args) != 3:
|
||||
print WRONG_NG_OF_ARGS
|
||||
parser.print_help()
|
||||
sys.exit(ERROR_CODE)
|
||||
# Check productType
|
||||
if args[1] not in VALID_PRODUCT_TYPES:
|
||||
print PRODUCT_TYPE_ERROR
|
||||
sys.exit(ERROR_CODE)
|
||||
# Check existence of application
|
||||
if not os.path.exists(args[0]):
|
||||
print APP_NOT_FOUND % args[0]
|
||||
sys.exit(ERROR_CODE)
|
||||
# Check existence of outputFolder basic type
|
||||
if not os.path.exists(args[2]):
|
||||
print WRONG_OUTPUT_FOLDER
|
||||
sys.exit(ERROR_CODE)
|
||||
# Convert all paths in absolute paths
|
||||
for i in (0,2):
|
||||
args[i] = os.path.abspath(args[i])
|
||||
def run(self):
|
||||
optParser = OptionParser(usage=GeneratorScript.__doc__)
|
||||
optParser.add_option("-c", "--i18n-clean", action='store_true',
|
||||
dest='i18nClean', default=False, help=C_OPTION)
|
||||
optParser.add_option("-s", "--i18n-sort", action='store_true',
|
||||
dest='i18nSort', default=False, help=S_OPTION)
|
||||
(options, args) = optParser.parse_args()
|
||||
try:
|
||||
self.manageArgs(optParser, options, args)
|
||||
print 'Generating %s product in %s...' % (args[1], args[2])
|
||||
self.generateProduct(options, *args)
|
||||
except GeneratorError, ge:
|
||||
sys.stderr.write(str(ge))
|
||||
sys.stderr.write('\n')
|
||||
optParser.print_help()
|
||||
sys.exit(ERROR_CODE)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
GeneratorScript().run()
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
import os, os.path, sys
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class TestMixin:
|
||||
'''This class is mixed in with any PloneTestCase.'''
|
||||
|
@ -12,16 +15,81 @@ class TestMixin:
|
|||
self.logout()
|
||||
self.login(userId)
|
||||
|
||||
def getNonEmptySubModules(self, moduleName):
|
||||
'''Returns the list fo sub-modules of p_app that are non-empty.'''
|
||||
res = []
|
||||
try:
|
||||
exec 'import %s' % moduleName
|
||||
exec 'moduleObj = %s' % moduleName
|
||||
moduleFile = moduleObj.__file__
|
||||
if moduleFile.endswith('.pyc'):
|
||||
moduleFile = moduleFile[:-1]
|
||||
except ImportError, ie:
|
||||
return res
|
||||
except SyntaxError, se:
|
||||
return res
|
||||
# Include the module if not empty. "Emptyness" is determined by the
|
||||
# absence of names beginning with other chars than "__".
|
||||
for elem in moduleObj.__dict__.iterkeys():
|
||||
if not elem.startswith('__'):
|
||||
print 'Element found in this module!!!', moduleObj, elem
|
||||
res.append(moduleObj)
|
||||
break
|
||||
# Include sub-modules if any
|
||||
if moduleFile.find("__init__.py") != -1:
|
||||
# Potentially, sub-modules exist.
|
||||
moduleFolder = os.path.dirname(moduleFile)
|
||||
for elem in os.listdir(moduleFolder):
|
||||
if elem.startswith('.'): continue
|
||||
subModuleName, ext = os.path.splitext(elem)
|
||||
if ((ext == '.py') and (subModuleName != '__init__')) or \
|
||||
os.path.isdir(os.path.join(moduleFolder, subModuleName)):
|
||||
# Submodules may be sub-folders or Python files
|
||||
subModuleName = '%s.%s' % (moduleName, subModuleName)
|
||||
res += self.getNonEmptySubModules(subModuleName)
|
||||
return res
|
||||
|
||||
def getCovFolder(self):
|
||||
'''Returns the folder where to put the coverage folder if needed.'''
|
||||
for arg in sys.argv:
|
||||
if arg.startswith('[coverage'):
|
||||
return arg[10:].strip(']')
|
||||
return None
|
||||
|
||||
# Functions executed before and after every test -------------------------------
|
||||
def beforeTest(test):
|
||||
'''Is executed before every test.'''
|
||||
g = test.globs
|
||||
g['tool'] = test.app.plone.get('portal_%s' % g['appName'].lower()).appy()
|
||||
g['appFolder'] = g['tool'].o.getProductConfig().diskFolder
|
||||
cfg = g['tool'].o.getProductConfig()
|
||||
g['appFolder'] = cfg.diskFolder
|
||||
moduleOrClassName = g['test'].name # Not used yet.
|
||||
# Initialize the test
|
||||
test.createUser('admin', ('Member','Manager'))
|
||||
test.login('admin')
|
||||
g['t'] = g['test']
|
||||
# Must we perform test coverage ?
|
||||
covFolder = test.getCovFolder()
|
||||
if covFolder:
|
||||
try:
|
||||
print 'COV!!!!', covFolder
|
||||
import coverage
|
||||
app = getattr(cfg, g['tool'].o.getAppName())
|
||||
from coverage import coverage
|
||||
cov = coverage()
|
||||
g['cov'] = cov
|
||||
g['covFolder'] = covFolder
|
||||
cov.start()
|
||||
except ImportError:
|
||||
print 'You must install the "coverage" product.'
|
||||
|
||||
def afterTest(test): pass
|
||||
def afterTest(test):
|
||||
'''Is executed after every test.'''
|
||||
g = test.globs
|
||||
if g.has_key('covFolder'):
|
||||
cov = g['cov']
|
||||
cov.stop()
|
||||
# Dumps the coverage report
|
||||
appModules = test.getNonEmptySubModules(g['tool'].o.getAppName())
|
||||
cov.html_report(directory=g['covFolder'], morfs=appModules)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -190,7 +190,7 @@
|
|||
</tal:editField>
|
||||
<tal:viewField tal:condition="not: isEdit">
|
||||
<tal:fileField condition="python: (appyType['type'] == 'File')">
|
||||
<span tal:condition="showLabel" tal:content="label"></span>
|
||||
<span tal:condition="showLabel" tal:content="label" class="appyLabel"></span>
|
||||
<metal:viewField use-macro="python: contextObj.widget(field.getName(), 'view', use_label=0)"/>
|
||||
</tal:fileField>
|
||||
<tal:date condition="python: appyType['type'] == 'Date'">
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
}
|
||||
|
||||
.appyList {
|
||||
line-height: 0;
|
||||
line-height: 1.1em;
|
||||
margin: 0 0 0.5em 1.2em;
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
class ToolWrapper:
|
||||
|
||||
def getInitiator(self):
|
||||
'''Retrieves the object that triggered the creation of the object
|
||||
being currently created (if any).'''
|
||||
|
@ -13,4 +12,8 @@ class ToolWrapper:
|
|||
def getObject(self, uid):
|
||||
'''Allow to retrieve an object from its unique identifier p_uid.'''
|
||||
return self.o.getObject(uid, appy=True)
|
||||
|
||||
def getDiskFolder(self):
|
||||
'''Returns the disk folder where the Appy application is stored.'''
|
||||
return self.o.getProductConfig().diskFolder
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue