# copyright 2010-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/>.importos,os.pathasospfromtempfileimportmkdtemp,NamedTemporaryFile,TemporaryFileimporttempfilefromQueueimportQueue,EmptyfromsubprocessimportPopen,check_call,CalledProcessErrorfromshutilimportrmtree,copyascopyfilefromuuidimportuuid4# imported by default to simplify further import statementsfromlogilab.common.testlibimportunittest_main,with_tempdir,InnerTest,Tagsfromlogilab.common.shellutilsimportgetloginimportcubicwebfromcubicweb.viewimportViewfromcubicweb.web.controllerimportControllerfromcubicweb.web.views.staticcontrollersimportStaticFileController,STATIC_CONTROLLERSfromcubicweb.devtools.httptestimportCubicWebServerTCclassVerboseCalledProcessError(CalledProcessError):def__init__(self,returncode,command,stdout,stderr):super(VerboseCalledProcessError,self).__init__(returncode,command)self.stdout=stdoutself.stderr=stderrdef__str__(self):str=[super(VerboseCalledProcessError,self).__str__()]ifself.stdout.strip():str.append('******************')str.append('* process stdout *')str.append('******************')str.append(self.stdout)ifself.stderr.strip():str.append('******************')str.append('* process stderr *')str.append('******************')str.append(self.stderr)return'\n'.join(str)classFirefoxHelper(object):profile_name_mask='PYTEST_PROFILE_%(uid)s'def__init__(self,url=None):self._process=Noneself._profile_dir=mkdtemp(prefix='cwtest-ffxprof-')self.firefox_cmd=['firefox','-no-remote']ifos.name=='posix':self.firefox_cmd=[osp.join(osp.dirname(__file__),'data','xvfb-run.sh'),'-a','-s','-noreset -screen 0 800x600x24']+self.firefox_cmddefstart(self,url):self.stop()cmd=self.firefox_cmd+['-silent','--profile',self._profile_dir,'-url',url]withopen(os.devnull,'w')asfnull:self._process=Popen(cmd,stdout=fnull,stderr=fnull)defstop(self):ifself._processisnotNone:assertself._process.returncodeisNone,self._process.returncodeself._process.terminate()self._process.wait()self._process=Nonedef__del__(self):self.stop()classQUnitTestCase(CubicWebServerTC):tags=CubicWebServerTC.tags|Tags(('qunit',))# testfile, (dep_a, dep_b)all_js_tests=()defsetUp(self):self.config.global_set_option('access-control-allow-origin','*')super(QUnitTestCase,self).setUp()self.test_queue=Queue()classMyQUnitResultController(QUnitResultController):tc=selftest_queue=self.test_queueself._qunit_controller=MyQUnitResultControllerself.vreg.register(MyQUnitResultController)self.vreg.register(QUnitView)self.vreg.register(CWSoftwareRootStaticController)deftearDown(self):super(QUnitTestCase,self).tearDown()self.vreg.unregister(self._qunit_controller)self.vreg.unregister(QUnitView)self.vreg.unregister(CWSoftwareRootStaticController)defabspath(self,path):"""use self.__module__ to build absolute path if necessary"""ifnotosp.isabs(path):dirname=osp.dirname(__import__(self.__module__).__file__)returnosp.abspath(osp.join(dirname,path))returnpathdeftest_javascripts(self):forargsinself.all_js_tests:self.assertIn(len(args),(1,2))test_file=self.abspath(args[0])iflen(args)>1:depends=[self.abspath(dep)fordepinargs[1]]else:depends=()forjs_testinself._test_qunit(test_file,depends):yieldjs_test@with_tempdirdef_test_qunit(self,test_file,depends=(),timeout=10):assertosp.exists(test_file),test_filefordepindepends:assertosp.exists(dep),depQUnitView.test_file=test_fileQUnitView.depends=dependswhilenotself.test_queue.empty():self.test_queue.get(False)browser=FirefoxHelper()browser.start(self.config['base-url']+"?vid=qunit")test_count=0error=Falsedefraise_exception(cls,*data):raisecls(*data)whilenoterror:try:result,test_name,msg=self.test_queue.get(timeout=timeout)test_name='%s (%s)'%(test_name,test_file)self.set_description(test_name)ifresultisNone:breaktest_count+=1ifresult:yieldInnerTest(test_name,lambda:1)else:yieldInnerTest(test_name,self.fail,msg)exceptEmpty:error=Truemsg='%s inactivity timeout (%is). %i test results received'yieldInnerTest(test_file,raise_exception,RuntimeError,msg%(test_file,timeout,test_count))browser.stop()iftest_count<=0andnoterror:yieldInnerTest(test_name,raise_exception,RuntimeError,'No test yielded by qunit for %s'%test_file)classQUnitResultController(Controller):__regid__='qunit_result'# Class variables to circumvent the instantiation of a new Controller for each request._log_stack=[]# store QUnit log messages_current_module_name=''# store the current QUnit module namedefpublish(self,rset=None):event=self._cw.form['event']getattr(self,'handle_%s'%event)()defhandle_module_start(self):self.__class__._current_module_name=self._cw.form.get('name','')defhandle_test_done(self):name='%s // %s'%(self._current_module_name,self._cw.form.get('name',''))failures=int(self._cw.form.get('failures',0))total=int(self._cw.form.get('total',0))self._log_stack.append('%i/%i assertions failed'%(failures,total))msg='\n'.join(self._log_stack)iffailures:self.tc.test_queue.put((False,name,msg))else:self.tc.test_queue.put((True,name,msg))self._log_stack[:]=[]defhandle_done(self):self.tc.test_queue.put((None,None,None))defhandle_log(self):result=self._cw.form['result']message=self._cw.form.get('message','<no message>')actual=self._cw.form.get('actual')expected=self._cw.form.get('expected')source=self._cw.form.get('source')log='%s: %s'%(result,message)ifresult=='false'andactualisnotNoneandexpectedisnotNone:log+=' (got: %s, expected: %s)'%(actual,expected)ifsourceisnotNone:log+='\n'+sourceself._log_stack.append(log)classQUnitView(View):__regid__='qunit'templatable=Falsedepends=Nonetest_file=Nonedefcall(self,**kwargs):w=self.wreq=self._cwdata={'jquery':req.data_url('jquery.js'),'web_test':req.build_url('cwsoftwareroot/devtools/data'),}w(u'''<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="application/html; charset=UTF-8"/> <!-- JS lib used as testing framework --> <link rel="stylesheet" type="text/css" media="all" href="%(web_test)s/qunit.css" /> <script src="%(jquery)s" type="text/javascript"></script> <script src="%(web_test)s/cwmock.js" type="text/javascript"></script> <script src="%(web_test)s/qunit.js" type="text/javascript"></script>'''%data)w(u'<!-- result report tools -->')w(u'<script type="text/javascript">')w(u"var BASE_URL = '%s';"%req.base_url())w(u''' QUnit.moduleStart(function (details) { jQuery.ajax({ url: BASE_URL + 'qunit_result', data: {"event": "module_start", "name": details.name}, async: false}); }); QUnit.testDone(function (details) { jQuery.ajax({ url: BASE_URL + 'qunit_result', data: {"event": "test_done", "name": details.name, "failures": details.failed, "total": details.total}, async: false}); }); QUnit.done(function (details) { jQuery.ajax({ url: BASE_URL + 'qunit_result', data: {"event": "done", "failures": details.failed, "total": details.total}, async: false}); }); QUnit.log(function (details) { jQuery.ajax({ url: BASE_URL + 'qunit_result', data: {"event": "log", "result": details.result, "actual": details.actual, "expected": details.expected, "source": details.source, "message": details.message}, async: false}); });''')w(u'</script>')w(u'<!-- Test script dependencies (tested code for example) -->')prefix=len(cubicweb.CW_SOFTWARE_ROOT)+1fordepinself.depends:dep=req.build_url('cwsoftwareroot/')+dep[prefix:]w(u' <script src="%s" type="text/javascript"></script>'%dep)w(u' <!-- Test script itself -->')test_url=req.build_url('cwsoftwareroot/')+self.test_file[prefix:]w(u' <script src="%s" type="text/javascript"></script>'%test_url)w(u''' </head> <body> <div id="qunit-fixture"></div> <div id="qunit"></div> </body> </html>''')classCWSoftwareRootStaticController(StaticFileController):__regid__='cwsoftwareroot'defpublish(self,rset=None):staticdir=cubicweb.CW_SOFTWARE_ROOTrelpath=self.relpath[len(self.__regid__)+1:]returnself.static_file(osp.join(staticdir,relpath))STATIC_CONTROLLERS.append(CWSoftwareRootStaticController)if__name__=='__main__':unittest_main()