xref: /petsc/config/testparse.py (revision c4762a1b19cd2af06abeed90e8f9d34fb975dd94)
129921a8fSScott Kruger#!/usr/bin/env python
229921a8fSScott Kruger"""
329921a8fSScott KrugerParse the test file and return a dictionary.
429921a8fSScott Kruger
529921a8fSScott KrugerQuick usage::
629921a8fSScott Kruger
7*c4762a1bSJed Brown  lib/petsc/bin/maint/testparse.py -t src/ksp/ksp/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 KrugerExample language
1429921a8fSScott Kruger----------------
1529921a8fSScott Kruger
1629921a8fSScott Kruger/*TEST
17aec507c4SScott Kruger   build:
18aec507c4SScott Kruger     requires: moab
19e53dc769SScott Kruger   # This is equivalent to test:
20e53dc769SScott Kruger   testset:
2129921a8fSScott 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
2229921a8fSScott Kruger
23e53dc769SScott Kruger   testset:
2429921a8fSScott Kruger      suffix: 2
2529921a8fSScott Kruger      nsize: 2
2629921a8fSScott 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
2729921a8fSScott Kruger
28e53dc769SScott Kruger   testset:
29e53dc769SScott Kruger      suffix: 2
30e53dc769SScott Kruger      nsize: 2
31e53dc769SScott 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
32e53dc769SScott Kruger      test:
33e53dc769SScott Kruger
3429921a8fSScott KrugerTEST*/
3529921a8fSScott Kruger
3629921a8fSScott Kruger"""
375b6bfdb9SJed Brownfrom __future__ import print_function
3829921a8fSScott Kruger
3929921a8fSScott Krugerimport os, re, glob, types
4029921a8fSScott Krugerimport sys
4129921a8fSScott Krugerimport logging
4229921a8fSScott Krugersys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
4329921a8fSScott Kruger
4429921a8fSScott Krugerimport inspect
4529921a8fSScott Krugerthisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
4629921a8fSScott Krugermaintdir=os.path.join(os.path.join(os.path.dirname(thisscriptdir),'bin'),'maint')
4729921a8fSScott Krugersys.path.insert(0,maintdir)
4829921a8fSScott Kruger
4929921a8fSScott Kruger# These are special keys describing build
5029921a8fSScott Krugerbuildkeys="requires TODO SKIP depends".split()
5129921a8fSScott Kruger
520a091e3eSScott Krugeracceptedkeys="test nsize requires command suffix args filter filter_output localrunfiles comments TODO SKIP output_file timeoutfactor".split()
5344776d8cSScott Krugerappendlist="args requires comments".split()
5468a9e459SScott Kruger
5568a9e459SScott Krugerimport re
5668a9e459SScott Kruger
575e361860SScott Krugerdef getDefaultOutputFileRoot(testname):
585e361860SScott Kruger  """
595e361860SScott Kruger  Given testname, give DefaultRoot and DefaultOutputFilename
605e361860SScott Kruger  e.g., runex1 gives ex1_1, output/ex1_1.out
615e361860SScott Kruger  """
625e361860SScott Kruger  defroot=(re.sub("run","",testname) if testname.startswith("run") else testname)
635e361860SScott Kruger  if not "_" in defroot: defroot=defroot+"_1"
645e361860SScott Kruger  return defroot
655e361860SScott Kruger
6644776d8cSScott Krugerdef _stripIndent(block,srcfile,entireBlock=False,fileNums=[]):
6729921a8fSScott Kruger  """
6829921a8fSScott Kruger  Go through and remove a level of indentation
6929921a8fSScott Kruger  Also strip of trailing whitespace
7029921a8fSScott Kruger  """
7129921a8fSScott Kruger  # The first entry should be test: but it might be indented.
7229921a8fSScott Kruger  ext=os.path.splitext(srcfile)[1]
7344776d8cSScott Kruger  stripstr=" "
7466db876fSScott Kruger  if len(fileNums)>0: lineNum=fileNums[0]-1
758ccd5183SScott Kruger  for lline in block.split("\n"):
7666db876fSScott Kruger    if len(fileNums)>0: lineNum+=1
778ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
7829921a8fSScott Kruger    if not line.strip(): continue
79c4b80baaSScott Kruger    if line.strip().startswith('#'): continue
8044776d8cSScott Kruger    if entireBlock:
8144776d8cSScott Kruger      var=line.split(":")[0].strip()
82aec507c4SScott Kruger      if not var in ['test','testset','build']:
8366db876fSScott Kruger        raise Exception("Formatting error: Cannot find test in file: "+srcfile+" at line: "+str(lineNum)+"\n")
8429921a8fSScott Kruger    nspace=len(line)-len(line.lstrip(stripstr))
8529921a8fSScott Kruger    newline=line[nspace:]
8629921a8fSScott Kruger    break
8729921a8fSScott Kruger
8829921a8fSScott Kruger  # Strip off any indentation for the whole string and any trailing
8929921a8fSScott Kruger  # whitespace for convenience
9029921a8fSScott Kruger  newTestStr="\n"
9144776d8cSScott Kruger  if len(fileNums)>0: lineNum=fileNums[0]-1
9244776d8cSScott Kruger  firstPass=True
938ccd5183SScott Kruger  for lline in block.split("\n"):
9444776d8cSScott Kruger    if len(fileNums)>0: lineNum+=1
958ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
9629921a8fSScott Kruger    if not line.strip(): continue
97c4b80baaSScott Kruger    if line.strip().startswith('#'):
98c4b80baaSScott Kruger      newTestStr+=line+'\n'
99c4b80baaSScott Kruger    else:
10029921a8fSScott Kruger      newline=line[nspace:]
101c4b80baaSScott Kruger      newTestStr+=newline.rstrip()+"\n"
10244776d8cSScott Kruger    # Do some basic indentation checks
10344776d8cSScott Kruger    if entireBlock:
10444776d8cSScott Kruger      # Don't need to check comment lines
10544776d8cSScott Kruger      if line.strip().startswith('#'): continue
10644776d8cSScott Kruger      if not newline.startswith(" "):
10744776d8cSScott Kruger        var=newline.split(":")[0].strip()
108aec507c4SScott Kruger        if not var in ['test','testset','build']:
10944776d8cSScott Kruger          err="Formatting error in file "+srcfile+" at line: " +line+"\n"
11044776d8cSScott Kruger          if len(fileNums)>0:
11144776d8cSScott Kruger            raise Exception(err+"Check indentation at line number: "+str(lineNum))
11244776d8cSScott Kruger          else:
11344776d8cSScott Kruger            raise Exception(err)
11444776d8cSScott Kruger      else:
11544776d8cSScott Kruger        var=line.split(":")[0].strip()
116aec507c4SScott Kruger        if var in ['test','testset','build']:
11744776d8cSScott Kruger          subnspace=len(line)-len(line.lstrip(stripstr))
11844776d8cSScott Kruger          if firstPass:
11944776d8cSScott Kruger            firstsubnspace=subnspace
12044776d8cSScott Kruger            firstPass=False
12144776d8cSScott Kruger          else:
12244776d8cSScott Kruger            if firstsubnspace!=subnspace:
12344776d8cSScott Kruger              err="Formatting subtest error in file "+srcfile+" at line: " +line+"\n"
12444776d8cSScott Kruger              if len(fileNums)>0:
12544776d8cSScott Kruger                raise Exception(err+"Check indentation at line number: "+str(lineNum))
12644776d8cSScott Kruger              else:
12744776d8cSScott Kruger                raise Exception(err)
12844776d8cSScott Kruger
12943c6e11bSMatthew G. Knepley  # Allow line continuation character '\'
13043c6e11bSMatthew G. Knepley  return newTestStr.replace('\\\n', ' ')
13129921a8fSScott Kruger
132aae9f2d9SScott Krugerdef parseLoopArgs(varset):
13344776d8cSScott Kruger  """
134aae9f2d9SScott Kruger  Given:   String containing loop variables
135aae9f2d9SScott Kruger  Return: tuple containing separate/shared and string of loop vars
13644776d8cSScott Kruger  """
137a449fbaeSJed Brown  keynm=varset.split("{{")[0].strip().lstrip('-')
138aae9f2d9SScott Kruger  if not keynm.strip(): keynm='nsize'
139aae9f2d9SScott Kruger  lvars=varset.split('{{')[1].split('}')[0]
140aae9f2d9SScott Kruger  suffx=varset.split('{{')[1].split('}')[1]
141aae9f2d9SScott Kruger  ftype='separate' if suffx.startswith('separate') else 'shared'
142aae9f2d9SScott Kruger  return keynm,lvars,ftype
14344776d8cSScott Kruger
144e53dc769SScott Krugerdef _getSeparateTestvars(testDict):
145e53dc769SScott Kruger  """
146e53dc769SScott Kruger  Given: dictionary that may have
147e53dc769SScott Kruger  Return:  Variables that cause a test split
148e53dc769SScott Kruger  """
149e53dc769SScott Kruger  vals=None
150e53dc769SScott Kruger  sepvars=[]
151e53dc769SScott Kruger  # Check nsize
1525b6bfdb9SJed Brown  if 'nsize' in testDict:
153e53dc769SScott Kruger    varset=testDict['nsize']
154aae9f2d9SScott Kruger    if '{{' in varset:
155aae9f2d9SScott Kruger      keynm,lvars,ftype=parseLoopArgs(varset)
156aae9f2d9SScott Kruger      if ftype=='separate': sepvars.append(keynm)
157e53dc769SScott Kruger
158e53dc769SScott Kruger  # Now check args
1595b6bfdb9SJed Brown  if 'args' not in testDict: return sepvars
160d87d9516SStefano Zampini  for varset in re.split('(^|\W)-(?=[a-zA-Z])',testDict['args']):
161e53dc769SScott Kruger    if not varset.strip(): continue
162aae9f2d9SScott Kruger    if '{{' in varset:
163e53dc769SScott Kruger      # Assuming only one for loop per var specification
164aae9f2d9SScott Kruger      keynm,lvars,ftype=parseLoopArgs(varset)
165aae9f2d9SScott Kruger      if ftype=='separate': sepvars.append(keynm)
166e53dc769SScott Kruger
167e53dc769SScott Kruger  return sepvars
168e53dc769SScott Kruger
1694d82b48cSScott Krugerdef _getNewArgs(args):
1704d82b48cSScott Kruger  """
1714d82b48cSScott Kruger  Given: String that has args that might have loops in them
1724d82b48cSScott Kruger  Return:  All of the arguments/values that do not have
1734d82b48cSScott Kruger             for 'separate output' in for loops
1744d82b48cSScott Kruger  """
1754d82b48cSScott Kruger  newargs=''
1764d82b48cSScott Kruger  if not args.strip(): return args
177d87d9516SStefano Zampini  for varset in re.split('(^|\W)-(?=[a-zA-Z])',args):
1784d82b48cSScott Kruger    if not varset.strip(): continue
17914f228deSScott Kruger    if '{{' in varset and 'separate' in varset: continue
1804d82b48cSScott Kruger    newargs+="-"+varset.strip()+" "
1814d82b48cSScott Kruger
1824d82b48cSScott Kruger  return newargs
1834d82b48cSScott Kruger
18444776d8cSScott Krugerdef _getVarVals(findvar,testDict):
18544776d8cSScott Kruger  """
18644776d8cSScott Kruger  Given: variable that is either nsize or in args
1874d82b48cSScott Kruger  Return:  Values to loop over and the other arguments
1884d82b48cSScott Kruger    Note that we keep the other arguments even if they have
1894d82b48cSScott Kruger    for loops to enable stepping through all of the for lops
19044776d8cSScott Kruger  """
1914d82b48cSScott Kruger  save_vals=None
19244776d8cSScott Kruger  if findvar=='nsize':
193e53dc769SScott Kruger    varset=testDict[findvar]
1944d82b48cSScott Kruger    keynm,save_vals,ftype=parseLoopArgs('nsize '+varset)
19544776d8cSScott Kruger  else:
19644776d8cSScott Kruger    varlist=[]
19744776d8cSScott Kruger    for varset in re.split('-(?=[a-zA-Z])',testDict['args']):
19844776d8cSScott Kruger      if not varset.strip(): continue
1994d82b48cSScott Kruger      if '{{' not in varset: continue
200aae9f2d9SScott Kruger      keyvar,vals,ftype=parseLoopArgs(varset)
2014d82b48cSScott Kruger      if keyvar==findvar:
2024d82b48cSScott Kruger        save_vals=vals
20344776d8cSScott Kruger
2045b6bfdb9SJed Brown  if not save_vals: raise Exception("Could not find separate_testvar: "+findvar)
2054d82b48cSScott Kruger  return save_vals
20644776d8cSScott Kruger
2074f8a0bffSScott Krugerdef genTestsSeparateTestvars(intests,indicts,final=False):
20844776d8cSScott Kruger  """
209e53dc769SScott Kruger  Given: testname, sdict with 'separate_testvars
21044776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
2114d82b48cSScott Kruger    The tricky part here is the {{ ... }separate output}
2124d82b48cSScott Kruger    that can be used multiple times
21344776d8cSScott Kruger  """
21444776d8cSScott Kruger  testnames=[]; sdicts=[]
21544776d8cSScott Kruger  for i in range(len(intests)):
21644776d8cSScott Kruger    testname=intests[i]; sdict=indicts[i]; i+=1
217e53dc769SScott Kruger    separate_testvars=_getSeparateTestvars(sdict)
218e53dc769SScott Kruger    if len(separate_testvars)>0:
2194d82b48cSScott Kruger      sep_dicts=[sdict.copy()]
2204d82b48cSScott Kruger      if 'args' in sep_dicts[0]:
2214d82b48cSScott Kruger        sep_dicts[0]['args']=_getNewArgs(sdict['args'])
2224d82b48cSScott Kruger      sep_testnames=[testname]
223e53dc769SScott Kruger      for kvar in separate_testvars:
2244d82b48cSScott Kruger        kvals=_getVarVals(kvar,sdict)
2254d82b48cSScott Kruger
2264d82b48cSScott Kruger        # Have to do loop over previous var/val combos as well
2274d82b48cSScott Kruger        # and accumulate as we go
2284d82b48cSScott Kruger        val_testnames=[]; val_dicts=[]
22944776d8cSScott Kruger        for val in kvals.split():
230541979b6SScott Kruger          gensuffix="_"+kvar+"-"+val.replace(',','__')
2314d82b48cSScott Kruger          for kvaltestnm in sep_testnames:
2324d82b48cSScott Kruger            val_testnames.append(kvaltestnm+gensuffix)
2334d82b48cSScott Kruger          for kv in sep_dicts:
2344d82b48cSScott Kruger            kvardict=kv.copy()
2354d82b48cSScott Kruger            # If the last var then we have the final version
2364d82b48cSScott Kruger            if 'suffix' in sdict:
2374d82b48cSScott Kruger              kvardict['suffix']+=gensuffix
2380bcc1aabSScott Kruger            else:
2390bcc1aabSScott Kruger              kvardict['suffix']=gensuffix
24044776d8cSScott Kruger            if kvar=='nsize':
24144776d8cSScott Kruger              kvardict[kvar]=val
24244776d8cSScott Kruger            else:
2434d82b48cSScott Kruger              kvardict['args']+="-"+kvar+" "+val+" "
2444d82b48cSScott Kruger            val_dicts.append(kvardict)
2454d82b48cSScott Kruger        sep_testnames=val_testnames
2464d82b48cSScott Kruger        sep_dicts=val_dicts
2474d82b48cSScott Kruger      testnames+=sep_testnames
2484d82b48cSScott Kruger      sdicts+=sep_dicts
24944776d8cSScott Kruger    else:
2504f8a0bffSScott Kruger      # These are plain vanilla tests (no subtests, no loops) that
2514f8a0bffSScott Kruger      # do not have a suffix.  This makes the targets match up with
2524f8a0bffSScott Kruger      # the output file (testname_1.out)
2534f8a0bffSScott Kruger      if final:
2544f8a0bffSScott Kruger          if '_' not in testname: testname+='_1'
25544776d8cSScott Kruger      testnames.append(testname)
25644776d8cSScott Kruger      sdicts.append(sdict)
25744776d8cSScott Kruger  return testnames,sdicts
25844776d8cSScott Kruger
25944776d8cSScott Krugerdef genTestsSubtestSuffix(testnames,sdicts):
26044776d8cSScott Kruger  """
26144776d8cSScott Kruger  Given: testname, sdict with separate_testvars
26244776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
26344776d8cSScott Kruger  """
26444776d8cSScott Kruger  tnms=[]; sdcts=[]
26544776d8cSScott Kruger  for i in range(len(testnames)):
26644776d8cSScott Kruger    testname=testnames[i]
26744776d8cSScott Kruger    rmsubtests=[]; keepSubtests=False
2685b6bfdb9SJed Brown    if 'subtests' in sdicts[i]:
26944776d8cSScott Kruger      for stest in sdicts[i]["subtests"]:
2705b6bfdb9SJed Brown        if 'suffix' in sdicts[i][stest]:
27144776d8cSScott Kruger          rmsubtests.append(stest)
27244776d8cSScott Kruger          gensuffix="_"+sdicts[i][stest]['suffix']
27344776d8cSScott Kruger          newtestnm=testname+gensuffix
27444776d8cSScott Kruger          tnms.append(newtestnm)
27544776d8cSScott Kruger          newsdict=sdicts[i].copy()
27644776d8cSScott Kruger          del newsdict['subtests']
27744776d8cSScott Kruger          # Have to hand update
27844776d8cSScott Kruger          # Append
27944776d8cSScott Kruger          for kup in appendlist:
2805b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
2815b6bfdb9SJed Brown              if kup in sdicts[i]:
28244776d8cSScott Kruger                newsdict[kup]=sdicts[i][kup]+" "+sdicts[i][stest][kup]
28344776d8cSScott Kruger              else:
28444776d8cSScott Kruger                newsdict[kup]=sdicts[i][stest][kup]
28544776d8cSScott Kruger          # Promote
28644776d8cSScott Kruger          for kup in acceptedkeys:
28744776d8cSScott Kruger            if kup in appendlist: continue
2885b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
28944776d8cSScott Kruger              newsdict[kup]=sdicts[i][stest][kup]
29044776d8cSScott Kruger          # Cleanup
29144776d8cSScott Kruger          for st in sdicts[i]["subtests"]: del newsdict[st]
29244776d8cSScott Kruger          sdcts.append(newsdict)
29344776d8cSScott Kruger        else:
29444776d8cSScott Kruger          keepSubtests=True
29544776d8cSScott Kruger    else:
29644776d8cSScott Kruger      tnms.append(testnames[i])
29744776d8cSScott Kruger      sdcts.append(sdicts[i])
2980bcc1aabSScott Kruger    # If a subtest without a suffix exists, then save it
29944776d8cSScott Kruger    if keepSubtests:
30044776d8cSScott Kruger      tnms.append(testnames[i])
3010bcc1aabSScott Kruger      newsdict=sdicts[i].copy()
3020bcc1aabSScott Kruger      # Prune the tests to prepare for keeping
3030bcc1aabSScott Kruger      for rmtest in rmsubtests:
3040bcc1aabSScott Kruger        newsdict['subtests'].remove(rmtest)
3050bcc1aabSScott Kruger        del newsdict[rmtest]
3060bcc1aabSScott Kruger      sdcts.append(newsdict)
30744776d8cSScott Kruger    i+=1
30844776d8cSScott Kruger  return tnms,sdcts
30944776d8cSScott Kruger
31044776d8cSScott Krugerdef splitTests(testname,sdict):
31144776d8cSScott Kruger  """
31244776d8cSScott Kruger  Given: testname and YAML-generated dictionary
31344776d8cSScott Kruger  Return: list of names and dictionaries corresponding to each test
31444776d8cSScott Kruger          given that the YAML language allows for multiple tests
31544776d8cSScott Kruger  """
31644776d8cSScott Kruger
31744776d8cSScott Kruger  # Order: Parent sep_tv, subtests suffix, subtests sep_tv
31844776d8cSScott Kruger  testnames,sdicts=genTestsSeparateTestvars([testname],[sdict])
31944776d8cSScott Kruger  testnames,sdicts=genTestsSubtestSuffix(testnames,sdicts)
3204f8a0bffSScott Kruger  testnames,sdicts=genTestsSeparateTestvars(testnames,sdicts,final=True)
32144776d8cSScott Kruger
32244776d8cSScott Kruger  # Because I am altering the list, I do this in passes.  Inelegant
32344776d8cSScott Kruger
32444776d8cSScott Kruger  return testnames, sdicts
32544776d8cSScott Kruger
3266cecdbdcSScott Krugerdef parseTest(testStr,srcfile,verbosity):
32729921a8fSScott Kruger  """
32829921a8fSScott Kruger  This parses an individual test
32929921a8fSScott Kruger  YAML is hierarchial so should use a state machine in the general case,
33053f2a965SBarry Smith  but in practice we only support two levels of test:
33129921a8fSScott Kruger  """
33229921a8fSScott Kruger  basename=os.path.basename(srcfile)
33329921a8fSScott Kruger  # Handle the new at the begininng
33429921a8fSScott Kruger  bn=re.sub("new_","",basename)
33529921a8fSScott Kruger  # This is the default
33629921a8fSScott Kruger  testname="run"+os.path.splitext(bn)[0]
33729921a8fSScott Kruger
33829921a8fSScott Kruger  # Tests that have default everything (so empty effectively)
33978659935SScott Kruger  if len(testStr)==0: return [testname], [{}]
34029921a8fSScott Kruger
34129921a8fSScott Kruger  striptest=_stripIndent(testStr,srcfile)
34229921a8fSScott Kruger
34329921a8fSScott Kruger  # go through and parse
34429921a8fSScott Kruger  subtestnum=0
34529921a8fSScott Kruger  subdict={}
34668a9e459SScott Kruger  comments=[]
34768a9e459SScott Kruger  indentlevel=0
34868a9e459SScott Kruger  for ln in striptest.split("\n"):
349c4b80baaSScott Kruger    line=ln.split('#')[0].rstrip()
350cadd188bSScott Kruger    if verbosity>2: print(line)
3510bcc1aabSScott Kruger    comment=("" if len(ln.split("#"))>0 else " ".join(ln.split("#")[1:]).strip())
35268a9e459SScott Kruger    if comment: comments.append(comment)
35329921a8fSScott Kruger    if not line.strip(): continue
35444776d8cSScott Kruger    lsplit=line.split(':')
35544776d8cSScott Kruger    if len(lsplit)==0: raise Exception("Missing : in line: "+line)
35644776d8cSScott Kruger    indentcount=lsplit[0].count(" ")
35744776d8cSScott Kruger    var=lsplit[0].strip()
35840ae0433SScott Kruger    val=line[line.find(':')+1:].strip()
35944776d8cSScott Kruger    if not var in acceptedkeys: raise Exception("Not a defined key: "+var+" from:  "+line)
36029921a8fSScott Kruger    # Start by seeing if we are in a subtest
36129921a8fSScott Kruger    if line.startswith(" "):
362ecc1beb5SScott Kruger      if var in subdict[subtestname]:
363ecc1beb5SScott Kruger        subdict[subtestname][var]+=" "+val
364ecc1beb5SScott Kruger      else:
365c0658d2aSScott Kruger        subdict[subtestname][var]=val
36668a9e459SScott Kruger      if not indentlevel: indentlevel=indentcount
367cadd188bSScott Kruger      #if indentlevel!=indentcount: print("Error in indentation:", ln)
36829921a8fSScott Kruger    # Determine subtest name and make dict
36929921a8fSScott Kruger    elif var=="test":
37029921a8fSScott Kruger      subtestname="test"+str(subtestnum)
37129921a8fSScott Kruger      subdict[subtestname]={}
3725b6bfdb9SJed Brown      if "subtests" not in subdict: subdict["subtests"]=[]
37329921a8fSScott Kruger      subdict["subtests"].append(subtestname)
37429921a8fSScott Kruger      subtestnum=subtestnum+1
37568a9e459SScott Kruger    # The rest are easy
37629921a8fSScott Kruger    else:
37744776d8cSScott Kruger      # For convenience, it is sometimes convenient to list twice
3785b6bfdb9SJed Brown      if var in subdict:
37944776d8cSScott Kruger        if var in appendlist:
38044776d8cSScott Kruger          subdict[var]+=" "+val
38144776d8cSScott Kruger        else:
38244776d8cSScott Kruger          raise Exception(var+" entered twice: "+line)
38344776d8cSScott Kruger      else:
384c0658d2aSScott Kruger        subdict[var]=val
38529921a8fSScott Kruger      if var=="suffix":
38629921a8fSScott Kruger        if len(val)>0:
3874f8a0bffSScott Kruger          testname+="_"+val
38829921a8fSScott Kruger
38968a9e459SScott Kruger  if len(comments): subdict['comments']="\n".join(comments).lstrip("\n")
3904f8a0bffSScott Kruger
3914f8a0bffSScott Kruger  # A test block can create multiple tests.  This does that logic
39244776d8cSScott Kruger  testnames,subdicts=splitTests(testname,subdict)
39344776d8cSScott Kruger  return testnames,subdicts
39429921a8fSScott Kruger
3956cecdbdcSScott Krugerdef parseTests(testStr,srcfile,fileNums,verbosity):
39629921a8fSScott Kruger  """
39729921a8fSScott Kruger  Parse the yaml string describing tests and return
39829921a8fSScott Kruger  a dictionary with the info in the form of:
39929921a8fSScott Kruger    testDict[test][subtest]
40029921a8fSScott Kruger  This is an inelegant parser as we do not wish to
40129921a8fSScott Kruger  introduce a new dependency by bringing in pyyaml.
40229921a8fSScott Kruger  The advantage is that validation can be done as
40329921a8fSScott Kruger  it is parsed (e.g., 'test' is the only top-level node)
40429921a8fSScott Kruger  """
40529921a8fSScott Kruger
40629921a8fSScott Kruger  testDict={}
40729921a8fSScott Kruger
40829921a8fSScott Kruger  # The first entry should be test: but it might be indented.
40944776d8cSScott Kruger  newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums)
410cadd188bSScott Kruger  if verbosity>2: print(srcfile)
41129921a8fSScott Kruger
412e4653983SScott Kruger  ## Check and see if we have build requirements
413e4653983SScott Kruger  addToRunRequirements=None
414aec507c4SScott Kruger  if "\nbuild:" in newTestStr:
415aec507c4SScott Kruger    testDict['build']={}
416aec507c4SScott Kruger    # The file info is already here and need to append
417aec507c4SScott Kruger    Part1=newTestStr.split("build:")[1]
418aec507c4SScott Kruger    fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0]
419aec507c4SScott Kruger    for bkey in buildkeys:
420aec507c4SScott Kruger      if bkey+":" in fileInfo:
421aec507c4SScott Kruger        testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
422aec507c4SScott Kruger        #if verbosity>1: bkey+": "+testDict['build'][bkey]
423e4653983SScott Kruger      # If a runtime requires are put into build, push them down to all run tests
424e4653983SScott Kruger      # At this point, we are working with strings and not lists
425e4653983SScott Kruger      if 'requires' in testDict['build']:
426e4653983SScott Kruger         if 'datafilespath' in testDict['build']['requires']:
427e4653983SScott Kruger             newreqs=re.sub('datafilespath','',testDict['build']['requires'])
428e4653983SScott Kruger             testDict['build']['requires']=newreqs.strip()
429e4653983SScott Kruger             addToRunRequirements='datafilespath'
430e4653983SScott Kruger
431aec507c4SScott Kruger
43229921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
433e53dc769SScott Kruger  for test in re.split("\ntest(?:set)?:",newTestStr)[1:]:
4346cecdbdcSScott Kruger    testnames,subdicts=parseTest(test,srcfile,verbosity)
43544776d8cSScott Kruger    for i in range(len(testnames)):
4365b6bfdb9SJed Brown      if testnames[i] in testDict:
4371acf9037SMatthew G. Knepley        raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile)
438e4653983SScott Kruger      # Add in build requirements that need to be moved
439e4653983SScott Kruger      if addToRunRequirements:
440e4653983SScott Kruger          if 'requires' in subdicts[i]:
441e4653983SScott Kruger              subdicts[i]['requires']+=addToRunRequirements
442e4653983SScott Kruger          else:
443e4653983SScott Kruger              subdicts[i]['requires']=addToRunRequirements
44444776d8cSScott Kruger      testDict[testnames[i]]=subdicts[i]
44529921a8fSScott Kruger
44629921a8fSScott Kruger  return testDict
44729921a8fSScott Kruger
4486cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity):
44929921a8fSScott Kruger  """
45029921a8fSScott Kruger  Parse single example files and return dictionary of the form:
45129921a8fSScott Kruger    testDict[srcfile][test][subtest]
45229921a8fSScott Kruger  """
45329921a8fSScott Kruger  debug=False
454cadd188bSScott Kruger  basename=os.path.basename(srcfile)
455cadd188bSScott Kruger  if basename=='makefile': return {}
456cadd188bSScott Kruger
45729921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
45829921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
45929921a8fSScott Kruger  os.chdir(basedir)
46029921a8fSScott Kruger
46129921a8fSScott Kruger  testDict={}
462cadd188bSScott Kruger  sh=open(basename,"r"); fileStr=sh.read(); sh.close()
46329921a8fSScott Kruger
46429921a8fSScott Kruger  ## Start with doing the tests
46529921a8fSScott Kruger  #
46629921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
46744776d8cSScott Kruger  fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1
46829921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
4696f029658SMatthew G. Knepley  # one
47029921a8fSScott Kruger  srcTests=[]
47129921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
47229921a8fSScott Kruger  testString=" ".join(srcTests)
47344776d8cSScott Kruger  flen=len(testString.split("\n"))
47444776d8cSScott Kruger  fend=fstart+flen-1
47544776d8cSScott Kruger  fileNums=range(fstart,fend)
4766cecdbdcSScott Kruger  testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity)
477aec507c4SScott Kruger  # Massage dictionary for build requirements
478aec507c4SScott Kruger  if 'build' in testDict[basename]:
479aec507c4SScott Kruger    testDict[basename].update(testDict[basename]['build'])
480aec507c4SScott Kruger    del testDict[basename]['build']
48129921a8fSScott Kruger
48229921a8fSScott Kruger
48329921a8fSScott Kruger  os.chdir(curdir)
48429921a8fSScott Kruger  return testDict
48529921a8fSScott Kruger
4866cecdbdcSScott Krugerdef parseTestDir(directory,verbosity):
48729921a8fSScott Kruger  """
48829921a8fSScott Kruger  Parse single example files and return dictionary of the form:
48929921a8fSScott Kruger    testDict[srcfile][test][subtest]
49029921a8fSScott Kruger  """
49129921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
49229921a8fSScott Kruger  basedir=os.path.realpath(directory)
49329921a8fSScott Kruger  os.chdir(basedir)
49429921a8fSScott Kruger
49529921a8fSScott Kruger  tDict={}
49609a6cbfcSBernhard M. Wiedemann  for test_file in sorted(glob.glob("new_ex*.*")):
4976cecdbdcSScott Kruger    tDict.update(parseTestFile(test_file,verbosity))
49829921a8fSScott Kruger
49929921a8fSScott Kruger  os.chdir(curdir)
50029921a8fSScott Kruger  return tDict
50129921a8fSScott Kruger
50229921a8fSScott Krugerdef printExParseDict(rDict):
50329921a8fSScott Kruger  """
50429921a8fSScott Kruger  This is useful for debugging
50529921a8fSScott Kruger  """
50629921a8fSScott Kruger  indent="   "
50729921a8fSScott Kruger  for sfile in rDict:
508cadd188bSScott Kruger    print(sfile)
509c3d83d22SScott Kruger    sortkeys=list(rDict[sfile].keys())
51044776d8cSScott Kruger    sortkeys.sort()
51144776d8cSScott Kruger    for runex in sortkeys:
51269fa9ab3SScott Kruger      if runex == 'requires':
51369fa9ab3SScott Kruger        print(indent+runex+':'+str(rDict[sfile][runex]))
51469fa9ab3SScott Kruger        continue
515cadd188bSScott Kruger      print(indent+runex)
5165b6bfdb9SJed Brown      if type(rDict[sfile][runex])==bytes:
517cadd188bSScott Kruger        print(indent*2+rDict[sfile][runex])
51829921a8fSScott Kruger      else:
51929921a8fSScott Kruger        for var in rDict[sfile][runex]:
52044776d8cSScott Kruger          if var.startswith("test"): continue
521cadd188bSScott Kruger          print(indent*2+var+": "+str(rDict[sfile][runex][var]))
5225b6bfdb9SJed Brown        if 'subtests' in rDict[sfile][runex]:
52344776d8cSScott Kruger          for var in rDict[sfile][runex]['subtests']:
524cadd188bSScott Kruger            print(indent*2+var)
52529921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
526cadd188bSScott Kruger              print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2]))
527cadd188bSScott Kruger      print("\n")
52829921a8fSScott Kruger  return
52929921a8fSScott Kruger
53029921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
53129921a8fSScott Kruger
53229921a8fSScott Kruger    if directory:
5336cecdbdcSScott Kruger      tDict=parseTestDir(directory,verbosity)
53429921a8fSScott Kruger    else:
5356cecdbdcSScott Kruger      tDict=parseTestFile(test_file,verbosity)
53629921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
53729921a8fSScott Kruger
53829921a8fSScott Kruger    return
53929921a8fSScott Kruger
54029921a8fSScott Krugerif __name__ == '__main__':
54129921a8fSScott Kruger    import optparse
54229921a8fSScott Kruger    parser = optparse.OptionParser()
54329921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
54429921a8fSScott Kruger                      default="", help='Directory containing files to parse')
54529921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
54629921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
54729921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
54829921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
54929921a8fSScott Kruger    opts, extra_args = parser.parse_args()
55029921a8fSScott Kruger
55129921a8fSScott Kruger    if extra_args:
55229921a8fSScott Kruger        import sys
55329921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
55429921a8fSScott Kruger        exit(1)
55529921a8fSScott Kruger    if not opts.test_file and not opts.directory:
556cadd188bSScott Kruger      print("test file or directory is required")
55729921a8fSScott Kruger      parser.print_usage()
55829921a8fSScott Kruger      sys.exit()
55929921a8fSScott Kruger
56029921a8fSScott Kruger    # Need verbosity to be an integer
56129921a8fSScott Kruger    try:
56229921a8fSScott Kruger      verbosity=int(opts.verbosity)
56329921a8fSScott Kruger    except:
56429921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
56529921a8fSScott Kruger
56629921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
567