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