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