TestCenter Reference
Base.py
Go to the documentation of this file.
1 #
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 ----------------------------------------------------------------------------{{{-
15 import difflib
16 import linecache
17 import os
18 import re
19 import subprocess
20 import sys
21 import hashlib
22 import traceback
23 
24 # ----------------------------------------------------------------------------------------------}}}-
25 
26 # -- local imports -----------------------------------------------------------------------------{{{-
27 from contextlib import contextmanager
28 
29 import mevis
30 
31 from TestSupport.TestHelper import (
32  TestHelper,
33  UseStackFrameFromCallerForLogging,
34  SetLoggingCallerStackFrame,
35  SuppressedErrors,
36  SuppressedWarnings,
37  ExpectInfos,
38 )
39 from TestSupport.FieldValueTests import FieldValueTestCaseSet
40 import collections
41 
42 # ----------------------------------------------------------------------------------------------}}}-
43 
44 gEncodingRegExp = re.compile("^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)")
45 gEncodingCache = {}
46 
47 
49  global gEncodingCache
50  gEncodingCache = {}
51 
52 
53 def getSourceFileEncoding(filename):
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 
76 def 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 
146 def setMacrosShouldLogOnSuccess(logOnSuccess):
147  TestHelper.getInstance().setMacrosLogOnSuccess(logOnSuccess)
148 
149 
150 def 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 = logOnSuccess
167 
168  def __call__(self, func):
169  def wrapper(*args, **kwds):
170  logOnSuccess = TestHelper.getInstance().getMacrosLogOnSuccess()
171  setMacrosShouldLogOnSuccess(self.__logOnSuccess__logOnSuccess)
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 
213 def 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
233 def 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
257 def 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
280 def 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
300 def 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
321 def 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
342 def 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
363 def 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
380 def 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
396 def 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
412 def 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
430 def 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
448 def 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
463 def 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
482 def 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
508 def 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
536 def 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
564 def 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
595 def 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
636 def 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
677 def 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
710 def 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
732 def 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
749 def 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
766 def 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
783 def 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
802 def 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 
878 def 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 
934 def 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 
1004 def 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):
1038  pushEnvironment()
1039  # execute macro
1040  try:
1041  result = func(*args, **kwds)
1042  finally:
1043  popEnvironment()
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 
1064 class 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.
1120 def 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
1129 def disableTestFunctionIf(condition):
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.
1161 def 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.
1247 def 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 
1256 from .ExtraTestCaseResult import ExtraTestCaseResult
1257 from .Image import calculateHashFromImage as Image_calculateHashFromImage
1258 from .Logging import error as Logging_error, warning as Logging_warning, info as Logging_info
1259 from .Macros import ERROR as Macros_ERROR, INFO as Macros_INFO
1260 from .Math import (
1261  compareFloatEqual as Math_compareFloatEqual,
1262  compareFloatLessThan as Math_compareFloatLessThan,
1263  compareFloatLessThanOrEqual as Math_compareFloatLessThanOrEqual,
1264 )
Decorator to globally enable or disable if the ASSERT_*/EXPECT_* macros log an info message on succes...
Definition: Base.py:164
def pushEnvironment()
Pushes the current environment dictionary on a stack, so that modifications can be applied and be pop...
Definition: Base.py:1015
def hardwareSupportsOffscreenRendering()
Definition: Base.py:1143
def 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
def pushEnvironmentDecorator(func)
Definition: Base.py:1036
def getHash(filename, hash="SHA1", encoder="Hex")
Compute a hash for the content of the given file.
Definition: Base.py:934
def hasWarningAndError(errorRegExp=None, warningRegExp=None)
Definition: Base.py:783
def getTestCaseContext()
Get the context of the test.
Definition: Base.py:104
def compareNotEqual(a, b, comment="", logOnSuccess=None)
Compare the two given values for inequality.
Definition: Base.py:257
def decodeSourceCodeLine(filename, line)
Definition: Base.py:76
def getDataDirectory()
Method to return path to the data files.
Definition: Base.py:841
def compareFloatEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None)
Compare the two given float values if they are epsilon-equal.
Definition: Base.py:363
def getResultDirectory()
Getter for the directory used to save results.
Definition: Base.py:827
def addExtraTestCaseResult(extraTestCaseResult)
Adds an ExtraTestCaseResult object.
Definition: Base.py:1004
def compareLessThan(a, b, comment="", logOnSuccess=None)
Compare the two given values if the first one is less than the second.
Definition: Base.py:280
def compareFloatGreaterThan(a, b, comment="", logOnSuccess=None)
Compare the two given float values if the first is greater than the second.
Definition: Base.py:412
def hasNoWarning(warningRegExp=None)
Definition: Base.py:732
def compareFloatNotEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None)
Compare the two given float values if they are epsilon-unequal.
Definition: Base.py:380
def hasInfo(infoRegExp=None, allowUnexpectedMessages=True)
Definition: Base.py:802
def clearEncodingCache()
Definition: Base.py:48
def 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
def runPythonScript(script, arguments, wait=True, expectSuccess=True, expectFailure=False, verbose=True, env=None)
Definition: Base.py:1247
def 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
def verifyTrue(expr, comment="", logOnSuccess=None, msg=None)
If the given expression evaluates to False, an error is logged.
Definition: Base.py:463
def createExtraTestCaseResult(testCaseName, package, author, duration, maintainer=None, file=None, line=None, comment=None, showTestFunctionSortingLiterals=False)
Creates an ExtraTestCaseResult object.
Definition: Base.py:991
def pushChangeSet()
Push a new ChangeSet to the stack.
Definition: Base.py:903
def runProcess(command, wait=True, expectSuccess=True, expectFailure=False, verbose=True, env=None)
Definition: Base.py:1161
def compareGreaterThan(a, b, comment="", logOnSuccess=None)
Compare the two given values if the first one is greater than the second.
Definition: Base.py:300
def 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
def 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
def getPackageList()
Returns the list of available packages.
Definition: Base.py:92
def existsFileInDataDirectory(filename)
Checks if the given file exists in the data directory.
Definition: Base.py:861
def EnableHighPrecisionLoggingTimeStampsDecorator(func)
Definition: Base.py:1052
def escapedUnicode(value)
Definition: Base.py:213
def 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
def disableTestFunctionIf(condition)
Definition: Base.py:1129
def popEnvironment()
Pops the last pushed environment dictionary from a stack and makes it the current one.
Definition: Base.py:1026
def getTestFunctionName()
Returns the name of the current test function.
Definition: Base.py:124
def getFileInDataDirectory(filename)
Returns the absolute filename of a file in the data directory using getDataDirectory().
Definition: Base.py:851
def getModuleName()
Return the name of the currently tested module.
Definition: Base.py:115
def hasError(errorRegExp=None)
Definition: Base.py:766
def expectError(function, args=[], kargs={}, errorRegExp=None, preventReplacingMessageSeverity=False)
Call the given function with the given parameters and expect an error.
Definition: Base.py:508
def verifyProcessResult(command, shortCommand, returncode, stdout, stderr, expectSuccess=True, expectFailure=False, verbose=True)
Definition: Base.py:1202
def popChangeSet()
Pop the last ChangeSet from the stack.
Definition: Base.py:914
def LoggingDecorator(func)
Internal decorator used for creating messages in compare methods.
Definition: Base.py:187
def compareFloatLessThan(a, b, comment="", logOnSuccess=None)
Compare the two given float values if the first is less than the second.
Definition: Base.py:396
def setMacrosShouldLogOnSuccess(logOnSuccess)
Globally enables or disables if the ASSERT_*/EXPECT_* macros log an info message on success.
Definition: Base.py:146
def verifyFalse(expr, comment="", logOnSuccess=None, msg=None)
If the given expression evaluates to True, an error is logged.
Definition: Base.py:482
def hasWarning(warningRegExp=None)
Definition: Base.py:749
def shouldLogOnSuccess(logOnSuccess=None)
Definition: Base.py:150
def createHtmlDiff(fromLines, toLines, showOnlyContextOfDiff=False, numberOfContextLines=10)
Definition: Base.py:1120
def getExternalDataDirectory(name, subDirectory=None)
Method to return path to the external data directory.
Definition: Base.py:878
def compareEqual(a, b, comment="", logOnSuccess=None)
Compare the two given values for equality.
Definition: Base.py:233
def expectWarning(function, args=[], kargs={}, warningRegExp=None)
Call the given function with the given parameters and expect a warning.
Definition: Base.py:536
def 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
def getSourceFileEncoding(filename)
Definition: Base.py:53
def 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
def 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