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