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