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