TestCenter Reference
TestHelper.py
Go to the documentation of this file.
2# Copyright 2007, 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
16
17# -- system imports ----------------------------------------------------------------------------{{{-
18import base64
19import os
20import sys
21
22import _thread
23import traceback
24# ----------------------------------------------------------------------------------------------}}}-
25
26# -- local imports -----------------------------------------------------------------------------{{{-
27import mevis
28
29from TestSupport.ChangeSet import ChangeSet
30from TestSupport.MessageFilters import MessageHandling, ErrorMessageFilter, WarningMessageFilter, InfoMessageFilter
31# ----------------------------------------------------------------------------------------------}}}-
32
33_mlab_do_not_reload = True # exempt this module from Python module reloading, value is currently ignored
34
35gCurrentSpecialMessage = ""
36gSpecialMessageMarker = "<MLAB TC=1/>"
37gEnablePrettyLogging = False
38gLogInfoMessages = True
39
41 global gCurrentSpecialMessage
42 msg = gCurrentSpecialMessage
43 gCurrentSpecialMessage = ""
44 return msg
45
46def emitSpecialMessage(loggingMethod, file, line, type, message, escapeHtml=True, formattedStack=''):
47 global gCurrentSpecialMessage
48 if escapeHtml:
49 escapedMsg = mevis.MLAB.escapeString(message)
50 escapedMsg = escapedMsg.replace("\n", "<br>")
51 else:
52 escapedMsg = message
53
54 if sys.version_info.major >= 3:
55 if isinstance(formattedStack, str):
56 formattedStack = formattedStack.encode()
57
58 encodedStackTrace = str(base64.b64encode(formattedStack))
59 # store internal message to be used from LogHandler:
60 specialMessage = "##M[%s|%s|%s|%s]: %s" % (file, line, type, encodedStackTrace, escapedMsg)
61 if gEnablePrettyLogging:
62 gCurrentSpecialMessage = specialMessage
63 escapedMsg = "<font style='font-weight:normal'><font color=black>" + mevis.MLAB.createHyperLinkWithLine(file, line) + "(" + str(line) + "):</font></font> " + escapedMsg
64 escapedMsg += gSpecialMessageMarker
65 loggingMethod(escapedMsg)
66 else:
67 loggingMethod(specialMessage)
68
69def emitSpecialCommand(file, line, type, fileList, message, escapeHtml=True):
70 global gCurrentSpecialMessage
71 if escapeHtml:
72 escapedMsg = mevis.MLAB.escapeString(message)
73 else:
74 escapedMsg = message
75
76 # store internal message to be used from LogHandler:
77 specialMessage = "##C[%s|%s|%s:%s] %s" % (file, line, type, ",".join(fileList), str(escapedMsg))
78 if gEnablePrettyLogging:
79 gCurrentSpecialMessage = specialMessage
80 escapedMsg = "<font style='font-weight:normal'><font color=black>" + mevis.MLAB.createHyperLinkWithLine(file, line) + "(" + str(line) + "):</font></font> " + escapedMsg
81 for fileInList in fileList:
82 if '|' in fileInList:
83 fileInList = fileInList[fileInList.index("|")+1:]
84 escapedMsg += " " + mevis.MLAB.createHyperLink(fileInList)
85 escapedMsg += gSpecialMessageMarker
86 mevis.MLAB.logHTML(escapedMsg)
87 else:
88 mevis.MLAB.logHTML(specialMessage)
89
90
92 """ This decorator is used to specify the stack frame that is used to detect the location while logging.
93 It uses the stack frame that calls the given function. Functions with this decorator can call each other
94 recursively. The first stack frame of such a recursion is then used. """
95 def setLoggingCallerStackFrameWrapper(*args, **kwargs):
97 return func(*args, **kwargs)
98 return setLoggingCallerStackFrameWrapper
99
100# Alias for backward compatibility of old TestCases
101IncreaseLoggingStackDepthDecorator = UseStackFrameFromCallerForLogging
102
103
105 def __init__(self, stackDepth):
106 self.__testHelper = TestHelper.getInstance()
107 self.__stackDepth = stackDepth
108 self.__hasSetStackFrame = False
109
110 def __enter__(self):
111 if not self.__testHelper.hasCallerStackFrame():
112 stackFrame = self.getCallingStackFrame(self.__stackDepth+1)
113 self.__testHelper.setCallerStackFrame(stackFrame)
114 self.__hasSetStackFrame = True
115
116 def __exit__(self, *_):
117 if self.__hasSetStackFrame:
118 self.__testHelper.resetCallerStackFrame()
119
120 def getCallingStackFrame(self, stackDepth):
121 return sys._getframe(stackDepth+1)
122
123# Returns pattern attribute or repr(exp) if the object does not have a pattern
125 try:
126 result = exp.pattern
127 except:
128 result = repr(exp)
129 return result
130
131
132
133# Helper object to supress errors,
134# make sure that you call handleResult() in a finally: clause
135# If no logErrorFunc it given, matching error will just be ignored, not expected, so no error will be logged if no match was found.
137 def __init__(self, errorRegExp, logErrorFunc, preventReplacingMessageSeverity=False):
138 self.__logErrorFunc = logErrorFunc
139 self.__errorRegExp = errorRegExp
140 handling = MessageHandling.EXPECT if logErrorFunc is not None else MessageHandling.IGNORE
141 self.__messageFilter = ErrorMessageFilter( handling, errorRegExp )
142 self.__preventReplacingMessageSeverity = preventReplacingMessageSeverity
144 self.__enableErrorHandling( True )
145
146 def __enableErrorHandling( self, enable ):
147 logHandler = TestHelper.getInstance().getLogHandler()
148 logHandler.pushOrPopMessageFilter(enable, self.__messageFilter)
150 if enable:
151 self.__previousKeepMessageSeverity = logHandler.setKeepMessageSeverity(True)
152 else:
153 if self.__previousKeepMessageSeverity != None:
154 logHandler.setKeepMessageSeverity(self.__previousKeepMessageSeverity)
156
157 def handleResult(self):
158 mevis.MLAB.priv().flushPendingMessages()
159 hasCaughtError = self.__messageFilter.hadMatch
160 self.__enableErrorHandling( False )
161 if self.__logErrorFunc and not hasCaughtError:
162 self.__logErrorFunc("Expected error did not occur: " + regexpToString(self.__errorRegExp))
163 return hasCaughtError
164
165
167 """Helper object to supress warnings.
168 Make sure that you call handleResult() in a finally: clause.
169 If no logErrorFunc it given, matching warnings will just be ignored, not expected,
170 so no error will be logged if no match was found.
171 """
172 def __init__(self, warningRegExp, logErrorFunc=None, expectWarning=True):
173 self.__logErrorFunc = logErrorFunc
174 handling = MessageHandling.EXPECT if logErrorFunc is not None else MessageHandling.IGNORE
175 self.__warningRegExp = warningRegExp
176 self.__expectWarning = expectWarning
177 self.__messageFilter = WarningMessageFilter( handling, warningRegExp )
178 self.__enableWarningHandling( True )
179
180 def __enableWarningHandling( self, enable ):
181 TestHelper.getInstance().getLogHandler().pushOrPopMessageFilter(enable, self.__messageFilter)
182
183 def handleResult(self):
184 mevis.MLAB.priv().flushPendingMessages()
185 hasCaughtWarning = self.__messageFilter.hadMatch
186 self.__enableWarningHandling(False)
187 if self.__logErrorFunc and hasCaughtWarning != self.__expectWarning:
188 if self.__expectWarning:
189 self.__logErrorFunc("Expected warning did not occur: " + regexpToString(self.__warningRegExp))
190 else:
191 self.__logErrorFunc("Unexpected warning did occur: " + regexpToString(self.__warningRegExp))
192
193 return hasCaughtWarning == self.__expectWarning
194
195
196# Helper object to expect infos,
197# make sure that you call handleResult() in a finally: clause
199 def __init__(self, infoRegExp, allowUnexpectedMessages, logErrorFunc):
200 self.__infoRegExp = infoRegExp
201 self.__allowUnexpectedMessages = allowUnexpectedMessages
202 self.__logErrorFunc = logErrorFunc
203 self.__messageFilter = InfoMessageFilter( MessageHandling.EXPECT, infoRegExp )
204 self.__enableInfoHandling( True )
205
206 def __enableInfoHandling( self, enable ):
207 TestHelper.getInstance().getLogHandler().pushOrPopMessageFilter(enable, self.__messageFilter)
208
209 def handleResult(self):
210 mevis.MLAB.priv().flushPendingMessages()
211 hadUnexpectedInfo = self.__messageFilter.hadNonMatch
212 hadExpectedInfo = self.__messageFilter.hadMatch
213 self.__enableInfoHandling( False )
214 if hadUnexpectedInfo:
215 if not self.__allowUnexpectedMessages:
216 self.__logErrorFunc("Unexpected info message occurred")
217 if not hadExpectedInfo:
218 self.__logErrorFunc("Expected info message did not occur: " + regexpToString(self.__infoRegExp))
219
220# -- class TC_ChangeSet ------------------------------------------------------------------------{{{-
222 # -- def __init__ ----------------------------------------------------------------------------{{{-
223
225 def __init__ (self, context):
226 ChangeSet.__init__(self, context)
227 # --------------------------------------------------------------------------------------------}}}-
228
229 # -- def _logDecorator -----------------------------------------------------------------------{{{-
230 def _logDecorator (func):
231 def wrapper (*args, **kwds):
233 loggingMethod, message = func(*args, **kwds)
234 if gLogInfoMessages or (loggingMethod != mevis.MLAB.logHTML):
235 stackFrame = TestHelper.getInstance().getCallerStackFrame()
236 stackLine = traceback.extract_stack(stackFrame, 1)[0]
237 emitSpecialMessage(loggingMethod, stackLine[0], stackLine[1], "ChangeSet", message, escapeHtml=True)
238 return wrapper
239 # --------------------------------------------------------------------------------------------}}}-
240
241 # -- def _logInfo ----------------------------------------------------------------------------{{{-
242 @_logDecorator
243 def _logInfo (self, message):
244 return mevis.MLAB.logHTML, message
245 # --------------------------------------------------------------------------------------------}}}-
246
247 # -- def _logError ---------------------------------------------------------------------------{{{-
248 @_logDecorator
249 def _logError (self, message):
250 return mevis.MLAB.logErrorHTML, message
251 # --------------------------------------------------------------------------------------------}}}-
252# ----------------------------------------------------------------------------------------------}}}-
253
254# Exception that is raised when a test execution is cancelled.
255# This may happen because of the stop-on-first-error feature
256# or because the user cancels a test run.
257class CancelTestException(Exception):
258 pass
259
260# -- class TestHelper --------------------------------------------------------------------------{{{-
261
265class TestHelper :
266 # -- member variables ------------------------------------------------------------------------{{{-
267
268 _mCtx = None
269
270 _mTestCaseContext = None
271
272 _mHelperModule = None
273
274 _mResultDirectory = None
275
276 _mPackageList = None
277
278
279 _mTestCaseName = None
280
281 _mModuleName = None
282
283
284 _mTestCaseDataDirectory = None
285
286 _mTestCaseResultDirectory = None
287
288
289 _mChangeSetStack = None
290
291
292 _mLogHandler = None
293
294
295 _mStackFrame = None
296
297
298 _mEnvironmentStack = None
299
300
301 _mExtraTestCaseResults = None
302
303
304 _mMacrosLogOnSuccess = True
305
306
307 __lockObj = _thread.allocate_lock()
308
309 __instance = None
310 # --------------------------------------------------------------------------------------------}}}-
311
312 # -- def __new__ -----------------------------------------------------------------------------{{{-
313
314 def __new__ (self, *args, **kargs):
315 return self.getInstance(*args, **kargs)
316 # --------------------------------------------------------------------------------------------}}}-
317
318 # -- def __init__ ----------------------------------------------------------------------------{{{-
319
322 def __init__ (self, *args, **kargs):
323 pass
324 # --------------------------------------------------------------------------------------------}}}-
325
326 # -- def getInstance -------------------------------------------------------------------------{{{-
327
328 @classmethod
329 def getInstance (self, *args, **kargs):
330 '''Static method to have a reference to **THE UNIQUE** instance'''
331 if len(kargs) not in (0, 2):
332 raise Exception("Singleton's getInstance must be called with context and result directory arguments or without any!")
333
334 initialize = (len(kargs) == 2)
335 # Critical section start
336 self.__lockObj.acquire()
337 try:
338 if self.__instance is None:
339 # (Some exception may be thrown...)
340 if not "context" in kargs or not "resultDir" in kargs:
341 raise Exception("Singleton must be initialized with context and result directory!")
342 # Initialize **the unique** instance
343 self.__instance = object.__new__(self)
344
345 if initialize:
346 '''Initialize object **here**, as you would do in __init__()...'''
347 self._mResultDirectory = kargs["resultDir"]
348 self._mCtx = kargs["context"]
349
350 # The FunctionalTesting module contains some helpful modules that are used in the methods.
351 if self._mHelperModule:
352 self._mHelperModule.remove()
353 self._mHelperModule = self._mCtx.addModule(mevis.MLAB.moduleLiteral("TestCenterHelperModule"))
354
355 self._mChangeSetStack = []
356 self._mEnvironmentStack = []
357
358 finally:
359 # Exit from critical section whatever happens
360 self.__lockObj.release()
361 # Critical section end
362 return self.__instance
363 # --------------------------------------------------------------------------------------------}}}-
364
365 # -- def hasInstance -------------------------------------------------------------------------{{{-
366
367 @classmethod
368 def hasInstance(self):
369 return self.__instance is not None
370 # --------------------------------------------------------------------------------------------}}}-
371
372 # -- def setPackageList ----------------------------------------------------------------------{{{-
373
374 def setPackageList (self, packageList):
375 self._mPackageList = packageList
376 # --------------------------------------------------------------------------------------------}}}-
377
378 # -- def getPackageList ----------------------------------------------------------------------{{{-
379
380 def getPackageList (self):
381 return self._mPackageList
382 # --------------------------------------------------------------------------------------------}}}-
383
384 # -- def getGlobalContext --------------------------------------------------------------------{{{-
386 return self._mCtx
387 # --------------------------------------------------------------------------------------------}}}-
388
389 # -- def setTestCaseContext ------------------------------------------------------------------{{{-
390
391 def setTestCaseContext (self, ctx):
392 self._mTestCaseContext = ctx
394 self._mExtraTestCaseResults = []
395 # --------------------------------------------------------------------------------------------}}}-
396
397 # -- def setMacrosLogOnSuccess ---------------------------------------------------------------{{{-
398 def setMacrosLogOnSuccess (self, macrosLogOnSuccess):
400 # --------------------------------------------------------------------------------------------}}}-
401
402 # -- def getMacrosLogOnSuccess ---------------------------------------------------------------{{{-
405 # --------------------------------------------------------------------------------------------}}}-
406
407 # -- def getTestCaseContext ------------------------------------------------------------------{{{-
408
410 return self._mTestCaseContext
411 # --------------------------------------------------------------------------------------------}}}-
412
413 # -- def getHelperContext --------------------------------------------------------------------{{{-
415 return self._mHelperModule
416 # --------------------------------------------------------------------------------------------}}}-
417
418 # -- def getResultDirectory ------------------------------------------------------------------{{{-
420 return self._mResultDirectory
421 # --------------------------------------------------------------------------------------------}}}-
422
423 # -- def setModuleName -----------------------------------------------------------------------{{{-
424 def setModuleName (self, moduleName):
425 self._mModuleName = moduleName
426 # --------------------------------------------------------------------------------------------}}}-
427
428 # -- def getModuleName -----------------------------------------------------------------------{{{-
429 def getModuleName (self):
430 return self._mModuleName
431 # --------------------------------------------------------------------------------------------}}}-
432
433 # -- def setTestCaseName ---------------------------------------------------------------------{{{-
434 def setTestCaseName (self, testCaseName):
435 self._mTestCaseName = testCaseName
436 # --------------------------------------------------------------------------------------------}}}-
437
438 # -- def getTestCaseName ---------------------------------------------------------------------{{{-
439 def getTestCaseName (self):
440 return self._mTestCaseName
441 # --------------------------------------------------------------------------------------------}}}-
442
443 # -- def setTestCaseDataDirectory ------------------------------------------------------------{{{-
444 def setTestCaseDataDirectory (self, testCaseDataPath):
445 self._mTestCaseDataDirectory = testCaseDataPath
446 # --------------------------------------------------------------------------------------------}}}-
447
448 # -- def getTestCaseDataDirectory ------------------------------------------------------------{{{-
451 # --------------------------------------------------------------------------------------------}}}-
452
453 # -- def setTestCaseResultDirectory ------------------------------------------------------------{{{-
454 def setTestCaseResultDirectory (self, testCaseName, functionName):
455 self._mTestCaseResultDirectory = os.path.join(self.getResultDirectory(), "files", testCaseName)
456 if functionName != None:
457 self._mTestCaseResultDirectory = os.path.join(self._mTestCaseResultDirectory, functionName)
458 # --------------------------------------------------------------------------------------------}}}-
459
460 # -- def getTestCaseResultDirectory ------------------------------------------------------------{{{-
462 if self._mTestCaseResultDirectory and not os.path.isdir(self._mTestCaseResultDirectory):
463 os.makedirs(self._mTestCaseResultDirectory)
464 return self._mTestCaseResultDirectory
465 # --------------------------------------------------------------------------------------------}}}-
466
467 # -- def pushChangeSet -----------------------------------------------------------------------{{{-
468
470 def pushChangeSet (self):
472 return len(self._mChangeSetStack)-1
473 # --------------------------------------------------------------------------------------------}}}-
474
475 # -- def popChangeSet ------------------------------------------------------------------------{{{-
476
479 def popChangeSet (self):
480 retVal = True
481 if len(self._mChangeSetStack) > 0:
482 cs = self._mChangeSetStack.pop()
483 cs.autoRevert()
484 else:
485 retVal = False
486 return retVal
487 # --------------------------------------------------------------------------------------------}}}-
488
489 # -- def popChangeSet ------------------------------------------------------------------------{{{-
490
491 def clearChangeSet (self):
492 for changeSet in self._mChangeSetStack:
493 changeSet.enableAutoRevert(False)
494 while self.popChangeSet():
495 pass
496 # --------------------------------------------------------------------------------------------}}}-
497
498# -- def getChangeSet ------------------------------------------------------------------------{{{-
499
500 def getChangeSet (self):
501 retVal = None
502 if len(self._mChangeSetStack) > 0:
503 retVal = self._mChangeSetStack[-1]
504 return retVal
505 # --------------------------------------------------------------------------------------------}}}-
506
507 # -- def getChangeSetStackLength -------------------------------------------------------------{{{-
508
511 return len(self._mChangeSetStack)
512 # --------------------------------------------------------------------------------------------}}}-
513
514 # -- def setLogHandler -----------------------------------------------------------------------{{{-
515
517 def setLogHandler (self, logHandler):
518 self._mLogHandler = logHandler
519 # --------------------------------------------------------------------------------------------}}}-
520
521 # -- def unsetLogHandler ---------------------------------------------------------------------{{{-
522
523 def unsetLogHandler (self):
524 self._mLogHandler = None
525 # --------------------------------------------------------------------------------------------}}}-
526
527 # -- def getLogHandler -----------------------------------------------------------------------{{{-
528
531 def getLogHandler (self):
532 return self._mLogHandler
533 # --------------------------------------------------------------------------------------------}}}-
534
535 def setCallerStackFrame(self, stackFrame):
536 self._mStackFrame = stackFrame
537
539 return self._mStackFrame
540
542 return self._mStackFrame != None
543
545 self._mStackFrame = None
546
547 # -- def addExtraTestCaseResult --------------------------------------------------------------{{{-
548 def addExtraTestCaseResult (self, extraTestCaseResult):
549 self._mExtraTestCaseResults.append(extraTestCaseResult)
550 # --------------------------------------------------------------------------------------------}}}-
551
552 # -- def takeExtraTestCaseResults ------------------------------------------------------------{{{-
555 self._mExtraTestCaseResults = []
556 return l
557 # --------------------------------------------------------------------------------------------}}}-
558
559 # -- def pushEnvironment ---------------------------------------------------------------------{{{-
560
561 def pushEnvironment (self):
562 self._mEnvironmentStack.append(os.environ)
563 os.environ = dict(os.environ)
564 # --------------------------------------------------------------------------------------------}}}-
565
566 # -- def popEnvironment ----------------------------------------------------------------------{{{-
567
569 def popEnvironment (self):
570 if len(self._mEnvironmentStack) > 0:
571 os.environ = self._mEnvironmentStack.pop()
572 return True
573 return False
574 # --------------------------------------------------------------------------------------------}}}-
575
576# ----------------------------------------------------------------------------------------------}}}-
577
Class to handle field changes and make them revertable.
Definition ChangeSet.py:33
__init__(self, infoRegExp, allowUnexpectedMessages, logErrorFunc)
__init__(self, errorRegExp, logErrorFunc, preventReplacingMessageSeverity=False)
__init__(self, warningRegExp, logErrorFunc=None, expectWarning=True)
__init__(self, context)
The default constructor.
pushChangeSet(self)
Push a new ChangeSet to the stack.
popEnvironment(self)
Pops an environment dictionary from the stack and makes it the current environment.
getLogHandler(self)
Get the current log handler.
_mStackFrame
The stack frame of the calling function that is used when logging events.
_mHelperModule
The context of the helper module.
_mTestCaseName
The name of the current test case.
setLogHandler(self, logHandler)
Set the current log handler.
setTestCaseName(self, testCaseName)
_mChangeSetStack
A stack of ChangeSets used to revert changes to field values.
_mExtraTestCaseResults
A list of extra test case results.
setMacrosLogOnSuccess(self, macrosLogOnSuccess)
bool _mMacrosLogOnSuccess
This is the default value for the 'logOnSuccess' parameter (if it is None) of the ASSERT_*/EXPECT_* m...
__new__(self, *args, **kargs)
The new method.
clearChangeSet(self)
Clear the change set stakc without restoring the field values.
popChangeSet(self)
Pop a ChangeSet from the stack and delete it.
__init__(self, *args, **kargs)
The default constructor.
_mEnvironmentStack
A stack of environment dictionaries used to revert changes to the environment.
_mModuleName
The name of the currently tested module.
setTestCaseContext(self, ctx)
Set the context of the test.
getChangeSet(self)
Return the ChangeSet on top of the stack.
_mTestCaseContext
The current test's context.
getChangeSetStackLength(self)
Get the length of the ChangeSet stack.
_mTestCaseDataDirectory
The path to the test case data.
setPackageList(self, packageList)
Set the list of available packages.
addExtraTestCaseResult(self, extraTestCaseResult)
setCallerStackFrame(self, stackFrame)
pushEnvironment(self)
Pushes the current environment to the stack.
setTestCaseResultDirectory(self, testCaseName, functionName)
hasInstance(self)
Check if an instance has been initialized.
getPackageList(self)
Return the list of available packages.
unsetLogHandler(self)
Unset the current log handler.
setTestCaseDataDirectory(self, testCaseDataPath)
_mPackageList
The list of packages that should be assumed to be available.
getTestCaseContext(self)
Get the context of the test.
getInstance(self, *args, **kargs)
The default constructor.
_mTestCaseResultDirectory
The path to the results.
_mLogHandler
The log handler that can be used to handle the reports.
_mResultDirectory
The directory used to save results.
emitSpecialMessage(loggingMethod, file, line, type, message, escapeHtml=True, formattedStack='')
Definition TestHelper.py:46
emitSpecialCommand(file, line, type, fileList, message, escapeHtml=True)
Definition TestHelper.py:69
UseStackFrameFromCallerForLogging(func)
Definition TestHelper.py:91