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 53def _stripIndent(block,srcfile): 54 """ 55 Go through and remove a level of indentation 56 Also strip of trailing whitespace 57 """ 58 # The first entry should be test: but it might be indented. 59 ext=os.path.splitext(srcfile)[1] 60 for lline in block.split("\n"): 61 line=lline[1:] if lline.startswith("!") else lline 62 line=line.split('#')[0] 63 if not line.strip(): continue 64 stripstr=" " 65 nspace=len(line)-len(line.lstrip(stripstr)) 66 newline=line[nspace:] 67 break 68 69 # Strip off any indentation for the whole string and any trailing 70 # whitespace for convenience 71 newTestStr="\n" 72 for lline in block.split("\n"): 73 line=lline[1:] if lline.startswith("!") else lline 74 line=line.split('#')[0] 75 if not line.strip(): continue 76 newline=line[nspace:] 77 newTestStr=newTestStr+newline.rstrip()+"\n" 78 79 return newTestStr 80 81def parseTestInsert(dct,var,val,prefix=''): 82 """ 83 Before inserting a string, look for implicit loop variables ({{a,b}}), and convert them into references to 84 automatically generated explicity loop variables in the dictionary (e.g., create the entry {var_0: {{a,b}}} and 85 replace '{{a,b}}' with @VAR_0@) 86 """ 87 count = 0 88 impLoop = re.search('{{.*?}}',val) 89 while impLoop: 90 if impLoop.group(0) == val: 91 break 92 expLoop = '_'.join([prefix,str(var),str(count)]) 93 while dct.has_key(expLoop): 94 count += 1 95 expLoop = '_'.join([prefix,str(var),str(count)]) 96 dct[expLoop] = impLoop.group(0) 97 subs = '@'+expLoop.upper()+'@' 98 val = re.sub(impLoop.group(0),subs,val,count=1) 99 impLoop = re.search('{{.*?}}',val) 100 dct[var] = val 101 102 103def parseTest(testStr,srcfile): 104 """ 105 This parses an individual test 106 YAML is hierarchial so should use a state machine in the general case, 107 but in practice we only support two levels of test: 108 """ 109 basename=os.path.basename(srcfile) 110 # Handle the new at the begininng 111 bn=re.sub("new_","",basename) 112 # This is the default 113 testname="run"+os.path.splitext(bn)[0] 114 115 # Tests that have default everything (so empty effectively) 116 if len(testStr)==0: return testname, {} 117 118 striptest=_stripIndent(testStr,srcfile) 119 120 # go through and parse 121 subtestnum=0 122 subdict={} 123 for line in striptest.split("\n"): 124 if not line.strip(): continue 125 var=line.split(":")[0].strip() 126 val=line.split(":")[1].strip() 127 # Start by seeing if we are in a subtest 128 if line.startswith(" "): 129 parseTestInsert(subdict[subtestname],var,val,prefix=subtestname) 130 # Determine subtest name and make dict 131 elif var=="test": 132 subtestname="test"+str(subtestnum) 133 subdict[subtestname]={} 134 if not subdict.has_key("subtests"): subdict["subtests"]=[] 135 subdict["subtests"].append(subtestname) 136 subtestnum=subtestnum+1 137 # The reset are easy 138 else: 139 parseTestInsert(subdict,var,val) 140 if var=="suffix": 141 if len(val)>0: 142 testname=testname+"_"+val 143 144 return testname,subdict 145 146def parseTests(testStr,srcfile): 147 """ 148 Parse the yaml string describing tests and return 149 a dictionary with the info in the form of: 150 testDict[test][subtest] 151 This is an inelegant parser as we do not wish to 152 introduce a new dependency by bringing in pyyaml. 153 The advantage is that validation can be done as 154 it is parsed (e.g., 'test' is the only top-level node) 155 """ 156 157 testDict={} 158 159 # The first entry should be test: but it might be indented. 160 newTestStr=_stripIndent(testStr,srcfile) 161 162 # Now go through each test. First elem in split is blank 163 for test in newTestStr.split("\ntest:\n")[1:]: 164 testname,subdict=parseTest(test,srcfile) 165 if testDict.has_key(testname): 166 print "Multiple test names specified: "+testname+" in file: "+srcfile 167 testDict[testname]=subdict 168 169 return testDict 170 171def parseTestFile(srcfile): 172 """ 173 Parse single example files and return dictionary of the form: 174 testDict[srcfile][test][subtest] 175 """ 176 debug=False 177 curdir=os.path.realpath(os.path.curdir) 178 basedir=os.path.dirname(os.path.realpath(srcfile)) 179 basename=os.path.basename(srcfile) 180 os.chdir(basedir) 181 182 testDict={} 183 sh=open(srcfile,"r"); fileStr=sh.read(); sh.close() 184 185 ## Start with doing the tests 186 # 187 fsplit=fileStr.split("/*TEST\n")[1:] 188 if len(fsplit)==0: 189 if debug: print "No test found in: "+srcfile 190 return {} 191 # Allow for multiple "/*TEST" blocks even though it really should be 192 # on 193 srcTests=[] 194 for t in fsplit: srcTests.append(t.split("TEST*/")[0]) 195 testString=" ".join(srcTests) 196 if len(testString.strip())==0: 197 print "No test found in: "+srcfile 198 return {} 199 testDict[basename]=parseTests(testString,srcfile) 200 201 ## Check and see if we have build reuqirements 202 # 203 if "/*T\n" in fileStr or "/*T " in fileStr: 204 # The file info is already here and need to append 205 Part1=fileStr.split("T*/")[0] 206 fileInfo=Part1.split("/*T")[1] 207 for bkey in buildkeys: 208 if bkey+":" in fileInfo: 209 testDict[basename][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip() 210 211 os.chdir(curdir) 212 return testDict 213 214def parseTestDir(directory): 215 """ 216 Parse single example files and return dictionary of the form: 217 testDict[srcfile][test][subtest] 218 """ 219 curdir=os.path.realpath(os.path.curdir) 220 basedir=os.path.realpath(directory) 221 os.chdir(basedir) 222 223 tDict={} 224 for test_file in glob.glob("new_ex*.*"): 225 tDict.update(parseTestFile(test_file)) 226 227 os.chdir(curdir) 228 return tDict 229 230def printExParseDict(rDict): 231 """ 232 This is useful for debugging 233 """ 234 indent=" " 235 for sfile in rDict: 236 print "\n\n"+sfile 237 for runex in rDict[sfile]: 238 print indent+runex 239 if type(rDict[sfile][runex])==types.StringType: 240 print indent*2+rDict[sfile][runex] 241 else: 242 for var in rDict[sfile][runex]: 243 if var.startswith("test"): 244 print indent*2+var 245 for var2 in rDict[sfile][runex][var]: 246 print indent*3+var2+": "+str(rDict[sfile][runex][var][var2]) 247 else: 248 print indent*2+var+": "+str(rDict[sfile][runex][var]) 249 return 250 251def main(directory='',test_file='',verbosity=0): 252 253 if directory: 254 tDict=parseTestDir(directory) 255 else: 256 tDict=parseTestFile(test_file) 257 if verbosity>0: printExParseDict(tDict) 258 259 return 260 261if __name__ == '__main__': 262 import optparse 263 parser = optparse.OptionParser() 264 parser.add_option('-d', '--directory', dest='directory', 265 default="", help='Directory containing files to parse') 266 parser.add_option('-t', '--test_file', dest='test_file', 267 default="", help='Test file, e.g., ex1.c, to parse') 268 parser.add_option('-v', '--verbosity', dest='verbosity', 269 help='Verbosity of output by level: 1, 2, or 3', default=0) 270 opts, extra_args = parser.parse_args() 271 272 if extra_args: 273 import sys 274 sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args)) 275 exit(1) 276 if not opts.test_file and not opts.directory: 277 print "test file or directory is required" 278 parser.print_usage() 279 sys.exit() 280 281 # Need verbosity to be an integer 282 try: 283 verbosity=int(opts.verbosity) 284 except: 285 raise Exception("Error: Verbosity must be integer") 286 287 main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity) 288