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