xref: /petsc/config/testparse.py (revision 3be2e2fd51614bfbd789968ca6fbd36d439d7250)
1df3bd252SSatish Balay#!/usr/bin/env python3
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
42c0558f20SBarry Smithfrom gmakegen import *
4329921a8fSScott Krugersys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
4429921a8fSScott Kruger
4529921a8fSScott Krugerimport inspect
4629921a8fSScott Krugerthisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
4729921a8fSScott Krugermaintdir=os.path.join(os.path.join(os.path.dirname(thisscriptdir),'bin'),'maint')
4829921a8fSScott Krugersys.path.insert(0,maintdir)
4929921a8fSScott Kruger
5029921a8fSScott Kruger# These are special keys describing build
5129921a8fSScott Krugerbuildkeys="requires TODO SKIP depends".split()
5229921a8fSScott Kruger
53fa236c6aSJacob Faibussowitschacceptedkeys=set("test nsize requires command suffix diff_args args filter filter_output localrunfiles comments TODO SKIP output_file timeoutfactor env".split())
54fa236c6aSJacob Faibussowitschappendlist="args diff_args requires comments env".split()
5568a9e459SScott Kruger
5668a9e459SScott Krugerimport re
5768a9e459SScott Kruger
585e361860SScott Krugerdef getDefaultOutputFileRoot(testname):
595e361860SScott Kruger  """
605e361860SScott Kruger  Given testname, give DefaultRoot and DefaultOutputFilename
615e361860SScott Kruger  e.g., runex1 gives ex1_1, output/ex1_1.out
625e361860SScott Kruger  """
635e361860SScott Kruger  defroot=(re.sub("run","",testname) if testname.startswith("run") else testname)
645e361860SScott Kruger  if not "_" in defroot: defroot=defroot+"_1"
655e361860SScott Kruger  return defroot
665e361860SScott Kruger
6744776d8cSScott Krugerdef _stripIndent(block,srcfile,entireBlock=False,fileNums=[]):
6829921a8fSScott Kruger  """
6929921a8fSScott Kruger  Go through and remove a level of indentation
7029921a8fSScott Kruger  Also strip of trailing whitespace
7129921a8fSScott Kruger  """
7229921a8fSScott Kruger  # The first entry should be test: but it might be indented.
73c0558f20SBarry Smith  ext = getlangext(srcfile)
7444776d8cSScott Kruger  stripstr=" "
7566db876fSScott Kruger  if len(fileNums)>0: lineNum=fileNums[0]-1
768ccd5183SScott Kruger  for lline in block.split("\n"):
7766db876fSScott Kruger    if len(fileNums)>0: lineNum+=1
788ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
7929921a8fSScott Kruger    if not line.strip(): continue
80c4b80baaSScott Kruger    if line.strip().startswith('#'): continue
8144776d8cSScott Kruger    if entireBlock:
8244776d8cSScott Kruger      var=line.split(":")[0].strip()
83aec507c4SScott Kruger      if not var in ['test','testset','build']:
8466db876fSScott Kruger        raise Exception("Formatting error: Cannot find test in file: "+srcfile+" at line: "+str(lineNum)+"\n")
8529921a8fSScott Kruger    nspace=len(line)-len(line.lstrip(stripstr))
8629921a8fSScott Kruger    newline=line[nspace:]
8729921a8fSScott Kruger    break
8829921a8fSScott Kruger
8929921a8fSScott Kruger  # Strip off any indentation for the whole string and any trailing
9029921a8fSScott Kruger  # whitespace for convenience
9129921a8fSScott Kruger  newTestStr="\n"
9244776d8cSScott Kruger  if len(fileNums)>0: lineNum=fileNums[0]-1
9344776d8cSScott Kruger  firstPass=True
948ccd5183SScott Kruger  for lline in block.split("\n"):
9544776d8cSScott Kruger    if len(fileNums)>0: lineNum+=1
968ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
9729921a8fSScott Kruger    if not line.strip(): continue
98c4b80baaSScott Kruger    if line.strip().startswith('#'):
99c4b80baaSScott Kruger      newTestStr+=line+'\n'
100c4b80baaSScott Kruger    else:
10129921a8fSScott Kruger      newline=line[nspace:]
102c4b80baaSScott Kruger      newTestStr+=newline.rstrip()+"\n"
10344776d8cSScott Kruger    # Do some basic indentation checks
10444776d8cSScott Kruger    if entireBlock:
10544776d8cSScott Kruger      # Don't need to check comment lines
10644776d8cSScott Kruger      if line.strip().startswith('#'): continue
10744776d8cSScott Kruger      if not newline.startswith(" "):
10844776d8cSScott Kruger        var=newline.split(":")[0].strip()
109aec507c4SScott Kruger        if not var in ['test','testset','build']:
11044776d8cSScott Kruger          err="Formatting error in file "+srcfile+" at line: " +line+"\n"
11144776d8cSScott Kruger          if len(fileNums)>0:
11244776d8cSScott Kruger            raise Exception(err+"Check indentation at line number: "+str(lineNum))
11344776d8cSScott Kruger          else:
11444776d8cSScott Kruger            raise Exception(err)
11544776d8cSScott Kruger      else:
11644776d8cSScott Kruger        var=line.split(":")[0].strip()
117aec507c4SScott Kruger        if var in ['test','testset','build']:
11844776d8cSScott Kruger          subnspace=len(line)-len(line.lstrip(stripstr))
11944776d8cSScott Kruger          if firstPass:
12044776d8cSScott Kruger            firstsubnspace=subnspace
12144776d8cSScott Kruger            firstPass=False
12244776d8cSScott Kruger          else:
12344776d8cSScott Kruger            if firstsubnspace!=subnspace:
12444776d8cSScott Kruger              err="Formatting subtest error in file "+srcfile+" at line: " +line+"\n"
12544776d8cSScott Kruger              if len(fileNums)>0:
12644776d8cSScott Kruger                raise Exception(err+"Check indentation at line number: "+str(lineNum))
12744776d8cSScott Kruger              else:
12844776d8cSScott Kruger                raise Exception(err)
12944776d8cSScott Kruger
13043c6e11bSMatthew G. Knepley  # Allow line continuation character '\'
13143c6e11bSMatthew G. Knepley  return newTestStr.replace('\\\n', ' ')
13229921a8fSScott Kruger
133aae9f2d9SScott Krugerdef parseLoopArgs(varset):
13444776d8cSScott Kruger  """
135aae9f2d9SScott Kruger  Given:   String containing loop variables
136aae9f2d9SScott Kruger  Return: tuple containing separate/shared and string of loop vars
13744776d8cSScott Kruger  """
138a449fbaeSJed Brown  keynm=varset.split("{{")[0].strip().lstrip('-')
139aae9f2d9SScott Kruger  if not keynm.strip(): keynm='nsize'
140aae9f2d9SScott Kruger  lvars=varset.split('{{')[1].split('}')[0]
141aae9f2d9SScott Kruger  suffx=varset.split('{{')[1].split('}')[1]
142aae9f2d9SScott Kruger  ftype='separate' if suffx.startswith('separate') else 'shared'
143aae9f2d9SScott Kruger  return keynm,lvars,ftype
14444776d8cSScott Kruger
1452bbaaa9fSScott Krugerdef _getLoopVars(testDict):
146e53dc769SScott Kruger  """
147e53dc769SScott Kruger  Given: dictionary that may have
148e53dc769SScott Kruger  Return:  Variables that cause a test split
149e53dc769SScott Kruger  """
150e53dc769SScott Kruger  vals=None
1512bbaaa9fSScott Kruger  loopVars={}
1522bbaaa9fSScott Kruger  loopVars['separate']=[]
1532bbaaa9fSScott Kruger  loopVars['shared']=[]
154e53dc769SScott Kruger  # Check nsize
1555b6bfdb9SJed Brown  if 'nsize' in testDict:
156e53dc769SScott Kruger    varset=testDict['nsize']
157aae9f2d9SScott Kruger    if '{{' in varset:
158aae9f2d9SScott Kruger      keynm,lvars,ftype=parseLoopArgs(varset)
1592bbaaa9fSScott Kruger      if ftype=='separate': loopVars['separate'].append(keynm)
160e53dc769SScott Kruger
161e53dc769SScott Kruger  # Now check args
1622bbaaa9fSScott Kruger  if 'args' not in testDict: return loopVars
163*3be2e2fdSJose E. Roman  for varset in re.split(r'(^|\W)-(?=[a-zA-Z])',testDict['args']):
164e53dc769SScott Kruger    if not varset.strip(): continue
165aae9f2d9SScott Kruger    if '{{' in varset:
166e53dc769SScott Kruger      # Assuming only one for loop per var specification
167aae9f2d9SScott Kruger      keynm,lvars,ftype=parseLoopArgs(varset)
1682bbaaa9fSScott Kruger      loopVars[ftype].append(keynm)
169e53dc769SScott Kruger
1702bbaaa9fSScott Kruger  return loopVars
171e53dc769SScott Kruger
1722bbaaa9fSScott Krugerdef _getNewArgs(args,separate=True):
1734d82b48cSScott Kruger  """
1744d82b48cSScott Kruger  Given: String that has args that might have loops in them
1754d82b48cSScott Kruger  Return:  All of the arguments/values that do not have
1764d82b48cSScott Kruger             for 'separate output' in for loops
1772bbaaa9fSScott Kruger             unless separate=False
1784d82b48cSScott Kruger  """
1794d82b48cSScott Kruger  newargs=''
1804d82b48cSScott Kruger  if not args.strip(): return args
181*3be2e2fdSJose E. Roman  for varset in re.split(r'(^|\W)-(?=[a-zA-Z])',args):
1824d82b48cSScott Kruger    if not varset.strip(): continue
1832bbaaa9fSScott Kruger    if '{{' in varset:
1842bbaaa9fSScott Kruger      if separate:
1852bbaaa9fSScott Kruger         if 'separate' in varset: continue
1862bbaaa9fSScott Kruger      else:
1872bbaaa9fSScott Kruger         if 'separate' not in varset: continue
1882bbaaa9fSScott Kruger
1894d82b48cSScott Kruger    newargs+="-"+varset.strip()+" "
1904d82b48cSScott Kruger
1914d82b48cSScott Kruger  return newargs
1924d82b48cSScott Kruger
19344776d8cSScott Krugerdef _getVarVals(findvar,testDict):
19444776d8cSScott Kruger  """
19544776d8cSScott Kruger  Given: variable that is either nsize or in args
1964d82b48cSScott Kruger  Return:  Values to loop over and the other arguments
1974d82b48cSScott Kruger    Note that we keep the other arguments even if they have
1984d82b48cSScott Kruger    for loops to enable stepping through all of the for lops
19944776d8cSScott Kruger  """
2004d82b48cSScott Kruger  save_vals=None
20144776d8cSScott Kruger  if findvar=='nsize':
202e53dc769SScott Kruger    varset=testDict[findvar]
2034d82b48cSScott Kruger    keynm,save_vals,ftype=parseLoopArgs('nsize '+varset)
20444776d8cSScott Kruger  else:
20544776d8cSScott Kruger    varlist=[]
20644776d8cSScott Kruger    for varset in re.split('-(?=[a-zA-Z])',testDict['args']):
20744776d8cSScott Kruger      if not varset.strip(): continue
2084d82b48cSScott Kruger      if '{{' not in varset: continue
209aae9f2d9SScott Kruger      keyvar,vals,ftype=parseLoopArgs(varset)
2104d82b48cSScott Kruger      if keyvar==findvar:
2114d82b48cSScott Kruger        save_vals=vals
21244776d8cSScott Kruger
2135b6bfdb9SJed Brown  if not save_vals: raise Exception("Could not find separate_testvar: "+findvar)
2144d82b48cSScott Kruger  return save_vals
21544776d8cSScott Kruger
2164f8a0bffSScott Krugerdef genTestsSeparateTestvars(intests,indicts,final=False):
21744776d8cSScott Kruger  """
218e53dc769SScott Kruger  Given: testname, sdict with 'separate_testvars
21944776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
2204d82b48cSScott Kruger    The tricky part here is the {{ ... }separate output}
2214d82b48cSScott Kruger    that can be used multiple times
22244776d8cSScott Kruger  """
22344776d8cSScott Kruger  testnames=[]; sdicts=[]
22444776d8cSScott Kruger  for i in range(len(intests)):
22544776d8cSScott Kruger    testname=intests[i]; sdict=indicts[i]; i+=1
2262bbaaa9fSScott Kruger    loopVars=_getLoopVars(sdict)
2272bbaaa9fSScott Kruger    if len(loopVars['shared'])>0 and not final:
2282bbaaa9fSScott Kruger      # Need to remove shared loop vars and push down to subtests
2292bbaaa9fSScott Kruger      if 'subtests' in sdict:
230*3be2e2fdSJose E. Roman        for varset in re.split(r'(^|\W)-(?=[a-zA-Z])',sdict['args']):
2312bbaaa9fSScott Kruger          if '{{' in varset:
2322bbaaa9fSScott Kruger              for stest in sdict['subtests']:
2332bbaaa9fSScott Kruger                if 'args' in sdict[stest]:
2342bbaaa9fSScott Kruger                  sdict[stest]['args']+=' -'+varset
2352bbaaa9fSScott Kruger                else:
2362bbaaa9fSScott Kruger                  sdict[stest]['args']="-"+varset
2372bbaaa9fSScott Kruger        sdict['args']=_getNewArgs(sdict['args'],separate=False)
2382bbaaa9fSScott Kruger    if len(loopVars['separate'])>0:
2394d82b48cSScott Kruger      sep_dicts=[sdict.copy()]
2404d82b48cSScott Kruger      if 'args' in sep_dicts[0]:
2414d82b48cSScott Kruger        sep_dicts[0]['args']=_getNewArgs(sdict['args'])
2424d82b48cSScott Kruger      sep_testnames=[testname]
2432bbaaa9fSScott Kruger      for kvar in loopVars['separate']:
2444d82b48cSScott Kruger        kvals=_getVarVals(kvar,sdict)
2454d82b48cSScott Kruger
2464d82b48cSScott Kruger        # Have to do loop over previous var/val combos as well
2474d82b48cSScott Kruger        # and accumulate as we go
2484d82b48cSScott Kruger        val_testnames=[]; val_dicts=[]
24944776d8cSScott Kruger        for val in kvals.split():
250541979b6SScott Kruger          gensuffix="_"+kvar+"-"+val.replace(',','__')
2514d82b48cSScott Kruger          for kvaltestnm in sep_testnames:
2524d82b48cSScott Kruger            val_testnames.append(kvaltestnm+gensuffix)
2534d82b48cSScott Kruger          for kv in sep_dicts:
2544d82b48cSScott Kruger            kvardict=kv.copy()
2554d82b48cSScott Kruger            # If the last var then we have the final version
2564d82b48cSScott Kruger            if 'suffix' in sdict:
2574d82b48cSScott Kruger              kvardict['suffix']+=gensuffix
2580bcc1aabSScott Kruger            else:
2590bcc1aabSScott Kruger              kvardict['suffix']=gensuffix
26044776d8cSScott Kruger            if kvar=='nsize':
26144776d8cSScott Kruger              kvardict[kvar]=val
26244776d8cSScott Kruger            else:
2634d82b48cSScott Kruger              kvardict['args']+="-"+kvar+" "+val+" "
2644d82b48cSScott Kruger            val_dicts.append(kvardict)
2654d82b48cSScott Kruger        sep_testnames=val_testnames
2664d82b48cSScott Kruger        sep_dicts=val_dicts
2674d82b48cSScott Kruger      testnames+=sep_testnames
2684d82b48cSScott Kruger      sdicts+=sep_dicts
26944776d8cSScott Kruger    else:
2704f8a0bffSScott Kruger      # These are plain vanilla tests (no subtests, no loops) that
2714f8a0bffSScott Kruger      # do not have a suffix.  This makes the targets match up with
2724f8a0bffSScott Kruger      # the output file (testname_1.out)
2734f8a0bffSScott Kruger      if final:
2744f8a0bffSScott Kruger          if '_' not in testname: testname+='_1'
27544776d8cSScott Kruger      testnames.append(testname)
27644776d8cSScott Kruger      sdicts.append(sdict)
27744776d8cSScott Kruger  return testnames,sdicts
27844776d8cSScott Kruger
27944776d8cSScott Krugerdef genTestsSubtestSuffix(testnames,sdicts):
28044776d8cSScott Kruger  """
28144776d8cSScott Kruger  Given: testname, sdict with separate_testvars
28244776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
28344776d8cSScott Kruger  """
28444776d8cSScott Kruger  tnms=[]; sdcts=[]
28544776d8cSScott Kruger  for i in range(len(testnames)):
28644776d8cSScott Kruger    testname=testnames[i]
28744776d8cSScott Kruger    rmsubtests=[]; keepSubtests=False
2885b6bfdb9SJed Brown    if 'subtests' in sdicts[i]:
28944776d8cSScott Kruger      for stest in sdicts[i]["subtests"]:
2905b6bfdb9SJed Brown        if 'suffix' in sdicts[i][stest]:
29144776d8cSScott Kruger          rmsubtests.append(stest)
29244776d8cSScott Kruger          gensuffix="_"+sdicts[i][stest]['suffix']
29344776d8cSScott Kruger          newtestnm=testname+gensuffix
29444776d8cSScott Kruger          tnms.append(newtestnm)
29544776d8cSScott Kruger          newsdict=sdicts[i].copy()
29644776d8cSScott Kruger          del newsdict['subtests']
29744776d8cSScott Kruger          # Have to hand update
29844776d8cSScott Kruger          # Append
29944776d8cSScott Kruger          for kup in appendlist:
3005b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
3015b6bfdb9SJed Brown              if kup in sdicts[i]:
30244776d8cSScott Kruger                newsdict[kup]=sdicts[i][kup]+" "+sdicts[i][stest][kup]
30344776d8cSScott Kruger              else:
30444776d8cSScott Kruger                newsdict[kup]=sdicts[i][stest][kup]
30544776d8cSScott Kruger          # Promote
30644776d8cSScott Kruger          for kup in acceptedkeys:
30744776d8cSScott Kruger            if kup in appendlist: continue
3085b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
30944776d8cSScott Kruger              newsdict[kup]=sdicts[i][stest][kup]
31044776d8cSScott Kruger          # Cleanup
31144776d8cSScott Kruger          for st in sdicts[i]["subtests"]: del newsdict[st]
31244776d8cSScott Kruger          sdcts.append(newsdict)
31344776d8cSScott Kruger        else:
31444776d8cSScott Kruger          keepSubtests=True
31544776d8cSScott Kruger    else:
31644776d8cSScott Kruger      tnms.append(testnames[i])
31744776d8cSScott Kruger      sdcts.append(sdicts[i])
3180bcc1aabSScott Kruger    # If a subtest without a suffix exists, then save it
31944776d8cSScott Kruger    if keepSubtests:
32044776d8cSScott Kruger      tnms.append(testnames[i])
3210bcc1aabSScott Kruger      newsdict=sdicts[i].copy()
3220bcc1aabSScott Kruger      # Prune the tests to prepare for keeping
3230bcc1aabSScott Kruger      for rmtest in rmsubtests:
3240bcc1aabSScott Kruger        newsdict['subtests'].remove(rmtest)
3250bcc1aabSScott Kruger        del newsdict[rmtest]
3260bcc1aabSScott Kruger      sdcts.append(newsdict)
32744776d8cSScott Kruger    i+=1
32844776d8cSScott Kruger  return tnms,sdcts
32944776d8cSScott Kruger
33044776d8cSScott Krugerdef splitTests(testname,sdict):
33144776d8cSScott Kruger  """
3322be3497aSPatrick Sanan  Given: testname and dictionary generated from the YAML-like definition
33344776d8cSScott Kruger  Return: list of names and dictionaries corresponding to each test
3342be3497aSPatrick Sanan          given that the YAML-like language allows for multiple tests
33544776d8cSScott Kruger  """
33644776d8cSScott Kruger
33744776d8cSScott Kruger  # Order: Parent sep_tv, subtests suffix, subtests sep_tv
33844776d8cSScott Kruger  testnames,sdicts=genTestsSeparateTestvars([testname],[sdict])
33944776d8cSScott Kruger  testnames,sdicts=genTestsSubtestSuffix(testnames,sdicts)
3404f8a0bffSScott Kruger  testnames,sdicts=genTestsSeparateTestvars(testnames,sdicts,final=True)
34144776d8cSScott Kruger
34244776d8cSScott Kruger  # Because I am altering the list, I do this in passes.  Inelegant
34344776d8cSScott Kruger
34444776d8cSScott Kruger  return testnames, sdicts
34544776d8cSScott Kruger
346080f0011SToby Isaac
347080f0011SToby Isaacdef testSplit(striptest):
348080f0011SToby Isaac  """
349080f0011SToby Isaac  Split up a test into lines, but use a shell parser to detect when newlines are within quotation marks
350080f0011SToby Isaac  and keep those together
351080f0011SToby Isaac  """
352080f0011SToby Isaac  import shlex
353080f0011SToby Isaac
354080f0011SToby Isaac  sl = shlex.shlex()
355080f0011SToby Isaac  sl.whitespace_split = True # only split at whitespace
356080f0011SToby Isaac  sl.commenters = ''
357080f0011SToby Isaac  sl.push_source(striptest)
358080f0011SToby Isaac  last_pos = sl.instream.tell()
359080f0011SToby Isaac  try:
360080f0011SToby Isaac    last_token = sl.read_token()
361080f0011SToby Isaac  except ValueError:
362080f0011SToby Isaac    print(striptest)
363080f0011SToby Isaac    raise ValueError
364080f0011SToby Isaac  last_line = ''
365080f0011SToby Isaac  while last_token != '':
366080f0011SToby Isaac    new_pos = sl.instream.tell()
367080f0011SToby Isaac    block = striptest[last_pos:new_pos]
368080f0011SToby Isaac    token_start = block.find(last_token)
369080f0011SToby Isaac    leading = block[0:token_start]
370080f0011SToby Isaac    trailing = block[(token_start + len(last_token)):]
371080f0011SToby Isaac    leading_split = leading.split('\n')
372080f0011SToby Isaac    if len(leading_split) > 1:
373080f0011SToby Isaac      yield last_line
374080f0011SToby Isaac      last_line = ''
375080f0011SToby Isaac    last_line += leading_split[-1]
376080f0011SToby Isaac    last_line += last_token
377080f0011SToby Isaac    trailing_split = trailing.split('\n')
378080f0011SToby Isaac    last_line += trailing_split[0]
379080f0011SToby Isaac    if len(trailing_split) > 1:
380080f0011SToby Isaac      yield last_line
381080f0011SToby Isaac      last_line = ''
382080f0011SToby Isaac    last_pos = new_pos
383080f0011SToby Isaac    try:
384080f0011SToby Isaac      last_token = sl.read_token()
385080f0011SToby Isaac    except ValueError:
386080f0011SToby Isaac      print(striptest)
387080f0011SToby Isaac      raise ValueError
388080f0011SToby Isaac  yield last_line
389080f0011SToby Isaac
390080f0011SToby Isaac
3916cecdbdcSScott Krugerdef parseTest(testStr,srcfile,verbosity):
39229921a8fSScott Kruger  """
39329921a8fSScott Kruger  This parses an individual test
3942be3497aSPatrick Sanan  Our YAML-like language is hierarchial so should use a state machine in the general case,
39553f2a965SBarry Smith  but in practice we only support two levels of test:
39629921a8fSScott Kruger  """
39729921a8fSScott Kruger  basename=os.path.basename(srcfile)
398d5b43468SJose E. Roman  # Handle the new at the beginning
39929921a8fSScott Kruger  bn=re.sub("new_","",basename)
40029921a8fSScott Kruger  # This is the default
401c0558f20SBarry Smith  testname="run"+getlangsplit(bn)
40229921a8fSScott Kruger
40329921a8fSScott Kruger  # Tests that have default everything (so empty effectively)
4042bbaaa9fSScott Kruger  if len(testStr)==0:
4052bbaaa9fSScott Kruger      if '_' not in testname: testname+='_1'
4062bbaaa9fSScott Kruger      return [testname], [{}]
40729921a8fSScott Kruger
40829921a8fSScott Kruger  striptest=_stripIndent(testStr,srcfile)
40929921a8fSScott Kruger
41029921a8fSScott Kruger  # go through and parse
41129921a8fSScott Kruger  subtestnum=0
41229921a8fSScott Kruger  subdict={}
41368a9e459SScott Kruger  comments=[]
41468a9e459SScott Kruger  indentlevel=0
415080f0011SToby Isaac  for ln in testSplit(striptest):
416c4b80baaSScott Kruger    line=ln.split('#')[0].rstrip()
417cadd188bSScott Kruger    if verbosity>2: print(line)
4180bcc1aabSScott Kruger    comment=("" if len(ln.split("#"))>0 else " ".join(ln.split("#")[1:]).strip())
41968a9e459SScott Kruger    if comment: comments.append(comment)
42029921a8fSScott Kruger    if not line.strip(): continue
42144776d8cSScott Kruger    lsplit=line.split(':')
42244776d8cSScott Kruger    if len(lsplit)==0: raise Exception("Missing : in line: "+line)
42344776d8cSScott Kruger    indentcount=lsplit[0].count(" ")
42444776d8cSScott Kruger    var=lsplit[0].strip()
42540ae0433SScott Kruger    val=line[line.find(':')+1:].strip()
42669647276SScott Kruger    if not var in acceptedkeys: raise Exception("Keyword: "+var+" from: "+line+" is not valid")
42729921a8fSScott Kruger    # Start by seeing if we are in a subtest
42829921a8fSScott Kruger    if line.startswith(" "):
429ecc1beb5SScott Kruger      if var in subdict[subtestname]:
430ecc1beb5SScott Kruger        subdict[subtestname][var]+=" "+val
431ecc1beb5SScott Kruger      else:
432c0658d2aSScott Kruger        subdict[subtestname][var]=val
43368a9e459SScott Kruger      if not indentlevel: indentlevel=indentcount
434cadd188bSScott Kruger      #if indentlevel!=indentcount: print("Error in indentation:", ln)
43529921a8fSScott Kruger    # Determine subtest name and make dict
43629921a8fSScott Kruger    elif var=="test":
43729921a8fSScott Kruger      subtestname="test"+str(subtestnum)
43829921a8fSScott Kruger      subdict[subtestname]={}
4395b6bfdb9SJed Brown      if "subtests" not in subdict: subdict["subtests"]=[]
44029921a8fSScott Kruger      subdict["subtests"].append(subtestname)
44129921a8fSScott Kruger      subtestnum=subtestnum+1
44268a9e459SScott Kruger    # The rest are easy
44329921a8fSScott Kruger    else:
44444776d8cSScott Kruger      # For convenience, it is sometimes convenient to list twice
4455b6bfdb9SJed Brown      if var in subdict:
44644776d8cSScott Kruger        if var in appendlist:
44744776d8cSScott Kruger          subdict[var]+=" "+val
44844776d8cSScott Kruger        else:
44944776d8cSScott Kruger          raise Exception(var+" entered twice: "+line)
45044776d8cSScott Kruger      else:
451c0658d2aSScott Kruger        subdict[var]=val
45229921a8fSScott Kruger      if var=="suffix":
45329921a8fSScott Kruger        if len(val)>0:
4544f8a0bffSScott Kruger          testname+="_"+val
455fa236c6aSJacob Faibussowitsch      if var == "env" and len(val) == 0:
456fa236c6aSJacob Faibussowitsch        mess = "value for {}: directive cannot be empty!".format(var)
457fa236c6aSJacob Faibussowitsch        raise Exception(mess)
45829921a8fSScott Kruger
45968a9e459SScott Kruger  if len(comments): subdict['comments']="\n".join(comments).lstrip("\n")
4604f8a0bffSScott Kruger
4614f8a0bffSScott Kruger  # A test block can create multiple tests.  This does that logic
46244776d8cSScott Kruger  testnames,subdicts=splitTests(testname,subdict)
46344776d8cSScott Kruger  return testnames,subdicts
46429921a8fSScott Kruger
4656cecdbdcSScott Krugerdef parseTests(testStr,srcfile,fileNums,verbosity):
46629921a8fSScott Kruger  """
4672be3497aSPatrick Sanan  Parse the YAML-like string describing tests and return
46829921a8fSScott Kruger  a dictionary with the info in the form of:
46929921a8fSScott Kruger    testDict[test][subtest]
47029921a8fSScott Kruger  """
47129921a8fSScott Kruger
47229921a8fSScott Kruger  testDict={}
47329921a8fSScott Kruger
47429921a8fSScott Kruger  # The first entry should be test: but it might be indented.
47544776d8cSScott Kruger  newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums)
476cadd188bSScott Kruger  if verbosity>2: print(srcfile)
47729921a8fSScott Kruger
478e4653983SScott Kruger  ## Check and see if we have build requirements
479e4653983SScott Kruger  addToRunRequirements=None
480aec507c4SScott Kruger  if "\nbuild:" in newTestStr:
481aec507c4SScott Kruger    testDict['build']={}
482aec507c4SScott Kruger    # The file info is already here and need to append
483aec507c4SScott Kruger    Part1=newTestStr.split("build:")[1]
484aec507c4SScott Kruger    fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0]
485aec507c4SScott Kruger    for bkey in buildkeys:
486aec507c4SScott Kruger      if bkey+":" in fileInfo:
487aec507c4SScott Kruger        testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
488aec507c4SScott Kruger        #if verbosity>1: bkey+": "+testDict['build'][bkey]
489e4653983SScott Kruger      # If a runtime requires are put into build, push them down to all run tests
490e4653983SScott Kruger      # At this point, we are working with strings and not lists
491e4653983SScott Kruger      if 'requires' in testDict['build']:
4922bbaaa9fSScott Kruger         addToRunRequirements=testDict['build']['requires']
4932bbaaa9fSScott Kruger         # People put datafilespath into build, but it needs to be a runtime
494e4653983SScott Kruger         if 'datafilespath' in testDict['build']['requires']:
495e4653983SScott Kruger             newreqs=re.sub('datafilespath','',testDict['build']['requires'])
496e4653983SScott Kruger             testDict['build']['requires']=newreqs.strip()
497e4653983SScott Kruger
498aec507c4SScott Kruger
49929921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
500e53dc769SScott Kruger  for test in re.split("\ntest(?:set)?:",newTestStr)[1:]:
5016cecdbdcSScott Kruger    testnames,subdicts=parseTest(test,srcfile,verbosity)
50244776d8cSScott Kruger    for i in range(len(testnames)):
5035b6bfdb9SJed Brown      if testnames[i] in testDict:
5041acf9037SMatthew G. Knepley        raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile)
505e4653983SScott Kruger      # Add in build requirements that need to be moved
506e4653983SScott Kruger      if addToRunRequirements:
507e4653983SScott Kruger          if 'requires' in subdicts[i]:
5082bbaaa9fSScott Kruger              subdicts[i]['requires']+=' '+addToRunRequirements
509e4653983SScott Kruger          else:
510e4653983SScott Kruger              subdicts[i]['requires']=addToRunRequirements
51144776d8cSScott Kruger      testDict[testnames[i]]=subdicts[i]
51229921a8fSScott Kruger
51329921a8fSScott Kruger  return testDict
51429921a8fSScott Kruger
5156cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity):
51629921a8fSScott Kruger  """
51729921a8fSScott Kruger  Parse single example files and return dictionary of the form:
51829921a8fSScott Kruger    testDict[srcfile][test][subtest]
51929921a8fSScott Kruger  """
52029921a8fSScott Kruger  debug=False
521cadd188bSScott Kruger  basename=os.path.basename(srcfile)
522cadd188bSScott Kruger  if basename=='makefile': return {}
523cadd188bSScott Kruger
52429921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
52529921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
52629921a8fSScott Kruger  os.chdir(basedir)
52729921a8fSScott Kruger
52829921a8fSScott Kruger  testDict={}
529cadd188bSScott Kruger  sh=open(basename,"r"); fileStr=sh.read(); sh.close()
530ef704b63SScott Kruger  # Handle Windows issues
531ef704b63SScott Kruger  if not os.linesep == "\n": fileStr=fileStr.replace(os.linesep,"\n")
53229921a8fSScott Kruger
53329921a8fSScott Kruger  ## Start with doing the tests
53429921a8fSScott Kruger  #
53529921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
53644776d8cSScott Kruger  fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1
53729921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
5386f029658SMatthew G. Knepley  # one
53929921a8fSScott Kruger  srcTests=[]
54029921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
54129921a8fSScott Kruger  testString=" ".join(srcTests)
54244776d8cSScott Kruger  flen=len(testString.split("\n"))
54344776d8cSScott Kruger  fend=fstart+flen-1
54444776d8cSScott Kruger  fileNums=range(fstart,fend)
5456cecdbdcSScott Kruger  testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity)
546aec507c4SScott Kruger  # Massage dictionary for build requirements
547aec507c4SScott Kruger  if 'build' in testDict[basename]:
548aec507c4SScott Kruger    testDict[basename].update(testDict[basename]['build'])
549aec507c4SScott Kruger    del testDict[basename]['build']
55029921a8fSScott Kruger
55129921a8fSScott Kruger
55229921a8fSScott Kruger  os.chdir(curdir)
55329921a8fSScott Kruger  return testDict
55429921a8fSScott Kruger
5556cecdbdcSScott Krugerdef parseTestDir(directory,verbosity):
55629921a8fSScott Kruger  """
55729921a8fSScott Kruger  Parse single example files and return dictionary of the form:
55829921a8fSScott Kruger    testDict[srcfile][test][subtest]
55929921a8fSScott Kruger  """
56029921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
56129921a8fSScott Kruger  basedir=os.path.realpath(directory)
56229921a8fSScott Kruger  os.chdir(basedir)
56329921a8fSScott Kruger
56429921a8fSScott Kruger  tDict={}
56509a6cbfcSBernhard M. Wiedemann  for test_file in sorted(glob.glob("new_ex*.*")):
5666cecdbdcSScott Kruger    tDict.update(parseTestFile(test_file,verbosity))
56729921a8fSScott Kruger
56829921a8fSScott Kruger  os.chdir(curdir)
56929921a8fSScott Kruger  return tDict
57029921a8fSScott Kruger
57129921a8fSScott Krugerdef printExParseDict(rDict):
57229921a8fSScott Kruger  """
57329921a8fSScott Kruger  This is useful for debugging
57429921a8fSScott Kruger  """
57529921a8fSScott Kruger  indent="   "
57629921a8fSScott Kruger  for sfile in rDict:
577cadd188bSScott Kruger    print(sfile)
578c3d83d22SScott Kruger    sortkeys=list(rDict[sfile].keys())
57944776d8cSScott Kruger    sortkeys.sort()
58044776d8cSScott Kruger    for runex in sortkeys:
58169fa9ab3SScott Kruger      if runex == 'requires':
58269fa9ab3SScott Kruger        print(indent+runex+':'+str(rDict[sfile][runex]))
58369fa9ab3SScott Kruger        continue
584cadd188bSScott Kruger      print(indent+runex)
5855b6bfdb9SJed Brown      if type(rDict[sfile][runex])==bytes:
586cadd188bSScott Kruger        print(indent*2+rDict[sfile][runex])
58729921a8fSScott Kruger      else:
58829921a8fSScott Kruger        for var in rDict[sfile][runex]:
58944776d8cSScott Kruger          if var.startswith("test"): continue
590cadd188bSScott Kruger          print(indent*2+var+": "+str(rDict[sfile][runex][var]))
5915b6bfdb9SJed Brown        if 'subtests' in rDict[sfile][runex]:
59244776d8cSScott Kruger          for var in rDict[sfile][runex]['subtests']:
593cadd188bSScott Kruger            print(indent*2+var)
59429921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
595cadd188bSScott Kruger              print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2]))
596cadd188bSScott Kruger      print("\n")
59729921a8fSScott Kruger  return
59829921a8fSScott Kruger
59929921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
60029921a8fSScott Kruger
60129921a8fSScott Kruger    if directory:
6026cecdbdcSScott Kruger      tDict=parseTestDir(directory,verbosity)
60329921a8fSScott Kruger    else:
6046cecdbdcSScott Kruger      tDict=parseTestFile(test_file,verbosity)
60529921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
60629921a8fSScott Kruger
60729921a8fSScott Kruger    return
60829921a8fSScott Kruger
60929921a8fSScott Krugerif __name__ == '__main__':
61029921a8fSScott Kruger    import optparse
61129921a8fSScott Kruger    parser = optparse.OptionParser()
61229921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
61329921a8fSScott Kruger                      default="", help='Directory containing files to parse')
61429921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
61529921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
61629921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
61729921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
61829921a8fSScott Kruger    opts, extra_args = parser.parse_args()
61929921a8fSScott Kruger
62029921a8fSScott Kruger    if extra_args:
62129921a8fSScott Kruger        import sys
62229921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
62329921a8fSScott Kruger        exit(1)
62429921a8fSScott Kruger    if not opts.test_file and not opts.directory:
625cadd188bSScott Kruger      print("test file or directory is required")
62629921a8fSScott Kruger      parser.print_usage()
62729921a8fSScott Kruger      sys.exit()
62829921a8fSScott Kruger
62929921a8fSScott Kruger    # Need verbosity to be an integer
63029921a8fSScott Kruger    try:
63129921a8fSScott Kruger      verbosity=int(opts.verbosity)
63229921a8fSScott Kruger    except:
63329921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
63429921a8fSScott Kruger
63529921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
636