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
14# -- system imports ----------------------------------------------------------------------------{{{-
15import copy
16import datetime
17import os
18import re
19import sys
20import time
21import logging
22import traceback
23import subprocess
24import tempfile
25import threading
26
27from copy import deepcopy
28
29import xml.etree.cElementTree as etree
30
31try:
32 import TestSupport
33except:
34 pass
35
36# ----------------------------------------------------------------------------------------------}}}-
37
38# -- local imports -----------------------------------------------------------------------------{{{-
39from . import Config
40from . import IPC
41from . import Utils
42
43from .LogHandler import LogHandler
44
45if sys.version_info.major >= 3:
46 unicode = str
47
48# ----------------------------------------------------------------------------------------------}}}-
49
51 def __init__(self, seconds):
52 self.start = time.monotonic()
53 self.availableSeconds = seconds
54
55 def extendTimeout(self, seconds):
56 self.availableSeconds += seconds
57
59 passedSeconds = time.monotonic() - self.start
60 return self.availableSeconds - passedSeconds
61
62 def isExpired(self):
63 return self.getSecondsRemaining() <= 0.0
64
65
66# ----------------------------------------------------------------------------------------------}}}-
67
69 nextSlaveId = 0
70
71 def __init__(self, config, logfileDir, env, isInsideTestCaseManager):
72 self.__id = SlaveProcess.nextSlaveId
73 SlaveProcess.nextSlaveId += 1
74 self.__wasKilled = False
75 self.__hasStarted = False
76 self.__logFileFD = None
77 self.__logFile = None
78 self.__slaveLogFile = None
79 self.__logger = logging.getLogger("TestCenter")
80 self.__workDir = os.path.join(config.getResultDir(), "work")
81
82 cmd = self.__getCommandLine(config, logfileDir, isInsideTestCaseManager)
83 if cmd:
84 self.__startProcess(cmd, config, env)
85
86 def hasStarted(self):
87 return self.__hasStarted
88
89 def __getCommandLine(self, config, logfileDir, isInsideTestCaseManager):
90 try:
91 cmd = config.getMLABTestCenterCommand(macroName="TestCenter")
92 if cmd:
93 self.__slaveLogFile = os.path.join(logfileDir, f"slaveLogfile_{self.getID()}.log")
94 cmd.insert(1, '-logfile')
95 cmd.insert(2, self.__slaveLogFile)
96 if isInsideTestCaseManager:
97 cmd.append('--started-from-test-case-manager')
99 cmd = None
100
101 if not cmd:
102 self.__logger.error("Failed to start slave as no MeVisLab executable was found. " + \
103 "Please verify your configuration (see TestCase Manager's " + \
104 "Configuration tab or check the file %s)!" % config.getConfigFilePath())
105 return cmd
106
107 def __startProcess(self, cmd, _, env):
108 self.__logFileFD, self.__logFile = tempfile.mkstemp()
109 self.__logger.debug('Starting slave with command line %s' % ' '.join(cmd))
110 try:
111 preexec_fn = os.setpgrp if Utils.isUnix else None
112 self.__process = subprocess.Popen(cmd, cwd=self.__workDir, preexec_fn=preexec_fn, env=env,
113 stdin=subprocess.PIPE,
114 stdout=self.__logFileFD, stderr=self.__logFileFD)
115 self.__hasStarted = True
116 except OSError:
117 self.__logger.error("Failed to start slave (%s)\n%s" % (cmd, traceback.format_exc()))
118
119 def getID(self):
120 return self.__id
121
122 def getPID(self):
123 return self.__process.pid
124
125 def isRunning(self):
126 return self.getExitCode() is None
127
128 def getExitCode(self):
129 return self.__process.poll()
130
131 def kill(self):
132 self.__wasKilled = Utils.kill(self.__process)
133 return self.__wasKilled
134
135 def wasKilled(self):
136 return self.__wasKilled
137
138 def __printLogsAfterError(self):
139 try:
140 output = ''
141 if self.__slaveLogFile is not None and os.path.exists(self.__slaveLogFile):
142 with open(self.__slaveLogFile, 'r', errors="replace") as fh:
143 output = fh.read()
144 if os.path.exists(self.__logFile):
145 with open(self.__logFile, 'r', errors="replace") as fh:
146 output += fh.read()
147 if not output:
148 output = 'none'
149 self.logErrorWithPID('Slave output: %s' % output)
150 except IOError as e:
151 self.logErrorWithPID(f"Could not read log file: {e}")
152
153 def finish(self, printLog):
154 try:
155 os.fsync(self.__logFileFD)
156 os.close(self.__logFileFD)
157 except IOError as e:
158 self.logErrorWithPID("Could not close log file: %s" % str(e))
159 if printLog:
161 if os.path.exists(self.__logFile):
162 try:
163 Utils.removeFile(self.__logFile)
164 except IOError as e:
165 self.logErrorWithPID("Could not remove log file: %s" % str(e))
166 else:
167 self.logMessageWithPID("Log file does not exist: %s" % self.__logFile)
168
169 def logMessageWithPID(self, message):
170 return "%s (PID: %s)" % (message, str(self.getPID()))
171
172 def logDebugWithPID(self, message):
173 self.__logger.debug(self.logMessageWithPID(message))
174
175 def logErrorWithPID(self, message):
176 self.__logger.error(self.logMessageWithPID(message))
177
178
180 def __init__(self, name, status, errorCount, warningCount, duration):
181 self.duration = duration
182 self.name = name
183 self.status = status
184 self.errorCount = errorCount
185 self.warningCount = warningCount
186
187
189 def __init__(self):
190 self.activeTestCase = None
193
194 def reset(self, testCaseCount):
195 self.activeTestCase = None
196 self.finishedTestCases = []
197 self.testCaseCount = testCaseCount
198
199
200# -- Class Master ------------------------------------------------------------------------------{{{-
201
207class Master :
208 # -- member variables ------------------------------------------------------------------------{{{-
209
210 __config = None
211
212 __com = None
213
214 # Slave instance to use if offline testing is used.
215 __slave = None
216
217 __slaveProc = None
218
219
220 __hangingSlaves = []
221
222 # The configuration of the current test run.
223 __testCfg = None
224
225 __xmlRoot = None
226
227
228 __logger = None
229
230
231 __testCasesToIgnoreList = []
232
233
234 __stackTraceFile = None
235
236 # --------------------------------------------------------------------------------------------}}}-
237
238 # -- def __init__ ----------------------------------------------------------------------------{{{-
239
247 def __init__ (self, cfgFile=None, verbose=False, slave=None, slaveEnvironment=None,
248 isInsideTestCaseManager=False):
249 self.__logger = logging.getLogger("TestCenter")
250
251 self.__config = Config.Configuration(cfgFile)
252
253 self.__com = None if slave is not None else IPC.ComMaster(connectTimeout=self.__config.getIPCConnectionTimeout())
254 self.__slave = slave
255 self.__slaveEnvironment = slaveEnvironment
256
258 self.__shouldStop = False
259 self.__lock = threading.Lock()
260
261 self.__testCaseListener = None
262
263 self.__testedPkgList = []
264
265 # Clean up existing result directory or create it.
266 resultDir = self.__config.getResultDir()
267 imgDir = os.path.join(resultDir, "images")
268 fileDir = os.path.join(resultDir, "files")
269 workDir = os.path.join(resultDir, "work")
270 self.logfileDir = os.path.join(resultDir, "logfiles")
271
272 if not os.path.exists(resultDir):
273 os.makedirs(resultDir)
274 # Clean up old file directory.
275 Utils.rmtree(fileDir)
276 os.makedirs(fileDir)
277 # Clean up old image directory.
278 Utils.rmtree(imgDir)
279 os.makedirs(imgDir)
280 # Clean up old work directory.
281 Utils.rmtree(workDir)
282 os.makedirs(workDir)
283 # Clean up old logfile directory.
284 Utils.rmtree(self.logfileDir)
285 os.makedirs(self.logfileDir)
286
287 if Utils.isLinux and "NO_MEVISLAB_DEBUGGER" not in os.environ:
288 self.__stackTraceFile = os.path.join(self.logfileDir, "lastCrashStackTrace.txt")
289 self.__slaveEnvironment = Utils.setStackTraceFile(self.__slaveEnvironment,
290 self.__stackTraceFile)
291
292 self.__isInsideTestCaseManager = isInsideTestCaseManager
293
294 self.__testCfg = self.__testConfigSkeleton()
295
296 self.__updateProgressFunc = None
297
298 # If not running offline start a second MeVisLab instance.
299 if self.__slave is None:
300 if not self.__startSlave():
301 raise Exception("Failed to start slave")
302
303 # --------------------------------------------------------------------------------------------}}}-
304
305 def onExit(self):
306 for slaveProc in self.__hangingSlaves:
307 slaveProc.finish(printLog=True)
308 if not slaveProc.kill():
309 self.__logger.error("Finally killing process %s failed." % slaveProc.getPID())
310 self.__hangingSlaves.clear()
311
312 def __enter__(self):
313 return self
314
315 def __exit__(self, *_):
316 self.onExit()
317
318 def __del__(self):
319 self.onExit()
320
321 # --------------------------------------------------------------------------------------------}}}-
322
323 def stop(self):
324 self.__lock.acquire()
325 self.__shouldStop = True
326 self.__lock.release()
327
328 def shouldStop(self):
329 self.__lock.acquire()
330 result = self.__shouldStop
331 self.__lock.release()
332 return result
333
335 self.__lock.acquire()
336 result = copy.deepcopy(self.__testProgress)
337 self.__lock.release()
338 return result
339
340 def resetTestProgress(self, testCaseCount):
341 self.__lock.acquire()
342 self.__testProgress.reset(testCaseCount)
343 self.__lock.release()
344
345 def updateTestProgress(self, finishedTestCase=None, additionalTestCaseCount=None):
346 progressTestCase = None
347 if finishedTestCase is not None:
348 errorCount = 0
349 warningCount = 0
350 for function in finishedTestCase.findall("Function"):
351 status = int(function.get("status", str(Utils.TEST_STATUS_DISABLED)))
352 if status in (Utils.TEST_STATUS_ERROR, Utils.TEST_STATUS_CRASHED,
353 Utils.TEST_STATUS_FUNCTION_NOT_FOUND, Utils.TEST_STATUS_TIMEOUT):
354 errorCount += 1
355 elif status == Utils.TEST_STATUS_WARNING:
356 warningCount += 1
357 progressTestCase = ProgressTestCase(finishedTestCase.get("name"),
358 int(finishedTestCase.get("status", str(Utils.TEST_STATUS_CANCELLED))),
359 errorCount,
360 warningCount,
361 float(finishedTestCase.get("duration", "0")))
362
363 self.__lock.acquire()
364 if progressTestCase is not None:
365 self.__testProgress.finishedTestCases.append(progressTestCase)
366 if additionalTestCaseCount is not None:
367 self.__testProgress.testCaseCount += additionalTestCaseCount
368 self.__lock.release()
369
370 def setUpdateProgressFunc(self, updateProgressFunc):
371 self.__updateProgressFunc = updateProgressFunc
372
373 def setActiveTestCase(self, testCase):
374 self.__lock.acquire()
375 self.__testProgress.activeTestCase = testCase
376 self.__lock.release()
377
378 # -- def __testConfigSkeleton() --------------------------------------------------------------{{{-
379
380 def __testConfigSkeleton(self):
381 return etree.fromstring("""
382 <Configuration>
383 <TestGroups />
384 <TestCases />
385 <TestSuites />
386 <Modules />
387 <Packages>
388 <Tested />
389 <Available />
390 <Ignored />
391 </Packages>
392 <TestOptions />
393 </Configuration>""")
394 # --------------------------------------------------------------------------------------------}}}-
395
396 def setTestCaseListener(self, listener):
397 self.__testCaseListener = listener
398
399 # -- def setTestCases ------------------------------------------------------------------------{{{-
400
402 def setTestCases (self, testCaseList):
403 assert self.__xmlRoot == None
404
405 xmlNode = self.__testCfg.find("TestCases")
406 for testCaseName in testCaseList:
407 etree.SubElement(xmlNode, "TestCase", name=testCaseName)
408 # --------------------------------------------------------------------------------------------}}}-
409
410 # -- def setIgnoreTestCases ------------------------------------------------------------------------{{{-
411
413 def setTestCasesToIgnore (self, testCaseToIgnoreList):
414 self.__testCasesToIgnoreList__testCasesToIgnoreList = testCaseToIgnoreList
415 # --------------------------------------------------------------------------------------------}}}-
416
417 # -- def setTestGroups -----------------------------------------------------------------------{{{-
418
420 def setTestGroups (self, testGroupList):
421 assert self.__xmlRoot == None
422
423 xmlNode = self.__testCfg.find("TestGroups")
424 for testGroupName in testGroupList:
425 etree.SubElement(xmlNode, "TestGroup", name=testGroupName)
426 # --------------------------------------------------------------------------------------------}}}-
427
428 def setTestSuites (self, testSuiteList):
429 assert self.__xmlRoot == None
430
431 xmlNode = self.__testCfg.find("TestSuites")
432 for testSuiteName in testSuiteList:
433 etree.SubElement(xmlNode, "TestSuite", name=testSuiteName)
434
435
436 # -- def setModules --------------------------------------------------------------------------{{{-
437
442 def setModules (self, moduleList=[], filterList=[]):
443 assert self.__xmlRoot == None
444
445 xmlNode = self.__testCfg.find("Modules")
446 for moduleName in moduleList:
447 etree.SubElement(xmlNode, "Module", name=moduleName)
448 for filter in filterList:
449 etree.SubElement(xmlNode, "Filter").text = filter
450 # --------------------------------------------------------------------------------------------}}}-
451
452 # -- def setPackages -------------------------------------------------------------------------{{{-
453
462 def setPackages (self, testedPkgList=[], availablePkgList=[], ignoredPkgList=[]):
463 assert self.__xmlRoot == None
464
465 self.__testedPkgList = testedPkgList
466
467 xmlNode = self.__testCfg.find("Packages/Tested")
468 for packageID in testedPkgList:
469 etree.SubElement(xmlNode, "Package", name=packageID)
470
471 xmlNode = self.__testCfg.find("Packages/Available")
472 for packageID in availablePkgList:
473 etree.SubElement(xmlNode, "Package", name=packageID)
474
475 xmlNode = self.__testCfg.find("Packages/Ignored")
476 for packageID in ignoredPkgList:
477 etree.SubElement(xmlNode, "Package", name=packageID)
478 # --------------------------------------------------------------------------------------------}}}-
479
480 def __testTypeAndName(self, xmlNode):
481 # Just print anything if we are in verbose mode.
482 testType = xmlNode.get('type')
483 testName = xmlNode.get('name') if testType == 'FunctionalTestCase' else "%s::%s" % (xmlNode.get('name'), xmlNode.get('module'))
484
485 return testType, testName
486
487 # -- def __statusMessage ---------------------------------------------------------------------{{{-
488
492 def __statusMessage (self, xmlNode, status):
493 assert type(status) == int
494 assert xmlNode.tag == 'TestCase'
495
496 statusStr = Utils.getStatusString(status)
497 testType, testName = self.__testTypeAndName(xmlNode)
498 fillIn = "."*(80-len(testType+testName+statusStr)-2)
499 msg = "%s::%s%s%s" % (testType, testName, fillIn, statusStr)
500 if status == Utils.TEST_STATUS_OK or status == Utils.TEST_STATUS_DO_NOT_TEST_MODULE:
501 self.__logger.debug(msg)
502 elif status == Utils.TEST_STATUS_WARNING:
503 self.__logger.warning(msg)
504 else:
505 self.__logger.error(msg)
506
507 # --------------------------------------------------------------------------------------------}}}-
508
509 # -- def __testFailed ------------------------------------------------------------------------{{{-
510
520 def __testFailed (self, xmlNode, comError):
521 assert xmlNode.tag == "TestCase"
522
523 testType = xmlNode.get('type')
524 if testType == u"GenericTestCase":
525 testName = "%s::%s" % (xmlNode.get('name'), xmlNode.get('module'))
526 else:
527 testName = xmlNode.get('name')
528
529 # Build a mapping from function names to result nodes.
530 funcDict = {}
531 for funcNode in xmlNode.findall('Function'):
532 funcDict.setdefault(funcNode.get('name'), funcNode)
533
534 # With the previously gathered error status the reason can be determined.
535 status = Utils.TEST_STATUS_ERROR
536 if comError in (IPC.Error.EMPTY_MESSAGE, IPC.Error.ON_RECEIVING_2):
537 status = Utils.TEST_STATUS_CRASHED
538 elif comError == IPC.Error.ON_RECEIVING_TIMEOUT:
539 status = Utils.TEST_STATUS_TIMEOUT
540
541 stackTraceMsg = None
542 if self.__stackTraceFile and os.path.exists(self.__stackTraceFile):
543 with open(self.__stackTraceFile, encoding = "utf8", errors = "surrogateescape") as f:
544 stacktrace = f.read().splitlines()
545 # filter out information lines about encountered threads (which there are a lot of):
546 new_lwp_re = re.compile(r"\[New LWP \d+\]", flags=re.IGNORECASE)
547 stacktrace =[x for x in stacktrace if not new_lwp_re.fullmatch(x)]
548 # remove the stack frames from the signal handler itself:
549 lastSignalHandlerFrame = -1
550 for i in range(len(stacktrace)):
551 if "/mlabLinuxSignalHandler.cpp:" in stacktrace[i]:
552 lastSignalHandlerFrame = i
553 # the last one counts, therefore no break here
554 if lastSignalHandlerFrame != -1:
555 stacktrace = stacktrace[lastSignalHandlerFrame+1:]
556 if stacktrace:
557 stackTraceMsg = ("MeVisLab crashed with the following stacktrace:\n" +
558 "\n".join(stacktrace))
559 os.unlink(self.__stackTraceFile)
560
561 # Read the slave's logfile and parse it to get as much results as possible.
562 logHandler = LogHandler(self.__config.getSlaveLogFilePath())
563 logHandler.parseLogFile(testType, testName, status, funcDict, stackTraceMsg=stackTraceMsg)
564
565 return status
566 # --------------------------------------------------------------------------------------------}}}-
567
568 # -- def __buildTestAgenda -------------------------------------------------------------------{{{-
569
572 def __buildTestAgenda (self):
573 resultNode = self.__sendRequest("BuildTestAgenda", self.__testCfg)
574 if resultNode.get('errorCode') != "Ok":
575 self.__logger.critical("Error (%s) in generating the test agenda!\n%s" % (resultNode.get('errorCode'), resultNode.find('Error').text))
576 return False
577 self.__xmlRoot = resultNode.find('TestRun')
578 self.__testCfg = self.__verifyConfiguration(resultNode.find('Configuration'))
579
580 # Set package list as testing is started afterwards.
581 self.__sendRequest('SetPackageList', self.__testCfg.find('Packages'), 5, restart=True)
582 return True
583 # --------------------------------------------------------------------------------------------}}}-
584
585 # -- def __buildTestCaseAgenda -------------------------------------------------------------------{{{-
586 def __buildTestCaseAgenda (self, testCaseNode):
587 parameterNode = etree.Element('Parameters')
588 genericTestCase = True # is it possible to determine the testcase type here?
589 if genericTestCase:
590 moduleList = []
591 for module in self.__xmlRoot.findall('Information/Modules/Module'):
592 moduleList.append(module.attrib['name'])
593 parameterNode.attrib['modules'] = ','.join(moduleList)
594 if len(self.__testedPkgList) > 0:
595 parameterNode.attrib['packages'] = ','.join(self.__testedPkgList)
596 else:
597 # test every package
598 parameterNode.attrib['packages'] = '*/*'
599 parameterNode.append(testCaseNode)
600 resultNode = self.__sendRequest("BuildTestCaseAgenda", parameterNode, restart=True)
601 if resultNode.get('errorCode') != "Ok":
602 self.__logger.critical("Error (%s) in generating the test case agenda!\n%s" % (resultNode.get('errorCode'), resultNode.find('Error').text))
603 return None
604
605 return resultNode
606
607 # --------------------------------------------------------------------------------------------}}}-
608
609 # -- def __verifyConfiguration ---------------------------------------------------------------{{{-
610
617 def __verifyConfiguration (self, testCfg):
618 # Verify package settings.
619 packageRoot = testCfg.find('Packages')
620
621 tPkgNode = packageRoot.find('Tested')
622 for packageNode in tPkgNode.findall('Package'):
623 if packageNode.get('status') not in ('Ok', 'Added', 'Substituted'):
624 self.__logger.error("Failed to verify package (%s with status '%s')!" % (packageNode.get('name'), packageNode.get('status')))
625
626 aPkgNode = packageRoot.find('Available')
627 for packageNode in aPkgNode.findall('Package'):
628 if packageNode.get('status') not in ('Ok', 'Added', 'Substituted'):
629 self.__logger.error("Failed to verify package (%s with status '%s')!" % (packageNode.get('name'), packageNode.get('status')))
630 else:
631 if packageNode.get('status') not in ('Substituted'):
632 packageRoot.append(packageNode)
633
634 packageRoot.remove(tPkgNode)
635 packageRoot.remove(aPkgNode)
636
637 iPkgNode = packageRoot.find('Ignored')
638 packageRoot.remove(iPkgNode)
639
640 # Verify the module settings.
641 for moduleNode in testCfg.findall('Modules/Module'):
642 isModuleFiltered = moduleNode.get('isModuleFiltered') == str(True)
643 if not isModuleFiltered and moduleNode.get('status') != str(Utils.TEST_STATUS_OK):
644 self.__logger.error("Failed to verify module (%s with status '%s')" % (moduleNode.get('name'), moduleNode.get('status')))
645 testCfg.find('Modules').clear()
646
647 # Verify the test case settings.
648 for testCaseNode in testCfg.findall('TestCases/TestCase'):
649 isNotInTestGroups = testCaseNode.get('isNotInTestGroups') == str(True)
650 if not isNotInTestGroups and testCaseNode.get('status') != str(Utils.TEST_STATUS_OK):
651 self.__logger.error("Failed to verify test case (%s with status '%s')" % (testCaseNode.get('name'), testCaseNode.get('status')))
652 testCfg.find('TestCases').clear()
653
654 return testCfg
655 # --------------------------------------------------------------------------------------------}}}-
656
657 def __createTimeoutTooHighErrorFunction(self, testCaseInfoNode, testResultNode, timeout):
658 functionName = 'TEST001_VerifyTestCaseTimeout'
659 testFunctions = testCaseInfoNode.find('TestFunctions')
660 testFunction = etree.SubElement(testFunctions, "Function")
661 testFunction.attrib = {'basename': 'VerifyTestCaseTimeout', 'is_disabled': 'False', 'name': functionName}
662 etree.SubElement(testFunction, "Documentation").text = 'Verifies if the TestCase timeout.'
663 timeoutTooHighTestFunction = etree.SubElement(testResultNode, "Function")
664 timeoutTooHighTestFunction.attrib = {'duration': '0', 'name': functionName, 'status': '2', 'is_disabled': 'False', 'try': '1'}
665 events = etree.SubElement(timeoutTooHighTestFunction, 'Events')
666 filename = testCaseInfoNode.find('File').text
667 line = testCaseInfoNode.find('Line').text
668 event = etree.SubElement(events, 'Event')
669 event.attrib = {'file': filename, 'internal': 'true', 'line': line, 'origin': '', 'type': 'Error',
670 'timestamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
671 error = 'The TestCase timeout (%ss) exceeds the maximum allowed timeout (%ss)' % (timeout, self.__config.getMaxTestTimeoutSeconds())
672 etree.SubElement(event, 'Message').text = error
673
674 def __buildListOfTestFunctionsToExecute (self, testCaseInfoNode, testCaseNode, funcList=None):
675 # Build up list of test functions that must be executed.
676 testFuncList = []
677 for funcNode in testCaseInfoNode.findall(".//Function"):
678 funcName = funcNode.get('name')
679 # If user selected to process only specific functions ignore the rest.
680 if funcList and funcName not in funcList and not funcName in Utils.VIRTUAL_TEST_CASE_FUNCTIONS:
681 continue
682
683 isDisabled = funcNode.get("is_disabled", str(False)) == str(True)
684 if testCaseInfoNode.get('name') in self.__testCasesToIgnoreList__testCasesToIgnoreList:
685 isDisabled = True
686 if isDisabled and funcList:
687 # If a user selected the function, then it should be executed
688 isDisabled = False
689 funcNode.set('is_disabled', str(False))
690 funcResultNode = etree.SubElement(testCaseNode, 'Function', name=funcName, is_disabled=str(isDisabled))
691 testFuncList.append(funcName)
692 return testFuncList
693
694
695 # -- def __doTest ----------------------------------------------------------------------------{{{-
696
702
703 def __doTest (self, testCaseInfoNode, testResultNode, funcList=None):
704 assert testResultNode.tag == 'TestCase'
705
706 testCaseNode = deepcopy(testResultNode)
707
708 # Build up list of test functions that must be executed.
709 testFuncList = self.__buildListOfTestFunctionsToExecute(testCaseInfoNode, testCaseNode, funcList)
710
711 testCaseName = testCaseInfoNode.get('name')
712 if self.__testCaseListener:
713 self.__testCaseListener.startTestCase(testCaseName, testFuncList)
714
715 # Gather time till timeout.
716 hasValidTimeout = True
717 timeout = int(testCaseInfoNode.get('timeout'))
718 if timeout == 0:
719 timeout = self.__config.getDefaultTestTimeoutSeconds()
720 if self.__config.getMaxTestTimeoutSeconds():
721 if timeout > self.__config.getMaxTestTimeoutSeconds():
722 self.__createTimeoutTooHighErrorFunction(testCaseInfoNode, testResultNode, timeout)
723 hasValidTimeout = False
724
725 # Use a bigger timeout if coverage or bullseye are enabled
726 if self.__config.isPythonCoverageEnabled():
727 timeout = timeout * 4.
728 if self.__config.isBullseyeCoverageEnabled():
729 timeout = timeout * 4.
730
731 if hasValidTimeout:
732 retries, status = self.__executeTestFunctions(testCaseInfoNode, testResultNode, testCaseNode, testFuncList, timeout)
733 else:
734 retries = 0
735 status = Utils.TEST_STATUS_ERROR
736
737 # Set status and number of retries for the given test case.
738 testResultNode.set("status", unicode(status))
739 testResultNode.set("retries", unicode(retries))
740 # Print status message.
741 self.__statusMessage(testResultNode, status)
742 if self.__testCaseListener:
743 self.__testCaseListener.endTestCase()
744 # --------------------------------------------------------------------------------------------}}}-
745
746
747 def transferNodeFromExecutionToResultXML(self, nodeName, executionNode, testResultNode):
748 coverageFile = executionNode.get(nodeName)
749 if executionNode.get(nodeName) is not None:
750 testResultNode.set(nodeName, coverageFile)
751
752 def __executeTestFunctions (self, testCaseInfoNode, testResultNode, testCaseNode, testFuncList, timeout):
753 if testCaseNode.get('status') == str(Utils.TEST_STATUS_DO_NOT_TEST_MODULE):
754 return 0, Utils.TEST_STATUS_DO_NOT_TEST_MODULE
755
756 retries = 0
757 failedFuncList = []
758 timer = TestCaseTimer(timeout)
759
760 status = Utils.TEST_STATUS_OK
761 maxFuncStatus = Utils.TEST_STATUS_OK
762 cancelled = False
763 while testFuncList and not timer.isExpired() and not cancelled:
764 # If not set status to timeout and stop execution of remaining test functions.
765
766 if self.shouldStop():
767 cancelled = True
768 status = Utils.TEST_STATUS_CANCELLED
769 break
770
771 # if we have a test case listener, we request progress calls from the slave:
772 if self.__testCaseListener:
773 testCaseNode.set("requestProgress", "1")
774
775 testCaseNode.set("maxErrorMessagesPerTestFunction", str(self.__config.getReportOptions().maxErrorMessagesPerTestFunction))
776 testCaseNode.set("maxInfoMessagesPerTestFunction", str(self.__config.getReportOptions().maxInfoMessagesPerTestFunction))
777
778 # Initiate the processing of the remaining test functions.
779 executionNode = self.__sendRequest("DoTest", testCaseNode, timeout=timer, tries=1)
780 if executionNode.get('errorCode') != "Ok":
781 self.__logger.debug('Restarting due to error in test execution.\n%s' % executionNode.find('Error').text)
782
783 # Try to determine what was the reason for the test to fail.
784 comError = self.__com.getLastError() if self.__com else 0
785
786 # Delete slave process.
787 self.stopSlave()
788
789 # Handle logfile to get gathered results.
790 status = self.__testFailed(testCaseNode, comError)
791 # Restart slave.
792 if not self.__startSlave():
793 self.__logger.debug('Failed to re-start slave.')
794 else:
795 testCaseNode = executionNode.find('TestCase')
796
797 self.transferNodeFromExecutionToResultXML('pythonCoverage', executionNode, testResultNode)
798 self.transferNodeFromExecutionToResultXML('bullseyeCoverage', executionNode, testResultNode)
799
800 extraTestCasesResults = testCaseNode.find('ExtraTestCasesResults')
801 if extraTestCasesResults != None:
802 testResultNode.append(deepcopy(extraTestCasesResults))
803
804 # functions to test.
805 for funcResultNode in testCaseNode.findall('Function'):
806 funcName = funcResultNode.get("name")
807
808 # If status is not set, the function has not been executed yet.
809 funcStatus = funcResultNode.get("status")
810 if not funcStatus:
811 # Function not processed yet.
812 continue
813 else:
814 funcStatus = int(funcStatus)
815 if maxFuncStatus < funcStatus:
816 maxFuncStatus = funcStatus
817
818 if funcName not in failedFuncList:
819 funcResultNode.set('try', '1')
820
821 if funcStatus in (Utils.TEST_STATUS_CRASHED , Utils.TEST_STATUS_TIMEOUT):
822 failedFuncList.append(funcName)
823 retries += 1
824 # Create copy of node as it will be overwritten in the next iteration.
825 testResultNode.append(deepcopy(funcResultNode))
826 funcResultNode.remove(funcResultNode.find("Events"))
827 else:
828 funcResultNode.set('try', '2')
829 failedFuncList.remove(funcName)
830
831 # Remove all processed but not failed for the first time functions from
832 # the temporary node.
833 if funcName not in failedFuncList:
834 testFuncList.remove(funcName)
835 # Move the processed function to the result node.
836 testResultNode.append(funcResultNode)
837 testCaseNode.remove(funcResultNode)
838 if funcStatus == Utils.TEST_STATUS_CANCELLED or funcStatus == Utils.TEST_STATUS_FUNCTION_NOT_FOUND:
839 cancelled = True
840 break
841
842 if timer.isExpired():
843 status = Utils.TEST_STATUS_TIMEOUT
844
845
846 # The status variable is used for status > 2 to handle situations where:
847 # - all test functions worked but the slave crashed or timed out.
848 # - the test case timed out but had crashes, too (global status should be
849 # timeout).
850 # Otherwise the maximum function status found in the reports is used.
851 status = status if status > Utils.TEST_STATUS_ERROR else maxFuncStatus
852 return retries, status
853
854
855 # -- def __handleExtraTestCaseResults --------------------------------------------------------{{{-
856
859 def __handleExtraTestCaseResults(self, testCaseInfos, resultNode, testResultNode):
860 node = testResultNode.find("ExtraTestCasesResults")
861 if node != None:
862 testResultNode.remove(node)
863 for node in node.findall("ExtraTestCaseResult"):
864 testCaseInfos.append(node.find("Information/TestCase"))
865 extraTestCaseResult = node.find("Result/TestCase")
866 extraTestCaseResult.set("slave_id", testResultNode.get("slave_id"))
867 resultNode.append(extraTestCaseResult)
868 # --------------------------------------------------------------------------------------------}}}-
869
870 # -- def shouldMeasurePythonCoverageGlobally -------------------------------------------------{{{-
872 return self.__config.isPythonCoverageEnabled() and self.__config.isGlobalPythonCoverageEnabled()
873 # --------------------------------------------------------------------------------------------}}}-
874
875 # -- def run ---------------------------------------------------------------------------------{{{-
876
885 def run(self, funcDict=None, stopSlaveAfterTestRun=True):
886 if not self.__buildTestAgenda():
887 self.__logger.error("Failed to build test agenda.")
888 return None
889
890 # Start the timer.
891 timeStarted = time.time()
892
893 testCaseInfos = self.__xmlRoot.find('Information/TestCases')
894
895 # Fetch counters which control after what number of testCases MLAB
896 # should be restarted.
897 freshCounterValue = self.__config.getRestartInterval()
898 restartCounter = freshCounterValue
899 restartDisabled = (restartCounter == 0)
900
901 testCaseInfosTestCases = testCaseInfos.findall('TestCase')
902 if self.__testCaseListener:
903 testNames = []
904 for node in testCaseInfosTestCases:
905 testNames.append(node.get('name'))
906 self.__testCaseListener.startTestRun(testNames)
907
908 self.resetTestProgress(len(testCaseInfosTestCases))
909
910 failedTestCases = 0
911 finishedTestCases = 0
912 totalTestCases = len(testCaseInfosTestCases)
913
914 resultNode = self.__xmlRoot.find('Results')
915 cancelled = False
916 for testCaseNode in testCaseInfosTestCases:
917 if cancelled:
918 testCaseInfos.remove(testCaseNode)
919 self.updateTestProgress(finishedTestCase=testCaseNode)
920 continue
921 try:
922 testCaseAgenda = self.__buildTestCaseAgenda(testCaseNode)
923 if testCaseAgenda != None:
924
925 testCaseInfoNodes = testCaseAgenda.findall('TestCase')
926 assert(len(testCaseInfoNodes) == 1)
927 testCaseInfoNode = testCaseInfoNodes[0]
928
929 testCaseInfos.remove(testCaseNode)
930 testCaseInfos.append(testCaseInfoNode)
931
932 testResultNodes = testCaseAgenda.find('Results').findall('TestCase')
933 if len(testResultNodes) > 1:
934 self.updateTestProgress(additionalTestCaseCount=len(testResultNodes)-1)
935 totalTestCases += len(testResultNodes)-1
936 for testResultNode in testResultNodes:
937 if cancelled:
938 break
939 try:
940 testCaseName = testCaseInfoNode.get('name')
941 debugType, debugName = self.__testTypeAndName(testResultNode)
942
943 preferredRendererNode = testCaseInfoNode.find('preferredRenderer')
944 preferredRenderer = "" if preferredRendererNode is None else preferredRendererNode.text.lower()
945 forceSoftwareRendering = (preferredRenderer == "software")
946 if preferredRenderer == "hardware" and Utils.isMesaForced():
947 self.__logger.warning('preferredRenderer settings "hardware" detected but use of software driver is enforced.')
948
949 if not restartDisabled or forceSoftwareRendering:
950 shouldRestart = (restartCounter == 0)
951 if shouldRestart or forceSoftwareRendering:
952 self.__logger.info("Restart slave for test case %s::%s" % (debugType, debugName))
953 if not self.__startSlave(forceSoftwareRendering=forceSoftwareRendering):
954 raise Exception('Failed to start slave.')
955
956 restartCounter = freshCounterValue
957
958 #decrement counter on each pass
959 restartCounter -= 1
960
961 self.setActiveTestCase(testCaseName)
962
963 if self.__updateProgressFunc is not None:
964 self.__updateProgressFunc(testCaseName, failedTestCases, finishedTestCases, totalTestCases)
965
966 timeFunctionStarted = time.time()
967 funcList = funcDict[testCaseName] if (funcDict and testCaseName in funcDict) else None
968 self.__doTest(testCaseInfoNode, testResultNode, funcList)
969 timeFunctionStopped = time.time()
970 if int(testResultNode.get("status")) == Utils.TEST_STATUS_CANCELLED:
971 cancelled = True
972
973 if int(testResultNode.get("status")) in (Utils.TEST_STATUS_CRASHED,
974 Utils.TEST_STATUS_ERROR,
975 Utils.TEST_STATUS_FUNCTION_NOT_FOUND,
976 Utils.TEST_STATUS_TIMEOUT):
977 failedTestCases += 1
978 finishedTestCases += 1
979
980 testResultNode.set('duration', str(timeFunctionStopped-timeFunctionStarted))
981
982 testResultNode.set('slave_id', '0' if self.__slave else str(self.__slaveProc.getID()))
983
984 resultNode.append(testResultNode)
985
986 self.__handleExtraTestCaseResults(testCaseInfos, resultNode, testResultNode)
987
988 except Exception as e:
989 traceback.print_exc()
990 self.__logger.error("Error executing test case %s::%s\n%s" % (debugType, debugName, e))
991 testResultNode.set("status", unicode(Utils.TEST_STATUS_TIMEOUT))
992 self.updateTestProgress(finishedTestCase=testResultNode)
993 else:
994 self.__logger.error("Failed to build the test agenda for test case %s." % testCaseNode.get('name', 'unknown'))
995 except Exception as e:
996 self.__logger.error("Error executing test case %s" % e)
997
999 resultNode.set("combinePythonCoverage", "True")
1000
1001 # Stop the timer.
1002 timeStopped = time.time()
1003
1004 self.__xmlRoot.set('timestamp', str(timeStarted))
1005 self.__xmlRoot.set('duration', str(timeStopped-timeStarted))
1006
1007 if stopSlaveAfterTestRun:
1008 self.stopSlave()
1009
1010 xml = Utils.saveXMLTree(self.__xmlRoot, self.__config.getResultFile())
1011
1012 if self.__testCaseListener:
1013 self.__testCaseListener.endTestRun()
1014
1015 return xml
1016 # --------------------------------------------------------------------------------------------}}}-
1017
1018 # -- def reset() -----------------------------------------------------------------------------{{{-
1019
1022 def reset(self):
1023 self.__xmlRoot = None
1024 self.__testCfg = self.__testConfigSkeleton()
1025 # --------------------------------------------------------------------------------------------}}}-
1026
1027 # -- def getConfig() -------------------------------------------------------------------------{{{-
1028 def getConfig (self):
1029 return self.__config
1030 # --------------------------------------------------------------------------------------------}}}-
1031
1032 # -- def __isSlaveRunning -------------------------------------------------------------------------{{{-
1033
1034 def __isSlaveRunning (self):
1035 return self.__slaveProc and self.__slaveProc.isRunning()
1036
1037 def __isSlaveStopped(self):
1038 return not self.__isSlaveRunning()
1039 # --------------------------------------------------------------------------------------------}}}-
1040
1041 def logMessageWithPID(self, message):
1042 pidString = str(self.__slaveProc.getPID()) if self.__slaveProc is not None else str(None)
1043
1044 return "%s (PID: %s)" % (message, pidString)
1045
1046 def logDebugWithPID(self, message):
1047 self.__logger.debug(self.logMessageWithPID(message))
1048
1049 def logErrorWithPID(self, message):
1050 self.__logger.error(self.logMessageWithPID(message))
1051
1052 def logMessageWithLastError(self, message):
1053 errorCode, errorMessage = self.__com.getLastErrorWithMessage()
1054 return "%s ([%d] %s)" % (message, errorCode, errorMessage)
1055
1056 # -- def __startSlave ------------------------------------------------------------------------{{{-
1057
1061 def __startSlave (self, forceSoftwareRendering=False):
1062 if self.__slave:
1063 return True
1064
1065 if self.__isSlaveRunning():
1066 if not self.stopSlave():
1067 self.__logger.error("Failed to stop the slave before restart.")
1068
1069 env = Utils.setForceMesa(self.__slaveEnvironment) if forceSoftwareRendering else self.__slaveEnvironment
1070 if self.__com:
1071 # server port is given through environment variable
1072 env = Utils.setTestCenterPort(env, self.__com.getPort())
1073 slaveProc = SlaveProcess(self.__config, self.logfileDir, env, self.__isInsideTestCaseManager)
1074 if slaveProc.hasStarted():
1075 self.__slaveProc = slaveProc
1076 else:
1077 return False
1078
1079 return self.__connectToSlave()
1080 # --------------------------------------------------------------------------------------------}}}-
1081
1082 def __connectToSlave(self):
1083 retries = 0
1084 timerSleep = 1
1085 maxTries = 15
1086 while retries < maxTries and not self.__com.connect(timeout = self.__config.getIPCConnectionTimeout()):
1087 retries += 1
1088 self.logDebugWithPID(self.logMessageWithLastError("Failed to connect to slave in %i. try" % retries))
1089
1090 if not self.__slaveProc.isRunning():
1091 self.__logger.error("Slave died with exit code %s" % self.__slaveProc.getExitCode())
1092 self.__slaveProc.finish(printLog=True)
1093 return False
1094
1095 # Use a sleep timer to give the slave process more time to start up.
1096 self.__logger.debug("Sleeping for %s seconds" % (timerSleep))
1097 time.sleep(timerSleep)
1098
1099 if not self.__com.isConnected():
1100 # quit the mlab that we were not able to talk to
1101 self.logErrorWithPID("Failed to connect to slave within %d seconds." % (maxTries*timerSleep))
1102 if not self.stopSlave():
1103 self.__logger.error("Failed to stop the slave after connection error.")
1104 self.__slaveProc.finish(printLog=True)
1105 return False
1106 else:
1107 self.logDebugWithPID("Connected to slave in %s. try" % (retries+1))
1108
1109 # Send slave a list of packages that are available. On first run an empty
1110 # string is sent; in the __buildTestAgenda call this list will be
1111 # retrieved. Later the verified list of tests will be sent.
1112 if self.__xmlRoot:
1113 resultNode = self.__sendRequest('SetPackageList', self.__testCfg.find('Packages'), 5)
1114 return resultNode.get('errorCode') == "Ok"
1115
1116 return True
1117
1118 # -- def stopSlave ---------------------------------------------------------------------------{{{-
1119 # # Stop the slave process.
1120 # If the slave is running this method tries to stop the slave. This method
1121 # will run until the slave has stopped. After 3 tries on unix a KILL signal
1122 # is sent.
1123 # @return True if the slave was stopped.
1124 def stopSlave (self):
1125 if self.__slave:
1126 #ugly special condition for when a running MeVisLab instance is used
1127 #as slave.
1128 return True
1129
1130 self.logDebugWithPID("Shutting down slave")
1131
1132 if not self.__isSlaveStopped():
1133 #try soft shutdown first by requesting clean quit
1134 xmlNode = self.__sendRequest('Quit', timeout=1, tries=1)
1135
1136 if xmlNode.get('errorCode') == 'Ok':
1137 self.logDebugWithPID("Response received, waiting for shutdown")
1138 # wait up to five seconds for the slave to stop:
1139 i = 0
1140 while not self.__isSlaveStopped() and i < 10:
1141 time.sleep(0.5)
1142 i += 1
1143
1144 #kindly asking for shutdown was obviously not enough, enforce shutdown
1145 if not self.__isSlaveStopped():
1146 self.logDebugWithPID("No response, killing slave")
1147 self.__slaveProc.kill()
1148
1149 if self.__isSlaveStopped():
1150 self.__slaveProc.finish(printLog=self.__slaveProc.wasKilled())
1151 else:
1152 self.logErrorWithPID("Failed to kill slave!")
1153 self.__hangingSlaves.append(self.__slaveProc)
1154 self.__slaveProc = None
1155
1156 return self.__isSlaveStopped()
1157 # --------------------------------------------------------------------------------------------}}}-
1158
1159 # -- def __sendRequest -----------------------------------------------------------------------{{{-
1160
1167 def __sendRequest (self, command, parameterNode=None, timeout=None, tries=2, restart=False):
1168 trial = 0
1169
1170 requestNode = etree.Element('Command', type=command)
1171 requestNode.append(parameterNode if parameterNode != None else etree.Element('Dummy'))
1172
1173 if self.__slave:
1174 self.logDebugWithPID("Handling '%s' request" % command)
1175 resultNode = self.__slave.runCommand(requestNode)
1176 else:
1177 self.logDebugWithPID("Sending '%s' request to slave" % command)
1178 resultNode = None
1179 # The default timeout is taken from the configuration file.
1180 if not timeout:
1181 timeout = self.__config.getDefaultTestTimeoutSeconds()
1182
1183 string = None
1184 while ( not string
1185 and (trial < tries)):
1186 timer = timeout if isinstance(timeout, TestCaseTimer) else TestCaseTimer(timeout)
1187 if self.__com.send(etree.tostring(requestNode), timer.getSecondsRemaining()):
1188 # since we allow receiving intermediate "Progress" result values,
1189 # we loop until we receive something that is not a "Progress" message:
1190 while not string and not timer.isExpired():
1191 string = self.__com.recv(timer.getSecondsRemaining())
1192 if string:
1193 recvNode = Utils.filterXMLString(string)
1194 self.logDebugWithPID("Received node of type '%s'" % recvNode.tag)
1195 if recvNode.tag == "Progress":
1196 shouldStop = False
1197 if self.__testCaseListener:
1198 try:
1199 self.__testCaseListener.startTestCaseFunction(recvNode.get("testType"), recvNode.get("testCase"), recvNode.get("testFunction"))
1201 shouldStop = True
1202 # tell slave if it should stop or go on:
1203 self.__com.send("0" if shouldStop else "1", 5)
1204 string = None
1205 elif recvNode.tag == "Status":
1206 if self.__testCaseListener:
1207 self.__testCaseListener.endTestCaseFunction(int(recvNode.get("status")), 0)
1208 string = None
1209 else:
1210 resultNode = recvNode
1211 else:
1212 if self.__stackTraceFile and os.path.exists(self.__stackTraceFile):
1213 self.__logger.warning("It seems the test client is crashing. Collecting the stacktrace may take some while.")
1214 # Add additional grace period to timer
1215 timer.extendTimeout(300)
1216 # wait for slave process to stop
1217 while not self.__isSlaveStopped() and not timer.isExpired():
1218 time.sleep(0.5)
1219 break
1220
1221 if not string:
1222 trial += 1
1223
1224 if restart and (trial < tries):
1225 self.__logger.info("Restarting due to communication error ...")
1226 self.__startSlave()
1227
1228 if resultNode == None:
1229 self.logDebugWithPID("Error: Received no result from slave.")
1230 resultNode = etree.Element('Result', errorCode='CommunicationError')
1231 etree.SubElement(resultNode, 'Error').text = "Communication error [%d]: %s" % self.__com.getLastErrorWithMessage()
1232 self.logDebugWithPID("Result error code: %s" % resultNode.get('errorCode'))
1233 content = etree.tostring(resultNode).decode()
1234 self.logDebugWithPID("Result content: %s%s" % (content[:150], " [...]" if len(content)>150 else ""))
1235 return resultNode
1236 # --------------------------------------------------------------------------------------------}}}-
1237# ----------------------------------------------------------------------------------------------}}}-
The connection's master.
Definition IPC.py:230
The coordinator of test execution.
Definition Master.py:207
transferNodeFromExecutionToResultXML(self, nodeName, executionNode, testResultNode)
Definition Master.py:747
resetTestProgress(self, testCaseCount)
Definition Master.py:340
__sendRequest(self, command, parameterNode=None, timeout=None, tries=2, restart=False)
Definition Master.py:1167
__doTest(self, testCaseInfoNode, testResultNode, funcList=None)
Definition Master.py:703
__statusMessage(self, xmlNode, status)
Definition Master.py:492
__init__(self, cfgFile=None, verbose=False, slave=None, slaveEnvironment=None, isInsideTestCaseManager=False)
The default constructor.
Definition Master.py:248
__verifyConfiguration(self, testCfg)
Definition Master.py:617
setTestSuites(self, testSuiteList)
Definition Master.py:428
setTestCases(self, testCaseList)
Set the list of test cases to execute.
Definition Master.py:402
logMessageWithPID(self, message)
Definition Master.py:1041
__testTypeAndName(self, xmlNode)
Definition Master.py:480
__executeTestFunctions(self, testCaseInfoNode, testResultNode, testCaseNode, testFuncList, timeout)
Definition Master.py:752
run(self, funcDict=None, stopSlaveAfterTestRun=True)
The main method which handles all the request from the master.
Definition Master.py:885
setPackages(self, testedPkgList=[], availablePkgList=[], ignoredPkgList=[])
Specify the packages that should be tested and thought available.
Definition Master.py:462
reset(self)
Resets the master to the state after instantiation.
Definition Master.py:1022
logMessageWithLastError(self, message)
Definition Master.py:1052
__createTimeoutTooHighErrorFunction(self, testCaseInfoNode, testResultNode, timeout)
Definition Master.py:657
__buildListOfTestFunctionsToExecute(self, testCaseInfoNode, testCaseNode, funcList=None)
Definition Master.py:674
updateTestProgress(self, finishedTestCase=None, additionalTestCaseCount=None)
Definition Master.py:345
setActiveTestCase(self, testCase)
Definition Master.py:373
setTestGroups(self, testGroupList)
Set the list of test groups to respect.
Definition Master.py:420
setTestCaseListener(self, listener)
Definition Master.py:396
__startSlave(self, forceSoftwareRendering=False)
Definition Master.py:1061
setModules(self, moduleList=[], filterList=[])
Specify the list of modules to test.
Definition Master.py:442
setTestCasesToIgnore(self, testCaseToIgnoreList)
Set the list of test cases to ignore.
Definition Master.py:413
__testFailed(self, xmlNode, comError)
Definition Master.py:520
__buildTestCaseAgenda(self, testCaseNode)
Definition Master.py:586
__handleExtraTestCaseResults(self, testCaseInfos, resultNode, testResultNode)
Definition Master.py:859
setUpdateProgressFunc(self, updateProgressFunc)
Definition Master.py:370
__init__(self, name, status, errorCount, warningCount, duration)
Definition Master.py:180
__getCommandLine(self, config, logfileDir, isInsideTestCaseManager)
Definition Master.py:89
__startProcess(self, cmd, _, env)
Definition Master.py:107
__init__(self, config, logfileDir, env, isInsideTestCaseManager)
Definition Master.py:71