xref: /petsc/config/testparse.py (revision a511783d1388ccb6ad2d594107e184679b1f65eb)
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