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