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