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