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