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