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
107 return inner
108
109
110class MeVisLabTestCase(unittest.TestCase):
111 master = None
112 tempDirectory = None
113
114 def __init__(self, methodName="runTest"):
115 super(MeVisLabTestCase, self).__init__(methodName)
116
117 if mlabroot is None:
118 raise Exception("MLAB_ROOT is not defined")
119
120 testCenterAdvancedPath = os.path.join(
121 mlabroot, "MeVisLab", "Standard", "Modules", "Macros", "Tests", "TestCenterAdvanced"
122 )
123 if not testCenterAdvancedPath in sys.path:
124 sys.path.insert(0, testCenterAdvancedPath)
125
126 @classmethod
127 def setUpClass(cls):
128 cls.tempDirectory = tempfile.mkdtemp()
129 # print 'Using temp folder %s' % cls.tempDirectory
130 configFilename = os.path.join(cls.tempDirectory, "tc_config.xml")
132 configFilename,
133 executablePath=os.path.join(mlabroot, "MeVisLab"),
134 resultDirectory=cls.tempDirectory,
135 reportDirectory=cls.tempDirectory,
136 )
137 # with open(configFilename, 'r') as f:
138 # print f.read()
139
140 env = _getEnvironmentForTestCenter(mlabroot)
141
142 logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-8s %(message)s")
143 logging.getLogger("TestCenter").disabled = True
144
145 from Master import Master
146
147 cls.master = Master(cfgFile=configFilename, slaveEnvironment=env)
148
149 @classmethod
151 if cls.master is not None:
152 cls.master.stopSlave()
153 if cls.tempDirectory is not None:
154 shutil.rmtree(cls.tempDirectory)
155 pass
156
157 @classmethod
158 def addTest(cls, testName, skip):
159 testFuncName = "test%s" % testName
160 if skip:
161 testFunc = skippedDummyTest
162 else:
163 testIdentifier = (cls.__name__, "TEST%s" % testName)
164 testFunc = cls.createRunTestMethod(testFuncName, testIdentifier)
165 setattr(cls, testFuncName, testFunc)
166
167 @classmethod
168 def createRunTestMethod(cls, testFuncName, testIdentifier):
169 def formatStack(event):
170 stack = event.find("Stack")
171 stackText = stack.text if stack is not None else "No stack trace available"
172 return "Stack trace (most recent call last):\n%s" % stackText
173
174 def runTestMethod(testCaseInstance):
175 cls.master.setTestCases([cls.__name__])
176 testFunctionDict = {testIdentifier[0]: [testIdentifier[1]]}
177 result = testCaseInstance.master.run(testFunctionDict, stopSlaveAfterTestRun=False)
178 testCaseInstance.master.reset()
179
180 if result is None:
181 raise Exception("Test failed: TestRun did not return anything.")
182
183 # find the results of running the test function in the TestRun XML tree
184 functionNode = result.find("Results/TestCase/Function")
185 if functionNode is None:
186 raise Exception("No function found in\n%s" % ElementTree.tostring(result))
187 # print ElementTree.tostring(functionNode)
188 if functionNode.get("is_disabled") == "True":
189 raise unittest.SkipTest(
190 "Skipped for unknown reasons. No test method should have been created for this test."
191 )
192
193 messages = []
194 status = int(functionNode.get("status"))
195 if status == 2:
196 messages.append("%s failed" % testIdentifier[1])
197 if status == 3:
198 messages.append("Test timed out")
199 if status == 4:
200 messages.append("MeVisLab crashed")
201
202 # print all non-Info events
203 for event in functionNode.iter("Event"):
204 eventType = event.get("type")
205 # filename = event.get('file')
206 # lineNumber = event.get('line')
207 timestamp = event.get("timestamp")
208 eventMessageText = _stripTags(event.find("Message").text)
209 if eventType == "Info":
210 pass
211 elif eventType == "Warning":
212 print("%s %s: %s" % (timestamp, eventType, eventMessageText))
213 elif eventType in ["Error", "ML Error", "Timeout", "Crashed", "ScriptError", "SemanticError"]:
214 messages.append("%s: %s" % (eventType, eventMessageText))
215 messages.append(formatStack(event))
216 else:
217 print('Unknown event type "%s"' % eventType)
218 print("%s %s: %s" % (timestamp, eventType, eventMessageText))
219
220 # fail test if test failed or had error in MeVisLab
221 if status == 2:
222 testCaseInstance.fail("\n".join(messages))
223 if status == 3 or status == 4:
224 raise Exception("\n".join(messages))
225
226 runTestMethod.__name__ = testFuncName
227 return runTestMethod
228
229
230def writeTestCenterConfigFile(configFilename, executablePath="", resultDirectory="", reportDirectory=""):
231 import Config
232
233 config = Config.Configuration(configFilename, autoSave=False)
234 config.setMLABExecutablePath(executablePath)
235 config.setResultDir(resultDirectory)
236 config.getReportOptions().directory = reportDirectory
237 config.save()
238
239
241 buildSystemRoot = os.path.join(mlabroot, "MeVis", "BuildSystem", "Sources")
242 sys.path.insert(0, buildSystemRoot)
243
244 import BuildSystem.SystemUtils
245
246 # remove path again to prevent name clash of IPC modules
247 sys.path.remove(buildSystemRoot)
248
249 testCenterEnvironment = dict(os.environ)
250 if testCenterEnvironment.get("PYCHARM_HOSTED") == "1":
251 # delete the PYTHONPATH set by PyCharm because it is wrong for TestCenter.py
252 del testCenterEnvironment["PYTHONPATH"]
253
254 requiredPackages = ["MeVis/ThirdParty", "MeVis/Foundation", "MeVisLab/IDE"]
255 additionalBinPaths = [os.path.join(mlabroot, package, "bin") for package in requiredPackages]
256 additionalBinPaths.append(os.path.join(mlabroot, "MeVis/ThirdParty/Sources/Qt4/qt/bin"))
257 additionalLibPaths = [os.path.join(mlabroot, package, "lib") for package in requiredPackages]
258 additionalLibPaths.append(os.path.join(mlabroot, "MeVis/ThirdParty/Python"))
259 additionalLibPaths.append(os.path.join(mlabroot, "MeVis/ThirdParty/Sources/Qt4/qt/lib"))
260 additionalBinPaths = [os.path.normpath(path) for path in additionalBinPaths]
261 additionalLibPaths = [os.path.normpath(path) for path in additionalLibPaths]
262 BuildSystem.SystemUtils.addPathsToExecutableSearchPaths(additionalBinPaths, environmentDict=testCenterEnvironment)
263 BuildSystem.SystemUtils.addPathsToLibrarySearchPaths(additionalLibPaths, environmentDict=testCenterEnvironment)
264
265 return testCenterEnvironment
266
267
268_stripTagsRE = re.compile(r"(<!--.*?-->|<[^>]*>)")
269
270
272 """Strips HTML tags from the given string.
273 Warning: This function is not fool-proof. It is only meant to handle well-formed HTML in MeVisLab log messages.
274 """
275 return _stripTagsRE.sub("", s)
createRunTestMethod(cls, testFuncName, testIdentifier)
The master instance of the TestCenter.
writeTestCenterConfigFile(configFilename, executablePath="", resultDirectory="", reportDirectory="")