TestCenter Reference
TestCase.py
Go to the documentation of this file.
2# Copyright 2009, 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
13
14# -- system imports ----------------------------------------------------------------------------{{{-
15import inspect
16import os
17import re
18import sys
19import time
20import traceback
21import unittest
22import asyncio
23from PythonQt import QtCore
24
25if sys.version_info.major >= 3:
26 unicode = str
27
28import xml.etree.cElementTree as etree
29# ----------------------------------------------------------------------------------------------}}}-
30
31# -- local imports -----------------------------------------------------------------------------{{{-
32import mevis
33from TestSupport.FieldValueTests import FieldValueTestCaseSet
34
35from TestSupport.TestHelper import TestHelper, CancelTestException
36from . import Utils
39
40# ----------------------------------------------------------------------------------------------}}}-
41
42# -- local imports -----------------------------------------------------------------------------{{{-
43MLABpriv = mevis.MLAB.priv()
44# ----------------------------------------------------------------------------------------------}}}-
45
46# Prints the exception that occurred during a test function.
48 exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
49 tb = exceptionTraceback.tb_next
50 traceback.print_tb(tb, limit=None, file=sys.stderr)
51 lines = traceback.format_exception_only(exceptionType, exceptionValue)
52 for line in lines:
53 sys.stderr.write(line)
54
55
57 def __init__(self, funcNode, funcName, logHandler, isLastTestFunction):
58 self.funcNode = funcNode
59 self.funcName = funcName
60 self.logHandler = logHandler
61 self.isLastTestFunction = isLastTestFunction
62 self.started = None
63 self.cancelled = False
64
65
66class UnitTestResult(unittest.TestResult):
67
68 # deactivate stdout/stderr redirection, the output is captured by the TestCenter anyway
69 def _setupStdout(self):
70 pass
71
72 def _restoreStdout(self):
73 pass
74
75 def logResult(self, successMsg):
76 if self.wasSuccessful() and successMsg:
77 mevis.MLAB.log( successMsg )
78 if self.errors:
79 for error in self.errors:
80 mevis.MLAB.logError( error[ 1 ] )
81 if self.failures:
82 for failure in self.failures:
83 mevis.MLAB.logError( failure[ 1 ] )
84
85
86# -- class TestCase ----------------------------------------------------------------------------{{{-
87
89 # -- member variables ------------------------------------------------------------------------{{{-
90
91 _ctx = None
92
93 _testHelper = None
94
95
96 _testCaseName = None
97
98 _testCaseType = None
99
100
101 __xmlRoot = None
102
103 __functionDict = None
104
105
106 __valid = None
107
108 # XML node for extra test case results
109 __extraTestCasesResults = None
110
111 __currentResultNode = None
112 # --------------------------------------------------------------------------------------------}}}-
113
114 # -- def __init__ ----------------------------------------------------------------------------{{{-
115
119 def __init__ (self, testCaseName, isNewlyCreated=False):
120 self._testCaseName = testCaseName
121 self._testHelper = TestHelper.getInstance()
122 self.__testCaseListener = None
123
124 self.__collectedStartupLog = None
127 self.__setupTestCaseCalled = False
128
129 self.__valid = False
130 self.__functionDict = {}
131
132
133 self.__dummyTestSuite = None
134
135
136 self.__previousTestClass = None
137
138 infoDict = self._getTestCaseInfo(testCaseName)
139
140 # Get the basic test case information and store them in the XML node.
141 self.__xmlRoot = etree.Element("TestCase", name=self._testCaseName, type=self._testCaseType)
142 self.__xmlRoot.set('timeout', infoDict.get('timeout', '0'))
143 self.__perFunctionResultDirectory = mevis.MLAB.valueIsTrue(infoDict.get('perFunctionResultDirectory', '1'))
144
145 etree.SubElement(self.__xmlRoot, 'Package').text = infoDict['package']
146 etree.SubElement(self.__xmlRoot, 'Author').text = infoDict['author']
147 etree.SubElement(self.__xmlRoot, 'Maintainer').text = infoDict.get('maintainer', '')
148 etree.SubElement(self.__xmlRoot, 'Comment').text = infoDict.get('comment', '')
149 etree.SubElement(self.__xmlRoot, 'File').text = infoDict['file']
150 etree.SubElement(self.__xmlRoot, 'Line').text = str(infoDict['lineno'])
151 showTestFunctionSortLiterals = "0"
152 if 'showTestFunctionSortingLiterals' in infoDict:
153 shouldShowLiterals = mevis.MLAB.valueIsTrue(infoDict['showTestFunctionSortingLiterals'])
154 showTestFunctionSortLiterals = "1" if shouldShowLiterals else "0"
155 etree.SubElement(self.__xmlRoot, 'showTestFunctionSortingLiterals').text = showTestFunctionSortLiterals
156 if 'preferredRenderer' in infoDict:
157 etree.SubElement(self.__xmlRoot, 'preferredRenderer').text = infoDict['preferredRenderer']
158
159 # Set the data directory. If not specified use default value.
160 if u'dataDirectory' in infoDict:
161 self._dataDirectory = infoDict['dataDirectory']
162 else:
163 self._dataDirectory = os.path.join(os.path.dirname(infoDict['file']), "Data")
164
165 MLABpriv.clearLogState()
166 MLABpriv.startLogCollect()
167
168 if not (infoDict and "scriptFile" in infoDict):
169 mevis.MLAB.logError(f"Missing scriptFile tag in TestCase {testCaseName}.")
170 else:
171 self._loadScriptFile(infoDict["scriptFile"])
172 # we need to activate the TestCase, because building of the virtual functions will call into
173 # TestHelper functions...
174 self.activate()
175
176 self._buildVirtualFunctions(isNewlyCreated)
177
178 self.deactivate()
179
180 etree.SubElement(self.__xmlRoot, 'Documentation').text = self._ctx.scriptVariable("__doc__")
181
182 # create a virtual test function to show the setup errors
183 if MLABpriv.hadLogWarnings() or MLABpriv.hadLogErrors():
184 self.__createVirtualTestFunction("LoadTestCase", Utils.VIRTUAL_LOAD_TEST_CASE_TEST_FUNCTION, "Collects errors while the test case is loaded")
185 else:
186 self.__valid = True
187
188 # create a virtual test function to show tear down errors
189 self.__createVirtualTestFunction("UnloadTestCase", Utils.VIRTUAL_UNLOAD_TEST_CASE_TEST_FUNCTION, "Collects errors while the test case is unloaded")
190
192 # --------------------------------------------------------------------------------------------}}}-
193
194 def __createVirtualTestFunction(self, basename, name, documentation):
195 functionsNode = self.__xmlRoot.find("TestFunctions")
196 if functionsNode == None:
197 functionsNode = etree.SubElement(self.__xmlRoot, "TestFunctions")
198 functionXMLNode = etree.SubElement(functionsNode, "Function", basename=basename, name=name, is_disabled=str(False))
199 etree.SubElement(functionXMLNode, "Documentation").text = documentation
200
201 def _getTestCaseInfo(self, testCaseName):
202 return mevis.MLABTestCaseDatabase.testCaseInfo(testCaseName)
203
204 # -- def __del__ -----------------------------------------------------------------------------{{{-
205
206 def __del__ (self):
207 self.destroyContext()
208 # --------------------------------------------------------------------------------------------}}}-
209
210 def destroyContext(self):
211 if self._ctx:
212 try:
213 self._ctx.remove()
214 finally:
215 self._ctx = None
216
217 def activate(self):
218 # Tell the TestHelper about us, but don't set a reference
219 # to self to the TestHelper to avoid that it reference counts us:
220 self._testHelper.setMacrosLogOnSuccess(True)
221 self._testHelper.setTestCaseContext(self._ctx)
222 self._testHelper.setTestCaseName(self._testCaseName)
223 self._testHelper.setTestCaseDataDirectory(self._dataDirectory)
224 self._testHelper.pushChangeSet()
225
226 # --------------------------------------------------------------------------------------------}}}-
227
228 def deactivate(self):
229 # Reset the test helper. If activate()/deactivate() should need to be
230 # called recursive in the the future, we would need to keep a stack here and in
231 # activate().
232 self._testHelper.setTestCaseContext(None)
233 self._testHelper.setTestCaseName("")
234 self._testHelper.setTestCaseDataDirectory("")
235 self._testHelper.popChangeSet()
236 self._testHelper.setMacrosLogOnSuccess(True)
237
238 # --------------------------------------------------------------------------------------------}}}-
239
240 def setTestCaseListener (self, testCaseListener):
241 self.__testCaseListener = testCaseListener
242
243 # -- def addExtraTestCaseResult ------------------------------------------------------------------{{{-
244 def addExtraTestCaseResult(self, extraTestCaseResult):
245 assert(self.__currentResultNode != None)
246 assert(self.__currentResultNode != self.__xmlRoot)
247 if self.__extraTestCasesResults == None:
248 self.__extraTestCasesResults = etree.SubElement(self.__currentResultNode, "ExtraTestCasesResults")
249 self.__extraTestCasesResults.append(extraTestCaseResult.toXML())
250 # --------------------------------------------------------------------------------------------}}}-
251
252 def _getTestFunction(self, funcName):
253 return self._ctx.scriptVariable(funcName)
254
255 # -- def __callTestFunction ------------------------------------------------------------------{{{-
256
258 def __callTestFunction (self, funcName):
259 if funcName in Utils.VIRTUAL_TEST_CASE_FUNCTIONS:
260 return
261
262 if not funcName in self.__functionDict:
263 raise Exception("Unknown function!")
264
265 changeSetLength = self._testHelper.pushChangeSet()
266
267 callInfo = self.__functionDict[funcName]
268
269 try:
270 if callInfo:
271 if type(callInfo) in (list, tuple):
272 testFunction, testArguments = callInfo
273 callWithKeywordArguments = False
274 if len(testArguments) == 1 and isinstance(testArguments[0], dict):
275 signature = inspect.signature(testFunction)
276 try:
277 # try to bind the test arguments as keyword arguments to the test function
278 signature.bind(**testArguments[0])
279 callWithKeywordArguments = True
280 except TypeError:
281 pass
282 if callWithKeywordArguments:
283 result = testFunction(**testArguments[0])
284 else:
285 result = testFunction(*testArguments)
286 else:
287 result = self._getTestFunction(callInfo)()
288 else:
289 result = self._getTestFunction(funcName)()
290
291 # if the result is a coroutine, we schedule it and process events until it
292 # has finished... This allows test functions to use the async keyword.
293 if asyncio.iscoroutine(result):
294 self.futureDone = False
295 def futureDone(*args): self.futureDone = True
296 future = asyncio.ensure_future(result)
297 future.add_done_callback(futureDone)
298 while not self.futureDone:
299 QtCore.QCoreApplication.processEvents()
300 future.result()
301
302 if TestSupport.Logging.gStopped:
303 raise CancelTestException
304
305 except CancelTestException:
306 self._testHelper.clearChangeSet()
307 raise
308 except TestSupport.Macros.AssertException:
309 # an assertion has failed and we should stop execution,
310 # but this is an expected exception, so we don't print it.
311 pass
312 except: # pylint: disable=W0702
313 mevis.MLAB.logError(f"Exception occurred in {funcName}:")
315
316 # Clean up the environment.
317 self._testHelper.popChangeSet()
318 while self._testHelper.getChangeSetStackLength() > changeSetLength:
319 mevis.MLAB.logError("You should pop your ChangeSets by yourself!")
320 self._testHelper.popChangeSet()
321 # --------------------------------------------------------------------------------------------}}}-
322
323 # -- def __stopCollectingAndStoreAsStartupLog ------------------------------------------------------------------------------{{{-
324
325 def __stopCollectingAndStoreAsStartupLog (self):
326 if MLABpriv.hadLogWarnings() or MLABpriv.hadLogErrors():
327 self.__collectedStartupLog = MLABpriv.collectedLog()
328 self.__collectedStartupErrorsState = MLABpriv.hadLogErrors()
329 self.__collectedStartupWarningsState = MLABpriv.hadLogWarnings()
330 else:
331 self.__collectedStartupLog = None
334 MLABpriv.stopLogCollect()
335
336 # -- def _loadScriptFile --------------------------------------------------------------------{{{-
337
339 def _loadScriptFile (self, filename):
340 self._ctx = self._testHelper.getGlobalContext().addModule(filename)
341 if self._ctx:
342 # set dynamic property so that MeVisLab and MATE know that this is a TestCase
343 self._ctx.setProperty("IsTestCase", True)
344 self._ctx.setProperty("TestCaseName", self._testCaseName)
345
346 # --------------------------------------------------------------------------------------------}}}-
347
348 # -- def callFieldValueTestCase --------------------------------------------------------------{{{-
349
354 def callFieldValueTestCase (self, filename, testcase):
355 fieldValueTestCaseSet = FieldValueTestCaseSet(self._ctx)
356 if not fieldValueTestCaseSet.load(filename):
357 mevis.MLAB.logError(f"Failed ot load field-value test ({filename}).")
358 else:
359 testCase = fieldValueTestCaseSet.get(testcase)
360 testCase.applyParameterization(TestHelper.getInstance().getChangeSet(), verbose=True)
361 if not testCase.verifyExpectedResults(verbose=True):
362 mevis.MLAB.logError("Failed to verify expected results.")
363 # --------------------------------------------------------------------------------------------}}}-
364
365 # -- def __createNormalTestNode ----------------------------------------------------------------------{{{-
366
370 def __createNormalTestNode (self, virtualFunctionName, functionName, functionBaseName, functionDict, isDisabled):
371 retVal = True
372 if not functionBaseName in functionDict["names"]:
373 xmlNode = self.__getFunctionInfo(functionBaseName, functionName, isDisabled=isDisabled, sourceName=functionName)
374 functionDict["names"].append(functionBaseName)
375 functionDict["nodes"][virtualFunctionName] = xmlNode
376 else:
377 mevis.MLAB.logError(f"Function with name {functionBaseName} already defined!")
378 retVal = False
379 return retVal
380 # --------------------------------------------------------------------------------------------}}}-
381
382 def _callGroupFunction(self, groupFunctionName):
383 return self._ctx.call(groupFunctionName, [])
384
385 # -- def __createTestGroupNode ---------------------------------------------------------------{{{-
386
391 def __createTestGroupNode (self, groupFunctionName, orderString, groupName, functionDict, isDisabled):
392 if groupName in functionDict["names"]:
393 mevis.MLAB.logError(f"A test group with name {groupName} already exists. No duplicate names allowed.")
394 return
395
396 # Get the field-value test case set file.
397 functionList = self._callGroupFunction(groupFunctionName)
398 if not functionList:
399 mevis.MLAB.logError(f"Failed to build virtual functions for the {groupFunctionName} test.")
400 return
401
402 if type(functionList) not in (list, tuple):
403 mevis.MLAB.logError(f"The test group function ({groupFunctionName}) must return a list or tuple with function objects!")
404 return
405
406 virtualGroupName = f"TEST{orderString}_{groupName}"
407 groupXMLNode = etree.Element("GroupNode", name=virtualGroupName, basename=groupName, type="TestGroup", sourceName=groupFunctionName)
408 etree.SubElement(groupXMLNode, "Documentation").text = self._ctx.scriptVariable(f"{groupFunctionName}.__doc__")
409
410 nameToFunctionMap = {}
411 for function in functionList:
412 # allow function names as string
413 if isinstance(function, str):
414 function = self._ctx.scriptVariable(function)
415 nameToFunctionMap[function.__name__] = function
416
417 for functionName in sorted(nameToFunctionMap.keys()):
418 function = nameToFunctionMap[functionName]
419
420 functionNodeName = functionName
421 if functionNodeName.startswith('DISABLED_'):
422 functionNodeName = functionNodeName[len('DISABLED_'):]
423
424 if functionNodeName not in functionDict["nodes"] or functionDict["nodes"][functionNodeName].tag != "Function":
425 mevis.MLAB.logError(f"Failed to compile function list. Given function {functionNodeName} not a test function "
426 f"or function already part of a test function group.")
427 return False
428
429 # Update function info, i.e. move the function node to the group node. The
430 # dictionary with the virtual function names must be updated too.
431 virtualFunctionName = (f"{virtualGroupName}_"
432 f"{functionNodeName[4:] if not functionNodeName.startswith('DISABLED_') else functionNodeName[13:]}"
433 )
434
435 functionNode = functionDict["nodes"][functionNodeName]
436 functionNode.set("name", virtualFunctionName)
437 # mark function as disabled if the whole group is disabled; otherwise, keep the current state of the function
438 # this way explicitly disabled functions stay disabled
439 if isDisabled:
440 functionNode.set("is_disabled", str(isDisabled))
441
442 # Remove function from dictionary of nodes. It will be part of the group!
443 del functionDict["nodes"][functionNodeName]
444
445 # Remove old virtual function
446 self.__functionDict.pop(functionName)
447 self.__functionDict[virtualFunctionName] = functionName
448
449 groupXMLNode.append(functionNode)
450
451 functionDict["names"].append(groupName)
452 functionDict["nodes"][virtualGroupName] = groupXMLNode
453 return True
454 # --------------------------------------------------------------------------------------------}}}-
455
456 # -- def __createFieldValueTestNode ----------------------------------------------------------{{{-
457
462 def __createFieldValueTestNode (self, virtualFunctionBaseName, functionName, groupName, functionDict, isDisabled):
463 # Find all the field-value test case functions, expand them and add the
464 # single items to the virtual function list.
465 if groupName in functionDict["names"]:
466 mevis.MLAB.logError(f"A test with name {groupName} already exists. No duplicate names allowed.")
467 return
468
469 # Get the field-value test case set file.
470 self._testHelper.pushChangeSet()
471 retVal = self._ctx.call(functionName, [])
472 self._testHelper.popChangeSet()
473
474 filename, testCaseList = retVal if type(retVal) in (list, tuple) else (retVal, None)
475
476 groupXMLNode = etree.Element("GroupNode", name=virtualFunctionBaseName, basename=groupName, type="FieldValueTest", sourceName=functionName)
477 etree.SubElement(groupXMLNode, "Documentation").text = self.getDocumentation(functionName)
478
479 # Keep in mind that duplicate names aren't allowed in field-value test case
480 # sets either.
481 functionDict["names"].append(groupName)
482 functionDict["nodes"][virtualFunctionBaseName] = groupXMLNode
483
484 fvtcs = FieldValueTestCaseSet(self._ctx)
485 if not fvtcs.load(filename):
486 mevis.MLAB.logError(f"Failed to load the field-value test case set ({filename})!")
487 return False
488
489 if not testCaseList:
490 for testcase in sorted(fvtcs.getList()):
491 virtualFunctionName = f"{virtualFunctionBaseName}_{testcase}"
492 groupXMLNode.append(self.__getFunctionInfo(testcase, virtualFunctionName, (
493 self.callFieldValueTestCasecallFieldValueTestCase, (filename, testcase)), isDisabled=isDisabled, sourceName=functionName))
494 else:
495 availableTestCaseList = fvtcs.getList()
496 for testcase in sorted(testCaseList):
497 if testcase in availableTestCaseList:
498 virtualFunctionName = f"{virtualFunctionBaseName}_{testcase}"
499 groupXMLNode.append(self.__getFunctionInfo(testcase, virtualFunctionName, (
500 self.callFieldValueTestCasecallFieldValueTestCase, (filename, testcase)), isDisabled=isDisabled, sourceName=functionName))
501 else:
502 mevis.MLAB.logError(f"The field-value test case {testcase} is unknown!")
503 # --------------------------------------------------------------------------------------------}}}-
504
505 # -- def __createIterativeTestNode -----------------------------------------------------------{{{-
506
511 def __createIterativeTestNode (self, virtualFunctionBaseName, functionName, groupName, functionDict, isDisabled):
512 # Find all iterative test functions, expand them and add the single items
513 # to the virtual function list.
514 if groupName in functionDict["names"]:
515 mevis.MLAB.logError(f"A test with name {groupName} already exists. No duplicate names allowed.")
516 return
517
518 # Get the iterations and the actual function to call.
519 self._testHelper.pushChangeSet()
520 retVal = self._ctx.call(functionName, [])
521 self._testHelper.popChangeSet()
522
523 try:
524 iterator, function = retVal
525 except: # pylint: disable=W0702
526 mevis.MLAB.logError(f"The iterative test function ({functionName}) must return a list or dictionary"
527 f" with parameters and a function to call!")
528 return
529
530 groupXMLNode = etree.Element("GroupNode", name=virtualFunctionBaseName, basename=groupName, type="IterativeTest", sourceName=functionName)
531 etree.SubElement(groupXMLNode, "Documentation").text = self.getDocumentation(functionName)
532
533 # Duplicate item nodes are not possible by design (either an index is used
534 # or a dictionary).
535 functionDict["names"].append(groupName)
536 functionDict["nodes"][virtualFunctionBaseName] = groupXMLNode
537 regExpFunctionNames = re.compile(r"^[.\w]+$")
538
539 # Create virtual functions for given iterative test.
540 if isinstance( iterator, (list, tuple) ):
541 ctr = 0
542 # Let the index used to identify the function be zero padded.
543 justificationWidth = len(unicode(len(iterator)-1))
544 for item in iterator:
545 # The zero padded identifier.
546 virtualFunctionId = unicode(ctr).rjust(justificationWidth, "0")
547 args = [item] if (type(item) not in (list, tuple)) else item
548 virtualFunctionName = f"{virtualFunctionBaseName}_{virtualFunctionId}"
549 if regExpFunctionNames.match(virtualFunctionName):
550 groupXMLNode.append(self.__getFunctionInfo(virtualFunctionId, virtualFunctionName, (function, args), isDisabled=isDisabled, sourceName=functionName))
551 else:
552 mevis.MLAB.logError(f"The iterative test function ({functionName}) defines non-alphanumeric virtual "
553 f"function name {virtualFunctionName}!")
554 ctr += 1
555 elif isinstance( iterator, dict ):
556 for item in sorted(iterator.keys()):
557 args = [iterator[item]] if (type(iterator[item]) not in (list, tuple)) else iterator[item]
558 virtualFunctionName = f"{virtualFunctionBaseName}_{item}"
559 if regExpFunctionNames.match(virtualFunctionName):
560 groupXMLNode.append(self.__getFunctionInfo(unicode(item), virtualFunctionName, (function, args), isDisabled=isDisabled, sourceName=functionName))
561 else:
562 mevis.MLAB.logError(f"The iterative test function ({functionName}) defines non-alphanumeric virtual "
563 f"function name {virtualFunctionName}!")
564 else:
565 mevis.MLAB.logError(f"The iterative test function ({functionName}) must return a list or dictionary with parameters!")
566
567 # --------------------------------------------------------------------------------------------}}}-
568
569 # -- def __createUnitTestWrapperNode -----------------------------------------------------------{{{-
570
576 def __createUnitTestWrapperNode(self, virtualFunctionBaseName, functionName, groupName, functionDict, isDisabled):
577 # Find all unit test wrapper functions, expand them and add the single items
578 # to the virtual function list.
579
580 if groupName in functionDict["names"]:
581 mevis.MLAB.logError(f"A test with name {groupName} already exists. No duplicate names allowed.")
582 return
583
584 # Get the unit test and the actual function to call.
585 self._testHelper.pushChangeSet()
586 suite = self._ctx.call(functionName, [])
587 self._testHelper.popChangeSet()
588
589 if not isinstance( suite, unittest.TestSuite ):
590 mevis.MLAB.logError(f"The unit test wrapper function ({functionName}) must return a unittest.TestSuite!")
591 return
592
593 groupXMLNode = etree.Element("GroupNode", name=virtualFunctionBaseName, basename=groupName, type="UnitTestWrapper", sourceName=functionName)
594 etree.SubElement(groupXMLNode, "Documentation").text = self.getDocumentation(functionName)
595
596 # Duplicate item nodes are not possible by design (either an index is used
597 # or a dictionary).
598 functionDict["names"].append(groupName)
599 functionDict["nodes"][virtualFunctionBaseName] = groupXMLNode
600
601 def _isSkipped(test):
602 isClassSkipped = getattr(test.__class__, "__unittest_skip__", False)
603 testMethod = getattr(test, test._testMethodName) # pylint: disable=W0212
604 isMethodSkipped = getattr(testMethod, "__unittest_skip__", False)
605 return isClassSkipped or isMethodSkipped
606
607 # Create virtual functions for given unit test wrapper
608 def _unpackSuite(suite):
609 allTests = []
610 for test in suite._tests: # pylint: disable=W0212
611 if isinstance( test, unittest.TestSuite ):
612 allTests.extend( _unpackSuite( test ) )
613 else:
614 if not _isSkipped(test):
615 allTests.append( test )
616 return allTests
617 allTests = _unpackSuite( suite )
618
619 def _makeName(test):
620 testCaseName = test.__class__.__name__
621 testFunctionName = test._testMethodName # pylint: disable=W0212
622 return '{0}_{1}'.format( testCaseName, testFunctionName )
623 allTests.sort(key=_makeName)
624 testNames = list(map( _makeName, allTests ))
625
626 # It could be smarter to integrate this more deeply into the callTestFunction part.
627 def _runUnitTestFunction(testFunction):
628 testResult = UnitTestResult()
629 testResult._previousTestClass = self.__previousTestClass
630 if self.__dummyTestSuite is None:
631 self.__dummyTestSuite = unittest.TestSuite()
632 # handle setup of class and module fixtures (copied from unittest.TestSuite):
633 self.__dummyTestSuite._tearDownPreviousClass(testFunction, testResult)
634 self.__dummyTestSuite._handleModuleFixture(testFunction, testResult)
635 self.__dummyTestSuite._handleClassSetUp(testFunction, testResult)
636 self.__previousTestClass = testFunction.__class__
637 # run test:
638 testFunction.run( testResult )
639 testResult.logResult( 'Test was successful' )
640
641 for testName, testFunction in zip( testNames, allTests ):
642 testMethod = getattr(testFunction, testFunction._testMethodName)
643 funcXMLNode = self.__getFunctionInfo(testName, '{}_{}'.format(virtualFunctionBaseName, testName),
644 (_runUnitTestFunction, [testFunction]), isDisabled=isDisabled)
645 if hasattr(testMethod, "__func__"): # testMethod is really a method
646 funcXMLNode.attrib["sourceFilename"] = testMethod.__func__.__code__.co_filename
647 funcXMLNode.attrib["sourceLine"] = str(testMethod.__func__.__code__.co_firstlineno)
648 elif hasattr(testMethod, "__code__"): # testMethod is actually a function
649 funcXMLNode.attrib["sourceFilename"] = testMethod.__code__.co_filename
650 funcXMLNode.attrib["sourceLine"] = str(testMethod.__code__.co_firstlineno)
651 else: # None or unsupported type - should we raise here?
652 funcXMLNode.attrib["sourceFilename"] = ""
653 funcXMLNode.attrib["sourceLine"] = "0"
654 groupXMLNode.append(funcXMLNode)
655
657 return self._ctx.callableFunctions()
658
659 # -- def __buildVirtualFunctions ---------------------------------------------------------------------{{{-
660
665 def _buildVirtualFunctions (self, isNewlyCreated):
666 # A list containing the found function names (to prevent duplicates).
667 functionInformationDict = { "names":[], "nodes":{} }
668
669 regExpDict = { "TestFunction": re.compile(r"^(DISABLED_)?TEST(\d*)_(\w+)$"),
670 "TestGroup": re.compile(r"^(DISABLED_)?GROUP(\d*)_(\w+)$"),
671 "FieldValueTest": re.compile(r"^(DISABLED_)?FIELDVALUETEST(\d*)_(\w+)$"),
672 "IterativeTest": re.compile(r"^(DISABLED_)?ITERATIVETEST(\d*)_(\w+)$"),
673 "UnitTestWrapper": re.compile(r"^(DISABLED_)?UNITTEST(\d*)_(\w+)$") }
674
675 # The result node.
676 functionsNode = etree.SubElement(self.__xmlRoot, "TestFunctions")
677
678 # Check the optional list of disabled test functions (it is defined by the disabledTestFunction() decorator)
679 disabledFunctions = self._ctx.scriptVariable('MLABTC_DISABLED_TEST_FUNCTIONS') or []
680
681 # The dictionary of all test functions sorted by type.
682 functionDict = { "TestFunction":[], "TestGroup":[], "FieldValueTest":[], "IterativeTest":[], "UnitTestWrapper":[] }
683 for functionName in self._callableFunctions():
684 for prefix, functionList in (("TEST", functionDict["TestFunction"]),
685 ("GROUP", functionDict["TestGroup"]),
686 ("FIELDVALUETEST", functionDict["FieldValueTest"]),
687 ("ITERATIVETEST", functionDict["IterativeTest"]),
688 ("UNITTEST", functionDict["UnitTestWrapper"]) ):
689 if functionName.startswith(prefix) or functionName.startswith("DISABLED_" + prefix):
690 functionList.append(functionName)
691
692 for functionName in sorted(functionDict["TestFunction"]):
693 mObj = regExpDict["TestFunction"].match(functionName)
694 if not mObj:
695 mevis.MLAB.logError(f"Name of test function invalid ({functionName})")
696 else:
697 disabledString, orderString, functionBaseName = mObj.groups()
698 isDisabled = (disabledString is not None) or (functionName in disabledFunctions)
699 fname = f"TEST{orderString}_{functionBaseName}"
700 self.__createNormalTestNode(fname, functionName, functionBaseName, functionInformationDict, isDisabled=isDisabled)
701
702 for functionName in sorted(functionDict["TestGroup"]):
703 mObj = regExpDict["TestGroup"].match(functionName)
704 if not mObj:
705 mevis.MLAB.logError(f"Name of test group invalid ({functionName})")
706 else:
707 disabledString, orderString, groupName = mObj.groups()
708 isDisabled = (disabledString is not None) or (functionName in disabledFunctions)
709 self.__createTestGroupNode(functionName, orderString, groupName, functionInformationDict, isDisabled=isDisabled)
710
711 for functionName in sorted(functionDict["FieldValueTest"]):
712 mObj = regExpDict["FieldValueTest"].match(functionName)
713 if not mObj:
714 mevis.MLAB.logError(f"Name of field value test invalid ({functionName})")
715 else:
716 disabledString, orderString, groupName = mObj.groups()
717 isDisabled = (disabledString is not None) or (functionName in disabledFunctions)
718 fname = f"TEST{orderString}_{groupName}"
719 self.__createFieldValueTestNode(fname, functionName, groupName, functionInformationDict, isDisabled=isDisabled)
720
721 for functionName in sorted(functionDict["IterativeTest"]):
722 mObj = regExpDict["IterativeTest"].match(functionName)
723 if not mObj:
724 mevis.MLAB.logError(f"Name of iterative test invalid ({functionName})")
725 else:
726 disabledString, orderString, groupName = mObj.groups()
727 isDisabled = (disabledString is not None) or (functionName in disabledFunctions)
728 fname = f"TEST{orderString}_{groupName}"
729 self.__createIterativeTestNode(fname, functionName, groupName, functionInformationDict, isDisabled=isDisabled)
730
731 for functionName in sorted(functionDict["UnitTestWrapper"]):
732 mObj = regExpDict["UnitTestWrapper"].match(functionName)
733 if not mObj:
734 mevis.MLAB.logError(f"Name of unit test wrapper invalid ({functionName})")
735 else:
736 disabledString, orderString, groupName = mObj.groups()
737 isDisabled = (disabledString is not None) or (functionName in disabledFunctions)
738 fname = f"TEST{orderString}_{groupName}"
739 self.__createUnitTestWrapperNode(fname, functionName, groupName, functionInformationDict, isDisabled=isDisabled)
740
741 # Append nodes in correct order. Essential for other methods to work
742 # properly.
743 keys = sorted(functionInformationDict["nodes"].keys())
744 if len(keys)>0:
745 for functionName in keys:
746 functionsNode.append(functionInformationDict["nodes"][functionName])
747 elif not isNewlyCreated:
748 mevis.MLAB.logError(f"No test functions found in TestCase {self._testCaseName}!")
749 # --------------------------------------------------------------------------------------------}}}-
750
751 # -- def __getFunctionInfo -------------------------------------------------------------------{{{-
752
756 def __getFunctionInfo (self, functionBaseName, functionName, calledFunction=None, isDisabled=False, sourceName=''):
757 self.__functionDict.setdefault(functionName, calledFunction)
758 functionXMLNode = etree.Element("Function", basename=functionBaseName, name=functionName, sourceName=sourceName, is_disabled=str(isDisabled))
759 documentation = self.getDocumentation(functionName)
760 etree.SubElement(functionXMLNode, "Documentation").text = documentation if documentation else ""
761 return functionXMLNode
762 # --------------------------------------------------------------------------------------------}}}-
763
764 # -- def isValid -----------------------------------------------------------------------------{{{-
765
767 def isValid (self):
768 return self.__valid
769 # --------------------------------------------------------------------------------------------}}}-
770
771 # -- def hasNetwork --------------------------------------------------------------------------{{{-
772
773 def hasNetwork ( self ):
774 return ( os.path.exists( self.networkFileName() ) )
775 # --------------------------------------------------------------------------------------------}}}-
776
777 # -- def networkFileName --------------------------------------------------------------------------{{{-
778
779 def networkFileName ( self ):
780 return ( self._ctx.network().filename() )
781 # --------------------------------------------------------------------------------------------}}}-
782
783 # -- def openFiles ---------------------------------------------------------------------------{{{-
784
785 def openFiles (self):
786 typeName = self.getName()
787 for fileName in self._ctx.getScriptSourceFiles():
788 mevis.MLAB.priv().openRelatedFile(typeName, fileName)
789 # --------------------------------------------------------------------------------------------}}}-
790
791 # -- def openNetwork -------------------------------------------------------------------------{{{-
792
793 def openNetwork (self):
794 if self._ctx:
795 mevis.MLAB.priv().showIDE()
796 self._ctx.showInternalNetwork()
797 # --------------------------------------------------------------------------------------------}}}-
798
799 # -- def getType -----------------------------------------------------------------------------{{{-
800
801 def getType (self):
802 return self._testCaseType
803 # --------------------------------------------------------------------------------------------}}}-
804
805 # -- def getModule -----------------------------------------------------------------------------{{{-
806
807 def getModule (self):
808 return self._ctx
809 # --------------------------------------------------------------------------------------------}}}-
810
811 # -- def getName -----------------------------------------------------------------------------{{{-
812
813 def getName (self):
814 return self._testCaseName
815 # --------------------------------------------------------------------------------------------}}}-
816
817 # -- def getListOfTestFuncs ------------------------------------------------------------------{{{-
818
820 return sorted(self.__functionDict.keys())
821 # --------------------------------------------------------------------------------------------}}}-
822
823 # -- def getPackage --------------------------------------------------------------------------{{{-
824
825 def getPackage (self):
826 return self.__xmlRoot.find("Package").text
827 # --------------------------------------------------------------------------------------------}}}-
828
829 # -- def getTestCaseNode ---------------------------------------------------------------------{{{-
830
831 def getTestCaseNode (self):
832 return self.__xmlRoot
833 # --------------------------------------------------------------------------------------------}}}-
834
835 # -- def __callSetUpTestCase -----------------------------------------------------------------{{{-
836
837 def __callSetUpTestCase (self):
838 if u"setUpTestCase" in self._ctx.callableFunctions():
839 self._ctx.call(u"setUpTestCase", [])
840 # --------------------------------------------------------------------------------------------}}}-
841
842 # -- def __callTearDownTestCase --------------------------------------------------------------{{{-
843
844 def __callTearDownTestCase (self):
845 if u"tearDownTestCase" in self._ctx.callableFunctions():
846 self._ctx.call(u"tearDownTestCase", [])
847 # --------------------------------------------------------------------------------------------}}}-
848
849 def __tearDown(self):
850 if self.__setupTestCaseCalled:
851 mevis.MLAB.log("Calling tearDown() function...")
853 if self.__dummyTestSuite:
854 # unittest support: clean up after running unittest.TestCases
855 testResult = UnitTestResult()
856 testResult._previousTestClass = self.__previousTestClass
857 self.__dummyTestSuite._tearDownPreviousClass(None, testResult)
858 self.__dummyTestSuite._handleModuleTearDown(testResult)
859 testResult.logResult('')
860 self.__previousTestClass = None
861 self.__dummyTestSuite = None
862 if TestSupport.Logging.gStopped:
863 raise CancelTestException
864 else:
865 mevis.MLAB.log("Destroying test case context...")
866 self.destroyContext()
867
868 # -- def run ---------------------------------------------------------------------------------{{{-
869
871 def run (self, resultNode):
872
873 self.activate()
874
875 TestSupport.Logging.gStopped = False
876
877 logHandler = self._testHelper.getLogHandler()
878
879 availTestFunctions = self.getListOfTestFuncs()
880
881 # Build dict mapping test function names to xml nodes.
882 funcList = resultNode.findall('.//Function')
883 length = len(funcList)
884
885 self.__currentResultNode = resultNode
886
887 # Make sure the virtual test function for loading the test case is the first, if it exists.
888 # This ensures that initial errors/warnings are appended to this function. Accordingly
889 # the unload test case function must be last.
890 loadTestCaseFunctionHandled = False
891 unloadTestCaseFunctionHandled = False
892 for i in range(0, length):
893 funcNode = funcList[i]
894 funcName = funcNode.get("name")
895 if not loadTestCaseFunctionHandled and funcName == Utils.VIRTUAL_LOAD_TEST_CASE_TEST_FUNCTION:
896 del funcList[i]
897 funcList.insert(0, funcNode)
898 loadTestCaseFunctionHandled = True
899 elif not unloadTestCaseFunctionHandled and funcName == Utils.VIRTUAL_UNLOAD_TEST_CASE_TEST_FUNCTION:
900 del funcList[i]
901 funcList.append(funcNode)
902 unloadTestCaseFunctionHandled = True
903 if loadTestCaseFunctionHandled and unloadTestCaseFunctionHandled:
904 break
905
906 self.__setupTestCaseCalled = False
907
908 # Iterate over all test functions (sorted by name) that should be tested.
909 # The iteration with an index is required to call setUpTestCase and
910 # tearDownTestCase properly.
911 for i in range(0, length):
912 funcNode = funcList[i]
913 funcName = funcNode.get("name")
914
915 isLastTestFunction = i == length-1
916 data = RunTestFunctionData(funcNode, funcName, logHandler, isLastTestFunction)
917
918 if not self.__prepareRunTestFunction(data, availTestFunctions):
919 continue
920
921 self.__runTestFunction(data)
922 self.__finishRunTestFunction(data)
923 if data.cancelled:
924 # Do NOT call __tearDown() when 'Stop on error' flag is set and an error occured.
925 if not (TestSupport.Logging.gStopOnFirstError and logHandler.numErrors() > 0):
926 self.__tearDown()
927 break
928
929 self.deactivate()
930
931 def __prepareRunTestFunction(self, data, availTestFunctions):
933 funcNameForResultDir = data.funcName
934 else:
935 funcNameForResultDir = None
936 self._testHelper.setTestCaseResultDirectory(self.getName().replace("::", os.sep), funcNameForResultDir)
937
938 shouldRunTestFunction = True
939
940 # Verify that a function with the given name exists.
941 if data.funcName not in availTestFunctions and data.funcName not in Utils.VIRTUAL_TEST_CASE_FUNCTIONS:
942 mevis.MLAB.logError(f"Function {data.funcName} does not exist in test case {self.getName()}!")
943 status = Utils.TEST_STATUS_FUNCTION_NOT_FOUND
944 data.funcNode.set('status', str(status))
945 shouldRunTestFunction = False
946 return shouldRunTestFunction
947
948 def __runTestFunction(self, data):
949 data.started = time.time()
950
951 try:
952 data.logHandler.startTestFunction(self.getType(), self.getName(), data.funcName)
953 if self.__testCaseListener:
954 self.__testCaseListener.startTestCaseFunction(self.getType(), self.getName(), data.funcName)
955
956 MLABpriv.clearLogState()
957
958 # append earlier testcase error/warnings to the first test function that is executed
959 self.__appendCollectedStartupLog(data.logHandler)
960
962
963 if data.funcName == Utils.VIRTUAL_UNLOAD_TEST_CASE_TEST_FUNCTION:
964 self.__tearDown()
965 if TestSupport.Logging.gStopped:
966 raise CancelTestException
967 except CancelTestException:
968 data.cancelled = True
969
970 def __finishRunTestFunction(self, data):
971 for extraTestCaseResult in self._testHelper.takeExtraTestCaseResults():
972 self.addExtraTestCaseResult(extraTestCaseResult)
973
974 # process events so that any delayed messages are also part of the test function
975 mevis.MLAB.processEvents()
976 # force messages from other threads to be printed inside of this test function execution
977 MLABpriv.flushPendingMessages()
978
979 duration = time.time()-data.started
980
981 # Gather status and additional information.
982 status = Utils.TEST_STATUS_OK
983 if MLABpriv.hadLogWarnings() or self.__collectedStartupWarningsState: status = Utils.TEST_STATUS_WARNING
984 if MLABpriv.hadLogErrors() or self.__collectedStartupErrorsState: status = Utils.TEST_STATUS_ERROR
987 data.logHandler.stopTestFunction(status, duration)
988 if self.__testCaseListener:
989 self.__testCaseListener.endTestCaseFunction(status, duration)
990
991 if data.cancelled:
992 status = Utils.TEST_STATUS_CANCELLED
993 data.funcNode.set('status', str(status))
994 data.funcNode.set('duration', str(duration))
995 data.funcNode.append(data.logHandler.getMessages())
996
997 def __appendCollectedStartupLog(self, logHandler):
998 if self.__collectedStartupLog:
999 log = self.__collectedStartupLog
1000 msgs = log.split('<br>')
1001 if len(msgs):
1002 mevis.MLAB.log("TestCase setup errors/warnings:")
1003 for msg in msgs:
1004 if msg!="":
1005 logHandler.appendMessage(msg)
1006 mevis.MLAB.log("TestCase execution:")
1007 self.__collectedStartupLog = None
1008
1009 def __setupTestCaseAndRunTestFunction(self, data):
1010 if data.funcNode.get('is_disabled', str(False)) == str(False):
1011 if not self.__setupTestCaseCalled:
1012 self.__setupTestCaseCalled = True
1013 self.__callSetUpTestCase()
1014 if TestSupport.Logging.gStopped:
1015 raise CancelTestException
1016 mevis.MLAB.log(f"Calling test function {data.funcName}()")
1017 self.__callTestFunction(data.funcName)
1018 else:
1019 mevis.MLAB.log(f"Skipping disabled test function {data.funcName}()")
1020
1021 # --------------------------------------------------------------------------------------------}}}-
1022
1023 # -- def getDocumentation --------------------------------------------------------------------{{{-
1024
1029 def getDocumentation (self, functionName):
1030 docu = self._ctx.scriptVariable(f"{functionName}.__doc__")
1031 testDocu = docu if docu else ""
1032 return re.sub(r"(?m)^\s+", "", testDocu)
1033 # --------------------------------------------------------------------------------------------}}}-
1034# ----------------------------------------------------------------------------------------------}}}-
__init__(self, funcNode, funcName, logHandler, isLastTestFunction)
Definition TestCase.py:57
The superclass for the different test case categories.
Definition TestCase.py:88
_callGroupFunction(self, groupFunctionName)
Definition TestCase.py:382
callFieldValueTestCase(self, filename, testcase)
Call the given field-value test case from the given field-value test case set.
Definition TestCase.py:354
_loadScriptFile(self, filename)
Load the test cases script file.
Definition TestCase.py:339
__createVirtualTestFunction(self, basename, name, documentation)
Definition TestCase.py:194
getName(self)
Return the name of the test case.
Definition TestCase.py:813
getPackage(self)
Return the identifier of the package the test case is in.
Definition TestCase.py:825
_testHelper
The instance of the TestHelper singleton.
Definition TestCase.py:93
isValid(self)
Has test case loaded without syntax errors?
Definition TestCase.py:767
getType(self)
Return the type of this test case (generic of functional).
Definition TestCase.py:801
__getFunctionInfo(self, functionBaseName, functionName, calledFunction=None, isDisabled=False, sourceName='')
Definition TestCase.py:756
_ctx
The TestCase's context.
Definition TestCase.py:91
__createTestGroupNode(self, groupFunctionName, orderString, groupName, functionDict, isDisabled)
Definition TestCase.py:391
openNetwork(self)
Open the internal network of the test case.
Definition TestCase.py:793
__createUnitTestWrapperNode(self, virtualFunctionBaseName, functionName, groupName, functionDict, isDisabled)
Definition TestCase.py:576
__appendCollectedStartupLog(self, logHandler)
Definition TestCase.py:997
getListOfTestFuncs(self)
Get a list of test functions this test has.
Definition TestCase.py:819
getModule(self)
Return the module, might be None.
Definition TestCase.py:807
getDocumentation(self, functionName)
Get the documentation for the given test function.
Definition TestCase.py:1029
getTestCaseNode(self)
Return the XML node with the test case's information.
Definition TestCase.py:831
__del__(self)
The default destructor.
Definition TestCase.py:206
__createFieldValueTestNode(self, virtualFunctionBaseName, functionName, groupName, functionDict, isDisabled)
Definition TestCase.py:462
run(self, resultNode)
Run the test case and store results in the given xml data structure.
Definition TestCase.py:871
openFiles(self)
Open the files associated with the test case.
Definition TestCase.py:785
_getTestCaseInfo(self, testCaseName)
Definition TestCase.py:201
_testCaseType
The type of the test case.
Definition TestCase.py:98
__init__(self, testCaseName, isNewlyCreated=False)
Default constructor for the test case.
Definition TestCase.py:119
__createNormalTestNode(self, virtualFunctionName, functionName, functionBaseName, functionDict, isDisabled)
Definition TestCase.py:370
setTestCaseListener(self, testCaseListener)
Definition TestCase.py:240
addExtraTestCaseResult(self, extraTestCaseResult)
Definition TestCase.py:244
networkFileName(self)
Returns the absolute file name of the attached network or an empty string if no network is attached.
Definition TestCase.py:779
hasNetwork(self)
Check whether the test case has a network attached.
Definition TestCase.py:773
__createIterativeTestNode(self, virtualFunctionBaseName, functionName, groupName, functionDict, isDisabled)
Definition TestCase.py:511
_testCaseName
The name of the current test case.
Definition TestCase.py:96
_buildVirtualFunctions(self, isNewlyCreated)
Build an XML node with the virtual functions.
Definition TestCase.py:665
__prepareRunTestFunction(self, data, availTestFunctions)
Definition TestCase.py:931
A class collecting a set of field-value test cases.
Package to provide logging functions.
Definition Logging.py:1
Adds GoogleTest like methods.
Definition Macros.py:1