xref: /petsc/config/testparse.py (revision 77cf3243b9e9a146570c4baf90fb71449ae79c7a)
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 Isaacdef testSplit(striptest):
347080f0011SToby Isaac  """
348080f0011SToby Isaac  Split up a test into lines, but use a shell parser to detect when newlines are within quotation marks
349080f0011SToby Isaac  and keep those together
350080f0011SToby Isaac  """
351080f0011SToby Isaac  import shlex
352080f0011SToby Isaac
353080f0011SToby Isaac  sl = shlex.shlex()
354080f0011SToby Isaac  sl.whitespace_split = True # only split at whitespace
355080f0011SToby Isaac  sl.commenters = ''
356080f0011SToby Isaac  sl.push_source(striptest)
357080f0011SToby Isaac  last_pos = sl.instream.tell()
358080f0011SToby Isaac  try:
359080f0011SToby Isaac    last_token = sl.read_token()
360080f0011SToby Isaac  except ValueError:
361080f0011SToby Isaac    print(striptest)
362080f0011SToby Isaac    raise ValueError
363080f0011SToby Isaac  last_line = ''
364080f0011SToby Isaac  while last_token != '':
365080f0011SToby Isaac    new_pos = sl.instream.tell()
366080f0011SToby Isaac    block = striptest[last_pos:new_pos]
367080f0011SToby Isaac    token_start = block.find(last_token)
368080f0011SToby Isaac    leading = block[0:token_start]
369080f0011SToby Isaac    trailing = block[(token_start + len(last_token)):]
370080f0011SToby Isaac    leading_split = leading.split('\n')
371080f0011SToby Isaac    if len(leading_split) > 1:
372080f0011SToby Isaac      yield last_line
373080f0011SToby Isaac      last_line = ''
374080f0011SToby Isaac    last_line += leading_split[-1]
375080f0011SToby Isaac    last_line += last_token
376080f0011SToby Isaac    trailing_split = trailing.split('\n')
377080f0011SToby Isaac    last_line += trailing_split[0]
378080f0011SToby Isaac    if len(trailing_split) > 1:
379080f0011SToby Isaac      yield last_line
380080f0011SToby Isaac      last_line = ''
381080f0011SToby Isaac    last_pos = new_pos
382080f0011SToby Isaac    try:
383080f0011SToby Isaac      last_token = sl.read_token()
384080f0011SToby Isaac    except ValueError:
385080f0011SToby Isaac      print(striptest)
386080f0011SToby Isaac      raise ValueError
387080f0011SToby Isaac  yield last_line
388080f0011SToby Isaac
3896cecdbdcSScott Krugerdef parseTest(testStr,srcfile,verbosity):
39029921a8fSScott Kruger  """
39129921a8fSScott Kruger  This parses an individual test
3922be3497aSPatrick Sanan  Our YAML-like language is hierarchial so should use a state machine in the general case,
39353f2a965SBarry Smith  but in practice we only support two levels of test:
39429921a8fSScott Kruger  """
39529921a8fSScott Kruger  basename=os.path.basename(srcfile)
396d5b43468SJose E. Roman  # Handle the new at the beginning
39729921a8fSScott Kruger  bn=re.sub("new_","",basename)
39829921a8fSScott Kruger  # This is the default
399c0558f20SBarry Smith  testname="run"+getlangsplit(bn)
40029921a8fSScott Kruger
40129921a8fSScott Kruger  # Tests that have default everything (so empty effectively)
4022bbaaa9fSScott Kruger  if len(testStr)==0:
4032bbaaa9fSScott Kruger      if '_' not in testname: testname+='_1'
4042bbaaa9fSScott Kruger      return [testname], [{}]
40529921a8fSScott Kruger
40629921a8fSScott Kruger  striptest=_stripIndent(testStr,srcfile)
40729921a8fSScott Kruger
40829921a8fSScott Kruger  # go through and parse
40929921a8fSScott Kruger  subtestnum=0
41029921a8fSScott Kruger  subdict={}
41168a9e459SScott Kruger  comments=[]
41268a9e459SScott Kruger  indentlevel=0
413080f0011SToby Isaac  for ln in testSplit(striptest):
414c4b80baaSScott Kruger    line=ln.split('#')[0].rstrip()
415cadd188bSScott Kruger    if verbosity>2: print(line)
4160bcc1aabSScott Kruger    comment=("" if len(ln.split("#"))>0 else " ".join(ln.split("#")[1:]).strip())
41768a9e459SScott Kruger    if comment: comments.append(comment)
41829921a8fSScott Kruger    if not line.strip(): continue
41944776d8cSScott Kruger    lsplit=line.split(':')
42045b74845SBarry Smith    if len(lsplit)==0:
42145b74845SBarry Smith      raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nMissing : in line: "+line)
42244776d8cSScott Kruger    indentcount=lsplit[0].count(" ")
42344776d8cSScott Kruger    var=lsplit[0].strip()
42440ae0433SScott Kruger    val=line[line.find(':')+1:].strip()
42545b74845SBarry Smith    if not var in acceptedkeys:
42645b74845SBarry Smith      raise Exception("\n\nError in test harness parsing file: "+srcfile+"\n"+var+" from: "+line+" is not a valid keyword")
42729921a8fSScott Kruger    # Start by seeing if we are in a subtest
42829921a8fSScott Kruger    if line.startswith(" "):
429376ff7dcSStefano Zampini      if not 'subtestname' in locals():
430376ff7dcSStefano Zampini        raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nInvalid indentation at line:"+line)
431ecc1beb5SScott Kruger      if var in subdict[subtestname]:
432ecc1beb5SScott Kruger        subdict[subtestname][var]+=" "+val
433ecc1beb5SScott Kruger      else:
434c0658d2aSScott Kruger        subdict[subtestname][var]=val
43568a9e459SScott Kruger      if not indentlevel: indentlevel=indentcount
436cadd188bSScott Kruger      #if indentlevel!=indentcount: print("Error in indentation:", ln)
43729921a8fSScott Kruger    # Determine subtest name and make dict
43829921a8fSScott Kruger    elif var=="test":
43929921a8fSScott Kruger      subtestname="test"+str(subtestnum)
44029921a8fSScott Kruger      subdict[subtestname]={}
4415b6bfdb9SJed Brown      if "subtests" not in subdict: subdict["subtests"]=[]
44229921a8fSScott Kruger      subdict["subtests"].append(subtestname)
44329921a8fSScott Kruger      subtestnum=subtestnum+1
44468a9e459SScott Kruger    # The rest are easy
44529921a8fSScott Kruger    else:
44644776d8cSScott Kruger      # For convenience, it is sometimes convenient to list twice
4475b6bfdb9SJed Brown      if var in subdict:
44844776d8cSScott Kruger        if var in appendlist:
44944776d8cSScott Kruger          subdict[var]+=" "+val
45044776d8cSScott Kruger        else:
45145b74845SBarry Smith          raise Exception("\n\nError in test harness parsing file: "+srcfile+"\n"+var+" entered twice: "+line)
45244776d8cSScott Kruger      else:
453c0658d2aSScott Kruger        subdict[var]=val
45429921a8fSScott Kruger      if var=="suffix":
45529921a8fSScott Kruger        if len(val)>0:
4564f8a0bffSScott Kruger          testname+="_"+val
457fa236c6aSJacob Faibussowitsch      if var == "env" and len(val) == 0:
45845b74845SBarry Smith        raise Exception("\n\nError in test harness parsing file: "+srcfile+"\nvalue for {}: directive cannot be empty!".format(var))
45929921a8fSScott Kruger
46068a9e459SScott Kruger  if len(comments): subdict['comments']="\n".join(comments).lstrip("\n")
4614f8a0bffSScott Kruger
462*e2535b21SJunchao Zhang  # add in error check: if 'requires' is added to subtests, make sure 'suffix' is also added.
463*e2535b21SJunchao Zhang  if 'subtests' in subdict:
464*e2535b21SJunchao Zhang    for stest in subdict['subtests']:
465*e2535b21SJunchao Zhang      if 'requires' in  subdict[stest] and not 'suffix' in subdict[stest]:
466*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!")
467*e2535b21SJunchao Zhang
4684f8a0bffSScott Kruger  # A test block can create multiple tests.  This does that logic
46944776d8cSScott Kruger  testnames,subdicts=splitTests(testname,subdict)
47044776d8cSScott Kruger  return testnames,subdicts
47129921a8fSScott Kruger
4726cecdbdcSScott Krugerdef parseTests(testStr,srcfile,fileNums,verbosity):
47329921a8fSScott Kruger  """
4742be3497aSPatrick Sanan  Parse the YAML-like string describing tests and return
47529921a8fSScott Kruger  a dictionary with the info in the form of:
47629921a8fSScott Kruger    testDict[test][subtest]
47729921a8fSScott Kruger  """
47829921a8fSScott Kruger
47929921a8fSScott Kruger  testDict={}
48029921a8fSScott Kruger
48129921a8fSScott Kruger  # The first entry should be test: but it might be indented.
48244776d8cSScott Kruger  newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums)
483cadd188bSScott Kruger  if verbosity>2: print(srcfile)
48429921a8fSScott Kruger
485e4653983SScott Kruger  ## Check and see if we have build requirements
486e4653983SScott Kruger  addToRunRequirements=None
487aec507c4SScott Kruger  if "\nbuild:" in newTestStr:
488aec507c4SScott Kruger    testDict['build']={}
489aec507c4SScott Kruger    # The file info is already here and need to append
490aec507c4SScott Kruger    Part1=newTestStr.split("build:")[1]
491aec507c4SScott Kruger    fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0]
492aec507c4SScott Kruger    for bkey in buildkeys:
493aec507c4SScott Kruger      if bkey+":" in fileInfo:
494aec507c4SScott Kruger        testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
495aec507c4SScott Kruger        #if verbosity>1: bkey+": "+testDict['build'][bkey]
496e4653983SScott Kruger      # If a runtime requires are put into build, push them down to all run tests
497e4653983SScott Kruger      # At this point, we are working with strings and not lists
498e4653983SScott Kruger      if 'requires' in testDict['build']:
4990338c944SBarry Smith         if 'todo' in testDict['build']['requires'] or 'TODO' in testDict['build']['requires']:
5000338c944SBarry Smith             raise Exception("Error: Do not list 'TODO' in requires: list it as a field, for example, TODO: XXX is currently unstable\n"+srcfile)
5012bbaaa9fSScott Kruger         addToRunRequirements=testDict['build']['requires']
5022bbaaa9fSScott Kruger         # People put datafilespath into build, but it needs to be a runtime
503e4653983SScott Kruger         if 'datafilespath' in testDict['build']['requires']:
504e4653983SScott Kruger             newreqs=re.sub('datafilespath','',testDict['build']['requires'])
505e4653983SScott Kruger             testDict['build']['requires']=newreqs.strip()
506e4653983SScott Kruger
50729921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
508e53dc769SScott Kruger  for test in re.split("\ntest(?:set)?:",newTestStr)[1:]:
5096cecdbdcSScott Kruger    testnames,subdicts=parseTest(test,srcfile,verbosity)
51044776d8cSScott Kruger    for i in range(len(testnames)):
5115b6bfdb9SJed Brown      if testnames[i] in testDict:
5121acf9037SMatthew G. Knepley        raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile)
513e4653983SScott Kruger      # Add in build requirements that need to be moved
514e4653983SScott Kruger      if addToRunRequirements:
515e4653983SScott Kruger          if 'requires' in subdicts[i]:
5162bbaaa9fSScott Kruger              subdicts[i]['requires']+=' '+addToRunRequirements
517e4653983SScott Kruger          else:
518e4653983SScott Kruger              subdicts[i]['requires']=addToRunRequirements
51944776d8cSScott Kruger      testDict[testnames[i]]=subdicts[i]
52029921a8fSScott Kruger
52129921a8fSScott Kruger  return testDict
52229921a8fSScott Kruger
5236cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity):
52429921a8fSScott Kruger  """
52529921a8fSScott Kruger  Parse single example files and return dictionary of the form:
52629921a8fSScott Kruger    testDict[srcfile][test][subtest]
52729921a8fSScott Kruger  """
52829921a8fSScott Kruger  debug=False
529cadd188bSScott Kruger  basename=os.path.basename(srcfile)
530cadd188bSScott Kruger  if basename=='makefile': return {}
531cadd188bSScott Kruger
53229921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
53329921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
53429921a8fSScott Kruger  os.chdir(basedir)
53529921a8fSScott Kruger
53629921a8fSScott Kruger  testDict={}
537cadd188bSScott Kruger  sh=open(basename,"r"); fileStr=sh.read(); sh.close()
538ef704b63SScott Kruger  # Handle Windows issues
539ef704b63SScott Kruger  if not os.linesep == "\n": fileStr=fileStr.replace(os.linesep,"\n")
54029921a8fSScott Kruger
54129921a8fSScott Kruger  ## Start with doing the tests
54229921a8fSScott Kruger  #
54329921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
54444776d8cSScott Kruger  fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1
54529921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
5466f029658SMatthew G. Knepley  # one
54729921a8fSScott Kruger  srcTests=[]
54829921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
54929921a8fSScott Kruger  testString=" ".join(srcTests)
55044776d8cSScott Kruger  flen=len(testString.split("\n"))
55144776d8cSScott Kruger  fend=fstart+flen-1
55244776d8cSScott Kruger  fileNums=range(fstart,fend)
5536cecdbdcSScott Kruger  testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity)
554aec507c4SScott Kruger  # Massage dictionary for build requirements
555aec507c4SScott Kruger  if 'build' in testDict[basename]:
556aec507c4SScott Kruger    testDict[basename].update(testDict[basename]['build'])
557aec507c4SScott Kruger    del testDict[basename]['build']
55829921a8fSScott Kruger
55929921a8fSScott Kruger  os.chdir(curdir)
56029921a8fSScott Kruger  return testDict
56129921a8fSScott Kruger
5626cecdbdcSScott Krugerdef parseTestDir(directory,verbosity):
56329921a8fSScott Kruger  """
56429921a8fSScott Kruger  Parse single example files and return dictionary of the form:
56529921a8fSScott Kruger    testDict[srcfile][test][subtest]
56629921a8fSScott Kruger  """
56729921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
56829921a8fSScott Kruger  basedir=os.path.realpath(directory)
56929921a8fSScott Kruger  os.chdir(basedir)
57029921a8fSScott Kruger
57129921a8fSScott Kruger  tDict={}
57209a6cbfcSBernhard M. Wiedemann  for test_file in sorted(glob.glob("new_ex*.*")):
5736cecdbdcSScott Kruger    tDict.update(parseTestFile(test_file,verbosity))
57429921a8fSScott Kruger
57529921a8fSScott Kruger  os.chdir(curdir)
57629921a8fSScott Kruger  return tDict
57729921a8fSScott Kruger
57829921a8fSScott Krugerdef printExParseDict(rDict):
57929921a8fSScott Kruger  """
58029921a8fSScott Kruger  This is useful for debugging
58129921a8fSScott Kruger  """
58229921a8fSScott Kruger  indent="   "
58329921a8fSScott Kruger  for sfile in rDict:
584cadd188bSScott Kruger    print(sfile)
585c3d83d22SScott Kruger    sortkeys=list(rDict[sfile].keys())
58644776d8cSScott Kruger    sortkeys.sort()
58744776d8cSScott Kruger    for runex in sortkeys:
58869fa9ab3SScott Kruger      if runex == 'requires':
58969fa9ab3SScott Kruger        print(indent+runex+':'+str(rDict[sfile][runex]))
59069fa9ab3SScott Kruger        continue
591cadd188bSScott Kruger      print(indent+runex)
5925b6bfdb9SJed Brown      if type(rDict[sfile][runex])==bytes:
593cadd188bSScott Kruger        print(indent*2+rDict[sfile][runex])
59429921a8fSScott Kruger      else:
59529921a8fSScott Kruger        for var in rDict[sfile][runex]:
59644776d8cSScott Kruger          if var.startswith("test"): continue
597cadd188bSScott Kruger          print(indent*2+var+": "+str(rDict[sfile][runex][var]))
5985b6bfdb9SJed Brown        if 'subtests' in rDict[sfile][runex]:
59944776d8cSScott Kruger          for var in rDict[sfile][runex]['subtests']:
600cadd188bSScott Kruger            print(indent*2+var)
60129921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
602cadd188bSScott Kruger              print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2]))
603cadd188bSScott Kruger      print("\n")
60429921a8fSScott Kruger  return
60529921a8fSScott Kruger
60629921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
60729921a8fSScott Kruger
60829921a8fSScott Kruger    if directory:
6096cecdbdcSScott Kruger      tDict=parseTestDir(directory,verbosity)
61029921a8fSScott Kruger    else:
6116cecdbdcSScott Kruger      tDict=parseTestFile(test_file,verbosity)
61229921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
61329921a8fSScott Kruger
61429921a8fSScott Kruger    return
61529921a8fSScott Kruger
61629921a8fSScott Krugerif __name__ == '__main__':
61729921a8fSScott Kruger    import optparse
61829921a8fSScott Kruger    parser = optparse.OptionParser()
61929921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
62029921a8fSScott Kruger                      default="", help='Directory containing files to parse')
62129921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
62229921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
62329921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
62429921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
62529921a8fSScott Kruger    opts, extra_args = parser.parse_args()
62629921a8fSScott Kruger
62729921a8fSScott Kruger    if extra_args:
62829921a8fSScott Kruger        import sys
62929921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
63029921a8fSScott Kruger        exit(1)
63129921a8fSScott Kruger    if not opts.test_file and not opts.directory:
632cadd188bSScott Kruger      print("test file or directory is required")
63329921a8fSScott Kruger      parser.print_usage()
63429921a8fSScott Kruger      sys.exit()
63529921a8fSScott Kruger
63629921a8fSScott Kruger    # Need verbosity to be an integer
63729921a8fSScott Kruger    try:
63829921a8fSScott Kruger      verbosity=int(opts.verbosity)
63929921a8fSScott Kruger    except:
64029921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
64129921a8fSScott Kruger
64229921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
643