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