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