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