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