TestCenter Reference
Master.py
Go to the documentation of this file.
2# Copyright 2007, 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
14import copy
15import datetime
16import logging
17import os
18import re
19import shutil
20import subprocess
21import tempfile
22import threading
23import time
24import traceback
25import xml.etree.cElementTree as etree
26from html.parser import HTMLParser
27from html import unescape
28from copy import deepcopy
29
30try:
31 import TestSupport
32except ImportError:
33 pass
34
35from . import Config
36from . import IPC
37from . import Utils
38
39from .LogHandler import LogHandler
40
41
42class HTMLStripper(HTMLParser):
43 def __init__(self):
44 super().__init__()
45 self.fed = []
46
47 def handle_data(self, data):
48 self.fed.append(data)
49
50 def get_data(self):
51 return "".join(self.fed)
52
53
55 def __init__(self, seconds):
56 self.start = time.monotonic()
57 self.availableSeconds = seconds
58
59 def extendTimeout(self, seconds):
60 self.availableSeconds += seconds
61
63 passedSeconds = time.monotonic() - self.start
64 return self.availableSeconds - passedSeconds
65
66 def isExpired(self):
67 return self.getSecondsRemaining() <= 0.0
68
69
71 nextSlaveId = 0
72
73 def __init__(self, config, logfileDir, env, isInsideTestCaseManager):
74 self.__id = SlaveProcess.nextSlaveId
75 SlaveProcess.nextSlaveId += 1
76 self.__wasKilled = False
77 self.__hasStarted = False
78 self.__logFileFD = None
79 self.__logFile = None
80 self.__slaveLogFile = None
81 self.__logger = logging.getLogger("TestCenter")
82 self.__workDir = os.path.join(config.getResultDir(), "work")
83
84 cmd = self.__getCommandLine(config, logfileDir, isInsideTestCaseManager)
85 if cmd:
86 self.__startProcess(cmd, config, env)
87
88 def hasStarted(self):
89 return self.__hasStarted
90
91 def __getCommandLine(self, config, logfileDir, isInsideTestCaseManager):
92 try:
93 cmd = config.getMLABTestCenterCommand(macroName="TestCenter")
94 if cmd:
95 self.__slaveLogFile = os.path.join(logfileDir, f"slaveLogfile_{self.getID()}.log")
96 cmd.insert(1, "-logfile")
97 cmd.insert(2, self.__slaveLogFile)
98 if isInsideTestCaseManager:
99 cmd.append("--started-from-test-case-manager")
101 cmd = None
102
103 if not cmd:
104 self.__logger.error(
105 "Failed to start slave as no MeVisLab executable was found. "
106 + "Please verify your configuration (see TestCase Manager's "
107 + "Configuration tab or check the file %s)!" % config.getConfigFilePath()
108 )
109 return cmd
110
111 def __startProcess(self, cmd, _, env):
112 self.__logFileFD, self.__logFile = tempfile.mkstemp()
113 self.__logger.debug("Starting slave with command line %s" % " ".join(cmd))
114 try:
115 preexec_fn = os.setpgrp if Utils.isUnix else None
116 self.__process = subprocess.Popen(
117 cmd,
118 cwd=self.__workDir,
119 preexec_fn=preexec_fn,
120 env=env,
121 stdout=self.__logFileFD,
122 stderr=self.__logFileFD,
123 )
124 self.__hasStarted = True
125 except OSError:
126 self.__logger.error("Failed to start slave (%s)\n%s" % (cmd, traceback.format_exc()))
127
128 def getID(self):
129 return self.__id
130
131 def getPID(self):
132 return self.__process.pid
133
134 def isRunning(self):
135 return self.getExitCode() is None
136
137 def getExitCode(self):
138 return self.__process.poll()
139
140 def kill(self):
141 self.__wasKilled = Utils.kill(self.__process)
142 return self.__wasKilled
143
144 def wasKilled(self):
145 return self.__wasKilled
146
147 def __printLogsAfterError(self):
148 try:
149 output = ""
150 if self.__slaveLogFile is not None and os.path.exists(self.__slaveLogFile):
151 with open(self.__slaveLogFile, "r", errors="replace") as fh:
152 output = fh.read()
153 if os.path.exists(self.__logFile):
154 with open(self.__logFile, "r", errors="replace") as fh:
155 output += fh.read()
156 if not output:
157 output = "none"
158 self.logErrorWithPID("Slave output: %s" % output)
159 except IOError as e:
160 self.logErrorWithPID(f"Could not read log file: {e}")
161
162 def finish(self, printLog):
163 try:
164 os.fsync(self.__logFileFD)
165 os.close(self.__logFileFD)
166 except IOError as e:
167 self.logErrorWithPID("Could not close log file: %s" % str(e))
168 if printLog:
170 if os.path.exists(self.__logFile):
171 try:
172 Utils.removeFile(self.__logFile)
173 except IOError as e:
174 self.logErrorWithPID("Could not remove log file: %s" % str(e))
175 else:
176 self.logMessageWithPID("Log file does not exist: %s" % self.__logFile)
177
178 def logMessageWithPID(self, message):
179 return "%s (PID: %s)" % (message, str(self.getPID()))
180
181 def logDebugWithPID(self, message):
182 self.__logger.debug(self.logMessageWithPID(message))
183
184 def logErrorWithPID(self, message):
185 self.__logger.error(self.logMessageWithPID(message))
186
187
189 def __init__(self, name, status, errorCount, warningCount, duration):
190 self.duration = duration
191 self.name = name
192 self.status = status
193 self.errorCount = errorCount
194 self.warningCount = warningCount
195
196
198 def __init__(self):
199 self.activeTestCase = None
202
203 def reset(self, testCaseCount):
204 self.activeTestCase = None
205 self.finishedTestCases = []
206 self.testCaseCount = testCaseCount
207
208
209class Master:
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.
216 """
217
218 def __init__(self, cfgFile=None, verbose=False, slave=None, slaveEnvironment=None, isInsideTestCaseManager=False):
219 """Initialize the master process.
220
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.
225 """
226 self.__logger = logging.getLogger("TestCenter")
227
228 self.__config = Config.Configuration(cfgFile)
229
230 # The communication device to talk to slave
231 self.__com = (
232 None if slave is not None else IPC.ComMaster(connectTimeout=self.__config.getIPCConnectionTimeout())
233 )
234 # Slave instance to use if offline testing is used
235 self.__slave = slave
236 self.__slaveProc = None
237 self.__slaveEnvironment = slaveEnvironment
238
240 self.__shouldStop = False
241 self.__lock = threading.Lock()
242
243 self.__testCaseListener = None
244
245 self.__testedPkgList = []
246 # Slave processes that could not be killed.
247 self.__hangingSlaves = []
248 # The result tree
249 self.__xmlRoot = None
251
252 # Clean up existing result directory or create it.
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")
257 self.logfileDir = os.path.join(resultDir, "logfiles")
258
259 os.makedirs(resultDir, exist_ok=True)
260
261 def logRemoveError(_func, path, excinfo):
262 self.__logger.warning(f"Failed to remove {path}: {excinfo[1]}")
263
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)
268
269 # Path of a file to receive the stack trace in case of a crash
270 self.__stackTraceFile = None
271 if Utils.isLinux and "NO_MEVISLAB_DEBUGGER" not in os.environ:
272 self.__stackTraceFile = os.path.join(self.logfileDir, "lastCrashStackTrace.txt")
273 self.__slaveEnvironment = Utils.setStackTraceFile(self.__slaveEnvironment, self.__stackTraceFile)
274
275 self.__isInsideTestCaseManager = isInsideTestCaseManager
276
277 # The configuration of the current test run
278 self.__testCfg = self.__testConfigSkeleton()
279
280 self.__testTimeoutFactor = Master.defaultTestTimeoutFactor()
281
282 self.__updateProgressFunc = None
283
284 # If not running offline start a second MeVisLab instance.
285 if self.__slave is None:
286 if not self.__startSlave():
287 raise Exception("Failed to start slave")
288
289 def onExit(self):
290 for slaveProc in self.__hangingSlaves:
291 slaveProc.finish(printLog=True)
292 if not slaveProc.kill():
293 self.__logger.error("Finally killing process %s failed." % slaveProc.getPID())
294 self.__hangingSlaves.clear()
295
296 def __enter__(self):
297 return self
298
299 def __exit__(self, *_):
300 self.onExit()
301
302 def __del__(self):
303 self.onExit()
304
305 def stop(self):
306 self.__lock.acquire()
307 self.__shouldStop = True
308 self.__lock.release()
309
310 def shouldStop(self):
311 self.__lock.acquire()
312 result = self.__shouldStop
313 self.__lock.release()
314 return result
315
317 self.__lock.acquire()
318 result = copy.deepcopy(self.__testProgress)
319 self.__lock.release()
320 return result
321
322 def resetTestProgress(self, testCaseCount):
323 self.__lock.acquire()
324 self.__testProgress.reset(testCaseCount)
325 self.__lock.release()
326
327 def updateTestProgress(self, finishedTestCase=None, additionalTestCaseCount=None):
328 progressTestCase = None
329 if finishedTestCase is not None:
330 errorCount = 0
331 warningCount = 0
332 for function in finishedTestCase.findall("Function"):
333 status = int(function.get("status", str(Utils.TEST_STATUS_DISABLED)))
334 if status in (
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,
340 ):
341 errorCount += 1
342 elif status == Utils.TEST_STATUS_WARNING:
343 warningCount += 1
344 progressTestCase = ProgressTestCase(
345 finishedTestCase.get("name"),
346 int(finishedTestCase.get("status", str(Utils.TEST_STATUS_CANCELLED))),
347 errorCount,
348 warningCount,
349 float(finishedTestCase.get("duration", "0")),
350 )
351
352 self.__lock.acquire()
353 if progressTestCase is not None:
354 self.__testProgress.finishedTestCases.append(progressTestCase)
355 if additionalTestCaseCount is not None:
356 self.__testProgress.testCaseCount += additionalTestCaseCount
357 self.__lock.release()
358
359 def setUpdateProgressFunc(self, updateProgressFunc):
360 self.__updateProgressFunc = updateProgressFunc
361
362 def setActiveTestCase(self, testCase):
363 self.__lock.acquire()
364 self.__testProgress.activeTestCase = testCase
365 self.__lock.release()
366
367 @staticmethod
368 def __testConfigSkeleton():
369 """Returns an empty configuration filled with the required information."""
370 return etree.fromstring(
371 """
372 <Configuration>
373 <TestGroups />
374 <TestCases />
375 <TestSuites />
376 <Modules />
377 <Packages>
378 <Tested />
379 <Available />
380 <Ignored />
381 </Packages>
382 <TestOptions />
383 </Configuration>"""
384 )
385
386 def setTestCaseListener(self, listener):
387 self.__testCaseListener = listener
388
389 def setTestCases(self, testCaseList):
390 """Set the list of test cases to execute.
391
392 @param testCaseList List of names of test cases to execute.
393 """
394 assert self.__xmlRoot is None
395
396 xmlNode = self.__testCfg.find("TestCases")
397 for testCaseName in testCaseList:
398 etree.SubElement(xmlNode, "TestCase", name=testCaseName)
399
400 def setTestCasesToIgnore(self, testCaseToIgnoreList):
401 """Set the list of test cases to ignore."""
402 self.__testCasesToIgnoreList = testCaseToIgnoreList
403
404 def setTestGroups(self, testGroupList):
405 """Set the list of test groups to take into account."""
406 assert self.__xmlRoot is None
407
408 xmlNode = self.__testCfg.find("TestGroups")
409 for testGroupName in testGroupList:
410 etree.SubElement(xmlNode, "TestGroup", name=testGroupName)
411
412 def setTestSuites(self, testSuiteList):
413 assert self.__xmlRoot is None
414
415 xmlNode = self.__testCfg.find("TestSuites")
416 for testSuiteName in testSuiteList:
417 etree.SubElement(xmlNode, "TestSuite", name=testSuiteName)
418
419 def setModules(self, moduleList=None, filterList=None):
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").
423
424 @param moduleList List of names of modules to take into account.
425 @param filterList List of filters to select modules.
426 """
427 assert self.__xmlRoot is None
428 moduleList = moduleList or []
429 filterList = filterList or []
430
431 xmlNode = self.__testCfg.find("Modules")
432 for moduleName in moduleList:
433 etree.SubElement(xmlNode, "Module", name=moduleName)
434 for filter in filterList:
435 etree.SubElement(xmlNode, "Filter").text = filter
436
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
442 the public packages.
443
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.
447 """
448 assert self.__xmlRoot is None
449
450 testedPkgList = testedPkgList or []
451 availablePkgList = availablePkgList or []
452 ignoredPkgList = ignoredPkgList or []
453
454 self.__testedPkgList = testedPkgList
455 xmlNode = self.__testCfg.find("Packages/Tested")
456 for packageID in testedPkgList:
457 etree.SubElement(xmlNode, "Package", name=packageID)
458
459 xmlNode = self.__testCfg.find("Packages/Available")
460 for packageID in availablePkgList:
461 etree.SubElement(xmlNode, "Package", name=packageID)
462
463 xmlNode = self.__testCfg.find("Packages/Ignored")
464 for packageID in ignoredPkgList:
465 etree.SubElement(xmlNode, "Package", name=packageID)
466
467 @staticmethod
469 envTimeout = os.environ.get("MLAB_TEST_TIMEOUT_FACTOR", "")
470 if envTimeout:
471 try:
472 return float(envTimeout)
473 except ValueError:
474 pass
475 return 1.0
476
477 def setTestTimeoutFactor(self, factor: float):
478 self.__testTimeoutFactor = factor
479
480 def __sortNodes(self, nodes):
481 nodeList = []
482 testAgendas = {}
483 invalidNodes = []
484 invalidAgendas = {}
485 for testCaseNode in nodes:
486 testCaseAgenda, success = self.__buildTestCaseAgenda(testCaseNode)
487 if success:
488 testAgendas[testCaseNode] = testCaseAgenda
489 else:
490 invalidAgendas[testCaseNode] = testCaseAgenda
491 invalidNodes.append(testCaseNode)
492 for prefRendererSetting in ["", "hardware", "software"]:
493 for testCaseNode in nodes:
494 if testCaseNode in invalidNodes:
495 continue
496 testCaseAgenda = testAgendas[testCaseNode]
497 testCaseInfoNodes = testCaseAgenda.findall("TestCase")
498 testCaseInfoNode = testCaseInfoNodes[0]
499 preferredRendererNode = testCaseInfoNode.find("preferredRenderer")
500 if (
501 preferredRendererNode is None
502 and prefRendererSetting == ""
503 or preferredRendererNode is not None
504 and preferredRendererNode.text.lower() == prefRendererSetting
505 ):
506 nodeList.append(testCaseNode)
507 assert len(testAgendas) == len(nodeList)
508 return nodeList, testAgendas, invalidAgendas
509
510 @staticmethod
511 def __testTypeAndName(xmlNode):
512 # Just print anything if we are in verbose mode.
513 testType = xmlNode.get("type")
514 testName = (
515 xmlNode.get("name")
516 if testType == "FunctionalTestCase"
517 else "%s::%s" % (xmlNode.get("name"), xmlNode.get("module"))
518 )
519
520 return testType, testName
521
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.
525
526 @param xmlNode The XML node that contains the results.
527 @param status The status of the test case.
528 """
529 assert type(status) == int
530 assert xmlNode.tag == "TestCase"
531
532 statusStr = Utils.getStatusString(status)
533 testType, testName = self.__testTypeAndName(xmlNode)
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:
537 self.__logger.debug(msg)
538 elif status == Utils.TEST_STATUS_WARNING:
539 self.__logger.warning(msg)
540 else:
541 self.__logger.error(msg)
542
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.
552
553 @param xmlNode Node to add the found information.
554 @param comError The last communication error.
555 """
556 assert xmlNode.tag == "TestCase"
557
558 testType = xmlNode.get("type")
559 if testType == "GenericTestCase":
560 testName = "%s::%s" % (xmlNode.get("name"), xmlNode.get("module"))
561 else:
562 testName = xmlNode.get("name")
563
564 # Build a mapping from function names to result nodes.
565 funcDict = {}
566 for funcNode in xmlNode.findall("Function"):
567 funcDict.setdefault(funcNode.get("name"), funcNode)
568
569 # With the previously gathered error status the reason can be determined.
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
575
576 stackTraceMsg = None
577 if self.__stackTraceFile and os.path.exists(self.__stackTraceFile):
578 with open(self.__stackTraceFile, encoding="utf8", errors="surrogateescape") as f:
579 stacktrace = f.read().splitlines()
580 # filter out information lines about encountered threads (which there are a lot of):
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)]
583 # remove the stack frames from the signal handler itself:
584 lastSignalHandlerFrame = -1
585 for i in range(len(stacktrace)):
586 if "/mlabLinuxSignalHandler.cpp:" in stacktrace[i]:
587 lastSignalHandlerFrame = i
588 # the last one counts, therefore no break here
589 if lastSignalHandlerFrame != -1:
590 stacktrace = stacktrace[lastSignalHandlerFrame + 1 :]
591 if stacktrace:
592 stackTraceMsg = "MeVisLab crashed with the following stacktrace:\n" + "\n".join(stacktrace)
593 os.unlink(self.__stackTraceFile)
594
595 # Read the slave's logfile and parse it to get as much results as possible.
596 logHandler = LogHandler(self.__config.getSlaveLogFilePath())
597 logHandler.parseLogFile(testType, testName, status, funcDict, stackTraceMsg=stackTraceMsg)
598
599 return status
600
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.
605 """
606 resultNode = self.__sendRequest("BuildTestAgenda", self.__testCfg)
607 if resultNode.get("errorCode") != "Ok":
608 self.__logger.critical(
609 "Error (%s) in generating the test agenda!\n%s"
610 % (resultNode.get("errorCode"), resultNode.find("Error").text)
611 )
612 return False
613 self.__xmlRoot = resultNode.find("TestRun")
614 self.__testCfg = self.__verifyConfiguration(resultNode.find("Configuration"))
615
616 # Set package list as testing is started afterwards.
617 self.__sendRequest("SetPackageList", self.__testCfg.find("Packages"), 5, restart=True)
618 return True
619
620 def __buildTestCaseAgenda(self, testCaseNode):
621 def _stripHTML(html):
622 stripper = HTMLStripper()
623 unescaped = unescape(html)
624 stripper.feed(unescaped)
625 return stripper.get_data()
626
627 parameterNode = etree.Element("Parameters")
628 genericTestCase = True # is it possible to determine the testcase type here?
629 if genericTestCase:
630 moduleList = []
631 for module in self.__xmlRoot.findall("Information/Modules/Module"):
632 moduleList.append(module.attrib["name"])
633 parameterNode.attrib["modules"] = ",".join(moduleList)
634 if len(self.__testedPkgList) > 0:
635 parameterNode.attrib["packages"] = ",".join(self.__testedPkgList)
636 else:
637 # test every package
638 parameterNode.attrib["packages"] = "*/*"
639 parameterNode.append(testCaseNode)
640 resultNode = self.__sendRequest("BuildTestCaseAgenda", parameterNode, restart=True)
641 if resultNode.get("errorCode") != "Ok":
642 self.__logger.critical(
643 f'Error {resultNode.get("errorCode")} in generating the test case agenda!\n{resultNode.find("Error").text}'
644 )
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")}')
656 else:
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
673
674 return resultNode, True
675
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.
681
682 @param testCfg The test configuration returned by the slave.
683 @return The verified test configuration.
684 """
685 # Verify package settings.
686 packageRoot = testCfg.find("Packages")
687
688 tPkgNode = packageRoot.find("Tested")
689 for packageNode in tPkgNode.findall("Package"):
690 if packageNode.get("status") not in ("Ok", "Added", "Substituted"):
691 self.__logger.error(
692 "Failed to verify package (%s with status '%s')!"
693 % (packageNode.get("name"), packageNode.get("status"))
694 )
695
696 aPkgNode = packageRoot.find("Available")
697 for packageNode in aPkgNode.findall("Package"):
698 if packageNode.get("status") not in ("Ok", "Added", "Substituted"):
699 self.__logger.error(
700 "Failed to verify package (%s with status '%s')!"
701 % (packageNode.get("name"), packageNode.get("status"))
702 )
703 else:
704 if packageNode.get("status") not in ("Substituted"):
705 packageRoot.append(packageNode)
706
707 packageRoot.remove(tPkgNode)
708 packageRoot.remove(aPkgNode)
709
710 iPkgNode = packageRoot.find("Ignored")
711 packageRoot.remove(iPkgNode)
712
713 # Verify the module settings.
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):
717 self.__logger.error(
718 "Failed to verify module (%s with status '%s')" % (moduleNode.get("name"), moduleNode.get("status"))
719 )
720 testCfg.find("Modules").clear()
721
722 # Verify the test case settings.
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):
726 self.__logger.error(
727 "Failed to verify test case (%s with status '%s')"
728 % (testCaseNode.get("name"), testCaseNode.get("status"))
729 )
730 testCfg.find("TestCases").clear()
731
732 return testCfg
733
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 = {
742 "duration": "0",
743 "name": functionName,
744 "status": "2",
745 "is_disabled": "False",
746 "try": "1",
747 }
748 events = etree.SubElement(timeoutTooHighTestFunction, "Events")
749 filename = testCaseInfoNode.find("File").text
750 line = testCaseInfoNode.find("Line").text
751 event = etree.SubElement(events, "Event")
752 event.attrib = {
753 "file": filename,
754 "internal": "true",
755 "line": line,
756 "origin": "",
757 "type": "Error",
758 "timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
759 }
760 error = "The TestCase timeout (%ss) exceeds the maximum allowed timeout (%ss)" % (
761 timeout,
762 self.__config.getMaxTestTimeoutSeconds(),
763 )
764 etree.SubElement(event, "Message").text = error
765
766 def __buildListOfTestFunctionsToExecute(self, testCaseInfoNode, testCaseNode, funcList=None):
767 # Build up list of test functions that must be executed.
768 testFuncList = []
769 for funcNode in testCaseInfoNode.findall(".//Function"):
770 funcName = funcNode.get("name")
771 # If user selected to process only specific functions ignore the rest.
772 if funcList and funcName not in funcList and not Utils.isInternalTestCaseFunction(funcName):
773 continue
774
775 isDisabled = funcNode.get("is_disabled", str(False)) == str(True)
776 if testCaseInfoNode.get("name") in self.__testCasesToIgnoreList:
777 isDisabled = True
778 if isDisabled and funcList:
779 # If a user selected the function, then it should be executed
780 isDisabled = False
781 funcNode.set("is_disabled", str(False))
782 funcResultNode = etree.SubElement(testCaseNode, "Function", name=funcName, is_disabled=str(isDisabled))
783 testFuncList.append(funcName)
784 return testFuncList
785
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.
791
792 @param testResultNode The test case that should be executed.
793 @param funcList The list of test functions to execute.
794 """
795 assert testResultNode.tag == "TestCase"
796
797 testCaseNode = deepcopy(testResultNode)
798
799 # Build up list of test functions that must be executed.
800 testFuncList = self.__buildListOfTestFunctionsToExecute(testCaseInfoNode, testCaseNode, funcList)
801
802 testCaseName = testCaseInfoNode.get("name")
803 if self.__testCaseListener:
804 self.__testCaseListener.startTestCase(testCaseName, testFuncList)
805
806 # Gather time till timeout.
807 hasValidTimeout = True
808 timeout = int(testCaseInfoNode.get("timeout"))
809 if timeout == 0:
810 timeout = self.__config.getDefaultTestTimeoutSeconds()
811 if self.__config.getMaxTestTimeoutSeconds():
812 if timeout > self.__config.getMaxTestTimeoutSeconds():
813 self.__createTimeoutTooHighErrorFunction(testCaseInfoNode, testResultNode, timeout)
814 hasValidTimeout = False
815
816 # Use a bigger timeout if coverage or bullseye are enabled
817 if self.__config.isPythonCoverageEnabled():
818 timeout = timeout * 4.0
819 if self.__config.isBullseyeCoverageEnabled():
820 timeout = timeout * 4.0
821 # Overall timeout factor given on the command line:
822 timeout = timeout * self.__testTimeoutFactor
823
824 if hasValidTimeout:
825 retries, status = self.__executeTestFunctions(
826 testCaseInfoNode, testResultNode, testCaseNode, testFuncList, timeout
827 )
828 else:
829 retries = 0
830 status = Utils.TEST_STATUS_ERROR
831
832 # Set status and number of retries for the given test case.
833 testResultNode.set("status", str(status))
834 testResultNode.set("retries", str(retries))
835 # Print status message.
836 self.__statusMessage(testResultNode, status)
837 if self.__testCaseListener:
838 self.__testCaseListener.endTestCase()
839
840 @staticmethod
841 def transferNodeFromExecutionToResultXML(nodeName, executionNode, testResultNode):
842 coverageFile = executionNode.get(nodeName)
843 if executionNode.get(nodeName) is not None:
844 testResultNode.set(nodeName, coverageFile)
845
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
849
850 retries = 0
851 failedFuncList = []
852 timer = TestCaseTimer(timeout)
853
854 status = Utils.TEST_STATUS_OK
855 maxFuncStatus = Utils.TEST_STATUS_OK
856 cancelled = False
857 while testFuncList and not timer.isExpired() and not cancelled:
858 # If not set status to timeout and stop execution of remaining test functions.
859
860 if self.shouldStop():
861 cancelled = True
862 status = Utils.TEST_STATUS_CANCELLED
863 break
864
865 # if we have a test case listener, we request progress calls from the slave:
866 if self.__testCaseListener:
867 testCaseNode.set("requestProgress", "1")
868
869 testCaseNode.set(
870 "maxErrorMessagesPerTestFunction", str(self.__config.getReportOptions().maxErrorMessagesPerTestFunction)
871 )
872 testCaseNode.set(
873 "maxInfoMessagesPerTestFunction", str(self.__config.getReportOptions().maxInfoMessagesPerTestFunction)
874 )
875
876 # Initiate the processing of the remaining test functions.
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)
880
881 # Try to determine what was the reason for the test to fail.
882 comError = self.__com.getLastError() if self.__com else 0
883
884 # Delete slave process.
885 self.stopSlave()
886
887 # Handle logfile to get gathered results.
888 status = self.__testFailed(testCaseNode, comError)
889 # Restart slave.
890 if not self.__startSlave():
891 self.__logger.debug("Failed to re-start slave.")
892 else:
893 testCaseNode = executionNode.find("TestCase")
894
895 self.transferNodeFromExecutionToResultXML("pythonCoverage", executionNode, testResultNode)
896 self.transferNodeFromExecutionToResultXML("bullseyeCoverage", executionNode, testResultNode)
897
898 extraTestCasesResults = testCaseNode.find("ExtraTestCasesResults")
899 if extraTestCasesResults != None:
900 testResultNode.append(deepcopy(extraTestCasesResults))
901
902 # functions to test.
903 for funcResultNode in testCaseNode.findall("Function"):
904 funcName = funcResultNode.get("name")
905
906 # If status is not set, the function has not been executed yet.
907 funcStatus = funcResultNode.get("status")
908 if not funcStatus:
909 # Function not processed yet.
910 continue
911 else:
912 funcStatus = int(funcStatus)
913 if maxFuncStatus < funcStatus:
914 maxFuncStatus = funcStatus
915
916 if funcName not in failedFuncList:
917 funcResultNode.set("try", "1")
918
919 if funcStatus in (
920 Utils.TEST_STATUS_CRASHED,
921 Utils.TEST_STATUS_TIMEOUT,
922 ):
923 failedFuncList.append(funcName)
924 retries += 1
925 # Create copy of node as it will be overwritten in the next iteration.
926 testResultNode.append(deepcopy(funcResultNode))
927 funcResultNode.remove(funcResultNode.find("Events"))
928 else:
929 funcResultNode.set("try", "2")
930 failedFuncList.remove(funcName)
931
932 # Remove all processed but not failed for the first time functions from
933 # the temporary node.
934 if funcName not in failedFuncList:
935 testFuncList.remove(funcName)
936 # Move the processed function to the result node.
937 testResultNode.append(funcResultNode)
938 testCaseNode.remove(funcResultNode)
939 if funcStatus == Utils.TEST_STATUS_CANCELLED or funcStatus == Utils.TEST_STATUS_FUNCTION_NOT_FOUND:
940 cancelled = True
941 break
942
943 if timer.isExpired():
944 status = Utils.TEST_STATUS_TIMEOUT
945
946 # The status variable is used for status > 2 to handle situations where:
947 # - all test functions worked but the slave crashed or timed out.
948 # - the test case timed out but had crashes, too (global status should be
949 # timeout).
950 # Otherwise the maximum function status found in the reports is used.
951 status = status if status > Utils.TEST_STATUS_ERROR else maxFuncStatus
952 return retries, status
953
954 @staticmethod
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.
959 """
960 node = testResultNode.find("ExtraTestCasesResults")
961 if node is not None:
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)
968
970 return self.__config.isPythonCoverageEnabled() and self.__config.isGlobalPythonCoverageEnabled()
971
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
977 given tests.
978
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.
983 """
984 if not self.__buildTestAgenda():
985 self.__logger.error("Failed to build test agenda.")
986 return None
987
988 # Start the timer.
989 timeStarted = time.time()
990
991 testCaseInfos = self.__xmlRoot.find("Information/TestCases")
992
993 # Fetch counters which control after what number of testCases MLAB
994 # should be restarted.
995 freshCounterValue = self.__config.getRestartInterval()
996 restartCounter = freshCounterValue
997 restartDisabled = restartCounter == 0
998
999 testCaseInfosTestCases = testCaseInfos.findall("TestCase")
1000 testCaseInfosTestCases, testAgendas, invalidAgendas = self.__sortNodes(testCaseInfosTestCases)
1001
1002 if self.__testCaseListener:
1003 testNames = []
1004 for node in testCaseInfosTestCases:
1005 testNames.append(node.get("name"))
1006 self.__testCaseListener.startTestRun(testNames)
1007
1008 self.resetTestProgress(len(testCaseInfosTestCases))
1009
1010 failedTestCases = 0
1011 finishedTestCases = 0
1012 totalTestCases = len(testCaseInfosTestCases)
1013
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])
1019
1020 cancelled = False
1021 previousRenderer = ""
1022 for testCaseNode in testCaseInfosTestCases:
1023 if cancelled:
1024 testCaseInfos.remove(testCaseNode)
1025 self.updateTestProgress(finishedTestCase=testCaseNode)
1026 continue
1027 try:
1028 testCaseAgenda = testAgendas[testCaseNode]
1029 if testCaseAgenda != None:
1030
1031 testCaseInfoNodes = testCaseAgenda.findall("TestCase")
1032 assert len(testCaseInfoNodes) == 1
1033
1034 testCaseInfoNode = testCaseInfoNodes[0]
1035
1036 testCaseInfos.remove(testCaseNode)
1037 testCaseInfos.append(testCaseInfoNode)
1038
1039 preferredRendererNode = testCaseInfoNode.find("preferredRenderer")
1040 preferredRenderer = "" if preferredRendererNode is None else preferredRendererNode.text.lower()
1041 if preferredRenderer == "hardware" and Utils.isMesaForced():
1042 self.__logger.warning(
1043 'preferredRenderer settings "hardware" detected but use of software driver is enforced.'
1044 )
1045 restartNeeded = previousRenderer != preferredRenderer and (
1046 preferredRenderer == "software" or previousRenderer == "software"
1047 )
1048 previousRenderer = preferredRenderer
1049
1050 testResultNodes = testCaseAgenda.find("Results").findall("TestCase")
1051 if len(testResultNodes) > 1:
1052 self.updateTestProgress(additionalTestCaseCount=len(testResultNodes) - 1)
1053 totalTestCases += len(testResultNodes) - 1
1054 for testResultNode in testResultNodes:
1055 if cancelled:
1056 break
1057 try:
1058 testCaseName = testCaseInfoNode.get("name")
1059 debugType, debugName = self.__testTypeAndName(testResultNode)
1060
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.")
1068
1069 restartNeeded = False
1070 restartCounter = freshCounterValue
1071
1072 # decrement counter on each pass
1073 restartCounter -= 1
1074
1075 self.setActiveTestCase(testCaseName)
1076
1077 if self.__updateProgressFunc is not None:
1079 testCaseName, failedTestCases, finishedTestCases, totalTestCases
1080 )
1081
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:
1087 cancelled = True
1088
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,
1095 ):
1096 failedTestCases += 1
1097 finishedTestCases += 1
1098
1099 testResultNode.set("duration", str(timeFunctionStopped - timeFunctionStarted))
1100
1101 testResultNode.set("slave_id", "0" if self.__slave else str(self.__slaveProc.getID()))
1102
1103 resultNode.append(testResultNode)
1104
1105 self.__handleExtraTestCaseResults(testCaseInfos, resultNode, testResultNode)
1106
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))
1111 self.updateTestProgress(finishedTestCase=testResultNode)
1112 else:
1113 self.__logger.error(
1114 "Failed to build the test agenda for test case %s." % testCaseNode.get("name", "unknown")
1115 )
1116 except Exception as e:
1117 self.__logger.error("Error executing test case %s" % e)
1118
1120 resultNode.set("combinePythonCoverage", "True")
1121
1122 # Stop the timer.
1123 timeStopped = time.time()
1124
1125 self.__xmlRoot.set("timestamp", str(timeStarted))
1126 self.__xmlRoot.set("duration", str(timeStopped - timeStarted))
1127
1128 if stopSlaveAfterTestRun:
1129 self.stopSlave()
1130
1131 xml = Utils.saveXMLTree(self.__xmlRoot, self.__config.getResultFile())
1132
1133 if self.__testCaseListener:
1134 self.__testCaseListener.endTestRun()
1135
1136 return xml
1137
1138 def reset(self):
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.
1142 """
1143 self.__xmlRoot = None
1144 self.__testCfg = self.__testConfigSkeleton()
1145
1146 def getConfig(self):
1147 return self.__config
1148
1149 def __isSlaveRunning(self):
1150 return self.__slaveProc and self.__slaveProc.isRunning()
1151
1152 def __isSlaveStopped(self):
1153 return not self.__isSlaveRunning()
1154
1155 def logMessageWithPID(self, message):
1156 pidString = str(self.__slaveProc.getPID()) if self.__slaveProc is not None else str(None)
1157
1158 return "%s (PID: %s)" % (message, pidString)
1159
1160 def logDebugWithPID(self, message):
1161 self.__logger.debug(self.logMessageWithPID(message))
1162
1163 def logErrorWithPID(self, message):
1164 self.__logger.error(self.logMessageWithPID(message))
1165
1166 def logMessageWithLastError(self, message):
1167 errorCode, errorMessage = self.__com.getLastErrorWithMessage()
1168 return "%s ([%d] %s)" % (message, errorCode, errorMessage)
1169
1170 def __startSlave(self, forceSoftwareRendering=False):
1171 """Start the slave process.
1172 If the slave is still running it will be stopped.
1173
1174 @return True if the slave could be started and the connection could be established.
1175 """
1176 if self.__slave:
1177 return True
1178
1179 if self.__isSlaveRunning():
1180 if not self.stopSlave():
1181 self.__logger.error("Failed to stop the slave before restart.")
1182
1183 env = Utils.setForceMesa(self.__slaveEnvironment) if forceSoftwareRendering else self.__slaveEnvironment
1184 if self.__com:
1185 # server port is given through environment variable
1186 env = Utils.setTestCenterPort(env, self.__com.getPort())
1187 slaveProc = SlaveProcess(self.__config, self.logfileDir, env, self.__isInsideTestCaseManager)
1188 if slaveProc.hasStarted():
1189 self.__slaveProc = slaveProc
1190 else:
1191 return False
1192
1193 return self.__connectToSlave()
1194
1195 def __connectToSlave(self):
1196 retries = 0
1197 timerSleep = 1
1198 maxTries = 15
1199 while retries < maxTries and not self.__com.connect(timeout=self.__config.getIPCConnectionTimeout()):
1200 retries += 1
1201 self.logDebugWithPID(self.logMessageWithLastError("Failed to connect to slave in %i. try" % retries))
1202
1203 if not self.__slaveProc.isRunning():
1204 self.__logger.error("Slave died with exit code %s" % self.__slaveProc.getExitCode())
1205 self.__slaveProc.finish(printLog=True)
1206 return False
1207
1208 # Use a sleep timer to give the slave process more time to start up.
1209 self.__logger.debug("Sleeping for %s seconds" % (timerSleep))
1210 time.sleep(timerSleep)
1211
1212 if not self.__com.isConnected():
1213 # quit the mlab that we were not able to talk to
1214 self.logErrorWithPID("Failed to connect to slave within %d seconds." % (maxTries * timerSleep))
1215 if not self.stopSlave():
1216 self.__logger.error("Failed to stop the slave after connection error.")
1217 self.__slaveProc.finish(printLog=True)
1218 return False
1219 else:
1220 self.logDebugWithPID("Connected to slave in %s. try" % (retries + 1))
1221
1222 # Send slave a list of packages that are available. On first run an empty
1223 # string is sent; in the __buildTestAgenda call this list will be
1224 # retrieved. Later the verified list of tests will be sent.
1225 if self.__xmlRoot:
1226 resultNode = self.__sendRequest("SetPackageList", self.__testCfg.find("Packages"), 5)
1227 return resultNode.get("errorCode") == "Ok"
1228
1229 return True
1230
1231 def stopSlave(self):
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
1235 is sent.
1236 @return True if the slave was stopped.
1237 """
1238 if self.__slave:
1239 # ugly special condition for when a running MeVisLab instance is used
1240 # as slave.
1241 return True
1242
1243 self.logDebugWithPID("Shutting down slave")
1244
1245 if not self.__isSlaveStopped():
1246 # try soft shutdown first by requesting clean quit
1247 xmlNode = self.__sendRequest("Quit", timeout=1, tries=1)
1248
1249 if xmlNode.get("errorCode") == "Ok":
1250 self.logDebugWithPID("Response received, waiting for shutdown")
1251 # wait up to five seconds for the slave to stop:
1252 i = 0
1253 while not self.__isSlaveStopped() and i < 10:
1254 time.sleep(0.5)
1255 i += 1
1256
1257 # kindly asking for shutdown was obviously not enough, enforce shutdown
1258 if not self.__isSlaveStopped():
1259 self.logDebugWithPID("No response, killing slave")
1260 self.__slaveProc.kill()
1261
1262 if self.__isSlaveStopped():
1263 self.__slaveProc.finish(printLog=self.__slaveProc.wasKilled())
1264 else:
1265 self.logErrorWithPID("Failed to kill slave!")
1266 self.__hangingSlaves.append(self.__slaveProc)
1267 self.__slaveProc = None
1268
1269 return self.__isSlaveStopped()
1270
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).
1274
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.
1280 """
1281 trial = 0
1282
1283 requestNode = etree.Element("Command", type=command)
1284 requestNode.append(parameterNode if parameterNode != None else etree.Element("Dummy"))
1285
1286 if self.__slave:
1287 self.logDebugWithPID("Handling '%s' request" % command)
1288 resultNode = self.__slave.runCommand(requestNode)
1289 else:
1290 self.logDebugWithPID("Sending '%s' request to slave" % command)
1291 resultNode = None
1292 # The default timeout is taken from the configuration file.
1293 if not timeout:
1294 timeout = self.__config.getDefaultTestTimeoutSeconds()
1295
1296 string = None
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()):
1300 # since we allow receiving intermediate "Progress" result values,
1301 # we loop until we receive something that is not a "Progress" message:
1302 while not string and not timer.isExpired():
1303 string = self.__com.recv(timer.getSecondsRemaining())
1304 if string:
1305 recvNode = Utils.filterXMLString(string)
1306 self.logDebugWithPID("Received node of type '%s'" % recvNode.tag)
1307 if recvNode.tag == "Progress":
1308 shouldStop = False
1309 if self.__testCaseListener:
1310 try:
1311 self.__testCaseListener.startTestCaseFunction(
1312 recvNode.get("testType"),
1313 recvNode.get("testCase"),
1314 recvNode.get("testFunction"),
1315 )
1317 shouldStop = True
1318 # tell slave if it should stop or go on:
1319 self.__com.send("0" if shouldStop else "1", 5)
1320 string = None
1321 elif recvNode.tag == "Status":
1322 if self.__testCaseListener:
1323 self.__testCaseListener.endTestCaseFunction(int(recvNode.get("status")), 0)
1324 string = None
1325 else:
1326 resultNode = recvNode
1327 else:
1328 if self.__stackTraceFile and os.path.exists(self.__stackTraceFile):
1329 self.__logger.warning(
1330 "It seems the test client is crashing. Collecting the stacktrace may take some while."
1331 )
1332 # Add additional grace period to timer
1333 timer.extendTimeout(300)
1334 # wait for slave process to stop
1335 while not self.__isSlaveStopped() and not timer.isExpired():
1336 time.sleep(0.5)
1337 break
1338
1339 if not string:
1340 trial += 1
1341
1342 if restart and (trial < tries):
1343 self.__logger.info("Restarting due to communication error ...")
1344 self.__startSlave()
1345
1346 if resultNode is None:
1347 self.logDebugWithPID("Error: Received no result from slave.")
1348 resultNode = etree.Element("Result", errorCode="CommunicationError")
1349 etree.SubElement(resultNode, "Error").text = (
1350 "Communication error [%d]: %s" % self.__com.getLastErrorWithMessage()
1351 )
1352
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 ""))
1356 return resultNode
The connection's master.
Definition IPC.py:246
The coordinator of test execution.
Definition Master.py:209
resetTestProgress(self, testCaseCount)
Definition Master.py:322
__sendRequest(self, command, parameterNode=None, timeout=None, tries=2, restart=False)
Definition Master.py:1271
__doTest(self, testCaseInfoNode, testResultNode, funcList=None)
Definition Master.py:786
setModules(self, moduleList=None, filterList=None)
Specify the list of modules to test.
Definition Master.py:419
__statusMessage(self, xmlNode, status)
Definition Master.py:522
__init__(self, cfgFile=None, verbose=False, slave=None, slaveEnvironment=None, isInsideTestCaseManager=False)
Initialize the master process.
Definition Master.py:218
__verifyConfiguration(self, testCfg)
Definition Master.py:676
setTestSuites(self, testSuiteList)
Definition Master.py:412
setPackages(self, testedPkgList=None, availablePkgList=None, ignoredPkgList=None)
Specify the packages that should be tested and thought available.
Definition Master.py:437
setTestCases(self, testCaseList)
Set the list of test cases to execute.
Definition Master.py:389
logMessageWithPID(self, message)
Definition Master.py:1155
setTestTimeoutFactor(self, float factor)
Definition Master.py:477
__executeTestFunctions(self, testCaseInfoNode, testResultNode, testCaseNode, testFuncList, timeout)
Definition Master.py:846
run(self, funcDict=None, stopSlaveAfterTestRun=True)
The main method which handles all the request from the master.
Definition Master.py:972
__handleExtraTestCaseResults(testCaseInfos, resultNode, testResultNode)
Definition Master.py:955
reset(self)
Resets the master to the state after instantiation.
Definition Master.py:1138
logMessageWithLastError(self, message)
Definition Master.py:1166
__createTimeoutTooHighErrorFunction(self, testCaseInfoNode, testResultNode, timeout)
Definition Master.py:734
__buildListOfTestFunctionsToExecute(self, testCaseInfoNode, testCaseNode, funcList=None)
Definition Master.py:766
updateTestProgress(self, finishedTestCase=None, additionalTestCaseCount=None)
Definition Master.py:327
setActiveTestCase(self, testCase)
Definition Master.py:362
setTestGroups(self, testGroupList)
Set the list of test groups to take into account.
Definition Master.py:404
setTestCaseListener(self, listener)
Definition Master.py:386
__startSlave(self, forceSoftwareRendering=False)
Definition Master.py:1170
transferNodeFromExecutionToResultXML(nodeName, executionNode, testResultNode)
Definition Master.py:841
setTestCasesToIgnore(self, testCaseToIgnoreList)
Set the list of test cases to ignore.
Definition Master.py:400
stopSlave(self)
Stop the slave process.
Definition Master.py:1231
__testFailed(self, xmlNode, comError)
Definition Master.py:543
__buildTestCaseAgenda(self, testCaseNode)
Definition Master.py:620
setUpdateProgressFunc(self, updateProgressFunc)
Definition Master.py:359
__init__(self, name, status, errorCount, warningCount, duration)
Definition Master.py:189
__getCommandLine(self, config, logfileDir, isInsideTestCaseManager)
Definition Master.py:91
__startProcess(self, cmd, _, env)
Definition Master.py:111
__init__(self, config, logfileDir, env, isInsideTestCaseManager)
Definition Master.py:73