TestCenter Reference
PythonUnitTest.py
Go to the documentation of this file.
1 #
2 # Copyright 2014, 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 from builtins import range
13 
14 """Tools for running MeVisLab tests as Python unit tests
15 
16 The functions, decorators and classes defined in this module allow you to
17 wrap your MeVisLab FunctionalTestCase in a unittest.TestCase so that the
18 test case can be executed using the standard Python unit testing framework.
19 
20 To wrap a FunctionalTestCase in a unittest.TestCase add the following at the
21 end of the test case file.
22 
23 from TestSupport import PythonUnitTest
24 
25 @PythonUnitTest.addTestsToMeVisLabTestCase(globals())
26 class NameOfFunctionalTestCase(PythonUnitTest.MeVisLabTestCase):
27  pass
28 
29 @note: NameOfFunctionalTestCase has to be identical with the name of the
30 FunctionalTestCase specified in the .def file. Otherwise, the MeVisLab
31 TestCenter that is used to execute the tests will not find the tests.
32 
33 The above is sufficient to allow PyCharm(TM) to run your MeVisLab functional
34 tests as any other Python unit tests.
35 
36 If you also want to run your tests from the command line, then, additionally,
37 add the following at the end of the file with the MeVisLab tests (just as for
38 any old Python unit test).
39 
40 if __name__ == '__main__':
41  import unittest
42  unittest.main()
43 
44 @note: Wrapping ITERATIVETEST functions only works if the generation of the
45 test dictionary returned by the ITERATIVETEST function does not require
46 functionality that only works if the test runs in MeVisLab. For example,
47 TestSupport.Base.getDataDirectory() cannot be used in ITERATIVETEST. Instead
48 you can use something like
49 
50  if __file__:
51  # use __file__ if running outside of MeVisLab (where Base.getDataDirectory() doesn't work)
52  dataDirectory = os.path.join(os.path.dirname(__file__), 'Data')
53  else:
54  # use Base.getDataDirectory() if running in MeVisLab (where __file__ is not set)
55  dataDirectory = Base.getDataDirectory()
56 
57 """
58 
59 import logging
60 import os
61 import re
62 import shutil
63 import sys
64 import tempfile
65 import unittest
66 from xml.etree import cElementTree as ElementTree
67 
68 
69 mlabroot = os.environ.get('MLAB_ROOT')
70 
71 
72 @unittest.skip('Test is marked as DISABLED')
74  pass
75 
76 
77 NormalTestRegExp = re.compile(r'^(?:DISABLED_)?TEST(\d*_\w+)$')
78 IterativeTestRegExp = re.compile(r'^(?:DISABLED_)?ITERATIVETEST(\d*_\w+)$')
79 
80 
82  def inner(testCaseClass):
83  # do nothing if we are running inside MeVisLab
84  runningInsideMeVisLab = hasattr(globals.get('ctx'), 'name')
85  if runningInsideMeVisLab:
86  return testCaseClass
87 
88  MLABTC_DISABLED_TEST_FUNCTIONS = globals.get('MLABTC_DISABLED_TEST_FUNCTIONS', [])
89  for name in globals:
90  skip = name.startswith('DISABLED_') or name in MLABTC_DISABLED_TEST_FUNCTIONS
91  m = NormalTestRegExp.match(name)
92  if m:
93  testName = m.group(1)
94  testCaseClass.addTest(testName, skip)
95  continue
96  m = IterativeTestRegExp.match(name)
97  if m:
98  basename = m.group(1)
99  testDict = globals[name]()[0]
100  if isinstance(testDict, (list, tuple)):
101  # test ids are zero-padded numbers
102  width = len(str(len(testDict)-1))
103  testDict = [str(n).zfill(width) for n in range(len(testDict))]
104  for key in testDict:
105  testName = '%s_%s' % (basename, key)
106  testCaseClass.addTest(testName, skip)
107  continue
108  return testCaseClass
109  return inner
110 
111 
112 class MeVisLabTestCase(unittest.TestCase):
113  master = None
114  tempDirectory = None
115 
116  def __init__(self, methodName='runTest'):
117  super(MeVisLabTestCase, self).__init__(methodName)
118 
119  if mlabroot is None:
120  raise Exception('MLAB_ROOT is not defined')
121 
122  testCenterAdvancedPath = os.path.join(mlabroot, 'MeVisLab', 'Standard', 'Modules', 'Macros', 'Tests', 'TestCenterAdvanced')
123  if not testCenterAdvancedPath in sys.path:
124  sys.path.insert(0, testCenterAdvancedPath)
125 
126  @classmethod
127  def setUpClass(cls):
128  cls.tempDirectorytempDirectory = tempfile.mkdtemp()
129  # print 'Using temp folder %s' % cls.tempDirectory
130  configFilename = os.path.join(cls.tempDirectorytempDirectory, 'tc_config.xml')
131  writeTestCenterConfigFile(configFilename,
132  executablePath=os.path.join(mlabroot, 'MeVisLab'),
133  resultDirectory=cls.tempDirectorytempDirectory,
134  reportDirectory=cls.tempDirectorytempDirectory)
135  # with open(configFilename, 'r') as f:
136  # print f.read()
137 
138  env = _getEnvironmentForTestCenter(mlabroot)
139 
140  logging.basicConfig(level=logging.INFO,
141  format='%(asctime)s %(levelname)-8s %(message)s')
142  logging.getLogger('TestCenter').disabled = True
143 
144  from Master import Master
145  cls.mastermaster = Master(cfgFile=configFilename, slaveEnvironment=env)
146 
147  @classmethod
148  def tearDownClass(cls):
149  if cls.mastermaster is not None:
150  cls.mastermaster.stopSlave()
151  if cls.tempDirectorytempDirectory is not None:
152  shutil.rmtree(cls.tempDirectorytempDirectory)
153  pass
154 
155  @classmethod
156  def addTest(cls, testName, skip):
157  testFuncName = 'test%s' % testName
158  if skip:
159  testFunc = skippedDummyTest
160  else:
161  testIdentifier = (cls.__name__, 'TEST%s' % testName)
162  testFunc = cls.createRunTestMethodcreateRunTestMethod(testFuncName, testIdentifier)
163  setattr(cls, testFuncName, testFunc)
164 
165  @classmethod
166  def createRunTestMethod(cls, testFuncName, testIdentifier):
167  def formatStack(event):
168  stack = event.find('Stack')
169  stackText = stack.text if stack is not None else 'No stack trace available'
170  return 'Stack trace (most recent call last):\n%s' % stackText
171 
172  def runTestMethod(testCaseInstance):
173  cls.mastermaster.setTestCases([cls.__name__])
174  testFunctionDict = {testIdentifier[0]: [testIdentifier[1]]}
175  result = testCaseInstance.master.run(testFunctionDict, stopSlaveAfterTestRun=False)
176  testCaseInstance.master.reset()
177 
178  if result is None:
179  raise Exception("Test failed: TestRun did not return anything.")
180 
181  # find the results of running the test function in the TestRun XML tree
182  functionNode = result.find('Results/TestCase/Function')
183  if functionNode is None:
184  raise Exception('No function found in\n%s' % ElementTree.tostring(result))
185  # print ElementTree.tostring(functionNode)
186  if functionNode.get('is_disabled') == 'True':
187  raise unittest.SkipTest('Skipped for unknown reasons. No test method should have been created for this test.')
188 
189  messages = []
190  status = int(functionNode.get('status'))
191  if status == 2:
192  messages.append('%s failed' % testIdentifier[1])
193  if status == 3:
194  messages.append('Test timed out')
195  if status == 4:
196  messages.append('MeVisLab crashed')
197 
198  # print all non-Info events
199  for event in functionNode.iter('Event'):
200  eventType = event.get('type')
201  # filename = event.get('file')
202  # lineNumber = event.get('line')
203  timestamp = event.get('timestamp')
204  eventMessageText = _stripTags(event.find('Message').text)
205  if eventType == 'Info':
206  pass
207  elif eventType == 'Warning':
208  print('%s %s: %s' % (timestamp, eventType, eventMessageText))
209  elif eventType in ['Error', 'ML Error', 'Timeout', 'Crashed', 'ScriptError', 'SemanticError']:
210  messages.append('%s: %s' % (eventType, eventMessageText))
211  messages.append(formatStack(event))
212  else:
213  print('Unknown event type "%s"' % eventType)
214  print('%s %s: %s' % (timestamp, eventType, eventMessageText))
215 
216  # fail test if test failed or had error in MeVisLab
217  if status == 2:
218  testCaseInstance.fail('\n'.join(messages))
219  if status == 3 or status == 4:
220  raise Exception('\n'.join(messages))
221 
222  runTestMethod.__name__ = testFuncName
223  return runTestMethod
224 
225 
226 def writeTestCenterConfigFile(configFilename,
227  executablePath='',
228  resultDirectory='',
229  reportDirectory=''):
230  import Config
231  config = Config.Configuration(configFilename, autoSave=False)
232  config.setMLABExecutablePath(executablePath)
233  config.setResultDir(resultDirectory)
234  config.getReportOptions().directory = reportDirectory
235  config.save()
236 
237 
238 def _getEnvironmentForTestCenter(mlabroot):
239  buildSystemRoot = os.path.join(mlabroot, 'MeVis', 'BuildSystem', 'Sources')
240  sys.path.insert(0, buildSystemRoot)
241 
242  import BuildSystem.SystemUtils
243 
244  # remove path again to prevent name clash of IPC modules
245  sys.path.remove(buildSystemRoot)
246 
247  testCenterEnvironment = dict(os.environ)
248  if testCenterEnvironment.get('PYCHARM_HOSTED') == '1':
249  # delete the PYTHONPATH set by PyCharm because it is wrong for TestCenter.py
250  del testCenterEnvironment['PYTHONPATH']
251 
252  requiredPackages = ['MeVis/ThirdParty', 'MeVis/Foundation', 'MeVisLab/IDE']
253  additionalBinPaths = [os.path.join(mlabroot, package, 'bin') for package in requiredPackages]
254  additionalBinPaths.append(os.path.join(mlabroot, 'MeVis/ThirdParty/Sources/Qt4/qt/bin'))
255  additionalLibPaths = [os.path.join(mlabroot, package, 'lib') for package in requiredPackages]
256  additionalLibPaths.append(os.path.join(mlabroot, 'MeVis/ThirdParty/Python'))
257  additionalLibPaths.append(os.path.join(mlabroot, 'MeVis/ThirdParty/Sources/Qt4/qt/lib'))
258  additionalBinPaths = [os.path.normpath(path) for path in additionalBinPaths]
259  additionalLibPaths = [os.path.normpath(path) for path in additionalLibPaths]
260  BuildSystem.SystemUtils.addPathsToExecutableSearchPaths(additionalBinPaths, environmentDict=testCenterEnvironment)
261  BuildSystem.SystemUtils.addPathsToLibrarySearchPaths(additionalLibPaths, environmentDict=testCenterEnvironment)
262 
263  return testCenterEnvironment
264 
265 
266 _stripTagsRE = re.compile(r'(<!--.*?-->|<[^>]*>)')
267 
268 def _stripTags(s):
269  """ Strips HTML tags from the given string.
270  Warning: This function is not fool-proof. It is only meant to handle well-formed HTML in MeVisLab log messages.
271  """
272  return _stripTagsRE.sub('', s)
def createRunTestMethod(cls, testFuncName, testIdentifier)
def __init__(self, methodName='runTest')
The master instance of the TestCenter.
def writeTestCenterConfigFile(configFilename, executablePath='', resultDirectory='', reportDirectory='')
def addTestsToMeVisLabTestCase(globals)