devtools/__init__.py
author Sandrine Ribeau <sandrine.ribeau@logilab.fr>
Thu, 06 Nov 2008 16:21:57 -0800
changeset 11 db9c539e0b1b
parent 0 b97547f5f1fa
child 298 3e6d32667140
permissions -rw-r--r--
Add module wfobjs to enable workflow in gae. Add module vcard to enable usage of cube person in gae. Add init file creation for cubes gae directory.

"""Test tools for cubicweb

:organization: Logilab
:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"

import os
import logging
from os.path import (abspath, join, exists, basename, dirname, normpath, split,
                     isfile, isabs)

from mx.DateTime import strptime, DateTimeDelta

from cubicweb import CW_SOFTWARE_ROOT, ConfigurationError
from cubicweb.toolsutils import read_config
from cubicweb.cwconfig import CubicWebConfiguration, merge_options
from cubicweb.server.serverconfig import ServerConfiguration
from cubicweb.etwist.twconfig import TwistedConfiguration

# validators are used to validate (XML, DTD, whatever) view's content
# validators availables are :
#  'dtd' : validates XML + declared DTD
#  'xml' : guarantees XML is well formed
#  None : do not try to validate anything
VIEW_VALIDATORS = {}
BASE_URL = 'http://testing.fr/cubicweb/'
DEFAULT_SOURCES = {'system': {'adapter' : 'native',
                              'db-encoding' : 'UTF-8', #'ISO-8859-1',
                              'db-user' : u'admin',
                              'db-password' : 'gingkow',
                              'db-name' : 'tmpdb',
                              'db-driver' : 'sqlite',
                              'db-host' : None,
                              },
                   'admin' : {'login': u'admin',
                              'password': u'gingkow',
                              },
                   }

class TestServerConfiguration(ServerConfiguration):
    mode = 'test'
    set_language = False
    read_application_schema = False
    bootstrap_schema = False
    init_repository = True
    options = merge_options(ServerConfiguration.options + (
        ('anonymous-user',
         {'type' : 'string',
          'default': None,
          'help': 'login of the CubicWeb user account to use for anonymous user (if you want to allow anonymous)',
          'group': 'main', 'inputlevel': 1,
          }),
        ('anonymous-password',
         {'type' : 'string',
          'default': None,
          'help': 'password of the CubicWeb user account matching login',
          'group': 'main', 'inputlevel': 1,
          }),
        ))
                            
    if not os.environ.get('APYCOT_ROOT'):
        REGISTRY_DIR = normpath(join(CW_SOFTWARE_ROOT, '../cubes'))
    
    def __init__(self, appid, log_threshold=logging.CRITICAL+10):
        ServerConfiguration.__init__(self, appid)
        self.global_set_option('log-file', None)
        self.init_log(log_threshold, force=True)
        # need this, usually triggered by cubicweb-ctl
        self.load_cwctl_plugins()

    anonymous_user = TwistedConfiguration.anonymous_user.im_func
        
    @property
    def apphome(self):
        if exists(self.appid):
            return abspath(self.appid)
        # application cube test
        return abspath('..')
    appdatahome = apphome
    
    def main_config_file(self):
        """return application's control configuration file"""
        return join(self.apphome, '%s.conf' % self.name)

    def instance_md5_version(self):
        return ''

    def bootstrap_cubes(self):
        try:
            super(TestServerConfiguration, self).bootstrap_cubes()
        except IOError:
            # no cubes
            self.init_cubes( () )

    sourcefile = None
    def sources_file(self):
        """define in subclasses self.sourcefile if necessary"""
        if self.sourcefile:
            print 'Reading sources from', self.sourcefile
            sourcefile = self.sourcefile
            if not isabs(sourcefile):
                sourcefile = join(self.apphome, sourcefile)
        else:
            sourcefile = super(TestServerConfiguration, self).sources_file()
        return sourcefile

    def sources(self):
        """By default, we run tests with the sqlite DB backend.  One may use its
        own configuration by just creating a 'sources' file in the test
        directory from wich tests are launched or by specifying an alternative
        sources file using self.sourcefile.
        """
        sources = super(TestServerConfiguration, self).sources()
        if not sources:
            sources = DEFAULT_SOURCES
        return sources
    
    def load_defaults(self):
        super(TestServerConfiguration, self).load_defaults()
        # note: don't call global set option here, OptionManager may not yet be initialized
        # add anonymous user
        self.set_option('anonymous-user', 'anon')
        self.set_option('anonymous-password', 'anon')
        # uncomment the line below if you want rql queries to be logged
        #self.set_option('query-log-file', '/tmp/test_rql_log.' + `os.getpid()`)
        self.set_option('sender-name', 'cubicweb-test')
        self.set_option('sender-addr', 'cubicweb-test@logilab.fr')
        try:
            send_to =  '%s@logilab.fr' % os.getlogin()
        except OSError:
            send_to =  '%s@logilab.fr' % (os.environ.get('USER')
                                          or os.environ.get('USERNAME')
                                          or os.environ.get('LOGNAME'))
        self.set_option('sender-addr', send_to)
        self.set_option('default-dest-addrs', send_to)
        self.set_option('base-url', BASE_URL)


class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration):
    repo_method = 'inmemory'
    options = merge_options(TestServerConfiguration.options + TwistedConfiguration.options)
    cubicweb_vobject_path = TestServerConfiguration.cubicweb_vobject_path | TwistedConfiguration.cubicweb_vobject_path
    cube_vobject_path = TestServerConfiguration.cube_vobject_path | TwistedConfiguration.cube_vobject_path

    def available_languages(self, *args):
        return ('en', 'fr', 'de')
    
    def ext_resources_file(self):
        """return application's external resources file"""
        return join(self.apphome, 'data', 'external_resources')
    
    def pyro_enabled(self):
        # but export PYRO_MULTITHREAD=0 or you get problems with sqlite and threads
        return True


class ApptestConfiguration(BaseApptestConfiguration):
    
    def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None):
        BaseApptestConfiguration.__init__(self, appid, log_threshold=log_threshold)
        self.init_repository = sourcefile is None
        self.sourcefile = sourcefile
        import re
        self.global_set_option('embed-allowed', re.compile('.*'))
        

class RealDatabaseConfiguration(ApptestConfiguration):
    init_repository = False
    sourcesdef =  {'system': {'adapter' : 'native',
                              'db-encoding' : 'UTF-8', #'ISO-8859-1',
                              'db-user' : u'admin',
                              'db-password' : 'gingkow',
                              'db-name' : 'seotest',
                              'db-driver' : 'postgres',
                              'db-host' : None,
                              },
                   'admin' : {'login': u'admin',
                              'password': u'gingkow',
                              },
                   }
    
    def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None):
        ApptestConfiguration.__init__(self, appid)
        self.init_repository = False


    def sources(self):
        """
        By default, we run tests with the sqlite DB backend.
        One may use its own configuration by just creating a
        'sources' file in the test directory from wich tests are
        launched. 
        """
        self._sources = self.sourcesdef
        return self._sources


def buildconfig(dbuser, dbpassword, dbname, adminuser, adminpassword, dbhost=None):
    """convenience function that builds a real-db configuration class"""
    sourcesdef =  {'system': {'adapter' : 'native',
                              'db-encoding' : 'UTF-8', #'ISO-8859-1',
                              'db-user' : dbuser,
                              'db-password' : dbpassword,
                              'db-name' : dbname,
                              'db-driver' : 'postgres',
                              'db-host' : dbhost,
                              },
                   'admin' : {'login': adminuser,
                              'password': adminpassword,
                              },
                   }
    return type('MyRealDBConfig', (RealDatabaseConfiguration,),
                {'sourcesdef': sourcesdef})

def loadconfig(filename):
    """convenience function that builds a real-db configuration class
    from a file
    """
    return type('MyRealDBConfig', (RealDatabaseConfiguration,),
                {'sourcesdef': read_config(filename)})
    

class LivetestConfiguration(BaseApptestConfiguration):
    init_repository = False
    
    def __init__(self, cube=None, sourcefile=None, pyro_name=None,
                 log_threshold=logging.CRITICAL):
        TestServerConfiguration.__init__(self, cube, log_threshold=log_threshold)
        self.appid = pyro_name or cube
        # don't change this, else some symlink problems may arise in some
        # environment (e.g. mine (syt) ;o)
        # XXX I'm afraid this test will prevent to run test from a production
        # environment
        self._sources = None
        # application cube test
        if cube is not None:
            self.apphome = self.cube_dir(cube)
        elif 'web' in os.getcwd().split(os.sep):
            # web test
            self.apphome = join(normpath(join(dirname(__file__), '..')), 'web')
        else:
            # application cube test
            self.apphome = abspath('..')
        self.sourcefile = sourcefile
        self.global_set_option('realm', '')
        self.use_pyro = pyro_name is not None

    def pyro_enabled(self):
        if self.use_pyro:
            return True
        else:
            return False

CubicWebConfiguration.cls_adjust_sys_path()
                                                    
def install_sqlite_path(querier):
    """This patch hotfixes the following sqlite bug :
     - http://www.sqlite.org/cvstrac/tktview?tn=1327,33
    (some dates are returned as strings rather thant date objects)
    """
    def wrap_execute(base_execute):
        def new_execute(*args, **kwargs):
            rset = base_execute(*args, **kwargs)
            if rset.description:
                found_date = False
                for row, rowdesc in zip(rset, rset.description):
                    for cellindex, (value, vtype) in enumerate(zip(row, rowdesc)):
                        if vtype in ('Date', 'Datetime') and type(value) is unicode:
                            found_date = True
                            try:
                                row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S')
                            except:
                                row[cellindex] = strptime(value, '%Y-%m-%d')
                        if vtype == 'Time' and type(value) is unicode:
                            found_date = True
                            try:
                                row[cellindex] = strptime(value, '%H:%M:%S')
                            except:
                                # DateTime used as Time?
                                row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S')
                        if vtype == 'Interval' and type(value) is int:
                            found_date = True
                            row[cellindex] = DateTimeDelta(0, 0, 0, value)
                    if not found_date:
                        break
            return rset
        return new_execute
    querier.__class__.execute = wrap_execute(querier.__class__.execute)


def init_test_database(driver='sqlite', configdir='data', config=None,
                       vreg=None):
    """init a test database for a specific driver"""
    from cubicweb.dbapi import in_memory_cnx
    if vreg and not config:
        config = vreg.config
    config = config or TestServerConfiguration(configdir)
    source = config.sources()
    if driver == 'sqlite':
        init_test_database_sqlite(config, source)
    elif driver == 'postgres':
        init_test_database_postgres(config, source)
    else:
        raise ValueError('no initialization function for driver %r' % driver)
    config._cubes = None # avoid assertion error
    repo, cnx = in_memory_cnx(vreg or config, unicode(source['admin']['login']),
                              source['admin']['password'] or 'xxx')
    if driver == 'sqlite':
        install_sqlite_path(repo.querier)
    return repo, cnx

def init_test_database_postgres(config, source, vreg=None):
    """initialize a fresh sqlite databse used for testing purpose"""
    if config.init_repository:
        from cubicweb.server import init_repository
        init_repository(config, interactive=False, drop=True, vreg=vreg)

def cleanup_sqlite(dbfile, removecube=False):
    try:
        os.remove(dbfile)
        os.remove('%s-journal' % dbfile)
    except OSError:
        pass
    if removecube:
        try:
            os.remove('%s-cube' % dbfile)
        except OSError:
            pass
    
def init_test_database_sqlite(config, source, vreg=None):
    """initialize a fresh sqlite databse used for testing purpose"""
    import shutil
    # remove database file if it exists (actually I know driver == 'sqlite' :)
    dbfile = source['system']['db-name']
    cleanup_sqlite(dbfile)
    cube = '%s-cube' % dbfile
    if exists(cube):
        shutil.copy(cube, dbfile)
    else:
        # initialize the database
        from cubicweb.server import init_repository
        init_repository(config, interactive=False, vreg=vreg)
        shutil.copy(dbfile, cube)