129921a8fSScott Kruger#!/usr/bin/env python 229921a8fSScott Kruger""" 329921a8fSScott KrugerParse the test file and return a dictionary. 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 4229921a8fSScott Krugersys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) 4329921a8fSScott Kruger 4429921a8fSScott Krugerimport inspect 4529921a8fSScott Krugerthisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 4629921a8fSScott Krugermaintdir=os.path.join(os.path.join(os.path.dirname(thisscriptdir),'bin'),'maint') 4729921a8fSScott Krugersys.path.insert(0,maintdir) 4829921a8fSScott Kruger 4929921a8fSScott Kruger# These are special keys describing build 5029921a8fSScott Krugerbuildkeys="requires TODO SKIP depends".split() 5129921a8fSScott Kruger 52*2bbaaa9fSScott Krugeracceptedkeys="test nsize requires command suffix diff_args args filter filter_output localrunfiles comments TODO SKIP output_file timeoutfactor".split() 53*2bbaaa9fSScott Krugerappendlist="args diff_args requires comments".split() 5468a9e459SScott Kruger 5568a9e459SScott Krugerimport re 5668a9e459SScott Kruger 575e361860SScott Krugerdef getDefaultOutputFileRoot(testname): 585e361860SScott Kruger """ 595e361860SScott Kruger Given testname, give DefaultRoot and DefaultOutputFilename 605e361860SScott Kruger e.g., runex1 gives ex1_1, output/ex1_1.out 615e361860SScott Kruger """ 625e361860SScott Kruger defroot=(re.sub("run","",testname) if testname.startswith("run") else testname) 635e361860SScott Kruger if not "_" in defroot: defroot=defroot+"_1" 645e361860SScott Kruger return defroot 655e361860SScott Kruger 6644776d8cSScott Krugerdef _stripIndent(block,srcfile,entireBlock=False,fileNums=[]): 6729921a8fSScott Kruger """ 6829921a8fSScott Kruger Go through and remove a level of indentation 6929921a8fSScott Kruger Also strip of trailing whitespace 7029921a8fSScott Kruger """ 7129921a8fSScott Kruger # The first entry should be test: but it might be indented. 7229921a8fSScott Kruger ext=os.path.splitext(srcfile)[1] 7344776d8cSScott Kruger stripstr=" " 7466db876fSScott Kruger if len(fileNums)>0: lineNum=fileNums[0]-1 758ccd5183SScott Kruger for lline in block.split("\n"): 7666db876fSScott Kruger if len(fileNums)>0: lineNum+=1 778ccd5183SScott Kruger line=lline[1:] if lline.startswith("!") else lline 7829921a8fSScott Kruger if not line.strip(): continue 79c4b80baaSScott Kruger if line.strip().startswith('#'): continue 8044776d8cSScott Kruger if entireBlock: 8144776d8cSScott Kruger var=line.split(":")[0].strip() 82aec507c4SScott Kruger if not var in ['test','testset','build']: 8366db876fSScott Kruger raise Exception("Formatting error: Cannot find test in file: "+srcfile+" at line: "+str(lineNum)+"\n") 8429921a8fSScott Kruger nspace=len(line)-len(line.lstrip(stripstr)) 8529921a8fSScott Kruger newline=line[nspace:] 8629921a8fSScott Kruger break 8729921a8fSScott Kruger 8829921a8fSScott Kruger # Strip off any indentation for the whole string and any trailing 8929921a8fSScott Kruger # whitespace for convenience 9029921a8fSScott Kruger newTestStr="\n" 9144776d8cSScott Kruger if len(fileNums)>0: lineNum=fileNums[0]-1 9244776d8cSScott Kruger firstPass=True 938ccd5183SScott Kruger for lline in block.split("\n"): 9444776d8cSScott Kruger if len(fileNums)>0: lineNum+=1 958ccd5183SScott Kruger line=lline[1:] if lline.startswith("!") else lline 9629921a8fSScott Kruger if not line.strip(): continue 97c4b80baaSScott Kruger if line.strip().startswith('#'): 98c4b80baaSScott Kruger newTestStr+=line+'\n' 99c4b80baaSScott Kruger else: 10029921a8fSScott Kruger newline=line[nspace:] 101c4b80baaSScott Kruger newTestStr+=newline.rstrip()+"\n" 10244776d8cSScott Kruger # Do some basic indentation checks 10344776d8cSScott Kruger if entireBlock: 10444776d8cSScott Kruger # Don't need to check comment lines 10544776d8cSScott Kruger if line.strip().startswith('#'): continue 10644776d8cSScott Kruger if not newline.startswith(" "): 10744776d8cSScott Kruger var=newline.split(":")[0].strip() 108aec507c4SScott Kruger if not var in ['test','testset','build']: 10944776d8cSScott Kruger err="Formatting error in file "+srcfile+" at line: " +line+"\n" 11044776d8cSScott Kruger if len(fileNums)>0: 11144776d8cSScott Kruger raise Exception(err+"Check indentation at line number: "+str(lineNum)) 11244776d8cSScott Kruger else: 11344776d8cSScott Kruger raise Exception(err) 11444776d8cSScott Kruger else: 11544776d8cSScott Kruger var=line.split(":")[0].strip() 116aec507c4SScott Kruger if var in ['test','testset','build']: 11744776d8cSScott Kruger subnspace=len(line)-len(line.lstrip(stripstr)) 11844776d8cSScott Kruger if firstPass: 11944776d8cSScott Kruger firstsubnspace=subnspace 12044776d8cSScott Kruger firstPass=False 12144776d8cSScott Kruger else: 12244776d8cSScott Kruger if firstsubnspace!=subnspace: 12344776d8cSScott Kruger err="Formatting subtest error in file "+srcfile+" at line: " +line+"\n" 12444776d8cSScott Kruger if len(fileNums)>0: 12544776d8cSScott Kruger raise Exception(err+"Check indentation at line number: "+str(lineNum)) 12644776d8cSScott Kruger else: 12744776d8cSScott Kruger raise Exception(err) 12844776d8cSScott Kruger 12943c6e11bSMatthew G. Knepley # Allow line continuation character '\' 13043c6e11bSMatthew G. Knepley return newTestStr.replace('\\\n', ' ') 13129921a8fSScott Kruger 132aae9f2d9SScott Krugerdef parseLoopArgs(varset): 13344776d8cSScott Kruger """ 134aae9f2d9SScott Kruger Given: String containing loop variables 135aae9f2d9SScott Kruger Return: tuple containing separate/shared and string of loop vars 13644776d8cSScott Kruger """ 137a449fbaeSJed Brown keynm=varset.split("{{")[0].strip().lstrip('-') 138aae9f2d9SScott Kruger if not keynm.strip(): keynm='nsize' 139aae9f2d9SScott Kruger lvars=varset.split('{{')[1].split('}')[0] 140aae9f2d9SScott Kruger suffx=varset.split('{{')[1].split('}')[1] 141aae9f2d9SScott Kruger ftype='separate' if suffx.startswith('separate') else 'shared' 142aae9f2d9SScott Kruger return keynm,lvars,ftype 14344776d8cSScott Kruger 144*2bbaaa9fSScott Krugerdef _getLoopVars(testDict): 145e53dc769SScott Kruger """ 146e53dc769SScott Kruger Given: dictionary that may have 147e53dc769SScott Kruger Return: Variables that cause a test split 148e53dc769SScott Kruger """ 149e53dc769SScott Kruger vals=None 150*2bbaaa9fSScott Kruger loopVars={} 151*2bbaaa9fSScott Kruger loopVars['separate']=[] 152*2bbaaa9fSScott Kruger loopVars['shared']=[] 153e53dc769SScott Kruger # Check nsize 1545b6bfdb9SJed Brown if 'nsize' in testDict: 155e53dc769SScott Kruger varset=testDict['nsize'] 156aae9f2d9SScott Kruger if '{{' in varset: 157aae9f2d9SScott Kruger keynm,lvars,ftype=parseLoopArgs(varset) 158*2bbaaa9fSScott Kruger if ftype=='separate': loopVars['separate'].append(keynm) 159e53dc769SScott Kruger 160e53dc769SScott Kruger # Now check args 161*2bbaaa9fSScott Kruger if 'args' not in testDict: return loopVars 162d87d9516SStefano Zampini for varset in re.split('(^|\W)-(?=[a-zA-Z])',testDict['args']): 163e53dc769SScott Kruger if not varset.strip(): continue 164aae9f2d9SScott Kruger if '{{' in varset: 165e53dc769SScott Kruger # Assuming only one for loop per var specification 166aae9f2d9SScott Kruger keynm,lvars,ftype=parseLoopArgs(varset) 167*2bbaaa9fSScott Kruger loopVars[ftype].append(keynm) 168e53dc769SScott Kruger 169*2bbaaa9fSScott Kruger return loopVars 170e53dc769SScott Kruger 171*2bbaaa9fSScott Krugerdef _getNewArgs(args,separate=True): 1724d82b48cSScott Kruger """ 1734d82b48cSScott Kruger Given: String that has args that might have loops in them 1744d82b48cSScott Kruger Return: All of the arguments/values that do not have 1754d82b48cSScott Kruger for 'separate output' in for loops 176*2bbaaa9fSScott Kruger unless separate=False 1774d82b48cSScott Kruger """ 1784d82b48cSScott Kruger newargs='' 1794d82b48cSScott Kruger if not args.strip(): return args 180d87d9516SStefano Zampini for varset in re.split('(^|\W)-(?=[a-zA-Z])',args): 1814d82b48cSScott Kruger if not varset.strip(): continue 182*2bbaaa9fSScott Kruger if '{{' in varset: 183*2bbaaa9fSScott Kruger if separate: 184*2bbaaa9fSScott Kruger if 'separate' in varset: continue 185*2bbaaa9fSScott Kruger else: 186*2bbaaa9fSScott Kruger if 'separate' not in varset: continue 187*2bbaaa9fSScott Kruger 1884d82b48cSScott Kruger newargs+="-"+varset.strip()+" " 1894d82b48cSScott Kruger 1904d82b48cSScott Kruger return newargs 1914d82b48cSScott Kruger 19244776d8cSScott Krugerdef _getVarVals(findvar,testDict): 19344776d8cSScott Kruger """ 19444776d8cSScott Kruger Given: variable that is either nsize or in args 1954d82b48cSScott Kruger Return: Values to loop over and the other arguments 1964d82b48cSScott Kruger Note that we keep the other arguments even if they have 1974d82b48cSScott Kruger for loops to enable stepping through all of the for lops 19844776d8cSScott Kruger """ 1994d82b48cSScott Kruger save_vals=None 20044776d8cSScott Kruger if findvar=='nsize': 201e53dc769SScott Kruger varset=testDict[findvar] 2024d82b48cSScott Kruger keynm,save_vals,ftype=parseLoopArgs('nsize '+varset) 20344776d8cSScott Kruger else: 20444776d8cSScott Kruger varlist=[] 20544776d8cSScott Kruger for varset in re.split('-(?=[a-zA-Z])',testDict['args']): 20644776d8cSScott Kruger if not varset.strip(): continue 2074d82b48cSScott Kruger if '{{' not in varset: continue 208aae9f2d9SScott Kruger keyvar,vals,ftype=parseLoopArgs(varset) 2094d82b48cSScott Kruger if keyvar==findvar: 2104d82b48cSScott Kruger save_vals=vals 21144776d8cSScott Kruger 2125b6bfdb9SJed Brown if not save_vals: raise Exception("Could not find separate_testvar: "+findvar) 2134d82b48cSScott Kruger return save_vals 21444776d8cSScott Kruger 2154f8a0bffSScott Krugerdef genTestsSeparateTestvars(intests,indicts,final=False): 21644776d8cSScott Kruger """ 217e53dc769SScott Kruger Given: testname, sdict with 'separate_testvars 21844776d8cSScott Kruger Return: testnames,sdicts: List of generated tests 2194d82b48cSScott Kruger The tricky part here is the {{ ... }separate output} 2204d82b48cSScott Kruger that can be used multiple times 22144776d8cSScott Kruger """ 22244776d8cSScott Kruger testnames=[]; sdicts=[] 22344776d8cSScott Kruger for i in range(len(intests)): 22444776d8cSScott Kruger testname=intests[i]; sdict=indicts[i]; i+=1 225*2bbaaa9fSScott Kruger loopVars=_getLoopVars(sdict) 226*2bbaaa9fSScott Kruger if len(loopVars['shared'])>0 and not final: 227*2bbaaa9fSScott Kruger # Need to remove shared loop vars and push down to subtests 228*2bbaaa9fSScott Kruger if 'subtests' in sdict: 229*2bbaaa9fSScott Kruger for varset in re.split('(^|\W)-(?=[a-zA-Z])',sdict['args']): 230*2bbaaa9fSScott Kruger if '{{' in varset: 231*2bbaaa9fSScott Kruger for stest in sdict['subtests']: 232*2bbaaa9fSScott Kruger if 'args' in sdict[stest]: 233*2bbaaa9fSScott Kruger sdict[stest]['args']+=' -'+varset 234*2bbaaa9fSScott Kruger else: 235*2bbaaa9fSScott Kruger sdict[stest]['args']="-"+varset 236*2bbaaa9fSScott Kruger sdict['args']=_getNewArgs(sdict['args'],separate=False) 237*2bbaaa9fSScott Kruger if len(loopVars['separate'])>0: 2384d82b48cSScott Kruger sep_dicts=[sdict.copy()] 2394d82b48cSScott Kruger if 'args' in sep_dicts[0]: 2404d82b48cSScott Kruger sep_dicts[0]['args']=_getNewArgs(sdict['args']) 2414d82b48cSScott Kruger sep_testnames=[testname] 242*2bbaaa9fSScott Kruger for kvar in loopVars['separate']: 2434d82b48cSScott Kruger kvals=_getVarVals(kvar,sdict) 2444d82b48cSScott Kruger 2454d82b48cSScott Kruger # Have to do loop over previous var/val combos as well 2464d82b48cSScott Kruger # and accumulate as we go 2474d82b48cSScott Kruger val_testnames=[]; val_dicts=[] 24844776d8cSScott Kruger for val in kvals.split(): 249541979b6SScott Kruger gensuffix="_"+kvar+"-"+val.replace(',','__') 2504d82b48cSScott Kruger for kvaltestnm in sep_testnames: 2514d82b48cSScott Kruger val_testnames.append(kvaltestnm+gensuffix) 2524d82b48cSScott Kruger for kv in sep_dicts: 2534d82b48cSScott Kruger kvardict=kv.copy() 2544d82b48cSScott Kruger # If the last var then we have the final version 2554d82b48cSScott Kruger if 'suffix' in sdict: 2564d82b48cSScott Kruger kvardict['suffix']+=gensuffix 2570bcc1aabSScott Kruger else: 2580bcc1aabSScott Kruger kvardict['suffix']=gensuffix 25944776d8cSScott Kruger if kvar=='nsize': 26044776d8cSScott Kruger kvardict[kvar]=val 26144776d8cSScott Kruger else: 2624d82b48cSScott Kruger kvardict['args']+="-"+kvar+" "+val+" " 2634d82b48cSScott Kruger val_dicts.append(kvardict) 2644d82b48cSScott Kruger sep_testnames=val_testnames 2654d82b48cSScott Kruger sep_dicts=val_dicts 2664d82b48cSScott Kruger testnames+=sep_testnames 2674d82b48cSScott Kruger sdicts+=sep_dicts 26844776d8cSScott Kruger else: 2694f8a0bffSScott Kruger # These are plain vanilla tests (no subtests, no loops) that 2704f8a0bffSScott Kruger # do not have a suffix. This makes the targets match up with 2714f8a0bffSScott Kruger # the output file (testname_1.out) 2724f8a0bffSScott Kruger if final: 2734f8a0bffSScott Kruger if '_' not in testname: testname+='_1' 27444776d8cSScott Kruger testnames.append(testname) 27544776d8cSScott Kruger sdicts.append(sdict) 27644776d8cSScott Kruger return testnames,sdicts 27744776d8cSScott Kruger 27844776d8cSScott Krugerdef genTestsSubtestSuffix(testnames,sdicts): 27944776d8cSScott Kruger """ 28044776d8cSScott Kruger Given: testname, sdict with separate_testvars 28144776d8cSScott Kruger Return: testnames,sdicts: List of generated tests 28244776d8cSScott Kruger """ 28344776d8cSScott Kruger tnms=[]; sdcts=[] 28444776d8cSScott Kruger for i in range(len(testnames)): 28544776d8cSScott Kruger testname=testnames[i] 28644776d8cSScott Kruger rmsubtests=[]; keepSubtests=False 2875b6bfdb9SJed Brown if 'subtests' in sdicts[i]: 28844776d8cSScott Kruger for stest in sdicts[i]["subtests"]: 2895b6bfdb9SJed Brown if 'suffix' in sdicts[i][stest]: 29044776d8cSScott Kruger rmsubtests.append(stest) 29144776d8cSScott Kruger gensuffix="_"+sdicts[i][stest]['suffix'] 29244776d8cSScott Kruger newtestnm=testname+gensuffix 29344776d8cSScott Kruger tnms.append(newtestnm) 29444776d8cSScott Kruger newsdict=sdicts[i].copy() 29544776d8cSScott Kruger del newsdict['subtests'] 29644776d8cSScott Kruger # Have to hand update 29744776d8cSScott Kruger # Append 29844776d8cSScott Kruger for kup in appendlist: 2995b6bfdb9SJed Brown if kup in sdicts[i][stest]: 3005b6bfdb9SJed Brown if kup in sdicts[i]: 30144776d8cSScott Kruger newsdict[kup]=sdicts[i][kup]+" "+sdicts[i][stest][kup] 30244776d8cSScott Kruger else: 30344776d8cSScott Kruger newsdict[kup]=sdicts[i][stest][kup] 30444776d8cSScott Kruger # Promote 30544776d8cSScott Kruger for kup in acceptedkeys: 30644776d8cSScott Kruger if kup in appendlist: continue 3075b6bfdb9SJed Brown if kup in sdicts[i][stest]: 30844776d8cSScott Kruger newsdict[kup]=sdicts[i][stest][kup] 30944776d8cSScott Kruger # Cleanup 31044776d8cSScott Kruger for st in sdicts[i]["subtests"]: del newsdict[st] 31144776d8cSScott Kruger sdcts.append(newsdict) 31244776d8cSScott Kruger else: 31344776d8cSScott Kruger keepSubtests=True 31444776d8cSScott Kruger else: 31544776d8cSScott Kruger tnms.append(testnames[i]) 31644776d8cSScott Kruger sdcts.append(sdicts[i]) 3170bcc1aabSScott Kruger # If a subtest without a suffix exists, then save it 31844776d8cSScott Kruger if keepSubtests: 31944776d8cSScott Kruger tnms.append(testnames[i]) 3200bcc1aabSScott Kruger newsdict=sdicts[i].copy() 3210bcc1aabSScott Kruger # Prune the tests to prepare for keeping 3220bcc1aabSScott Kruger for rmtest in rmsubtests: 3230bcc1aabSScott Kruger newsdict['subtests'].remove(rmtest) 3240bcc1aabSScott Kruger del newsdict[rmtest] 3250bcc1aabSScott Kruger sdcts.append(newsdict) 32644776d8cSScott Kruger i+=1 32744776d8cSScott Kruger return tnms,sdcts 32844776d8cSScott Kruger 32944776d8cSScott Krugerdef splitTests(testname,sdict): 33044776d8cSScott Kruger """ 33144776d8cSScott Kruger Given: testname and YAML-generated dictionary 33244776d8cSScott Kruger Return: list of names and dictionaries corresponding to each test 33344776d8cSScott Kruger given that the YAML language allows for multiple tests 33444776d8cSScott Kruger """ 33544776d8cSScott Kruger 33644776d8cSScott Kruger # Order: Parent sep_tv, subtests suffix, subtests sep_tv 33744776d8cSScott Kruger testnames,sdicts=genTestsSeparateTestvars([testname],[sdict]) 33844776d8cSScott Kruger testnames,sdicts=genTestsSubtestSuffix(testnames,sdicts) 3394f8a0bffSScott Kruger testnames,sdicts=genTestsSeparateTestvars(testnames,sdicts,final=True) 34044776d8cSScott Kruger 34144776d8cSScott Kruger # Because I am altering the list, I do this in passes. Inelegant 34244776d8cSScott Kruger 34344776d8cSScott Kruger return testnames, sdicts 34444776d8cSScott Kruger 3456cecdbdcSScott Krugerdef parseTest(testStr,srcfile,verbosity): 34629921a8fSScott Kruger """ 34729921a8fSScott Kruger This parses an individual test 34829921a8fSScott Kruger YAML is hierarchial so should use a state machine in the general case, 34953f2a965SBarry Smith but in practice we only support two levels of test: 35029921a8fSScott Kruger """ 35129921a8fSScott Kruger basename=os.path.basename(srcfile) 35229921a8fSScott Kruger # Handle the new at the begininng 35329921a8fSScott Kruger bn=re.sub("new_","",basename) 35429921a8fSScott Kruger # This is the default 35529921a8fSScott Kruger testname="run"+os.path.splitext(bn)[0] 35629921a8fSScott Kruger 35729921a8fSScott Kruger # Tests that have default everything (so empty effectively) 358*2bbaaa9fSScott Kruger if len(testStr)==0: 359*2bbaaa9fSScott Kruger if '_' not in testname: testname+='_1' 360*2bbaaa9fSScott Kruger return [testname], [{}] 36129921a8fSScott Kruger 36229921a8fSScott Kruger striptest=_stripIndent(testStr,srcfile) 36329921a8fSScott Kruger 36429921a8fSScott Kruger # go through and parse 36529921a8fSScott Kruger subtestnum=0 36629921a8fSScott Kruger subdict={} 36768a9e459SScott Kruger comments=[] 36868a9e459SScott Kruger indentlevel=0 36968a9e459SScott Kruger for ln in striptest.split("\n"): 370c4b80baaSScott Kruger line=ln.split('#')[0].rstrip() 371cadd188bSScott Kruger if verbosity>2: print(line) 3720bcc1aabSScott Kruger comment=("" if len(ln.split("#"))>0 else " ".join(ln.split("#")[1:]).strip()) 37368a9e459SScott Kruger if comment: comments.append(comment) 37429921a8fSScott Kruger if not line.strip(): continue 37544776d8cSScott Kruger lsplit=line.split(':') 37644776d8cSScott Kruger if len(lsplit)==0: raise Exception("Missing : in line: "+line) 37744776d8cSScott Kruger indentcount=lsplit[0].count(" ") 37844776d8cSScott Kruger var=lsplit[0].strip() 37940ae0433SScott Kruger val=line[line.find(':')+1:].strip() 38044776d8cSScott Kruger if not var in acceptedkeys: raise Exception("Not a defined key: "+var+" from: "+line) 38129921a8fSScott Kruger # Start by seeing if we are in a subtest 38229921a8fSScott Kruger if line.startswith(" "): 383ecc1beb5SScott Kruger if var in subdict[subtestname]: 384ecc1beb5SScott Kruger subdict[subtestname][var]+=" "+val 385ecc1beb5SScott Kruger else: 386c0658d2aSScott Kruger subdict[subtestname][var]=val 38768a9e459SScott Kruger if not indentlevel: indentlevel=indentcount 388cadd188bSScott Kruger #if indentlevel!=indentcount: print("Error in indentation:", ln) 38929921a8fSScott Kruger # Determine subtest name and make dict 39029921a8fSScott Kruger elif var=="test": 39129921a8fSScott Kruger subtestname="test"+str(subtestnum) 39229921a8fSScott Kruger subdict[subtestname]={} 3935b6bfdb9SJed Brown if "subtests" not in subdict: subdict["subtests"]=[] 39429921a8fSScott Kruger subdict["subtests"].append(subtestname) 39529921a8fSScott Kruger subtestnum=subtestnum+1 39668a9e459SScott Kruger # The rest are easy 39729921a8fSScott Kruger else: 39844776d8cSScott Kruger # For convenience, it is sometimes convenient to list twice 3995b6bfdb9SJed Brown if var in subdict: 40044776d8cSScott Kruger if var in appendlist: 40144776d8cSScott Kruger subdict[var]+=" "+val 40244776d8cSScott Kruger else: 40344776d8cSScott Kruger raise Exception(var+" entered twice: "+line) 40444776d8cSScott Kruger else: 405c0658d2aSScott Kruger subdict[var]=val 40629921a8fSScott Kruger if var=="suffix": 40729921a8fSScott Kruger if len(val)>0: 4084f8a0bffSScott Kruger testname+="_"+val 40929921a8fSScott Kruger 41068a9e459SScott Kruger if len(comments): subdict['comments']="\n".join(comments).lstrip("\n") 4114f8a0bffSScott Kruger 4124f8a0bffSScott Kruger # A test block can create multiple tests. This does that logic 41344776d8cSScott Kruger testnames,subdicts=splitTests(testname,subdict) 41444776d8cSScott Kruger return testnames,subdicts 41529921a8fSScott Kruger 4166cecdbdcSScott Krugerdef parseTests(testStr,srcfile,fileNums,verbosity): 41729921a8fSScott Kruger """ 41829921a8fSScott Kruger Parse the yaml string describing tests and return 41929921a8fSScott Kruger a dictionary with the info in the form of: 42029921a8fSScott Kruger testDict[test][subtest] 42129921a8fSScott Kruger This is an inelegant parser as we do not wish to 42229921a8fSScott Kruger introduce a new dependency by bringing in pyyaml. 42329921a8fSScott Kruger The advantage is that validation can be done as 42429921a8fSScott Kruger it is parsed (e.g., 'test' is the only top-level node) 42529921a8fSScott Kruger """ 42629921a8fSScott Kruger 42729921a8fSScott Kruger testDict={} 42829921a8fSScott Kruger 42929921a8fSScott Kruger # The first entry should be test: but it might be indented. 43044776d8cSScott Kruger newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums) 431cadd188bSScott Kruger if verbosity>2: print(srcfile) 43229921a8fSScott Kruger 433e4653983SScott Kruger ## Check and see if we have build requirements 434e4653983SScott Kruger addToRunRequirements=None 435aec507c4SScott Kruger if "\nbuild:" in newTestStr: 436aec507c4SScott Kruger testDict['build']={} 437aec507c4SScott Kruger # The file info is already here and need to append 438aec507c4SScott Kruger Part1=newTestStr.split("build:")[1] 439aec507c4SScott Kruger fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0] 440aec507c4SScott Kruger for bkey in buildkeys: 441aec507c4SScott Kruger if bkey+":" in fileInfo: 442aec507c4SScott Kruger testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip() 443aec507c4SScott Kruger #if verbosity>1: bkey+": "+testDict['build'][bkey] 444e4653983SScott Kruger # If a runtime requires are put into build, push them down to all run tests 445e4653983SScott Kruger # At this point, we are working with strings and not lists 446e4653983SScott Kruger if 'requires' in testDict['build']: 447*2bbaaa9fSScott Kruger addToRunRequirements=testDict['build']['requires'] 448*2bbaaa9fSScott Kruger # People put datafilespath into build, but it needs to be a runtime 449e4653983SScott Kruger if 'datafilespath' in testDict['build']['requires']: 450e4653983SScott Kruger newreqs=re.sub('datafilespath','',testDict['build']['requires']) 451e4653983SScott Kruger testDict['build']['requires']=newreqs.strip() 452e4653983SScott Kruger 453aec507c4SScott Kruger 45429921a8fSScott Kruger # Now go through each test. First elem in split is blank 455e53dc769SScott Kruger for test in re.split("\ntest(?:set)?:",newTestStr)[1:]: 4566cecdbdcSScott Kruger testnames,subdicts=parseTest(test,srcfile,verbosity) 45744776d8cSScott Kruger for i in range(len(testnames)): 4585b6bfdb9SJed Brown if testnames[i] in testDict: 4591acf9037SMatthew G. Knepley raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile) 460e4653983SScott Kruger # Add in build requirements that need to be moved 461e4653983SScott Kruger if addToRunRequirements: 462e4653983SScott Kruger if 'requires' in subdicts[i]: 463*2bbaaa9fSScott Kruger subdicts[i]['requires']+=' '+addToRunRequirements 464e4653983SScott Kruger else: 465e4653983SScott Kruger subdicts[i]['requires']=addToRunRequirements 46644776d8cSScott Kruger testDict[testnames[i]]=subdicts[i] 46729921a8fSScott Kruger 46829921a8fSScott Kruger return testDict 46929921a8fSScott Kruger 4706cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity): 47129921a8fSScott Kruger """ 47229921a8fSScott Kruger Parse single example files and return dictionary of the form: 47329921a8fSScott Kruger testDict[srcfile][test][subtest] 47429921a8fSScott Kruger """ 47529921a8fSScott Kruger debug=False 476cadd188bSScott Kruger basename=os.path.basename(srcfile) 477cadd188bSScott Kruger if basename=='makefile': return {} 478cadd188bSScott Kruger 47929921a8fSScott Kruger curdir=os.path.realpath(os.path.curdir) 48029921a8fSScott Kruger basedir=os.path.dirname(os.path.realpath(srcfile)) 48129921a8fSScott Kruger os.chdir(basedir) 48229921a8fSScott Kruger 48329921a8fSScott Kruger testDict={} 484cadd188bSScott Kruger sh=open(basename,"r"); fileStr=sh.read(); sh.close() 48529921a8fSScott Kruger 48629921a8fSScott Kruger ## Start with doing the tests 48729921a8fSScott Kruger # 48829921a8fSScott Kruger fsplit=fileStr.split("/*TEST\n")[1:] 48944776d8cSScott Kruger fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1 49029921a8fSScott Kruger # Allow for multiple "/*TEST" blocks even though it really should be 4916f029658SMatthew G. Knepley # one 49229921a8fSScott Kruger srcTests=[] 49329921a8fSScott Kruger for t in fsplit: srcTests.append(t.split("TEST*/")[0]) 49429921a8fSScott Kruger testString=" ".join(srcTests) 49544776d8cSScott Kruger flen=len(testString.split("\n")) 49644776d8cSScott Kruger fend=fstart+flen-1 49744776d8cSScott Kruger fileNums=range(fstart,fend) 4986cecdbdcSScott Kruger testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity) 499aec507c4SScott Kruger # Massage dictionary for build requirements 500aec507c4SScott Kruger if 'build' in testDict[basename]: 501aec507c4SScott Kruger testDict[basename].update(testDict[basename]['build']) 502aec507c4SScott Kruger del testDict[basename]['build'] 50329921a8fSScott Kruger 50429921a8fSScott Kruger 50529921a8fSScott Kruger os.chdir(curdir) 50629921a8fSScott Kruger return testDict 50729921a8fSScott Kruger 5086cecdbdcSScott Krugerdef parseTestDir(directory,verbosity): 50929921a8fSScott Kruger """ 51029921a8fSScott Kruger Parse single example files and return dictionary of the form: 51129921a8fSScott Kruger testDict[srcfile][test][subtest] 51229921a8fSScott Kruger """ 51329921a8fSScott Kruger curdir=os.path.realpath(os.path.curdir) 51429921a8fSScott Kruger basedir=os.path.realpath(directory) 51529921a8fSScott Kruger os.chdir(basedir) 51629921a8fSScott Kruger 51729921a8fSScott Kruger tDict={} 51809a6cbfcSBernhard M. Wiedemann for test_file in sorted(glob.glob("new_ex*.*")): 5196cecdbdcSScott Kruger tDict.update(parseTestFile(test_file,verbosity)) 52029921a8fSScott Kruger 52129921a8fSScott Kruger os.chdir(curdir) 52229921a8fSScott Kruger return tDict 52329921a8fSScott Kruger 52429921a8fSScott Krugerdef printExParseDict(rDict): 52529921a8fSScott Kruger """ 52629921a8fSScott Kruger This is useful for debugging 52729921a8fSScott Kruger """ 52829921a8fSScott Kruger indent=" " 52929921a8fSScott Kruger for sfile in rDict: 530cadd188bSScott Kruger print(sfile) 531c3d83d22SScott Kruger sortkeys=list(rDict[sfile].keys()) 53244776d8cSScott Kruger sortkeys.sort() 53344776d8cSScott Kruger for runex in sortkeys: 53469fa9ab3SScott Kruger if runex == 'requires': 53569fa9ab3SScott Kruger print(indent+runex+':'+str(rDict[sfile][runex])) 53669fa9ab3SScott Kruger continue 537cadd188bSScott Kruger print(indent+runex) 5385b6bfdb9SJed Brown if type(rDict[sfile][runex])==bytes: 539cadd188bSScott Kruger print(indent*2+rDict[sfile][runex]) 54029921a8fSScott Kruger else: 54129921a8fSScott Kruger for var in rDict[sfile][runex]: 54244776d8cSScott Kruger if var.startswith("test"): continue 543cadd188bSScott Kruger print(indent*2+var+": "+str(rDict[sfile][runex][var])) 5445b6bfdb9SJed Brown if 'subtests' in rDict[sfile][runex]: 54544776d8cSScott Kruger for var in rDict[sfile][runex]['subtests']: 546cadd188bSScott Kruger print(indent*2+var) 54729921a8fSScott Kruger for var2 in rDict[sfile][runex][var]: 548cadd188bSScott Kruger print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2])) 549cadd188bSScott Kruger print("\n") 55029921a8fSScott Kruger return 55129921a8fSScott Kruger 55229921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0): 55329921a8fSScott Kruger 55429921a8fSScott Kruger if directory: 5556cecdbdcSScott Kruger tDict=parseTestDir(directory,verbosity) 55629921a8fSScott Kruger else: 5576cecdbdcSScott Kruger tDict=parseTestFile(test_file,verbosity) 55829921a8fSScott Kruger if verbosity>0: printExParseDict(tDict) 55929921a8fSScott Kruger 56029921a8fSScott Kruger return 56129921a8fSScott Kruger 56229921a8fSScott Krugerif __name__ == '__main__': 56329921a8fSScott Kruger import optparse 56429921a8fSScott Kruger parser = optparse.OptionParser() 56529921a8fSScott Kruger parser.add_option('-d', '--directory', dest='directory', 56629921a8fSScott Kruger default="", help='Directory containing files to parse') 56729921a8fSScott Kruger parser.add_option('-t', '--test_file', dest='test_file', 56829921a8fSScott Kruger default="", help='Test file, e.g., ex1.c, to parse') 56929921a8fSScott Kruger parser.add_option('-v', '--verbosity', dest='verbosity', 57029921a8fSScott Kruger help='Verbosity of output by level: 1, 2, or 3', default=0) 57129921a8fSScott Kruger opts, extra_args = parser.parse_args() 57229921a8fSScott Kruger 57329921a8fSScott Kruger if extra_args: 57429921a8fSScott Kruger import sys 57529921a8fSScott Kruger sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args)) 57629921a8fSScott Kruger exit(1) 57729921a8fSScott Kruger if not opts.test_file and not opts.directory: 578cadd188bSScott Kruger print("test file or directory is required") 57929921a8fSScott Kruger parser.print_usage() 58029921a8fSScott Kruger sys.exit() 58129921a8fSScott Kruger 58229921a8fSScott Kruger # Need verbosity to be an integer 58329921a8fSScott Kruger try: 58429921a8fSScott Kruger verbosity=int(opts.verbosity) 58529921a8fSScott Kruger except: 58629921a8fSScott Kruger raise Exception("Error: Verbosity must be integer") 58729921a8fSScott Kruger 58829921a8fSScott Kruger main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity) 589