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 from builtins import str
12 
13 # Already ported to Python 3
14 
16 
17 # -- system imports ----------------------------------------------------------------------------{{{-
18 import difflib
19 import linecache
20 import os
21 import re
22 import subprocess
23 import sys
24 import hashlib
25 import traceback
26 # ----------------------------------------------------------------------------------------------}}}-
27 
28 # -- local imports -----------------------------------------------------------------------------{{{-
29 from contextlib import contextmanager
30 
31 import mevis
32 
33 from TestSupport.TestHelper import TestHelper, UseStackFrameFromCallerForLogging, SetLoggingCallerStackFrame, \
34  SuppressedErrors, SuppressedWarnings, ExpectInfos
35 from TestSupport.FieldValueTests import FieldValueTestCaseSet
36 import collections
37 # ----------------------------------------------------------------------------------------------}}}-
38 
39 gEncodingRegExp = re.compile("^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)")
40 gEncodingCache = {}
41 
43  global gEncodingCache
44  gEncodingCache = {}
45 
46 def getSourceFileEncoding(filename):
47  encoding = gEncodingCache.get(filename)
48  if encoding is None:
49  encoding = "latin1"
50  try:
51  with open(filename, 'rb') as fh:
52  bom = fh.read(3)
53  except IOError:
54  bom = None
55  if bom == b"\xef\xbb\xbf":
56  encoding = "utf8"
57  else:
58  line = linecache.getline(filename, 0)
59  m = gEncodingRegExp.match(line)
60  if m == None:
61  line = linecache.getline(filename, 1)
62  m = gEncodingRegExp.match(line)
63  if m is not None:
64  encoding = m.group(1)
65  gEncodingCache[filename] = encoding
66  return encoding
67 
68 def decodeSourceCodeLine(filename, line):
69  encoding = getSourceFileEncoding(filename)
70  try:
71  decodedLine = line.decode(encoding)
72  except UnicodeDecodeError:
73  decodedLine = line.decode("latin1")
74  return decodedLine
75 
76 
77 # -- def getPackageList ------------------------------------------------------------------------{{{-
78 
85  return TestHelper.getInstance().getPackageList()
86 # ----------------------------------------------------------------------------------------------}}}-
87 
88 # -- def getTestCaseContext --------------------------------------------------------------------{{{-
89 
94  return TestHelper.getInstance().getTestCaseContext()
95 # ----------------------------------------------------------------------------------------------}}}-
96 
97 # -- def getModuleName -------------------------------------------------------------------------{{{-
98 
102  return TestHelper.getInstance().getModuleName()
103 # ----------------------------------------------------------------------------------------------}}}-
104 
105 
108  frame = sys._getframe()
109  lastFunction = frame.f_code.co_name
110  while frame:
111  if frame.f_code.co_name == '__callTestFunction':
112  class_ = frame.f_locals['self'].__class__
113  # verify that the class of __callTestFunction is TestCase
114  if hasattr(class_, '_TestCase__callSetUpTestCase'):
115  return lastFunction
116  lastFunction = frame.f_code.co_name
117  frame = frame.f_back
118  return 'Unknown'
119 
120 # -- def MacrosShouldLogOnSuccess --------------------------------------------------------------{{{-
121 
128 def setMacrosShouldLogOnSuccess(logOnSuccess):
129  TestHelper.getInstance().setMacrosLogOnSuccess(logOnSuccess)
130 
131 def shouldLogOnSuccess(logOnSuccess=None):
132  if logOnSuccess is None:
133  return TestHelper.getInstance().getMacrosLogOnSuccess()
134  else:
135  return logOnSuccess
136 
137 
145  def __init__(self, logOnSuccess):
146  self.__logOnSuccess__logOnSuccess = logOnSuccess
147  def __call__(self, func):
148  def wrapper (*args, **kwds):
149  logOnSuccess = TestHelper.getInstance().getMacrosLogOnSuccess()
150  setMacrosShouldLogOnSuccess(self.__logOnSuccess__logOnSuccess)
151  try:
152  r = func(*args, **kwds)
153  finally:
154  setMacrosShouldLogOnSuccess(logOnSuccess)
155  return r
156  return wrapper
157 # ----------------------------------------------------------------------------------------------}}}-
158 
159 # -- def LoggingDecorator ----------------------------------------------------------------------{{{-
160 # @cond
161 
162 def LoggingDecorator (func):
163  def wrapper (*args, **kwds):
165  expr, msg, comment, logOnSuccess = func(*args, **kwds)
166  stackLine = traceback.extract_stack(TestHelper.getInstance().getCallerStackFrame(), 1)[0]
167  codeLine = stackLine[3]
168  if isinstance(codeLine, bytes):
169  codeLine = decodeSourceCodeLine(stackLine[0], stackLine[3])
170  if not expr:
171  Logging_error("%s: %s%s" % (codeLine, msg, ": %s" % (comment) if comment else ''), type="Compare", depth=1)
172  else:
173  if logOnSuccess:
174  Logging_info("%s: %s%s" % (codeLine, msg, ": %s" % (comment) if comment else ''), type="Compare", depth=1)
175  return expr
176  return wrapper
177 # @endcond
178 # ----------------------------------------------------------------------------------------------}}}-
179 
180 def escapedUnicode(value):
181  """ Returns a unicode representation of value. If value is of type bytes then non-ASCII characters
182  are escaped and a representation of the string is returned.
183  For example, the 8-bit literal b'\x01' becomes 'u"\x01"'.
184  """
185  if isinstance(value, bytes):
186  return repr(str(value))
187  else:
188  return str(value)
189 
190 # -- def compareEqual --------------------------------------------------------------------------{{{-
191 
198 @LoggingDecorator
199 def compareEqual (a, b, comment="", logOnSuccess=None):
200  expr = a==b
201  if hasattr(expr, 'all'): # looks like a numpy.ndarray
202  expr = expr.all()
203  return expr, u"%s (%s) == %s (%s)" % (escapedUnicode(a), type(a).__name__, escapedUnicode(b), type(b).__name__), comment, shouldLogOnSuccess(logOnSuccess)
204 # ----------------------------------------------------------------------------------------------}}}-
205 
206 # -- def compareNotEqual -----------------------------------------------------------------------{{{-
207 
214 @LoggingDecorator
215 def compareNotEqual (a, b, comment="", logOnSuccess=None):
216  expr = a!=b
217  if hasattr(expr, 'any'): # looks like a numpy.ndarray
218  expr = expr.any()
219  return expr, u"%s (%s) != %s (%s)" % (escapedUnicode(a), type(a).__name__, escapedUnicode(b), type(b).__name__), comment, shouldLogOnSuccess(logOnSuccess)
220 # ----------------------------------------------------------------------------------------------}}}-
221 
222 # -- def compareLessThan -----------------------------------------------------------------------{{{-
223 
229 @LoggingDecorator
230 def compareLessThan (a, b, comment="", logOnSuccess=None):
231  return a<b, u"%s (%s) < %s (%s)" % (escapedUnicode(a), type(a).__name__, escapedUnicode(b), type(b).__name__), comment, shouldLogOnSuccess(logOnSuccess)
232 # ----------------------------------------------------------------------------------------------}}}-
233 
234 # -- def compareLessThanOrEqual ----------------------------------------------------------------{{{-
235 
242 @LoggingDecorator
243 def compareLessThanOrEqual (a, b, comment="", logOnSuccess=None):
244  return a<=b, u"%s (%s) <= %s (%s)" % (escapedUnicode(a), type(a).__name__, escapedUnicode(b), type(b).__name__), comment, shouldLogOnSuccess(logOnSuccess)
245 # ----------------------------------------------------------------------------------------------}}}-
246 
247 # -- def compareFloatEqual ---------------------------------------------------------------------{{{-
248 
255 @LoggingDecorator
256 def compareFloatEqual (a, b, comment="", epsilon=0.0001, logOnSuccess=None):
257  msg = u"Comparing %s (%s) == %s (%s) with epsilon=%f" % (str(a), type(a).__name__, str(b), type(b).__name__, epsilon)
258  return Math_compareFloatEqual(a, b, epsilon), msg, comment, shouldLogOnSuccess(logOnSuccess)
259 # ----------------------------------------------------------------------------------------------}}}-
260 
261 # -- def compareFloatNotEqual ------------------------------------------------------------------{{{-
262 
269 @LoggingDecorator
270 def compareFloatNotEqual (a, b, comment="", epsilon=0.0001, logOnSuccess=None):
271  msg = u"Comparing %s (%s) != %s (%s) with epsilon=%f" % (str(a), type(a).__name__, str(b), type(b).__name__, epsilon)
272  return not Math_compareFloatEqual(a, b, epsilon), msg, comment, shouldLogOnSuccess(logOnSuccess)
273 # ----------------------------------------------------------------------------------------------}}}-
274 
275 # -- def compareFloatLessThan ------------------------------------------------------------------{{{-
276 
282 @LoggingDecorator
283 def compareFloatLessThan (a, b, comment="", logOnSuccess=None):
284  msg = u"Comparing %s (%s) < %s (%s)" % (str(a), type(a).__name__, str(b), type(b).__name__)
285  return Math_compareFloatLessThan(a, b), msg, comment, shouldLogOnSuccess(logOnSuccess)
286 # ----------------------------------------------------------------------------------------------}}}-
287 
288 # -- def compareFloatLessThanOrEqual ---------------------------------------------------------{{{-
289 
297 @LoggingDecorator
298 def compareFloatLessThanOrEqual (a, b, comment="", epsilon=0.0001, logOnSuccess=None):
299  msg = u"Comparing %s (%s) <= %s (%s) with epsilon=%f" % (str(a), type(a).__name__, str(b), type(b).__name__, epsilon)
300  return Math_compareFloatLessThanOrEqual(a, b, epsilon), msg, comment, shouldLogOnSuccess(logOnSuccess)
301 # ----------------------------------------------------------------------------------------------}}}-
302 
303 # -- def verifyTrue ----------------------------------------------------------------------------{{{-
304 
309 @LoggingDecorator
310 def verifyTrue (expr, comment="", logOnSuccess=None, msg=None):
311  success = bool( expr ) # we must do this before evaluating expr for the msg string generation which may have side effects
312  if msg is None:
313  msg = u"Must evaluate to True: {} ({})".format( escapedUnicode(expr), type(expr).__name__ )
314  return success, msg, comment, shouldLogOnSuccess(logOnSuccess)
315 # ----------------------------------------------------------------------------------------------}}}-
316 
317 # -- def verifyFalse ---------------------------------------------------------------------------{{{-
318 
323 @LoggingDecorator
324 def verifyFalse (expr, comment="", logOnSuccess=None, msg=None):
325  success = not bool( expr ) # we must do this before evaluating expr for the msg string generation which may have side effects
326  if msg is None:
327  msg = u"Must evaluate to False: {} ({})".format( expr, type(expr).__name__ )
328  return success, msg, comment, shouldLogOnSuccess(logOnSuccess)
329 # ----------------------------------------------------------------------------------------------}}}-
330 
331 # -- def expectError ---------------------------------------------------------------------------{{{-
332 
344 @UseStackFrameFromCallerForLogging
345 def expectError (function, args=[], kargs={}, errorRegExp=None, preventReplacingMessageSeverity=False):
346  testHelper = TestHelper.getInstance()
347  logHandler = testHelper.getLogHandler()
348  assert not logHandler is None
349 
350  errors = SuppressedErrors(errorRegExp, Logging_error, preventReplacingMessageSeverity)
351  try:
352  retVal = function(*args, **kargs)
353  finally:
354  errors.handleResult()
355 
356  return retVal
357 # ----------------------------------------------------------------------------------------------}}}-
358 
359 # -- def expectWarning -------------------------------------------------------------------------{{{-
360 
369 @UseStackFrameFromCallerForLogging
370 def expectWarning (function, args=[], kargs={}, warningRegExp=None):
371  testHelper = TestHelper.getInstance()
372  logHandler = testHelper.getLogHandler()
373  assert not logHandler is None
374 
375  warnings = SuppressedWarnings(warningRegExp, Logging_error)
376  try:
377  retVal = function(*args, **kargs)
378  finally:
379  warnings.handleResult()
380 
381  return retVal
382 # ----------------------------------------------------------------------------------------------}}}-
383 
384 # -- def expectWarningAndError ---------------------------------------------------------------------------{{{-
385 
394 @UseStackFrameFromCallerForLogging
395 def expectWarningAndError (function, args=[], kargs={}, errorRegExp=None, warningRegExp=None):
396  testHelper = TestHelper.getInstance()
397  logHandler = testHelper.getLogHandler()
398  assert not logHandler is None
399 
400  warnings = SuppressedWarnings(warningRegExp, Logging_error)
401  errors = SuppressedErrors(errorRegExp, Logging_error)
402  try:
403  retVal = function(*args, **kargs)
404  finally:
405  errors.handleResult()
406  warnings.handleResult()
407 
408  return retVal
409 
410 # -- def ignoreError ---------------------------------------------------------------------------{{{-
411 
424 @UseStackFrameFromCallerForLogging
425 def ignoreError (function, args=(), kargs=None, errorRegExp=None, resultInfoDict=None):
426  def ___doNotLog( _msg ): pass
427  #
428  kargs = kargs or {}
429  testHelper = TestHelper.getInstance()
430  logHandler = testHelper.getLogHandler()
431 
432  assert not logHandler is None
433 
434  hadIgnoredErrors = False
435  errors = SuppressedErrors( errorRegExp, logErrorFunc=None )
436  try:
437  retVal = function(*args, **kargs)
438  finally:
439  hadIgnoredErrors = errors.handleResult()
440  if isinstance( resultInfoDict, dict ):
441  resultInfoDict[ "hadIgnoredErrors" ] = hadIgnoredErrors
442 
443  return retVal
444 # ----------------------------------------------------------------------------------------------}}}-
445 
446 
447 # -- def ignoreWarning ---------------------------------------------------------------------------{{{-
448 
461 @UseStackFrameFromCallerForLogging
462 def ignoreWarning (function, args=(), kargs=None, warningRegExp=None, resultInfoDict=None ):
463  def ___doNotLog( _msg ): pass
464  #
465  kargs = kargs or {}
466  testHelper = TestHelper.getInstance()
467  logHandler = testHelper.getLogHandler()
468 
469  assert not logHandler is None
470 
471  hadIgnoredWarnings = False
472  warnings = SuppressedWarnings( warningRegExp, logErrorFunc=None )
473  try:
474  retVal = function(*args, **kargs)
475  finally:
476  hadIgnoredWarnings = warnings.handleResult()
477  if isinstance( resultInfoDict, dict ):
478  resultInfoDict[ "hadIgnoredWarnings" ] = hadIgnoredWarnings
479 
480  return retVal
481 # ----------------------------------------------------------------------------------------------}}}-
482 
483 # -- def ignoreWarningAndError ---------------------------------------------------------------------------{{{-
484 
497 @UseStackFrameFromCallerForLogging
498 def ignoreWarningAndError (function, args=[], kargs={}, errorRegExp=None, warningRegExp=None, resultInfoDict=None):
499  testHelper = TestHelper.getInstance()
500  logHandler = testHelper.getLogHandler()
501  assert not logHandler is None
502 
503  warnings = SuppressedWarnings(warningRegExp, logErrorFunc=None)
504  errors = SuppressedErrors(errorRegExp, logErrorFunc=None)
505  try:
506  retVal = function(*args, **kargs)
507  finally:
508  hadIgnoredErrors = errors.handleResult()
509  hadIgnoredWarnings = warnings.handleResult()
510  if isinstance( resultInfoDict, dict ):
511  resultInfoDict[ "hadIgnoredWarnings" ] = hadIgnoredWarnings
512  resultInfoDict[ "hadIgnoredErrors" ] = hadIgnoredErrors
513 
514  return retVal
515 # ----------------------------------------------------------------------------------------------}}}-
516 
517 
518 # -- def expectInfo ---------------------------------------------------------------------------{{{-
519 
528 @UseStackFrameFromCallerForLogging
529 def expectInfo (function, args=[], kargs={}, infoRegExp=None, allowUnexpectedMessages=True):
530  testHelper = TestHelper.getInstance()
531  logHandler = testHelper.getLogHandler()
532  assert not logHandler is None
533 
534  expectInfos = ExpectInfos(infoRegExp, allowUnexpectedMessages, Logging_error)
535  try:
536  retVal = function(*args, **kargs)
537  finally:
538  expectInfos.handleResult()
539 
540  return retVal
541 # ----------------------------------------------------------------------------------------------}}}-
542 
543 
544 # Helpers for expected errors or warnings that can be used with a context manager
545 
546 
547 @contextmanager
548 @UseStackFrameFromCallerForLogging
549 def hasNoWarning(warningRegExp=None):
550  """Expect that the expression in the context block issues no warning that matches `warningRegExp`.
551  If `warningRegExp` is not given, no warning is expected at all.
552 
553  Usage:
554  with hasNoWarning():
555  doStuff()
556  """
557  warnings = SuppressedWarnings(warningRegExp, expectWarning=False)
558  try:
559  yield
560  finally:
561  warnings.handleResult()
562 
563 
564 @contextmanager
565 @UseStackFrameFromCallerForLogging
566 def hasWarning(warningRegExp=None):
567  """Expect that the expression in the context block logs a warning that matches `warningRegExp`.
568  If `warningRegExp` is not given, any warning is expected.
569 
570  Usage:
571  with hasWarning():
572  doStuff()
573  """
574  warnings = SuppressedWarnings(warningRegExp, expectWarning=True)
575  try:
576  yield
577  finally:
578  warnings.handleResult()
579 
580 
581 @contextmanager
582 @UseStackFrameFromCallerForLogging
583 def hasError(errorRegExp=None):
584  """Expect that the expression in the context logs an error that matches `errorRegExp`.
585  If `errorRegExp` is not given, any error is expected.
586 
587  Usage:
588  with hasError():
589  doStuff()
590  """
591  errors = SuppressedErrors(errorRegExp, Logging_error)
592  try:
593  yield
594  finally:
595  errors.handleResult()
596 
597 
598 @contextmanager
599 @UseStackFrameFromCallerForLogging
600 def hasWarningAndError(errorRegExp=None, warningRegExp=None):
601  """Expect that the expression in the context logs errors and warnings.
602  See `hasError` and `hasWarning` for the arguments.
603 
604  Usage:
605  with hasWarningAndError():
606  doStuff()
607  """
608  warnings = SuppressedWarnings(warningRegExp, Logging_error)
609  errors = SuppressedErrors(errorRegExp, Logging_error)
610  try:
611  yield
612  finally:
613  errors.handleResult()
614  warnings.handleResult()
615 
616 
617 @contextmanager
618 @UseStackFrameFromCallerForLogging
619 def hasInfo(infoRegExp=None, allowUnexpectedMessages=True):
620  """Expect that the expression in the context logs an info entry that matches `infoRegExp`.
621  If `infoRegExp` is not given, any info message is expected.
622  If `allowUnexpectedMessages` is set to True (the default), messages that do not match `infoRegExp` are ignored,
623  otherwise they are considered a failure.
624 
625  Usage:
626  with hasInfo(infoRegExp=".*Unknown module.*"):
627  doStuff()
628  """
629  expectInfos = ExpectInfos(infoRegExp, allowUnexpectedMessages, Logging_error)
630  try:
631  yield
632  finally:
633  expectInfos.handleResult()
634 
635 # ----------------------------------------------------------------------------------------------}}}-
636 
637 # -- def getResultDirectory --------------------------------------------------------------------{{{-
638 
643  return TestHelper.getInstance().getTestCaseResultDirectory()
644 # ----------------------------------------------------------------------------------------------}}}-
645 
646 # -- def getDataDirectory ----------------------------------------------------------------------{{{-
647 
654  return TestHelper.getInstance().getTestCaseDataDirectory()
655 # ----------------------------------------------------------------------------------------------}}}-
656 
657 # -- def getFileInDataDirectory ----------------------------------------------------------------{{{-
658 
660 def getFileInDataDirectory (filename):
661  return os.path.join(getDataDirectory(), filename)
662 # ----------------------------------------------------------------------------------------------}}}-
663 
664 # -- def existsFileInDataDirectory -------------------------------------------------------------{{{-
665 
668  return os.path.exists(getFileInDataDirectory(filename))
669 # ----------------------------------------------------------------------------------------------}}}-
670 
671 # -- def getExternalDataDirectory --------------------------------------------------------------{{{-
672 
681 def getExternalDataDirectory (name, subDirectory=None):
682  testHelper = TestHelper.getInstance()
683  testCaseContext = testHelper.getTestCaseContext()
684  externalDataVariable = 'TESTCENTER_EXTERNAL_DATA_%s' % name
685  externalDataPlaceholder = '$(%s)' % externalDataVariable
686  externalDataBaseDirectory = testCaseContext.expandFilename(externalDataPlaceholder)
687  if externalDataBaseDirectory == externalDataPlaceholder:
688  raise LookupError('Variable %s could not be expanded. Please define it in your .prefs file.' %
689  externalDataVariable)
690  if os.path.isdir(externalDataBaseDirectory):
691  if subDirectory:
692  return os.path.join(externalDataBaseDirectory, subDirectory)
693  else:
694  testCasePath = testCaseContext.localPath()
695  path = mevis.MLABPackageManager.findPackageIdentifierAndRelativePath(testCasePath)[1]
696  return os.path.join(externalDataBaseDirectory, path)
697  raise ValueError('"%s" is not a directory.' % externalDataBaseDirectory)
698 # ----------------------------------------------------------------------------------------------}}}-
699 
700 # -- def pushChangeSet -------------------------------------------------------------------------{{{-
701 
703  return TestHelper.getInstance().pushChangeSet()
704 # ----------------------------------------------------------------------------------------------}}}-
705 
706 # -- def popChangeSet --------------------------------------------------------------------------{{{-
707 
711  retVal = True
712  if TestHelper.getInstance().getChangeSetStackLength() > 2:
713  retVal = TestHelper.getInstance().popChangeSet()
714  else:
715  retVal = False
716  return retVal
717 # ----------------------------------------------------------------------------------------------}}}-
718 
719 # -- def getHash -------------------------------------------------------------------------------{{{-
720 
727 def getHash (filename, hash="SHA1", encoder="Hex"):
728  result = None
729  # Images are handled using the ImageHash module!
730  b, ext = os.path.splitext(filename)
731  if ext.lower() in (".dcm", ".hdr", ".img", ".bmp", ".jpg", ".jpeg", ".png", ".pnm", ".quo", ".raw", ".tif", ".tiff"):
732  result = Image_calculateHashFromImage(filename, hash, encoder)
733  else:
734  try:
735  h = hashlib.new(hash)
736  with open(filename, "rb") as f:
737  h.update(f.read())
738  if encoder == "Hex":
739  result = h.hexdigest()
740  elif encoder == "Base64":
741  result = h.digest()
742  else:
743  Logging_error("Unknown encoder (%s) selected." % (encoder))
744  except Exception as e:
745  Logging_error("Failed to generate hash: %s." % e)
746  return result
747 # ----------------------------------------------------------------------------------------------}}}-
748 
749 # -- def createExtraTestCaseResult ----------------------------------------------------------------{{{-
750 
758 def createExtraTestCaseResult(testCaseName, package, author, duration, maintainer=None, file=None, line=None, comment=None, showTestFunctionSortingLiterals=False):
759  return ExtraTestCaseResult(testCaseName, package, author, duration, maintainer, file, line, comment, showTestFunctionSortingLiterals)
760 # ----------------------------------------------------------------------------------------------}}}-
761 
762 
763 # -- def addExtraTestCaseResult ----------------------------------------------------------------{{{-
764 
767 def addExtraTestCaseResult(extraTestCaseResult):
768  return TestHelper.getInstance().addExtraTestCaseResult(extraTestCaseResult)
769 # ----------------------------------------------------------------------------------------------}}}-
770 
771 
772 # -- def pushEnvironment -----------------------------------------------------------------------{{{-
773 
777  return TestHelper.getInstance().pushEnvironment()
778 # ----------------------------------------------------------------------------------------------}}}-
779 
780 
781 # -- def popEnvironment -----------------------------------------------------------------------{{{-
782 
786  return TestHelper.getInstance().popEnvironment()
787 # ----------------------------------------------------------------------------------------------}}}-
788 
789 # -- def pushEnvironmentDecorator --------------------------------------------------------------{{{-
790 
791 # Pushes the current environment before the function is called and pops it again after it has finished
793  def wrapper (*args, **kwds):
795  # execute macro
796  try:
797  result = func(*args, **kwds)
798  finally:
800  return result
801  return wrapper
802 # ----------------------------------------------------------------------------------------------}}}-
803 
805  def wrapper (*args, **kwds):
806  wereHighPrecisionTimeStampsEnabled = mevis.MLAB.setHighPrecisionLoggingTimeStampsEnabled(True)
807  try:
808  result = func(*args, **kwds)
809  finally:
810  mevis.MLAB.setHighPrecisionLoggingTimeStampsEnabled(wereHighPrecisionTimeStampsEnabled)
811  return result
812  return wrapper
813 
814 class HtmlDiff(difflib.HtmlDiff):
815 
816  _styles = """
817  table.diff {font-family:monospace;border:medium;margin:0;}
818  .diff_header {background-color:#e0e0e0}
819  td.diff_header {text-align:right}
820  .diff_next {background-color:#c0c0c0}
821  .diff_add {background-color:#aaffaa}
822  .diff_chg {background-color:#ffff77}
823  .diff_sub {background-color:#ffaaaa}
824  tr, td, th {vertical-align:top;}
825  table.diff tbody {
826  white-space: -pre-wrap;
827  white-space: -o-pre-wrap;
828  white-space: -moz-pre-wrap;
829  white-space: pre-wrap;
830  word-wrap: break-word;
831  }"""
832 
833  _table_template = """
834  <table class="diff" id="difflib_chg_%(prefix)s_top"
835  cellspacing="0" cellpadding="1" rules="groups" width="100%%">
836  <colgroup></colgroup> <colgroup></colgroup> <colgroup width="50%%"></colgroup>
837  <colgroup></colgroup> <colgroup></colgroup> <colgroup width="50%%"></colgroup>
838  %(header_row)s
839  <tbody>
840 %(data_rows)s </tbody>
841  </table>"""
842 
843  def __init__(self):
844  difflib.HtmlDiff.__init__(self)
845 
846  def _format_line(self,side,flag,linenum,text):
847  try:
848  linenum = '%d' % linenum
849  id = ' id="%s%s"' % (self._prefix[side],linenum)
850  except TypeError:
851  # handle blank lines where linenum is '>' or ''
852  id = ''
853  # replace those things that would get confused with HTML symbols
854  text=text.replace("&","&amp;").replace(">","&gt;").replace("<","&lt;")
855  if len(text) == 0 or text == '\n':
856  text = '<span style="display: block; background-color:#e0e0e0;">' + text + '</span>'
857  return '<td class="diff_header"%s>%s</td><td>%s</td>' \
858  % (id,linenum,text)
859 
860 # ----------------------------------------------------------------------------------------------}}}-
861 
862 # -- def pushEnvironmentDecorator --------------------------------------------------------------{{{-
863 
864 # Creates a HTML diff from the two files
865 # @param fromLines The array containing the lines of the left string.
866 # @param toLines The array containing the lines of the right string.
867 # @param showOnlyContextOfDiff If true, then only the context lines around the diff are shown, otherwise the whole content is shown.
868 # @param numberOfContextLines The number of context line only the diff context is shown.
869 def createHtmlDiff(fromLines, toLines, showOnlyContextOfDiff=False, numberOfContextLines=10):
870  diff = HtmlDiff().make_file(fromLines, toLines, "", "", showOnlyContextOfDiff, numberOfContextLines)
871  return diff
872 
873 # ----------------------------------------------------------------------------------------------}}}-
874 
875 
876 # Decorator for disabling test functions conditionally
877 def disableTestFunctionIf(condition):
878  def MLABTC__disableTestFunctionIf_Decorator(func):
879  disableTestFunction = condition() if isinstance(condition, collections.abc.Callable) else condition
880  if disableTestFunction:
881  module = sys.modules[func.__module__]
882  if not hasattr(module, 'MLABTC_DISABLED_TEST_FUNCTIONS'):
883  setattr(module, 'MLABTC_DISABLED_TEST_FUNCTIONS', [])
884  module.MLABTC_DISABLED_TEST_FUNCTIONS.append(func.__name__)
885  return func
886  return MLABTC__disableTestFunctionIf_Decorator
887 
888 
889 # Returns True if offscreen rendering is supported by the hardware. Otherwise returns False.
891  context = TestHelper.getInstance().getHelperContext()
892  return context.field('oSSOffscreenRender.supported').value
893 
894 
895 # Creates a subprocess.Popen instance.
896 # @param command A list containing the executable and arguments for the process.
897 # @param wait If True (default), then the function waits for the process to finish. Afterwards it checks the
898 # return code, stderr, and stdout according to the values of expectSuccess and expectFailure. Finally,
899 # a tuple (stdout, stderr, returncode) is returned.
900 # If False, then the subprocess.Popen instance is returned after it was created and started.
901 # @param expectSuccess If True (default), then this function expects the process to return 0.
902 # Stdout/stderr is logged as an info message on success, otherwise as error messages.
903 # @param expectFailure If True, then this function expects the process to return non-zero.
904 # Stdout/stderr is logged as an info message on failure, otherwise as error messages.
905 # @param verbose A boolean to indicate if stdout/stderr should be printed via Macros_INFO.
906 # @param env A dictionary with environment variables. Default is os.environ.
907 # @return A tuple (stdout, stderr, returncode) if wait is True, the subprocess.Popen instance otherwise.
908 def runProcess(command, wait=True, expectSuccess=True, expectFailure=False, verbose=True, env=None):
909  Macros_INFO("Running: " + " ".join(command))
910  if env is None:
911  env = os.environ
912  if wait:
913  p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
914  else:
915  p = subprocess.Popen(command, env=env)
916  if wait:
917  shortCommand = []
918  for c in command:
919  if len(os.path.splitext(c)[1]) > 0:
920  c = os.path.basename(c)
921  shortCommand.append(c)
922  shortCommand = " ".join(shortCommand)
923  stdout, stderr = p.communicate()
924  stdout = stdout.strip()
925  stderr = stderr.strip()
926  stderr = stderr.decode('utf-8','replace')
927  stdout = stdout.decode('utf-8','replace')
928  verifyProcessResult(" ".join(command), shortCommand, p.returncode, stdout, stderr, expectSuccess, expectFailure, verbose)
929  return stdout, stderr, p.returncode
930  else:
931  return p
932 
933 
934 # Helper function for runProcess().
935 # @param command A list containing the executable and arguments for the process.
936 # @param shortCommand A shorter command string to improve readability of log messages.
937 # @param returncode The returncode of the process.
938 # @param stdout The stdout output of the process.
939 # @param stderr The stderr output of the process.
940 # @param expectSuccess If True (default), then this function expects the process to return 0.
941 # Stdout/stderr is logged as an info message on success, otherwise as error messages.
942 # @param expectFailure If True, then this function expects the process to return non-zero.
943 # Stdout/stderr is logged as an info message on failure, otherwise as error messages.
944 # @param verbose A boolean to indicate if stdout/stderr should be printed via Macros_INFO.
945 def verifyProcessResult(command, shortCommand, returncode, stdout, stderr, expectSuccess=True, expectFailure=False, verbose=True):
946  if expectSuccess:
947  if returncode != 0:
948  Macros_ERROR("Command returned non-zero: '" + command + "', return code: " + str(returncode))
949  if len(stderr): Macros_ERROR(stderr)
950  if len(stdout): Macros_ERROR(stdout)
951  else:
952  msg = "Success: " + shortCommand
953  if verbose:
954  if len(stderr): msg += "\n\n" + stderr
955  if len(stdout): msg += "\n\n" + stdout
956  Macros_INFO(msg)
957  elif expectFailure:
958  if returncode == 0:
959  Macros_ERROR("Command returned zero: '" + command + "'")
960  if len(stderr): Macros_ERROR(stderr)
961  if len(stdout): Macros_ERROR(stdout)
962  else:
963  msg = "Expected failure: " + shortCommand
964  if verbose:
965  if len(stderr): msg += "\n\n" + stderr
966  if len(stdout): msg += "\n\n" + stdout
967  Macros_INFO(msg)
968 
969 
970 # Creates and starts a process from the python script using runProcess()
971 # @param wait If True (default), then the function waits for the process to finish. Afterwards it checks the
972 # return code, stderr, and stdout according to the values of expectSuccess and expectFailure. Finally,
973 # a tuple (stdout, stderr, returncode) is returned.
974 # If False, then the subprocess.Popen instance is returned after it was created and started.
975 # @param expectSuccess If True (default), then this function expects the process to return 0.
976 # Stdout/stderr is logged as an info message on success, otherwise as error messages.
977 # @param expectFailure If True, then this function expects the process to return non-zero.
978 # Stdout/stderr is logged as an info message on failure, otherwise as error messages.
979 # @param verbose A boolean to indicate if stdout/stderr should be printed via Macros_INFO.
980 # @param env A dictionary with environment variables. Default is os.environ.
981 # @return A tuple (stdout, stderr, returncode) if wait is True, the subprocess.Popen instance otherwise.
982 def runPythonScript(script, arguments, wait=True, expectSuccess=True, expectFailure=False, verbose=True, env=None):
983  python = mevis.MLABFileManager.getExecutable("MeVisPython")
984  command = [python, "-u", script]
985  command.extend(arguments)
986  return runProcess(command, wait=wait, expectSuccess=expectSuccess, expectFailure=expectFailure, verbose=verbose, env=env)
987 
988 
989 from .ExtraTestCaseResult import ExtraTestCaseResult
990 from .Image import calculateHashFromImage as Image_calculateHashFromImage
991 from .Logging import error as Logging_error, warning as Logging_warning, info as Logging_info
992 from .Macros import ERROR as Macros_ERROR, INFO as Macros_INFO
993 from .Math import compareFloatEqual as Math_compareFloatEqual, \
994  compareFloatLessThan as Math_compareFloatLessThan, \
995  compareFloatLessThanOrEqual as Math_compareFloatLessThanOrEqual
def __init__(self)
Definition: Base.py:843
Decorator to globally enable or disable if the ASSERT_*/EXPECT_* macros log an info message on succes...
Definition: Base.py:144
def pushEnvironment()
Pushes the current environment dictionary on a stack, so that modifications can be applied and be pop...
Definition: Base.py:776
def hardwareSupportsOffscreenRendering()
Definition: Base.py:890
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:462
def pushEnvironmentDecorator(func)
Definition: Base.py:792
def getHash(filename, hash="SHA1", encoder="Hex")
Compute a hash for the content of the given file.
Definition: Base.py:727
def hasWarningAndError(errorRegExp=None, warningRegExp=None)
Definition: Base.py:600
def getTestCaseContext()
Get the context of the test.
Definition: Base.py:93
def compareNotEqual(a, b, comment="", logOnSuccess=None)
Compare the two given values for inequality.
Definition: Base.py:215
def decodeSourceCodeLine(filename, line)
Definition: Base.py:68
def getDataDirectory()
Method to return path to the data files.
Definition: Base.py:653
def compareFloatEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None)
Compare the two given float values if they are epsilon-equal.
Definition: Base.py:256
def getResultDirectory()
Getter for the directory used to save results.
Definition: Base.py:642
def addExtraTestCaseResult(extraTestCaseResult)
Adds an ExtraTestCaseResult object.
Definition: Base.py:767
def compareLessThan(a, b, comment="", logOnSuccess=None)
Compare the two given values if the first one is less than the second.
Definition: Base.py:230
def hasNoWarning(warningRegExp=None)
Definition: Base.py:549
def compareFloatNotEqual(a, b, comment="", epsilon=0.0001, logOnSuccess=None)
Compare the two given float values if they are epsilon-unequal.
Definition: Base.py:270
def hasInfo(infoRegExp=None, allowUnexpectedMessages=True)
Definition: Base.py:619
def clearEncodingCache()
Definition: Base.py:42
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:498
def runPythonScript(script, arguments, wait=True, expectSuccess=True, expectFailure=False, verbose=True, env=None)
Definition: Base.py:982
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:298
def verifyTrue(expr, comment="", logOnSuccess=None, msg=None)
If the given expression evaluates to False, an error is logged.
Definition: Base.py:310
def createExtraTestCaseResult(testCaseName, package, author, duration, maintainer=None, file=None, line=None, comment=None, showTestFunctionSortingLiterals=False)
Creates an ExtraTestCaseResult object.
Definition: Base.py:758
def pushChangeSet()
Push a new ChangeSet to the stack.
Definition: Base.py:702
def runProcess(command, wait=True, expectSuccess=True, expectFailure=False, verbose=True, env=None)
Definition: Base.py:908
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:425
def getPackageList()
Returns the list of available packages.
Definition: Base.py:84
def existsFileInDataDirectory(filename)
Checks if the given file exists in the data directory.
Definition: Base.py:667
def EnableHighPrecisionLoggingTimeStampsDecorator(func)
Definition: Base.py:804
def escapedUnicode(value)
Definition: Base.py:180
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:529
def disableTestFunctionIf(condition)
Definition: Base.py:877
def popEnvironment()
Pops the last pushed environment dictionary from a stack and makes it the current one.
Definition: Base.py:785
def getTestFunctionName()
Returns the name of the current test function.
Definition: Base.py:107
def getFileInDataDirectory(filename)
Returns the absolute filename of a file in the data directory using getDataDirectory().
Definition: Base.py:660
def getModuleName()
Return the name of the currently tested module.
Definition: Base.py:101
def hasError(errorRegExp=None)
Definition: Base.py:583
def expectError(function, args=[], kargs={}, errorRegExp=None, preventReplacingMessageSeverity=False)
Call the given function with the given parameters and expect an error.
Definition: Base.py:345
def verifyProcessResult(command, shortCommand, returncode, stdout, stderr, expectSuccess=True, expectFailure=False, verbose=True)
Definition: Base.py:945
def popChangeSet()
Pop the last ChangeSet from the stack.
Definition: Base.py:710
def LoggingDecorator(func)
Internal decorator used for creating messages in compare methods.
Definition: Base.py:162
def compareFloatLessThan(a, b, comment="", logOnSuccess=None)
Compare the two given float values if the first is less than the second.
Definition: Base.py:283
def setMacrosShouldLogOnSuccess(logOnSuccess)
Globally enables or disables if the ASSERT_*/EXPECT_* macros log an info message on success.
Definition: Base.py:128
def verifyFalse(expr, comment="", logOnSuccess=None, msg=None)
If the given expression evaluates to True, an error is logged.
Definition: Base.py:324
def hasWarning(warningRegExp=None)
Definition: Base.py:566
def shouldLogOnSuccess(logOnSuccess=None)
Definition: Base.py:131
def createHtmlDiff(fromLines, toLines, showOnlyContextOfDiff=False, numberOfContextLines=10)
Definition: Base.py:869
def getExternalDataDirectory(name, subDirectory=None)
Method to return path to the external data directory.
Definition: Base.py:681
def compareEqual(a, b, comment="", logOnSuccess=None)
Compare the two given values for equality.
Definition: Base.py:199
def expectWarning(function, args=[], kargs={}, warningRegExp=None)
Call the given function with the given parameters and expect a warning.
Definition: Base.py:370
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:395
def getSourceFileEncoding(filename)
Definition: Base.py:46
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:243