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