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