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