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