xref: /petsc/config/testparse.py (revision 5e3618604c1c5afb5b01a824de3cb89fd9424a1e)
129921a8fSScott Kruger#!/usr/bin/env python
229921a8fSScott Kruger"""
329921a8fSScott KrugerParse the test file and return a dictionary.
429921a8fSScott Kruger
529921a8fSScott KrugerQuick usage::
629921a8fSScott Kruger
7c3a89c15SBarry Smith  lib/petsc/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 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
57*5e361860SScott Krugerdef getDefaultOutputFileRoot(testname):
58*5e361860SScott Kruger  """
59*5e361860SScott Kruger  Given testname, give DefaultRoot and DefaultOutputFilename
60*5e361860SScott Kruger  e.g., runex1 gives ex1_1, output/ex1_1.out
61*5e361860SScott Kruger  """
62*5e361860SScott Kruger  defroot=(re.sub("run","",testname) if testname.startswith("run") else testname)
63*5e361860SScott Kruger  if not "_" in defroot: defroot=defroot+"_1"
64*5e361860SScott Kruger  return defroot
65*5e361860SScott 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
1794d82b48cSScott Kruger    if '{{' not in varset:
1804d82b48cSScott Kruger      if 'separate' not in varset:
1814d82b48cSScott Kruger        newargs+="-"+varset.strip()+" "
1824d82b48cSScott Kruger
1834d82b48cSScott Kruger  return newargs
1844d82b48cSScott Kruger
18544776d8cSScott Krugerdef _getVarVals(findvar,testDict):
18644776d8cSScott Kruger  """
18744776d8cSScott Kruger  Given: variable that is either nsize or in args
1884d82b48cSScott Kruger  Return:  Values to loop over and the other arguments
1894d82b48cSScott Kruger    Note that we keep the other arguments even if they have
1904d82b48cSScott Kruger    for loops to enable stepping through all of the for lops
19144776d8cSScott Kruger  """
1924d82b48cSScott Kruger  save_vals=None
19344776d8cSScott Kruger  if findvar=='nsize':
194e53dc769SScott Kruger    varset=testDict[findvar]
1954d82b48cSScott Kruger    keynm,save_vals,ftype=parseLoopArgs('nsize '+varset)
19644776d8cSScott Kruger  else:
19744776d8cSScott Kruger    varlist=[]
19844776d8cSScott Kruger    for varset in re.split('-(?=[a-zA-Z])',testDict['args']):
19944776d8cSScott Kruger      if not varset.strip(): continue
2004d82b48cSScott Kruger      if '{{' not in varset: continue
201aae9f2d9SScott Kruger      keyvar,vals,ftype=parseLoopArgs(varset)
2024d82b48cSScott Kruger      if keyvar==findvar:
2034d82b48cSScott Kruger        save_vals=vals
20444776d8cSScott Kruger
2055b6bfdb9SJed Brown  if not save_vals: raise Exception("Could not find separate_testvar: "+findvar)
2064d82b48cSScott Kruger  return save_vals
20744776d8cSScott Kruger
2084f8a0bffSScott Krugerdef genTestsSeparateTestvars(intests,indicts,final=False):
20944776d8cSScott Kruger  """
210e53dc769SScott Kruger  Given: testname, sdict with 'separate_testvars
21144776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
2124d82b48cSScott Kruger    The tricky part here is the {{ ... }separate output}
2134d82b48cSScott Kruger    that can be used multiple times
21444776d8cSScott Kruger  """
21544776d8cSScott Kruger  testnames=[]; sdicts=[]
21644776d8cSScott Kruger  for i in range(len(intests)):
21744776d8cSScott Kruger    testname=intests[i]; sdict=indicts[i]; i+=1
218e53dc769SScott Kruger    separate_testvars=_getSeparateTestvars(sdict)
219e53dc769SScott Kruger    if len(separate_testvars)>0:
2204d82b48cSScott Kruger      sep_dicts=[sdict.copy()]
2214d82b48cSScott Kruger      if 'args' in sep_dicts[0]:
2224d82b48cSScott Kruger        sep_dicts[0]['args']=_getNewArgs(sdict['args'])
2234d82b48cSScott Kruger      sep_testnames=[testname]
224e53dc769SScott Kruger      for kvar in separate_testvars:
2254d82b48cSScott Kruger        kvals=_getVarVals(kvar,sdict)
2264d82b48cSScott Kruger
2274d82b48cSScott Kruger        # Have to do loop over previous var/val combos as well
2284d82b48cSScott Kruger        # and accumulate as we go
2294d82b48cSScott Kruger        val_testnames=[]; val_dicts=[]
23044776d8cSScott Kruger        for val in kvals.split():
231541979b6SScott Kruger          gensuffix="_"+kvar+"-"+val.replace(',','__')
2324d82b48cSScott Kruger          for kvaltestnm in sep_testnames:
2334d82b48cSScott Kruger            val_testnames.append(kvaltestnm+gensuffix)
2344d82b48cSScott Kruger          for kv in sep_dicts:
2354d82b48cSScott Kruger            kvardict=kv.copy()
2364d82b48cSScott Kruger            # If the last var then we have the final version
2374d82b48cSScott Kruger            if 'suffix' in sdict:
2384d82b48cSScott Kruger              kvardict['suffix']+=gensuffix
2390bcc1aabSScott Kruger            else:
2400bcc1aabSScott Kruger              kvardict['suffix']=gensuffix
24144776d8cSScott Kruger            if kvar=='nsize':
24244776d8cSScott Kruger              kvardict[kvar]=val
24344776d8cSScott Kruger            else:
2444d82b48cSScott Kruger              kvardict['args']+="-"+kvar+" "+val+" "
2454d82b48cSScott Kruger            val_dicts.append(kvardict)
2464d82b48cSScott Kruger        sep_testnames=val_testnames
2474d82b48cSScott Kruger        sep_dicts=val_dicts
2484d82b48cSScott Kruger      testnames+=sep_testnames
2494d82b48cSScott Kruger      sdicts+=sep_dicts
25044776d8cSScott Kruger    else:
2514f8a0bffSScott Kruger      # These are plain vanilla tests (no subtests, no loops) that
2524f8a0bffSScott Kruger      # do not have a suffix.  This makes the targets match up with
2534f8a0bffSScott Kruger      # the output file (testname_1.out)
2544f8a0bffSScott Kruger      if final:
2554f8a0bffSScott Kruger          if '_' not in testname: testname+='_1'
25644776d8cSScott Kruger      testnames.append(testname)
25744776d8cSScott Kruger      sdicts.append(sdict)
25844776d8cSScott Kruger  return testnames,sdicts
25944776d8cSScott Kruger
26044776d8cSScott Krugerdef genTestsSubtestSuffix(testnames,sdicts):
26144776d8cSScott Kruger  """
26244776d8cSScott Kruger  Given: testname, sdict with separate_testvars
26344776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
26444776d8cSScott Kruger  """
26544776d8cSScott Kruger  tnms=[]; sdcts=[]
26644776d8cSScott Kruger  for i in range(len(testnames)):
26744776d8cSScott Kruger    testname=testnames[i]
26844776d8cSScott Kruger    rmsubtests=[]; keepSubtests=False
2695b6bfdb9SJed Brown    if 'subtests' in sdicts[i]:
27044776d8cSScott Kruger      for stest in sdicts[i]["subtests"]:
2715b6bfdb9SJed Brown        if 'suffix' in sdicts[i][stest]:
27244776d8cSScott Kruger          rmsubtests.append(stest)
27344776d8cSScott Kruger          gensuffix="_"+sdicts[i][stest]['suffix']
27444776d8cSScott Kruger          newtestnm=testname+gensuffix
27544776d8cSScott Kruger          tnms.append(newtestnm)
27644776d8cSScott Kruger          newsdict=sdicts[i].copy()
27744776d8cSScott Kruger          del newsdict['subtests']
27844776d8cSScott Kruger          # Have to hand update
27944776d8cSScott Kruger          # Append
28044776d8cSScott Kruger          for kup in appendlist:
2815b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
2825b6bfdb9SJed Brown              if kup in sdicts[i]:
28344776d8cSScott Kruger                newsdict[kup]=sdicts[i][kup]+" "+sdicts[i][stest][kup]
28444776d8cSScott Kruger              else:
28544776d8cSScott Kruger                newsdict[kup]=sdicts[i][stest][kup]
28644776d8cSScott Kruger          # Promote
28744776d8cSScott Kruger          for kup in acceptedkeys:
28844776d8cSScott Kruger            if kup in appendlist: continue
2895b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
29044776d8cSScott Kruger              newsdict[kup]=sdicts[i][stest][kup]
29144776d8cSScott Kruger          # Cleanup
29244776d8cSScott Kruger          for st in sdicts[i]["subtests"]: del newsdict[st]
29344776d8cSScott Kruger          sdcts.append(newsdict)
29444776d8cSScott Kruger        else:
29544776d8cSScott Kruger          keepSubtests=True
29644776d8cSScott Kruger    else:
29744776d8cSScott Kruger      tnms.append(testnames[i])
29844776d8cSScott Kruger      sdcts.append(sdicts[i])
2990bcc1aabSScott Kruger    # If a subtest without a suffix exists, then save it
30044776d8cSScott Kruger    if keepSubtests:
30144776d8cSScott Kruger      tnms.append(testnames[i])
3020bcc1aabSScott Kruger      newsdict=sdicts[i].copy()
3030bcc1aabSScott Kruger      # Prune the tests to prepare for keeping
3040bcc1aabSScott Kruger      for rmtest in rmsubtests:
3050bcc1aabSScott Kruger        newsdict['subtests'].remove(rmtest)
3060bcc1aabSScott Kruger        del newsdict[rmtest]
3070bcc1aabSScott Kruger      sdcts.append(newsdict)
30844776d8cSScott Kruger    i+=1
30944776d8cSScott Kruger  return tnms,sdcts
31044776d8cSScott Kruger
31144776d8cSScott Krugerdef splitTests(testname,sdict):
31244776d8cSScott Kruger  """
31344776d8cSScott Kruger  Given: testname and YAML-generated dictionary
31444776d8cSScott Kruger  Return: list of names and dictionaries corresponding to each test
31544776d8cSScott Kruger          given that the YAML language allows for multiple tests
31644776d8cSScott Kruger  """
31744776d8cSScott Kruger
31844776d8cSScott Kruger  # Order: Parent sep_tv, subtests suffix, subtests sep_tv
31944776d8cSScott Kruger  testnames,sdicts=genTestsSeparateTestvars([testname],[sdict])
32044776d8cSScott Kruger  testnames,sdicts=genTestsSubtestSuffix(testnames,sdicts)
3214f8a0bffSScott Kruger  testnames,sdicts=genTestsSeparateTestvars(testnames,sdicts,final=True)
32244776d8cSScott Kruger
32344776d8cSScott Kruger  # Because I am altering the list, I do this in passes.  Inelegant
32444776d8cSScott Kruger
32544776d8cSScott Kruger  return testnames, sdicts
32644776d8cSScott Kruger
3276cecdbdcSScott Krugerdef parseTest(testStr,srcfile,verbosity):
32829921a8fSScott Kruger  """
32929921a8fSScott Kruger  This parses an individual test
33029921a8fSScott Kruger  YAML is hierarchial so should use a state machine in the general case,
33153f2a965SBarry Smith  but in practice we only support two levels of test:
33229921a8fSScott Kruger  """
33329921a8fSScott Kruger  basename=os.path.basename(srcfile)
33429921a8fSScott Kruger  # Handle the new at the begininng
33529921a8fSScott Kruger  bn=re.sub("new_","",basename)
33629921a8fSScott Kruger  # This is the default
33729921a8fSScott Kruger  testname="run"+os.path.splitext(bn)[0]
33829921a8fSScott Kruger
33929921a8fSScott Kruger  # Tests that have default everything (so empty effectively)
34078659935SScott Kruger  if len(testStr)==0: return [testname], [{}]
34129921a8fSScott Kruger
34229921a8fSScott Kruger  striptest=_stripIndent(testStr,srcfile)
34329921a8fSScott Kruger
34429921a8fSScott Kruger  # go through and parse
34529921a8fSScott Kruger  subtestnum=0
34629921a8fSScott Kruger  subdict={}
34768a9e459SScott Kruger  comments=[]
34868a9e459SScott Kruger  indentlevel=0
34968a9e459SScott Kruger  for ln in striptest.split("\n"):
350c4b80baaSScott Kruger    line=ln.split('#')[0].rstrip()
351cadd188bSScott Kruger    if verbosity>2: print(line)
3520bcc1aabSScott Kruger    comment=("" if len(ln.split("#"))>0 else " ".join(ln.split("#")[1:]).strip())
35368a9e459SScott Kruger    if comment: comments.append(comment)
35429921a8fSScott Kruger    if not line.strip(): continue
35544776d8cSScott Kruger    lsplit=line.split(':')
35644776d8cSScott Kruger    if len(lsplit)==0: raise Exception("Missing : in line: "+line)
35744776d8cSScott Kruger    indentcount=lsplit[0].count(" ")
35844776d8cSScott Kruger    var=lsplit[0].strip()
35940ae0433SScott Kruger    val=line[line.find(':')+1:].strip()
36044776d8cSScott Kruger    if not var in acceptedkeys: raise Exception("Not a defined key: "+var+" from:  "+line)
36129921a8fSScott Kruger    # Start by seeing if we are in a subtest
36229921a8fSScott Kruger    if line.startswith(" "):
363ecc1beb5SScott Kruger      if var in subdict[subtestname]:
364ecc1beb5SScott Kruger        subdict[subtestname][var]+=" "+val
365ecc1beb5SScott Kruger      else:
366c0658d2aSScott Kruger        subdict[subtestname][var]=val
36768a9e459SScott Kruger      if not indentlevel: indentlevel=indentcount
368cadd188bSScott Kruger      #if indentlevel!=indentcount: print("Error in indentation:", ln)
36929921a8fSScott Kruger    # Determine subtest name and make dict
37029921a8fSScott Kruger    elif var=="test":
37129921a8fSScott Kruger      subtestname="test"+str(subtestnum)
37229921a8fSScott Kruger      subdict[subtestname]={}
3735b6bfdb9SJed Brown      if "subtests" not in subdict: subdict["subtests"]=[]
37429921a8fSScott Kruger      subdict["subtests"].append(subtestname)
37529921a8fSScott Kruger      subtestnum=subtestnum+1
37668a9e459SScott Kruger    # The rest are easy
37729921a8fSScott Kruger    else:
37844776d8cSScott Kruger      # For convenience, it is sometimes convenient to list twice
3795b6bfdb9SJed Brown      if var in subdict:
38044776d8cSScott Kruger        if var in appendlist:
38144776d8cSScott Kruger          subdict[var]+=" "+val
38244776d8cSScott Kruger        else:
38344776d8cSScott Kruger          raise Exception(var+" entered twice: "+line)
38444776d8cSScott Kruger      else:
385c0658d2aSScott Kruger        subdict[var]=val
38629921a8fSScott Kruger      if var=="suffix":
38729921a8fSScott Kruger        if len(val)>0:
3884f8a0bffSScott Kruger          testname+="_"+val
38929921a8fSScott Kruger
39068a9e459SScott Kruger  if len(comments): subdict['comments']="\n".join(comments).lstrip("\n")
3914f8a0bffSScott Kruger
3924f8a0bffSScott Kruger  # A test block can create multiple tests.  This does that logic
39344776d8cSScott Kruger  testnames,subdicts=splitTests(testname,subdict)
39444776d8cSScott Kruger  return testnames,subdicts
39529921a8fSScott Kruger
3966cecdbdcSScott Krugerdef parseTests(testStr,srcfile,fileNums,verbosity):
39729921a8fSScott Kruger  """
39829921a8fSScott Kruger  Parse the yaml string describing tests and return
39929921a8fSScott Kruger  a dictionary with the info in the form of:
40029921a8fSScott Kruger    testDict[test][subtest]
40129921a8fSScott Kruger  This is an inelegant parser as we do not wish to
40229921a8fSScott Kruger  introduce a new dependency by bringing in pyyaml.
40329921a8fSScott Kruger  The advantage is that validation can be done as
40429921a8fSScott Kruger  it is parsed (e.g., 'test' is the only top-level node)
40529921a8fSScott Kruger  """
40629921a8fSScott Kruger
40729921a8fSScott Kruger  testDict={}
40829921a8fSScott Kruger
40929921a8fSScott Kruger  # The first entry should be test: but it might be indented.
41044776d8cSScott Kruger  newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums)
411cadd188bSScott Kruger  if verbosity>2: print(srcfile)
41229921a8fSScott Kruger
413e4653983SScott Kruger  ## Check and see if we have build requirements
414e4653983SScott Kruger  addToRunRequirements=None
415aec507c4SScott Kruger  if "\nbuild:" in newTestStr:
416aec507c4SScott Kruger    testDict['build']={}
417aec507c4SScott Kruger    # The file info is already here and need to append
418aec507c4SScott Kruger    Part1=newTestStr.split("build:")[1]
419aec507c4SScott Kruger    fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0]
420aec507c4SScott Kruger    for bkey in buildkeys:
421aec507c4SScott Kruger      if bkey+":" in fileInfo:
422aec507c4SScott Kruger        testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
423aec507c4SScott Kruger        #if verbosity>1: bkey+": "+testDict['build'][bkey]
424e4653983SScott Kruger      # If a runtime requires are put into build, push them down to all run tests
425e4653983SScott Kruger      # At this point, we are working with strings and not lists
426e4653983SScott Kruger      if 'requires' in testDict['build']:
427e4653983SScott Kruger         if 'datafilespath' in testDict['build']['requires']:
428e4653983SScott Kruger             newreqs=re.sub('datafilespath','',testDict['build']['requires'])
429e4653983SScott Kruger             testDict['build']['requires']=newreqs.strip()
430e4653983SScott Kruger             addToRunRequirements='datafilespath'
431e4653983SScott Kruger
432aec507c4SScott Kruger
43329921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
434e53dc769SScott Kruger  for test in re.split("\ntest(?:set)?:",newTestStr)[1:]:
4356cecdbdcSScott Kruger    testnames,subdicts=parseTest(test,srcfile,verbosity)
43644776d8cSScott Kruger    for i in range(len(testnames)):
4375b6bfdb9SJed Brown      if testnames[i] in testDict:
4381acf9037SMatthew G. Knepley        raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile)
439e4653983SScott Kruger      # Add in build requirements that need to be moved
440e4653983SScott Kruger      if addToRunRequirements:
441e4653983SScott Kruger          if 'requires' in subdicts[i]:
442e4653983SScott Kruger              subdicts[i]['requires']+=addToRunRequirements
443e4653983SScott Kruger          else:
444e4653983SScott Kruger              subdicts[i]['requires']=addToRunRequirements
44544776d8cSScott Kruger      testDict[testnames[i]]=subdicts[i]
44629921a8fSScott Kruger
44729921a8fSScott Kruger  return testDict
44829921a8fSScott Kruger
4496cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity):
45029921a8fSScott Kruger  """
45129921a8fSScott Kruger  Parse single example files and return dictionary of the form:
45229921a8fSScott Kruger    testDict[srcfile][test][subtest]
45329921a8fSScott Kruger  """
45429921a8fSScott Kruger  debug=False
455cadd188bSScott Kruger  basename=os.path.basename(srcfile)
456cadd188bSScott Kruger  if basename=='makefile': return {}
457cadd188bSScott Kruger
45829921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
45929921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
46029921a8fSScott Kruger  os.chdir(basedir)
46129921a8fSScott Kruger
46229921a8fSScott Kruger  testDict={}
463cadd188bSScott Kruger  sh=open(basename,"r"); fileStr=sh.read(); sh.close()
46429921a8fSScott Kruger
46529921a8fSScott Kruger  ## Start with doing the tests
46629921a8fSScott Kruger  #
46729921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
46844776d8cSScott Kruger  fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1
46929921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
4706f029658SMatthew G. Knepley  # one
47129921a8fSScott Kruger  srcTests=[]
47229921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
47329921a8fSScott Kruger  testString=" ".join(srcTests)
47444776d8cSScott Kruger  flen=len(testString.split("\n"))
47544776d8cSScott Kruger  fend=fstart+flen-1
47644776d8cSScott Kruger  fileNums=range(fstart,fend)
4776cecdbdcSScott Kruger  testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity)
478aec507c4SScott Kruger  # Massage dictionary for build requirements
479aec507c4SScott Kruger  if 'build' in testDict[basename]:
480aec507c4SScott Kruger    testDict[basename].update(testDict[basename]['build'])
481aec507c4SScott Kruger    del testDict[basename]['build']
48229921a8fSScott Kruger
48329921a8fSScott Kruger
48429921a8fSScott Kruger  os.chdir(curdir)
48529921a8fSScott Kruger  return testDict
48629921a8fSScott Kruger
4876cecdbdcSScott Krugerdef parseTestDir(directory,verbosity):
48829921a8fSScott Kruger  """
48929921a8fSScott Kruger  Parse single example files and return dictionary of the form:
49029921a8fSScott Kruger    testDict[srcfile][test][subtest]
49129921a8fSScott Kruger  """
49229921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
49329921a8fSScott Kruger  basedir=os.path.realpath(directory)
49429921a8fSScott Kruger  os.chdir(basedir)
49529921a8fSScott Kruger
49629921a8fSScott Kruger  tDict={}
49709a6cbfcSBernhard M. Wiedemann  for test_file in sorted(glob.glob("new_ex*.*")):
4986cecdbdcSScott Kruger    tDict.update(parseTestFile(test_file,verbosity))
49929921a8fSScott Kruger
50029921a8fSScott Kruger  os.chdir(curdir)
50129921a8fSScott Kruger  return tDict
50229921a8fSScott Kruger
50329921a8fSScott Krugerdef printExParseDict(rDict):
50429921a8fSScott Kruger  """
50529921a8fSScott Kruger  This is useful for debugging
50629921a8fSScott Kruger  """
50729921a8fSScott Kruger  indent="   "
50829921a8fSScott Kruger  for sfile in rDict:
509cadd188bSScott Kruger    print(sfile)
510c3d83d22SScott Kruger    sortkeys=list(rDict[sfile].keys())
51144776d8cSScott Kruger    sortkeys.sort()
51244776d8cSScott Kruger    for runex in sortkeys:
513cadd188bSScott Kruger      print(indent+runex)
5145b6bfdb9SJed Brown      if type(rDict[sfile][runex])==bytes:
515cadd188bSScott Kruger        print(indent*2+rDict[sfile][runex])
51629921a8fSScott Kruger      else:
51729921a8fSScott Kruger        for var in rDict[sfile][runex]:
51844776d8cSScott Kruger          if var.startswith("test"): continue
519cadd188bSScott Kruger          print(indent*2+var+": "+str(rDict[sfile][runex][var]))
5205b6bfdb9SJed Brown        if 'subtests' in rDict[sfile][runex]:
52144776d8cSScott Kruger          for var in rDict[sfile][runex]['subtests']:
522cadd188bSScott Kruger            print(indent*2+var)
52329921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
524cadd188bSScott Kruger              print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2]))
525cadd188bSScott Kruger      print("\n")
52629921a8fSScott Kruger  return
52729921a8fSScott Kruger
52829921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
52929921a8fSScott Kruger
53029921a8fSScott Kruger    if directory:
5316cecdbdcSScott Kruger      tDict=parseTestDir(directory,verbosity)
53229921a8fSScott Kruger    else:
5336cecdbdcSScott Kruger      tDict=parseTestFile(test_file,verbosity)
53429921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
53529921a8fSScott Kruger
53629921a8fSScott Kruger    return
53729921a8fSScott Kruger
53829921a8fSScott Krugerif __name__ == '__main__':
53929921a8fSScott Kruger    import optparse
54029921a8fSScott Kruger    parser = optparse.OptionParser()
54129921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
54229921a8fSScott Kruger                      default="", help='Directory containing files to parse')
54329921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
54429921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
54529921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
54629921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
54729921a8fSScott Kruger    opts, extra_args = parser.parse_args()
54829921a8fSScott Kruger
54929921a8fSScott Kruger    if extra_args:
55029921a8fSScott Kruger        import sys
55129921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
55229921a8fSScott Kruger        exit(1)
55329921a8fSScott Kruger    if not opts.test_file and not opts.directory:
554cadd188bSScott Kruger      print("test file or directory is required")
55529921a8fSScott Kruger      parser.print_usage()
55629921a8fSScott Kruger      sys.exit()
55729921a8fSScott Kruger
55829921a8fSScott Kruger    # Need verbosity to be an integer
55929921a8fSScott Kruger    try:
56029921a8fSScott Kruger      verbosity=int(opts.verbosity)
56129921a8fSScott Kruger    except:
56229921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
56329921a8fSScott Kruger
56429921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
565