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