xref: /petsc/config/testparse.py (revision e2535b2137190c208cc963c7f95b17c7548a9055)
1df3bd252SSatish Balay#!/usr/bin/env python3
229921a8fSScott Kruger"""
30338c944SBarry SmithParse the PETSc tutorial or test file (example) and return a dictionary containing information about the tests to be run for the example
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
53d68d54c7SToby Isaacacceptedkeys=set("test nsize requires command suffix diff_args args filter filter_output localrunfiles comments TODO SKIP output_file timeoutfactor env temporaries".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
1633be2e2fdSJose 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
1813be2e2fdSJose 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:
2303be2e2fdSJose 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(':')
42245b74845SBarry Smith    if len(lsplit)==0:
42345b74845SBarry Smith      raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nMissing : in line: "+line)
42444776d8cSScott Kruger    indentcount=lsplit[0].count(" ")
42544776d8cSScott Kruger    var=lsplit[0].strip()
42640ae0433SScott Kruger    val=line[line.find(':')+1:].strip()
42745b74845SBarry Smith    if not var in acceptedkeys:
42845b74845SBarry Smith      raise Exception("\n\nError in test harness parsing file: "+srcfile+"\n"+var+" from: "+line+" is not a valid keyword")
42929921a8fSScott Kruger    # Start by seeing if we are in a subtest
43029921a8fSScott Kruger    if line.startswith(" "):
431376ff7dcSStefano Zampini      if not 'subtestname' in locals():
432376ff7dcSStefano Zampini        raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nInvalid indentation at line:"+line)
433ecc1beb5SScott Kruger      if var in subdict[subtestname]:
434ecc1beb5SScott Kruger        subdict[subtestname][var]+=" "+val
435ecc1beb5SScott Kruger      else:
436c0658d2aSScott Kruger        subdict[subtestname][var]=val
43768a9e459SScott Kruger      if not indentlevel: indentlevel=indentcount
438cadd188bSScott Kruger      #if indentlevel!=indentcount: print("Error in indentation:", ln)
43929921a8fSScott Kruger    # Determine subtest name and make dict
44029921a8fSScott Kruger    elif var=="test":
44129921a8fSScott Kruger      subtestname="test"+str(subtestnum)
44229921a8fSScott Kruger      subdict[subtestname]={}
4435b6bfdb9SJed Brown      if "subtests" not in subdict: subdict["subtests"]=[]
44429921a8fSScott Kruger      subdict["subtests"].append(subtestname)
44529921a8fSScott Kruger      subtestnum=subtestnum+1
44668a9e459SScott Kruger    # The rest are easy
44729921a8fSScott Kruger    else:
44844776d8cSScott Kruger      # For convenience, it is sometimes convenient to list twice
4495b6bfdb9SJed Brown      if var in subdict:
45044776d8cSScott Kruger        if var in appendlist:
45144776d8cSScott Kruger          subdict[var]+=" "+val
45244776d8cSScott Kruger        else:
45345b74845SBarry Smith          raise Exception("\n\nError in test harness parsing file: "+srcfile+"\n"+var+" entered twice: "+line)
45444776d8cSScott Kruger      else:
455c0658d2aSScott Kruger        subdict[var]=val
45629921a8fSScott Kruger      if var=="suffix":
45729921a8fSScott Kruger        if len(val)>0:
4584f8a0bffSScott Kruger          testname+="_"+val
459fa236c6aSJacob Faibussowitsch      if var == "env" and len(val) == 0:
46045b74845SBarry Smith        raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nvalue for {}: directive cannot be empty!".format(var))
46129921a8fSScott Kruger
46268a9e459SScott Kruger  if len(comments): subdict['comments']="\n".join(comments).lstrip("\n")
4634f8a0bffSScott Kruger
464*e2535b21SJunchao Zhang  # add in error check: if 'requires' is added to subtests, make sure 'suffix' is also added.
465*e2535b21SJunchao Zhang  if 'subtests' in subdict:
466*e2535b21SJunchao Zhang    for stest in subdict['subtests']:
467*e2535b21SJunchao Zhang      if 'requires' in  subdict[stest] and not 'suffix' in subdict[stest]:
468*e2535b21SJunchao Zhang        raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nA test in a testset with 'requires' directive should also have a 'suffix' directive!")
469*e2535b21SJunchao Zhang
4704f8a0bffSScott Kruger  # A test block can create multiple tests.  This does that logic
47144776d8cSScott Kruger  testnames,subdicts=splitTests(testname,subdict)
47244776d8cSScott Kruger  return testnames,subdicts
47329921a8fSScott Kruger
4746cecdbdcSScott Krugerdef parseTests(testStr,srcfile,fileNums,verbosity):
47529921a8fSScott Kruger  """
4762be3497aSPatrick Sanan  Parse the YAML-like string describing tests and return
47729921a8fSScott Kruger  a dictionary with the info in the form of:
47829921a8fSScott Kruger    testDict[test][subtest]
47929921a8fSScott Kruger  """
48029921a8fSScott Kruger
48129921a8fSScott Kruger  testDict={}
48229921a8fSScott Kruger
48329921a8fSScott Kruger  # The first entry should be test: but it might be indented.
48444776d8cSScott Kruger  newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums)
485cadd188bSScott Kruger  if verbosity>2: print(srcfile)
48629921a8fSScott Kruger
487e4653983SScott Kruger  ## Check and see if we have build requirements
488e4653983SScott Kruger  addToRunRequirements=None
489aec507c4SScott Kruger  if "\nbuild:" in newTestStr:
490aec507c4SScott Kruger    testDict['build']={}
491aec507c4SScott Kruger    # The file info is already here and need to append
492aec507c4SScott Kruger    Part1=newTestStr.split("build:")[1]
493aec507c4SScott Kruger    fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0]
494aec507c4SScott Kruger    for bkey in buildkeys:
495aec507c4SScott Kruger      if bkey+":" in fileInfo:
496aec507c4SScott Kruger        testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
497aec507c4SScott Kruger        #if verbosity>1: bkey+": "+testDict['build'][bkey]
498e4653983SScott Kruger      # If a runtime requires are put into build, push them down to all run tests
499e4653983SScott Kruger      # At this point, we are working with strings and not lists
500e4653983SScott Kruger      if 'requires' in testDict['build']:
5010338c944SBarry Smith         if 'todo' in testDict['build']['requires'] or 'TODO' in testDict['build']['requires']:
5020338c944SBarry Smith             raise Exception("Error: Do not list 'TODO' in requires: list it as a field, for example, TODO: XXX is currently unstable\n"+srcfile)
5032bbaaa9fSScott Kruger         addToRunRequirements=testDict['build']['requires']
5042bbaaa9fSScott Kruger         # People put datafilespath into build, but it needs to be a runtime
505e4653983SScott Kruger         if 'datafilespath' in testDict['build']['requires']:
506e4653983SScott Kruger             newreqs=re.sub('datafilespath','',testDict['build']['requires'])
507e4653983SScott Kruger             testDict['build']['requires']=newreqs.strip()
508e4653983SScott Kruger
509aec507c4SScott Kruger
51029921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
511e53dc769SScott Kruger  for test in re.split("\ntest(?:set)?:",newTestStr)[1:]:
5126cecdbdcSScott Kruger    testnames,subdicts=parseTest(test,srcfile,verbosity)
51344776d8cSScott Kruger    for i in range(len(testnames)):
5145b6bfdb9SJed Brown      if testnames[i] in testDict:
5151acf9037SMatthew G. Knepley        raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile)
516e4653983SScott Kruger      # Add in build requirements that need to be moved
517e4653983SScott Kruger      if addToRunRequirements:
518e4653983SScott Kruger          if 'requires' in subdicts[i]:
5192bbaaa9fSScott Kruger              subdicts[i]['requires']+=' '+addToRunRequirements
520e4653983SScott Kruger          else:
521e4653983SScott Kruger              subdicts[i]['requires']=addToRunRequirements
52244776d8cSScott Kruger      testDict[testnames[i]]=subdicts[i]
52329921a8fSScott Kruger
52429921a8fSScott Kruger  return testDict
52529921a8fSScott Kruger
5266cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity):
52729921a8fSScott Kruger  """
52829921a8fSScott Kruger  Parse single example files and return dictionary of the form:
52929921a8fSScott Kruger    testDict[srcfile][test][subtest]
53029921a8fSScott Kruger  """
53129921a8fSScott Kruger  debug=False
532cadd188bSScott Kruger  basename=os.path.basename(srcfile)
533cadd188bSScott Kruger  if basename=='makefile': return {}
534cadd188bSScott Kruger
53529921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
53629921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
53729921a8fSScott Kruger  os.chdir(basedir)
53829921a8fSScott Kruger
53929921a8fSScott Kruger  testDict={}
540cadd188bSScott Kruger  sh=open(basename,"r"); fileStr=sh.read(); sh.close()
541ef704b63SScott Kruger  # Handle Windows issues
542ef704b63SScott Kruger  if not os.linesep == "\n": fileStr=fileStr.replace(os.linesep,"\n")
54329921a8fSScott Kruger
54429921a8fSScott Kruger  ## Start with doing the tests
54529921a8fSScott Kruger  #
54629921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
54744776d8cSScott Kruger  fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1
54829921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
5496f029658SMatthew G. Knepley  # one
55029921a8fSScott Kruger  srcTests=[]
55129921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
55229921a8fSScott Kruger  testString=" ".join(srcTests)
55344776d8cSScott Kruger  flen=len(testString.split("\n"))
55444776d8cSScott Kruger  fend=fstart+flen-1
55544776d8cSScott Kruger  fileNums=range(fstart,fend)
5566cecdbdcSScott Kruger  testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity)
557aec507c4SScott Kruger  # Massage dictionary for build requirements
558aec507c4SScott Kruger  if 'build' in testDict[basename]:
559aec507c4SScott Kruger    testDict[basename].update(testDict[basename]['build'])
560aec507c4SScott Kruger    del testDict[basename]['build']
56129921a8fSScott Kruger
56229921a8fSScott Kruger
56329921a8fSScott Kruger  os.chdir(curdir)
56429921a8fSScott Kruger  return testDict
56529921a8fSScott Kruger
5666cecdbdcSScott Krugerdef parseTestDir(directory,verbosity):
56729921a8fSScott Kruger  """
56829921a8fSScott Kruger  Parse single example files and return dictionary of the form:
56929921a8fSScott Kruger    testDict[srcfile][test][subtest]
57029921a8fSScott Kruger  """
57129921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
57229921a8fSScott Kruger  basedir=os.path.realpath(directory)
57329921a8fSScott Kruger  os.chdir(basedir)
57429921a8fSScott Kruger
57529921a8fSScott Kruger  tDict={}
57609a6cbfcSBernhard M. Wiedemann  for test_file in sorted(glob.glob("new_ex*.*")):
5776cecdbdcSScott Kruger    tDict.update(parseTestFile(test_file,verbosity))
57829921a8fSScott Kruger
57929921a8fSScott Kruger  os.chdir(curdir)
58029921a8fSScott Kruger  return tDict
58129921a8fSScott Kruger
58229921a8fSScott Krugerdef printExParseDict(rDict):
58329921a8fSScott Kruger  """
58429921a8fSScott Kruger  This is useful for debugging
58529921a8fSScott Kruger  """
58629921a8fSScott Kruger  indent="   "
58729921a8fSScott Kruger  for sfile in rDict:
588cadd188bSScott Kruger    print(sfile)
589c3d83d22SScott Kruger    sortkeys=list(rDict[sfile].keys())
59044776d8cSScott Kruger    sortkeys.sort()
59144776d8cSScott Kruger    for runex in sortkeys:
59269fa9ab3SScott Kruger      if runex == 'requires':
59369fa9ab3SScott Kruger        print(indent+runex+':'+str(rDict[sfile][runex]))
59469fa9ab3SScott Kruger        continue
595cadd188bSScott Kruger      print(indent+runex)
5965b6bfdb9SJed Brown      if type(rDict[sfile][runex])==bytes:
597cadd188bSScott Kruger        print(indent*2+rDict[sfile][runex])
59829921a8fSScott Kruger      else:
59929921a8fSScott Kruger        for var in rDict[sfile][runex]:
60044776d8cSScott Kruger          if var.startswith("test"): continue
601cadd188bSScott Kruger          print(indent*2+var+": "+str(rDict[sfile][runex][var]))
6025b6bfdb9SJed Brown        if 'subtests' in rDict[sfile][runex]:
60344776d8cSScott Kruger          for var in rDict[sfile][runex]['subtests']:
604cadd188bSScott Kruger            print(indent*2+var)
60529921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
606cadd188bSScott Kruger              print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2]))
607cadd188bSScott Kruger      print("\n")
60829921a8fSScott Kruger  return
60929921a8fSScott Kruger
61029921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
61129921a8fSScott Kruger
61229921a8fSScott Kruger    if directory:
6136cecdbdcSScott Kruger      tDict=parseTestDir(directory,verbosity)
61429921a8fSScott Kruger    else:
6156cecdbdcSScott Kruger      tDict=parseTestFile(test_file,verbosity)
61629921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
61729921a8fSScott Kruger
61829921a8fSScott Kruger    return
61929921a8fSScott Kruger
62029921a8fSScott Krugerif __name__ == '__main__':
62129921a8fSScott Kruger    import optparse
62229921a8fSScott Kruger    parser = optparse.OptionParser()
62329921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
62429921a8fSScott Kruger                      default="", help='Directory containing files to parse')
62529921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
62629921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
62729921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
62829921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
62929921a8fSScott Kruger    opts, extra_args = parser.parse_args()
63029921a8fSScott Kruger
63129921a8fSScott Kruger    if extra_args:
63229921a8fSScott Kruger        import sys
63329921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
63429921a8fSScott Kruger        exit(1)
63529921a8fSScott Kruger    if not opts.test_file and not opts.directory:
636cadd188bSScott Kruger      print("test file or directory is required")
63729921a8fSScott Kruger      parser.print_usage()
63829921a8fSScott Kruger      sys.exit()
63929921a8fSScott Kruger
64029921a8fSScott Kruger    # Need verbosity to be an integer
64129921a8fSScott Kruger    try:
64229921a8fSScott Kruger      verbosity=int(opts.verbosity)
64329921a8fSScott Kruger    except:
64429921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
64529921a8fSScott Kruger
64629921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
647