xref: /petsc/config/testparse.py (revision d87d95160f28bf6a9abb137e9fe5faa4e562da09)
129921a8fSScott Kruger#!/usr/bin/env python
229921a8fSScott Kruger"""
329921a8fSScott KrugerParse the test file and return a dictionary.
429921a8fSScott Kruger
529921a8fSScott KrugerQuick usage::
629921a8fSScott Kruger
7c3a89c15SBarry Smith  lib/petsc/bin/maint/testparse.py -t src/ksp/ksp/examples/tutorials/ex1.c
829921a8fSScott Kruger
929921a8fSScott KrugerFrom the command line, it prints out the dictionary.
1029921a8fSScott KrugerThis is meant to be used by other scripts, but it is
1129921a8fSScott Krugeruseful to debug individual files.
1229921a8fSScott Kruger
1329921a8fSScott KrugerExample language
1429921a8fSScott Kruger----------------
1529921a8fSScott Kruger
1629921a8fSScott Kruger/*TEST
17aec507c4SScott Kruger   build:
18aec507c4SScott Kruger     requires: moab
19e53dc769SScott Kruger   # This is equivalent to test:
20e53dc769SScott Kruger   testset:
2129921a8fSScott Kruger      args: -pc_type mg -ksp_type fgmres -da_refine 2 -ksp_monitor_short -mg_levels_ksp_monitor_short -mg_levels_ksp_norm_type unpreconditioned -ksp_view -pc_mg_type full
2229921a8fSScott Kruger
23e53dc769SScott Kruger   testset:
2429921a8fSScott Kruger      suffix: 2
2529921a8fSScott Kruger      nsize: 2
2629921a8fSScott Kruger      args: -pc_type mg -ksp_type fgmres -da_refine 2 -ksp_monitor_short -mg_levels_ksp_monitor_short -mg_levels_ksp_norm_type unpreconditioned -ksp_view -pc_mg_type full
2729921a8fSScott Kruger
28e53dc769SScott Kruger   testset:
29e53dc769SScott Kruger      suffix: 2
30e53dc769SScott Kruger      nsize: 2
31e53dc769SScott Kruger      args: -pc_type mg -ksp_type fgmres -da_refine 2 -ksp_monitor_short -mg_levels_ksp_monitor_short -mg_levels_ksp_norm_type unpreconditioned -ksp_view -pc_mg_type full
32e53dc769SScott Kruger      test:
33e53dc769SScott Kruger
3429921a8fSScott KrugerTEST*/
3529921a8fSScott Kruger
3629921a8fSScott Kruger"""
375b6bfdb9SJed Brownfrom __future__ import print_function
3829921a8fSScott Kruger
3929921a8fSScott Krugerimport os, re, glob, types
4029921a8fSScott Krugerimport sys
4129921a8fSScott Krugerimport logging
4229921a8fSScott Krugersys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
4329921a8fSScott Kruger
4429921a8fSScott Krugerimport inspect
4529921a8fSScott Krugerthisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
4629921a8fSScott Krugermaintdir=os.path.join(os.path.join(os.path.dirname(thisscriptdir),'bin'),'maint')
4729921a8fSScott Krugersys.path.insert(0,maintdir)
4829921a8fSScott Kruger
4929921a8fSScott Kruger# These are special keys describing build
5029921a8fSScott Krugerbuildkeys="requires TODO SKIP depends".split()
5129921a8fSScott Kruger
520a091e3eSScott Krugeracceptedkeys="test nsize requires command suffix args filter filter_output localrunfiles comments TODO SKIP output_file timeoutfactor".split()
5344776d8cSScott Krugerappendlist="args requires comments".split()
5468a9e459SScott Kruger
5568a9e459SScott Krugerimport re
5668a9e459SScott Kruger
5744776d8cSScott Krugerdef _stripIndent(block,srcfile,entireBlock=False,fileNums=[]):
5829921a8fSScott Kruger  """
5929921a8fSScott Kruger  Go through and remove a level of indentation
6029921a8fSScott Kruger  Also strip of trailing whitespace
6129921a8fSScott Kruger  """
6229921a8fSScott Kruger  # The first entry should be test: but it might be indented.
6329921a8fSScott Kruger  ext=os.path.splitext(srcfile)[1]
6444776d8cSScott Kruger  stripstr=" "
6566db876fSScott Kruger  if len(fileNums)>0: lineNum=fileNums[0]-1
668ccd5183SScott Kruger  for lline in block.split("\n"):
6766db876fSScott Kruger    if len(fileNums)>0: lineNum+=1
688ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
6929921a8fSScott Kruger    if not line.strip(): continue
70c4b80baaSScott Kruger    if line.strip().startswith('#'): continue
7144776d8cSScott Kruger    if entireBlock:
7244776d8cSScott Kruger      var=line.split(":")[0].strip()
73aec507c4SScott Kruger      if not var in ['test','testset','build']:
7466db876fSScott Kruger        raise Exception("Formatting error: Cannot find test in file: "+srcfile+" at line: "+str(lineNum)+"\n")
7529921a8fSScott Kruger    nspace=len(line)-len(line.lstrip(stripstr))
7629921a8fSScott Kruger    newline=line[nspace:]
7729921a8fSScott Kruger    break
7829921a8fSScott Kruger
7929921a8fSScott Kruger  # Strip off any indentation for the whole string and any trailing
8029921a8fSScott Kruger  # whitespace for convenience
8129921a8fSScott Kruger  newTestStr="\n"
8244776d8cSScott Kruger  if len(fileNums)>0: lineNum=fileNums[0]-1
8344776d8cSScott Kruger  firstPass=True
848ccd5183SScott Kruger  for lline in block.split("\n"):
8544776d8cSScott Kruger    if len(fileNums)>0: lineNum+=1
868ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
8729921a8fSScott Kruger    if not line.strip(): continue
88c4b80baaSScott Kruger    if line.strip().startswith('#'):
89c4b80baaSScott Kruger      newTestStr+=line+'\n'
90c4b80baaSScott Kruger    else:
9129921a8fSScott Kruger      newline=line[nspace:]
92c4b80baaSScott Kruger      newTestStr+=newline.rstrip()+"\n"
9344776d8cSScott Kruger    # Do some basic indentation checks
9444776d8cSScott Kruger    if entireBlock:
9544776d8cSScott Kruger      # Don't need to check comment lines
9644776d8cSScott Kruger      if line.strip().startswith('#'): continue
9744776d8cSScott Kruger      if not newline.startswith(" "):
9844776d8cSScott Kruger        var=newline.split(":")[0].strip()
99aec507c4SScott Kruger        if not var in ['test','testset','build']:
10044776d8cSScott Kruger          err="Formatting error in file "+srcfile+" at line: " +line+"\n"
10144776d8cSScott Kruger          if len(fileNums)>0:
10244776d8cSScott Kruger            raise Exception(err+"Check indentation at line number: "+str(lineNum))
10344776d8cSScott Kruger          else:
10444776d8cSScott Kruger            raise Exception(err)
10544776d8cSScott Kruger      else:
10644776d8cSScott Kruger        var=line.split(":")[0].strip()
107aec507c4SScott Kruger        if var in ['test','testset','build']:
10844776d8cSScott Kruger          subnspace=len(line)-len(line.lstrip(stripstr))
10944776d8cSScott Kruger          if firstPass:
11044776d8cSScott Kruger            firstsubnspace=subnspace
11144776d8cSScott Kruger            firstPass=False
11244776d8cSScott Kruger          else:
11344776d8cSScott Kruger            if firstsubnspace!=subnspace:
11444776d8cSScott Kruger              err="Formatting subtest error in file "+srcfile+" at line: " +line+"\n"
11544776d8cSScott Kruger              if len(fileNums)>0:
11644776d8cSScott Kruger                raise Exception(err+"Check indentation at line number: "+str(lineNum))
11744776d8cSScott Kruger              else:
11844776d8cSScott Kruger                raise Exception(err)
11944776d8cSScott Kruger
12043c6e11bSMatthew G. Knepley  # Allow line continuation character '\'
12143c6e11bSMatthew G. Knepley  return newTestStr.replace('\\\n', ' ')
12229921a8fSScott Kruger
123aae9f2d9SScott Krugerdef parseLoopArgs(varset):
12444776d8cSScott Kruger  """
125aae9f2d9SScott Kruger  Given:   String containing loop variables
126aae9f2d9SScott Kruger  Return: tuple containing separate/shared and string of loop vars
12744776d8cSScott Kruger  """
128a449fbaeSJed Brown  keynm=varset.split("{{")[0].strip().lstrip('-')
129aae9f2d9SScott Kruger  if not keynm.strip(): keynm='nsize'
130aae9f2d9SScott Kruger  lvars=varset.split('{{')[1].split('}')[0]
131aae9f2d9SScott Kruger  suffx=varset.split('{{')[1].split('}')[1]
132aae9f2d9SScott Kruger  ftype='separate' if suffx.startswith('separate') else 'shared'
133aae9f2d9SScott Kruger  return keynm,lvars,ftype
13444776d8cSScott Kruger
135e53dc769SScott Krugerdef _getSeparateTestvars(testDict):
136e53dc769SScott Kruger  """
137e53dc769SScott Kruger  Given: dictionary that may have
138e53dc769SScott Kruger  Return:  Variables that cause a test split
139e53dc769SScott Kruger  """
140e53dc769SScott Kruger  vals=None
141e53dc769SScott Kruger  sepvars=[]
142e53dc769SScott Kruger  # Check nsize
1435b6bfdb9SJed Brown  if 'nsize' in testDict:
144e53dc769SScott Kruger    varset=testDict['nsize']
145aae9f2d9SScott Kruger    if '{{' in varset:
146aae9f2d9SScott Kruger      keynm,lvars,ftype=parseLoopArgs(varset)
147aae9f2d9SScott Kruger      if ftype=='separate': sepvars.append(keynm)
148e53dc769SScott Kruger
149e53dc769SScott Kruger  # Now check args
1505b6bfdb9SJed Brown  if 'args' not in testDict: return sepvars
151*d87d9516SStefano Zampini  for varset in re.split('(^|\W)-(?=[a-zA-Z])',testDict['args']):
152e53dc769SScott Kruger    if not varset.strip(): continue
153aae9f2d9SScott Kruger    if '{{' in varset:
154e53dc769SScott Kruger      # Assuming only one for loop per var specification
155aae9f2d9SScott Kruger      keynm,lvars,ftype=parseLoopArgs(varset)
156aae9f2d9SScott Kruger      if ftype=='separate': sepvars.append(keynm)
157e53dc769SScott Kruger
158e53dc769SScott Kruger  return sepvars
159e53dc769SScott Kruger
1604d82b48cSScott Krugerdef _getNewArgs(args):
1614d82b48cSScott Kruger  """
1624d82b48cSScott Kruger  Given: String that has args that might have loops in them
1634d82b48cSScott Kruger  Return:  All of the arguments/values that do not have
1644d82b48cSScott Kruger             for 'separate output' in for loops
1654d82b48cSScott Kruger  """
1664d82b48cSScott Kruger  newargs=''
1674d82b48cSScott Kruger  if not args.strip(): return args
168*d87d9516SStefano Zampini  for varset in re.split('(^|\W)-(?=[a-zA-Z])',args):
1694d82b48cSScott Kruger    if not varset.strip(): continue
1704d82b48cSScott Kruger    if '{{' not in varset:
1714d82b48cSScott Kruger      if 'separate' not in varset:
1724d82b48cSScott Kruger        newargs+="-"+varset.strip()+" "
1734d82b48cSScott Kruger
1744d82b48cSScott Kruger  return newargs
1754d82b48cSScott Kruger
17644776d8cSScott Krugerdef _getVarVals(findvar,testDict):
17744776d8cSScott Kruger  """
17844776d8cSScott Kruger  Given: variable that is either nsize or in args
1794d82b48cSScott Kruger  Return:  Values to loop over and the other arguments
1804d82b48cSScott Kruger    Note that we keep the other arguments even if they have
1814d82b48cSScott Kruger    for loops to enable stepping through all of the for lops
18244776d8cSScott Kruger  """
1834d82b48cSScott Kruger  save_vals=None
18444776d8cSScott Kruger  if findvar=='nsize':
185e53dc769SScott Kruger    varset=testDict[findvar]
1864d82b48cSScott Kruger    keynm,save_vals,ftype=parseLoopArgs('nsize '+varset)
18744776d8cSScott Kruger  else:
18844776d8cSScott Kruger    varlist=[]
18944776d8cSScott Kruger    for varset in re.split('-(?=[a-zA-Z])',testDict['args']):
19044776d8cSScott Kruger      if not varset.strip(): continue
1914d82b48cSScott Kruger      if '{{' not in varset: continue
192aae9f2d9SScott Kruger      keyvar,vals,ftype=parseLoopArgs(varset)
1934d82b48cSScott Kruger      if keyvar==findvar:
1944d82b48cSScott Kruger        save_vals=vals
19544776d8cSScott Kruger
1965b6bfdb9SJed Brown  if not save_vals: raise Exception("Could not find separate_testvar: "+findvar)
1974d82b48cSScott Kruger  return save_vals
19844776d8cSScott Kruger
1994f8a0bffSScott Krugerdef genTestsSeparateTestvars(intests,indicts,final=False):
20044776d8cSScott Kruger  """
201e53dc769SScott Kruger  Given: testname, sdict with 'separate_testvars
20244776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
2034d82b48cSScott Kruger    The tricky part here is the {{ ... }separate output}
2044d82b48cSScott Kruger    that can be used multiple times
20544776d8cSScott Kruger  """
20644776d8cSScott Kruger  testnames=[]; sdicts=[]
20744776d8cSScott Kruger  for i in range(len(intests)):
20844776d8cSScott Kruger    testname=intests[i]; sdict=indicts[i]; i+=1
209e53dc769SScott Kruger    separate_testvars=_getSeparateTestvars(sdict)
210e53dc769SScott Kruger    if len(separate_testvars)>0:
2114d82b48cSScott Kruger      sep_dicts=[sdict.copy()]
2124d82b48cSScott Kruger      if 'args' in sep_dicts[0]:
2134d82b48cSScott Kruger        sep_dicts[0]['args']=_getNewArgs(sdict['args'])
2144d82b48cSScott Kruger      sep_testnames=[testname]
215e53dc769SScott Kruger      for kvar in separate_testvars:
2164d82b48cSScott Kruger        kvals=_getVarVals(kvar,sdict)
2174d82b48cSScott Kruger
2184d82b48cSScott Kruger        # Have to do loop over previous var/val combos as well
2194d82b48cSScott Kruger        # and accumulate as we go
2204d82b48cSScott Kruger        val_testnames=[]; val_dicts=[]
22144776d8cSScott Kruger        for val in kvals.split():
222541979b6SScott Kruger          gensuffix="_"+kvar+"-"+val.replace(',','__')
2234d82b48cSScott Kruger          for kvaltestnm in sep_testnames:
2244d82b48cSScott Kruger            val_testnames.append(kvaltestnm+gensuffix)
2254d82b48cSScott Kruger          for kv in sep_dicts:
2264d82b48cSScott Kruger            kvardict=kv.copy()
2274d82b48cSScott Kruger            # If the last var then we have the final version
2284d82b48cSScott Kruger            if 'suffix' in sdict:
2294d82b48cSScott Kruger              kvardict['suffix']+=gensuffix
2300bcc1aabSScott Kruger            else:
2310bcc1aabSScott Kruger              kvardict['suffix']=gensuffix
23244776d8cSScott Kruger            if kvar=='nsize':
23344776d8cSScott Kruger              kvardict[kvar]=val
23444776d8cSScott Kruger            else:
2354d82b48cSScott Kruger              kvardict['args']+="-"+kvar+" "+val+" "
2364d82b48cSScott Kruger            val_dicts.append(kvardict)
2374d82b48cSScott Kruger        sep_testnames=val_testnames
2384d82b48cSScott Kruger        sep_dicts=val_dicts
2394d82b48cSScott Kruger      testnames+=sep_testnames
2404d82b48cSScott Kruger      sdicts+=sep_dicts
24144776d8cSScott Kruger    else:
2424f8a0bffSScott Kruger      # These are plain vanilla tests (no subtests, no loops) that
2434f8a0bffSScott Kruger      # do not have a suffix.  This makes the targets match up with
2444f8a0bffSScott Kruger      # the output file (testname_1.out)
2454f8a0bffSScott Kruger      if final:
2464f8a0bffSScott Kruger          if '_' not in testname: testname+='_1'
24744776d8cSScott Kruger      testnames.append(testname)
24844776d8cSScott Kruger      sdicts.append(sdict)
24944776d8cSScott Kruger  return testnames,sdicts
25044776d8cSScott Kruger
25144776d8cSScott Krugerdef genTestsSubtestSuffix(testnames,sdicts):
25244776d8cSScott Kruger  """
25344776d8cSScott Kruger  Given: testname, sdict with separate_testvars
25444776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
25544776d8cSScott Kruger  """
25644776d8cSScott Kruger  tnms=[]; sdcts=[]
25744776d8cSScott Kruger  for i in range(len(testnames)):
25844776d8cSScott Kruger    testname=testnames[i]
25944776d8cSScott Kruger    rmsubtests=[]; keepSubtests=False
2605b6bfdb9SJed Brown    if 'subtests' in sdicts[i]:
26144776d8cSScott Kruger      for stest in sdicts[i]["subtests"]:
2625b6bfdb9SJed Brown        if 'suffix' in sdicts[i][stest]:
26344776d8cSScott Kruger          rmsubtests.append(stest)
26444776d8cSScott Kruger          gensuffix="_"+sdicts[i][stest]['suffix']
26544776d8cSScott Kruger          newtestnm=testname+gensuffix
26644776d8cSScott Kruger          tnms.append(newtestnm)
26744776d8cSScott Kruger          newsdict=sdicts[i].copy()
26844776d8cSScott Kruger          del newsdict['subtests']
26944776d8cSScott Kruger          # Have to hand update
27044776d8cSScott Kruger          # Append
27144776d8cSScott Kruger          for kup in appendlist:
2725b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
2735b6bfdb9SJed Brown              if kup in sdicts[i]:
27444776d8cSScott Kruger                newsdict[kup]=sdicts[i][kup]+" "+sdicts[i][stest][kup]
27544776d8cSScott Kruger              else:
27644776d8cSScott Kruger                newsdict[kup]=sdicts[i][stest][kup]
27744776d8cSScott Kruger          # Promote
27844776d8cSScott Kruger          for kup in acceptedkeys:
27944776d8cSScott Kruger            if kup in appendlist: continue
2805b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
28144776d8cSScott Kruger              newsdict[kup]=sdicts[i][stest][kup]
28244776d8cSScott Kruger          # Cleanup
28344776d8cSScott Kruger          for st in sdicts[i]["subtests"]: del newsdict[st]
28444776d8cSScott Kruger          sdcts.append(newsdict)
28544776d8cSScott Kruger        else:
28644776d8cSScott Kruger          keepSubtests=True
28744776d8cSScott Kruger    else:
28844776d8cSScott Kruger      tnms.append(testnames[i])
28944776d8cSScott Kruger      sdcts.append(sdicts[i])
2900bcc1aabSScott Kruger    # If a subtest without a suffix exists, then save it
29144776d8cSScott Kruger    if keepSubtests:
29244776d8cSScott Kruger      tnms.append(testnames[i])
2930bcc1aabSScott Kruger      newsdict=sdicts[i].copy()
2940bcc1aabSScott Kruger      # Prune the tests to prepare for keeping
2950bcc1aabSScott Kruger      for rmtest in rmsubtests:
2960bcc1aabSScott Kruger        newsdict['subtests'].remove(rmtest)
2970bcc1aabSScott Kruger        del newsdict[rmtest]
2980bcc1aabSScott Kruger      sdcts.append(newsdict)
29944776d8cSScott Kruger    i+=1
30044776d8cSScott Kruger  return tnms,sdcts
30144776d8cSScott Kruger
30244776d8cSScott Krugerdef splitTests(testname,sdict):
30344776d8cSScott Kruger  """
30444776d8cSScott Kruger  Given: testname and YAML-generated dictionary
30544776d8cSScott Kruger  Return: list of names and dictionaries corresponding to each test
30644776d8cSScott Kruger          given that the YAML language allows for multiple tests
30744776d8cSScott Kruger  """
30844776d8cSScott Kruger
30944776d8cSScott Kruger  # Order: Parent sep_tv, subtests suffix, subtests sep_tv
31044776d8cSScott Kruger  testnames,sdicts=genTestsSeparateTestvars([testname],[sdict])
31144776d8cSScott Kruger  testnames,sdicts=genTestsSubtestSuffix(testnames,sdicts)
3124f8a0bffSScott Kruger  testnames,sdicts=genTestsSeparateTestvars(testnames,sdicts,final=True)
31344776d8cSScott Kruger
31444776d8cSScott Kruger  # Because I am altering the list, I do this in passes.  Inelegant
31544776d8cSScott Kruger
31644776d8cSScott Kruger  return testnames, sdicts
31744776d8cSScott Kruger
3186cecdbdcSScott Krugerdef parseTest(testStr,srcfile,verbosity):
31929921a8fSScott Kruger  """
32029921a8fSScott Kruger  This parses an individual test
32129921a8fSScott Kruger  YAML is hierarchial so should use a state machine in the general case,
32253f2a965SBarry Smith  but in practice we only support two levels of test:
32329921a8fSScott Kruger  """
32429921a8fSScott Kruger  basename=os.path.basename(srcfile)
32529921a8fSScott Kruger  # Handle the new at the begininng
32629921a8fSScott Kruger  bn=re.sub("new_","",basename)
32729921a8fSScott Kruger  # This is the default
32829921a8fSScott Kruger  testname="run"+os.path.splitext(bn)[0]
32929921a8fSScott Kruger
33029921a8fSScott Kruger  # Tests that have default everything (so empty effectively)
33178659935SScott Kruger  if len(testStr)==0: return [testname], [{}]
33229921a8fSScott Kruger
33329921a8fSScott Kruger  striptest=_stripIndent(testStr,srcfile)
33429921a8fSScott Kruger
33529921a8fSScott Kruger  # go through and parse
33629921a8fSScott Kruger  subtestnum=0
33729921a8fSScott Kruger  subdict={}
33868a9e459SScott Kruger  comments=[]
33968a9e459SScott Kruger  indentlevel=0
34068a9e459SScott Kruger  for ln in striptest.split("\n"):
341c4b80baaSScott Kruger    line=ln.split('#')[0].rstrip()
342cadd188bSScott Kruger    if verbosity>2: print(line)
3430bcc1aabSScott Kruger    comment=("" if len(ln.split("#"))>0 else " ".join(ln.split("#")[1:]).strip())
34468a9e459SScott Kruger    if comment: comments.append(comment)
34529921a8fSScott Kruger    if not line.strip(): continue
34644776d8cSScott Kruger    lsplit=line.split(':')
34744776d8cSScott Kruger    if len(lsplit)==0: raise Exception("Missing : in line: "+line)
34844776d8cSScott Kruger    indentcount=lsplit[0].count(" ")
34944776d8cSScott Kruger    var=lsplit[0].strip()
35040ae0433SScott Kruger    val=line[line.find(':')+1:].strip()
35144776d8cSScott Kruger    if not var in acceptedkeys: raise Exception("Not a defined key: "+var+" from:  "+line)
35229921a8fSScott Kruger    # Start by seeing if we are in a subtest
35329921a8fSScott Kruger    if line.startswith(" "):
354ecc1beb5SScott Kruger      if var in subdict[subtestname]:
355ecc1beb5SScott Kruger        subdict[subtestname][var]+=" "+val
356ecc1beb5SScott Kruger      else:
357c0658d2aSScott Kruger        subdict[subtestname][var]=val
35868a9e459SScott Kruger      if not indentlevel: indentlevel=indentcount
359cadd188bSScott Kruger      #if indentlevel!=indentcount: print("Error in indentation:", ln)
36029921a8fSScott Kruger    # Determine subtest name and make dict
36129921a8fSScott Kruger    elif var=="test":
36229921a8fSScott Kruger      subtestname="test"+str(subtestnum)
36329921a8fSScott Kruger      subdict[subtestname]={}
3645b6bfdb9SJed Brown      if "subtests" not in subdict: subdict["subtests"]=[]
36529921a8fSScott Kruger      subdict["subtests"].append(subtestname)
36629921a8fSScott Kruger      subtestnum=subtestnum+1
36768a9e459SScott Kruger    # The rest are easy
36829921a8fSScott Kruger    else:
36944776d8cSScott Kruger      # For convenience, it is sometimes convenient to list twice
3705b6bfdb9SJed Brown      if var in subdict:
37144776d8cSScott Kruger        if var in appendlist:
37244776d8cSScott Kruger          subdict[var]+=" "+val
37344776d8cSScott Kruger        else:
37444776d8cSScott Kruger          raise Exception(var+" entered twice: "+line)
37544776d8cSScott Kruger      else:
376c0658d2aSScott Kruger        subdict[var]=val
37729921a8fSScott Kruger      if var=="suffix":
37829921a8fSScott Kruger        if len(val)>0:
3794f8a0bffSScott Kruger          testname+="_"+val
38029921a8fSScott Kruger
38168a9e459SScott Kruger  if len(comments): subdict['comments']="\n".join(comments).lstrip("\n")
3824f8a0bffSScott Kruger
3834f8a0bffSScott Kruger  # A test block can create multiple tests.  This does that logic
38444776d8cSScott Kruger  testnames,subdicts=splitTests(testname,subdict)
38544776d8cSScott Kruger  return testnames,subdicts
38629921a8fSScott Kruger
3876cecdbdcSScott Krugerdef parseTests(testStr,srcfile,fileNums,verbosity):
38829921a8fSScott Kruger  """
38929921a8fSScott Kruger  Parse the yaml string describing tests and return
39029921a8fSScott Kruger  a dictionary with the info in the form of:
39129921a8fSScott Kruger    testDict[test][subtest]
39229921a8fSScott Kruger  This is an inelegant parser as we do not wish to
39329921a8fSScott Kruger  introduce a new dependency by bringing in pyyaml.
39429921a8fSScott Kruger  The advantage is that validation can be done as
39529921a8fSScott Kruger  it is parsed (e.g., 'test' is the only top-level node)
39629921a8fSScott Kruger  """
39729921a8fSScott Kruger
39829921a8fSScott Kruger  testDict={}
39929921a8fSScott Kruger
40029921a8fSScott Kruger  # The first entry should be test: but it might be indented.
40144776d8cSScott Kruger  newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums)
402cadd188bSScott Kruger  if verbosity>2: print(srcfile)
40329921a8fSScott Kruger
404e4653983SScott Kruger  ## Check and see if we have build requirements
405e4653983SScott Kruger  addToRunRequirements=None
406aec507c4SScott Kruger  if "\nbuild:" in newTestStr:
407aec507c4SScott Kruger    testDict['build']={}
408aec507c4SScott Kruger    # The file info is already here and need to append
409aec507c4SScott Kruger    Part1=newTestStr.split("build:")[1]
410aec507c4SScott Kruger    fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0]
411aec507c4SScott Kruger    for bkey in buildkeys:
412aec507c4SScott Kruger      if bkey+":" in fileInfo:
413aec507c4SScott Kruger        testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
414aec507c4SScott Kruger        #if verbosity>1: bkey+": "+testDict['build'][bkey]
415e4653983SScott Kruger      # If a runtime requires are put into build, push them down to all run tests
416e4653983SScott Kruger      # At this point, we are working with strings and not lists
417e4653983SScott Kruger      if 'requires' in testDict['build']:
418e4653983SScott Kruger         if 'datafilespath' in testDict['build']['requires']:
419e4653983SScott Kruger             newreqs=re.sub('datafilespath','',testDict['build']['requires'])
420e4653983SScott Kruger             testDict['build']['requires']=newreqs.strip()
421e4653983SScott Kruger             addToRunRequirements='datafilespath'
422e4653983SScott Kruger
423aec507c4SScott Kruger
42429921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
425e53dc769SScott Kruger  for test in re.split("\ntest(?:set)?:",newTestStr)[1:]:
4266cecdbdcSScott Kruger    testnames,subdicts=parseTest(test,srcfile,verbosity)
42744776d8cSScott Kruger    for i in range(len(testnames)):
4285b6bfdb9SJed Brown      if testnames[i] in testDict:
4291acf9037SMatthew G. Knepley        raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile)
430e4653983SScott Kruger      # Add in build requirements that need to be moved
431e4653983SScott Kruger      if addToRunRequirements:
432e4653983SScott Kruger          if 'requires' in subdicts[i]:
433e4653983SScott Kruger              subdicts[i]['requires']+=addToRunRequirements
434e4653983SScott Kruger          else:
435e4653983SScott Kruger              subdicts[i]['requires']=addToRunRequirements
43644776d8cSScott Kruger      testDict[testnames[i]]=subdicts[i]
43729921a8fSScott Kruger
43829921a8fSScott Kruger  return testDict
43929921a8fSScott Kruger
4406cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity):
44129921a8fSScott Kruger  """
44229921a8fSScott Kruger  Parse single example files and return dictionary of the form:
44329921a8fSScott Kruger    testDict[srcfile][test][subtest]
44429921a8fSScott Kruger  """
44529921a8fSScott Kruger  debug=False
446cadd188bSScott Kruger  basename=os.path.basename(srcfile)
447cadd188bSScott Kruger  if basename=='makefile': return {}
448cadd188bSScott Kruger
44929921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
45029921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
45129921a8fSScott Kruger  os.chdir(basedir)
45229921a8fSScott Kruger
45329921a8fSScott Kruger  testDict={}
454cadd188bSScott Kruger  sh=open(basename,"r"); fileStr=sh.read(); sh.close()
45529921a8fSScott Kruger
45629921a8fSScott Kruger  ## Start with doing the tests
45729921a8fSScott Kruger  #
45829921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
45944776d8cSScott Kruger  fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1
46029921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
4616f029658SMatthew G. Knepley  # one
46229921a8fSScott Kruger  srcTests=[]
46329921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
46429921a8fSScott Kruger  testString=" ".join(srcTests)
46544776d8cSScott Kruger  flen=len(testString.split("\n"))
46644776d8cSScott Kruger  fend=fstart+flen-1
46744776d8cSScott Kruger  fileNums=range(fstart,fend)
4686cecdbdcSScott Kruger  testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity)
469aec507c4SScott Kruger  # Massage dictionary for build requirements
470aec507c4SScott Kruger  if 'build' in testDict[basename]:
471aec507c4SScott Kruger    testDict[basename].update(testDict[basename]['build'])
472aec507c4SScott Kruger    del testDict[basename]['build']
47329921a8fSScott Kruger
47429921a8fSScott Kruger
47529921a8fSScott Kruger  os.chdir(curdir)
47629921a8fSScott Kruger  return testDict
47729921a8fSScott Kruger
4786cecdbdcSScott Krugerdef parseTestDir(directory,verbosity):
47929921a8fSScott Kruger  """
48029921a8fSScott Kruger  Parse single example files and return dictionary of the form:
48129921a8fSScott Kruger    testDict[srcfile][test][subtest]
48229921a8fSScott Kruger  """
48329921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
48429921a8fSScott Kruger  basedir=os.path.realpath(directory)
48529921a8fSScott Kruger  os.chdir(basedir)
48629921a8fSScott Kruger
48729921a8fSScott Kruger  tDict={}
48809a6cbfcSBernhard M. Wiedemann  for test_file in sorted(glob.glob("new_ex*.*")):
4896cecdbdcSScott Kruger    tDict.update(parseTestFile(test_file,verbosity))
49029921a8fSScott Kruger
49129921a8fSScott Kruger  os.chdir(curdir)
49229921a8fSScott Kruger  return tDict
49329921a8fSScott Kruger
49429921a8fSScott Krugerdef printExParseDict(rDict):
49529921a8fSScott Kruger  """
49629921a8fSScott Kruger  This is useful for debugging
49729921a8fSScott Kruger  """
49829921a8fSScott Kruger  indent="   "
49929921a8fSScott Kruger  for sfile in rDict:
500cadd188bSScott Kruger    print(sfile)
501c3d83d22SScott Kruger    sortkeys=list(rDict[sfile].keys())
50244776d8cSScott Kruger    sortkeys.sort()
50344776d8cSScott Kruger    for runex in sortkeys:
504cadd188bSScott Kruger      print(indent+runex)
5055b6bfdb9SJed Brown      if type(rDict[sfile][runex])==bytes:
506cadd188bSScott Kruger        print(indent*2+rDict[sfile][runex])
50729921a8fSScott Kruger      else:
50829921a8fSScott Kruger        for var in rDict[sfile][runex]:
50944776d8cSScott Kruger          if var.startswith("test"): continue
510cadd188bSScott Kruger          print(indent*2+var+": "+str(rDict[sfile][runex][var]))
5115b6bfdb9SJed Brown        if 'subtests' in rDict[sfile][runex]:
51244776d8cSScott Kruger          for var in rDict[sfile][runex]['subtests']:
513cadd188bSScott Kruger            print(indent*2+var)
51429921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
515cadd188bSScott Kruger              print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2]))
516cadd188bSScott Kruger      print("\n")
51729921a8fSScott Kruger  return
51829921a8fSScott Kruger
51929921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
52029921a8fSScott Kruger
52129921a8fSScott Kruger    if directory:
5226cecdbdcSScott Kruger      tDict=parseTestDir(directory,verbosity)
52329921a8fSScott Kruger    else:
5246cecdbdcSScott Kruger      tDict=parseTestFile(test_file,verbosity)
52529921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
52629921a8fSScott Kruger
52729921a8fSScott Kruger    return
52829921a8fSScott Kruger
52929921a8fSScott Krugerif __name__ == '__main__':
53029921a8fSScott Kruger    import optparse
53129921a8fSScott Kruger    parser = optparse.OptionParser()
53229921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
53329921a8fSScott Kruger                      default="", help='Directory containing files to parse')
53429921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
53529921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
53629921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
53729921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
53829921a8fSScott Kruger    opts, extra_args = parser.parse_args()
53929921a8fSScott Kruger
54029921a8fSScott Kruger    if extra_args:
54129921a8fSScott Kruger        import sys
54229921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
54329921a8fSScott Kruger        exit(1)
54429921a8fSScott Kruger    if not opts.test_file and not opts.directory:
545cadd188bSScott Kruger      print("test file or directory is required")
54629921a8fSScott Kruger      parser.print_usage()
54729921a8fSScott Kruger      sys.exit()
54829921a8fSScott Kruger
54929921a8fSScott Kruger    # Need verbosity to be an integer
55029921a8fSScott Kruger    try:
55129921a8fSScott Kruger      verbosity=int(opts.verbosity)
55229921a8fSScott Kruger    except:
55329921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
55429921a8fSScott Kruger
55529921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
556