# copyright 2003-2011 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/>."""some utilities to ease repository testingThis module contains functions to initialize a new repository."""__docformat__="restructuredtext en"fromcopyimportdeepcopyfrompprintimportpprintfromlogilab.common.decoratorsimportclear_cachefromlogilab.common.testlibimportSkipTestdeftuplify(list):foriinrange(len(list)):iftype(list[i])isnottype(()):list[i]=tuple(list[i])returnlistdefsnippet_cmp(a,b):a=(a[0],[e.expressionforeina[1]])b=(b[0],[e.expressionforeinb[1]])returncmp(a,b)deftest_plan(self,rql,expected,kwargs=None):plan=self._prepare_plan(rql,kwargs)self.planner.build_plan(plan)try:self.assertEqual(len(plan.steps),len(expected),'expected %s steps, got %s'%(len(expected),len(plan.steps)))# step order is importantfori,stepinenumerate(plan.steps):compare_steps(self,step.test_repr(),expected[i])exceptAssertionError:pprint([step.test_repr()forstepinplan.steps])raisedefcompare_steps(self,step,expected):try:self.assertEqual(step[0],expected[0],'expected step type %s, got %s'%(expected[0],step[0]))iflen(step)>2andisinstance(step[1],list)andisinstance(expected[1],list):queries,equeries=step[1],expected[1]self.assertEqual(len(queries),len(equeries),'expected %s queries, got %s'%(len(equeries),len(queries)))fori,(rql,sol)inenumerate(queries):self.assertEqual(rql,equeries[i][0])self.assertEqual(sorted(sol),sorted(equeries[i][1]))idx=2else:idx=1self.assertEqual(step[idx:-1],expected[idx:-1],'expected step characteristic \n%s\n, got\n%s'%(expected[1:-1],step[1:-1]))self.assertEqual(len(step[-1]),len(expected[-1]),'got %s child steps, expected %s'%(len(step[-1]),len(expected[-1])))exceptAssertionError:print'error on step ',pprint(step[:-1])raisechildren=step[-1]ifstep[0]in('UnionFetchStep','UnionStep'):# sort childrenchildren=sorted(children)expectedchildren=sorted(expected[-1])else:expectedchildren=expected[-1]fori,substepinenumerate(children):compare_steps(self,substep,expectedchildren[i])classDumbOrderedDict(list):def__iter__(self):returnself.iterkeys()def__contains__(self,key):returnkeyinself.iterkeys()def__getitem__(self,key):forkey_,valueinlist.__iter__(self):ifkey==key_:returnvalueraiseKeyError(key)defiterkeys(self):return(xforx,yinlist.__iter__(self))defiteritems(self):return(xforxinlist.__iter__(self))defitems(self):return[xforxinlist.__iter__(self)]classDumbOrderedDict2(object):def__init__(self,origdict,sortkey):self.origdict=origdictself.sortkey=sortkeydef__getattr__(self,attr):returngetattr(self.origdict,attr)def__iter__(self):returniter(sorted(self.origdict,key=self.sortkey))defschema_eids_idx(schema):"""return a dictionary mapping schema types to their eids so we can reread it from the fs instead of the db (too costly) between tests """schema_eids={}forxinschema.entities():schema_eids[x]=x.eidforxinschema.relations():schema_eids[x]=x.eidforrdefinx.rdefs.itervalues():schema_eids[(rdef.subject,rdef.rtype,rdef.object)]=rdef.eidreturnschema_eidsdefrestore_schema_eids_idx(schema,schema_eids):"""rebuild schema eid index"""forxinschema.entities():x.eid=schema_eids[x]schema._eid_index[x.eid]=xforxinschema.relations():x.eid=schema_eids[x]schema._eid_index[x.eid]=xforrdefinx.rdefs.itervalues():rdef.eid=schema_eids[(rdef.subject,rdef.rtype,rdef.object)]schema._eid_index[rdef.eid]=rdeffromlogilab.common.testlibimportTestCase,mock_objectfromlogilab.databaseimportget_db_helperfromrqlimportRQLHelperfromcubicweb.devtools.fakeimportFakeRepo,FakeSessionfromcubicweb.serverimportset_debug,debuggedfromcubicweb.server.querierimportQuerierHelperfromcubicweb.server.sessionimportSessionfromcubicweb.server.sources.rql2sqlimportSQLGenerator,remove_unused_solutionsclassRQLGeneratorTC(TestCase):schema=backend=None# set this in concrete class@classmethoddefsetUpClass(cls):ifcls.backendisnotNone:try:cls.dbhelper=get_db_helper(cls.backend)exceptImportError,ex:raiseSkipTest(str(ex))defsetUp(self):self.repo=FakeRepo(self.schema)self.repo.system_source=mock_object(dbdriver=self.backend)self.rqlhelper=RQLHelper(self.schema,special_relations={'eid':'uid','has_text':'fti'},backend=self.backend)self.qhelper=QuerierHelper(self.repo,self.schema)ExecutionPlan._check_permissions=_dummy_check_permissionsrqlannotation._select_principal=_select_principalifself.backendisnotNone:self.o=SQLGenerator(self.schema,self.dbhelper)deftearDown(self):ExecutionPlan._check_permissions=_orig_check_permissionsrqlannotation._select_principal=_orig_select_principaldefset_debug(self,debug):set_debug(debug)defdebugged(self,debug):returndebugged(debug)def_prepare(self,rql):#print '******************** prepare', rqlunion=self.rqlhelper.parse(rql)#print '********* parsed', union.as_string()self.rqlhelper.compute_solutions(union)#print '********* solutions', solutionsself.rqlhelper.simplify(union)#print '********* simplified', union.as_string()plan=self.qhelper.plan_factory(union,{},FakeSession(self.repo))plan.preprocess(union)forselectinunion.children:select.solutions.sort()#print '********* ppsolutions', solutionsreturnunionclassBaseQuerierTC(TestCase):repo=None# set this in concrete classdefsetUp(self):self.o=self.repo.querierself.session=self.repo._sessions.values()[0]self.ueid=self.session.user.eidassertself.ueid!=-1self.repo._type_source_cache={}# clear cacheself.cnxset=self.session.set_cnxset()self.maxeid=self.get_max_eid()do_monkey_patch()self._dumb_sessions=[]defget_max_eid(self):returnself.session.execute('Any MAX(X)')[0][0]defcleanup(self):self.session.set_cnxset()self.session.execute('DELETE Any X WHERE X eid > %s'%self.maxeid)deftearDown(self):undo_monkey_patch()self.session.rollback()self.cleanup()self.commit()# properly close dumb sessionsforsessioninself._dumb_sessions:session.rollback()session.close()self.repo._free_cnxset(self.cnxset)assertself.session.user.eid!=-1defset_debug(self,debug):set_debug(debug)defdebugged(self,debug):returndebugged(debug)def_rqlhelper(self):rqlhelper=self.repo.vreg.rqlhelper# reset uid_func so it don't try to get type from eidsrqlhelper._analyser.uid_func=Nonerqlhelper._analyser.uid_func_mapping={}returnrqlhelperdef_prepare_plan(self,rql,kwargs=None,simplify=True):rqlhelper=self._rqlhelper()rqlst=rqlhelper.parse(rql)rqlhelper.compute_solutions(rqlst,kwargs=kwargs)ifsimplify:rqlhelper.simplify(rqlst)forselectinrqlst.children:select.solutions.sort()returnself.o.plan_factory(rqlst,kwargs,self.session)def_prepare(self,rql,kwargs=None):plan=self._prepare_plan(rql,kwargs,simplify=False)plan.preprocess(plan.rqlst)rqlst=plan.rqlst.children[0]rqlst.solutions=remove_unused_solutions(rqlst,rqlst.solutions,{},self.repo.schema)[0]returnrqlstdefuser_groups_session(self,*groups):"""lightweight session using the current user with hi-jacked groups"""# use self.session.user.eid to get correct owned_by relation, unless explicit eidu=self.repo._build_user(self.session,self.session.user.eid)u._groups=set(groups)s=Session(u,self.repo)s._threaddata.cnxset=self.cnxsets._threaddata.ctx_count=1# register session to ensure it gets closedself._dumb_sessions.append(s)returnsdefexecute(self,rql,args=None,build_descr=True):returnself.o.execute(self.session,rql,args,build_descr)defcommit(self):self.session.commit()self.session.set_cnxset()classBasePlannerTC(BaseQuerierTC):newsources=()defsetup(self):clear_cache(self.repo,'rel_type_sources')clear_cache(self.repo,'rel_type_sources')clear_cache(self.repo,'can_cross_relation')clear_cache(self.repo,'is_multi_sources_relation')# XXX source_defsself.o=self.repo.querierself.session=self.repo._sessions.values()[0]self.cnxset=self.session.set_cnxset()self.schema=self.o.schemaself.sources=self.o._repo.sourcesself.system=self.sources[-1]do_monkey_patch()self._dumb_sessions=[]# by hi-jacked parent setupself.repo.vreg.rqlhelper.backend='postgres'# so FTIRANK is consideredself.newsources=[]defadd_source(self,sourcecls,uri):source=sourcecls(self.repo,{'uri':uri,'type':'whatever'})ifnotsource.copy_based_source:self.sources.append(source)self.newsources.append(source)self.repo.sources_by_uri[uri]=sourcesetattr(self,uri,source)deftearDown(self):forsourceinself.newsources:ifnotsource.copy_based_source:self.sources.remove(source)delself.repo.sources_by_uri[source.uri]undo_monkey_patch()forsessioninself._dumb_sessions:session._threaddata.cnxset=Nonesession.close()def_prepare_plan(self,rql,kwargs=None):rqlst=self.o.parse(rql,annotate=True)self.o.solutions(self.session,rqlst,kwargs)ifrqlst.TYPE=='select':self.repo.vreg.rqlhelper.annotate(rqlst)forselectinrqlst.children:select.solutions.sort()else:rqlst.solutions.sort()returnself.o.plan_factory(rqlst,kwargs,self.session)# monkey patch some methods to get predicatable results #######################fromcubicwebimportrqlrewrite_orig_iter_relations=rqlrewrite.iter_relations_orig_insert_snippets=rqlrewrite.RQLRewriter.insert_snippets_orig_build_variantes=rqlrewrite.RQLRewriter.build_variantesdef_insert_snippets(self,snippets,varexistsmap=None):_orig_insert_snippets(self,sorted(snippets,snippet_cmp),varexistsmap)def_build_variantes(self,newsolutions):variantes=_orig_build_variantes(self,newsolutions)sortedvariantes=[]forvarianteinvariantes:orderedkeys=sorted((k[1],k[2],v)fork,vinvariante.iteritems())variante=DumbOrderedDict(sorted(variante.iteritems(),lambdaa,b:cmp((a[0][1],a[0][2],a[1]),(b[0][1],b[0][2],b[1]))))sortedvariantes.append((orderedkeys,variante))return[vforok,vinsorted(sortedvariantes)]fromcubicweb.server.querierimportExecutionPlan_orig_check_permissions=ExecutionPlan._check_permissions_orig_init_temp_table=ExecutionPlan.init_temp_tabledef_check_permissions(*args,**kwargs):res,restricted=_orig_check_permissions(*args,**kwargs)res=DumbOrderedDict(sorted(res.iteritems(),lambdaa,b:cmp(a[1],b[1])))returnres,restricteddef_dummy_check_permissions(self,rqlst):return{():rqlst.solutions},set()def_init_temp_table(self,table,selection,solution):ifself.tablesinorderisNone:tablesinorder=self.tablesinorder={}else:tablesinorder=self.tablesinorderifnottableintablesinorder:tablesinorder[table]='table%s'%len(tablesinorder)return_orig_init_temp_table(self,table,selection,solution)fromcubicweb.serverimportrqlannotation_orig_select_principal=rqlannotation._select_principaldef_select_principal(scope,relations):defsort_key(something):try:returnsomething.r_typeexceptAttributeError:return(something[0].r_type,something[1])return_orig_select_principal(scope,relations,_sort=lambdarels:sorted(rels,key=sort_key))try:fromcubicweb.server.msplannerimportPartPlanInformationexceptImportError:classPartPlanInformation(object):defmerge_input_maps(self,*args,**kwargs):passdef_choose_term(self,sourceterms):pass_orig_merge_input_maps=PartPlanInformation.merge_input_maps_orig_choose_term=PartPlanInformation._choose_termdef_merge_input_maps(*args,**kwargs):returnsorted(_orig_merge_input_maps(*args,**kwargs))def_choose_term(self,source,sourceterms):# predictable order for test purposedefget_key(x):try:# variablereturnx.nameexceptAttributeError:try:# relationreturnx.r_typeexceptAttributeError:# constreturnx.valuereturn_orig_choose_term(self,source,DumbOrderedDict2(sourceterms,get_key))fromcubicweb.server.sources.pyrorqlimportPyroRQLSource_orig_syntax_tree_search=PyroRQLSource.syntax_tree_searchdef_syntax_tree_search(*args,**kwargs):returndeepcopy(_orig_syntax_tree_search(*args,**kwargs))def_ordered_iter_relations(stinfo):returnsorted(_orig_iter_relations(stinfo),key=lambdax:x.r_type)defdo_monkey_patch():rqlrewrite.iter_relations=_ordered_iter_relationsrqlrewrite.RQLRewriter.insert_snippets=_insert_snippetsrqlrewrite.RQLRewriter.build_variantes=_build_variantesExecutionPlan._check_permissions=_check_permissionsExecutionPlan.tablesinorder=NoneExecutionPlan.init_temp_table=_init_temp_tablePartPlanInformation.merge_input_maps=_merge_input_mapsPartPlanInformation._choose_term=_choose_termPyroRQLSource.syntax_tree_search=_syntax_tree_searchdefundo_monkey_patch():rqlrewrite.iter_relations=_orig_iter_relationsrqlrewrite.RQLRewriter.insert_snippets=_orig_insert_snippetsrqlrewrite.RQLRewriter.build_variantes=_orig_build_variantesExecutionPlan._check_permissions=_orig_check_permissionsExecutionPlan.init_temp_table=_orig_init_temp_tablePartPlanInformation.merge_input_maps=_orig_merge_input_mapsPartPlanInformation._choose_term=_orig_choose_termPyroRQLSource.syntax_tree_search=_orig_syntax_tree_search