xref: /petsc/config/testparse.py (revision 2bbaaa9f75d4074843bfaa1fcbd2889eec50d2b3)
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
52*2bbaaa9fSScott Krugeracceptedkeys="test nsize requires command suffix diff_args args filter filter_output localrunfiles comments TODO SKIP output_file timeoutfactor".split()
53*2bbaaa9fSScott Krugerappendlist="args diff_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
144*2bbaaa9fSScott Krugerdef _getLoopVars(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
150*2bbaaa9fSScott Kruger  loopVars={}
151*2bbaaa9fSScott Kruger  loopVars['separate']=[]
152*2bbaaa9fSScott Kruger  loopVars['shared']=[]
153e53dc769SScott Kruger  # Check nsize
1545b6bfdb9SJed Brown  if 'nsize' in testDict:
155e53dc769SScott Kruger    varset=testDict['nsize']
156aae9f2d9SScott Kruger    if '{{' in varset:
157aae9f2d9SScott Kruger      keynm,lvars,ftype=parseLoopArgs(varset)
158*2bbaaa9fSScott Kruger      if ftype=='separate': loopVars['separate'].append(keynm)
159e53dc769SScott Kruger
160e53dc769SScott Kruger  # Now check args
161*2bbaaa9fSScott Kruger  if 'args' not in testDict: return loopVars
162d87d9516SStefano Zampini  for varset in re.split('(^|\W)-(?=[a-zA-Z])',testDict['args']):
163e53dc769SScott Kruger    if not varset.strip(): continue
164aae9f2d9SScott Kruger    if '{{' in varset:
165e53dc769SScott Kruger      # Assuming only one for loop per var specification
166aae9f2d9SScott Kruger      keynm,lvars,ftype=parseLoopArgs(varset)
167*2bbaaa9fSScott Kruger      loopVars[ftype].append(keynm)
168e53dc769SScott Kruger
169*2bbaaa9fSScott Kruger  return loopVars
170e53dc769SScott Kruger
171*2bbaaa9fSScott Krugerdef _getNewArgs(args,separate=True):
1724d82b48cSScott Kruger  """
1734d82b48cSScott Kruger  Given: String that has args that might have loops in them
1744d82b48cSScott Kruger  Return:  All of the arguments/values that do not have
1754d82b48cSScott Kruger             for 'separate output' in for loops
176*2bbaaa9fSScott Kruger             unless separate=False
1774d82b48cSScott Kruger  """
1784d82b48cSScott Kruger  newargs=''
1794d82b48cSScott Kruger  if not args.strip(): return args
180d87d9516SStefano Zampini  for varset in re.split('(^|\W)-(?=[a-zA-Z])',args):
1814d82b48cSScott Kruger    if not varset.strip(): continue
182*2bbaaa9fSScott Kruger    if '{{' in varset:
183*2bbaaa9fSScott Kruger      if separate:
184*2bbaaa9fSScott Kruger         if 'separate' in varset: continue
185*2bbaaa9fSScott Kruger      else:
186*2bbaaa9fSScott Kruger         if 'separate' not in varset: continue
187*2bbaaa9fSScott Kruger
1884d82b48cSScott Kruger    newargs+="-"+varset.strip()+" "
1894d82b48cSScott Kruger
1904d82b48cSScott Kruger  return newargs
1914d82b48cSScott Kruger
19244776d8cSScott Krugerdef _getVarVals(findvar,testDict):
19344776d8cSScott Kruger  """
19444776d8cSScott Kruger  Given: variable that is either nsize or in args
1954d82b48cSScott Kruger  Return:  Values to loop over and the other arguments
1964d82b48cSScott Kruger    Note that we keep the other arguments even if they have
1974d82b48cSScott Kruger    for loops to enable stepping through all of the for lops
19844776d8cSScott Kruger  """
1994d82b48cSScott Kruger  save_vals=None
20044776d8cSScott Kruger  if findvar=='nsize':
201e53dc769SScott Kruger    varset=testDict[findvar]
2024d82b48cSScott Kruger    keynm,save_vals,ftype=parseLoopArgs('nsize '+varset)
20344776d8cSScott Kruger  else:
20444776d8cSScott Kruger    varlist=[]
20544776d8cSScott Kruger    for varset in re.split('-(?=[a-zA-Z])',testDict['args']):
20644776d8cSScott Kruger      if not varset.strip(): continue
2074d82b48cSScott Kruger      if '{{' not in varset: continue
208aae9f2d9SScott Kruger      keyvar,vals,ftype=parseLoopArgs(varset)
2094d82b48cSScott Kruger      if keyvar==findvar:
2104d82b48cSScott Kruger        save_vals=vals
21144776d8cSScott Kruger
2125b6bfdb9SJed Brown  if not save_vals: raise Exception("Could not find separate_testvar: "+findvar)
2134d82b48cSScott Kruger  return save_vals
21444776d8cSScott Kruger
2154f8a0bffSScott Krugerdef genTestsSeparateTestvars(intests,indicts,final=False):
21644776d8cSScott Kruger  """
217e53dc769SScott Kruger  Given: testname, sdict with 'separate_testvars
21844776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
2194d82b48cSScott Kruger    The tricky part here is the {{ ... }separate output}
2204d82b48cSScott Kruger    that can be used multiple times
22144776d8cSScott Kruger  """
22244776d8cSScott Kruger  testnames=[]; sdicts=[]
22344776d8cSScott Kruger  for i in range(len(intests)):
22444776d8cSScott Kruger    testname=intests[i]; sdict=indicts[i]; i+=1
225*2bbaaa9fSScott Kruger    loopVars=_getLoopVars(sdict)
226*2bbaaa9fSScott Kruger    if len(loopVars['shared'])>0 and not final:
227*2bbaaa9fSScott Kruger      # Need to remove shared loop vars and push down to subtests
228*2bbaaa9fSScott Kruger      if 'subtests' in sdict:
229*2bbaaa9fSScott Kruger        for varset in re.split('(^|\W)-(?=[a-zA-Z])',sdict['args']):
230*2bbaaa9fSScott Kruger          if '{{' in varset:
231*2bbaaa9fSScott Kruger              for stest in sdict['subtests']:
232*2bbaaa9fSScott Kruger                if 'args' in sdict[stest]:
233*2bbaaa9fSScott Kruger                  sdict[stest]['args']+=' -'+varset
234*2bbaaa9fSScott Kruger                else:
235*2bbaaa9fSScott Kruger                  sdict[stest]['args']="-"+varset
236*2bbaaa9fSScott Kruger        sdict['args']=_getNewArgs(sdict['args'],separate=False)
237*2bbaaa9fSScott Kruger    if len(loopVars['separate'])>0:
2384d82b48cSScott Kruger      sep_dicts=[sdict.copy()]
2394d82b48cSScott Kruger      if 'args' in sep_dicts[0]:
2404d82b48cSScott Kruger        sep_dicts[0]['args']=_getNewArgs(sdict['args'])
2414d82b48cSScott Kruger      sep_testnames=[testname]
242*2bbaaa9fSScott Kruger      for kvar in loopVars['separate']:
2434d82b48cSScott Kruger        kvals=_getVarVals(kvar,sdict)
2444d82b48cSScott Kruger
2454d82b48cSScott Kruger        # Have to do loop over previous var/val combos as well
2464d82b48cSScott Kruger        # and accumulate as we go
2474d82b48cSScott Kruger        val_testnames=[]; val_dicts=[]
24844776d8cSScott Kruger        for val in kvals.split():
249541979b6SScott Kruger          gensuffix="_"+kvar+"-"+val.replace(',','__')
2504d82b48cSScott Kruger          for kvaltestnm in sep_testnames:
2514d82b48cSScott Kruger            val_testnames.append(kvaltestnm+gensuffix)
2524d82b48cSScott Kruger          for kv in sep_dicts:
2534d82b48cSScott Kruger            kvardict=kv.copy()
2544d82b48cSScott Kruger            # If the last var then we have the final version
2554d82b48cSScott Kruger            if 'suffix' in sdict:
2564d82b48cSScott Kruger              kvardict['suffix']+=gensuffix
2570bcc1aabSScott Kruger            else:
2580bcc1aabSScott Kruger              kvardict['suffix']=gensuffix
25944776d8cSScott Kruger            if kvar=='nsize':
26044776d8cSScott Kruger              kvardict[kvar]=val
26144776d8cSScott Kruger            else:
2624d82b48cSScott Kruger              kvardict['args']+="-"+kvar+" "+val+" "
2634d82b48cSScott Kruger            val_dicts.append(kvardict)
2644d82b48cSScott Kruger        sep_testnames=val_testnames
2654d82b48cSScott Kruger        sep_dicts=val_dicts
2664d82b48cSScott Kruger      testnames+=sep_testnames
2674d82b48cSScott Kruger      sdicts+=sep_dicts
26844776d8cSScott Kruger    else:
2694f8a0bffSScott Kruger      # These are plain vanilla tests (no subtests, no loops) that
2704f8a0bffSScott Kruger      # do not have a suffix.  This makes the targets match up with
2714f8a0bffSScott Kruger      # the output file (testname_1.out)
2724f8a0bffSScott Kruger      if final:
2734f8a0bffSScott Kruger          if '_' not in testname: testname+='_1'
27444776d8cSScott Kruger      testnames.append(testname)
27544776d8cSScott Kruger      sdicts.append(sdict)
27644776d8cSScott Kruger  return testnames,sdicts
27744776d8cSScott Kruger
27844776d8cSScott Krugerdef genTestsSubtestSuffix(testnames,sdicts):
27944776d8cSScott Kruger  """
28044776d8cSScott Kruger  Given: testname, sdict with separate_testvars
28144776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
28244776d8cSScott Kruger  """
28344776d8cSScott Kruger  tnms=[]; sdcts=[]
28444776d8cSScott Kruger  for i in range(len(testnames)):
28544776d8cSScott Kruger    testname=testnames[i]
28644776d8cSScott Kruger    rmsubtests=[]; keepSubtests=False
2875b6bfdb9SJed Brown    if 'subtests' in sdicts[i]:
28844776d8cSScott Kruger      for stest in sdicts[i]["subtests"]:
2895b6bfdb9SJed Brown        if 'suffix' in sdicts[i][stest]:
29044776d8cSScott Kruger          rmsubtests.append(stest)
29144776d8cSScott Kruger          gensuffix="_"+sdicts[i][stest]['suffix']
29244776d8cSScott Kruger          newtestnm=testname+gensuffix
29344776d8cSScott Kruger          tnms.append(newtestnm)
29444776d8cSScott Kruger          newsdict=sdicts[i].copy()
29544776d8cSScott Kruger          del newsdict['subtests']
29644776d8cSScott Kruger          # Have to hand update
29744776d8cSScott Kruger          # Append
29844776d8cSScott Kruger          for kup in appendlist:
2995b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
3005b6bfdb9SJed Brown              if kup in sdicts[i]:
30144776d8cSScott Kruger                newsdict[kup]=sdicts[i][kup]+" "+sdicts[i][stest][kup]
30244776d8cSScott Kruger              else:
30344776d8cSScott Kruger                newsdict[kup]=sdicts[i][stest][kup]
30444776d8cSScott Kruger          # Promote
30544776d8cSScott Kruger          for kup in acceptedkeys:
30644776d8cSScott Kruger            if kup in appendlist: continue
3075b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
30844776d8cSScott Kruger              newsdict[kup]=sdicts[i][stest][kup]
30944776d8cSScott Kruger          # Cleanup
31044776d8cSScott Kruger          for st in sdicts[i]["subtests"]: del newsdict[st]
31144776d8cSScott Kruger          sdcts.append(newsdict)
31244776d8cSScott Kruger        else:
31344776d8cSScott Kruger          keepSubtests=True
31444776d8cSScott Kruger    else:
31544776d8cSScott Kruger      tnms.append(testnames[i])
31644776d8cSScott Kruger      sdcts.append(sdicts[i])
3170bcc1aabSScott Kruger    # If a subtest without a suffix exists, then save it
31844776d8cSScott Kruger    if keepSubtests:
31944776d8cSScott Kruger      tnms.append(testnames[i])
3200bcc1aabSScott Kruger      newsdict=sdicts[i].copy()
3210bcc1aabSScott Kruger      # Prune the tests to prepare for keeping
3220bcc1aabSScott Kruger      for rmtest in rmsubtests:
3230bcc1aabSScott Kruger        newsdict['subtests'].remove(rmtest)
3240bcc1aabSScott Kruger        del newsdict[rmtest]
3250bcc1aabSScott Kruger      sdcts.append(newsdict)
32644776d8cSScott Kruger    i+=1
32744776d8cSScott Kruger  return tnms,sdcts
32844776d8cSScott Kruger
32944776d8cSScott Krugerdef splitTests(testname,sdict):
33044776d8cSScott Kruger  """
33144776d8cSScott Kruger  Given: testname and YAML-generated dictionary
33244776d8cSScott Kruger  Return: list of names and dictionaries corresponding to each test
33344776d8cSScott Kruger          given that the YAML language allows for multiple tests
33444776d8cSScott Kruger  """
33544776d8cSScott Kruger
33644776d8cSScott Kruger  # Order: Parent sep_tv, subtests suffix, subtests sep_tv
33744776d8cSScott Kruger  testnames,sdicts=genTestsSeparateTestvars([testname],[sdict])
33844776d8cSScott Kruger  testnames,sdicts=genTestsSubtestSuffix(testnames,sdicts)
3394f8a0bffSScott Kruger  testnames,sdicts=genTestsSeparateTestvars(testnames,sdicts,final=True)
34044776d8cSScott Kruger
34144776d8cSScott Kruger  # Because I am altering the list, I do this in passes.  Inelegant
34244776d8cSScott Kruger
34344776d8cSScott Kruger  return testnames, sdicts
34444776d8cSScott Kruger
3456cecdbdcSScott Krugerdef parseTest(testStr,srcfile,verbosity):
34629921a8fSScott Kruger  """
34729921a8fSScott Kruger  This parses an individual test
34829921a8fSScott Kruger  YAML is hierarchial so should use a state machine in the general case,
34953f2a965SBarry Smith  but in practice we only support two levels of test:
35029921a8fSScott Kruger  """
35129921a8fSScott Kruger  basename=os.path.basename(srcfile)
35229921a8fSScott Kruger  # Handle the new at the begininng
35329921a8fSScott Kruger  bn=re.sub("new_","",basename)
35429921a8fSScott Kruger  # This is the default
35529921a8fSScott Kruger  testname="run"+os.path.splitext(bn)[0]
35629921a8fSScott Kruger
35729921a8fSScott Kruger  # Tests that have default everything (so empty effectively)
358*2bbaaa9fSScott Kruger  if len(testStr)==0:
359*2bbaaa9fSScott Kruger      if '_' not in testname: testname+='_1'
360*2bbaaa9fSScott Kruger      return [testname], [{}]
36129921a8fSScott Kruger
36229921a8fSScott Kruger  striptest=_stripIndent(testStr,srcfile)
36329921a8fSScott Kruger
36429921a8fSScott Kruger  # go through and parse
36529921a8fSScott Kruger  subtestnum=0
36629921a8fSScott Kruger  subdict={}
36768a9e459SScott Kruger  comments=[]
36868a9e459SScott Kruger  indentlevel=0
36968a9e459SScott Kruger  for ln in striptest.split("\n"):
370c4b80baaSScott Kruger    line=ln.split('#')[0].rstrip()
371cadd188bSScott Kruger    if verbosity>2: print(line)
3720bcc1aabSScott Kruger    comment=("" if len(ln.split("#"))>0 else " ".join(ln.split("#")[1:]).strip())
37368a9e459SScott Kruger    if comment: comments.append(comment)
37429921a8fSScott Kruger    if not line.strip(): continue
37544776d8cSScott Kruger    lsplit=line.split(':')
37644776d8cSScott Kruger    if len(lsplit)==0: raise Exception("Missing : in line: "+line)
37744776d8cSScott Kruger    indentcount=lsplit[0].count(" ")
37844776d8cSScott Kruger    var=lsplit[0].strip()
37940ae0433SScott Kruger    val=line[line.find(':')+1:].strip()
38044776d8cSScott Kruger    if not var in acceptedkeys: raise Exception("Not a defined key: "+var+" from:  "+line)
38129921a8fSScott Kruger    # Start by seeing if we are in a subtest
38229921a8fSScott Kruger    if line.startswith(" "):
383ecc1beb5SScott Kruger      if var in subdict[subtestname]:
384ecc1beb5SScott Kruger        subdict[subtestname][var]+=" "+val
385ecc1beb5SScott Kruger      else:
386c0658d2aSScott Kruger        subdict[subtestname][var]=val
38768a9e459SScott Kruger      if not indentlevel: indentlevel=indentcount
388cadd188bSScott Kruger      #if indentlevel!=indentcount: print("Error in indentation:", ln)
38929921a8fSScott Kruger    # Determine subtest name and make dict
39029921a8fSScott Kruger    elif var=="test":
39129921a8fSScott Kruger      subtestname="test"+str(subtestnum)
39229921a8fSScott Kruger      subdict[subtestname]={}
3935b6bfdb9SJed Brown      if "subtests" not in subdict: subdict["subtests"]=[]
39429921a8fSScott Kruger      subdict["subtests"].append(subtestname)
39529921a8fSScott Kruger      subtestnum=subtestnum+1
39668a9e459SScott Kruger    # The rest are easy
39729921a8fSScott Kruger    else:
39844776d8cSScott Kruger      # For convenience, it is sometimes convenient to list twice
3995b6bfdb9SJed Brown      if var in subdict:
40044776d8cSScott Kruger        if var in appendlist:
40144776d8cSScott Kruger          subdict[var]+=" "+val
40244776d8cSScott Kruger        else:
40344776d8cSScott Kruger          raise Exception(var+" entered twice: "+line)
40444776d8cSScott Kruger      else:
405c0658d2aSScott Kruger        subdict[var]=val
40629921a8fSScott Kruger      if var=="suffix":
40729921a8fSScott Kruger        if len(val)>0:
4084f8a0bffSScott Kruger          testname+="_"+val
40929921a8fSScott Kruger
41068a9e459SScott Kruger  if len(comments): subdict['comments']="\n".join(comments).lstrip("\n")
4114f8a0bffSScott Kruger
4124f8a0bffSScott Kruger  # A test block can create multiple tests.  This does that logic
41344776d8cSScott Kruger  testnames,subdicts=splitTests(testname,subdict)
41444776d8cSScott Kruger  return testnames,subdicts
41529921a8fSScott Kruger
4166cecdbdcSScott Krugerdef parseTests(testStr,srcfile,fileNums,verbosity):
41729921a8fSScott Kruger  """
41829921a8fSScott Kruger  Parse the yaml string describing tests and return
41929921a8fSScott Kruger  a dictionary with the info in the form of:
42029921a8fSScott Kruger    testDict[test][subtest]
42129921a8fSScott Kruger  This is an inelegant parser as we do not wish to
42229921a8fSScott Kruger  introduce a new dependency by bringing in pyyaml.
42329921a8fSScott Kruger  The advantage is that validation can be done as
42429921a8fSScott Kruger  it is parsed (e.g., 'test' is the only top-level node)
42529921a8fSScott Kruger  """
42629921a8fSScott Kruger
42729921a8fSScott Kruger  testDict={}
42829921a8fSScott Kruger
42929921a8fSScott Kruger  # The first entry should be test: but it might be indented.
43044776d8cSScott Kruger  newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums)
431cadd188bSScott Kruger  if verbosity>2: print(srcfile)
43229921a8fSScott Kruger
433e4653983SScott Kruger  ## Check and see if we have build requirements
434e4653983SScott Kruger  addToRunRequirements=None
435aec507c4SScott Kruger  if "\nbuild:" in newTestStr:
436aec507c4SScott Kruger    testDict['build']={}
437aec507c4SScott Kruger    # The file info is already here and need to append
438aec507c4SScott Kruger    Part1=newTestStr.split("build:")[1]
439aec507c4SScott Kruger    fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0]
440aec507c4SScott Kruger    for bkey in buildkeys:
441aec507c4SScott Kruger      if bkey+":" in fileInfo:
442aec507c4SScott Kruger        testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
443aec507c4SScott Kruger        #if verbosity>1: bkey+": "+testDict['build'][bkey]
444e4653983SScott Kruger      # If a runtime requires are put into build, push them down to all run tests
445e4653983SScott Kruger      # At this point, we are working with strings and not lists
446e4653983SScott Kruger      if 'requires' in testDict['build']:
447*2bbaaa9fSScott Kruger         addToRunRequirements=testDict['build']['requires']
448*2bbaaa9fSScott Kruger         # People put datafilespath into build, but it needs to be a runtime
449e4653983SScott Kruger         if 'datafilespath' in testDict['build']['requires']:
450e4653983SScott Kruger             newreqs=re.sub('datafilespath','',testDict['build']['requires'])
451e4653983SScott Kruger             testDict['build']['requires']=newreqs.strip()
452e4653983SScott Kruger
453aec507c4SScott Kruger
45429921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
455e53dc769SScott Kruger  for test in re.split("\ntest(?:set)?:",newTestStr)[1:]:
4566cecdbdcSScott Kruger    testnames,subdicts=parseTest(test,srcfile,verbosity)
45744776d8cSScott Kruger    for i in range(len(testnames)):
4585b6bfdb9SJed Brown      if testnames[i] in testDict:
4591acf9037SMatthew G. Knepley        raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile)
460e4653983SScott Kruger      # Add in build requirements that need to be moved
461e4653983SScott Kruger      if addToRunRequirements:
462e4653983SScott Kruger          if 'requires' in subdicts[i]:
463*2bbaaa9fSScott Kruger              subdicts[i]['requires']+=' '+addToRunRequirements
464e4653983SScott Kruger          else:
465e4653983SScott Kruger              subdicts[i]['requires']=addToRunRequirements
46644776d8cSScott Kruger      testDict[testnames[i]]=subdicts[i]
46729921a8fSScott Kruger
46829921a8fSScott Kruger  return testDict
46929921a8fSScott Kruger
4706cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity):
47129921a8fSScott Kruger  """
47229921a8fSScott Kruger  Parse single example files and return dictionary of the form:
47329921a8fSScott Kruger    testDict[srcfile][test][subtest]
47429921a8fSScott Kruger  """
47529921a8fSScott Kruger  debug=False
476cadd188bSScott Kruger  basename=os.path.basename(srcfile)
477cadd188bSScott Kruger  if basename=='makefile': return {}
478cadd188bSScott Kruger
47929921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
48029921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
48129921a8fSScott Kruger  os.chdir(basedir)
48229921a8fSScott Kruger
48329921a8fSScott Kruger  testDict={}
484cadd188bSScott Kruger  sh=open(basename,"r"); fileStr=sh.read(); sh.close()
48529921a8fSScott Kruger
48629921a8fSScott Kruger  ## Start with doing the tests
48729921a8fSScott Kruger  #
48829921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
48944776d8cSScott Kruger  fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1
49029921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
4916f029658SMatthew G. Knepley  # one
49229921a8fSScott Kruger  srcTests=[]
49329921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
49429921a8fSScott Kruger  testString=" ".join(srcTests)
49544776d8cSScott Kruger  flen=len(testString.split("\n"))
49644776d8cSScott Kruger  fend=fstart+flen-1
49744776d8cSScott Kruger  fileNums=range(fstart,fend)
4986cecdbdcSScott Kruger  testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity)
499aec507c4SScott Kruger  # Massage dictionary for build requirements
500aec507c4SScott Kruger  if 'build' in testDict[basename]:
501aec507c4SScott Kruger    testDict[basename].update(testDict[basename]['build'])
502aec507c4SScott Kruger    del testDict[basename]['build']
50329921a8fSScott Kruger
50429921a8fSScott Kruger
50529921a8fSScott Kruger  os.chdir(curdir)
50629921a8fSScott Kruger  return testDict
50729921a8fSScott Kruger
5086cecdbdcSScott Krugerdef parseTestDir(directory,verbosity):
50929921a8fSScott Kruger  """
51029921a8fSScott Kruger  Parse single example files and return dictionary of the form:
51129921a8fSScott Kruger    testDict[srcfile][test][subtest]
51229921a8fSScott Kruger  """
51329921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
51429921a8fSScott Kruger  basedir=os.path.realpath(directory)
51529921a8fSScott Kruger  os.chdir(basedir)
51629921a8fSScott Kruger
51729921a8fSScott Kruger  tDict={}
51809a6cbfcSBernhard M. Wiedemann  for test_file in sorted(glob.glob("new_ex*.*")):
5196cecdbdcSScott Kruger    tDict.update(parseTestFile(test_file,verbosity))
52029921a8fSScott Kruger
52129921a8fSScott Kruger  os.chdir(curdir)
52229921a8fSScott Kruger  return tDict
52329921a8fSScott Kruger
52429921a8fSScott Krugerdef printExParseDict(rDict):
52529921a8fSScott Kruger  """
52629921a8fSScott Kruger  This is useful for debugging
52729921a8fSScott Kruger  """
52829921a8fSScott Kruger  indent="   "
52929921a8fSScott Kruger  for sfile in rDict:
530cadd188bSScott Kruger    print(sfile)
531c3d83d22SScott Kruger    sortkeys=list(rDict[sfile].keys())
53244776d8cSScott Kruger    sortkeys.sort()
53344776d8cSScott Kruger    for runex in sortkeys:
53469fa9ab3SScott Kruger      if runex == 'requires':
53569fa9ab3SScott Kruger        print(indent+runex+':'+str(rDict[sfile][runex]))
53669fa9ab3SScott Kruger        continue
537cadd188bSScott Kruger      print(indent+runex)
5385b6bfdb9SJed Brown      if type(rDict[sfile][runex])==bytes:
539cadd188bSScott Kruger        print(indent*2+rDict[sfile][runex])
54029921a8fSScott Kruger      else:
54129921a8fSScott Kruger        for var in rDict[sfile][runex]:
54244776d8cSScott Kruger          if var.startswith("test"): continue
543cadd188bSScott Kruger          print(indent*2+var+": "+str(rDict[sfile][runex][var]))
5445b6bfdb9SJed Brown        if 'subtests' in rDict[sfile][runex]:
54544776d8cSScott Kruger          for var in rDict[sfile][runex]['subtests']:
546cadd188bSScott Kruger            print(indent*2+var)
54729921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
548cadd188bSScott Kruger              print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2]))
549cadd188bSScott Kruger      print("\n")
55029921a8fSScott Kruger  return
55129921a8fSScott Kruger
55229921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
55329921a8fSScott Kruger
55429921a8fSScott Kruger    if directory:
5556cecdbdcSScott Kruger      tDict=parseTestDir(directory,verbosity)
55629921a8fSScott Kruger    else:
5576cecdbdcSScott Kruger      tDict=parseTestFile(test_file,verbosity)
55829921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
55929921a8fSScott Kruger
56029921a8fSScott Kruger    return
56129921a8fSScott Kruger
56229921a8fSScott Krugerif __name__ == '__main__':
56329921a8fSScott Kruger    import optparse
56429921a8fSScott Kruger    parser = optparse.OptionParser()
56529921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
56629921a8fSScott Kruger                      default="", help='Directory containing files to parse')
56729921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
56829921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
56929921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
57029921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
57129921a8fSScott Kruger    opts, extra_args = parser.parse_args()
57229921a8fSScott Kruger
57329921a8fSScott Kruger    if extra_args:
57429921a8fSScott Kruger        import sys
57529921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
57629921a8fSScott Kruger        exit(1)
57729921a8fSScott Kruger    if not opts.test_file and not opts.directory:
578cadd188bSScott Kruger      print("test file or directory is required")
57929921a8fSScott Kruger      parser.print_usage()
58029921a8fSScott Kruger      sys.exit()
58129921a8fSScott Kruger
58229921a8fSScott Kruger    # Need verbosity to be an integer
58329921a8fSScott Kruger    try:
58429921a8fSScott Kruger      verbosity=int(opts.verbosity)
58529921a8fSScott Kruger    except:
58629921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
58729921a8fSScott Kruger
58829921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
589