AdaptedList -> SameETypeList
*NO BW COMPAT*, benefit from cw 3.6 releasing of folder,file and blog
which use it to get update at the same time.
CMHN and PEGASE will need update (but won't go to 3.6 without update,
so seem fine).
"""this module contains base classes and utilities for cubicweb tests:organization: Logilab:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""__docformat__="restructuredtext en"importosimportsysimportrefromurllibimportunquotefrommathimportlogimportsimplejsonimportyams.schemafromlogilab.common.testlibimportTestCase,InnerTestfromlogilab.common.pytestimportnocoverage,pause_tracing,resume_tracingfromlogilab.common.debuggerimportDebuggerfromlogilab.common.umessageimportmessage_from_stringfromlogilab.common.decoratorsimportcached,classproperty,clear_cachefromlogilab.common.deprecationimportdeprecatedfromcubicwebimportValidationError,NoSelectableObject,AuthenticationErrorfromcubicwebimportcwconfig,devtools,web,serverfromcubicweb.dbapiimportrepo_connect,ConnectionProperties,ProgrammingErrorfromcubicweb.sobjectsimportnotificationfromcubicweb.webimportRedirect,applicationfromcubicweb.devtoolsimportSYSTEM_ENTITIES,SYSTEM_RELATIONS,VIEW_VALIDATORSfromcubicweb.devtoolsimportfake,htmlparser# 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_entitiesdefget_versions(self,checkversions=False):"""return the a dictionary containing cubes used by this instance as key with their version as value, including cubicweb version. This is a public method, not requiring a session id. replace Repository.get_versions by this method if you don't want versions checking """vcconf={'cubicweb':self.config.cubicweb_version()}self.config.bootstrap_cubes()forpkinself.config.cubes():version=self.config.cube_version(pk)vcconf[pk]=versionself.config._cubes=Nonereturnvcconfdefrefresh_repo(repo):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()# 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.ApptestConfiguration@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:cls.cnx.rollback()cls._refresh_repo()@classmethoddef_build_repo(cls):cls.repo,cls.cnx=devtools.init_test_database(config=cls.config)cls.init_config(cls.config)cls.vreg=cls.repo.vregcls._orig_cnx=cls.cnxcls.config.repository=lambdax=None:cls.repo# necessary for authentication testscls.cnx.login=cls.admlogincls.cnx.authinfo={'password':cls.admpassword}@classmethoddef_refresh_repo(cls):refresh_repo(cls.repo)# global resources accessors ###############################################@propertydefschema(self):"""return the application schema"""returnself.vreg.schema@propertydefsession(self):"""return current server side session (using default manager account)"""returnself.repo._sessions[self.cnx.sessionid]@propertydefadminsession(self):"""return current server side session (using default manager account)"""returnself.repo._sessions[self._orig_cnx.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()self._init_repo()resume_tracing()self.setup_database()self.commit()MAILBOX[:]=[]# reset mailboxdefsetup_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):"""create and return a new user entity"""ifpasswordisNone:password=login.encode('utf8')cursor=self._orig_cnx.cursor(reqorself.request())rset=cursor.execute('INSERT CWUser X: X login %(login)s, X upassword %(passwd)s',{'login':unicode(login),'passwd':password})user=rset.get_entity(0,0)cursor.execute('SET X in_group G WHERE X eid %%(x)s, G name IN(%s)'%','.join(repr(g)forgingroups),{'x':user.eid},'x')user.clear_related_cache('in_group','subject')ifcommit:self._orig_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),cnxprops=ConnectionProperties('inmemory'),**kwargs)iflogin==self.vreg.config.anonymous_user()[0]:self.cnx.anonymous_connection=Truereturnself.cnxdefrestore_connection(self):ifnotself.cnxisself._orig_cnx:try:self.cnx.close()exceptProgrammingError:pass# already closedself.cnx=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 """req=reqorself.request(rql=rql)returnself.cnx.cursor(req).execute(unicode(rql),args,eidkey)@nocoveragedefcommit(self):self.cnx.commit()@nocoveragedefrollback(self):try:self.cnx.rollback()exceptProgrammingError:pass# # server side db api #######################################################defsexecute(self,rql,args=None,eid_key=None):self.session.set_pool()returnself.session.execute(rql,args,eid_key)# other utilities #########################################################defentity(self,rql,args=None,eidkey=None,req=None):returnself.execute(rql,args,eidkey,req=req).get_entity(0,0)# 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_connection(self.cnx)returnreqdefremote_call(self,fname,*args):"""remote json call simulation"""dump=simplejson.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):"""call the publish method of the edit controller"""ctrl=self.vreg['controllers'].select('edit',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:res=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)path=path[len(req.base_url()):]returnpath,paramselse:self.fail('expected a Redirect exception')defexpect_redirect_publish(self,req,path='view'):"""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()origcnx=req.cnxreq.cnx=Nonesh=self.app.session_handlerauthm=sh.session_manager.authmanagerauthm.authinforetreivers[-1].anoninfo=self.vreg.config.anonymous_user()# not properly cleaned between testsself.open_sessions=sh.session_manager._sessions={}returnreq,origcnxdefassertAuthSuccess(self,req,origcnx,nbsessions=1):sh=self.app.session_handlerpath,params=self.expect_redirect(lambdax:self.app.connect(x),req)cnx=req.cnxself.assertEquals(len(self.open_sessions),nbsessions,self.open_sessions)self.assertEquals(cnx.login,origcnx.login)self.assertEquals(cnx.anonymous_connection,False)self.assertEquals(path,'view')self.assertEquals(params,{'__message':'welcome %s !'%cnx.user().login})defassertAuthFailure(self,req,nbsessions=0):self.assertRaises(AuthenticationError,self.app.connect,req)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 %s, mod=%s (%s)"%(vid,view.__module__,rset.printable_rql()))else:self.set_description("testing %s, mod=%s (no 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.6] use self.request().create_entity(...)')defadd_entity(self,etype,req=None,**kwargs):ifreqisNone:req=self.request()returnreq.create_entity(etype,**kwargs)@deprecated('[3.4] use self.vreg["etypes"].etype_class(etype)(self.request())')defetype_instance(self,etype,req=None):req=reqorself.request()e=self.vreg['etypes'].etype_class(etype)(req)e.eid=Nonereturne@nocoverage@deprecated('[3.4] use req = self.request(); rset = req.execute()',stacklevel=3)defrset_and_req(self,rql,optional_args=None,args=None,eidkey=None):"""executes <rql>, builds a resultset, and returns a couple (rset, req) where req is a FakeRequest """return(self.execute(rql,args,eidkey),self.request(rql=rql,**optional_argsor{}))# auto-populating test classes and utilities ###################################fromcubicweb.devtools.fillimportinsert_entity_queries,make_relations_queriesdefhow_many_dict(schema,cursor,how_many,skip):"""compute how many entities by type we need to be able to satisfy relations cardinality """# compute how many entities by type we need to be able to satisfy relation constraintrelmap={}forrschemainschema.relations():ifrschema.final:continueforsubj,objinrschema.rdefs:card=rschema.rdef(subj,obj).cardinalityifcard[0]in'1?'andlen(rschema.subjects(obj))==1:relmap.setdefault((rschema,subj),[]).append(str(obj))ifcard[1]in'1?'andlen(rschema.objects(subj))==1:relmap.setdefault((rschema,obj),[]).append(str(subj))unprotected=unprotected_entities(schema)foretypeinskip:unprotected.add(etype)howmanydict={}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_manyfor(rschema,etype),targetsinrelmap.iteritems():# XXX should 1. check no cycle 2. propagate changesrelfactor=sum(howmanydict[e]foreintargets)howmanydict[str(etype)]=max(relfactor,howmanydict[etype])returnhowmanydictclassAutoPopulateTest(CubicWebTC):"""base class for test with auto-populating of the database"""__abstract__=Truepdbclass=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 """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"""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