210 """The coordinator of test execution.
211 The master can be used in two ways: either with tests executed in another
212 MeVisLab instance or in the same. In the first scenario a new MeVisLab
213 instance is started, the TestCenter module loaded via the runMacro parameter
214 and communication handled using socket based interprocess communication. In
215 the second one the slave is instantiated directly in the master.
218 def __init__(self, cfgFile=None, verbose=False, slave=None, slaveEnvironment=None, isInsideTestCaseManager=False):
219 """Initialize the master process.
221 @param cfgFile The configuration file to load (defaults to tc_cfg_<platform>.xml)
222 @param verbose Unused
223 @param slave Run the tests in the same MeVisLab instance using the given slave instance.
224 @param slaveEnvironment Environment to use for the MeVisLab slave instance.
226 self.
__logger = logging.getLogger(
"TestCenter")
241 self.
__lock = threading.Lock()
253 resultDir = self.
__config.getResultDir()
254 imgDir = os.path.join(resultDir,
"images")
255 fileDir = os.path.join(resultDir,
"files")
256 workDir = os.path.join(resultDir,
"work")
259 os.makedirs(resultDir, exist_ok=
True)
261 def logRemoveError(_func, path, excinfo):
262 self.
__logger.warning(f
"Failed to remove {path}: {excinfo[1]}")
264 for subdir
in [imgDir, fileDir, workDir, self.
logfileDir]:
265 if os.path.exists(subdir):
266 shutil.rmtree(subdir, onerror=logRemoveError)
267 os.makedirs(subdir, exist_ok=
True)
271 if Utils.isLinux
and "NO_MEVISLAB_DEBUGGER" not in os.environ:
287 raise Exception(
"Failed to start slave")
291 slaveProc.finish(printLog=
True)
292 if not slaveProc.kill():
293 self.
__logger.error(
"Finally killing process %s failed." % slaveProc.getPID())
328 progressTestCase =
None
329 if finishedTestCase
is not None:
332 for function
in finishedTestCase.findall(
"Function"):
333 status = int(function.get(
"status", str(Utils.TEST_STATUS_DISABLED)))
335 Utils.TEST_STATUS_ERROR,
336 Utils.TEST_STATUS_LOAD_ERROR,
337 Utils.TEST_STATUS_CRASHED,
338 Utils.TEST_STATUS_FUNCTION_NOT_FOUND,
339 Utils.TEST_STATUS_TIMEOUT,
342 elif status == Utils.TEST_STATUS_WARNING:
345 finishedTestCase.get(
"name"),
346 int(finishedTestCase.get(
"status", str(Utils.TEST_STATUS_CANCELLED))),
349 float(finishedTestCase.get(
"duration",
"0")),
353 if progressTestCase
is not None:
355 if additionalTestCaseCount
is not None:
368 def __testConfigSkeleton():
369 """Returns an empty configuration filled with the required information."""
370 return etree.fromstring(
390 """Set the list of test cases to execute.
392 @param testCaseList List of names of test cases to execute.
396 xmlNode = self.
__testCfg.find(
"TestCases")
397 for testCaseName
in testCaseList:
398 etree.SubElement(xmlNode,
"TestCase", name=testCaseName)
401 """Set the list of test cases to ignore."""
405 """Set the list of test groups to take into account."""
408 xmlNode = self.
__testCfg.find(
"TestGroups")
409 for testGroupName
in testGroupList:
410 etree.SubElement(xmlNode,
"TestGroup", name=testGroupName)
415 xmlNode = self.
__testCfg.find(
"TestSuites")
416 for testSuiteName
in testSuiteList:
417 etree.SubElement(xmlNode,
"TestSuite", name=testSuiteName)
420 """Specify the list of modules to test.
421 The filters can be used to specify a larger list of modules, for example
422 all the modules of a certain author ("author:Jon Doe").
424 @param moduleList List of names of modules to take into account.
425 @param filterList List of filters to select modules.
428 moduleList = moduleList
or []
429 filterList = filterList
or []
432 for moduleName
in moduleList:
433 etree.SubElement(xmlNode,
"Module", name=moduleName)
434 for filter
in filterList:
435 etree.SubElement(xmlNode,
"Filter").text = filter
437 def setPackages(self, testedPkgList=None, availablePkgList=None, ignoredPkgList=None):
438 """Specify the packages that should be tested and thought available.
439 The packages tested are those that test cases are taken from. The available packages are those
440 that should be expected to be there. This is important for checking the meta information like
441 the seeAlso tags. In released installers all seeAlso links should only point to modules being
444 @param testedPkgList The list of packages tests are taken from.
445 @param availablePkgList List of packages that should be available.
446 @param ignoredPkgList List of package that should be ignored when using wildcard.
450 testedPkgList = testedPkgList
or []
451 availablePkgList = availablePkgList
or []
452 ignoredPkgList = ignoredPkgList
or []
455 xmlNode = self.
__testCfg.find(
"Packages/Tested")
456 for packageID
in testedPkgList:
457 etree.SubElement(xmlNode,
"Package", name=packageID)
459 xmlNode = self.
__testCfg.find(
"Packages/Available")
460 for packageID
in availablePkgList:
461 etree.SubElement(xmlNode,
"Package", name=packageID)
463 xmlNode = self.
__testCfg.find(
"Packages/Ignored")
464 for packageID
in ignoredPkgList:
465 etree.SubElement(xmlNode,
"Package", name=packageID)
469 envTimeout = os.environ.get(
"MLAB_TEST_TIMEOUT_FACTOR",
"")
472 return float(envTimeout)
480 def __sortNodes(self, nodes):
485 for testCaseNode
in nodes:
488 testAgendas[testCaseNode] = testCaseAgenda
490 invalidAgendas[testCaseNode] = testCaseAgenda
491 invalidNodes.append(testCaseNode)
492 for prefRendererSetting
in [
"",
"hardware",
"software"]:
493 for testCaseNode
in nodes:
494 if testCaseNode
in invalidNodes:
496 testCaseAgenda = testAgendas[testCaseNode]
497 testCaseInfoNodes = testCaseAgenda.findall(
"TestCase")
498 testCaseInfoNode = testCaseInfoNodes[0]
499 preferredRendererNode = testCaseInfoNode.find(
"preferredRenderer")
501 preferredRendererNode
is None
502 and prefRendererSetting ==
""
503 or preferredRendererNode
is not None
504 and preferredRendererNode.text.lower() == prefRendererSetting
506 nodeList.append(testCaseNode)
507 assert len(testAgendas) == len(nodeList)
508 return nodeList, testAgendas, invalidAgendas
511 def __testTypeAndName(xmlNode):
513 testType = xmlNode.get(
"type")
516 if testType ==
"FunctionalTestCase"
517 else "%s::%s" % (xmlNode.get(
"name"), xmlNode.get(
"module"))
520 return testType, testName
522 def __statusMessage(self, xmlNode, status):
523 """Sets status messages with the results of the given test.
524 The message is built from the test type, the test name and the status.
526 @param xmlNode The XML node that contains the results.
527 @param status The status of the test case.
529 assert type(status) == int
530 assert xmlNode.tag ==
"TestCase"
532 statusStr = Utils.getStatusString(status)
534 fillIn =
"." * (80 - len(testType + testName + statusStr) - 2)
535 msg =
"%s::%s%s%s" % (testType, testName, fillIn, statusStr)
536 if status == Utils.TEST_STATUS_OK
or status == Utils.TEST_STATUS_DO_NOT_TEST_MODULE:
538 elif status == Utils.TEST_STATUS_WARNING:
543 def __testFailed(self, xmlNode, comError):
544 """Handle failed tests.
545 There are two reasons for a test to fail: either the slave crashed or the
546 slave did not respond within the timeout (which might be an indicator for
547 an infinite loop). In either case there must be some sort of handling of
548 the situation to represent the situation within the XML data structure.
549 This method will load the log files and try to figure out what happened in
550 the last test run. This is possible as all action is surrounded by guards
551 in the logfile. The extracted information is added to the given XML node.
553 @param xmlNode Node to add the found information.
554 @param comError The last communication error.
556 assert xmlNode.tag ==
"TestCase"
558 testType = xmlNode.get(
"type")
559 if testType ==
"GenericTestCase":
560 testName =
"%s::%s" % (xmlNode.get(
"name"), xmlNode.get(
"module"))
562 testName = xmlNode.get(
"name")
566 for funcNode
in xmlNode.findall(
"Function"):
567 funcDict.setdefault(funcNode.get(
"name"), funcNode)
570 status = Utils.TEST_STATUS_ERROR
571 if comError
in (IPC.Error.EMPTY_MESSAGE, IPC.Error.ON_RECEIVING_2):
572 status = Utils.TEST_STATUS_CRASHED
573 elif comError == IPC.Error.ON_RECEIVING_TIMEOUT:
574 status = Utils.TEST_STATUS_TIMEOUT
578 with open(self.
__stackTraceFile, encoding=
"utf8", errors=
"surrogateescape")
as f:
579 stacktrace = f.read().splitlines()
581 new_lwp_re = re.compile(
r"\[New LWP \d+\]", flags=re.IGNORECASE)
582 stacktrace = [x
for x
in stacktrace
if not new_lwp_re.fullmatch(x)]
584 lastSignalHandlerFrame = -1
585 for i
in range(len(stacktrace)):
586 if "/mlabLinuxSignalHandler.cpp:" in stacktrace[i]:
587 lastSignalHandlerFrame = i
589 if lastSignalHandlerFrame != -1:
590 stacktrace = stacktrace[lastSignalHandlerFrame + 1 :]
592 stackTraceMsg =
"MeVisLab crashed with the following stacktrace:\n" +
"\n".join(stacktrace)
596 logHandler = LogHandler(self.
__config.getSlaveLogFilePath())
597 logHandler.parseLogFile(testType, testName, status, funcDict, stackTraceMsg=stackTraceMsg)
601 def __buildTestAgenda(self):
602 """Build up the basic XML structure with information what tests have to be run.
603 Please note that the methods setPackages, setModules, setTestCases
604 and setTestGroups must be called prior to this method.
607 if resultNode.get(
"errorCode") !=
"Ok":
609 "Error (%s) in generating the test agenda!\n%s"
610 % (resultNode.get(
"errorCode"), resultNode.find(
"Error").text)
613 self.
__xmlRoot = resultNode.find(
"TestRun")
620 def __buildTestCaseAgenda(self, testCaseNode):
621 def _stripHTML(html):
623 unescaped = unescape(html)
624 stripper.feed(unescaped)
625 return stripper.get_data()
627 parameterNode = etree.Element(
"Parameters")
628 genericTestCase =
True
631 for module
in self.
__xmlRoot.findall(
"Information/Modules/Module"):
632 moduleList.append(module.attrib[
"name"])
633 parameterNode.attrib[
"modules"] =
",".join(moduleList)
638 parameterNode.attrib[
"packages"] =
"*/*"
639 parameterNode.append(testCaseNode)
640 resultNode = self.
__sendRequest(
"BuildTestCaseAgenda", parameterNode, restart=
True)
641 if resultNode.get(
"errorCode") !=
"Ok":
643 f
'Error {resultNode.get("errorCode")} in generating the test case agenda!\n{resultNode.find("Error").text}'
645 functionResultNode = self.
__xmlRoot.find(
"Results")
646 testCaseResultNode = etree.SubElement(functionResultNode,
"TestCase")
647 testCaseResultNode.set(
"name", testCaseNode.get(
"name"))
648 testCaseResultNode.set(
"status",
"5")
649 testCaseResultNode.set(
"retries",
"0")
650 testCaseResultNode.set(
"duration",
"0")
651 testCaseResultNode.set(
"slave_id",
"0")
652 testCaseResultNode.set(
"type", testCaseNode.get(
"type"))
653 tempTestcase = resultNode.find(
"TestCase")
654 if tempTestcase
is None:
655 self.
__logger.critical(f
'Invalid response: {etree.tostring(resultNode, encoding="unicode")}')
657 errorLog = resultNode.find(
"Events")
658 testFunctions = tempTestcase.find(
"TestFunctions")
659 for testFunction
in testFunctions.findall(
"Function"):
660 if testFunction.get(
"basename") ==
"LoadTestCase":
661 testFunction.append(errorLog)
662 functionXMLNode = etree.SubElement(testCaseResultNode,
"Function")
663 functionXMLNode.set(
"name", testFunction.get(
"name"))
664 functionXMLNode.set(
"is_disabled",
"False")
665 functionXMLNode.set(
"status",
"5")
666 functionXMLNode.set(
"duration",
"0")
667 functionXMLNode.set(
"try",
"1")
668 functionXMLNode.append(errorLog)
669 for event
in errorLog.findall(
"Event"):
670 messageText = _stripHTML(event.find(
"Message").text)
671 self.
__logger.error(f
"{event.get('type')}: {messageText}")
672 return tempTestcase,
False
674 return resultNode,
True
676 def __verifyConfiguration(self, testCfg):
677 """Verify the settings made by the slave.
678 The slave returns the modified test configuration that contains status information for the entities set.
679 These are parsed right here and failure is reported.
680 The test configuration is set to the value that is returned.
682 @param testCfg The test configuration returned by the slave.
683 @return The verified test configuration.
686 packageRoot = testCfg.find(
"Packages")
688 tPkgNode = packageRoot.find(
"Tested")
689 for packageNode
in tPkgNode.findall(
"Package"):
690 if packageNode.get(
"status")
not in (
"Ok",
"Added",
"Substituted"):
692 "Failed to verify package (%s with status '%s')!"
693 % (packageNode.get(
"name"), packageNode.get(
"status"))
696 aPkgNode = packageRoot.find(
"Available")
697 for packageNode
in aPkgNode.findall(
"Package"):
698 if packageNode.get(
"status")
not in (
"Ok",
"Added",
"Substituted"):
700 "Failed to verify package (%s with status '%s')!"
701 % (packageNode.get(
"name"), packageNode.get(
"status"))
704 if packageNode.get(
"status")
not in (
"Substituted"):
705 packageRoot.append(packageNode)
707 packageRoot.remove(tPkgNode)
708 packageRoot.remove(aPkgNode)
710 iPkgNode = packageRoot.find(
"Ignored")
711 packageRoot.remove(iPkgNode)
714 for moduleNode
in testCfg.findall(
"Modules/Module"):
715 isModuleFiltered = moduleNode.get(
"isModuleFiltered") == str(
True)
716 if not isModuleFiltered
and moduleNode.get(
"status") != str(Utils.TEST_STATUS_OK):
718 "Failed to verify module (%s with status '%s')" % (moduleNode.get(
"name"), moduleNode.get(
"status"))
720 testCfg.find(
"Modules").clear()
723 for testCaseNode
in testCfg.findall(
"TestCases/TestCase"):
724 isNotInTestGroups = testCaseNode.get(
"isNotInTestGroups") == str(
True)
725 if not isNotInTestGroups
and testCaseNode.get(
"status") != str(Utils.TEST_STATUS_OK):
727 "Failed to verify test case (%s with status '%s')"
728 % (testCaseNode.get(
"name"), testCaseNode.get(
"status"))
730 testCfg.find(
"TestCases").clear()
734 def __createTimeoutTooHighErrorFunction(self, testCaseInfoNode, testResultNode, timeout):
735 functionName =
"TEST001_VerifyTestCaseTimeout"
736 testFunctions = testCaseInfoNode.find(
"TestFunctions")
737 testFunction = etree.SubElement(testFunctions,
"Function")
738 testFunction.attrib = {
"basename":
"VerifyTestCaseTimeout",
"is_disabled":
"False",
"name": functionName}
739 etree.SubElement(testFunction,
"Documentation").text =
"Verifies if the TestCase timeout."
740 timeoutTooHighTestFunction = etree.SubElement(testResultNode,
"Function")
741 timeoutTooHighTestFunction.attrib = {
743 "name": functionName,
745 "is_disabled":
"False",
748 events = etree.SubElement(timeoutTooHighTestFunction,
"Events")
749 filename = testCaseInfoNode.find(
"File").text
750 line = testCaseInfoNode.find(
"Line").text
751 event = etree.SubElement(events,
"Event")
758 "timestamp": datetime.datetime.now().strftime(
"%Y-%m-%d %H:%M:%S"),
760 error =
"The TestCase timeout (%ss) exceeds the maximum allowed timeout (%ss)" % (
762 self.
__config.getMaxTestTimeoutSeconds(),
764 etree.SubElement(event,
"Message").text = error
766 def __buildListOfTestFunctionsToExecute(self, testCaseInfoNode, testCaseNode, funcList=None):
769 for funcNode
in testCaseInfoNode.findall(
".//Function"):
770 funcName = funcNode.get(
"name")
772 if funcList
and funcName
not in funcList
and not Utils.isInternalTestCaseFunction(funcName):
775 isDisabled = funcNode.get(
"is_disabled", str(
False)) == str(
True)
778 if isDisabled
and funcList:
781 funcNode.set(
"is_disabled", str(
False))
782 funcResultNode = etree.SubElement(testCaseNode,
"Function", name=funcName, is_disabled=str(isDisabled))
783 testFuncList.append(funcName)
786 def __doTest(self, testCaseInfoNode, testResultNode, funcList=None):
787 """Do the actual test.
788 This method sends a copy of a test case node with all required test
789 functions to the slave. Failing functions will be requested one more time
790 such that a wasted environment can't spoil the results of a test.
792 @param testResultNode The test case that should be executed.
793 @param funcList The list of test functions to execute.
795 assert testResultNode.tag ==
"TestCase"
797 testCaseNode = deepcopy(testResultNode)
802 testCaseName = testCaseInfoNode.get(
"name")
807 hasValidTimeout =
True
808 timeout = int(testCaseInfoNode.get(
"timeout"))
810 timeout = self.
__config.getDefaultTestTimeoutSeconds()
811 if self.
__config.getMaxTestTimeoutSeconds():
812 if timeout > self.
__config.getMaxTestTimeoutSeconds():
814 hasValidTimeout =
False
817 if self.
__config.isPythonCoverageEnabled():
818 timeout = timeout * 4.0
819 if self.
__config.isBullseyeCoverageEnabled():
820 timeout = timeout * 4.0
826 testCaseInfoNode, testResultNode, testCaseNode, testFuncList, timeout
830 status = Utils.TEST_STATUS_ERROR
833 testResultNode.set(
"status", str(status))
834 testResultNode.set(
"retries", str(retries))
842 coverageFile = executionNode.get(nodeName)
843 if executionNode.get(nodeName)
is not None:
844 testResultNode.set(nodeName, coverageFile)
846 def __executeTestFunctions(self, testCaseInfoNode, testResultNode, testCaseNode, testFuncList, timeout):
847 if testCaseNode.get(
"status") == str(Utils.TEST_STATUS_DO_NOT_TEST_MODULE):
848 return 0, Utils.TEST_STATUS_DO_NOT_TEST_MODULE
854 status = Utils.TEST_STATUS_OK
855 maxFuncStatus = Utils.TEST_STATUS_OK
857 while testFuncList
and not timer.isExpired()
and not cancelled:
862 status = Utils.TEST_STATUS_CANCELLED
867 testCaseNode.set(
"requestProgress",
"1")
870 "maxErrorMessagesPerTestFunction", str(self.
__config.getReportOptions().maxErrorMessagesPerTestFunction)
873 "maxInfoMessagesPerTestFunction", str(self.
__config.getReportOptions().maxInfoMessagesPerTestFunction)
877 executionNode = self.
__sendRequest(
"DoTest", testCaseNode, timeout=timer, tries=1)
878 if executionNode.get(
"errorCode") !=
"Ok":
879 self.
__logger.debug(
"Restarting due to error in test execution.\n%s" % executionNode.find(
"Error").text)
882 comError = self.
__com.getLastError()
if self.
__com else 0
891 self.
__logger.debug(
"Failed to re-start slave.")
893 testCaseNode = executionNode.find(
"TestCase")
898 extraTestCasesResults = testCaseNode.find(
"ExtraTestCasesResults")
899 if extraTestCasesResults !=
None:
900 testResultNode.append(deepcopy(extraTestCasesResults))
903 for funcResultNode
in testCaseNode.findall(
"Function"):
904 funcName = funcResultNode.get(
"name")
907 funcStatus = funcResultNode.get(
"status")
912 funcStatus = int(funcStatus)
913 if maxFuncStatus < funcStatus:
914 maxFuncStatus = funcStatus
916 if funcName
not in failedFuncList:
917 funcResultNode.set(
"try",
"1")
920 Utils.TEST_STATUS_CRASHED,
921 Utils.TEST_STATUS_TIMEOUT,
923 failedFuncList.append(funcName)
926 testResultNode.append(deepcopy(funcResultNode))
927 funcResultNode.remove(funcResultNode.find(
"Events"))
929 funcResultNode.set(
"try",
"2")
930 failedFuncList.remove(funcName)
934 if funcName
not in failedFuncList:
935 testFuncList.remove(funcName)
937 testResultNode.append(funcResultNode)
938 testCaseNode.remove(funcResultNode)
939 if funcStatus == Utils.TEST_STATUS_CANCELLED
or funcStatus == Utils.TEST_STATUS_FUNCTION_NOT_FOUND:
943 if timer.isExpired():
944 status = Utils.TEST_STATUS_TIMEOUT
951 status = status
if status > Utils.TEST_STATUS_ERROR
else maxFuncStatus
952 return retries, status
955 def __handleExtraTestCaseResults(testCaseInfos, resultNode, testResultNode):
956 """This method looks for the node "ExtraTestCasesResults" to support that a test case can created
957 additional virtual test cases and their results. For example, this is used for the web tests,
958 where the web application generates an xml report.
960 node = testResultNode.find(
"ExtraTestCasesResults")
962 testResultNode.remove(node)
963 for node
in node.findall(
"ExtraTestCaseResult"):
964 testCaseInfos.append(node.find(
"Information/TestCase"))
965 extraTestCaseResult = node.find(
"Result/TestCase")
966 extraTestCaseResult.set(
"slave_id", testResultNode.get(
"slave_id"))
967 resultNode.append(extraTestCaseResult)
970 return self.
__config.isPythonCoverageEnabled()
and self.
__config.isGlobalPythonCoverageEnabled()
972 def run(self, funcDict=None, stopSlaveAfterTestRun=True):
973 """The main method which handles all the request from the master.
974 The master asks the slave to serve some request. This is divided into two
975 major blocks; on the first hand verifying whether the given information is
976 valid and what tests to execute and on the second hand to process the
979 @param funcDict A dictionary with information which test functions for
980 which test cases should be executed.
981 @param stopSlaveAfterTestRun If False, then the slave can be re-used for
982 another test run after resetting the master.
985 self.
__logger.error(
"Failed to build test agenda.")
989 timeStarted = time.time()
991 testCaseInfos = self.
__xmlRoot.find(
"Information/TestCases")
995 freshCounterValue = self.
__config.getRestartInterval()
996 restartCounter = freshCounterValue
997 restartDisabled = restartCounter == 0
999 testCaseInfosTestCases = testCaseInfos.findall(
"TestCase")
1000 testCaseInfosTestCases, testAgendas, invalidAgendas = self.
__sortNodes(testCaseInfosTestCases)
1004 for node
in testCaseInfosTestCases:
1005 testNames.append(node.get(
"name"))
1011 finishedTestCases = 0
1012 totalTestCases = len(testCaseInfosTestCases)
1014 resultNode = self.
__xmlRoot.find(
"Results")
1015 for invalidNode
in invalidAgendas.keys():
1016 if invalidAgendas[invalidNode]
is not None:
1017 testCaseInfos.remove(invalidNode)
1018 testCaseInfos.append(invalidAgendas[invalidNode])
1021 previousRenderer =
""
1022 for testCaseNode
in testCaseInfosTestCases:
1024 testCaseInfos.remove(testCaseNode)
1028 testCaseAgenda = testAgendas[testCaseNode]
1029 if testCaseAgenda !=
None:
1031 testCaseInfoNodes = testCaseAgenda.findall(
"TestCase")
1032 assert len(testCaseInfoNodes) == 1
1034 testCaseInfoNode = testCaseInfoNodes[0]
1036 testCaseInfos.remove(testCaseNode)
1037 testCaseInfos.append(testCaseInfoNode)
1039 preferredRendererNode = testCaseInfoNode.find(
"preferredRenderer")
1040 preferredRenderer =
"" if preferredRendererNode
is None else preferredRendererNode.text.lower()
1041 if preferredRenderer ==
"hardware" and Utils.isMesaForced():
1043 'preferredRenderer settings "hardware" detected but use of software driver is enforced.'
1045 restartNeeded = previousRenderer != preferredRenderer
and (
1046 preferredRenderer ==
"software" or previousRenderer ==
"software"
1048 previousRenderer = preferredRenderer
1050 testResultNodes = testCaseAgenda.find(
"Results").findall(
"TestCase")
1051 if len(testResultNodes) > 1:
1053 totalTestCases += len(testResultNodes) - 1
1054 for testResultNode
in testResultNodes:
1058 testCaseName = testCaseInfoNode.get(
"name")
1061 if not restartDisabled
or restartNeeded:
1062 shouldRestart = restartCounter == 0
1063 if shouldRestart
or restartNeeded:
1064 self.
__logger.info(
"Restart slave for test case %s::%s" % (debugType, debugName))
1065 if not self.
__startSlave(forceSoftwareRendering=(preferredRenderer ==
"software")):
1066 resultNode.append(testResultNode)
1067 raise Exception(
"Failed to start slave.")
1069 restartNeeded =
False
1070 restartCounter = freshCounterValue
1079 testCaseName, failedTestCases, finishedTestCases, totalTestCases
1082 timeFunctionStarted = time.time()
1083 funcList = funcDict[testCaseName]
if (funcDict
and testCaseName
in funcDict)
else None
1084 self.
__doTest(testCaseInfoNode, testResultNode, funcList)
1085 timeFunctionStopped = time.time()
1086 if int(testResultNode.get(
"status")) == Utils.TEST_STATUS_CANCELLED:
1089 if int(testResultNode.get(
"status"))
in (
1090 Utils.TEST_STATUS_CRASHED,
1091 Utils.TEST_STATUS_ERROR,
1092 Utils.TEST_STATUS_LOAD_ERROR,
1093 Utils.TEST_STATUS_FUNCTION_NOT_FOUND,
1094 Utils.TEST_STATUS_TIMEOUT,
1096 failedTestCases += 1
1097 finishedTestCases += 1
1099 testResultNode.set(
"duration", str(timeFunctionStopped - timeFunctionStarted))
1101 testResultNode.set(
"slave_id",
"0" if self.
__slave else str(self.
__slaveProc.getID()))
1103 resultNode.append(testResultNode)
1107 except Exception
as e:
1108 traceback.print_exc()
1109 self.
__logger.error(
"Error executing test case %s::%s\n%s" % (debugType, debugName, e))
1110 testResultNode.set(
"status", str(Utils.TEST_STATUS_ERROR))
1114 "Failed to build the test agenda for test case %s." % testCaseNode.get(
"name",
"unknown")
1116 except Exception
as e:
1117 self.
__logger.error(
"Error executing test case %s" % e)
1120 resultNode.set(
"combinePythonCoverage",
"True")
1123 timeStopped = time.time()
1125 self.
__xmlRoot.set(
"timestamp", str(timeStarted))
1126 self.
__xmlRoot.set(
"duration", str(timeStopped - timeStarted))
1128 if stopSlaveAfterTestRun:
1139 """Resets the master to the state after instantiation.
1140 In particular, it removes the configuration and the results of the last test run.
1141 After resetting the master you can start another test run with other tests.
1149 def __isSlaveRunning(self):
1152 def __isSlaveStopped(self):
1158 return "%s (PID: %s)" % (message, pidString)
1167 errorCode, errorMessage = self.
__com.getLastErrorWithMessage()
1168 return "%s ([%d] %s)" % (message, errorCode, errorMessage)
1170 def __startSlave(self, forceSoftwareRendering=False):
1171 """Start the slave process.
1172 If the slave is still running it will be stopped.
1174 @return True if the slave could be started and the connection could be established.
1181 self.
__logger.error(
"Failed to stop the slave before restart.")
1186 env = Utils.setTestCenterPort(env, self.
__com.getPort())
1188 if slaveProc.hasStarted():
1195 def __connectToSlave(self):
1199 while retries < maxTries
and not self.
__com.connect(timeout=self.
__config.getIPCConnectionTimeout()):
1209 self.
__logger.debug(
"Sleeping for %s seconds" % (timerSleep))
1210 time.sleep(timerSleep)
1212 if not self.
__com.isConnected():
1214 self.
logErrorWithPID(
"Failed to connect to slave within %d seconds." % (maxTries * timerSleep))
1216 self.
__logger.error(
"Failed to stop the slave after connection error.")
1227 return resultNode.get(
"errorCode") ==
"Ok"
1232 """Stop the slave process.
1233 If the slave is running this method tries to stop the slave. This method
1234 will run until the slave has stopped. After 3 tries on unix a KILL signal
1236 @return True if the slave was stopped.
1249 if xmlNode.get(
"errorCode") ==
"Ok":
1271 def __sendRequest(self, command, parameterNode=None, timeout=None, tries=2, restart=False):
1272 """Sends a message to the slave and waits for the response.
1273 The message consists of a tag (aka a command) and a message (aka the parameter).
1275 @param command The command sent.
1276 @param parameterNode Information connected with the command (XML).
1277 @param timeout The timeout used for the command.
1278 @param tries The number of retries used for the command.
1279 @return The resulting XML node.
1283 requestNode = etree.Element(
"Command", type=command)
1284 requestNode.append(parameterNode
if parameterNode !=
None else etree.Element(
"Dummy"))
1288 resultNode = self.
__slave.runCommand(requestNode)
1294 timeout = self.
__config.getDefaultTestTimeoutSeconds()
1297 while not string
and (trial < tries):
1298 timer = timeout
if isinstance(timeout, TestCaseTimer)
else TestCaseTimer(timeout)
1299 if self.
__com.send(etree.tostring(requestNode), timer.getSecondsRemaining()):
1302 while not string
and not timer.isExpired():
1303 string = self.
__com.recv(timer.getSecondsRemaining())
1305 recvNode = Utils.filterXMLString(string)
1307 if recvNode.tag ==
"Progress":
1312 recvNode.get(
"testType"),
1313 recvNode.get(
"testCase"),
1314 recvNode.get(
"testFunction"),
1319 self.
__com.send(
"0" if shouldStop
else "1", 5)
1321 elif recvNode.tag ==
"Status":
1326 resultNode = recvNode
1330 "It seems the test client is crashing. Collecting the stacktrace may take some while."
1333 timer.extendTimeout(300)
1342 if restart
and (trial < tries):
1343 self.
__logger.info(
"Restarting due to communication error ...")
1346 if resultNode
is None:
1348 resultNode = etree.Element(
"Result", errorCode=
"CommunicationError")
1349 etree.SubElement(resultNode,
"Error").text = (
1350 "Communication error [%d]: %s" % self.
__com.getLastErrorWithMessage()
1353 self.
logDebugWithPID(
"Result error code: %s" % resultNode.get(
"errorCode"))
1354 content = etree.tostring(resultNode).decode()
1355 self.
logDebugWithPID(
"Result content: %s%s" % (content[:150],
" [...]" if len(content) > 150
else ""))