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