def getDisplayInfo(self, machines, app): for machine in machines: displayName, pid, proc = self.createDisplay(machine, app) if displayName: return machine, displayName, pid, proc else: plugins.printWarning("Virtual display program Xvfb not available on " + machine, stdout=True)
def createView(self): hbox = gtk.HBox() self.label = gtk.Label() self.label.set_name("GUI status") self.label.set_ellipsize(pango.ELLIPSIZE_END) # It seems difficult to say 'ellipsize when you'd otherwise need # to enlarge the window', so we'll have to settle for a fixed number # of max char's ... The current setting (90) is just a good choice # based on my preferred window size, on the test case I used to # develop this code. (since different chars have different widths, # the optimal number depends on the string to display) \ Mattias++ self.label.set_max_width_chars(90) self.label.set_use_markup(True) self.label.set_markup(plugins.convertForMarkup("TextTest started at " + plugins.localtime() + ".")) hbox.pack_start(self.label, expand=False, fill=False) imageDir = plugins.installationDir("images") try: staticIcon = os.path.join(imageDir, "throbber_inactive.png") temp = gtk.gdk.pixbuf_new_from_file(staticIcon) self.throbber = gtk.Image() self.throbber.set_from_pixbuf(temp) animationIcon = os.path.join(imageDir, "throbber_active.gif") self.animation = gtk.gdk.PixbufAnimation(animationIcon) hbox.pack_end(self.throbber, expand=False, fill=False) except Exception, e: plugins.printWarning("Failed to create icons for the status throbber:\n" + str(e) + \ "\nAs a result, the throbber will be disabled.", stdout=True) self.throbber = None
def notifyExtraTest(self, testPath, appName, versions): rootSuite = self.getRootSuite(appName, versions) if rootSuite: rootSuite.addTestCaseWithPath(testPath) else: message = "Couldn't add extra test for application: " + str(appName) plugins.printWarning(message)
def startXvfb(self, startArgs, machine): for _ in range(5): self.diag.info("Starting Xvfb using args " + repr(startArgs)) # Ignore job control signals for remote processes # Otherwise the ssh process gets killed, but the stuff it's started remotely doesn't, and we leak Xvfb processes preexec_fn = None if machine == "localhost" else self.ignoreSignals displayProc = subprocess.Popen(startArgs, preexec_fn=preexec_fn, stdin=open(os.devnull), stdout=subprocess.PIPE, stderr=open(os.devnull, "w")) line = plugins.retryOnInterrupt(displayProc.stdout.readline) if "Time Out!" in line: displayProc.wait() displayProc.stdout.close() self.diag.info("Timed out waiting for Xvfb to come up") # We try again and hope for a better process ID! continue try: displayNum, pid = map(int, line.strip().split(",")) displayProc.stdout.close() return self.getDisplayName(machine, displayNum), pid, displayProc except ValueError: #pragma : no cover - should never happen, just a fail-safe sys.stderr.write("ERROR: Failed to parse startXvfb.py line :\n " + line + "\n") displayProc.stdout.close() return None, None, None messages = "Failed to start Xvfb in 5 attempts, giving up" plugins.printWarning(messages) return None, None, None
def getDisplay(self, machines, app): for machine in machines: displayName, pid = self.createDisplay(machine, app) if displayName: return machine, displayName, pid else: plugins.printWarning("Virtual display program Xvfb not available on " + machine) return None, None, None
def getAccelerator(self, title): realAcc = guiConfig.getCompositeValue("gui_accelerators", title) if realAcc: key, mod = gtk.accelerator_parse(realAcc) if gtk.accelerator_valid(key, mod): return realAcc else: plugins.printWarning("Keyboard accelerator '" + realAcc + "' for action '" \ + title + "' is not valid, ignoring ...")
def extract(self, test, sourceFile, targetFile, collationErrFile): stem = os.path.splitext(os.path.basename(targetFile))[0] scripts = test.getCompositeConfigValue("collate_script", stem) if len(scripts) == 0: return shutil.copyfile(sourceFile, targetFile) self.collationProc = None stdin = None for script in scripts: args = getScriptArgs(script) if self.collationProc: stdin = self.collationProc.stdout else: args.append(sourceFile) self.diag.info("Opening extract process with args " + repr(args)) if script is scripts[-1]: stdout = open(targetFile, "w") stderr = open(collationErrFile, "w") else: stdout = subprocess.PIPE stderr = subprocess.STDOUT useShell = os.name == "nt" and len(scripts) == 1 self.collationProc = self.runCollationScript(args, test, stdin, stdout, stderr, useShell) if not self.collationProc: if os.path.isfile(targetFile): os.remove(targetFile) errorMsg = "Could not find extract script '" + script + "', not extracting file at\n" + sourceFile + "\n" stderr = open(collationErrFile, "w") stderr.write(errorMsg) plugins.printWarning(errorMsg.strip()) stderr.close() return if self.collationProc: self.diag.info("Waiting for collation process to terminate...") self.collationProc.wait() if self.collationProc: self.collationProc = None else: procName = args[0] briefText = "KILLED (" + os.path.basename(procName) + ")" freeText = "Killed collation script '" + procName + "'\n while collating file at " + sourceFile + "\n" test.changeState(Killed(briefText, freeText, test.state)) stdout.close() stderr.close() if os.path.getsize(sourceFile) > 0 and os.path.getsize(targetFile) == 0 and os.path.getsize(collationErrFile) == 0: # Collation scripts that don't write anything shouldn't produce empty files... # If they write errors though, we might want to pick those up os.remove(targetFile) collateErrMsg = test.app.filterErrorText(collationErrFile) if collateErrMsg: msg = "Errors occurred running collate_script(s) " + " and ".join(scripts) + \ "\nwhile trying to extract file at \n" + sourceFile + " : \n" + collateErrMsg plugins.printWarning(msg)
def addValuesToTotal(self, localName, valuesLine, totalValues): catValues = plugins.commasplit(valuesLine.strip()) try: for value in catValues: catName, count = value.split("=") if not totalValues.has_key(catName): totalValues[catName] = 0 totalValues[catName] += int(count) except ValueError: plugins.printWarning("Found truncated or old format batch report (" + localName + ") - could not parse result correctly.")
def _getFromApps(self, method, *args, **kwargs): callables = [ plugins.Callable(method, app, *args) for app in self.apps ] aggregator = plugins.ResponseAggregator(callables) try: return aggregator(**kwargs) except plugins.AggregationError, e: app = self.apps[e.index] plugins.printWarning("GUI configuration '" + "::".join(args) +\ "' differs between applications, ignoring that from " + repr(app) + "\n" + \ "Value was " + repr(e.value2) + ", change from " + repr(e.value1), stdout=True) return e.value1
def __getitem__(self, key): if self.readingFile: msg = ( "Bug file at " + self.readingFile + " has duplicated sections named '" + key + "', the later ones will be ignored" ) plugins.printWarning(msg) return OrderedDict.__getitem__(self, key)
def saveToRepository(self, test): testRepository = self.repositories[test.app] targetFile = os.path.join(testRepository, test.app.name, getVersionName(test.app, self.allApps), \ test.getRelPath(), self.fileName) if os.path.isfile(targetFile): plugins.printWarning("File already exists at " + targetFile + " - not overwriting!") else: try: plugins.ensureDirExistsForFile(targetFile) shutil.copyfile(test.getStateFile(), targetFile) except IOError: plugins.printWarning("Could not write file at " + targetFile)
def setUpVirtualDisplay(self, guiSuites): if len(guiSuites) == 0: return machines = self.findMachines(guiSuites) displayCount = max((suite.getConfigValue("virtual_display_count") for suite in guiSuites)) for _ in range(displayCount): displayInfo = self.getDisplayInfo(machines, guiSuites[0].app) if displayInfo: self.displayInfo.append(displayInfo) self.guiSuites = guiSuites elif len(machines) > 0: plugins.printWarning("Failed to start virtual display on " + ",".join(machines) + " - using real display.")
def _getFromApps(self, method, *args, **kwargs): prevValue = None for app in self.apps: currValue = method(app, *args, **kwargs) toUse = self.chooseValueFrom(prevValue, currValue) if toUse is None and prevValue is not None: plugins.printWarning("GUI configuration '" + "::".join(args) +\ "' differs between applications, ignoring that from " + repr(app) + "\n" + \ "Value was " + repr(currValue) + ", change from " + repr(prevValue)) else: prevValue = toUse return prevValue
def makeParser(fileName): parser = ConfigParser() # Default behaviour transforms to lower case: we want case-sensitive parser.optionxform = str # There isn't a nice way to change the behaviour on getting a duplicate section # so we use a nasty way :) parser._sections = ParserSectionDict(fileName) try: parser.read(fileName) parser._sections.readingFile = None return parser except Exception: plugins.printWarning("Bug file at " + fileName + " not understood, ignoring")
def setUpVirtualDisplay(self, guiSuites): if len(guiSuites) == 0: return machines = self.findMachines(guiSuites) machine, display, pid = self.getDisplay(machines, guiSuites[0].app) if display: self.displayName = display self.displayMachine = machine self.displayPid = pid self.guiSuites = guiSuites plugins.log.info("Tests will run with DISPLAY variable set to " + display) elif len(machines) > 0: plugins.printWarning("Failed to start virtual display on " + ",".join(machines) + " - using real display.")
def getRootSuite(self, appName, versions): for app, testSuite in self.appSuites.items(): if app.name == appName and app.versions == versions: return testSuite dirCache = self.makeDirectoryCache(appName) if dirCache: newApp = self.addApplication(appName, dirCache, versions)[0] return self.createEmptySuite(newApp) else: message = "Couldn't create directory cache for application: " + str(appName) plugins.printWarning(message) return None
def getAppRepositoryInfo(self): appInfo = seqdict() for app in self.appsToGenerate: repository = app.getCompositeConfigValue("batch_result_repository", self.batchSession) if not repository: continue repository = os.path.join(repository, app.name) if not os.path.isdir(repository): plugins.printWarning("Batch result repository " + repository + " does not exist - not creating pages for " + repr(app)) continue pageTitle = app.getCompositeConfigValue("historical_report_page_name", self.batchSession) appInfo.setdefault(pageTitle, []).append((app, repository)) return appInfo
def makeParser(self, fileName): parser = ConfigParser() # Default behaviour transforms to lower case: we want case-sensitive parser.optionxform = str parser._sections = seqdict() # There isn't a nice way to change the behaviour on getting a duplicate section # so we use a nasty way :) realLookup = parser._sections.__getitem__ parser._sections.__getitem__ = plugins.Callable(self.lookupSection, fileName, realLookup) try: parser.read(fileName) parser._sections.__getitem__ = realLookup return parser except: plugins.printWarning("Bug file at " + fileName + " not understood, ignoring", stderr=True, stdout=False)
def createView(self): # Create toplevel window to show it all. self.topWindow = gtk.Window(gtk.WINDOW_TOPLEVEL) self.topWindow.set_name("Top Window") try: import stockitems stockitems.register(self.topWindow) except Exception: #pragma : no cover - should never happen plugins.printWarning("Failed to register texttest stock icons.") plugins.printException() iconFile = self.getIcon() try: self.topWindow.set_icon_from_file(iconFile) except Exception, e: plugins.printWarning("Failed to set texttest window icon.\n" + str(e), stdout=True)
def migrate(self, test): for bugFileName in test.findAllStdFiles("knownbugs"): parser = ConfigParser() # Default behaviour transforms to lower case: we want case-sensitive parser.optionxform = str try: parser.read(bugFileName) except Exception: plugins.printWarning("Bug file at " + bugFileName + " not understood, ignoring") continue if not parser.has_section("Migrated section 1"): self.describe(test, " - " + os.path.basename(bugFileName)) sys.stdout.flush() self.updateFile(bugFileName, parser) else: self.describe(test, " (already migrated)")
def findDefinitionFileStems(self, test, tmpFiles, ignoreMissing): if ignoreMissing: return test.expandedDefFileStems() stems = test.expandedDefFileStems("regenerate") for defFile in test.defFileStems("builtin") + test.defFileStems("default"): if tmpFiles.has_key(defFile): stems.append(defFile) # On the whole, warn the user if unexpected things get generated # Make an exception for recording as usecase-related files may be recorded that # won't necessarily be re-recorded if not test.app.isRecording(): plugins.printWarning("A file was generated with stem '" + defFile + "'.\n" + "This stem is intended to indicate a definition file and hence should not be generated.\n" + "Please change the configuration so that the file is called something else,\n" + "or adjust the config file setting 'definition_file_stems' accordingly.") return stems
def createView(self): # Create toplevel window to show it all. self.topWindow = gtk.Window(gtk.WINDOW_TOPLEVEL) self.topWindow.set_name("Top Window") try: import stockitems stockitems.register(self.topWindow) except: #pragma : no cover - should never happen plugins.printWarning("Failed to register texttest stock icons.") plugins.printException() self.topWindow.set_icon_from_file(self.getIcon()) self.setWindowTitle() self.topWindow.add(self.subguis[0].createView()) self.adjustSize() self.topWindow.show() self.topWindow.set_default_size(-1, -1) self.notify("TopWindow", self.topWindow) self.topWindow.connect("delete-event", self.windowClosed) return self.topWindow
def startXvfb(self, startArgs, machine): for i in range(5): self.diag.info("Starting Xvfb using args " + repr(startArgs)) self.displayProc = subprocess.Popen(startArgs, stdin=open(os.devnull), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) line = plugins.retryOnInterrupt(self.displayProc.stdout.readline) if "Time Out!" in line: self.displayProc.wait() self.displayProc.stdout.close() self.diag.info("Timed out waiting for Xvfb to come up") # We try again and hope for a better process ID! continue try: displayNum, pid = map(int, line.strip().split(",")) self.displayProc.stdout.close() return self.getDisplayName(machine, displayNum), pid except ValueError: #pragma : no cover - should never happen, just a fail-safe plugins.log.info("Failed to parse line :\n " + line + self.displayProc.stdout.read()) return None, None messages = "Failed to start Xvfb in 5 attempts, giving up" plugins.printWarning(messages) return None, None
def getAppRepositoryInfo(self): appInfo = OrderedDict() for suite in self.suitesToGenerate: repository = getBatchRepository(suite) if not repository: continue app = suite.app repository = os.path.join(repository, app.name) if not os.path.isdir(repository): plugins.printWarning( "Batch result repository " + repository + " does not exist - not creating pages for " + repr(app) ) continue pageTitle = app.getBatchConfigValue("historical_report_page_name") extraApps = [] for extraApp in app.extras: extraPageTitle = extraApp.getBatchConfigValue("historical_report_page_name") if extraPageTitle != pageTitle and extraPageTitle != extraApp.getDefaultPageName(): appInfo.setdefault(extraPageTitle, []).append((extraApp, repository, [])) else: extraApps.append(extraApp) appInfo.setdefault(pageTitle, []).append((app, repository, extraApps)) return appInfo
def lookupSection(self, name, fileName, realLookup): msg = "Bug file at " + fileName + " has duplicated sections named '" + name + "', the later ones will be ignored" plugins.printWarning(msg, stderr=True, stdout=False) return realLookup(name)
def checkRepository(self, repository, app): if not os.path.isdir(repository): plugins.printWarning("Batch result repository " + repository + " does not exist - not creating pages for " + repr(app)) return False return True
def setActionSequence(self, actionSequence): self.actionSequence = [] # Copy the action sequence, so we can edit it and mark progress for action in actionSequence: self.actionSequence.append(action) def handleExceptions(self, method, *args): try: method(*args) return True except plugins.TextTestError, e: self.failTest(str(e)) except: exceptionText = plugins.getExceptionString() plugins.printWarning("Caught exception while running " + repr(self.test) + " changing state to UNRUNNABLE :\n" + exceptionText.rstrip(), stdout=True) self.failTest(exceptionText) return False def failTest(self, excString): execHosts = self.test.state.executionHosts failState = plugins.Unrunnable(freeText=excString, briefText="TEXTTEST EXCEPTION", executionHosts=execHosts) self.test.changeState(failState) def performActions(self, previousTestRunner): tearDownSuites, setUpSuites = self.findSuitesToChange(previousTestRunner) for suite in tearDownSuites: self.handleExceptions(previousTestRunner.appRunner.tearDownSuite, suite) for suite in setUpSuites: self.appRunner.markForSetUp(suite) abandon = self.test.state.shouldAbandon()