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