[uiprops] use lazystr and update some variables usage in css
to get a chance to change style without having to change all properties.
Also had missing boxHeader.png file (formerly header.png).
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb 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 Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""this module contains base classes and utilities for cubicweb tests"""from__future__importwith_statement__docformat__="restructuredtext en"importosimportsysimportrefromurllibimportunquotefrommathimportlogfromcontextlibimportcontextmanagerfromwarningsimportwarnimportyams.schemafromlogilab.common.testlibimportTestCase,InnerTest,Tagsfromlogilab.common.pytestimportnocoverage,pause_tracing,resume_tracingfromlogilab.common.debuggerimportDebuggerfromlogilab.common.umessageimportmessage_from_stringfromlogilab.common.decoratorsimportcached,classproperty,clear_cachefromlogilab.common.deprecationimportdeprecatedfromcubicwebimportValidationError,NoSelectableObject,AuthenticationErrorfromcubicwebimportcwconfig,devtools,web,serverfromcubicweb.dbapiimportProgrammingError,DBAPISession,repo_connectfromcubicweb.sobjectsimportnotificationfromcubicweb.webimportRedirect,applicationfromcubicweb.server.sessionimportsecurity_enabledfromcubicweb.devtoolsimportSYSTEM_ENTITIES,SYSTEM_RELATIONS,VIEW_VALIDATORSfromcubicweb.devtoolsimportfake,htmlparserfromcubicweb.utilsimportjson# low-level utilities ##########################################################classCubicWebDebugger(Debugger):"""special debugger class providing a 'view' function which saves some html into a temporary file and open a web browser to examinate it. """defdo_view(self,arg):importwebbrowserdata=self._getval(arg)file('/tmp/toto.html','w').write(data)webbrowser.open('file:///tmp/toto.html')defline_context_filter(line_no,center,before=3,after=None):"""return true if line are in context if after is None: after = before """ifafterisNone:after=beforereturncenter-before<=line_no<=center+afterdefunprotected_entities(schema,strict=False):"""returned a set of each non final entity type, excluding "system" entities (eg CWGroup, CWUser...) """ifstrict:protected_entities=yams.schema.BASE_TYPESelse:protected_entities=yams.schema.BASE_TYPES.union(SYSTEM_ENTITIES)returnset(schema.entities())-protected_entitiesdefrefresh_repo(repo,resetschema=False,resetvreg=False):forpoolinrepo.pools:pool.close(True)repo.system_source.shutdown()devtools.reset_test_database(repo.config)forpoolinrepo.pools:pool.reconnect()repo._type_source_cache={}repo._extid_cache={}repo.querier._rql_cache={}forsourceinrepo.sources:source.reset_caches()ifresetschema:repo.set_schema(repo.config.load_schema(),resetvreg=resetvreg)# email handling, to test emails sent by an application ########################MAILBOX=[]classEmail:"""you'll get instances of Email into MAILBOX during tests that trigger some notification. * `msg` is the original message object * `recipients` is a list of email address which are the recipients of this message """def__init__(self,recipients,msg):self.recipients=recipientsself.msg=msg@propertydefmessage(self):returnmessage_from_string(self.msg)@propertydefsubject(self):returnself.message.get('Subject')@propertydefcontent(self):returnself.message.get_payload(decode=True)def__repr__(self):return'<Email to %s with subject %s>'%(','.join(self.recipients),self.message.get('Subject'))# the trick to get email into MAILBOX instead of actually sent: monkey patch# cwconfig.SMTP objectclassMockSMTP:def__init__(self,server,port):passdefclose(self):passdefsendmail(self,helo_addr,recipients,msg):MAILBOX.append(Email(recipients,msg))cwconfig.SMTP=MockSMTP# base class for cubicweb tests requiring a full cw environments ###############classCubicWebTC(TestCase):"""abstract class for test using an apptest environment attributes: * `vreg`, the vregistry * `schema`, self.vreg.schema * `config`, cubicweb configuration * `cnx`, dbapi connection to the repository using an admin user * `session`, server side session associated to `cnx` * `app`, the cubicweb publisher (for web testing) * `repo`, the repository object * `admlogin`, login of the admin user * `admpassword`, password of the admin user """appid='data'configcls=devtools.ApptestConfigurationreset_schema=reset_vreg=False# reset schema / vreg between teststags=TestCase.tags|Tags('cubicweb','cw_repo')@classpropertydefconfig(cls):"""return the configuration object. Configuration is cached on the test class. """try:returncls.__dict__['_config']exceptKeyError:config=cls._config=cls.configcls(cls.appid)config.mode='test'returnconfig@classmethoddefinit_config(cls,config):"""configuration initialization hooks. You may want to override this."""source=config.sources()['system']cls.admlogin=unicode(source['db-user'])cls.admpassword=source['db-password']# uncomment the line below if you want rql queries to be logged#config.global_set_option('query-log-file',# '/tmp/test_rql_log.' + `os.getpid()`)config.global_set_option('log-file',None)# set default-dest-addrs to a dumb email address to avoid mailbox or# mail queue pollutionconfig.global_set_option('default-dest-addrs',['whatever'])try:send_to='%s@logilab.fr'%os.getlogin()# AttributeError since getlogin not available under all platformsexcept(OSError,AttributeError):send_to='%s@logilab.fr'%(os.environ.get('USER')oros.environ.get('USERNAME')oros.environ.get('LOGNAME'))config.global_set_option('sender-addr',send_to)config.global_set_option('default-dest-addrs',send_to)config.global_set_option('sender-name','cubicweb-test')config.global_set_option('sender-addr','cubicweb-test@logilab.fr')# web resourcesconfig.global_set_option('base-url',devtools.BASE_URL)try:config.global_set_option('embed-allowed',re.compile('.*'))except:# not in server only configurationpass@classmethoddef_init_repo(cls):"""init the repository and connection to it. Repository and connection are cached on the test class. Once initialized, we simply reset connections and repository caches. """ifnot'repo'incls.__dict__:cls._build_repo()else:try:cls.cnx.rollback()exceptProgrammingError:passcls._refresh_repo()@classmethoddef_build_repo(cls):cls.repo,cls.cnx=devtools.init_test_database(config=cls.config)cls.init_config(cls.config)cls.repo.hm.call_hooks('server_startup',repo=cls.repo)cls.vreg=cls.repo.vregcls.websession=DBAPISession(cls.cnx,cls.admlogin,{'password':cls.admpassword})cls._orig_cnx=(cls.cnx,cls.websession)cls.config.repository=lambdax=None:cls.repo@classmethoddef_refresh_repo(cls):refresh_repo(cls.repo,cls.reset_schema,cls.reset_vreg)# global resources accessors ###############################################@propertydefschema(self):"""return the application schema"""returnself.vreg.schema@propertydefsession(self):"""return current server side session (using default manager account)"""session=self.repo._sessions[self.cnx.sessionid]session.set_pool()returnsession@propertydefadminsession(self):"""return current server side session (using default manager account)"""returnself.repo._sessions[self._orig_cnx[0].sessionid]defset_option(self,optname,value):self.config.global_set_option(optname,value)defset_debug(self,debugmode):server.set_debug(debugmode)# default test setup and teardown #########################################defsetUp(self):pause_tracing()previous_failure=self.__class__.__dict__.get('_repo_init_failed')ifprevious_failureisnotNone:self.skip('repository is not initialised: %r'%previous_failure)try:self._init_repo()exceptException,ex:self.__class__._repo_init_failed=exraiseresume_tracing()self._cnxs=[]self.setup_database()self.commit()MAILBOX[:]=[]# reset mailboxdeftearDown(self):ifnotself.cnx._closed:self.cnx.rollback()forcnxinself._cnxs:ifnotcnx._closed:cnx.close()defsetup_database(self):"""add your database setup code by overriding this method"""# user / session management ###############################################defuser(self,req=None):"""return the application schema"""ifreqisNone:req=self.request()returnself.cnx.user(req)else:returnreq.userdefcreate_user(self,login,groups=('users',),password=None,req=None,commit=True,**kwargs):"""create and return a new user entity"""ifpasswordisNone:password=login.encode('utf8')ifreqisNone:req=self._orig_cnx[0].request()user=req.create_entity('CWUser',login=unicode(login),upassword=password,**kwargs)req.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'%','.join(repr(g)forgingroups),{'x':user.eid})user.cw_clear_relation_cache('in_group','subject')ifcommit:req.cnx.commit()returnuserdeflogin(self,login,**kwargs):"""return a connection for the given login/password"""iflogin==self.admlogin:self.restore_connection()else:ifnotkwargs:kwargs['password']=str(login)self.cnx=repo_connect(self.repo,unicode(login),**kwargs)self.websession=DBAPISession(self.cnx)self._cnxs.append(self.cnx)iflogin==self.vreg.config.anonymous_user()[0]:self.cnx.anonymous_connection=Truereturnself.cnxdefrestore_connection(self):ifnotself.cnxisself._orig_cnx[0]:ifnotself.cnx._closed:self.cnx.close()try:self._cnxs.remove(self.cnx)exceptValueError:passself.cnx,self.websession=self._orig_cnx# db api ##################################################################@nocoveragedefcursor(self,req=None):returnself.cnx.cursor(reqorself.request())@nocoveragedefexecute(self,rql,args=None,eidkey=None,req=None):"""executes <rql>, builds a resultset, and returns a couple (rset, req) where req is a FakeRequest """ifeidkeyisnotNone:warn('[3.8] eidkey is deprecated, you can safely remove this argument',DeprecationWarning,stacklevel=2)req=reqorself.request(rql=rql)returnreq.execute(unicode(rql),args)@nocoveragedefcommit(self):try:returnself.cnx.commit()finally:self.session.set_pool()# ensure pool still set after commit@nocoveragedefrollback(self):try:self.cnx.rollback()exceptProgrammingError:pass# connection closedfinally:self.session.set_pool()# ensure pool still set after commit# # server side db api #######################################################defsexecute(self,rql,args=None,eid_key=None):ifeid_keyisnotNone:warn('[3.8] eid_key is deprecated, you can safely remove this argument',DeprecationWarning,stacklevel=2)self.session.set_pool()returnself.session.execute(rql,args)# other utilities #########################################################@contextmanagerdeftemporary_appobjects(self,*appobjects):self.vreg._loadedmods.setdefault(self.__module__,{})forobjinappobjects:self.vreg.register(obj)try:yieldfinally:forobjinappobjects:self.vreg.unregister(obj)# vregistry inspection utilities ###########################################defpviews(self,req,rset):returnsorted((a.__regid__,a.__class__)forainself.vreg['views'].possible_views(req,rset=rset))defpactions(self,req,rset,skipcategories=('addrelated','siteactions','useractions','footer')):return[(a.__regid__,a.__class__)forainself.vreg['actions'].poss_visible_objects(req,rset=rset)ifa.categorynotinskipcategories]defpactions_by_cats(self,req,rset,categories=('addrelated',)):return[(a.__regid__,a.__class__)forainself.vreg['actions'].poss_visible_objects(req,rset=rset)ifa.categoryincategories]defpactionsdict(self,req,rset,skipcategories=('addrelated','siteactions','useractions','footer')):res={}forainself.vreg['actions'].poss_visible_objects(req,rset=rset):ifa.categorynotinskipcategories:res.setdefault(a.category,[]).append(a.__class__)returnresdefaction_submenu(self,req,rset,id):returnself._test_action(self.vreg['actions'].select(id,req,rset=rset))def_test_action(self,action):classfake_menu(list):@propertydefitems(self):returnselfclassfake_box(object):defmk_action(self,label,url,**kwargs):return(label,url)defbox_action(self,action,**kwargs):return(action.title,action.url())submenu=fake_menu()action.fill_menu(fake_box(),submenu)returnsubmenudeflist_views_for(self,rset):"""returns the list of views that can be applied on `rset`"""req=rset.reqonly_once_vids=('primary','secondary','text')req.data['ex']=ValueError("whatever")viewsvreg=self.vreg['views']forvid,viewsinviewsvreg.items():ifvid[0]=='_':continueifrset.rowcount>1andvidinonly_once_vids:continueviews=[viewforviewinviewsifview.category!='startupview'andnotissubclass(view,notification.NotificationView)]ifviews:try:view=viewsvreg._select_best(views,req,rset=rset)ifview.linkable():yieldviewelse:not_selected(self.vreg,view)# else the view is expected to be used as subview and should# not be tested directlyexceptNoSelectableObject:continuedeflist_actions_for(self,rset):"""returns the list of actions that can be applied on `rset`"""req=rset.reqforactioninself.vreg['actions'].possible_objects(req,rset=rset):yieldactiondeflist_boxes_for(self,rset):"""returns the list of boxes that can be applied on `rset`"""req=rset.reqforboxinself.vreg['boxes'].possible_objects(req,rset=rset):yieldboxdeflist_startup_views(self):"""returns the list of startup views"""req=self.request()forviewinself.vreg['views'].possible_views(req,None):ifview.category=='startupview':yieldview.__regid__else:not_selected(self.vreg,view)# web ui testing utilities #################################################@property@cacheddefapp(self):"""return a cubicweb publisher"""publisher=application.CubicWebPublisher(self.config,vreg=self.vreg)defraise_error_handler(*args,**kwargs):raisepublisher.error_handler=raise_error_handlerreturnpublisherrequestcls=fake.FakeRequestdefrequest(self,*args,**kwargs):"""return a web ui request"""req=self.requestcls(self.vreg,form=kwargs)req.set_session(self.websession)returnreqdefremote_call(self,fname,*args):"""remote json call simulation"""dump=json.dumpsargs=[dump(arg)forarginargs]req=self.request(fname=fname,pageid='123',arg=args)ctrl=self.vreg['controllers'].select('json',req)returnctrl.publish(),reqdefapp_publish(self,req,path='view'):returnself.app.publish(path,req)defctrl_publish(self,req,ctrl='edit'):"""call the publish method of the edit controller"""ctrl=self.vreg['controllers'].select(ctrl,req)try:result=ctrl.publish()req.cnx.commit()exceptweb.Redirect:req.cnx.commit()raisereturnresultdefexpect_redirect(self,callback,req):"""call the given callback with req as argument, expecting to get a Redirect exception """try:callback(req)exceptRedirect,ex:try:path,params=ex.location.split('?',1)exceptValueError:path=ex.locationparams={}else:cleanup=lambdap:(p[0],unquote(p[1]))params=dict(cleanup(p.split('=',1))forpinparams.split('&')ifp)ifpath.startswith(req.base_url()):# may be relativepath=path[len(req.base_url()):]returnpath,paramselse:self.fail('expected a Redirect exception')defexpect_redirect_publish(self,req,path='edit'):"""call the publish method of the application publisher, expecting to get a Redirect exception """returnself.expect_redirect(lambdax:self.app_publish(x,path),req)definit_authentication(self,authmode,anonuser=None):self.set_option('auth-mode',authmode)self.set_option('anonymous-user',anonuser)req=self.request()origsession=req.sessionreq.session=req.cnx=Nonedelreq.execute# get back to class implementationsh=self.app.session_handlerauthm=sh.session_manager.authmanagerauthm.anoninfo=self.vreg.config.anonymous_user()authm.anoninfo=authm.anoninfo[0],{'password':authm.anoninfo[1]}# not properly cleaned between testsself.open_sessions=sh.session_manager._sessions={}returnreq,origsessiondefassertAuthSuccess(self,req,origsession,nbsessions=1):sh=self.app.session_handlerpath,params=self.expect_redirect(lambdax:self.app.connect(x),req)session=req.sessionself.assertEquals(len(self.open_sessions),nbsessions,self.open_sessions)self.assertEquals(session.login,origsession.login)self.assertEquals(session.anonymous_session,False)self.assertEquals(path,'view')self.assertEquals(params,{'__message':'welcome %s !'%req.user.login})defassertAuthFailure(self,req,nbsessions=0):self.app.connect(req)self.assertIsInstance(req.session,DBAPISession)self.assertEquals(req.session.cnx,None)self.assertEquals(req.cnx,None)self.assertEquals(len(self.open_sessions),nbsessions)clear_cache(req,'get_authorization')# content validation ######################################################## validators are used to validate (XML, DTD, whatever) view's content# validators availables are :# DTDValidator : validates XML + declared DTD# SaxOnlyValidator : guarantees XML is well formed# None : do not try to validate anything# validators used must be imported from from.devtools.htmlparsercontent_type_validators={# maps MIME type : validator name## do not set html validators here, we need HTMLValidator for html# snippets#'text/html': DTDValidator,#'application/xhtml+xml': DTDValidator,'application/xml':htmlparser.SaxOnlyValidator,'text/xml':htmlparser.SaxOnlyValidator,'text/plain':None,'text/comma-separated-values':None,'text/x-vcard':None,'text/calendar':None,'application/json':None,'image/png':None,}# maps vid : validator name (override content_type_validators)vid_validators=dict((vid,htmlparser.VALMAP[valkey])forvid,valkeyinVIEW_VALIDATORS.iteritems())defview(self,vid,rset=None,req=None,template='main-template',**kwargs):"""This method tests the view `vid` on `rset` using `template` If no error occured while rendering the view, the HTML is analyzed and parsed. :returns: an instance of `cubicweb.devtools.htmlparser.PageInfo` encapsulation the generated HTML """req=reqorrsetandrset.reqorself.request()req.form['vid']=vidkwargs['rset']=rsetviewsreg=self.vreg['views']view=viewsreg.select(vid,req,**kwargs)# set explicit test descriptionifrsetisnotNone:self.set_description("testing vid=%s defined in %s with (%s)"%(vid,view.__module__,rset.printable_rql()))else:self.set_description("testing vid=%s defined in %s without rset"%(vid,view.__module__))iftemplateisNone:# raw view testing, no templateviewfunc=view.renderelse:kwargs['view']=viewtemplateview=viewsreg.select(template,req,**kwargs)viewfunc=lambda**k:viewsreg.main_template(req,template,**kwargs)kwargs.pop('rset')returnself._test_view(viewfunc,view,template,kwargs)def_test_view(self,viewfunc,view,template='main-template',kwargs={}):"""this method does the actual call to the view If no error occured while rendering the view, the HTML is analyzed and parsed. :returns: an instance of `cubicweb.devtools.htmlparser.PageInfo` encapsulation the generated HTML """output=Nonetry:output=viewfunc(**kwargs)returnself._check_html(output,view,template)except(SystemExit,KeyboardInterrupt):raiseexcept:# hijack exception: generative tests stop when the exception# is not an AssertionErrorklass,exc,tcbk=sys.exc_info()try:msg='[%s in %s] %s'%(klass,view.__regid__,exc)except:msg='[%s in %s] undisplayable exception'%(klass,view.__regid__)ifoutputisnotNone:position=getattr(exc,"position",(0,))[0]ifposition:# define filteroutput=output.splitlines()width=int(log(len(output),10))+1line_template=" %"+("%i"%width)+"i: %s"# XXX no need to iterate the whole file except to get# the line numberoutput='\n'.join(line_template%(idx+1,line)foridx,lineinenumerate(output)ifline_context_filter(idx+1,position))msg+='\nfor output:\n%s'%outputraiseAssertionError,msg,tcbk@nocoveragedef_check_html(self,output,view,template='main-template'):"""raises an exception if the HTML is invalid"""try:validatorclass=self.vid_validators[view.__regid__]exceptKeyError:ifview.content_typein('text/html','application/xhtml+xml'):iftemplateisNone:default_validator=htmlparser.HTMLValidatorelse:default_validator=htmlparser.DTDValidatorelse:default_validator=Nonevalidatorclass=self.content_type_validators.get(view.content_type,default_validator)ifvalidatorclassisNone:returnNonevalidator=validatorclass()ifisinstance(validator,htmlparser.DTDValidator):# XXX remove <canvas> used in progress widget, unknown in html dtdoutput=re.sub('<canvas.*?></canvas>','',output)returnvalidator.parse_string(output.strip())# deprecated ###############################################################@deprecated('[3.8] use self.execute(...).get_entity(0, 0)')defentity(self,rql,args=None,eidkey=None,req=None):ifeidkeyisnotNone:warn('[3.8] eidkey is deprecated, you can safely remove this argument',DeprecationWarning,stacklevel=2)returnself.execute(rql,args,req=req).get_entity(0,0)@deprecated('[3.6] use self.request().create_entity(...)')defadd_entity(self,etype,req=None,**kwargs):ifreqisNone:req=self.request()returnreq.create_entity(etype,**kwargs)# auto-populating test classes and utilities ###################################fromcubicweb.devtools.fillimportinsert_entity_queries,make_relations_queries# XXX cleanup unprotected_entities & all messdefhow_many_dict(schema,cursor,how_many,skip):"""given a schema, compute how many entities by type we need to be able to satisfy relations cardinality. The `how_many` argument tells how many entities of which type we want at least. Return a dictionary with entity types as key, and the number of entities for this type as value. """relmap={}forrschemainschema.relations():ifrschema.final:continueforsubj,objinrschema.rdefs:card=rschema.rdef(subj,obj).cardinality# if the relation is mandatory, we'll need at least as many subj and# obj to satisfy itifcard[0]in'1+'andcard[1]in'1?':# subj has to be linked to at least one obj,# but obj can be linked to only one subj# -> we need at least as many subj as obj to satisfy# cardinalities for this relationrelmap.setdefault((rschema,subj),[]).append(str(obj))ifcard[1]in'1+'andcard[0]in'1?':# reverse subj and obj in the above explanationrelmap.setdefault((rschema,obj),[]).append(str(subj))unprotected=unprotected_entities(schema)foretypeinskip:# XXX (syt) duh? explain or killunprotected.add(etype)howmanydict={}# step 1, compute a base number of each entity types: number of already# existing entities of this type + `how_many`foretypeinunprotected_entities(schema,strict=True):howmanydict[str(etype)]=cursor.execute('Any COUNT(X) WHERE X is %s'%etype)[0][0]ifetypeinunprotected:howmanydict[str(etype)]+=how_many# step 2, augment nb entity per types to satisfy cardinality constraints,# by recomputing for each relation that constrained an entity type:## new num for etype = max(current num, sum(num for possible target etypes))## XXX we should first check there is no cycle then propagate changesfor(rschema,etype),targetsinrelmap.iteritems():relfactor=sum(howmanydict[e]foreintargets)howmanydict[str(etype)]=max(relfactor,howmanydict[etype])returnhowmanydictclassAutoPopulateTest(CubicWebTC):"""base class for test with auto-populating of the database"""__abstract__=Truetags=CubicWebTC.tags|Tags('autopopulated')pdbclass=CubicWebDebugger# this is a hook to be able to define a list of rql queries# that are application dependent and cannot be guessed automaticallyapplication_rql=[]no_auto_populate=()ignored_relations=set()defto_test_etypes(self):returnunprotected_entities(self.schema,strict=True)defcustom_populate(self,how_many,cursor):passdefpost_populate(self,cursor):pass@nocoveragedefauto_populate(self,how_many):"""this method populates the database with `how_many` entities of each possible type. It also inserts random relations between them """withsecurity_enabled(self.session,read=False,write=False):self._auto_populate(how_many)def_auto_populate(self,how_many):cu=self.cursor()self.custom_populate(how_many,cu)vreg=self.vreghowmanydict=how_many_dict(self.schema,cu,how_many,self.no_auto_populate)foretypeinunprotected_entities(self.schema):ifetypeinself.no_auto_populate:continuenb=howmanydict.get(etype,how_many)forrql,argsininsert_entity_queries(etype,self.schema,vreg,nb):cu.execute(rql,args)edict={}foretypeinunprotected_entities(self.schema,strict=True):rset=cu.execute('%s X'%etype)edict[str(etype)]=set(row[0]forrowinrset.rows)existingrels={}ignored_relations=SYSTEM_RELATIONS|self.ignored_relationsforrschemainself.schema.relations():ifrschema.finalorrschemainignored_relations:continuerset=cu.execute('DISTINCT Any X,Y WHERE X %s Y'%rschema)existingrels.setdefault(rschema.type,set()).update((x,y)forx,yinrset)q=make_relations_queries(self.schema,edict,cu,ignored_relations,existingrels=existingrels)forrql,argsinq:try:cu.execute(rql,args)exceptValidationError,ex:# failed to satisfy some constraintprint'error in automatic db population',exself.post_populate(cu)self.commit()defiter_individual_rsets(self,etypes=None,limit=None):etypes=etypesorself.to_test_etypes()foretypeinetypes:iflimit:rql='Any X LIMIT %s WHERE X is %s'%(limit,etype)else:rql='Any X WHERE X is %s'%etyperset=self.execute(rql)forrowinxrange(len(rset)):iflimitandrow>limit:break# XXX iirkrset2=rset.limit(limit=1,offset=row)yieldrset2defiter_automatic_rsets(self,limit=10):"""generates basic resultsets for each entity type"""etypes=self.to_test_etypes()ifnotetypes:returnforetypeinetypes:yieldself.execute('Any X LIMIT %s WHERE X is %s'%(limit,etype))etype1=etypes.pop()try:etype2=etypes.pop()exceptKeyError:etype2=etype1# test a mixed query (DISTINCT/GROUP to avoid getting duplicate# X which make muledit view failing for instance (html validation fails# because of some duplicate "id" attributes)yieldself.execute('DISTINCT Any X, MAX(Y) GROUPBY X WHERE X is %s, Y is %s'%(etype1,etype2))# test some application-specific queries if definedforrqlinself.application_rql:yieldself.execute(rql)def_test_everything_for(self,rset):"""this method tries to find everything that can be tested for `rset` and yields a callable test (as needed in generative tests) """propdefs=self.vreg['propertydefs']# make all components visiblefork,vinpropdefs.items():ifk.endswith('visible')andnotv['default']:propdefs[k]['default']=Trueforviewinself.list_views_for(rset):backup_rset=rset.copy(rset.rows,rset.description)yieldInnerTest(self._testname(rset,view.__regid__,'view'),self.view,view.__regid__,rset,rset.req.reset_headers(),'main-template')# We have to do this because some views modify the# resultset's syntax treerset=backup_rsetforactioninself.list_actions_for(rset):yieldInnerTest(self._testname(rset,action.__regid__,'action'),self._test_action,action)forboxinself.list_boxes_for(rset):yieldInnerTest(self._testname(rset,box.__regid__,'box'),box.render)@staticmethoddef_testname(rset,objid,objtype):return'%s_%s_%s'%('_'.join(rset.column_types(0)),objid,objtype)# concrete class for automated application testing ############################classAutomaticWebTest(AutoPopulateTest):"""import this if you wan automatic tests to be ran"""tags=AutoPopulateTest.tags|Tags('web','generated')defsetUp(self):AutoPopulateTest.setUp(self)# access to self.app for proper initialization of the authentication# machinery (else some views may fail)self.app## one eachdeftest_one_each_config(self):self.auto_populate(1)forrsetinself.iter_automatic_rsets(limit=1):fortestargsinself._test_everything_for(rset):yieldtestargs## ten eachdeftest_ten_each_config(self):self.auto_populate(10)forrsetinself.iter_automatic_rsets(limit=10):fortestargsinself._test_everything_for(rset):yieldtestargs## startup viewsdeftest_startup_views(self):forvidinself.list_startup_views():req=self.request()yieldself.view,vid,None,req# registry instrumentization ###################################################defnot_selected(vreg,appobject):try:vreg._selected[appobject.__class__]-=1except(KeyError,AttributeError):passdefvreg_instrumentize(testclass):# XXX brokenfromcubicweb.devtools.apptestimportTestEnvironmentenv=testclass._env=TestEnvironment('data',configcls=testclass.configcls)forreginenv.vreg.values():reg._selected={}try:orig_select_best=reg.__class__.__orig_select_bestexcept:orig_select_best=reg.__class__._select_bestdefinstr_select_best(self,*args,**kwargs):selected=orig_select_best(self,*args,**kwargs)try:self._selected[selected.__class__]+=1exceptKeyError:self._selected[selected.__class__]=1exceptAttributeError:pass# occurs on reg used to restore databasereturnselectedreg.__class__._select_best=instr_select_bestreg.__class__.__orig_select_best=orig_select_bestdefprint_untested_objects(testclass,skipregs=('hooks','etypes')):forregname,regintestclass._env.vreg.iteritems():ifregnameinskipregs:continueforappobjectsinreg.itervalues():forappobjectinappobjects:ifnotreg._selected.get(appobject):print'not tested',regname,appobject