xref: /petsc/config/testparse.py (revision 2be3497a0667c37409bb8ce1ecca867fd735ff54)
129921a8fSScott Kruger#!/usr/bin/env python
229921a8fSScott Kruger"""
329921a8fSScott KrugerParse the test file and return a dictionary.
429921a8fSScott Kruger
529921a8fSScott KrugerQuick usage::
629921a8fSScott Kruger
7c4762a1bSJed 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  """
312*2be3497aSPatrick Sanan  Given: testname and dictionary generated from the YAML-like definition
31344776d8cSScott Kruger  Return: list of names and dictionaries corresponding to each test
314*2be3497aSPatrick Sanan          given that the YAML-like 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
329*2be3497aSPatrick Sanan  Our YAML-like language 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  """
397*2be3497aSPatrick Sanan  Parse the YAML-like string describing tests and return
39829921a8fSScott Kruger  a dictionary with the info in the form of:
39929921a8fSScott Kruger    testDict[test][subtest]
40029921a8fSScott Kruger  """
40129921a8fSScott Kruger
40229921a8fSScott Kruger  testDict={}
40329921a8fSScott Kruger
40429921a8fSScott Kruger  # The first entry should be test: but it might be indented.
40544776d8cSScott Kruger  newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums)
406cadd188bSScott Kruger  if verbosity>2: print(srcfile)
40729921a8fSScott Kruger
408e4653983SScott Kruger  ## Check and see if we have build requirements
409e4653983SScott Kruger  addToRunRequirements=None
410aec507c4SScott Kruger  if "\nbuild:" in newTestStr:
411aec507c4SScott Kruger    testDict['build']={}
412aec507c4SScott Kruger    # The file info is already here and need to append
413aec507c4SScott Kruger    Part1=newTestStr.split("build:")[1]
414aec507c4SScott Kruger    fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0]
415aec507c4SScott Kruger    for bkey in buildkeys:
416aec507c4SScott Kruger      if bkey+":" in fileInfo:
417aec507c4SScott Kruger        testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
418aec507c4SScott Kruger        #if verbosity>1: bkey+": "+testDict['build'][bkey]
419e4653983SScott Kruger      # If a runtime requires are put into build, push them down to all run tests
420e4653983SScott Kruger      # At this point, we are working with strings and not lists
421e4653983SScott Kruger      if 'requires' in testDict['build']:
422e4653983SScott Kruger         if 'datafilespath' in testDict['build']['requires']:
423e4653983SScott Kruger             newreqs=re.sub('datafilespath','',testDict['build']['requires'])
424e4653983SScott Kruger             testDict['build']['requires']=newreqs.strip()
425e4653983SScott Kruger             addToRunRequirements='datafilespath'
426e4653983SScott Kruger
427aec507c4SScott Kruger
42829921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
429e53dc769SScott Kruger  for test in re.split("\ntest(?:set)?:",newTestStr)[1:]:
4306cecdbdcSScott Kruger    testnames,subdicts=parseTest(test,srcfile,verbosity)
43144776d8cSScott Kruger    for i in range(len(testnames)):
4325b6bfdb9SJed Brown      if testnames[i] in testDict:
4331acf9037SMatthew G. Knepley        raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile)
434e4653983SScott Kruger      # Add in build requirements that need to be moved
435e4653983SScott Kruger      if addToRunRequirements:
436e4653983SScott Kruger          if 'requires' in subdicts[i]:
437e4653983SScott Kruger              subdicts[i]['requires']+=addToRunRequirements
438e4653983SScott Kruger          else:
439e4653983SScott Kruger              subdicts[i]['requires']=addToRunRequirements
44044776d8cSScott Kruger      testDict[testnames[i]]=subdicts[i]
44129921a8fSScott Kruger
44229921a8fSScott Kruger  return testDict
44329921a8fSScott Kruger
4446cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity):
44529921a8fSScott Kruger  """
44629921a8fSScott Kruger  Parse single example files and return dictionary of the form:
44729921a8fSScott Kruger    testDict[srcfile][test][subtest]
44829921a8fSScott Kruger  """
44929921a8fSScott Kruger  debug=False
450cadd188bSScott Kruger  basename=os.path.basename(srcfile)
451cadd188bSScott Kruger  if basename=='makefile': return {}
452cadd188bSScott Kruger
45329921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
45429921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
45529921a8fSScott Kruger  os.chdir(basedir)
45629921a8fSScott Kruger
45729921a8fSScott Kruger  testDict={}
458cadd188bSScott Kruger  sh=open(basename,"r"); fileStr=sh.read(); sh.close()
45929921a8fSScott Kruger
46029921a8fSScott Kruger  ## Start with doing the tests
46129921a8fSScott Kruger  #
46229921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
46344776d8cSScott Kruger  fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1
46429921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
4656f029658SMatthew G. Knepley  # one
46629921a8fSScott Kruger  srcTests=[]
46729921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
46829921a8fSScott Kruger  testString=" ".join(srcTests)
46944776d8cSScott Kruger  flen=len(testString.split("\n"))
47044776d8cSScott Kruger  fend=fstart+flen-1
47144776d8cSScott Kruger  fileNums=range(fstart,fend)
4726cecdbdcSScott Kruger  testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity)
473aec507c4SScott Kruger  # Massage dictionary for build requirements
474aec507c4SScott Kruger  if 'build' in testDict[basename]:
475aec507c4SScott Kruger    testDict[basename].update(testDict[basename]['build'])
476aec507c4SScott Kruger    del testDict[basename]['build']
47729921a8fSScott Kruger
47829921a8fSScott Kruger
47929921a8fSScott Kruger  os.chdir(curdir)
48029921a8fSScott Kruger  return testDict
48129921a8fSScott Kruger
4826cecdbdcSScott Krugerdef parseTestDir(directory,verbosity):
48329921a8fSScott Kruger  """
48429921a8fSScott Kruger  Parse single example files and return dictionary of the form:
48529921a8fSScott Kruger    testDict[srcfile][test][subtest]
48629921a8fSScott Kruger  """
48729921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
48829921a8fSScott Kruger  basedir=os.path.realpath(directory)
48929921a8fSScott Kruger  os.chdir(basedir)
49029921a8fSScott Kruger
49129921a8fSScott Kruger  tDict={}
49209a6cbfcSBernhard M. Wiedemann  for test_file in sorted(glob.glob("new_ex*.*")):
4936cecdbdcSScott Kruger    tDict.update(parseTestFile(test_file,verbosity))
49429921a8fSScott Kruger
49529921a8fSScott Kruger  os.chdir(curdir)
49629921a8fSScott Kruger  return tDict
49729921a8fSScott Kruger
49829921a8fSScott Krugerdef printExParseDict(rDict):
49929921a8fSScott Kruger  """
50029921a8fSScott Kruger  This is useful for debugging
50129921a8fSScott Kruger  """
50229921a8fSScott Kruger  indent="   "
50329921a8fSScott Kruger  for sfile in rDict:
504cadd188bSScott Kruger    print(sfile)
505c3d83d22SScott Kruger    sortkeys=list(rDict[sfile].keys())
50644776d8cSScott Kruger    sortkeys.sort()
50744776d8cSScott Kruger    for runex in sortkeys:
50869fa9ab3SScott Kruger      if runex == 'requires':
50969fa9ab3SScott Kruger        print(indent+runex+':'+str(rDict[sfile][runex]))
51069fa9ab3SScott Kruger        continue
511cadd188bSScott Kruger      print(indent+runex)
5125b6bfdb9SJed Brown      if type(rDict[sfile][runex])==bytes:
513cadd188bSScott Kruger        print(indent*2+rDict[sfile][runex])
51429921a8fSScott Kruger      else:
51529921a8fSScott Kruger        for var in rDict[sfile][runex]:
51644776d8cSScott Kruger          if var.startswith("test"): continue
517cadd188bSScott Kruger          print(indent*2+var+": "+str(rDict[sfile][runex][var]))
5185b6bfdb9SJed Brown        if 'subtests' in rDict[sfile][runex]:
51944776d8cSScott Kruger          for var in rDict[sfile][runex]['subtests']:
520cadd188bSScott Kruger            print(indent*2+var)
52129921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
522cadd188bSScott Kruger              print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2]))
523cadd188bSScott Kruger      print("\n")
52429921a8fSScott Kruger  return
52529921a8fSScott Kruger
52629921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
52729921a8fSScott Kruger
52829921a8fSScott Kruger    if directory:
5296cecdbdcSScott Kruger      tDict=parseTestDir(directory,verbosity)
53029921a8fSScott Kruger    else:
5316cecdbdcSScott Kruger      tDict=parseTestFile(test_file,verbosity)
53229921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
53329921a8fSScott Kruger
53429921a8fSScott Kruger    return
53529921a8fSScott Kruger
53629921a8fSScott Krugerif __name__ == '__main__':
53729921a8fSScott Kruger    import optparse
53829921a8fSScott Kruger    parser = optparse.OptionParser()
53929921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
54029921a8fSScott Kruger                      default="", help='Directory containing files to parse')
54129921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
54229921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
54329921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
54429921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
54529921a8fSScott Kruger    opts, extra_args = parser.parse_args()
54629921a8fSScott Kruger
54729921a8fSScott Kruger    if extra_args:
54829921a8fSScott Kruger        import sys
54929921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
55029921a8fSScott Kruger        exit(1)
55129921a8fSScott Kruger    if not opts.test_file and not opts.directory:
552cadd188bSScott Kruger      print("test file or directory is required")
55329921a8fSScott Kruger      parser.print_usage()
55429921a8fSScott Kruger      sys.exit()
55529921a8fSScott Kruger
55629921a8fSScott Kruger    # Need verbosity to be an integer
55729921a8fSScott Kruger    try:
55829921a8fSScott Kruger      verbosity=int(opts.verbosity)
55929921a8fSScott Kruger    except:
56029921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
56129921a8fSScott Kruger
56229921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
563