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