1
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=[]
28
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
35
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
57 """Automatically call the 'init'-method of the whole tree"""
58
59 for b in theClass.__bases__:
60 if b not in [object,CTestRun]:
61 self.__recursiveInit(b,called)
62
63
64 if "init" in dir(theClass):
65
66 if not theClass.init.im_func in called:
67 theClass.init(self)
68 called.append(theClass.init.im_func)
69
70
72 for a in args:
73 self.__addToClone.append(a)
74
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
87 vals={}
88
89 for k,v in self.__parameters.iteritems():
90 vals[k]=v["value"]
91
92 return vals
93
95 for k in keys:
96 if k in self.__parameters:
97 self.__parameters[k]["used"]=True
98
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
112 return type(self).__name__
113
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),
127 ("small",300),
128 ("medium",1800),
129 ("big",7200),
130 ("huge",43200),
131 ("monster",172800),
132 ("unlimited",259200)
133 ]
134
137
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
238 """read the runInfo from a file"""
239 pick=pickle.Unpickler(open(path.join(self.caseDir,"runInfo.pickle")))
240 self.__runInfo=pick.load()
241
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
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
254 return self.runAndCatchExceptions(original_callable,self,*args,**kwargs)
255 setattr(self,name,wrapped)
256
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
263
264 for m in callbackMethods:
265 print "Wrapping method",m
266
267 self.wrapACallback(m)
268
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
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
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
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
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
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
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
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
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
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
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
803
805 """print a status message about the test"""
806 print "TEST",self.shortTestName(),":",
807 for a in args:
808 print a,
809 print
810
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
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
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
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
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):
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
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=""):
1021
1022 - def isSmaller(self,value,threshold=0,message=""):
1027
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
1034 """Decomposition used if no callback is specified"""
1035 deco=Decomposer(args=[self.caseDir,
1036 str(self["nrCpus"]),
1037 "--all-regions"])
1038
1040 """Reconstruction used if no callback is specified"""
1041 self.execute("reconstructPar","-latestTime")
1042
1043 @isCallback
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
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
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
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