[devtools] qunit: use new async testing APIs
http://qunitjs.com/cookbook/#asynchronous-callbacks
QUnit keeps track of all the assert.async() objects created inside the
test functions and expects all done() functions to be called. Failure to
do so will result in the test being failed.
Unlike .start and .stop which were internal APIs, assert.async() is
stricter and fails tests if assert methods are used *after* all done()
functions are called (see "test callback execution order").
Related to #5533333.
# copyright 2003-2012 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/>."""provides simpleTAL extensions for CubicWeb"""__docformat__="restructuredtext en"importsysimportrefromos.pathimportexists,isdir,joinfromloggingimportgetLoggerfromStringIOimportStringIOfromsimpletalimportsimpleTAL,simpleTALESfromlogilab.common.decoratorsimportcachedLOGGER=getLogger('cubicweb.tal')classLoggerAdapter(object):def__init__(self,tal_logger):self.tal_logger=tal_loggerdefdebug(self,msg):LOGGER.debug(msg)defwarn(self,msg):LOGGER.warning(msg)def__getattr__(self,attrname):returngetattr(self.tal_logger,attrname)classCubicWebContext(simpleTALES.Context):"""add facilities to access entity / resultset"""def__init__(self,options=None,allowPythonPath=1):simpleTALES.Context.__init__(self,options,allowPythonPath)self.log=LoggerAdapter(self.log)defupdate(self,context):forvarname,valueincontext.items():self.addGlobal(varname,value)defaddRepeat(self,name,var,initialValue):simpleTALES.Context.addRepeat(self,name,var,initialValue)# XXX FIXME need to find a clean to define OPCODE values for extensionsI18N_CONTENT=18I18N_REPLACE=19RQL_EXECUTE=20# simpleTAL uses the OPCODE values to define priority over commands.# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but# we can't use the same OPCODE for two different commands without changing# the simpleTAL implementation. Another solution would be to totally override# the REPEAT implementation with the ITER one, but some specific operations# (involving len() for instance) are not implemented for ITER, so we prefer# to keep both implementations for now, and to fool simpleTAL by using a float# number between 3 and 4TAL_ITER=3.1# FIX simpleTAL HTML 4.01 stupidity# (simpleTAL never closes tags like INPUT, IMG, HR ...)simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()classCubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):"""extends default compiler by adding i18n:content commands"""def__init__(self):simpleTAL.HTMLTemplateCompiler.__init__(self)self.commandHandler[I18N_CONTENT]=self.compile_cmd_i18n_contentself.commandHandler[I18N_REPLACE]=self.compile_cmd_i18n_replaceself.commandHandler[RQL_EXECUTE]=self.compile_cmd_rqlself.commandHandler[TAL_ITER]=self.compile_cmd_tal_iterdefsetTALPrefix(self,prefix):simpleTAL.TemplateCompiler.setTALPrefix(self,prefix)self.tal_attribute_map['i18n:content']=I18N_CONTENTself.tal_attribute_map['i18n:replace']=I18N_REPLACEself.tal_attribute_map['rql:execute']=RQL_EXECUTEself.tal_attribute_map['tal:iter']=TAL_ITERdefcompile_cmd_i18n_content(self,argument):# XXX tal:content structure=, text= should we support this ?structure_flag=0return(I18N_CONTENT,(argument,False,structure_flag,self.endTagSymbol))defcompile_cmd_i18n_replace(self,argument):# XXX tal:content structure=, text= should we support this ?structure_flag=0return(I18N_CONTENT,(argument,True,structure_flag,self.endTagSymbol))defcompile_cmd_rql(self,argument):return(RQL_EXECUTE,(argument,self.endTagSymbol))defcompile_cmd_tal_iter(self,argument):original_id,(var_name,expression,end_tag_symbol)= \simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self,argument)return(TAL_ITER,(var_name,expression,self.endTagSymbol))defgetTemplate(self):returnCubicWebTemplate(self.commandList,self.macroMap,self.symbolLocationTable)defcompileCmdAttributes(self,argument):"""XXX modified to support single attribute definition ending by a ';' backport this to simpleTAL """# Compile tal:attributes into attribute command# Argument: [(attributeName, expression)]# Break up the list of attribute settings firstcommandArgs=[]# We only want to match semi-colons that are not escapedargumentSplitter=re.compile(r'(?<!;);(?!;)')forattributeStmtinargumentSplitter.split(argument):ifnotattributeStmt.strip():continue# remove any leading space and un-escape any semi-colonsattributeStmt=attributeStmt.lstrip().replace(';;',';')# Break each attributeStmt into name and expressionstmtBits=attributeStmt.split(' ')if(len(stmtBits)<2):# Error, badly formed attributes commandmsg="Badly formed attributes command '%s'. Attributes commands must be of the form: 'name expression[;name expression]'"%argumentself.log.error(msg)raisesimpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag),msg)attName=stmtBits[0]attExpr=" ".join(stmtBits[1:])commandArgs.append((attName,attExpr))return(simpleTAL.TAL_ATTRIBUTES,commandArgs)classCubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):"""provides implementation for interpreting cubicweb extensions"""def__init__(self):simpleTAL.TemplateInterpreter.__init__(self)self.commandHandler[I18N_CONTENT]=self.cmd_i18nself.commandHandler[TAL_ITER]=self.cmdRepeat# self.commandHandler[RQL_EXECUTE] = self.cmd_rqldefcmd_i18n(self,command,args):"""i18n:content and i18n:replace implementation"""string,replace_flag,structure_flag,end_symbol=argsifreplace_flag:self.outputTag=0result=self.context.globals['_'](string)self.tagContent=(0,result)self.movePCForward=self.symbolTable[end_symbol]self.programCounter+=1classCubicWebTemplate(simpleTAL.HTMLTemplate):"""overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter """defexpand(self,context,outputFile):interpreter=CubicWebTemplateInterpreter()interpreter.initialise(context,outputFile)simpleTAL.HTMLTemplate.expand(self,context,outputFile,# outputEncoding='unicode',interpreter=interpreter)defexpandInline(self,context,outputFile,interpreter):""" Internally used when expanding a template that is part of a context."""try:interpreter.execute(self)exceptUnicodeErrorasunierror:LOGGER.exception(str(unierror))raisesimpleTALES.ContextContentException("found non-unicode %r string in Context!"%unierror.args[1]),None,sys.exc_info()[-1]defcompile_template(template):"""compiles a TAL template string :type template: unicode :param template: a TAL-compliant template string """string_buffer=StringIO(template)compiler=CubicWebTemplateCompiler()compiler.parseTemplate(string_buffer)# , inputEncoding='unicode')returncompiler.getTemplate()defcompile_template_file(filepath):"""compiles a TAL template file :type filepath: str :param template: path of the file to compile """fp=file(filepath)file_content=unicode(fp.read())# template file should be pure ASCIIfp.close()returncompile_template(file_content)defevaluatePython(self,expr):ifnotself.allowPythonPath:returnself.falseglobals={}forname,valueinself.globals.items():ifisinstance(value,simpleTALES.ContextVariable):value=value.rawValue()globals[name]=valueglobals['path']=self.pythonPathFuncs.pathglobals['string']=self.pythonPathFuncs.stringglobals['exists']=self.pythonPathFuncs.existsglobals['nocall']=self.pythonPathFuncs.nocallglobals['test']=self.pythonPathFuncs.testlocals={}forname,valueinself.locals.items():if(isinstance(value,simpleTALES.ContextVariable)):value=value.rawValue()locals[name]=value# XXX precompile expr will avoid late syntax errortry:result=eval(expr,globals,locals)exceptExceptionasex:ex=ex.__class__('in %r: %s'%(expr,ex))raiseex,None,sys.exc_info()[-1]if(isinstance(result,simpleTALES.ContextVariable)):returnresult.value()returnresultsimpleTALES.Context.evaluatePython=evaluatePythonclasstalbased(object):def__init__(self,filename,write=True):## if not osp.isfile(filepath):## # print "[tal.py] just for tests..."## # get parent frame## directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))## filepath = osp.join(directory, filepath)self.filename=filenameself.write=writedef__call__(self,viewfunc):defwrapped(instance,*args,**kwargs):variables=viewfunc(instance,*args,**kwargs)html=instance.tal_render(self._compiled_template(instance),variables)ifself.write:instance.w(html)else:returnhtmlreturnwrappeddef_compiled_template(self,instance):forfileordirectoryininstance.config.appobjects_path():filepath=join(fileordirectory,self.filename)ifisdir(fileordirectory)andexists(filepath):returncompile_template_file(filepath)raiseException('no such template %s'%self.filename)_compiled_template=cached(_compiled_template,0)