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