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