Package PyFoam :: Package Infrastructure :: Module CTestRun
[hide private]
[frames] | no frames]

Source Code for Module PyFoam.Infrastructure.CTestRun

   1  #  ICE Revision: $Id: /local/openfoam/Python/PyFoam/PyFoam/Infrastructure/CTestRun.py 7936 2012-03-16T15:06:46.196748Z bgschaid  $  
   2  """A wrapper to run a solver as a CTest""" 
   3   
   4  import sys 
   5  import os 
   6  from os import path 
   7  import subprocess 
   8  import shutil 
   9  import traceback 
  10  import inspect 
  11  import cPickle as pickle 
  12  import time 
  13   
  14  from PyFoam.Applications.CloneCase import CloneCase 
  15  from PyFoam.Applications.Runner import Runner 
  16  from PyFoam.RunDictionary.SolutionDirectory import SolutionDirectory 
  17  from PyFoam.RunDictionary.ParsedParameterFile import ParsedParameterFile 
  18  from PyFoam.Applications.SamplePlot import SamplePlot 
  19  from PyFoam.Applications.TimelinePlot import TimelinePlot 
  20  from PyFoam.Applications.Decomposer import Decomposer 
  21  from PyFoam.Basics.Data2DStatistics import Data2DStatistics 
  22   
  23  callbackMethods=[] 
24 25 -def isCallback(f):
26 callbackMethods.append(f.__name__) 27 return f
28
29 -class CTestRun(object):
30 """This class runs a solver on a test case, examines the results 31 and fails if they don't live up the expectations""" 32
33 - def __init__(self):
34 pass
35
36 - def __new__(cls,*args,**kwargs):
37 obj=super(CTestRun,cls).__new__(cls,*args,**kwargs) 38 39 obj.__parameters={} 40 41 obj.setParameters(sizeClass="unknown", 42 parallel=False, 43 autoDecompose=True, 44 doReconstruct=True, 45 nrCpus=None, 46 originalCaseBasis=None) 47 48 obj.__addToClone=[] 49 50 called=[] 51 obj.__recursiveInit(obj.__class__,called) 52 obj.__setParameterAsUsed(["nrCpus","autoDecompose","doReconstruct"]) 53 54 return obj
55
56 - def __recursiveInit(self,theClass,called):
57 """Automatically call the 'init'-method of the whole tree""" 58 # print theClass 59 for b in theClass.__bases__: 60 if b not in [object,CTestRun]: 61 self.__recursiveInit(b,called) 62 63 # subclass overwrites superclasses 64 if "init" in dir(theClass): 65 # make sure this is only called once 66 if not theClass.init.im_func in called: 67 theClass.init(self) 68 called.append(theClass.init.im_func)
69 # print "Calling init for",theClass 70
71 - def addToClone(self,*args):
72 for a in args: 73 self.__addToClone.append(a)
74
75 - def setParameters(self,**kwargs):
76 """Update the parameters with a set of keyword-arguments""" 77 78 caller=inspect.stack()[1] 79 setter="Set by %s in %s line %d" % (caller[3],caller[1],caller[2]) 80 81 for k,v in kwargs.iteritems(): 82 self.__parameters[k]={"value":v, 83 "setter":setter, 84 "used":False}
85
86 - def parameterValues(self):
87 vals={} 88 89 for k,v in self.__parameters.iteritems(): 90 vals[k]=v["value"] 91 92 return vals
93
94 - def __setParameterAsUsed(self,keys):
95 for k in keys: 96 if k in self.__parameters: 97 self.__parameters[k]["used"]=True
98
99 - def __getitem__(self,key):
100 """Get a parameter""" 101 try: 102 parameter=self.__parameters[key] 103 except KeyError,e: 104 print "Unknown parameter",key,"(Parameters:",self.__parameters.keys(),")" 105 raise e 106 107 parameter["used"]=True 108 109 return parameter["value"]
110
111 - def shortTestName(self):
112 return type(self).__name__
113
114 - def testName(self):
115 """Return the full test name with which this test is identified""" 116 result=self.shortTestName()+"_"+self["solver"] 117 if self["parallel"]: 118 result+="_parallel_"+str(self["nrCpus"])+"Cpus" 119 else: 120 result+="_serial" 121 result+="_"+self["sizeClass"] 122 return result
123 124 timeoutDefinitions=[ 125 ("unknown",60), 126 ("tiny",60), # a minute 127 ("small",300), # 5 minutes 128 ("medium",1800), # half an hour 129 ("big",7200), # 2 hours 130 ("huge",43200), # 12 hours 131 ("monster",172800), # 2 days 132 ("unlimited",259200) # 30 days 133 ] 134
135 - def sizeClassString(self):
136 return ", ".join(["%s = %ds"% t for t in CTestRun.timeoutDefinitions])
137
138 - def setTimeout(self,quiet=False):
139 if self["sizeClass"]=="unknown": 140 if not quiet: 141 self.warn("The parameter 'sizeClass' has not been set yet. Assuming 'tiny'") 142 143 self.toSmallTimeout=0 144 self.proposedSizeClass="unknown" 145 146 try: 147 self.timeout=dict(CTestRun.timeoutDefinitions)[self["sizeClass"]] 148 index=-1 149 for i,v in enumerate(CTestRun.timeoutDefinitions): 150 if v[0]==self["sizeClass"]: 151 index=i 152 break 153 if index>2: 154 self.proposedSizeClass,self.toSmallTimeout=CTestRun.timeoutDefinitions[index-2] 155 except KeyError: 156 self.fatalFail("sizeClass is specified as",self["sizeClass"], 157 ". Valid values are with their timeout values", 158 self.sizeClassString()) 159 self.timeout=dict(CTestRun.timeoutDefinitions["unknown"]) # just in case that we continue
160
161 - def __doInit(self, 162 solver, 163 originalCase, 164 minimumRunTime=None, 165 referenceData=None, 166 tailLength=50, 167 headLength=50, 168 **kwargs):
169 """Initialzation method to be called before running the actual 170 test (purpose of this method is to avoid cascaded of 171 constructor-calls 172 173 @param solver: name of the solver to test 174 @param originalCase: location of the original case files (they 175 will be copied) 176 @param minimumRuntime: the solver has to run at least to this time 177 to be considered "ran successful" 178 @param referenceData: directory with data that is used for testing 179 @param tailLength: output that many lines from the end of the solver output 180 @param headLength: output that many lines from the beginning of the solver output 181 """ 182 print "Creating test",self.testName() 183 184 self.__setParameterAsUsed(["solver","originalCase","minimumRunTime", 185 "referenceData","tailLength","headLength"]) 186 187 self.__failed=False 188 self.__failMessage="" 189 self.__runInfo=None 190 191 self.__tailLength=tailLength 192 self.__headLength=headLength 193 194 self.setTimeout() 195 196 self.solver=self.which(solver) 197 if not self.solver: 198 self.fatalFail("Solver",solver,"not in PATH") 199 print "Using solver",self.solver 200 201 if self["originalCaseBasis"]: 202 originalCase=path.join(self["originalCaseBasis"],originalCase) 203 print "Expanding original case path with",self["originalCaseBasis"] 204 205 self.originalCase=path.expandvars(originalCase) 206 if not path.exists(self.originalCase): 207 self.fatalFail("Original case",self.originalCase,"does not exist") 208 print "Original case",self.originalCase 209 210 self.caseDir=path.join(self.workDir(),self.testName()+"_runDir") 211 print "Running case in",self.caseDir 212 if path.exists(self.caseDir): 213 if self.removeOldCase: 214 self.warn("Removing old case",self.caseDir) 215 shutil.rmtree(self.caseDir) 216 elif self.doClone: 217 self.fatalFail(self.caseDir,"already existing") 218 else: 219 self.fail(self.caseDir,"already existing") 220 221 if referenceData: 222 self.referenceData=path.join(self.dataDir(),referenceData) 223 if not path.exists(self.referenceData): 224 self.fatalFail("Data directory",self.referenceData,"does not exist") 225 print "Using reference data from" 226 else: 227 self.referenceData=None 228 print "No reference data specified" 229 230 if self.doReadRunInfo: 231 print "Attempting to read the runInfo-file" 232 self.readRunInfo() 233 234 self.minimumRunTime=minimumRunTime 235 print
236
237 - def readRunInfo(self):
238 """read the runInfo from a file""" 239 pick=pickle.Unpickler(open(path.join(self.caseDir,"runInfo.pickle"))) 240 self.__runInfo=pick.load()
241
242 - def writeRunInfo(self):
243 """read the runInfo from a file""" 244 pick=pickle.Pickler(open(path.join(self.caseDir,"runInfo.pickle"),"w")) 245 pick.dump(self.__runInfo)
246
247 - def wrapACallback(self,name):
248 """Has to be a separate method because the loop in 249 wrapCallbacks didn't work""" 250 original=getattr(self,name) 251 original_callable=getattr(original,'im_func') 252 def wrapped(*args,**kwargs): 253 # print "Wrapping",name,args,kwargs,original_callable 254 return self.runAndCatchExceptions(original_callable,self,*args,**kwargs)
255 setattr(self,name,wrapped)
256
257 - def wrapCallbacks(self):
258 """Wrap the callback methods with a Python exception handler. 259 This is not done here so that methoids that the child classes 260 overwrote will be wrapped to""" 261 262 # not yet working 263 264 for m in callbackMethods: 265 print "Wrapping method",m 266 # setattr(self,m,original) 267 self.wrapACallback(m)
268
269 - def processOptions(self):
270 """Select which phase of the test should be run""" 271 272 from optparse import OptionParser,OptionGroup 273 274 parser = OptionParser(usage="%prog: [options]") 275 phases=OptionGroup(parser, 276 "Phase", 277 "Select which phases to run") 278 parser.add_option_group(phases) 279 phases.add_option("--no-clone", 280 action="store_false", 281 dest="doClone", 282 default=True, 283 help="Skip cloning phase") 284 phases.add_option("--no-preparation", 285 action="store_false", 286 dest="doPreparation", 287 default=True, 288 help="Skip preparation phase") 289 phases.add_option("--no-serial-pre-tests", 290 action="store_false", 291 dest="doSerialPreTests", 292 default=True, 293 help="Skip pre-run test phase") 294 phases.add_option("--no-decompose", 295 action="store_false", 296 dest="doDecompose", 297 default=True, 298 help="Skip decomposition phase") 299 phases.add_option("--no-parallel-preparation", 300 action="store_false", 301 dest="doParallelPreparation", 302 default=True, 303 help="Skip the parallel preparation phase") 304 phases.add_option("--no-pre-tests", 305 action="store_false", 306 dest="doPreTests", 307 default=True, 308 help="Skip pre-run test phase") 309 phases.add_option("--no-simulation", 310 action="store_false", 311 dest="doSimulation", 312 default=True, 313 help="Skip simulation phase") 314 phases.add_option("--no-postprocessing", 315 action="store_false", 316 dest="doPostprocessing", 317 default=True, 318 help="Skip postprocessing phase") 319 phases.add_option("--no-post-tests", 320 action="store_false", 321 dest="doPostTests", 322 default=True, 323 help="Skip post-run test phase") 324 phases.add_option("--no-reconstruction", 325 action="store_false", 326 dest="doReconstruction", 327 default=True, 328 help="Skip reconstruction phase") 329 phases.add_option("--no-serial-post-tests", 330 action="store_false", 331 dest="doSerialPostTests", 332 default=True, 333 help="Skip serial post-run test phase") 334 phases.add_option("--jump-to-tests", 335 action="store_true", 336 dest="jumpToTests", 337 default=False, 338 help="Skip everything except the final tests") 339 340 behave=OptionGroup(parser, 341 "Behaviour", 342 "Determine the behaviour") 343 parser.add_option_group(behave) 344 behave.add_option("--fatal-not-fatal", 345 action="store_true", 346 dest="fatalIsNotFatal", 347 default=False, 348 help="Continue running the tests although a fatal error occured") 349 behave.add_option("--remove-old-case", 350 action="store_true", 351 dest="removeOldCase", 352 default=False, 353 help="Remove the case directory if it exists") 354 355 info=OptionGroup(parser, 356 "Info", 357 "Information about the test (all these options print to the screen and stop the test before doing anything)") 358 parser.add_option_group(info) 359 info.add_option("--parameter-value", 360 action="store", 361 dest="parameterValue", 362 default=None, 363 help="Just print the value of a parameter. Nothing if the parameter does not exist") 364 info.add_option("--dump-parameters", 365 action="store_true", 366 dest="dumpParameters", 367 default=False, 368 help="Dump all the parameter values") 369 info.add_option("--verbose-dump-parameters", 370 action="store_true", 371 dest="verboseDumpParameters", 372 default=False, 373 help="Dump all the parameter values plus the information where they were set") 374 data=OptionGroup(parser, 375 "Data", 376 "Reading and writing data that allows rerunning cases") 377 parser.add_option_group(data) 378 data.add_option("--read-run-info", 379 action="store_true", 380 dest="readRunInfo", 381 default=False, 382 help="Read the runInfo-File if it exists in the runDirectory (mostly used to test tests without running the solver)") 383 data.add_option("--print-run-info", 384 action="store_true", 385 dest="printRunInfo", 386 default=False, 387 help="Print the runInfo when it becomes available") 388 389 script=OptionGroup(parser, 390 "Script parameters", 391 "Information about the test (all these options print to the screen and stop the test before doing anything and can be used as input in scripts)") 392 parser.add_option_group(script) 393 script.add_option("--print-test-name", 394 action="store_true", 395 dest="printTestName", 396 default=False, 397 help="Print the test name under which this test will be known to the world") 398 script.add_option("--timeout", 399 action="store_true", 400 dest="timeout", 401 default=False, 402 help="Print the timeout for this test") 403 404 (options, args) = parser.parse_args() 405 406 if options.parameterValue: 407 try: 408 print self[options.parameterValue] 409 sys.exit(0) 410 except KeyError: 411 sys.exit(1) 412 413 if options.printTestName: 414 print self.testName() 415 sys.exit(0) 416 417 if options.timeout: 418 self.setTimeout(quiet=True) 419 print self.timeout 420 sys.exit(0) 421 422 if options.dumpParameters or options.verboseDumpParameters: 423 keys=self.__parameters.keys() 424 keys.sort() 425 maxLen=max([len(n) for n in keys]) 426 for k in keys: 427 print k," "*(maxLen-len(k)),":",self[k] 428 if options.verboseDumpParameters: 429 print " ",self.__parameters[k]["setter"] 430 print 431 432 sys.exit(0) 433 434 self.doReadRunInfo=options.readRunInfo 435 self.doPrintRunInfo=options.printRunInfo 436 437 self.doClone=options.doClone 438 self.doPreparation=options.doPreparation 439 self.doSerialPreTests=options.doSerialPreTests 440 self.doDecompose=options.doDecompose 441 self.doParallelPreparation=options.doParallelPreparation 442 self.doPreTests=options.doPreTests 443 self.doSimulation=options.doSimulation 444 self.doPostprocessing=options.doPostprocessing 445 self.doPostTests=options.doPostTests 446 self.doReconstruction=options.doReconstruction 447 self.doSerialPostTests=options.doSerialPostTests 448 449 if options.jumpToTests: 450 self.doClone=False 451 self.doPreparation=False 452 self.doSerialPreTests=False 453 self.doDecompose=False 454 self.doParallelPreparation=False 455 self.doPreTests=False 456 self.doSimulation=False 457 self.doPostprocessing=False 458 459 self.fatalIsNotFatal=options.fatalIsNotFatal 460 self.removeOldCase=options.removeOldCase
461
462 - def run(self):
463 """Run the actual test""" 464 465 startTime=time.time() 466 467 self.processOptions() 468 469 self.__doInit(**self.parameterValues()) 470 471 self.wrapCallbacks() 472 473 self.__runParallel=False 474 475 if self.doClone: 476 self.status("Cloning case") 477 478 clone=CloneCase([self.originalCase,self.caseDir]+ 479 ["--add="+a for a in self.__addToClone]) 480 else: 481 self.status("Skipping cloning") 482 483 if self.doPreparation: 484 if self.referenceData: 485 if path.exists(path.join(self.referenceData,"copyToCase")): 486 self.status("Copying reference data") 487 self.cloneData(path.join(self.referenceData,"copyToCase"), 488 self.caseDir) 489 else: 490 self.status("No reference data - No 'copyToCase' in",self.referenceData) 491 492 if path.exists(path.join(self.referenceData,"additionalFunctionObjects")): 493 self.status("Adding function objects") 494 self.addFunctionObjects(path.join(self.referenceData,"additionalFunctionObjects")) 495 else: 496 self.status("No additional function objects - No 'additionalFunctionObjects' in",self.referenceData) 497 498 self.status("Preparing mesh") 499 self.meshPrepare() 500 501 self.status("Preparing case") 502 self.casePrepare() 503 else: 504 self.status("Skipping case preparation") 505 506 if self.doSerialPreTests: 507 self.status("Running serial pre-run tests") 508 self.runTests("serialPreRunTest",warnSerial=True) 509 else: 510 self.status("Skipping the serial pre-tests") 511 512 if self["parallel"]: 513 if self.doDecompose: 514 self.status("Decomposing the case") 515 if self["autoDecompose"]: 516 self.autoDecompose() 517 else: 518 self.decompose() 519 else: 520 self.status("Skipping the decomposition") 521 522 self.__runParallel=self["parallel"] 523 524 if self.doPreTests: 525 self.status("Running pre-run tests") 526 self.runTests("preRunTest") 527 else: 528 self.status("Skipping the pre-tests") 529 530 if self.doSimulation: 531 self.status("Run solver") 532 self.__runInfo=self.execute(self.solver) 533 self.writeRunInfo() 534 print 535 if not self.runInfo()["OK"]: 536 self.fail("Solver",self.solver,"ended with an error") 537 else: 538 try: 539 self.status("Solver ran until time",self.runInfo()["time"]) 540 except KeyError: 541 self.fail("No information how long the solver ran") 542 else: 543 self.status("Skipping running of the simulation") 544 545 if self.doPrintRunInfo: 546 print 547 print "runInfo used in furthere tests" 548 import pprint 549 550 printer=pprint.PrettyPrinter() 551 printer.pprint(self.__runInfo.getData()) 552 553 if self.doPostprocessing: 554 self.status("Running postprocessing tools") 555 self.postprocess() 556 else: 557 self.status("Skipping the postprocessing tools") 558 559 if self.doPostTests: 560 self.status("Running post-run tests") 561 self.runTests("postRunTest") 562 else: 563 self.status("Skipping the post-run tests") 564 565 self.__runParallel=False 566 567 if self["parallel"]: 568 if self.doReconstruction and self["doReconstruct"]: 569 self.status("Reconstructing the case") 570 if self["autoDecompose"]: 571 self.autoReconstruct() 572 else: 573 self.reconstruct() 574 else: 575 self.status("Skipping the decomposition") 576 577 if self.doSerialPostTests: 578 self.status("Running serial post-run tests") 579 self.runTests("serialPostRunTest",warnSerial=True) 580 else: 581 self.status("Skipping the serial post-tests") 582 583 if self.minimumRunTime: 584 try: 585 if float(self.runInfo()["time"])<self.minimumRunTime: 586 self.fail("Solver only ran to",self.runInfo()["time"], 587 "but should at least run to",self.minimumRunTime) 588 except KeyError: 589 self.fail("No information about run-time. Should have run to", 590 self.minimumRunTime) 591 except TypeError: 592 self.warn("Silently ignoring missing runInfo()") 593 594 runTime=time.time()-startTime 595 self.status("Total running time",runTime,"seconds") 596 if runTime>self.timeout: 597 self.warn("Running time",runTime,"bigger than assigned timeout", 598 self.timeout,". Consider other sizeclass than", 599 self["sizeClass"],"from sizeclasses",self.sizeClassString()) 600 elif runTime<self.toSmallTimeout: 601 self.warn("Running time",runTime,"much smaller than assigned timeout", 602 self.timeout,". Consider other sizeclass than", 603 self["sizeClass"],"from sizeclasses", 604 self.sizeClassString(),"for instance",self.proposedSizeClass) 605 return self.endTest()
606 607 # make configuration-dependent
608 - def workDir(self):
609 try: 610 return os.environ["PYFOAM_CTESTRUN_WORKDIR"] 611 except KeyError: 612 if not hasattr(self,"__checkWorkDir"): 613 self.__checkWorkDir=None 614 self.warn("No environment variable PYFOAM_CTESTRUN_WORKDIR defined. Using current directory") 615 return path.curdir
616 617 # make configuration-dependent
618 - def dataDir(self):
619 try: 620 return os.environ["PYFOAM_CTESTRUN_DATADIR"] 621 except KeyError: 622 if not hasattr(self,"__checkDataDir"): 623 self.__checkDataDir=None 624 self.warn("No environment variable PYFOAM_CTESTRUN_DATADIR defined. Using current directory") 625 return path.curdir
626
627 - def addFunctionObjects(self,templateFile):
628 """Add entries for libraries and functionObjects to the controlDict 629 (if they don't exist 630 @param templateFile: file withe the data that should be added 631 """ 632 tf=ParsedParameterFile(templateFile) 633 cd=self.controlDict() 634 touchedCD=False 635 if "libs" in tf: 636 touchedCD=True 637 if not "libs" in cd: 638 cd["libs"]=[] 639 for l in tf["libs"]: 640 if l in cd["libs"]: 641 self.warn(l,"already in 'libs' in the controlDict") 642 else: 643 cd["libs"].append(l) 644 if "functions" in tf: 645 touchedCD=True 646 if not "functions" in cd: 647 cd["functions"]={} 648 for k,v in tf["functions"].iteritems(): 649 if k in cd["functions"]: 650 self.warn("Overwriting function object",k) 651 cd["functions"][k]=v 652 653 if touchedCD: 654 cd.writeFile()
655
656 - def cloneData(self,src,dst):
657 """Copy files recurivly into a case 658 @param src: the source directory the files come fro 659 @param dst: the destination directory the files go to""" 660 661 for f in os.listdir(src): 662 if f[0]=='.': 663 self.warn("Ignoring dot-file",path.join(src,f)) 664 continue 665 if path.isdir(path.join(src,f)): 666 if not path.exists(path.join(dst,f)): 667 os.mkdir(path.join(dst,f)) 668 self.cloneData(path.join(src,f),path.join(dst,f)) 669 else: 670 if path.exists(path.join(dst,f)): 671 self.warn("File",path.join(dst,f),"exists already in case. Overwritten") 672 shutil.copy(path.join(src,f),path.join(dst,f))
673
674 - def runCommand(self,*args):
675 """Run a command and let it directly write to the output""" 676 p=subprocess.Popen(" ".join(map(str,args)), 677 shell=True, 678 stdout=subprocess.PIPE, 679 stderr=subprocess.STDOUT) 680 681 print p.communicate()[0] 682 sts=p.returncode 683 684 if sts!=0: 685 self.fail("Command"," ".join(args),"ended with status",sts)
686
687 - def shell(self, 688 *args):
689 """Run a command in the case directory and let it directly 690 write to the output 691 @param workingDirectory: change to this directory""" 692 693 workingDirectory=None 694 if not workingDirectory: 695 workingDirectory=self.caseDir 696 cmd=" ".join(map(str,args)) 697 self.status("Executing",cmd,"in",workingDirectory) 698 oldDir=os.getcwd() 699 os.chdir(workingDirectory) 700 701 p=subprocess.Popen(cmd, 702 shell=True, 703 stdout=subprocess.PIPE, 704 stderr=subprocess.STDOUT) 705 706 self.status("Output of the command") 707 self.line() 708 print p.communicate()[0] 709 self.line() 710 711 sts=p.returncode 712 713 if sts!=0: 714 self.fail("Command",cmd,"ended with status",sts) 715 else: 716 self.status(cmd,"ran OK") 717 718 os.chdir(oldDir)
719
720 - def execute(self,*args,**kwargs):
721 """Execute the passed arguments on the case and check if 722 everything went alright 723 @param regexps: a list of regular expressions that the output should be scanned for""" 724 725 try: 726 regexps=kwargs["regexps"] 727 if type(regexps)!=list: 728 self.fail(regexps,"is not a list of strings") 729 raise KeyError 730 except KeyError: 731 regexps=None 732 733 if len(args)==1 and type(args[0])==str: 734 args=[a.replace("%case%",self.solution().name) for a in args[0].split()] 735 736 pyArgs=["--silent","--no-server-process"] 737 if self.__runParallel: 738 pyArgs+=["--procnr=%d" % self["nrCpus"]] 739 740 argList=list(args)+\ 741 ["-case",self.caseDir] 742 self.status("Executing"," ".join(argList)) 743 if regexps: 744 self.status("Also looking for the expressions",'"'+('" "'.join(regexps))+'"') 745 pyArgs+=[r'--custom-regexp=%s' % r for r in regexps] 746 747 runner=Runner(args=pyArgs+argList) 748 self.status("Execution ended") 749 750 if not runner["OK"]: 751 self.fail("Running "," ".join(argList),"failed") 752 else: 753 self.status("Execution was OK") 754 if "warnings" in runner: 755 self.status(runner["warnings"],"during execution") 756 print 757 self.status("Output of"," ".join(argList),":") 758 if runner["lines"]>(self.__tailLength+self.__headLength): 759 self.status("The first",self.__headLength,"lines of the output.", 760 "Of a total of",runner["lines"]) 761 self.line() 762 self.runCommand("head","-n",self.__headLength,runner["logfile"]) 763 self.line() 764 print 765 self.status("The last",self.__tailLength,"lines of the output.", 766 "Of a total of",runner["lines"]) 767 self.line() 768 self.runCommand("tail","-n",self.__tailLength,runner["logfile"]) 769 self.line() 770 else: 771 self.line() 772 self.runCommand("cat",runner["logfile"]) 773 self.line() 774 775 self.status("End of output") 776 print 777 778 return runner
779
780 - def runInfo(self):
781 """return the run information. If the solver was actually run""" 782 if self.__runInfo==None: 783 self.fatalFail("runInfo() called although solver was not yet run") 784 else: 785 return self.__runInfo
786
787 - def solution(self):
788 """Access to a SolutionDirectory-object that represents the 789 current solution""" 790 if not hasattr(self,"_solution"): 791 self._solution=SolutionDirectory(self.caseDir, 792 archive=None) 793 return self._solution
794
795 - def controlDict(self):
796 """Access a representation of the controlDict of the case""" 797 if not hasattr(self,"_controlDict"): 798 self._controlDict=ParsedParameterFile(self.solution().controlDict()) 799 return self._controlDict
800
801 - def line(self):
802 self.status("/\\"*((78-len("TEST "+self.shortTestName()+" :"))/2))
803
804 - def status(self,*args):
805 """print a status message about the test""" 806 print "TEST",self.shortTestName(),":", 807 for a in args: 808 print a, 809 print
810
811 - def messageGeneral(self,prefix,say,*args):
812 """Everything that passes through this method will be repeated 813 in the end 814 @param args: arbitrary number of arguments that build the 815 fail-message 816 @param prefix: General classification of the message 817 """ 818 msg=prefix.upper()+": "+str(args[0]) 819 for a in args[1:]: 820 msg+=" "+str(a) 821 822 print 823 print say,msg 824 print 825 826 self.__failMessage+=msg+"\n"
827
828 - def failGeneral(self,prefix,*args):
829 """@param args: arbitrary number of arguments that build the 830 fail-message 831 @param prefix: General classification of the failure 832 """ 833 self.__failed=True 834 self.messageGeneral(prefix,"Test failed:",*args)
835
836 - def warn(self,*args):
837 """@param args: arbitrary number of arguments that build the 838 warning-message""" 839 self.messageGeneral("warning","",*args)
840
841 - def fail(self,*args):
842 """To be called if the test failed but other tests should be tried 843 @param args: arbitrary number of arguments that build the 844 fail-message""" 845 846 self.failGeneral("failure",*args)
847
848 - def fatalFail(self,*args):
849 """@param args: arbitrary number of arguments that build the 850 fail-message""" 851 852 self.failGeneral("fatal failure",*args) 853 if not self.fatalIsNotFatal: 854 self.endTest()
855
856 - def endTest(self):
857 unused=[] 858 for k,v in self.__parameters.iteritems(): 859 if not v["used"]: 860 unused.append(k) 861 if len(unused)>0: 862 self.warn("Unused parameters (possible typo):",unused) 863 864 print 865 if self.__failed: 866 print "Test failed." 867 print 868 print "Summary of failures" 869 print self.__failMessage 870 print 871 872 sys.exit(1) 873 else: 874 print "Test successful" 875 print 876 if len(self.__failMessage)>0: 877 print "Summary of warnings" 878 print self.__failMessage 879 print 880 881 sys.exit(0)
882
883 - def which(self,command):
884 """Like the regular which command - return the full path to an 885 executable""" 886 for d in os.environ["PATH"].split(os.pathsep): 887 if path.exists(path.join(d,command)): 888 return path.join(d,command) 889 890 return None
891
892 - def runAndCatchExceptions(self,func,*args,**kwargs):
893 """Run a callable and catch Python-exceptions if they occur 894 @param func: The actual thing to be run""" 895 try: 896 func(*args,**kwargs) 897 return True 898 except SystemExit,e: 899 self.fail("sys.exit() called somewhere while executing", 900 func.__name__,":",e) 901 traceback.print_exc() 902 raise e 903 except Exception,e: 904 self.fail("Python problem during execution of", 905 func.__name__,":",e) 906 traceback.print_exc() 907 return False
908
909 - def runTests(self,namePrefix,warnSerial=False):
910 """Run all methods that fit a certain name prefix""" 911 self.status("Looking for tests that fit the prefix",namePrefix) 912 cnt=0 913 for n in dir(self): 914 if n.find(namePrefix)==0: 915 meth=getattr(self,n) 916 if not inspect.ismethod(meth): 917 self.fail("Found attribute",n, 918 "that fits the prefix",namePrefix, 919 "in test class but it is not a method") 920 else: 921 self.status("Running the test",n) 922 if not self["parallel"] and warnSerial: 923 self.warn("This is a serial test. No need to have special serial tests like",n) 924 self.runAndCatchExceptions(meth) 925 cnt+=1 926 if cnt>0: 927 self.status(cnt,"tests with prefix",namePrefix,"run") 928 else: 929 self.status("No test fit the prefix",namePrefix)
930
931 - def generalTest(self, 932 testFunction, 933 args, 934 *message):
935 if not apply(testFunction,args): 936 self.fail(*message)
937
938 - def compareSamples(self, 939 data, 940 reference, 941 fields, 942 time=None, 943 line=None, 944 scaleData=1, 945 offsetData=0, 946 scaleX=1, 947 offsetX=0):
948 """Compare sample data and return the statistics 949 @param data: the name of the data directory 950 @param reference:the name of the directory with the reference data 951 @param fields: list of the fields to compare 952 @param time: the time to compare for. If empty the latest time is used""" 953 timeOpt=["--latest-time"] 954 if time: 955 timeOpt=["--time="+str(time)] 956 if line: 957 timeOpt+=["--line=%s" % line] 958 959 sample=SamplePlot(args=[self.caseDir, 960 "--silent", 961 "--dir="+data, 962 "--reference-dir="+reference, 963 "--tolerant-reference-time", 964 "--compare", 965 "--index-tolerant-compare", 966 "--common-range-compare", 967 "--metrics", 968 "--scale-data=%f" % scaleData, 969 "--scale-x=%f" % scaleX, 970 "--offset-data=%f" % offsetData, 971 "--offset-x=%f" % offsetX 972 ]+ 973 timeOpt+ 974 ["--field="+f for f in fields]) 975 return Data2DStatistics(metrics=sample["metrics"], 976 compare=sample["compare"], 977 noStrings=True, 978 failureValue=0)
979
980 - def compareTimelines(self, 981 data, 982 reference, 983 fields):
984 """Compare timeline data and return the statistics 985 @param data: the name of the data directory 986 @param reference:the name of the directory with the reference data 987 @param fields: list of the fields to compare""" 988 sample=TimelinePlot(args=[self.caseDir, 989 "--silent", 990 "--dir="+data, 991 "--reference-dir="+reference, 992 "--compare", 993 "--basic-mode=lines", 994 "--metrics"]+ 995 ["--field="+f for f in fields]) 996 return Data2DStatistics(metrics=sample["metrics"], 997 compare=sample["compare"], 998 noStrings=True, 999 failureValue=0)
1000 1001
1002 - def isNotEqual(self,value,target=0,tolerance=1e-10,message=""):
1003 self.generalTest( 1004 lambda x,y:abs(x-y)>tolerance, 1005 (value,target), 1006 message,"( value",value,"within tolerance",tolerance, 1007 "of target",target,")")
1008
1009 - def isEqual(self,value,target=0,tolerance=1e-10,message=""):
1010 self.generalTest( 1011 lambda x,y:abs(x-y)<tolerance, 1012 (value,target), 1013 message,"( value",value," not within tolerance",tolerance, 1014 "of target",target,")")
1015
1016 - def isBigger(self,value,threshold=0,message=""):
1017 self.generalTest( 1018 lambda x:x>threshold, 1019 (value), 1020 message,"( value",value," not bigger than",threshold)
1021
1022 - def isSmaller(self,value,threshold=0,message=""):
1023 self.generalTest( 1024 lambda x:x<threshold, 1025 (value), 1026 message,"( value",value," not smaller than",threshold)
1027
1028 - def preRunTestCheckMesh(self):
1029 """This test is always run. If this is not desirable it has to 1030 be overridden in a child-class""" 1031 self.execute("checkMesh")
1032
1033 - def autoDecompose(self):
1034 """Decomposition used if no callback is specified""" 1035 deco=Decomposer(args=[self.caseDir, 1036 str(self["nrCpus"]), 1037 "--all-regions"])
1038
1039 - def autoReconstruct(self):
1040 """Reconstruction used if no callback is specified""" 1041 self.execute("reconstructPar","-latestTime")
1042 1043 @isCallback
1044 - def meshPrepare(self):
1045 """Callback to prepare the mesh for the case. Default 1046 behaviour is to run blockMesh on the case""" 1047 result=self.execute("blockMesh") 1048 if not result["OK"]: 1049 self.fatalFail("blockMesh was not able to create a mesh")
1050 1051 @isCallback
1052 - def casePrepare(self):
1053 """Callback to prepare the case. Default behaviour is to do 1054 nothing""" 1055 pass
1056 1057 @isCallback
1058 - def postprocess(self):
1059 """Callback to run after the solver has finished. Default 1060 behaviour is to do nothing""" 1061 pass
1062 1063 @isCallback
1064 - def decompose(self):
1065 """Callback to do the decomposition (if automatic is not sufficient)""" 1066 self.fatalFail("Manual decomposition specified but no callback for manual decomposition specified")
1067 1068 @isCallback
1069 - def reconstruct(self):
1070 """Callback to do the reconstruction (if automatic is not sufficient)""" 1071 self.warn("Manual decomposition specified, but no callback 'reconstruct' implemented. Using the automatic reconstruction") 1072 self.autoReconstruct()
1073