xref: /petsc/config/testparse.py (revision c0658d2af0617fd5f0839559dfeb92f88412867f)
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 Krugerdef extractForVars(argStr):
5668a9e459SScott Kruger  """
5768a9e459SScott Kruger  Given: 'args: -bs {{1 2 3 4 5}} -pc_type {{cholesky sor}} -ksp_monitor'
5868a9e459SScott Kruger  Return:
5968a9e459SScott Kruger      args: -ksp_monitor
6068a9e459SScott Kruger      forlist="bs pc_type"   # Don't assume OrderedDict
6168a9e459SScott Kruger      forargs[bs]['val']="1 2 3 4 5"
6268a9e459SScott Kruger      forargs[pc_type]['val']="cholesky sor"
6368a9e459SScott Kruger  """
6468a9e459SScott Kruger  loopstr=argStr
6568a9e459SScott Kruger  forargs={}
6668a9e459SScott Kruger  forlist=[]
6768a9e459SScott Kruger  newargs=""
6868a9e459SScott Kruger  for varset in re.split('-(?=[a-zA-Z])',loopstr):
6968a9e459SScott Kruger    if not varset.strip(): continue
7068a9e459SScott Kruger    if len(re.findall('{{(.*?)}}',varset))>0:
7168a9e459SScott Kruger      # Assuming only one for loop per var specification
7268a9e459SScott Kruger      forvar=varset.split("{{")[0].strip()
7368a9e459SScott Kruger      forlist.append(forvar)
7468a9e459SScott Kruger      forargs[forvar]={}
7568a9e459SScott Kruger      forargs[forvar]['val']=re.findall('{{(.*?)}}',varset)[0]
7668a9e459SScott Kruger    else:
7768a9e459SScott Kruger      newargs=newargs+"-"+varset+" "
7868a9e459SScott Kruger
7968a9e459SScott Kruger  return forlist,forargs,newargs.strip()
8068a9e459SScott Kruger
8168a9e459SScott Kruger
8229921a8fSScott Krugerdef _stripIndent(block,srcfile):
8329921a8fSScott Kruger  """
8429921a8fSScott Kruger  Go through and remove a level of indentation
8529921a8fSScott Kruger  Also strip of trailing whitespace
8629921a8fSScott Kruger  """
8729921a8fSScott Kruger  # The first entry should be test: but it might be indented.
8829921a8fSScott Kruger  ext=os.path.splitext(srcfile)[1]
898ccd5183SScott Kruger  for lline in block.split("\n"):
908ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
91ca789a6bSScott Kruger    line=line.split('#')[0]
9229921a8fSScott Kruger    if not line.strip(): continue
938ccd5183SScott Kruger    stripstr=" "
9429921a8fSScott Kruger    nspace=len(line)-len(line.lstrip(stripstr))
9529921a8fSScott Kruger    newline=line[nspace:]
9629921a8fSScott Kruger    break
9729921a8fSScott Kruger
9829921a8fSScott Kruger  # Strip off any indentation for the whole string and any trailing
9929921a8fSScott Kruger  # whitespace for convenience
10029921a8fSScott Kruger  newTestStr="\n"
1018ccd5183SScott Kruger  for lline in block.split("\n"):
1028ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
10329921a8fSScott Kruger    if not line.strip(): continue
10429921a8fSScott Kruger    newline=line[nspace:]
10529921a8fSScott Kruger    newTestStr=newTestStr+newline.rstrip()+"\n"
10629921a8fSScott Kruger
10729921a8fSScott Kruger  return newTestStr
10829921a8fSScott Kruger
10929921a8fSScott Krugerdef parseTest(testStr,srcfile):
11029921a8fSScott Kruger  """
11129921a8fSScott Kruger  This parses an individual test
11229921a8fSScott Kruger  YAML is hierarchial so should use a state machine in the general case,
11353f2a965SBarry Smith  but in practice we only support two levels of test:
11429921a8fSScott Kruger  """
11529921a8fSScott Kruger  basename=os.path.basename(srcfile)
11629921a8fSScott Kruger  # Handle the new at the begininng
11729921a8fSScott Kruger  bn=re.sub("new_","",basename)
11829921a8fSScott Kruger  # This is the default
11929921a8fSScott Kruger  testname="run"+os.path.splitext(bn)[0]
12029921a8fSScott Kruger
12129921a8fSScott Kruger  # Tests that have default everything (so empty effectively)
12229921a8fSScott Kruger  if len(testStr)==0: return testname, {}
12329921a8fSScott Kruger
12429921a8fSScott Kruger  striptest=_stripIndent(testStr,srcfile)
12529921a8fSScott Kruger
12629921a8fSScott Kruger  # go through and parse
12729921a8fSScott Kruger  subtestnum=0
12829921a8fSScott Kruger  subdict={}
12968a9e459SScott Kruger  comments=[]
13068a9e459SScott Kruger  indentlevel=0
13168a9e459SScott Kruger  for ln in striptest.split("\n"):
13268a9e459SScott Kruger    line=ln.split('#')[0]
13368a9e459SScott Kruger    comment=("" if len(ln.split("#"))==1 else " ".join(ln.split("#")[1:]).strip())
13468a9e459SScott Kruger    if comment: comments.append(comment)
13529921a8fSScott Kruger    if not line.strip(): continue
13668a9e459SScott Kruger    indentcount=line.split(":")[0].count(" ")
13729921a8fSScott Kruger    var=line.split(":")[0].strip()
13829921a8fSScott Kruger    val=line.split(":")[1].strip()
13929921a8fSScott Kruger    # Start by seeing if we are in a subtest
14029921a8fSScott Kruger    if line.startswith(" "):
141*c0658d2aSScott Kruger      subdict[subtestname][var]=val
14268a9e459SScott Kruger      if not indentlevel: indentlevel=indentcount
14368a9e459SScott Kruger      #if indentlevel!=indentcount: print "Error in indentation:", ln
14429921a8fSScott Kruger    # Determine subtest name and make dict
14529921a8fSScott Kruger    elif var=="test":
14629921a8fSScott Kruger      subtestname="test"+str(subtestnum)
14729921a8fSScott Kruger      subdict[subtestname]={}
14829921a8fSScott Kruger      if not subdict.has_key("subtests"): subdict["subtests"]=[]
14929921a8fSScott Kruger      subdict["subtests"].append(subtestname)
15029921a8fSScott Kruger      subtestnum=subtestnum+1
15168a9e459SScott Kruger    # The rest are easy
15229921a8fSScott Kruger    else:
153*c0658d2aSScott Kruger      subdict[var]=val
15429921a8fSScott Kruger      if var=="suffix":
15529921a8fSScott Kruger        if len(val)>0:
15629921a8fSScott Kruger          testname=testname+"_"+val
15729921a8fSScott Kruger
15868a9e459SScott Kruger  if len(comments): subdict['comments']="\n".join(comments).lstrip("\n")
15929921a8fSScott Kruger  return testname,subdict
16029921a8fSScott Kruger
16129921a8fSScott Krugerdef parseTests(testStr,srcfile):
16229921a8fSScott Kruger  """
16329921a8fSScott Kruger  Parse the yaml string describing tests and return
16429921a8fSScott Kruger  a dictionary with the info in the form of:
16529921a8fSScott Kruger    testDict[test][subtest]
16629921a8fSScott Kruger  This is an inelegant parser as we do not wish to
16729921a8fSScott Kruger  introduce a new dependency by bringing in pyyaml.
16829921a8fSScott Kruger  The advantage is that validation can be done as
16929921a8fSScott Kruger  it is parsed (e.g., 'test' is the only top-level node)
17029921a8fSScott Kruger  """
17129921a8fSScott Kruger
17229921a8fSScott Kruger  testDict={}
17329921a8fSScott Kruger
17429921a8fSScott Kruger  # The first entry should be test: but it might be indented.
17529921a8fSScott Kruger  newTestStr=_stripIndent(testStr,srcfile)
17629921a8fSScott Kruger
17729921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
17829921a8fSScott Kruger  for test in newTestStr.split("\ntest:\n")[1:]:
17929921a8fSScott Kruger    testname,subdict=parseTest(test,srcfile)
18029921a8fSScott Kruger    if testDict.has_key(testname):
18129921a8fSScott Kruger      print "Multiple test names specified: "+testname+" in file: "+srcfile
18229921a8fSScott Kruger    testDict[testname]=subdict
18329921a8fSScott Kruger
18429921a8fSScott Kruger  return testDict
18529921a8fSScott Kruger
18629921a8fSScott Krugerdef parseTestFile(srcfile):
18729921a8fSScott Kruger  """
18829921a8fSScott Kruger  Parse single example files and return dictionary of the form:
18929921a8fSScott Kruger    testDict[srcfile][test][subtest]
19029921a8fSScott Kruger  """
19129921a8fSScott Kruger  debug=False
19229921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
19329921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
19429921a8fSScott Kruger  basename=os.path.basename(srcfile)
19529921a8fSScott Kruger  os.chdir(basedir)
19629921a8fSScott Kruger
19729921a8fSScott Kruger  testDict={}
19829921a8fSScott Kruger  sh=open(srcfile,"r"); fileStr=sh.read(); sh.close()
19929921a8fSScott Kruger
20029921a8fSScott Kruger  ## Start with doing the tests
20129921a8fSScott Kruger  #
20229921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
20329921a8fSScott Kruger  if len(fsplit)==0:
20429921a8fSScott Kruger    if debug: print "No test found in: "+srcfile
20529921a8fSScott Kruger    return {}
20629921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
20729921a8fSScott Kruger  # on
20829921a8fSScott Kruger  srcTests=[]
20929921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
21029921a8fSScott Kruger  testString=" ".join(srcTests)
21129921a8fSScott Kruger  if len(testString.strip())==0:
21229921a8fSScott Kruger    print "No test found in: "+srcfile
21329921a8fSScott Kruger    return {}
21429921a8fSScott Kruger  testDict[basename]=parseTests(testString,srcfile)
21529921a8fSScott Kruger
21629921a8fSScott Kruger  ## Check and see if we have build reuqirements
21729921a8fSScott Kruger  #
21829921a8fSScott Kruger  if "/*T\n" in fileStr or "/*T " in fileStr:
21929921a8fSScott Kruger    # The file info is already here and need to append
22029921a8fSScott Kruger    Part1=fileStr.split("T*/")[0]
22129921a8fSScott Kruger    fileInfo=Part1.split("/*T")[1]
22229921a8fSScott Kruger    for bkey in buildkeys:
22329921a8fSScott Kruger      if bkey+":" in fileInfo:
22429921a8fSScott Kruger        testDict[basename][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
22529921a8fSScott Kruger
22629921a8fSScott Kruger  os.chdir(curdir)
22729921a8fSScott Kruger  return testDict
22829921a8fSScott Kruger
22929921a8fSScott Krugerdef parseTestDir(directory):
23029921a8fSScott Kruger  """
23129921a8fSScott Kruger  Parse single example files and return dictionary of the form:
23229921a8fSScott Kruger    testDict[srcfile][test][subtest]
23329921a8fSScott Kruger  """
23429921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
23529921a8fSScott Kruger  basedir=os.path.realpath(directory)
23629921a8fSScott Kruger  os.chdir(basedir)
23729921a8fSScott Kruger
23829921a8fSScott Kruger  tDict={}
23929921a8fSScott Kruger  for test_file in glob.glob("new_ex*.*"):
24029921a8fSScott Kruger    tDict.update(parseTestFile(test_file))
24129921a8fSScott Kruger
24229921a8fSScott Kruger  os.chdir(curdir)
24329921a8fSScott Kruger  return tDict
24429921a8fSScott Kruger
24529921a8fSScott Krugerdef printExParseDict(rDict):
24629921a8fSScott Kruger  """
24729921a8fSScott Kruger  This is useful for debugging
24829921a8fSScott Kruger  """
24929921a8fSScott Kruger  indent="   "
25029921a8fSScott Kruger  for sfile in rDict:
25129921a8fSScott Kruger    print "\n\n"+sfile
25229921a8fSScott Kruger    for runex in rDict[sfile]:
25329921a8fSScott Kruger      print indent+runex
25429921a8fSScott Kruger      if type(rDict[sfile][runex])==types.StringType:
25529921a8fSScott Kruger        print indent*2+rDict[sfile][runex]
25629921a8fSScott Kruger      else:
25729921a8fSScott Kruger        for var in rDict[sfile][runex]:
25829921a8fSScott Kruger          if var.startswith("test"):
25929921a8fSScott Kruger            print indent*2+var
26029921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
26129921a8fSScott Kruger              print indent*3+var2+": "+str(rDict[sfile][runex][var][var2])
26229921a8fSScott Kruger          else:
26329921a8fSScott Kruger            print indent*2+var+": "+str(rDict[sfile][runex][var])
26429921a8fSScott Kruger  return
26529921a8fSScott Kruger
26629921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
26729921a8fSScott Kruger
26829921a8fSScott Kruger    if directory:
26929921a8fSScott Kruger      tDict=parseTestDir(directory)
27029921a8fSScott Kruger    else:
27129921a8fSScott Kruger      tDict=parseTestFile(test_file)
27229921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
27329921a8fSScott Kruger
27429921a8fSScott Kruger    return
27529921a8fSScott Kruger
27629921a8fSScott Krugerif __name__ == '__main__':
27729921a8fSScott Kruger    import optparse
27829921a8fSScott Kruger    parser = optparse.OptionParser()
27929921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
28029921a8fSScott Kruger                      default="", help='Directory containing files to parse')
28129921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
28229921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
28329921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
28429921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
28529921a8fSScott Kruger    opts, extra_args = parser.parse_args()
28629921a8fSScott Kruger
28729921a8fSScott Kruger    if extra_args:
28829921a8fSScott Kruger        import sys
28929921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
29029921a8fSScott Kruger        exit(1)
29129921a8fSScott Kruger    if not opts.test_file and not opts.directory:
29229921a8fSScott Kruger      print "test file or directory is required"
29329921a8fSScott Kruger      parser.print_usage()
29429921a8fSScott Kruger      sys.exit()
29529921a8fSScott Kruger
29629921a8fSScott Kruger    # Need verbosity to be an integer
29729921a8fSScott Kruger    try:
29829921a8fSScott Kruger      verbosity=int(opts.verbosity)
29929921a8fSScott Kruger    except:
30029921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
30129921a8fSScott Kruger
30229921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
303