1df3bd252SSatish Balay#!/usr/bin/env python3 229921a8fSScott Kruger""" 30338c944SBarry SmithParse the PETSc tutorial or test file (example) and return a dictionary containing information about the tests to be run for the example 429921a8fSScott Kruger 529921a8fSScott KrugerQuick usage:: 629921a8fSScott Kruger 7c4762a1bSJed Brown lib/petsc/bin/maint/testparse.py -t src/ksp/ksp/tutorials/ex1.c 829921a8fSScott Kruger 929921a8fSScott KrugerFrom the command line, it prints out the dictionary. 1029921a8fSScott KrugerThis is meant to be used by other scripts, but it is 1129921a8fSScott Krugeruseful to debug individual files. 1229921a8fSScott Kruger 1329921a8fSScott KrugerExample language 1429921a8fSScott Kruger---------------- 1529921a8fSScott Kruger 1629921a8fSScott Kruger/*TEST 17aec507c4SScott Kruger build: 18aec507c4SScott Kruger requires: moab 19e53dc769SScott Kruger # This is equivalent to test: 20e53dc769SScott Kruger testset: 2129921a8fSScott Kruger args: -pc_type mg -ksp_type fgmres -da_refine 2 -ksp_monitor_short -mg_levels_ksp_monitor_short -mg_levels_ksp_norm_type unpreconditioned -ksp_view -pc_mg_type full 2229921a8fSScott Kruger 23e53dc769SScott Kruger testset: 2429921a8fSScott Kruger suffix: 2 2529921a8fSScott Kruger nsize: 2 2629921a8fSScott Kruger args: -pc_type mg -ksp_type fgmres -da_refine 2 -ksp_monitor_short -mg_levels_ksp_monitor_short -mg_levels_ksp_norm_type unpreconditioned -ksp_view -pc_mg_type full 2729921a8fSScott Kruger 28e53dc769SScott Kruger testset: 29e53dc769SScott Kruger suffix: 2 30e53dc769SScott Kruger nsize: 2 31e53dc769SScott Kruger args: -pc_type mg -ksp_type fgmres -da_refine 2 -ksp_monitor_short -mg_levels_ksp_monitor_short -mg_levels_ksp_norm_type unpreconditioned -ksp_view -pc_mg_type full 32e53dc769SScott Kruger test: 33e53dc769SScott Kruger 3429921a8fSScott KrugerTEST*/ 3529921a8fSScott Kruger 3629921a8fSScott Kruger""" 375b6bfdb9SJed Brownfrom __future__ import print_function 3829921a8fSScott Kruger 3929921a8fSScott Krugerimport os, re, glob, types 4029921a8fSScott Krugerimport sys 4129921a8fSScott Krugerimport logging 42c0558f20SBarry Smithfrom gmakegen import * 4329921a8fSScott Krugersys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) 4429921a8fSScott Kruger 4529921a8fSScott Krugerimport inspect 4629921a8fSScott Krugerthisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 4729921a8fSScott Krugermaintdir=os.path.join(os.path.join(os.path.dirname(thisscriptdir),'bin'),'maint') 4829921a8fSScott Krugersys.path.insert(0,maintdir) 4929921a8fSScott Kruger 5029921a8fSScott Kruger# These are special keys describing build 5129921a8fSScott Krugerbuildkeys="requires TODO SKIP depends".split() 5229921a8fSScott Kruger 53d68d54c7SToby Isaacacceptedkeys=set("test nsize requires command suffix diff_args args filter filter_output localrunfiles comments TODO SKIP output_file timeoutfactor env temporaries".split()) 54fa236c6aSJacob Faibussowitschappendlist="args diff_args requires comments env".split() 5568a9e459SScott Kruger 5668a9e459SScott Krugerimport re 5768a9e459SScott Kruger 585e361860SScott Krugerdef getDefaultOutputFileRoot(testname): 595e361860SScott Kruger """ 605e361860SScott Kruger Given testname, give DefaultRoot and DefaultOutputFilename 615e361860SScott Kruger e.g., runex1 gives ex1_1, output/ex1_1.out 625e361860SScott Kruger """ 635e361860SScott Kruger defroot=(re.sub("run","",testname) if testname.startswith("run") else testname) 645e361860SScott Kruger if not "_" in defroot: defroot=defroot+"_1" 655e361860SScott Kruger return defroot 665e361860SScott Kruger 6744776d8cSScott Krugerdef _stripIndent(block,srcfile,entireBlock=False,fileNums=[]): 6829921a8fSScott Kruger """ 6929921a8fSScott Kruger Go through and remove a level of indentation 7029921a8fSScott Kruger Also strip of trailing whitespace 7129921a8fSScott Kruger """ 7229921a8fSScott Kruger # The first entry should be test: but it might be indented. 73c0558f20SBarry Smith ext = getlangext(srcfile) 7444776d8cSScott Kruger stripstr=" " 7566db876fSScott Kruger if len(fileNums)>0: lineNum=fileNums[0]-1 768ccd5183SScott Kruger for lline in block.split("\n"): 7766db876fSScott Kruger if len(fileNums)>0: lineNum+=1 788ccd5183SScott Kruger line=lline[1:] if lline.startswith("!") else lline 7929921a8fSScott Kruger if not line.strip(): continue 80c4b80baaSScott Kruger if line.strip().startswith('#'): continue 8144776d8cSScott Kruger if entireBlock: 8244776d8cSScott Kruger var=line.split(":")[0].strip() 83aec507c4SScott Kruger if not var in ['test','testset','build']: 8466db876fSScott Kruger raise Exception("Formatting error: Cannot find test in file: "+srcfile+" at line: "+str(lineNum)+"\n") 8529921a8fSScott Kruger nspace=len(line)-len(line.lstrip(stripstr)) 8629921a8fSScott Kruger newline=line[nspace:] 8729921a8fSScott Kruger break 8829921a8fSScott Kruger 8929921a8fSScott Kruger # Strip off any indentation for the whole string and any trailing 9029921a8fSScott Kruger # whitespace for convenience 9129921a8fSScott Kruger newTestStr="\n" 9244776d8cSScott Kruger if len(fileNums)>0: lineNum=fileNums[0]-1 9344776d8cSScott Kruger firstPass=True 948ccd5183SScott Kruger for lline in block.split("\n"): 9544776d8cSScott Kruger if len(fileNums)>0: lineNum+=1 968ccd5183SScott Kruger line=lline[1:] if lline.startswith("!") else lline 9729921a8fSScott Kruger if not line.strip(): continue 98c4b80baaSScott Kruger if line.strip().startswith('#'): 99c4b80baaSScott Kruger newTestStr+=line+'\n' 100c4b80baaSScott Kruger else: 10129921a8fSScott Kruger newline=line[nspace:] 102c4b80baaSScott Kruger newTestStr+=newline.rstrip()+"\n" 10344776d8cSScott Kruger # Do some basic indentation checks 10444776d8cSScott Kruger if entireBlock: 10544776d8cSScott Kruger # Don't need to check comment lines 10644776d8cSScott Kruger if line.strip().startswith('#'): continue 10744776d8cSScott Kruger if not newline.startswith(" "): 10844776d8cSScott Kruger var=newline.split(":")[0].strip() 109aec507c4SScott Kruger if not var in ['test','testset','build']: 11044776d8cSScott Kruger err="Formatting error in file "+srcfile+" at line: " +line+"\n" 11144776d8cSScott Kruger if len(fileNums)>0: 11244776d8cSScott Kruger raise Exception(err+"Check indentation at line number: "+str(lineNum)) 11344776d8cSScott Kruger else: 11444776d8cSScott Kruger raise Exception(err) 11544776d8cSScott Kruger else: 11644776d8cSScott Kruger var=line.split(":")[0].strip() 117aec507c4SScott Kruger if var in ['test','testset','build']: 11844776d8cSScott Kruger subnspace=len(line)-len(line.lstrip(stripstr)) 11944776d8cSScott Kruger if firstPass: 12044776d8cSScott Kruger firstsubnspace=subnspace 12144776d8cSScott Kruger firstPass=False 12244776d8cSScott Kruger else: 12344776d8cSScott Kruger if firstsubnspace!=subnspace: 12444776d8cSScott Kruger err="Formatting subtest error in file "+srcfile+" at line: " +line+"\n" 12544776d8cSScott Kruger if len(fileNums)>0: 12644776d8cSScott Kruger raise Exception(err+"Check indentation at line number: "+str(lineNum)) 12744776d8cSScott Kruger else: 12844776d8cSScott Kruger raise Exception(err) 12944776d8cSScott Kruger 13043c6e11bSMatthew G. Knepley # Allow line continuation character '\' 13143c6e11bSMatthew G. Knepley return newTestStr.replace('\\\n', ' ') 13229921a8fSScott Kruger 133aae9f2d9SScott Krugerdef parseLoopArgs(varset): 13444776d8cSScott Kruger """ 135aae9f2d9SScott Kruger Given: String containing loop variables 136aae9f2d9SScott Kruger Return: tuple containing separate/shared and string of loop vars 13744776d8cSScott Kruger """ 138a449fbaeSJed Brown keynm=varset.split("{{")[0].strip().lstrip('-') 139aae9f2d9SScott Kruger if not keynm.strip(): keynm='nsize' 140aae9f2d9SScott Kruger lvars=varset.split('{{')[1].split('}')[0] 141aae9f2d9SScott Kruger suffx=varset.split('{{')[1].split('}')[1] 142aae9f2d9SScott Kruger ftype='separate' if suffx.startswith('separate') else 'shared' 143aae9f2d9SScott Kruger return keynm,lvars,ftype 14444776d8cSScott Kruger 1452bbaaa9fSScott Krugerdef _getLoopVars(testDict): 146e53dc769SScott Kruger """ 147e53dc769SScott Kruger Given: dictionary that may have 148e53dc769SScott Kruger Return: Variables that cause a test split 149e53dc769SScott Kruger """ 150e53dc769SScott Kruger vals=None 1512bbaaa9fSScott Kruger loopVars={} 1522bbaaa9fSScott Kruger loopVars['separate']=[] 1532bbaaa9fSScott Kruger loopVars['shared']=[] 154e53dc769SScott Kruger # Check nsize 1555b6bfdb9SJed Brown if 'nsize' in testDict: 156e53dc769SScott Kruger varset=testDict['nsize'] 157aae9f2d9SScott Kruger if '{{' in varset: 158aae9f2d9SScott Kruger keynm,lvars,ftype=parseLoopArgs(varset) 1592bbaaa9fSScott Kruger if ftype=='separate': loopVars['separate'].append(keynm) 160e53dc769SScott Kruger 161e53dc769SScott Kruger # Now check args 1622bbaaa9fSScott Kruger if 'args' not in testDict: return loopVars 1633be2e2fdSJose E. Roman for varset in re.split(r'(^|\W)-(?=[a-zA-Z])',testDict['args']): 164e53dc769SScott Kruger if not varset.strip(): continue 165aae9f2d9SScott Kruger if '{{' in varset: 166e53dc769SScott Kruger # Assuming only one for loop per var specification 167aae9f2d9SScott Kruger keynm,lvars,ftype=parseLoopArgs(varset) 1682bbaaa9fSScott Kruger loopVars[ftype].append(keynm) 169e53dc769SScott Kruger 1702bbaaa9fSScott Kruger return loopVars 171e53dc769SScott Kruger 1722bbaaa9fSScott Krugerdef _getNewArgs(args,separate=True): 1734d82b48cSScott Kruger """ 1744d82b48cSScott Kruger Given: String that has args that might have loops in them 1754d82b48cSScott Kruger Return: All of the arguments/values that do not have 1764d82b48cSScott Kruger for 'separate output' in for loops 1772bbaaa9fSScott Kruger unless separate=False 1784d82b48cSScott Kruger """ 1794d82b48cSScott Kruger newargs='' 1804d82b48cSScott Kruger if not args.strip(): return args 1813be2e2fdSJose E. Roman for varset in re.split(r'(^|\W)-(?=[a-zA-Z])',args): 1824d82b48cSScott Kruger if not varset.strip(): continue 1832bbaaa9fSScott Kruger if '{{' in varset: 1842bbaaa9fSScott Kruger if separate: 1852bbaaa9fSScott Kruger if 'separate' in varset: continue 1862bbaaa9fSScott Kruger else: 1872bbaaa9fSScott Kruger if 'separate' not in varset: continue 1882bbaaa9fSScott Kruger 1894d82b48cSScott Kruger newargs+="-"+varset.strip()+" " 1904d82b48cSScott Kruger 1914d82b48cSScott Kruger return newargs 1924d82b48cSScott Kruger 19344776d8cSScott Krugerdef _getVarVals(findvar,testDict): 19444776d8cSScott Kruger """ 19544776d8cSScott Kruger Given: variable that is either nsize or in args 1964d82b48cSScott Kruger Return: Values to loop over and the other arguments 1974d82b48cSScott Kruger Note that we keep the other arguments even if they have 1984d82b48cSScott Kruger for loops to enable stepping through all of the for lops 19944776d8cSScott Kruger """ 2004d82b48cSScott Kruger save_vals=None 20144776d8cSScott Kruger if findvar=='nsize': 202e53dc769SScott Kruger varset=testDict[findvar] 2034d82b48cSScott Kruger keynm,save_vals,ftype=parseLoopArgs('nsize '+varset) 20444776d8cSScott Kruger else: 20544776d8cSScott Kruger varlist=[] 20644776d8cSScott Kruger for varset in re.split('-(?=[a-zA-Z])',testDict['args']): 20744776d8cSScott Kruger if not varset.strip(): continue 2084d82b48cSScott Kruger if '{{' not in varset: continue 209aae9f2d9SScott Kruger keyvar,vals,ftype=parseLoopArgs(varset) 2104d82b48cSScott Kruger if keyvar==findvar: 2114d82b48cSScott Kruger save_vals=vals 21244776d8cSScott Kruger 2135b6bfdb9SJed Brown if not save_vals: raise Exception("Could not find separate_testvar: "+findvar) 2144d82b48cSScott Kruger return save_vals 21544776d8cSScott Kruger 2164f8a0bffSScott Krugerdef genTestsSeparateTestvars(intests,indicts,final=False): 21744776d8cSScott Kruger """ 218e53dc769SScott Kruger Given: testname, sdict with 'separate_testvars 21944776d8cSScott Kruger Return: testnames,sdicts: List of generated tests 2204d82b48cSScott Kruger The tricky part here is the {{ ... }separate output} 2214d82b48cSScott Kruger that can be used multiple times 22244776d8cSScott Kruger """ 22344776d8cSScott Kruger testnames=[]; sdicts=[] 22444776d8cSScott Kruger for i in range(len(intests)): 22544776d8cSScott Kruger testname=intests[i]; sdict=indicts[i]; i+=1 2262bbaaa9fSScott Kruger loopVars=_getLoopVars(sdict) 2272bbaaa9fSScott Kruger if len(loopVars['shared'])>0 and not final: 2282bbaaa9fSScott Kruger # Need to remove shared loop vars and push down to subtests 2292bbaaa9fSScott Kruger if 'subtests' in sdict: 2303be2e2fdSJose E. Roman for varset in re.split(r'(^|\W)-(?=[a-zA-Z])',sdict['args']): 2312bbaaa9fSScott Kruger if '{{' in varset: 2322bbaaa9fSScott Kruger for stest in sdict['subtests']: 2332bbaaa9fSScott Kruger if 'args' in sdict[stest]: 2342bbaaa9fSScott Kruger sdict[stest]['args']+=' -'+varset 2352bbaaa9fSScott Kruger else: 2362bbaaa9fSScott Kruger sdict[stest]['args']="-"+varset 2372bbaaa9fSScott Kruger sdict['args']=_getNewArgs(sdict['args'],separate=False) 2382bbaaa9fSScott Kruger if len(loopVars['separate'])>0: 2394d82b48cSScott Kruger sep_dicts=[sdict.copy()] 2404d82b48cSScott Kruger if 'args' in sep_dicts[0]: 2414d82b48cSScott Kruger sep_dicts[0]['args']=_getNewArgs(sdict['args']) 2424d82b48cSScott Kruger sep_testnames=[testname] 2432bbaaa9fSScott Kruger for kvar in loopVars['separate']: 2444d82b48cSScott Kruger kvals=_getVarVals(kvar,sdict) 2454d82b48cSScott Kruger 2464d82b48cSScott Kruger # Have to do loop over previous var/val combos as well 2474d82b48cSScott Kruger # and accumulate as we go 2484d82b48cSScott Kruger val_testnames=[]; val_dicts=[] 24944776d8cSScott Kruger for val in kvals.split(): 250541979b6SScott Kruger gensuffix="_"+kvar+"-"+val.replace(',','__') 2514d82b48cSScott Kruger for kvaltestnm in sep_testnames: 2524d82b48cSScott Kruger val_testnames.append(kvaltestnm+gensuffix) 2534d82b48cSScott Kruger for kv in sep_dicts: 2544d82b48cSScott Kruger kvardict=kv.copy() 2554d82b48cSScott Kruger # If the last var then we have the final version 2564d82b48cSScott Kruger if 'suffix' in sdict: 2574d82b48cSScott Kruger kvardict['suffix']+=gensuffix 2580bcc1aabSScott Kruger else: 2590bcc1aabSScott Kruger kvardict['suffix']=gensuffix 26044776d8cSScott Kruger if kvar=='nsize': 26144776d8cSScott Kruger kvardict[kvar]=val 26244776d8cSScott Kruger else: 2634d82b48cSScott Kruger kvardict['args']+="-"+kvar+" "+val+" " 2644d82b48cSScott Kruger val_dicts.append(kvardict) 2654d82b48cSScott Kruger sep_testnames=val_testnames 2664d82b48cSScott Kruger sep_dicts=val_dicts 2674d82b48cSScott Kruger testnames+=sep_testnames 2684d82b48cSScott Kruger sdicts+=sep_dicts 26944776d8cSScott Kruger else: 2704f8a0bffSScott Kruger # These are plain vanilla tests (no subtests, no loops) that 2714f8a0bffSScott Kruger # do not have a suffix. This makes the targets match up with 2724f8a0bffSScott Kruger # the output file (testname_1.out) 2734f8a0bffSScott Kruger if final: 2744f8a0bffSScott Kruger if '_' not in testname: testname+='_1' 27544776d8cSScott Kruger testnames.append(testname) 27644776d8cSScott Kruger sdicts.append(sdict) 27744776d8cSScott Kruger return testnames,sdicts 27844776d8cSScott Kruger 27944776d8cSScott Krugerdef genTestsSubtestSuffix(testnames,sdicts): 28044776d8cSScott Kruger """ 28144776d8cSScott Kruger Given: testname, sdict with separate_testvars 28244776d8cSScott Kruger Return: testnames,sdicts: List of generated tests 28344776d8cSScott Kruger """ 28444776d8cSScott Kruger tnms=[]; sdcts=[] 28544776d8cSScott Kruger for i in range(len(testnames)): 28644776d8cSScott Kruger testname=testnames[i] 28744776d8cSScott Kruger rmsubtests=[]; keepSubtests=False 2885b6bfdb9SJed Brown if 'subtests' in sdicts[i]: 28944776d8cSScott Kruger for stest in sdicts[i]["subtests"]: 2905b6bfdb9SJed Brown if 'suffix' in sdicts[i][stest]: 29144776d8cSScott Kruger rmsubtests.append(stest) 29244776d8cSScott Kruger gensuffix="_"+sdicts[i][stest]['suffix'] 29344776d8cSScott Kruger newtestnm=testname+gensuffix 29444776d8cSScott Kruger tnms.append(newtestnm) 29544776d8cSScott Kruger newsdict=sdicts[i].copy() 29644776d8cSScott Kruger del newsdict['subtests'] 29744776d8cSScott Kruger # Have to hand update 29844776d8cSScott Kruger # Append 29944776d8cSScott Kruger for kup in appendlist: 3005b6bfdb9SJed Brown if kup in sdicts[i][stest]: 3015b6bfdb9SJed Brown if kup in sdicts[i]: 30244776d8cSScott Kruger newsdict[kup]=sdicts[i][kup]+" "+sdicts[i][stest][kup] 30344776d8cSScott Kruger else: 30444776d8cSScott Kruger newsdict[kup]=sdicts[i][stest][kup] 30544776d8cSScott Kruger # Promote 30644776d8cSScott Kruger for kup in acceptedkeys: 30744776d8cSScott Kruger if kup in appendlist: continue 3085b6bfdb9SJed Brown if kup in sdicts[i][stest]: 30944776d8cSScott Kruger newsdict[kup]=sdicts[i][stest][kup] 31044776d8cSScott Kruger # Cleanup 31144776d8cSScott Kruger for st in sdicts[i]["subtests"]: del newsdict[st] 31244776d8cSScott Kruger sdcts.append(newsdict) 31344776d8cSScott Kruger else: 31444776d8cSScott Kruger keepSubtests=True 31544776d8cSScott Kruger else: 31644776d8cSScott Kruger tnms.append(testnames[i]) 31744776d8cSScott Kruger sdcts.append(sdicts[i]) 3180bcc1aabSScott Kruger # If a subtest without a suffix exists, then save it 31944776d8cSScott Kruger if keepSubtests: 32044776d8cSScott Kruger tnms.append(testnames[i]) 3210bcc1aabSScott Kruger newsdict=sdicts[i].copy() 3220bcc1aabSScott Kruger # Prune the tests to prepare for keeping 3230bcc1aabSScott Kruger for rmtest in rmsubtests: 3240bcc1aabSScott Kruger newsdict['subtests'].remove(rmtest) 3250bcc1aabSScott Kruger del newsdict[rmtest] 3260bcc1aabSScott Kruger sdcts.append(newsdict) 32744776d8cSScott Kruger i+=1 32844776d8cSScott Kruger return tnms,sdcts 32944776d8cSScott Kruger 33044776d8cSScott Krugerdef splitTests(testname,sdict): 33144776d8cSScott Kruger """ 3322be3497aSPatrick Sanan Given: testname and dictionary generated from the YAML-like definition 33344776d8cSScott Kruger Return: list of names and dictionaries corresponding to each test 3342be3497aSPatrick Sanan given that the YAML-like language allows for multiple tests 33544776d8cSScott Kruger """ 33644776d8cSScott Kruger 33744776d8cSScott Kruger # Order: Parent sep_tv, subtests suffix, subtests sep_tv 33844776d8cSScott Kruger testnames,sdicts=genTestsSeparateTestvars([testname],[sdict]) 33944776d8cSScott Kruger testnames,sdicts=genTestsSubtestSuffix(testnames,sdicts) 3404f8a0bffSScott Kruger testnames,sdicts=genTestsSeparateTestvars(testnames,sdicts,final=True) 34144776d8cSScott Kruger 34244776d8cSScott Kruger # Because I am altering the list, I do this in passes. Inelegant 34344776d8cSScott Kruger 34444776d8cSScott Kruger return testnames, sdicts 34544776d8cSScott Kruger 346080f0011SToby Isaacdef testSplit(striptest): 347080f0011SToby Isaac """ 348080f0011SToby Isaac Split up a test into lines, but use a shell parser to detect when newlines are within quotation marks 349080f0011SToby Isaac and keep those together 350080f0011SToby Isaac """ 351080f0011SToby Isaac import shlex 352080f0011SToby Isaac 353080f0011SToby Isaac sl = shlex.shlex() 354080f0011SToby Isaac sl.whitespace_split = True # only split at whitespace 355080f0011SToby Isaac sl.commenters = '' 356080f0011SToby Isaac sl.push_source(striptest) 357080f0011SToby Isaac last_pos = sl.instream.tell() 358080f0011SToby Isaac try: 359080f0011SToby Isaac last_token = sl.read_token() 360080f0011SToby Isaac except ValueError: 361080f0011SToby Isaac print(striptest) 362080f0011SToby Isaac raise ValueError 363080f0011SToby Isaac last_line = '' 364080f0011SToby Isaac while last_token != '': 365080f0011SToby Isaac new_pos = sl.instream.tell() 366080f0011SToby Isaac block = striptest[last_pos:new_pos] 367080f0011SToby Isaac token_start = block.find(last_token) 368080f0011SToby Isaac leading = block[0:token_start] 369080f0011SToby Isaac trailing = block[(token_start + len(last_token)):] 370080f0011SToby Isaac leading_split = leading.split('\n') 371080f0011SToby Isaac if len(leading_split) > 1: 372080f0011SToby Isaac yield last_line 373080f0011SToby Isaac last_line = '' 374080f0011SToby Isaac last_line += leading_split[-1] 375080f0011SToby Isaac last_line += last_token 376080f0011SToby Isaac trailing_split = trailing.split('\n') 377080f0011SToby Isaac last_line += trailing_split[0] 378080f0011SToby Isaac if len(trailing_split) > 1: 379080f0011SToby Isaac yield last_line 380080f0011SToby Isaac last_line = '' 381080f0011SToby Isaac last_pos = new_pos 382080f0011SToby Isaac try: 383080f0011SToby Isaac last_token = sl.read_token() 384080f0011SToby Isaac except ValueError: 385080f0011SToby Isaac print(striptest) 386080f0011SToby Isaac raise ValueError 387080f0011SToby Isaac yield last_line 388080f0011SToby Isaac 3896cecdbdcSScott Krugerdef parseTest(testStr,srcfile,verbosity): 39029921a8fSScott Kruger """ 39129921a8fSScott Kruger This parses an individual test 3922be3497aSPatrick Sanan Our YAML-like language is hierarchial so should use a state machine in the general case, 39353f2a965SBarry Smith but in practice we only support two levels of test: 39429921a8fSScott Kruger """ 39529921a8fSScott Kruger basename=os.path.basename(srcfile) 396d5b43468SJose E. Roman # Handle the new at the beginning 39729921a8fSScott Kruger bn=re.sub("new_","",basename) 39829921a8fSScott Kruger # This is the default 399c0558f20SBarry Smith testname="run"+getlangsplit(bn) 40029921a8fSScott Kruger 40129921a8fSScott Kruger # Tests that have default everything (so empty effectively) 4022bbaaa9fSScott Kruger if len(testStr)==0: 4032bbaaa9fSScott Kruger if '_' not in testname: testname+='_1' 4042bbaaa9fSScott Kruger return [testname], [{}] 40529921a8fSScott Kruger 40629921a8fSScott Kruger striptest=_stripIndent(testStr,srcfile) 40729921a8fSScott Kruger 40829921a8fSScott Kruger # go through and parse 40929921a8fSScott Kruger subtestnum=0 41029921a8fSScott Kruger subdict={} 41168a9e459SScott Kruger comments=[] 41268a9e459SScott Kruger indentlevel=0 413080f0011SToby Isaac for ln in testSplit(striptest): 414c4b80baaSScott Kruger line=ln.split('#')[0].rstrip() 415cadd188bSScott Kruger if verbosity>2: print(line) 4160bcc1aabSScott Kruger comment=("" if len(ln.split("#"))>0 else " ".join(ln.split("#")[1:]).strip()) 41768a9e459SScott Kruger if comment: comments.append(comment) 41829921a8fSScott Kruger if not line.strip(): continue 41944776d8cSScott Kruger lsplit=line.split(':') 42045b74845SBarry Smith if len(lsplit)==0: 42145b74845SBarry Smith raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nMissing : in line: "+line) 42244776d8cSScott Kruger indentcount=lsplit[0].count(" ") 42344776d8cSScott Kruger var=lsplit[0].strip() 42440ae0433SScott Kruger val=line[line.find(':')+1:].strip() 42545b74845SBarry Smith if not var in acceptedkeys: 42645b74845SBarry Smith raise Exception("\n\nError in test harness parsing file: "+srcfile+"\n"+var+" from: "+line+" is not a valid keyword") 42729921a8fSScott Kruger # Start by seeing if we are in a subtest 42829921a8fSScott Kruger if line.startswith(" "): 429376ff7dcSStefano Zampini if not 'subtestname' in locals(): 430376ff7dcSStefano Zampini raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nInvalid indentation at line:"+line) 431ecc1beb5SScott Kruger if var in subdict[subtestname]: 432ecc1beb5SScott Kruger subdict[subtestname][var]+=" "+val 433ecc1beb5SScott Kruger else: 434c0658d2aSScott Kruger subdict[subtestname][var]=val 43568a9e459SScott Kruger if not indentlevel: indentlevel=indentcount 436cadd188bSScott Kruger #if indentlevel!=indentcount: print("Error in indentation:", ln) 43729921a8fSScott Kruger # Determine subtest name and make dict 43829921a8fSScott Kruger elif var=="test": 43929921a8fSScott Kruger subtestname="test"+str(subtestnum) 44029921a8fSScott Kruger subdict[subtestname]={} 4415b6bfdb9SJed Brown if "subtests" not in subdict: subdict["subtests"]=[] 44229921a8fSScott Kruger subdict["subtests"].append(subtestname) 44329921a8fSScott Kruger subtestnum=subtestnum+1 44468a9e459SScott Kruger # The rest are easy 44529921a8fSScott Kruger else: 44644776d8cSScott Kruger # For convenience, it is sometimes convenient to list twice 4475b6bfdb9SJed Brown if var in subdict: 44844776d8cSScott Kruger if var in appendlist: 44944776d8cSScott Kruger subdict[var]+=" "+val 45044776d8cSScott Kruger else: 45145b74845SBarry Smith raise Exception("\n\nError in test harness parsing file: "+srcfile+"\n"+var+" entered twice: "+line) 45244776d8cSScott Kruger else: 453c0658d2aSScott Kruger subdict[var]=val 45429921a8fSScott Kruger if var=="suffix": 45529921a8fSScott Kruger if len(val)>0: 4564f8a0bffSScott Kruger testname+="_"+val 457fa236c6aSJacob Faibussowitsch if var == "env" and len(val) == 0: 45845b74845SBarry Smith raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nvalue for {}: directive cannot be empty!".format(var)) 45929921a8fSScott Kruger 46068a9e459SScott Kruger if len(comments): subdict['comments']="\n".join(comments).lstrip("\n") 4614f8a0bffSScott Kruger 462*e2535b21SJunchao Zhang # add in error check: if 'requires' is added to subtests, make sure 'suffix' is also added. 463*e2535b21SJunchao Zhang if 'subtests' in subdict: 464*e2535b21SJunchao Zhang for stest in subdict['subtests']: 465*e2535b21SJunchao Zhang if 'requires' in subdict[stest] and not 'suffix' in subdict[stest]: 466*e2535b21SJunchao Zhang raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nA test in a testset with 'requires' directive should also have a 'suffix' directive!") 467*e2535b21SJunchao Zhang 4684f8a0bffSScott Kruger # A test block can create multiple tests. This does that logic 46944776d8cSScott Kruger testnames,subdicts=splitTests(testname,subdict) 47044776d8cSScott Kruger return testnames,subdicts 47129921a8fSScott Kruger 4726cecdbdcSScott Krugerdef parseTests(testStr,srcfile,fileNums,verbosity): 47329921a8fSScott Kruger """ 4742be3497aSPatrick Sanan Parse the YAML-like string describing tests and return 47529921a8fSScott Kruger a dictionary with the info in the form of: 47629921a8fSScott Kruger testDict[test][subtest] 47729921a8fSScott Kruger """ 47829921a8fSScott Kruger 47929921a8fSScott Kruger testDict={} 48029921a8fSScott Kruger 48129921a8fSScott Kruger # The first entry should be test: but it might be indented. 48244776d8cSScott Kruger newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums) 483cadd188bSScott Kruger if verbosity>2: print(srcfile) 48429921a8fSScott Kruger 485e4653983SScott Kruger ## Check and see if we have build requirements 486e4653983SScott Kruger addToRunRequirements=None 487aec507c4SScott Kruger if "\nbuild:" in newTestStr: 488aec507c4SScott Kruger testDict['build']={} 489aec507c4SScott Kruger # The file info is already here and need to append 490aec507c4SScott Kruger Part1=newTestStr.split("build:")[1] 491aec507c4SScott Kruger fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0] 492aec507c4SScott Kruger for bkey in buildkeys: 493aec507c4SScott Kruger if bkey+":" in fileInfo: 494aec507c4SScott Kruger testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip() 495aec507c4SScott Kruger #if verbosity>1: bkey+": "+testDict['build'][bkey] 496e4653983SScott Kruger # If a runtime requires are put into build, push them down to all run tests 497e4653983SScott Kruger # At this point, we are working with strings and not lists 498e4653983SScott Kruger if 'requires' in testDict['build']: 4990338c944SBarry Smith if 'todo' in testDict['build']['requires'] or 'TODO' in testDict['build']['requires']: 5000338c944SBarry Smith raise Exception("Error: Do not list 'TODO' in requires: list it as a field, for example, TODO: XXX is currently unstable\n"+srcfile) 5012bbaaa9fSScott Kruger addToRunRequirements=testDict['build']['requires'] 5022bbaaa9fSScott Kruger # People put datafilespath into build, but it needs to be a runtime 503e4653983SScott Kruger if 'datafilespath' in testDict['build']['requires']: 504e4653983SScott Kruger newreqs=re.sub('datafilespath','',testDict['build']['requires']) 505e4653983SScott Kruger testDict['build']['requires']=newreqs.strip() 506e4653983SScott Kruger 50729921a8fSScott Kruger # Now go through each test. First elem in split is blank 508e53dc769SScott Kruger for test in re.split("\ntest(?:set)?:",newTestStr)[1:]: 5096cecdbdcSScott Kruger testnames,subdicts=parseTest(test,srcfile,verbosity) 51044776d8cSScott Kruger for i in range(len(testnames)): 5115b6bfdb9SJed Brown if testnames[i] in testDict: 5121acf9037SMatthew G. Knepley raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile) 513e4653983SScott Kruger # Add in build requirements that need to be moved 514e4653983SScott Kruger if addToRunRequirements: 515e4653983SScott Kruger if 'requires' in subdicts[i]: 5162bbaaa9fSScott Kruger subdicts[i]['requires']+=' '+addToRunRequirements 517e4653983SScott Kruger else: 518e4653983SScott Kruger subdicts[i]['requires']=addToRunRequirements 51944776d8cSScott Kruger testDict[testnames[i]]=subdicts[i] 52029921a8fSScott Kruger 52129921a8fSScott Kruger return testDict 52229921a8fSScott Kruger 5236cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity): 52429921a8fSScott Kruger """ 52529921a8fSScott Kruger Parse single example files and return dictionary of the form: 52629921a8fSScott Kruger testDict[srcfile][test][subtest] 52729921a8fSScott Kruger """ 52829921a8fSScott Kruger debug=False 529cadd188bSScott Kruger basename=os.path.basename(srcfile) 530cadd188bSScott Kruger if basename=='makefile': return {} 531cadd188bSScott Kruger 53229921a8fSScott Kruger curdir=os.path.realpath(os.path.curdir) 53329921a8fSScott Kruger basedir=os.path.dirname(os.path.realpath(srcfile)) 53429921a8fSScott Kruger os.chdir(basedir) 53529921a8fSScott Kruger 53629921a8fSScott Kruger testDict={} 537cadd188bSScott Kruger sh=open(basename,"r"); fileStr=sh.read(); sh.close() 538ef704b63SScott Kruger # Handle Windows issues 539ef704b63SScott Kruger if not os.linesep == "\n": fileStr=fileStr.replace(os.linesep,"\n") 54029921a8fSScott Kruger 54129921a8fSScott Kruger ## Start with doing the tests 54229921a8fSScott Kruger # 54329921a8fSScott Kruger fsplit=fileStr.split("/*TEST\n")[1:] 54444776d8cSScott Kruger fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1 54529921a8fSScott Kruger # Allow for multiple "/*TEST" blocks even though it really should be 5466f029658SMatthew G. Knepley # one 54729921a8fSScott Kruger srcTests=[] 54829921a8fSScott Kruger for t in fsplit: srcTests.append(t.split("TEST*/")[0]) 54929921a8fSScott Kruger testString=" ".join(srcTests) 55044776d8cSScott Kruger flen=len(testString.split("\n")) 55144776d8cSScott Kruger fend=fstart+flen-1 55244776d8cSScott Kruger fileNums=range(fstart,fend) 5536cecdbdcSScott Kruger testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity) 554aec507c4SScott Kruger # Massage dictionary for build requirements 555aec507c4SScott Kruger if 'build' in testDict[basename]: 556aec507c4SScott Kruger testDict[basename].update(testDict[basename]['build']) 557aec507c4SScott Kruger del testDict[basename]['build'] 55829921a8fSScott Kruger 55929921a8fSScott Kruger os.chdir(curdir) 56029921a8fSScott Kruger return testDict 56129921a8fSScott Kruger 5626cecdbdcSScott Krugerdef parseTestDir(directory,verbosity): 56329921a8fSScott Kruger """ 56429921a8fSScott Kruger Parse single example files and return dictionary of the form: 56529921a8fSScott Kruger testDict[srcfile][test][subtest] 56629921a8fSScott Kruger """ 56729921a8fSScott Kruger curdir=os.path.realpath(os.path.curdir) 56829921a8fSScott Kruger basedir=os.path.realpath(directory) 56929921a8fSScott Kruger os.chdir(basedir) 57029921a8fSScott Kruger 57129921a8fSScott Kruger tDict={} 57209a6cbfcSBernhard M. Wiedemann for test_file in sorted(glob.glob("new_ex*.*")): 5736cecdbdcSScott Kruger tDict.update(parseTestFile(test_file,verbosity)) 57429921a8fSScott Kruger 57529921a8fSScott Kruger os.chdir(curdir) 57629921a8fSScott Kruger return tDict 57729921a8fSScott Kruger 57829921a8fSScott Krugerdef printExParseDict(rDict): 57929921a8fSScott Kruger """ 58029921a8fSScott Kruger This is useful for debugging 58129921a8fSScott Kruger """ 58229921a8fSScott Kruger indent=" " 58329921a8fSScott Kruger for sfile in rDict: 584cadd188bSScott Kruger print(sfile) 585c3d83d22SScott Kruger sortkeys=list(rDict[sfile].keys()) 58644776d8cSScott Kruger sortkeys.sort() 58744776d8cSScott Kruger for runex in sortkeys: 58869fa9ab3SScott Kruger if runex == 'requires': 58969fa9ab3SScott Kruger print(indent+runex+':'+str(rDict[sfile][runex])) 59069fa9ab3SScott Kruger continue 591cadd188bSScott Kruger print(indent+runex) 5925b6bfdb9SJed Brown if type(rDict[sfile][runex])==bytes: 593cadd188bSScott Kruger print(indent*2+rDict[sfile][runex]) 59429921a8fSScott Kruger else: 59529921a8fSScott Kruger for var in rDict[sfile][runex]: 59644776d8cSScott Kruger if var.startswith("test"): continue 597cadd188bSScott Kruger print(indent*2+var+": "+str(rDict[sfile][runex][var])) 5985b6bfdb9SJed Brown if 'subtests' in rDict[sfile][runex]: 59944776d8cSScott Kruger for var in rDict[sfile][runex]['subtests']: 600cadd188bSScott Kruger print(indent*2+var) 60129921a8fSScott Kruger for var2 in rDict[sfile][runex][var]: 602cadd188bSScott Kruger print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2])) 603cadd188bSScott Kruger print("\n") 60429921a8fSScott Kruger return 60529921a8fSScott Kruger 60629921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0): 60729921a8fSScott Kruger 60829921a8fSScott Kruger if directory: 6096cecdbdcSScott Kruger tDict=parseTestDir(directory,verbosity) 61029921a8fSScott Kruger else: 6116cecdbdcSScott Kruger tDict=parseTestFile(test_file,verbosity) 61229921a8fSScott Kruger if verbosity>0: printExParseDict(tDict) 61329921a8fSScott Kruger 61429921a8fSScott Kruger return 61529921a8fSScott Kruger 61629921a8fSScott Krugerif __name__ == '__main__': 61729921a8fSScott Kruger import optparse 61829921a8fSScott Kruger parser = optparse.OptionParser() 61929921a8fSScott Kruger parser.add_option('-d', '--directory', dest='directory', 62029921a8fSScott Kruger default="", help='Directory containing files to parse') 62129921a8fSScott Kruger parser.add_option('-t', '--test_file', dest='test_file', 62229921a8fSScott Kruger default="", help='Test file, e.g., ex1.c, to parse') 62329921a8fSScott Kruger parser.add_option('-v', '--verbosity', dest='verbosity', 62429921a8fSScott Kruger help='Verbosity of output by level: 1, 2, or 3', default=0) 62529921a8fSScott Kruger opts, extra_args = parser.parse_args() 62629921a8fSScott Kruger 62729921a8fSScott Kruger if extra_args: 62829921a8fSScott Kruger import sys 62929921a8fSScott Kruger sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args)) 63029921a8fSScott Kruger exit(1) 63129921a8fSScott Kruger if not opts.test_file and not opts.directory: 632cadd188bSScott Kruger print("test file or directory is required") 63329921a8fSScott Kruger parser.print_usage() 63429921a8fSScott Kruger sys.exit() 63529921a8fSScott Kruger 63629921a8fSScott Kruger # Need verbosity to be an integer 63729921a8fSScott Kruger try: 63829921a8fSScott Kruger verbosity=int(opts.verbosity) 63929921a8fSScott Kruger except: 64029921a8fSScott Kruger raise Exception("Error: Verbosity must be integer") 64129921a8fSScott Kruger 64229921a8fSScott Kruger main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity) 643