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