xref: /petsc/config/gmakegentest.py (revision 5a856986583887c326abe5dfd149e8184a29cd80)
1#!/usr/bin/env python
2
3from __future__ import print_function
4import os,shutil, string, re
5import sys
6import logging, time
7import types
8sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
9from collections import defaultdict
10from gmakegen import *
11
12import inspect
13thisscriptdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
14sys.path.insert(0,thisscriptdir)
15import testparse
16import example_template
17
18
19"""
20
21There are 2 modes of running tests: Normal builds and run from prefix of
22install.  They affect where to find things:
23
24
25Case 1.  Normal builds:
26
27     +---------------------+----------------------------------+
28     | PETSC_DIR           | <git dir>                        |
29     +---------------------+----------------------------------+
30     | PETSC_ARCH          | arch-foo                         |
31     +---------------------+----------------------------------+
32     | PETSC_LIBDIR        | PETSC_DIR/PETSC_ARCH/lib         |
33     +---------------------+----------------------------------+
34     | PETSC_EXAMPLESDIR   | PETSC_DIR/src                    |
35     +---------------------+----------------------------------+
36     | PETSC_TESTDIR       | PETSC_DIR/PETSC_ARCH/tests       |
37     +---------------------+----------------------------------+
38     | PETSC_GMAKEFILETEST | PETSC_DIR/gmakefile.test         |
39     +---------------------+----------------------------------+
40     | PETSC_GMAKEGENTEST  | PETSC_DIR/config/gmakegentest.py |
41     +---------------------+----------------------------------+
42
43
44Case 2.  From install dir:
45
46     +---------------------+-------------------------------------------------------+
47     | PETSC_DIR           | <prefix dir>                                          |
48     +---------------------+-------------------------------------------------------+
49     | PETSC_ARCH          | ''                                                    |
50     +---------------------+-------------------------------------------------------+
51     | PETSC_LIBDIR        | PETSC_DIR/PETSC_ARCH/lib                              |
52     +---------------------+-------------------------------------------------------+
53     | PETSC_EXAMPLESDIR   | PETSC_DIR/share/petsc/examples/src                    |
54     +---------------------+-------------------------------------------------------+
55     | PETSC_TESTDIR       | PETSC_DIR/PETSC_ARCH/tests                            |
56     +---------------------+-------------------------------------------------------+
57     | PETSC_GMAKEFILETEST | PETSC_DIR/share/petsc/examples/gmakefile.test         |
58     +---------------------+-------------------------------------------------------+
59     | PETSC_GMAKEGENTEST  | PETSC_DIR/share/petsc/examples/config/gmakegentest.py |
60     +---------------------+-------------------------------------------------------+
61
62"""
63
64def install_files(source, destdir):
65  """Install file or directory 'source' to 'destdir'.  Does not preserve
66  mode (permissions).
67  """
68  if not os.path.isdir(destdir):
69    os.makedirs(destdir)
70  if os.path.isdir(source):
71    for name in os.listdir(source):
72      install_files(os.path.join(source, name), os.path.join(destdir, os.path.basename(source)))
73  else:
74    shutil.copyfile(source, os.path.join(destdir, os.path.basename(source)))
75
76class generateExamples(Petsc):
77  """
78    gmakegen.py has basic structure for finding the files, writing out
79      the dependencies, etc.
80  """
81  def __init__(self,petsc_dir=None, petsc_arch=None, pkg_dir=None, pkg_arch=None, pkg_name=None, pkg_pkgs=None, testdir='tests', verbose=False, single_ex=False, srcdir=None):
82    super(generateExamples, self).__init__(petsc_dir=petsc_dir, petsc_arch=petsc_arch, pkg_dir=pkg_dir, pkg_arch=pkg_arch, pkg_name=pkg_name, pkg_pkgs=pkg_pkgs, verbose=verbose)
83
84    self.single_ex=single_ex
85    self.srcdir=srcdir
86
87    # Set locations to handle movement
88    self.inInstallDir=self.getInInstallDir(thisscriptdir)
89
90    if self.inInstallDir:
91      # Case 2 discussed above
92      # set PETSC_ARCH to install directory to allow script to work in both
93      dirlist=thisscriptdir.split(os.path.sep)
94      installdir=os.path.sep.join(dirlist[0:len(dirlist)-4])
95      self.arch_dir=installdir
96      if self.srcdir is None:
97        self.srcdir=os.path.join(os.path.dirname(thisscriptdir),'src')
98    else:
99      if petsc_arch == '':
100        raise RuntimeError('PETSC_ARCH must be set when running from build directory')
101      # Case 1 discussed above
102      self.arch_dir=os.path.join(self.petsc_dir,self.petsc_arch)
103      if self.srcdir is None:
104        self.srcdir=os.path.join(self.petsc_dir,'src')
105
106    self.testroot_dir=os.path.abspath(testdir)
107
108    self.ptNaming=True
109    self.verbose=verbose
110    # Whether to write out a useful debugging
111    self.summarize=True if verbose else False
112
113    # For help in setting the requirements
114    self.precision_types="single double __float128 int32".split()
115    self.integer_types="int32 int64 long32 long64".split()
116    self.languages="fortran cuda cxx cpp".split()    # Always requires C so do not list
117
118    # Things that are not test
119    self.buildkeys=testparse.buildkeys
120
121    # Adding a dictionary for storing sources, objects, and tests
122    # to make building the dependency tree easier
123    self.sources={}
124    self.objects={}
125    self.tests={}
126    for pkg in self.pkg_pkgs:
127      self.sources[pkg]={}
128      self.objects[pkg]=[]
129      self.tests[pkg]={}
130      for lang in LANGS:
131        self.sources[pkg][lang]={}
132        self.sources[pkg][lang]['srcs']=[]
133        self.tests[pkg][lang]={}
134
135    if not os.path.isdir(self.testroot_dir): os.makedirs(self.testroot_dir)
136
137    self.indent="   "
138    if self.verbose: print('Finishing the constructor')
139    return
140
141  def srcrelpath(self,rdir):
142    """
143    Get relative path to source directory
144    """
145    return os.path.relpath(rdir,self.srcdir)
146
147  def getInInstallDir(self,thisscriptdir):
148    """
149    When petsc is installed then this file in installed in:
150         <PREFIX>/share/petsc/examples/config/gmakegentest.py
151    otherwise the path is:
152         <PETSC_DIR>/config/gmakegentest.py
153    We use this difference to determine if we are in installdir
154    """
155    dirlist=thisscriptdir.split(os.path.sep)
156    if len(dirlist)>4:
157      lastfour=os.path.sep.join(dirlist[len(dirlist)-4:])
158      if lastfour==os.path.join('share','petsc','examples','config'):
159        return True
160      else:
161        return False
162    else:
163      return False
164
165  def nameSpace(self,srcfile,srcdir):
166    """
167    Because the scripts have a non-unique naming, the pretty-printing
168    needs to convey the srcdir and srcfile.  There are two ways of doing this.
169    """
170    if self.ptNaming:
171      if srcfile.startswith('run'): srcfile=re.sub('^run','',srcfile)
172      cdir=srcdir
173      prefix=cdir.replace('/examples/','_').replace("/","_")+"-"
174      nameString=prefix+srcfile
175    else:
176      #nameString=srcdir+": "+srcfile
177      nameString=srcfile
178    return nameString
179
180  def getLanguage(self,srcfile):
181    """
182    Based on the source, determine associated language as found in gmakegen.LANGS
183    Can we just return srcext[1:\] now?
184    """
185    langReq=None
186    srcext=os.path.splitext(srcfile)[-1]
187    if srcext in ".F90".split(): langReq="F90"
188    if srcext in ".F".split(): langReq="F"
189    if srcext in ".cxx".split(): langReq="cxx"
190    if srcext in ".cpp".split(): langReq="cpp"
191    if srcext == ".cu": langReq="cu"
192    if srcext == ".c": langReq="c"
193    #if not langReq: print("ERROR: ", srcext, srcfile)
194    return langReq
195
196  def _getLoopVars(self,inDict,testname, isSubtest=False):
197    """
198    Given: 'args: -bs {{1 2 3 4 5}} -pc_type {{cholesky sor}} -ksp_monitor'
199    Return:
200      inDict['args']: -ksp_monitor
201      inDict['subargs']: -bs ${bs} -pc_type ${pc_type}
202      loopVars['subargs']['varlist']=['bs' 'pc_type']   # Don't worry about OrderedDict
203      loopVars['subargs']['bs']=[["bs"],["1 2 3 4 5"]]
204      loopVars['subargs']['pc_type']=[["pc_type"],["cholesky sor"]]
205    subst should be passed in instead of inDict
206    """
207    loopVars={}; newargs=[]
208    lsuffix='_'
209    argregex = re.compile(' (?=-[a-zA-Z])')
210    from testparse import parseLoopArgs
211    for key in inDict:
212      if key in ('SKIP', 'regexes'):
213        continue
214      akey=('subargs' if key=='args' else key)  # what to assign
215      if akey not in inDict: inDict[akey]=''
216      if akey == 'nsize' and not inDict['nsize'].startswith('{{'):
217        # Always generate a loop over nsize, even if there is only one value
218        inDict['nsize'] = '{{' + inDict['nsize'] + '}}'
219      keystr = str(inDict[key])
220      varlist = []
221      for varset in argregex.split(keystr):
222        if not varset.strip(): continue
223        if '{{' in varset:
224          keyvar,lvars,ftype=parseLoopArgs(varset)
225          if akey not in loopVars: loopVars[akey]={}
226          varlist.append(keyvar)
227          loopVars[akey][keyvar]=[keyvar,lvars]
228          if akey=='nsize':
229            if len(lvars.split()) > 1:
230              lsuffix += akey +'-${' + keyvar + '}'
231          else:
232            inDict[akey] += ' -'+keyvar+' ${' + keyvar + '}'
233            lsuffix+=keyvar+'-${' + keyvar + '}_'
234        else:
235          if key=='args':
236            newargs.append(varset.strip())
237        if varlist:
238          loopVars[akey]['varlist']=varlist
239
240    # For subtests, args are always substituted in (not top level)
241    if isSubtest:
242      inDict['subargs'] += " ".join(newargs)
243      inDict['args']=''
244      if 'label_suffix' in inDict:
245        inDict['label_suffix']+=lsuffix.rstrip('_')
246      else:
247        inDict['label_suffix']=lsuffix.rstrip('_')
248    else:
249      if loopVars:
250        inDict['args'] = ' '.join(newargs)
251        inDict['label_suffix']=lsuffix.rstrip('_')
252    return loopVars
253
254  def getArgLabel(self,testDict):
255    """
256    In all of the arguments in the test dictionary, create a simple
257    string for searching within the makefile system.  For simplicity in
258    search, remove "-", for strings, etc.
259    Also, concatenate the arg commands
260    For now, ignore nsize -- seems hard to search for anyway
261    """
262    # Collect all of the args associated with a test
263    argStr=("" if 'args' not in testDict else testDict['args'])
264    if 'subtests' in testDict:
265      for stest in testDict["subtests"]:
266         sd=testDict[stest]
267         argStr=argStr+("" if 'args' not in sd else sd['args'])
268
269    # Now go through and cleanup
270    argStr=re.sub('{{(.*?)}}',"",argStr)
271    argStr=re.sub('-'," ",argStr)
272    for digit in string.digits: argStr=re.sub(digit," ",argStr)
273    argStr=re.sub("\.","",argStr)
274    argStr=re.sub(",","",argStr)
275    argStr=re.sub('\+',' ',argStr)
276    argStr=re.sub(' +',' ',argStr)  # Remove repeated white space
277    return argStr.strip()
278
279  def addToSources(self,exfile,rpath,srcDict):
280    """
281      Put into data structure that allows easy generation of makefile
282    """
283    pkg=rpath.split(os.path.sep)[0]
284    relpfile=os.path.join(rpath,exfile)
285    lang=self.getLanguage(exfile)
286    if not lang: return
287    if pkg not in self.sources: return
288    self.sources[pkg][lang]['srcs'].append(relpfile)
289    self.sources[pkg][lang][relpfile] = []
290    if 'depends' in srcDict:
291      depSrcList=srcDict['depends'].split()
292      for depSrc in depSrcList:
293        depObj=os.path.splitext(depSrc)[0]+".o"
294        self.sources[pkg][lang][relpfile].append(os.path.join(rpath,depObj))
295
296    # In gmakefile, ${TESTDIR} var specifies the object compilation
297    testsdir=rpath+"/"
298    objfile="${TESTDIR}/"+testsdir+os.path.splitext(exfile)[0]+".o"
299    self.objects[pkg].append(objfile)
300    return
301
302  def addToTests(self,test,rpath,exfile,execname,testDict):
303    """
304      Put into data structure that allows easy generation of makefile
305      Organized by languages to allow testing of languages
306    """
307    pkg=rpath.split("/")[0]
308    nmtest=os.path.join(rpath,test)
309    lang=self.getLanguage(exfile)
310    if not lang: return
311    if pkg not in self.tests: return
312    self.tests[pkg][lang][nmtest]={}
313    self.tests[pkg][lang][nmtest]['exfile']=os.path.join(rpath,exfile)
314    self.tests[pkg][lang][nmtest]['exec']=execname
315    self.tests[pkg][lang][nmtest]['argLabel']=self.getArgLabel(testDict)
316    return
317
318  def getExecname(self,exfile,rpath):
319    """
320      Generate bash script using template found next to this file.
321      This file is read in at constructor time to avoid file I/O
322    """
323    if self.single_ex:
324      execname=rpath.split("/")[1]+"-ex"
325    else:
326      execname=os.path.splitext(exfile)[0]
327    return execname
328
329  def getSubstVars(self,testDict,rpath,testname):
330    """
331      Create a dictionary with all of the variables that get substituted
332      into the template commands found in example_template.py
333    """
334    subst={}
335
336    # Handle defaults of testparse.acceptedkeys (e.g., ignores subtests)
337    if 'nsize' not in testDict: testDict['nsize'] = '1'
338    if 'timeoutfactor' not in testDict: testDict['timeoutfactor']="1"
339    for ak in testparse.acceptedkeys:
340      if ak=='test': continue
341      subst[ak]=(testDict[ak] if ak in testDict else '')
342
343    # Now do other variables
344    subst['execname']=testDict['execname']
345    if 'filter' in testDict:
346      subst['filter']="'"+testDict['filter']+"'"   # Quotes are tricky - overwrite
347
348    # Others
349    subst['subargs']=''  # Default.  For variables override
350    subst['srcdir']=os.path.join(self.srcdir, rpath)
351    subst['label_suffix']=''
352    subst['comments']="\n#".join(subst['comments'].split("\n"))
353    if subst['comments']: subst['comments']="#"+subst['comments']
354    subst['exec']="../"+subst['execname']
355    subst['testroot']=self.testroot_dir
356    subst['testname']=testname
357    dp = self.conf.get('DATAFILESPATH','')
358    subst['datafilespath_line'] = 'DATAFILESPATH=${DATAFILESPATH:-"'+dp+'"}'
359
360    # This is used to label some matrices
361    subst['petsc_index_size']=str(self.conf['PETSC_INDEX_SIZE'])
362    subst['petsc_scalar_size']=str(self.conf['PETSC_SCALAR_SIZE'])
363
364    #Conf vars
365    if self.petsc_arch.find('valgrind')>=0:
366      subst['mpiexec']='petsc_mpiexec_valgrind ' + self.conf['MPIEXEC']
367    else:
368      subst['mpiexec']=self.conf['MPIEXEC']
369    subst['pkg_name']=self.pkg_name
370    subst['pkg_dir']=self.pkg_dir
371    subst['pkg_arch']=self.petsc_arch
372    subst['CONFIG_DIR']=thisscriptdir
373    subst['PETSC_BINDIR']=os.path.join(self.petsc_dir,'lib','petsc','bin')
374    subst['diff']=self.conf['DIFF']
375    subst['rm']=self.conf['RM']
376    subst['grep']=self.conf['GREP']
377    subst['petsc_lib_dir']=self.conf['PETSC_LIB_DIR']
378    subst['wpetsc_dir']=self.conf['wPETSC_DIR']
379
380    # Output file is special because of subtests override
381    defroot=(re.sub("run","",testname) if testname.startswith("run") else testname)
382    if not "_" in defroot: defroot=defroot+"_1"
383    subst['defroot']=defroot
384    subst['label']=self.nameSpace(defroot,self.srcrelpath(subst['srcdir']))
385    subst['redirect_file']=defroot+".tmp"
386    if 'output_file' not in testDict:
387      subst['output_file']="output/"+defroot+".out"
388    # Add in the full path here.
389    subst['output_file']=os.path.join(subst['srcdir'],subst['output_file'])
390    if not os.path.isfile(os.path.join(self.petsc_dir,subst['output_file'])):
391      if not subst['TODO']:
392        print("Warning: "+subst['output_file']+" not found.")
393    # Worry about alt files here -- see
394    #   src/snes/examples/tutorials/output/ex22*.out
395    altlist=[subst['output_file']]
396    basefile,ext = os.path.splitext(subst['output_file'])
397    for i in range(1,9):
398      altroot=basefile+"_alt"
399      if i > 1: altroot=altroot+"_"+str(i)
400      af=altroot+".out"
401      srcaf=os.path.join(subst['srcdir'],af)
402      fullaf=os.path.join(self.petsc_dir,srcaf)
403      if os.path.isfile(fullaf): altlist.append(srcaf)
404    if len(altlist)>1: subst['altfiles']=altlist
405    #if len(altlist)>1: print("Found alt files: ",altlist)
406
407    subst['regexes']={}
408    for subkey in subst:
409      if subkey=='regexes': continue
410      if not isinstance(subst[subkey],str): continue
411      patt="@"+subkey.upper()+"@"
412      subst['regexes'][subkey]=re.compile(patt)
413
414    return subst
415
416  def _substVars(self,subst,origStr):
417    """
418      Substitute variables
419    """
420    Str=origStr
421    for subkey in subst:
422      if subkey=='regexes': continue
423      if not isinstance(subst[subkey],str): continue
424      if subkey.upper() not in Str: continue
425      Str=subst['regexes'][subkey].sub(subst[subkey],Str)
426    return Str
427
428  def getCmds(self,subst,i):
429    """
430      Generate bash script using template found next to this file.
431      This file is read in at constructor time to avoid file I/O
432    """
433    nindnt=i # the start and has to be consistent with below
434    cmdindnt=self.indent*nindnt
435    cmdLines=""
436
437    # MPI is the default -- but we have a few odd commands
438    if not subst['command']:
439      cmd=cmdindnt+self._substVars(subst,example_template.mpitest)
440    else:
441      cmd=cmdindnt+self._substVars(subst,example_template.commandtest)
442    cmdLines+=cmd+"\n"+cmdindnt+"res=$?\n\n"
443
444    cmdLines+=cmdindnt+'if test $res = 0; then\n'
445    diffindnt=self.indent*(nindnt+1)
446    if not subst['filter_output']:
447      if 'altfiles' not in subst:
448        cmd=diffindnt+self._substVars(subst,example_template.difftest)
449      else:
450        # Have to do it by hand a bit because of variable number of alt files
451        rf=subst['redirect_file']
452        cmd=diffindnt+example_template.difftest.split('@')[0]
453        for i in range(len(subst['altfiles'])):
454          af=subst['altfiles'][i]
455          cmd+=af+' '+rf
456          if i!=len(subst['altfiles'])-1:
457            cmd+=' > diff-${testname}-'+str(i)+'.out 2> diff-${testname}-'+str(i)+'.out'
458            cmd+=' || ${diff_exe} '
459          else:
460            cmd+='" diff-${testname}.out diff-${testname}.out diff-${label}'
461            cmd+=subst['label_suffix']+' ""'  # Quotes are painful
462    else:
463      cmd=diffindnt+self._substVars(subst,example_template.filterdifftest)
464    cmdLines+=cmd+"\n"
465    cmdLines+=cmdindnt+'else\n'
466    cmdLines+=diffindnt+'printf "ok ${label} # SKIP Command failed so no diff\\n"\n'
467    cmdLines+=cmdindnt+'fi\n'
468    return cmdLines
469
470  def _writeTodoSkip(self,fh,tors,reasons,footer):
471    """
472    Write out the TODO and SKIP lines in the file
473    The TODO or SKIP variable, tors, should be lower case
474    """
475    TORS=tors.upper()
476    template=eval("example_template."+tors+"line")
477    tsStr=re.sub("@"+TORS+"COMMENT@",', '.join(reasons),template)
478    tab = ''
479    if reasons:
480      fh.write('if ! $force; then\n')
481      tab = tab + '    '
482    if reasons == ["Requires DATAFILESPATH"]:
483      # The only reason not to run is DATAFILESPATH, which we check at run-time
484      fh.write(tab + 'if test -z "${DATAFILESPATH}"; then\n')
485      tab = tab + '    '
486    if reasons:
487      fh.write(tab+tsStr+"\n" + tab + "total=1; "+tors+"=1\n")
488      fh.write(tab+footer+"\n")
489      fh.write(tab+"exit\n")
490    if reasons == ["Requires DATAFILESPATH"]:
491      fh.write('    fi\n')
492    if reasons:
493      fh.write('fi\n')
494    fh.write('\n\n')
495    return
496
497  def getLoopVarsHead(self,loopVars,i,usedVars={}):
498    """
499    Generate a nicely indented string with the format loops
500    Here is what the data structure looks like
501      loopVars['subargs']['varlist']=['bs' 'pc_type']   # Don't worry about OrderedDict
502      loopVars['subargs']['bs']=["i","1 2 3 4 5"]
503      loopVars['subargs']['pc_type']=["j","cholesky sor"]
504    """
505    outstr=''; indnt=self.indent
506
507    for key in loopVars:
508      if key in usedVars: continue         # Do not duplicate setting vars
509      for var in loopVars[key]['varlist']:
510        varval=loopVars[key][var]
511        outstr += "{0}_in=${{{0}:-{1}}}\n".format(*varval)
512    outstr += "\n\n"
513
514    for key in loopVars:
515      for var in loopVars[key]['varlist']:
516        varval=loopVars[key][var]
517        outstr += indnt * i + "for {0} in ${{{0}_in}}; do\n".format(*varval)
518        i = i + 1
519    return (outstr,i)
520
521  def getLoopVarsFoot(self,loopVars,i):
522    outstr=''; indnt=self.indent
523    for key in loopVars:
524      for var in loopVars[key]['varlist']:
525        i = i - 1
526        outstr += indnt * i + "done\n"
527    return (outstr,i)
528
529  def genRunScript(self,testname,root,isRun,srcDict):
530    """
531      Generate bash script using template found next to this file.
532      This file is read in at constructor time to avoid file I/O
533    """
534    # runscript_dir directory has to be consistent with gmakefile
535    testDict=srcDict[testname]
536    rpath=self.srcrelpath(root)
537    runscript_dir=os.path.join(self.testroot_dir,rpath)
538    if not os.path.isdir(runscript_dir): os.makedirs(runscript_dir)
539    fh=open(os.path.join(runscript_dir,testname+".sh"),"w")
540
541    # Get variables to go into shell scripts.  last time testDict used
542    subst=self.getSubstVars(testDict,rpath,testname)
543    loopVars = self._getLoopVars(subst,testname)  # Alters subst as well
544    if 'subtests' in testDict:
545      # The subtests inherit inDict, so we don't need top-level loops.
546      loopVars = {}
547
548    #Handle runfiles
549    for lfile in subst.get('localrunfiles','').split():
550      install_files(os.path.join(root, lfile),
551                    os.path.join(runscript_dir, os.path.dirname(lfile)))
552    # Check subtests for local runfiles
553    for stest in subst.get("subtests",[]):
554      for lfile in testDict[stest].get('localrunfiles','').split():
555        install_files(os.path.join(root, lfile),
556                      os.path.join(runscript_dir, os.path.dirname(lfile)))
557
558    # Now substitute the key variables into the header and footer
559    header=self._substVars(subst,example_template.header)
560    # The header is done twice to enable @...@ in header
561    header=self._substVars(subst,header)
562    footer=re.sub('@TESTROOT@',subst['testroot'],example_template.footer)
563
564    # Start writing the file
565    fh.write(header+"\n")
566
567    # If there is a TODO or a SKIP then we do it before writing out the
568    # rest of the command (which is useful for working on the test)
569    # SKIP and TODO can be for the source file or for the runs
570    self._writeTodoSkip(fh,'todo',[s for s in [srcDict.get('TODO',''), testDict.get('TODO','')] if s],footer)
571    self._writeTodoSkip(fh,'skip',srcDict.get('SKIP',[]) + testDict.get('SKIP',[]),footer)
572
573    j=0  # for indentation
574
575    if loopVars:
576      (loopHead,j) = self.getLoopVarsHead(loopVars,j)
577      if (loopHead): fh.write(loopHead+"\n")
578
579    # Subtests are special
580    allLoopVars=list(loopVars.keys())
581    if 'subtests' in testDict:
582      substP=subst   # Subtests can inherit args but be careful
583      k=0  # for label suffixes
584      for stest in testDict["subtests"]:
585        subst=substP.copy()
586        subst.update(testDict[stest])
587        subst['label_suffix']='-'+string.ascii_letters[k]; k+=1
588        sLoopVars = self._getLoopVars(subst,testname,isSubtest=True)
589        if sLoopVars:
590          (sLoopHead,j) = self.getLoopVarsHead(sLoopVars,j,allLoopVars)
591          allLoopVars+=list(sLoopVars.keys())
592          fh.write(sLoopHead+"\n")
593        fh.write(self.getCmds(subst,j)+"\n")
594        if sLoopVars:
595          (sLoopFoot,j) = self.getLoopVarsFoot(sLoopVars,j)
596          fh.write(sLoopFoot+"\n")
597    else:
598      fh.write(self.getCmds(subst,j)+"\n")
599
600    if loopVars:
601      (loopFoot,j) = self.getLoopVarsFoot(loopVars,j)
602      fh.write(loopFoot+"\n")
603
604    fh.write(footer+"\n")
605    os.chmod(os.path.join(runscript_dir,testname+".sh"),0o755)
606    #if '10_9' in testname: sys.exit()
607    return
608
609  def  genScriptsAndInfo(self,exfile,root,srcDict):
610    """
611    Generate scripts from the source file, determine if built, etc.
612     For every test in the exfile with info in the srcDict:
613      1. Determine if it needs to be run for this arch
614      2. Generate the script
615      3. Generate the data needed to write out the makefile in a
616         convenient way
617     All tests are *always* run, but some may be SKIP'd per the TAP standard
618    """
619    debug=False
620    rpath=self.srcrelpath(root)
621    execname=self.getExecname(exfile,rpath)
622    isBuilt=self._isBuilt(exfile,srcDict)
623    for test in srcDict:
624      if test in self.buildkeys: continue
625      if debug: print(self.nameSpace(exfile,root), test)
626      srcDict[test]['execname']=execname   # Convenience in generating scripts
627      isRun=self._isRun(srcDict[test])
628      self.genRunScript(test,root,isRun,srcDict)
629      srcDict[test]['isrun']=isRun
630      self.addToTests(test,rpath,exfile,execname,srcDict[test])
631
632    # This adds to datastructure for building deps
633    if isBuilt: self.addToSources(exfile,rpath,srcDict)
634    return
635
636  def _isBuilt(self,exfile,srcDict):
637    """
638    Determine if this file should be built.
639    """
640    # Get the language based on file extension
641    srcDict['SKIP'] = []
642    lang=self.getLanguage(exfile)
643    if (lang=="F" or lang=="F90"):
644      if not self.have_fortran:
645        srcDict["SKIP"].append("Fortran required for this test")
646      elif lang=="F90" and 'PETSC_USING_F90FREEFORM' not in self.conf:
647        srcDict["SKIP"].append("Fortran f90freeform required for this test")
648    if lang=="cu" and 'PETSC_HAVE_CUDA' not in self.conf:
649      srcDict["SKIP"].append("CUDA required for this test")
650    if lang=="cxx" and 'PETSC_HAVE_CXX' not in self.conf:
651      srcDict["SKIP"].append("C++ required for this test")
652    if lang=="cpp" and 'PETSC_HAVE_CXX' not in self.conf:
653      srcDict["SKIP"].append("C++ required for this test")
654
655    # Deprecated source files
656    if srcDict.get("TODO"):
657      return False
658
659    # isRun can work with srcDict to handle the requires
660    if "requires" in srcDict:
661      if srcDict["requires"]:
662        return self._isRun(srcDict)
663
664    return srcDict['SKIP'] == []
665
666
667  def _isRun(self,testDict, debug=False):
668    """
669    Based on the requirements listed in the src file and the petscconf.h
670    info, determine whether this test should be run or not.
671    """
672    indent="  "
673
674    if 'SKIP' not in testDict:
675      testDict['SKIP'] = []
676    # MPI requirements
677    if 'MPI_IS_MPIUNI' in self.conf:
678      if testDict.get('nsize', '1') != '1':
679        testDict['SKIP'].append("Parallel test with serial build")
680
681      # The requirements for the test are the sum of all the run subtests
682      if 'subtests' in testDict:
683        if 'requires' not in testDict: testDict['requires']=""
684        for stest in testDict['subtests']:
685          if 'requires' in testDict[stest]:
686            testDict['requires']+=" "+testDict[stest]['requires']
687          if testDict.get('nsize', '1') != '1':
688            testDict['SKIP'].append("Parallel test with serial build")
689            break
690
691    # Now go through all requirements
692    if 'requires' in testDict:
693      for requirement in testDict['requires'].split():
694        requirement=requirement.strip()
695        if not requirement: continue
696        if debug: print(indent+"Requirement: ", requirement)
697        isNull=False
698        if requirement.startswith("!"):
699          requirement=requirement[1:]; isNull=True
700        # Precision requirement for reals
701        if requirement in self.precision_types:
702          if self.conf['PETSC_PRECISION']==requirement:
703            if isNull:
704              testDict['SKIP'].append("not "+requirement+" required")
705              continue
706            continue  # Success
707          elif not isNull:
708            testDict['SKIP'].append(requirement+" required")
709            continue
710        # Precision requirement for ints
711        if requirement in self.integer_types:
712          if requirement=="int32":
713            if self.conf['PETSC_SIZEOF_INT']==4:
714              if isNull:
715                testDict['SKIP'].append("not int32 required")
716                continue
717              continue  # Success
718            elif not isNull:
719              testDict['SKIP'].append("int32 required")
720              continue
721          if requirement=="int64":
722            if self.conf['PETSC_SIZEOF_INT']==8:
723              if isNull:
724                testDict['SKIP'].append("NOT int64 required")
725                continue
726              continue  # Success
727            elif not isNull:
728              testDict['SKIP'].append("int64 required")
729              continue
730          if requirement.startswith("long"):
731            reqsize = int(requirement[4:])//8
732            longsize = int(self.conf['PETSC_SIZEOF_LONG'].strip())
733            if longsize==reqsize:
734              if isNull:
735                testDict['SKIP'].append("not %s required" % requirement)
736                continue
737              continue  # Success
738            elif not isNull:
739              testDict['SKIP'].append("%s required" % requirement)
740              continue
741        # Datafilespath
742        if requirement=="datafilespath" and not isNull:
743          testDict['SKIP'].append("Requires DATAFILESPATH")
744          continue
745        # Defines -- not sure I have comments matching
746        if "define(" in requirement.lower():
747          reqdef=requirement.split("(")[1].split(")")[0]
748          if reqdef in self.conf:
749            if isNull:
750              testDict['SKIP'].append("Null requirement not met: "+requirement)
751              continue
752            continue  # Success
753          elif not isNull:
754            testDict['SKIP'].append("Required: "+requirement)
755            continue
756
757        # Rest should be packages that we can just get from conf
758        if requirement == "complex":
759          petscconfvar="PETSC_USE_COMPLEX"
760          pkgconfvar="PETSC_USE_COMPLEX"
761        else:
762          petscconfvar="PETSC_HAVE_"+requirement.upper()
763          pkgconfvar=self.pkg_name.upper()+'_HAVE_'+requirement.upper()
764        petsccv = self.conf.get(petscconfvar)
765        pkgcv = self.conf.get(pkgconfvar)
766
767        if petsccv or pkgcv:
768          if isNull:
769            if petsccv:
770              testDict['SKIP'].append("Not "+petscconfvar+" requirement not met")
771              continue
772            else:
773              testDict['SKIP'].append("Not "+pkgconfvar+" requirement not met")
774              continue
775          continue  # Success
776        elif not isNull:
777          if not petsccv and not pkgcv:
778            if debug: print("requirement not found: ", requirement)
779            if self.pkg_name == 'petsc':
780              testDict['SKIP'].append(petscconfvar+" requirement not met")
781            else:
782              testDict['SKIP'].append(petscconfvar+" or "+pkgconfvar+" requirement not met")
783            continue
784    return testDict['SKIP'] == []
785
786  def genPetscTests_summarize(self,dataDict):
787    """
788    Required method to state what happened
789    """
790    if not self.summarize: return
791    indent="   "
792    fhname=os.path.join(self.testroot_dir,'GenPetscTests_summarize.txt')
793    fh=open(fhname,"w")
794    for root in dataDict:
795      relroot=self.srcrelpath(root)
796      pkg=relroot.split("/")[1]
797      fh.write(relroot+"\n")
798      allSrcs=[]
799      for lang in LANGS: allSrcs+=self.sources[pkg][lang]['srcs']
800      for exfile in dataDict[root]:
801        # Basic  information
802        rfile=os.path.join(relroot,exfile)
803        builtStatus=(" Is built" if rfile in allSrcs else " Is NOT built")
804        fh.write(indent+exfile+indent*4+builtStatus+"\n")
805
806        for test in dataDict[root][exfile]:
807          if test in self.buildkeys: continue
808          line=indent*2+test
809          fh.write(line+"\n")
810          # Looks nice to have the keys in order
811          #for key in dataDict[root][exfile][test]:
812          for key in "isrun abstracted nsize args requires script".split():
813            if key not in dataDict[root][exfile][test]: continue
814            line=indent*3+key+": "+str(dataDict[root][exfile][test][key])
815            fh.write(line+"\n")
816          fh.write("\n")
817        fh.write("\n")
818      fh.write("\n")
819    #fh.write("\nClass Sources\n"+str(self.sources)+"\n")
820    #fh.write("\nClass Tests\n"+str(self.tests)+"\n")
821    fh.close()
822    return
823
824  def genPetscTests(self,root,dirs,files,dataDict):
825    """
826     Go through and parse the source files in the directory to generate
827     the examples based on the metadata contained in the source files
828    """
829    debug=False
830    # Use examplesAnalyze to get what the makefles think are sources
831    #self.examplesAnalyze(root,dirs,files,anlzDict)
832
833    dataDict[root]={}
834
835    for exfile in files:
836      #TST: Until we replace files, still leaving the orginals as is
837      #if not exfile.startswith("new_"+"ex"): continue
838      #if not exfile.startswith("ex"): continue
839
840      # Ignore emacs and other temporary files
841      if exfile.startswith("."): continue
842      if exfile.startswith("#"): continue
843      if exfile.endswith("~"): continue
844      # Only parse source files
845      ext=os.path.splitext(exfile)[-1].lstrip('.')
846      if ext not in LANGS: continue
847
848      # Convenience
849      fullex=os.path.join(root,exfile)
850      if self.verbose: print('   --> '+fullex)
851      dataDict[root].update(testparse.parseTestFile(fullex,0))
852      if exfile in dataDict[root]:
853        self.genScriptsAndInfo(exfile,root,dataDict[root][exfile])
854
855    return
856
857  def walktree(self,top):
858    """
859    Walk a directory tree, starting from 'top'
860    """
861    # Goal of action is to fill this dictionary
862    dataDict={}
863    for root, dirs, files in os.walk(top, topdown=True):
864      dirs.sort()
865      files.sort()
866      if not "examples" in root: continue
867      if "dSYM" in root: continue
868      if os.path.basename(root.rstrip("/")) == 'output': continue
869      if self.verbose: print(root)
870      self.genPetscTests(root,dirs,files,dataDict)
871    # Now summarize this dictionary
872    if self.verbose: self.genPetscTests_summarize(dataDict)
873    return dataDict
874
875  def gen_gnumake(self, fd):
876    """
877     Overwrite of the method in the base PETSc class
878    """
879    def write(stem, srcs):
880      for lang in LANGS:
881        if srcs[lang]['srcs']:
882          fd.write('%(stem)s.%(lang)s := %(srcs)s\n' % dict(stem=stem, lang=lang, srcs=' '.join(srcs[lang]['srcs'])))
883    for pkg in self.pkg_pkgs:
884        srcs = self.gen_pkg(pkg)
885        write('testsrcs-' + pkg, srcs)
886        # Handle dependencies
887        for lang in LANGS:
888            for exfile in srcs[lang]['srcs']:
889                if exfile in srcs[lang]:
890                    ex='$(TESTDIR)/'+os.path.splitext(exfile)[0]
891                    exfo='$(TESTDIR)/'+os.path.splitext(exfile)[0]+'.o'
892                    deps = [os.path.join('$(TESTDIR)', dep) for dep in srcs[lang][exfile]]
893                    if deps:
894                        # The executable literally depends on the object file because it is linked
895                        fd.write(ex   +": " + " ".join(deps) +'\n')
896                        # The object file containing 'main' does not normally depend on other object
897                        # files, but it does when it includes their modules.  This dependency is
898                        # overly blunt and could be reduced to only depend on object files for
899                        # modules that are used, like "*f90aux.o".
900                        fd.write(exfo +": " + " ".join(deps) +'\n')
901
902    return self.gendeps
903
904  def gen_pkg(self, pkg):
905    """
906     Overwrite of the method in the base PETSc class
907    """
908    return self.sources[pkg]
909
910  def write_gnumake(self, dataDict, output=None):
911    """
912     Write out something similar to files from gmakegen.py
913
914     Test depends on script which also depends on source
915     file, but since I don't have a good way generating
916     acting on a single file (oops) just depend on
917     executable which in turn will depend on src file
918    """
919    # Different options for how to set up the targets
920    compileExecsFirst=False
921
922    # Open file
923    fd = open(output, 'w')
924
925    # Write out the sources
926    gendeps = self.gen_gnumake(fd)
927
928    # Write out the tests and execname targets
929    fd.write("\n#Tests and executables\n")    # Delimiter
930
931    for pkg in self.pkg_pkgs:
932      # These grab the ones that are built
933      for lang in LANGS:
934        testdeps=[]
935        for ftest in self.tests[pkg][lang]:
936          test=os.path.basename(ftest)
937          basedir=os.path.dirname(ftest)
938          testdeps.append(self.nameSpace(test,basedir))
939        fd.write("test-"+pkg+"."+lang+" := "+' '.join(testdeps)+"\n")
940        fd.write('test-%s.%s : $(test-%s.%s)\n' % (pkg, lang, pkg, lang))
941
942        # test targets
943        for ftest in self.tests[pkg][lang]:
944          test=os.path.basename(ftest)
945          basedir=os.path.dirname(ftest)
946          testdir="${TESTDIR}/"+basedir+"/"
947          nmtest=self.nameSpace(test,basedir)
948          rundir=os.path.join(testdir,test)
949          script=test+".sh"
950
951          # Deps
952          exfile=self.tests[pkg][lang][ftest]['exfile']
953          fullex=os.path.join(self.srcdir,exfile)
954          localexec=self.tests[pkg][lang][ftest]['exec']
955          execname=os.path.join(testdir,localexec)
956          fullscript=os.path.join(testdir,script)
957          tmpfile=os.path.join(testdir,test,test+".tmp")
958
959          # *.counts depends on the script and either executable (will
960          # be run) or the example source file (SKIP or TODO)
961          fd.write('%s.counts : %s %s'
962              % (os.path.join('$(TESTDIR)/counts', nmtest),
963                 fullscript,
964                 execname if exfile in self.sources[pkg][lang]['srcs'] else fullex)
965              )
966          if exfile in self.sources[pkg][lang]:
967            for dep in self.sources[pkg][lang][exfile]:
968              fd.write(' %s' % os.path.join('$(TESTDIR)',dep))
969          fd.write('\n')
970
971          # Now write the args:
972          fd.write(nmtest+"_ARGS := '"+self.tests[pkg][lang][ftest]['argLabel']+"'\n")
973
974    fd.close()
975    return
976
977def main(petsc_dir=None, petsc_arch=None, pkg_dir=None, pkg_arch=None, pkg_name=None, pkg_pkgs=None, verbose=False, single_ex=False, srcdir=None, testdir=None):
978    # Allow petsc_arch to have both petsc_dir and petsc_arch for convenience
979    testdir=os.path.normpath(testdir)
980    if petsc_arch:
981        petsc_arch=petsc_arch.rstrip(os.path.sep)
982        if len(petsc_arch.split(os.path.sep))>1:
983            petsc_dir,petsc_arch=os.path.split(petsc_arch)
984    output = os.path.join(testdir, 'testfiles')
985
986    pEx=generateExamples(petsc_dir=petsc_dir, petsc_arch=petsc_arch,
987                         pkg_dir=pkg_dir, pkg_arch=pkg_arch, pkg_name=pkg_name, pkg_pkgs=pkg_pkgs,
988                         verbose=verbose, single_ex=single_ex, srcdir=srcdir,
989                         testdir=testdir)
990    dataDict=pEx.walktree(os.path.join(pEx.srcdir))
991    pEx.write_gnumake(dataDict, output)
992
993if __name__ == '__main__':
994    import optparse
995    parser = optparse.OptionParser()
996    parser.add_option('--verbose', help='Show mismatches between makefiles and the filesystem', action='store_true', default=False)
997    parser.add_option('--petsc-dir', help='Set PETSC_DIR different from environment', default=os.environ.get('PETSC_DIR'))
998    parser.add_option('--petsc-arch', help='Set PETSC_ARCH different from environment', default=os.environ.get('PETSC_ARCH'))
999    parser.add_option('--srcdir', help='Set location of sources different from PETSC_DIR/src', default=None)
1000    parser.add_option('-s', '--single_executable', dest='single_executable', action="store_false", help='Whether there should be single executable per src subdir.  Default is false')
1001    parser.add_option('-t', '--testdir', dest='testdir',  help='Test directory [$PETSC_ARCH/tests]')
1002    parser.add_option('--pkg-dir', help='Set the directory of the package (different from PETSc) you want to generate the makefile rules for', default=None)
1003    parser.add_option('--pkg-name', help='Set the name of the package you want to generate the makefile rules for', default=None)
1004    parser.add_option('--pkg-arch', help='Set the package arch name you want to generate the makefile rules for', default=None)
1005    parser.add_option('--pkg-pkgs', help='Set the package folders (comma separated list, different from the usual sys,vec,mat etc) you want to generate the makefile rules for', default=None)
1006
1007    opts, extra_args = parser.parse_args()
1008    if extra_args:
1009        import sys
1010        sys.stderr.write('Unknown arguments: %s\n' % ' '.join(extra_args))
1011        exit(1)
1012    if opts.testdir is None:
1013      opts.testdir = os.path.join(opts.petsc_arch, 'tests')
1014
1015    main(petsc_dir=opts.petsc_dir, petsc_arch=opts.petsc_arch,
1016         pkg_dir=opts.pkg_dir,pkg_arch=opts.pkg_arch,pkg_name=opts.pkg_name,pkg_pkgs=opts.pkg_pkgs,
1017         verbose=opts.verbose,
1018         single_ex=opts.single_executable, srcdir=opts.srcdir,
1019         testdir=opts.testdir)
1020