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