xref: /petsc/config/testparse.py (revision 080f0011773203ad8803837596fa1e3f25ef47ae)
129921a8fSScott Kruger#!/usr/bin/env python
229921a8fSScott Kruger"""
329921a8fSScott KrugerParse the test file and return a dictionary.
429921a8fSScott Kruger
529921a8fSScott KrugerQuick usage::
629921a8fSScott Kruger
7c4762a1bSJed Brown  lib/petsc/bin/maint/testparse.py -t src/ksp/ksp/tutorials/ex1.c
829921a8fSScott Kruger
929921a8fSScott KrugerFrom the command line, it prints out the dictionary.
1029921a8fSScott KrugerThis is meant to be used by other scripts, but it is
1129921a8fSScott Krugeruseful to debug individual files.
1229921a8fSScott Kruger
1329921a8fSScott KrugerExample language
1429921a8fSScott Kruger----------------
1529921a8fSScott Kruger
1629921a8fSScott Kruger/*TEST
17aec507c4SScott Kruger   build:
18aec507c4SScott Kruger     requires: moab
19e53dc769SScott Kruger   # This is equivalent to test:
20e53dc769SScott Kruger   testset:
2129921a8fSScott Kruger      args: -pc_type mg -ksp_type fgmres -da_refine 2 -ksp_monitor_short -mg_levels_ksp_monitor_short -mg_levels_ksp_norm_type unpreconditioned -ksp_view -pc_mg_type full
2229921a8fSScott Kruger
23e53dc769SScott Kruger   testset:
2429921a8fSScott Kruger      suffix: 2
2529921a8fSScott Kruger      nsize: 2
2629921a8fSScott Kruger      args: -pc_type mg -ksp_type fgmres -da_refine 2 -ksp_monitor_short -mg_levels_ksp_monitor_short -mg_levels_ksp_norm_type unpreconditioned -ksp_view -pc_mg_type full
2729921a8fSScott Kruger
28e53dc769SScott Kruger   testset:
29e53dc769SScott Kruger      suffix: 2
30e53dc769SScott Kruger      nsize: 2
31e53dc769SScott Kruger      args: -pc_type mg -ksp_type fgmres -da_refine 2 -ksp_monitor_short -mg_levels_ksp_monitor_short -mg_levels_ksp_norm_type unpreconditioned -ksp_view -pc_mg_type full
32e53dc769SScott Kruger      test:
33e53dc769SScott Kruger
3429921a8fSScott KrugerTEST*/
3529921a8fSScott Kruger
3629921a8fSScott Kruger"""
375b6bfdb9SJed Brownfrom __future__ import print_function
3829921a8fSScott Kruger
3929921a8fSScott Krugerimport os, re, glob, types
4029921a8fSScott Krugerimport sys
4129921a8fSScott Krugerimport logging
4229921a8fSScott Krugersys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
4329921a8fSScott Kruger
4429921a8fSScott Krugerimport inspect
4529921a8fSScott Krugerthisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
4629921a8fSScott Krugermaintdir=os.path.join(os.path.join(os.path.dirname(thisscriptdir),'bin'),'maint')
4729921a8fSScott Krugersys.path.insert(0,maintdir)
4829921a8fSScott Kruger
4929921a8fSScott Kruger# These are special keys describing build
5029921a8fSScott Krugerbuildkeys="requires TODO SKIP depends".split()
5129921a8fSScott Kruger
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
575e361860SScott Krugerdef getDefaultOutputFileRoot(testname):
585e361860SScott Kruger  """
595e361860SScott Kruger  Given testname, give DefaultRoot and DefaultOutputFilename
605e361860SScott Kruger  e.g., runex1 gives ex1_1, output/ex1_1.out
615e361860SScott Kruger  """
625e361860SScott Kruger  defroot=(re.sub("run","",testname) if testname.startswith("run") else testname)
635e361860SScott Kruger  if not "_" in defroot: defroot=defroot+"_1"
645e361860SScott Kruger  return defroot
655e361860SScott Kruger
6644776d8cSScott Krugerdef _stripIndent(block,srcfile,entireBlock=False,fileNums=[]):
6729921a8fSScott Kruger  """
6829921a8fSScott Kruger  Go through and remove a level of indentation
6929921a8fSScott Kruger  Also strip of trailing whitespace
7029921a8fSScott Kruger  """
7129921a8fSScott Kruger  # The first entry should be test: but it might be indented.
7229921a8fSScott Kruger  ext=os.path.splitext(srcfile)[1]
7344776d8cSScott Kruger  stripstr=" "
7466db876fSScott Kruger  if len(fileNums)>0: lineNum=fileNums[0]-1
758ccd5183SScott Kruger  for lline in block.split("\n"):
7666db876fSScott Kruger    if len(fileNums)>0: lineNum+=1
778ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
7829921a8fSScott Kruger    if not line.strip(): continue
79c4b80baaSScott Kruger    if line.strip().startswith('#'): continue
8044776d8cSScott Kruger    if entireBlock:
8144776d8cSScott Kruger      var=line.split(":")[0].strip()
82aec507c4SScott Kruger      if not var in ['test','testset','build']:
8366db876fSScott Kruger        raise Exception("Formatting error: Cannot find test in file: "+srcfile+" at line: "+str(lineNum)+"\n")
8429921a8fSScott Kruger    nspace=len(line)-len(line.lstrip(stripstr))
8529921a8fSScott Kruger    newline=line[nspace:]
8629921a8fSScott Kruger    break
8729921a8fSScott Kruger
8829921a8fSScott Kruger  # Strip off any indentation for the whole string and any trailing
8929921a8fSScott Kruger  # whitespace for convenience
9029921a8fSScott Kruger  newTestStr="\n"
9144776d8cSScott Kruger  if len(fileNums)>0: lineNum=fileNums[0]-1
9244776d8cSScott Kruger  firstPass=True
938ccd5183SScott Kruger  for lline in block.split("\n"):
9444776d8cSScott Kruger    if len(fileNums)>0: lineNum+=1
958ccd5183SScott Kruger    line=lline[1:] if lline.startswith("!") else lline
9629921a8fSScott Kruger    if not line.strip(): continue
97c4b80baaSScott Kruger    if line.strip().startswith('#'):
98c4b80baaSScott Kruger      newTestStr+=line+'\n'
99c4b80baaSScott Kruger    else:
10029921a8fSScott Kruger      newline=line[nspace:]
101c4b80baaSScott Kruger      newTestStr+=newline.rstrip()+"\n"
10244776d8cSScott Kruger    # Do some basic indentation checks
10344776d8cSScott Kruger    if entireBlock:
10444776d8cSScott Kruger      # Don't need to check comment lines
10544776d8cSScott Kruger      if line.strip().startswith('#'): continue
10644776d8cSScott Kruger      if not newline.startswith(" "):
10744776d8cSScott Kruger        var=newline.split(":")[0].strip()
108aec507c4SScott Kruger        if not var in ['test','testset','build']:
10944776d8cSScott Kruger          err="Formatting error in file "+srcfile+" at line: " +line+"\n"
11044776d8cSScott Kruger          if len(fileNums)>0:
11144776d8cSScott Kruger            raise Exception(err+"Check indentation at line number: "+str(lineNum))
11244776d8cSScott Kruger          else:
11344776d8cSScott Kruger            raise Exception(err)
11444776d8cSScott Kruger      else:
11544776d8cSScott Kruger        var=line.split(":")[0].strip()
116aec507c4SScott Kruger        if var in ['test','testset','build']:
11744776d8cSScott Kruger          subnspace=len(line)-len(line.lstrip(stripstr))
11844776d8cSScott Kruger          if firstPass:
11944776d8cSScott Kruger            firstsubnspace=subnspace
12044776d8cSScott Kruger            firstPass=False
12144776d8cSScott Kruger          else:
12244776d8cSScott Kruger            if firstsubnspace!=subnspace:
12344776d8cSScott Kruger              err="Formatting subtest error in file "+srcfile+" at line: " +line+"\n"
12444776d8cSScott Kruger              if len(fileNums)>0:
12544776d8cSScott Kruger                raise Exception(err+"Check indentation at line number: "+str(lineNum))
12644776d8cSScott Kruger              else:
12744776d8cSScott Kruger                raise Exception(err)
12844776d8cSScott Kruger
12943c6e11bSMatthew G. Knepley  # Allow line continuation character '\'
13043c6e11bSMatthew G. Knepley  return newTestStr.replace('\\\n', ' ')
13129921a8fSScott Kruger
132aae9f2d9SScott Krugerdef parseLoopArgs(varset):
13344776d8cSScott Kruger  """
134aae9f2d9SScott Kruger  Given:   String containing loop variables
135aae9f2d9SScott Kruger  Return: tuple containing separate/shared and string of loop vars
13644776d8cSScott Kruger  """
137a449fbaeSJed Brown  keynm=varset.split("{{")[0].strip().lstrip('-')
138aae9f2d9SScott Kruger  if not keynm.strip(): keynm='nsize'
139aae9f2d9SScott Kruger  lvars=varset.split('{{')[1].split('}')[0]
140aae9f2d9SScott Kruger  suffx=varset.split('{{')[1].split('}')[1]
141aae9f2d9SScott Kruger  ftype='separate' if suffx.startswith('separate') else 'shared'
142aae9f2d9SScott Kruger  return keynm,lvars,ftype
14344776d8cSScott Kruger
144e53dc769SScott Krugerdef _getSeparateTestvars(testDict):
145e53dc769SScott Kruger  """
146e53dc769SScott Kruger  Given: dictionary that may have
147e53dc769SScott Kruger  Return:  Variables that cause a test split
148e53dc769SScott Kruger  """
149e53dc769SScott Kruger  vals=None
150e53dc769SScott Kruger  sepvars=[]
151e53dc769SScott Kruger  # Check nsize
1525b6bfdb9SJed Brown  if 'nsize' in testDict:
153e53dc769SScott Kruger    varset=testDict['nsize']
154aae9f2d9SScott Kruger    if '{{' in varset:
155aae9f2d9SScott Kruger      keynm,lvars,ftype=parseLoopArgs(varset)
156aae9f2d9SScott Kruger      if ftype=='separate': sepvars.append(keynm)
157e53dc769SScott Kruger
158e53dc769SScott Kruger  # Now check args
1595b6bfdb9SJed Brown  if 'args' not in testDict: return sepvars
160d87d9516SStefano Zampini  for varset in re.split('(^|\W)-(?=[a-zA-Z])',testDict['args']):
161e53dc769SScott Kruger    if not varset.strip(): continue
162aae9f2d9SScott Kruger    if '{{' in varset:
163e53dc769SScott Kruger      # Assuming only one for loop per var specification
164aae9f2d9SScott Kruger      keynm,lvars,ftype=parseLoopArgs(varset)
165aae9f2d9SScott Kruger      if ftype=='separate': sepvars.append(keynm)
166e53dc769SScott Kruger
167e53dc769SScott Kruger  return sepvars
168e53dc769SScott Kruger
1694d82b48cSScott Krugerdef _getNewArgs(args):
1704d82b48cSScott Kruger  """
1714d82b48cSScott Kruger  Given: String that has args that might have loops in them
1724d82b48cSScott Kruger  Return:  All of the arguments/values that do not have
1734d82b48cSScott Kruger             for 'separate output' in for loops
1744d82b48cSScott Kruger  """
1754d82b48cSScott Kruger  newargs=''
1764d82b48cSScott Kruger  if not args.strip(): return args
177d87d9516SStefano Zampini  for varset in re.split('(^|\W)-(?=[a-zA-Z])',args):
1784d82b48cSScott Kruger    if not varset.strip(): continue
17914f228deSScott Kruger    if '{{' in varset and 'separate' in varset: continue
1804d82b48cSScott Kruger    newargs+="-"+varset.strip()+" "
1814d82b48cSScott Kruger
1824d82b48cSScott Kruger  return newargs
1834d82b48cSScott Kruger
18444776d8cSScott Krugerdef _getVarVals(findvar,testDict):
18544776d8cSScott Kruger  """
18644776d8cSScott Kruger  Given: variable that is either nsize or in args
1874d82b48cSScott Kruger  Return:  Values to loop over and the other arguments
1884d82b48cSScott Kruger    Note that we keep the other arguments even if they have
1894d82b48cSScott Kruger    for loops to enable stepping through all of the for lops
19044776d8cSScott Kruger  """
1914d82b48cSScott Kruger  save_vals=None
19244776d8cSScott Kruger  if findvar=='nsize':
193e53dc769SScott Kruger    varset=testDict[findvar]
1944d82b48cSScott Kruger    keynm,save_vals,ftype=parseLoopArgs('nsize '+varset)
19544776d8cSScott Kruger  else:
19644776d8cSScott Kruger    varlist=[]
19744776d8cSScott Kruger    for varset in re.split('-(?=[a-zA-Z])',testDict['args']):
19844776d8cSScott Kruger      if not varset.strip(): continue
1994d82b48cSScott Kruger      if '{{' not in varset: continue
200aae9f2d9SScott Kruger      keyvar,vals,ftype=parseLoopArgs(varset)
2014d82b48cSScott Kruger      if keyvar==findvar:
2024d82b48cSScott Kruger        save_vals=vals
20344776d8cSScott Kruger
2045b6bfdb9SJed Brown  if not save_vals: raise Exception("Could not find separate_testvar: "+findvar)
2054d82b48cSScott Kruger  return save_vals
20644776d8cSScott Kruger
2074f8a0bffSScott Krugerdef genTestsSeparateTestvars(intests,indicts,final=False):
20844776d8cSScott Kruger  """
209e53dc769SScott Kruger  Given: testname, sdict with 'separate_testvars
21044776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
2114d82b48cSScott Kruger    The tricky part here is the {{ ... }separate output}
2124d82b48cSScott Kruger    that can be used multiple times
21344776d8cSScott Kruger  """
21444776d8cSScott Kruger  testnames=[]; sdicts=[]
21544776d8cSScott Kruger  for i in range(len(intests)):
21644776d8cSScott Kruger    testname=intests[i]; sdict=indicts[i]; i+=1
217e53dc769SScott Kruger    separate_testvars=_getSeparateTestvars(sdict)
218e53dc769SScott Kruger    if len(separate_testvars)>0:
2194d82b48cSScott Kruger      sep_dicts=[sdict.copy()]
2204d82b48cSScott Kruger      if 'args' in sep_dicts[0]:
2214d82b48cSScott Kruger        sep_dicts[0]['args']=_getNewArgs(sdict['args'])
2224d82b48cSScott Kruger      sep_testnames=[testname]
223e53dc769SScott Kruger      for kvar in separate_testvars:
2244d82b48cSScott Kruger        kvals=_getVarVals(kvar,sdict)
2254d82b48cSScott Kruger
2264d82b48cSScott Kruger        # Have to do loop over previous var/val combos as well
2274d82b48cSScott Kruger        # and accumulate as we go
2284d82b48cSScott Kruger        val_testnames=[]; val_dicts=[]
22944776d8cSScott Kruger        for val in kvals.split():
230541979b6SScott Kruger          gensuffix="_"+kvar+"-"+val.replace(',','__')
2314d82b48cSScott Kruger          for kvaltestnm in sep_testnames:
2324d82b48cSScott Kruger            val_testnames.append(kvaltestnm+gensuffix)
2334d82b48cSScott Kruger          for kv in sep_dicts:
2344d82b48cSScott Kruger            kvardict=kv.copy()
2354d82b48cSScott Kruger            # If the last var then we have the final version
2364d82b48cSScott Kruger            if 'suffix' in sdict:
2374d82b48cSScott Kruger              kvardict['suffix']+=gensuffix
2380bcc1aabSScott Kruger            else:
2390bcc1aabSScott Kruger              kvardict['suffix']=gensuffix
24044776d8cSScott Kruger            if kvar=='nsize':
24144776d8cSScott Kruger              kvardict[kvar]=val
24244776d8cSScott Kruger            else:
2434d82b48cSScott Kruger              kvardict['args']+="-"+kvar+" "+val+" "
2444d82b48cSScott Kruger            val_dicts.append(kvardict)
2454d82b48cSScott Kruger        sep_testnames=val_testnames
2464d82b48cSScott Kruger        sep_dicts=val_dicts
2474d82b48cSScott Kruger      testnames+=sep_testnames
2484d82b48cSScott Kruger      sdicts+=sep_dicts
24944776d8cSScott Kruger    else:
2504f8a0bffSScott Kruger      # These are plain vanilla tests (no subtests, no loops) that
2514f8a0bffSScott Kruger      # do not have a suffix.  This makes the targets match up with
2524f8a0bffSScott Kruger      # the output file (testname_1.out)
2534f8a0bffSScott Kruger      if final:
2544f8a0bffSScott Kruger          if '_' not in testname: testname+='_1'
25544776d8cSScott Kruger      testnames.append(testname)
25644776d8cSScott Kruger      sdicts.append(sdict)
25744776d8cSScott Kruger  return testnames,sdicts
25844776d8cSScott Kruger
25944776d8cSScott Krugerdef genTestsSubtestSuffix(testnames,sdicts):
26044776d8cSScott Kruger  """
26144776d8cSScott Kruger  Given: testname, sdict with separate_testvars
26244776d8cSScott Kruger  Return: testnames,sdicts: List of generated tests
26344776d8cSScott Kruger  """
26444776d8cSScott Kruger  tnms=[]; sdcts=[]
26544776d8cSScott Kruger  for i in range(len(testnames)):
26644776d8cSScott Kruger    testname=testnames[i]
26744776d8cSScott Kruger    rmsubtests=[]; keepSubtests=False
2685b6bfdb9SJed Brown    if 'subtests' in sdicts[i]:
26944776d8cSScott Kruger      for stest in sdicts[i]["subtests"]:
2705b6bfdb9SJed Brown        if 'suffix' in sdicts[i][stest]:
27144776d8cSScott Kruger          rmsubtests.append(stest)
27244776d8cSScott Kruger          gensuffix="_"+sdicts[i][stest]['suffix']
27344776d8cSScott Kruger          newtestnm=testname+gensuffix
27444776d8cSScott Kruger          tnms.append(newtestnm)
27544776d8cSScott Kruger          newsdict=sdicts[i].copy()
27644776d8cSScott Kruger          del newsdict['subtests']
27744776d8cSScott Kruger          # Have to hand update
27844776d8cSScott Kruger          # Append
27944776d8cSScott Kruger          for kup in appendlist:
2805b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
2815b6bfdb9SJed Brown              if kup in sdicts[i]:
28244776d8cSScott Kruger                newsdict[kup]=sdicts[i][kup]+" "+sdicts[i][stest][kup]
28344776d8cSScott Kruger              else:
28444776d8cSScott Kruger                newsdict[kup]=sdicts[i][stest][kup]
28544776d8cSScott Kruger          # Promote
28644776d8cSScott Kruger          for kup in acceptedkeys:
28744776d8cSScott Kruger            if kup in appendlist: continue
2885b6bfdb9SJed Brown            if kup in sdicts[i][stest]:
28944776d8cSScott Kruger              newsdict[kup]=sdicts[i][stest][kup]
29044776d8cSScott Kruger          # Cleanup
29144776d8cSScott Kruger          for st in sdicts[i]["subtests"]: del newsdict[st]
29244776d8cSScott Kruger          sdcts.append(newsdict)
29344776d8cSScott Kruger        else:
29444776d8cSScott Kruger          keepSubtests=True
29544776d8cSScott Kruger    else:
29644776d8cSScott Kruger      tnms.append(testnames[i])
29744776d8cSScott Kruger      sdcts.append(sdicts[i])
2980bcc1aabSScott Kruger    # If a subtest without a suffix exists, then save it
29944776d8cSScott Kruger    if keepSubtests:
30044776d8cSScott Kruger      tnms.append(testnames[i])
3010bcc1aabSScott Kruger      newsdict=sdicts[i].copy()
3020bcc1aabSScott Kruger      # Prune the tests to prepare for keeping
3030bcc1aabSScott Kruger      for rmtest in rmsubtests:
3040bcc1aabSScott Kruger        newsdict['subtests'].remove(rmtest)
3050bcc1aabSScott Kruger        del newsdict[rmtest]
3060bcc1aabSScott Kruger      sdcts.append(newsdict)
30744776d8cSScott Kruger    i+=1
30844776d8cSScott Kruger  return tnms,sdcts
30944776d8cSScott Kruger
31044776d8cSScott Krugerdef splitTests(testname,sdict):
31144776d8cSScott Kruger  """
3122be3497aSPatrick Sanan  Given: testname and dictionary generated from the YAML-like definition
31344776d8cSScott Kruger  Return: list of names and dictionaries corresponding to each test
3142be3497aSPatrick Sanan          given that the YAML-like language allows for multiple tests
31544776d8cSScott Kruger  """
31644776d8cSScott Kruger
31744776d8cSScott Kruger  # Order: Parent sep_tv, subtests suffix, subtests sep_tv
31844776d8cSScott Kruger  testnames,sdicts=genTestsSeparateTestvars([testname],[sdict])
31944776d8cSScott Kruger  testnames,sdicts=genTestsSubtestSuffix(testnames,sdicts)
3204f8a0bffSScott Kruger  testnames,sdicts=genTestsSeparateTestvars(testnames,sdicts,final=True)
32144776d8cSScott Kruger
32244776d8cSScott Kruger  # Because I am altering the list, I do this in passes.  Inelegant
32344776d8cSScott Kruger
32444776d8cSScott Kruger  return testnames, sdicts
32544776d8cSScott Kruger
326*080f0011SToby Isaac
327*080f0011SToby Isaacdef testSplit(striptest):
328*080f0011SToby Isaac  """
329*080f0011SToby Isaac  Split up a test into lines, but use a shell parser to detect when newlines are within quotation marks
330*080f0011SToby Isaac  and keep those together
331*080f0011SToby Isaac  """
332*080f0011SToby Isaac  import shlex
333*080f0011SToby Isaac
334*080f0011SToby Isaac  sl = shlex.shlex()
335*080f0011SToby Isaac  sl.whitespace_split = True # only split at whitespace
336*080f0011SToby Isaac  sl.commenters = ''
337*080f0011SToby Isaac  sl.push_source(striptest)
338*080f0011SToby Isaac  last_pos = sl.instream.tell()
339*080f0011SToby Isaac  try:
340*080f0011SToby Isaac    last_token = sl.read_token()
341*080f0011SToby Isaac  except ValueError:
342*080f0011SToby Isaac    print(striptest)
343*080f0011SToby Isaac    raise ValueError
344*080f0011SToby Isaac  last_line = ''
345*080f0011SToby Isaac  while last_token != '':
346*080f0011SToby Isaac    new_pos = sl.instream.tell()
347*080f0011SToby Isaac    block = striptest[last_pos:new_pos]
348*080f0011SToby Isaac    token_start = block.find(last_token)
349*080f0011SToby Isaac    leading = block[0:token_start]
350*080f0011SToby Isaac    trailing = block[(token_start + len(last_token)):]
351*080f0011SToby Isaac    leading_split = leading.split('\n')
352*080f0011SToby Isaac    if len(leading_split) > 1:
353*080f0011SToby Isaac      yield last_line
354*080f0011SToby Isaac      last_line = ''
355*080f0011SToby Isaac    last_line += leading_split[-1]
356*080f0011SToby Isaac    last_line += last_token
357*080f0011SToby Isaac    trailing_split = trailing.split('\n')
358*080f0011SToby Isaac    last_line += trailing_split[0]
359*080f0011SToby Isaac    if len(trailing_split) > 1:
360*080f0011SToby Isaac      yield last_line
361*080f0011SToby Isaac      last_line = ''
362*080f0011SToby Isaac    last_pos = new_pos
363*080f0011SToby Isaac    try:
364*080f0011SToby Isaac      last_token = sl.read_token()
365*080f0011SToby Isaac    except ValueError:
366*080f0011SToby Isaac      print(striptest)
367*080f0011SToby Isaac      raise ValueError
368*080f0011SToby Isaac  yield last_line
369*080f0011SToby Isaac
370*080f0011SToby Isaac
3716cecdbdcSScott Krugerdef parseTest(testStr,srcfile,verbosity):
37229921a8fSScott Kruger  """
37329921a8fSScott Kruger  This parses an individual test
3742be3497aSPatrick Sanan  Our YAML-like language is hierarchial so should use a state machine in the general case,
37553f2a965SBarry Smith  but in practice we only support two levels of test:
37629921a8fSScott Kruger  """
37729921a8fSScott Kruger  basename=os.path.basename(srcfile)
37829921a8fSScott Kruger  # Handle the new at the begininng
37929921a8fSScott Kruger  bn=re.sub("new_","",basename)
38029921a8fSScott Kruger  # This is the default
38129921a8fSScott Kruger  testname="run"+os.path.splitext(bn)[0]
38229921a8fSScott Kruger
38329921a8fSScott Kruger  # Tests that have default everything (so empty effectively)
38478659935SScott Kruger  if len(testStr)==0: return [testname], [{}]
38529921a8fSScott Kruger
38629921a8fSScott Kruger  striptest=_stripIndent(testStr,srcfile)
38729921a8fSScott Kruger
38829921a8fSScott Kruger  # go through and parse
38929921a8fSScott Kruger  subtestnum=0
39029921a8fSScott Kruger  subdict={}
39168a9e459SScott Kruger  comments=[]
39268a9e459SScott Kruger  indentlevel=0
393*080f0011SToby Isaac  for ln in testSplit(striptest):
394c4b80baaSScott Kruger    line=ln.split('#')[0].rstrip()
395cadd188bSScott Kruger    if verbosity>2: print(line)
3960bcc1aabSScott Kruger    comment=("" if len(ln.split("#"))>0 else " ".join(ln.split("#")[1:]).strip())
39768a9e459SScott Kruger    if comment: comments.append(comment)
39829921a8fSScott Kruger    if not line.strip(): continue
39944776d8cSScott Kruger    lsplit=line.split(':')
40044776d8cSScott Kruger    if len(lsplit)==0: raise Exception("Missing : in line: "+line)
40144776d8cSScott Kruger    indentcount=lsplit[0].count(" ")
40244776d8cSScott Kruger    var=lsplit[0].strip()
40340ae0433SScott Kruger    val=line[line.find(':')+1:].strip()
40444776d8cSScott Kruger    if not var in acceptedkeys: raise Exception("Not a defined key: "+var+" from:  "+line)
40529921a8fSScott Kruger    # Start by seeing if we are in a subtest
40629921a8fSScott Kruger    if line.startswith(" "):
407ecc1beb5SScott Kruger      if var in subdict[subtestname]:
408ecc1beb5SScott Kruger        subdict[subtestname][var]+=" "+val
409ecc1beb5SScott Kruger      else:
410c0658d2aSScott Kruger        subdict[subtestname][var]=val
41168a9e459SScott Kruger      if not indentlevel: indentlevel=indentcount
412cadd188bSScott Kruger      #if indentlevel!=indentcount: print("Error in indentation:", ln)
41329921a8fSScott Kruger    # Determine subtest name and make dict
41429921a8fSScott Kruger    elif var=="test":
41529921a8fSScott Kruger      subtestname="test"+str(subtestnum)
41629921a8fSScott Kruger      subdict[subtestname]={}
4175b6bfdb9SJed Brown      if "subtests" not in subdict: subdict["subtests"]=[]
41829921a8fSScott Kruger      subdict["subtests"].append(subtestname)
41929921a8fSScott Kruger      subtestnum=subtestnum+1
42068a9e459SScott Kruger    # The rest are easy
42129921a8fSScott Kruger    else:
42244776d8cSScott Kruger      # For convenience, it is sometimes convenient to list twice
4235b6bfdb9SJed Brown      if var in subdict:
42444776d8cSScott Kruger        if var in appendlist:
42544776d8cSScott Kruger          subdict[var]+=" "+val
42644776d8cSScott Kruger        else:
42744776d8cSScott Kruger          raise Exception(var+" entered twice: "+line)
42844776d8cSScott Kruger      else:
429c0658d2aSScott Kruger        subdict[var]=val
43029921a8fSScott Kruger      if var=="suffix":
43129921a8fSScott Kruger        if len(val)>0:
4324f8a0bffSScott Kruger          testname+="_"+val
43329921a8fSScott Kruger
43468a9e459SScott Kruger  if len(comments): subdict['comments']="\n".join(comments).lstrip("\n")
4354f8a0bffSScott Kruger
4364f8a0bffSScott Kruger  # A test block can create multiple tests.  This does that logic
43744776d8cSScott Kruger  testnames,subdicts=splitTests(testname,subdict)
43844776d8cSScott Kruger  return testnames,subdicts
43929921a8fSScott Kruger
4406cecdbdcSScott Krugerdef parseTests(testStr,srcfile,fileNums,verbosity):
44129921a8fSScott Kruger  """
4422be3497aSPatrick Sanan  Parse the YAML-like string describing tests and return
44329921a8fSScott Kruger  a dictionary with the info in the form of:
44429921a8fSScott Kruger    testDict[test][subtest]
44529921a8fSScott Kruger  """
44629921a8fSScott Kruger
44729921a8fSScott Kruger  testDict={}
44829921a8fSScott Kruger
44929921a8fSScott Kruger  # The first entry should be test: but it might be indented.
45044776d8cSScott Kruger  newTestStr=_stripIndent(testStr,srcfile,entireBlock=True,fileNums=fileNums)
451cadd188bSScott Kruger  if verbosity>2: print(srcfile)
45229921a8fSScott Kruger
453e4653983SScott Kruger  ## Check and see if we have build requirements
454e4653983SScott Kruger  addToRunRequirements=None
455aec507c4SScott Kruger  if "\nbuild:" in newTestStr:
456aec507c4SScott Kruger    testDict['build']={}
457aec507c4SScott Kruger    # The file info is already here and need to append
458aec507c4SScott Kruger    Part1=newTestStr.split("build:")[1]
459aec507c4SScott Kruger    fileInfo=re.split("\ntest(?:set)?:",newTestStr)[0]
460aec507c4SScott Kruger    for bkey in buildkeys:
461aec507c4SScott Kruger      if bkey+":" in fileInfo:
462aec507c4SScott Kruger        testDict['build'][bkey]=fileInfo.split(bkey+":")[1].split("\n")[0].strip()
463aec507c4SScott Kruger        #if verbosity>1: bkey+": "+testDict['build'][bkey]
464e4653983SScott Kruger      # If a runtime requires are put into build, push them down to all run tests
465e4653983SScott Kruger      # At this point, we are working with strings and not lists
466e4653983SScott Kruger      if 'requires' in testDict['build']:
467e4653983SScott Kruger         if 'datafilespath' in testDict['build']['requires']:
468e4653983SScott Kruger             newreqs=re.sub('datafilespath','',testDict['build']['requires'])
469e4653983SScott Kruger             testDict['build']['requires']=newreqs.strip()
470e4653983SScott Kruger             addToRunRequirements='datafilespath'
471e4653983SScott Kruger
472aec507c4SScott Kruger
47329921a8fSScott Kruger  # Now go through each test.  First elem in split is blank
474e53dc769SScott Kruger  for test in re.split("\ntest(?:set)?:",newTestStr)[1:]:
4756cecdbdcSScott Kruger    testnames,subdicts=parseTest(test,srcfile,verbosity)
47644776d8cSScott Kruger    for i in range(len(testnames)):
4775b6bfdb9SJed Brown      if testnames[i] in testDict:
4781acf9037SMatthew G. Knepley        raise RuntimeError("Multiple test names specified: "+testnames[i]+" in file: "+srcfile)
479e4653983SScott Kruger      # Add in build requirements that need to be moved
480e4653983SScott Kruger      if addToRunRequirements:
481e4653983SScott Kruger          if 'requires' in subdicts[i]:
482e4653983SScott Kruger              subdicts[i]['requires']+=addToRunRequirements
483e4653983SScott Kruger          else:
484e4653983SScott Kruger              subdicts[i]['requires']=addToRunRequirements
48544776d8cSScott Kruger      testDict[testnames[i]]=subdicts[i]
48629921a8fSScott Kruger
48729921a8fSScott Kruger  return testDict
48829921a8fSScott Kruger
4896cecdbdcSScott Krugerdef parseTestFile(srcfile,verbosity):
49029921a8fSScott Kruger  """
49129921a8fSScott Kruger  Parse single example files and return dictionary of the form:
49229921a8fSScott Kruger    testDict[srcfile][test][subtest]
49329921a8fSScott Kruger  """
49429921a8fSScott Kruger  debug=False
495cadd188bSScott Kruger  basename=os.path.basename(srcfile)
496cadd188bSScott Kruger  if basename=='makefile': return {}
497cadd188bSScott Kruger
49829921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
49929921a8fSScott Kruger  basedir=os.path.dirname(os.path.realpath(srcfile))
50029921a8fSScott Kruger  os.chdir(basedir)
50129921a8fSScott Kruger
50229921a8fSScott Kruger  testDict={}
503cadd188bSScott Kruger  sh=open(basename,"r"); fileStr=sh.read(); sh.close()
50429921a8fSScott Kruger
50529921a8fSScott Kruger  ## Start with doing the tests
50629921a8fSScott Kruger  #
50729921a8fSScott Kruger  fsplit=fileStr.split("/*TEST\n")[1:]
50844776d8cSScott Kruger  fstart=len(fileStr.split("/*TEST\n")[0].split("\n"))+1
50929921a8fSScott Kruger  # Allow for multiple "/*TEST" blocks even though it really should be
5106f029658SMatthew G. Knepley  # one
51129921a8fSScott Kruger  srcTests=[]
51229921a8fSScott Kruger  for t in fsplit: srcTests.append(t.split("TEST*/")[0])
51329921a8fSScott Kruger  testString=" ".join(srcTests)
51444776d8cSScott Kruger  flen=len(testString.split("\n"))
51544776d8cSScott Kruger  fend=fstart+flen-1
51644776d8cSScott Kruger  fileNums=range(fstart,fend)
5176cecdbdcSScott Kruger  testDict[basename]=parseTests(testString,srcfile,fileNums,verbosity)
518aec507c4SScott Kruger  # Massage dictionary for build requirements
519aec507c4SScott Kruger  if 'build' in testDict[basename]:
520aec507c4SScott Kruger    testDict[basename].update(testDict[basename]['build'])
521aec507c4SScott Kruger    del testDict[basename]['build']
52229921a8fSScott Kruger
52329921a8fSScott Kruger
52429921a8fSScott Kruger  os.chdir(curdir)
52529921a8fSScott Kruger  return testDict
52629921a8fSScott Kruger
5276cecdbdcSScott Krugerdef parseTestDir(directory,verbosity):
52829921a8fSScott Kruger  """
52929921a8fSScott Kruger  Parse single example files and return dictionary of the form:
53029921a8fSScott Kruger    testDict[srcfile][test][subtest]
53129921a8fSScott Kruger  """
53229921a8fSScott Kruger  curdir=os.path.realpath(os.path.curdir)
53329921a8fSScott Kruger  basedir=os.path.realpath(directory)
53429921a8fSScott Kruger  os.chdir(basedir)
53529921a8fSScott Kruger
53629921a8fSScott Kruger  tDict={}
53709a6cbfcSBernhard M. Wiedemann  for test_file in sorted(glob.glob("new_ex*.*")):
5386cecdbdcSScott Kruger    tDict.update(parseTestFile(test_file,verbosity))
53929921a8fSScott Kruger
54029921a8fSScott Kruger  os.chdir(curdir)
54129921a8fSScott Kruger  return tDict
54229921a8fSScott Kruger
54329921a8fSScott Krugerdef printExParseDict(rDict):
54429921a8fSScott Kruger  """
54529921a8fSScott Kruger  This is useful for debugging
54629921a8fSScott Kruger  """
54729921a8fSScott Kruger  indent="   "
54829921a8fSScott Kruger  for sfile in rDict:
549cadd188bSScott Kruger    print(sfile)
550c3d83d22SScott Kruger    sortkeys=list(rDict[sfile].keys())
55144776d8cSScott Kruger    sortkeys.sort()
55244776d8cSScott Kruger    for runex in sortkeys:
55369fa9ab3SScott Kruger      if runex == 'requires':
55469fa9ab3SScott Kruger        print(indent+runex+':'+str(rDict[sfile][runex]))
55569fa9ab3SScott Kruger        continue
556cadd188bSScott Kruger      print(indent+runex)
5575b6bfdb9SJed Brown      if type(rDict[sfile][runex])==bytes:
558cadd188bSScott Kruger        print(indent*2+rDict[sfile][runex])
55929921a8fSScott Kruger      else:
56029921a8fSScott Kruger        for var in rDict[sfile][runex]:
56144776d8cSScott Kruger          if var.startswith("test"): continue
562cadd188bSScott Kruger          print(indent*2+var+": "+str(rDict[sfile][runex][var]))
5635b6bfdb9SJed Brown        if 'subtests' in rDict[sfile][runex]:
56444776d8cSScott Kruger          for var in rDict[sfile][runex]['subtests']:
565cadd188bSScott Kruger            print(indent*2+var)
56629921a8fSScott Kruger            for var2 in rDict[sfile][runex][var]:
567cadd188bSScott Kruger              print(indent*3+var2+": "+str(rDict[sfile][runex][var][var2]))
568cadd188bSScott Kruger      print("\n")
56929921a8fSScott Kruger  return
57029921a8fSScott Kruger
57129921a8fSScott Krugerdef main(directory='',test_file='',verbosity=0):
57229921a8fSScott Kruger
57329921a8fSScott Kruger    if directory:
5746cecdbdcSScott Kruger      tDict=parseTestDir(directory,verbosity)
57529921a8fSScott Kruger    else:
5766cecdbdcSScott Kruger      tDict=parseTestFile(test_file,verbosity)
57729921a8fSScott Kruger    if verbosity>0: printExParseDict(tDict)
57829921a8fSScott Kruger
57929921a8fSScott Kruger    return
58029921a8fSScott Kruger
58129921a8fSScott Krugerif __name__ == '__main__':
58229921a8fSScott Kruger    import optparse
58329921a8fSScott Kruger    parser = optparse.OptionParser()
58429921a8fSScott Kruger    parser.add_option('-d', '--directory', dest='directory',
58529921a8fSScott Kruger                      default="", help='Directory containing files to parse')
58629921a8fSScott Kruger    parser.add_option('-t', '--test_file', dest='test_file',
58729921a8fSScott Kruger                      default="", help='Test file, e.g., ex1.c, to parse')
58829921a8fSScott Kruger    parser.add_option('-v', '--verbosity', dest='verbosity',
58929921a8fSScott Kruger                      help='Verbosity of output by level: 1, 2, or 3', default=0)
59029921a8fSScott Kruger    opts, extra_args = parser.parse_args()
59129921a8fSScott Kruger
59229921a8fSScott Kruger    if extra_args:
59329921a8fSScott Kruger        import sys
59429921a8fSScott Kruger        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
59529921a8fSScott Kruger        exit(1)
59629921a8fSScott Kruger    if not opts.test_file and not opts.directory:
597cadd188bSScott Kruger      print("test file or directory is required")
59829921a8fSScott Kruger      parser.print_usage()
59929921a8fSScott Kruger      sys.exit()
60029921a8fSScott Kruger
60129921a8fSScott Kruger    # Need verbosity to be an integer
60229921a8fSScott Kruger    try:
60329921a8fSScott Kruger      verbosity=int(opts.verbosity)
60429921a8fSScott Kruger    except:
60529921a8fSScott Kruger      raise Exception("Error: Verbosity must be integer")
60629921a8fSScott Kruger
60729921a8fSScott Kruger    main(directory=opts.directory,test_file=opts.test_file,verbosity=verbosity)
608