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