xref: /petsc/config/testparse.py (revision c4b80baaa27d7046ff527fe76aa314263ed2d797)
129921a8fSScott Kruger#!/usr/bin/env python
229921a8fSScott Kruger"""
329921a8fSScott KrugerParse the test file and return a dictionary.
429921a8fSScott Kruger
529921a8fSScott KrugerQuick usage::
629921a8fSScott Kruger
729921a8fSScott Kruger  bin/maint/testparse.py -t src/ksp/ksp/examples/tutorials/ex1.c
829921a8fSScott Kruger
929921a8fSScott KrugerFrom the command line, it prints out the dictionary.
1029921a8fSScott KrugerThis is meant to be used by other scripts, but it is
1129921a8fSScott Krugeruseful to debug individual files.
1229921a8fSScott Kruger
1329921a8fSScott Kruger
1429921a8fSScott Kruger
1529921a8fSScott KrugerExample language
1629921a8fSScott Kruger----------------
1729921a8fSScott Kruger/*T
1829921a8fSScott Kruger   Concepts:
1929921a8fSScott Kruger   requires: moab
2029921a8fSScott KrugerT*/
2129921a8fSScott Kruger
2229921a8fSScott Kruger
2329921a8fSScott Kruger
2429921a8fSScott Kruger/*TEST
2529921a8fSScott Kruger
2629921a8fSScott Kruger   test:
2729921a8fSScott Kruger      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
2829921a8fSScott Kruger      output_file: output/ex25_1.out
2929921a8fSScott Kruger
3029921a8fSScott Kruger   test:
3129921a8fSScott Kruger      suffix: 2
3229921a8fSScott Kruger      nsize: 2
3329921a8fSScott Kruger      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
3429921a8fSScott Kruger
3529921a8fSScott KrugerTEST*/
3629921a8fSScott Kruger
3729921a8fSScott Kruger"""
3829921a8fSScott Kruger
3929921a8fSScott Krugerimport os, re, glob, types
4029921a8fSScott Krugerfrom distutils.sysconfig import parse_makefile
4129921a8fSScott Krugerimport sys
4229921a8fSScott Krugerimport logging
4329921a8fSScott Krugersys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
4429921a8fSScott Kruger
4529921a8fSScott Krugerimport inspect
4629921a8fSScott Krugerthisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
4729921a8fSScott Krugermaintdir=os.path.join(os.path.join(os.path.dirname(thisscriptdir),'bin'),'maint')
4829921a8fSScott Krugersys.path.insert(0,maintdir)
4929921a8fSScott Kruger
5029921a8fSScott Kruger# These are special keys describing build
5129921a8fSScott Krugerbuildkeys="requires TODO SKIP depends".split()
5229921a8fSScott Kruger
5368a9e459SScott Kruger
5468a9e459SScott Krugerimport re
5568a9e459SScott Kruger
5629921a8fSScott Krugerdef _stripIndent(block,srcfile):
5729921a8fSScott Kruger  """
5829921a8fSScott Kruger  Go through and remove a level of indentation
5929921a8fSScott Kruger  Also strip of trailing whitespace
6029921a8fSScott Kruger  """
6129921a8fSScott Kruger  # The first entry should be test: but it might be indented.
6229921a8fSScott Kruger  ext=os.path.splitext(srcfile)[1]
638ccd5183SScott Kruger  for lline in block.split("\n"):
648ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
6529921a8fSScott Kruger    if not line.strip(): continue
66*c4b80baaSScott Kruger    if line.strip().startswith('#'): continue
678ccd5183SScott Kruger    stripstr=" "
6829921a8fSScott Kruger    nspace=len(line)-len(line.lstrip(stripstr))
6929921a8fSScott Kruger    newline=line[nspace:]
7029921a8fSScott Kruger    break
7129921a8fSScott Kruger
7229921a8fSScott Kruger  # Strip off any indentation for the whole string and any trailing
7329921a8fSScott Kruger  # whitespace for convenience
7429921a8fSScott Kruger  newTestStr="\n"
758ccd5183SScott Kruger  for lline in block.split("\n"):
768ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
7729921a8fSScott Kruger    if not line.strip(): continue
78*c4b80baaSScott Kruger    if line.strip().startswith('#'):
79*c4b80baaSScott Kruger      newTestStr+=line+'\n'
80*c4b80baaSScott Kruger    else:
8129921a8fSScott Kruger      newline=line[nspace:]
82*c4b80baaSScott Kruger      newTestStr+=newline.rstrip()+"\n"
8329921a8fSScott Kruger
8429921a8fSScott Kruger  return newTestStr
8529921a8fSScott Kruger
8629921a8fSScott Krugerdef parseTest(testStr,srcfile):
8729921a8fSScott Kruger  """
8829921a8fSScott Kruger  This parses an individual test
8929921a8fSScott Kruger  YAML is hierarchial so should use a state machine in the general case,
9053f2a965SBarry Smith  but in practice we only support two levels of test:
9129921a8fSScott Kruger  """
9229921a8fSScott Kruger  basename=os.path.basename(srcfile)
9329921a8fSScott Kruger  # Handle the new at the begininng
9429921a8fSScott Kruger  bn=re.sub("new_","",basename)
9529921a8fSScott Kruger  # This is the default
9629921a8fSScott Kruger  testname="run"+os.path.splitext(bn)[0]
9729921a8fSScott Kruger
9829921a8fSScott Kruger  # Tests that have default everything (so empty effectively)
9929921a8fSScott Kruger  if len(testStr)==0: return testname, {}
10029921a8fSScott Kruger
10129921a8fSScott Kruger  striptest=_stripIndent(testStr,srcfile)
10229921a8fSScott Kruger
10329921a8fSScott Kruger  # go through and parse
10429921a8fSScott Kruger  subtestnum=0
10529921a8fSScott Kruger  subdict={}
10668a9e459SScott Kruger  comments=[]
10768a9e459SScott Kruger  indentlevel=0
10868a9e459SScott Kruger  for ln in striptest.split("\n"):
109*c4b80baaSScott Kruger    line=ln.split('#')[0].rstrip()
11068a9e459SScott Kruger    comment=("" if len(ln.split("#"))==1 else " ".join(ln.split("#")[1:]).strip())
11168a9e459SScott Kruger    if comment: comments.append(comment)
11229921a8fSScott Kruger    if not line.strip(): continue
11368a9e459SScott Kruger    indentcount=line.split(":")[0].count(" ")
11429921a8fSScott Kruger    var=line.split(":")[0].strip()
11529921a8fSScott Kruger    val=line.split(":")[1].strip()
11629921a8fSScott Kruger    # Start by seeing if we are in a subtest
11729921a8fSScott Kruger    if line.startswith(" "):
118c0658d2aSScott Kruger      subdict[subtestname][var]=val
11968a9e459SScott Kruger      if not indentlevel: indentlevel=indentcount
12068a9e459SScott Kruger      #if indentlevel!=indentcount: print "Error in indentation:", ln
12129921a8fSScott Kruger    # Determine subtest name and make dict
12229921a8fSScott Kruger    elif var=="test":
12329921a8fSScott Kruger      subtestname="test"+str(subtestnum)
12429921a8fSScott Kruger      subdict[subtestname]={}
12529921a8fSScott Kruger      if not subdict.has_key("subtests"): subdict["subtests"]=[]
12629921a8fSScott Kruger      subdict["subtests"].append(subtestname)
12729921a8fSScott Kruger      subtestnum=subtestnum+1
12868a9e459SScott Kruger    # The rest are easy
12929921a8fSScott Kruger    else:
130c0658d2aSScott Kruger      subdict[var]=val
13129921a8fSScott Kruger      if var=="suffix":
13229921a8fSScott Kruger        if len(val)>0:
13329921a8fSScott Kruger          testname=testname+"_"+val
13429921a8fSScott Kruger
13568a9e459SScott Kruger  if len(comments): subdict['comments']="\n".join(comments).lstrip("\n")
13629921a8fSScott Kruger  return testname,subdict
13729921a8fSScott Kruger
13829921a8fSScott Krugerdef parseTests(testStr,srcfile):
13929921a8fSScott Kruger  """
14029921a8fSScott Kruger  Parse the yaml string describing tests and return
14129921a8fSScott Kruger  a dictionary with the info in the form of:
14229921a8fSScott Kruger    testDict[test][subtest]
14329921a8fSScott Kruger  This is an inelegant parser as we do not wish to
14429921a8fSScott Kruger  introduce a new dependency by bringing in pyyaml.
14529921a8fSScott Kruger  The advantage is that validation can be done as
14629921a8fSScott Kruger  it is parsed (e.g., 'test' is the only top-level node)
14729921a8fSScott Kruger  """
14829921a8fSScott Kruger
14929921a8fSScott Kruger  testDict={}
15029921a8fSScott Kruger
15129921a8fSScott Kruger  # The first entry should be test: but it might be indented.
15229921a8fSScott Kruger  newTestStr=_stripIndent(testStr,srcfile)
15329921a8fSScott Kruger
15429921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
155*c4b80baaSScott Kruger  for test in newTestStr.split("\ntest:")[1:]:
15629921a8fSScott Kruger    testname,subdict=parseTest(test,srcfile)
15729921a8fSScott Kruger    if testDict.has_key(testname):
15829921a8fSScott Kruger      print "Multiple test names specified: "+testname+" in file: "+srcfile
15929921a8fSScott Kruger    testDict[testname]=subdict
16029921a8fSScott Kruger
16129921a8fSScott Kruger  return testDict
16229921a8fSScott Kruger
16329921a8fSScott Krugerdef parseTestFile(srcfile):
16429921a8fSScott Kruger  """
16529921a8fSScott Kruger  Parse single example files and return dictionary of the form:
16629921a8fSScott Kruger    testDict[srcfile][test][subtest]
16729921a8fSScott Kruger  """
16829921a8fSScott Kruger  debug=False
16929921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
17029921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
17129921a8fSScott Kruger  basename=os.path.basename(srcfile)
17229921a8fSScott Kruger  os.chdir(basedir)
17329921a8fSScott Kruger
17429921a8fSScott Kruger  testDict={}
17529921a8fSScott Kruger  sh=open(srcfile,"r"); fileStr=sh.read(); sh.close()
17629921a8fSScott Kruger
17729921a8fSScott Kruger  ## Start with doing the tests
17829921a8fSScott Kruger  #
17929921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
18029921a8fSScott Kruger  if len(fsplit)==0:
18129921a8fSScott Kruger    if debug: print "No test found in: "+srcfile
18229921a8fSScott Kruger    return {}
18329921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
18429921a8fSScott Kruger  # on
18529921a8fSScott Kruger  srcTests=[]
18629921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
18729921a8fSScott Kruger  testString=" ".join(srcTests)
18829921a8fSScott Kruger  if len(testString.strip())==0:
18929921a8fSScott Kruger    print "No test found in: "+srcfile
19029921a8fSScott Kruger    return {}
19129921a8fSScott Kruger  testDict[basename]=parseTests(testString,srcfile)
19229921a8fSScott Kruger
19329921a8fSScott Kruger  ## Check and see if we have build reuqirements
19429921a8fSScott Kruger  #
19529921a8fSScott Kruger  if "/*T\n" in fileStr or "/*T " in fileStr:
19629921a8fSScott Kruger    # The file info is already here and need to append
19729921a8fSScott Kruger    Part1=fileStr.split("T*/")[0]
19829921a8fSScott Kruger    fileInfo=Part1.split("/*T")[1]
19929921a8fSScott Kruger    for bkey in buildkeys:
20029921a8fSScott Kruger      if bkey+":" in fileInfo:
20129921a8fSScott Kruger        testDict[basename][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
20229921a8fSScott Kruger
20329921a8fSScott Kruger  os.chdir(curdir)
20429921a8fSScott Kruger  return testDict
20529921a8fSScott Kruger
20629921a8fSScott Krugerdef parseTestDir(directory):
20729921a8fSScott Kruger  """
20829921a8fSScott Kruger  Parse single example files and return dictionary of the form:
20929921a8fSScott Kruger    testDict[srcfile][test][subtest]
21029921a8fSScott Kruger  """
21129921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
21229921a8fSScott Kruger  basedir=os.path.realpath(directory)
21329921a8fSScott Kruger  os.chdir(basedir)
21429921a8fSScott Kruger
21529921a8fSScott Kruger  tDict={}
21629921a8fSScott Kruger  for test_file in glob.glob("new_ex*.*"):
21729921a8fSScott Kruger    tDict.update(parseTestFile(test_file))
21829921a8fSScott Kruger
21929921a8fSScott Kruger  os.chdir(curdir)
22029921a8fSScott Kruger  return tDict
22129921a8fSScott Kruger
22229921a8fSScott Krugerdef printExParseDict(rDict):
22329921a8fSScott Kruger  """
22429921a8fSScott Kruger  This is useful for debugging
22529921a8fSScott Kruger  """
22629921a8fSScott Kruger  indent="   "
22729921a8fSScott Kruger  for sfile in rDict:
22829921a8fSScott Kruger    print "\n\n"+sfile
22929921a8fSScott Kruger    for runex in rDict[sfile]:
23029921a8fSScott Kruger      print indent+runex
23129921a8fSScott Kruger      if type(rDict[sfile][runex])==types.StringType:
23229921a8fSScott Kruger        print indent*2+rDict[sfile][runex]
23329921a8fSScott Kruger      else:
23429921a8fSScott Kruger        for var in rDict[sfile][runex]:
23529921a8fSScott Kruger          if var.startswith("test"):
23629921a8fSScott Kruger            print indent*2+var
23729921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
23829921a8fSScott Kruger              print indent*3+var2+": "+str(rDict[sfile][runex][var][var2])
23929921a8fSScott Kruger          else:
24029921a8fSScott Kruger            print indent*2+var+": "+str(rDict[sfile][runex][var])
24129921a8fSScott Kruger  return
24229921a8fSScott Kruger
24329921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
24429921a8fSScott Kruger
24529921a8fSScott Kruger    if directory:
24629921a8fSScott Kruger      tDict=parseTestDir(directory)
24729921a8fSScott Kruger    else:
24829921a8fSScott Kruger      tDict=parseTestFile(test_file)
24929921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
25029921a8fSScott Kruger
25129921a8fSScott Kruger    return
25229921a8fSScott Kruger
25329921a8fSScott Krugerif __name__ == '__main__':
25429921a8fSScott Kruger    import optparse
25529921a8fSScott Kruger    parser = optparse.OptionParser()
25629921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
25729921a8fSScott Kruger                      default="", help='Directory containing files to parse')
25829921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
25929921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
26029921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
26129921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
26229921a8fSScott Kruger    opts, extra_args = parser.parse_args()
26329921a8fSScott Kruger
26429921a8fSScott Kruger    if extra_args:
26529921a8fSScott Kruger        import sys
26629921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
26729921a8fSScott Kruger        exit(1)
26829921a8fSScott Kruger    if not opts.test_file and not opts.directory:
26929921a8fSScott Kruger      print "test file or directory is required"
27029921a8fSScott Kruger      parser.print_usage()
27129921a8fSScott Kruger      sys.exit()
27229921a8fSScott Kruger
27329921a8fSScott Kruger    # Need verbosity to be an integer
27429921a8fSScott Kruger    try:
27529921a8fSScott Kruger      verbosity=int(opts.verbosity)
27629921a8fSScott Kruger    except:
27729921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
27829921a8fSScott Kruger
27929921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
280