Jump to content

Module:Convert/tester

From Wikipedia, the free encyclopedia

-- Test the output from a template by comparing it with fixed text.-- The expected text must be in a single line, but can include-- "\n" (two characters) to indicate that a newline is expected.-- Tests are run (or created) by setting p.tests (string or table), or-- by setting page=PAGE_TITLE (and optionally section=SECTION_TITLE),-- then executing run_tests (or make_tests).localCollection={}Collection.__index=CollectiondofunctionCollection:add(item)ifitem~=nilthenself.n=self.n+1self[self.n]=itemendendfunctionCollection:join(sep)returntable.concat(self,sep)endfunctionCollection.new()returnsetmetatable({n=0},Collection)endendlocalfunctionempty(text)-- Return true if text is nil or empty (assuming a string).returntext==nilortext==''endlocalfunctionstrip(text)-- Return text with no leading/trailing whitespace.returntext:match("^%s*(.-)%s*$")endlocalfunctionnormalize(text)-- Return text with any strip markers normalized by replacing the-- unique number with a fixed value so comparisons work.returntext:gsub('(\127[^\127]*UNIQ[^\127]*%-)(%x+)(-QINU[^\127]*\127)','%100000000%3')endlocalfunctionstatus_box(stats,expected,actual,iscomment)locallabel,bgcolor,align,isfailifiscommentthenactual=''align='center'bgcolor='silver'label='Cmnt'elseifexpected==''thenstats.ignored=stats.ignored+1return'',actualelseifnormalize(expected)==normalize(actual)thenstats.pass=stats.pass+1actual=''align='center'bgcolor='green'label='Pass'elsestats.fail=stats.fail+1align='center'bgcolor='red'label='Fail'isfail=trueendlocalsbox='style="text-align:'..align..';color:white;background:'..bgcolor..';" | '..labelreturnsbox,actual,isfailendlocalfunctionstatus_text(stats)localbgcolor,ignored_text,msg,ttextifstats.templatethenttext="'''Using [[Template:"..stats.template.."]]:''' "elsettext=''endifstats.fail==0thenifstats.pass==0thenbgcolor='salmon'msg='No tests performed'elsebgcolor='green'msg=string.format('All %d tests passed',stats.pass)endelsebgcolor='darkred'msg=string.format('%d test%s failed',stats.fail,stats.fail==1and''or's')endifstats.ignored==0thenignored_text=''elsebgcolor='salmon'ignored_text=string.format(', %d test%s ignored because expected text is blank',stats.ignored,stats.ignored==1and''or's')endreturnttext..'<span style="font-size:120%;color:white;background-color:'..bgcolor..';">'..msg..ignored_text..'.</span>'endlocalfunctionrun_template(frame,template,args,collapse_multiline)-- Template "{{ example | 2 = def | abc | name = ghi jkl }}"-- gives xargs { " abc ", "def", name = "ghi jkl" }.iftemplate:sub(1,2)=='{{'andtemplate:sub(-2,-1)=='}}'thentemplate=template:sub(3,-3)..'|'-- append sentinel to get last fieldelsereturn'(invalid template)'endlocalxargs={}localindex=1localtemplatenamelocalfunctionput_arg(k,v)-- Kludge: Module:Val uses Module:Arguments which trims arguments and-- omits blank arguments. Simulate that here.-- LATER Need a parameter to control this.iftemplatename:sub(1,3)=='val'thenv=strip(v)ifv==''thenreturnendendxargs[k]=vendtemplate=template:gsub('(%[%[[^%[%]]-)|(.-%]%])','%1\0%2')-- replace pipe in piped link with a zero byteforfieldintemplate:gmatch('(.-)|')dofield=field:gsub('%z','|')-- restore pipe in piped linkiftemplatename==nilthentemplatename=args.templateorstrip(field)iftemplatename==''thenreturn'(invalid template)'endelselocalk,eq,v=field:match("^(.-)(=)(.*)$")ifeqthenk,v=strip(k),strip(v)-- k and/or v can be emptylocali=tonumber(k)ifiandi>0andstring.match(k,'^%d+$')thenput_arg(i,v)elseput_arg(k,v)endelsewhilexargs[index]~=nildo-- Skip any explicit numbered parameters like "|5=five".index=index+1endput_arg(index,field)endendendifargs.testandnotxargs.testthen-- For convert, allow test=preview or test=nopreview to be injected into-- the convert under test, if it does not already use that parameter.-- That allows, for example, a preview of make_tests to show nopreview results.xargs.test=args.testendlocalfunctionexpand(t)returnframe:expandTemplate(t)endlocalok,result=pcall(expand,{title=templatename,args=xargs})ifnotokthenresult='Error: '..resultendifcollapse_multilinethenresult=result:gsub('\n','\\n')endreturnresultendlocalfunction_make_tests(frame,all_tests,args)localmaxlen=38for_,iteminipairs(all_tests)dolocaltemplate=item[1]iftemplatethenlocaltemplen=mw.ustring.len(template)item.templen=templenifmaxlen<templenandtemplen<=70thenmaxlen=templenendendendlocalresult=Collection.new()for_,iteminipairs(all_tests)dolocaltemplate=item[1]iftemplatethenlocalactual=run_template(frame,template,args,true)localpad=string.rep(' ',maxlen-item.templen)..' 'result:add(template..pad..actual)elselocaltext=item.textiftextthenresult:add(text)endendend-- Pre tags returned by a module are html tags, not like wikitext <pre>...</pre>.return'<pre>\n'..mw.text.nowiki(result:join('\n'))..'\n</pre>'endlocalfunction_run_tests(frame,all_tests,args)localfunctionsafe_cell(text,multiline)-- For testing {{convert}}, want wikitext like '[[kilogram|kg]]' to be unchanged-- so the link works and so the displayed text is short (just "kg" in example).text=text:gsub('(%[%[[^%[%]]-)|(.-%]%])','%1\0%2')-- replace pipe in piped link with a zero bytetext=text:gsub('{','&#123;'):gsub('|','&#124;')-- escape '{' and '|'text=text:gsub('%z','|')-- restore pipe in piped linkifmultilinethentext=text:gsub('\\n','<br />')endreturntextendlocalfunctionnowiki_cell(text,multiline)text=mw.text.nowiki(text)ifmultilinethentext=text:gsub('\\n','<br />')endreturntextendlocalstats={pass=0,fail=0,ignored=0,template=args.template}localresult=Collection.new()result:add('{| class="wikitable sortable"')result:add('! Template !! Expected !! Actual, if different !! Status')for_,iteminipairs(all_tests)dolocaltemplate,expected=item[1],item[2]or''iftemplatethenlocalactual=run_template(frame,template,args,true)localsbox,actual,isfail=status_box(stats,expected,actual)result:add('|-')result:add('| '..safe_cell(template))result:add('| '..safe_cell(expected,true))result:add('| '..safe_cell(actual,true))result:add('| '..sbox)ifisfailthenresult:add('|-')result:add('| align="center"| (above, nowiki)')result:add('| '..nowiki_cell(normalize(expected),true))result:add('| '..nowiki_cell(normalize(actual),true))result:add('|')endelselocaltext=item.textiftextandtext:sub(1,3)=='---'thenresult:add('|-')result:add('| colspan="3" style="color:white;background:silver;" | '..safe_cell(strip(text:sub(4)),true))result:add('| '..status_box(stats,'','',true))endendendresult:add('|}')returnstatus_text(stats)..'\n\n'..result:join('\n')endlocalfunctionget_page_content(page_title,ignore_error)localt=mw.title.new(page_title)iftthenlocalcontent=t:getContent()ifcontentthenifcontent:sub(-1)~='\n'thencontent=content..'\n'endreturncontentendendifnotignore_errorthenerror('Could not read wikitext from "[['..page_title..']]".',0)endendlocalfunction_compare(frame,page_pairs)localprefix=frame.args.prefixor'*'localfunctiondiff_link(title1,title2)return'<span class="plainlinks">['..tostring(mw.uri.fullUrl('Special:ComparePages',{page1=title1,page2=title2}))..' diff]</span>'endlocalfunctionlink(title)return'[['..title..']]'endlocalfunctionmessage(text,isgood)localcolor=isgoodand'green'or'darkred'return'<span style="color:'..color..';">'..text..'</span>'endlocalresult=Collection.new()for_,iteminipairs(page_pairs)dolocallabellocaltitle1=item[1]localtitle2=item[2]iftitle1==title2thenlabel=message('same title',false)elselocalcontent1=get_page_content(title1,true)localcontent2=get_page_content(title2,true)ifnotcontent1ornotcontent2thenlabel=message('does not exist',false)elseifcontent1==content2thenlabel=message('same content',true)elselabel=message('different',false)..' ('..diff_link(title1,title2)..')'endendresult:add(prefix..link(title1)..' • '..link(title2)..' • '..label)endreturnresult:join('\n')endlocalfunctionsections(text)return{first=1,-- just after the newline at the end of the last headingthis_section=1,next_heading=function(self)localfirst=self.firstwhilefirst<=#textdolocallast,headingfirst,last,heading=text:find('==+[\t ]*([^\n]-)[\t ]*==+[\t\r ]*\n',first)iffirsttheniffirst==1ortext:sub(first-1,first-1)=='\n'thenself.this_section=firstself.first=last+1returnheadingendfirst=last+1elsebreakendendself.first=#text+1returnnilend,current_section=function(self)localfirst=self.this_sectionlocallast=text:find('\n==[^\n]-==[\t\r ]*\n',first)ifnotlastthenlast=-1endreturntext:sub(first,last)end,}endlocalfunctionget_tests(frame,tests)localargs=frame.argslocalpage_title,section_title=args.page,args.sectionlocalshow_all=(args.show=='all')ifnotempty(page_title)thenifnotempty(tests)thenerror('Invoke must not set "page='..page_title..'" if also setting p.tests.',0)endifpage_title:sub(1,2)=='[['andpage_title:sub(-2)==']]'thenpage_title=strip(page_title:sub(3,-3))endtests=get_page_content(page_title)ifnotempty(section_title)thenlocals=sections(tests)whiletruedolocalheading=s:next_heading()ifheadingthenifheading==section_titlethentests=s:current_section()breakendelseerror('Section "'..section_title..'" not found in page [['..page_title..']].',0)endendendendiftype(tests)~='string'theniftype(tests)=='table'thenreturntestsenderror('No tests were specified; see [[Module:Convert/tester/doc]].',0)endiftests:sub(-1)~='\n'thentests=tests..'\n'endlocaltemplate_count=0localall_tests=Collection.new()forlinein(tests):gmatch('([^\n]-)[\t\r ]*\n')dolocaltemplate,expected=line:match('^({{.-}})%s*(.-)%s*$')iftemplatethentemplate_count=template_count+1all_tests:add({template,expected})elseifshow_allthenall_tests:add({text=line})endendiftemplate_count==0thenerror('No templates found; see [[Module:Convert/tester/doc]].',0)endreturnall_testsendlocalfunctionmain(frame,p,worker)localok,result=pcall(get_tests,frame,p.tests)ifokthenok,result=pcall(worker,frame,result,frame.args)ifokthenreturnresultendendreturn'<strong class="error">Error</strong>\n\n'..resultendlocalmodules={-- For convenience, a key defined here can be used to refer to the-- corresponding list of modules.countries={-- Commons'Countries','Countries/Africa','Countries/Americas','Countries/Arab world','Countries/Asia','Countries/Caribbean','Countries/Central America','Countries/Europe','Countries/North America','Countries/North America (subcontinent)','Countries/Oceania','Countries/South America','Countries/United Kingdom',},convert={'Convert','Convert/data','Convert/text','Convert/extra','Convert/wikidata','Convert/wikidata/data',},cs1={'Citation/CS1','Citation/CS1/Configuration',},cs1all={'Citation/CS1','Citation/CS1/Configuration','Citation/CS1/Whitelist','Citation/CS1/Date validation',},team={'Team appearances list','Team appearances list/data','Team appearances list/show',},val={'Val','Val/units',},}localp={}functionp.compare(frame)localpage_pairs=p.pairsifnotpage_pairsthenlocalargs=frame.argsifnotargs[2]thenlocalbuiltins=modules[args[1]or'convert']ifbuiltinsthenargs=builtinsendendpage_pairs={}fori,titleinipairs(args)doifnottitle:find(':',1,true)thentitle='Module:'..titleendpage_pairs[i]={title,title..'/sandbox'}endendlocalok,result=pcall(_compare,frame,page_pairs)ifokthenreturnresultendreturn'<strong class="error">Error</strong>\n\n'..resultendp.check_sandbox=p.comparefunctionp.make_tests(frame)returnmain(frame,p,_make_tests)endfunctionp.run_tests(frame)returnmain(frame,p,_run_tests)endreturnp
close