1#!/usr/bin/env python 2""" 3Parse the test file and return a dictionary. 4 5Quick usage:: 6 7 bin/maint/testparse.py -t src/ksp/ksp/examples/tutorials/ex1.c 8 9From the command line, it prints out the dictionary. 10This is meant to be used by other scripts, but it is 11useful to debug individual files. 12 13 14 15Example language 16---------------- 17/*T 18 Concepts: 19 requires: moab 20T*/ 21 22 23 24/*TEST 25 26 test: 27 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 28 output_file: output/ex25_1.out 29 30 test: 31 suffix: 2 32 nsize: 2 33 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 34 35TEST*/ 36 37""" 38 39import os, re, glob, types 40from distutils.sysconfig import parse_makefile 41import sys 42import logging 43sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) 44 45import inspect 46thisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 47maintdir=os.path.join(os.path.join(os.path.dirname(thisscriptdir),'bin'),'maint') 48sys.path.insert(0,maintdir) 49 50# These are special keys describing build 51buildkeys="requires TODO SKIP depends".split() 52 53 54import re 55def extractForVars(argStr): 56 """ 57 Given: 'args: -bs {{1 2 3 4 5}} -pc_type {{cholesky sor}} -ksp_monitor' 58 Return: 59 args: -ksp_monitor 60 forlist="bs pc_type" # Don't assume OrderedDict 61 forargs[bs]['val']="1 2 3 4 5" 62 forargs[pc_type]['val']="cholesky sor" 63 """ 64 loopstr=argStr 65 forargs={} 66 forlist=[] 67 newargs="" 68 for varset in re.split('-(?=[a-zA-Z])',loopstr): 69 if not varset.strip(): continue 70 if len(re.findall('{{(.*?)}}',varset))>0: 71 # Assuming only one for loop per var specification 72 forvar=varset.split("{{")[0].strip() 73 forlist.append(forvar) 74 forargs[forvar]={} 75 forargs[forvar]['val']=re.findall('{{(.*?)}}',varset)[0] 76 else: 77 newargs=newargs+"-"+varset+" " 78 79 return forlist,forargs,newargs.strip() 80 81 82def _stripIndent(block,srcfile): 83 """ 84 Go through and remove a level of indentation 85 Also strip of trailing whitespace 86 """ 87 # The first entry should be test: but it might be indented. 88 ext=os.path.splitext(srcfile)[1] 89 for lline in block.split("\n"): 90 line=lline[1:] if lline.startswith("!") else lline 91 line=line.split('#')[0] 92 if not line.strip(): continue 93 stripstr=" " 94 nspace=len(line)-len(line.lstrip(stripstr)) 95 newline=line[nspace:] 96 break 97 98 # Strip off any indentation for the whole string and any trailing 99 # whitespace for convenience 100 newTestStr="\n" 101 for lline in block.split("\n"): 102 line=lline[1:] if lline.startswith("!") else lline 103 if not line.strip(): continue 104 newline=line[nspace:] 105 newTestStr=newTestStr+newline.rstrip()+"\n" 106 107 return newTestStr 108 109def parseTest(testStr,srcfile): 110 """ 111 This parses an individual test 112 YAML is hierarchial so should use a state machine in the general case, 113 but in practice we only support two levels of test: 114 """ 115 basename=os.path.basename(srcfile) 116 # Handle the new at the begininng 117 bn=re.sub("new_","",basename) 118 # This is the default 119 testname="run"+os.path.splitext(bn)[0] 120 121 # Tests that have default everything (so empty effectively) 122 if len(testStr)==0: return testname, {} 123 124 striptest=_stripIndent(testStr,srcfile) 125 126 # go through and parse 127 subtestnum=0 128 subdict={} 129 comments=[] 130 indentlevel=0 131 for ln in striptest.split("\n"): 132 line=ln.split('#')[0] 133 comment=("" if len(ln.split("#"))==1 else " ".join(ln.split("#")[1:]).strip()) 134 if comment: comments.append(comment) 135 if not line.strip(): continue 136 indentcount=line.split(":")[0].count(" ") 137 var=line.split(":")[0].strip() 138 val=line.split(":")[1].strip() 139 # Start by seeing if we are in a subtest 140 if line.startswith(" "): 141 subdict[subtestname][var]=val 142 if not indentlevel: indentlevel=indentcount 143 #if indentlevel!=indentcount: print "Error in indentation:", ln 144 # Determine subtest name and make dict 145 elif var=="test": 146 subtestname="test"+str(subtestnum) 147 subdict[subtestname]={} 148 if not subdict.has_key("subtests"): subdict["subtests"]=[] 149 subdict["subtests"].append(subtestname) 150 subtestnum=subtestnum+1 151 # The rest are easy 152 else: 153 subdict[var]=val 154 if var=="suffix": 155 if len(val)>0: 156 testname=testname+"_"+val 157 158 if len(comments): subdict['comments']="\n".join(comments).lstrip("\n") 159 return testname,subdict 160 161def parseTests(testStr,srcfile): 162 """ 163 Parse the yaml string describing tests and return 164 a dictionary with the info in the form of: 165 testDict[test][subtest] 166 This is an inelegant parser as we do not wish to 167 introduce a new dependency by bringing in pyyaml. 168 The advantage is that validation can be done as 169 it is parsed (e.g., 'test' is the only top-level node) 170 """ 171 172 testDict={} 173 174 # The first entry should be test: but it might be indented. 175 newTestStr=_stripIndent(testStr,srcfile) 176 177 # Now go through each test. First elem in split is blank 178 for test in newTestStr.split("\ntest:\n")[1:]: 179 testname,subdict=parseTest(test,srcfile) 180 if testDict.has_key(testname): 181 print "Multiple test names specified: "+testname+" in file: "+srcfile 182 testDict[testname]=subdict 183 184 return testDict 185 186def parseTestFile(srcfile): 187 """ 188 Parse single example files and return dictionary of the form: 189 testDict[srcfile][test][subtest] 190 """ 191 debug=False 192 curdir=os.path.realpath(os.path.curdir) 193 basedir=os.path.dirname(os.path.realpath(srcfile)) 194 basename=os.path.basename(srcfile) 195 os.chdir(basedir) 196 197 testDict={} 198 sh=open(srcfile,"r"); fileStr=sh.read(); sh.close() 199 200 ## Start with doing the tests 201 # 202 fsplit=fileStr.split("/*TEST\n")[1:] 203 if len(fsplit)==0: 204 if debug: print "No test found in: "+srcfile 205 return {} 206 # Allow for multiple "/*TEST" blocks even though it really should be 207 # on 208 srcTests=[] 209 for t in fsplit: srcTests.append(t.split("TEST*/")[0]) 210 testString=" ".join(srcTests) 211 if len(testString.strip())==0: 212 print "No test found in: "+srcfile 213 return {} 214 testDict[basename]=parseTests(testString,srcfile) 215 216 ## Check and see if we have build reuqirements 217 # 218 if "/*T\n" in fileStr or "/*T " in fileStr: 219 # The file info is already here and need to append 220 Part1=fileStr.split("T*/")[0] 221 fileInfo=Part1.split("/*T")[1] 222 for bkey in buildkeys: 223 if bkey+":" in fileInfo: 224 testDict[basename][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip() 225 226 os.chdir(curdir) 227 return testDict 228 229def parseTestDir(directory): 230 """ 231 Parse single example files and return dictionary of the form: 232 testDict[srcfile][test][subtest] 233 """ 234 curdir=os.path.realpath(os.path.curdir) 235 basedir=os.path.realpath(directory) 236 os.chdir(basedir) 237 238 tDict={} 239 for test_file in glob.glob("new_ex*.*"): 240 tDict.update(parseTestFile(test_file)) 241 242 os.chdir(curdir) 243 return tDict 244 245def printExParseDict(rDict): 246 """ 247 This is useful for debugging 248 """ 249 indent=" " 250 for sfile in rDict: 251 print "\n\n"+sfile 252 for runex in rDict[sfile]: 253 print indent+runex 254 if type(rDict[sfile][runex])==types.StringType: 255 print indent*2+rDict[sfile][runex] 256 else: 257 for var in rDict[sfile][runex]: 258 if var.startswith("test"): 259 print indent*2+var 260 for var2 in rDict[sfile][runex][var]: 261 print indent*3+var2+": "+str(rDict[sfile][runex][var][var2]) 262 else: 263 print indent*2+var+": "+str(rDict[sfile][runex][var]) 264 return 265 266def main(directory='',test_file='',verbosity=0): 267 268 if directory: 269 tDict=parseTestDir(directory) 270 else: 271 tDict=parseTestFile(test_file) 272 if verbosity>0: printExParseDict(tDict) 273 274 return 275 276if __name__ == '__main__': 277 import optparse 278 parser = optparse.OptionParser() 279 parser.add_option('-d', '--directory', dest='directory', 280 default="", help='Directory containing files to parse') 281 parser.add_option('-t', '--test_file', dest='test_file', 282 default="", help='Test file, e.g., ex1.c, to parse') 283 parser.add_option('-v', '--verbosity', dest='verbosity', 284 help='Verbosity of output by level: 1, 2, or 3', default=0) 285 opts, extra_args = parser.parse_args() 286 287 if extra_args: 288 import sys 289 sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args)) 290 exit(1) 291 if not opts.test_file and not opts.directory: 292 print "test file or directory is required" 293 parser.print_usage() 294 sys.exit() 295 296 # Need verbosity to be an integer 297 try: 298 verbosity=int(opts.verbosity) 299 except: 300 raise Exception("Error: Verbosity must be integer") 301 302 main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity) 303