TestCenter Reference
Base.py
Go to the documentation of this file.
2# Copyright 2009, MeVis Medical Solutions AG
3#
4# The user may use this file in accordance with the license agreement provided with
5# the Software or, alternatively, in accordance with the terms contained in a
6# written agreement between the user and MeVis Medical Solutions AG.
7#
8# For further information use the contact form at https://www.mevislab.de/contact
9#
10
11
13
14# -- system imports ----------------------------------------------------------------------------{{{-
15import difflib
16import linecache
17import os
18import re
19import subprocess
20import sys
21import hashlib
22import traceback
23
24# ----------------------------------------------------------------------------------------------}}}-
25
26# -- local imports -----------------------------------------------------------------------------{{{-
27from contextlib import contextmanager
28
29import mevis
30
31from TestSupport.TestHelper import (
32 TestHelper,
33 UseStackFrameFromCallerForLogging,
34 SetLoggingCallerStackFrame,
35 SuppressedErrors,
36 SuppressedWarnings,
37 ExpectInfos,
38)
39from TestSupport.FieldValueTests import FieldValueTestCaseSet
40import collections
41
42# ----------------------------------------------------------------------------------------------}}}-
43
44gEncodingRegExp = re.compile("^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)")
45gEncodingCache = {}
46
47
49 global gEncodingCache
50 gEncodingCache = {}
51
52
54 encoding = gEncodingCache.get(filename)
55 if encoding is None:
56 encoding = "latin1"
57 try:
58 with open(filename, "rb") as fh:
59 bom = fh.read(3)
60 except IOError:
61 bom = None
62 if bom == b"\xef\xbb\xbf":
63 encoding = "utf8"
64 else:
65 line = linecache.getline(filename, 0)
66 m = gEncodingRegExp.match(line)
67 if m == None:
68 line = linecache.getline(filename, 1)
69 m = gEncodingRegExp.match(line)
70 if m is not None:
71 encoding = m.group(1)
72 gEncodingCache[filename] = encoding
73 return encoding
74
75
76def decodeSourceCodeLine(filename, line):
77 encoding = getSourceFileEncoding(filename)
78 try:
79 decodedLine = line.decode(encoding)
80 except UnicodeDecodeError:
81 decodedLine = line.decode("latin1")
82 return decodedLine
83
84
85# -- def getPackageList ------------------------------------------------------------------------{{{-
86
93 return TestHelper.getInstance().getPackageList()
94
95
96# ----------------------------------------------------------------------------------------------}}}-
97
98
99# -- def getTestCaseContext --------------------------------------------------------------------{{{-
100
105 return TestHelper.getInstance().getTestCaseContext()
106
107
108# ----------------------------------------------------------------------------------------------}}}-
109
110
111# -- def getModuleName -------------------------------------------------------------------------{{{-
112
116 return TestHelper.getInstance().getModuleName()
117
118
119# ----------------------------------------------------------------------------------------------}}}-
120
121
122
125 frame = sys._getframe()
126 lastFunction = frame.f_code.co_name
127 while frame:
128 if frame.f_code.co_name == "__callTestFunction":
129 class_ = frame.f_locals["self"].__class__
130 # verify that the class of __callTestFunction is TestCase
131 if hasattr(class_, "_TestCase__callSetUpTestCase"):
132 return lastFunction
133 lastFunction = frame.f_code.co_name
134 frame = frame.f_back
135 return "Unknown"
136
137
138# -- def MacrosShouldLogOnSuccess --------------------------------------------------------------{{{-
139
147 TestHelper.getInstance().setMacrosLogOnSuccess(logOnSuccess)
148
149
150def shouldLogOnSuccess(logOnSuccess=None):
151 if logOnSuccess is None:
152 return TestHelper.getInstance().getMacrosLogOnSuccess()
153 else:
154 return logOnSuccess
155
156
157
165 def __init__(self, logOnSuccess):
166 self.__logOnSuccess = logOnSuccess
167
168 def __call__(self, func):
169 def wrapper(*args, **kwds):
170 logOnSuccess = TestHelper.getInstance().getMacrosLogOnSuccess()
172 try:
173 r = func(*args, **kwds)
174 finally:
175 setMacrosShouldLogOnSuccess(logOnSuccess)
176 return r
177
178 return wrapper
179
180
181# ----------------------------------------------------------------------------------------------}}}-
182
183
184# -- def LoggingDecorator ----------------------------------------------------------------------{{{-
185# @cond
186
188 def wrapper(*args, **kwds):
190 expr, msg, comment, logOnSuccess = func(*args, **kwds)
191 stackLine = traceback.extract_stack(TestHelper.getInstance().getCallerStackFrame(), 1)[0]
192 codeLine = stackLine[3]
193 if isinstance(codeLine, bytes):
194 codeLine = decodeSourceCodeLine(stackLine[0], stackLine[3])
195 if not expr:
196 Logging_error(
197 "%s: %s%s" % (codeLine, msg, ": %s" % (comment) if comment else ""), type="Compare", depth=1
198 )
199 else:
200 if logOnSuccess:
201 Logging_info(
202 "%s: %s%s" % (codeLine, msg, ": %s" % (comment) if comment else ""), type="Compare", depth=1
203 )
204 return expr
205
206 return wrapper
207
208
209# @endcond
210# ----------------------------------------------------------------------------------------------}}}-
211
212
213def escapedUnicode(value):
214 """Returns a unicode representation of value. If value is of type bytes then non-ASCII characters
215 are escaped and a representation of the string is returned.
216 For example, the 8-bit literal b'\x01' becomes 'u"\x01"'.
217 """
218 if isinstance(value, bytes):
219 return repr(str(value))
220 else:
221 return str(value)
222
223
224# -- def compareEqual --------------------------------------------------------------------------{{{-
225
232@LoggingDecorator
233def compareEqual(a, b, comment="", logOnSuccess=None):
234 expr = a == b
235 if hasattr(expr, "all"): # looks like a numpy.ndarray
236 expr = expr.all()
237 return (
238 expr,
239 f"{escapedUnicode(a)} ({type(a).__name__}) == {escapedUnicode(b)} ({type(b).__name__})",
240 comment,
241 shouldLogOnSuccess(logOnSuccess),
242 )
243
244
245# ----------------------------------------------------------------------------------------------}}}-
246
247
248# -- def compareNotEqual -----------------------------------------------------------------------{{{-
249
256@LoggingDecorator
257def compareNotEqual(a, b, comment="", logOnSuccess=None):
258 expr = a != b
259 if hasattr(expr, "any"): # looks like a numpy.ndarray
260 expr = expr.any()
261 return (
262 expr,
263 f"{escapedUnicode(a)} ({type(a).__name__}) != {escapedUnicode(b)} ({type(b).__name__})",
264 comment,
265 shouldLogOnSuccess(logOnSuccess),
266 )
267
268
269# ----------------------------------------------------------------------------------------------}}}-
270
271
272# -- def compareLessThan -----------------------------------------------------------------------{{{-
273
279@LoggingDecorator
280def compareLessThan(a, b, comment="", logOnSuccess=None):
281 return (
282 a < b,
283 f"{escapedUnicode(a)} ({type(a).__name__}) < {escapedUnicode(b)} ({type(b).__name__})",
284 comment,
285 shouldLogOnSuccess(logOnSuccess),
286 )
287
288
289# ----------------------------------------------------------------------------------------------}}}-
290
291
292# -- def compareGreaterThan -----------------------------------------------------------------------{{{-
293
299@LoggingDecorator
300def compareGreaterThan(a, b, comment="", logOnSuccess=None):
301 return (
302 a > b,
303 f"{escapedUnicode(a)} ({type(a).__name__}) > {escapedUnicode(b)} ({type(b).__name__})",
304 comment,
305 shouldLogOnSuccess(logOnSuccess),
306 )
307
308
309# ----------------------------------------------------------------------------------------------}}}-
310
311
312# -- def compareLessThanOrEqual ----------------------------------------------------------------{{{-
313
320@LoggingDecorator
321def compareGreaterThanOrEqual(a, b, comment="", logOnSuccess=None):
322 return (
323 a >= b,
324 f"{escapedUnicode(a)} ({type(a).__name__}) >= {escapedUnicode(b)} ({type(b).__name__})",
325 comment,
326 shouldLogOnSuccess(logOnSuccess),
327 )
328
329
330# ----------------------------------------------------------------------------------------------}}}-
331
332
333# -- def compareLessThanOrEqual ----------------------------------------------------------------{{{-
334
341@LoggingDecorator
342def compareLessThanOrEqual(a, b, comment="", logOnSuccess=None):
343 return (
344 a <= b,
345 f"{escapedUnicode(a)} ({type(a).__name__}) <= {escapedUnicode(b)} ({type(b).__name__})",
346 comment,
347 shouldLogOnSuccess(logOnSuccess),
348 )
349
350
351# ----------------------------------------------------------------------------------------------}}}-
352
353
354# -- def compareFloatEqual ---------------------------------------------------------------------{{{-
355
362@LoggingDecorator
363def compareFloatEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None):
364 msg = f"Comparing {str(a)} ({type(a).__name__}) == {str(b)} ({type(b).__name__}) with epsilon={epsilon}"
365 return Math_compareFloatEqual(a, b, epsilon), msg, comment, shouldLogOnSuccess(logOnSuccess)
366
367
368# ----------------------------------------------------------------------------------------------}}}-
369
370
371# -- def compareFloatNotEqual ------------------------------------------------------------------{{{-
372
379@LoggingDecorator
380def compareFloatNotEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None):
381 msg = f"Comparing {str(a)} ({type(a).__name__}) != {str(b)} ({type(b).__name__}) with epsilon={epsilon}"
382 return not Math_compareFloatEqual(a, b, epsilon), msg, comment, shouldLogOnSuccess(logOnSuccess)
383
384
385# ----------------------------------------------------------------------------------------------}}}-
386
387
388# -- def compareFloatLessThan ------------------------------------------------------------------{{{-
389
395@LoggingDecorator
396def compareFloatLessThan(a, b, comment="", logOnSuccess=None):
397 msg = f"Comparing {str(a)} ({type(a).__name__}) < {str(b)} ({type(b).__name__})"
398 return Math_compareFloatLessThan(a, b), msg, comment, shouldLogOnSuccess(logOnSuccess)
399
400
401# ----------------------------------------------------------------------------------------------}}}-
402
403
404# -- def compareFloatGreaterThan ------------------------------------------------------------------{{{-
405
411@LoggingDecorator
412def compareFloatGreaterThan(a, b, comment="", logOnSuccess=None):
413 msg = f"Comparing {str(a)} ({type(a).__name__}) > {str(b)} ({type(b).__name__})"
414 return Math_compareFloatLessThan(b, a), msg, comment, shouldLogOnSuccess(logOnSuccess)
415
416
417# ----------------------------------------------------------------------------------------------}}}-
418
419
420# -- def compareFloatLessThanOrEqual ---------------------------------------------------------{{{-
421
429@LoggingDecorator
430def compareFloatLessThanOrEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None):
431 msg = f"Comparing {str(a)} ({type(a).__name__}) <= {str(b)} ({type(b).__name__}) with epsilon={epsilon}"
432 return Math_compareFloatLessThanOrEqual(a, b, epsilon), msg, comment, shouldLogOnSuccess(logOnSuccess)
433
434
435# ----------------------------------------------------------------------------------------------}}}-
436
437
438# -- def compareFloatGreaterThanOrEqual ---------------------------------------------------------{{{-
439
447@LoggingDecorator
448def compareFloatGreaterThanOrEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None):
449 msg = f"Comparing {str(a)} ({type(a).__name__}) >= {str(b)} ({type(b).__name__}) with epsilon={epsilon}"
450 return Math_compareFloatLessThanOrEqual(b, a, epsilon), msg, comment, shouldLogOnSuccess(logOnSuccess)
451
452
453# ----------------------------------------------------------------------------------------------}}}-
454
455
456# -- def verifyTrue ----------------------------------------------------------------------------{{{-
457
462@LoggingDecorator
463def verifyTrue(expr, comment="", logOnSuccess=None, msg=None):
464 success = bool(
465 expr
466 ) # we must do this before evaluating expr for the msg string generation which may have side effects
467 if msg is None:
468 msg = f"Must evaluate to True: {escapedUnicode(expr)} ({type(expr).__name__})"
469 return success, msg, comment, shouldLogOnSuccess(logOnSuccess)
470
471
472# ----------------------------------------------------------------------------------------------}}}-
473
474
475# -- def verifyFalse ---------------------------------------------------------------------------{{{-
476
481@LoggingDecorator
482def verifyFalse(expr, comment="", logOnSuccess=None, msg=None):
483 success = not bool(
484 expr
485 ) # we must do this before evaluating expr for the msg string generation which may have side effects
486 if msg is None:
487 msg = f"Must evaluate to False: {escapedUnicode(expr)} ({type(expr).__name__})"
488 return success, msg, comment, shouldLogOnSuccess(logOnSuccess)
489
490
491# ----------------------------------------------------------------------------------------------}}}-
492
493
494# -- def expectError ---------------------------------------------------------------------------{{{-
495
507@UseStackFrameFromCallerForLogging
508def expectError(function, args=[], kargs={}, errorRegExp=None, preventReplacingMessageSeverity=False):
509 testHelper = TestHelper.getInstance()
510 logHandler = testHelper.getLogHandler()
511 assert not logHandler is None
512
513 errors = SuppressedErrors(errorRegExp, Logging_error, preventReplacingMessageSeverity)
514 try:
515 retVal = function(*args, **kargs)
516 finally:
517 errors.handleResult()
518
519 return retVal
520
521
522# ----------------------------------------------------------------------------------------------}}}-
523
524
525# -- def expectWarning -------------------------------------------------------------------------{{{-
526
535@UseStackFrameFromCallerForLogging
536def expectWarning(function, args=[], kargs={}, warningRegExp=None):
537 testHelper = TestHelper.getInstance()
538 logHandler = testHelper.getLogHandler()
539 assert not logHandler is None
540
541 warnings = SuppressedWarnings(warningRegExp, Logging_error)
542 try:
543 retVal = function(*args, **kargs)
544 finally:
545 warnings.handleResult()
546
547 return retVal
548
549
550# ----------------------------------------------------------------------------------------------}}}-
551
552
553# -- def expectWarningAndError ---------------------------------------------------------------------------{{{-
554
563@UseStackFrameFromCallerForLogging
564def expectWarningAndError(function, args=[], kargs={}, errorRegExp=None, warningRegExp=None):
565 testHelper = TestHelper.getInstance()
566 logHandler = testHelper.getLogHandler()
567 assert not logHandler is None
568
569 warnings = SuppressedWarnings(warningRegExp, Logging_error)
570 errors = SuppressedErrors(errorRegExp, Logging_error)
571 try:
572 retVal = function(*args, **kargs)
573 finally:
574 errors.handleResult()
575 warnings.handleResult()
576
577 return retVal
578
579
580# -- def ignoreError ---------------------------------------------------------------------------{{{-
581
594@UseStackFrameFromCallerForLogging
595def ignoreError(function, args=(), kargs=None, errorRegExp=None, resultInfoDict=None):
596 def ___doNotLog(_msg):
597 pass
598
599 #
600 kargs = kargs or {}
601 testHelper = TestHelper.getInstance()
602 logHandler = testHelper.getLogHandler()
603
604 assert not logHandler is None
605
606 hadIgnoredErrors = False
607 errors = SuppressedErrors(errorRegExp, logErrorFunc=None)
608 try:
609 retVal = function(*args, **kargs)
610 finally:
611 hadIgnoredErrors = errors.handleResult()
612 if isinstance(resultInfoDict, dict):
613 resultInfoDict["hadIgnoredErrors"] = hadIgnoredErrors
614
615 return retVal
616
617
618# ----------------------------------------------------------------------------------------------}}}-
619
620
621# -- def ignoreWarning ---------------------------------------------------------------------------{{{-
622
635@UseStackFrameFromCallerForLogging
636def ignoreWarning(function, args=(), kargs=None, warningRegExp=None, resultInfoDict=None):
637 def ___doNotLog(_msg):
638 pass
639
640 #
641 kargs = kargs or {}
642 testHelper = TestHelper.getInstance()
643 logHandler = testHelper.getLogHandler()
644
645 assert not logHandler is None
646
647 hadIgnoredWarnings = False
648 warnings = SuppressedWarnings(warningRegExp, logErrorFunc=None)
649 try:
650 retVal = function(*args, **kargs)
651 finally:
652 hadIgnoredWarnings = warnings.handleResult()
653 if isinstance(resultInfoDict, dict):
654 resultInfoDict["hadIgnoredWarnings"] = hadIgnoredWarnings
655
656 return retVal
657
658
659# ----------------------------------------------------------------------------------------------}}}-
660
661
662# -- def ignoreWarningAndError ---------------------------------------------------------------------------{{{-
663
676@UseStackFrameFromCallerForLogging
677def ignoreWarningAndError(function, args=[], kargs={}, errorRegExp=None, warningRegExp=None, resultInfoDict=None):
678 testHelper = TestHelper.getInstance()
679 logHandler = testHelper.getLogHandler()
680 assert not logHandler is None
681
682 warnings = SuppressedWarnings(warningRegExp, logErrorFunc=None)
683 errors = SuppressedErrors(errorRegExp, logErrorFunc=None)
684 try:
685 retVal = function(*args, **kargs)
686 finally:
687 hadIgnoredErrors = errors.handleResult()
688 hadIgnoredWarnings = warnings.handleResult()
689 if isinstance(resultInfoDict, dict):
690 resultInfoDict["hadIgnoredWarnings"] = hadIgnoredWarnings
691 resultInfoDict["hadIgnoredErrors"] = hadIgnoredErrors
692
693 return retVal
694
695
696# ----------------------------------------------------------------------------------------------}}}-
697
698
699# -- def expectInfo ---------------------------------------------------------------------------{{{-
700
709@UseStackFrameFromCallerForLogging
710def expectInfo(function, args=[], kargs={}, infoRegExp=None, allowUnexpectedMessages=True):
711 testHelper = TestHelper.getInstance()
712 logHandler = testHelper.getLogHandler()
713 assert not logHandler is None
714
715 expectInfos = ExpectInfos(infoRegExp, allowUnexpectedMessages, Logging_error)
716 try:
717 retVal = function(*args, **kargs)
718 finally:
719 expectInfos.handleResult()
720
721 return retVal
722
723
724# ----------------------------------------------------------------------------------------------}}}-
725
726
727# Helpers for expected errors or warnings that can be used with a context manager
728
729
730@contextmanager
731@UseStackFrameFromCallerForLogging
732def hasNoWarning(warningRegExp=None):
733 """Expect that the expression in the context block issues no warning that matches `warningRegExp`.
734 If `warningRegExp` is not given, no warning is expected at all.
735
736 Usage:
737 with hasNoWarning():
738 doStuff()
739 """
740 warnings = SuppressedWarnings(warningRegExp, expectWarning=False)
741 try:
742 yield
743 finally:
744 warnings.handleResult()
745
746
747@contextmanager
748@UseStackFrameFromCallerForLogging
749def hasWarning(warningRegExp=None):
750 """Expect that the expression in the context block logs a warning that matches `warningRegExp`.
751 If `warningRegExp` is not given, any warning is expected.
752
753 Usage:
754 with hasWarning():
755 doStuff()
756 """
757 warnings = SuppressedWarnings(warningRegExp, expectWarning=True)
758 try:
759 yield
760 finally:
761 warnings.handleResult()
762
763
764@contextmanager
765@UseStackFrameFromCallerForLogging
766def hasError(errorRegExp=None):
767 """Expect that the expression in the context logs an error that matches `errorRegExp`.
768 If `errorRegExp` is not given, any error is expected.
769
770 Usage:
771 with hasError():
772 doStuff()
773 """
774 errors = SuppressedErrors(errorRegExp, Logging_error)
775 try:
776 yield
777 finally:
778 errors.handleResult()
779
780
781@contextmanager
782@UseStackFrameFromCallerForLogging
783def hasWarningAndError(errorRegExp=None, warningRegExp=None):
784 """Expect that the expression in the context logs errors and warnings.
785 See `hasError` and `hasWarning` for the arguments.
786
787 Usage:
788 with hasWarningAndError():
789 doStuff()
790 """
791 warnings = SuppressedWarnings(warningRegExp, Logging_error)
792 errors = SuppressedErrors(errorRegExp, Logging_error)
793 try:
794 yield
795 finally:
796 errors.handleResult()
797 warnings.handleResult()
798
799
800@contextmanager
801@UseStackFrameFromCallerForLogging
802def hasInfo(infoRegExp=None, allowUnexpectedMessages=True):
803 """Expect that the expression in the context logs an info entry that matches `infoRegExp`.
804 If `infoRegExp` is not given, any info message is expected.
805 If `allowUnexpectedMessages` is set to True (the default), messages that do not match `infoRegExp` are ignored,
806 otherwise they are considered a failure.
807
808 Usage:
809 with hasInfo(infoRegExp=".*Unknown module.*"):
810 doStuff()
811 """
812 expectInfos = ExpectInfos(infoRegExp, allowUnexpectedMessages, Logging_error)
813 try:
814 yield
815 finally:
816 expectInfos.handleResult()
817
818
819# ----------------------------------------------------------------------------------------------}}}-
820
821
822# -- def getResultDirectory --------------------------------------------------------------------{{{-
823
828 return TestHelper.getInstance().getTestCaseResultDirectory()
829
830
831# ----------------------------------------------------------------------------------------------}}}-
832
833
834# -- def getDataDirectory ----------------------------------------------------------------------{{{-
835
842 return TestHelper.getInstance().getTestCaseDataDirectory()
843
844
845# ----------------------------------------------------------------------------------------------}}}-
846
847
848# -- def getFileInDataDirectory ----------------------------------------------------------------{{{-
849
852 return os.path.join(getDataDirectory(), filename)
853
854
855# ----------------------------------------------------------------------------------------------}}}-
856
857
858# -- def existsFileInDataDirectory -------------------------------------------------------------{{{-
859
862 return os.path.exists(getFileInDataDirectory(filename))
863
864
865# ----------------------------------------------------------------------------------------------}}}-
866
867
868# -- def getExternalDataDirectory --------------------------------------------------------------{{{-
869
878def getExternalDataDirectory(name, subDirectory=None):
879 testHelper = TestHelper.getInstance()
880 testCaseContext = testHelper.getTestCaseContext()
881 externalDataVariable = "TESTCENTER_EXTERNAL_DATA_%s" % name
882 externalDataPlaceholder = "$(%s)" % externalDataVariable
883 externalDataBaseDirectory = testCaseContext.expandFilename(externalDataPlaceholder)
884 if externalDataBaseDirectory == externalDataPlaceholder:
885 raise LookupError(
886 "Variable %s could not be expanded. Please define it in your .prefs file." % externalDataVariable
887 )
888 if os.path.isdir(externalDataBaseDirectory):
889 if subDirectory:
890 return os.path.join(externalDataBaseDirectory, subDirectory)
891 else:
892 testCasePath = testCaseContext.localPath()
893 path = mevis.MLABPackageManager.findPackageIdentifierAndRelativePath(testCasePath)[1]
894 return os.path.join(externalDataBaseDirectory, path)
895 raise ValueError('"%s" is not a directory.' % externalDataBaseDirectory)
896
897
898# ----------------------------------------------------------------------------------------------}}}-
899
900
901# -- def pushChangeSet -------------------------------------------------------------------------{{{-
902
904 return TestHelper.getInstance().pushChangeSet()
905
906
907# ----------------------------------------------------------------------------------------------}}}-
908
909
910# -- def popChangeSet --------------------------------------------------------------------------{{{-
911
915 retVal = True
916 if TestHelper.getInstance().getChangeSetStackLength() > 2:
917 retVal = TestHelper.getInstance().popChangeSet()
918 else:
919 retVal = False
920 return retVal
921
922
923# ----------------------------------------------------------------------------------------------}}}-
924
925
926# -- def getHash -------------------------------------------------------------------------------{{{-
927
934def getHash(filename, hash="SHA1", encoder="Hex"):
935 result = None
936 # Images are handled using the ImageHash module!
937 b, ext = os.path.splitext(filename)
938 if ext.lower() in (
939 ".dcm",
940 ".hdr",
941 ".img",
942 ".bmp",
943 ".jpg",
944 ".jpeg",
945 ".png",
946 ".pnm",
947 ".quo",
948 ".raw",
949 ".tif",
950 ".tiff",
951 ):
952 result = Image_calculateHashFromImage(filename, hash, encoder)
953 else:
954 try:
955 h = hashlib.new(hash)
956 with open(filename, "rb") as f:
957 h.update(f.read())
958 if encoder == "Hex":
959 result = h.hexdigest()
960 elif encoder == "Base64":
961 result = h.digest()
962 else:
963 Logging_error("Unknown encoder (%s) selected." % (encoder))
964 except Exception as e:
965 Logging_error("Failed to generate hash: %s." % e)
966 return result
967
968
969# ----------------------------------------------------------------------------------------------}}}-
970
971
972# -- def createExtraTestCaseResult ----------------------------------------------------------------{{{-
973
982 testCaseName,
983 package,
984 author,
985 duration,
986 maintainer=None,
987 file=None,
988 line=None,
989 comment=None,
990 showTestFunctionSortingLiterals=False,
991):
992 return ExtraTestCaseResult(
993 testCaseName, package, author, duration, maintainer, file, line, comment, showTestFunctionSortingLiterals
994 )
995
996
997# ----------------------------------------------------------------------------------------------}}}-
998
999
1000# -- def addExtraTestCaseResult ----------------------------------------------------------------{{{-
1001
1004def addExtraTestCaseResult(extraTestCaseResult):
1005 return TestHelper.getInstance().addExtraTestCaseResult(extraTestCaseResult)
1006
1007
1008# ----------------------------------------------------------------------------------------------}}}-
1009
1010
1011# -- def pushEnvironment -----------------------------------------------------------------------{{{-
1012
1016 return TestHelper.getInstance().pushEnvironment()
1017
1018
1019# ----------------------------------------------------------------------------------------------}}}-
1020
1021
1022# -- def popEnvironment -----------------------------------------------------------------------{{{-
1023
1027 return TestHelper.getInstance().popEnvironment()
1028
1029
1030# ----------------------------------------------------------------------------------------------}}}-
1031
1032# -- def pushEnvironmentDecorator --------------------------------------------------------------{{{-
1033
1034
1035# Pushes the current environment before the function is called and pops it again after it has finished
1037 def wrapper(*args, **kwds):
1039 # execute macro
1040 try:
1041 result = func(*args, **kwds)
1042 finally:
1044 return result
1045
1046 return wrapper
1047
1048
1049# ----------------------------------------------------------------------------------------------}}}-
1050
1051
1053 def wrapper(*args, **kwds):
1054 wereHighPrecisionTimeStampsEnabled = mevis.MLAB.setHighPrecisionLoggingTimeStampsEnabled(True)
1055 try:
1056 result = func(*args, **kwds)
1057 finally:
1058 mevis.MLAB.setHighPrecisionLoggingTimeStampsEnabled(wereHighPrecisionTimeStampsEnabled)
1059 return result
1060
1061 return wrapper
1062
1063
1064class HtmlDiff(difflib.HtmlDiff):
1065
1066 _styles = """
1067 table.diff {font-family:monospace;border:medium;margin:0;}
1068 .diff_header {background-color:#e0e0e0}
1069 td.diff_header {text-align:right}
1070 .diff_next {background-color:#c0c0c0}
1071 .diff_add {background-color:#aaffaa}
1072 .diff_chg {background-color:#ffff77}
1073 .diff_sub {background-color:#ffaaaa}
1074 tr, td, th {vertical-align:top;}
1075 table.diff tbody {
1076 white-space: -pre-wrap;
1077 white-space: -o-pre-wrap;
1078 white-space: -moz-pre-wrap;
1079 white-space: pre-wrap;
1080 word-wrap: break-word;
1081 }"""
1082
1083 _table_template = """
1084 <table class="diff" id="difflib_chg_%(prefix)s_top"
1085 cellspacing="0" cellpadding="1" rules="groups" width="100%%">
1086 <colgroup></colgroup> <colgroup></colgroup> <colgroup width="50%%"></colgroup>
1087 <colgroup></colgroup> <colgroup></colgroup> <colgroup width="50%%"></colgroup>
1088 %(header_row)s
1089 <tbody>
1090%(data_rows)s </tbody>
1091 </table>"""
1092
1093 def __init__(self):
1094 difflib.HtmlDiff.__init__(self)
1095
1096 def _format_line(self, side, flag, linenum, text):
1097 try:
1098 linenum = "%d" % linenum
1099 id = ' id="%s%s"' % (self._prefix[side], linenum)
1100 except TypeError:
1101 # handle blank lines where linenum is '>' or ''
1102 id = ""
1103 # replace those things that would get confused with HTML symbols
1104 text = text.replace("&", "&amp;").replace(">", "&gt;").replace("<", "&lt;")
1105 if len(text) == 0 or text == "\n":
1106 text = '<span style="display: block; background-color:#e0e0e0;">' + text + "</span>"
1107 return '<td class="diff_header"%s>%s</td><td>%s</td>' % (id, linenum, text)
1108
1109
1110# ----------------------------------------------------------------------------------------------}}}-
1111
1112# -- def pushEnvironmentDecorator --------------------------------------------------------------{{{-
1113
1114
1115# Creates a HTML diff from the two files
1116# @param fromLines The array containing the lines of the left string.
1117# @param toLines The array containing the lines of the right string.
1118# @param showOnlyContextOfDiff If true, then only the context lines around the diff are shown, otherwise the whole content is shown.
1119# @param numberOfContextLines The number of context line only the diff context is shown.
1120def createHtmlDiff(fromLines, toLines, showOnlyContextOfDiff=False, numberOfContextLines=10):
1121 diff = HtmlDiff().make_file(fromLines, toLines, "", "", showOnlyContextOfDiff, numberOfContextLines)
1122 return diff
1123
1124
1125# ----------------------------------------------------------------------------------------------}}}-
1126
1127
1128# Decorator for disabling test functions conditionally
1130 def MLABTC__disableTestFunctionIf_Decorator(func):
1131 disableTestFunction = condition() if isinstance(condition, collections.abc.Callable) else condition
1132 if disableTestFunction:
1133 module = sys.modules[func.__module__]
1134 if not hasattr(module, "MLABTC_DISABLED_TEST_FUNCTIONS"):
1135 setattr(module, "MLABTC_DISABLED_TEST_FUNCTIONS", [])
1136 module.MLABTC_DISABLED_TEST_FUNCTIONS.append(func.__name__)
1137 return func
1138
1139 return MLABTC__disableTestFunctionIf_Decorator
1140
1141
1142# Returns True if offscreen rendering is supported by the hardware. Otherwise returns False.
1144 context = TestHelper.getInstance().getHelperContext()
1145 return context.field("oSSOffscreenRender.supported").value
1146
1147
1148# Creates a subprocess.Popen instance.
1149# @param command A list containing the executable and arguments for the process.
1150# @param wait If True (default), then the function waits for the process to finish. Afterwards it checks the
1151# return code, stderr, and stdout according to the values of expectSuccess and expectFailure. Finally,
1152# a tuple (stdout, stderr, returncode) is returned.
1153# If False, then the subprocess.Popen instance is returned after it was created and started.
1154# @param expectSuccess If True (default), then this function expects the process to return 0.
1155# Stdout/stderr is logged as an info message on success, otherwise as error messages.
1156# @param expectFailure If True, then this function expects the process to return non-zero.
1157# Stdout/stderr is logged as an info message on failure, otherwise as error messages.
1158# @param verbose A boolean to indicate if stdout/stderr should be printed via Macros_INFO.
1159# @param env A dictionary with environment variables. Default is os.environ.
1160# @return A tuple (stdout, stderr, returncode) if wait is True, the subprocess.Popen instance otherwise.
1161def runProcess(command, wait=True, expectSuccess=True, expectFailure=False, verbose=True, env=None):
1162 Macros_INFO("Running: " + " ".join(command))
1163 if env is None:
1164 env = os.environ
1165 if wait:
1166 p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
1167 else:
1168 p = subprocess.Popen(command, env=env)
1169 if wait:
1170 shortCommand = []
1171 for c in command:
1172 if len(os.path.splitext(c)[1]) > 0:
1173 c = os.path.basename(c)
1174 shortCommand.append(c)
1175 shortCommand = " ".join(shortCommand)
1176 stdout, stderr = p.communicate()
1177 stdout = stdout.strip()
1178 stderr = stderr.strip()
1179 stderr = stderr.decode("utf-8", "replace")
1180 stdout = stdout.decode("utf-8", "replace")
1182 " ".join(command), shortCommand, p.returncode, stdout, stderr, expectSuccess, expectFailure, verbose
1183 )
1184 return stdout, stderr, p.returncode
1185 else:
1186 return p
1187
1188
1189# Helper function for runProcess().
1190# @param command A list containing the executable and arguments for the process.
1191# @param shortCommand A shorter command string to improve readability of log messages.
1192# @param returncode The returncode of the process.
1193# @param stdout The stdout output of the process.
1194# @param stderr The stderr output of the process.
1195# @param expectSuccess If True (default), then this function expects the process to return 0.
1196# Stdout/stderr is logged as an info message on success, otherwise as error messages.
1197# @param expectFailure If True, then this function expects the process to return non-zero.
1198# Stdout/stderr is logged as an info message on failure, otherwise as error messages.
1199# @param verbose A boolean to indicate if stdout/stderr should be printed via Macros_INFO.
1201 command, shortCommand, returncode, stdout, stderr, expectSuccess=True, expectFailure=False, verbose=True
1202):
1203 if expectSuccess:
1204 if returncode != 0:
1205 Macros_ERROR("Command returned non-zero: '" + command + "', return code: " + str(returncode))
1206 if len(stderr):
1207 Macros_ERROR(stderr)
1208 if len(stdout):
1209 Macros_ERROR(stdout)
1210 else:
1211 msg = "Success: " + shortCommand
1212 if verbose:
1213 if len(stderr):
1214 msg += "\n\n" + stderr
1215 if len(stdout):
1216 msg += "\n\n" + stdout
1217 Macros_INFO(msg)
1218 elif expectFailure:
1219 if returncode == 0:
1220 Macros_ERROR("Command returned zero: '" + command + "'")
1221 if len(stderr):
1222 Macros_ERROR(stderr)
1223 if len(stdout):
1224 Macros_ERROR(stdout)
1225 else:
1226 msg = "Expected failure: " + shortCommand
1227 if verbose:
1228 if len(stderr):
1229 msg += "\n\n" + stderr
1230 if len(stdout):
1231 msg += "\n\n" + stdout
1232 Macros_INFO(msg)
1233
1234
1235# Creates and starts a process from the python script using runProcess()
1236# @param wait If True (default), then the function waits for the process to finish. Afterwards it checks the
1237# return code, stderr, and stdout according to the values of expectSuccess and expectFailure. Finally,
1238# a tuple (stdout, stderr, returncode) is returned.
1239# If False, then the subprocess.Popen instance is returned after it was created and started.
1240# @param expectSuccess If True (default), then this function expects the process to return 0.
1241# Stdout/stderr is logged as an info message on success, otherwise as error messages.
1242# @param expectFailure If True, then this function expects the process to return non-zero.
1243# Stdout/stderr is logged as an info message on failure, otherwise as error messages.
1244# @param verbose A boolean to indicate if stdout/stderr should be printed via Macros_INFO.
1245# @param env A dictionary with environment variables. Default is os.environ.
1246# @return A tuple (stdout, stderr, returncode) if wait is True, the subprocess.Popen instance otherwise.
1247def runPythonScript(script, arguments, wait=True, expectSuccess=True, expectFailure=False, verbose=True, env=None):
1248 python = mevis.MLABFileManager.getExecutable("MeVisPython")
1249 command = [python, "-u", script]
1250 command.extend(arguments)
1251 return runProcess(
1252 command, wait=wait, expectSuccess=expectSuccess, expectFailure=expectFailure, verbose=verbose, env=env
1253 )
1254
1255
1256from .ExtraTestCaseResult import ExtraTestCaseResult
1257from .Image import calculateHashFromImage as Image_calculateHashFromImage
1258from .Logging import error as Logging_error, warning as Logging_warning, info as Logging_info
1259from .Macros import ERROR as Macros_ERROR, INFO as Macros_INFO
1260from .Math import (
1261 compareFloatEqual as Math_compareFloatEqual,
1262 compareFloatLessThan as Math_compareFloatLessThan,
1263 compareFloatLessThanOrEqual as Math_compareFloatLessThanOrEqual,
1264)
_format_line(self, side, flag, linenum, text)
Definition Base.py:1096
Decorator to globally enable or disable if the ASSERT_*/EXPECT_* macros log an info message on succes...
Definition Base.py:164
EnableHighPrecisionLoggingTimeStampsDecorator(func)
Definition Base.py:1052
getHash(filename, hash="SHA1", encoder="Hex")
Compute a hash for the content of the given file.
Definition Base.py:934
compareFloatLessThanOrEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None)
Compare the two given float values if the first is less than or equal to the second.
Definition Base.py:430
verifyProcessResult(command, shortCommand, returncode, stdout, stderr, expectSuccess=True, expectFailure=False, verbose=True)
Definition Base.py:1202
addExtraTestCaseResult(extraTestCaseResult)
Adds an ExtraTestCaseResult object.
Definition Base.py:1004
runPythonScript(script, arguments, wait=True, expectSuccess=True, expectFailure=False, verbose=True, env=None)
Definition Base.py:1247
compareFloatGreaterThanOrEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None)
Compare the two given float values if the first is greater than or equal to the second.
Definition Base.py:448
escapedUnicode(value)
Definition Base.py:213
hasInfo(infoRegExp=None, allowUnexpectedMessages=True)
Definition Base.py:802
hasWarningAndError(errorRegExp=None, warningRegExp=None)
Definition Base.py:783
getDataDirectory()
Method to return path to the data files.
Definition Base.py:841
compareGreaterThan(a, b, comment="", logOnSuccess=None)
Compare the two given values if the first one is greater than the second.
Definition Base.py:300
setMacrosShouldLogOnSuccess(logOnSuccess)
Globally enables or disables if the ASSERT_*/EXPECT_* macros log an info message on success.
Definition Base.py:146
getResultDirectory()
Getter for the directory used to save results.
Definition Base.py:827
compareNotEqual(a, b, comment="", logOnSuccess=None)
Compare the two given values for inequality.
Definition Base.py:257
disableTestFunctionIf(condition)
Definition Base.py:1129
runProcess(command, wait=True, expectSuccess=True, expectFailure=False, verbose=True, env=None)
Definition Base.py:1161
hasNoWarning(warningRegExp=None)
Definition Base.py:732
getPackageList()
Returns the list of available packages.
Definition Base.py:92
compareFloatEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None)
Compare the two given float values if they are epsilon-equal.
Definition Base.py:363
existsFileInDataDirectory(filename)
Checks if the given file exists in the data directory.
Definition Base.py:861
ignoreWarning(function, args=(), kargs=None, warningRegExp=None, resultInfoDict=None)
Call the given function with the given parameters and ignores potential warnings.
Definition Base.py:636
getModuleName()
Return the name of the currently tested module.
Definition Base.py:115
compareLessThanOrEqual(a, b, comment="", logOnSuccess=None)
Compare the two given values if the first one is less than or equal to the second.
Definition Base.py:342
expectWarning(function, args=[], kargs={}, warningRegExp=None)
Call the given function with the given parameters and expect a warning.
Definition Base.py:536
expectWarningAndError(function, args=[], kargs={}, errorRegExp=None, warningRegExp=None)
Call the given function with the given parameters and expect both a warning and an error.
Definition Base.py:564
shouldLogOnSuccess(logOnSuccess=None)
Definition Base.py:150
getTestCaseContext()
Get the context of the test.
Definition Base.py:104
compareFloatLessThan(a, b, comment="", logOnSuccess=None)
Compare the two given float values if the first is less than the second.
Definition Base.py:396
getFileInDataDirectory(filename)
Returns the absolute filename of a file in the data directory using getDataDirectory().
Definition Base.py:851
compareEqual(a, b, comment="", logOnSuccess=None)
Compare the two given values for equality.
Definition Base.py:233
compareLessThan(a, b, comment="", logOnSuccess=None)
Compare the two given values if the first one is less than the second.
Definition Base.py:280
verifyTrue(expr, comment="", logOnSuccess=None, msg=None)
If the given expression evaluates to False, an error is logged.
Definition Base.py:463
hasError(errorRegExp=None)
Definition Base.py:766
createHtmlDiff(fromLines, toLines, showOnlyContextOfDiff=False, numberOfContextLines=10)
Definition Base.py:1120
getTestFunctionName()
Returns the name of the current test function.
Definition Base.py:124
ignoreWarningAndError(function, args=[], kargs={}, errorRegExp=None, warningRegExp=None, resultInfoDict=None)
Call the given function with the given parameters and ignore both a warning and an error.
Definition Base.py:677
createExtraTestCaseResult(testCaseName, package, author, duration, maintainer=None, file=None, line=None, comment=None, showTestFunctionSortingLiterals=False)
Creates an ExtraTestCaseResult object.
Definition Base.py:991
compareFloatGreaterThan(a, b, comment="", logOnSuccess=None)
Compare the two given float values if the first is greater than the second.
Definition Base.py:412
LoggingDecorator(func)
Internal decorator used for creating messages in compare methods.
Definition Base.py:187
ignoreError(function, args=(), kargs=None, errorRegExp=None, resultInfoDict=None)
Call the given function with the given parameters and ignores potential errors.
Definition Base.py:595
expectError(function, args=[], kargs={}, errorRegExp=None, preventReplacingMessageSeverity=False)
Call the given function with the given parameters and expect an error.
Definition Base.py:508
getExternalDataDirectory(name, subDirectory=None)
Method to return path to the external data directory.
Definition Base.py:878
hasWarning(warningRegExp=None)
Definition Base.py:749
compareFloatNotEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None)
Compare the two given float values if they are epsilon-unequal.
Definition Base.py:380
decodeSourceCodeLine(filename, line)
Definition Base.py:76
verifyFalse(expr, comment="", logOnSuccess=None, msg=None)
If the given expression evaluates to True, an error is logged.
Definition Base.py:482
pushChangeSet()
Push a new ChangeSet to the stack.
Definition Base.py:903
hardwareSupportsOffscreenRendering()
Definition Base.py:1143
pushEnvironment()
Pushes the current environment dictionary on a stack, so that modifications can be applied and be pop...
Definition Base.py:1015
clearEncodingCache()
Definition Base.py:48
compareGreaterThanOrEqual(a, b, comment="", logOnSuccess=None)
Compare the two given values if the first one is greater than or equal to the second.
Definition Base.py:321
pushEnvironmentDecorator(func)
Definition Base.py:1036
expectInfo(function, args=[], kargs={}, infoRegExp=None, allowUnexpectedMessages=True)
Call the given function with the given parameters and expect an info message.
Definition Base.py:710
popEnvironment()
Pops the last pushed environment dictionary from a stack and makes it the current one.
Definition Base.py:1026
popChangeSet()
Pop the last ChangeSet from the stack.
Definition Base.py:914
getSourceFileEncoding(filename)
Definition Base.py:53